diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 13:34:06 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 13:34:06 +0300 |
commit | 859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 (patch) | |
tree | d7f2700abe6b4ffcb2dcfc80631b2d87d0609239 /app/assets/javascripts/terraform | |
parent | 446d496a6d000c73a304be52587cd9bbc7493136 (diff) |
Add latest changes from gitlab-org/gitlab@13-9-stable-eev13.9.0-rc42
Diffstat (limited to 'app/assets/javascripts/terraform')
7 files changed, 194 insertions, 17 deletions
diff --git a/app/assets/javascripts/terraform/components/states_table.vue b/app/assets/javascripts/terraform/components/states_table.vue index d0d49233334..2577664a5e8 100644 --- a/app/assets/javascripts/terraform/components/states_table.vue +++ b/app/assets/javascripts/terraform/components/states_table.vue @@ -1,18 +1,29 @@ <script> -import { GlBadge, GlIcon, GlLink, GlSprintf, GlTable, GlTooltip } from '@gitlab/ui'; -import { s__ } from '~/locale'; +import { + GlAlert, + GlBadge, + GlIcon, + GlLink, + GlLoadingIcon, + GlSprintf, + GlTable, + GlTooltip, +} from '@gitlab/ui'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { s__ } from '~/locale'; import CiBadge from '~/vue_shared/components/ci_badge_link.vue'; -import StateActions from './states_table_actions.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; +import StateActions from './states_table_actions.vue'; export default { components: { CiBadge, + GlAlert, GlBadge, GlIcon, GlLink, + GlLoadingIcon, GlSprintf, GlTable, GlTooltip, @@ -66,15 +77,21 @@ export default { jobStatus: s__('Terraform|Job status'), locked: s__('Terraform|Locked'), lockedByUser: s__('Terraform|Locked by %{user} %{timeAgo}'), + lockingState: s__('Terraform|Locking state'), name: s__('Terraform|Name'), pipeline: s__('Terraform|Pipeline'), + removing: s__('Terraform|Removing'), unknownUser: s__('Terraform|Unknown User'), + unlockingState: s__('Terraform|Unlocking state'), updatedUser: s__('Terraform|%{user} updated %{timeAgo}'), }, methods: { createdByUserName(item) { return item.latestVersion?.createdByUser?.name; }, + loadingLockText(item) { + return item.lockedAt ? this.$options.i18n.unlockingState : this.$options.i18n.lockingState; + }, lockedByUserName(item) { return item.lockedByUser?.name || this.$options.i18n.unknownUser; }, @@ -105,6 +122,7 @@ export default { :items="states" :fields="fields" data-testid="terraform-states-table" + details-td-class="gl-p-0!" fixed stacked="md" > @@ -117,7 +135,27 @@ export default { {{ item.name }} </p> - <div v-if="item.lockedAt" :id="`terraformLockedBadgeContainer${item.name}`" class="gl-mx-2"> + <div v-if="item.loadingLock" class="gl-mx-3"> + <p class="gl-display-flex gl-justify-content-start gl-align-items-baseline gl-m-0"> + <gl-loading-icon class="gl-pr-1" /> + {{ loadingLockText(item) }} + </p> + </div> + + <div v-else-if="item.loadingRemove" class="gl-mx-3"> + <p + class="gl-display-flex gl-justify-content-start gl-align-items-baseline gl-m-0 gl-text-red-500" + > + <gl-loading-icon class="gl-pr-1" /> + {{ $options.i18n.removing }} + </p> + </div> + + <div + v-else-if="item.lockedAt" + :id="`terraformLockedBadgeContainer${item.name}`" + class="gl-mx-3" + > <gl-badge :id="`terraformLockedBadge${item.name}`"> <gl-icon name="lock" /> {{ $options.i18n.locked }} @@ -189,5 +227,21 @@ export default { <template v-if="terraformAdmin" #cell(actions)="{ item }"> <state-actions :state="item" /> </template> + + <template #row-details="row"> + <gl-alert + data-testid="terraform-states-table-error" + variant="danger" + @dismiss="row.toggleDetails" + > + <span + v-for="errorMessage in row.item.errorMessages" + :key="errorMessage" + class="gl-display-flex gl-justify-content-start" + > + {{ errorMessage }} + </span> + </gl-alert> + </template> </gl-table> </template> diff --git a/app/assets/javascripts/terraform/components/states_table_actions.vue b/app/assets/javascripts/terraform/components/states_table_actions.vue index 44b0713e544..c4fd97188de 100644 --- a/app/assets/javascripts/terraform/components/states_table_actions.vue +++ b/app/assets/javascripts/terraform/components/states_table_actions.vue @@ -9,10 +9,11 @@ import { GlModal, GlSprintf, } from '@gitlab/ui'; -import { s__ } from '~/locale'; +import { s__, sprintf } from '~/locale'; +import addDataToState from '../graphql/mutations/add_data_to_state.mutation.graphql'; import lockState from '../graphql/mutations/lock_state.mutation.graphql'; -import unlockState from '../graphql/mutations/unlock_state.mutation.graphql'; import removeState from '../graphql/mutations/remove_state.mutation.graphql'; +import unlockState from '../graphql/mutations/unlock_state.mutation.graphql'; export default { components: { @@ -33,13 +34,13 @@ export default { }, data() { return { - loading: false, showRemoveModal: false, removeConfirmText: '', }; }, i18n: { downloadJSON: s__('Terraform|Download JSON'), + errorUpdate: s__('Terraform|An error occurred while changing the state file'), lock: s__('Terraform|Lock'), modalBody: s__( 'Terraform|You are about to remove the State file %{name}. This will permanently delete all the State versions and history. The infrastructure provisioned previously will remain intact, only the state file with all its versions are to be removed. This action is non-revertible.', @@ -51,6 +52,7 @@ export default { ), modalRemove: s__('Terraform|Remove'), remove: s__('Terraform|Remove state file and versions'), + removeSuccessful: s__('Terraform|%{name} successfully removed'), unlock: s__('Terraform|Unlock'), }, computed: { @@ -63,6 +65,9 @@ export default { disableModalSubmit() { return this.removeConfirmText !== this.state.name; }, + loading() { + return this.state.loadingLock || this.state.loadingRemove; + }, primaryModalProps() { return { text: this.$options.i18n.modalRemove, @@ -76,19 +81,56 @@ export default { this.removeConfirmText = ''; }, lock() { - this.stateMutation(lockState); + this.updateStateCache({ + _showDetails: false, + errorMessages: [], + loadingLock: true, + loadingRemove: false, + }); + + this.stateActionMutation(lockState); }, unlock() { - this.stateMutation(unlockState); + this.updateStateCache({ + _showDetails: false, + errorMessages: [], + loadingLock: true, + loadingRemove: false, + }); + + this.stateActionMutation(unlockState); + }, + updateStateCache(newData) { + this.$apollo.mutate({ + mutation: addDataToState, + variables: { + terraformState: { + ...this.state, + ...newData, + }, + }, + }); }, remove() { if (!this.disableModalSubmit) { this.hideModal(); - this.stateMutation(removeState); + + this.updateStateCache({ + _showDetails: false, + errorMessages: [], + loadingLock: false, + loadingRemove: true, + }); + + this.stateActionMutation( + removeState, + sprintf(this.$options.i18n.removeSuccessful, { name: this.state.name }), + ); } }, - stateMutation(mutation) { - this.loading = true; + stateActionMutation(mutation, successMessage = null) { + let errorMessages = []; + this.$apollo .mutate({ mutation, @@ -99,9 +141,27 @@ export default { awaitRefetchQueries: true, notifyOnNetworkStatusChange: true, }) - .catch(() => {}) + .then(({ data }) => { + errorMessages = + data?.terraformStateDelete?.errors || + data?.terraformStateLock?.errors || + data?.terraformStateUnlock?.errors || + []; + + if (errorMessages.length === 0 && successMessage) { + this.$toast.show(successMessage); + } + }) + .catch(() => { + errorMessages = [this.$options.i18n.errorUpdate]; + }) .finally(() => { - this.loading = false; + this.updateStateCache({ + _showDetails: Boolean(errorMessages.length), + errorMessages, + loadingLock: false, + loadingRemove: false, + }); }); }, }, diff --git a/app/assets/javascripts/terraform/components/terraform_list.vue b/app/assets/javascripts/terraform/components/terraform_list.vue index b71133d8813..a18f33ebb1f 100644 --- a/app/assets/javascripts/terraform/components/terraform_list.vue +++ b/app/assets/javascripts/terraform/components/terraform_list.vue @@ -1,9 +1,9 @@ <script> import { GlAlert, GlBadge, GlKeysetPagination, GlLoadingIcon, GlTab, GlTabs } from '@gitlab/ui'; +import { MAX_LIST_COUNT } from '../constants'; import getStatesQuery from '../graphql/queries/get_states.query.graphql'; import EmptyState from './empty_state.vue'; import StatesTable from './states_table.vue'; -import { MAX_LIST_COUNT } from '../constants'; export default { apollo: { diff --git a/app/assets/javascripts/terraform/graphql/fragments/state.fragment.graphql b/app/assets/javascripts/terraform/graphql/fragments/state.fragment.graphql index 49f9ae3dd97..fb823336411 100644 --- a/app/assets/javascripts/terraform/graphql/fragments/state.fragment.graphql +++ b/app/assets/javascripts/terraform/graphql/fragments/state.fragment.graphql @@ -2,6 +2,11 @@ #import "./state_version.fragment.graphql" fragment State on TerraformState { + _showDetails @client + errorMessages @client + loadingLock @client + loadingRemove @client + id name lockedAt diff --git a/app/assets/javascripts/terraform/graphql/mutations/add_data_to_state.mutation.graphql b/app/assets/javascripts/terraform/graphql/mutations/add_data_to_state.mutation.graphql new file mode 100644 index 00000000000..645b9766e2b --- /dev/null +++ b/app/assets/javascripts/terraform/graphql/mutations/add_data_to_state.mutation.graphql @@ -0,0 +1,3 @@ +mutation addDataToTerraformState($terraformState: State!) { + addDataToTerraformState(terraformState: $terraformState) @client +} diff --git a/app/assets/javascripts/terraform/graphql/resolvers.js b/app/assets/javascripts/terraform/graphql/resolvers.js new file mode 100644 index 00000000000..d27379bdd9f --- /dev/null +++ b/app/assets/javascripts/terraform/graphql/resolvers.js @@ -0,0 +1,45 @@ +import TerraformState from './fragments/state.fragment.graphql'; + +export default { + TerraformState: { + _showDetails: (state) => { + // eslint-disable-next-line no-underscore-dangle + return state._showDetails || false; + }, + errorMessages: (state) => { + return state.errorMessages || []; + }, + loadingLock: (state) => { + return state.loadingLock || false; + }, + loadingRemove: (state) => { + return state.loadingRemove || false; + }, + }, + Mutation: { + addDataToTerraformState: (_, { terraformState }, { client }) => { + const fragmentData = { + id: terraformState.id, + fragment: TerraformState, + // eslint-disable-next-line @gitlab/require-i18n-strings + fragmentName: 'State', + }; + + const previousTerraformState = client.readFragment(fragmentData); + + if (previousTerraformState) { + client.writeFragment({ + ...fragmentData, + data: { + ...previousTerraformState, + // eslint-disable-next-line no-underscore-dangle + _showDetails: terraformState._showDetails, + errorMessages: terraformState.errorMessages, + loadingLock: terraformState.loadingLock, + loadingRemove: terraformState.loadingRemove, + }, + }); + } + }, + }, +}; diff --git a/app/assets/javascripts/terraform/index.js b/app/assets/javascripts/terraform/index.js index e27a29433f3..3f986423836 100644 --- a/app/assets/javascripts/terraform/index.js +++ b/app/assets/javascripts/terraform/index.js @@ -1,8 +1,12 @@ +import { GlToast } from '@gitlab/ui'; +import { defaultDataIdFromObject } from 'apollo-cache-inmemory'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; -import TerraformList from './components/terraform_list.vue'; import createDefaultClient from '~/lib/graphql'; +import TerraformList from './components/terraform_list.vue'; +import resolvers from './graphql/resolvers'; +Vue.use(GlToast); Vue.use(VueApollo); export default () => { @@ -12,7 +16,13 @@ export default () => { return null; } - const defaultClient = createDefaultClient(); + const defaultClient = createDefaultClient(resolvers, { + cacheConfig: { + dataIdFromObject: (object) => { + return object.id || defaultDataIdFromObject(object); + }, + }, + }); const { emptyStateImage, projectPath } = el.dataset; |