summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/environments
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-01-05 18:10:25 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-01-05 18:10:25 +0000
commitf368b4968e55b32dcedfaefe7c31f7a9463454cf (patch)
treeb3e4652bd0131adf46f4b7e07346a0dbfa32da05 /app/assets/javascripts/environments
parent2c2b5aeac04350b0d3e13d4b52add0b520bf2ebb (diff)
downloadgitlab-ce-f368b4968e55b32dcedfaefe7c31f7a9463454cf.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/environments')
-rw-r--r--app/assets/javascripts/environments/components/canary_deployment_callout.vue68
-rw-r--r--app/assets/javascripts/environments/components/canary_ingress.vue109
-rw-r--r--app/assets/javascripts/environments/components/canary_update_modal.vue133
-rw-r--r--app/assets/javascripts/environments/components/deploy_board.vue216
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue10
-rw-r--r--app/assets/javascripts/environments/constants.js40
-rw-r--r--app/assets/javascripts/environments/graphql/mutations/update_canary_ingress.mutation.graphql5
-rw-r--r--app/assets/javascripts/environments/mixins/environments_mixin.js2
-rw-r--r--app/assets/javascripts/environments/stores/environments_store.js27
-rw-r--r--app/assets/javascripts/environments/stores/helpers.js21
10 files changed, 620 insertions, 11 deletions
diff --git a/app/assets/javascripts/environments/components/canary_deployment_callout.vue b/app/assets/javascripts/environments/components/canary_deployment_callout.vue
new file mode 100644
index 00000000000..a5c0d78524b
--- /dev/null
+++ b/app/assets/javascripts/environments/components/canary_deployment_callout.vue
@@ -0,0 +1,68 @@
+<script>
+import { GlButton, GlLink, GlIcon } from '@gitlab/ui';
+import PersistentUserCallout from '~/persistent_user_callout';
+
+export default {
+ components: {
+ GlButton,
+ GlLink,
+ GlIcon,
+ },
+ props: {
+ canaryDeploymentFeatureId: {
+ type: String,
+ required: true,
+ },
+ userCalloutsPath: {
+ type: String,
+ required: true,
+ },
+ lockPromotionSvgPath: {
+ type: String,
+ required: true,
+ },
+ helpCanaryDeploymentsPath: {
+ type: String,
+ required: true,
+ },
+ },
+ mounted() {
+ const callout = this.$refs['canary-deployment-callout'];
+ PersistentUserCallout.factory(callout);
+ },
+};
+</script>
+
+<template>
+ <div
+ ref="canary-deployment-callout"
+ class="p-3 canary-deployment-callout"
+ :data-dismiss-endpoint="userCalloutsPath"
+ :data-feature-id="canaryDeploymentFeatureId"
+ >
+ <img class="canary-deployment-callout-lock pr-3" :src="lockPromotionSvgPath" />
+
+ <div class="pl-3">
+ <p class="font-weight-bold mb-1">
+ {{ __('Upgrade plan to unlock Canary Deployments feature') }}
+ </p>
+
+ <p class="canary-deployment-callout-message">
+ {{
+ __(
+ 'Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application.',
+ )
+ }}
+ <gl-link :href="helpCanaryDeploymentsPath">{{ __('Read more') }}</gl-link>
+ </p>
+
+ <gl-button href="https://about.gitlab.com/sales/" category="secondary" variant="info">{{
+ __('Contact sales to upgrade')
+ }}</gl-button>
+ </div>
+
+ <div class="ml-auto pr-2 canary-deployment-callout-close js-close">
+ <gl-icon name="close" />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/environments/components/canary_ingress.vue b/app/assets/javascripts/environments/components/canary_ingress.vue
new file mode 100644
index 00000000000..f8cdbb96bc2
--- /dev/null
+++ b/app/assets/javascripts/environments/components/canary_ingress.vue
@@ -0,0 +1,109 @@
+<script>
+import { uniqueId } from 'lodash';
+import { GlDropdown, GlDropdownItem, GlModalDirective as GlModal } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import { CANARY_UPDATE_MODAL } from '../constants';
+
+export default {
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ },
+ directives: {
+ GlModal,
+ },
+ props: {
+ canaryIngress: {
+ required: true,
+ type: Object,
+ },
+ },
+ ingressOptions: Array(100 / 5 + 1)
+ .fill(0)
+ .map((_, i) => i * 5),
+
+ translations: {
+ stableLabel: s__('CanaryIngress|Stable'),
+ canaryLabel: s__('CanaryIngress|Canary'),
+ },
+
+ CANARY_UPDATE_MODAL,
+
+ css: {
+ label: [
+ 'gl-font-base',
+ 'gl-font-weight-normal',
+ 'gl-line-height-normal',
+ 'gl-inset-border-1-gray-200',
+ 'gl-py-3',
+ 'gl-px-4',
+ 'gl-mb-0',
+ ],
+ },
+ computed: {
+ stableWeightId() {
+ return uniqueId('stable-weight-');
+ },
+ canaryWeightId() {
+ return uniqueId('canary-weight-');
+ },
+ stableWeight() {
+ return (100 - this.canaryIngress.canary_weight).toString();
+ },
+ canaryWeight() {
+ return this.canaryIngress.canary_weight.toString();
+ },
+ },
+ methods: {
+ changeCanary(weight) {
+ this.$emit('change', weight);
+ },
+ changeStable(weight) {
+ this.$emit('change', 100 - weight);
+ },
+ },
+};
+</script>
+<template>
+ <section class="gl-display-flex gl-bg-white gl-m-3">
+ <div class="gl-display-flex gl-flex-direction-column">
+ <label :for="stableWeightId" :class="$options.css.label" class="gl-rounded-top-left-base">
+ {{ $options.translations.stableLabel }}
+ </label>
+ <gl-dropdown
+ :id="stableWeightId"
+ :text="stableWeight"
+ data-testid="stable-weight"
+ class="gl-w-full"
+ toggle-class="gl-rounded-top-left-none! gl-rounded-top-right-none! gl-rounded-bottom-right-none!"
+ >
+ <gl-dropdown-item
+ v-for="option in $options.ingressOptions"
+ :key="option"
+ v-gl-modal="$options.CANARY_UPDATE_MODAL"
+ @click="changeStable(option)"
+ >{{ option }}</gl-dropdown-item
+ >
+ </gl-dropdown>
+ </div>
+ <div class="gl-display-flex gl-display-flex gl-flex-direction-column">
+ <label :for="canaryWeightId" :class="$options.css.label" class="gl-rounded-top-right-base">{{
+ $options.translations.canaryLabel
+ }}</label>
+ <gl-dropdown
+ :id="canaryWeightId"
+ :text="canaryWeight"
+ data-testid="canary-weight"
+ toggle-class="gl-rounded-top-left-none! gl-rounded-top-right-none! gl-rounded-bottom-left-none! gl-border-l-none!"
+ >
+ <gl-dropdown-item
+ v-for="option in $options.ingressOptions"
+ :key="option"
+ v-gl-modal="$options.CANARY_UPDATE_MODAL"
+ @click="changeCanary(option)"
+ >{{ option }}</gl-dropdown-item
+ >
+ </gl-dropdown>
+ </div>
+ </section>
+</template>
diff --git a/app/assets/javascripts/environments/components/canary_update_modal.vue b/app/assets/javascripts/environments/components/canary_update_modal.vue
new file mode 100644
index 00000000000..fc63d6272c8
--- /dev/null
+++ b/app/assets/javascripts/environments/components/canary_update_modal.vue
@@ -0,0 +1,133 @@
+<script>
+import { GlAlert, GlModal, GlSprintf } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+import updateCanaryIngress from '../graphql/mutations/update_canary_ingress.mutation.graphql';
+import { CANARY_UPDATE_MODAL } from '../constants';
+
+export default {
+ components: {
+ GlAlert,
+ GlModal,
+ GlSprintf,
+ },
+ props: {
+ environment: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ weight: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ visible: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ translations: {
+ title: s__('CanaryIngress|Change the ratio of canary deployments?'),
+ ratioChange: s__(
+ 'CanaryIngress|You are changing the ratio of the canary rollout for %{environment} compared to the stable deployment to:',
+ ),
+ stableWeight: s__('CanaryIngress|%{boldStart}Stable:%{boldEnd} %{stable}'),
+ canaryWeight: s__('CanaryIngress|%{boldStart}Canary:%{boldEnd} %{canary}'),
+ deploymentWarning: s__(
+ 'CanaryIngress|Doing so will set a deployment change in progress. This temporarily blocks any further configuration until the deployment is finished.',
+ ),
+ },
+ modal: {
+ modalId: CANARY_UPDATE_MODAL,
+ actionPrimary: {
+ text: s__('CanaryIngress|Change ratio'),
+ attributes: [{ variant: 'info' }],
+ },
+ actionCancel: { text: __('Cancel') },
+ static: true,
+ },
+ data() {
+ return { error: '', dismissed: true };
+ },
+ computed: {
+ stableWeight() {
+ return (100 - this.weight).toString();
+ },
+ canaryWeight() {
+ return this.weight.toString();
+ },
+ hasError() {
+ return Boolean(this.error);
+ },
+ environmentName() {
+ return this.environment?.name ?? '';
+ },
+ },
+ methods: {
+ submitCanaryChange() {
+ return this.$apollo
+ .mutate({
+ mutation: updateCanaryIngress,
+ variables: {
+ input: {
+ id: this.environment.global_id,
+ weight: this.weight,
+ },
+ },
+ })
+ .then(
+ ({
+ data: {
+ environmentsCanaryIngressUpdate: {
+ errors: [error],
+ },
+ },
+ }) => {
+ this.error = error;
+ },
+ )
+ .catch(() => {
+ this.error = __('Something went wrong. Please try again later');
+ });
+ },
+ dismiss() {
+ this.error = '';
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-alert v-if="hasError" variant="danger" @dismiss="dismiss">{{ error }}</gl-alert>
+ <gl-modal v-bind="$options.modal" :visible="visible" @primary="submitCanaryChange">
+ <template #modal-title>{{ $options.translations.title }}</template>
+ <template #default>
+ <p>
+ <gl-sprintf :message="$options.translations.ratioChange">
+ <template #environment>{{ environmentName }}</template>
+ </gl-sprintf>
+ </p>
+ <ul class="gl-list-style-none gl-p-0">
+ <li>
+ <gl-sprintf :message="$options.translations.stableWeight">
+ <template #bold="{ content }">
+ <span class="gl-font-weight-bold">{{ content }}</span>
+ </template>
+ <template #stable>{{ stableWeight }}</template>
+ </gl-sprintf>
+ </li>
+ <li>
+ <gl-sprintf :message="$options.translations.canaryWeight">
+ <template #bold="{ content }">
+ <span class="gl-font-weight-bold">{{ content }}</span>
+ </template>
+ <template #canary>{{ canaryWeight }}</template>
+ </gl-sprintf>
+ </li>
+ </ul>
+ <p>{{ $options.translations.deploymentWarning }}</p>
+ </template>
+ </gl-modal>
+ </div>
+</template>
diff --git a/app/assets/javascripts/environments/components/deploy_board.vue b/app/assets/javascripts/environments/components/deploy_board.vue
new file mode 100644
index 00000000000..28e812d5220
--- /dev/null
+++ b/app/assets/javascripts/environments/components/deploy_board.vue
@@ -0,0 +1,216 @@
+<script>
+/* eslint-disable @gitlab/vue-require-i18n-strings */
+/**
+ * Renders a deploy board.
+ *
+ * A deploy board is composed by:
+ * - Information area with percentage of completion.
+ * - Instances with status.
+ * - Button Actions.
+ * [Mockup](https://gitlab.com/gitlab-org/gitlab-foss/uploads/2f655655c0eadf655d0ae7467b53002a/environments__deploy-graphic.png)
+ */
+import { isEmpty } from 'lodash';
+import {
+ GlIcon,
+ GlLoadingIcon,
+ GlLink,
+ GlTooltip,
+ GlTooltipDirective,
+ GlSafeHtmlDirective as SafeHtml,
+} from '@gitlab/ui';
+import deployBoardSvg from 'empty_states/icons/_deploy_board.svg';
+import instanceComponent from '~/vue_shared/components/deployment_instance.vue';
+import { n__ } from '~/locale';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { STATUS_MAP, CANARY_STATUS } from '../constants';
+import CanaryIngress from './canary_ingress.vue';
+
+export default {
+ components: {
+ instanceComponent,
+ CanaryIngress,
+ GlIcon,
+ GlLoadingIcon,
+ GlLink,
+ GlTooltip,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ SafeHtml,
+ },
+ mixins: [glFeatureFlagsMixin()],
+ props: {
+ deployBoardData: {
+ type: Object,
+ required: true,
+ },
+ deployBoardsHelpPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ isLoading: {
+ type: Boolean,
+ required: true,
+ },
+ isEmpty: {
+ type: Boolean,
+ required: true,
+ },
+ logsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ canRenderDeployBoard() {
+ return !this.isEmpty && !isEmpty(this.deployBoardData);
+ },
+ canRenderEmptyState() {
+ return this.isEmpty;
+ },
+ canRenderCanaryWeight() {
+ return (
+ this.glFeatures.canaryIngressWeightControl && !isEmpty(this.deployBoardData.canary_ingress)
+ );
+ },
+ instanceCount() {
+ const { instances } = this.deployBoardData;
+
+ return Array.isArray(instances) ? instances.length : 0;
+ },
+ instanceIsCompletedCount() {
+ const completionPercentage = this.deployBoardData.completion / 100;
+ const completionCount = Math.floor(completionPercentage * this.instanceCount);
+
+ return Number.isNaN(completionCount) ? 0 : completionCount;
+ },
+ instanceIsCompletedText() {
+ const title = n__('instance completed', 'instances completed', this.instanceIsCompletedCount);
+
+ return `${this.instanceIsCompletedCount} ${title}`;
+ },
+ instanceTitle() {
+ return n__('Instance', 'Instances', this.instanceCount);
+ },
+ deployBoardSvg() {
+ return deployBoardSvg;
+ },
+ deployBoardActions() {
+ return this.deployBoardData.rollback_url || this.deployBoardData.abort_url;
+ },
+ statuses() {
+ // Canary is not a pod status but it needs to be in the legend.
+ // Hence adding it here.
+ return {
+ ...STATUS_MAP,
+ CANARY_STATUS,
+ };
+ },
+ },
+ methods: {
+ changeCanaryWeight(weight) {
+ this.$emit('changeCanaryWeight', weight);
+ },
+ },
+};
+</script>
+<template>
+ <div class="js-deploy-board deploy-board">
+ <gl-loading-icon v-if="isLoading" class="loading-icon" />
+ <template v-else>
+ <div v-if="canRenderDeployBoard" class="deploy-board-information gl-p-5">
+ <div class="deploy-board-information gl-w-full">
+ <section class="deploy-board-status">
+ <span v-gl-tooltip :title="instanceIsCompletedText">
+ <span ref="percentage" class="gl-text-center text-plain gl-font-lg"
+ >{{ deployBoardData.completion }}%</span
+ >
+ <span class="text text-center text-secondary">{{ __('Complete') }}</span>
+ </span>
+ </section>
+
+ <section class="deploy-board-instances">
+ <div class="gl-font-base text-secondary">
+ <span class="deploy-board-instances-text"
+ >{{ instanceTitle }} ({{ instanceCount }})</span
+ >
+ <span ref="legend-icon" data-testid="legend-tooltip-target">
+ <gl-icon class="gl-text-blue-500 gl-ml-2" name="question" />
+ </span>
+ <gl-tooltip :target="() => $refs['legend-icon']" boundary="#content-body">
+ <div class="deploy-board-legend gl-display-flex gl-flex-direction-column">
+ <div
+ v-for="status in statuses"
+ :key="status.text"
+ class="gl-display-flex gl-align-items-center"
+ >
+ <instance-component :status="status.class" :stable="status.stable" />
+ <span class="legend-text gl-ml-3">{{ status.text }}</span>
+ </div>
+ </div>
+ </gl-tooltip>
+ </div>
+
+ <div class="deploy-board-instances-container d-flex flex-wrap flex-row">
+ <template v-for="(instance, i) in deployBoardData.instances">
+ <instance-component
+ :key="i"
+ :status="instance.status"
+ :tooltip-text="instance.tooltip"
+ :pod-name="instance.pod_name"
+ :logs-path="logsPath"
+ :stable="instance.stable"
+ />
+ </template>
+ </div>
+ </section>
+
+ <canary-ingress
+ v-if="canRenderCanaryWeight"
+ class="deploy-board-canary-ingress"
+ :canary-ingress="deployBoardData.canary_ingress"
+ @change="changeCanaryWeight"
+ />
+
+ <section v-if="deployBoardActions" class="deploy-board-actions">
+ <gl-link
+ v-if="deployBoardData.rollback_url"
+ :href="deployBoardData.rollback_url"
+ class="btn"
+ data-method="post"
+ rel="nofollow"
+ >{{ __('Rollback') }}</gl-link
+ >
+ <gl-link
+ v-if="deployBoardData.abort_url"
+ :href="deployBoardData.abort_url"
+ class="btn btn-danger btn-inverted"
+ data-method="post"
+ rel="nofollow"
+ >{{ __('Abort') }}</gl-link
+ >
+ </section>
+ </div>
+ </div>
+
+ <div v-if="canRenderEmptyState" class="deploy-board-empty">
+ <section v-safe-html="deployBoardSvg" class="deploy-board-empty-state-svg"></section>
+
+ <section class="deploy-board-empty-state-text">
+ <span class="deploy-board-empty-state-title d-flex">{{
+ __('Kubernetes deployment not found')
+ }}</span>
+ <span>
+ To see deployment progress for your environments, make sure you are deploying to
+ <code>$KUBE_NAMESPACE</code> and annotating with
+ <code>app.gitlab.com/app=$CI_PROJECT_PATH_SLUG</code>
+ and
+ <code>app.gitlab.com/env=$CI_ENVIRONMENT_SLUG</code>.
+ </span>
+ </section>
+ </div>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index 1efea30a5f7..ff183e51395 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -6,16 +6,18 @@ import { GlLoadingIcon } from '@gitlab/ui';
import { flow, reverse, sortBy } from 'lodash/fp';
import { s__ } from '~/locale';
import EnvironmentItem from './environment_item.vue';
+import DeployBoard from './deploy_board.vue';
+import CanaryUpdateModal from './canary_update_modal.vue';
+import CanaryDeploymentCallout from './canary_deployment_callout.vue';
export default {
components: {
EnvironmentItem,
GlLoadingIcon,
- DeployBoard: () => import('ee_component/environments/components/deploy_board_component.vue'),
- CanaryDeploymentCallout: () =>
- import('ee_component/environments/components/canary_deployment_callout.vue'),
+ DeployBoard,
+ CanaryDeploymentCallout,
EnvironmentAlert: () => import('ee_component/environments/components/environment_alert.vue'),
- CanaryUpdateModal: () => import('ee_component/environments/components/canary_update_modal.vue'),
+ CanaryUpdateModal,
},
props: {
environments: {
diff --git a/app/assets/javascripts/environments/constants.js b/app/assets/javascripts/environments/constants.js
new file mode 100644
index 00000000000..6d427bef4e6
--- /dev/null
+++ b/app/assets/javascripts/environments/constants.js
@@ -0,0 +1,40 @@
+import { __ } from '~/locale';
+
+// These statuses are based on how the backend defines pod phases here
+// lib/gitlab/kubernetes/pod.rb
+
+export const STATUS_MAP = {
+ succeeded: {
+ class: 'succeeded',
+ text: __('Succeeded'),
+ stable: true,
+ },
+ running: {
+ class: 'running',
+ text: __('Running'),
+ stable: true,
+ },
+ failed: {
+ class: 'failed',
+ text: __('Failed'),
+ stable: true,
+ },
+ pending: {
+ class: 'pending',
+ text: __('Pending'),
+ stable: true,
+ },
+ unknown: {
+ class: 'unknown',
+ text: __('Unknown'),
+ stable: true,
+ },
+};
+
+export const CANARY_STATUS = {
+ class: 'canary-icon',
+ text: __('Canary'),
+ stable: false,
+};
+
+export const CANARY_UPDATE_MODAL = 'confirm-canary-change';
diff --git a/app/assets/javascripts/environments/graphql/mutations/update_canary_ingress.mutation.graphql b/app/assets/javascripts/environments/graphql/mutations/update_canary_ingress.mutation.graphql
new file mode 100644
index 00000000000..04ea5cbcaef
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/mutations/update_canary_ingress.mutation.graphql
@@ -0,0 +1,5 @@
+mutation($input: EnvironmentsCanaryIngressUpdateInput!) {
+ environmentsCanaryIngressUpdate(input: $input) {
+ errors
+ }
+}
diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js
index 3d301f6094d..15a00c11ee6 100644
--- a/app/assets/javascripts/environments/mixins/environments_mixin.js
+++ b/app/assets/javascripts/environments/mixins/environments_mixin.js
@@ -3,7 +3,7 @@
*/
import { isEqual, isFunction, omitBy } from 'lodash';
import Visibility from 'visibilityjs';
-import EnvironmentsStore from 'ee_else_ce/environments/stores/environments_store';
+import EnvironmentsStore from '../stores/environments_store';
import Poll from '../../lib/utils/poll';
import { getParameterByName } from '../../lib/utils/common_utils';
import { s__ } from '../../locale';
diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js
index 6ef8b9f643f..2f4f53953f6 100644
--- a/app/assets/javascripts/environments/stores/environments_store.js
+++ b/app/assets/javascripts/environments/stores/environments_store.js
@@ -1,4 +1,4 @@
-import { setDeployBoard } from 'ee_else_ce/environments/stores/helpers';
+import { setDeployBoard } from './helpers';
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
/**
@@ -81,6 +81,17 @@ export default class EnvironmentsStore {
this.state.environments = filteredEnvironments;
+ /**
+ * Add the canary callout banner underneath the second environment listed.
+ *
+ * If there is only one environment, then add to it underneath the first.
+ */
+ if (this.state.environments.length >= 2) {
+ this.state.environments[1].showCanaryCallout = true;
+ } else if (this.state.environments.length === 1) {
+ this.state.environments[0].showCanaryCallout = true;
+ }
+
return filteredEnvironments;
}
@@ -135,12 +146,22 @@ export default class EnvironmentsStore {
/**
* Toggles deploy board visibility for the provided environment ID.
- * Currently only works on EE.
*
* @param {Object} environment
* @return {Array}
*/
- toggleDeployBoard() {
+ toggleDeployBoard(environmentID) {
+ const environments = this.state.environments.slice();
+
+ this.state.environments = environments.map((env) => {
+ let updated = { ...env };
+
+ if (env.id === environmentID) {
+ updated = { ...updated, isDeployBoardVisible: !env.isDeployBoardVisible };
+ }
+ return updated;
+ });
+
return this.state.environments;
}
diff --git a/app/assets/javascripts/environments/stores/helpers.js b/app/assets/javascripts/environments/stores/helpers.js
index eb47ba29412..89457da0614 100644
--- a/app/assets/javascripts/environments/stores/helpers.js
+++ b/app/assets/javascripts/environments/stores/helpers.js
@@ -1,7 +1,22 @@
/**
- * Deploy boards are EE only.
- *
* @param {Object} environment
* @returns {Object}
*/
-export const setDeployBoard = (oldEnvironmentState, environment) => environment;
+export const setDeployBoard = (oldEnvironmentState, environment) => {
+ let parsedEnvironment = environment;
+ if (environment.size === 1 && environment.rollout_status) {
+ parsedEnvironment = {
+ ...environment,
+ hasDeployBoard: true,
+ isDeployBoardVisible:
+ oldEnvironmentState.isDeployBoardVisible === false
+ ? oldEnvironmentState.isDeployBoardVisible
+ : true,
+ deployBoardData:
+ environment.rollout_status.status === 'found' ? environment.rollout_status : {},
+ isLoadingDeployBoard: environment.rollout_status.status === 'loading',
+ isEmptyDeployBoard: environment.rollout_status.status === 'not_found',
+ };
+ }
+ return parsedEnvironment;
+};