summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/environments
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/environments')
-rw-r--r--app/assets/javascripts/environments/components/confirm_rollback_modal.vue41
-rw-r--r--app/assets/javascripts/environments/components/delete_environment_modal.vue31
-rw-r--r--app/assets/javascripts/environments/components/enable_review_app_modal.vue12
-rw-r--r--app/assets/javascripts/environments/components/environment_delete.vue23
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue2
-rw-r--r--app/assets/javascripts/environments/components/environment_rollback.vue34
-rw-r--r--app/assets/javascripts/environments/components/new_environment_folder.vue27
-rw-r--r--app/assets/javascripts/environments/components/new_environments_app.vue180
-rw-r--r--app/assets/javascripts/environments/graphql/client.js16
-rw-r--r--app/assets/javascripts/environments/graphql/mutations/set_environment_to_delete.mutation.graphql3
-rw-r--r--app/assets/javascripts/environments/graphql/mutations/set_environment_to_rollback.mutation.graphql3
-rw-r--r--app/assets/javascripts/environments/graphql/queries/environmentApp.query.graphql8
-rw-r--r--app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql9
-rw-r--r--app/assets/javascripts/environments/graphql/queries/environment_to_delete.query.graphql7
-rw-r--r--app/assets/javascripts/environments/graphql/queries/environment_to_rollback.query.graphql7
-rw-r--r--app/assets/javascripts/environments/graphql/queries/page_info.query.graphql8
-rw-r--r--app/assets/javascripts/environments/graphql/queries/poll_interval.query.graphql3
-rw-r--r--app/assets/javascripts/environments/graphql/resolvers.js123
-rw-r--r--app/assets/javascripts/environments/graphql/typedefs.graphql46
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
+}