1<script>
2/* eslint-disable @gitlab/vue-require-i18n-strings */
3/**
4 * Renders a deploy board.
5 *
6 * A deploy board is composed by:
7 * - Information area with percentage of completion.
8 * - Instances with status.
9 * - Button Actions.
10 * [Mockup](https://gitlab.com/gitlab-org/gitlab-foss/uploads/2f655655c0eadf655d0ae7467b53002a/environments__deploy-graphic.png)
11 */
12import deployBoardSvg from '@gitlab/svgs/dist/illustrations/deploy-boards.svg';
13import {
14  GlIcon,
15  GlLoadingIcon,
16  GlLink,
17  GlTooltip,
18  GlTooltipDirective,
19  GlSafeHtmlDirective as SafeHtml,
20} from '@gitlab/ui';
21import { isEmpty } from 'lodash';
22import { n__ } from '~/locale';
23import instanceComponent from '~/vue_shared/components/deployment_instance.vue';
24import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
25import { STATUS_MAP, CANARY_STATUS } from '../constants';
26import CanaryIngress from './canary_ingress.vue';
27
28export default {
29  components: {
30    instanceComponent,
31    CanaryIngress,
32    GlIcon,
33    GlLoadingIcon,
34    GlLink,
35    GlTooltip,
36  },
37  directives: {
38    GlTooltip: GlTooltipDirective,
39    SafeHtml,
40  },
41  mixins: [glFeatureFlagsMixin()],
42  props: {
43    deployBoardData: {
44      type: Object,
45      required: true,
46    },
47    isLoading: {
48      type: Boolean,
49      required: true,
50    },
51    isEmpty: {
52      type: Boolean,
53      required: true,
54    },
55    logsPath: {
56      type: String,
57      required: false,
58      default: '',
59    },
60  },
61  computed: {
62    canRenderDeployBoard() {
63      return !this.isEmpty && !isEmpty(this.deployBoardData);
64    },
65    canRenderEmptyState() {
66      return this.isEmpty;
67    },
68    canRenderCanaryWeight() {
69      return !isEmpty(this.deployBoardData.canary_ingress);
70    },
71    instanceCount() {
72      const { instances } = this.deployBoardData;
73
74      return Array.isArray(instances) ? instances.length : 0;
75    },
76    instanceIsCompletedCount() {
77      const completionPercentage = this.deployBoardData.completion / 100;
78      const completionCount = Math.floor(completionPercentage * this.instanceCount);
79
80      return Number.isNaN(completionCount) ? 0 : completionCount;
81    },
82    instanceIsCompletedText() {
83      const title = n__('instance completed', 'instances completed', this.instanceIsCompletedCount);
84
85      return `${this.instanceIsCompletedCount} ${title}`;
86    },
87    instanceTitle() {
88      return n__('Instance', 'Instances', this.instanceCount);
89    },
90    deployBoardSvg() {
91      return deployBoardSvg;
92    },
93    deployBoardActions() {
94      return this.deployBoardData.rollback_url || this.deployBoardData.abort_url;
95    },
96    statuses() {
97      // Canary is not a pod status but it needs to be in the legend.
98      // Hence adding it here.
99      return {
100        ...STATUS_MAP,
101        CANARY_STATUS,
102      };
103    },
104  },
105  methods: {
106    changeCanaryWeight(weight) {
107      this.$emit('changeCanaryWeight', weight);
108    },
109  },
110};
111</script>
112<template>
113  <div class="js-deploy-board deploy-board">
114    <gl-loading-icon v-if="isLoading" size="sm" class="loading-icon" />
115    <template v-else>
116      <div v-if="canRenderDeployBoard" class="deploy-board-information gl-p-5">
117        <div class="deploy-board-information gl-w-full">
118          <section class="deploy-board-status">
119            <span v-gl-tooltip :title="instanceIsCompletedText">
120              <span ref="percentage" class="gl-text-center text-plain gl-font-lg"
121                >{{ deployBoardData.completion }}%</span
122              >
123              <span class="text text-center text-secondary">{{ __('Complete') }}</span>
124            </span>
125          </section>
126
127          <section class="deploy-board-instances">
128            <div class="gl-font-base text-secondary">
129              <span class="deploy-board-instances-text"
130                >{{ instanceTitle }} ({{ instanceCount }})</span
131              >
132              <span ref="legend-icon" data-testid="legend-tooltip-target">
133                <gl-icon class="gl-text-blue-500 gl-ml-2" name="question" />
134              </span>
135              <gl-tooltip :target="() => $refs['legend-icon']" boundary="#content-body">
136                <div class="deploy-board-legend gl-display-flex gl-flex-direction-column">
137                  <div
138                    v-for="status in statuses"
139                    :key="status.text"
140                    class="gl-display-flex gl-align-items-center"
141                  >
142                    <instance-component :status="status.class" :stable="status.stable" />
143                    <span class="legend-text gl-ml-3">{{ status.text }}</span>
144                  </div>
145                </div>
146              </gl-tooltip>
147            </div>
148
149            <div class="deploy-board-instances-container d-flex flex-wrap flex-row">
150              <template v-for="(instance, i) in deployBoardData.instances">
151                <instance-component
152                  :key="i"
153                  :status="instance.status"
154                  :tooltip-text="instance.tooltip"
155                  :pod-name="instance.pod_name"
156                  :logs-path="logsPath"
157                  :stable="instance.stable"
158                />
159              </template>
160            </div>
161          </section>
162
163          <canary-ingress
164            v-if="canRenderCanaryWeight"
165            class="deploy-board-canary-ingress"
166            :canary-ingress="deployBoardData.canary_ingress"
167            @change="changeCanaryWeight"
168          />
169
170          <section v-if="deployBoardActions" class="deploy-board-actions">
171            <gl-link
172              v-if="deployBoardData.rollback_url"
173              :href="deployBoardData.rollback_url"
174              class="btn"
175              data-method="post"
176              rel="nofollow"
177              >{{ __('Rollback') }}</gl-link
178            >
179            <gl-link
180              v-if="deployBoardData.abort_url"
181              :href="deployBoardData.abort_url"
182              class="btn btn-danger btn-inverted"
183              data-method="post"
184              rel="nofollow"
185              >{{ __('Abort') }}</gl-link
186            >
187          </section>
188        </div>
189      </div>
190
191      <div v-if="canRenderEmptyState" class="deploy-board-empty">
192        <section v-safe-html="deployBoardSvg" class="deploy-board-empty-state-svg"></section>
193
194        <section class="deploy-board-empty-state-text">
195          <span class="deploy-board-empty-state-title d-flex">{{
196            __('Kubernetes deployment not found')
197          }}</span>
198          <span>
199            To see deployment progress for your environments, make sure you are deploying to
200            <code>$KUBE_NAMESPACE</code> and annotating with
201            <code>app.gitlab.com/app=$CI_PROJECT_PATH_SLUG</code>
202            and
203            <code>app.gitlab.com/env=$CI_ENVIRONMENT_SLUG</code>.
204          </span>
205        </section>
206      </div>
207    </template>
208  </div>
209</template>
210