diff options
Diffstat (limited to 'app/assets/javascripts/environments')
19 files changed, 507 insertions, 76 deletions
diff --git a/app/assets/javascripts/environments/components/confirm_rollback_modal.vue b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue index 4783b92942c..0e556f093e2 100644 --- a/app/assets/javascripts/environments/components/confirm_rollback_modal.vue +++ b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue @@ -7,6 +7,7 @@ import { escape } from 'lodash'; import csrf from '~/lib/utils/csrf'; import { __, s__, sprintf } from '~/locale'; +import rollbackEnvironment from '../graphql/mutations/rollback_environment.mutation.graphql'; import eventHub from '../event_hub'; export default { @@ -40,10 +41,15 @@ export default { required: false, default: null, }, + graphql: { + type: Boolean, + required: false, + default: false, + }, }, computed: { modalTitle() { - const title = this.environment.isLastDeployment + const title = this.isLastDeployment ? s__('Environments|Re-deploy environment %{name}?') : s__('Environments|Rollback environment %{name}?'); @@ -53,6 +59,11 @@ export default { }, commitShortSha() { if (this.hasMultipleCommits) { + if (this.graphql) { + const { lastDeployment } = this.environment; + return this.commitData(lastDeployment, 'shortId'); + } + const { last_deployment } = this.environment; return this.commitData(last_deployment, 'short_id'); } @@ -61,6 +72,11 @@ export default { }, commitUrl() { if (this.hasMultipleCommits) { + if (this.graphql) { + const { lastDeployment } = this.environment; + return this.commitData(lastDeployment, 'commitPath'); + } + const { last_deployment } = this.environment; return this.commitData(last_deployment, 'commit_path'); } @@ -68,9 +84,7 @@ export default { return this.environment.commitUrl; }, modalActionText() { - return this.environment.isLastDeployment - ? s__('Environments|Re-deploy') - : s__('Environments|Rollback'); + return this.isLastDeployment ? s__('Environments|Re-deploy') : s__('Environments|Rollback'); }, primaryProps() { let attributes = [{ variant: 'danger' }]; @@ -84,20 +98,27 @@ export default { attributes, }; }, + isLastDeployment() { + // eslint-disable-next-line @gitlab/require-i18n-strings + return this.environment?.isLastDeployment || this.environment?.lastDeployment?.['last?']; + }, }, methods: { handleChange(event) { this.$emit('change', event); }, onOk() { - eventHub.$emit('rollbackEnvironment', this.environment); + if (this.graphql) { + this.$apollo.mutate({ + mutation: rollbackEnvironment, + variables: { environment: this.environment }, + }); + } else { + eventHub.$emit('rollbackEnvironment', this.environment); + } }, commitData(lastDeployment, key) { - if (lastDeployment && lastDeployment.commit) { - return lastDeployment.commit[key]; - } - - return ''; + return lastDeployment?.commit?.[key] ?? ''; }, }, csrf, diff --git a/app/assets/javascripts/environments/components/delete_environment_modal.vue b/app/assets/javascripts/environments/components/delete_environment_modal.vue index 26ec882472b..d3d4c7d23d8 100644 --- a/app/assets/javascripts/environments/components/delete_environment_modal.vue +++ b/app/assets/javascripts/environments/components/delete_environment_modal.vue @@ -1,7 +1,9 @@ <script> import { GlTooltipDirective, GlModal } from '@gitlab/ui'; +import createFlash from '~/flash'; import { __, s__, sprintf } from '~/locale'; import eventHub from '../event_hub'; +import deleteEnvironmentMutation from '../graphql/mutations/delete_environment.mutation.graphql'; export default { id: 'delete-environment-modal', @@ -17,6 +19,11 @@ export default { type: Object, required: true, }, + graphql: { + type: Boolean, + required: false, + default: false, + }, }, computed: { primaryProps() { @@ -49,7 +56,29 @@ export default { }, methods: { onSubmit() { - eventHub.$emit('deleteEnvironment', this.environment); + if (this.graphql) { + this.$apollo + .mutate({ + mutation: deleteEnvironmentMutation, + variables: { environment: this.environment }, + }) + .then(([message]) => { + if (message) { + createFlash({ message }); + } + }) + .catch((error) => + createFlash({ + message: s__( + 'Environments|An error occurred while deleting the environment. Check if the environment stopped; if not, stop it and try again.', + ), + error, + captureError: true, + }), + ); + } else { + eventHub.$emit('deleteEnvironment', this.environment); + } }, }, }; diff --git a/app/assets/javascripts/environments/components/enable_review_app_modal.vue b/app/assets/javascripts/environments/components/enable_review_app_modal.vue index d770a2302e8..b757c55bfdb 100644 --- a/app/assets/javascripts/environments/components/enable_review_app_modal.vue +++ b/app/assets/javascripts/environments/components/enable_review_app_modal.vue @@ -12,11 +12,20 @@ export default { ModalCopyButton, }, inject: ['defaultBranchName'], + model: { + prop: 'visible', + event: 'change', + }, props: { modalId: { type: String, required: true, }, + visible: { + type: Boolean, + required: false, + default: false, + }, }, instructionText: { step1: s__( @@ -57,12 +66,15 @@ export default { </script> <template> <gl-modal + :visible="visible" :modal-id="modalId" :title="$options.modalInfo.title" + static size="lg" ok-only ok-variant="light" :ok-title="$options.modalInfo.closeText" + @change="$emit('change', $event)" > <p> <gl-sprintf :message="$options.instructionText.step1"> diff --git a/app/assets/javascripts/environments/components/environment_delete.vue b/app/assets/javascripts/environments/components/environment_delete.vue index 8609503e486..63169b790c7 100644 --- a/app/assets/javascripts/environments/components/environment_delete.vue +++ b/app/assets/javascripts/environments/components/environment_delete.vue @@ -7,6 +7,7 @@ import { GlDropdownItem, GlModalDirective } from '@gitlab/ui'; import { s__ } from '~/locale'; import eventHub from '../event_hub'; +import setEnvironmentToDelete from '../graphql/mutations/set_environment_to_delete.mutation.graphql'; export default { components: { @@ -20,6 +21,11 @@ export default { type: Object, required: true, }, + graphql: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -30,14 +36,25 @@ export default { title: s__('Environments|Delete environment'), }, mounted() { - eventHub.$on('deleteEnvironment', this.onDeleteEnvironment); + if (!this.graphql) { + eventHub.$on('deleteEnvironment', this.onDeleteEnvironment); + } }, beforeDestroy() { - eventHub.$off('deleteEnvironment', this.onDeleteEnvironment); + if (!this.graphql) { + eventHub.$off('deleteEnvironment', this.onDeleteEnvironment); + } }, methods: { onClick() { - eventHub.$emit('requestDeleteEnvironment', this.environment); + if (this.graphql) { + this.$apollo.mutate({ + mutation: setEnvironmentToDelete, + variables: { environment: this.environment }, + }); + } else { + eventHub.$emit('requestDeleteEnvironment', this.environment); + } }, onDeleteEnvironment(environment) { if (this.environment.id === environment.id) { diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index db01d455b2b..be9bfb50de5 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -5,7 +5,7 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { __, s__, sprintf } from '~/locale'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; import CommitComponent from '~/vue_shared/components/commit.vue'; -import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; +import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; import eventHub from '../event_hub'; diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue index 00497b3c683..f7f0cf4cb8d 100644 --- a/app/assets/javascripts/environments/components/environment_rollback.vue +++ b/app/assets/javascripts/environments/components/environment_rollback.vue @@ -8,6 +8,7 @@ import { GlModalDirective, GlDropdownItem } from '@gitlab/ui'; import { s__ } from '~/locale'; import eventHub from '../event_hub'; +import setEnvironmentToRollback from '../graphql/mutations/set_environment_to_rollback.mutation.graphql'; export default { components: { @@ -32,11 +33,12 @@ export default { type: String, required: true, }, - }, - data() { - return { - isLoading: false, - }; + + graphql: { + type: Boolean, + required: false, + default: false, + }, }, computed: { @@ -49,16 +51,18 @@ export default { methods: { onClick() { - eventHub.$emit('requestRollbackEnvironment', { - ...this.environment, - retryUrl: this.retryUrl, - isLastDeployment: this.isLastDeployment, - }); - eventHub.$on('rollbackEnvironment', (environment) => { - if (environment.id === this.environment.id) { - this.isLoading = true; - } - }); + if (this.graphql) { + this.$apollo.mutate({ + mutation: setEnvironmentToRollback, + variables: { environment: this.environment }, + }); + } else { + eventHub.$emit('requestRollbackEnvironment', { + ...this.environment, + retryUrl: this.retryUrl, + isLastDeployment: this.isLastDeployment, + }); + } }, }, }; diff --git a/app/assets/javascripts/environments/components/new_environment_folder.vue b/app/assets/javascripts/environments/components/new_environment_folder.vue index 0615bdef537..fe3d6f1e8ca 100644 --- a/app/assets/javascripts/environments/components/new_environment_folder.vue +++ b/app/assets/javascripts/environments/components/new_environment_folder.vue @@ -1,9 +1,11 @@ <script> -import { GlCollapse, GlIcon, GlBadge, GlLink } from '@gitlab/ui'; +import { GlButton, GlCollapse, GlIcon, GlBadge, GlLink } from '@gitlab/ui'; +import { __, s__ } from '~/locale'; import folderQuery from '../graphql/queries/folder.query.graphql'; export default { components: { + GlButton, GlCollapse, GlIcon, GlBadge, @@ -26,12 +28,20 @@ export default { }, }, }, + i18n: { + collapse: __('Collapse'), + expand: __('Expand'), + link: s__('Environments|Show all'), + }, computed: { icons() { return this.visible ? { caret: 'angle-down', folder: 'folder-open' } : { caret: 'angle-right', folder: 'folder-o' }; }, + label() { + return this.visible ? this.$options.i18n.collapse : this.$options.i18n.expand; + }, count() { return this.folder?.availableCount ?? 0; }, @@ -51,18 +61,21 @@ export default { </script> <template> <div class="gl-border-b-solid gl-border-gray-100 gl-border-1 gl-px-3 gl-pt-3 gl-pb-5"> - <div class="gl-w-full gl-display-flex gl-align-items-center" @click="toggleCollapse"> - <gl-icon - class="gl-mr-2 gl-fill-current-color gl-text-gray-500" - :name="icons.caret" - :size="12" + <div class="gl-w-full gl-display-flex gl-align-items-center"> + <gl-button + class="gl-mr-4 gl-fill-current-color gl-text-gray-500" + :aria-label="label" + :icon="icons.caret" + size="small" + category="tertiary" + @click="toggleCollapse" /> <gl-icon class="gl-mr-2 gl-fill-current-color gl-text-gray-500" :name="icons.folder" /> <div class="gl-mr-2 gl-text-gray-500" :class="folderClass"> {{ nestedEnvironment.name }} </div> <gl-badge size="sm" class="gl-mr-auto">{{ count }}</gl-badge> - <gl-link v-if="visible" :href="folderPath">{{ s__('Environments|Show all') }}</gl-link> + <gl-link v-if="visible" :href="folderPath">{{ $options.i18n.link }}</gl-link> </div> <gl-collapse :visible="visible" /> </div> diff --git a/app/assets/javascripts/environments/components/new_environments_app.vue b/app/assets/javascripts/environments/components/new_environments_app.vue index a5526f9cd71..8d94e7021ca 100644 --- a/app/assets/javascripts/environments/components/new_environments_app.vue +++ b/app/assets/javascripts/environments/components/new_environments_app.vue @@ -1,47 +1,205 @@ <script> -import { GlBadge, GlTab, GlTabs } from '@gitlab/ui'; -import environmentAppQuery from '../graphql/queries/environmentApp.query.graphql'; +import { GlBadge, GlPagination, GlTab, GlTabs } from '@gitlab/ui'; +import { s__, __, sprintf } from '~/locale'; +import { updateHistory, setUrlParams, queryToObject } from '~/lib/utils/url_utility'; +import environmentAppQuery from '../graphql/queries/environment_app.query.graphql'; +import pollIntervalQuery from '../graphql/queries/poll_interval.query.graphql'; +import pageInfoQuery from '../graphql/queries/page_info.query.graphql'; import EnvironmentFolder from './new_environment_folder.vue'; +import EnableReviewAppModal from './enable_review_app_modal.vue'; export default { components: { EnvironmentFolder, + EnableReviewAppModal, GlBadge, + GlPagination, GlTab, GlTabs, }, apollo: { environmentApp: { query: environmentAppQuery, + variables() { + return { + scope: this.scope, + page: this.page ?? 1, + }; + }, + pollInterval() { + return this.interval; + }, }, + interval: { + query: pollIntervalQuery, + }, + pageInfo: { + query: pageInfoQuery, + }, + }, + inject: ['newEnvironmentPath', 'canCreateEnvironment'], + i18n: { + newEnvironmentButtonLabel: s__('Environments|New environment'), + reviewAppButtonLabel: s__('Environments|Enable review app'), + available: __('Available'), + stopped: __('Stopped'), + prevPage: __('Go to previous page'), + nextPage: __('Go to next page'), + next: __('Next'), + prev: __('Prev'), + goto: (page) => sprintf(__('Go to page %{page}'), { page }), + }, + modalId: 'enable-review-app-info', + data() { + const { page = '1', scope = 'available' } = queryToObject(window.location.search); + return { + interval: undefined, + isReviewAppModalVisible: false, + page: parseInt(page, 10), + scope, + }; }, computed: { + canSetupReviewApp() { + return this.environmentApp?.reviewApp?.canSetupReviewApp; + }, folders() { return this.environmentApp?.environments.filter((e) => e.size > 1) ?? []; }, availableCount() { return this.environmentApp?.availableCount; }, + addEnvironment() { + if (!this.canCreateEnvironment) { + return null; + } + + return { + text: this.$options.i18n.newEnvironmentButtonLabel, + attributes: { + href: this.newEnvironmentPath, + category: 'primary', + variant: 'confirm', + }, + }; + }, + openReviewAppModal() { + if (!this.canSetupReviewApp) { + return null; + } + + return { + text: this.$options.i18n.reviewAppButtonLabel, + attributes: { + category: 'secondary', + variant: 'confirm', + }, + }; + }, + stoppedCount() { + return this.environmentApp?.stoppedCount; + }, + totalItems() { + return this.pageInfo?.total; + }, + itemsPerPage() { + return this.pageInfo?.perPage; + }, + }, + mounted() { + window.addEventListener('popstate', this.syncPageFromQueryParams); + }, + destroyed() { + window.removeEventListener('popstate', this.syncPageFromQueryParams); + this.$apollo.queries.environmentApp.stopPolling(); + }, + methods: { + showReviewAppModal() { + this.isReviewAppModalVisible = true; + }, + setScope(scope) { + this.scope = scope; + this.resetPolling(); + }, + movePage(direction) { + this.moveToPage(this.pageInfo[`${direction}Page`]); + }, + moveToPage(page) { + this.page = page; + updateHistory({ + url: setUrlParams({ page: this.page }), + title: document.title, + }); + this.resetPolling(); + }, + syncPageFromQueryParams() { + const { page = '1' } = queryToObject(window.location.search); + this.page = parseInt(page, 10); + }, + resetPolling() { + this.$apollo.queries.environmentApp.stopPolling(); + this.$nextTick(() => { + if (this.interval) { + this.$apollo.queries.environmentApp.startPolling(this.interval); + } else { + this.$apollo.queries.environmentApp.refetch({ scope: this.scope, page: this.page }); + } + }); + }, }, }; </script> <template> <div> - <gl-tabs> - <gl-tab> + <enable-review-app-modal + v-if="canSetupReviewApp" + v-model="isReviewAppModalVisible" + :modal-id="$options.modalId" + data-testid="enable-review-app-modal" + /> + <gl-tabs + :action-secondary="addEnvironment" + :action-primary="openReviewAppModal" + sync-active-tab-with-query-params + query-param-name="scope" + @primary="showReviewAppModal" + > + <gl-tab query-param-value="available" @click="setScope('available')"> <template #title> - <span>{{ __('Available') }}</span> + <span>{{ $options.i18n.available }}</span> <gl-badge size="sm" class="gl-tab-counter-badge"> {{ availableCount }} </gl-badge> </template> - <environment-folder - v-for="folder in folders" - :key="folder.name" - class="gl-mb-3" - :nested-environment="folder" - /> + </gl-tab> + <gl-tab query-param-value="stopped" @click="setScope('stopped')"> + <template #title> + <span>{{ $options.i18n.stopped }}</span> + <gl-badge size="sm" class="gl-tab-counter-badge"> + {{ stoppedCount }} + </gl-badge> + </template> </gl-tab> </gl-tabs> + <environment-folder + v-for="folder in folders" + :key="folder.name" + class="gl-mb-3" + :nested-environment="folder" + /> + <gl-pagination + align="center" + :total-items="totalItems" + :per-page="itemsPerPage" + :value="page" + :next="$options.i18n.next" + :prev="$options.i18n.prev" + :label-previous-page="$options.prevPage" + :label-next-page="$options.nextPage" + :label-page="$options.goto" + @next="movePage('next')" + @previous="movePage('previous')" + @input="moveToPage" + /> </div> </template> diff --git a/app/assets/javascripts/environments/graphql/client.js b/app/assets/javascripts/environments/graphql/client.js index c734c2fba0c..64b18c2003b 100644 --- a/app/assets/javascripts/environments/graphql/client.js +++ b/app/assets/javascripts/environments/graphql/client.js @@ -1,6 +1,7 @@ import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; -import environmentApp from './queries/environmentApp.query.graphql'; +import environmentApp from './queries/environment_app.query.graphql'; +import pageInfoQuery from './queries/page_info.query.graphql'; import { resolvers } from './resolvers'; import typeDefs from './typedefs.graphql'; @@ -19,6 +20,19 @@ export const apolloProvider = (endpoint) => { stoppedCount: 0, }, }); + + cache.writeQuery({ + query: pageInfoQuery, + data: { + pageInfo: { + total: 0, + perPage: 20, + nextPage: 0, + previousPage: 0, + __typename: 'LocalPageInfo', + }, + }, + }); return new VueApollo({ defaultClient, }); diff --git a/app/assets/javascripts/environments/graphql/mutations/set_environment_to_delete.mutation.graphql b/app/assets/javascripts/environments/graphql/mutations/set_environment_to_delete.mutation.graphql new file mode 100644 index 00000000000..ea72067bd37 --- /dev/null +++ b/app/assets/javascripts/environments/graphql/mutations/set_environment_to_delete.mutation.graphql @@ -0,0 +1,3 @@ +mutation SetEnvironmentToDelete($environment: Environment) { + setEnvironmentToDelete(environment: $environment) @client +} diff --git a/app/assets/javascripts/environments/graphql/mutations/set_environment_to_rollback.mutation.graphql b/app/assets/javascripts/environments/graphql/mutations/set_environment_to_rollback.mutation.graphql new file mode 100644 index 00000000000..aba978ed79e --- /dev/null +++ b/app/assets/javascripts/environments/graphql/mutations/set_environment_to_rollback.mutation.graphql @@ -0,0 +1,3 @@ +mutation SetEnvironmentToRollback($environment: Environment) { + setEnvironmentToRollback(environment: $environment) @client +} diff --git a/app/assets/javascripts/environments/graphql/queries/environmentApp.query.graphql b/app/assets/javascripts/environments/graphql/queries/environmentApp.query.graphql deleted file mode 100644 index faa76c0a42c..00000000000 --- a/app/assets/javascripts/environments/graphql/queries/environmentApp.query.graphql +++ /dev/null @@ -1,8 +0,0 @@ -query getEnvironmentApp { - environmentApp @client { - availableCount - environments - reviewApp - stoppedCount - } -} diff --git a/app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql b/app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql new file mode 100644 index 00000000000..2c17c42dd6d --- /dev/null +++ b/app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql @@ -0,0 +1,9 @@ +query getEnvironmentApp($page: Int, $scope: String) { + environmentApp(page: $page, scope: $scope) @client { + availableCount + stoppedCount + environments + reviewApp + stoppedCount + } +} diff --git a/app/assets/javascripts/environments/graphql/queries/environment_to_delete.query.graphql b/app/assets/javascripts/environments/graphql/queries/environment_to_delete.query.graphql new file mode 100644 index 00000000000..5d39de8a0f1 --- /dev/null +++ b/app/assets/javascripts/environments/graphql/queries/environment_to_delete.query.graphql @@ -0,0 +1,7 @@ +query environmentToDelete { + environmentToDelete @client { + id + name + deletePath + } +} diff --git a/app/assets/javascripts/environments/graphql/queries/environment_to_rollback.query.graphql b/app/assets/javascripts/environments/graphql/queries/environment_to_rollback.query.graphql new file mode 100644 index 00000000000..f7586e27665 --- /dev/null +++ b/app/assets/javascripts/environments/graphql/queries/environment_to_rollback.query.graphql @@ -0,0 +1,7 @@ +query environmentToRollback { + environmentToRollback @client { + id + name + lastDeployment + } +} diff --git a/app/assets/javascripts/environments/graphql/queries/page_info.query.graphql b/app/assets/javascripts/environments/graphql/queries/page_info.query.graphql new file mode 100644 index 00000000000..d77ca05d46f --- /dev/null +++ b/app/assets/javascripts/environments/graphql/queries/page_info.query.graphql @@ -0,0 +1,8 @@ +query getPageInfo { + pageInfo @client { + total + perPage + nextPage + previousPage + } +} diff --git a/app/assets/javascripts/environments/graphql/queries/poll_interval.query.graphql b/app/assets/javascripts/environments/graphql/queries/poll_interval.query.graphql new file mode 100644 index 00000000000..28afc30a0dd --- /dev/null +++ b/app/assets/javascripts/environments/graphql/queries/poll_interval.query.graphql @@ -0,0 +1,3 @@ +query pollInterval { + interval @client +} diff --git a/app/assets/javascripts/environments/graphql/resolvers.js b/app/assets/javascripts/environments/graphql/resolvers.js index 8322b806370..9ebbc0ad1f8 100644 --- a/app/assets/javascripts/environments/graphql/resolvers.js +++ b/app/assets/javascripts/environments/graphql/resolvers.js @@ -1,5 +1,20 @@ import axios from '~/lib/utils/axios_utils'; -import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { s__ } from '~/locale'; +import { + convertObjectPropsToCamelCase, + parseIntPagination, + normalizeHeaders, +} from '~/lib/utils/common_utils'; + +import pollIntervalQuery from './queries/poll_interval.query.graphql'; +import environmentToRollbackQuery from './queries/environment_to_rollback.query.graphql'; +import environmentToDeleteQuery from './queries/environment_to_delete.query.graphql'; +import pageInfoQuery from './queries/page_info.query.graphql'; + +const buildErrors = (errors = []) => ({ + errors, + __typename: 'LocalEnvironmentErrors', +}); const mapNestedEnvironment = (env) => ({ ...convertObjectPropsToCamelCase(env, { deep: true }), @@ -12,17 +27,34 @@ const mapEnvironment = (env) => ({ export const resolvers = (endpoint) => ({ Query: { - environmentApp() { - return axios.get(endpoint, { params: { nested: true } }).then((res) => ({ - availableCount: res.data.available_count, - environments: res.data.environments.map(mapNestedEnvironment), - reviewApp: { - ...convertObjectPropsToCamelCase(res.data.review_app), - __typename: 'ReviewApp', - }, - stoppedCount: res.data.stopped_count, - __typename: 'LocalEnvironmentApp', - })); + environmentApp(_context, { page, scope }, { cache }) { + return axios.get(endpoint, { params: { nested: true, page, scope } }).then((res) => { + const headers = normalizeHeaders(res.headers); + const interval = headers['POLL-INTERVAL']; + const pageInfo = { ...parseIntPagination(headers), __typename: 'LocalPageInfo' }; + + if (interval) { + cache.writeQuery({ query: pollIntervalQuery, data: { interval: parseFloat(interval) } }); + } else { + cache.writeQuery({ query: pollIntervalQuery, data: { interval: undefined } }); + } + + cache.writeQuery({ + query: pageInfoQuery, + data: { pageInfo }, + }); + + return { + availableCount: res.data.available_count, + environments: res.data.environments.map(mapNestedEnvironment), + reviewApp: { + ...convertObjectPropsToCamelCase(res.data.review_app), + __typename: 'ReviewApp', + }, + stoppedCount: res.data.stopped_count, + __typename: 'LocalEnvironmentApp', + }; + }); }, folder(_, { environment: { folderPath } }) { return axios.get(folderPath, { params: { per_page: 3 } }).then((res) => ({ @@ -32,19 +64,72 @@ export const resolvers = (endpoint) => ({ __typename: 'LocalEnvironmentFolder', })); }, + isLastDeployment(_, { environment }) { + // eslint-disable-next-line @gitlab/require-i18n-strings + return environment?.lastDeployment?.['last?']; + }, }, - Mutations: { - stopEnvironment(_, { environment: { stopPath } }) { - return axios.post(stopPath); + Mutation: { + stopEnvironment(_, { environment }) { + return axios + .post(environment.stopPath) + .then(() => buildErrors()) + .catch(() => { + return buildErrors([ + s__('Environments|An error occurred while stopping the environment, please try again'), + ]); + }); }, deleteEnvironment(_, { environment: { deletePath } }) { - return axios.delete(deletePath); + return axios + .delete(deletePath) + .then(() => buildErrors()) + .catch(() => + buildErrors([ + s__( + 'Environments|An error occurred while deleting the environment. Check if the environment stopped; if not, stop it and try again.', + ), + ]), + ); + }, + rollbackEnvironment(_, { environment, isLastDeployment }) { + return axios + .post(environment?.retryUrl) + .then(() => buildErrors()) + .catch(() => { + buildErrors([ + isLastDeployment + ? s__( + 'Environments|An error occurred while re-deploying the environment, please try again', + ) + : s__( + 'Environments|An error occurred while rolling back the environment, please try again', + ), + ]); + }); + }, + setEnvironmentToDelete(_, { environment }, { client }) { + client.writeQuery({ + query: environmentToDeleteQuery, + data: { environmentToDelete: environment }, + }); }, - rollbackEnvironment(_, { environment: { retryUrl } }) { - return axios.post(retryUrl); + setEnvironmentToRollback(_, { environment }, { client }) { + client.writeQuery({ + query: environmentToRollbackQuery, + data: { environmentToRollback: environment }, + }); }, cancelAutoStop(_, { environment: { autoStopPath } }) { - return axios.post(autoStopPath); + return axios + .post(autoStopPath) + .then(() => buildErrors()) + .catch((err) => + buildErrors([ + err?.response?.data?.message || + s__('Environments|An error occurred while canceling the auto stop, please try again'), + ]), + ); }, }, }); diff --git a/app/assets/javascripts/environments/graphql/typedefs.graphql b/app/assets/javascripts/environments/graphql/typedefs.graphql index 49ea719449e..4a3abb0e89f 100644 --- a/app/assets/javascripts/environments/graphql/typedefs.graphql +++ b/app/assets/javascripts/environments/graphql/typedefs.graphql @@ -9,12 +9,29 @@ type LocalEnvironment { autoStopPath: String } +input LocalEnvironmentInput { + id: Int! + globalId: ID! + name: String! + folderPath: String + stopPath: String + deletePath: String + retryUrl: String + autoStopPath: String +} + type NestedLocalEnvironment { name: String! size: Int! latest: LocalEnvironment! } +input NestedLocalEnvironmentInput { + name: String! + size: Int! + latest: LocalEnvironmentInput! +} + type LocalEnvironmentFolder { environments: [LocalEnvironment!]! availableCount: Int! @@ -33,3 +50,32 @@ type LocalEnvironmentApp { environments: [NestedLocalEnvironment!]! reviewApp: ReviewApp! } + +type LocalErrors { + errors: [String!]! +} + +type LocalPageInfo { + total: Int! + perPage: Int! + nextPage: Int! + previousPage: Int! +} + +extend type Query { + environmentApp(page: Int, scope: String): LocalEnvironmentApp + folder(environment: NestedLocalEnvironmentInput): LocalEnvironmentFolder + environmentToDelete: LocalEnvironment + pageInfo: LocalPageInfo + environmentToRollback: LocalEnvironment + isLastDeployment: Boolean +} + +extend type Mutation { + stopEnvironment(environment: LocalEnvironmentInput): LocalErrors + deleteEnvironment(environment: LocalEnvironmentInput): LocalErrors + rollbackEnvironment(environment: LocalEnvironmentInput): LocalErrors + cancelAutoStop(environment: LocalEnvironmentInput): LocalErrors + setEnvironmentToDelete(environment: LocalEnvironmentInput): LocalErrors + setEnvironmentToRollback(environment: LocalEnvironmentInput): LocalErrors +} |