diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-15 09:09:30 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-15 09:09:30 +0000 |
commit | 03c73563048c1f808a4a3fb302f0dcbba37f5f76 (patch) | |
tree | 9e4e9b9dffe111fe3779e1980d1dfa5cbfddb643 | |
parent | cc74c1d821edef69a8b32b2660336a44a14e3f3b (diff) | |
download | gitlab-ce-03c73563048c1f808a4a3fb302f0dcbba37f5f76.tar.gz |
Add latest changes from gitlab-org/gitlab@master
29 files changed, 367 insertions, 153 deletions
diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue index dae24338e45..23e4edea40f 100644 --- a/app/assets/javascripts/boards/components/board_column.vue +++ b/app/assets/javascripts/boards/components/board_column.vue @@ -1,9 +1,11 @@ <script> +import { mapGetters, mapActions } from 'vuex'; import Sortable from 'sortablejs'; import isWipLimitsOn from 'ee_else_ce/boards/mixins/is_wip_limits'; import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue'; import Tooltip from '~/vue_shared/directives/tooltip'; import EmptyComponent from '~/vue_shared/components/empty_component'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import BoardBlankState from './board_blank_state.vue'; import BoardList from './board_list.vue'; import boardsStore from '../stores/boards_store'; @@ -21,7 +23,7 @@ export default { directives: { Tooltip, }, - mixins: [isWipLimitsOn], + mixins: [isWipLimitsOn, glFeatureFlagMixin()], props: { list: { type: Object, @@ -62,6 +64,7 @@ export default { }; }, computed: { + ...mapGetters(['getIssues']), showBoardListAndBoardInfo() { return this.list.type !== ListType.blank && this.list.type !== ListType.promotion; }, @@ -69,19 +72,36 @@ export default { // eslint-disable-next-line @gitlab/require-i18n-strings return `boards.${this.boardId}.${this.list.type}.${this.list.id}`; }, + listIssues() { + if (!this.glFeatures.graphqlBoardLists) { + return this.list.issues; + } + return this.getIssues(this.list.id); + }, + shouldFetchIssues() { + return this.glFeatures.graphqlBoardLists && this.list.type !== ListType.blank; + }, }, watch: { filter: { handler() { - this.list.page = 1; - this.list.getIssues(true).catch(() => { - // TODO: handle request error - }); + if (this.shouldFetchIssues) { + this.fetchIssuesForList(this.list.id); + } else { + this.list.page = 1; + this.list.getIssues(true).catch(() => { + // TODO: handle request error + }); + } }, deep: true, }, }, mounted() { + if (this.shouldFetchIssues) { + this.fetchIssuesForList(this.list.id); + } + const instance = this; const sortableOptions = getBoardSortableDefaultOptions({ @@ -108,6 +128,7 @@ export default { Sortable.create(this.$el.parentNode, sortableOptions); }, methods: { + ...mapActions(['fetchIssuesForList']), showListNewIssueForm(listId) { eventHub.$emit('showForm', listId); }, @@ -142,7 +163,7 @@ export default { :disabled="disabled" :group-id="groupId || null" :issue-link-base="issueLinkBase" - :issues="list.issues" + :issues="listIssues" :list="list" :loading="list.loading" :root-path="rootPath" diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 1a26782f6f0..6b19f7f3353 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -6,6 +6,7 @@ import boardCard from './board_card.vue'; import eventHub from '../eventhub'; import boardsStore from '../stores/boards_store'; import { sprintf, __ } from '~/locale'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { deprecatedCreateFlash as createFlash } from '~/flash'; import { getBoardSortableDefaultOptions, @@ -24,6 +25,7 @@ export default { boardNewIssue, GlLoadingIcon, }, + mixins: [glFeatureFlagMixin()], props: { groupId: { type: Number, @@ -83,6 +85,7 @@ export default { deep: true, }, issues() { + if (this.glFeatures.graphqlBoardLists) return; this.$nextTick(() => { if ( this.scrollHeight() <= this.listHeight() && @@ -413,6 +416,8 @@ export default { this.showIssueForm = !this.showIssueForm; }, onScroll() { + if (this.glFeatures.graphqlBoardLists) return; + if (!this.list.loadingMore && this.scrollTop() > this.scrollHeight() - this.scrollOffset) { this.loadNextPage(); } diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue index c6c996a0074..0f27961cb3f 100644 --- a/app/assets/javascripts/boards/components/board_list_header.vue +++ b/app/assets/javascripts/boards/components/board_list_header.vue @@ -129,6 +129,9 @@ export default { collapsedTooltipTitle() { return this.listTitle || this.listAssignee; }, + shouldDisplaySwimlanes() { + return this.glFeatures.boardsWithSwimlanes && this.isSwimlanesOn; + }, }, methods: { ...mapActions(['updateList']), @@ -158,7 +161,7 @@ export default { } }, updateListFunction() { - if (this.glFeatures.boardsWithSwimlanes && this.isSwimlanesHeader) { + if (this.shouldDisplaySwimlanes || this.glFeatures.graphqlBoardLists) { this.updateList({ listId: this.list.id, collapsed: !this.list.isExpanded }); } else { this.list.update(); @@ -184,7 +187,7 @@ export default { <h3 :class="{ 'user-can-drag': !disabled && !list.preset, - 'gl-py-3': !list.isExpanded && !isSwimlanesHeader, + 'gl-py-3 gl-h-full': !list.isExpanded && !isSwimlanesHeader, 'gl-border-b-0': !list.isExpanded || isSwimlanesHeader, 'gl-py-2': !list.isExpanded && isSwimlanesHeader, }" diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue index 2817f9cb13d..924874d4dd2 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.vue +++ b/app/assets/javascripts/boards/components/board_new_issue.vue @@ -42,6 +42,9 @@ export default { } return this.title === ''; }, + shouldDisplaySwimlanes() { + return this.glFeatures.boardsWithSwimlanes && this.isSwimlanesOn; + }, }, mounted() { this.$refs.input.focus(); @@ -75,7 +78,7 @@ export default { eventHub.$emit(`scroll-board-list-${this.list.id}`); this.cancel(); - if (this.glFeatures.boardsWithSwimlanes && this.isSwimlanesOn) { + if (this.shouldDisplaySwimlanes || this.glFeatures.graphqlBoardLists) { this.addListIssue({ list: this.list, issue, position: 0 }); } @@ -85,7 +88,7 @@ export default { // Need this because our jQuery very kindly disables buttons on ALL form submissions $(this.$refs.submitButton).enable(); - if (!this.glFeatures.boardsWithSwimlanes || !this.isSwimlanesOn) { + if (!this.shouldDisplaySwimlanes && !this.glFeatures.graphqlBoardLists) { boardsStore.setIssueDetail(issue); boardsStore.setListDetail(this.list); } @@ -95,7 +98,7 @@ export default { $(this.$refs.submitButton).enable(); // Remove the issue - if (this.glFeatures.boardsWithSwimlanes && this.isSwimlanesOn) { + if (this.shouldDisplaySwimlanes || this.glFeatures.graphqlBoardLists) { this.addListIssueFailure({ list: this.list, issue }); } else { this.list.removeIssue(issue); diff --git a/app/assets/javascripts/boards/components/issuable_title.vue b/app/assets/javascripts/boards/components/issuable_title.vue new file mode 100644 index 00000000000..40627a9fab8 --- /dev/null +++ b/app/assets/javascripts/boards/components/issuable_title.vue @@ -0,0 +1,21 @@ +<script> +export default { + props: { + title: { + type: String, + required: true, + }, + refPath: { + type: String, + required: true, + }, + }, +}; +</script> + +<template> + <div data-testid="issue-title"> + <p class="gl-font-weight-bold">{{ title }}</p> + <p class="gl-mb-0">{{ refPath }}</p> + </div> +</template> diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js index f421faeb52d..fff89832bf0 100644 --- a/app/assets/javascripts/boards/filtered_search_boards.js +++ b/app/assets/javascripts/boards/filtered_search_boards.js @@ -27,7 +27,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager { updateObject(path) { this.store.path = path.substr(1); - if (gon.features.boardsWithSwimlanes) { + if (gon.features.boardsWithSwimlanes || gon.features.graphqlBoardLists) { boardsStore.updateFiltersUrl(); boardsStore.performSearch(); } diff --git a/app/assets/javascripts/boards/queries/issue.fragment.graphql b/app/assets/javascripts/boards/queries/issue.fragment.graphql index 21b52766190..4b429f875a6 100644 --- a/app/assets/javascripts/boards/queries/issue.fragment.graphql +++ b/app/assets/javascripts/boards/queries/issue.fragment.graphql @@ -7,15 +7,10 @@ fragment IssueNode on Issue { referencePath: reference(full: true) dueDate timeEstimate - weight confidential webUrl subscribed - blocked relativePosition - epic { - id - } assignees { nodes { ...User diff --git a/app/assets/javascripts/boards/queries/lists_issues.query.graphql b/app/assets/javascripts/boards/queries/lists_issues.query.graphql index a9fc99fd916..c66cdf68cf4 100644 --- a/app/assets/javascripts/boards/queries/lists_issues.query.graphql +++ b/app/assets/javascripts/boards/queries/lists_issues.query.graphql @@ -1,15 +1,16 @@ -#import "./issue.fragment.graphql" +#import "ee_else_ce/boards/queries/issue.fragment.graphql" query ListIssues( $fullPath: ID! $boardId: ID! + $id: ID $filters: BoardIssueInput $isGroup: Boolean = false $isProject: Boolean = false ) { group(fullPath: $fullPath) @include(if: $isGroup) { board(id: $boardId) { - lists { + lists(id: $id) { nodes { id issues(filters: $filters) { @@ -23,7 +24,7 @@ query ListIssues( } project(fullPath: $fullPath) @include(if: $isProject) { board(id: $boardId) { - lists { + lists(id: $id) { nodes { id issues(filters: $filters) { diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js index f8767211abd..248e06ba264 100644 --- a/app/assets/javascripts/boards/stores/actions.js +++ b/app/assets/javascripts/boards/stores/actions.js @@ -79,10 +79,10 @@ export default { lists = lists.nodes.map(list => boardStore.updateListPosition({ ...list, - id: getIdFromGraphQLId(list.id), + doNotFetchIssues: true, }), ); - commit(types.RECEIVE_LISTS, sortBy(lists, 'position')); + commit(types.RECEIVE_BOARD_LISTS_SUCCESS, sortBy(lists, 'position')); // Backlog list needs to be created if it doesn't exist if (!lists.find(l => l.type === ListType.backlog)) { dispatch('createList', { backlog: true }); @@ -113,7 +113,7 @@ export default { commit(types.CREATE_LIST_FAILURE); } else { const list = data.boardListCreate?.list; - dispatch('addList', { ...list, id: getIdFromGraphQLId(list.id) }); + dispatch('addList', list); } }) .catch(() => { @@ -124,8 +124,8 @@ export default { addList: ({ state, commit }, list) => { const lists = state.boardLists; // Temporarily using positioning logic from boardStore - lists.push(boardStore.updateListPosition(list)); - commit(types.RECEIVE_LISTS, sortBy(lists, 'position')); + lists.push(boardStore.updateListPosition({ ...list, doNotFetchIssues: true })); + commit(types.RECEIVE_BOARD_LISTS_SUCCESS, sortBy(lists, 'position')); }, showWelcomeList: ({ state, dispatch }) => { @@ -197,8 +197,33 @@ export default { notImplemented(); }, - fetchIssuesForList: () => { - notImplemented(); + fetchIssuesForList: ({ state, commit }, listId) => { + const { endpoints, boardType, filterParams } = state; + const { fullPath, boardId } = endpoints; + + const variables = { + fullPath, + boardId: fullBoardId(boardId), + id: listId, + filters: filterParams, + isGroup: boardType === BoardType.group, + isProject: boardType === BoardType.project, + }; + + return gqlClient + .query({ + query: listsIssuesQuery, + context: { + isSingleRequest: true, + }, + variables, + }) + .then(({ data }) => { + const { lists } = data[boardType]?.board; + const listIssues = formatListIssues(lists); + commit(types.RECEIVE_ISSUES_FOR_LIST_SUCCESS, { listIssues, listId }); + }) + .catch(() => commit(types.RECEIVE_ISSUES_FOR_LIST_FAILURE, listId)); }, fetchIssuesForAllLists: ({ state, commit }) => { diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 4fdbfbc36c5..faf4f9ebfd3 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -304,7 +304,11 @@ const boardsStore = { onNewListIssueResponse(list, issue, data) { issue.refreshData(data); - if (!gon.features.boardsWithSwimlanes && list.issuesSize > 1) { + if ( + !gon.features.boardsWithSwimlanes && + !gon.features.graphqlBoardLists && + list.issues.length > 1 + ) { const moveBeforeId = list.issues[1].id; this.moveIssue(issue.id, null, null, null, moveBeforeId); } @@ -723,6 +727,10 @@ const boardsStore = { newListIssue(list, issue) { list.addIssue(issue, null, 0); list.issuesSize += 1; + let listId = list.id; + if (typeof listId === 'string') { + listId = getIdFromGraphQLId(listId); + } return this.newIssue(list.id, issue) .then(res => res.data) diff --git a/app/assets/javascripts/boards/stores/getters.js b/app/assets/javascripts/boards/stores/getters.js index 80acf8e66cc..3688476dc5f 100644 --- a/app/assets/javascripts/boards/stores/getters.js +++ b/app/assets/javascripts/boards/stores/getters.js @@ -14,6 +14,11 @@ export default { return state.issues[id] || {}; }, + getIssues: (state, getters) => listId => { + const listIssueIds = state.issuesByListId[listId] || []; + return listIssueIds.map(id => getters.getIssueById(id)); + }, + getActiveIssue: state => { return state.issues[state.activeId] || {}; }, diff --git a/app/assets/javascripts/boards/stores/mutation_types.js b/app/assets/javascripts/boards/stores/mutation_types.js index 5a3d62dc703..f0a283f6161 100644 --- a/app/assets/javascripts/boards/stores/mutation_types.js +++ b/app/assets/javascripts/boards/stores/mutation_types.js @@ -2,7 +2,7 @@ export const SET_INITIAL_BOARD_DATA = 'SET_INITIAL_BOARD_DATA'; export const SET_FILTERS = 'SET_FILTERS'; export const CREATE_LIST_SUCCESS = 'CREATE_LIST_SUCCESS'; export const CREATE_LIST_FAILURE = 'CREATE_LIST_FAILURE'; -export const RECEIVE_LISTS = 'RECEIVE_LISTS'; +export const RECEIVE_BOARD_LISTS_SUCCESS = 'RECEIVE_BOARD_LISTS_SUCCESS'; export const SHOW_PROMOTION_LIST = 'SHOW_PROMOTION_LIST'; export const REQUEST_ADD_LIST = 'REQUEST_ADD_LIST'; export const RECEIVE_ADD_LIST_SUCCESS = 'RECEIVE_ADD_LIST_SUCCESS'; @@ -13,6 +13,8 @@ export const REQUEST_REMOVE_LIST = 'REQUEST_REMOVE_LIST'; export const RECEIVE_REMOVE_LIST_SUCCESS = 'RECEIVE_REMOVE_LIST_SUCCESS'; export const RECEIVE_REMOVE_LIST_ERROR = 'RECEIVE_REMOVE_LIST_ERROR'; export const REQUEST_ISSUES_FOR_ALL_LISTS = 'REQUEST_ISSUES_FOR_ALL_LISTS'; +export const RECEIVE_ISSUES_FOR_LIST_FAILURE = 'RECEIVE_ISSUES_FOR_LIST_FAILURE'; +export const RECEIVE_ISSUES_FOR_LIST_SUCCESS = 'RECEIVE_ISSUES_FOR_LIST_SUCCESS'; export const RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS = 'RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS'; export const RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE = 'RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE'; export const REQUEST_ADD_ISSUE = 'REQUEST_ADD_ISSUE'; diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js index a03c541ada5..faeb3e25a71 100644 --- a/app/assets/javascripts/boards/stores/mutations.js +++ b/app/assets/javascripts/boards/stores/mutations.js @@ -35,7 +35,7 @@ export default { state.showPromotion = showPromotion; }, - [mutationTypes.RECEIVE_LISTS]: (state, lists) => { + [mutationTypes.RECEIVE_BOARD_LISTS_SUCCESS]: (state, lists) => { state.boardLists = lists; }, @@ -89,6 +89,20 @@ export default { notImplemented(); }, + [mutationTypes.RECEIVE_ISSUES_FOR_LIST_SUCCESS]: (state, { listIssues, listId }) => { + const { listData, issues } = listIssues; + Vue.set(state, 'issues', { ...state.issues, ...issues }); + Vue.set(state.issuesByListId, listId, listData[listId]); + const listIndex = state.boardLists.findIndex(l => l.id === listId); + Vue.set(state.boardLists[listIndex], 'loading', false); + }, + + [mutationTypes.RECEIVE_ISSUES_FOR_LIST_FAILURE]: (state, listId) => { + state.error = __('An error occurred while fetching the board issues. Please reload the page.'); + const listIndex = state.boardLists.findIndex(l => l.id === listId); + Vue.set(state.boardLists[listIndex], 'loading', false); + }, + [mutationTypes.REQUEST_ISSUES_FOR_ALL_LISTS]: state => { state.isLoadingIssues = true; }, diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index c8b7168dce1..02396a4ba1b 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -1,32 +1,26 @@ <script> import { mapActions, mapGetters, mapState } from 'vuex'; import { escape } from 'lodash'; -import { GlButton, GlLoadingIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; +import { GlLoadingIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -import { sprintf } from '~/locale'; +import { __, sprintf } from '~/locale'; import { deprecatedCreateFlash as createFlash } from '~/flash'; import { hasDiff } from '~/helpers/diffs_helper'; import eventHub from '../../notes/event_hub'; import DiffFileHeader from './diff_file_header.vue'; import DiffContent from './diff_content.vue'; import { diffViewerErrors } from '~/ide/constants'; -import { GENERIC_ERROR, DIFF_FILE } from '../i18n'; export default { components: { DiffFileHeader, DiffContent, - GlButton, GlLoadingIcon, }, directives: { SafeHtml, }, mixins: [glFeatureFlagsMixin()], - i18n: { - genericError: GENERIC_ERROR, - ...DIFF_FILE, - }, props: { file: { type: Object, @@ -59,7 +53,7 @@ export default { ...mapGetters('diffs', ['getDiffFileDiscussions']), viewBlobLink() { return sprintf( - this.i18n.blobView, + __('You can %{linkStart}view the blob%{linkEnd} instead.'), { linkStart: `<a href="${escape(this.file.view_path)}">`, linkEnd: '</a>', @@ -81,7 +75,9 @@ export default { }, forkMessage() { return sprintf( - this.i18n.editInFork, + __( + "You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request.", + ), { tag_start: '<span class="js-file-fork-suggestion-section-action">', tag_end: '</span>', @@ -152,7 +148,7 @@ export default { }) .catch(() => { this.isLoadingCollapsedDiff = false; - createFlash(this.i18n.genericError); + createFlash(__('Something went wrong on our end. Please try again!')); }); }, showForkMessage() { @@ -192,14 +188,14 @@ export default { <a :href="file.fork_path" class="js-fork-suggestion-button btn btn-grouped btn-inverted btn-success" - >{{ $options.i18n.fork }}</a + >{{ __('Fork') }}</a > <button class="js-cancel-fork-suggestion-button btn btn-grouped" type="button" @click="hideForkMessage" > - {{ $options.i18n.cancel }} + {{ __('Cancel') }} </button> </div> <gl-loading-icon v-if="showLoadingIcon" class="diff-content loading" /> @@ -209,17 +205,11 @@ export default { <div v-safe-html="errorMessage" class="nothing-here-block"></div> </div> <template v-else> - <div v-show="isCollapsed" class="gl-p-7 gl-text-center collapsed-file-warning"> - <p class="gl-mb-8 gl-mt-5"> - {{ $options.i18n.collapsed }} - </p> - <gl-button - class="gl-alert-action gl-mb-5" - data-testid="expandButton" - @click="handleToggle" - > - {{ $options.i18n.expand }} - </gl-button> + <div v-show="isCollapsed" class="nothing-here-block diff-collapsed"> + {{ __('This diff is collapsed.') }} + <a class="click-to-expand js-click-to-expand" href="#" @click.prevent="handleToggle">{{ + __('Click to expand it.') + }}</a> </div> <diff-content v-show="!isCollapsed && !isFileTooLarge" diff --git a/app/assets/javascripts/diffs/i18n.js b/app/assets/javascripts/diffs/i18n.js deleted file mode 100644 index 8b91543587c..00000000000 --- a/app/assets/javascripts/diffs/i18n.js +++ /dev/null @@ -1,14 +0,0 @@ -import { __ } from '~/locale'; - -export const GENERIC_ERROR = __('Something went wrong on our end. Please try again!'); - -export const DIFF_FILE = { - blobView: __('You can %{linkStart}view the blob%{linkEnd} instead.'), - editInFork: __( - "You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request.", - ), - fork: __('Fork'), - cancel: __('Cancel'), - collapsed: __('This file is collapsed.'), - expand: __('Expand file'), -}; diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 7238ff6c77d..c4852974a4d 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -116,7 +116,6 @@ .board-title { flex-direction: column; - height: 100%; } .board-title-caret { diff --git a/app/views/registrations/welcome.html.haml b/app/views/registrations/welcome.html.haml index bdde5de0f61..068ce70b9ea 100644 --- a/app/views/registrations/welcome.html.haml +++ b/app/views/registrations/welcome.html.haml @@ -23,4 +23,3 @@ = render "registrations/welcome/button" - else = f.submit _('Get started!'), class: 'btn-register btn btn-block gl-mb-0 gl-p-3' - diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml index 7a4c495e177..86c73e36317 100644 --- a/app/views/shared/boards/_show.html.haml +++ b/app/views/shared/boards/_show.html.haml @@ -19,7 +19,7 @@ #board-app.boards-app.position-relative{ "v-cloak" => "true", data: board_data, ":class" => "{ 'is-compact': detailIssueVisible }" } = render 'shared/issuable/search_bar', type: :boards, board: board - - if Feature.enabled?(:boards_with_swimlanes, current_board_parent) + - if Feature.enabled?(:boards_with_swimlanes, current_board_parent) || Feature.enabled?(:graphql_board_lists, current_board_parent) %board-content{ "v-cloak" => "true", "ref" => "board_content", ":lists" => "state.lists", diff --git a/changelogs/unreleased/feature-highlight-collapsed-diff-files.yml b/changelogs/unreleased/feature-highlight-collapsed-diff-files.yml deleted file mode 100644 index 9c066e1ffa3..00000000000 --- a/changelogs/unreleased/feature-highlight-collapsed-diff-files.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Expand the visible highlight for collapsed diffs -merge_request: 41393 -author: -type: other diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index 3cb04a59ed6..2a49005da1b 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -300,7 +300,7 @@ include Gitlab::Database::MigrationHelpers def up with_lock_retries do - add_foreign_key :imports, :projects, column: :project_id, on_delete: :cascade + add_foreign_key :imports, :projects, column: :project_id, on_delete: :cascade # rubocop:disable Migration/AddConcurrentForeignKey end end @@ -318,7 +318,7 @@ include Gitlab::Database::MigrationHelpers def up with_lock_retries do - add_foreign_key :imports, :users, column: :user_id, on_delete: :cascade + add_foreign_key :imports, :users, column: :user_id, on_delete: :cascade # rubocop:disable Migration/AddConcurrentForeignKey end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index fe3915db883..668e833072b 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -10357,9 +10357,6 @@ msgstr "" msgid "Expand dropdown" msgstr "" -msgid "Expand file" -msgstr "" - msgid "Expand milestones" msgstr "" @@ -25733,9 +25730,6 @@ msgstr "" msgid "This field is required." msgstr "" -msgid "This file is collapsed." -msgstr "" - msgid "This group" msgstr "" diff --git a/spec/features/groups/board_sidebar_spec.rb b/spec/features/groups/board_sidebar_spec.rb index 3bbeed10948..690d661ba2f 100644 --- a/spec/features/groups/board_sidebar_spec.rb +++ b/spec/features/groups/board_sidebar_spec.rb @@ -19,6 +19,8 @@ RSpec.describe 'Group Issue Boards', :js do let(:card) { find('.board:nth-child(1)').first('.board-card') } before do + # stubbing until sidebar work is done: https://gitlab.com/gitlab-org/gitlab/-/issues/230711 + stub_feature_flags(graphql_board_lists: false) sign_in(user) visit group_board_path(group, board) diff --git a/spec/features/merge_request/user_expands_diff_spec.rb b/spec/features/merge_request/user_expands_diff_spec.rb index b0cfa8d0d54..fc17ef011c2 100644 --- a/spec/features/merge_request/user_expands_diff_spec.rb +++ b/spec/features/merge_request/user_expands_diff_spec.rb @@ -17,11 +17,11 @@ RSpec.describe 'User expands diff', :js do it 'allows user to expand diff' do page.within find('[id="19763941ab80e8c09871c0a425f0560d9053bcb3"]') do - find('[data-testid="expandButton"]').click + click_link 'Click to expand it.' wait_for_requests - expect(page).not_to have_content('Expand File') + expect(page).not_to have_content('Click to expand it.') expect(page).to have_selector('.code') end end diff --git a/spec/frontend/boards/components/issuable_title_spec.js b/spec/frontend/boards/components/issuable_title_spec.js new file mode 100644 index 00000000000..4b7f491b998 --- /dev/null +++ b/spec/frontend/boards/components/issuable_title_spec.js @@ -0,0 +1,33 @@ +import { shallowMount } from '@vue/test-utils'; +import IssuableTitle from '~/boards/components/issuable_title.vue'; + +describe('IssuableTitle', () => { + let wrapper; + const defaultProps = { + title: 'One', + refPath: 'path', + }; + const createComponent = () => { + wrapper = shallowMount(IssuableTitle, { + propsData: { ...defaultProps }, + }); + }; + const findIssueContent = () => wrapper.find('[data-testid="issue-title"]'); + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('renders a title of an issue in the sidebar', () => { + expect(findIssueContent().text()).toContain('One'); + }); + + it('renders a referencePath of an issue in the sidebar', () => { + expect(findIssueContent().text()).toContain('path'); + }); +}); diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js index 717ab5f3add..a84970eb736 100644 --- a/spec/frontend/boards/mock_data.js +++ b/spec/frontend/boards/mock_data.js @@ -98,12 +98,35 @@ export const mockMilestone = { due_date: '2019-12-31', }; +const assignees = [ + { + id: 'gid://gitlab/User/2', + username: 'angelina.herman', + name: 'Bernardina Bosco', + avatar: 'https://www.gravatar.com/avatar/eb7b664b13a30ad9f9ba4b61d7075470?s=80&d=identicon', + webUrl: 'http://127.0.0.1:3000/angelina.herman', + }, +]; + +const labels = [ + { + id: 'gid://gitlab/GroupLabel/5', + title: 'Cosync', + color: '#34ebec', + description: null, + }, +]; + export const rawIssue = { - title: 'Testing', - id: 'gid://gitlab/Issue/1', - iid: 1, + title: 'Issue 1', + id: 'gid://gitlab/Issue/436', + iid: 27, + dueDate: null, + timeEstimate: 0, + weight: null, confidential: false, - referencePath: 'gitlab-org/gitlab-test#1', + referencePath: 'gitlab-org/gitlab-test#27', + path: '/gitlab-org/gitlab-test/-/issues/27', labels: { nodes: [ { @@ -115,23 +138,24 @@ export const rawIssue = { ], }, assignees: { - nodes: [ - { - id: 1, - name: 'name', - username: 'username', - avatar_url: 'http://avatar_url', - }, - ], + nodes: assignees, + }, + epic: { + id: 'gid://gitlab/Epic/41', }, }; export const mockIssue = { - title: 'Testing', - id: 1, - iid: 1, + id: 'gid://gitlab/Issue/436', + iid: 27, + title: 'Issue 1', + dueDate: null, + timeEstimate: 0, + weight: null, confidential: false, - referencePath: 'gitlab-org/gitlab-test#1', + referencePath: 'gitlab-org/gitlab-test#27', + path: '/gitlab-org/gitlab-test/-/issues/27', + assignees, labels: [ { id: 1, @@ -140,44 +164,64 @@ export const mockIssue = { description: 'testing', }, ], - assignees: [ - { - id: 1, - name: 'name', - username: 'username', - avatar_url: 'http://avatar_url', - }, - ], + epic: { + id: 'gid://gitlab/Epic/41', + }, }; export const mockIssueWithModel = new ListIssue(mockIssue); export const mockIssue2 = { - title: 'Planning', - id: 2, - iid: 2, + id: 'gid://gitlab/Issue/437', + iid: 28, + title: 'Issue 2', + dueDate: null, + timeEstimate: 0, + weight: null, confidential: false, referencePath: 'gitlab-org/gitlab-test#2', - labels: [ - { - id: 1, - title: 'plan', - color: 'blue', - description: 'planning', - }, - ], - assignees: [ - { - id: 1, - name: 'name', - username: 'username', - avatar_url: 'http://avatar_url', - }, - ], + path: '/gitlab-org/gitlab-test/-/issues/28', + assignees, + labels, + epic: { + id: 'gid://gitlab/Epic/40', + }, }; export const mockIssue2WithModel = new ListIssue(mockIssue2); +export const mockIssue3 = { + id: 'gid://gitlab/Issue/438', + iid: 29, + title: 'Issue 3', + referencePath: '#29', + dueDate: null, + timeEstimate: 0, + weight: null, + confidential: false, + path: '/gitlab-org/gitlab-test/-/issues/28', + assignees, + labels, + epic: null, +}; + +export const mockIssue4 = { + id: 'gid://gitlab/Issue/439', + iid: 30, + title: 'Issue 4', + referencePath: '#30', + dueDate: null, + timeEstimate: 0, + weight: null, + confidential: false, + path: '/gitlab-org/gitlab-test/-/issues/28', + assignees, + labels, + epic: null, +}; + +export const mockIssues = [mockIssue, mockIssue2]; + export const BoardsMockData = { GET: { '/test/-/boards/1/lists/300/issues?id=300&page=1': { @@ -239,6 +283,7 @@ export const mockLists = [ label: null, assignee: null, milestone: null, + loading: false, }, { id: 'gid://gitlab/List/2', @@ -255,9 +300,22 @@ export const mockLists = [ }, assignee: null, milestone: null, + loading: false, }, ]; export const mockListsWithModel = mockLists.map(listMock => Vue.observable(new List({ ...listMock, doNotFetchIssues: true })), ); + +export const mockIssuesByListId = { + 'gid://gitlab/List/1': [mockIssue.id, mockIssue3.id, mockIssue4.id], + 'gid://gitlab/List/2': mockIssues.map(({ id }) => id), +}; + +export const issues = { + [mockIssue.id]: mockIssue, + [mockIssue2.id]: mockIssue2, + [mockIssue3.id]: mockIssue3, + [mockIssue4.id]: mockIssue4, +}; diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js index 4eb1a370a9f..8efad4d3ac7 100644 --- a/spec/frontend/boards/stores/actions_spec.js +++ b/spec/frontend/boards/stores/actions_spec.js @@ -3,7 +3,6 @@ import { mockListsWithModel, mockLists, mockIssue, - mockIssue2, mockIssueWithModel, mockIssue2WithModel, rawIssue, @@ -134,7 +133,7 @@ describe('createList', () => { { backlog: true }, state, [], - [{ type: 'addList', payload: { ...backlogList, id: 1 } }], + [{ type: 'addList', payload: backlogList }], done, ); }); @@ -232,19 +231,15 @@ describe('deleteList', () => { expectNotImplemented(actions.deleteList); }); -describe('fetchIssuesForList', () => { - expectNotImplemented(actions.fetchIssuesForList); -}); - describe('moveIssue', () => { const listIssues = { - 'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id], + 'gid://gitlab/List/1': [436, 437], 'gid://gitlab/List/2': [], }; const issues = { - '1': mockIssueWithModel, - '2': mockIssue2WithModel, + '436': mockIssueWithModel, + '437': mockIssue2WithModel, }; const state = { @@ -269,7 +264,7 @@ describe('moveIssue', () => { testAction( actions.moveIssue, { - issueId: mockIssue.id, + issueId: '436', issueIid: mockIssue.iid, issuePath: mockIssue.referencePath, fromListId: 'gid://gitlab/List/1', @@ -308,7 +303,7 @@ describe('moveIssue', () => { testAction( actions.moveIssue, { - issueId: mockIssue.id, + issueId: '436', issueIid: mockIssue.iid, issuePath: mockIssue.referencePath, fromListId: 'gid://gitlab/List/1', diff --git a/spec/frontend/boards/stores/getters_spec.js b/spec/frontend/boards/stores/getters_spec.js index 267451845ba..288143a0f21 100644 --- a/spec/frontend/boards/stores/getters_spec.js +++ b/spec/frontend/boards/stores/getters_spec.js @@ -1,5 +1,6 @@ import getters from '~/boards/stores/getters'; import { inactiveId } from '~/boards/constants'; +import { mockIssue, mockIssue2, mockIssues, mockIssuesByListId, issues } from '../mock_data'; describe('Boards - Getters', () => { describe('getLabelToggleState', () => { @@ -115,4 +116,18 @@ describe('Boards - Getters', () => { expect(getters.getActiveIssue(state)).toEqual(expected); }); }); + + describe('getIssues', () => { + const boardsState = { + issuesByListId: mockIssuesByListId, + issues, + }; + it('returns issues for a given listId', () => { + const getIssueById = issueId => [mockIssue, mockIssue2].find(({ id }) => id === issueId); + + expect(getters.getIssues(boardsState, { getIssueById })('gid://gitlab/List/2')).toEqual( + mockIssues, + ); + }); + }); }); diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js index 350eccfa82e..a13a99a507e 100644 --- a/spec/frontend/boards/stores/mutations_spec.js +++ b/spec/frontend/boards/stores/mutations_spec.js @@ -54,11 +54,11 @@ describe('Board Store Mutations', () => { }); }); - describe('RECEIVE_LISTS', () => { + describe('RECEIVE_BOARD_LISTS_SUCCESS', () => { it('Should set boardLists to state', () => { const lists = [listObj, listObjDuplicate]; - mutations[types.RECEIVE_LISTS](state, lists); + mutations[types.RECEIVE_BOARD_LISTS_SUCCESS](state, lists); expect(state.boardLists).toEqual(lists); }); @@ -145,6 +145,33 @@ describe('Board Store Mutations', () => { expectNotImplemented(mutations.RECEIVE_REMOVE_LIST_ERROR); }); + describe('RECEIVE_ISSUES_FOR_LIST_SUCCESS', () => { + it('updates issuesByListId and issues on state', () => { + const listIssues = { + 'gid://gitlab/List/1': [mockIssue.id], + }; + const issues = { + '1': mockIssue, + }; + + state = { + ...state, + isLoadingIssues: true, + issuesByListId: {}, + issues: {}, + boardLists: mockListsWithModel, + }; + + mutations.RECEIVE_ISSUES_FOR_LIST_SUCCESS(state, { + listIssues: { listData: listIssues, issues }, + listId: 'gid://gitlab/List/1', + }); + + expect(state.issuesByListId).toEqual(listIssues); + expect(state.issues).toEqual(issues); + }); + }); + describe('REQUEST_ISSUES_FOR_ALL_LISTS', () => { it('sets isLoadingIssues to true', () => { expect(state.isLoadingIssues).toBe(false); @@ -155,10 +182,28 @@ describe('Board Store Mutations', () => { }); }); + describe('RECEIVE_ISSUES_FOR_LIST_FAILURE', () => { + it('sets error message', () => { + state = { + ...state, + boardLists: mockListsWithModel, + error: undefined, + }; + + const listId = 'gid://gitlab/List/1'; + + mutations.RECEIVE_ISSUES_FOR_LIST_FAILURE(state, listId); + + expect(state.error).toEqual( + 'An error occurred while fetching the board issues. Please reload the page.', + ); + }); + }); + describe('RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS', () => { it('sets isLoadingIssues to false and updates issuesByListId object', () => { const listIssues = { - '': [mockIssue.id], + 'gid://gitlab/List/1': [mockIssue.id], }; const issues = { '1': mockIssue, @@ -287,7 +332,7 @@ describe('Board Store Mutations', () => { describe('MOVE_ISSUE_SUCCESS', () => { it('updates issue in issues state', () => { const issues = { - '1': { id: rawIssue.id }, + '436': { id: rawIssue.id }, }; state = { @@ -299,7 +344,7 @@ describe('Board Store Mutations', () => { issue: rawIssue, }); - expect(state.issues).toEqual({ '1': { ...mockIssueWithModel, id: 1 } }); + expect(state.issues).toEqual({ '436': { ...mockIssueWithModel, id: 436 } }); }); }); diff --git a/spec/frontend/diffs/components/diff_file_spec.js b/spec/frontend/diffs/components/diff_file_spec.js index 3c39dd2d385..0b0a7f966c5 100644 --- a/spec/frontend/diffs/components/diff_file_spec.js +++ b/spec/frontend/diffs/components/diff_file_spec.js @@ -90,8 +90,8 @@ describe('DiffFile', () => { vm.isCollapsed = true; vm.$nextTick(() => { - expect(vm.$el.innerText).toContain('This file is collapsed.'); - expect(vm.$el.querySelector('[data-testid="expandButton"]')).not.toBeFalsy(); + expect(vm.$el.innerText).toContain('This diff is collapsed'); + expect(vm.$el.querySelectorAll('.js-click-to-expand').length).toEqual(1); done(); }); @@ -102,8 +102,8 @@ describe('DiffFile', () => { vm.isCollapsed = true; vm.$nextTick(() => { - expect(vm.$el.innerText).toContain('This file is collapsed.'); - expect(vm.$el.querySelector('[data-testid="expandButton"]')).not.toBeFalsy(); + expect(vm.$el.innerText).toContain('This diff is collapsed'); + expect(vm.$el.querySelectorAll('.js-click-to-expand').length).toEqual(1); done(); }); @@ -121,8 +121,8 @@ describe('DiffFile', () => { vm.isCollapsed = true; vm.$nextTick(() => { - expect(vm.$el.innerText).toContain('This file is collapsed.'); - expect(vm.$el.querySelector('[data-testid="expandButton"]')).not.toBeFalsy(); + expect(vm.$el.innerText).toContain('This diff is collapsed'); + expect(vm.$el.querySelectorAll('.js-click-to-expand').length).toEqual(1); done(); }); @@ -135,7 +135,7 @@ describe('DiffFile', () => { vm.file.viewer.name = diffViewerModes.renamed; vm.$nextTick(() => { - expect(vm.$el.innerText).not.toContain('This file is collapsed.'); + expect(vm.$el.innerText).not.toContain('This diff is collapsed'); done(); }); @@ -148,7 +148,7 @@ describe('DiffFile', () => { vm.file.viewer.name = diffViewerModes.mode_changed; vm.$nextTick(() => { - expect(vm.$el.innerText).not.toContain('This file is collapsed.'); + expect(vm.$el.innerText).not.toContain('This diff is collapsed'); done(); }); |