diff options
Diffstat (limited to 'app')
9 files changed, 159 insertions, 43 deletions
diff --git a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue index ca495cd2eca..7530c1dfcaf 100644 --- a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue +++ b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue @@ -1,20 +1,17 @@ <script> -import { mapState, mapActions } from 'vuex'; -import { GlLoadingIcon } from '@gitlab/ui'; +import { mapActions } from 'vuex'; +import { FETCH_SETTINGS_ERROR_MESSAGE } from '../constants'; + import SettingsForm from './settings_form.vue'; export default { components: { - GlLoadingIcon, SettingsForm, }, - computed: { - ...mapState({ - isLoading: 'isLoading', - }), - }, mounted() { - this.fetchSettings(); + this.fetchSettings().catch(() => + this.$toast.show(FETCH_SETTINGS_ERROR_MESSAGE, { type: 'error' }), + ); }, methods: { ...mapActions(['fetchSettings']), @@ -37,7 +34,6 @@ export default { }} </li> </ul> - <gl-loading-icon v-if="isLoading" ref="loading-icon" size="xl" /> - <settings-form v-else ref="settings-form" /> + <settings-form ref="settings-form" /> </div> </template> diff --git a/app/assets/javascripts/registry/settings/components/settings_form.vue b/app/assets/javascripts/registry/settings/components/settings_form.vue index 457bf35daab..b713cfe2e34 100644 --- a/app/assets/javascripts/registry/settings/components/settings_form.vue +++ b/app/assets/javascripts/registry/settings/components/settings_form.vue @@ -1,8 +1,20 @@ <script> import { mapActions, mapState } from 'vuex'; -import { GlFormGroup, GlToggle, GlFormSelect, GlFormTextarea, GlButton, GlCard } from '@gitlab/ui'; +import { + GlFormGroup, + GlToggle, + GlFormSelect, + GlFormTextarea, + GlButton, + GlCard, + GlLoadingIcon, +} from '@gitlab/ui'; import { s__, __, sprintf } from '~/locale'; -import { NAME_REGEX_LENGTH } from '../constants'; +import { + NAME_REGEX_LENGTH, + UPDATE_SETTINGS_ERROR_MESSAGE, + UPDATE_SETTINGS_SUCCESS_MESSAGE, +} from '../constants'; import { mapComputed } from '~/vuex_shared/bindings'; export default { @@ -13,13 +25,14 @@ export default { GlFormTextarea, GlButton, GlCard, + GlLoadingIcon, }, labelsConfig: { cols: 3, align: 'right', }, computed: { - ...mapState(['formOptions']), + ...mapState(['formOptions', 'isLoading']), ...mapComputed( [ 'enabled', @@ -64,15 +77,26 @@ export default { formIsInvalid() { return this.nameRegexState === false; }, + isFormElementDisabled() { + return !this.enabled || this.isLoading; + }, + isSubmitButtonDisabled() { + return this.formIsInvalid || this.isLoading; + }, }, methods: { ...mapActions(['resetSettings', 'saveSettings']), + submit() { + this.saveSettings() + .then(() => this.$toast.show(UPDATE_SETTINGS_SUCCESS_MESSAGE, { type: 'success' })) + .catch(() => this.$toast.show(UPDATE_SETTINGS_ERROR_MESSAGE, { type: 'error' })); + }, }, }; </script> <template> - <form ref="form-element" @submit.prevent="saveSettings" @reset.prevent="resetSettings"> + <form ref="form-element" @submit.prevent="submit" @reset.prevent="resetSettings"> <gl-card> <template #header> {{ s__('ContainerRegistry|Tag expiration policy') }} @@ -86,7 +110,7 @@ export default { :label="s__('ContainerRegistry|Expiration policy:')" > <div class="d-flex align-items-start"> - <gl-toggle id="expiration-policy-toggle" v-model="enabled" /> + <gl-toggle id="expiration-policy-toggle" v-model="enabled" :disabled="isLoading" /> <span class="mb-2 ml-1 lh-2" v-html="toggleDescriptionText"></span> </div> </gl-form-group> @@ -98,7 +122,11 @@ export default { label-for="expiration-policy-interval" :label="s__('ContainerRegistry|Expiration interval:')" > - <gl-form-select id="expiration-policy-interval" v-model="older_than" :disabled="!enabled"> + <gl-form-select + id="expiration-policy-interval" + v-model="older_than" + :disabled="isFormElementDisabled" + > <option v-for="option in formOptions.olderThan" :key="option.key" :value="option.key"> {{ option.label }} </option> @@ -112,7 +140,11 @@ export default { label-for="expiration-policy-schedule" :label="s__('ContainerRegistry|Expiration schedule:')" > - <gl-form-select id="expiration-policy-schedule" v-model="cadence" :disabled="!enabled"> + <gl-form-select + id="expiration-policy-schedule" + v-model="cadence" + :disabled="isFormElementDisabled" + > <option v-for="option in formOptions.cadence" :key="option.key" :value="option.key"> {{ option.label }} </option> @@ -126,7 +158,11 @@ export default { label-for="expiration-policy-latest" :label="s__('ContainerRegistry|Number of tags to retain:')" > - <gl-form-select id="expiration-policy-latest" v-model="keep_n" :disabled="!enabled"> + <gl-form-select + id="expiration-policy-latest" + v-model="keep_n" + :disabled="isFormElementDisabled" + > <option v-for="option in formOptions.keepN" :key="option.key" :value="option.key"> {{ option.label }} </option> @@ -149,7 +185,7 @@ export default { v-model="name_regex" :placeholder="nameRegexPlaceholder" :state="nameRegexState" - :disabled="!enabled" + :disabled="isFormElementDisabled" trim /> <template #description> @@ -159,17 +195,18 @@ export default { </template> <template #footer> <div class="d-flex justify-content-end"> - <gl-button ref="cancel-button" type="reset" class="mr-2 d-block">{{ - __('Cancel') - }}</gl-button> + <gl-button ref="cancel-button" type="reset" class="mr-2 d-block" :disabled="isLoading"> + {{ __('Cancel') }} + </gl-button> <gl-button ref="save-button" type="submit" - :disabled="formIsInvalid" + :disabled="isSubmitButtonDisabled" variant="success" - class="d-block" + class="d-flex justify-content-center align-items-center js-no-auto-disable" > {{ __('Save expiration policy') }} + <gl-loading-icon v-if="isLoading" class="ml-2" /> </gl-button> </div> </template> diff --git a/app/assets/javascripts/registry/settings/registry_settings_bundle.js b/app/assets/javascripts/registry/settings/registry_settings_bundle.js index 927b6059884..6ae1dbb72c4 100644 --- a/app/assets/javascripts/registry/settings/registry_settings_bundle.js +++ b/app/assets/javascripts/registry/settings/registry_settings_bundle.js @@ -1,8 +1,10 @@ import Vue from 'vue'; +import { GlToast } from '@gitlab/ui'; import Translate from '~/vue_shared/translate'; import store from './store/'; import RegistrySettingsApp from './components/registry_settings_app.vue'; +Vue.use(GlToast); Vue.use(Translate); export default () => { diff --git a/app/assets/javascripts/registry/settings/store/actions.js b/app/assets/javascripts/registry/settings/store/actions.js index 5e46d564121..21a2008fef6 100644 --- a/app/assets/javascripts/registry/settings/store/actions.js +++ b/app/assets/javascripts/registry/settings/store/actions.js @@ -1,18 +1,10 @@ import Api from '~/api'; -import createFlash from '~/flash'; -import { - FETCH_SETTINGS_ERROR_MESSAGE, - UPDATE_SETTINGS_ERROR_MESSAGE, - UPDATE_SETTINGS_SUCCESS_MESSAGE, -} from '../constants'; import * as types from './mutation_types'; export const setInitialState = ({ commit }, data) => commit(types.SET_INITIAL_STATE, data); export const updateSettings = ({ commit }, data) => commit(types.UPDATE_SETTINGS, data); export const toggleLoading = ({ commit }) => commit(types.TOGGLE_LOADING); export const receiveSettingsSuccess = ({ commit }, data = {}) => commit(types.SET_SETTINGS, data); -export const receiveSettingsError = () => createFlash(FETCH_SETTINGS_ERROR_MESSAGE); -export const updateSettingsError = () => createFlash(UPDATE_SETTINGS_ERROR_MESSAGE); export const resetSettings = ({ commit }) => commit(types.RESET_SETTINGS); export const fetchSettings = ({ dispatch, state }) => { @@ -21,7 +13,6 @@ export const fetchSettings = ({ dispatch, state }) => { .then(({ data: { container_expiration_policy } }) => dispatch('receiveSettingsSuccess', container_expiration_policy), ) - .catch(() => dispatch('receiveSettingsError')) .finally(() => dispatch('toggleLoading')); }; @@ -30,11 +21,9 @@ export const saveSettings = ({ dispatch, state }) => { return Api.updateProject(state.projectId, { container_expiration_policy_attributes: state.settings, }) - .then(({ data: { container_expiration_policy } }) => { - dispatch('receiveSettingsSuccess', container_expiration_policy); - createFlash(UPDATE_SETTINGS_SUCCESS_MESSAGE, 'success'); - }) - .catch(() => dispatch('updateSettingsError')) + .then(({ data: { container_expiration_policy } }) => + dispatch('receiveSettingsSuccess', container_expiration_policy), + ) .finally(() => dispatch('toggleLoading')); }; diff --git a/app/models/note.rb b/app/models/note.rb index 7731b477ad0..de9478ce68d 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -124,7 +124,7 @@ class Note < ApplicationRecord scope :inc_author, -> { includes(:author) } scope :inc_relations_for_view, -> do includes(:project, { author: :status }, :updated_by, :resolved_by, :award_emoji, - :system_note_metadata, :note_diff_file, :suggestions) + { system_note_metadata: :description_version }, :note_diff_file, :suggestions) end scope :with_notes_filter, -> (notes_filter) do diff --git a/app/models/project_ci_cd_setting.rb b/app/models/project_ci_cd_setting.rb index 1dd65c76258..a495d34c07c 100644 --- a/app/models/project_ci_cd_setting.rb +++ b/app/models/project_ci_cd_setting.rb @@ -1,9 +1,6 @@ # frozen_string_literal: true class ProjectCiCdSetting < ApplicationRecord - include IgnorableColumns - # https://gitlab.com/gitlab-org/gitlab/issues/36651 - ignore_column :merge_trains_enabled, remove_with: '12.7', remove_after: '2019-12-22' belongs_to :project, inverse_of: :ci_cd_settings # The version of the schema that first introduced this model/table. diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index f26a2201550..f19dd0e4a48 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -192,3 +192,4 @@ - self_monitoring_project_create - self_monitoring_project_delete - merge_request_mergeability_check +- phabricator_import_import_tasks diff --git a/app/workers/gitlab/phabricator_import/base_worker.rb b/app/workers/gitlab/phabricator_import/base_worker.rb new file mode 100644 index 00000000000..faae71d4627 --- /dev/null +++ b/app/workers/gitlab/phabricator_import/base_worker.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +# All workers within a Phabricator import should inherit from this worker and +# implement the `#import` method. The jobs should then be scheduled using the +# `.schedule` class method instead of `.perform_async` +# +# Doing this makes sure that only one job of that type is running at the same time +# for a certain project. This will avoid deadlocks. When a job is already running +# we'll wait for it for 10 times 5 seconds to restart. If the running job hasn't +# finished, by then, we'll retry in 30 seconds. +# +# It also makes sure that we keep the import state of the project up to date: +# - It keeps track of the jobs so we know how many jobs are running for the +# project +# - It refreshes the import jid, so it doesn't get cleaned up by the +# `StuckImportJobsWorker` +# - It marks the import as failed if a job failed to many times +# - It marks the import as finished when all remaining jobs are done +module Gitlab + module PhabricatorImport + class BaseWorker + include WorkerAttributes + include Gitlab::ExclusiveLeaseHelpers + + feature_category :importers + + class << self + def schedule(project_id, *args) + perform_async(project_id, *args) + add_job(project_id) + end + + def add_job(project_id) + worker_state(project_id).add_job + end + + def remove_job(project_id) + worker_state(project_id).remove_job + end + + def worker_state(project_id) + Gitlab::PhabricatorImport::WorkerState.new(project_id) + end + end + + def perform(project_id, *args) + in_lock("#{self.class.name.underscore}/#{project_id}/#{args}", ttl: 2.hours, sleep_sec: 5.seconds) do + project = Project.find_by_id(project_id) + next unless project + + # Bail if the import job already failed + next unless project.import_state&.in_progress? + + project.import_state.refresh_jid_expiration + + import(project, *args) + + # If this is the last running job, finish the import + project.after_import if self.class.worker_state(project_id).running_count < 2 + + self.class.remove_job(project_id) + end + rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError + # Reschedule a job if there was already a running one + # Running them at the same time could cause a deadlock updating the same + # resource + self.class.perform_in(30.seconds, project_id, *args) + end + + private + + def import(project, *args) + importer_class.new(project, *args).execute + end + + def importer_class + raise NotImplementedError, "Implement `#{__method__}` on #{self.class}" + end + end + end +end diff --git a/app/workers/gitlab/phabricator_import/import_tasks_worker.rb b/app/workers/gitlab/phabricator_import/import_tasks_worker.rb new file mode 100644 index 00000000000..b5d9e80797b --- /dev/null +++ b/app/workers/gitlab/phabricator_import/import_tasks_worker.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true +module Gitlab + module PhabricatorImport + class ImportTasksWorker < BaseWorker + include ApplicationWorker + include ProjectImportOptions # This marks the project as failed after too many tries + + def importer_class + Gitlab::PhabricatorImport::Issues::Importer + end + end + end +end |