Commit e9900469 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 7f3527b1 0a315e69
...@@ -23,31 +23,6 @@ Graphql/Descriptions: ...@@ -23,31 +23,6 @@ Graphql/Descriptions:
- 'ee/app/graphql/types/vulnerability_severity_enum.rb' - 'ee/app/graphql/types/vulnerability_severity_enum.rb'
- 'ee/app/graphql/types/vulnerability_state_enum.rb' - 'ee/app/graphql/types/vulnerability_state_enum.rb'
- 'ee/app/graphql/types/vulnerability_confidence_enum.rb' - 'ee/app/graphql/types/vulnerability_confidence_enum.rb'
- 'app/graphql/types/repository/blob_type.rb'
- 'app/graphql/types/root_storage_statistics_type.rb'
- 'app/graphql/types/snippet_type.rb'
- 'app/graphql/types/snippets/blob_type.rb'
- 'app/graphql/types/snippets/visibility_scopes_enum.rb'
- 'app/graphql/types/terraform/state_type.rb'
- 'app/graphql/types/terraform/state_version_type.rb'
- 'app/graphql/types/timelog_type.rb'
- 'app/graphql/types/todo_state_enum.rb'
- 'app/graphql/types/todo_target_enum.rb'
- 'app/graphql/types/todo_type.rb'
- 'app/graphql/types/user_interface.rb'
- 'app/graphql/types/user_merge_request_interaction_type.rb'
- 'app/graphql/types/user_state_enum.rb'
- 'ee/app/graphql/ee/mutations/alert_management/http_integration/create.rb'
- 'ee/app/graphql/ee/mutations/alert_management/http_integration/update.rb'
- 'ee/app/graphql/ee/mutations/boards/issues/issue_move_list.rb'
- 'ee/app/graphql/ee/mutations/issues/create.rb'
- 'ee/app/graphql/ee/mutations/issues/update.rb'
- 'ee/app/graphql/ee/types/alert_management/http_integration_type.rb'
- 'ee/app/graphql/ee/types/board_list_type.rb'
- 'ee/app/graphql/ee/types/board_type.rb'
- 'ee/app/graphql/ee/types/group_type.rb'
- 'ee/app/graphql/ee/types/project_type.rb'
- 'ee/app/graphql/ee/types/query_type.rb'
- 'ee/app/graphql/mutations/app_sec/fuzzing/api/ci_configuration/create.rb' - 'ee/app/graphql/mutations/app_sec/fuzzing/api/ci_configuration/create.rb'
- 'ee/app/graphql/mutations/boards/epic_boards/create.rb' - 'ee/app/graphql/mutations/boards/epic_boards/create.rb'
- 'ee/app/graphql/mutations/boards/epic_boards/epic_move_list.rb' - 'ee/app/graphql/mutations/boards/epic_boards/epic_move_list.rb'
......
d924490032231edb9452acdaca7d8e4747cf6ab4 50eff62c1b1f5730c7ca18597493b0b2926e72ca
...@@ -2,9 +2,6 @@ ...@@ -2,9 +2,6 @@
import { GlFormRadio, GlFormRadioGroup, GlTooltipDirective as GlTooltip } from '@gitlab/ui'; import { GlFormRadio, GlFormRadioGroup, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue'; import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue';
import { ListType } from '~/boards/constants';
import boardsStore from '~/boards/stores/boards_store';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
export default { export default {
components: { components: {
...@@ -24,7 +21,7 @@ export default { ...@@ -24,7 +21,7 @@ export default {
}, },
computed: { computed: {
...mapState(['labels', 'labelsLoading']), ...mapState(['labels', 'labelsLoading']),
...mapGetters(['getListByLabelId', 'shouldUseGraphQL']), ...mapGetters(['getListByLabelId']),
columnForSelected() { columnForSelected() {
return this.getListByLabelId(this.selectedId); return this.getListByLabelId(this.selectedId);
}, },
...@@ -34,17 +31,6 @@ export default { ...@@ -34,17 +31,6 @@ export default {
}, },
methods: { methods: {
...mapActions(['createList', 'fetchLabels', 'highlightList', 'setAddColumnFormVisibility']), ...mapActions(['createList', 'fetchLabels', 'highlightList', 'setAddColumnFormVisibility']),
highlight(listId) {
if (this.shouldUseGraphQL) {
this.highlightList(listId);
} else {
const list = boardsStore.state.lists.find(({ id }) => id === listId);
list.highlighted = true;
setTimeout(() => {
list.highlighted = false;
}, 2000);
}
},
addList() { addList() {
if (!this.selectedLabel) { if (!this.selectedLabel) {
return; return;
...@@ -54,23 +40,11 @@ export default { ...@@ -54,23 +40,11 @@ export default {
if (this.columnForSelected) { if (this.columnForSelected) {
const listId = this.columnForSelected.id; const listId = this.columnForSelected.id;
this.highlight(listId); this.highlightList(listId);
return; return;
} }
if (this.shouldUseGraphQL) { this.createList({ labelId: this.selectedId });
this.createList({ labelId: this.selectedId });
} else {
const listObj = {
labelId: getIdFromGraphQLId(this.selectedId),
title: this.selectedLabel.title,
position: boardsStore.state.lists.length - 2,
list_type: ListType.label,
label: this.selectedLabel,
};
boardsStore.new(listObj);
}
}, },
filterItems(searchTerm) { filterItems(searchTerm) {
......
...@@ -62,17 +62,7 @@ export default { ...@@ -62,17 +62,7 @@ export default {
// Don't do anything if this happened on a no trigger element // Don't do anything if this happened on a no trigger element
if (e.target.classList.contains('js-no-trigger')) return; if (e.target.classList.contains('js-no-trigger')) return;
if (this.glFeatures.graphqlBoardLists || this.isSwimlanesOn) { this.setActiveId({ id: this.issue.id, sidebarType: ISSUABLE });
this.setActiveId({ id: this.issue.id, sidebarType: ISSUABLE });
return;
}
const isMultiSelect = e.ctrlKey || e.metaKey;
if (this.showDetail || isMultiSelect) {
this.showDetail = false;
this.$emit('show', { event: e, isMultiSelect });
}
}, },
}, },
}; };
......
...@@ -5,24 +5,20 @@ import Draggable from 'vuedraggable'; ...@@ -5,24 +5,20 @@ import Draggable from 'vuedraggable';
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column.vue'; import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column.vue';
import defaultSortableConfig from '~/sortable/sortable_config'; import defaultSortableConfig from '~/sortable/sortable_config';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { DraggableItemTypes } from '../constants'; import { DraggableItemTypes } from '../constants';
import BoardColumn from './board_column.vue'; import BoardColumn from './board_column.vue';
import BoardColumnDeprecated from './board_column_deprecated.vue';
export default { export default {
draggableItemTypes: DraggableItemTypes, draggableItemTypes: DraggableItemTypes,
components: { components: {
BoardAddNewColumn, BoardAddNewColumn,
BoardColumn, BoardColumn,
BoardColumnDeprecated,
BoardContentSidebar: () => import('~/boards/components/board_content_sidebar.vue'), BoardContentSidebar: () => import('~/boards/components/board_content_sidebar.vue'),
EpicBoardContentSidebar: () => EpicBoardContentSidebar: () =>
import('ee_component/boards/components/epic_board_content_sidebar.vue'), import('ee_component/boards/components/epic_board_content_sidebar.vue'),
EpicsSwimlanes: () => import('ee_component/boards/components/epics_swimlanes.vue'), EpicsSwimlanes: () => import('ee_component/boards/components/epics_swimlanes.vue'),
GlAlert, GlAlert,
}, },
mixins: [glFeatureFlagMixin()],
inject: ['canAdminList'], inject: ['canAdminList'],
props: { props: {
lists: { lists: {
...@@ -37,20 +33,15 @@ export default { ...@@ -37,20 +33,15 @@ export default {
}, },
computed: { computed: {
...mapState(['boardLists', 'error', 'addColumnForm']), ...mapState(['boardLists', 'error', 'addColumnForm']),
...mapGetters(['isSwimlanesOn', 'isEpicBoard']), ...mapGetters(['isSwimlanesOn', 'isEpicBoard', 'isIssueBoard']),
useNewBoardColumnComponent() {
return this.glFeatures.graphqlBoardLists || this.isSwimlanesOn || this.isEpicBoard;
},
addColumnFormVisible() { addColumnFormVisible() {
return this.addColumnForm?.visible; return this.addColumnForm?.visible;
}, },
boardListsToUse() { boardListsToUse() {
return this.useNewBoardColumnComponent return sortBy([...Object.values(this.boardLists)], 'position');
? sortBy([...Object.values(this.boardLists)], 'position')
: this.lists;
}, },
canDragColumns() { canDragColumns() {
return (this.isEpicBoard || this.glFeatures.graphqlBoardLists) && this.canAdminList; return this.canAdminList;
}, },
boardColumnWrapper() { boardColumnWrapper() {
return this.canDragColumns ? Draggable : 'div'; return this.canDragColumns ? Draggable : 'div';
...@@ -68,9 +59,6 @@ export default { ...@@ -68,9 +59,6 @@ export default {
return this.canDragColumns ? options : {}; return this.canDragColumns ? options : {};
}, },
boardColumnComponent() {
return this.useNewBoardColumnComponent ? BoardColumn : BoardColumnDeprecated;
},
}, },
methods: { methods: {
...mapActions(['moveList', 'unsetError']), ...mapActions(['moveList', 'unsetError']),
...@@ -95,8 +83,7 @@ export default { ...@@ -95,8 +83,7 @@ export default {
class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap" class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
@end="moveList" @end="moveList"
> >
<component <board-column
:is="boardColumnComponent"
v-for="(list, index) in boardListsToUse" v-for="(list, index) in boardListsToUse"
:key="index" :key="index"
ref="board" ref="board"
...@@ -118,10 +105,7 @@ export default { ...@@ -118,10 +105,7 @@ export default {
:disabled="disabled" :disabled="disabled"
/> />
<board-content-sidebar <board-content-sidebar v-if="isIssueBoard" data-testid="issue-boards-sidebar" />
v-if="isSwimlanesOn || glFeatures.graphqlBoardLists"
data-testid="issue-boards-sidebar"
/>
<epic-board-content-sidebar v-else-if="isEpicBoard" data-testid="epic-boards-sidebar" /> <epic-board-content-sidebar v-else-if="isEpicBoard" data-testid="epic-boards-sidebar" />
</div> </div>
......
...@@ -31,20 +31,13 @@ export default { ...@@ -31,20 +31,13 @@ export default {
}; };
}, },
computed: { computed: {
...mapGetters(['isSidebarOpen', 'shouldUseGraphQL', 'isEpicBoard']), ...mapGetters(['isSidebarOpen', 'isEpicBoard']),
...mapState(['activeId', 'sidebarType', 'boardLists']), ...mapState(['activeId', 'sidebarType', 'boardLists']),
isWipLimitsOn() { isWipLimitsOn() {
return this.glFeatures.wipLimits && !this.isEpicBoard; return this.glFeatures.wipLimits && !this.isEpicBoard;
}, },
activeList() { activeList() {
/* return this.boardLists[this.activeId];
Warning: Though a computed property it is not reactive because we are
referencing a List Model class. Reactivity only applies to plain JS objects
*/
if (this.shouldUseGraphQL || this.isEpicBoard) {
return this.boardLists[this.activeId];
}
return boardsStore.state.lists.find(({ id }) => id === this.activeId);
}, },
activeListLabel() { activeListLabel() {
return this.activeList.label; return this.activeList.label;
...@@ -73,12 +66,8 @@ export default { ...@@ -73,12 +66,8 @@ export default {
deleteBoard() { deleteBoard() {
// eslint-disable-next-line no-alert // eslint-disable-next-line no-alert
if (window.confirm(__('Are you sure you want to remove this list?'))) { if (window.confirm(__('Are you sure you want to remove this list?'))) {
if (this.shouldUseGraphQL || this.isEpicBoard) { this.track('click_button', { label: 'remove_list' });
this.track('click_button', { label: 'remove_list' }); this.removeList(this.activeId);
this.removeList(this.activeId);
} else {
this.activeList.destroy();
}
this.unsetActiveId(); this.unsetActiveId();
} }
}, },
......
...@@ -4,7 +4,6 @@ import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable ...@@ -4,7 +4,6 @@ import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable
import { updateHistory } from '~/lib/utils/url_utility'; import { updateHistory } from '~/lib/utils/url_utility';
import FilteredSearchContainer from '../filtered_search/container'; import FilteredSearchContainer from '../filtered_search/container';
import vuexstore from './stores'; import vuexstore from './stores';
import boardsStore from './stores/boards_store';
export default class FilteredSearchBoards extends FilteredSearchManager { export default class FilteredSearchBoards extends FilteredSearchManager {
constructor(store, updateUrl = false, cantEdit = []) { constructor(store, updateUrl = false, cantEdit = []) {
...@@ -26,7 +25,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager { ...@@ -26,7 +25,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
this.cantEdit = cantEdit.filter((i) => typeof i === 'string'); this.cantEdit = cantEdit.filter((i) => typeof i === 'string');
this.cantEditWithValue = cantEdit.filter((i) => typeof i === 'object'); this.cantEditWithValue = cantEdit.filter((i) => typeof i === 'object');
if (vuexstore.getters.shouldUseGraphQL && vuexstore.state.boardConfig) { if (vuexstore.state.boardConfig) {
const boardConfigPath = transformBoardConfig(vuexstore.state.boardConfig); const boardConfigPath = transformBoardConfig(vuexstore.state.boardConfig);
// TODO Refactor: https://gitlab.com/gitlab-org/gitlab/-/issues/329274 // TODO Refactor: https://gitlab.com/gitlab-org/gitlab/-/issues/329274
// here we are using "window.location.search" as a temporary store // here we are using "window.location.search" as a temporary store
...@@ -45,14 +44,10 @@ export default class FilteredSearchBoards extends FilteredSearchManager { ...@@ -45,14 +44,10 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
const groupByParam = new URLSearchParams(window.location.search).get('group_by'); const groupByParam = new URLSearchParams(window.location.search).get('group_by');
this.store.path = `${path.substr(1)}${groupByParam ? `&group_by=${groupByParam}` : ''}`; this.store.path = `${path.substr(1)}${groupByParam ? `&group_by=${groupByParam}` : ''}`;
if (vuexstore.getters.shouldUseGraphQL) { updateHistory({
updateHistory({ url: `?${path.substr(1)}${groupByParam ? `&group_by=${groupByParam}` : ''}`,
url: `?${path.substr(1)}${groupByParam ? `&group_by=${groupByParam}` : ''}`, });
}); vuexstore.dispatch('performSearch');
vuexstore.dispatch('performSearch');
} else if (this.updateUrl) {
boardsStore.updateFiltersUrl();
}
} }
removeTokens() { removeTokens() {
......
...@@ -2,7 +2,7 @@ import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; ...@@ -2,7 +2,7 @@ import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import PortalVue from 'portal-vue'; import PortalVue from 'portal-vue';
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { mapActions, mapGetters } from 'vuex'; import { mapActions } from 'vuex';
import 'ee_else_ce/boards/models/issue'; import 'ee_else_ce/boards/models/issue';
import 'ee_else_ce/boards/models/list'; import 'ee_else_ce/boards/models/list';
...@@ -78,10 +78,7 @@ export default () => { ...@@ -78,10 +78,7 @@ export default () => {
initBoardsFilteredSearch(apolloProvider); initBoardsFilteredSearch(apolloProvider);
} }
if (!gon?.features?.graphqlBoardLists) { boardsStore.create();
boardsStore.create();
boardsStore.setTimeTrackingLimitToHours($boardApp.dataset.timeTrackingLimitToHours);
}
// eslint-disable-next-line @gitlab/no-runtime-template-compiler // eslint-disable-next-line @gitlab/no-runtime-template-compiler
issueBoardsApp = new Vue({ issueBoardsApp = new Vue({
...@@ -133,7 +130,6 @@ export default () => { ...@@ -133,7 +130,6 @@ export default () => {
}; };
}, },
computed: { computed: {
...mapGetters(['shouldUseGraphQL']),
detailIssueVisible() { detailIssueVisible() {
return Object.keys(this.detailIssue.issue).length; return Object.keys(this.detailIssue.issue).length;
}, },
...@@ -174,14 +170,12 @@ export default () => { ...@@ -174,14 +170,12 @@ export default () => {
eventHub.$on('newDetailIssue', this.updateDetailIssue); eventHub.$on('newDetailIssue', this.updateDetailIssue);
eventHub.$on('clearDetailIssue', this.clearDetailIssue); eventHub.$on('clearDetailIssue', this.clearDetailIssue);
sidebarEventHub.$on('toggleSubscription', this.toggleSubscription); sidebarEventHub.$on('toggleSubscription', this.toggleSubscription);
eventHub.$on('initialBoardLoad', this.initialBoardLoad);
}, },
beforeDestroy() { beforeDestroy() {
eventHub.$off('updateTokens', this.updateTokens); eventHub.$off('updateTokens', this.updateTokens);
eventHub.$off('newDetailIssue', this.updateDetailIssue); eventHub.$off('newDetailIssue', this.updateDetailIssue);
eventHub.$off('clearDetailIssue', this.clearDetailIssue); eventHub.$off('clearDetailIssue', this.clearDetailIssue);
sidebarEventHub.$off('toggleSubscription', this.toggleSubscription); sidebarEventHub.$off('toggleSubscription', this.toggleSubscription);
eventHub.$off('initialBoardLoad', this.initialBoardLoad);
}, },
mounted() { mounted() {
if (!gon?.features?.issueBoardsFilteredSearch) { if (!gon?.features?.issueBoardsFilteredSearch) {
...@@ -196,28 +190,9 @@ export default () => { ...@@ -196,28 +190,9 @@ export default () => {
this.performSearch(); this.performSearch();
boardsStore.disabled = this.disabled; boardsStore.disabled = this.disabled;
if (!this.shouldUseGraphQL) {
this.initialBoardLoad();
}
}, },
methods: { methods: {
...mapActions(['setInitialBoardData', 'performSearch', 'setError']), ...mapActions(['setInitialBoardData', 'performSearch', 'setError']),
initialBoardLoad() {
boardsStore
.all()
.then((res) => res.data)
.then((lists) => {
lists.forEach((list) => boardsStore.addList(list));
this.loading = false;
})
.catch((error) => {
this.setError({
error,
message: __('An error occurred while fetching the board lists. Please try again.'),
});
});
},
updateTokens() { updateTokens() {
this.filterManager.updateTokens(); this.filterManager.updateTokens();
}, },
......
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { mapGetters } from 'vuex';
import BoardsSelector from 'ee_else_ce/boards/components/boards_selector.vue'; import BoardsSelector from 'ee_else_ce/boards/components/boards_selector.vue';
import BoardsSelectorDeprecated from '~/boards/components/boards_selector_deprecated.vue';
import store from '~/boards/stores'; import store from '~/boards/stores';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
Vue.use(VueApollo); Vue.use(VueApollo);
...@@ -25,9 +22,7 @@ export default (params = {}) => { ...@@ -25,9 +22,7 @@ export default (params = {}) => {
el: boardsSwitcherElement, el: boardsSwitcherElement,
components: { components: {
BoardsSelector, BoardsSelector,
BoardsSelectorDeprecated,
}, },
mixins: [glFeatureFlagMixin()],
apolloProvider, apolloProvider,
store, store,
provide: { provide: {
...@@ -52,16 +47,8 @@ export default (params = {}) => { ...@@ -52,16 +47,8 @@ export default (params = {}) => {
return { boardsSelectorProps }; return { boardsSelectorProps };
}, },
computed: {
...mapGetters(['shouldUseGraphQL', 'isEpicBoard']),
},
render(createElement) { render(createElement) {
if (this.shouldUseGraphQL || this.isEpicBoard) { return createElement(BoardsSelector, {
return createElement(BoardsSelector, {
props: this.boardsSelectorProps,
});
}
return createElement(BoardsSelectorDeprecated, {
props: this.boardsSelectorProps, props: this.boardsSelectorProps,
}); });
}, },
......
...@@ -82,11 +82,8 @@ export default { ...@@ -82,11 +82,8 @@ export default {
'setFilters', 'setFilters',
convertObjectPropsToCamelCase(queryToObject(window.location.search, { gatherArrays: true })), convertObjectPropsToCamelCase(queryToObject(window.location.search, { gatherArrays: true })),
); );
dispatch('fetchLists');
if (gon.features.graphqlBoardLists) { dispatch('resetIssues');
dispatch('fetchLists');
dispatch('resetIssues');
}
}, },
fetchLists: ({ commit, state, dispatch }) => { fetchLists: ({ commit, state, dispatch }) => {
...@@ -182,7 +179,7 @@ export default { ...@@ -182,7 +179,7 @@ export default {
}); });
}, },
fetchLabels: ({ state, commit, getters }, searchTerm) => { fetchLabels: ({ state, commit }, searchTerm) => {
const { fullPath, boardType } = state; const { fullPath, boardType } = state;
const variables = { const variables = {
...@@ -200,14 +197,7 @@ export default { ...@@ -200,14 +197,7 @@ export default {
variables, variables,
}) })
.then(({ data }) => { .then(({ data }) => {
let labels = data[boardType]?.labels.nodes; const labels = data[boardType]?.labels.nodes;
if (!getters.shouldUseGraphQL && !getters.isEpicBoard) {
labels = labels.map((label) => ({
...label,
id: getIdFromGraphQLId(label.id),
}));
}
commit(types.RECEIVE_LABELS_SUCCESS, labels); commit(types.RECEIVE_LABELS_SUCCESS, labels);
return labels; return labels;
......
...@@ -51,8 +51,4 @@ export default { ...@@ -51,8 +51,4 @@ export default {
isEpicBoard: () => { isEpicBoard: () => {
return false; return false;
}, },
shouldUseGraphQL: () => {
return gon?.features?.graphqlBoardLists;
},
}; };
...@@ -7,7 +7,6 @@ class Groups::BoardsController < Groups::ApplicationController ...@@ -7,7 +7,6 @@ class Groups::BoardsController < Groups::ApplicationController
before_action :assign_endpoint_vars before_action :assign_endpoint_vars
before_action do before_action do
push_frontend_feature_flag(:graphql_board_lists, group, default_enabled: :yaml)
push_frontend_feature_flag(:issue_boards_filtered_search, group, default_enabled: :yaml) push_frontend_feature_flag(:issue_boards_filtered_search, group, default_enabled: :yaml)
push_frontend_feature_flag(:board_multi_select, group, default_enabled: :yaml) push_frontend_feature_flag(:board_multi_select, group, default_enabled: :yaml)
push_frontend_feature_flag(:swimlanes_buffered_rendering, group, default_enabled: :yaml) push_frontend_feature_flag(:swimlanes_buffered_rendering, group, default_enabled: :yaml)
......
...@@ -8,7 +8,6 @@ class Projects::BoardsController < Projects::ApplicationController ...@@ -8,7 +8,6 @@ class Projects::BoardsController < Projects::ApplicationController
before_action :assign_endpoint_vars before_action :assign_endpoint_vars
before_action do before_action do
push_frontend_feature_flag(:swimlanes_buffered_rendering, project, default_enabled: :yaml) push_frontend_feature_flag(:swimlanes_buffered_rendering, project, default_enabled: :yaml)
push_frontend_feature_flag(:graphql_board_lists, project, default_enabled: :yaml)
push_frontend_feature_flag(:issue_boards_filtered_search, project, default_enabled: :yaml) push_frontend_feature_flag(:issue_boards_filtered_search, project, default_enabled: :yaml)
push_frontend_feature_flag(:board_multi_select, project, default_enabled: :yaml) push_frontend_feature_flag(:board_multi_select, project, default_enabled: :yaml)
push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml) push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml)
......
...@@ -48,10 +48,10 @@ module Types ...@@ -48,10 +48,10 @@ module Types
description: 'Size (in bytes) of the blob, or the blob target if stored externally.' description: 'Size (in bytes) of the blob, or the blob target if stored externally.'
field :raw_blob, GraphQL::Types::String, null: true, method: :data, field :raw_blob, GraphQL::Types::String, null: true, method: :data,
description: 'The raw content of the blob.' description: 'Raw content of the blob.'
field :raw_text_blob, GraphQL::Types::String, null: true, method: :text_only_data, field :raw_text_blob, GraphQL::Types::String, null: true, method: :text_only_data,
description: 'The raw content of the blob, if the blob is text data.' description: 'Raw content of the blob, if the blob is text data.'
field :stored_externally, GraphQL::Types::Boolean, null: true, method: :stored_externally?, field :stored_externally, GraphQL::Types::Boolean, null: true, method: :stored_externally?,
description: "Whether the blob's content is stored externally (for instance, in LFS)." description: "Whether the blob's content is stored externally (for instance, in LFS)."
...@@ -69,7 +69,7 @@ module Types ...@@ -69,7 +69,7 @@ module Types
description: 'Web path to replace the blob content.' description: 'Web path to replace the blob content.'
field :file_type, GraphQL::Types::String, null: true, field :file_type, GraphQL::Types::String, null: true,
description: 'The expected format of the blob based on the extension.' description: 'Expected format of the blob based on the extension.'
field :simple_viewer, type: Types::BlobViewerType, field :simple_viewer, type: Types::BlobViewerType,
description: 'Blob content simple viewer.', description: 'Blob content simple viewer.',
......
...@@ -6,14 +6,14 @@ module Types ...@@ -6,14 +6,14 @@ module Types
authorize :read_statistics authorize :read_statistics
field :storage_size, GraphQL::FLOAT_TYPE, null: false, description: 'The total storage in bytes.' field :storage_size, GraphQL::FLOAT_TYPE, null: false, description: 'Total storage in bytes.'
field :repository_size, GraphQL::FLOAT_TYPE, null: false, description: 'The Git repository size in bytes.' field :repository_size, GraphQL::FLOAT_TYPE, null: false, description: 'Git repository size in bytes.'
field :lfs_objects_size, GraphQL::FLOAT_TYPE, null: false, description: 'The LFS objects size in bytes.' field :lfs_objects_size, GraphQL::FLOAT_TYPE, null: false, description: 'LFS objects size in bytes.'
field :build_artifacts_size, GraphQL::FLOAT_TYPE, null: false, description: 'The CI artifacts size in bytes.' field :build_artifacts_size, GraphQL::FLOAT_TYPE, null: false, description: 'CI artifacts size in bytes.'
field :packages_size, GraphQL::FLOAT_TYPE, null: false, description: 'The packages size in bytes.' field :packages_size, GraphQL::FLOAT_TYPE, null: false, description: 'Packages size in bytes.'
field :wiki_size, GraphQL::FLOAT_TYPE, null: false, description: 'The wiki size in bytes.' field :wiki_size, GraphQL::FLOAT_TYPE, null: false, description: 'Wiki size in bytes.'
field :snippets_size, GraphQL::FLOAT_TYPE, null: false, description: 'The snippets size in bytes.' field :snippets_size, GraphQL::FLOAT_TYPE, null: false, description: 'Snippets size in bytes.'
field :pipeline_artifacts_size, GraphQL::FLOAT_TYPE, null: false, description: 'The CI pipeline artifacts size in bytes.' field :pipeline_artifacts_size, GraphQL::FLOAT_TYPE, null: false, description: 'CI pipeline artifacts size in bytes.'
field :uploads_size, GraphQL::FLOAT_TYPE, null: false, description: 'The uploads size in bytes.' field :uploads_size, GraphQL::FLOAT_TYPE, null: false, description: 'Uploads size in bytes.'
end end
end end
...@@ -22,7 +22,7 @@ module Types ...@@ -22,7 +22,7 @@ module Types
null: false null: false
field :project, Types::ProjectType, field :project, Types::ProjectType,
description: 'The project the snippet is associated with.', description: 'Project the snippet is associated with.',
null: true, null: true,
authorize: :read_project authorize: :read_project
...@@ -30,7 +30,7 @@ module Types ...@@ -30,7 +30,7 @@ module Types
# when the admin setting restricted visibility # when the admin setting restricted visibility
# level is set to public # level is set to public
field :author, Types::UserType, field :author, Types::UserType,
description: 'The owner of the snippet.', description: 'Owner of the snippet.',
null: true null: true
field :file_name, GraphQL::Types::String, field :file_name, GraphQL::Types::String,
......
...@@ -17,7 +17,7 @@ module Types ...@@ -17,7 +17,7 @@ module Types
null: true null: true
field :raw_plain_data, GraphQL::Types::String, field :raw_plain_data, GraphQL::Types::String,
description: 'The raw content of the blob, if the blob is text data.', description: 'Raw content of the blob, if the blob is text data.',
null: true null: true
field :raw_path, GraphQL::Types::String, field :raw_path, GraphQL::Types::String,
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
module Types module Types
module Snippets module Snippets
class VisibilityScopesEnum < BaseEnum class VisibilityScopesEnum < BaseEnum
value 'private', description: 'The snippet is visible only to the snippet creator.', value: 'are_private' value 'private', description: 'Snippet is visible only to the snippet creator.', value: 'are_private'
value 'internal', description: 'The snippet is visible for any logged in user except external users.', value: 'are_internal' value 'internal', description: 'Snippet is visible for any logged in user except external users.', value: 'are_internal'
value 'public', description: 'The snippet can be accessed without any authentication.', value: 'are_public' value 'public', description: 'Snippet can be accessed without any authentication.', value: 'are_public'
end end
end end
end end
...@@ -19,7 +19,7 @@ module Types ...@@ -19,7 +19,7 @@ module Types
field :locked_by_user, Types::UserType, field :locked_by_user, Types::UserType,
null: true, null: true,
description: 'The user currently holding a lock on the Terraform state.' description: 'User currently holding a lock on the Terraform state.'
field :locked_at, Types::TimeType, field :locked_at, Types::TimeType,
null: true, null: true,
...@@ -28,7 +28,7 @@ module Types ...@@ -28,7 +28,7 @@ module Types
field :latest_version, Types::Terraform::StateVersionType, field :latest_version, Types::Terraform::StateVersionType,
complexity: 3, complexity: 3,
null: true, null: true,
description: 'The latest version of the Terraform state.' description: 'Latest version of the Terraform state.'
field :created_at, Types::TimeType, field :created_at, Types::TimeType,
null: false, null: false,
......
...@@ -15,7 +15,7 @@ module Types ...@@ -15,7 +15,7 @@ module Types
field :created_by_user, Types::UserType, field :created_by_user, Types::UserType,
null: true, null: true,
description: 'The user that created this version.' description: 'User that created this version.'
field :download_path, GraphQL::Types::String, field :download_path, GraphQL::Types::String,
null: true, null: true,
...@@ -23,7 +23,7 @@ module Types ...@@ -23,7 +23,7 @@ module Types
field :job, Types::Ci::JobType, field :job, Types::Ci::JobType,
null: true, null: true,
description: 'The job that created this version.' description: 'Job that created this version.'
field :serial, GraphQL::Types::Int, field :serial, GraphQL::Types::Int,
null: true, null: true,
......
...@@ -14,31 +14,31 @@ module Types ...@@ -14,31 +14,31 @@ module Types
field :time_spent, field :time_spent,
GraphQL::Types::Int, GraphQL::Types::Int,
null: false, null: false,
description: 'The time spent displayed in seconds.' description: 'Time spent displayed in seconds.'
field :user, field :user,
Types::UserType, Types::UserType,
null: false, null: false,
description: 'The user that logged the time.' description: 'User that logged the time.'
field :issue, field :issue,
Types::IssueType, Types::IssueType,
null: true, null: true,
description: 'The issue that logged time was added to.' description: 'Issue that logged time was added to.'
field :merge_request, field :merge_request,
Types::MergeRequestType, Types::MergeRequestType,
null: true, null: true,
description: 'The merge request that logged time was added to.' description: 'Merge request that logged time was added to.'
field :note, field :note,
Types::Notes::NoteType, Types::Notes::NoteType,
null: true, null: true,
description: 'The note where the quick action to add the logged time was executed.' description: 'Note where the quick action was executed to add the logged time.'
field :summary, GraphQL::Types::String, field :summary, GraphQL::Types::String,
null: true, null: true,
description: 'The summary of how the time was spent.' description: 'Summary of how the time was spent.'
def user def user
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.user_id).find Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.user_id).find
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
module Types module Types
class TodoStateEnum < BaseEnum class TodoStateEnum < BaseEnum
value 'pending', description: "The state of the todo is pending." value 'pending', description: "State of the todo is pending."
value 'done', description: "The state of the todo is done." value 'done', description: "State of the todo is done."
end end
end end
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
module Types module Types
class TodoTargetEnum < BaseEnum class TodoTargetEnum < BaseEnum
value 'COMMIT', value: 'Commit', description: 'A Commit.' value 'COMMIT', value: 'Commit', description: 'Commit.'
value 'ISSUE', value: 'Issue', description: 'An Issue.' value 'ISSUE', value: 'Issue', description: 'Issue.'
value 'MERGEREQUEST', value: 'MergeRequest', description: 'A MergeRequest.' value 'MERGEREQUEST', value: 'MergeRequest', description: 'Merge request.'
value 'DESIGN', value: 'DesignManagement::Design', description: 'A Design.' value 'DESIGN', value: 'DesignManagement::Design', description: 'Design.'
value 'ALERT', value: 'AlertManagement::Alert', description: 'An Alert.' value 'ALERT', value: 'AlertManagement::Alert', description: 'Alert.'
end end
end end
......
...@@ -14,7 +14,7 @@ module Types ...@@ -14,7 +14,7 @@ module Types
null: false null: false
field :project, Types::ProjectType, field :project, Types::ProjectType,
description: 'The project this to-do item is associated with.', description: 'Project this to-do item is associated with.',
null: true, null: true,
authorize: :read_project authorize: :read_project
...@@ -24,7 +24,7 @@ module Types ...@@ -24,7 +24,7 @@ module Types
authorize: :read_group authorize: :read_group
field :author, Types::UserType, field :author, Types::UserType,
description: 'The author of this to-do item.', description: 'Author of this to-do item.',
null: false null: false
field :action, Types::TodoActionEnum, field :action, Types::TodoActionEnum,
......
...@@ -72,7 +72,7 @@ module Types ...@@ -72,7 +72,7 @@ module Types
field :location, field :location,
type: ::GraphQL::Types::String, type: ::GraphQL::Types::String,
null: true, null: true,
description: 'The location of the user.' description: 'Location of the user.'
field :project_memberships, field :project_memberships,
type: Types::ProjectMemberType.connection_type, type: Types::ProjectMemberType.connection_type,
null: true, null: true,
......
...@@ -28,7 +28,7 @@ module Types ...@@ -28,7 +28,7 @@ module Types
field :review_state, field :review_state,
::Types::MergeRequestReviewStateEnum, ::Types::MergeRequestReviewStateEnum,
null: true, null: true,
description: 'The state of the review by this user.' description: 'State of the review by this user.'
field :reviewed, field :reviewed,
type: ::GraphQL::Types::Boolean, type: ::GraphQL::Types::Boolean,
......
...@@ -5,8 +5,8 @@ module Types ...@@ -5,8 +5,8 @@ module Types
graphql_name 'UserState' graphql_name 'UserState'
description 'Possible states of a user' description 'Possible states of a user'
value 'active', 'The user is active and is able to use the system.', value: 'active' value 'active', 'User is active and is able to use the system.', value: 'active'
value 'blocked', 'The user has been blocked and is prevented from using the system.', value: 'blocked' value 'blocked', 'User has been blocked and is prevented from using the system.', value: 'blocked'
value 'deactivated', 'The user is no longer active and is unable to use the system.', value: 'deactivated' value 'deactivated', 'User is no longer active and is unable to use the system.', value: 'deactivated'
end end
end end
- board = local_assigns.fetch(:board, nil) - board = local_assigns.fetch(:board, nil)
- group = local_assigns.fetch(:group, false)
- @no_breadcrumb_container = true - @no_breadcrumb_container = true
- @no_container = true - @no_container = true
- @content_wrapper_class = "#{@content_wrapper_class} gl-relative" - @content_wrapper_class = "#{@content_wrapper_class} gl-relative"
...@@ -20,6 +19,4 @@ ...@@ -20,6 +19,4 @@
= render 'shared/issuable/search_bar', type: :boards, board: board = render 'shared/issuable/search_bar', type: :boards, board: board
#board-app.boards-app.position-relative{ "v-cloak" => "true", data: board_data, ":class" => "{ 'is-compact': detailIssueVisible }" } #board-app.boards-app.position-relative{ "v-cloak" => "true", data: board_data, ":class" => "{ 'is-compact': detailIssueVisible }" }
%board-content{ ":lists" => "state.lists", ":disabled" => "disabled" } %board-content{ ":lists" => "state.lists", ":disabled" => "disabled" }
- if !is_epic_board && !Feature.enabled?(:graphql_board_lists, default_enabled: :yaml)
= render "shared/boards/components/sidebar", group: group
%board-settings-sidebar %board-settings-sidebar
%board-sidebar{ "inline-template" => true, ":current-user" => (UserSerializer.new.represent(current_user) || {}).to_json }
%transition{ name: "boards-sidebar-slide" }
%aside.right-sidebar.right-sidebar-expanded.boards-sidebar{ "v-show" => "showSidebar", 'aria-label': s_('Boards|Board'), 'data-testid': 'issue-boards-sidebar' }
.issuable-sidebar
.block.issuable-sidebar-header.position-relative
%span.issuable-header-text.hide-collapsed.float-left
%strong.bold
{{ issue.title }}
%br/
%span
= render_if_exists "shared/boards/components/sidebar/issue_project_path"
= precede "#" do
{{ issue.iid }}
%a.gutter-toggle.position-absolute.position-top-0.position-right-0{ role: "button",
href: "#",
"@click.prevent" => "closeSidebar",
"aria-label" => "Toggle sidebar" }
= custom_icon("icon_close", size: 15)
.js-issuable-update
= render "shared/boards/components/sidebar/assignee"
= render_if_exists "shared/boards/components/sidebar/epic"
= render "shared/boards/components/sidebar/milestone"
= render "shared/boards/components/sidebar/time_tracker"
= render "shared/boards/components/sidebar/due_date"
= render "shared/boards/components/sidebar/labels"
= render_if_exists "shared/boards/components/sidebar/weight"
= render "shared/boards/components/sidebar/notifications"
---
name: graphql_board_lists
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37905
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/248908
milestone: '13.4'
type: development
group: group::project management
default_enabled: true
This diff is collapsed.
...@@ -229,8 +229,7 @@ and vice versa. ...@@ -229,8 +229,7 @@ and vice versa.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/285074) in GitLab 13.9. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/285074) in GitLab 13.9.
> - [Deployed behind a feature flag](../feature_flags.md), enabled by default. > - [Deployed behind a feature flag](../feature_flags.md), enabled by default.
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/248908) in GitLab 14.1 > - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/248908) in GitLab 14.1
> - Recommended for production use. > - [Feature flag `graphql_board_lists`](https://gitlab.com/gitlab-org/gitlab/-/issues/248908) removed in GitLab 14.3
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-graphql-based-issue-boards). **(FREE SELF)**
There can be There can be
[risks when disabling released features](../../administration/feature_flags.md#risks-when-disabling-released-features). [risks when disabling released features](../../administration/feature_flags.md#risks-when-disabling-released-features).
...@@ -673,24 +672,6 @@ A few things to remember: ...@@ -673,24 +672,6 @@ A few things to remember:
by default. If you have more than 20 issues, start scrolling down and the next by default. If you have more than 20 issues, start scrolling down and the next
20 appear. 20 appear.
### Enable or disable GraphQL-based issue boards **(FREE SELF)**
It is deployed behind a feature flag that is **enabled by default** as of GitLab 14.1.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can disable it.
To enable it:
```ruby
Feature.enable(:graphql_board_lists)
```
To disable it:
```ruby
Feature.disable(:graphql_board_lists)
```
### Enable or disable iteration lists in boards **(PREMIUM SELF)** ### Enable or disable iteration lists in boards **(PREMIUM SELF)**
The iteration list is under development but ready for production use. It is The iteration list is under development but ready for production use. It is
......
...@@ -11,8 +11,6 @@ import { ...@@ -11,8 +11,6 @@ import {
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue'; import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue';
import { ListType } from '~/boards/constants'; import { ListType } from '~/boards/constants';
import boardsStore from '~/boards/stores/boards_store';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { isScopedLabel } from '~/lib/utils/common_utils'; import { isScopedLabel } from '~/lib/utils/common_utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
...@@ -87,7 +85,7 @@ export default { ...@@ -87,7 +85,7 @@ export default {
'assignees', 'assignees',
'assigneesLoading', 'assigneesLoading',
]), ]),
...mapGetters(['getListByTypeId', 'shouldUseGraphQL', 'isEpicBoard']), ...mapGetters(['getListByTypeId', 'isEpicBoard']),
info() { info() {
return listTypeInfo[this.columnType] || {}; return listTypeInfo[this.columnType] || {};
...@@ -132,16 +130,10 @@ export default { ...@@ -132,16 +130,10 @@ export default {
return false; return false;
} }
if (this.shouldUseGraphQL || this.isEpicBoard) { const key = `${this.columnType}Id`;
const key = `${this.columnType}Id`; return this.getListByTypeId({
return this.getListByTypeId({ [key]: this.selectedId,
[key]: this.selectedId, });
});
}
return boardsStore.state.lists.find(
(list) => list[this.columnType]?.id === getIdFromGraphQLId(this.selectedId),
);
}, },
loading() { loading() {
...@@ -187,17 +179,6 @@ export default { ...@@ -187,17 +179,6 @@ export default {
'fetchIterations', 'fetchIterations',
'fetchMilestones', 'fetchMilestones',
]), ]),
highlight(listId) {
if (this.shouldUseGraphQL || this.isEpicBoard) {
this.highlightList(listId);
} else {
const list = boardsStore.state.lists.find(({ id }) => id === listId);
list.highlighted = true;
setTimeout(() => {
list.highlighted = false;
}, 2000);
}
},
addList() { addList() {
if (!this.selectedItem) { if (!this.selectedItem) {
return; return;
...@@ -207,45 +188,12 @@ export default { ...@@ -207,45 +188,12 @@ export default {
if (this.columnForSelected) { if (this.columnForSelected) {
const listId = this.columnForSelected.id; const listId = this.columnForSelected.id;
this.highlight(listId); this.highlightList(listId);
return; return;
} }
if (this.shouldUseGraphQL || this.isEpicBoard) { // eslint-disable-next-line @gitlab/require-i18n-strings
// eslint-disable-next-line @gitlab/require-i18n-strings this.createList({ [`${this.columnType}Id`]: this.selectedId });
this.createList({ [`${this.columnType}Id`]: this.selectedId });
} else {
const { length } = boardsStore.state.lists;
const position = this.hideClosed ? length - 1 : length - 2;
const listObj = {
// eslint-disable-next-line @gitlab/require-i18n-strings
[`${this.columnType}Id`]: getIdFromGraphQLId(this.selectedId),
title: this.selectedItem.title,
position,
list_type: this.columnType,
};
if (this.labelTypeSelected) {
listObj.label = this.selectedItem;
} else if (this.milestoneTypeSelected) {
listObj.milestone = {
...this.selectedItem,
id: getIdFromGraphQLId(this.selectedItem.id),
};
} else if (this.iterationTypeSelected) {
listObj.iteration = {
...this.selectedItem,
id: getIdFromGraphQLId(this.selectedItem.id),
};
} else if (this.assigneeTypeSelected) {
listObj.assignee = {
...this.selectedItem,
id: getIdFromGraphQLId(this.selectedItem.id),
};
}
boardsStore.new(listObj);
}
}, },
filterItems(searchTerm) { filterItems(searchTerm) {
......
<script> <script>
import { GlButton, GlFormInput } from '@gitlab/ui'; import { GlButton, GlFormInput } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapState } from 'vuex';
import boardsStoreEE from 'ee/boards/stores/boards_store_ee';
import { inactiveId } from '~/boards/constants';
import { __, n__ } from '~/locale'; import { __, n__ } from '~/locale';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
...@@ -36,7 +34,6 @@ export default { ...@@ -36,7 +34,6 @@ export default {
}, },
computed: { computed: {
...mapState(['activeId']), ...mapState(['activeId']),
...mapGetters(['shouldUseGraphQL']),
wipLimitTypeText() { wipLimitTypeText() {
return n__('%d issue', '%d issues', this.maxIssueCount); return n__('%d issue', '%d issues', this.maxIssueCount);
}, },
...@@ -76,11 +73,6 @@ export default { ...@@ -76,11 +73,6 @@ export default {
const id = this.activeId; const id = this.activeId;
this.updateListWipLimit({ maxIssueCount: wipLimit, listId: id }) this.updateListWipLimit({ maxIssueCount: wipLimit, listId: id })
.then(() => {
if (!this.shouldUseGraphQL) {
boardsStoreEE.setMaxIssueCountOnList(id, wipLimit);
}
})
.catch(() => { .catch(() => {
this.unsetActiveId(); this.unsetActiveId();
this.setError({ this.setError({
...@@ -96,11 +88,6 @@ export default { ...@@ -96,11 +88,6 @@ export default {
}, },
clearWipLimit() { clearWipLimit() {
this.updateListWipLimit({ maxIssueCount: 0, listId: this.activeId }) this.updateListWipLimit({ maxIssueCount: 0, listId: this.activeId })
.then(() => {
if (!this.shouldUseGraphQL) {
boardsStoreEE.setMaxIssueCountOnList(this.activeId, inactiveId);
}
})
.catch(() => { .catch(() => {
this.unsetActiveId(); this.unsetActiveId();
this.setError({ this.setError({
......
...@@ -65,19 +65,12 @@ export default Vue.extend({ ...@@ -65,19 +65,12 @@ export default Vue.extend({
return list; return list;
}, },
handleItemClick(item) { handleItemClick(item) {
if ( if (!this.vuexStore.getters.getListByTitle(item.title)) {
this.vuexStore.getters.shouldUseGraphQL &&
!this.vuexStore.getters.getListByTitle(item.title)
) {
if (this.listType === 'milestones') { if (this.listType === 'milestones') {
this.vuexStore.dispatch('createList', { milestoneId: fullMilestoneId(item.id) }); this.vuexStore.dispatch('createList', { milestoneId: fullMilestoneId(item.id) });
} else if (this.listType === 'assignees') { } else if (this.listType === 'assignees') {
this.vuexStore.dispatch('createList', { assigneeId: fullUserId(item.id) }); this.vuexStore.dispatch('createList', { assigneeId: fullUserId(item.id) });
} }
} else if (!this.store.findList('title', item.title)) {
const list = this.prepareListObject(item);
this.store.new(list);
} }
}, },
}, },
......
...@@ -6,15 +6,12 @@ import { ...@@ -6,15 +6,12 @@ import {
filterVariables, filterVariables,
} from '~/boards/boards_util'; } from '~/boards/boards_util';
import { BoardType } from '~/boards/constants'; import { BoardType } from '~/boards/constants';
import eventHub from '~/boards/eventhub';
import groupBoardMembersQuery from '~/boards/graphql/group_board_members.query.graphql'; import groupBoardMembersQuery from '~/boards/graphql/group_board_members.query.graphql';
import listsIssuesQuery from '~/boards/graphql/lists_issues.query.graphql'; import listsIssuesQuery from '~/boards/graphql/lists_issues.query.graphql';
import projectBoardMembersQuery from '~/boards/graphql/project_board_members.query.graphql'; import projectBoardMembersQuery from '~/boards/graphql/project_board_members.query.graphql';
import actionsCE, { gqlClient } from '~/boards/stores/actions'; import actionsCE, { gqlClient } from '~/boards/stores/actions';
import boardsStore from '~/boards/stores/boards_store';
import * as typesCE from '~/boards/stores/mutation_types'; import * as typesCE from '~/boards/stores/mutation_types';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import axios from '~/lib/utils/axios_utils';
import { historyPushState, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { historyPushState, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { mergeUrlParams, removeParams, queryToObject } from '~/lib/utils/url_utility'; import { mergeUrlParams, removeParams, queryToObject } from '~/lib/utils/url_utility';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
...@@ -39,7 +36,6 @@ import projectBoardIterationsQuery from '../graphql/project_board_iterations.que ...@@ -39,7 +36,6 @@ import projectBoardIterationsQuery from '../graphql/project_board_iterations.que
import updateBoardEpicUserPreferencesMutation from '../graphql/update_board_epic_user_preferences.mutation.graphql'; import updateBoardEpicUserPreferencesMutation from '../graphql/update_board_epic_user_preferences.mutation.graphql';
import updateEpicLabelsMutation from '../graphql/update_epic_labels.mutation.graphql'; import updateEpicLabelsMutation from '../graphql/update_epic_labels.mutation.graphql';
import boardsStoreEE from './boards_store_ee';
import * as types from './mutation_types'; import * as types from './mutation_types';
const fetchAndFormatListIssues = (state, extraVariables) => { const fetchAndFormatListIssues = (state, extraVariables) => {
...@@ -121,13 +117,11 @@ export default { ...@@ -121,13 +117,11 @@ export default {
if (getters.isSwimlanesOn) { if (getters.isSwimlanesOn) {
dispatch('resetEpics'); dispatch('resetEpics');
dispatch('resetIssues');
dispatch('fetchEpicsSwimlanes'); dispatch('fetchEpicsSwimlanes');
dispatch('fetchLists');
} else if (gon.features.graphqlBoardLists || getters.isEpicBoard) {
dispatch('fetchLists');
dispatch('resetIssues');
} }
dispatch('fetchLists');
dispatch('resetIssues');
}, },
fetchEpicsSwimlanes({ state, commit }, { fetchNext = false } = {}) { fetchEpicsSwimlanes({ state, commit }, { fetchNext = false } = {}) {
...@@ -221,38 +215,30 @@ export default { ...@@ -221,38 +215,30 @@ export default {
commit(types.SET_SHOW_LABELS, val); commit(types.SET_SHOW_LABELS, val);
}, },
updateListWipLimit({ commit, getters, dispatch }, { maxIssueCount, listId }) { updateListWipLimit({ commit, dispatch }, { maxIssueCount, listId }) {
if (getters.shouldUseGraphQL) { return gqlClient
return gqlClient .mutate({
.mutate({ mutation: listUpdateLimitMetricsMutation,
mutation: listUpdateLimitMetricsMutation, variables: {
variables: { input: {
input: { listId,
listId, maxIssueCount,
maxIssueCount,
},
}, },
}) },
.then(({ data }) => { })
if (data?.boardListUpdateLimitMetrics?.errors.length) { .then(({ data }) => {
throw new Error(); if (data?.boardListUpdateLimitMetrics?.errors.length) {
} throw new Error();
}
commit(types.UPDATE_LIST_SUCCESS, { commit(types.UPDATE_LIST_SUCCESS, {
listId, listId,
list: data.boardListUpdateLimitMetrics?.list, list: data.boardListUpdateLimitMetrics?.list,
});
})
.catch(() => {
dispatch('handleUpdateListFailure');
}); });
} })
.catch(() => {
return axios.put(`${boardsStoreEE.store.state.endpoints.listsEndpoint}/${listId}`, { dispatch('handleUpdateListFailure');
list: { });
max_issue_count: maxIssueCount,
},
});
}, },
fetchItemsForList: ( fetchItemsForList: (
...@@ -316,10 +302,6 @@ export default { ...@@ -316,10 +302,6 @@ export default {
); );
dispatch('fetchEpicsSwimlanes'); dispatch('fetchEpicsSwimlanes');
dispatch('fetchLists'); dispatch('fetchLists');
} else if (!gon.features.graphqlBoardLists) {
historyPushState(removeParams(['group_by']), window.location.href, true);
boardsStore.create();
eventHub.$emit('initialBoardLoad');
} else { } else {
historyPushState(removeParams(['group_by']), window.location.href, true); historyPushState(removeParams(['group_by']), window.location.href, true);
} }
......
...@@ -57,9 +57,6 @@ class BoardsStoreEE { ...@@ -57,9 +57,6 @@ class BoardsStoreEE {
this.store.scopedLabels = { this.store.scopedLabels = {
enabled: parseBoolean(scopedLabels), enabled: parseBoolean(scopedLabels),
}; };
if (!gon.features.graphqlBoardLists) {
this.initBoardFilters();
}
} }
}; };
......
...@@ -54,8 +54,4 @@ export default { ...@@ -54,8 +54,4 @@ export default {
isEpicBoard: (state) => { isEpicBoard: (state) => {
return state.issuableType === issuableTypes.epic; return state.issuableType === issuableTypes.epic;
}, },
shouldUseGraphQL: (state) => {
return state.isShowingEpicsSwimlanes || gon?.features?.graphqlBoardLists;
},
}; };
...@@ -10,11 +10,11 @@ module EE ...@@ -10,11 +10,11 @@ module EE
prepended do prepended do
argument :payload_example, ::Types::JsonStringType, argument :payload_example, ::Types::JsonStringType,
required: false, required: false,
description: 'The example of an alert payload.' description: 'Example of an alert payload.'
argument :payload_attribute_mappings, [::Types::AlertManagement::PayloadAlertFieldInputType], argument :payload_attribute_mappings, [::Types::AlertManagement::PayloadAlertFieldInputType],
required: false, required: false,
description: 'The custom mapping of GitLab alert attributes to fields from the payload_example.' description: 'Custom mapping of GitLab alert attributes to fields from the payload example.'
end end
end end
end end
......
...@@ -10,11 +10,11 @@ module EE ...@@ -10,11 +10,11 @@ module EE
prepended do prepended do
argument :payload_example, ::Types::JsonStringType, argument :payload_example, ::Types::JsonStringType,
required: false, required: false,
description: 'The example of an alert payload.' description: 'Example of an alert payload.'
argument :payload_attribute_mappings, [::Types::AlertManagement::PayloadAlertFieldInputType], argument :payload_attribute_mappings, [::Types::AlertManagement::PayloadAlertFieldInputType],
required: false, required: false,
description: 'The custom mapping of GitLab alert attributes to fields from the payload_example.' description: 'Custom mapping of GitLab alert attributes to fields from the payload example.'
end end
end end
end end
......
...@@ -12,7 +12,7 @@ module EE ...@@ -12,7 +12,7 @@ module EE
argument :epic_id, ::Types::GlobalIDType[::Epic], argument :epic_id, ::Types::GlobalIDType[::Epic],
required: false, required: false,
description: 'The ID of an epic to associate the issue with.' description: 'ID of an epic to associate the issue with.'
end end
override :resolve override :resolve
......
...@@ -12,7 +12,7 @@ module EE ...@@ -12,7 +12,7 @@ module EE
argument :epic_id, ::Types::GlobalIDType[::Epic], argument :epic_id, ::Types::GlobalIDType[::Epic],
required: false, required: false,
loads: ::Types::EpicType, loads: ::Types::EpicType,
description: 'The ID of the parent epic. NULL when removing the association.' description: 'ID of the parent epic. NULL when removing the association.'
end end
def resolve(**args) def resolve(**args)
......
...@@ -9,7 +9,7 @@ module EE ...@@ -9,7 +9,7 @@ module EE
prepended do prepended do
field :payload_example, ::Types::JsonStringType, field :payload_example, ::Types::JsonStringType,
null: true, null: true,
description: 'The example of an alert payload.' description: 'Example of an alert payload.'
field :payload_attribute_mappings, [::Types::AlertManagement::PayloadAlertMappingFieldType], field :payload_attribute_mappings, [::Types::AlertManagement::PayloadAlertMappingFieldType],
null: true, null: true,
......
...@@ -17,7 +17,7 @@ module EE ...@@ -17,7 +17,7 @@ module EE
field :assignee, ::Types::UserType, null: true, field :assignee, ::Types::UserType, null: true,
description: 'Assignee in the list.' description: 'Assignee in the list.'
field :limit_metric, ::EE::Types::ListLimitMetricEnum, null: true, field :limit_metric, ::EE::Types::ListLimitMetricEnum, null: true,
description: 'The current limit metric for the list.' description: 'Current limit metric for the list.'
field :total_weight, GraphQL::Types::Int, null: true, field :total_weight, GraphQL::Types::Int, null: true,
description: 'Total weight of all issues in the list.' description: 'Total weight of all issues in the list.'
......
...@@ -7,7 +7,7 @@ module EE ...@@ -7,7 +7,7 @@ module EE
prepended do prepended do
field :assignee, type: ::Types::UserType, null: true, field :assignee, type: ::Types::UserType, null: true,
description: 'The board assignee.' description: 'Board assignee.'
field :epics, ::Types::Boards::BoardEpicType.connection_type, null: true, field :epics, ::Types::Boards::BoardEpicType.connection_type, null: true,
description: 'Epics associated with board issues.', description: 'Epics associated with board issues.',
...@@ -18,10 +18,10 @@ module EE ...@@ -18,10 +18,10 @@ module EE
description: 'Labels of the board.' description: 'Labels of the board.'
field :milestone, type: ::Types::MilestoneType, null: true, field :milestone, type: ::Types::MilestoneType, null: true,
description: 'The board milestone.' description: 'Board milestone.'
field :iteration, type: ::Types::IterationType, null: true, field :iteration, type: ::Types::IterationType, null: true,
description: 'The board iteration.' description: 'Board iteration.'
field :weight, type: GraphQL::Types::Int, null: true, field :weight, type: GraphQL::Types::Int, null: true,
description: 'Weight of the board.' description: 'Weight of the board.'
......
...@@ -84,13 +84,13 @@ module EE ...@@ -84,13 +84,13 @@ module EE
field :billable_members_count, ::GraphQL::Types::Int, field :billable_members_count, ::GraphQL::Types::Int,
null: true, null: true,
description: 'The number of billable users in the group.' description: 'Number of billable users in the group.'
field :dora, field :dora,
::Types::DoraType, ::Types::DoraType,
null: true, null: true,
method: :itself, method: :itself,
description: "The group's DORA metrics." description: "Group's DORA metrics."
end end
end end
end end
......
...@@ -87,7 +87,7 @@ module EE ...@@ -87,7 +87,7 @@ module EE
field :dast_scanner_profiles, field :dast_scanner_profiles,
::Types::DastScannerProfileType.connection_type, ::Types::DastScannerProfileType.connection_type,
null: true, null: true,
description: 'The DAST scanner profiles associated with the project.' description: 'DAST scanner profiles associated with the project.'
field :dast_site_validations, field :dast_site_validations,
::Types::DastSiteValidationType.connection_type, ::Types::DastSiteValidationType.connection_type,
...@@ -165,7 +165,7 @@ module EE ...@@ -165,7 +165,7 @@ module EE
field :push_rules, field :push_rules,
::Types::PushRulesType, ::Types::PushRulesType,
null: true, null: true,
description: "The project's push rules settings.", description: "Project's push rules settings.",
method: :push_rule method: :push_rule
field :path_locks, field :path_locks,
...@@ -192,7 +192,7 @@ module EE ...@@ -192,7 +192,7 @@ module EE
::Types::DoraType, ::Types::DoraType,
null: true, null: true,
method: :itself, method: :itself,
description: "The project's DORA metrics." description: "Project's DORA metrics."
end end
def api_fuzzing_ci_configuration def api_fuzzing_ci_configuration
......
...@@ -26,7 +26,7 @@ module EE ...@@ -26,7 +26,7 @@ module EE
description: "Find a vulnerability." do description: "Find a vulnerability." do
argument :id, ::Types::GlobalIDType[::Vulnerability], argument :id, ::Types::GlobalIDType[::Vulnerability],
required: true, required: true,
description: 'The Global ID of the Vulnerability.' description: 'Global ID of the Vulnerability.'
end end
field :vulnerabilities_count_by_day, field :vulnerabilities_count_by_day,
...@@ -64,7 +64,7 @@ module EE ...@@ -64,7 +64,7 @@ module EE
field :ci_minutes_usage, ::Types::Ci::Minutes::NamespaceMonthlyUsageType.connection_type, field :ci_minutes_usage, ::Types::Ci::Minutes::NamespaceMonthlyUsageType.connection_type,
null: true, null: true,
description: 'The monthly CI minutes usage data for the current user.' description: 'Monthly CI minutes usage data for the current user.'
end end
def vulnerability(id:) def vulnerability(id:)
......
...@@ -61,13 +61,4 @@ RSpec.describe 'Multiple Issue Boards', :js do ...@@ -61,13 +61,4 @@ RSpec.describe 'Multiple Issue Boards', :js do
it_behaves_like 'multiple issue boards' it_behaves_like 'multiple issue boards'
end end
context 'when graphql_board_lists FF disabled' do
before do
stub_feature_flags(graphql_board_lists: false)
stub_licensed_features(multiple_group_issue_boards: true)
end
it_behaves_like 'multiple issue boards'
end
end end
# frozen_string_literal: true
# To be removed as :graphql_board_lists gets removed
# https://gitlab.com/gitlab-org/gitlab/-/issues/248908
require 'spec_helper'
RSpec.describe 'label issues', :js do
include BoardHelpers
let(:user) { create(:user) }
let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, namespace: group) }
let(:board) { create(:board, group: group) }
let!(:development) { create(:label, project: project, name: 'Development') }
let!(:issue) { create(:labeled_issue, project: project, labels: [development]) }
let!(:list) { create(:list, board: board, label: development, position: 0) }
before do
stub_licensed_features(multiple_group_issue_boards: true)
# stubbing until sidebar work is done: https://gitlab.com/gitlab-org/gitlab/-/issues/230711
stub_feature_flags(graphql_board_lists: false)
group.add_maintainer(user)
sign_in(user)
visit group_boards_path(group)
wait_for_requests
end
it 'adds a new group label from sidebar' do
card = find('.board:nth-child(2)').first('.board-card')
click_card(card)
page.within '.right-sidebar .labels' do
click_link 'Edit'
click_link 'Create group label'
fill_in 'new_label_name', with: 'test label'
first('.suggest-colors-dropdown a').click
# We need to hover before clicking to trigger
# dropdown repositioning so that the click isn't flaky
create_button = find_button('Create')
create_button.hover
create_button.click
end
page.within '.labels' do
expect(page).to have_link 'test label'
end
end
end
This diff is collapsed.
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'User adds milestone lists', :js do RSpec.describe 'User adds milestone lists', :js do
using RSpec::Parameterized::TableSyntax
let_it_be(:group) { create(:group, :nested) } let_it_be(:group) { create(:group, :nested) }
let_it_be(:project) { create(:project, :public, namespace: group) } let_it_be(:project) { create(:project, :public, namespace: group) }
let_it_be(:group_board) { create(:board, group: group) } let_it_be(:group_board) { create(:board, group: group) }
...@@ -25,11 +23,8 @@ RSpec.describe 'User adds milestone lists', :js do ...@@ -25,11 +23,8 @@ RSpec.describe 'User adds milestone lists', :js do
group.add_owner(user) group.add_owner(user)
end end
where(:board_type, :graphql_board_lists_enabled) do where(:board_type) do
:project | true [[:project], [:group]]
:project | false
:group | true
:group | false
end end
with_them do with_them do
...@@ -43,10 +38,6 @@ RSpec.describe 'User adds milestone lists', :js do ...@@ -43,10 +38,6 @@ RSpec.describe 'User adds milestone lists', :js do
set_cookie('sidebar_collapsed', 'true') set_cookie('sidebar_collapsed', 'true')
stub_feature_flags(
graphql_board_lists: graphql_board_lists_enabled
)
if board_type == :project if board_type == :project
visit project_board_path(project, project_board) visit project_board_path(project, project_board)
elsif board_type == :group elsif board_type == :group
......
...@@ -145,14 +145,6 @@ RSpec.describe 'Filter issues by iteration', :js do ...@@ -145,14 +145,6 @@ RSpec.describe 'Filter issues by iteration', :js do
let(:issue_title_selector) { '.board-card .board-card-title' } let(:issue_title_selector) { '.board-card .board-card-title' }
it_behaves_like 'filters by iteration' it_behaves_like 'filters by iteration'
context 'when graphql_board_lists is disabled' do
before do
stub_feature_flags(graphql_board_lists: false)
end
it_behaves_like 'filters by iteration'
end
end end
context 'group board' do context 'group board' do
......
...@@ -15,7 +15,6 @@ Vue.use(Vuex); ...@@ -15,7 +15,6 @@ Vue.use(Vuex);
describe('BoardAddNewColumn', () => { describe('BoardAddNewColumn', () => {
let wrapper; let wrapper;
let shouldUseGraphQL;
const selectItem = (id) => { const selectItem = (id) => {
wrapper.findByTestId('selectItem').vm.$emit('change', id); wrapper.findByTestId('selectItem').vm.$emit('change', id);
...@@ -59,7 +58,6 @@ describe('BoardAddNewColumn', () => { ...@@ -59,7 +58,6 @@ describe('BoardAddNewColumn', () => {
...actions, ...actions,
}, },
getters: { getters: {
shouldUseGraphQL: () => shouldUseGraphQL,
getListByTypeId: () => getListByTypeId, getListByTypeId: () => getListByTypeId,
isEpicBoard: () => false, isEpicBoard: () => false,
}, },
...@@ -103,10 +101,6 @@ describe('BoardAddNewColumn', () => { ...@@ -103,10 +101,6 @@ describe('BoardAddNewColumn', () => {
radio.vm.$emit('change', type); radio.vm.$emit('change', type);
}; };
beforeEach(() => {
shouldUseGraphQL = true;
});
it('clicking cancel hides the form', () => { it('clicking cancel hides the form', () => {
const setAddColumnFormVisibility = jest.fn(); const setAddColumnFormVisibility = jest.fn();
mountComponent({ mountComponent({
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import EpicBoardContentSidebar from 'ee/boards/components/epic_board_content_sidebar.vue';
import BoardContent from '~/boards/components/board_content.vue'; import BoardContent from '~/boards/components/board_content.vue';
import BoardContentSidebar from '~/boards/components/board_content_sidebar.vue'; import BoardContentSidebar from '~/boards/components/board_content_sidebar.vue';
import { createStore } from '~/boards/stores'; import { createStore } from '~/boards/stores';
...@@ -35,20 +36,22 @@ describe('ee/BoardContent', () => { ...@@ -35,20 +36,22 @@ describe('ee/BoardContent', () => {
}); });
describe.each` describe.each`
licenseEnabled | state | result state | resultIssue | resultEpic
${true} | ${{ isShowingEpicsSwimlanes: true }} | ${true} ${{ isShowingEpicsSwimlanes: true, issuableType: 'issue' }} | ${true} | ${false}
${true} | ${{ isShowingEpicsSwimlanes: false }} | ${false} ${{ isShowingEpicsSwimlanes: false, issuableType: 'issue' }} | ${true} | ${false}
${false} | ${{ isShowingEpicsSwimlanes: true }} | ${false} ${{ isShowingEpicsSwimlanes: false, issuableType: 'epic' }} | ${false} | ${true}
${false} | ${{ isShowingEpicsSwimlanes: false }} | ${false} `('with state=$state', ({ state, resultIssue, resultEpic }) => {
`('with licenseEnabled=$licenseEnabled and state=$state', ({ licenseEnabled, state, result }) => {
beforeEach(() => { beforeEach(() => {
gon.licensed_features.swimlanes = licenseEnabled;
Object.assign(store.state, state); Object.assign(store.state, state);
createComponent(); createComponent();
}); });
it(`renders BoardContentSidebar = ${result}`, () => { it(`renders BoardContentSidebar = ${resultIssue}`, () => {
expect(wrapper.find(BoardContentSidebar).exists()).toBe(result); expect(wrapper.find(BoardContentSidebar).exists()).toBe(resultIssue);
});
it(`renders EpicBoardContentSidebar = ${resultEpic}`, () => {
expect(wrapper.find(EpicBoardContentSidebar).exists()).toBe(resultEpic);
}); });
}); });
}); });
...@@ -11,11 +11,6 @@ import axios from '~/lib/utils/axios_utils'; ...@@ -11,11 +11,6 @@ import axios from '~/lib/utils/axios_utils';
jest.mock('~/flash'); jest.mock('~/flash');
describe('BoardListSelector', () => { describe('BoardListSelector', () => {
global.gon.features = {
...(global.gon.features || {}),
graphqlBoardLists: false,
};
const dummyEndpoint = `${TEST_HOST}/users.json`; const dummyEndpoint = `${TEST_HOST}/users.json`;
const createComponent = () => const createComponent = () =>
...@@ -93,19 +88,7 @@ describe('BoardListSelector', () => { ...@@ -93,19 +88,7 @@ describe('BoardListSelector', () => {
}); });
describe('handleItemClick', () => { describe('handleItemClick', () => {
it('graphqlBoardLists FF off - creates new list in a store instance', () => { it('creates new list in a store instance', () => {
jest.spyOn(vm.store, 'new').mockReturnValue({});
const assignee = mockAssigneesList[0];
expect(vm.store.findList('title', assignee.name)).not.toBeDefined();
vm.handleItemClick(assignee);
expect(vm.store.new).toHaveBeenCalledWith(expect.any(Object));
});
it('graphqlBoardLists FF on - creates new list in a store instance', () => {
global.gon.features.graphqlBoardLists = true;
jest.spyOn(vm.vuexStore, 'dispatch').mockReturnValue({}); jest.spyOn(vm.vuexStore, 'dispatch').mockReturnValue({});
const assignee = mockAssigneesList[0]; const assignee = mockAssigneesList[0];
......
import '~/boards/models/list'; import { shallowMount } from '@vue/test-utils';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import Vue from 'vue';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import Vuex from 'vuex'; import Vuex from 'vuex';
import BoardSettingsListTypes from 'ee_component/boards/components/board_settings_list_types.vue'; import BoardSettingsListTypes from 'ee_component/boards/components/board_settings_list_types.vue';
import BoardSettingsWipLimit from 'ee_component/boards/components/board_settings_wip_limit.vue'; import BoardSettingsWipLimit from 'ee_component/boards/components/board_settings_wip_limit.vue';
import { mockLabelList, mockMilestoneList } from 'jest/boards/mock_data';
import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue'; import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue';
import { LIST } from '~/boards/constants'; import { LIST } from '~/boards/constants';
import boardsStore from '~/boards/stores/boards_store';
import getters from '~/boards/stores/getters'; import getters from '~/boards/stores/getters';
const localVue = createLocalVue(); Vue.use(Vuex);
localVue.use(Vuex);
describe('ee/BoardSettingsSidebar', () => { describe('ee/BoardSettingsSidebar', () => {
let wrapper; let wrapper;
let storeActions; let storeActions;
const labelTitle = 'test';
const labelColor = '#FFFF';
const listId = 1;
let mock;
const createComponent = (actions = {}, isWipLimitsOn = false) => { const createComponent = ({ actions = {}, isWipLimitsOn = false, list = {} }) => {
storeActions = actions; storeActions = actions;
const boardLists = {
[list.id]: { ...list, maxIssueCount: 0 },
};
const store = new Vuex.Store({ const store = new Vuex.Store({
state: { sidebarType: LIST, activeId: listId }, state: { sidebarType: LIST, activeId: list.id, boardLists },
getters, getters,
actions: storeActions, actions: storeActions,
}); });
wrapper = shallowMount(BoardSettingsSidebar, { wrapper = shallowMount(BoardSettingsSidebar, {
store, store,
localVue,
provide: { provide: {
glFeatures: { glFeatures: {
wipLimits: isWipLimitsOn, wipLimits: isWipLimitsOn,
...@@ -47,41 +41,18 @@ describe('ee/BoardSettingsSidebar', () => { ...@@ -47,41 +41,18 @@ describe('ee/BoardSettingsSidebar', () => {
}); });
}; };
beforeEach(() => {
mock = new MockAdapter(axios);
boardsStore.create();
});
afterEach(() => { afterEach(() => {
mock.restore();
wrapper.destroy(); wrapper.destroy();
}); });
it('confirms we render BoardSettingsSidebarWipLimit', () => { it('confirms we render BoardSettingsSidebarWipLimit', () => {
boardsStore.addList({ createComponent({ list: mockLabelList, isWipLimitsOn: true });
id: listId,
label: { title: labelTitle, color: labelColor },
max_issue_count: 0,
list_type: 'label',
});
createComponent({}, true);
expect(wrapper.find(BoardSettingsWipLimit).exists()).toBe(true); expect(wrapper.find(BoardSettingsWipLimit).exists()).toBe(true);
}); });
it('confirms we render BoardSettingsListTypes', () => { it('confirms we render BoardSettingsListTypes', () => {
boardsStore.addList({ createComponent({ list: mockMilestoneList });
id: 1,
milestone: {
webUrl: 'https://gitlab.com/h5bp/html5-boilerplate/-/milestones/1',
title: 'Backlog',
},
max_issue_count: 1,
list_type: 'milestone',
});
createComponent();
expect(wrapper.find(BoardSettingsListTypes).exists()).toBe(true); expect(wrapper.find(BoardSettingsListTypes).exists()).toBe(true);
}); });
......
import '~/boards/models/list';
import { GlFormInput } from '@gitlab/ui'; import { GlFormInput } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { noop } from 'lodash'; import { noop } from 'lodash';
import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import BoardSettingsWipLimit from 'ee_component/boards/components/board_settings_wip_limit.vue'; import BoardSettingsWipLimit from 'ee_component/boards/components/board_settings_wip_limit.vue';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import boardsStore from '~/boards/stores/boards_store'; import { mockLabelList } from 'jest/boards/mock_data';
const localVue = createLocalVue(); Vue.use(Vuex);
localVue.use(Vuex);
describe('BoardSettingsWipLimit', () => { describe('BoardSettingsWipLimit', () => {
let wrapper; let wrapper;
let storeActions; let storeActions;
const labelTitle = 'test'; const listId = mockLabelList.id;
const labelColor = '#FFFF';
const listId = 1;
const currentWipLimit = 1; // Needs to be other than null to trigger requests const currentWipLimit = 1; // Needs to be other than null to trigger requests
let mock;
const addList = (maxIssueCount = 0) => {
boardsStore.addList({
id: listId,
label: { title: labelTitle, color: labelColor },
max_issue_count: maxIssueCount,
list_type: 'label',
});
};
const clickEdit = () => wrapper.find('.js-edit-button').vm.$emit('click'); const clickEdit = () => wrapper.find('.js-edit-button').vm.$emit('click');
const findRemoveWipLimit = () => wrapper.find('.js-remove-limit'); const findRemoveWipLimit = () => wrapper.find('.js-remove-limit');
const findWipLimit = () => wrapper.find('.js-wip-limit'); const findWipLimit = () => wrapper.find('.js-wip-limit');
...@@ -46,13 +31,11 @@ describe('BoardSettingsWipLimit', () => { ...@@ -46,13 +31,11 @@ describe('BoardSettingsWipLimit', () => {
const store = new Vuex.Store({ const store = new Vuex.Store({
state: vuexState, state: vuexState,
actions: storeActions, actions: storeActions,
getters: { shouldUseGraphQL: () => false },
}); });
wrapper = shallowMount(BoardSettingsWipLimit, { wrapper = shallowMount(BoardSettingsWipLimit, {
propsData: props, propsData: props,
store, store,
localVue,
data() { data() {
return localState; return localState;
}, },
...@@ -69,13 +52,7 @@ describe('BoardSettingsWipLimit', () => { ...@@ -69,13 +52,7 @@ describe('BoardSettingsWipLimit', () => {
} }
}; };
beforeEach(() => {
boardsStore.create();
mock = new MockAdapter(axios);
});
afterEach(() => { afterEach(() => {
mock.restore();
jest.restoreAllMocks(); jest.restoreAllMocks();
wrapper.destroy(); wrapper.destroy();
}); });
...@@ -83,25 +60,28 @@ describe('BoardSettingsWipLimit', () => { ...@@ -83,25 +60,28 @@ describe('BoardSettingsWipLimit', () => {
describe('when activeList is present', () => { describe('when activeList is present', () => {
describe('when activeListWipLimit is 0', () => { describe('when activeListWipLimit is 0', () => {
it('renders "None" in the block', () => { it('renders "None" in the block', () => {
createComponent({ vuexState: { activeId: listId } }); createComponent({
vuexState: {
activeId: listId,
},
});
expect(findWipLimit().text()).toBe('None'); expect(findWipLimit().text()).toBe('None');
}); });
}); });
describe('when activeId is greater than 0', () => { describe('when activeListWipLimit is greater than 0', () => {
afterEach(() => {
boardsStore.removeList(listId);
});
it.each` it.each`
num | expected num | expected
${1} | ${'1 issue'} ${1} | ${'1 issue'}
${11} | ${'11 issues'} ${11} | ${'11 issues'}
`('it renders $num', ({ num, expected }) => { `('it renders $num', ({ num, expected }) => {
addList(4); createComponent({
vuexState: {
createComponent({ vuexState: { activeId: num }, props: { maxIssueCount: num } }); activeId: listId,
},
props: { maxIssueCount: num },
});
expect(findWipLimit().text()).toBe(expected); expect(findWipLimit().text()).toBe(expected);
}); });
...@@ -112,7 +92,9 @@ describe('BoardSettingsWipLimit', () => { ...@@ -112,7 +92,9 @@ describe('BoardSettingsWipLimit', () => {
const maxIssueCount = 4; const maxIssueCount = 4;
beforeEach(async () => { beforeEach(async () => {
createComponent({ createComponent({
vuexState: { activeId: listId }, vuexState: {
activeId: listId,
},
actions: { updateListWipLimit: noop }, actions: { updateListWipLimit: noop },
props: { maxIssueCount }, props: { maxIssueCount },
}); });
...@@ -137,15 +119,14 @@ describe('BoardSettingsWipLimit', () => { ...@@ -137,15 +119,14 @@ describe('BoardSettingsWipLimit', () => {
describe('remove limit', () => { describe('remove limit', () => {
describe('when wipLimit is set', () => { describe('when wipLimit is set', () => {
const spy = jest.fn().mockResolvedValue({
data: { boardListUpdateLimitMetrics: { list: { maxIssueCount: 0 } } },
});
beforeEach(() => { beforeEach(() => {
addList(4);
const spy = jest.fn().mockResolvedValue({
config: { data: JSON.stringify({ list: { max_issue_count: 0 } }) },
});
createComponent({ createComponent({
vuexState: { activeId: listId }, vuexState: {
activeId: listId,
},
actions: { updateListWipLimit: spy }, actions: { updateListWipLimit: spy },
props: { maxIssueCount: 4 }, props: { maxIssueCount: 4 },
}); });
...@@ -156,18 +137,22 @@ describe('BoardSettingsWipLimit', () => { ...@@ -156,18 +137,22 @@ describe('BoardSettingsWipLimit', () => {
findRemoveWipLimit().vm.$emit('click'); findRemoveWipLimit().vm.$emit('click');
await waitForPromises();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
// WARNING: https://gitlab.com/gitlab-org/gitlab/-/issues/232573 expect(spy).toHaveBeenCalledWith(
expect(boardsStore.findList('id', listId).maxIssueCount).toBe(0); expect.anything(),
expect.objectContaining({ listId, maxIssueCount: 0 }),
);
}); });
}); });
describe('when wipLimit is not set', () => { describe('when wipLimit is not set', () => {
beforeEach(() => { beforeEach(() => {
addList(); createComponent({
vuexState: { activeId: listId },
createComponent({ vuexState: { activeId: listId }, actions: { updateListWipLimit: noop } }); actions: { updateListWipLimit: noop },
});
}); });
it('does not render the remove limit button', () => { it('does not render the remove limit button', () => {
...@@ -177,14 +162,6 @@ describe('BoardSettingsWipLimit', () => { ...@@ -177,14 +162,6 @@ describe('BoardSettingsWipLimit', () => {
}); });
describe('when edit is true', () => { describe('when edit is true', () => {
beforeEach(() => {
addList(2);
});
afterEach(() => {
boardsStore.removeList(listId);
});
describe.each` describe.each`
blurMethod blurMethod
${'enter'} ${'enter'}
...@@ -193,10 +170,12 @@ describe('BoardSettingsWipLimit', () => { ...@@ -193,10 +170,12 @@ describe('BoardSettingsWipLimit', () => {
describe(`when blur is triggered by ${blurMethod}`, () => { describe(`when blur is triggered by ${blurMethod}`, () => {
it('calls updateListWipLimit', async () => { it('calls updateListWipLimit', async () => {
const spy = jest.fn().mockResolvedValue({ const spy = jest.fn().mockResolvedValue({
config: { data: JSON.stringify({ list: { max_issue_count: '4' } }) }, data: { boardListUpdateLimitMetrics: { list: { maxIssueCount: 4 } } },
}); });
createComponent({ createComponent({
vuexState: { activeId: listId }, vuexState: {
activeId: listId,
},
actions: { updateListWipLimit: spy }, actions: { updateListWipLimit: spy },
localState: { edit: true, currentWipLimit }, localState: { edit: true, currentWipLimit },
}); });
...@@ -209,10 +188,12 @@ describe('BoardSettingsWipLimit', () => { ...@@ -209,10 +188,12 @@ describe('BoardSettingsWipLimit', () => {
}); });
describe('when component wipLimit and List.maxIssueCount are equal', () => { describe('when component wipLimit and List.maxIssueCount are equal', () => {
it('doesnt call updateListWipLimit', async () => { it('does not call updateListWipLimit', async () => {
const spy = jest.fn().mockResolvedValue({}); const spy = jest.fn().mockResolvedValue({});
createComponent({ createComponent({
vuexState: { activeId: listId }, vuexState: {
activeId: listId,
},
actions: { updateListWipLimit: spy }, actions: { updateListWipLimit: spy },
localState: { edit: true, currentWipLimit: 2 }, localState: { edit: true, currentWipLimit: 2 },
props: { maxIssueCount: 2 }, props: { maxIssueCount: 2 },
...@@ -227,7 +208,7 @@ describe('BoardSettingsWipLimit', () => { ...@@ -227,7 +208,7 @@ describe('BoardSettingsWipLimit', () => {
}); });
describe('when currentWipLimit is null', () => { describe('when currentWipLimit is null', () => {
it('doesnt call updateListWipLimit', async () => { it('does not call updateListWipLimit', async () => {
const spy = jest.fn().mockResolvedValue({}); const spy = jest.fn().mockResolvedValue({});
createComponent({ createComponent({
vuexState: { activeId: listId }, vuexState: { activeId: listId },
...@@ -249,9 +230,12 @@ describe('BoardSettingsWipLimit', () => { ...@@ -249,9 +230,12 @@ describe('BoardSettingsWipLimit', () => {
beforeEach(() => { beforeEach(() => {
const spy = jest.fn().mockResolvedValue({}); const spy = jest.fn().mockResolvedValue({});
createComponent({ createComponent({
vuexState: { activeId: listId }, vuexState: {
activeId: listId,
},
actions: { updateListWipLimit: spy }, actions: { updateListWipLimit: spy },
localState: { edit: true, currentWipLimit: maxIssueCount }, localState: { edit: true, currentWipLimit: maxIssueCount },
props: { maxIssueCount },
}); });
triggerBlur(blurMethod); triggerBlur(blurMethod);
...@@ -260,14 +244,7 @@ describe('BoardSettingsWipLimit', () => { ...@@ -260,14 +244,7 @@ describe('BoardSettingsWipLimit', () => {
}); });
it('sets activeWipLimit to new maxIssueCount value', () => { it('sets activeWipLimit to new maxIssueCount value', () => {
/* expect(findWipLimit().text()).toContain(maxIssueCount);
* DANGER: bad coupling to the computed prop of the component because the
* computed prop relys on the list from boardStore, for now this is the way around
* stale values from boardsStore being updated, when we move List and BoardsStore to Vuex
* or Graphql we will be able to query the DOM for the new value.
*/
expect(boardsStore.findList('id', 1).maxIssueCount).toBe(maxIssueCount);
}); });
it('toggles GlFormInput on blur', () => { it('toggles GlFormInput on blur', () => {
......
...@@ -112,12 +112,7 @@ describe('setFilters', () => { ...@@ -112,12 +112,7 @@ describe('setFilters', () => {
}); });
describe('performSearch', () => { describe('performSearch', () => {
it('should dispatch setFilters action', (done) => { it('should dispatch setFilters, fetchLists and resetIssues action', async () => {
testAction(actions.performSearch, {}, {}, [], [{ type: 'setFilters', payload: {} }], done);
});
it('should dispatch setFilters, fetchLists and resetIssues action when graphqlBoardLists FF is on', async () => {
window.gon = { features: { graphqlBoardLists: true } };
const getters = { isSwimlanesOn: false }; const getters = { isSwimlanesOn: false };
await testAction({ await testAction({
...@@ -139,9 +134,9 @@ describe('performSearch', () => { ...@@ -139,9 +134,9 @@ describe('performSearch', () => {
expectedActions: [ expectedActions: [
{ type: 'setFilters', payload: {} }, { type: 'setFilters', payload: {} },
{ type: 'resetEpics' }, { type: 'resetEpics' },
{ type: 'resetIssues' },
{ type: 'fetchEpicsSwimlanes' }, { type: 'fetchEpicsSwimlanes' },
{ type: 'fetchLists' }, { type: 'fetchLists' },
{ type: 'resetIssues' },
], ],
}); });
}); });
...@@ -464,7 +459,6 @@ describe('setShowLabels', () => { ...@@ -464,7 +459,6 @@ describe('setShowLabels', () => {
describe('updateListWipLimit', () => { describe('updateListWipLimit', () => {
let storeMock; let storeMock;
const getters = { shouldUseGraphQL: false };
beforeEach(() => { beforeEach(() => {
storeMock = { storeMock = {
...@@ -483,26 +477,9 @@ describe('updateListWipLimit', () => { ...@@ -483,26 +477,9 @@ describe('updateListWipLimit', () => {
jest.restoreAllMocks(); jest.restoreAllMocks();
}); });
it('axios - should call the correct url', () => { it('commit UPDATE_LIST_SUCCESS mutation on success', () => {
const maxIssueCount = 0;
const activeId = 1;
return actions
.updateListWipLimit({ state: { activeId }, getters }, { maxIssueCount, listId: activeId })
.then(() => {
expect(axios.put).toHaveBeenCalledWith(
`${boardsStoreEE.store.state.endpoints.listsEndpoint}/${activeId}`,
{
list: { max_issue_count: maxIssueCount },
},
);
});
});
it('graphql - commit UPDATE_LIST_SUCCESS mutation on success', () => {
const maxIssueCount = 0; const maxIssueCount = 0;
const activeId = 1; const activeId = 1;
getters.shouldUseGraphQL = true;
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({ jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
data: { data: {
boardListUpdateLimitMetrics: { boardListUpdateLimitMetrics: {
...@@ -517,7 +494,7 @@ describe('updateListWipLimit', () => { ...@@ -517,7 +494,7 @@ describe('updateListWipLimit', () => {
return testAction( return testAction(
actions.updateListWipLimit, actions.updateListWipLimit,
{ maxIssueCount, listId: activeId }, { maxIssueCount, listId: activeId },
{ isShowingEpicsSwimlanes: true, ...getters }, { isShowingEpicsSwimlanes: true },
[ [
{ {
type: types.UPDATE_LIST_SUCCESS, type: types.UPDATE_LIST_SUCCESS,
...@@ -533,16 +510,15 @@ describe('updateListWipLimit', () => { ...@@ -533,16 +510,15 @@ describe('updateListWipLimit', () => {
); );
}); });
it('graphql - dispatch handleUpdateListFailure on failure', () => { it('dispatch handleUpdateListFailure on failure', () => {
const maxIssueCount = 0; const maxIssueCount = 0;
const activeId = 1; const activeId = 1;
getters.shouldUseGraphQL = true;
jest.spyOn(gqlClient, 'mutate').mockResolvedValue(Promise.reject()); jest.spyOn(gqlClient, 'mutate').mockResolvedValue(Promise.reject());
return testAction( return testAction(
actions.updateListWipLimit, actions.updateListWipLimit,
{ maxIssueCount, listId: activeId }, { maxIssueCount, listId: activeId },
{ isShowingEpicsSwimlanes: true, ...getters }, { isShowingEpicsSwimlanes: true },
[], [],
[{ type: 'handleUpdateListFailure' }], [{ type: 'handleUpdateListFailure' }],
); );
......
...@@ -3626,9 +3626,6 @@ msgstr "" ...@@ -3626,9 +3626,6 @@ msgstr ""
msgid "An error occurred while fetching terraform reports." msgid "An error occurred while fetching terraform reports."
msgstr "" msgstr ""
msgid "An error occurred while fetching the board lists. Please try again."
msgstr ""
msgid "An error occurred while fetching the job log." msgid "An error occurred while fetching the job log."
msgstr "" msgstr ""
...@@ -5513,9 +5510,6 @@ msgid_plural "Boards|Blocked by %{blockedByCount} %{issuableType}s" ...@@ -5513,9 +5510,6 @@ msgid_plural "Boards|Blocked by %{blockedByCount} %{issuableType}s"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "Boards|Board"
msgstr ""
msgid "Boards|Collapse" msgid "Boards|Collapse"
msgstr "" msgstr ""
......
...@@ -43,12 +43,12 @@ RSpec.describe 'Multi Select Issue', :js do ...@@ -43,12 +43,12 @@ RSpec.describe 'Multi Select Issue', :js do
# Multi select drag&drop support is temporarily disabled # Multi select drag&drop support is temporarily disabled
# https://gitlab.com/gitlab-org/gitlab/-/issues/289797 # https://gitlab.com/gitlab-org/gitlab/-/issues/289797
stub_feature_flags(graphql_board_lists: false, board_multi_select: project) stub_feature_flags(board_multi_select: project)
sign_in(user) sign_in(user)
end end
context 'with lists' do xcontext 'with lists' do
let(:label1) { create(:label, project: project, name: 'Label 1', description: 'Test') } let(:label1) { create(:label, project: project, name: 'Label 1', description: 'Test') }
let(:label2) { create(:label, project: project, name: 'Label 2', description: 'Test') } let(:label2) { create(:label, project: project, name: 'Label 2', description: 'Test') }
let!(:list1) { create(:list, board: board, label: label1, position: 0) } let!(:list1) { create(:list, board: board, label: label1, position: 0) }
......
...@@ -5,8 +5,9 @@ require 'spec_helper' ...@@ -5,8 +5,9 @@ require 'spec_helper'
RSpec.describe 'Project issue boards sidebar labels', :js do RSpec.describe 'Project issue boards sidebar labels', :js do
include BoardHelpers include BoardHelpers
let_it_be(:group) { create(:group, :public) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) } let_it_be(:project) { create(:project, :public, namespace: group) }
let_it_be(:development) { create(:label, project: project, name: 'Development') } let_it_be(:development) { create(:label, project: project, name: 'Development') }
let_it_be(:bug) { create(:label, project: project, name: 'Bug') } let_it_be(:bug) { create(:label, project: project, name: 'Bug') }
let_it_be(:regression) { create(:label, project: project, name: 'Regression') } let_it_be(:regression) { create(:label, project: project, name: 'Regression') }
......
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'User adds lists', :js do RSpec.describe 'User adds lists', :js do
using RSpec::Parameterized::TableSyntax
let_it_be(:group) { create(:group, :nested) } let_it_be(:group) { create(:group, :nested) }
let_it_be(:project) { create(:project, :public, namespace: group) } let_it_be(:project) { create(:project, :public, namespace: group) }
let_it_be(:group_board) { create(:board, group: group) } let_it_be(:group_board) { create(:board, group: group) }
...@@ -27,11 +25,8 @@ RSpec.describe 'User adds lists', :js do ...@@ -27,11 +25,8 @@ RSpec.describe 'User adds lists', :js do
group.add_owner(user) group.add_owner(user)
end end
where(:board_type, :graphql_board_lists_enabled) do where(:board_type) do
:project | true [[:project], [:group]]
:project | false
:group | true
:group | false
end end
with_them do with_them do
...@@ -40,10 +35,6 @@ RSpec.describe 'User adds lists', :js do ...@@ -40,10 +35,6 @@ RSpec.describe 'User adds lists', :js do
set_cookie('sidebar_collapsed', 'true') set_cookie('sidebar_collapsed', 'true')
stub_feature_flags(
graphql_board_lists: graphql_board_lists_enabled
)
if board_type == :project if board_type == :project
visit project_board_path(project, project_board) visit project_board_path(project, project_board)
elsif board_type == :group elsif board_type == :group
...@@ -53,14 +44,12 @@ RSpec.describe 'User adds lists', :js do ...@@ -53,14 +44,12 @@ RSpec.describe 'User adds lists', :js do
wait_for_all_requests wait_for_all_requests
end end
it 'creates new column for label containing labeled issue' do it 'creates new column for label containing labeled issue', :aggregate_failures do
click_button 'Create list' click_button 'Create list'
wait_for_all_requests wait_for_all_requests
select_label(group_label) select_label(group_label)
wait_for_all_requests
expect(page).to have_selector('.board', text: group_label.title) expect(page).to have_selector('.board', text: group_label.title)
expect(find('.board:nth-child(2) .board-card')).to have_content(issue.title) expect(find('.board:nth-child(2) .board-card')).to have_content(issue.title)
end end
......
...@@ -42,30 +42,4 @@ RSpec.describe 'Group Issue Boards', :js do ...@@ -42,30 +42,4 @@ RSpec.describe 'Group Issue Boards', :js do
end end
end end
end end
context 'when graphql_board_lists FF disabled' do
before do
stub_feature_flags(graphql_board_lists: false)
sign_in(user)
visit group_board_path(group, board)
wait_for_requests
end
it 'only shows valid labels for the issue project and group' do
click_card(card)
page.within('.labels') do
click_link 'Edit'
wait_for_requests
page.within('.selectbox') do
expect(page).to have_content(project_1_label.title)
expect(page).to have_content(group_label.title)
expect(page).not_to have_content(project_2_label.title)
end
end
end
end
end end
...@@ -214,44 +214,6 @@ RSpec.describe 'Labels Hierarchy', :js do ...@@ -214,44 +214,6 @@ RSpec.describe 'Labels Hierarchy', :js do
end end
end end
context 'issuable sidebar when graphql_board_lists FF disabled' do
let!(:issue) { create(:issue, project: project_1) }
before do
stub_feature_flags(graphql_board_lists: false)
end
context 'on project board issue sidebar' do
before do
project_1.add_developer(user)
board = create(:board, project: project_1)
visit project_board_path(project_1, board)
wait_for_requests
find('.board-card').click
end
it_behaves_like 'assigning labels from sidebar'
end
context 'on group board issue sidebar' do
before do
parent.add_developer(user)
board = create(:board, group: parent)
visit group_board_path(parent, board)
wait_for_requests
find('.board-card').click
end
it_behaves_like 'assigning labels from sidebar'
end
end
context 'issuable filtering' do context 'issuable filtering' do
let!(:labeled_issue) { create(:labeled_issue, project: project_1, labels: [grandparent_group_label, parent_group_label, project_label_1]) } let!(:labeled_issue) { create(:labeled_issue, project: project_1, labels: [grandparent_group_label, parent_group_label, project_label_1]) }
let!(:issue) { create(:issue, project: project_1) } let!(:issue) { create(:issue, project: project_1) }
......
...@@ -48,7 +48,6 @@ describe('Board card layout', () => { ...@@ -48,7 +48,6 @@ describe('Board card layout', () => {
...actions, ...actions,
}, },
getters: { getters: {
shouldUseGraphQL: () => true,
getListByLabelId: () => getListByLabelId, getListByLabelId: () => getListByLabelId,
}, },
state: { state: {
......
...@@ -8,7 +8,6 @@ import MockAdapter from 'axios-mock-adapter'; ...@@ -8,7 +8,6 @@ import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import BoardCardDeprecated from '~/boards/components/board_card_deprecated.vue'; import BoardCardDeprecated from '~/boards/components/board_card_deprecated.vue';
import issueCardInner from '~/boards/components/issue_card_inner_deprecated.vue'; import issueCardInner from '~/boards/components/issue_card_inner_deprecated.vue';
import eventHub from '~/boards/eventhub';
import store from '~/boards/stores'; import store from '~/boards/stores';
import boardsStore from '~/boards/stores/boards_store'; import boardsStore from '~/boards/stores/boards_store';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
...@@ -165,46 +164,9 @@ describe('BoardCard', () => { ...@@ -165,46 +164,9 @@ describe('BoardCard', () => {
expect(boardsStore.detail.issue).toEqual({}); expect(boardsStore.detail.issue).toEqual({});
}); });
it('sets detail issue to card issue on mouse up', () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
mountComponent();
wrapper.trigger('mousedown');
wrapper.trigger('mouseup');
expect(eventHub.$emit).toHaveBeenCalledWith('newDetailIssue', wrapper.vm.issue, false);
expect(boardsStore.detail.list).toEqual(wrapper.vm.list);
});
it('resets detail issue to empty if already set', () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
const [issue] = list.issues;
boardsStore.detail.issue = issue;
mountComponent();
wrapper.trigger('mousedown');
wrapper.trigger('mouseup');
expect(eventHub.$emit).toHaveBeenCalledWith('clearDetailIssue', false);
});
}); });
describe('sidebarHub events', () => { describe('sidebarHub events', () => {
it('closes all sidebars before showing an issue if no issues are opened', () => {
jest.spyOn(sidebarEventHub, '$emit').mockImplementation(() => {});
boardsStore.detail.issue = {};
mountComponent();
// sets conditional so that event is emitted.
wrapper.trigger('mousedown');
wrapper.trigger('mouseup');
expect(sidebarEventHub.$emit).toHaveBeenCalledWith('sidebar.closeAll');
});
it('it does not closes all sidebars before showing an issue if an issue is opened', () => { it('it does not closes all sidebars before showing an issue if an issue is opened', () => {
jest.spyOn(sidebarEventHub, '$emit').mockImplementation(() => {}); jest.spyOn(sidebarEventHub, '$emit').mockImplementation(() => {});
const [issue] = list.issues; const [issue] = list.issues;
......
...@@ -111,18 +111,14 @@ describe('Board card layout', () => { ...@@ -111,18 +111,14 @@ describe('Board card layout', () => {
expect(wrapper.vm.showDetail).toBe(false); expect(wrapper.vm.showDetail).toBe(false);
}); });
it("calls 'setActiveId' when 'graphqlBoardLists' feature flag is turned on", async () => { it("calls 'setActiveId'", async () => {
const setActiveId = jest.fn(); const setActiveId = jest.fn();
createStore({ createStore({
actions: { actions: {
setActiveId, setActiveId,
}, },
}); });
mountComponent({ mountComponent();
provide: {
glFeatures: { graphqlBoardLists: true },
},
});
wrapper.trigger('mouseup'); wrapper.trigger('mouseup');
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
......
...@@ -5,7 +5,7 @@ import Draggable from 'vuedraggable'; ...@@ -5,7 +5,7 @@ import Draggable from 'vuedraggable';
import Vuex from 'vuex'; import Vuex from 'vuex';
import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue'; import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
import getters from 'ee_else_ce/boards/stores/getters'; import getters from 'ee_else_ce/boards/stores/getters';
import BoardColumnDeprecated from '~/boards/components/board_column_deprecated.vue'; import BoardColumn from '~/boards/components/board_column.vue';
import BoardContent from '~/boards/components/board_content.vue'; import BoardContent from '~/boards/components/board_content.vue';
import { mockLists, mockListsWithModel } from '../mock_data'; import { mockLists, mockListsWithModel } from '../mock_data';
...@@ -33,12 +33,7 @@ describe('BoardContent', () => { ...@@ -33,12 +33,7 @@ describe('BoardContent', () => {
}); });
}; };
const createComponent = ({ const createComponent = ({ state, props = {}, canAdminList = true } = {}) => {
state,
props = {},
graphqlBoardListsEnabled = false,
canAdminList = true,
} = {}) => {
const store = createStore({ const store = createStore({
...defaultState, ...defaultState,
...state, ...state,
...@@ -51,63 +46,41 @@ describe('BoardContent', () => { ...@@ -51,63 +46,41 @@ describe('BoardContent', () => {
}, },
provide: { provide: {
canAdminList, canAdminList,
glFeatures: { graphqlBoardLists: graphqlBoardListsEnabled },
}, },
store, store,
}); });
}; };
beforeEach(() => {
createComponent();
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
it('renders a BoardColumnDeprecated component per list', () => { it('renders a BoardColumn component per list', () => {
createComponent(); expect(wrapper.findAllComponents(BoardColumn)).toHaveLength(mockListsWithModel.length);
expect(wrapper.findAllComponents(BoardColumnDeprecated)).toHaveLength(
mockListsWithModel.length,
);
}); });
it('does not display EpicsSwimlanes component', () => { it('does not display EpicsSwimlanes component', () => {
createComponent();
expect(wrapper.find(EpicsSwimlanes).exists()).toBe(false); expect(wrapper.find(EpicsSwimlanes).exists()).toBe(false);
expect(wrapper.find(GlAlert).exists()).toBe(false); expect(wrapper.find(GlAlert).exists()).toBe(false);
}); });
describe('graphqlBoardLists feature flag enabled', () => { describe('can admin list', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ graphqlBoardListsEnabled: true }); createComponent({ canAdminList: true });
gon.features = {
graphqlBoardLists: true,
};
}); });
describe('can admin list', () => { it('renders draggable component', () => {
beforeEach(() => { expect(wrapper.find(Draggable).exists()).toBe(true);
createComponent({ graphqlBoardListsEnabled: true, canAdminList: true });
});
it('renders draggable component', () => {
expect(wrapper.find(Draggable).exists()).toBe(true);
});
});
describe('can not admin list', () => {
beforeEach(() => {
createComponent({ graphqlBoardListsEnabled: true, canAdminList: false });
});
it('does not render draggable component', () => {
expect(wrapper.find(Draggable).exists()).toBe(false);
});
}); });
}); });
describe('graphqlBoardLists feature flag disabled', () => { describe('can not admin list', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ graphqlBoardListsEnabled: false }); createComponent({ canAdminList: false });
}); });
it('does not render draggable component', () => { it('does not render draggable component', () => {
......
...@@ -2,7 +2,6 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -2,7 +2,6 @@ import { shallowMount } from '@vue/test-utils';
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import BoardFilteredSearch from '~/boards/components/board_filtered_search.vue'; import BoardFilteredSearch from '~/boards/components/board_filtered_search.vue';
import { createStore } from '~/boards/stores';
import * as urlUtility from '~/lib/utils/url_utility'; import * as urlUtility from '~/lib/utils/url_utility';
import { __ } from '~/locale'; import { __ } from '~/locale';
import FilteredSearchBarRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; import FilteredSearchBarRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
...@@ -44,6 +43,12 @@ describe('BoardFilteredSearch', () => { ...@@ -44,6 +43,12 @@ describe('BoardFilteredSearch', () => {
]; ];
const createComponent = ({ initialFilterParams = {} } = {}) => { const createComponent = ({ initialFilterParams = {} } = {}) => {
store = new Vuex.Store({
actions: {
performSearch: jest.fn(),
},
});
wrapper = shallowMount(BoardFilteredSearch, { wrapper = shallowMount(BoardFilteredSearch, {
provide: { initialFilterParams, fullPath: '' }, provide: { initialFilterParams, fullPath: '' },
store, store,
...@@ -55,22 +60,15 @@ describe('BoardFilteredSearch', () => { ...@@ -55,22 +60,15 @@ describe('BoardFilteredSearch', () => {
const findFilteredSearch = () => wrapper.findComponent(FilteredSearchBarRoot); const findFilteredSearch = () => wrapper.findComponent(FilteredSearchBarRoot);
beforeEach(() => {
// this needed for actions call for performSearch
window.gon = { features: {} };
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('default', () => { describe('default', () => {
beforeEach(() => { beforeEach(() => {
store = createStore(); createComponent();
jest.spyOn(store, 'dispatch'); jest.spyOn(store, 'dispatch');
createComponent();
}); });
it('renders FilteredSearch', () => { it('renders FilteredSearch', () => {
...@@ -103,8 +101,6 @@ describe('BoardFilteredSearch', () => { ...@@ -103,8 +101,6 @@ describe('BoardFilteredSearch', () => {
describe('when searching', () => { describe('when searching', () => {
beforeEach(() => { beforeEach(() => {
store = createStore();
createComponent(); createComponent();
jest.spyOn(wrapper.vm, 'performSearch').mockImplementation(); jest.spyOn(wrapper.vm, 'performSearch').mockImplementation();
...@@ -133,11 +129,9 @@ describe('BoardFilteredSearch', () => { ...@@ -133,11 +129,9 @@ describe('BoardFilteredSearch', () => {
describe('when url params are already set', () => { describe('when url params are already set', () => {
beforeEach(() => { beforeEach(() => {
store = createStore(); createComponent({ initialFilterParams: { authorUsername: 'root', labelName: ['label'] } });
jest.spyOn(store, 'dispatch'); jest.spyOn(store, 'dispatch');
createComponent({ initialFilterParams: { authorUsername: 'root', labelName: ['label'] } });
}); });
it('passes the correct props to FilterSearchBar', () => { it('passes the correct props to FilterSearchBar', () => {
......
import '~/boards/models/list'; import '~/boards/models/list';
import { GlDrawer, GlLabel } from '@gitlab/ui'; import { GlDrawer, GlLabel } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { MountingPortal } from 'portal-vue'; import { MountingPortal } from 'portal-vue';
import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue'; import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue';
import { inactiveId, LIST } from '~/boards/constants'; import { inactiveId, LIST } from '~/boards/constants';
import { createStore } from '~/boards/stores'; import actions from '~/boards/stores/actions';
import boardsStore from '~/boards/stores/boards_store'; import getters from '~/boards/stores/getters';
import mutations from '~/boards/stores/mutations';
import sidebarEventHub from '~/sidebar/event_hub'; import sidebarEventHub from '~/sidebar/event_hub';
import { mockLabelList } from '../mock_data';
const localVue = createLocalVue(); Vue.use(Vuex);
localVue.use(Vuex);
describe('BoardSettingsSidebar', () => { describe('BoardSettingsSidebar', () => {
let wrapper; let wrapper;
let mock; const labelTitle = mockLabelList.label.title;
let store; const labelColor = mockLabelList.label.color;
const labelTitle = 'test'; const listId = mockLabelList.id;
const labelColor = '#FFFF';
const listId = 1;
const findRemoveButton = () => wrapper.findByTestId('remove-list'); const findRemoveButton = () => wrapper.findByTestId('remove-list');
const createComponent = ({ canAdminList = false } = {}) => { const createComponent = ({
canAdminList = false,
list = {},
sidebarType = LIST,
activeId = inactiveId,
} = {}) => {
const boardLists = {
[listId]: list,
};
const store = new Vuex.Store({
state: { sidebarType, activeId, boardLists },
getters,
mutations,
actions,
});
wrapper = extendedWrapper( wrapper = extendedWrapper(
shallowMount(BoardSettingsSidebar, { shallowMount(BoardSettingsSidebar, {
store, store,
localVue,
provide: { provide: {
canAdminList, canAdminList,
}, },
...@@ -40,16 +51,10 @@ describe('BoardSettingsSidebar', () => { ...@@ -40,16 +51,10 @@ describe('BoardSettingsSidebar', () => {
const findLabel = () => wrapper.find(GlLabel); const findLabel = () => wrapper.find(GlLabel);
const findDrawer = () => wrapper.find(GlDrawer); const findDrawer = () => wrapper.find(GlDrawer);
beforeEach(() => {
store = createStore();
store.state.activeId = inactiveId;
store.state.sidebarType = LIST;
boardsStore.create();
});
afterEach(() => { afterEach(() => {
jest.restoreAllMocks(); jest.restoreAllMocks();
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
it('finds a MountingPortal component', () => { it('finds a MountingPortal component', () => {
...@@ -100,86 +105,40 @@ describe('BoardSettingsSidebar', () => { ...@@ -100,86 +105,40 @@ describe('BoardSettingsSidebar', () => {
}); });
describe('when activeId is greater than zero', () => { describe('when activeId is greater than zero', () => {
beforeEach(() => { it('renders GlDrawer with open true', () => {
mock = new MockAdapter(axios); createComponent({ list: mockLabelList, activeId: listId });
boardsStore.addList({
id: listId,
label: { title: labelTitle, color: labelColor },
list_type: 'label',
});
store.state.activeId = 1;
store.state.sidebarType = LIST;
});
afterEach(() => {
boardsStore.removeList(listId);
});
it('renders GlDrawer with open false', () => {
createComponent();
expect(findDrawer().props('open')).toBe(true); expect(findDrawer().props('open')).toBe(true);
}); });
}); });
describe('when activeId is in boardsStore', () => { describe('when activeId is in state', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
boardsStore.addList({
id: listId,
label: { title: labelTitle, color: labelColor },
list_type: 'label',
});
store.state.activeId = listId;
store.state.sidebarType = LIST;
createComponent();
});
afterEach(() => {
mock.restore();
});
it('renders label title', () => { it('renders label title', () => {
createComponent({ list: mockLabelList, activeId: listId });
expect(findLabel().props('title')).toBe(labelTitle); expect(findLabel().props('title')).toBe(labelTitle);
}); });
it('renders label background color', () => { it('renders label background color', () => {
createComponent({ list: mockLabelList, activeId: listId });
expect(findLabel().props('backgroundColor')).toBe(labelColor); expect(findLabel().props('backgroundColor')).toBe(labelColor);
}); });
}); });
describe('when activeId is not in boardsStore', () => { describe('when activeId is not in state', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
boardsStore.addList({ id: listId, label: { title: labelTitle, color: labelColor } });
store.state.activeId = inactiveId;
createComponent();
});
afterEach(() => {
mock.restore();
});
it('does not render GlLabel', () => { it('does not render GlLabel', () => {
createComponent({ list: mockLabelList });
expect(findLabel().exists()).toBe(false); expect(findLabel().exists()).toBe(false);
}); });
}); });
}); });
describe('when sidebarType is not List', () => { describe('when sidebarType is not List', () => {
beforeEach(() => {
store.state.sidebarType = '';
createComponent();
});
it('does not render GlDrawer', () => { it('does not render GlDrawer', () => {
createComponent({ sidebarType: '' });
expect(findDrawer().exists()).toBe(false); expect(findDrawer().exists()).toBe(false);
}); });
}); });
...@@ -191,20 +150,9 @@ describe('BoardSettingsSidebar', () => { ...@@ -191,20 +150,9 @@ describe('BoardSettingsSidebar', () => {
}); });
describe('when user can admin the boards list', () => { describe('when user can admin the boards list', () => {
beforeEach(() => {
store.state.activeId = listId;
store.state.sidebarType = LIST;
boardsStore.addList({
id: listId,
label: { title: labelTitle, color: labelColor },
list_type: 'label',
});
createComponent({ canAdminList: true });
});
it('renders "Remove list" button', () => { it('renders "Remove list" button', () => {
createComponent({ canAdminList: true, activeId: listId, list: mockLabelList });
expect(findRemoveButton().exists()).toBe(true); expect(findRemoveButton().exists()).toBe(true);
}); });
}); });
......
...@@ -336,6 +336,22 @@ export const mockLabelList = { ...@@ -336,6 +336,22 @@ export const mockLabelList = {
issuesCount: 0, issuesCount: 0,
}; };
export const mockMilestoneList = {
id: 'gid://gitlab/List/3',
title: 'To Do',
position: 0,
listType: 'milestone',
collapsed: false,
label: null,
assignee: null,
milestone: {
webUrl: 'https://gitlab.com/h5bp/html5-boilerplate/-/milestones/1',
title: 'Backlog',
},
loading: false,
issuesCount: 0,
};
export const mockLists = [mockList, mockLabelList]; export const mockLists = [mockList, mockLabelList];
export const mockListsById = keyBy(mockLists, 'id'); export const mockListsById = keyBy(mockLists, 'id');
......
...@@ -107,12 +107,7 @@ describe('setFilters', () => { ...@@ -107,12 +107,7 @@ describe('setFilters', () => {
}); });
describe('performSearch', () => { describe('performSearch', () => {
it('should dispatch setFilters action', (done) => { it('should dispatch setFilters, fetchLists and resetIssues action', (done) => {
testAction(actions.performSearch, {}, {}, [], [{ type: 'setFilters', payload: {} }], done);
});
it('should dispatch setFilters, fetchLists and resetIssues action when graphqlBoardLists FF is on', (done) => {
window.gon = { features: { graphqlBoardLists: true } };
testAction( testAction(
actions.performSearch, actions.performSearch,
{}, {},
...@@ -496,12 +491,9 @@ describe('fetchLabels', () => { ...@@ -496,12 +491,9 @@ describe('fetchLabels', () => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse); jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
const commit = jest.fn(); const commit = jest.fn();
const getters = {
shouldUseGraphQL: () => true,
};
const state = { boardType: 'group' }; const state = { boardType: 'group' };
await actions.fetchLabels({ getters, state, commit }); await actions.fetchLabels({ state, commit });
expect(commit).toHaveBeenCalledWith(types.RECEIVE_LABELS_SUCCESS, labels); expect(commit).toHaveBeenCalledWith(types.RECEIVE_LABELS_SUCCESS, labels);
}); });
...@@ -954,7 +946,7 @@ describe('moveIssue', () => { ...@@ -954,7 +946,7 @@ describe('moveIssue', () => {
}); });
describe('moveIssueCard and undoMoveIssueCard', () => { describe('moveIssueCard and undoMoveIssueCard', () => {
describe('card should move without clonning', () => { describe('card should move without cloning', () => {
let state; let state;
let params; let params;
let moveMutations; let moveMutations;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment