1<script> 2import { GlButton } from '@gitlab/ui'; 3import api from '~/api'; 4import { __ } from '~/locale'; 5import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue'; 6import Popover from '~/vue_shared/components/help_popover.vue'; 7import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; 8import { status, SLOT_SUCCESS, SLOT_LOADING, SLOT_ERROR } from '../constants'; 9import IssuesList from './issues_list.vue'; 10 11export default { 12 name: 'ReportSection', 13 components: { 14 GlButton, 15 IssuesList, 16 Popover, 17 StatusIcon, 18 }, 19 mixins: [glFeatureFlagsMixin()], 20 props: { 21 alwaysOpen: { 22 type: Boolean, 23 required: false, 24 default: false, 25 }, 26 component: { 27 type: String, 28 required: false, 29 default: '', 30 }, 31 status: { 32 type: String, 33 required: true, 34 }, 35 loadingText: { 36 type: String, 37 required: false, 38 default: '', 39 }, 40 errorText: { 41 type: String, 42 required: false, 43 default: '', 44 }, 45 successText: { 46 type: String, 47 required: false, 48 default: '', 49 }, 50 unresolvedIssues: { 51 type: Array, 52 required: false, 53 default: () => [], 54 }, 55 resolvedIssues: { 56 type: Array, 57 required: false, 58 default: () => [], 59 }, 60 neutralIssues: { 61 type: Array, 62 required: false, 63 default: () => [], 64 }, 65 infoText: { 66 type: [String, Boolean], 67 required: false, 68 default: false, 69 }, 70 hasIssues: { 71 type: Boolean, 72 required: true, 73 }, 74 popoverOptions: { 75 type: Object, 76 default: () => ({}), 77 required: false, 78 }, 79 showReportSectionStatusIcon: { 80 type: Boolean, 81 required: false, 82 default: true, 83 }, 84 issuesUlElementClass: { 85 type: String, 86 required: false, 87 default: undefined, 88 }, 89 issuesListContainerClass: { 90 type: String, 91 required: false, 92 default: undefined, 93 }, 94 issueItemClass: { 95 type: String, 96 required: false, 97 default: undefined, 98 }, 99 shouldEmitToggleEvent: { 100 type: Boolean, 101 required: false, 102 default: false, 103 }, 104 trackAction: { 105 type: String, 106 required: false, 107 default: null, 108 }, 109 }, 110 111 data() { 112 return { 113 isCollapsed: true, 114 }; 115 }, 116 117 computed: { 118 collapseText() { 119 return this.isCollapsed ? __('Expand') : __('Collapse'); 120 }, 121 isLoading() { 122 return this.status === status.LOADING; 123 }, 124 loadingFailed() { 125 return this.status === status.ERROR; 126 }, 127 isSuccess() { 128 return this.status === status.SUCCESS; 129 }, 130 isCollapsible() { 131 return !this.alwaysOpen && this.hasIssues; 132 }, 133 isExpanded() { 134 return this.alwaysOpen || !this.isCollapsed; 135 }, 136 statusIconName() { 137 if (this.isLoading) { 138 return 'loading'; 139 } 140 if (this.loadingFailed || this.unresolvedIssues.length || this.neutralIssues.length) { 141 return 'warning'; 142 } 143 return 'success'; 144 }, 145 headerText() { 146 if (this.isLoading) { 147 return this.loadingText; 148 } 149 150 if (this.isSuccess) { 151 return this.successText; 152 } 153 154 if (this.loadingFailed) { 155 return this.errorText; 156 } 157 158 return ''; 159 }, 160 hasPopover() { 161 return Object.keys(this.popoverOptions).length > 0; 162 }, 163 slotName() { 164 if (this.isSuccess) { 165 return SLOT_SUCCESS; 166 } else if (this.isLoading) { 167 return SLOT_LOADING; 168 } 169 170 return SLOT_ERROR; 171 }, 172 }, 173 methods: { 174 toggleCollapsed() { 175 if (this.trackAction && this.glFeatures.usersExpandingWidgetsUsageData) { 176 api.trackRedisHllUserEvent(this.trackAction); 177 } 178 179 if (this.shouldEmitToggleEvent) { 180 this.$emit('toggleEvent'); 181 } 182 this.isCollapsed = !this.isCollapsed; 183 }, 184 }, 185}; 186</script> 187<template> 188 <section class="media-section"> 189 <div class="media"> 190 <status-icon :status="statusIconName" :size="24" class="align-self-center" /> 191 <div class="media-body d-flex flex-align-self-center align-items-center"> 192 <div data-testid="report-section-code-text" class="js-code-text code-text"> 193 <div class="gl-display-flex gl-align-items-center"> 194 <p class="gl-line-height-normal gl-m-0">{{ headerText }}</p> 195 <slot :name="slotName"></slot> 196 <popover 197 v-if="hasPopover" 198 :options="popoverOptions" 199 class="gl-ml-2 gl-display-inline-flex" 200 /> 201 </div> 202 <slot name="sub-heading"></slot> 203 </div> 204 205 <slot name="action-buttons" :is-collapsible="isCollapsible"></slot> 206 207 <gl-button 208 v-if="isCollapsible" 209 class="js-collapse-btn" 210 data-testid="report-section-expand-button" 211 data-qa-selector="expand_report_button" 212 @click="toggleCollapsed" 213 > 214 {{ collapseText }} 215 </gl-button> 216 </div> 217 </div> 218 219 <div v-if="hasIssues" v-show="isExpanded" class="js-report-section-container"> 220 <slot name="body"> 221 <issues-list 222 :unresolved-issues="unresolvedIssues" 223 :resolved-issues="resolvedIssues" 224 :neutral-issues="neutralIssues" 225 :component="component" 226 :show-report-section-status-icon="showReportSectionStatusIcon" 227 :issues-ul-element-class="issuesUlElementClass" 228 :class="issuesListContainerClass" 229 :issue-item-class="issueItemClass" 230 /> 231 </slot> 232 </div> 233 </section> 234</template> 235