Commit 16ecd9f6 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch '231401-ancestors-sidebar-widget' into 'master'

Epic boards - Ancestors sidebar widget

See merge request gitlab-org/gitlab!63594
parents f0e07359 f683d9b8
<script>
import { GlDrawer } from '@gitlab/ui';
import { mapState, mapActions, mapGetters } from 'vuex';
import SidebarAncestorsWidget from 'ee_component/sidebar/components/ancestors_tree/sidebar_ancestors_widget.vue';
import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
import { ISSUABLE } from '~/boards/constants';
......@@ -20,6 +21,7 @@ export default {
SidebarDateWidget,
SidebarParticipantsWidget,
SidebarSubscriptionsWidget,
SidebarAncestorsWidget,
},
computed: {
...mapGetters(['isSidebarOpen', 'activeBoardItem']),
......@@ -65,16 +67,21 @@ export default {
:can-inherit="true"
/>
<board-sidebar-labels-select class="labels" />
<sidebar-participants-widget
<sidebar-confidentiality-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
:issuable-type="issuableType"
@confidentialityUpdated="setActiveItemConfidential($event)"
/>
<sidebar-ancestors-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
issuable-type="epic"
/>
<sidebar-confidentiality-widget
<sidebar-participants-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
:issuable-type="issuableType"
@confidentialityUpdated="setActiveItemConfidential($event)"
issuable-type="epic"
/>
<sidebar-subscriptions-widget
:iid="activeBoardItem.iid"
......
......@@ -81,7 +81,7 @@ export default {
<gl-icon :name="getIcon(ancestor)" />
</div>
<div class="vertical-timeline-content">
<gl-link :href="ancestor.url">{{ ancestor.title }}</gl-link>
<gl-link :href="ancestor.url" class="gl-text-gray-900">{{ ancestor.title }}</gl-link>
</div>
</li>
</ul>
......
<script>
import { __ } from '~/locale';
import { ancestorsQueries } from '../../constants';
import Ancestors from './ancestors_tree.vue';
export default {
i18n: {
fetchingError: __('An error occurred while fetching ancestors'),
},
components: {
Ancestors,
},
props: {
iid: {
type: String,
required: true,
},
fullPath: {
type: String,
required: true,
},
issuableType: {
required: true,
type: String,
},
},
data() {
return {
ancestors: [],
};
},
apollo: {
ancestors: {
query() {
return ancestorsQueries[this.issuableType].query;
},
variables() {
return {
fullPath: this.fullPath,
iid: this.iid,
};
},
update(data) {
return data.workspace?.issuable?.ancestors.nodes || [];
},
error(error) {
this.$emit('fetch-error', {
message: this.$options.i18n.fetchingError,
error,
});
},
},
},
computed: {
isLoading() {
return this.$apollo.queries.ancestors.loading;
},
},
};
</script>
<template>
<ancestors :is-fetching="isLoading" :ancestors="ancestors" class="block ancestors" />
</template>
......@@ -5,6 +5,7 @@ import {
IssuableAttributeState as IssuableAttributeStateFoss,
issuableAttributesQueries as issuableAttributesQueriesFoss,
} from '~/sidebar/constants';
import epicAncestorsQuery from './queries/epic_ancestors.query.graphql';
import groupEpicsQuery from './queries/group_epics.query.graphql';
import groupIterationsQuery from './queries/group_iterations.query.graphql';
import projectIssueEpicMutation from './queries/project_issue_epic.mutation.graphql';
......@@ -122,3 +123,9 @@ export const issuableAttributesQueries = {
list: epicsQueries,
},
};
export const ancestorsQueries = {
[IssuableType.Epic]: {
query: epicAncestorsQuery,
},
};
query epicAncestors($fullPath: ID!, $iid: ID) {
workspace: group(fullPath: $fullPath) {
__typename
issuable: epic(iid: $iid) {
__typename
id
ancestors {
nodes {
id
title
state
url: webUrl
}
}
}
}
}
......@@ -2,6 +2,7 @@ import { GlDrawer } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import EpicBoardContentSidebar from 'ee_component/boards/components/epic_board_content_sidebar.vue';
import SidebarAncestorsWidget from 'ee_component/sidebar/components/ancestors_tree/sidebar_ancestors_widget.vue';
import { stubComponent } from 'helpers/stub_component';
import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
......@@ -99,6 +100,9 @@ describe('EpicBoardContentSidebar', () => {
it('renders SidebarSubscriptionsWidget', () => {
expect(wrapper.findComponent(SidebarSubscriptionsWidget).exists()).toBe(true);
});
it('renders SidebarAncestorsWidget', () => {
expect(wrapper.findComponent(SidebarAncestorsWidget).exists()).toBe(true);
});
describe('when we emit close', () => {
let toggleBoardItem;
......
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import Ancestors from 'ee_component/sidebar/components/ancestors_tree/ancestors_tree.vue';
import SidebarAncestorsWidget from 'ee_component/sidebar/components/ancestors_tree/sidebar_ancestors_widget.vue';
import epicAncestorsQuery from 'ee_component/sidebar/queries/epic_ancestors.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { epicAncestorsResponse } from '../../mock_data';
Vue.use(VueApollo);
describe('Sidebar Ancestors Widget', () => {
let wrapper;
let fakeApollo;
const findAncestors = () => wrapper.findComponent(Ancestors);
const createComponent = ({
ancestorsQueryHandler = jest.fn().mockResolvedValue(epicAncestorsResponse()),
} = {}) => {
fakeApollo = createMockApollo([[epicAncestorsQuery, ancestorsQueryHandler]]);
wrapper = shallowMount(SidebarAncestorsWidget, {
apolloProvider: fakeApollo,
propsData: {
fullPath: 'group',
iid: '1',
issuableType: 'epic',
},
});
};
afterEach(() => {
wrapper.destroy();
fakeApollo = null;
});
it('passes a `isFetching` prop as true to child component when query is loading', () => {
createComponent();
expect(findAncestors().props('isFetching')).toBe(true);
});
describe('when ancestors are loaded', () => {
beforeEach(() => {
createComponent({
ancestorsQueryHandler: jest.fn().mockResolvedValue(epicAncestorsResponse()),
});
return waitForPromises();
});
it('passes a `isFetching` prop as false to editable item', () => {
expect(findAncestors().props('isFetching')).toBe(false);
});
it('passes ancestors to child component', () => {
expect(findAncestors().props('ancestors')).toEqual(
epicAncestorsResponse().data.workspace.issuable.ancestors.nodes,
);
});
});
describe('when error occurs', () => {
it('emits error event with correct parameters', async () => {
const mockError = new Error('mayday');
createComponent({
ancestorsQueryHandler: jest.fn().mockRejectedValue(mockError),
});
await waitForPromises();
const [
[
{
message,
error: { networkError },
},
],
] = wrapper.emitted('fetch-error');
expect(message).toBe(wrapper.vm.$options.i18n.fetchingError);
expect(networkError).toEqual(mockError);
});
});
});
......@@ -144,3 +144,25 @@ export const mockEpicMutationResponse = {
},
},
};
export const epicAncestorsResponse = () => ({
data: {
workspace: {
__typename: 'Group',
issuable: {
__typename: 'Epic',
id: 'gid://gitlab/Epic/4',
ancestors: {
nodes: [
{
id: 'gid://gitlab/Epic/2',
title: 'Ancestor epic',
url: 'http://gdk.test:3000/groups/gitlab-org/-/epics/2',
state: 'opened',
},
],
},
},
},
},
});
......@@ -3501,6 +3501,9 @@ msgstr ""
msgid "An error occurred while enabling Service Desk."
msgstr ""
msgid "An error occurred while fetching ancestors"
msgstr ""
msgid "An error occurred while fetching branches. Retry the search."
msgstr ""
......
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