Commit 234b64f1 authored by Tom Quirk's avatar Tom Quirk

Move Jira issue reference component to vue_shared

- rename to "copyable"
- add loading state
- use gitlab-ui classes where possible
parent da611499
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { __, 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(__('Copy %{name}'), { name: this.name }),
};
},
loadingIconLabel() {
return sprintf(__('Loading %{name}'), { name: this.name });
},
},
};
</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"
>
<gl-loading-icon v-if="isLoading" inline :label="loadingIconLabel" />
<template v-else>
<span class="gl-overflow-hidden gl-text-overflow-ellipsis gl-white-space-nowrap">
<slot></slot>
</span>
<clipboard-button size="small" v-bind="clipboardProps" />
</template>
</div>
</div>
</template>
<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 { GlSprintf } from '@gitlab/ui';
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',
components: {
GlSprintf,
Assignee,
IssueDueDate,
IssueField,
IssueReference,
CopyableField,
LabelsSelect,
},
inject: {
......@@ -45,6 +45,8 @@ export default {
labelsFilterParam,
i18n: {
statusTitle: __('Status'),
referenceName: __('Reference'),
referenceText: __('Reference: %{reference}'),
},
};
</script>
......@@ -52,10 +54,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 +65,19 @@ 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"
>
<span class="cross-project-reference">
<gl-sprintf :message="$options.i18n.referenceText">
<template #reference>
<cite>{{ reference }}</cite>
</template>
</gl-sprintf>
</span>
</copyable-field>
</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);
});
});
});
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,
slots: {
default: 'Reference: Gl-1',
},
});
};
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`', () => {
it('renders loading icon', () => {
createComponent({ ...defaultProps, isLoading: true });
expect(findLoadingIcon().exists()).toBe(true);
expect(findLoadingIcon().props('label')).toBe('Loading Reference');
});
});
});
});
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