summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/registry/settings/components/registry_settings_app.vue18
-rw-r--r--app/assets/javascripts/registry/settings/components/settings_form.vue65
-rw-r--r--app/assets/javascripts/registry/settings/registry_settings_bundle.js2
-rw-r--r--app/assets/javascripts/registry/settings/store/actions.js17
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/project_ci_cd_setting.rb3
-rw-r--r--app/workers/all_queues.yml1
-rw-r--r--app/workers/gitlab/phabricator_import/base_worker.rb81
-rw-r--r--app/workers/gitlab/phabricator_import/import_tasks_worker.rb13
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