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:
- 'ee/app/graphql/types/vulnerability_severity_enum.rb'
- 'ee/app/graphql/types/vulnerability_state_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/boards/epic_boards/create.rb'
- 'ee/app/graphql/mutations/boards/epic_boards/epic_move_list.rb'
......
d924490032231edb9452acdaca7d8e4747cf6ab4
50eff62c1b1f5730c7ca18597493b0b2926e72ca
......@@ -2,9 +2,6 @@
import { GlFormRadio, GlFormRadioGroup, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
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 {
components: {
......@@ -24,7 +21,7 @@ export default {
},
computed: {
...mapState(['labels', 'labelsLoading']),
...mapGetters(['getListByLabelId', 'shouldUseGraphQL']),
...mapGetters(['getListByLabelId']),
columnForSelected() {
return this.getListByLabelId(this.selectedId);
},
......@@ -34,17 +31,6 @@ export default {
},
methods: {
...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() {
if (!this.selectedLabel) {
return;
......@@ -54,23 +40,11 @@ export default {
if (this.columnForSelected) {
const listId = this.columnForSelected.id;
this.highlight(listId);
this.highlightList(listId);
return;
}
if (this.shouldUseGraphQL) {
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);
}
this.createList({ labelId: this.selectedId });
},
filterItems(searchTerm) {
......
......@@ -62,17 +62,7 @@ export default {
// Don't do anything if this happened on a no trigger element
if (e.target.classList.contains('js-no-trigger')) return;
if (this.glFeatures.graphqlBoardLists || this.isSwimlanesOn) {
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 });
}
this.setActiveId({ id: this.issue.id, sidebarType: ISSUABLE });
},
},
};
......
......@@ -5,24 +5,20 @@ import Draggable from 'vuedraggable';
import { mapState, mapGetters, mapActions } from 'vuex';
import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column.vue';
import defaultSortableConfig from '~/sortable/sortable_config';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { DraggableItemTypes } from '../constants';
import BoardColumn from './board_column.vue';
import BoardColumnDeprecated from './board_column_deprecated.vue';
export default {
draggableItemTypes: DraggableItemTypes,
components: {
BoardAddNewColumn,
BoardColumn,
BoardColumnDeprecated,
BoardContentSidebar: () => import('~/boards/components/board_content_sidebar.vue'),
EpicBoardContentSidebar: () =>
import('ee_component/boards/components/epic_board_content_sidebar.vue'),
EpicsSwimlanes: () => import('ee_component/boards/components/epics_swimlanes.vue'),
GlAlert,
},
mixins: [glFeatureFlagMixin()],
inject: ['canAdminList'],
props: {
lists: {
......@@ -37,20 +33,15 @@ export default {
},
computed: {
...mapState(['boardLists', 'error', 'addColumnForm']),
...mapGetters(['isSwimlanesOn', 'isEpicBoard']),
useNewBoardColumnComponent() {
return this.glFeatures.graphqlBoardLists || this.isSwimlanesOn || this.isEpicBoard;
},
...mapGetters(['isSwimlanesOn', 'isEpicBoard', 'isIssueBoard']),
addColumnFormVisible() {
return this.addColumnForm?.visible;
},
boardListsToUse() {
return this.useNewBoardColumnComponent
? sortBy([...Object.values(this.boardLists)], 'position')
: this.lists;
return sortBy([...Object.values(this.boardLists)], 'position');
},
canDragColumns() {
return (this.isEpicBoard || this.glFeatures.graphqlBoardLists) && this.canAdminList;
return this.canAdminList;
},
boardColumnWrapper() {
return this.canDragColumns ? Draggable : 'div';
......@@ -68,9 +59,6 @@ export default {
return this.canDragColumns ? options : {};
},
boardColumnComponent() {
return this.useNewBoardColumnComponent ? BoardColumn : BoardColumnDeprecated;
},
},
methods: {
...mapActions(['moveList', 'unsetError']),
......@@ -95,8 +83,7 @@ export default {
class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
@end="moveList"
>
<component
:is="boardColumnComponent"
<board-column
v-for="(list, index) in boardListsToUse"
:key="index"
ref="board"
......@@ -118,10 +105,7 @@ export default {
:disabled="disabled"
/>
<board-content-sidebar
v-if="isSwimlanesOn || glFeatures.graphqlBoardLists"
data-testid="issue-boards-sidebar"
/>
<board-content-sidebar v-if="isIssueBoard" data-testid="issue-boards-sidebar" />
<epic-board-content-sidebar v-else-if="isEpicBoard" data-testid="epic-boards-sidebar" />
</div>
......
......@@ -31,20 +31,13 @@ export default {
};
},
computed: {
...mapGetters(['isSidebarOpen', 'shouldUseGraphQL', 'isEpicBoard']),
...mapGetters(['isSidebarOpen', 'isEpicBoard']),
...mapState(['activeId', 'sidebarType', 'boardLists']),
isWipLimitsOn() {
return this.glFeatures.wipLimits && !this.isEpicBoard;
},
activeList() {
/*
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);
return this.boardLists[this.activeId];
},
activeListLabel() {
return this.activeList.label;
......@@ -73,12 +66,8 @@ export default {
deleteBoard() {
// eslint-disable-next-line no-alert
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.removeList(this.activeId);
} else {
this.activeList.destroy();
}
this.track('click_button', { label: 'remove_list' });
this.removeList(this.activeId);
this.unsetActiveId();
}
},
......
......@@ -4,7 +4,6 @@ import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable
import { updateHistory } from '~/lib/utils/url_utility';
import FilteredSearchContainer from '../filtered_search/container';
import vuexstore from './stores';
import boardsStore from './stores/boards_store';
export default class FilteredSearchBoards extends FilteredSearchManager {
constructor(store, updateUrl = false, cantEdit = []) {
......@@ -26,7 +25,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
this.cantEdit = cantEdit.filter((i) => typeof i === 'string');
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);
// TODO Refactor: https://gitlab.com/gitlab-org/gitlab/-/issues/329274
// here we are using "window.location.search" as a temporary store
......@@ -45,14 +44,10 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
const groupByParam = new URLSearchParams(window.location.search).get('group_by');
this.store.path = `${path.substr(1)}${groupByParam ? `&group_by=${groupByParam}` : ''}`;
if (vuexstore.getters.shouldUseGraphQL) {
updateHistory({
url: `?${path.substr(1)}${groupByParam ? `&group_by=${groupByParam}` : ''}`,
});
vuexstore.dispatch('performSearch');
} else if (this.updateUrl) {
boardsStore.updateFiltersUrl();
}
updateHistory({
url: `?${path.substr(1)}${groupByParam ? `&group_by=${groupByParam}` : ''}`,
});
vuexstore.dispatch('performSearch');
}
removeTokens() {
......
......@@ -2,7 +2,7 @@ import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import PortalVue from 'portal-vue';
import Vue from 'vue';
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/list';
......@@ -78,10 +78,7 @@ export default () => {
initBoardsFilteredSearch(apolloProvider);
}
if (!gon?.features?.graphqlBoardLists) {
boardsStore.create();
boardsStore.setTimeTrackingLimitToHours($boardApp.dataset.timeTrackingLimitToHours);
}
boardsStore.create();
// eslint-disable-next-line @gitlab/no-runtime-template-compiler
issueBoardsApp = new Vue({
......@@ -133,7 +130,6 @@ export default () => {
};
},
computed: {
...mapGetters(['shouldUseGraphQL']),
detailIssueVisible() {
return Object.keys(this.detailIssue.issue).length;
},
......@@ -174,14 +170,12 @@ export default () => {
eventHub.$on('newDetailIssue', this.updateDetailIssue);
eventHub.$on('clearDetailIssue', this.clearDetailIssue);
sidebarEventHub.$on('toggleSubscription', this.toggleSubscription);
eventHub.$on('initialBoardLoad', this.initialBoardLoad);
},
beforeDestroy() {
eventHub.$off('updateTokens', this.updateTokens);
eventHub.$off('newDetailIssue', this.updateDetailIssue);
eventHub.$off('clearDetailIssue', this.clearDetailIssue);
sidebarEventHub.$off('toggleSubscription', this.toggleSubscription);
eventHub.$off('initialBoardLoad', this.initialBoardLoad);
},
mounted() {
if (!gon?.features?.issueBoardsFilteredSearch) {
......@@ -196,28 +190,9 @@ export default () => {
this.performSearch();
boardsStore.disabled = this.disabled;
if (!this.shouldUseGraphQL) {
this.initialBoardLoad();
}
},
methods: {
...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() {
this.filterManager.updateTokens();
},
......
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { mapGetters } from 'vuex';
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 createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
Vue.use(VueApollo);
......@@ -25,9 +22,7 @@ export default (params = {}) => {
el: boardsSwitcherElement,
components: {
BoardsSelector,
BoardsSelectorDeprecated,
},
mixins: [glFeatureFlagMixin()],
apolloProvider,
store,
provide: {
......@@ -52,16 +47,8 @@ export default (params = {}) => {
return { boardsSelectorProps };
},
computed: {
...mapGetters(['shouldUseGraphQL', 'isEpicBoard']),
},
render(createElement) {
if (this.shouldUseGraphQL || this.isEpicBoard) {
return createElement(BoardsSelector, {
props: this.boardsSelectorProps,
});
}
return createElement(BoardsSelectorDeprecated, {
return createElement(BoardsSelector, {
props: this.boardsSelectorProps,
});
},
......
......@@ -82,11 +82,8 @@ export default {
'setFilters',
convertObjectPropsToCamelCase(queryToObject(window.location.search, { gatherArrays: true })),
);
if (gon.features.graphqlBoardLists) {
dispatch('fetchLists');
dispatch('resetIssues');
}
dispatch('fetchLists');
dispatch('resetIssues');
},
fetchLists: ({ commit, state, dispatch }) => {
......@@ -182,7 +179,7 @@ export default {
});
},
fetchLabels: ({ state, commit, getters }, searchTerm) => {
fetchLabels: ({ state, commit }, searchTerm) => {
const { fullPath, boardType } = state;
const variables = {
......@@ -200,14 +197,7 @@ export default {
variables,
})
.then(({ data }) => {
let labels = data[boardType]?.labels.nodes;
if (!getters.shouldUseGraphQL && !getters.isEpicBoard) {
labels = labels.map((label) => ({
...label,
id: getIdFromGraphQLId(label.id),
}));
}
const labels = data[boardType]?.labels.nodes;
commit(types.RECEIVE_LABELS_SUCCESS, labels);
return labels;
......
......@@ -51,8 +51,4 @@ export default {
isEpicBoard: () => {
return false;
},
shouldUseGraphQL: () => {
return gon?.features?.graphqlBoardLists;
},
};
......@@ -7,7 +7,6 @@ class Groups::BoardsController < Groups::ApplicationController
before_action :assign_endpoint_vars
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(:board_multi_select, group, default_enabled: :yaml)
push_frontend_feature_flag(:swimlanes_buffered_rendering, group, default_enabled: :yaml)
......
......@@ -8,7 +8,6 @@ class Projects::BoardsController < Projects::ApplicationController
before_action :assign_endpoint_vars
before_action do
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(:board_multi_select, project, default_enabled: :yaml)
push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml)
......
......@@ -48,10 +48,10 @@ module Types
description: 'Size (in bytes) of the blob, or the blob target if stored externally.'
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,
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?,
description: "Whether the blob's content is stored externally (for instance, in LFS)."
......@@ -69,7 +69,7 @@ module Types
description: 'Web path to replace the blob content.'
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,
description: 'Blob content simple viewer.',
......
......@@ -6,14 +6,14 @@ module Types
authorize :read_statistics
field :storage_size, GraphQL::FLOAT_TYPE, null: false, description: 'The total storage in bytes.'
field :repository_size, GraphQL::FLOAT_TYPE, null: false, description: 'The Git repository size in bytes.'
field :lfs_objects_size, GraphQL::FLOAT_TYPE, null: false, description: 'The LFS objects size in bytes.'
field :build_artifacts_size, GraphQL::FLOAT_TYPE, null: false, description: 'The CI artifacts size in bytes.'
field :packages_size, GraphQL::FLOAT_TYPE, null: false, description: 'The packages size in bytes.'
field :wiki_size, GraphQL::FLOAT_TYPE, null: false, description: 'The wiki size in bytes.'
field :snippets_size, GraphQL::FLOAT_TYPE, null: false, description: 'The snippets size in bytes.'
field :pipeline_artifacts_size, GraphQL::FLOAT_TYPE, null: false, description: 'The CI pipeline artifacts size in bytes.'
field :uploads_size, GraphQL::FLOAT_TYPE, null: false, description: 'The uploads size in bytes.'
field :storage_size, GraphQL::FLOAT_TYPE, null: false, description: 'Total storage 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: 'LFS objects 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: 'Packages 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: 'Snippets 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: 'Uploads size in bytes.'
end
end
......@@ -22,7 +22,7 @@ module Types
null: false
field :project, Types::ProjectType,
description: 'The project the snippet is associated with.',
description: 'Project the snippet is associated with.',
null: true,
authorize: :read_project
......@@ -30,7 +30,7 @@ module Types
# when the admin setting restricted visibility
# level is set to public
field :author, Types::UserType,
description: 'The owner of the snippet.',
description: 'Owner of the snippet.',
null: true
field :file_name, GraphQL::Types::String,
......
......@@ -17,7 +17,7 @@ module Types
null: true
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
field :raw_path, GraphQL::Types::String,
......
......@@ -3,9 +3,9 @@
module Types
module Snippets
class VisibilityScopesEnum < BaseEnum
value 'private', description: 'The 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 'public', description: 'The snippet can be accessed without any authentication.', value: 'are_public'
value 'private', description: 'Snippet is visible only to the snippet creator.', value: 'are_private'
value 'internal', description: 'Snippet is visible for any logged in user except external users.', value: 'are_internal'
value 'public', description: 'Snippet can be accessed without any authentication.', value: 'are_public'
end
end
end
......@@ -19,7 +19,7 @@ module Types
field :locked_by_user, Types::UserType,
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,
null: true,
......@@ -28,7 +28,7 @@ module Types
field :latest_version, Types::Terraform::StateVersionType,
complexity: 3,
null: true,
description: 'The latest version of the Terraform state.'
description: 'Latest version of the Terraform state.'
field :created_at, Types::TimeType,
null: false,
......
......@@ -15,7 +15,7 @@ module Types
field :created_by_user, Types::UserType,
null: true,
description: 'The user that created this version.'
description: 'User that created this version.'
field :download_path, GraphQL::Types::String,
null: true,
......@@ -23,7 +23,7 @@ module Types
field :job, Types::Ci::JobType,
null: true,
description: 'The job that created this version.'
description: 'Job that created this version.'
field :serial, GraphQL::Types::Int,
null: true,
......
......@@ -14,31 +14,31 @@ module Types
field :time_spent,
GraphQL::Types::Int,
null: false,
description: 'The time spent displayed in seconds.'
description: 'Time spent displayed in seconds.'
field :user,
Types::UserType,
null: false,
description: 'The user that logged the time.'
description: 'User that logged the time.'
field :issue,
Types::IssueType,
null: true,
description: 'The issue that logged time was added to.'
description: 'Issue that logged time was added to.'
field :merge_request,
Types::MergeRequestType,
null: true,
description: 'The merge request that logged time was added to.'
description: 'Merge request that logged time was added to.'
field :note,
Types::Notes::NoteType,
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,
null: true,
description: 'The summary of how the time was spent.'
description: 'Summary of how the time was spent.'
def user
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.user_id).find
......
......@@ -2,7 +2,7 @@
module Types
class TodoStateEnum < BaseEnum
value 'pending', description: "The state of the todo is pending."
value 'done', description: "The state of the todo is done."
value 'pending', description: "State of the todo is pending."
value 'done', description: "State of the todo is done."
end
end
......@@ -2,11 +2,11 @@
module Types
class TodoTargetEnum < BaseEnum
value 'COMMIT', value: 'Commit', description: 'A Commit.'
value 'ISSUE', value: 'Issue', description: 'An Issue.'
value 'MERGEREQUEST', value: 'MergeRequest', description: 'A MergeRequest.'
value 'DESIGN', value: 'DesignManagement::Design', description: 'A Design.'
value 'ALERT', value: 'AlertManagement::Alert', description: 'An Alert.'
value 'COMMIT', value: 'Commit', description: 'Commit.'
value 'ISSUE', value: 'Issue', description: 'Issue.'
value 'MERGEREQUEST', value: 'MergeRequest', description: 'Merge request.'
value 'DESIGN', value: 'DesignManagement::Design', description: 'Design.'
value 'ALERT', value: 'AlertManagement::Alert', description: 'Alert.'
end
end
......
......@@ -14,7 +14,7 @@ module Types
null: false
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,
authorize: :read_project
......@@ -24,7 +24,7 @@ module Types
authorize: :read_group
field :author, Types::UserType,
description: 'The author of this to-do item.',
description: 'Author of this to-do item.',
null: false
field :action, Types::TodoActionEnum,
......
......@@ -72,7 +72,7 @@ module Types
field :location,
type: ::GraphQL::Types::String,
null: true,
description: 'The location of the user.'
description: 'Location of the user.'
field :project_memberships,
type: Types::ProjectMemberType.connection_type,
null: true,
......
......@@ -28,7 +28,7 @@ module Types
field :review_state,
::Types::MergeRequestReviewStateEnum,
null: true,
description: 'The state of the review by this user.'
description: 'State of the review by this user.'
field :reviewed,
type: ::GraphQL::Types::Boolean,
......
......@@ -5,8 +5,8 @@ module Types
graphql_name 'UserState'
description 'Possible states of a user'
value 'active', 'The 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 'deactivated', 'The user is no longer active and is unable to use the system.', value: 'deactivated'
value 'active', 'User is active and is able to use the system.', value: 'active'
value 'blocked', 'User has been blocked and is prevented from using the system.', value: 'blocked'
value 'deactivated', 'User is no longer active and is unable to use the system.', value: 'deactivated'
end
end
- board = local_assigns.fetch(:board, nil)
- group = local_assigns.fetch(:group, false)
- @no_breadcrumb_container = true
- @no_container = true
- @content_wrapper_class = "#{@content_wrapper_class} gl-relative"
......@@ -20,6 +19,4 @@
= 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-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-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.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/285074) in GitLab 13.9.
> - [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
> - Recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-graphql-based-issue-boards). **(FREE SELF)**
> - [Feature flag `graphql_board_lists`](https://gitlab.com/gitlab-org/gitlab/-/issues/248908) removed in GitLab 14.3
There can be
[risks when disabling released features](../../administration/feature_flags.md#risks-when-disabling-released-features).
......@@ -673,24 +672,6 @@ A few things to remember:
by default. If you have more than 20 issues, start scrolling down and the next
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)**
The iteration list is under development but ready for production use. It is
......
......@@ -11,8 +11,6 @@ import {
import { mapActions, mapGetters, mapState } from 'vuex';
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';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
......@@ -87,7 +85,7 @@ export default {
'assignees',
'assigneesLoading',
]),
...mapGetters(['getListByTypeId', 'shouldUseGraphQL', 'isEpicBoard']),
...mapGetters(['getListByTypeId', 'isEpicBoard']),
info() {
return listTypeInfo[this.columnType] || {};
......@@ -132,16 +130,10 @@ export default {
return false;
}
if (this.shouldUseGraphQL || this.isEpicBoard) {
const key = `${this.columnType}Id`;
return this.getListByTypeId({
[key]: this.selectedId,
});
}
return boardsStore.state.lists.find(
(list) => list[this.columnType]?.id === getIdFromGraphQLId(this.selectedId),
);
const key = `${this.columnType}Id`;
return this.getListByTypeId({
[key]: this.selectedId,
});
},
loading() {
......@@ -187,17 +179,6 @@ export default {
'fetchIterations',
'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() {
if (!this.selectedItem) {
return;
......@@ -207,45 +188,12 @@ export default {
if (this.columnForSelected) {
const listId = this.columnForSelected.id;
this.highlight(listId);
this.highlightList(listId);
return;
}
if (this.shouldUseGraphQL || this.isEpicBoard) {
// eslint-disable-next-line @gitlab/require-i18n-strings
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);
}
// eslint-disable-next-line @gitlab/require-i18n-strings
this.createList({ [`${this.columnType}Id`]: this.selectedId });
},
filterItems(searchTerm) {
......
<script>
import { GlButton, GlFormInput } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
import boardsStoreEE from 'ee/boards/stores/boards_store_ee';
import { inactiveId } from '~/boards/constants';
import { mapActions, mapState } from 'vuex';
import { __, n__ } from '~/locale';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
......@@ -36,7 +34,6 @@ export default {
},
computed: {
...mapState(['activeId']),
...mapGetters(['shouldUseGraphQL']),
wipLimitTypeText() {
return n__('%d issue', '%d issues', this.maxIssueCount);
},
......@@ -76,11 +73,6 @@ export default {
const id = this.activeId;
this.updateListWipLimit({ maxIssueCount: wipLimit, listId: id })
.then(() => {
if (!this.shouldUseGraphQL) {
boardsStoreEE.setMaxIssueCountOnList(id, wipLimit);
}
})
.catch(() => {
this.unsetActiveId();
this.setError({
......@@ -96,11 +88,6 @@ export default {
},
clearWipLimit() {
this.updateListWipLimit({ maxIssueCount: 0, listId: this.activeId })
.then(() => {
if (!this.shouldUseGraphQL) {
boardsStoreEE.setMaxIssueCountOnList(this.activeId, inactiveId);
}
})
.catch(() => {
this.unsetActiveId();
this.setError({
......
......@@ -65,19 +65,12 @@ export default Vue.extend({
return list;
},
handleItemClick(item) {
if (
this.vuexStore.getters.shouldUseGraphQL &&
!this.vuexStore.getters.getListByTitle(item.title)
) {
if (!this.vuexStore.getters.getListByTitle(item.title)) {
if (this.listType === 'milestones') {
this.vuexStore.dispatch('createList', { milestoneId: fullMilestoneId(item.id) });
} else if (this.listType === 'assignees') {
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 {
filterVariables,
} from '~/boards/boards_util';
import { BoardType } from '~/boards/constants';
import eventHub from '~/boards/eventhub';
import groupBoardMembersQuery from '~/boards/graphql/group_board_members.query.graphql';
import listsIssuesQuery from '~/boards/graphql/lists_issues.query.graphql';
import projectBoardMembersQuery from '~/boards/graphql/project_board_members.query.graphql';
import actionsCE, { gqlClient } from '~/boards/stores/actions';
import boardsStore from '~/boards/stores/boards_store';
import * as typesCE from '~/boards/stores/mutation_types';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import axios from '~/lib/utils/axios_utils';
import { historyPushState, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { mergeUrlParams, removeParams, queryToObject } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
......@@ -39,7 +36,6 @@ import projectBoardIterationsQuery from '../graphql/project_board_iterations.que
import updateBoardEpicUserPreferencesMutation from '../graphql/update_board_epic_user_preferences.mutation.graphql';
import updateEpicLabelsMutation from '../graphql/update_epic_labels.mutation.graphql';
import boardsStoreEE from './boards_store_ee';
import * as types from './mutation_types';
const fetchAndFormatListIssues = (state, extraVariables) => {
......@@ -121,13 +117,11 @@ export default {
if (getters.isSwimlanesOn) {
dispatch('resetEpics');
dispatch('resetIssues');
dispatch('fetchEpicsSwimlanes');
dispatch('fetchLists');
} else if (gon.features.graphqlBoardLists || getters.isEpicBoard) {
dispatch('fetchLists');
dispatch('resetIssues');
}
dispatch('fetchLists');
dispatch('resetIssues');
},
fetchEpicsSwimlanes({ state, commit }, { fetchNext = false } = {}) {
......@@ -221,38 +215,30 @@ export default {
commit(types.SET_SHOW_LABELS, val);
},
updateListWipLimit({ commit, getters, dispatch }, { maxIssueCount, listId }) {
if (getters.shouldUseGraphQL) {
return gqlClient
.mutate({
mutation: listUpdateLimitMetricsMutation,
variables: {
input: {
listId,
maxIssueCount,
},
updateListWipLimit({ commit, dispatch }, { maxIssueCount, listId }) {
return gqlClient
.mutate({
mutation: listUpdateLimitMetricsMutation,
variables: {
input: {
listId,
maxIssueCount,
},
})
.then(({ data }) => {
if (data?.boardListUpdateLimitMetrics?.errors.length) {
throw new Error();
}
},
})
.then(({ data }) => {
if (data?.boardListUpdateLimitMetrics?.errors.length) {
throw new Error();
}
commit(types.UPDATE_LIST_SUCCESS, {
listId,
list: data.boardListUpdateLimitMetrics?.list,
});
})
.catch(() => {
dispatch('handleUpdateListFailure');
commit(types.UPDATE_LIST_SUCCESS, {
listId,
list: data.boardListUpdateLimitMetrics?.list,
});
}
return axios.put(`${boardsStoreEE.store.state.endpoints.listsEndpoint}/${listId}`, {
list: {
max_issue_count: maxIssueCount,
},
});
})
.catch(() => {
dispatch('handleUpdateListFailure');
});
},
fetchItemsForList: (
......@@ -316,10 +302,6 @@ export default {
);
dispatch('fetchEpicsSwimlanes');
dispatch('fetchLists');
} else if (!gon.features.graphqlBoardLists) {
historyPushState(removeParams(['group_by']), window.location.href, true);
boardsStore.create();
eventHub.$emit('initialBoardLoad');
} else {
historyPushState(removeParams(['group_by']), window.location.href, true);
}
......
......@@ -57,9 +57,6 @@ class BoardsStoreEE {
this.store.scopedLabels = {
enabled: parseBoolean(scopedLabels),
};
if (!gon.features.graphqlBoardLists) {
this.initBoardFilters();
}
}
};
......
......@@ -54,8 +54,4 @@ export default {
isEpicBoard: (state) => {
return state.issuableType === issuableTypes.epic;
},
shouldUseGraphQL: (state) => {
return state.isShowingEpicsSwimlanes || gon?.features?.graphqlBoardLists;
},
};
......@@ -10,11 +10,11 @@ module EE
prepended do
argument :payload_example, ::Types::JsonStringType,
required: false,
description: 'The example of an alert payload.'
description: 'Example of an alert payload.'
argument :payload_attribute_mappings, [::Types::AlertManagement::PayloadAlertFieldInputType],
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
......
......@@ -10,11 +10,11 @@ module EE
prepended do
argument :payload_example, ::Types::JsonStringType,
required: false,
description: 'The example of an alert payload.'
description: 'Example of an alert payload.'
argument :payload_attribute_mappings, [::Types::AlertManagement::PayloadAlertFieldInputType],
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
......
......@@ -12,7 +12,7 @@ module EE
argument :epic_id, ::Types::GlobalIDType[::Epic],
required: false,
description: 'The ID of an epic to associate the issue with.'
description: 'ID of an epic to associate the issue with.'
end
override :resolve
......
......@@ -12,7 +12,7 @@ module EE
argument :epic_id, ::Types::GlobalIDType[::Epic],
required: false,
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
def resolve(**args)
......
......@@ -9,7 +9,7 @@ module EE
prepended do
field :payload_example, ::Types::JsonStringType,
null: true,
description: 'The example of an alert payload.'
description: 'Example of an alert payload.'
field :payload_attribute_mappings, [::Types::AlertManagement::PayloadAlertMappingFieldType],
null: true,
......
......@@ -17,7 +17,7 @@ module EE
field :assignee, ::Types::UserType, null: true,
description: 'Assignee in the list.'
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,
description: 'Total weight of all issues in the list.'
......
......@@ -7,7 +7,7 @@ module EE
prepended do
field :assignee, type: ::Types::UserType, null: true,
description: 'The board assignee.'
description: 'Board assignee.'
field :epics, ::Types::Boards::BoardEpicType.connection_type, null: true,
description: 'Epics associated with board issues.',
......@@ -18,10 +18,10 @@ module EE
description: 'Labels of the board.'
field :milestone, type: ::Types::MilestoneType, null: true,
description: 'The board milestone.'
description: 'Board milestone.'
field :iteration, type: ::Types::IterationType, null: true,
description: 'The board iteration.'
description: 'Board iteration.'
field :weight, type: GraphQL::Types::Int, null: true,
description: 'Weight of the board.'
......
......@@ -84,13 +84,13 @@ module EE
field :billable_members_count, ::GraphQL::Types::Int,
null: true,
description: 'The number of billable users in the group.'
description: 'Number of billable users in the group.'
field :dora,
::Types::DoraType,
null: true,
method: :itself,
description: "The group's DORA metrics."
description: "Group's DORA metrics."
end
end
end
......
......@@ -87,7 +87,7 @@ module EE
field :dast_scanner_profiles,
::Types::DastScannerProfileType.connection_type,
null: true,
description: 'The DAST scanner profiles associated with the project.'
description: 'DAST scanner profiles associated with the project.'
field :dast_site_validations,
::Types::DastSiteValidationType.connection_type,
......@@ -165,7 +165,7 @@ module EE
field :push_rules,
::Types::PushRulesType,
null: true,
description: "The project's push rules settings.",
description: "Project's push rules settings.",
method: :push_rule
field :path_locks,
......@@ -192,7 +192,7 @@ module EE
::Types::DoraType,
null: true,
method: :itself,
description: "The project's DORA metrics."
description: "Project's DORA metrics."
end
def api_fuzzing_ci_configuration
......
......@@ -26,7 +26,7 @@ module EE
description: "Find a vulnerability." do
argument :id, ::Types::GlobalIDType[::Vulnerability],
required: true,
description: 'The Global ID of the Vulnerability.'
description: 'Global ID of the Vulnerability.'
end
field :vulnerabilities_count_by_day,
......@@ -64,7 +64,7 @@ module EE
field :ci_minutes_usage, ::Types::Ci::Minutes::NamespaceMonthlyUsageType.connection_type,
null: true,
description: 'The monthly CI minutes usage data for the current user.'
description: 'Monthly CI minutes usage data for the current user.'
end
def vulnerability(id:)
......
......@@ -61,13 +61,4 @@ RSpec.describe 'Multiple Issue Boards', :js do
it_behaves_like 'multiple issue boards'
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
# 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 @@
require 'spec_helper'
RSpec.describe 'User adds milestone lists', :js do
using RSpec::Parameterized::TableSyntax
let_it_be(:group) { create(:group, :nested) }
let_it_be(:project) { create(:project, :public, namespace: group) }
let_it_be(:group_board) { create(:board, group: group) }
......@@ -25,11 +23,8 @@ RSpec.describe 'User adds milestone lists', :js do
group.add_owner(user)
end
where(:board_type, :graphql_board_lists_enabled) do
:project | true
:project | false
:group | true
:group | false
where(:board_type) do
[[:project], [:group]]
end
with_them do
......@@ -43,10 +38,6 @@ RSpec.describe 'User adds milestone lists', :js do
set_cookie('sidebar_collapsed', 'true')
stub_feature_flags(
graphql_board_lists: graphql_board_lists_enabled
)
if board_type == :project
visit project_board_path(project, project_board)
elsif board_type == :group
......
......@@ -145,14 +145,6 @@ RSpec.describe 'Filter issues by iteration', :js do
let(:issue_title_selector) { '.board-card .board-card-title' }
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
context 'group board' do
......
......@@ -15,7 +15,6 @@ Vue.use(Vuex);
describe('BoardAddNewColumn', () => {
let wrapper;
let shouldUseGraphQL;
const selectItem = (id) => {
wrapper.findByTestId('selectItem').vm.$emit('change', id);
......@@ -59,7 +58,6 @@ describe('BoardAddNewColumn', () => {
...actions,
},
getters: {
shouldUseGraphQL: () => shouldUseGraphQL,
getListByTypeId: () => getListByTypeId,
isEpicBoard: () => false,
},
......@@ -103,10 +101,6 @@ describe('BoardAddNewColumn', () => {
radio.vm.$emit('change', type);
};
beforeEach(() => {
shouldUseGraphQL = true;
});
it('clicking cancel hides the form', () => {
const setAddColumnFormVisibility = jest.fn();
mountComponent({
......
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 BoardContentSidebar from '~/boards/components/board_content_sidebar.vue';
import { createStore } from '~/boards/stores';
......@@ -35,20 +36,22 @@ describe('ee/BoardContent', () => {
});
describe.each`
licenseEnabled | state | result
${true} | ${{ isShowingEpicsSwimlanes: true }} | ${true}
${true} | ${{ isShowingEpicsSwimlanes: false }} | ${false}
${false} | ${{ isShowingEpicsSwimlanes: true }} | ${false}
${false} | ${{ isShowingEpicsSwimlanes: false }} | ${false}
`('with licenseEnabled=$licenseEnabled and state=$state', ({ licenseEnabled, state, result }) => {
state | resultIssue | resultEpic
${{ isShowingEpicsSwimlanes: true, issuableType: 'issue' }} | ${true} | ${false}
${{ isShowingEpicsSwimlanes: false, issuableType: 'issue' }} | ${true} | ${false}
${{ isShowingEpicsSwimlanes: false, issuableType: 'epic' }} | ${false} | ${true}
`('with state=$state', ({ state, resultIssue, resultEpic }) => {
beforeEach(() => {
gon.licensed_features.swimlanes = licenseEnabled;
Object.assign(store.state, state);
createComponent();
});
it(`renders BoardContentSidebar = ${result}`, () => {
expect(wrapper.find(BoardContentSidebar).exists()).toBe(result);
it(`renders BoardContentSidebar = ${resultIssue}`, () => {
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';
jest.mock('~/flash');
describe('BoardListSelector', () => {
global.gon.features = {
...(global.gon.features || {}),
graphqlBoardLists: false,
};
const dummyEndpoint = `${TEST_HOST}/users.json`;
const createComponent = () =>
......@@ -93,19 +88,7 @@ describe('BoardListSelector', () => {
});
describe('handleItemClick', () => {
it('graphqlBoardLists FF off - 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;
it('creates new list in a store instance', () => {
jest.spyOn(vm.vuexStore, 'dispatch').mockReturnValue({});
const assignee = mockAssigneesList[0];
......
import '~/boards/models/list';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
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 { mockLabelList, mockMilestoneList } from 'jest/boards/mock_data';
import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue';
import { LIST } from '~/boards/constants';
import boardsStore from '~/boards/stores/boards_store';
import getters from '~/boards/stores/getters';
const localVue = createLocalVue();
localVue.use(Vuex);
Vue.use(Vuex);
describe('ee/BoardSettingsSidebar', () => {
let wrapper;
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;
const boardLists = {
[list.id]: { ...list, maxIssueCount: 0 },
};
const store = new Vuex.Store({
state: { sidebarType: LIST, activeId: listId },
state: { sidebarType: LIST, activeId: list.id, boardLists },
getters,
actions: storeActions,
});
wrapper = shallowMount(BoardSettingsSidebar, {
store,
localVue,
provide: {
glFeatures: {
wipLimits: isWipLimitsOn,
......@@ -47,41 +41,18 @@ describe('ee/BoardSettingsSidebar', () => {
});
};
beforeEach(() => {
mock = new MockAdapter(axios);
boardsStore.create();
});
afterEach(() => {
mock.restore();
wrapper.destroy();
});
it('confirms we render BoardSettingsSidebarWipLimit', () => {
boardsStore.addList({
id: listId,
label: { title: labelTitle, color: labelColor },
max_issue_count: 0,
list_type: 'label',
});
createComponent({}, true);
createComponent({ list: mockLabelList, isWipLimitsOn: true });
expect(wrapper.find(BoardSettingsWipLimit).exists()).toBe(true);
});
it('confirms we render BoardSettingsListTypes', () => {
boardsStore.addList({
id: 1,
milestone: {
webUrl: 'https://gitlab.com/h5bp/html5-boilerplate/-/milestones/1',
title: 'Backlog',
},
max_issue_count: 1,
list_type: 'milestone',
});
createComponent();
createComponent({ list: mockMilestoneList });
expect(wrapper.find(BoardSettingsListTypes).exists()).toBe(true);
});
......
import '~/boards/models/list';
import { GlFormInput } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { shallowMount } from '@vue/test-utils';
import { noop } from 'lodash';
import Vue from 'vue';
import Vuex from 'vuex';
import BoardSettingsWipLimit from 'ee_component/boards/components/board_settings_wip_limit.vue';
import waitForPromises from 'helpers/wait_for_promises';
import boardsStore from '~/boards/stores/boards_store';
import { mockLabelList } from 'jest/boards/mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
Vue.use(Vuex);
describe('BoardSettingsWipLimit', () => {
let wrapper;
let storeActions;
const labelTitle = 'test';
const labelColor = '#FFFF';
const listId = 1;
const listId = mockLabelList.id;
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 findRemoveWipLimit = () => wrapper.find('.js-remove-limit');
const findWipLimit = () => wrapper.find('.js-wip-limit');
......@@ -46,13 +31,11 @@ describe('BoardSettingsWipLimit', () => {
const store = new Vuex.Store({
state: vuexState,
actions: storeActions,
getters: { shouldUseGraphQL: () => false },
});
wrapper = shallowMount(BoardSettingsWipLimit, {
propsData: props,
store,
localVue,
data() {
return localState;
},
......@@ -69,13 +52,7 @@ describe('BoardSettingsWipLimit', () => {
}
};
beforeEach(() => {
boardsStore.create();
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
jest.restoreAllMocks();
wrapper.destroy();
});
......@@ -83,25 +60,28 @@ describe('BoardSettingsWipLimit', () => {
describe('when activeList is present', () => {
describe('when activeListWipLimit is 0', () => {
it('renders "None" in the block', () => {
createComponent({ vuexState: { activeId: listId } });
createComponent({
vuexState: {
activeId: listId,
},
});
expect(findWipLimit().text()).toBe('None');
});
});
describe('when activeId is greater than 0', () => {
afterEach(() => {
boardsStore.removeList(listId);
});
describe('when activeListWipLimit is greater than 0', () => {
it.each`
num | expected
${1} | ${'1 issue'}
${11} | ${'11 issues'}
`('it renders $num', ({ num, expected }) => {
addList(4);
createComponent({ vuexState: { activeId: num }, props: { maxIssueCount: num } });
createComponent({
vuexState: {
activeId: listId,
},
props: { maxIssueCount: num },
});
expect(findWipLimit().text()).toBe(expected);
});
......@@ -112,7 +92,9 @@ describe('BoardSettingsWipLimit', () => {
const maxIssueCount = 4;
beforeEach(async () => {
createComponent({
vuexState: { activeId: listId },
vuexState: {
activeId: listId,
},
actions: { updateListWipLimit: noop },
props: { maxIssueCount },
});
......@@ -137,15 +119,14 @@ describe('BoardSettingsWipLimit', () => {
describe('remove limit', () => {
describe('when wipLimit is set', () => {
const spy = jest.fn().mockResolvedValue({
data: { boardListUpdateLimitMetrics: { list: { maxIssueCount: 0 } } },
});
beforeEach(() => {
addList(4);
const spy = jest.fn().mockResolvedValue({
config: { data: JSON.stringify({ list: { max_issue_count: 0 } }) },
});
createComponent({
vuexState: { activeId: listId },
vuexState: {
activeId: listId,
},
actions: { updateListWipLimit: spy },
props: { maxIssueCount: 4 },
});
......@@ -156,18 +137,22 @@ describe('BoardSettingsWipLimit', () => {
findRemoveWipLimit().vm.$emit('click');
await waitForPromises();
await wrapper.vm.$nextTick();
// WARNING: https://gitlab.com/gitlab-org/gitlab/-/issues/232573
expect(boardsStore.findList('id', listId).maxIssueCount).toBe(0);
expect(spy).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ listId, maxIssueCount: 0 }),
);
});
});
describe('when wipLimit is not set', () => {
beforeEach(() => {
addList();
createComponent({ vuexState: { activeId: listId }, actions: { updateListWipLimit: noop } });
createComponent({
vuexState: { activeId: listId },
actions: { updateListWipLimit: noop },
});
});
it('does not render the remove limit button', () => {
......@@ -177,14 +162,6 @@ describe('BoardSettingsWipLimit', () => {
});
describe('when edit is true', () => {
beforeEach(() => {
addList(2);
});
afterEach(() => {
boardsStore.removeList(listId);
});
describe.each`
blurMethod
${'enter'}
......@@ -193,10 +170,12 @@ describe('BoardSettingsWipLimit', () => {
describe(`when blur is triggered by ${blurMethod}`, () => {
it('calls updateListWipLimit', async () => {
const spy = jest.fn().mockResolvedValue({
config: { data: JSON.stringify({ list: { max_issue_count: '4' } }) },
data: { boardListUpdateLimitMetrics: { list: { maxIssueCount: 4 } } },
});
createComponent({
vuexState: { activeId: listId },
vuexState: {
activeId: listId,
},
actions: { updateListWipLimit: spy },
localState: { edit: true, currentWipLimit },
});
......@@ -209,10 +188,12 @@ describe('BoardSettingsWipLimit', () => {
});
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({});
createComponent({
vuexState: { activeId: listId },
vuexState: {
activeId: listId,
},
actions: { updateListWipLimit: spy },
localState: { edit: true, currentWipLimit: 2 },
props: { maxIssueCount: 2 },
......@@ -227,7 +208,7 @@ describe('BoardSettingsWipLimit', () => {
});
describe('when currentWipLimit is null', () => {
it('doesnt call updateListWipLimit', async () => {
it('does not call updateListWipLimit', async () => {
const spy = jest.fn().mockResolvedValue({});
createComponent({
vuexState: { activeId: listId },
......@@ -249,9 +230,12 @@ describe('BoardSettingsWipLimit', () => {
beforeEach(() => {
const spy = jest.fn().mockResolvedValue({});
createComponent({
vuexState: { activeId: listId },
vuexState: {
activeId: listId,
},
actions: { updateListWipLimit: spy },
localState: { edit: true, currentWipLimit: maxIssueCount },
props: { maxIssueCount },
});
triggerBlur(blurMethod);
......@@ -260,14 +244,7 @@ describe('BoardSettingsWipLimit', () => {
});
it('sets activeWipLimit to new maxIssueCount value', () => {
/*
* 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);
expect(findWipLimit().text()).toContain(maxIssueCount);
});
it('toggles GlFormInput on blur', () => {
......
......@@ -112,12 +112,7 @@ describe('setFilters', () => {
});
describe('performSearch', () => {
it('should dispatch setFilters action', (done) => {
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 } };
it('should dispatch setFilters, fetchLists and resetIssues action', async () => {
const getters = { isSwimlanesOn: false };
await testAction({
......@@ -139,9 +134,9 @@ describe('performSearch', () => {
expectedActions: [
{ type: 'setFilters', payload: {} },
{ type: 'resetEpics' },
{ type: 'resetIssues' },
{ type: 'fetchEpicsSwimlanes' },
{ type: 'fetchLists' },
{ type: 'resetIssues' },
],
});
});
......@@ -464,7 +459,6 @@ describe('setShowLabels', () => {
describe('updateListWipLimit', () => {
let storeMock;
const getters = { shouldUseGraphQL: false };
beforeEach(() => {
storeMock = {
......@@ -483,26 +477,9 @@ describe('updateListWipLimit', () => {
jest.restoreAllMocks();
});
it('axios - should call the correct url', () => {
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', () => {
it('commit UPDATE_LIST_SUCCESS mutation on success', () => {
const maxIssueCount = 0;
const activeId = 1;
getters.shouldUseGraphQL = true;
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
data: {
boardListUpdateLimitMetrics: {
......@@ -517,7 +494,7 @@ describe('updateListWipLimit', () => {
return testAction(
actions.updateListWipLimit,
{ maxIssueCount, listId: activeId },
{ isShowingEpicsSwimlanes: true, ...getters },
{ isShowingEpicsSwimlanes: true },
[
{
type: types.UPDATE_LIST_SUCCESS,
......@@ -533,16 +510,15 @@ describe('updateListWipLimit', () => {
);
});
it('graphql - dispatch handleUpdateListFailure on failure', () => {
it('dispatch handleUpdateListFailure on failure', () => {
const maxIssueCount = 0;
const activeId = 1;
getters.shouldUseGraphQL = true;
jest.spyOn(gqlClient, 'mutate').mockResolvedValue(Promise.reject());
return testAction(
actions.updateListWipLimit,
{ maxIssueCount, listId: activeId },
{ isShowingEpicsSwimlanes: true, ...getters },
{ isShowingEpicsSwimlanes: true },
[],
[{ type: 'handleUpdateListFailure' }],
);
......
......@@ -3626,9 +3626,6 @@ msgstr ""
msgid "An error occurred while fetching terraform reports."
msgstr ""
msgid "An error occurred while fetching the board lists. Please try again."
msgstr ""
msgid "An error occurred while fetching the job log."
msgstr ""
......@@ -5513,9 +5510,6 @@ msgid_plural "Boards|Blocked by %{blockedByCount} %{issuableType}s"
msgstr[0] ""
msgstr[1] ""
msgid "Boards|Board"
msgstr ""
msgid "Boards|Collapse"
msgstr ""
......
......@@ -43,12 +43,12 @@ RSpec.describe 'Multi Select Issue', :js do
# Multi select drag&drop support is temporarily disabled
# 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)
end
context 'with lists' do
xcontext 'with lists' do
let(:label1) { create(:label, project: project, name: 'Label 1', description: 'Test') }
let(:label2) { create(:label, project: project, name: 'Label 2', description: 'Test') }
let!(:list1) { create(:list, board: board, label: label1, position: 0) }
......
......@@ -5,8 +5,9 @@ require 'spec_helper'
RSpec.describe 'Project issue boards sidebar labels', :js do
include BoardHelpers
let_it_be(:group) { create(:group, :public) }
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(:bug) { create(:label, project: project, name: 'Bug') }
let_it_be(:regression) { create(:label, project: project, name: 'Regression') }
......
......@@ -3,8 +3,6 @@
require 'spec_helper'
RSpec.describe 'User adds lists', :js do
using RSpec::Parameterized::TableSyntax
let_it_be(:group) { create(:group, :nested) }
let_it_be(:project) { create(:project, :public, namespace: group) }
let_it_be(:group_board) { create(:board, group: group) }
......@@ -27,11 +25,8 @@ RSpec.describe 'User adds lists', :js do
group.add_owner(user)
end
where(:board_type, :graphql_board_lists_enabled) do
:project | true
:project | false
:group | true
:group | false
where(:board_type) do
[[:project], [:group]]
end
with_them do
......@@ -40,10 +35,6 @@ RSpec.describe 'User adds lists', :js do
set_cookie('sidebar_collapsed', 'true')
stub_feature_flags(
graphql_board_lists: graphql_board_lists_enabled
)
if board_type == :project
visit project_board_path(project, project_board)
elsif board_type == :group
......@@ -53,14 +44,12 @@ RSpec.describe 'User adds lists', :js do
wait_for_all_requests
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'
wait_for_all_requests
select_label(group_label)
wait_for_all_requests
expect(page).to have_selector('.board', text: group_label.title)
expect(find('.board:nth-child(2) .board-card')).to have_content(issue.title)
end
......
......@@ -42,30 +42,4 @@ RSpec.describe 'Group Issue Boards', :js do
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
......@@ -214,44 +214,6 @@ RSpec.describe 'Labels Hierarchy', :js do
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
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) }
......
......@@ -48,7 +48,6 @@ describe('Board card layout', () => {
...actions,
},
getters: {
shouldUseGraphQL: () => true,
getListByLabelId: () => getListByLabelId,
},
state: {
......
......@@ -8,7 +8,6 @@ import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import BoardCardDeprecated from '~/boards/components/board_card_deprecated.vue';
import issueCardInner from '~/boards/components/issue_card_inner_deprecated.vue';
import eventHub from '~/boards/eventhub';
import store from '~/boards/stores';
import boardsStore from '~/boards/stores/boards_store';
import axios from '~/lib/utils/axios_utils';
......@@ -165,46 +164,9 @@ describe('BoardCard', () => {
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', () => {
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', () => {
jest.spyOn(sidebarEventHub, '$emit').mockImplementation(() => {});
const [issue] = list.issues;
......
......@@ -111,18 +111,14 @@ describe('Board card layout', () => {
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();
createStore({
actions: {
setActiveId,
},
});
mountComponent({
provide: {
glFeatures: { graphqlBoardLists: true },
},
});
mountComponent();
wrapper.trigger('mouseup');
await wrapper.vm.$nextTick();
......
......@@ -5,7 +5,7 @@ import Draggable from 'vuedraggable';
import Vuex from 'vuex';
import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
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 { mockLists, mockListsWithModel } from '../mock_data';
......@@ -33,12 +33,7 @@ describe('BoardContent', () => {
});
};
const createComponent = ({
state,
props = {},
graphqlBoardListsEnabled = false,
canAdminList = true,
} = {}) => {
const createComponent = ({ state, props = {}, canAdminList = true } = {}) => {
const store = createStore({
...defaultState,
...state,
......@@ -51,63 +46,41 @@ describe('BoardContent', () => {
},
provide: {
canAdminList,
glFeatures: { graphqlBoardLists: graphqlBoardListsEnabled },
},
store,
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('renders a BoardColumnDeprecated component per list', () => {
createComponent();
expect(wrapper.findAllComponents(BoardColumnDeprecated)).toHaveLength(
mockListsWithModel.length,
);
it('renders a BoardColumn component per list', () => {
expect(wrapper.findAllComponents(BoardColumn)).toHaveLength(mockListsWithModel.length);
});
it('does not display EpicsSwimlanes component', () => {
createComponent();
expect(wrapper.find(EpicsSwimlanes).exists()).toBe(false);
expect(wrapper.find(GlAlert).exists()).toBe(false);
});
describe('graphqlBoardLists feature flag enabled', () => {
describe('can admin list', () => {
beforeEach(() => {
createComponent({ graphqlBoardListsEnabled: true });
gon.features = {
graphqlBoardLists: true,
};
createComponent({ canAdminList: true });
});
describe('can admin list', () => {
beforeEach(() => {
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);
});
it('renders draggable component', () => {
expect(wrapper.find(Draggable).exists()).toBe(true);
});
});
describe('graphqlBoardLists feature flag disabled', () => {
describe('can not admin list', () => {
beforeEach(() => {
createComponent({ graphqlBoardListsEnabled: false });
createComponent({ canAdminList: false });
});
it('does not render draggable component', () => {
......
......@@ -2,7 +2,6 @@ import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import BoardFilteredSearch from '~/boards/components/board_filtered_search.vue';
import { createStore } from '~/boards/stores';
import * as urlUtility from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import FilteredSearchBarRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
......@@ -44,6 +43,12 @@ describe('BoardFilteredSearch', () => {
];
const createComponent = ({ initialFilterParams = {} } = {}) => {
store = new Vuex.Store({
actions: {
performSearch: jest.fn(),
},
});
wrapper = shallowMount(BoardFilteredSearch, {
provide: { initialFilterParams, fullPath: '' },
store,
......@@ -55,22 +60,15 @@ describe('BoardFilteredSearch', () => {
const findFilteredSearch = () => wrapper.findComponent(FilteredSearchBarRoot);
beforeEach(() => {
// this needed for actions call for performSearch
window.gon = { features: {} };
});
afterEach(() => {
wrapper.destroy();
});
describe('default', () => {
beforeEach(() => {
store = createStore();
createComponent();
jest.spyOn(store, 'dispatch');
createComponent();
});
it('renders FilteredSearch', () => {
......@@ -103,8 +101,6 @@ describe('BoardFilteredSearch', () => {
describe('when searching', () => {
beforeEach(() => {
store = createStore();
createComponent();
jest.spyOn(wrapper.vm, 'performSearch').mockImplementation();
......@@ -133,11 +129,9 @@ describe('BoardFilteredSearch', () => {
describe('when url params are already set', () => {
beforeEach(() => {
store = createStore();
createComponent({ initialFilterParams: { authorUsername: 'root', labelName: ['label'] } });
jest.spyOn(store, 'dispatch');
createComponent({ initialFilterParams: { authorUsername: 'root', labelName: ['label'] } });
});
it('passes the correct props to FilterSearchBar', () => {
......
import '~/boards/models/list';
import { GlDrawer, GlLabel } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { shallowMount } from '@vue/test-utils';
import { MountingPortal } from 'portal-vue';
import Vue from 'vue';
import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue';
import { inactiveId, LIST } from '~/boards/constants';
import { createStore } from '~/boards/stores';
import boardsStore from '~/boards/stores/boards_store';
import actions from '~/boards/stores/actions';
import getters from '~/boards/stores/getters';
import mutations from '~/boards/stores/mutations';
import sidebarEventHub from '~/sidebar/event_hub';
import { mockLabelList } from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
Vue.use(Vuex);
describe('BoardSettingsSidebar', () => {
let wrapper;
let mock;
let store;
const labelTitle = 'test';
const labelColor = '#FFFF';
const listId = 1;
const labelTitle = mockLabelList.label.title;
const labelColor = mockLabelList.label.color;
const listId = mockLabelList.id;
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(
shallowMount(BoardSettingsSidebar, {
store,
localVue,
provide: {
canAdminList,
},
......@@ -40,16 +51,10 @@ describe('BoardSettingsSidebar', () => {
const findLabel = () => wrapper.find(GlLabel);
const findDrawer = () => wrapper.find(GlDrawer);
beforeEach(() => {
store = createStore();
store.state.activeId = inactiveId;
store.state.sidebarType = LIST;
boardsStore.create();
});
afterEach(() => {
jest.restoreAllMocks();
wrapper.destroy();
wrapper = null;
});
it('finds a MountingPortal component', () => {
......@@ -100,86 +105,40 @@ describe('BoardSettingsSidebar', () => {
});
describe('when activeId is greater than zero', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
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();
it('renders GlDrawer with open true', () => {
createComponent({ list: mockLabelList, activeId: listId });
expect(findDrawer().props('open')).toBe(true);
});
});
describe('when activeId is in boardsStore', () => {
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();
});
describe('when activeId is in state', () => {
it('renders label title', () => {
createComponent({ list: mockLabelList, activeId: listId });
expect(findLabel().props('title')).toBe(labelTitle);
});
it('renders label background color', () => {
createComponent({ list: mockLabelList, activeId: listId });
expect(findLabel().props('backgroundColor')).toBe(labelColor);
});
});
describe('when activeId is not in boardsStore', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
boardsStore.addList({ id: listId, label: { title: labelTitle, color: labelColor } });
store.state.activeId = inactiveId;
createComponent();
});
afterEach(() => {
mock.restore();
});
describe('when activeId is not in state', () => {
it('does not render GlLabel', () => {
createComponent({ list: mockLabelList });
expect(findLabel().exists()).toBe(false);
});
});
});
describe('when sidebarType is not List', () => {
beforeEach(() => {
store.state.sidebarType = '';
createComponent();
});
it('does not render GlDrawer', () => {
createComponent({ sidebarType: '' });
expect(findDrawer().exists()).toBe(false);
});
});
......@@ -191,20 +150,9 @@ describe('BoardSettingsSidebar', () => {
});
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', () => {
createComponent({ canAdminList: true, activeId: listId, list: mockLabelList });
expect(findRemoveButton().exists()).toBe(true);
});
});
......
......@@ -336,6 +336,22 @@ export const mockLabelList = {
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 mockListsById = keyBy(mockLists, 'id');
......
......@@ -107,12 +107,7 @@ describe('setFilters', () => {
});
describe('performSearch', () => {
it('should dispatch setFilters 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 } };
it('should dispatch setFilters, fetchLists and resetIssues action', (done) => {
testAction(
actions.performSearch,
{},
......@@ -496,12 +491,9 @@ describe('fetchLabels', () => {
jest.spyOn(gqlClient, 'query').mockResolvedValue(queryResponse);
const commit = jest.fn();
const getters = {
shouldUseGraphQL: () => true,
};
const state = { boardType: 'group' };
await actions.fetchLabels({ getters, state, commit });
await actions.fetchLabels({ state, commit });
expect(commit).toHaveBeenCalledWith(types.RECEIVE_LABELS_SUCCESS, labels);
});
......@@ -954,7 +946,7 @@ describe('moveIssue', () => {
});
describe('moveIssueCard and undoMoveIssueCard', () => {
describe('card should move without clonning', () => {
describe('card should move without cloning', () => {
let state;
let params;
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