diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index ee5bbe50a455265be956905f6932d998fe94701d..e56618ef710d09f399a90c92a3f7c0b307038a1f 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -9,6 +9,7 @@ import { sortableStart, sortableEnd } from '~/sortable/utils'; import Tracking from '~/tracking'; import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql'; import setActiveBoardItemMutation from 'ee_else_ce/boards/graphql/client/set_active_board_item.mutation.graphql'; +import BoardNewIssue from 'ee_else_ce/boards/components/board_new_issue.vue'; import BoardCardMoveToPosition from '~/boards/components/board_card_move_to_position.vue'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { @@ -26,7 +27,6 @@ import { } from '../graphql/cache_updates'; import { shouldCloneCard, moveItemVariables } from '../boards_util'; import BoardCard from './board_card.vue'; -import BoardNewIssue from './board_new_issue.vue'; import BoardCutLine from './board_cut_line.vue'; export default { diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue index 6c21ea4293625ece94f5624dafed2a674ec0754a..c8d5456cec5e0f0b703fd1446214ab559a1b809b 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.vue +++ b/app/assets/javascripts/boards/components/board_new_issue.vue @@ -1,7 +1,6 @@ <script> import { s__ } from '~/locale'; import { getMilestone, formatIssueInput, getBoardQuery } from 'ee_else_ce/boards/boards_util'; -import BoardNewIssueMixin from 'ee_else_ce/boards/mixins/board_new_issue'; import { setError } from '../graphql/cache_updates'; @@ -17,7 +16,6 @@ export default { BoardNewItem, ProjectSelect, }, - mixins: [BoardNewIssueMixin], inject: ['boardType', 'groupId', 'fullPath', 'isGroupBoard', 'isEpicBoard'], props: { list: { diff --git a/app/assets/javascripts/boards/mixins/board_new_issue.js b/app/assets/javascripts/boards/mixins/board_new_issue.js deleted file mode 100644 index d4b7454473539520740adf0ff1017b88994c81a3..0000000000000000000000000000000000000000 --- a/app/assets/javascripts/boards/mixins/board_new_issue.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - // EE-only - methods: { - extraIssueInput: () => {}, - }, -}; diff --git a/ee/app/assets/javascripts/boards/components/board_new_issue.vue b/ee/app/assets/javascripts/boards/components/board_new_issue.vue new file mode 100644 index 0000000000000000000000000000000000000000..014d1ed7603074520c71afd8932d79ee472083bd --- /dev/null +++ b/ee/app/assets/javascripts/boards/components/board_new_issue.vue @@ -0,0 +1,74 @@ +<script> +import { s__ } from '~/locale'; +import BoardNewIssueFoss from '~/boards/components/board_new_issue.vue'; +import { setError } from '~/boards/graphql/cache_updates'; +import { formatIssueInput } from '../boards_util'; +import { IterationIDs } from '../constants'; + +import currentIterationQuery from '../graphql/board_current_iteration.query.graphql'; + +// This is a false violation of @gitlab/no-runtime-template-compiler, since it +// extends a valid Vue single file component. +// eslint-disable-next-line @gitlab/no-runtime-template-compiler +export default { + extends: BoardNewIssueFoss, + data() { + return { + currentIteration: {}, + }; + }, + apollo: { + currentIteration: { + query: currentIterationQuery, + context: { + isSingleRequest: true, + }, + variables() { + return { + isGroup: this.isGroupBoard, + fullPath: this.fullPath, + }; + }, + update(data) { + return data[this.boardType]?.iterations?.nodes?.[0]; + }, + skip() { + const { iteration, iterationCadence } = this.board; + return iteration?.id !== IterationIDs.CURRENT || iterationCadence?.id !== undefined; + }, + error(error) { + setError({ + error, + message: s__('Boards|No cadence matches current iteration filter'), + }); + }, + }, + }, + methods: { + addNewIssueToList({ issueInput }) { + const { labels, assignee, milestone, weight, iteration, iterationCadence } = this.board; + const config = { + labels, + assigneeId: assignee?.id || null, + milestoneId: milestone?.id || null, + weight, + iterationId: iteration?.id || null, + iterationCadenceId: iterationCadence?.id || null, + }; + + // When board is scoped to current iteration we need to fetch and assign a cadence to the issue being created + if (!config.iterationCadenceId && config.iterationId === IterationIDs.CURRENT) { + config.iterationCadenceId = this.currentIteration.iterationCadence.id; + } + + const input = formatIssueInput(issueInput, config); + + if (!this.isGroupBoard) { + input.projectPath = this.fullPath; + } + + this.$emit('addNewIssue', input); + }, + }, +}; +</script> diff --git a/ee/app/assets/javascripts/boards/mixins/board_new_issue.js b/ee/app/assets/javascripts/boards/mixins/board_new_issue.js deleted file mode 100644 index 79af3a6e0899c30bc9aee55ab8af906589780f15..0000000000000000000000000000000000000000 --- a/ee/app/assets/javascripts/boards/mixins/board_new_issue.js +++ /dev/null @@ -1,14 +0,0 @@ -export default { - inject: ['groupId', 'weightFeatureAvailable', 'boardWeight'], - methods: { - extraIssueInput() { - if (this.weightFeatureAvailable) { - return { - weight: this.boardWeight >= 0 ? this.boardWeight : null, - }; - } - - return {}; - }, - }, -}; diff --git a/ee/spec/features/boards/new_issue_spec.rb b/ee/spec/features/boards/new_issue_spec.rb index efa37dba7690fae85b4a36bc35899afddb9ed790..0361306d6b8b1fcd47c97a78047ed3d09f87ab14 100644 --- a/ee/spec/features/boards/new_issue_spec.rb +++ b/ee/spec/features/boards/new_issue_spec.rb @@ -58,7 +58,8 @@ describe 'board scoped to current iteration' do let!(:iteration) do - create(:current_iteration, iterations_cadence: create(:iterations_cadence, group: group), + create(:current_iteration, title: 'Iteration 1', + iterations_cadence: create(:iterations_cadence, group: group), start_date: 3.days.ago, due_date: 3.days.from_now) end @@ -102,7 +103,7 @@ def scope_board_to_current_iteration click_button 'Edit' page.within(".dropdown-menu") do - click_button "Current" + click_button "Current iteration" end end diff --git a/ee/spec/frontend/boards/components/board_new_issue_spec.js b/ee/spec/frontend/boards/components/board_new_issue_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..fe3d53dea9dae7507aeac6139780f44b99525dce --- /dev/null +++ b/ee/spec/frontend/boards/components/board_new_issue_spec.js @@ -0,0 +1,92 @@ +import { shallowMount } from '@vue/test-utils'; +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; +import waitForPromises from 'helpers/wait_for_promises'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import BoardNewIssue from 'ee/boards/components/board_new_issue.vue'; +import currentIterationQuery from 'ee/boards/graphql/board_current_iteration.query.graphql'; +import BoardNewItem from '~/boards/components/board_new_item.vue'; +import groupBoardQuery from '~/boards/graphql/group_board.query.graphql'; + +import { mockList, mockGroupProjects, mockGroupBoardResponse } from 'jest/boards/mock_data'; +import { + mockGroupBoardCurrentIterationResponse, + currentIterationQueryResponse, +} from '../mock_data'; + +Vue.use(VueApollo); + +const groupBoardQueryHandlerSuccess = jest.fn().mockResolvedValue(mockGroupBoardResponse); +const currentIterationBoardQueryHandlerSuccess = jest + .fn() + .mockResolvedValue(mockGroupBoardCurrentIterationResponse); +const currentIterationQueryHandlerSuccess = jest + .fn() + .mockResolvedValue(currentIterationQueryResponse); + +const createComponent = ({ + isGroupBoard = true, + data = { selectedProject: mockGroupProjects[0] }, + provide = {}, + boardQueryHandler = groupBoardQueryHandlerSuccess, +} = {}) => { + const mockApollo = createMockApollo([ + [groupBoardQuery, boardQueryHandler], + [currentIterationQuery, currentIterationQueryHandlerSuccess], + ]); + return shallowMount(BoardNewIssue, { + apolloProvider: mockApollo, + propsData: { + list: mockList, + boardId: 'gid://gitlab/Board/1', + }, + data: () => data, + provide: { + groupId: 1, + fullPath: mockGroupProjects[0].fullPath, + weightFeatureAvailable: false, + boardWeight: null, + isGroupBoard, + boardType: isGroupBoard ? 'group' : 'project', + isEpicBoard: false, + ...provide, + }, + stubs: { + BoardNewItem, + }, + }); +}; + +describe('Issue boards new issue form', () => { + let wrapper; + + const findBoardNewItem = () => wrapper.findComponent(BoardNewItem); + + it('does not fetch current iteration and cadence by default', async () => { + wrapper = createComponent(); + + await nextTick(); + findBoardNewItem().vm.$emit('form-submit', { title: 'Foo' }); + + await nextTick(); + expect(currentIterationQueryHandlerSuccess).not.toHaveBeenCalled(); + }); + + it('fetches current iteration and cadence when board scope is set to current iteration without a cadence', async () => { + wrapper = createComponent({ boardQueryHandler: currentIterationBoardQueryHandlerSuccess }); + + await waitForPromises(); + findBoardNewItem().vm.$emit('form-submit', { title: 'Foo' }); + + await waitForPromises(); + expect(currentIterationQueryHandlerSuccess).toHaveBeenCalled(); + expect(wrapper.emitted('addNewIssue')).toEqual([ + [ + expect.objectContaining({ + iterationCadenceId: 'gid://gitlab/Iterations::Cadence/1', + iterationWildcardId: 'CURRENT', + }), + ], + ]); + }); +}); diff --git a/ee/spec/frontend/boards/mock_data.js b/ee/spec/frontend/boards/mock_data.js index b38cd0df6aeaf28ca81d20a6b07f4fc45faf75df..30125d3c84a189939b6ecc2eec66fdbd61c717f2 100644 --- a/ee/spec/frontend/boards/mock_data.js +++ b/ee/spec/frontend/boards/mock_data.js @@ -935,3 +935,49 @@ export const createEpicMutationResponse = { }, }, }; + +export const mockGroupBoardCurrentIterationResponse = { + data: { + workspace: { + id: 'gid://gitlab/Group/1', + board: { + id: 'gid://gitlab/Board/1', + name: 'Current iteration board', + hideBacklogList: false, + hideClosedList: false, + labels: [], + milestone: null, + assignee: null, + weight: null, + iterationCadence: null, + iteration: { + id: 'gid://gitlab/Iteration/-4', + title: 'Current', + __typename: 'Iteration', + }, + __typename: 'Board', + }, + __typename: 'Group', + }, + }, +}; + +export const currentIterationQueryResponse = { + data: { + group: { + id: 'gid://gitlab/Group/1', + iterations: { + nodes: [ + { + id: 'gid://gitlab/Iteration/1', + iterationCadence: { + id: 'gid://gitlab/Iterations::Cadence/1', + }, + __typename: 'Iteration', + }, + ], + }, + __typename: 'Group', + }, + }, +}; diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 65c3be8aee2275a4118f0073cdfa6b8d7d4ebd6f..3ef2c3a9423bab39b9096e9184caf47a8f3ec8f2 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8491,6 +8491,9 @@ msgstr "" msgid "Boards|New board" msgstr "" +msgid "Boards|No cadence matches current iteration filter" +msgstr "" + msgid "Boards|Retrieving blocking %{issuableType}s" msgstr ""