Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/vue_merge_request_widget/components')
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js83
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue41
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue103
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/action_buttons.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue32
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue37
8 files changed, 164 insertions, 146 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
index 028f5370028..f7c0f960c0e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
@@ -68,7 +68,7 @@ export default {
},
isCollapsible() {
if (!this.isLoadingSummary && this.loadingState !== LOADING_STATES.collapsedError) {
- if (this.shouldCollapse) {
+ if ('shouldCollapse' in this) {
return this.shouldCollapse(this.collapsedData);
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js
index 7e329399957..0b8f5ffa397 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import { markRaw } from '~/lib/utils/vue3compat/mark_raw';
import ExtensionBase from './base.vue';
// Holds all the currently registered extensions
@@ -7,45 +8,47 @@ export const registeredExtensions = Vue.observable({ extensions: [] });
export const registerExtension = (extension) => {
// Pushes into the extenions array a dynamically created Vue component
// that gets exteneded from `base.vue`
- registeredExtensions.extensions.push({
- extends: ExtensionBase,
- name: extension.name,
- props: {
- mr: {
- type: Object,
- required: true,
+ registeredExtensions.extensions.push(
+ markRaw({
+ extends: ExtensionBase,
+ name: extension.name,
+ props: {
+ mr: {
+ type: Object,
+ required: true,
+ },
},
- },
- telemetry: extension.telemetry,
- i18n: extension.i18n,
- expandEvent: extension.expandEvent,
- enablePolling: extension.enablePolling,
- enableExpandedPolling: extension.enableExpandedPolling,
- modalComponent: extension.modalComponent,
- computed: {
- ...extension.props.reduce(
- (acc, propKey) => ({
- ...acc,
- [propKey]() {
- return this.mr[propKey];
- },
- }),
- {},
- ),
- ...Object.keys(extension.computed).reduce(
- (acc, computedKey) => ({
- ...acc,
- // Making the computed property a method allows us to pass in arguments
- // this allows for each computed property to receive some data
- [computedKey]() {
- return extension.computed[computedKey];
- },
- }),
- {},
- ),
- },
- methods: {
- ...extension.methods,
- },
- });
+ telemetry: extension.telemetry,
+ i18n: extension.i18n,
+ expandEvent: extension.expandEvent,
+ enablePolling: extension.enablePolling,
+ enableExpandedPolling: extension.enableExpandedPolling,
+ modalComponent: extension.modalComponent,
+ computed: {
+ ...extension.props.reduce(
+ (acc, propKey) => ({
+ ...acc,
+ [propKey]() {
+ return this.mr[propKey];
+ },
+ }),
+ {},
+ ),
+ ...Object.keys(extension.computed).reduce(
+ (acc, computedKey) => ({
+ ...acc,
+ // Making the computed property a method allows us to pass in arguments
+ // this allows for each computed property to receive some data
+ [computedKey]() {
+ return extension.computed[computedKey];
+ },
+ }),
+ {},
+ ),
+ },
+ methods: {
+ ...extension.methods,
+ },
+ }),
+ );
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue
index 5baeb309f79..8bf4d8816be 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue
@@ -1,10 +1,9 @@
<script>
-import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { GlDisclosureDropdown } from '@gitlab/ui';
export default {
components: {
- GlDropdown,
- GlDropdownItem,
+ GlDisclosureDropdown,
},
props: {
commits: {
@@ -13,28 +12,36 @@ export default {
default: () => [],
},
},
+ computed: {
+ dropdownItems() {
+ return this.commits.map((commit) => ({
+ text: commit.title,
+ extraAttrs: {
+ text: commit.shortId || commit.short_Id,
+ },
+ action: () => {
+ this.$emit('input', commit.message);
+ },
+ }));
+ },
+ },
};
</script>
<template>
<div>
- <gl-dropdown
- right
- text="Use an existing commit message"
+ <gl-disclosure-dropdown
+ placement="right"
+ toggle-text="Use an existing commit message"
category="tertiary"
- variant="confirm"
+ :items="dropdownItems"
size="small"
class="mr-commit-dropdown"
>
- <gl-dropdown-item
- v-for="(commit, index) in commits"
- :key="index"
- class="text-nowrap text-truncate"
- @click="$emit('input', commit.message)"
- >
- <span class="monospace mr-2">{{ commit.shortId || commit.short_id }}</span>
- {{ commit.title }}
- </gl-dropdown-item>
- </gl-dropdown>
+ <template #list-item="{ item }">
+ <span class="gl-mr-2">{{ item.extraAttrs.text }}</span>
+ {{ item.text }}
+ </template>
+ </gl-disclosure-dropdown>
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index 52cdafd4717..7071759b8bb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -27,7 +27,6 @@ import readyToMergeSubscription from '~/vue_merge_request_widget/queries/states/
import HelpPopover from '~/vue_shared/components/help_popover.vue';
import {
AUTO_MERGE_STRATEGIES,
- WARNING,
MT_MERGE_STRATEGY,
PIPELINE_FAILED_STATE,
STATE_MACHINE,
@@ -42,7 +41,6 @@ import CommitMessageDropdown from './commit_message_dropdown.vue';
import SquashBeforeMerge from './squash_before_merge.vue';
import MergeFailedPipelineConfirmationDialog from './merge_failed_pipeline_confirmation_dialog.vue';
-const PIPELINE_RUNNING_STATE = 'running';
const PIPELINE_PENDING_STATE = 'pending';
const PIPELINE_SUCCESS_STATE = 'success';
@@ -133,8 +131,6 @@ export default {
GlFormCheckbox,
GlSkeletonLoader,
MergeFailedPipelineConfirmationDialog,
- MergeTrainHelperIcon: () =>
- import('ee_component/vue_merge_request_widget/components/merge_train_helper_icon.vue'),
MergeImmediatelyConfirmationDialog: () =>
import(
'ee_component/vue_merge_request_widget/components/merge_immediately_confirmation_dialog.vue'
@@ -176,14 +172,11 @@ export default {
};
},
computed: {
- stateData() {
- return this.state;
- },
hasCI() {
- return this.stateData.hasCI || this.stateData.hasCi;
+ return this.state.hasCI || this.state.hasCi;
},
isAutoMergeAvailable() {
- return !isEmpty(this.stateData.availableAutoMergeStrategies);
+ return !isEmpty(this.state.availableAutoMergeStrategies);
},
pipeline() {
return this.state.headPipeline;
@@ -246,30 +239,11 @@ export default {
return PIPELINE_SUCCESS_STATE;
},
- iconClass() {
- if (this.shouldRenderMergeTrainHelperIcon && !this.mr.preventMerge) {
- return PIPELINE_RUNNING_STATE;
- }
-
- if (
- this.status === PIPELINE_FAILED_STATE ||
- !this.commitMessage.length ||
- !this.isMergeAllowed ||
- this.mr.preventMerge
- ) {
- return WARNING;
- }
-
- return PIPELINE_SUCCESS_STATE;
- },
mergeButtonText() {
if (this.isMergingImmediately) {
return __('Merge in progress');
}
- if (this.isAutoMergeAvailable && !this.autoMergeLabelsEnabled) {
- return this.autoMergeTextLegacy;
- }
- if (this.isAutoMergeAvailable && this.autoMergeLabelsEnabled) {
+ if (this.isAutoMergeAvailable) {
return this.autoMergeText;
}
@@ -279,9 +253,6 @@ export default {
return __('Merge');
},
- autoMergeLabelsEnabled() {
- return window.gon?.features?.autoMergeLabelsMrWidget;
- },
showAutoMergeHelperText() {
return (
!(this.status === PIPELINE_FAILED_STATE || this.isPipelineFailed) &&
@@ -289,7 +260,7 @@ export default {
);
},
hasPipelineMustSucceedConflict() {
- return !this.hasCI && this.stateData.onlyAllowMergeIfPipelineSucceeds;
+ return !this.hasCI && this.state.onlyAllowMergeIfPipelineSucceeds;
},
isNotClosed() {
return this.mr.state !== STATUS_CLOSED;
@@ -322,12 +293,7 @@ export default {
return this.preferredAutoMergeStrategy === MT_MERGE_STRATEGY && this.isPipelineFailed;
},
shouldShowMergeControls() {
- return (
- (this.isMergeAllowed || this.isAutoMergeAvailable) &&
- (this.stateData.userPermissions?.canMerge || this.mr.canMerge) &&
- !this.mr.mergeOngoing &&
- !this.mr.autoMergeEnabled
- );
+ return this.state.userPermissions?.canMerge && this.mr.state === 'readyToMerge';
},
sourceBranchDeletedText() {
const isPreMerge = this.mr.state !== STATUS_MERGED;
@@ -354,6 +320,11 @@ export default {
};
},
},
+ watch: {
+ 'mr.state': function mrStateWatcher() {
+ this.isMakingRequest = false;
+ },
+ },
mounted() {
eventHub.$on('ApprovalUpdated', this.updateGraphqlState);
eventHub.$on('MRWidgetUpdateRequested', this.updateGraphqlState);
@@ -441,8 +412,6 @@ export default {
}
this.updateGraphqlState();
-
- this.isMakingRequest = false;
})
.catch(() => {
this.isMakingRequest = false;
@@ -511,13 +480,13 @@ export default {
},
i18n: {
mergeCommitTemplateHintText: s__(
- 'mrWidget|To change this default message, edit the template for merge commit messages. %{linkStart}Learn more.%{linkEnd}',
+ 'mrWidget|To change this default message, edit the template for merge commit messages. %{linkStart}Learn more%{linkEnd}.',
),
squashCommitTemplateHintText: s__(
- 'mrWidget|To change this default message, edit the template for squash commit messages. %{linkStart}Learn more.%{linkEnd}',
+ 'mrWidget|To change this default message, edit the template for squash commit messages. %{linkStart}Learn more%{linkEnd}.',
),
mergeAndSquashCommitTemplatesHintText: s__(
- 'mrWidget|To change these default messages, edit the templates for both the merge and squash commit messages. %{linkStart}Learn more.%{linkEnd}',
+ 'mrWidget|To change these default messages, edit the templates for both the merge and squash commit messages. %{linkStart}Learn more%{linkEnd}.',
),
sourceDivergedFromTargetText: s__('mrWidget|The source branch is %{link} the target branch'),
divergedCommits: (count) => n__('%d commit behind', '%d commits behind', count),
@@ -619,9 +588,8 @@ export default {
:href="commitTemplateHelpPage"
class="inline-link"
target="_blank"
+ >{{ content }}</gl-link
>
- {{ content }}
- </gl-link>
</template>
</gl-sprintf>
</p>
@@ -692,35 +660,21 @@ export default {
>
{{ __('Merge immediately') }}
</gl-dropdown-item>
- <merge-immediately-confirmation-dialog
- ref="confirmationDialog"
- :docs-url="mr.mergeImmediatelyDocsPath"
- @mergeImmediately="onMergeImmediatelyConfirmation"
- />
</gl-dropdown>
- <merge-train-failed-pipeline-confirmation-dialog
- :visible="isPipelineFailedModalVisibleMergeTrain"
- @startMergeTrain="onStartMergeTrainConfirmation"
- @cancel="isPipelineFailedModalVisibleMergeTrain = false"
- />
- <merge-failed-pipeline-confirmation-dialog
- :visible="isPipelineFailedModalVisibleNormalMerge"
- @mergeWithFailedPipeline="onMergeWithFailedPipelineConfirmation"
- @cancel="isPipelineFailedModalVisibleNormalMerge = false"
- />
</gl-button-group>
- <merge-train-helper-icon
- v-if="shouldRenderMergeTrainHelperIcon && !autoMergeLabelsEnabled"
- class="gl-mx-3"
- />
- <template v-if="showAutoMergeHelperText && autoMergeLabelsEnabled">
+ <template v-if="showAutoMergeHelperText">
<div
class="gl-ml-4 gl-text-gray-500 gl-font-sm"
data-qa-selector="auto_merge_helper_text"
+ data-testid="auto-merge-helper-text"
>
{{ autoMergeHelperText }}
</div>
- <help-popover class="gl-ml-2" :options="autoMergeHelpPopoverOptions">
+ <help-popover
+ class="gl-ml-2"
+ :options="autoMergeHelpPopoverOptions"
+ data-testid="auto-merge-helper-text-icon"
+ >
<gl-sprintf :message="autoMergePopoverSettings.bodyText">
<template #link="{ content }">
<gl-link
@@ -784,6 +738,21 @@ export default {
</div>
</div>
</div>
+ <merge-immediately-confirmation-dialog
+ ref="confirmationDialog"
+ :docs-url="mr.mergeImmediatelyDocsPath"
+ @mergeImmediately="onMergeImmediatelyConfirmation"
+ />
+ <merge-train-failed-pipeline-confirmation-dialog
+ :visible="isPipelineFailedModalVisibleMergeTrain"
+ @startMergeTrain="onStartMergeTrainConfirmation"
+ @cancel="isPipelineFailedModalVisibleMergeTrain = false"
+ />
+ <merge-failed-pipeline-confirmation-dialog
+ :visible="isPipelineFailedModalVisibleNormalMerge"
+ @mergeWithFailedPipeline="onMergeWithFailedPipelineConfirmation"
+ @cancel="isPipelineFailedModalVisibleNormalMerge = false"
+ />
</template>
</div>
</template>
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 6655af92a55..c38c253564a 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
@@ -23,7 +23,7 @@ export default {
default: () => [],
},
},
- data: () => {
+ data() {
return {
timeout: null,
updatingTooltip: false,
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 334fc01c9f7..258fa4edcda 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,16 +5,24 @@ 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'),
},
+
props: {
mr: {
type: Object,
required: true,
},
},
+
computed: {
+ terraformPlansWidget() {
+ return this.mr.terraformReportsPath && 'MrTerraformWidget';
+ },
+
widgets() {
- return ['MrSecurityWidget'];
+ return [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 cdce7c6625a..ec979861283 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
@@ -30,6 +30,11 @@ export default {
required: false,
default: 2,
},
+ rowIndex: {
+ type: Number,
+ required: false,
+ default: -1,
+ },
},
computed: {
statusIcon() {
@@ -44,6 +49,9 @@ export default {
generatedSupportingText() {
return generateText(this.data.supportingText);
},
+ shouldShowThirdLevel() {
+ return this.data.children?.length > 0 && this.level === 2;
+ },
},
methods: {
onClickedAction(action) {
@@ -60,16 +68,19 @@ export default {
:widget-name="widgetName"
:header="data.header"
:help-popover="data.helpPopover"
+ :class="{ 'gl-border-top-0': rowIndex === 0 }"
>
<template #body>
- <div class="gl-display-flex gl-flex-direction-column">
- <div>
- <p v-safe-html="generatedText" class="gl-mb-0"></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>
- <gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'">
- {{ data.badge.text }}
- </gl-badge>
+ <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>
+ <gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'">
+ {{ data.badge.text }}
+ </gl-badge>
+ </div>
<actions
:widget="widgetName"
:tertiary-buttons="data.actions"
@@ -78,10 +89,7 @@ export default {
/>
<p v-if="data.subtext" v-safe-html="generatedSubtext" class="gl-m-0 gl-font-sm"></p>
</div>
- <ul
- v-if="data.children && data.children.length > 0 && level === 2"
- class="gl-m-0 gl-p-0 gl-list-style-none"
- >
+ <ul v-if="shouldShowThirdLevel" class="gl-m-0 gl-p-0 gl-list-style-none">
<li v-for="(childData, index) in data.children" :key="childData.id || index">
<dynamic-content
:data="childData"
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 54eb15c8ac8..e327d848d8f 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
@@ -10,6 +10,7 @@ import HelpPopover from '~/vue_shared/components/help_popover.vue';
import { DynamicScroller, DynamicScrollerItem } from 'vendor/vue-virtual-scroller';
import { EXTENSION_ICONS } from '../../constants';
import { createTelemetryHub } from '../extensions/telemetry';
+import { generateText } from '../extensions/utils';
import ContentRow from './widget_content_row.vue';
import DynamicContent from './dynamic_content.vue';
import StatusIcon from './status_icon.vue';
@@ -72,9 +73,12 @@ export default {
},
// If the summary slot is not used, this value will be used as a fallback.
summary: {
- type: String,
+ type: Object,
required: false,
default: undefined,
+ validator: (s) => {
+ return Boolean(s.title);
+ },
},
// If the content slot is not used, this value will be used as a fallback.
content: {
@@ -154,7 +158,7 @@ export default {
return {
isExpandedForTheFirstTime: true,
isCollapsed: true,
- isLoading: false,
+ isLoading: true,
isLoadingExpandedContent: false,
summaryError: null,
contentError: null,
@@ -162,6 +166,12 @@ export default {
};
},
computed: {
+ generatedSummary() {
+ return generateText(this.summary?.title || '');
+ },
+ generatedSubSummary() {
+ return generateText(this.summary?.subtitle || '');
+ },
collapseButtonLabel() {
return sprintf(this.isCollapsed ? __('Show details') : __('Hide details'));
},
@@ -171,6 +181,9 @@ export default {
hasActionButtons() {
return this.actionButtons.length > 0 || Boolean(this.$scopedSlots['action-buttons']);
},
+ contentWithKeyField() {
+ return this.content?.map((item, index) => ({ ...item, id: item.id || index }));
+ },
},
watch: {
hasError: {
@@ -289,7 +302,7 @@ export default {
<template>
<section class="media-section" data-testid="widget-extension">
- <div class="gl-px-5 gl-pr-4 gl-py-4 gl-align-items-center gl-display-flex">
+ <div class="gl-px-5 gl-pr-4 gl-py-4 gl-display-flex">
<status-icon
:level="1"
:name="widgetName"
@@ -302,7 +315,14 @@ 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">{{ isLoading ? loadingText : summary }}</slot>
+ <slot v-else name="summary"
+ ><div v-safe-html="isLoading ? loadingText : generatedSummary"></div>
+ <div
+ v-if="!isLoading && generatedSubSummary"
+ v-safe-html="generatedSubSummary"
+ class="gl-font-sm gl-text-gray-700"
+ ></div
+ ></slot>
</div>
<div class="gl-display-flex">
<help-popover
@@ -336,7 +356,7 @@ export default {
</slot>
</div>
<div
- v-if="isCollapsible"
+ v-if="isCollapsible && !isLoading"
class="gl-border-l-1 gl-border-l-solid gl-border-gray-100 gl-ml-3 gl-pl-3 gl-h-6"
>
<gl-button
@@ -376,8 +396,8 @@ export default {
<div v-else class="gl-w-full">
<slot name="content">
<dynamic-scroller
- v-if="content"
- :items="content"
+ v-if="contentWithKeyField"
+ :items="contentWithKeyField"
:min-item-size="32"
:style="{ maxHeight: '170px' }"
data-testid="dynamic-content-scroller"
@@ -390,6 +410,9 @@ export default {
:data="item"
:widget-name="widgetName"
:level="2"
+ :row-index="index"
+ data-testid="extension-list-item"
+ @clickedAction="onActionClick"
/>
</dynamic-scroller-item>
</template>