summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/registry/settings
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-10-21 07:08:36 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-10-21 07:08:36 +0000
commit48aff82709769b098321c738f3444b9bdaa694c6 (patch)
treee00c7c43e2d9b603a5a6af576b1685e400410dee /app/assets/javascripts/registry/settings
parent879f5329ee916a948223f8f43d77fba4da6cd028 (diff)
downloadgitlab-ce-4d844e2fbf8315eaf3fddb9a0b241a909be3ecbf.tar.gz
Add latest changes from gitlab-org/gitlab@13-5-stable-eev13.5.0-rc42
Diffstat (limited to 'app/assets/javascripts/registry/settings')
-rw-r--r--app/assets/javascripts/registry/settings/components/registry_settings_app.vue50
-rw-r--r--app/assets/javascripts/registry/settings/components/settings_form.vue145
-rw-r--r--app/assets/javascripts/registry/settings/graphql/fragments/container_expiration_policy.fragment.graphql8
-rw-r--r--app/assets/javascripts/registry/settings/graphql/index.js14
-rw-r--r--app/assets/javascripts/registry/settings/graphql/mutations/update_container_expiration_policy.graphql10
-rw-r--r--app/assets/javascripts/registry/settings/graphql/queries/get_expiration_policy.graphql9
-rw-r--r--app/assets/javascripts/registry/settings/graphql/utils/cache_update.js22
-rw-r--r--app/assets/javascripts/registry/settings/registry_settings_bundle.js13
-rw-r--r--app/assets/javascripts/registry/settings/store/actions.js30
-rw-r--r--app/assets/javascripts/registry/settings/store/getters.js26
-rw-r--r--app/assets/javascripts/registry/settings/store/index.js18
-rw-r--r--app/assets/javascripts/registry/settings/store/mutation_types.js5
-rw-r--r--app/assets/javascripts/registry/settings/store/mutations.js29
-rw-r--r--app/assets/javascripts/registry/settings/store/state.js42
14 files changed, 208 insertions, 213 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..264d39a406a 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, get } 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 = { ...get(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="!isDisabled"
+ 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 7a26fb5cbee..a9b35d4e29f 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,85 @@ export default {
},
fieldsAreValid: true,
apiErrors: null,
+ mutationLoading: false,
};
},
computed: {
- ...mapState(['formOptions', 'isLoading']),
- ...mapGetters({ isEdited: 'getIsEdited' }),
- ...mapComputed([{ key: 'settings', getter: 'getSettings' }], 'updateSettings'),
+ prefilledForm() {
+ return {
+ ...this.value,
+ cadence: this.findDefaultOption('cadence'),
+ keepN: this.findDefaultOption('keepN'),
+ olderThan: this.findDefaultOption('olderThan'),
+ };
+ },
+ 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']),
+ findDefaultOption(option) {
+ return this.value[option] || this.$options.formOptions[option].find(f => f.default)?.key;
+ },
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 +146,8 @@ export default {
</template>
<template #default>
<expiration-policy-fields
- :value="settings"
- :form-options="formOptions"
+ :value="prefilledForm"
+ :form-options="$options.formOptions"
:is-loading="isLoading"
:api-errors="apiErrors"
@validated="fieldsAreValid = true"
@@ -103,27 +156,25 @@ export default {
/>
</template>
<template #footer>
- <div class="gl-display-flex gl-justify-content-end">
- <gl-button
- ref="cancel-button"
- type="reset"
- class="gl-mr-3 gl-display-block"
- :disabled="isCancelButtonDisabled"
- >
- {{ __('Cancel') }}
- </gl-button>
- <gl-button
- ref="save-button"
- type="submit"
- :disabled="isSubmitButtonDisabled"
- variant="success"
- category="primary"
- class="gl-display-flex gl-justify-content-center gl-align-items-center js-no-auto-disable"
- >
- {{ $options.i18n.SET_CLEANUP_POLICY_BUTTON }}
- <gl-loading-icon v-if="isLoading" class="gl-ml-3" />
- </gl-button>
- </div>
+ <gl-button
+ ref="cancel-button"
+ type="reset"
+ class="gl-mr-3 gl-display-block float-right"
+ :disabled="isCancelButtonDisabled"
+ >
+ {{ __('Cancel') }}
+ </gl-button>
+ <gl-button
+ 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-button>
</template>
</gl-card>
</form>
diff --git a/app/assets/javascripts/registry/settings/graphql/fragments/container_expiration_policy.fragment.graphql b/app/assets/javascripts/registry/settings/graphql/fragments/container_expiration_policy.fragment.graphql
new file mode 100644
index 00000000000..224e0ed9472
--- /dev/null
+++ b/app/assets/javascripts/registry/settings/graphql/fragments/container_expiration_policy.fragment.graphql
@@ -0,0 +1,8 @@
+fragment ContainerExpirationPolicyFields on ContainerExpirationPolicy {
+ cadence
+ enabled
+ keepN
+ nameRegex
+ nameRegexKeep
+ olderThan
+}
diff --git a/app/assets/javascripts/registry/settings/graphql/index.js b/app/assets/javascripts/registry/settings/graphql/index.js
new file mode 100644
index 00000000000..16152eb81f6
--- /dev/null
+++ b/app/assets/javascripts/registry/settings/graphql/index.js
@@ -0,0 +1,14 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+
+Vue.use(VueApollo);
+
+export const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(
+ {},
+ {
+ assumeImmutableResults: true,
+ },
+ ),
+});
diff --git a/app/assets/javascripts/registry/settings/graphql/mutations/update_container_expiration_policy.graphql b/app/assets/javascripts/registry/settings/graphql/mutations/update_container_expiration_policy.graphql
new file mode 100644
index 00000000000..c40cd115ab0
--- /dev/null
+++ b/app/assets/javascripts/registry/settings/graphql/mutations/update_container_expiration_policy.graphql
@@ -0,0 +1,10 @@
+#import "../fragments/container_expiration_policy.fragment.graphql"
+
+mutation updateContainerExpirationPolicy($input: UpdateContainerExpirationPolicyInput!) {
+ updateContainerExpirationPolicy(input: $input) {
+ containerExpirationPolicy {
+ ...ContainerExpirationPolicyFields
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/registry/settings/graphql/queries/get_expiration_policy.graphql b/app/assets/javascripts/registry/settings/graphql/queries/get_expiration_policy.graphql
new file mode 100644
index 00000000000..c171be0ad07
--- /dev/null
+++ b/app/assets/javascripts/registry/settings/graphql/queries/get_expiration_policy.graphql
@@ -0,0 +1,9 @@
+#import "../fragments/container_expiration_policy.fragment.graphql"
+
+query getProjectExpirationPolicy($projectPath: ID!) {
+ project(fullPath: $projectPath) {
+ containerExpirationPolicy {
+ ...ContainerExpirationPolicyFields
+ }
+ }
+}
diff --git a/app/assets/javascripts/registry/settings/graphql/utils/cache_update.js b/app/assets/javascripts/registry/settings/graphql/utils/cache_update.js
new file mode 100644
index 00000000000..88067d52b51
--- /dev/null
+++ b/app/assets/javascripts/registry/settings/graphql/utils/cache_update.js
@@ -0,0 +1,22 @@
+import { produce } from 'immer';
+import expirationPolicyQuery from '../queries/get_expiration_policy.graphql';
+
+export const updateContainerExpirationPolicy = projectPath => (client, { data: updatedData }) => {
+ const queryAndParams = {
+ query: expirationPolicyQuery,
+ variables: { projectPath },
+ };
+ const sourceData = client.readQuery(queryAndParams);
+
+ const data = produce(sourceData, draftState => {
+ // eslint-disable-next-line no-param-reassign
+ draftState.project.containerExpirationPolicy = {
+ ...updatedData.updateContainerExpirationPolicy.containerExpirationPolicy,
+ };
+ });
+
+ client.writeQuery({
+ ...queryAndParams,
+ data,
+ });
+};
diff --git a/app/assets/javascripts/registry/settings/registry_settings_bundle.js b/app/assets/javascripts/registry/settings/registry_settings_bundle.js
index a318aa2a694..f7b1c5abd3a 100644
--- a/app/assets/javascripts/registry/settings/registry_settings_bundle.js
+++ b/app/assets/javascripts/registry/settings/registry_settings_bundle.js
@@ -1,8 +1,9 @@
import Vue from 'vue';
import { GlToast } from '@gitlab/ui';
import Translate from '~/vue_shared/translate';
-import store from './store';
+import { parseBoolean } from '~/lib/utils/common_utils';
import RegistrySettingsApp from './components/registry_settings_app.vue';
+import { apolloProvider } from './graphql/index';
Vue.use(GlToast);
Vue.use(Translate);
@@ -12,13 +13,19 @@ export default () => {
if (!el) {
return null;
}
- store.dispatch('setInitialState', el.dataset);
+ const { projectPath, isAdmin, adminSettingsPath, enableHistoricEntries } = el.dataset;
return new Vue({
el,
- store,
+ apolloProvider,
components: {
RegistrySettingsApp,
},
+ provide: {
+ projectPath,
+ isAdmin: parseBoolean(isAdmin),
+ adminSettingsPath,
+ enableHistoricEntries: parseBoolean(enableHistoricEntries),
+ },
render(createElement) {
return createElement('registry-settings-app', {});
},
diff --git a/app/assets/javascripts/registry/settings/store/actions.js b/app/assets/javascripts/registry/settings/store/actions.js
deleted file mode 100644
index 0530a870ecc..00000000000
--- a/app/assets/javascripts/registry/settings/store/actions.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import Api from '~/api';
-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 resetSettings = ({ commit }) => commit(types.RESET_SETTINGS);
-
-export const fetchSettings = ({ dispatch, state }) => {
- dispatch('toggleLoading');
- return Api.project(state.projectId)
- .then(({ data: { container_expiration_policy } }) =>
- dispatch('receiveSettingsSuccess', container_expiration_policy),
- )
- .finally(() => dispatch('toggleLoading'));
-};
-
-export const saveSettings = ({ dispatch, state }) => {
- dispatch('toggleLoading');
- return Api.updateProject(state.projectId, {
- container_expiration_policy_attributes: state.settings,
- })
- .then(({ data: { container_expiration_policy } }) =>
- dispatch('receiveSettingsSuccess', container_expiration_policy),
- )
- .finally(() => dispatch('toggleLoading'));
-};
diff --git a/app/assets/javascripts/registry/settings/store/getters.js b/app/assets/javascripts/registry/settings/store/getters.js
deleted file mode 100644
index ac1a931d8e0..00000000000
--- a/app/assets/javascripts/registry/settings/store/getters.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import { isEqual } from 'lodash';
-import { findDefaultOption } from '../../shared/utils';
-
-export const getCadence = state =>
- state.settings.cadence || findDefaultOption(state.formOptions.cadence);
-
-export const getKeepN = state =>
- state.settings.keep_n || findDefaultOption(state.formOptions.keepN);
-
-export const getOlderThan = state =>
- state.settings.older_than || findDefaultOption(state.formOptions.olderThan);
-
-export const getSettings = (state, getters) => ({
- enabled: state.settings.enabled,
- cadence: getters.getCadence,
- older_than: getters.getOlderThan,
- keep_n: getters.getKeepN,
- name_regex: state.settings.name_regex,
- name_regex_keep: state.settings.name_regex_keep,
-});
-
-export const getIsEdited = state => !isEqual(state.original, state.settings);
-
-export const getIsDisabled = state => {
- return !(state.original || state.enableHistoricEntries);
-};
diff --git a/app/assets/javascripts/registry/settings/store/index.js b/app/assets/javascripts/registry/settings/store/index.js
deleted file mode 100644
index c2500454d8e..00000000000
--- a/app/assets/javascripts/registry/settings/store/index.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import Vue from 'vue';
-import Vuex from 'vuex';
-import * as actions from './actions';
-import mutations from './mutations';
-import * as getters from './getters';
-import state from './state';
-
-Vue.use(Vuex);
-
-export const createStore = () =>
- new Vuex.Store({
- state,
- actions,
- mutations,
- getters,
- });
-
-export default createStore();
diff --git a/app/assets/javascripts/registry/settings/store/mutation_types.js b/app/assets/javascripts/registry/settings/store/mutation_types.js
deleted file mode 100644
index db499ffa761..00000000000
--- a/app/assets/javascripts/registry/settings/store/mutation_types.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export const SET_INITIAL_STATE = 'SET_INITIAL_STATE';
-export const UPDATE_SETTINGS = 'UPDATE_SETTINGS';
-export const TOGGLE_LOADING = 'TOGGLE_LOADING';
-export const SET_SETTINGS = 'SET_SETTINGS';
-export const RESET_SETTINGS = 'RESET_SETTINGS';
diff --git a/app/assets/javascripts/registry/settings/store/mutations.js b/app/assets/javascripts/registry/settings/store/mutations.js
deleted file mode 100644
index 3ba13419b98..00000000000
--- a/app/assets/javascripts/registry/settings/store/mutations.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import { parseBoolean } from '~/lib/utils/common_utils';
-import * as types from './mutation_types';
-
-export default {
- [types.SET_INITIAL_STATE](state, initialState) {
- state.projectId = initialState.projectId;
- state.formOptions = {
- cadence: JSON.parse(initialState.cadenceOptions),
- keepN: JSON.parse(initialState.keepNOptions),
- olderThan: JSON.parse(initialState.olderThanOptions),
- };
- state.enableHistoricEntries = parseBoolean(initialState.enableHistoricEntries);
- state.isAdmin = parseBoolean(initialState.isAdmin);
- state.adminSettingsPath = initialState.adminSettingsPath;
- },
- [types.UPDATE_SETTINGS](state, data) {
- state.settings = { ...state.settings, ...data.settings };
- },
- [types.SET_SETTINGS](state, settings) {
- state.settings = settings ?? state.settings;
- state.original = Object.freeze(settings);
- },
- [types.RESET_SETTINGS](state) {
- state.settings = { ...state.original };
- },
- [types.TOGGLE_LOADING](state) {
- state.isLoading = !state.isLoading;
- },
-};
diff --git a/app/assets/javascripts/registry/settings/store/state.js b/app/assets/javascripts/registry/settings/store/state.js
deleted file mode 100644
index fccc0991c1c..00000000000
--- a/app/assets/javascripts/registry/settings/store/state.js
+++ /dev/null
@@ -1,42 +0,0 @@
-export default () => ({
- /*
- * Project Id used to build the API call
- */
- projectId: '',
- /*
- * Boolean to determine if the UI is loading data from the API
- */
- isLoading: false,
- /*
- * Boolean to determine if the user is an admin
- */
- isAdmin: false,
- /*
- * String containing the full path to the admin config page for CI/CD
- */
- adminSettingsPath: '',
- /*
- * Boolean to determine if project created before 12.8 can use this feature
- */
- enableHistoricEntries: false,
- /*
- * This contains the data shown and manipulated in the UI
- * Has the following structure:
- * {
- * enabled: Boolean
- * cadence: String,
- * older_than: String,
- * keep_n: String,
- * name_regex: String
- * }
- */
- settings: {},
- /*
- * Same structure as settings, above but Frozen object and used only in case the user clicks 'cancel', initialized to null
- */
- original: null,
- /*
- * Contains the options used to populate the form selects
- */
- formOptions: {},
-});