diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-17 14:59:07 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-17 14:59:07 +0300 |
commit | 8b573c94895dc0ac0e1d9d59cf3e8745e8b539ca (patch) | |
tree | 544930fb309b30317ae9797a9683768705d664c4 /app/assets/javascripts/terraform | |
parent | 4b1de649d0168371549608993deac953eb692019 (diff) |
Add latest changes from gitlab-org/gitlab@13-7-stable-eev13.7.0-rc42
Diffstat (limited to 'app/assets/javascripts/terraform')
8 files changed, 358 insertions, 47 deletions
diff --git a/app/assets/javascripts/terraform/components/states_table.vue b/app/assets/javascripts/terraform/components/states_table.vue index 2e4c18c5a5b..d0d49233334 100644 --- a/app/assets/javascripts/terraform/components/states_table.vue +++ b/app/assets/javascripts/terraform/components/states_table.vue @@ -1,16 +1,22 @@ <script> -import { GlBadge, GlIcon, GlSprintf, GlTable, GlTooltip } from '@gitlab/ui'; +import { GlBadge, GlIcon, GlLink, GlSprintf, GlTable, GlTooltip } from '@gitlab/ui'; import { s__ } from '~/locale'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +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'; export default { components: { + CiBadge, GlBadge, GlIcon, + GlLink, GlSprintf, GlTable, GlTooltip, + StateActions, TimeAgoTooltip, }, mixins: [timeagoMixin], @@ -19,28 +25,73 @@ export default { required: true, type: Array, }, + terraformAdmin: { + required: false, + type: Boolean, + default: false, + }, }, computed: { fields() { - return [ + const columns = [ { key: 'name', - thClass: 'gl-display-none', + label: this.$options.i18n.name, + }, + { + key: 'pipeline', + label: this.$options.i18n.pipeline, }, { key: 'updated', - thClass: 'gl-display-none', - tdClass: 'gl-text-right', + label: this.$options.i18n.details, }, ]; + + if (this.terraformAdmin) { + columns.push({ + key: 'actions', + label: this.$options.i18n.actions, + thClass: 'gl-w-12', + tdClass: 'gl-text-right', + }); + } + + return columns; }, }, + i18n: { + actions: s__('Terraform|Actions'), + details: s__('Terraform|Details'), + jobStatus: s__('Terraform|Job status'), + locked: s__('Terraform|Locked'), + lockedByUser: s__('Terraform|Locked by %{user} %{timeAgo}'), + name: s__('Terraform|Name'), + pipeline: s__('Terraform|Pipeline'), + unknownUser: s__('Terraform|Unknown User'), + updatedUser: s__('Terraform|%{user} updated %{timeAgo}'), + }, methods: { createdByUserName(item) { return item.latestVersion?.createdByUser?.name; }, lockedByUserName(item) { - return item.lockedByUser?.name || s__('Terraform|Unknown User'); + return item.lockedByUser?.name || this.$options.i18n.unknownUser; + }, + pipelineDetailedStatus(item) { + return item.latestVersion?.job?.detailedStatus; + }, + pipelineID(item) { + let id = item.latestVersion?.job?.pipeline?.id; + + if (id) { + id = getIdFromGraphQLId(id); + } + + return id; + }, + pipelinePath(item) { + return item.latestVersion?.job?.pipeline?.path; }, updatedTime(item) { return item.latestVersion?.updatedAt || item.updatedAt; @@ -50,25 +101,34 @@ export default { </script> <template> - <gl-table :items="states" :fields="fields" data-testid="terraform-states-table"> + <gl-table + :items="states" + :fields="fields" + data-testid="terraform-states-table" + fixed + stacked="md" + > <template #cell(name)="{ item }"> - <div class="gl-display-flex align-items-center" data-testid="terraform-states-table-name"> + <div + class="gl-display-flex align-items-center gl-justify-content-end gl-justify-content-md-start" + data-testid="terraform-states-table-name" + > <p class="gl-font-weight-bold gl-m-0 gl-text-gray-900"> {{ item.name }} </p> - <div v-if="item.lockedAt" id="terraformLockedBadgeContainer" class="gl-mx-2"> - <gl-badge id="terraformLockedBadge"> + <div v-if="item.lockedAt" :id="`terraformLockedBadgeContainer${item.name}`" class="gl-mx-2"> + <gl-badge :id="`terraformLockedBadge${item.name}`"> <gl-icon name="lock" /> - {{ s__('Terraform|Locked') }} + {{ $options.i18n.locked }} </gl-badge> <gl-tooltip - container="terraformLockedBadgeContainer" + :container="`terraformLockedBadgeContainer${item.name}`" + :target="`terraformLockedBadge${item.name}`" placement="right" - target="terraformLockedBadge" > - <gl-sprintf :message="s__('Terraform|Locked by %{user} %{timeAgo}')"> + <gl-sprintf :message="$options.i18n.lockedByUser"> <template #user> {{ lockedByUserName(item) }} </template> @@ -82,9 +142,37 @@ export default { </div> </template> + <template #cell(pipeline)="{ item }"> + <div data-testid="terraform-states-table-pipeline" class="gl-min-h-7"> + <gl-link v-if="pipelineID(item)" :href="pipelinePath(item)"> + #{{ pipelineID(item) }} + </gl-link> + + <div + v-if="pipelineDetailedStatus(item)" + :id="`terraformJobStatusContainer${item.name}`" + class="gl-my-2" + > + <ci-badge + :id="`terraformJobStatus${item.name}`" + :status="pipelineDetailedStatus(item)" + class="gl-py-1" + /> + + <gl-tooltip + :container="`terraformJobStatusContainer${item.name}`" + :target="`terraformJobStatus${item.name}`" + placement="right" + > + {{ $options.i18n.jobStatus }} + </gl-tooltip> + </div> + </div> + </template> + <template #cell(updated)="{ item }"> <p class="gl-m-0" data-testid="terraform-states-table-updated"> - <gl-sprintf :message="s__('Terraform|%{user} updated %{timeAgo}')"> + <gl-sprintf :message="$options.i18n.updatedUser"> <template #user> <span v-if="item.latestVersion"> {{ createdByUserName(item) }} @@ -97,5 +185,9 @@ export default { </gl-sprintf> </p> </template> + + <template v-if="terraformAdmin" #cell(actions)="{ item }"> + <state-actions :state="item" /> + </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 new file mode 100644 index 00000000000..44b0713e544 --- /dev/null +++ b/app/assets/javascripts/terraform/components/states_table_actions.vue @@ -0,0 +1,192 @@ +<script> +import { + GlDropdown, + GlDropdownDivider, + GlDropdownItem, + GlFormGroup, + GlFormInput, + GlIcon, + GlModal, + GlSprintf, +} from '@gitlab/ui'; +import { s__ } from '~/locale'; +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'; + +export default { + components: { + GlDropdown, + GlDropdownDivider, + GlDropdownItem, + GlFormGroup, + GlFormInput, + GlIcon, + GlModal, + GlSprintf, + }, + props: { + state: { + required: true, + type: Object, + }, + }, + data() { + return { + loading: false, + showRemoveModal: false, + removeConfirmText: '', + }; + }, + i18n: { + downloadJSON: s__('Terraform|Download JSON'), + 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.', + ), + modalCancel: s__('Terraform|Cancel'), + modalHeader: s__('Terraform|Are you sure you want to remove the Terraform State %{name}?'), + modalInputLabel: s__( + 'Terraform|To remove the State file and its versions, type %{name} to confirm:', + ), + modalRemove: s__('Terraform|Remove'), + remove: s__('Terraform|Remove state file and versions'), + unlock: s__('Terraform|Unlock'), + }, + computed: { + cancelModalProps() { + return { + text: this.$options.i18n.modalCancel, + attributes: [], + }; + }, + disableModalSubmit() { + return this.removeConfirmText !== this.state.name; + }, + primaryModalProps() { + return { + text: this.$options.i18n.modalRemove, + attributes: [{ disabled: this.disableModalSubmit }, { variant: 'danger' }], + }; + }, + }, + methods: { + hideModal() { + this.showRemoveModal = false; + this.removeConfirmText = ''; + }, + lock() { + this.stateMutation(lockState); + }, + unlock() { + this.stateMutation(unlockState); + }, + remove() { + if (!this.disableModalSubmit) { + this.hideModal(); + this.stateMutation(removeState); + } + }, + stateMutation(mutation) { + this.loading = true; + this.$apollo + .mutate({ + mutation, + variables: { + stateID: this.state.id, + }, + refetchQueries: () => ['getStates'], + awaitRefetchQueries: true, + notifyOnNetworkStatusChange: true, + }) + .catch(() => {}) + .finally(() => { + this.loading = false; + }); + }, + }, +}; +</script> + +<template> + <div> + <gl-dropdown + icon="ellipsis_v" + right + :data-testid="`terraform-state-actions-${state.name}`" + :disabled="loading" + toggle-class="gl-px-3! gl-shadow-none!" + > + <template #button-content> + <gl-icon class="gl-mr-0" name="ellipsis_v" /> + </template> + + <gl-dropdown-item + v-if="state.latestVersion" + data-testid="terraform-state-download" + :download="`${state.name}.json`" + :href="state.latestVersion.downloadPath" + > + {{ $options.i18n.downloadJSON }} + </gl-dropdown-item> + + <gl-dropdown-item v-if="state.lockedAt" data-testid="terraform-state-unlock" @click="unlock"> + {{ $options.i18n.unlock }} + </gl-dropdown-item> + + <gl-dropdown-item v-else data-testid="terraform-state-lock" @click="lock"> + {{ $options.i18n.lock }} + </gl-dropdown-item> + + <gl-dropdown-divider /> + + <gl-dropdown-item data-testid="terraform-state-remove" @click="showRemoveModal = true"> + {{ $options.i18n.remove }} + </gl-dropdown-item> + </gl-dropdown> + + <gl-modal + :modal-id="`terraform-state-actions-remove-modal-${state.name}`" + :visible="showRemoveModal" + :action-primary="primaryModalProps" + :action-cancel="cancelModalProps" + @ok="remove" + @cancel="hideModal" + @close="hideModal" + @hide="hideModal" + > + <template #modal-title> + <gl-sprintf :message="$options.i18n.modalHeader"> + <template #name> + <span>{{ state.name }}</span> + </template> + </gl-sprintf> + </template> + + <p> + <gl-sprintf :message="$options.i18n.modalBody"> + <template #name> + <span>{{ state.name }}</span> + </template> + </gl-sprintf> + </p> + + <gl-form-group> + <template #label> + <gl-sprintf :message="$options.i18n.modalInputLabel"> + <template #name> + <code>{{ state.name }}</code> + </template> + </gl-sprintf> + </template> + <gl-form-input + :id="`terraform-state-remove-input-${state.name}`" + ref="input" + v-model="removeConfirmText" + type="text" + @keyup.enter="remove" + /> + </gl-form-group> + </gl-modal> + </div> +</template> diff --git a/app/assets/javascripts/terraform/components/terraform_list.vue b/app/assets/javascripts/terraform/components/terraform_list.vue index f614bdc8d43..26a0bfe5fa5 100644 --- a/app/assets/javascripts/terraform/components/terraform_list.vue +++ b/app/assets/javascripts/terraform/components/terraform_list.vue @@ -15,13 +15,7 @@ export default { ...this.cursor, }; }, - update: data => { - return { - count: data?.project?.terraformStates?.count, - list: data?.project?.terraformStates?.nodes, - pageInfo: data?.project?.terraformStates?.pageInfo, - }; - }, + update: data => data, error() { this.states = null; }, @@ -46,6 +40,11 @@ export default { required: true, type: String, }, + terraformAdmin: { + required: false, + type: Boolean, + default: false, + }, }, data() { return { @@ -62,35 +61,34 @@ export default { return this.$apollo.queries.states.loading; }, pageInfo() { - return this.states?.pageInfo || {}; + return this.states?.project?.terraformStates?.pageInfo || {}; }, showPagination() { return this.pageInfo.hasPreviousPage || this.pageInfo.hasNextPage; }, statesCount() { - return this.states?.count; + return this.states?.project?.terraformStates?.count; }, statesList() { - return this.states?.list; + return this.states?.project?.terraformStates?.nodes; }, }, methods: { - updatePagination(item) { - if (item === this.pageInfo.endCursor) { - this.cursor = { - first: MAX_LIST_COUNT, - after: item, - last: null, - before: null, - }; - } else { - this.cursor = { - first: null, - after: null, - last: MAX_LIST_COUNT, - before: item, - }; - } + nextPage(item) { + this.cursor = { + first: MAX_LIST_COUNT, + after: item, + last: null, + before: null, + }; + }, + prevPage(item) { + this.cursor = { + first: null, + after: null, + last: MAX_LIST_COUNT, + before: item, + }; }, }, }; @@ -111,14 +109,10 @@ export default { <div v-else-if="statesList"> <div v-if="statesCount"> - <states-table :states="statesList" /> + <states-table :states="statesList" :terraform-admin="terraformAdmin" /> <div v-if="showPagination" class="gl-display-flex gl-justify-content-center gl-mt-5"> - <gl-keyset-pagination - v-bind="pageInfo" - @prev="updatePagination" - @next="updatePagination" - /> + <gl-keyset-pagination v-bind="pageInfo" @prev="prevPage" @next="nextPage" /> </div> </div> diff --git a/app/assets/javascripts/terraform/graphql/fragments/state_version.fragment.graphql b/app/assets/javascripts/terraform/graphql/fragments/state_version.fragment.graphql index c7e9700c696..70ba5c960be 100644 --- a/app/assets/javascripts/terraform/graphql/fragments/state_version.fragment.graphql +++ b/app/assets/javascripts/terraform/graphql/fragments/state_version.fragment.graphql @@ -1,9 +1,26 @@ #import "~/graphql_shared/fragments/user.fragment.graphql" fragment StateVersion on TerraformStateVersion { + downloadPath + serial updatedAt createdByUser { ...User } + + job { + detailedStatus { + detailsPath + group + icon + label + text + } + + pipeline { + id + path + } + } } diff --git a/app/assets/javascripts/terraform/graphql/mutations/lock_state.mutation.graphql b/app/assets/javascripts/terraform/graphql/mutations/lock_state.mutation.graphql new file mode 100644 index 00000000000..aea0f8b025a --- /dev/null +++ b/app/assets/javascripts/terraform/graphql/mutations/lock_state.mutation.graphql @@ -0,0 +1,5 @@ +mutation lockState($stateID: TerraformStateID!) { + terraformStateLock(input: { id: $stateID }) { + errors + } +} diff --git a/app/assets/javascripts/terraform/graphql/mutations/remove_state.mutation.graphql b/app/assets/javascripts/terraform/graphql/mutations/remove_state.mutation.graphql new file mode 100644 index 00000000000..d85ebb9cea2 --- /dev/null +++ b/app/assets/javascripts/terraform/graphql/mutations/remove_state.mutation.graphql @@ -0,0 +1,5 @@ +mutation removeState($stateID: TerraformStateID!) { + terraformStateDelete(input: { id: $stateID }) { + errors + } +} diff --git a/app/assets/javascripts/terraform/graphql/mutations/unlock_state.mutation.graphql b/app/assets/javascripts/terraform/graphql/mutations/unlock_state.mutation.graphql new file mode 100644 index 00000000000..1909fe95cf3 --- /dev/null +++ b/app/assets/javascripts/terraform/graphql/mutations/unlock_state.mutation.graphql @@ -0,0 +1,5 @@ +mutation unlockState($stateID: TerraformStateID!) { + terraformStateUnlock(input: { id: $stateID }) { + errors + } +} diff --git a/app/assets/javascripts/terraform/index.js b/app/assets/javascripts/terraform/index.js index 579d2d14023..e27a29433f3 100644 --- a/app/assets/javascripts/terraform/index.js +++ b/app/assets/javascripts/terraform/index.js @@ -24,6 +24,7 @@ export default () => { props: { emptyStateImage, projectPath, + terraformAdmin: el.hasAttribute('data-terraform-admin'), }, }); }, |