Commit dadd7d9f authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '300659-refactor-project-reference-sidebar-component-to-use-vue-apollo' into 'master'

Added reference Vue component

See merge request gitlab-org/gitlab!55431
parents b2f43329 36ce3b5d
<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 epicConfidentialQuery from '~/sidebar/queries/epic_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 updateIssueConfidentialMutation from '~/sidebar/queries/update_issue_confidential.mutation.graphql';
import getIssueParticipants from '~/vue_shared/components/sidebar/queries/get_issue_participants.query.graphql';
......@@ -31,3 +33,12 @@ export const confidentialityQueries = {
mutation: updateEpicMutation,
},
};
export const referenceQueries = {
[IssuableType.Issue]: {
query: issueReferenceQuery,
},
[IssuableType.MergeRequest]: {
query: mergeRequestReferenceQuery,
},
};
......@@ -2,6 +2,7 @@ import $ from 'jquery';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createFlash from '~/flash';
import { IssuableType } from '~/issue_show/constants';
import {
isInIssuePage,
isInDesignPage,
......@@ -10,6 +11,7 @@ import {
} from '~/lib/utils/common_utils';
import { __ } from '~/locale';
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 Translate from '../vue_shared/translate';
import SidebarAssignees from './components/assignees/sidebar_assignees.vue';
......@@ -75,7 +77,9 @@ function mountAssigneesComponent(mediator) {
field: el.dataset.field,
signedIn: el.hasAttribute('data-signed-in'),
issuableType:
isInIssuePage() || isInIncidentPage() || isInDesignPage() ? 'issue' : 'merge_request',
isInIssuePage() || isInIncidentPage() || isInDesignPage()
? IssuableType.Issue
: IssuableType.MergeRequest,
assigneeAvailabilityStatus,
},
}),
......@@ -156,7 +160,41 @@ function mountConfidentialComponent() {
createElement('sidebar-confidentiality-widget', {
props: {
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) {
mountAssigneesComponent(mediator);
mountReviewersComponent(mediator);
mountConfidentialComponent(mediator);
mountReferenceComponent(mediator);
mountLockComponent();
mountParticipantsComponent(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 {
<template>
<gl-button
v-gl-tooltip.hover.blur="{
v-gl-tooltip.hover.blur.viewport="{
placement: tooltipPlacement,
container: tooltipContainer,
boundary: tooltipBoundary,
......
......@@ -130,17 +130,8 @@
- if signed_in
.js-sidebar-subscriptions-entry-point
- project_ref = issuable_sidebar[:reference]
.block.with-sub-blocks
.project-reference.sub-block
.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')
#js-reference-entry-point
- if issuable_type == 'merge_request'
.sidebar-source-branch.sub-block
.sidebar-collapsed-icon.dont-change-state
......
......@@ -3396,6 +3396,9 @@ msgstr ""
msgid "An error occurred while fetching projects autocomplete."
msgstr ""
msgid "An error occurred while fetching reference"
msgstr ""
msgid "An error occurred while fetching sidebar data"
msgstr ""
......@@ -25031,6 +25034,9 @@ msgstr ""
msgid "Reduce this project’s visibility?"
msgstr ""
msgid "Reference"
msgstr ""
msgid "Reference:"
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) => ({
},
});
export const issueReferenceResponse = (reference) => ({
data: {
workspace: {
__typename: 'Project',
issuable: {
__typename: 'Issue',
id: 'gid://gitlab/Issue/4',
reference,
},
},
},
});
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