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 @@ ...@@ -238,10 +238,6 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
cite {
font-style: normal;
}
button { button {
float: right; float: right;
padding: 1px 5px; padding: 1px 5px;
......
...@@ -165,6 +165,6 @@ ...@@ -165,6 +165,6 @@
.cross-project-reference.hide-collapsed .cross-project-reference.hide-collapsed
%span %span
= s_('MilestoneSidebar|Reference:') = s_('MilestoneSidebar|Reference:')
%cite{ title: milestone_ref } %span{ title: milestone_ref }
= milestone_ref = milestone_ref
= clipboard_button(text: milestone_ref, title: s_('MilestoneSidebar|Copy reference'), placement: "left", boundary: 'viewport') = 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> <script>
import { labelsFilterParam } from 'ee/integrations/jira/issues_show/constants'; import { labelsFilterParam } from 'ee/integrations/jira/issues_show/constants';
import { __ } from '~/locale'; 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 LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
import Assignee from './assignee.vue'; import Assignee from './assignee.vue';
import IssueDueDate from './issue_due_date.vue'; import IssueDueDate from './issue_due_date.vue';
import IssueField from './issue_field.vue'; import IssueField from './issue_field.vue';
import IssueReference from './issue_reference.vue';
export default { export default {
name: 'JiraIssuesSidebar', name: 'JiraIssuesSidebar',
...@@ -15,7 +13,7 @@ export default { ...@@ -15,7 +13,7 @@ export default {
Assignee, Assignee,
IssueDueDate, IssueDueDate,
IssueField, IssueField,
IssueReference, CopyableField,
LabelsSelect, LabelsSelect,
}, },
inject: { inject: {
...@@ -45,6 +43,7 @@ export default { ...@@ -45,6 +43,7 @@ export default {
labelsFilterParam, labelsFilterParam,
i18n: { i18n: {
statusTitle: __('Status'), statusTitle: __('Status'),
referenceName: __('Reference'),
}, },
}; };
</script> </script>
...@@ -52,10 +51,8 @@ export default { ...@@ -52,10 +51,8 @@ export default {
<template> <template>
<div> <div>
<assignee class="block" :assignee="assignee" /> <assignee class="block" :assignee="assignee" />
<issue-due-date :due-date="issue.dueDate" /> <issue-due-date :due-date="issue.dueDate" />
<issue-field icon="progress" :title="$options.i18n.statusTitle" :value="issue.status" /> <issue-field icon="progress" :title="$options.i18n.statusTitle" :value="issue.status" />
<labels-select <labels-select
:selected-labels="issue.labels" :selected-labels="issue.labels"
:labels-filter-base-path="issuesListPath" :labels-filter-base-path="issuesListPath"
...@@ -65,6 +62,11 @@ export default { ...@@ -65,6 +62,11 @@ export default {
> >
{{ __('None') }} {{ __('None') }}
</labels-select> </labels-select>
<issue-reference v-if="reference" :reference="reference" /> <copyable-field
v-if="reference"
class="block"
:name="$options.i18n.referenceName"
:value="reference"
/>
</div> </div>
</template> </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'; ...@@ -2,9 +2,9 @@ import { shallowMount } from '@vue/test-utils';
import Assignee from 'ee/integrations/jira/issues_show/components/sidebar/assignee.vue'; 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 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 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 Sidebar from 'ee/integrations/jira/issues_show/components/sidebar/jira_issues_sidebar_root.vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; 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 LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
import { mockJiraIssue as mockJiraIssueData } from '../../mock_data'; import { mockJiraIssue as mockJiraIssueData } from '../../mock_data';
...@@ -35,7 +35,7 @@ describe('JiraIssuesSidebar', () => { ...@@ -35,7 +35,7 @@ describe('JiraIssuesSidebar', () => {
const findAssignee = () => wrapper.findComponent(Assignee); const findAssignee = () => wrapper.findComponent(Assignee);
const findIssueDueDate = () => wrapper.findComponent(IssueDueDate); const findIssueDueDate = () => wrapper.findComponent(IssueDueDate);
const findIssueField = () => wrapper.findComponent(IssueField); const findIssueField = () => wrapper.findComponent(IssueField);
const findIssueReference = () => wrapper.findComponent(IssueReference); const findCopyableField = () => wrapper.findComponent(CopyableField);
it('renders Labels block', () => { it('renders Labels block', () => {
createComponent(); createComponent();
...@@ -67,7 +67,7 @@ describe('JiraIssuesSidebar', () => { ...@@ -67,7 +67,7 @@ describe('JiraIssuesSidebar', () => {
}); });
describe('when references.relative is null', () => { describe('when references.relative is null', () => {
it('does not render IssueReference', () => { it('does not render issue reference', () => {
createComponent({ createComponent({
props: { props: {
issue: { issue: {
...@@ -76,15 +76,17 @@ describe('JiraIssuesSidebar', () => { ...@@ -76,15 +76,17 @@ describe('JiraIssuesSidebar', () => {
}, },
}); });
expect(findIssueReference().exists()).toBe(false); expect(findCopyableField().exists()).toBe(false);
}); });
}); });
describe('when references.relative is provided', () => { describe('when references.relative is provided', () => {
it('renders IssueReference', () => { it('renders issue reference', () => {
createComponent(); 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 "" ...@@ -8542,6 +8542,9 @@ msgstr ""
msgid "Copy %{http_label} clone URL" msgid "Copy %{http_label} clone URL"
msgstr "" msgstr ""
msgid "Copy %{name}"
msgstr ""
msgid "Copy %{protocol} clone URL" msgid "Copy %{protocol} clone URL"
msgstr "" msgstr ""
...@@ -18482,6 +18485,9 @@ msgstr "" ...@@ -18482,6 +18485,9 @@ msgstr ""
msgid "Loading" msgid "Loading"
msgstr "" msgstr ""
msgid "Loading %{name}"
msgstr ""
msgid "Loading contribution stats for group members" msgid "Loading contribution stats for group members"
msgstr "" msgstr ""
...@@ -25276,9 +25282,6 @@ msgstr "" ...@@ -25276,9 +25282,6 @@ msgstr ""
msgid "Reference" msgid "Reference"
msgstr "" msgstr ""
msgid "Reference:"
msgstr ""
msgid "References" msgid "References"
msgstr "" msgstr ""
...@@ -28181,6 +28184,9 @@ msgstr "" ...@@ -28181,6 +28184,9 @@ msgstr ""
msgid "Side-by-side" msgid "Side-by-side"
msgstr "" msgstr ""
msgid "Sidebar|%{name}: %{value}"
msgstr ""
msgid "Sidebar|Assign health status" msgid "Sidebar|Assign health status"
msgstr "" 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