Commit 36ce3b5d authored by Donald Cook's avatar Donald Cook Committed by Natalia Tepluhina

Added reference Apollo component

Including tests and also fixed clipboard_button tooltip going offscreen
parent 1e7ea614
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import { referenceQueries } from '~/sidebar/constants';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
export default {
i18n: {
copyReference: __('Copy reference'),
text: __('Reference'),
},
components: {
ClipboardButton,
GlLoadingIcon,
},
inject: ['fullPath', 'iid'],
props: {
issuableType: {
required: true,
type: String,
},
},
data() {
return {
reference: '',
};
},
apollo: {
reference: {
query() {
return referenceQueries[this.issuableType].query;
},
variables() {
return {
fullPath: this.fullPath,
iid: this.iid,
};
},
update(data) {
return data.workspace?.issuable?.reference || '';
},
error(error) {
this.$emit('fetch-error', {
message: __('An error occurred while fetching reference'),
error,
});
},
},
},
computed: {
isLoading() {
return this.$apollo.queries.reference.loading;
},
},
};
</script>
<template>
<div class="sub-block">
<clipboard-button
v-if="!isLoading"
:title="$options.i18n.copyReference"
:text="reference"
category="tertiary"
css-class="sidebar-collapsed-icon dont-change-state"
tooltip-placement="left"
/>
<div class="gl-display-flex gl-align-items-center gl-justify-between gl-mb-2 hide-collapsed">
<span class="gl-overflow-hidden gl-text-overflow-ellipsis gl-white-space-nowrap">
{{ $options.i18n.text }}: {{ reference }}
<gl-loading-icon v-if="isLoading" inline :label="$options.i18n.text" />
</span>
<clipboard-button
v-if="!isLoading"
:title="$options.i18n.copyReference"
:text="reference"
size="small"
category="tertiary"
css-class="gl-mr-1"
tooltip-placement="left"
/>
</div>
</div>
</template>
import { IssuableType } from '~/issue_show/constants'; import { IssuableType } from '~/issue_show/constants';
import epicConfidentialQuery from '~/sidebar/queries/epic_confidential.query.graphql'; import epicConfidentialQuery from '~/sidebar/queries/epic_confidential.query.graphql';
import issueConfidentialQuery from '~/sidebar/queries/issue_confidential.query.graphql'; import issueConfidentialQuery from '~/sidebar/queries/issue_confidential.query.graphql';
import issueReferenceQuery from '~/sidebar/queries/issue_reference.query.graphql';
import mergeRequestReferenceQuery from '~/sidebar/queries/merge_request_reference.query.graphql';
import updateEpicMutation from '~/sidebar/queries/update_epic_confidential.mutation.graphql'; import updateEpicMutation from '~/sidebar/queries/update_epic_confidential.mutation.graphql';
import updateIssueConfidentialMutation from '~/sidebar/queries/update_issue_confidential.mutation.graphql'; import updateIssueConfidentialMutation from '~/sidebar/queries/update_issue_confidential.mutation.graphql';
import getIssueParticipants from '~/vue_shared/components/sidebar/queries/get_issue_participants.query.graphql'; import getIssueParticipants from '~/vue_shared/components/sidebar/queries/get_issue_participants.query.graphql';
...@@ -31,3 +33,12 @@ export const confidentialityQueries = { ...@@ -31,3 +33,12 @@ export const confidentialityQueries = {
mutation: updateEpicMutation, mutation: updateEpicMutation,
}, },
}; };
export const referenceQueries = {
[IssuableType.Issue]: {
query: issueReferenceQuery,
},
[IssuableType.MergeRequest]: {
query: mergeRequestReferenceQuery,
},
};
...@@ -2,6 +2,7 @@ import $ from 'jquery'; ...@@ -2,6 +2,7 @@ import $ from 'jquery';
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { IssuableType } from '~/issue_show/constants';
import { import {
isInIssuePage, isInIssuePage,
isInDesignPage, isInDesignPage,
...@@ -10,6 +11,7 @@ import { ...@@ -10,6 +11,7 @@ import {
} from '~/lib/utils/common_utils'; } from '~/lib/utils/common_utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue'; import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue';
import SidebarReferenceWidget from '~/sidebar/components/reference/sidebar_reference_widget.vue';
import { apolloProvider } from '~/sidebar/graphql'; import { apolloProvider } from '~/sidebar/graphql';
import Translate from '../vue_shared/translate'; import Translate from '../vue_shared/translate';
import SidebarAssignees from './components/assignees/sidebar_assignees.vue'; import SidebarAssignees from './components/assignees/sidebar_assignees.vue';
...@@ -75,7 +77,9 @@ function mountAssigneesComponent(mediator) { ...@@ -75,7 +77,9 @@ function mountAssigneesComponent(mediator) {
field: el.dataset.field, field: el.dataset.field,
signedIn: el.hasAttribute('data-signed-in'), signedIn: el.hasAttribute('data-signed-in'),
issuableType: issuableType:
isInIssuePage() || isInIncidentPage() || isInDesignPage() ? 'issue' : 'merge_request', isInIssuePage() || isInIncidentPage() || isInDesignPage()
? IssuableType.Issue
: IssuableType.MergeRequest,
assigneeAvailabilityStatus, assigneeAvailabilityStatus,
}, },
}), }),
...@@ -156,7 +160,41 @@ function mountConfidentialComponent() { ...@@ -156,7 +160,41 @@ function mountConfidentialComponent() {
createElement('sidebar-confidentiality-widget', { createElement('sidebar-confidentiality-widget', {
props: { props: {
issuableType: issuableType:
isInIssuePage() || isInIncidentPage() || isInDesignPage() ? 'issue' : 'merge_request', isInIssuePage() || isInIncidentPage() || isInDesignPage()
? IssuableType.Issue
: IssuableType.MergeRequest,
},
}),
});
}
function mountReferenceComponent() {
const el = document.getElementById('js-reference-entry-point');
if (!el) {
return;
}
const { fullPath, iid } = getSidebarOptions();
// eslint-disable-next-line no-new
new Vue({
el,
apolloProvider,
components: {
SidebarReferenceWidget,
},
provide: {
iid: String(iid),
fullPath,
},
render: (createElement) =>
createElement('sidebar-reference-widget', {
props: {
issuableType:
isInIssuePage() || isInIncidentPage() || isInDesignPage()
? IssuableType.Issue
: IssuableType.MergeRequest,
}, },
}), }),
}); });
...@@ -307,6 +345,7 @@ export function mountSidebar(mediator) { ...@@ -307,6 +345,7 @@ export function mountSidebar(mediator) {
mountAssigneesComponent(mediator); mountAssigneesComponent(mediator);
mountReviewersComponent(mediator); mountReviewersComponent(mediator);
mountConfidentialComponent(mediator); mountConfidentialComponent(mediator);
mountReferenceComponent(mediator);
mountLockComponent(); mountLockComponent();
mountParticipantsComponent(mediator); mountParticipantsComponent(mediator);
mountSubscriptionsComponent(mediator); mountSubscriptionsComponent(mediator);
......
query issueReference($fullPath: ID!, $iid: String) {
workspace: project(fullPath: $fullPath) {
__typename
issuable: issue(iid: $iid) {
__typename
id
reference(full: true)
}
}
}
query mergeRequestReference($fullPath: ID!, $iid: String!) {
workspace: project(fullPath: $fullPath) {
__typename
issuable: mergeRequest(iid: $iid) {
__typename
id
reference(full: true)
}
}
}
...@@ -80,7 +80,7 @@ export default { ...@@ -80,7 +80,7 @@ export default {
<template> <template>
<gl-button <gl-button
v-gl-tooltip.hover.blur="{ v-gl-tooltip.hover.blur.viewport="{
placement: tooltipPlacement, placement: tooltipPlacement,
container: tooltipContainer, container: tooltipContainer,
boundary: tooltipBoundary, boundary: tooltipBoundary,
......
...@@ -130,17 +130,8 @@ ...@@ -130,17 +130,8 @@
- if signed_in - if signed_in
.js-sidebar-subscriptions-entry-point .js-sidebar-subscriptions-entry-point
- project_ref = issuable_sidebar[:reference]
.block.with-sub-blocks .block.with-sub-blocks
.project-reference.sub-block #js-reference-entry-point
.sidebar-collapsed-icon.dont-change-state
= clipboard_button(text: project_ref, title: _('Copy reference'), placement: "left", boundary: 'viewport')
.cross-project-reference.hide-collapsed
%span
= _('Reference:')
%cite{ title: project_ref }
= project_ref
= clipboard_button(text: project_ref, title: _('Copy reference'), placement: "left", boundary: 'viewport')
- if issuable_type == 'merge_request' - if issuable_type == 'merge_request'
.sidebar-source-branch.sub-block .sidebar-source-branch.sub-block
.sidebar-collapsed-icon.dont-change-state .sidebar-collapsed-icon.dont-change-state
......
...@@ -3368,6 +3368,9 @@ msgstr "" ...@@ -3368,6 +3368,9 @@ msgstr ""
msgid "An error occurred while fetching projects autocomplete." msgid "An error occurred while fetching projects autocomplete."
msgstr "" msgstr ""
msgid "An error occurred while fetching reference"
msgstr ""
msgid "An error occurred while fetching sidebar data" msgid "An error occurred while fetching sidebar data"
msgstr "" msgstr ""
...@@ -24979,6 +24982,9 @@ msgstr "" ...@@ -24979,6 +24982,9 @@ msgstr ""
msgid "Reduce this project’s visibility?" msgid "Reduce this project’s visibility?"
msgstr "" msgstr ""
msgid "Reference"
msgstr ""
msgid "Reference:" msgid "Reference:"
msgstr "" msgstr ""
......
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { IssuableType } from '~/issue_show/constants';
import SidebarReferenceWidget from '~/sidebar/components/reference/sidebar_reference_widget.vue';
import issueReferenceQuery from '~/sidebar/queries/issue_reference.query.graphql';
import mergeRequestReferenceQuery from '~/sidebar/queries/merge_request_reference.query.graphql';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { issueReferenceResponse } from '../../mock_data';
describe('Sidebar Reference Widget', () => {
let wrapper;
let fakeApollo;
const referenceText = 'reference';
const createComponent = ({
issuableType,
referenceQuery = issueReferenceQuery,
referenceQueryHandler = jest.fn().mockResolvedValue(issueReferenceResponse(referenceText)),
} = {}) => {
Vue.use(VueApollo);
fakeApollo = createMockApollo([[referenceQuery, referenceQueryHandler]]);
wrapper = shallowMount(SidebarReferenceWidget, {
apolloProvider: fakeApollo,
provide: {
fullPath: 'group/project',
iid: '1',
},
propsData: {
issuableType,
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe.each([
[IssuableType.Issue, issueReferenceQuery],
[IssuableType.MergeRequest, mergeRequestReferenceQuery],
])('when issuableType is %s', (issuableType, referenceQuery) => {
it('displays the reference text', async () => {
createComponent({
issuableType,
referenceQuery,
});
await waitForPromises();
expect(wrapper.text()).toContain(referenceText);
});
it('displays loading icon while fetching and hides clipboard icon', async () => {
createComponent({
issuableType,
referenceQuery,
});
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.find(ClipboardButton).exists()).toBe(false);
});
it('calls createFlash with correct parameters', async () => {
const mockError = new Error('mayday');
createComponent({
issuableType,
referenceQuery,
referenceQueryHandler: jest.fn().mockRejectedValue(mockError),
});
await waitForPromises();
const [
[
{
message,
error: { networkError },
},
],
] = wrapper.emitted('fetch-error');
expect(message).toBe('An error occurred while fetching reference');
expect(networkError).toEqual(mockError);
});
});
});
...@@ -233,4 +233,16 @@ export const issueConfidentialityResponse = (confidential = false) => ({ ...@@ -233,4 +233,16 @@ export const issueConfidentialityResponse = (confidential = false) => ({
}, },
}); });
export const issueReferenceResponse = (reference) => ({
data: {
workspace: {
__typename: 'Project',
issuable: {
__typename: 'Issue',
id: 'gid://gitlab/Issue/4',
reference,
},
},
},
});
export default mockData; export default mockData;
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