Commit 6e994ff9 authored by Mark Florian's avatar Mark Florian

Merge branch 'combine-sidebar-reference-components' into 'master'

Combine sidebar reference components

See merge request gitlab-org/gitlab!56822
parents 4e4e9fdc 46fc370f
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { s__, __, sprintf } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
/**
* Renders an inline field, whose value can be copied to the clipboard,
* for use in the GitLab sidebar (issues, MRs, etc.).
*/
export default {
name: 'CopyableField',
components: {
GlLoadingIcon,
ClipboardButton,
},
props: {
value: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
isLoading: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
clipboardProps() {
return {
category: 'tertiary',
tooltipBoundary: 'viewport',
tooltipPlacement: 'left',
text: this.value,
title: sprintf(this.$options.i18n.clipboardTooltip, { name: this.name }),
};
},
loadingIconLabel() {
return sprintf(this.$options.i18n.loadingIconLabel, { name: this.name });
},
templateText() {
return sprintf(this.$options.i18n.templateText, {
name: this.name,
value: this.value,
});
},
},
i18n: {
loadingIconLabel: __('Loading %{name}'),
clipboardTooltip: __('Copy %{name}'),
templateText: s__('Sidebar|%{name}: %{value}'),
},
};
</script>
<template>
<div>
<clipboard-button
v-if="!isLoading"
css-class="sidebar-collapsed-icon dont-change-state"
v-bind="clipboardProps"
/>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-space-between hide-collapsed"
>
<span
class="gl-overflow-hidden gl-text-overflow-ellipsis gl-white-space-nowrap"
:title="value"
>
{{ templateText }}
</span>
<gl-loading-icon v-if="isLoading" inline :label="loadingIconLabel" />
<clipboard-button v-else size="small" v-bind="clipboardProps" />
</div>
</div>
</template>
......@@ -238,10 +238,6 @@
text-overflow: ellipsis;
}
cite {
font-style: normal;
}
button {
float: right;
padding: 1px 5px;
......
......@@ -165,6 +165,6 @@
.cross-project-reference.hide-collapsed
%span
= s_('MilestoneSidebar|Reference:')
%cite{ title: milestone_ref }
%span{ title: milestone_ref }
= milestone_ref
= clipboard_button(text: milestone_ref, title: s_('MilestoneSidebar|Copy reference'), placement: "left", boundary: 'viewport')
<script>
import { __ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
export default {
name: 'JiraIssuesSidebarReference',
components: {
ClipboardButton,
},
props: {
reference: {
type: String,
required: true,
},
},
computed: {
clipboardProps() {
return {
category: 'tertiary',
tooltipBoundary: 'viewport',
tooltipPlacement: 'left',
text: this.reference,
title: this.$options.i18n.copyTitle,
};
},
},
i18n: {
copyTitle: __('Copy reference'),
},
};
</script>
<template>
<div class="block">
<div class="sidebar-collapsed-icon dont-change-state">
<clipboard-button css-class="btn-clipboard" v-bind="clipboardProps" />
</div>
<div class="cross-project-reference hide-collapsed">
<span
>{{ __('Reference:') }}
<cite :title="reference">{{ reference }}</cite>
</span>
<clipboard-button size="small" v-bind="clipboardProps" />
</div>
</div>
</template>
<script>
import { labelsFilterParam } from 'ee/integrations/jira/issues_show/constants';
import { __ } from '~/locale';
import CopyableField from '~/vue_shared/components/sidebar/copyable_field.vue';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
import Assignee from './assignee.vue';
import IssueDueDate from './issue_due_date.vue';
import IssueField from './issue_field.vue';
import IssueReference from './issue_reference.vue';
export default {
name: 'JiraIssuesSidebar',
......@@ -15,7 +13,7 @@ export default {
Assignee,
IssueDueDate,
IssueField,
IssueReference,
CopyableField,
LabelsSelect,
},
inject: {
......@@ -45,6 +43,7 @@ export default {
labelsFilterParam,
i18n: {
statusTitle: __('Status'),
referenceName: __('Reference'),
},
};
</script>
......@@ -52,10 +51,8 @@ export default {
<template>
<div>
<assignee class="block" :assignee="assignee" />
<issue-due-date :due-date="issue.dueDate" />
<issue-field icon="progress" :title="$options.i18n.statusTitle" :value="issue.status" />
<labels-select
:selected-labels="issue.labels"
:labels-filter-base-path="issuesListPath"
......@@ -65,6 +62,11 @@ export default {
>
{{ __('None') }}
</labels-select>
<issue-reference v-if="reference" :reference="reference" />
<copyable-field
v-if="reference"
class="block"
:name="$options.i18n.referenceName"
:value="reference"
/>
</div>
</template>
import { shallowMount } from '@vue/test-utils';
import IssueReference from 'ee/integrations/jira/issues_show/components/sidebar/issue_reference.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
describe('IssueReference', () => {
let wrapper;
const defaultProps = {
reference: 'GL-1',
};
const createComponent = () => {
wrapper = shallowMount(IssueReference, {
propsData: defaultProps,
});
};
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
const findClipboardButton = () => wrapper.findComponent(ClipboardButton);
it('renders reference', () => {
createComponent();
expect(wrapper.text()).toContain(defaultProps.reference);
});
it('renders ClipboardButton', () => {
createComponent();
expect(findClipboardButton().exists()).toBe(true);
});
});
......@@ -2,9 +2,9 @@ import { shallowMount } from '@vue/test-utils';
import Assignee from 'ee/integrations/jira/issues_show/components/sidebar/assignee.vue';
import IssueDueDate from 'ee/integrations/jira/issues_show/components/sidebar/issue_due_date.vue';
import IssueField from 'ee/integrations/jira/issues_show/components/sidebar/issue_field.vue';
import IssueReference from 'ee/integrations/jira/issues_show/components/sidebar/issue_reference.vue';
import Sidebar from 'ee/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root.vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import CopyableField from '~/vue_shared/components/sidebar/copyable_field.vue';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
import { mockJiraIssue as mockJiraIssueData } from '../../mock_data';
......@@ -35,7 +35,7 @@ describe('JiraIssuesSidebar', () => {
const findAssignee = () => wrapper.findComponent(Assignee);
const findIssueDueDate = () => wrapper.findComponent(IssueDueDate);
const findIssueField = () => wrapper.findComponent(IssueField);
const findIssueReference = () => wrapper.findComponent(IssueReference);
const findCopyableField = () => wrapper.findComponent(CopyableField);
it('renders Labels block', () => {
createComponent();
......@@ -67,7 +67,7 @@ describe('JiraIssuesSidebar', () => {
});
describe('when references.relative is null', () => {
it('does not render IssueReference', () => {
it('does not render issue reference', () => {
createComponent({
props: {
issue: {
......@@ -76,15 +76,17 @@ describe('JiraIssuesSidebar', () => {
},
});
expect(findIssueReference().exists()).toBe(false);
expect(findCopyableField().exists()).toBe(false);
});
});
describe('when references.relative is provided', () => {
it('renders IssueReference', () => {
it('renders issue reference', () => {
createComponent();
const issueReference = findCopyableField();
expect(findIssueReference().exists()).toBe(true);
expect(issueReference.exists()).toBe(true);
expect(issueReference.props('value')).toBe(defaultProps.issue.references.relative);
});
});
});
......@@ -8542,6 +8542,9 @@ msgstr ""
msgid "Copy %{http_label} clone URL"
msgstr ""
msgid "Copy %{name}"
msgstr ""
msgid "Copy %{protocol} clone URL"
msgstr ""
......@@ -18482,6 +18485,9 @@ msgstr ""
msgid "Loading"
msgstr ""
msgid "Loading %{name}"
msgstr ""
msgid "Loading contribution stats for group members"
msgstr ""
......@@ -25276,9 +25282,6 @@ msgstr ""
msgid "Reference"
msgstr ""
msgid "Reference:"
msgstr ""
msgid "References"
msgstr ""
......@@ -28181,6 +28184,9 @@ msgstr ""
msgid "Side-by-side"
msgstr ""
msgid "Sidebar|%{name}: %{value}"
msgstr ""
msgid "Sidebar|Assign health status"
msgstr ""
......
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import CopyableField from '~/vue_shared/components/sidebar/copyable_field.vue';
describe('SidebarCopyableField', () => {
let wrapper;
const defaultProps = {
value: 'Gl-1',
name: 'Reference',
};
const createComponent = (propsData = defaultProps) => {
wrapper = shallowMount(CopyableField, {
propsData,
});
};
afterEach(() => {
wrapper.destroy();
});
const findClipboardButton = () => wrapper.findComponent(ClipboardButton);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
describe('template', () => {
describe('when `isLoading` prop is `false`', () => {
beforeEach(() => {
createComponent();
});
it('renders copyable field', () => {
expect(wrapper.text()).toContain('Reference: Gl-1');
});
it('renders ClipboardButton with correct props', () => {
const clipboardButton = findClipboardButton();
expect(clipboardButton.exists()).toBe(true);
expect(clipboardButton.props('title')).toBe(`Copy ${defaultProps.name}`);
expect(clipboardButton.props('text')).toBe(defaultProps.value);
});
it('does not render loading icon', () => {
expect(findLoadingIcon().exists()).toBe(false);
});
});
describe('when `isLoading` prop is `true`', () => {
beforeEach(() => {
createComponent({ ...defaultProps, isLoading: true });
});
it('renders loading icon', () => {
expect(findLoadingIcon().exists()).toBe(true);
expect(findLoadingIcon().props('label')).toBe('Loading Reference');
});
it('does not render clipboard button', () => {
expect(findClipboardButton().exists()).toBe(false);
});
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment