Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
cc74c1d821
commit
03c7356304
29 changed files with 367 additions and 153 deletions
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}"
|
||||
|
|
|
@ -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);
|
||||
|
|
21
app/assets/javascripts/boards/components/issuable_title.vue
Normal file
21
app/assets/javascripts/boards/components/issuable_title.vue
Normal file
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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] || {};
|
||||
},
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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'),
|
||||
};
|
|
@ -116,7 +116,6 @@
|
|||
|
||||
.board-title {
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.board-title-caret {
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Expand the visible highlight for collapsed diffs
|
||||
merge_request: 41393
|
||||
author:
|
||||
type: other
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
33
spec/frontend/boards/components/issuable_title_spec.js
Normal file
33
spec/frontend/boards/components/issuable_title_spec.js
Normal file
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 } });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue