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
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-08-15 18:09:53 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-08-15 18:09:53 +0300
commit51d5328e82229d69456d3a43dd2cf10518bf64c7 (patch)
treecca8c7aaea10c92398e03ae182b76c5cc514185c /app
parent14771dc276c9cfdeed1a3915ee29301cd848b475 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/content_editor/extensions/image.js6
-rw-r--r--app/assets/javascripts/content_editor/extensions/link.js5
-rw-r--r--app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js17
-rw-r--r--app/assets/javascripts/content_editor/services/serialization_helpers.js13
-rw-r--r--app/assets/javascripts/issuable/popover/components/issue_popover.vue7
-rw-r--r--app/assets/javascripts/issuable/popover/queries/issue.query.graphql1
-rw-r--r--app/assets/javascripts/issues/list/components/issues_list_app.vue1
-rw-r--r--app/assets/javascripts/issues/list/queries/issue.fragment.graphql1
-rw-r--r--app/assets/javascripts/lib/gfm/index.js32
-rw-r--r--app/assets/javascripts/runner/admin_runners/admin_runners_app.vue11
-rw-r--r--app/assets/javascripts/runner/components/runner_bulk_delete.vue16
-rw-r--r--app/assets/javascripts/runner/components/runner_bulk_delete_checkbox.vue59
-rw-r--r--app/assets/javascripts/runner/components/runner_list.vue15
-rw-r--r--app/assets/javascripts/runner/graphql/list/local_state.js7
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/action_buttons.vue (renamed from app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue)12
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/added_commit_message.vue48
-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/child_content.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/state_container.vue55
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue66
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue44
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue61
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue127
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue116
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue13
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue30
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue39
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue26
-rw-r--r--app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue8
-rw-r--r--app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue6
-rw-r--r--app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue20
-rw-r--r--app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue7
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail.vue36
-rw-r--r--app/assets/javascripts/work_items/components/work_item_type_icon.vue44
-rw-r--r--app/assets/javascripts/work_items/constants.js24
-rw-r--r--app/assets/javascripts/work_items/graphql/work_item.fragment.graphql1
-rw-r--r--app/assets/stylesheets/page_bundles/merge_requests.scss32
-rw-r--r--app/helpers/issuables_helper.rb8
-rw-r--r--app/helpers/nav/top_nav_helper.rb7
-rw-r--r--app/models/work_items/type.rb2
40 files changed, 707 insertions, 320 deletions
diff --git a/app/assets/javascripts/content_editor/extensions/image.js b/app/assets/javascripts/content_editor/extensions/image.js
index 25f976f524f..65849ec4d0d 100644
--- a/app/assets/javascripts/content_editor/extensions/image.js
+++ b/app/assets/javascripts/content_editor/extensions/image.js
@@ -34,6 +34,7 @@ export default Image.extend({
canonicalSrc: {
default: null,
parseHTML: (element) => element.dataset.canonicalSrc,
+ renderHTML: () => '',
},
alt: {
default: null,
@@ -51,6 +52,10 @@ export default Image.extend({
return img.getAttribute('title');
},
},
+ isReference: {
+ default: false,
+ renderHTML: () => '',
+ },
};
},
parseHTML() {
@@ -71,7 +76,6 @@ export default Image.extend({
src: HTMLAttributes.src,
alt: HTMLAttributes.alt,
title: HTMLAttributes.title,
- 'data-canonical-src': HTMLAttributes.canonicalSrc,
},
];
},
diff --git a/app/assets/javascripts/content_editor/extensions/link.js b/app/assets/javascripts/content_editor/extensions/link.js
index f9b12f631fe..e985e561fda 100644
--- a/app/assets/javascripts/content_editor/extensions/link.js
+++ b/app/assets/javascripts/content_editor/extensions/link.js
@@ -56,6 +56,11 @@ export default Link.extend({
canonicalSrc: {
default: null,
parseHTML: (element) => element.dataset.canonicalSrc,
+ renderHTML: () => '',
+ },
+ isReference: {
+ default: false,
+ renderHTML: () => '',
},
};
},
diff --git a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js
index 66923a4eb4d..d9c60ade00a 100644
--- a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js
+++ b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js
@@ -126,7 +126,8 @@ const factorySpecs = {
selector: 'img',
getAttrs: (hastNode) => ({
src: hastNode.properties.src,
- canonicalSrc: hastNode.properties.src,
+ canonicalSrc: hastNode.properties.identifier ?? hastNode.properties.src,
+ isReference: hastNode.properties.isReference === 'true',
title: hastNode.properties.title,
alt: hastNode.properties.alt,
}),
@@ -156,8 +157,9 @@ const factorySpecs = {
type: 'mark',
selector: 'a',
getAttrs: (hastNode) => ({
- canonicalSrc: hastNode.properties.href,
+ canonicalSrc: hastNode.properties.identifier ?? hastNode.properties.href,
href: hastNode.properties.href,
+ isReference: hastNode.properties.isReference === 'true',
title: hastNode.properties.title,
}),
},
@@ -185,7 +187,7 @@ const factorySpecs = {
},
};
-const SANITIZE_ALLOWLIST = ['level', 'identifier', 'numeric', 'language', 'url'];
+const SANITIZE_ALLOWLIST = ['level', 'identifier', 'numeric', 'language', 'url', 'isReference'];
const sanitizeAttribute = (attributeName, attributeValue, hastNode) => {
if (!attributeValue || SANITIZE_ALLOWLIST.includes(attributeName)) {
@@ -230,7 +232,14 @@ export default () => {
attributeTransformer,
markdown,
}),
- skipRendering: ['footnoteReference', 'footnoteDefinition', 'code', 'definition'],
+ skipRendering: [
+ 'footnoteReference',
+ 'footnoteDefinition',
+ 'code',
+ 'definition',
+ 'linkReference',
+ 'imageReference',
+ ],
});
return { document };
diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js
index f9ab1c1d959..41114571df7 100644
--- a/app/assets/javascripts/content_editor/services/serialization_helpers.js
+++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js
@@ -307,12 +307,15 @@ export function renderHardBreak(state, node, parent, index) {
}
export function renderImage(state, node) {
- const { alt, canonicalSrc, src, title } = node.attrs;
+ const { alt, canonicalSrc, src, title, isReference } = node.attrs;
if (isString(src) || isString(canonicalSrc)) {
const quotedTitle = title ? ` ${state.quote(title)}` : '';
+ const sourceExpression = isReference
+ ? `[${canonicalSrc}]`
+ : `(${state.esc(canonicalSrc || src)}${quotedTitle})`;
- state.write(`![${state.esc(alt || '')}](${state.esc(canonicalSrc || src)}${quotedTitle})`);
+ state.write(`![${state.esc(alt || '')}]${sourceExpression}`);
}
}
@@ -587,7 +590,11 @@ export const link = {
return isBracketAutoLink(mark.attrs.sourceMarkdown) ? '>' : '';
}
- const { canonicalSrc, href, title, sourceMarkdown } = mark.attrs;
+ const { canonicalSrc, href, title, sourceMarkdown, isReference } = mark.attrs;
+
+ if (isReference) {
+ return `][${state.esc(canonicalSrc || href)}]`;
+ }
if (linkType(sourceMarkdown) === LINK_HTML) {
return closeTag('a');
diff --git a/app/assets/javascripts/issuable/popover/components/issue_popover.vue b/app/assets/javascripts/issuable/popover/components/issue_popover.vue
index 4ae6de4783c..945a3782642 100644
--- a/app/assets/javascripts/issuable/popover/components/issue_popover.vue
+++ b/app/assets/javascripts/issuable/popover/components/issue_popover.vue
@@ -6,6 +6,7 @@ import IssueMilestone from '~/issuable/components/issue_milestone.vue';
import StatusBox from '~/issuable/components/status_box.vue';
import { IssuableStatus } from '~/issues/constants';
import timeagoMixin from '~/vue_shared/mixins/timeago';
+import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
export default {
components: {
@@ -16,6 +17,7 @@ export default {
IssueMilestone,
IssueWeight: () => import('ee_component/boards/components/issue_card_weight.vue'),
StatusBox,
+ WorkItemTypeIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -96,8 +98,9 @@ export default {
</div>
<h5 v-if="!$apollo.queries.issue.loading" class="gl-my-3">{{ title }}</h5>
<!-- eslint-disable @gitlab/vue-require-i18n-strings -->
- <div class="gl-text-secondary">
- {{ `${projectPath}#${iid}` }}
+ <div>
+ <work-item-type-icon v-if="!$apollo.queries.issue.loading" :work-item-type="issue.type" />
+ <span class="gl-text-secondary">{{ `${projectPath}#${iid}` }}</span>
</div>
<!-- eslint-enable @gitlab/vue-require-i18n-strings -->
diff --git a/app/assets/javascripts/issuable/popover/queries/issue.query.graphql b/app/assets/javascripts/issuable/popover/queries/issue.query.graphql
index 5a07d11c579..1bbe6cb6eac 100644
--- a/app/assets/javascripts/issuable/popover/queries/issue.query.graphql
+++ b/app/assets/javascripts/issuable/popover/queries/issue.query.graphql
@@ -14,6 +14,7 @@ query issue($projectPath: ID!, $iid: String!) {
startDate
dueDate
}
+ type
}
}
}
diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue
index d6acd700148..11911adb401 100644
--- a/app/assets/javascripts/issues/list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue
@@ -774,6 +774,7 @@ export default {
:show-page-size-change-controls="showPageSizeControls"
:has-next-page="pageInfo.hasNextPage"
:has-previous-page="pageInfo.hasPreviousPage"
+ show-work-item-type-icon
@click-tab="handleClickTab"
@dismiss-alert="handleDismissAlert"
@filter="handleFilter"
diff --git a/app/assets/javascripts/issues/list/queries/issue.fragment.graphql b/app/assets/javascripts/issues/list/queries/issue.fragment.graphql
index 35762120f71..99c3b6b11bf 100644
--- a/app/assets/javascripts/issues/list/queries/issue.fragment.graphql
+++ b/app/assets/javascripts/issues/list/queries/issue.fragment.graphql
@@ -18,6 +18,7 @@ fragment IssueFragment on Issue {
userDiscussionsCount @include(if: $isSignedIn)
webPath
webUrl
+ type
assignees @skip(if: $hideUsers) {
nodes {
__typename
diff --git a/app/assets/javascripts/lib/gfm/index.js b/app/assets/javascripts/lib/gfm/index.js
index 05534401a2a..1ab9fc22705 100644
--- a/app/assets/javascripts/lib/gfm/index.js
+++ b/app/assets/javascripts/lib/gfm/index.js
@@ -1,4 +1,5 @@
import { pick } from 'lodash';
+import normalize from 'mdurl/encode';
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkGfm from 'remark-gfm';
@@ -29,6 +30,37 @@ const skipRenderingHandlers = {
[{ type: 'text', value: `[${node.identifier}]: ${node.url}${title}` }],
);
},
+ linkReference: (h, node) => {
+ const definition = h.definition(node.identifier);
+
+ return h(
+ node.position,
+ 'a',
+ {
+ href: normalize(definition.url ?? ''),
+ identifier: node.identifier,
+ isReference: 'true',
+ title: definition.title,
+ },
+ all(h, node),
+ );
+ },
+ imageReference: (h, node) => {
+ const definition = h.definition(node.identifier);
+
+ return h(
+ node.position,
+ 'img',
+ {
+ src: normalize(definition.url ?? ''),
+ alt: node.alt,
+ identifier: node.identifier,
+ isReference: 'true',
+ title: definition.title,
+ },
+ all(h, node),
+ );
+ },
};
const createParser = ({ skipRendering = [] }) => {
diff --git a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
index c66a1eecfc5..777a332333d 100644
--- a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
+++ b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
@@ -17,6 +17,7 @@ import allRunnersCountQuery from 'ee_else_ce/runner/graphql/list/all_runners_cou
import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue';
import RunnerBulkDelete from '../components/runner_bulk_delete.vue';
+import RunnerBulkDeleteCheckbox from '../components/runner_bulk_delete_checkbox.vue';
import RunnerList from '../components/runner_list.vue';
import RunnerListEmptyState from '../components/runner_list_empty_state.vue';
import RunnerName from '../components/runner_name.vue';
@@ -38,6 +39,7 @@ export default {
RegistrationDropdown,
RunnerFilteredSearchBar,
RunnerBulkDelete,
+ RunnerBulkDeleteCheckbox,
RunnerList,
RunnerListEmptyState,
RunnerName,
@@ -203,13 +205,20 @@ export default {
:filtered-svg-path="emptyStateFilteredSvgPath"
/>
<template v-else>
- <runner-bulk-delete v-if="isBulkDeleteEnabled" @deleted="onDeleted" />
+ <runner-bulk-delete
+ v-if="isBulkDeleteEnabled"
+ :runners="runners.items"
+ @deleted="onDeleted"
+ />
<runner-list
:runners="runners.items"
:loading="runnersLoading"
:checkable="isBulkDeleteEnabled"
@checked="onChecked"
>
+ <template v-if="isBulkDeleteEnabled" #head-checkbox>
+ <runner-bulk-delete-checkbox :runners="runners.items" />
+ </template>
<template #runner-name="{ runner }">
<gl-link :href="runner.adminUrl">
<runner-name :runner="runner" />
diff --git a/app/assets/javascripts/runner/components/runner_bulk_delete.vue b/app/assets/javascripts/runner/components/runner_bulk_delete.vue
index fd55487c458..703da01d9c8 100644
--- a/app/assets/javascripts/runner/components/runner_bulk_delete.vue
+++ b/app/assets/javascripts/runner/components/runner_bulk_delete.vue
@@ -16,6 +16,13 @@ export default {
GlModal: GlModalDirective,
},
inject: ['localMutations'],
+ props: {
+ runners: {
+ type: Array,
+ default: () => [],
+ required: false,
+ },
+ },
data() {
return {
isDeleting: false,
@@ -28,8 +35,13 @@ export default {
},
},
computed: {
+ currentCheckedRunnerIds() {
+ return this.runners
+ .map(({ id }) => id)
+ .filter((id) => this.checkedRunnerIds.indexOf(id) >= 0);
+ },
checkedCount() {
- return this.checkedRunnerIds.length || 0;
+ return this.currentCheckedRunnerIds.length || 0;
},
bannerMessage() {
return sprintf(
@@ -98,7 +110,7 @@ export default {
mutation: BulkRunnerDelete,
variables: {
input: {
- ids: this.checkedRunnerIds,
+ ids: this.currentCheckedRunnerIds,
},
},
update: (cache, { data }) => {
diff --git a/app/assets/javascripts/runner/components/runner_bulk_delete_checkbox.vue b/app/assets/javascripts/runner/components/runner_bulk_delete_checkbox.vue
new file mode 100644
index 00000000000..dde5a5a4a05
--- /dev/null
+++ b/app/assets/javascripts/runner/components/runner_bulk_delete_checkbox.vue
@@ -0,0 +1,59 @@
+<script>
+import { GlFormCheckbox } from '@gitlab/ui';
+import checkedRunnerIdsQuery from '../graphql/list/checked_runner_ids.query.graphql';
+
+export default {
+ components: {
+ GlFormCheckbox,
+ },
+ inject: ['localMutations'],
+ props: {
+ runners: {
+ type: Array,
+ default: () => [],
+ required: false,
+ },
+ },
+ data() {
+ return {
+ checkedRunnerIds: [],
+ };
+ },
+ apollo: {
+ checkedRunnerIds: {
+ query: checkedRunnerIdsQuery,
+ },
+ },
+ computed: {
+ disabled() {
+ return !this.runners.length;
+ },
+ checked() {
+ return Boolean(this.runners.length) && this.runners.every(this.isChecked);
+ },
+ indeterminate() {
+ return !this.checked && this.runners.some(this.isChecked);
+ },
+ },
+ methods: {
+ isChecked({ id }) {
+ return this.checkedRunnerIds.indexOf(id) >= 0;
+ },
+ onChange($event) {
+ this.localMutations.setRunnersChecked({
+ runners: this.runners,
+ isChecked: $event,
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-form-checkbox
+ :indeterminate="indeterminate"
+ :checked="checked"
+ :disabled="disabled"
+ @change="onChange"
+ />
+</template>
diff --git a/app/assets/javascripts/runner/components/runner_list.vue b/app/assets/javascripts/runner/components/runner_list.vue
index f1f99c728c5..2e406f71792 100644
--- a/app/assets/javascripts/runner/components/runner_list.vue
+++ b/app/assets/javascripts/runner/components/runner_list.vue
@@ -1,5 +1,5 @@
<script>
-import { GlTableLite, GlTooltipDirective, GlSkeletonLoader } from '@gitlab/ui';
+import { GlFormCheckbox, GlTableLite, GlTooltipDirective, GlSkeletonLoader } from '@gitlab/ui';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __, s__ } from '~/locale';
@@ -23,6 +23,7 @@ const defaultFields = [
export default {
components: {
+ GlFormCheckbox,
GlTableLite,
GlSkeletonLoader,
TooltipOnTruncate,
@@ -123,19 +124,11 @@ export default {
fixed
>
<template #head(checkbox)>
- <!--
- Checkbox to select all to be added here
- See https://gitlab.com/gitlab-org/gitlab/-/issues/339525/
- -->
- <span></span>
+ <slot name="head-checkbox"></slot>
</template>
<template #cell(checkbox)="{ item }">
- <input
- type="checkbox"
- :checked="isChecked(item)"
- @change="onCheckboxChange(item, $event.target.checked)"
- />
+ <gl-form-checkbox :checked="isChecked(item)" @change="onCheckboxChange(item, $event)" />
</template>
<template #head(status)="{ label }">
diff --git a/app/assets/javascripts/runner/graphql/list/local_state.js b/app/assets/javascripts/runner/graphql/list/local_state.js
index ce6d125311c..154af261bba 100644
--- a/app/assets/javascripts/runner/graphql/list/local_state.js
+++ b/app/assets/javascripts/runner/graphql/list/local_state.js
@@ -57,6 +57,13 @@ export const createLocalState = () => {
[runner.id]: isChecked,
});
},
+ setRunnersChecked({ runners, isChecked }) {
+ const newVal = runners.reduce(
+ (acc, { id }) => ({ ...acc, [id]: isChecked }),
+ checkedRunnerIdsVar(),
+ );
+ checkedRunnerIdsVar(newVal);
+ },
clearChecked() {
checkedRunnerIdsVar({});
},
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue b/app/assets/javascripts/vue_merge_request_widget/components/action_buttons.vue
index b76d5d90ead..38f40e8a3c8 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/action_buttons.vue
@@ -14,7 +14,8 @@ export default {
props: {
widget: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
tertiaryButtons: {
type: Array,
@@ -30,6 +31,8 @@ export default {
},
computed: {
dropdownLabel() {
+ if (!this.widget) return undefined;
+
return sprintf(__('%{widget} options'), { widget: this.widget });
},
},
@@ -85,6 +88,7 @@ export default {
:href="btn.href"
:target="btn.target"
:data-clipboard-text="btn.dataClipboardText"
+ :data-method="btn.dataMethod"
@click="onClickAction(btn)"
>
{{ btn.text }}
@@ -99,11 +103,15 @@ export default {
:title="setTooltip(btn)"
:href="btn.href"
:target="btn.target"
- :class="{ 'gl-mr-3': index !== tertiaryButtons.length - 1 }"
+ :class="[{ 'gl-mr-3': index !== tertiaryButtons.length - 1 }, btn.class]"
:data-clipboard-text="btn.dataClipboardText"
+ :data-qa-selector="btn.dataQaSelector"
+ :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"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/added_commit_message.vue b/app/assets/javascripts/vue_merge_request_widget/components/added_commit_message.vue
index 266f1fb594b..254b280bf14 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/added_commit_message.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/added_commit_message.vue
@@ -2,9 +2,9 @@
import { GlSprintf } from '@gitlab/ui';
import { escape } from 'lodash';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { n__, s__ } from '~/locale';
+import { n__, s__, sprintf } from '~/locale';
-const mergeCommitCount = s__('mrWidgetCommitsAdded|1 merge commit');
+const mergeCommitCount = s__('mrWidgetCommitsAdded|%{strongStart}1%{strongEnd} merge commit');
export default {
components: {
@@ -49,7 +49,16 @@ export default {
return escape(this.targetBranch);
},
commitsCountMessage() {
- return n__('%d commit', '%d commits', this.isSquashEnabled ? 1 : this.commitsCount);
+ const count = this.isSquashEnabled ? 1 : this.commitsCount;
+
+ return sprintf(
+ n__(
+ '%{strongStart}%{count}%{strongEnd} commit',
+ '%{strongStart}%{count}%{strongEnd} commits',
+ count,
+ ),
+ { count },
+ );
},
message() {
if (this.state === 'closed') {
@@ -68,10 +77,17 @@ export default {
},
squashCommitMessage() {
if (this.isMerged) {
- return s__('mergedCommitsAdded|(commits were squashed)');
+ return s__('mergedCommitsAdded| (commits were squashed)');
}
- return n__('(squashes %d commit)', '(squashes %d commits)', this.commitsCount);
+ return sprintf(
+ n__(
+ ' (squashes %{strongStart}%{count}%{strongEnd} commit)',
+ ' (squashes %{strongStart}%{count}%{strongEnd} commits)',
+ this.commitsCount,
+ ),
+ { count: this.commitsCount },
+ );
},
},
mergeCommitCount,
@@ -82,16 +98,30 @@ export default {
<span>
<gl-sprintf :message="message">
<template #commitCount>
- <span class="commits-count-message">{{ commitsCountMessage }}</span>
+ <gl-sprintf :message="commitsCountMessage">
+ <template #strong="{ content }">
+ <span class="gl-font-weight-bold">{{ content }}</span>
+ </template>
+ </gl-sprintf>
</template>
<template #mergeCommitCount>
- <span>{{ $options.mergeCommitCount }}</span>
+ <gl-sprintf :message="$options.mergeCommitCount">
+ <template #strong="{ content }">
+ <span class="gl-font-weight-bold">{{ content }}</span>
+ </template>
+ </gl-sprintf>
</template>
<template #targetBranch>
- <span class="label-branch">{{ targetBranchEscaped }}</span>
+ <span class="label-branch gl-font-weight-bold">{{ targetBranchEscaped }}</span>
</template>
<template #squashedCommits>
- <template v-if="isSquashEnabled"> {{ squashCommitMessage }}</template>
+ <template v-if="isSquashEnabled">
+ <gl-sprintf :message="squashCommitMessage">
+ <template #strong="{ content }">
+ <span class="gl-font-weight-bold">{{ content }}</span>
+ </template>
+ </gl-sprintf>
+ </template>
</template>
<template #mergeCommitSha>
<span class="label-branch">{{ mergeCommitSha }}</span>
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 410331004e4..414c5bf9691 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
@@ -12,8 +12,8 @@ import { sprintf, s__, __ } from '~/locale';
import Poll from '~/lib/utils/poll';
import { normalizeHeaders } from '~/lib/utils/common_utils';
import { EXTENSION_ICON_CLASS, EXTENSION_ICONS } from '../../constants';
+import Actions from '../action_buttons.vue';
import StatusIcon from './status_icon.vue';
-import Actions from './actions.vue';
import ChildContent from './child_content.vue';
import { createTelemetryHub } from './telemetry';
import { generateText } from './utils';
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue
index 38f83a61b30..1eccc7de660 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue
@@ -1,7 +1,7 @@
<script>
import { GlBadge, GlLink, GlSafeHtmlDirective, GlModalDirective } from '@gitlab/ui';
+import Actions from '../action_buttons.vue';
import StatusIcon from './status_icon.vue';
-import Actions from './actions.vue';
import { generateText } from './utils';
export default {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/state_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/state_container.vue
new file mode 100644
index 00000000000..4a5a03fb598
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/state_container.vue
@@ -0,0 +1,55 @@
+<script>
+import StatusIcon from './mr_widget_status_icon.vue';
+import Actions from './action_buttons.vue';
+
+export default {
+ components: {
+ StatusIcon,
+ Actions,
+ },
+ props: {
+ isLoading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ status: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ actions: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="mr-widget-body media">
+ <div v-if="isLoading" class="gl-w-full mr-conflict-loader">
+ <slot name="loading"></slot>
+ </div>
+ <template v-else>
+ <slot name="icon">
+ <status-icon :status="status" />
+ </slot>
+ <div
+ :class="{ 'gl-display-flex': actions.length, 'gl-md-display-flex': !actions.length }"
+ class="media-body"
+ >
+ <slot></slot>
+ <div
+ :class="{ 'gl-flex-direction-column-reverse': !actions.length }"
+ class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto gl-mt-1"
+ >
+ <slot name="actions">
+ <actions v-if="actions.length" :tertiary-buttons="actions" />
+ </slot>
+ </div>
+ </div>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
index 63bd8f3d8dc..690acc9a6dc 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
@@ -1,5 +1,5 @@
<script>
-import { GlSkeletonLoader, GlIcon, GlButton, GlSprintf } from '@gitlab/ui';
+import { GlSkeletonLoader, GlIcon, GlSprintf } from '@gitlab/ui';
import autoMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/auto_merge';
import autoMergeEnabledQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql';
import createFlash from '~/flash';
@@ -9,6 +9,7 @@ import { AUTO_MERGE_STRATEGIES } from '../../constants';
import eventHub from '../../event_hub';
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
import MrWidgetAuthor from '../mr_widget_author.vue';
+import StateContainer from '../state_container.vue';
export default {
name: 'MRWidgetAutoMergeEnabled',
@@ -28,8 +29,8 @@ export default {
MrWidgetAuthor,
GlSkeletonLoader,
GlIcon,
- GlButton,
GlSprintf,
+ StateContainer,
},
mixins: [autoMergeMixin, glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
props: {
@@ -77,6 +78,26 @@ export default {
autoMergeStrategy() {
return (this.glFeatures.mergeRequestWidgetGraphql ? this.state : this.mr).autoMergeStrategy;
},
+ actions() {
+ const actions = [];
+
+ if (this.loading) {
+ return actions;
+ }
+
+ if (this.mr.canCancelAutomaticMerge) {
+ actions.push({
+ text: this.cancelButtonText,
+ loading: this.isCancellingAutoMerge,
+ dataQaSelector: 'cancel_auto_merge_button',
+ class: 'js-cancel-auto-merge',
+ testId: 'cancelAutomaticMergeButton',
+ onClick: () => this.cancelAutomaticMerge(),
+ });
+ }
+
+ return actions;
+ },
},
methods: {
cancelAutomaticMerge() {
@@ -130,38 +151,25 @@ export default {
};
</script>
<template>
- <div class="mr-widget-body media">
- <div v-if="loading" class="gl-w-full mr-conflict-loader">
+ <state-container status="scheduled" :is-loading="loading" :actions="actions">
+ <template #loading>
<gl-skeleton-loader :width="334" :height="30">
<rect x="0" y="3" width="24" height="24" rx="4" />
<rect x="32" y="7" width="150" height="16" rx="4" />
<rect x="190" y="7" width="144" height="16" rx="4" />
</gl-skeleton-loader>
- </div>
- <template v-else>
+ </template>
+ <template v-if="!loading">
+ <h4 class="gl-mr-3" data-testid="statusText">
+ <gl-sprintf :message="statusText" data-testid="statusText">
+ <template #merge_author>
+ <mr-widget-author :author="mergeUser" />
+ </template>
+ </gl-sprintf>
+ </h4>
+ </template>
+ <template v-if="!loading" #icon>
<gl-icon name="status_scheduled" :size="24" class="gl-text-blue-500 gl-mr-3 gl-mt-1" />
- <div class="media-body">
- <h4 class="gl-display-flex">
- <span class="gl-mr-3">
- <gl-sprintf :message="statusText" data-testid="statusText">
- <template #merge_author>
- <mr-widget-author :author="mergeUser" />
- </template>
- </gl-sprintf>
- </span>
- <gl-button
- v-if="mr.canCancelAutomaticMerge"
- :loading="isCancellingAutoMerge"
- size="small"
- class="js-cancel-auto-merge"
- data-qa-selector="cancel_auto_merge_button"
- data-testid="cancelAutomaticMergeButton"
- @click="cancelAutomaticMerge"
- >
- {{ cancelButtonText }}
- </gl-button>
- </h4>
- </div>
</template>
- </div>
+ </state-container>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
index 1a764d3d091..b0cda85f361 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
@@ -1,17 +1,15 @@
<script>
-import { GlLoadingIcon, GlButton } from '@gitlab/ui';
+import { s__ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import eventHub from '../../event_hub';
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
import autoMergeFailedQuery from '../../queries/states/auto_merge_failed.query.graphql';
-import statusIcon from '../mr_widget_status_icon.vue';
+import StateContainer from '../state_container.vue';
export default {
name: 'MRWidgetAutoMergeFailed',
components: {
- statusIcon,
- GlLoadingIcon,
- GlButton,
+ StateContainer,
},
mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
apollo: {
@@ -38,6 +36,17 @@ export default {
isRefreshing: false,
};
},
+ computed: {
+ actions() {
+ return [
+ {
+ text: s__('mrWidget|Refresh'),
+ loading: this.isRefreshing,
+ onClick: () => this.refreshWidget(),
+ },
+ ];
+ },
+ },
methods: {
refreshWidget() {
this.isRefreshing = true;
@@ -49,23 +58,10 @@ export default {
};
</script>
<template>
- <div class="mr-widget-body media">
- <status-icon status="warning" />
- <div class="media-body space-children gl-display-flex gl-flex-wrap gl-align-items-center">
- <span class="bold">
- <template v-if="mergeError">{{ mergeError }}</template>
- {{ s__('mrWidget|This merge request failed to be merged automatically') }}
- </span>
- <gl-button
- :disabled="isRefreshing"
- category="secondary"
- variant="default"
- size="small"
- @click="refreshWidget"
- >
- <gl-loading-icon v-if="isRefreshing" size="sm" :inline="true" />
- {{ s__('mrWidget|Refresh') }}
- </gl-button>
- </div>
- </div>
+ <state-container status="warning" :actions="actions">
+ <span class="bold gl-ml-0!">
+ <template v-if="mergeError">{{ mergeError }}</template>
+ {{ s__('mrWidget|This merge request failed to be merged automatically') }}
+ </span>
+ </state-container>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
index 7652d7049f2..8abd915b93e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
@@ -4,14 +4,14 @@ import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
import userPermissionsQuery from '../../queries/permissions.query.graphql';
import conflictsStateQuery from '../../queries/states/conflicts.query.graphql';
-import StatusIcon from '../mr_widget_status_icon.vue';
+import StateContainer from '../state_container.vue';
export default {
name: 'MRWidgetConflicts',
components: {
GlSkeletonLoader,
- StatusIcon,
GlButton,
+ StateContainer,
},
mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
apollo: {
@@ -86,25 +86,23 @@ export default {
};
</script>
<template>
- <div class="mr-widget-body media">
- <status-icon :show-disabled-button="true" status="warning" />
-
- <div v-if="isLoading" class="gl-ml-4 gl-w-full mr-conflict-loader">
+ <state-container status="warning" :is-loading="isLoading">
+ <template #loading>
<gl-skeleton-loader :width="334" :height="30">
<rect x="0" y="7" width="150" height="16" rx="4" />
<rect x="158" y="7" width="84" height="16" rx="4" />
<rect x="250" y="7" width="84" height="16" rx="4" />
</gl-skeleton-loader>
- </div>
- <div v-else class="media-body space-children gl-display-flex gl-align-items-center">
- <span v-if="shouldBeRebased" class="gl-ml-0! gl-text-body! bold">
+ </template>
+ <template v-if="!isLoading">
+ <span v-if="shouldBeRebased" class="bold gl-ml-0! gl-text-body!">
{{
s__(`mrWidget|Merge blocked: fast-forward merge is not possible.
To merge this request, first rebase locally.`)
}}
</span>
<template v-else>
- <span class="gl-ml-0! gl-text-body! bold">
+ <span class="bold gl-ml-0! gl-text-body! gl-flex-grow-1 gl-w-full gl-md-w-auto gl-mr-2">
{{ s__('mrWidget|Merge blocked: merge conflicts must be resolved.') }}
<span v-if="!canMerge">
{{
@@ -114,23 +112,30 @@ export default {
}}
</span>
</span>
- <gl-button
- v-if="showResolveButton"
- :href="mr.conflictResolutionPath"
- size="small"
- data-testid="resolve-conflicts-button"
- >
- {{ s__('mrWidget|Resolve conflicts') }}
- </gl-button>
- <gl-button
- v-if="canMerge"
- size="small"
- data-testid="merge-locally-button"
- class="js-check-out-modal-trigger"
- >
- {{ s__('mrWidget|Resolve locally') }}
- </gl-button>
</template>
- </div>
- </div>
+ </template>
+ <template v-if="!isLoading && !shouldBeRebased" #actions>
+ <gl-button
+ v-if="canMerge"
+ size="small"
+ variant="confirm"
+ category="secondary"
+ data-testid="merge-locally-button"
+ class="js-check-out-modal-trigger gl-align-self-start"
+ :class="{ 'gl-mr-2': showResolveButton }"
+ >
+ {{ s__('mrWidget|Resolve locally') }}
+ </gl-button>
+ <gl-button
+ v-if="showResolveButton"
+ :href="mr.conflictResolutionPath"
+ size="small"
+ variant="confirm"
+ class="gl-mb-2 gl-md-mb-0 gl-align-self-start"
+ data-testid="resolve-conflicts-button"
+ >
+ {{ s__('mrWidget|Resolve conflicts') }}
+ </gl-button>
+ </template>
+ </state-container>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
index 19df346759f..4416123cd51 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
@@ -1,5 +1,5 @@
<script>
-import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
+import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
import api from '~/api';
import createFlash from '~/flash';
import { s__, __ } from '~/locale';
@@ -7,6 +7,7 @@ import { OPEN_REVERT_MODAL, OPEN_CHERRY_PICK_MODAL } from '~/projects/commit/con
import modalEventHub from '~/projects/commit/event_hub';
import eventHub from '../../event_hub';
import MrWidgetAuthorTime from '../mr_widget_author_time.vue';
+import StateContainer from '../state_container.vue';
export default {
name: 'MRWidgetMerged',
@@ -16,7 +17,7 @@ export default {
components: {
MrWidgetAuthorTime,
GlIcon,
- GlButton,
+ StateContainer,
},
props: {
mr: {
@@ -72,6 +73,53 @@ export default {
cherryPickLabel() {
return s__('mrWidget|Cherry-pick');
},
+ actions() {
+ const actions = [];
+
+ if (this.mr.canRevertInCurrentMR) {
+ actions.push({
+ text: this.revertLabel,
+ tooltipText: this.revertTitle,
+ dataQaSelector: 'revert_button',
+ onClick: () => this.openRevertModal(),
+ });
+ } else if (this.mr.revertInForkPath) {
+ actions.push({
+ text: this.revertLabel,
+ tooltipText: this.revertTitle,
+ href: this.mr.revertInForkPath,
+ dataQaSelector: 'revert_button',
+ dataMethod: 'post',
+ });
+ }
+
+ if (this.mr.canCherryPickInCurrentMR) {
+ actions.push({
+ text: this.cherryPickLabel,
+ tooltipText: this.cherryPickTitle,
+ dataQaSelector: 'cherry_pick_button',
+ onClick: () => this.openCherryPickModal(),
+ });
+ } else if (this.mr.cherryPickInForkPath) {
+ actions.push({
+ text: this.cherryPickLabel,
+ tooltipText: this.cherryPickTitle,
+ href: this.mr.cherryPickInForkPath,
+ dataQaSelector: 'cherry_pick_button',
+ dataMethod: 'post',
+ });
+ }
+
+ if (this.shouldShowRemoveSourceBranch) {
+ actions.push({
+ text: s__('mrWidget|Delete source branch'),
+ class: 'js-remove-branch-button',
+ onClick: () => this.removeSourceBranch(),
+ });
+ }
+
+ return actions;
+ },
},
mounted() {
document.dispatchEvent(new CustomEvent('merged:UpdateActions'));
@@ -115,68 +163,15 @@ export default {
};
</script>
<template>
- <div class="mr-widget-body media">
- <gl-icon name="merge" :size="24" class="gl-text-blue-500 gl-mr-3 gl-mt-1" />
- <div class="media-body">
- <div class="space-children">
- <mr-widget-author-time
- :action-text="s__('mrWidget|Merged by')"
- :author="mr.metrics.mergedBy"
- :date-title="mr.metrics.mergedAt"
- :date-readable="mr.metrics.readableMergedAt"
- />
- <gl-button
- v-if="mr.canRevertInCurrentMR"
- v-gl-tooltip.hover
- :title="revertTitle"
- size="small"
- category="secondary"
- data-qa-selector="revert_button"
- @click="openRevertModal"
- >
- {{ revertLabel }}
- </gl-button>
- <gl-button
- v-else-if="mr.revertInForkPath"
- v-gl-tooltip.hover
- :href="mr.revertInForkPath"
- :title="revertTitle"
- size="small"
- category="secondary"
- data-method="post"
- >
- {{ revertLabel }}
- </gl-button>
- <gl-button
- v-if="mr.canCherryPickInCurrentMR"
- v-gl-tooltip.hover
- :title="cherryPickTitle"
- size="small"
- data-qa-selector="cherry_pick_button"
- @click="openCherryPickModal"
- >
- {{ cherryPickLabel }}
- </gl-button>
- <gl-button
- v-else-if="mr.cherryPickInForkPath"
- v-gl-tooltip.hover
- :href="mr.cherryPickInForkPath"
- :title="cherryPickTitle"
- size="small"
- data-method="post"
- >
- {{ cherryPickLabel }}
- </gl-button>
- <gl-button
- v-if="shouldShowRemoveSourceBranch"
- :disabled="isMakingRequest"
- size="small"
- class="js-remove-branch-button"
- @click="removeSourceBranch"
- >
- {{ s__('mrWidget|Delete source branch') }}
- </gl-button>
- </div>
- </div>
- </div>
+ <state-container :actions="actions">
+ <template #icon>
+ <gl-icon name="merge" :size="24" class="gl-text-blue-500 gl-mr-3 gl-mt-1" />
+ </template>
+ <mr-widget-author-time
+ :action-text="s__('mrWidget|Merged by')"
+ :author="mr.metrics.mergedBy"
+ :date-title="mr.metrics.mergedAt"
+ :date-readable="mr.metrics.readableMergedAt"
+ />
+ </state-container>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
index 0880fd81500..d25d1ade7eb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
@@ -8,7 +8,7 @@ import simplePoll from '~/lib/utils/simple_poll';
import eventHub from '../../event_hub';
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
import rebaseQuery from '../../queries/states/rebase.query.graphql';
-import statusIcon from '../mr_widget_status_icon.vue';
+import StateContainer from '../state_container.vue';
export default {
name: 'MRWidgetRebase',
@@ -25,9 +25,9 @@ export default {
},
},
components: {
- statusIcon,
GlSkeletonLoader,
GlButton,
+ StateContainer,
},
mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
props: {
@@ -76,6 +76,10 @@ export default {
return this.mr.targetBranch;
},
status() {
+ if (this.isLoading) {
+ return undefined;
+ }
+
if (this.rebaseInProgress || this.isMakingRequest) {
return 'loading';
}
@@ -148,69 +152,71 @@ export default {
};
</script>
<template>
- <div class="mr-widget-body media">
- <div v-if="isLoading" class="gl-w-full mr-conflict-loader">
+ <state-container :status="status" :is-loading="isLoading">
+ <template #loading>
<gl-skeleton-loader :width="334" :height="30">
<rect x="0" y="3" width="24" height="24" rx="4" />
<rect x="32" y="5" width="302" height="20" rx="4" />
</gl-skeleton-loader>
- </div>
- <template v-else>
- <status-icon :status="status" :show-disabled-button="showDisabledButton" />
-
- <div class="rebase-state-find-class-convention media media-body space-children">
+ </template>
+ <template v-if="!isLoading">
+ <span
+ v-if="rebaseInProgress || isMakingRequest"
+ class="gl-ml-0! gl-text-body! gl-font-weight-bold"
+ data-testid="rebase-message"
+ >{{ __('Rebase in progress') }}</span
+ >
+ <span
+ v-if="!rebaseInProgress && !canPushToSourceBranch"
+ class="gl-text-body! gl-font-weight-bold gl-ml-0!"
+ data-testid="rebase-message"
+ >{{ fastForwardMergeText }}</span
+ >
+ <div
+ v-if="!rebaseInProgress && canPushToSourceBranch && !isMakingRequest"
+ class="accept-merge-holder clearfix js-toggle-container media gl-md-display-flex gl-flex-wrap gl-flex-grow-1"
+ >
<span
- v-if="rebaseInProgress || isMakingRequest"
- class="gl-ml-0! gl-text-body! gl-font-weight-bold"
+ v-if="!rebasingError"
+ class="gl-font-weight-bold gl-w-100 gl-md-w-auto gl-flex-grow-1 gl-ml-0! gl-text-body! gl-md-mr-3"
data-testid="rebase-message"
- >{{ __('Rebase in progress') }}</span
+ data-qa-selector="no_fast_forward_message_content"
+ >{{
+ __('Merge blocked: the source branch must be rebased onto the target branch.')
+ }}</span
>
<span
- v-if="!rebaseInProgress && !canPushToSourceBranch"
- class="gl-text-body! gl-font-weight-bold gl-ml-0!"
+ v-else
+ class="gl-font-weight-bold danger gl-w-100 gl-md-w-auto gl-flex-grow-1 gl-md-mr-3"
data-testid="rebase-message"
- >{{ fastForwardMergeText }}</span
- >
- <div
- v-if="!rebaseInProgress && canPushToSourceBranch && !isMakingRequest"
- class="accept-merge-holder clearfix js-toggle-container accept-action media space-children gl-align-items-center"
+ >{{ rebasingError }}</span
>
- <span
- v-if="!rebasingError"
- class="gl-ml-0! gl-text-body! gl-font-weight-bold"
- data-testid="rebase-message"
- data-qa-selector="no_fast_forward_message_content"
- >{{
- __('Merge blocked: the source branch must be rebased onto the target branch.')
- }}</span
- >
- <span v-else class="gl-font-weight-bold danger" data-testid="rebase-message">{{
- rebasingError
- }}</span>
- <gl-button
- :loading="isMakingRequest"
- variant="confirm"
- size="small"
- data-qa-selector="mr_rebase_button"
- data-testid="standard-rebase-button"
- class="gl-ml-3!"
- @click="rebase"
- >
- {{ __('Rebase') }}
- </gl-button>
- <gl-button
- v-if="showRebaseWithoutCi"
- :loading="isMakingRequest"
- variant="confirm"
- size="small"
- category="secondary"
- data-testid="rebase-without-ci-button"
- @click="rebaseWithoutCi"
- >
- {{ __('Rebase without pipeline') }}
- </gl-button>
- </div>
</div>
</template>
- </div>
+ <template v-if="!isLoading" #actions>
+ <gl-button
+ v-if="showRebaseWithoutCi"
+ :loading="isMakingRequest"
+ variant="confirm"
+ size="small"
+ category="secondary"
+ data-testid="rebase-without-ci-button"
+ class="gl-align-self-start gl-mr-2"
+ @click="rebaseWithoutCi"
+ >
+ {{ __('Rebase without pipeline') }}
+ </gl-button>
+ <gl-button
+ :loading="isMakingRequest"
+ variant="confirm"
+ size="small"
+ data-qa-selector="mr_rebase_button"
+ data-testid="standard-rebase-button"
+ class="gl-mb-2 gl-md-mb-0 gl-align-self-start"
+ @click="rebase"
+ >
+ {{ __('Rebase') }}
+ </gl-button>
+ </template>
+ </state-container>
</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 884fc666276..d2c85b14999 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
@@ -335,6 +335,9 @@ export default {
? __('Did not delete the source branch.')
: __('Source branch will not be deleted.');
},
+ showMergeDetailsHeader() {
+ return ['readyToMerge'].indexOf(this.mr.state) >= 0;
+ },
},
mounted() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
@@ -524,7 +527,7 @@ export default {
</div>
</div>
<template v-else>
- <div class="mr-widget-body media mr-widget-body-line-height-1">
+ <div class="mr-widget-body mr-widget-body-ready-merge media mr-widget-body-line-height-1">
<div class="media-body">
<div class="mr-widget-body-controls gl-display-flex gl-align-items-center gl-flex-wrap">
<gl-button-group v-if="shouldShowMergeControls" class="gl-align-self-start">
@@ -651,13 +654,13 @@ export default {
</div>
<div
v-if="!shouldShowMergeControls"
- class="gl-w-full gl-order-n1 gl-text-gray-500"
+ class="gl-w-full gl-order-n1 mr-widget-merge-details"
data-qa-selector="merged_status_content"
>
- <strong v-if="mr.state !== 'closed'">
+ <p v-if="showMergeDetailsHeader" class="gl-mb-3 gl-text-gray-900">
{{ __('Merge details') }}
- </strong>
- <ul class="gl-pl-4 gl-m-0">
+ </p>
+ <ul class="gl-pl-4 gl-mb-0 gl-ml-3 gl-text-gray-600">
<li v-if="mr.divergedCommitsCount > 0" class="gl-line-height-normal">
<gl-sprintf
:message="s__('mrWidget|The source branch is %{link} the target branch')"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue
index 06f70bc456e..d149f5208fc 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue
@@ -1,13 +1,13 @@
<script>
import { GlButton } from '@gitlab/ui';
import { I18N_SHA_MISMATCH } from '../../i18n';
-import statusIcon from '../mr_widget_status_icon.vue';
+import StateContainer from '../state_container.vue';
export default {
name: 'ShaMismatch',
components: {
- statusIcon,
GlButton,
+ StateContainer,
},
i18n: {
I18N_SHA_MISMATCH,
@@ -22,24 +22,24 @@ export default {
</script>
<template>
- <div class="mr-widget-body media">
- <status-icon :show-disabled-button="false" status="warning" />
- <div class="media-body">
- <span
- class="gl-ml-0! gl-text-body! gl-font-weight-bold"
- data-qa-selector="head_mismatch_content"
- >
- {{ $options.i18n.I18N_SHA_MISMATCH.warningMessage }}
- </span>
+ <state-container status="warning">
+ <span
+ class="gl-font-weight-bold gl-md-mr-3 gl-flex-grow-1 gl-ml-0! gl-text-body!"
+ data-qa-selector="head_mismatch_content"
+ >
+ {{ $options.i18n.I18N_SHA_MISMATCH.warningMessage }}
+ </span>
+ <template #actions>
<gl-button
- class="gl-ml-3"
data-testid="action-button"
size="small"
category="primary"
variant="confirm"
+ class="gl-align-self-start"
:href="mr.mergeRequestDiffsPath"
- >{{ $options.i18n.I18N_SHA_MISMATCH.actionButtonLabel }}</gl-button
>
- </div>
- </div>
+ {{ $options.i18n.I18N_SHA_MISMATCH.actionButtonLabel }}
+ </gl-button>
+ </template>
+ </state-container>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue
index 7e9ebcf0b2e..035d62eaa59 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue
@@ -1,13 +1,13 @@
<script>
import { GlButton } from '@gitlab/ui';
import notesEventHub from '~/notes/event_hub';
-import statusIcon from '../mr_widget_status_icon.vue';
+import StateContainer from '../state_container.vue';
export default {
name: 'UnresolvedDiscussions',
components: {
- statusIcon,
GlButton,
+ StateContainer,
},
props: {
mr: {
@@ -24,30 +24,33 @@ export default {
</script>
<template>
- <div class="mr-widget-body media gl-flex-wrap">
- <status-icon show-disabled-button status="warning" />
- <div class="media-body">
- <span class="gl-ml-0! gl-text-body! gl-ml-3 gl-font-weight-bold gl-w-100">
- {{ s__('mrWidget|Merge blocked: all threads must be resolved.') }}
- </span>
+ <state-container status="warning">
+ <span
+ class="gl-ml-3 gl-font-weight-bold gl-w-100 gl-flex-grow-1 gl-md-mr-3 gl-ml-0! gl-text-body!"
+ >
+ {{ s__('mrWidget|Merge blocked: all threads must be resolved.') }}
+ </span>
+ <template #actions>
<gl-button
- data-testid="jump-to-first"
- class="gl-ml-3"
+ v-if="mr.createIssueToResolveDiscussionsPath"
+ :href="mr.createIssueToResolveDiscussionsPath"
+ class="js-create-issue gl-align-self-start gl-vertical-align-top gl-mr-2"
size="small"
variant="confirm"
category="secondary"
- @click="jumpToFirstUnresolvedDiscussion"
>
- {{ s__('mrWidget|Jump to first unresolved thread') }}
+ {{ s__('mrWidget|Create issue to resolve all threads') }}
</gl-button>
<gl-button
- v-if="mr.createIssueToResolveDiscussionsPath"
- :href="mr.createIssueToResolveDiscussionsPath"
- class="js-create-issue gl-ml-3"
+ data-testid="jump-to-first"
+ class="gl-mb-2 gl-md-mb-0 gl-align-self-start gl-vertical-align-top"
size="small"
+ variant="confirm"
+ category="primary"
+ @click="jumpToFirstUnresolvedDiscussion"
>
- {{ s__('mrWidget|Create issue to resolve all threads') }}
+ {{ s__('mrWidget|Jump to first unresolved thread') }}
</gl-button>
- </div>
- </div>
+ </template>
+ </state-container>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
index 501aee1a18d..cf7f83c014a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
@@ -12,13 +12,13 @@ import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_va
import getStateQuery from '../../queries/get_state.query.graphql';
import draftQuery from '../../queries/states/draft.query.graphql';
import removeDraftMutation from '../../queries/toggle_draft.mutation.graphql';
-import StatusIcon from '../mr_widget_status_icon.vue';
+import StateContainer from '../state_container.vue';
export default {
name: 'WorkInProgress',
components: {
- StatusIcon,
GlButton,
+ StateContainer,
},
mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
apollo: {
@@ -163,26 +163,22 @@ export default {
</script>
<template>
- <div class="mr-widget-body media">
- <status-icon :show-disabled-button="canUpdate" status="warning" />
- <div class="media-body">
- <div class="float-left">
- <span class="gl-ml-0! gl-text-body! gl-font-weight-bold">
- {{
- __("Merge blocked: merge request must be marked as ready. It's still marked as draft.")
- }}
- </span>
- </div>
+ <state-container status="warning">
+ <span class="gl-font-weight-bold gl-ml-0! gl-text-body! gl-flex-grow-1">
+ {{ __("Merge blocked: merge request must be marked as ready. It's still marked as draft.") }}
+ </span>
+ <template #actions>
<gl-button
v-if="canUpdate"
size="small"
:disabled="isMakingRequest"
:loading="isMakingRequest"
- class="js-remove-draft gl-ml-3"
+ variant="confirm"
+ class="js-remove-draft gl-md-ml-3 gl-align-self-start"
@click="handleRemoveDraft"
>
{{ s__('mrWidget|Mark as ready') }}
</gl-button>
- </div>
- </div>
+ </template>
+ </state-container>
</template>
diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue
index 59b49a4e10c..38083327593 100644
--- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue
+++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue
@@ -7,6 +7,7 @@ import { differenceInSeconds, getTimeago, SECONDS_IN_DAY } from '~/lib/utils/dat
import { isExternal, setUrlFragment } from '~/lib/utils/url_utility';
import { __, n__, sprintf } from '~/locale';
import IssuableAssignees from '~/issuable/components/issue_assignees.vue';
+import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
export default {
@@ -17,6 +18,7 @@ export default {
GlFormCheckbox,
GlSprintf,
IssuableAssignees,
+ WorkItemTypeIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -50,6 +52,11 @@ export default {
required: false,
default: false,
},
+ showWorkItemTypeIcon: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
issuableId() {
@@ -225,6 +232,7 @@ export default {
</span>
</div>
<div class="issuable-info">
+ <work-item-type-icon v-if="showWorkItemTypeIcon" :work-item-type="issuable.type" />
<slot v-if="hasSlotContents('reference')" name="reference"></slot>
<span v-else data-testid="issuable-reference" class="issuable-reference">
{{ reference }}
diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue
index 189bbb56432..bc10f84b819 100644
--- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue
+++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue
@@ -182,6 +182,11 @@ export default {
required: false,
default: false,
},
+ showWorkItemTypeIcon: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -344,6 +349,7 @@ export default {
:label-filter-param="labelFilterParam"
:show-checkbox="showBulkEditSidebar"
:checked="issuableChecked(issuable)"
+ :show-work-item-type-icon="showWorkItemTypeIcon"
@checked-input="handleIssuableCheckedInput(issuable, $event)"
>
<template #reference>
diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue
index 63ab1eb17d4..1f23fdfaafd 100644
--- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue
+++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue
@@ -12,6 +12,7 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { isExternal } from '~/lib/utils/url_utility';
import { n__, sprintf } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
import { IssuableStates } from '~/vue_shared/issuable/list/constants';
export default {
@@ -22,6 +23,7 @@ export default {
GlAvatarLink,
GlAvatarLabeled,
TimeAgoTooltip,
+ WorkItemTypeIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -65,6 +67,16 @@ export default {
required: false,
default: null,
},
+ issuableType: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ showWorkItemTypeIcon: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
badgeVariant() {
@@ -122,7 +134,13 @@ export default {
</div>
</div>
<span>
- {{ __('Created') }}
+ <template v-if="showWorkItemTypeIcon">
+ <work-item-type-icon :work-item-type="issuableType" show-text />
+ {{ __('created') }}
+ </template>
+ <template v-else>
+ {{ __('Created') }}
+ </template>
<time-ago-tooltip data-testid="startTimeItem" :time="createdAt" />
{{ __('by') }}
</span>
diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue
index 7ed93c042f8..2bc57ecba55 100644
--- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue
+++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue
@@ -87,6 +87,11 @@ export default {
required: false,
default: 0,
},
+ showWorkItemTypeIcon: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
methods: {
handleKeydownTitle(e, issuableMeta) {
@@ -110,6 +115,8 @@ export default {
:created-at="issuable.createdAt"
:author="issuable.author"
:task-completion-status="taskCompletionStatus"
+ :issuable-type="issuable.type"
+ :show-work-item-type-icon="showWorkItemTypeIcon"
>
<template #status-badge>
<slot name="status-badge"></slot>
diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index 44c9ef9e44b..7b3eeab649b 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -10,6 +10,7 @@ import {
} from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
+import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
import {
i18n,
WIDGET_TYPE_ASSIGNEES,
@@ -55,6 +56,7 @@ export default {
WorkItemWeight,
WorkItemInformation,
LocalStorageSync,
+ WorkItemTypeIcon,
},
mixins: [glFeatureFlagMixin()],
props: {
@@ -146,6 +148,9 @@ export default {
parentUrl() {
return `../../issues/${this.parentWorkItem?.iid}`;
},
+ workItemIconName() {
+ return this.workItem?.workItemType?.iconName;
+ },
},
beforeDestroy() {
/** make sure that if the user has not even dismissed the alert ,
@@ -243,16 +248,21 @@ export default {
<li
class="gl-px-4 gl-py-3 gl-line-height-0 gl-display-flex gl-align-items-center gl-overflow-hidden gl-flex-shrink-0"
>
- <gl-icon name="task-done" class="gl-mr-2 gl-flex-shrink-0" />
+ <work-item-type-icon
+ :work-item-icon-name="workItemIconName"
+ :work-item-type="workItemType && workItemType.toUpperCase()"
+ />
{{ workItemType }}
</li>
</ul>
- <span
+ <work-item-type-icon
v-else
+ :work-item-icon-name="workItemIconName"
+ :work-item-type="workItemType && workItemType.toUpperCase()"
+ show-text
class="gl-font-weight-bold gl-text-secondary gl-mr-auto"
data-testid="work-item-type"
- >{{ workItemType }}</span
- >
+ />
<gl-loading-icon v-if="updateInProgress" :inline="true" class="gl-mr-3" />
<gl-badge v-if="workItem.confidential" variant="warning" icon="eye-slash" class="gl-mr-3">{{
__('Confidential')
@@ -317,16 +327,16 @@ export default {
:can-update="canUpdate"
@error="error = $event"
/>
- <work-item-weight
- v-if="workItemWeight"
- class="gl-mb-5"
- :can-update="canUpdate"
- :weight="workItemWeight.weight"
- :work-item-id="workItem.id"
- :work-item-type="workItemType"
- @error="error = $event"
- />
</template>
+ <work-item-weight
+ v-if="workItemWeight"
+ class="gl-mb-5"
+ :can-update="canUpdate"
+ :weight="workItemWeight.weight"
+ :work-item-id="workItem.id"
+ :work-item-type="workItemType"
+ @error="error = $event"
+ />
<work-item-description
v-if="hasDescriptionWidget"
:work-item-id="workItem.id"
diff --git a/app/assets/javascripts/work_items/components/work_item_type_icon.vue b/app/assets/javascripts/work_items/components/work_item_type_icon.vue
new file mode 100644
index 00000000000..fd914fa350b
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/work_item_type_icon.vue
@@ -0,0 +1,44 @@
+<script>
+import { GlIcon } from '@gitlab/ui';
+import { WORK_ITEMS_TYPE_MAP } from '../constants';
+
+export default {
+ components: {
+ GlIcon,
+ },
+ props: {
+ workItemType: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ showText: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ workItemIconName: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ iconName() {
+ return (
+ this.workItemIconName || WORK_ITEMS_TYPE_MAP[this.workItemType]?.icon || 'issue-type-issue'
+ );
+ },
+ workItemTypeName() {
+ return WORK_ITEMS_TYPE_MAP[this.workItemType]?.name;
+ },
+ },
+};
+</script>
+
+<template>
+ <span>
+ <gl-icon :name="iconName" class="gl-mr-2" />
+ <span v-if="workItemTypeName" :class="{ 'gl-sr-only': !showText }">{{ workItemTypeName }}</span>
+ </span>
+</template>
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
index b0ade1fbe45..d89978bff82 100644
--- a/app/assets/javascripts/work_items/constants.js
+++ b/app/assets/javascripts/work_items/constants.js
@@ -21,6 +21,7 @@ export const WORK_ITEM_TYPE_ENUM_INCIDENT = 'INCIDENT';
export const WORK_ITEM_TYPE_ENUM_ISSUE = 'ISSUE';
export const WORK_ITEM_TYPE_ENUM_TASK = 'TASK';
export const WORK_ITEM_TYPE_ENUM_TEST_CASE = 'TEST_CASE';
+export const WORK_ITEM_TYPE_ENUM_REQUIREMENTS = 'REQUIREMENTS';
export const i18n = {
fetchError: s__('WorkItem|Something went wrong when fetching the work item. Please try again.'),
@@ -39,3 +40,26 @@ export const WORK_ITEM_STATUS_TEXT = {
export const WORK_ITEM_TYPE_IDS = {
TASK: 'gid://gitlab/WorkItems::Type/5',
};
+
+export const WORK_ITEMS_TYPE_MAP = {
+ [WORK_ITEM_TYPE_ENUM_INCIDENT]: {
+ icon: `issue-type-incident`,
+ name: s__('WorkItem|Incident'),
+ },
+ [WORK_ITEM_TYPE_ENUM_ISSUE]: {
+ icon: `issue-type-issue`,
+ name: s__('WorkItem|Issue'),
+ },
+ [WORK_ITEM_TYPE_ENUM_TASK]: {
+ icon: `issue-type-task`,
+ name: s__('WorkItem|Task'),
+ },
+ [WORK_ITEM_TYPE_ENUM_TEST_CASE]: {
+ icon: `issue-type-test-case`,
+ name: s__('WorkItem|Test case'),
+ },
+ [WORK_ITEM_TYPE_ENUM_REQUIREMENTS]: {
+ icon: `issue-type-requirements`,
+ name: s__('WorkItem|Requirements'),
+ },
+};
diff --git a/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql b/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql
index dacc1527a43..fd6a71ae9b4 100644
--- a/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql
+++ b/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql
@@ -9,6 +9,7 @@ fragment WorkItem on WorkItem {
workItemType {
id
name
+ iconName
}
userPermissions {
deleteWorkItem
diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss
index 1b27e51e793..b7a75884425 100644
--- a/app/assets/stylesheets/page_bundles/merge_requests.scss
+++ b/app/assets/stylesheets/page_bundles/merge_requests.scss
@@ -416,8 +416,6 @@ $tabs-holder-z-index: 250;
.label-branch {
@include gl-font-monospace;
font-size: 95%;
- color: var(--gl-text-color, $gl-text-color);
- font-weight: normal;
overflow: hidden;
word-break: break-all;
}
@@ -477,8 +475,7 @@ $tabs-holder-z-index: 250;
margin: 0 0 0 10px;
}
- .bold,
- .gl-font-weight-bold {
+ .bold {
font-weight: $gl-font-weight-bold;
color: var(--gray-600, $gray-600);
margin-left: 10px;
@@ -494,8 +491,7 @@ $tabs-holder-z-index: 250;
}
.spacing,
- .bold,
- .gl-font-weight-bold {
+ .bold {
vertical-align: middle;
}
@@ -602,6 +598,12 @@ $tabs-holder-z-index: 250;
padding: $gl-padding;
}
+.mr-widget-body-ready-merge {
+ @include media-breakpoint-down(sm) {
+ @include gl-p-3;
+ }
+}
+
.mr-widget-border-top {
border-top: 1px solid var(--border-color, $border-color);
}
@@ -820,3 +822,21 @@ $tabs-holder-z-index: 250;
height: 180px;
}
}
+
+.mr-widget-merge-details {
+ li:not(:last-child) {
+ @include gl-mb-3;
+ }
+}
+
+.mr-ready-merge-related-links,
+.mr-widget-merge-details {
+ a {
+ @include gl-text-decoration-underline;
+
+ &:hover,
+ &:focus {
+ @include gl-text-decoration-none;
+ }
+ }
+}
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 069e7316ac1..8fd004233e2 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -154,7 +154,13 @@ module IssuablesHelper
def issuable_meta(issuable, project)
output = []
- output << "Created #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
+
+ if issuable.respond_to?(:work_item_type) && WorkItems::Type::WI_TYPES_WITH_CREATED_HEADER.include?(issuable.work_item_type.base_type)
+ output << content_tag(:span, sprite_icon("#{issuable.work_item_type.icon_name}", css_class: 'gl-icon gl-vertical-align-middle'), class: 'gl-mr-2', aria: { hidden: 'true' })
+ output << s_('IssuableStatus|%{wi_type} created %{created_at} by ').html_safe % { wi_type: issuable.issue_type.capitalize, created_at: time_ago_with_tooltip(issuable.created_at) }
+ else
+ output << s_('IssuableStatus|Created %{created_at} by').html_safe % { created_at: time_ago_with_tooltip(issuable.created_at) }
+ end
if issuable.is_a?(Issue) && issuable.service_desk_reply_to
output << "#{html_escape(issuable.service_desk_reply_to)} via "
diff --git a/app/helpers/nav/top_nav_helper.rb b/app/helpers/nav/top_nav_helper.rb
index 3ceb60251c2..efec6f2d0d8 100644
--- a/app/helpers/nav/top_nav_helper.rb
+++ b/app/helpers/nav/top_nav_helper.rb
@@ -81,13 +81,6 @@ module Nav
**snippets_menu_item_attrs
)
end
-
- builder.add_secondary_menu_item(
- id: 'help',
- title: _('Help'),
- icon: 'question-o',
- href: help_path
- )
end
def build_view_model(builder:, project:, group:)
diff --git a/app/models/work_items/type.rb b/app/models/work_items/type.rb
index d715dfe041e..753fcbcb8f9 100644
--- a/app/models/work_items/type.rb
+++ b/app/models/work_items/type.rb
@@ -28,6 +28,8 @@ module WorkItems
task: [Widgets::Assignees, Widgets::Labels, Widgets::Description, Widgets::Hierarchy, Widgets::StartAndDueDate]
}.freeze
+ WI_TYPES_WITH_CREATED_HEADER = %w[issue incident].freeze
+
cache_markdown_field :description, pipeline: :single_line
enum base_type: BASE_TYPES.transform_values { |value| value[:enum_value] }