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