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