diff options
Diffstat (limited to 'app/assets/javascripts/vue_merge_request_widget/components/widget')
4 files changed, 94 insertions, 62 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/action_buttons.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/action_buttons.vue index c38c253564a..9dd4e76befe 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/widget/action_buttons.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/action_buttons.vue @@ -19,8 +19,7 @@ export default { }, tertiaryButtons: { type: Array, - required: false, - default: () => [], + required: true, }, }, data() { @@ -76,7 +75,6 @@ export default { <template> <div class="gl-display-flex gl-align-items-flex-start"> <gl-dropdown - v-if="tertiaryButtons.length" v-gl-tooltip :title="__('Options')" :text="dropdownLabel" @@ -102,33 +100,31 @@ export default { {{ btn.text }} </gl-dropdown-item> </gl-dropdown> - <template v-if="tertiaryButtons.length"> - <gl-button - v-for="(btn, index) in tertiaryButtons" - :id="btn.id" - :key="index" - v-gl-tooltip.hover - :title="setTooltip(btn)" - :href="btn.href" - :target="btn.target" - :class="[{ 'gl-mr-3': index !== tertiaryButtons.length - 1 }, btn.class]" - :data-clipboard-text="btn.dataClipboardText" - :data-qa-selector="actionButtonQaSelector(btn)" - :data-method="btn.dataMethod" - :icon="btn.icon" - :data-testid="btn.testId || 'extension-actions-button'" - :variant="btn.variant || 'confirm'" - :loading="btn.loading" - :disabled="btn.loading" - category="tertiary" - size="small" - class="gl-display-none gl-md-display-block gl-float-left" - @click="onClickAction(btn)" - > - <template v-if="btn.text"> - {{ btn.text }} - </template> - </gl-button> - </template> + <gl-button + v-for="(btn, index) in tertiaryButtons" + :id="btn.id" + :key="index" + v-gl-tooltip.hover + :title="setTooltip(btn)" + :href="btn.href" + :target="btn.target" + :class="[{ 'gl-mr-3': index !== tertiaryButtons.length - 1 }, btn.class]" + :data-clipboard-text="btn.dataClipboardText" + :data-qa-selector="actionButtonQaSelector(btn)" + :data-method="btn.dataMethod" + :icon="btn.icon" + :data-testid="btn.testId || 'extension-actions-button'" + :variant="btn.variant || 'confirm'" + :loading="btn.loading" + :disabled="btn.loading" + category="tertiary" + size="small" + class="gl-display-none gl-md-display-block gl-float-left" + @click="onClickAction(btn)" + > + <template v-if="btn.text"> + {{ btn.text }} + </template> + </gl-button> </div> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue index 258fa4edcda..9bb39ba22e0 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue @@ -5,8 +5,9 @@ export default { import( '~/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports.vue' ), - MrTerraformWidget: () => import('~/vue_merge_request_widget/extensions/terraform/index.vue'), + MrCodeQualityWidget: () => + import('~/vue_merge_request_widget/extensions/code_quality/index.vue'), }, props: { @@ -21,8 +22,14 @@ export default { return this.mr.terraformReportsPath && 'MrTerraformWidget'; }, + codeQualityWidget() { + return this.mr.codequalityReportsPath ? 'MrCodeQualityWidget' : undefined; + }, + widgets() { - return [this.terraformPlansWidget, 'MrSecurityWidget'].filter((w) => w); + return [this.codeQualityWidget, this.terraformPlansWidget, 'MrSecurityWidget'].filter( + (w) => w, + ); }, }, }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue index ec979861283..618d1e71f81 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue @@ -52,6 +52,9 @@ export default { shouldShowThirdLevel() { return this.data.children?.length > 0 && this.level === 2; }, + hasActionButtons() { + return this.data.actions?.length > 0; + }, }, methods: { onClickedAction(action) { @@ -73,15 +76,22 @@ export default { <template #body> <div class="gl-w-full gl-display-flex" :class="{ 'gl-flex-direction-column': level === 1 }"> <div class="gl-display-flex gl-flex-grow-1"> - <div class="gl-display-flex gl-flex-grow-1 gl-flex-direction-column"> - <p v-safe-html="generatedText" class="gl-mb-0 gl-mr-1"></p> - <gl-link v-if="data.link" :href="data.link.href">{{ data.link.text }}</gl-link> - <p v-if="data.supportingText" v-safe-html="generatedSupportingText" class="gl-mb-0"></p> + <div class="gl-display-flex gl-flex-grow-1 gl-align-items-baseline"> + <div> + <p v-safe-html="generatedText" class="gl-mb-0 gl-mr-1"></p> + <gl-link v-if="data.link" :href="data.link.href">{{ data.link.text }}</gl-link> + <p + v-if="data.supportingText" + v-safe-html="generatedSupportingText" + class="gl-mb-0" + ></p> + </div> <gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'"> {{ data.badge.text }} </gl-badge> </div> <actions + v-if="hasActionButtons" :widget="widgetName" :tertiary-buttons="data.actions" class="gl-ml-auto gl-pl-3" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue index e327d848d8f..2c8bf90064e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue @@ -1,3 +1,4 @@ +<!-- eslint-disable vue/multi-word-component-names --> <script> import { GlButton, GlLink, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui'; import * as Sentry from '@sentry/browser'; @@ -16,14 +17,19 @@ import DynamicContent from './dynamic_content.vue'; import StatusIcon from './status_icon.vue'; import ActionButtons from './action_buttons.vue'; -const FETCH_TYPE_COLLAPSED = 'collapsed'; -const FETCH_TYPE_EXPANDED = 'expanded'; const WIDGET_PREFIX = 'Widget'; const MISSING_RESPONSE_HEADERS = 'MR Widget: raesponse object should contain status and headers object. Make sure to include that in your `fetchCollapsedData` and `fetchExpandedData` functions.'; +const LOADING_STATE_COLLAPSED = 'collapsed'; +const LOADING_STATE_EXPANDED = 'expanded'; +const LOADING_STATE_STATUS_ICON = 'status_icon'; + export default { MISSING_RESPONSE_HEADERS, + LOADING_STATE_COLLAPSED, + LOADING_STATE_EXPANDED, + LOADING_STATE_STATUS_ICON, components: { ActionButtons, @@ -42,20 +48,29 @@ export default { SafeHtml, }, props: { - /** - * @param {value.collapsed} Object - * @param {value.expanded} Object - */ - value: { - type: Object, - required: false, - default: () => ({}), - }, loadingText: { type: String, required: false, default: __('Loading'), }, + // Use this property when you need to control the loading state from the + // parent component. + loadingState: { + type: String, + required: false, + default: undefined, + validator: (s) => { + if (!s) { + return true; + } + + return [ + LOADING_STATE_EXPANDED, + LOADING_STATE_COLLAPSED, + LOADING_STATE_STATUS_ICON, + ].includes(s); + }, + }, errorText: { type: String, required: false, @@ -158,7 +173,7 @@ export default { return { isExpandedForTheFirstTime: true, isCollapsed: true, - isLoading: true, + isLoadingCollapsedContent: true, isLoadingExpandedContent: false, summaryError: null, contentError: null, @@ -166,6 +181,12 @@ export default { }; }, computed: { + isSummaryLoading() { + return this.isLoadingCollapsedContent || this.loadingState === LOADING_STATE_COLLAPSED; + }, + shouldShowLoadingIcon() { + return this.isSummaryLoading || this.loadingState === LOADING_STATE_STATUS_ICON; + }, generatedSummary() { return generateText(this.summary?.title || ''); }, @@ -192,7 +213,7 @@ export default { }, immediate: true, }, - isLoading(newValue) { + isLoadingCollapsedContent(newValue) { this.$emit('is-loading', newValue); }, }, @@ -202,18 +223,18 @@ export default { } }, async mounted() { - this.isLoading = true; + this.isLoadingCollapsedContent = true; this.telemetryHub?.viewed(); try { if (this.fetchCollapsedData) { - await this.fetch(this.fetchCollapsedData, FETCH_TYPE_COLLAPSED); + await this.fetch(this.fetchCollapsedData); } } catch { this.summaryError = this.errorText; } - this.isLoading = false; + this.isLoadingCollapsedContent = false; }, methods: { onActionClick(action) { @@ -240,7 +261,7 @@ export default { this.contentError = null; try { - await this.fetch(this.fetchExpandedData, FETCH_TYPE_EXPANDED); + await this.fetch(this.fetchExpandedData); } catch { this.contentError = this.errorText; @@ -251,7 +272,7 @@ export default { this.isLoadingExpandedContent = false; }, - fetch(handler, dataType) { + fetch(handler) { const requests = this.multiPolling ? handler() : [handler]; const promises = requests.map((request) => { @@ -288,9 +309,7 @@ export default { }); }); - return Promise.all(promises).then((data) => { - this.$emit('input', { ...this.value, [dataType]: this.multiPolling ? data : data[0] }); - }); + return Promise.all(promises); }, }, failedStatusIcon: EXTENSION_ICONS.failed, @@ -306,7 +325,7 @@ export default { <status-icon :level="1" :name="widgetName" - :is-loading="isLoading" + :is-loading="shouldShowLoadingIcon" :icon-name="summaryStatusIcon" /> <div @@ -316,9 +335,9 @@ export default { <div class="gl-flex-grow-1" data-testid="widget-extension-top-level-summary"> <span v-if="summaryError">{{ summaryError }}</span> <slot v-else name="summary" - ><div v-safe-html="isLoading ? loadingText : generatedSummary"></div> + ><div v-safe-html="isSummaryLoading ? loadingText : generatedSummary"></div> <div - v-if="!isLoading && generatedSubSummary" + v-if="!isSummaryLoading && generatedSubSummary" v-safe-html="generatedSubSummary" class="gl-font-sm gl-text-gray-700" ></div @@ -356,7 +375,7 @@ export default { </slot> </div> <div - v-if="isCollapsible && !isLoading" + v-if="isCollapsible && !isSummaryLoading" class="gl-border-l-1 gl-border-l-solid gl-border-gray-100 gl-ml-3 gl-pl-3 gl-h-6" > <gl-button |