diff options
Diffstat (limited to 'app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue')
-rw-r--r-- | app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue new file mode 100644 index 00000000000..6e39bda0b07 --- /dev/null +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue @@ -0,0 +1,242 @@ +<script> +import { createAlert } from '~/flash'; +import { __ } from '~/locale'; +import { mapEnvironmentNames, reportMessageToSentry } from '../utils'; +import { + ADD_MUTATION_ACTION, + DELETE_MUTATION_ACTION, + UPDATE_MUTATION_ACTION, + environmentFetchErrorText, + genericMutationErrorText, + variableFetchErrorText, +} from '../constants'; +import CiVariableSettings from './ci_variable_settings.vue'; + +export default { + components: { + CiVariableSettings, + }, + inject: ['endpoint'], + props: { + areScopedVariablesAvailable: { + required: true, + type: Boolean, + }, + componentName: { + required: true, + type: String, + }, + entity: { + required: false, + type: String, + default: '', + }, + fullPath: { + required: false, + type: String, + default: null, + }, + hideEnvironmentScope: { + type: Boolean, + required: false, + default: false, + }, + id: { + required: false, + type: String, + default: null, + }, + mutationData: { + required: true, + type: Object, + validator: (obj) => { + const hasValidKeys = Object.keys(obj).includes( + ADD_MUTATION_ACTION, + UPDATE_MUTATION_ACTION, + DELETE_MUTATION_ACTION, + ); + + const hasValidValues = Object.values(obj).reduce((acc, val) => { + return acc && typeof val === 'object'; + }, true); + + return hasValidKeys && hasValidValues; + }, + }, + refetchAfterMutation: { + required: false, + type: Boolean, + default: false, + }, + queryData: { + required: true, + type: Object, + validator: (obj) => { + const { ciVariables, environments } = obj; + const hasCiVariablesKey = Boolean(ciVariables); + let hasCorrectEnvData = true; + + const hasCorrectVariablesData = + typeof ciVariables?.lookup === 'function' && typeof ciVariables.query === 'object'; + + if (environments) { + hasCorrectEnvData = + typeof environments?.lookup === 'function' && typeof environments.query === 'object'; + } + + return hasCiVariablesKey && hasCorrectVariablesData && hasCorrectEnvData; + }, + }, + }, + data() { + return { + ciVariables: [], + hasNextPage: false, + isInitialLoading: true, + isLoadingMoreItems: false, + loadingCounter: 0, + maxVariableLimit: 0, + pageInfo: {}, + }; + }, + apollo: { + ciVariables: { + query() { + return this.queryData.ciVariables.query; + }, + variables() { + return { + fullPath: this.fullPath || undefined, + }; + }, + update(data) { + return this.queryData.ciVariables.lookup(data)?.nodes || []; + }, + result({ data }) { + this.maxVariableLimit = this.queryData.ciVariables.lookup(data)?.limit || 0; + + this.pageInfo = this.queryData.ciVariables.lookup(data)?.pageInfo || this.pageInfo; + this.hasNextPage = this.pageInfo?.hasNextPage || false; + + // Because graphQL has a limit of 100 items, + // we batch load all the variables by making successive queries + // to keep the same UX. As a safeguard, we make sure that we cannot go over + // 20 consecutive API calls, which means 2000 variables loaded maximum. + if (!this.hasNextPage) { + this.isLoadingMoreItems = false; + } else if (this.loadingCounter < 20) { + this.hasNextPage = false; + this.fetchMoreVariables(); + this.loadingCounter += 1; + } else { + createAlert({ message: this.$options.tooManyCallsError }); + reportMessageToSentry(this.componentName, this.$options.tooManyCallsError, {}); + } + }, + error() { + this.isLoadingMoreItems = false; + this.hasNextPage = false; + createAlert({ message: variableFetchErrorText }); + }, + watchLoading(flag) { + if (!flag) { + this.isInitialLoading = false; + } + }, + }, + environments: { + query() { + return this.queryData?.environments?.query || {}; + }, + skip() { + return !this.queryData?.environments?.query; + }, + variables() { + return { + fullPath: this.fullPath, + }; + }, + update(data) { + return mapEnvironmentNames(this.queryData.environments.lookup(data)?.nodes); + }, + error() { + createAlert({ message: environmentFetchErrorText }); + }, + }, + }, + computed: { + isLoading() { + return ( + (this.$apollo.queries.ciVariables.loading && this.isInitialLoading) || + this.$apollo.queries.environments.loading || + this.isLoadingMoreItems + ); + }, + }, + methods: { + addVariable(variable) { + this.variableMutation(ADD_MUTATION_ACTION, variable); + }, + deleteVariable(variable) { + this.variableMutation(DELETE_MUTATION_ACTION, variable); + }, + fetchMoreVariables() { + this.isLoadingMoreItems = true; + + this.$apollo.queries.ciVariables.fetchMore({ + variables: { + after: this.pageInfo.endCursor, + }, + }); + }, + updateVariable(variable) { + this.variableMutation(UPDATE_MUTATION_ACTION, variable); + }, + async variableMutation(mutationAction, variable) { + try { + const currentMutation = this.mutationData[mutationAction]; + + const { data } = await this.$apollo.mutate({ + mutation: currentMutation, + variables: { + endpoint: this.endpoint, + fullPath: this.fullPath || undefined, + id: this.id || undefined, + variable, + }, + }); + + if (data.ciVariableMutation?.errors?.length) { + const { errors } = data.ciVariableMutation; + createAlert({ message: errors[0] }); + } else if (this.refetchAfterMutation) { + // The writing to cache for admin variable is not working + // because there is no ID in the cache at the top level. + // We therefore need to manually refetch. + this.$apollo.queries.ciVariables.refetch(); + } + } catch (e) { + createAlert({ message: genericMutationErrorText }); + } + }, + }, + i18n: { + tooManyCallsError: __('Maximum number of variables loaded (2000)'), + }, +}; +</script> + +<template> + <ci-variable-settings + :are-scoped-variables-available="areScopedVariablesAvailable" + :entity="entity" + :hide-environment-scope="hideEnvironmentScope" + :is-loading="isLoading" + :variables="ciVariables" + :max-variable-limit="maxVariableLimit" + :environments="environments" + @add-variable="addVariable" + @delete-variable="deleteVariable" + @update-variable="updateVariable" + /> +</template> |