diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-30 12:09:53 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-30 12:09:53 +0000 |
commit | 6aa5c04c74d2d70ee7d19ef3a155b2def9dd46de (patch) | |
tree | 81f7b81234bc5b889c57e71f87b94878ab286383 /app/assets/javascripts/registry | |
parent | 418c3b29009dcc0a2c6b4872557d0274ba0b8077 (diff) | |
download | gitlab-ce-6aa5c04c74d2d70ee7d19ef3a155b2def9dd46de.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/registry')
4 files changed, 132 insertions, 68 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 2ee7bbef4c6..fcb86fd18f0 100644 --- a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue +++ b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue @@ -1,7 +1,7 @@ <script> -import { mapActions, mapGetters, mapState } from 'vuex'; import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; - +import { isEqual } from 'lodash'; +import expirationPolicyQuery from '../graphql/queries/get_expiration_policy.graphql'; import { FETCH_SETTINGS_ERROR_MESSAGE } from '../../shared/constants'; import SettingsForm from './settings_form.vue'; @@ -19,21 +19,39 @@ export default { GlSprintf, GlLink, }, + inject: ['projectPath', 'isAdmin', 'adminSettingsPath', 'enableHistoricEntries'], i18n: { UNAVAILABLE_FEATURE_TITLE, UNAVAILABLE_FEATURE_INTRO_TEXT, FETCH_SETTINGS_ERROR_MESSAGE, }, + apollo: { + containerExpirationPolicy: { + query: expirationPolicyQuery, + variables() { + return { + projectPath: this.projectPath, + }; + }, + update: data => data.project?.containerExpirationPolicy, + result({ data }) { + this.workingCopy = { ...data.project?.containerExpirationPolicy }; + }, + error(e) { + this.fetchSettingsError = e; + }, + }, + }, data() { return { fetchSettingsError: false, + containerExpirationPolicy: null, + workingCopy: {}, }; }, computed: { - ...mapState(['isAdmin', 'adminSettingsPath']), - ...mapGetters({ isDisabled: 'getIsDisabled' }), - showSettingForm() { - return !this.isDisabled && !this.fetchSettingsError; + isDisabled() { + return !(this.containerExpirationPolicy || this.enableHistoricEntries); }, showDisabledFormMessage() { return this.isDisabled && !this.fetchSettingsError; @@ -41,21 +59,27 @@ export default { unavailableFeatureMessage() { return this.isAdmin ? UNAVAILABLE_ADMIN_FEATURE_TEXT : UNAVAILABLE_USER_FEATURE_TEXT; }, - }, - mounted() { - this.fetchSettings().catch(() => { - this.fetchSettingsError = true; - }); + isEdited() { + return !isEqual(this.containerExpirationPolicy, this.workingCopy); + }, }, methods: { - ...mapActions(['fetchSettings']), + restoreOriginal() { + this.workingCopy = { ...this.containerExpirationPolicy }; + }, }, }; </script> <template> <div> - <settings-form v-if="showSettingForm" /> + <settings-form + v-if="containerExpirationPolicy" + v-model="workingCopy" + :is-loading="$apollo.queries.containerExpirationPolicy.loading" + :is-edited="isEdited" + @reset="restoreOriginal" + /> <template v-else> <gl-alert v-if="showDisabledFormMessage" diff --git a/app/assets/javascripts/registry/settings/components/settings_form.vue b/app/assets/javascripts/registry/settings/components/settings_form.vue index 25c88daa54d..7deb1f92686 100644 --- a/app/assets/javascripts/registry/settings/components/settings_form.vue +++ b/app/assets/javascripts/registry/settings/components/settings_form.vue @@ -1,28 +1,45 @@ <script> -import { get } from 'lodash'; -import { mapActions, mapState, mapGetters } from 'vuex'; -import { GlCard, GlButton, GlLoadingIcon } from '@gitlab/ui'; +import { GlCard, GlButton } from '@gitlab/ui'; import Tracking from '~/tracking'; -import { mapComputed } from '~/vuex_shared/bindings'; import { UPDATE_SETTINGS_ERROR_MESSAGE, UPDATE_SETTINGS_SUCCESS_MESSAGE, } from '../../shared/constants'; import ExpirationPolicyFields from '../../shared/components/expiration_policy_fields.vue'; import { SET_CLEANUP_POLICY_BUTTON, CLEANUP_POLICY_CARD_HEADER } from '../constants'; +import { formOptionsGenerator } from '~/registry/shared/utils'; +import updateContainerExpirationPolicyMutation from '../graphql/mutations/update_container_expiration_policy.graphql'; +import { updateContainerExpirationPolicy } from '../graphql/utils/cache_update'; export default { components: { GlCard, GlButton, - GlLoadingIcon, ExpirationPolicyFields, }, mixins: [Tracking.mixin()], + inject: ['projectPath'], + props: { + value: { + type: Object, + required: true, + }, + isLoading: { + type: Boolean, + required: false, + default: false, + }, + isEdited: { + type: Boolean, + required: false, + default: false, + }, + }, labelsConfig: { cols: 3, align: 'right', }, + formOptions: formOptionsGenerator(), i18n: { CLEANUP_POLICY_CARD_HEADER, SET_CLEANUP_POLICY_BUTTON, @@ -34,49 +51,74 @@ export default { }, fieldsAreValid: true, apiErrors: null, + mutationLoading: false, }; }, computed: { - ...mapState(['formOptions', 'isLoading']), - ...mapGetters({ isEdited: 'getIsEdited' }), - ...mapComputed([{ key: 'settings', getter: 'getSettings' }], 'updateSettings'), + showLoadingIcon() { + return this.isLoading || this.mutationLoading; + }, isSubmitButtonDisabled() { - return !this.fieldsAreValid || this.isLoading; + return !this.fieldsAreValid || this.showLoadingIcon; }, isCancelButtonDisabled() { - return !this.isEdited || this.isLoading; + return !this.isEdited || this.isLoading || this.mutationLoading; + }, + mutationVariables() { + return { + projectPath: this.projectPath, + enabled: this.value.enabled, + cadence: this.value.cadence, + olderThan: this.value.olderThan, + keepN: this.value.keepN, + nameRegex: this.value.nameRegex, + nameRegexKeep: this.value.nameRegexKeep, + }; }, }, methods: { - ...mapActions(['resetSettings', 'saveSettings']), reset() { this.track('reset_form'); this.apiErrors = null; - this.resetSettings(); + this.$emit('reset'); }, setApiErrors(response) { - const messages = get(response, 'data.message', []); - - this.apiErrors = Object.keys(messages).reduce((acc, curr) => { - if (curr.startsWith('container_expiration_policy.')) { - const key = curr.replace('container_expiration_policy.', ''); - acc[key] = get(messages, [curr, 0], ''); - } + this.apiErrors = response.graphQLErrors.reduce((acc, curr) => { + curr.extensions.problems.forEach(item => { + acc[item.path[0]] = item.message; + }); return acc; }, {}); }, submit() { this.track('submit_form'); this.apiErrors = null; - this.saveSettings() - .then(() => this.$toast.show(UPDATE_SETTINGS_SUCCESS_MESSAGE, { type: 'success' })) - .catch(({ response }) => { - this.setApiErrors(response); + this.mutationLoading = true; + return this.$apollo + .mutate({ + mutation: updateContainerExpirationPolicyMutation, + variables: { + input: this.mutationVariables, + }, + update: updateContainerExpirationPolicy(this.projectPath), + }) + .then(({ data }) => { + const errorMessage = data?.updateContainerExpirationPolicy?.errors[0]; + if (errorMessage) { + this.$toast.show(errorMessage, { type: 'error' }); + } + this.$toast.show(UPDATE_SETTINGS_SUCCESS_MESSAGE, { type: 'success' }); + }) + .catch(error => { + this.setApiErrors(error); this.$toast.show(UPDATE_SETTINGS_ERROR_MESSAGE, { type: 'error' }); + }) + .finally(() => { + this.mutationLoading = false; }); }, onModelChange(changePayload) { - this.settings = changePayload.newValue; + this.$emit('input', changePayload.newValue); if (this.apiErrors) { this.apiErrors[changePayload.modified] = undefined; } @@ -93,8 +135,8 @@ export default { </template> <template #default> <expiration-policy-fields - :value="settings" - :form-options="formOptions" + :value="value" + :form-options="$options.formOptions" :is-loading="isLoading" :api-errors="apiErrors" @validated="fieldsAreValid = true" @@ -115,12 +157,12 @@ export default { ref="save-button" type="submit" :disabled="isSubmitButtonDisabled" + :loading="showLoadingIcon" variant="success" category="primary" class="js-no-auto-disable" > {{ $options.i18n.SET_CLEANUP_POLICY_BUTTON }} - <gl-loading-icon v-if="isLoading" class="gl-ml-3" /> </gl-button> </template> </gl-card> diff --git a/app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue b/app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue index 1ff2f6f99e5..2b8e9f6ff64 100644 --- a/app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue +++ b/app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue @@ -68,34 +68,31 @@ export default { { name: 'expiration-policy-interval', label: EXPIRATION_INTERVAL_LABEL, - model: 'older_than', - optionKey: 'olderThan', + model: 'olderThan', }, { name: 'expiration-policy-schedule', label: EXPIRATION_SCHEDULE_LABEL, model: 'cadence', - optionKey: 'cadence', }, { name: 'expiration-policy-latest', label: KEEP_N_LABEL, - model: 'keep_n', - optionKey: 'keepN', + model: 'keepN', }, ], textAreaList: [ { name: 'expiration-policy-name-matching', label: NAME_REGEX_LABEL, - model: 'name_regex', + model: 'nameRegex', placeholder: NAME_REGEX_PLACEHOLDER, description: NAME_REGEX_DESCRIPTION, }, { name: 'expiration-policy-keep-name', label: NAME_REGEX_KEEP_LABEL, - model: 'name_regex_keep', + model: 'nameRegexKeep', placeholder: NAME_REGEX_KEEP_PLACEHOLDER, description: NAME_REGEX_KEEP_DESCRIPTION, }, @@ -107,17 +104,16 @@ export default { }, computed: { ...mapComputedToEvent( - ['enabled', 'cadence', 'older_than', 'keep_n', 'name_regex', 'name_regex_keep'], + ['enabled', 'cadence', 'olderThan', 'keepN', 'nameRegex', 'nameRegexKeep'], 'value', ), policyEnabledText() { return this.enabled ? ENABLED_TEXT : DISABLED_TEXT; }, textAreaValidation() { - const nameRegexErrors = - this.apiErrors?.name_regex || this.validateRegexLength(this.name_regex); + const nameRegexErrors = this.apiErrors?.nameRegex || this.validateRegexLength(this.nameRegex); const nameKeepRegexErrors = - this.apiErrors?.name_regex_keep || this.validateRegexLength(this.name_regex_keep); + this.apiErrors?.nameRegexKeep || this.validateRegexLength(this.nameRegexKeep); return { /* @@ -127,11 +123,11 @@ export default { * false: red border, error message * So in this function we keep null if the are no message otherwise we 'invert' the error message */ - name_regex: { + nameRegex: { state: nameRegexErrors === null ? null : !nameRegexErrors, message: nameRegexErrors, }, - name_regex_keep: { + nameRegexKeep: { state: nameKeepRegexErrors === null ? null : !nameKeepRegexErrors, message: nameKeepRegexErrors, }, @@ -139,8 +135,8 @@ export default { }, fieldsValidity() { return ( - this.textAreaValidation.name_regex.state !== false && - this.textAreaValidation.name_regex_keep.state !== false + this.textAreaValidation.nameRegex.state !== false && + this.textAreaValidation.nameRegexKeep.state !== false ); }, isFormElementDisabled() { @@ -216,11 +212,7 @@ export default { :disabled="isFormElementDisabled" @input="updateModel($event, select.model)" > - <option - v-for="option in formOptions[select.optionKey]" - :key="option.key" - :value="option.key" - > + <option v-for="option in formOptions[select.model]" :key="option.key" :value="option.key"> {{ option.label }} </option> </gl-form-select> diff --git a/app/assets/javascripts/registry/shared/utils.js b/app/assets/javascripts/registry/shared/utils.js index f84325cd438..bdf1ab9507d 100644 --- a/app/assets/javascripts/registry/shared/utils.js +++ b/app/assets/javascripts/registry/shared/utils.js @@ -21,20 +21,26 @@ export const mapComputedToEvent = (list, root) => { return result; }; -export const optionLabelGenerator = (collection, singularSentence, pluralSentence) => +export const olderThanTranslationGenerator = variable => + n__( + '%d day until tags are automatically removed', + '%d days until tags are automatically removed', + variable, + ); + +export const keepNTranslationGenerator = variable => + n__('%d tag per image name', '%d tags per image name', variable); + +export const optionLabelGenerator = (collection, translationFn) => collection.map(option => ({ ...option, - label: n__(singularSentence, pluralSentence, option.variable), + label: translationFn(option.variable), })); export const formOptionsGenerator = () => { return { - olderThan: optionLabelGenerator( - OLDER_THAN_OPTIONS, - '%d days until tags are automatically removed', - '%d day until tags are automatically removed', - ), + olderThan: optionLabelGenerator(OLDER_THAN_OPTIONS, olderThanTranslationGenerator), cadence: CADENCE_OPTIONS, - keepN: optionLabelGenerator(KEEP_N_OPTIONS, '%d tag per image name', '%d tags per image name'), + keepN: optionLabelGenerator(KEEP_N_OPTIONS, keepNTranslationGenerator), }; }; |