diff options
Diffstat (limited to 'app/assets/javascripts/environments')
19 files changed, 478 insertions, 55 deletions
diff --git a/app/assets/javascripts/environments/components/confirm_rollback_modal.vue b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue new file mode 100644 index 00000000000..70b5c6b0094 --- /dev/null +++ b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue @@ -0,0 +1,108 @@ +<script> +/** + * Render modal to confirm rollback/redeploy. + */ + +import _ from 'underscore'; +import { GlModal } from '@gitlab/ui'; +import { s__, sprintf } from '~/locale'; + +import eventHub from '../event_hub'; + +export default { + name: 'ConfirmRollbackModal', + + components: { + GlModal, + }, + + props: { + environment: { + type: Object, + required: true, + }, + }, + + computed: { + modalTitle() { + const title = this.environment.isLastDeployment + ? s__('Environments|Re-deploy environment %{name}?') + : s__('Environments|Rollback environment %{name}?'); + + return sprintf(title, { + name: _.escape(this.environment.name), + }); + }, + + commitShortSha() { + const { last_deployment } = this.environment; + return this.commitData(last_deployment, 'short_id'); + }, + + commitUrl() { + const { last_deployment } = this.environment; + return this.commitData(last_deployment, 'commit_path'); + }, + + commitTitle() { + const { last_deployment } = this.environment; + return this.commitData(last_deployment, 'title'); + }, + + modalText() { + const linkStart = `<a class="commit-sha mr-0" href="${_.escape(this.commitUrl)}">`; + const commitId = _.escape(this.commitShortSha); + const linkEnd = '</a>'; + const name = _.escape(this.name); + const body = this.environment.isLastDeployment + ? s__( + 'Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?', + ) + : s__( + 'Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?', + ); + return sprintf( + body, + { + commitId, + linkStart, + linkEnd, + name, + }, + false, + ); + }, + + modalActionText() { + return this.environment.isLastDeployment + ? s__('Environments|Re-deploy') + : s__('Environments|Rollback'); + }, + }, + + methods: { + onOk() { + eventHub.$emit('rollbackEnvironment', this.environment); + }, + + commitData(lastDeployment, key) { + if (lastDeployment && lastDeployment.commit) { + return lastDeployment.commit[key]; + } + + return ''; + }, + }, +}; +</script> +<template> + <gl-modal + :title="modalTitle" + modal-id="confirm-rollback-modal" + :ok-title="modalActionText" + ok-variant="danger" + @ok="onOk" + > + <p v-html="modalText"></p> + </gl-modal> +</template> diff --git a/app/assets/javascripts/environments/components/container.vue b/app/assets/javascripts/environments/components/container.vue index 6ece8b92a30..be80661223c 100644 --- a/app/assets/javascripts/environments/components/container.vue +++ b/app/assets/javascripts/environments/components/container.vue @@ -1,14 +1,16 @@ <script> import { GlLoadingIcon } from '@gitlab/ui'; -import tablePagination from '../../vue_shared/components/table_pagination.vue'; -import environmentTable from '../components/environments_table.vue'; +import TablePagination from '~/vue_shared/components/table_pagination.vue'; +import containerMixin from 'ee_else_ce/environments/mixins/container_mixin'; +import EnvironmentTable from '../components/environments_table.vue'; export default { components: { - environmentTable, - tablePagination, + EnvironmentTable, + TablePagination, GlLoadingIcon, }, + mixins: [containerMixin], props: { isLoading: { type: Boolean, @@ -47,7 +49,15 @@ export default { <slot name="emptyState"></slot> <div v-if="!isLoading && environments.length > 0" class="table-holder"> - <environment-table :environments="environments" :can-read-environment="canReadEnvironment" /> + <environment-table + :environments="environments" + :can-read-environment="canReadEnvironment" + :canary-deployment-feature-id="canaryDeploymentFeatureId" + :show-canary-deployment-callout="showCanaryDeploymentCallout" + :user-callouts-path="userCalloutsPath" + :lock-promotion-svg-path="lockPromotionSvgPath" + :help-canary-deployments-path="helpCanaryDeploymentsPath" + /> <table-pagination v-if="pagination && pagination.totalPages > 1" diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index 503c1b38f71..f0e80cba753 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -3,8 +3,8 @@ import Timeago from 'timeago.js'; import _ from 'underscore'; import { GlTooltipDirective } from '@gitlab/ui'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; -import { humanize } from '~/lib/utils/text_utility'; import Icon from '~/vue_shared/components/icon.vue'; +import environmentItemMixin from 'ee_else_ce/environments/mixins/environment_item_mixin'; import ActionsComponent from './environment_actions.vue'; import ExternalUrlComponent from './environment_external_url.vue'; import StopComponent from './environment_stop.vue'; @@ -35,10 +35,10 @@ export default { TerminalButtonComponent, MonitoringButtonComponent, }, - directives: { GlTooltip: GlTooltipDirective, }, + mixins: [environmentItemMixin], props: { model: { @@ -156,7 +156,7 @@ export default { const combinedActions = (manualActions || []).concat(scheduledActions || []); return combinedActions.map(action => ({ ...action, - name: humanize(action.name), + name: action.name, })); }, @@ -459,19 +459,37 @@ export default { class="gl-responsive-table-row" role="row" > - <div - v-gl-tooltip - :title="model.name" - class="table-section section-wrap section-15 text-truncate" - role="gridcell" - > + <div class="table-section section-wrap section-15 text-truncate" role="gridcell"> <div v-if="!model.isFolder" class="table-mobile-header" role="rowheader"> {{ s__('Environments|Environment') }} </div> - <span v-if="!model.isFolder" class="environment-name table-mobile-content"> - <a class="qa-environment-link" :href="environmentPath"> {{ model.name }} </a> + + <span v-if="shouldRenderDeployBoard" class="deploy-board-icon" @click="toggleDeployBoard"> + <icon :name="deployIconName" /> + </span> + + <span + v-if="!model.isFolder" + v-gl-tooltip + :title="model.name" + class="environment-name table-mobile-content" + > + <a class="qa-environment-link" :href="environmentPath"> + <span v-if="model.size === 1">{{ model.name }}</span> + <span v-else>{{ model.name_without_type }}</span> + </a> + <span v-if="isProtected" class="badge badge-success"> + {{ s__('Environments|protected') }} + </span> </span> - <span v-else class="folder-name" role="button" @click="onClickFolder"> + <span + v-else + v-gl-tooltip + :title="model.folderName" + class="folder-name" + role="button" + @click="onClickFolder" + > <icon :name="folderIconName" class="folder-icon" /> <icon name="folder" class="folder-icon" /> @@ -486,22 +504,28 @@ export default { class="table-section section-10 deployment-column d-none d-sm-none d-md-block" role="gridcell" > - <span v-if="shouldRenderDeploymentID"> {{ deploymentInternalId }} </span> + <span v-if="shouldRenderDeploymentID" class="text-break-word"> + {{ deploymentInternalId }} + </span> - <span v-if="!model.isFolder && deploymentHasUser"> + <span v-if="!model.isFolder && deploymentHasUser" class="text-break-word"> by <user-avatar-link :link-href="deploymentUser.web_url" :img-src="deploymentUser.avatar_url" :img-alt="userImageAltDescription" :tooltip-text="deploymentUser.username" - class="js-deploy-user-container" + class="js-deploy-user-container float-none" /> </span> </div> <div class="table-section section-15 d-none d-sm-none d-md-block" role="gridcell"> - <a v-if="shouldRenderBuildName" :href="buildPath" class="build-link flex-truncate-parent"> + <a + v-if="shouldRenderBuildName" + :href="buildPath" + class="build-link cgray flex-truncate-parent" + > <span class="flex-truncate-child">{{ buildName }}</span> </a> </div> @@ -556,6 +580,7 @@ export default { <rollback-component v-if="canRetry" + :environment="model" :is-last-deployment="isLastDeployment" :retry-url="retryUrl" /> diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue index 50c86af057c..bafbc00597e 100644 --- a/app/assets/javascripts/environments/components/environment_rollback.vue +++ b/app/assets/javascripts/environments/components/environment_rollback.vue @@ -5,29 +5,38 @@ * * Makes a post request when the button is clicked. */ +import { GlTooltipDirective, GlLoadingIcon, GlModalDirective, GlButton } from '@gitlab/ui'; import { s__ } from '~/locale'; -import { GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui'; import Icon from '~/vue_shared/components/icon.vue'; +import ConfirmRollbackModal from './confirm_rollback_modal.vue'; import eventHub from '../event_hub'; export default { components: { Icon, GlLoadingIcon, + GlButton, + ConfirmRollbackModal, }, directives: { GlTooltip: GlTooltipDirective, + GlModal: GlModalDirective, }, props: { - retryUrl: { - type: String, - default: '', - }, - isLastDeployment: { type: Boolean, default: true, }, + + environment: { + type: Object, + required: true, + }, + + retryUrl: { + type: String, + required: true, + }, }, data() { return { @@ -45,23 +54,30 @@ export default { methods: { onClick() { - this.isLoading = true; - - eventHub.$emit('postAction', { endpoint: this.retryUrl }); + eventHub.$emit('requestRollbackEnvironment', { + ...this.environment, + retryUrl: this.retryUrl, + isLastDeployment: this.isLastDeployment, + }); + eventHub.$on('rollbackEnvironment', environment => { + if (environment.id === this.environment.id) { + this.isLoading = true; + } + }); }, }, }; </script> <template> - <button + <gl-button v-gl-tooltip + v-gl-modal.confirm-rollback-modal :disabled="isLoading" :title="title" - type="button" - class="btn d-none d-sm-none d-md-block" + class="d-none d-md-block text-secondary" @click="onClick" > <icon v-if="isLastDeployment" name="repeat" /> <icon v-else name="redo" /> <gl-loading-icon v-if="isLoading" /> - </button> + </gl-button> </template> diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.vue b/app/assets/javascripts/environments/components/environment_terminal_button.vue index 6d74d136a94..13195d32cc4 100644 --- a/app/assets/javascripts/environments/components/environment_terminal_button.vue +++ b/app/assets/javascripts/environments/components/environment_terminal_button.vue @@ -39,7 +39,7 @@ export default { :aria-label="title" :href="terminalPath" :class="{ disabled: disabled }" - class="btn terminal-button d-none d-sm-none d-md-block" + class="btn terminal-button d-none d-sm-none d-md-block text-secondary" > <icon name="terminal" /> </a> diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue index aa2417d3194..ec78240217b 100644 --- a/app/assets/javascripts/environments/components/environments_app.vue +++ b/app/assets/javascripts/environments/components/environments_app.vue @@ -1,4 +1,5 @@ <script> +import envrionmentsAppMixin from 'ee_else_ce/environments/mixins/environments_app_mixin'; import Flash from '../../flash'; import { s__ } from '../../locale'; import emptyState from './empty_state.vue'; @@ -6,14 +7,16 @@ import eventHub from '../event_hub'; import environmentsMixin from '../mixins/environments_mixin'; import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; import StopEnvironmentModal from './stop_environment_modal.vue'; +import ConfirmRollbackModal from './confirm_rollback_modal.vue'; export default { components: { emptyState, StopEnvironmentModal, + ConfirmRollbackModal, }, - mixins: [CIPaginationMixin, environmentsMixin], + mixins: [CIPaginationMixin, environmentsMixin, envrionmentsAppMixin], props: { endpoint: { @@ -87,14 +90,15 @@ export default { <template> <div :class="cssContainerClass"> <stop-environment-modal :environment="environmentInStopModal" /> + <confirm-rollback-modal :environment="environmentInRollbackModal" /> <div class="top-area"> <tabs :tabs="tabs" scope="environments" @onChangeTab="onChangeTab" /> <div v-if="canCreateEnvironment && !isLoading" class="nav-controls"> - <a :href="newEnvironmentPath" class="btn btn-success">{{ - s__('Environments|New environment') - }}</a> + <a :href="newEnvironmentPath" class="btn btn-success"> + {{ s__('Environments|New environment') }} + </a> </div> </div> @@ -103,6 +107,11 @@ export default { :environments="state.environments" :pagination="state.paginationInformation" :can-read-environment="canReadEnvironment" + :canary-deployment-feature-id="canaryDeploymentFeatureId" + :show-canary-deployment-callout="showCanaryDeploymentCallout" + :user-callouts-path="userCalloutsPath" + :lock-promotion-svg-path="lockPromotionSvgPath" + :help-canary-deployments-path="helpCanaryDeploymentsPath" @onChangePage="onChangePage" > <empty-state diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue index e2c304de00a..55613d815ce 100644 --- a/app/assets/javascripts/environments/components/environments_table.vue +++ b/app/assets/javascripts/environments/components/environments_table.vue @@ -3,27 +3,40 @@ * Render environments table. */ import { GlLoadingIcon } from '@gitlab/ui'; -import environmentItem from './environment_item.vue'; +import _ from 'underscore'; +import environmentTableMixin from 'ee_else_ce/environments/mixins/environments_table_mixin'; +import EnvironmentItem from './environment_item.vue'; export default { components: { - environmentItem, + EnvironmentItem, GlLoadingIcon, + DeployBoard: () => import('ee_component/environments/components/deploy_board_component.vue'), + CanaryDeploymentCallout: () => + import('ee_component/environments/components/canary_deployment_callout.vue'), }, - + mixins: [environmentTableMixin], props: { environments: { type: Array, required: true, default: () => [], }, - canReadEnvironment: { type: Boolean, required: false, default: false, }, }, + computed: { + sortedEnvironments() { + return this.sortEnvironments(this.environments).map(env => + this.shouldRenderFolderContent(env) + ? { ...env, children: this.sortEnvironments(env.children) } + : env, + ); + }, + }, methods: { folderUrl(model) { return `${window.location.pathname}/folders/${model.folderName}`; @@ -31,6 +44,30 @@ export default { shouldRenderFolderContent(env) { return env.isFolder && env.isOpen && env.children && env.children.length > 0; }, + sortEnvironments(environments) { + /* + * The sorting algorithm should sort in the following priorities: + * + * 1. folders first, + * 2. last updated descending, + * 3. by name ascending, + * + * the sorting algorithm must: + * + * 1. Sort by name ascending, + * 2. Reverse (sort by name descending), + * 3. Sort by last deployment ascending, + * 4. Reverse (last deployment descending, name ascending), + * 5. Put folders first. + */ + return _.chain(environments) + .sortBy(env => (env.isFolder ? env.folderName : env.name)) + .reverse() + .sortBy(env => (env.last_deployment ? env.last_deployment.created_at : '0000')) + .reverse() + .sortBy(env => (env.isFolder ? -1 : 1)) + .value(); + }, }, }; </script> @@ -53,7 +90,7 @@ export default { {{ s__('Environments|Updated') }} </div> </div> - <template v-for="(model, i) in environments" :model="model"> + <template v-for="(model, i) in sortedEnvironments" :model="model"> <div is="environment-item" :key="`environment-item-${i}`" @@ -61,6 +98,21 @@ export default { :can-read-environment="canReadEnvironment" /> + <div + v-if="shouldRenderDeployBoard(model)" + :key="`deploy-board-row-${i}`" + class="js-deploy-board-row" + > + <div class="deploy-board-container"> + <deploy-board + :deploy-board-data="model.deployBoardData" + :is-loading="model.isLoadingDeployBoard" + :is-empty="model.isEmptyDeployBoard" + :logs-path="model.logs_path" + /> + </div> + </div> + <template v-if="shouldRenderFolderContent(model)"> <div v-if="model.isLoadingFolderContent" :key="`loading-item-${i}`"> <gl-loading-icon :size="2" class="prepend-top-16" /> @@ -77,13 +129,24 @@ export default { <div :key="`sub-div-${i}`"> <div class="text-center prepend-top-10"> - <a :href="folderUrl(model)" class="btn btn-default">{{ - s__('Environments|Show all') - }}</a> + <a :href="folderUrl(model)" class="btn btn-default"> + {{ s__('Environments|Show all') }} + </a> </div> </div> </template> </template> + + <template v-if="shouldShowCanaryCallout(model)"> + <canary-deployment-callout + :key="`canary-promo-${i}`" + :canary-deployment-feature-id="canaryDeploymentFeatureId" + :user-callouts-path="userCalloutsPath" + :lock-promotion-svg-path="lockPromotionSvgPath" + :help-canary-deployments-path="helpCanaryDeploymentsPath" + :data-js-canary-promo-key="i" + /> + </template> </template> </div> </template> diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js index 56e7f69cad6..c1bfe8d05fe 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js +++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import canaryCalloutMixin from 'ee_else_ce/environments/mixins/canary_callout_mixin'; import environmentsFolderApp from './environments_folder_view.vue'; import { parseBoolean } from '../../lib/utils/common_utils'; import Translate from '../../vue_shared/translate'; @@ -11,6 +12,7 @@ export default () => components: { environmentsFolderApp, }, + mixins: [canaryCalloutMixin], data() { const environmentsData = document.querySelector(this.$options.el).dataset; @@ -28,6 +30,7 @@ export default () => folderName: this.folderName, cssContainerClass: this.cssContainerClass, canReadEnvironment: this.canReadEnvironment, + ...this.canaryCalloutProps, }, }); }, diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue index 80f0e00400b..6fd0561f682 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.vue +++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue @@ -1,4 +1,5 @@ <script> +import folderMixin from 'ee_else_ce/environments/mixins/environments_folder_view_mixin'; import environmentsMixin from '../mixins/environments_mixin'; import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; import StopEnvironmentModal from '../components/stop_environment_modal.vue'; @@ -8,7 +9,7 @@ export default { StopEnvironmentModal, }, - mixins: [environmentsMixin, CIPaginationMixin], + mixins: [environmentsMixin, CIPaginationMixin, folderMixin], props: { endpoint: { @@ -41,7 +42,8 @@ export default { <div v-if="!isLoading" class="top-area"> <h4 class="js-folder-name environments-folder-name"> - {{ s__('Environments|Environments') }} / <b>{{ folderName }}</b> + {{ s__('Environments|Environments') }} / + <b>{{ folderName }}</b> </h4> <tabs :tabs="tabs" scope="environments" @onChangeTab="onChangeTab" /> @@ -52,6 +54,11 @@ export default { :environments="state.environments" :pagination="state.paginationInformation" :can-read-environment="canReadEnvironment" + :canary-deployment-feature-id="canaryDeploymentFeatureId" + :show-canary-deployment-callout="showCanaryDeploymentCallout" + :user-callouts-path="userCalloutsPath" + :lock-promotion-svg-path="lockPromotionSvgPath" + :help-canary-deployments-path="helpCanaryDeploymentsPath" @onChangePage="onChangePage" /> </div> diff --git a/app/assets/javascripts/environments/index.js b/app/assets/javascripts/environments/index.js index 6af66d0f86e..b53d42f202b 100644 --- a/app/assets/javascripts/environments/index.js +++ b/app/assets/javascripts/environments/index.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import canaryCalloutMixin from 'ee_else_ce/environments/mixins/canary_callout_mixin'; import environmentsComponent from './components/environments_app.vue'; import { parseBoolean } from '../lib/utils/common_utils'; import Translate from '../vue_shared/translate'; @@ -11,6 +12,7 @@ export default () => components: { environmentsComponent, }, + mixins: [canaryCalloutMixin], data() { const environmentsData = document.querySelector(this.$options.el).dataset; @@ -32,6 +34,7 @@ export default () => cssContainerClass: this.cssContainerClass, canCreateEnvironment: this.canCreateEnvironment, canReadEnvironment: this.canReadEnvironment, + ...this.canaryCalloutProps, }, }); }, diff --git a/app/assets/javascripts/environments/mixins/canary_callout_mixin.js b/app/assets/javascripts/environments/mixins/canary_callout_mixin.js new file mode 100644 index 00000000000..f6d3d67b777 --- /dev/null +++ b/app/assets/javascripts/environments/mixins/canary_callout_mixin.js @@ -0,0 +1,5 @@ +export default { + computed: { + canaryCalloutProps() {}, + }, +}; diff --git a/app/assets/javascripts/environments/mixins/container_mixin.js b/app/assets/javascripts/environments/mixins/container_mixin.js new file mode 100644 index 00000000000..f2907c120f8 --- /dev/null +++ b/app/assets/javascripts/environments/mixins/container_mixin.js @@ -0,0 +1,29 @@ +export default { + props: { + canaryDeploymentFeatureId: { + type: String, + required: false, + default: null, + }, + showCanaryDeploymentCallout: { + type: Boolean, + required: false, + default: false, + }, + userCalloutsPath: { + type: String, + required: false, + default: null, + }, + lockPromotionSvgPath: { + type: String, + required: false, + default: null, + }, + helpCanaryDeploymentsPath: { + type: String, + required: false, + default: null, + }, + }, +}; diff --git a/app/assets/javascripts/environments/mixins/environment_item_mixin.js b/app/assets/javascripts/environments/mixins/environment_item_mixin.js new file mode 100644 index 00000000000..2dfed36ec99 --- /dev/null +++ b/app/assets/javascripts/environments/mixins/environment_item_mixin.js @@ -0,0 +1,13 @@ +export default { + computed: { + deployIconName() { + return ''; + }, + shouldRenderDeployBoard() { + return false; + }, + }, + methods: { + toggleDeployBoard() {}, + }, +}; diff --git a/app/assets/javascripts/environments/mixins/environments_app_mixin.js b/app/assets/javascripts/environments/mixins/environments_app_mixin.js new file mode 100644 index 00000000000..fc805b9235a --- /dev/null +++ b/app/assets/javascripts/environments/mixins/environments_app_mixin.js @@ -0,0 +1,32 @@ +export default { + props: { + canaryDeploymentFeatureId: { + type: String, + required: false, + default: '', + }, + showCanaryDeploymentCallout: { + type: Boolean, + required: false, + default: false, + }, + userCalloutsPath: { + type: String, + required: false, + default: '', + }, + lockPromotionSvgPath: { + type: String, + required: false, + default: '', + }, + helpCanaryDeploymentsPath: { + type: String, + required: false, + default: '', + }, + }, + metods: { + toggleDeployBoard() {}, + }, +}; diff --git a/app/assets/javascripts/environments/mixins/environments_folder_view_mixin.js b/app/assets/javascripts/environments/mixins/environments_folder_view_mixin.js new file mode 100644 index 00000000000..e793a7cadf2 --- /dev/null +++ b/app/assets/javascripts/environments/mixins/environments_folder_view_mixin.js @@ -0,0 +1,29 @@ +export default { + props: { + canaryDeploymentFeatureId: { + type: String, + required: false, + default: '', + }, + showCanaryDeploymentCallout: { + type: Boolean, + required: false, + default: false, + }, + userCalloutsPath: { + type: String, + required: false, + default: '', + }, + lockPromotionSvgPath: { + type: String, + required: false, + default: '', + }, + helpCanaryDeploymentsPath: { + type: String, + required: false, + default: '', + }, + }, +}; diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js index e81a1525df0..a5812b173dc 100644 --- a/app/assets/javascripts/environments/mixins/environments_mixin.js +++ b/app/assets/javascripts/environments/mixins/environments_mixin.js @@ -3,13 +3,13 @@ */ import _ from 'underscore'; import Visibility from 'visibilityjs'; +import EnvironmentsStore from 'ee_else_ce/environments/stores/environments_store'; import Poll from '../../lib/utils/poll'; import { getParameterByName } from '../../lib/utils/common_utils'; import { s__ } from '../../locale'; import Flash from '../../flash'; import eventHub from '../event_hub'; -import EnvironmentsStore from '../stores/environments_store'; import EnvironmentsService from '../services/environments_service'; import tablePagination from '../../vue_shared/components/table_pagination.vue'; import environmentTable from '../components/environments_table.vue'; @@ -36,6 +36,7 @@ export default { page: getParameterByName('page') || '1', requestData: {}, environmentInStopModal: {}, + environmentInRollbackModal: {}, }; }, @@ -43,7 +44,11 @@ export default { saveData(resp) { this.isLoading = false; - if (_.isEqual(resp.config.params, this.requestData)) { + // Prevent the absence of the nested flag from causing mismatches + const response = this.filterNilValues(resp.config.params); + const request = this.filterNilValues(this.requestData); + + if (_.isEqual(response, request)) { this.store.storeAvailableCount(resp.data.available_count); this.store.storeStoppedCount(resp.data.stopped_count); this.store.storeEnvironments(resp.data.environments); @@ -51,6 +56,10 @@ export default { } }, + filterNilValues(obj) { + return _.omit(obj, value => _.isUndefined(value) || _.isNull(value)); + }, + /** * Handles URL and query parameter changes. * When the user uses the pagination or the tabs, @@ -64,10 +73,9 @@ export default { // fetch new data return this.service .fetchEnvironments(this.requestData) - .then(response => this.successCallback(response)) - .then(() => { - // restart polling - this.poll.restart({ data: this.requestData }); + .then(response => { + this.successCallback(response); + this.poll.enable({ data: this.requestData, response }); }) .catch(() => { this.errorCallback(); @@ -109,6 +117,10 @@ export default { this.environmentInStopModal = environment; }, + updateRollbackModal(environment) { + this.environmentInRollbackModal = environment; + }, + stopEnvironment(environment) { const endpoint = environment.stop_path; const errorMessage = s__( @@ -116,6 +128,16 @@ export default { ); this.postAction({ endpoint, errorMessage }); }, + + rollbackEnvironment(environment) { + const { retryUrl, isLastDeployment } = environment; + const errorMessage = 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', + ); + this.postAction({ endpoint: retryUrl, errorMessage }); + }, }, computed: { @@ -174,11 +196,17 @@ export default { eventHub.$on('postAction', this.postAction); eventHub.$on('requestStopEnvironment', this.updateStopModal); eventHub.$on('stopEnvironment', this.stopEnvironment); + + eventHub.$on('requestRollbackEnvironment', this.updateRollbackModal); + eventHub.$on('rollbackEnvironment', this.rollbackEnvironment); }, beforeDestroy() { eventHub.$off('postAction', this.postAction); eventHub.$off('requestStopEnvironment', this.updateStopModal); eventHub.$off('stopEnvironment', this.stopEnvironment); + + eventHub.$off('requestRollbackEnvironment', this.updateRollbackModal); + eventHub.$off('rollbackEnvironment', this.rollbackEnvironment); }, }; diff --git a/app/assets/javascripts/environments/mixins/environments_table_mixin.js b/app/assets/javascripts/environments/mixins/environments_table_mixin.js new file mode 100644 index 00000000000..208f1a7373d --- /dev/null +++ b/app/assets/javascripts/environments/mixins/environments_table_mixin.js @@ -0,0 +1,10 @@ +export default { + methods: { + shouldShowCanaryCallout() { + return false; + }, + shouldRenderDeployBoard() { + return false; + }, + }, +}; diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js index ac9a31c202c..5fb420e9da5 100644 --- a/app/assets/javascripts/environments/stores/environments_store.js +++ b/app/assets/javascripts/environments/stores/environments_store.js @@ -1,4 +1,6 @@ import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; +import { setDeployBoard } from 'ee_else_ce/environments/stores/helpers'; + /** * Environments Store. * @@ -31,6 +33,14 @@ export default class EnvironmentsStore { * If the `size` is bigger than 1, it means it should be rendered as a folder. * In those cases we add `isFolder` key in order to render it properly. * + * Top level environments - when the size is 1 - with `rollout_status` + * can render a deploy board. We add `isDeployBoardVisible` and `deployBoardData` + * keys to those environments. + * The first key will let's us know if we should or not render the deploy board. + * It will be toggled when the user clicks to seee the deploy board. + * + * The second key will allow us to update the environment with the received deploy board data. + * * @param {Array} environments * @returns {Array} */ @@ -63,6 +73,7 @@ export default class EnvironmentsStore { filtered = Object.assign(filtered, env); } + filtered = setDeployBoard(oldEnvironmentState, filtered); return filtered; }); @@ -71,6 +82,20 @@ export default class EnvironmentsStore { return filteredEnvironments; } + /** + * Stores the pagination information needed to render the pagination for the + * table. + * + * Normalizes the headers to uppercase since they can be provided either + * in uppercase or lowercase. + * + * Parses to an integer the normalized ones needed for the pagination component. + * + * Stores the normalized and parsed information. + * + * @param {Object} pagination = {} + * @return {Object} + */ setPagination(pagination = {}) { const normalizedHeaders = normalizeHeaders(pagination); const paginationInformation = parseIntPagination(normalizedHeaders); diff --git a/app/assets/javascripts/environments/stores/helpers.js b/app/assets/javascripts/environments/stores/helpers.js new file mode 100644 index 00000000000..8eba6c00601 --- /dev/null +++ b/app/assets/javascripts/environments/stores/helpers.js @@ -0,0 +1,8 @@ +/** + * Deploy boards are EE only. + * + * @param {Object} environment + * @returns {Object} + */ +// eslint-disable-next-line import/prefer-default-export +export const setDeployBoard = (oldEnvironmentState, environment) => environment; |