Commit 6261e620 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '300642-remove-unused-board-milestone-code' into 'master'

Boards - Remove unused milestone related code [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!63714
parents 628a8427 d379f582
<script>
import {
GlDropdown,
GlDropdownItem,
GlDropdownText,
GlSearchBoxByType,
GlDropdownDivider,
GlLoadingIcon,
} from '@gitlab/ui';
import { mapGetters, mapActions } from 'vuex';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
import { __, s__ } from '~/locale';
import projectMilestones from '../../graphql/project_milestones.query.graphql';
export default {
components: {
BoardEditableItem,
GlDropdown,
GlLoadingIcon,
GlDropdownItem,
GlDropdownText,
GlSearchBoxByType,
GlDropdownDivider,
},
data() {
return {
milestones: [],
searchTitle: '',
loading: false,
edit: false,
};
},
apollo: {
milestones: {
query: projectMilestones,
debounce: 250,
skip() {
return !this.edit;
},
variables() {
return {
fullPath: this.projectPath,
searchTitle: this.searchTitle,
state: 'active',
includeAncestors: true,
};
},
update(data) {
const edges = data?.project?.milestones?.edges ?? [];
return edges.map((item) => item.node);
},
error(error) {
this.setError({ error, message: this.$options.i18n.fetchMilestonesError });
},
},
},
computed: {
...mapGetters(['activeBoardItem']),
hasMilestone() {
return this.activeBoardItem.milestone !== null;
},
groupFullPath() {
const { referencePath = '' } = this.activeBoardItem;
return referencePath.slice(0, referencePath.indexOf('/'));
},
projectPath() {
const { referencePath = '' } = this.activeBoardItem;
return referencePath.slice(0, referencePath.indexOf('#'));
},
dropdownText() {
return this.activeBoardItem.milestone?.title ?? this.$options.i18n.noMilestone;
},
},
methods: {
...mapActions(['setActiveIssueMilestone', 'setError']),
handleOpen() {
this.edit = true;
this.$refs.dropdown.show();
},
handleClose() {
this.edit = false;
this.$refs.sidebarItem.collapse();
},
async setMilestone(milestoneId) {
this.loading = true;
this.searchTitle = '';
this.handleClose();
try {
const input = { milestoneId, projectPath: this.projectPath };
await this.setActiveIssueMilestone(input);
} catch (e) {
this.setError({ error: e, message: this.$options.i18n.updateMilestoneError });
} finally {
this.loading = false;
}
},
},
i18n: {
milestone: __('Milestone'),
noMilestone: __('No milestone'),
assignMilestone: __('Assign milestone'),
noMilestonesFound: s__('Milestones|No milestones found'),
fetchMilestonesError: __('There was a problem fetching milestones.'),
updateMilestoneError: __('An error occurred while updating the milestone.'),
},
};
</script>
<template>
<board-editable-item
ref="sidebarItem"
:title="$options.i18n.milestone"
:loading="loading"
data-testid="sidebar-milestones"
@open="handleOpen"
@close="handleClose"
>
<template v-if="hasMilestone" #collapsed>
<strong class="gl-text-gray-900">{{ activeBoardItem.milestone.title }}</strong>
</template>
<gl-dropdown
ref="dropdown"
:text="dropdownText"
:header-text="$options.i18n.assignMilestone"
block
@hide="handleClose"
>
<gl-search-box-by-type ref="search" v-model.trim="searchTitle" class="gl-m-3" />
<gl-dropdown-item
data-testid="no-milestone-item"
:is-check-item="true"
:is-checked="!activeBoardItem.milestone"
@click="setMilestone(null)"
>
{{ $options.i18n.noMilestone }}
</gl-dropdown-item>
<gl-dropdown-divider />
<gl-loading-icon v-if="$apollo.loading" class="gl-py-4" />
<template v-else-if="milestones.length > 0">
<gl-dropdown-item
v-for="milestone in milestones"
:key="milestone.id"
:is-check-item="true"
:is-checked="activeBoardItem.milestone && milestone.id === activeBoardItem.milestone.id"
data-testid="milestone-item"
@click="setMilestone(milestone.id)"
>
{{ milestone.title }}
</gl-dropdown-item>
</template>
<gl-dropdown-text v-else data-testid="no-milestones-found">
{{ $options.i18n.noMilestonesFound }}
</gl-dropdown-text>
</gl-dropdown>
</board-editable-item>
</template>
......@@ -14,10 +14,6 @@ fragment IssueNode on Issue {
confidential
webUrl
relativePosition
milestone {
id
title
}
assignees {
nodes {
...User
......
mutation issueSetMilestone($input: UpdateIssueInput!) {
updateIssue(input: $input) {
issue {
milestone {
id
title
description
}
}
errors
}
}
......@@ -37,7 +37,6 @@ import groupProjectsQuery from '../graphql/group_projects.query.graphql';
import issueCreateMutation from '../graphql/issue_create.mutation.graphql';
import issueSetDueDateMutation from '../graphql/issue_set_due_date.mutation.graphql';
import issueSetLabelsMutation from '../graphql/issue_set_labels.mutation.graphql';
import issueSetMilestoneMutation from '../graphql/issue_set_milestone.mutation.graphql';
import listsIssuesQuery from '../graphql/lists_issues.query.graphql';
import * as types from './mutation_types';
......@@ -479,30 +478,6 @@ export default {
});
},
setActiveIssueMilestone: async ({ commit, getters }, input) => {
const { activeBoardItem } = getters;
const { data } = await gqlClient.mutate({
mutation: issueSetMilestoneMutation,
variables: {
input: {
iid: String(activeBoardItem.iid),
milestoneId: getIdFromGraphQLId(input.milestoneId),
projectPath: input.projectPath,
},
},
});
if (data.updateIssue.errors?.length > 0) {
throw new Error(data.updateIssue.errors);
}
commit(types.UPDATE_BOARD_ITEM_BY_ID, {
itemId: activeBoardItem.id,
prop: 'milestone',
value: data.updateIssue.issue.milestone,
});
},
addListItem: ({ commit }, { list, item, position, inProgress = false }) => {
commit(types.ADD_BOARD_ITEM_TO_LIST, {
listId: list.id,
......
......@@ -20,10 +20,6 @@ fragment IssueNode on Issue {
epic {
id
}
milestone {
id
title
}
assignees {
nodes {
...User
......
......@@ -3750,9 +3750,6 @@ msgstr ""
msgid "An error occurred while updating the configuration."
msgstr ""
msgid "An error occurred while updating the milestone."
msgstr ""
msgid "An error occurred while updating the notification settings. Please try again."
msgstr ""
......@@ -21264,9 +21261,6 @@ msgstr ""
msgid "Milestones|Milestone %{milestoneTitle} was not found"
msgstr ""
msgid "Milestones|No milestones found"
msgstr ""
msgid "Milestones|Ongoing Issues (open and assigned)"
msgstr ""
......
import { GlLoadingIcon, GlDropdown } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { mockMilestone as TEST_MILESTONE } from 'jest/boards/mock_data';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
import BoardSidebarMilestoneSelect from '~/boards/components/sidebar/board_sidebar_milestone_select.vue';
import { createStore } from '~/boards/stores';
const TEST_ISSUE = { id: 'gid://gitlab/Issue/1', iid: 9, referencePath: 'h/b#2' };
describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () => {
let wrapper;
let store;
afterEach(() => {
wrapper.destroy();
store = null;
wrapper = null;
});
const createWrapper = ({ milestone = null, loading = false } = {}) => {
store = createStore();
store.state.boardItems = { [TEST_ISSUE.id]: { ...TEST_ISSUE, milestone } };
store.state.activeId = TEST_ISSUE.id;
wrapper = shallowMount(BoardSidebarMilestoneSelect, {
store,
provide: {
canUpdate: true,
},
data: () => ({
milestones: [TEST_MILESTONE],
}),
stubs: {
'board-editable-item': BoardEditableItem,
},
mocks: {
$apollo: {
loading,
},
},
});
};
const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]');
const findLoader = () => wrapper.find(GlLoadingIcon);
const findDropdown = () => wrapper.find(GlDropdown);
const findBoardEditableItem = () => wrapper.find(BoardEditableItem);
const findDropdownItem = () => wrapper.find('[data-testid="milestone-item"]');
const findUnsetMilestoneItem = () => wrapper.find('[data-testid="no-milestone-item"]');
const findNoMilestonesFoundItem = () => wrapper.find('[data-testid="no-milestones-found"]');
describe('when not editing', () => {
it('opens the milestone dropdown on clicking edit', async () => {
createWrapper();
wrapper.vm.$refs.dropdown.show = jest.fn();
await findBoardEditableItem().vm.$emit('open');
expect(wrapper.vm.$refs.dropdown.show).toHaveBeenCalledTimes(1);
});
});
describe('when editing', () => {
beforeEach(() => {
createWrapper();
jest.spyOn(wrapper.vm.$refs.sidebarItem, 'collapse');
});
it('collapses BoardEditableItem on clicking edit', async () => {
await findBoardEditableItem().vm.$emit('close');
expect(wrapper.vm.$refs.sidebarItem.collapse).toHaveBeenCalledTimes(1);
});
it('collapses BoardEditableItem on hiding dropdown', async () => {
await findDropdown().vm.$emit('hide');
expect(wrapper.vm.$refs.sidebarItem.collapse).toHaveBeenCalledTimes(1);
});
});
it('renders "None" when no milestone is selected', () => {
createWrapper();
expect(findCollapsed().text()).toBe('None');
});
it('renders milestone title when set', () => {
createWrapper({ milestone: TEST_MILESTONE });
expect(findCollapsed().text()).toContain(TEST_MILESTONE.title);
});
it('shows loader while Apollo is loading', async () => {
createWrapper({ milestone: TEST_MILESTONE, loading: true });
expect(findLoader().exists()).toBe(true);
});
it('shows message when error or no milestones found', async () => {
createWrapper();
await wrapper.setData({ milestones: [] });
expect(findNoMilestonesFoundItem().text()).toBe('No milestones found');
});
describe('when milestone is selected', () => {
beforeEach(async () => {
createWrapper();
jest.spyOn(wrapper.vm, 'setActiveIssueMilestone').mockImplementation(() => {
store.state.boardItems[TEST_ISSUE.id].milestone = TEST_MILESTONE;
});
findDropdownItem().vm.$emit('click');
await wrapper.vm.$nextTick();
});
it('collapses sidebar and renders selected milestone', () => {
expect(findCollapsed().isVisible()).toBe(true);
expect(findCollapsed().text()).toContain(TEST_MILESTONE.title);
});
it('commits change to the server', () => {
expect(wrapper.vm.setActiveIssueMilestone).toHaveBeenCalledWith({
milestoneId: TEST_MILESTONE.id,
projectPath: 'h/b',
});
});
});
describe('when milestone is set to "None"', () => {
beforeEach(async () => {
createWrapper({ milestone: TEST_MILESTONE });
jest.spyOn(wrapper.vm, 'setActiveIssueMilestone').mockImplementation(() => {
store.state.boardItems[TEST_ISSUE.id].milestone = null;
});
findUnsetMilestoneItem().vm.$emit('click');
await wrapper.vm.$nextTick();
});
it('collapses sidebar and renders "None"', () => {
expect(findCollapsed().isVisible()).toBe(true);
expect(findCollapsed().text()).toBe('None');
});
it('commits change to the server', () => {
expect(wrapper.vm.setActiveIssueMilestone).toHaveBeenCalledWith({
milestoneId: null,
projectPath: 'h/b',
});
});
});
describe('when the mutation fails', () => {
const testMilestone = { id: '1', title: 'Former milestone' };
beforeEach(async () => {
createWrapper({ milestone: testMilestone });
jest.spyOn(wrapper.vm, 'setActiveIssueMilestone').mockImplementation(() => {
throw new Error(['failed mutation']);
});
jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {});
findDropdownItem().vm.$emit('click');
await wrapper.vm.$nextTick();
});
it('collapses sidebar and renders former milestone', () => {
expect(findCollapsed().isVisible()).toBe(true);
expect(findCollapsed().text()).toContain(testMilestone.title);
expect(wrapper.vm.setError).toHaveBeenCalled();
});
});
});
......@@ -30,7 +30,6 @@ import {
mockIssue2,
rawIssue,
mockIssues,
mockMilestone,
labels,
mockActiveIssue,
mockGroupProjects,
......@@ -1495,60 +1494,6 @@ describe('setActiveItemSubscribed', () => {
});
});
describe('setActiveIssueMilestone', () => {
const state = { boardItems: { [mockIssue.id]: mockIssue } };
const getters = { activeBoardItem: mockIssue };
const testMilestone = {
...mockMilestone,
id: 'gid://gitlab/Milestone/1',
};
const input = {
milestoneId: testMilestone.id,
projectPath: 'h/b',
};
it('should commit milestone after setting the issue', (done) => {
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
data: {
updateIssue: {
issue: {
milestone: testMilestone,
},
errors: [],
},
},
});
const payload = {
itemId: getters.activeBoardItem.id,
prop: 'milestone',
value: testMilestone,
};
testAction(
actions.setActiveIssueMilestone,
input,
{ ...state, ...getters },
[
{
type: types.UPDATE_BOARD_ITEM_BY_ID,
payload,
},
],
[],
done,
);
});
it('throws error if fails', async () => {
jest
.spyOn(gqlClient, 'mutate')
.mockResolvedValue({ data: { updateIssue: { errors: ['failed mutation'] } } });
await expect(actions.setActiveIssueMilestone({ getters }, input)).rejects.toThrow(Error);
});
});
describe('setActiveItemTitle', () => {
const state = {
boardItems: { [mockIssue.id]: mockIssue },
......
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