summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-15 09:09:30 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-15 09:09:30 +0000
commit03c73563048c1f808a4a3fb302f0dcbba37f5f76 (patch)
tree9e4e9b9dffe111fe3779e1980d1dfa5cbfddb643
parentcc74c1d821edef69a8b32b2660336a44a14e3f3b (diff)
downloadgitlab-ce-03c73563048c1f808a4a3fb302f0dcbba37f5f76.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/boards/components/board_column.vue33
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue5
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue7
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue9
-rw-r--r--app/assets/javascripts/boards/components/issuable_title.vue21
-rw-r--r--app/assets/javascripts/boards/filtered_search_boards.js2
-rw-r--r--app/assets/javascripts/boards/queries/issue.fragment.graphql5
-rw-r--r--app/assets/javascripts/boards/queries/lists_issues.query.graphql7
-rw-r--r--app/assets/javascripts/boards/stores/actions.js39
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js10
-rw-r--r--app/assets/javascripts/boards/stores/getters.js5
-rw-r--r--app/assets/javascripts/boards/stores/mutation_types.js4
-rw-r--r--app/assets/javascripts/boards/stores/mutations.js16
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue38
-rw-r--r--app/assets/javascripts/diffs/i18n.js14
-rw-r--r--app/assets/stylesheets/pages/boards.scss1
-rw-r--r--app/views/registrations/welcome.html.haml1
-rw-r--r--app/views/shared/boards/_show.html.haml2
-rw-r--r--changelogs/unreleased/feature-highlight-collapsed-diff-files.yml5
-rw-r--r--doc/development/migration_style_guide.md4
-rw-r--r--locale/gitlab.pot6
-rw-r--r--spec/features/groups/board_sidebar_spec.rb2
-rw-r--r--spec/features/merge_request/user_expands_diff_spec.rb4
-rw-r--r--spec/frontend/boards/components/issuable_title_spec.js33
-rw-r--r--spec/frontend/boards/mock_data.js144
-rw-r--r--spec/frontend/boards/stores/actions_spec.js17
-rw-r--r--spec/frontend/boards/stores/getters_spec.js15
-rw-r--r--spec/frontend/boards/stores/mutations_spec.js55
-rw-r--r--spec/frontend/diffs/components/diff_file_spec.js16
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();
});