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
......@@ -56,7 +56,7 @@ Returns [`CiConfig`](#ciconfig).
### `Query.ciMinutesUsage`
The monthly CI minutes usage data for the current user.
Monthly CI minutes usage data for the current user.
Returns [`CiMinutesNamespaceMonthlyUsageConnection`](#ciminutesnamespacemonthlyusageconnection).
......@@ -504,7 +504,7 @@ Returns [`Vulnerability`](#vulnerability).
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="queryvulnerabilityid"></a>`id` | [`VulnerabilityID!`](#vulnerabilityid) | The Global ID of the Vulnerability. |
| <a id="queryvulnerabilityid"></a>`id` | [`VulnerabilityID!`](#vulnerabilityid) | Global ID of the Vulnerability. |
## `Mutation` type
......@@ -1261,7 +1261,7 @@ Input type: `CreateIssueInput`
| <a id="mutationcreateissuedescription"></a>`description` | [`String`](#string) | Description of the issue. |
| <a id="mutationcreateissuediscussiontoresolve"></a>`discussionToResolve` | [`String`](#string) | ID of a discussion to resolve. Also pass `merge_request_to_resolve_discussions_of`. |
| <a id="mutationcreateissueduedate"></a>`dueDate` | [`ISO8601Date`](#iso8601date) | Due date of the issue. |
| <a id="mutationcreateissueepicid"></a>`epicId` | [`EpicID`](#epicid) | The ID of an epic to associate the issue with. |
| <a id="mutationcreateissueepicid"></a>`epicId` | [`EpicID`](#epicid) | ID of an epic to associate the issue with. |
| <a id="mutationcreateissuehealthstatus"></a>`healthStatus` | [`HealthStatus`](#healthstatus) | The desired health status. |
| <a id="mutationcreateissueiid"></a>`iid` | [`Int`](#int) | IID (internal ID) of a project issue. Only admins and project owners can modify. |
| <a id="mutationcreateissuelabelids"></a>`labelIds` | [`[LabelID!]`](#labelid) | IDs of labels to be added to the issue. |
......@@ -2413,8 +2413,8 @@ Input type: `HttpIntegrationCreateInput`
| <a id="mutationhttpintegrationcreateactive"></a>`active` | [`Boolean!`](#boolean) | Whether the integration is receiving alerts. |
| <a id="mutationhttpintegrationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationhttpintegrationcreatename"></a>`name` | [`String!`](#string) | Name of the integration. |
| <a id="mutationhttpintegrationcreatepayloadattributemappings"></a>`payloadAttributeMappings` | [`[AlertManagementPayloadAlertFieldInput!]`](#alertmanagementpayloadalertfieldinput) | The custom mapping of GitLab alert attributes to fields from the payload_example. |
| <a id="mutationhttpintegrationcreatepayloadexample"></a>`payloadExample` | [`JsonString`](#jsonstring) | The example of an alert payload. |
| <a id="mutationhttpintegrationcreatepayloadattributemappings"></a>`payloadAttributeMappings` | [`[AlertManagementPayloadAlertFieldInput!]`](#alertmanagementpayloadalertfieldinput) | Custom mapping of GitLab alert attributes to fields from the payload example. |
| <a id="mutationhttpintegrationcreatepayloadexample"></a>`payloadExample` | [`JsonString`](#jsonstring) | Example of an alert payload. |
| <a id="mutationhttpintegrationcreateprojectpath"></a>`projectPath` | [`ID!`](#id) | Project to create the integration in. |
#### Fields
......@@ -2475,8 +2475,8 @@ Input type: `HttpIntegrationUpdateInput`
| <a id="mutationhttpintegrationupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationhttpintegrationupdateid"></a>`id` | [`AlertManagementHttpIntegrationID!`](#alertmanagementhttpintegrationid) | ID of the integration to mutate. |
| <a id="mutationhttpintegrationupdatename"></a>`name` | [`String`](#string) | Name of the integration. |
| <a id="mutationhttpintegrationupdatepayloadattributemappings"></a>`payloadAttributeMappings` | [`[AlertManagementPayloadAlertFieldInput!]`](#alertmanagementpayloadalertfieldinput) | The custom mapping of GitLab alert attributes to fields from the payload_example. |
| <a id="mutationhttpintegrationupdatepayloadexample"></a>`payloadExample` | [`JsonString`](#jsonstring) | The example of an alert payload. |
| <a id="mutationhttpintegrationupdatepayloadattributemappings"></a>`payloadAttributeMappings` | [`[AlertManagementPayloadAlertFieldInput!]`](#alertmanagementpayloadalertfieldinput) | Custom mapping of GitLab alert attributes to fields from the payload example. |
| <a id="mutationhttpintegrationupdatepayloadexample"></a>`payloadExample` | [`JsonString`](#jsonstring) | Example of an alert payload. |
#### Fields
......@@ -4268,7 +4268,7 @@ Input type: `UpdateIssueInput`
| <a id="mutationupdateissueconfidential"></a>`confidential` | [`Boolean`](#boolean) | Indicates the issue is confidential. |
| <a id="mutationupdateissuedescription"></a>`description` | [`String`](#string) | Description of the issue. |
| <a id="mutationupdateissueduedate"></a>`dueDate` | [`ISO8601Date`](#iso8601date) | Due date of the issue. |
| <a id="mutationupdateissueepicid"></a>`epicId` | [`EpicID`](#epicid) | The ID of the parent epic. NULL when removing the association. |
| <a id="mutationupdateissueepicid"></a>`epicId` | [`EpicID`](#epicid) | ID of the parent epic. NULL when removing the association. |
| <a id="mutationupdateissuehealthstatus"></a>`healthStatus` | [`HealthStatus`](#healthstatus) | The desired health status. |
| <a id="mutationupdateissueiid"></a>`iid` | [`String!`](#string) | IID of the issue to mutate. |
| <a id="mutationupdateissuelabelids"></a>`labelIds` | [`[ID!]`](#id) | IDs of labels to be set. Replaces existing issue labels. |
......@@ -7563,7 +7563,7 @@ An endpoint and credentials used to accept alerts for a project.
| <a id="alertmanagementhttpintegrationname"></a>`name` | [`String`](#string) | Name of the integration. |
| <a id="alertmanagementhttpintegrationpayloadalertfields"></a>`payloadAlertFields` | [`[AlertManagementPayloadAlertField!]`](#alertmanagementpayloadalertfield) | Extract alert fields from payload example for custom mapping. |
| <a id="alertmanagementhttpintegrationpayloadattributemappings"></a>`payloadAttributeMappings` | [`[AlertManagementPayloadAlertMappingField!]`](#alertmanagementpayloadalertmappingfield) | The custom mapping of GitLab alert attributes to fields from the payload_example. |
| <a id="alertmanagementhttpintegrationpayloadexample"></a>`payloadExample` | [`JsonString`](#jsonstring) | The example of an alert payload. |
| <a id="alertmanagementhttpintegrationpayloadexample"></a>`payloadExample` | [`JsonString`](#jsonstring) | Example of an alert payload. |
| <a id="alertmanagementhttpintegrationtoken"></a>`token` | [`String`](#string) | Token used to authenticate alert notification requests. |
| <a id="alertmanagementhttpintegrationtype"></a>`type` | [`AlertManagementIntegrationType!`](#alertmanagementintegrationtype) | Type of integration. |
| <a id="alertmanagementhttpintegrationurl"></a>`url` | [`String`](#string) | Endpoint which accepts alert notifications. |
......@@ -7719,14 +7719,14 @@ Represents a project or group issue board.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="boardassignee"></a>`assignee` | [`UserCore`](#usercore) | The board assignee. |
| <a id="boardassignee"></a>`assignee` | [`UserCore`](#usercore) | Board assignee. |
| <a id="boardcreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of when the board was created. |
| <a id="boardhidebackloglist"></a>`hideBacklogList` | [`Boolean`](#boolean) | Whether or not backlog list is hidden. |
| <a id="boardhideclosedlist"></a>`hideClosedList` | [`Boolean`](#boolean) | Whether or not closed list is hidden. |
| <a id="boardid"></a>`id` | [`ID!`](#id) | ID (global ID) of the board. |
| <a id="boarditeration"></a>`iteration` | [`Iteration`](#iteration) | The board iteration. |
| <a id="boarditeration"></a>`iteration` | [`Iteration`](#iteration) | Board iteration. |
| <a id="boardlabels"></a>`labels` | [`LabelConnection`](#labelconnection) | Labels of the board. (see [Connections](#connections)) |
| <a id="boardmilestone"></a>`milestone` | [`Milestone`](#milestone) | The board milestone. |
| <a id="boardmilestone"></a>`milestone` | [`Milestone`](#milestone) | Board milestone. |
| <a id="boardname"></a>`name` | [`String`](#string) | Name of the board. |
| <a id="boardupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the board was last updated. |
| <a id="boardwebpath"></a>`webPath` | [`String!`](#string) | Web path of the board. |
......@@ -7943,7 +7943,7 @@ Represents a list for an issue board.
| <a id="boardlistissuescount"></a>`issuesCount` | [`Int`](#int) | Count of issues in the list. |
| <a id="boardlistiteration"></a>`iteration` | [`Iteration`](#iteration) | Iteration of the list. |
| <a id="boardlistlabel"></a>`label` | [`Label`](#label) | Label of the list. |
| <a id="boardlistlimitmetric"></a>`limitMetric` | [`ListLimitMetric`](#listlimitmetric) | The current limit metric for the list. |
| <a id="boardlistlimitmetric"></a>`limitMetric` | [`ListLimitMetric`](#listlimitmetric) | Current limit metric for the list. |
| <a id="boardlistlisttype"></a>`listType` | [`String!`](#string) | Type of the list. |
| <a id="boardlistmaxissuecount"></a>`maxIssueCount` | [`Int`](#int) | Maximum number of issues in the list. |
| <a id="boardlistmaxissueweight"></a>`maxIssueWeight` | [`Int`](#int) | Maximum weight of issues in the list. |
......@@ -9676,7 +9676,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupadditionalpurchasedstoragesize"></a>`additionalPurchasedStorageSize` | [`Float`](#float) | Additional storage purchased for the root namespace in bytes. |
| <a id="groupautodevopsenabled"></a>`autoDevopsEnabled` | [`Boolean`](#boolean) | Indicates whether Auto DevOps is enabled for all projects within this group. |
| <a id="groupavatarurl"></a>`avatarUrl` | [`String`](#string) | Avatar URL of the group. |
| <a id="groupbillablememberscount"></a>`billableMembersCount` | [`Int`](#int) | The number of billable users in the group. |
| <a id="groupbillablememberscount"></a>`billableMembersCount` | [`Int`](#int) | Number of billable users in the group. |
| <a id="groupcontainerrepositoriescount"></a>`containerRepositoriesCount` | [`Int!`](#int) | Number of container repositories in the group. |
| <a id="groupcontainslockedprojects"></a>`containsLockedProjects` | [`Boolean!`](#boolean) | Includes at least one project where the repository size exceeds the limit. |
| <a id="groupcustomemoji"></a>`customEmoji` | [`CustomEmojiConnection`](#customemojiconnection) | Custom emoji within this namespace. Available only when feature flag `custom_emoji` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) |
......@@ -9688,7 +9688,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupdependencyproxytotalsize"></a>`dependencyProxyTotalSize` | [`String!`](#string) | Total size of the dependency proxy cached images. |
| <a id="groupdescription"></a>`description` | [`String`](#string) | Description of the namespace. |
| <a id="groupdescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. |
| <a id="groupdora"></a>`dora` | [`Dora`](#dora) | The group's DORA metrics. |
| <a id="groupdora"></a>`dora` | [`Dora`](#dora) | Group's DORA metrics. |
| <a id="groupemailsdisabled"></a>`emailsDisabled` | [`Boolean`](#boolean) | Indicates if a group has email notifications disabled. |
| <a id="groupepicboards"></a>`epicBoards` | [`EpicBoardConnection`](#epicboardconnection) | Find epic boards. (see [Connections](#connections)) |
| <a id="groupepicsenabled"></a>`epicsEnabled` | [`Boolean`](#boolean) | Indicates if Epics are enabled for namespace. |
......@@ -10921,7 +10921,7 @@ A user assigned to a merge request.
| <a id="mergerequestassigneegroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. |
| <a id="mergerequestassigneegroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
| <a id="mergerequestassigneeid"></a>`id` | [`ID!`](#id) | ID of the user. |
| <a id="mergerequestassigneelocation"></a>`location` | [`String`](#string) | The location of the user. |
| <a id="mergerequestassigneelocation"></a>`location` | [`String`](#string) | Location of the user. |
| <a id="mergerequestassigneemergerequestinteraction"></a>`mergeRequestInteraction` | [`UserMergeRequestInteraction`](#usermergerequestinteraction) | Details of this user's interactions with the merge request. |
| <a id="mergerequestassigneename"></a>`name` | [`String!`](#string) | Human-readable name of the user. |
| <a id="mergerequestassigneenamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
......@@ -11167,7 +11167,7 @@ A user assigned to a merge request as a reviewer.
| <a id="mergerequestreviewergroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. |
| <a id="mergerequestreviewergroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
| <a id="mergerequestreviewerid"></a>`id` | [`ID!`](#id) | ID of the user. |
| <a id="mergerequestreviewerlocation"></a>`location` | [`String`](#string) | The location of the user. |
| <a id="mergerequestreviewerlocation"></a>`location` | [`String`](#string) | Location of the user. |
| <a id="mergerequestreviewermergerequestinteraction"></a>`mergeRequestInteraction` | [`UserMergeRequestInteraction`](#usermergerequestinteraction) | Details of this user's interactions with the merge request. |
| <a id="mergerequestreviewername"></a>`name` | [`String!`](#string) | Human-readable name of the user. |
| <a id="mergerequestreviewernamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
......@@ -11998,11 +11998,11 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="projectcontainerrepositoriescount"></a>`containerRepositoriesCount` | [`Int!`](#int) | Number of container repositories in the project. |
| <a id="projectcreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of the project creation. |
| <a id="projectdastprofiles"></a>`dastProfiles` | [`DastProfileConnection`](#dastprofileconnection) | DAST Profiles associated with the project. (see [Connections](#connections)) |
| <a id="projectdastscannerprofiles"></a>`dastScannerProfiles` | [`DastScannerProfileConnection`](#dastscannerprofileconnection) | The DAST scanner profiles associated with the project. (see [Connections](#connections)) |
| <a id="projectdastscannerprofiles"></a>`dastScannerProfiles` | [`DastScannerProfileConnection`](#dastscannerprofileconnection) | DAST scanner profiles associated with the project. (see [Connections](#connections)) |
| <a id="projectdastsiteprofiles"></a>`dastSiteProfiles` | [`DastSiteProfileConnection`](#dastsiteprofileconnection) | DAST Site Profiles associated with the project. (see [Connections](#connections)) |
| <a id="projectdescription"></a>`description` | [`String`](#string) | Short description of the project. |
| <a id="projectdescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. |
| <a id="projectdora"></a>`dora` | [`Dora`](#dora) | The project's DORA metrics. |
| <a id="projectdora"></a>`dora` | [`Dora`](#dora) | Project's DORA metrics. |
| <a id="projectforkscount"></a>`forksCount` | [`Int!`](#int) | Number of times the project has been forked. |
| <a id="projectfullpath"></a>`fullPath` | [`ID!`](#id) | Full path of the project. |
| <a id="projectgrafanaintegration"></a>`grafanaIntegration` | [`GrafanaIntegration`](#grafanaintegration) | Grafana integration details for the project. |
......@@ -12030,7 +12030,7 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="projectpipelineanalytics"></a>`pipelineAnalytics` | [`PipelineAnalytics`](#pipelineanalytics) | Pipeline analytics. |
| <a id="projectprintingmergerequestlinkenabled"></a>`printingMergeRequestLinkEnabled` | [`Boolean`](#boolean) | Indicates if a link to create or view a merge request should display after a push to Git repositories of the project from the command line. |
| <a id="projectpublicjobs"></a>`publicJobs` | [`Boolean`](#boolean) | Indicates if there is public access to pipelines and job details of the project, including output logs and artifacts. |
| <a id="projectpushrules"></a>`pushRules` | [`PushRules`](#pushrules) | The project's push rules settings. |
| <a id="projectpushrules"></a>`pushRules` | [`PushRules`](#pushrules) | Project's push rules settings. |
| <a id="projectremovesourcebranchaftermerge"></a>`removeSourceBranchAfterMerge` | [`Boolean`](#boolean) | Indicates if `Delete source branch` option should be enabled by default for all new merge requests of the project. |
| <a id="projectrepository"></a>`repository` | [`Repository`](#repository) | Git repository of the project. |
| <a id="projectrepositorysizeexcess"></a>`repositorySizeExcess` | [`Float`](#float) | Size of repository that exceeds the limit in bytes. |
......@@ -13197,7 +13197,7 @@ Returns [`Tree`](#tree).
| <a id="repositoryblobcanmodifyblob"></a>`canModifyBlob` | [`Boolean`](#boolean) | Whether the current user can modify the blob. |
| <a id="repositoryblobeditblobpath"></a>`editBlobPath` | [`String`](#string) | Web path to edit the blob in the old-style editor. |
| <a id="repositoryblobexternalstorageurl"></a>`externalStorageUrl` | [`String`](#string) | Web path to download the raw blob via external storage, if enabled. |
| <a id="repositoryblobfiletype"></a>`fileType` | [`String`](#string) | The expected format of the blob based on the extension. |
| <a id="repositoryblobfiletype"></a>`fileType` | [`String`](#string) | Expected format of the blob based on the extension. |
| <a id="repositoryblobforkandeditpath"></a>`forkAndEditPath` | [`String`](#string) | Web path to edit this blob using a forked project. |
| <a id="repositoryblobid"></a>`id` | [`ID!`](#id) | ID of the blob. |
| <a id="repositoryblobideeditpath"></a>`ideEditPath` | [`String`](#string) | Web path to edit this blob in the Web IDE. |
......@@ -13208,10 +13208,10 @@ Returns [`Tree`](#tree).
| <a id="repositorybloboid"></a>`oid` | [`String!`](#string) | OID of the blob. |
| <a id="repositoryblobpath"></a>`path` | [`String!`](#string) | Path of the blob. |
| <a id="repositoryblobplaindata"></a>`plainData` | [`String`](#string) | Blob plain highlighted data. |
| <a id="repositoryblobrawblob"></a>`rawBlob` | [`String`](#string) | The raw content of the blob. |
| <a id="repositoryblobrawblob"></a>`rawBlob` | [`String`](#string) | Raw content of the blob. |
| <a id="repositoryblobrawpath"></a>`rawPath` | [`String`](#string) | Web path to download the raw blob. |
| <a id="repositoryblobrawsize"></a>`rawSize` | [`Int`](#int) | Size (in bytes) of the blob, or the blob target if stored externally. |
| <a id="repositoryblobrawtextblob"></a>`rawTextBlob` | [`String`](#string) | The raw content of the blob, if the blob is text data. |
| <a id="repositoryblobrawtextblob"></a>`rawTextBlob` | [`String`](#string) | Raw content of the blob, if the blob is text data. |
| <a id="repositoryblobreplacepath"></a>`replacePath` | [`String`](#string) | Web path to replace the blob content. |
| <a id="repositoryblobrichviewer"></a>`richViewer` | [`BlobViewer`](#blobviewer) | Blob content rich viewer. |
| <a id="repositoryblobsimpleviewer"></a>`simpleViewer` | [`BlobViewer!`](#blobviewer) | Blob content simple viewer. |
......@@ -13291,15 +13291,15 @@ Counts of requirements by their state.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="rootstoragestatisticsbuildartifactssize"></a>`buildArtifactsSize` | [`Float!`](#float) | The CI artifacts size in bytes. |
| <a id="rootstoragestatisticslfsobjectssize"></a>`lfsObjectsSize` | [`Float!`](#float) | The LFS objects size in bytes. |
| <a id="rootstoragestatisticspackagessize"></a>`packagesSize` | [`Float!`](#float) | The packages size in bytes. |
| <a id="rootstoragestatisticspipelineartifactssize"></a>`pipelineArtifactsSize` | [`Float!`](#float) | The CI pipeline artifacts size in bytes. |
| <a id="rootstoragestatisticsrepositorysize"></a>`repositorySize` | [`Float!`](#float) | The Git repository size in bytes. |
| <a id="rootstoragestatisticssnippetssize"></a>`snippetsSize` | [`Float!`](#float) | The snippets size in bytes. |
| <a id="rootstoragestatisticsstoragesize"></a>`storageSize` | [`Float!`](#float) | The total storage in bytes. |
| <a id="rootstoragestatisticsuploadssize"></a>`uploadsSize` | [`Float!`](#float) | The uploads size in bytes. |
| <a id="rootstoragestatisticswikisize"></a>`wikiSize` | [`Float!`](#float) | The wiki size in bytes. |
| <a id="rootstoragestatisticsbuildartifactssize"></a>`buildArtifactsSize` | [`Float!`](#float) | CI artifacts size in bytes. |
| <a id="rootstoragestatisticslfsobjectssize"></a>`lfsObjectsSize` | [`Float!`](#float) | LFS objects size in bytes. |
| <a id="rootstoragestatisticspackagessize"></a>`packagesSize` | [`Float!`](#float) | Packages size in bytes. |
| <a id="rootstoragestatisticspipelineartifactssize"></a>`pipelineArtifactsSize` | [`Float!`](#float) | CI pipeline artifacts size in bytes. |
| <a id="rootstoragestatisticsrepositorysize"></a>`repositorySize` | [`Float!`](#float) | Git repository size in bytes. |
| <a id="rootstoragestatisticssnippetssize"></a>`snippetsSize` | [`Float!`](#float) | Snippets size in bytes. |
| <a id="rootstoragestatisticsstoragesize"></a>`storageSize` | [`Float!`](#float) | Total storage in bytes. |
| <a id="rootstoragestatisticsuploadssize"></a>`uploadsSize` | [`Float!`](#float) | Uploads size in bytes. |
| <a id="rootstoragestatisticswikisize"></a>`wikiSize` | [`Float!`](#float) | Wiki size in bytes. |
### `RunnerArchitecture`
......@@ -13644,7 +13644,7 @@ Represents a snippet entry.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="snippetauthor"></a>`author` | [`UserCore`](#usercore) | The owner of the snippet. |
| <a id="snippetauthor"></a>`author` | [`UserCore`](#usercore) | Owner of the snippet. |
| <a id="snippetcreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp this snippet was created. |
| <a id="snippetdescription"></a>`description` | [`String`](#string) | Description of the snippet. |
| <a id="snippetdescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. |
......@@ -13653,7 +13653,7 @@ Represents a snippet entry.
| <a id="snippethttpurltorepo"></a>`httpUrlToRepo` | [`String`](#string) | HTTP URL to the snippet repository. |
| <a id="snippetid"></a>`id` | [`SnippetID!`](#snippetid) | ID of the snippet. |
| <a id="snippetnotes"></a>`notes` | [`NoteConnection!`](#noteconnection) | All notes on this noteable. (see [Connections](#connections)) |
| <a id="snippetproject"></a>`project` | [`Project`](#project) | The project the snippet is associated with. |
| <a id="snippetproject"></a>`project` | [`Project`](#project) | Project the snippet is associated with. |
| <a id="snippetrawurl"></a>`rawUrl` | [`String!`](#string) | Raw URL of the snippet. |
| <a id="snippetsshurltorepo"></a>`sshUrlToRepo` | [`String`](#string) | SSH URL to the snippet repository. |
| <a id="snippettitle"></a>`title` | [`String!`](#string) | Title of the snippet. |
......@@ -13695,7 +13695,7 @@ Represents the snippet blob.
| <a id="snippetblobpath"></a>`path` | [`String`](#string) | Blob path. |
| <a id="snippetblobplaindata"></a>`plainData` | [`String`](#string) | Blob plain highlighted data. |
| <a id="snippetblobrawpath"></a>`rawPath` | [`String!`](#string) | Blob raw content endpoint path. |
| <a id="snippetblobrawplaindata"></a>`rawPlainData` | [`String`](#string) | The raw content of the blob, if the blob is text data. |
| <a id="snippetblobrawplaindata"></a>`rawPlainData` | [`String`](#string) | Raw content of the blob, if the blob is text data. |
| <a id="snippetblobrenderedastext"></a>`renderedAsText` | [`Boolean!`](#boolean) | Shows whether the blob is rendered as text. |
| <a id="snippetblobrichdata"></a>`richData` | [`String`](#string) | Blob highlighted data. |
| <a id="snippetblobrichviewer"></a>`richViewer` | [`SnippetBlobViewer`](#snippetblobviewer) | Blob content rich viewer. |
......@@ -13795,9 +13795,9 @@ Completion status of tasks.
| ---- | ---- | ----------- |
| <a id="terraformstatecreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp the Terraform state was created. |
| <a id="terraformstateid"></a>`id` | [`ID!`](#id) | ID of the Terraform state. |
| <a id="terraformstatelatestversion"></a>`latestVersion` | [`TerraformStateVersion`](#terraformstateversion) | The latest version of the Terraform state. |
| <a id="terraformstatelatestversion"></a>`latestVersion` | [`TerraformStateVersion`](#terraformstateversion) | Latest version of the Terraform state. |
| <a id="terraformstatelockedat"></a>`lockedAt` | [`Time`](#time) | Timestamp the Terraform state was locked. |
| <a id="terraformstatelockedbyuser"></a>`lockedByUser` | [`UserCore`](#usercore) | The user currently holding a lock on the Terraform state. |
| <a id="terraformstatelockedbyuser"></a>`lockedByUser` | [`UserCore`](#usercore) | User currently holding a lock on the Terraform state. |
| <a id="terraformstatename"></a>`name` | [`String!`](#string) | Name of the Terraform state. |
| <a id="terraformstateupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp the Terraform state was updated. |
......@@ -13808,10 +13808,10 @@ Completion status of tasks.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="terraformstateversioncreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp the version was created. |
| <a id="terraformstateversioncreatedbyuser"></a>`createdByUser` | [`UserCore`](#usercore) | The user that created this version. |
| <a id="terraformstateversioncreatedbyuser"></a>`createdByUser` | [`UserCore`](#usercore) | User that created this version. |
| <a id="terraformstateversiondownloadpath"></a>`downloadPath` | [`String`](#string) | URL for downloading the version's JSON file. |
| <a id="terraformstateversionid"></a>`id` | [`ID!`](#id) | ID of the Terraform state version. |
| <a id="terraformstateversionjob"></a>`job` | [`CiJob`](#cijob) | The job that created this version. |
| <a id="terraformstateversionjob"></a>`job` | [`CiJob`](#cijob) | Job that created this version. |
| <a id="terraformstateversionserial"></a>`serial` | [`Int`](#int) | Serial number of the version. |
| <a id="terraformstateversionupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp the version was updated. |
......@@ -13966,13 +13966,13 @@ Represents a historically accurate report about the timebox.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="timelogissue"></a>`issue` | [`Issue`](#issue) | The issue that logged time was added to. |
| <a id="timelogmergerequest"></a>`mergeRequest` | [`MergeRequest`](#mergerequest) | The merge request that logged time was added to. |
| <a id="timelognote"></a>`note` | [`Note`](#note) | The note where the quick action to add the logged time was executed. |
| <a id="timelogissue"></a>`issue` | [`Issue`](#issue) | Issue that logged time was added to. |
| <a id="timelogmergerequest"></a>`mergeRequest` | [`MergeRequest`](#mergerequest) | Merge request that logged time was added to. |
| <a id="timelognote"></a>`note` | [`Note`](#note) | Note where the quick action was executed to add the logged time. |
| <a id="timelogspentat"></a>`spentAt` | [`Time`](#time) | Timestamp of when the time tracked was spent at. |
| <a id="timelogsummary"></a>`summary` | [`String`](#string) | The summary of how the time was spent. |
| <a id="timelogtimespent"></a>`timeSpent` | [`Int!`](#int) | The time spent displayed in seconds. |
| <a id="timeloguser"></a>`user` | [`UserCore!`](#usercore) | The user that logged the time. |
| <a id="timelogsummary"></a>`summary` | [`String`](#string) | Summary of how the time was spent. |
| <a id="timelogtimespent"></a>`timeSpent` | [`Int!`](#int) | Time spent displayed in seconds. |
| <a id="timeloguser"></a>`user` | [`UserCore!`](#usercore) | User that logged the time. |
### `Todo`
......@@ -13983,12 +13983,12 @@ Representing a to-do entry.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="todoaction"></a>`action` | [`TodoActionEnum!`](#todoactionenum) | Action of the to-do item. |
| <a id="todoauthor"></a>`author` | [`UserCore!`](#usercore) | The author of this to-do item. |
| <a id="todoauthor"></a>`author` | [`UserCore!`](#usercore) | Author of this to-do item. |
| <a id="todobody"></a>`body` | [`String!`](#string) | Body of the to-do item. |
| <a id="todocreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp this to-do item was created. |
| <a id="todogroup"></a>`group` | [`Group`](#group) | Group this to-do item is associated with. |
| <a id="todoid"></a>`id` | [`ID!`](#id) | ID of the to-do item. |
| <a id="todoproject"></a>`project` | [`Project`](#project) | The project this to-do item is associated with. |
| <a id="todoproject"></a>`project` | [`Project`](#project) | Project this to-do item is associated with. |
| <a id="todostate"></a>`state` | [`TodoStateEnum!`](#todostateenum) | State of the to-do item. |
| <a id="todotargettype"></a>`targetType` | [`TodoTargetEnum!`](#todotargetenum) | Target type of the to-do item. |
......@@ -14056,7 +14056,7 @@ Core represention of a GitLab user.
| <a id="usercoregroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. |
| <a id="usercoregroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
| <a id="usercoreid"></a>`id` | [`ID!`](#id) | ID of the user. |
| <a id="usercorelocation"></a>`location` | [`String`](#string) | The location of the user. |
| <a id="usercorelocation"></a>`location` | [`String`](#string) | Location of the user. |
| <a id="usercorename"></a>`name` | [`String!`](#string) | Human-readable name of the user. |
| <a id="usercorenamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="usercoreprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
......@@ -14266,7 +14266,7 @@ fields relate to interactions between the two entities.
| <a id="usermergerequestinteractionapproved"></a>`approved` | [`Boolean!`](#boolean) | Whether this user has approved this merge request. |
| <a id="usermergerequestinteractioncanmerge"></a>`canMerge` | [`Boolean!`](#boolean) | Whether this user can merge this merge request. |
| <a id="usermergerequestinteractioncanupdate"></a>`canUpdate` | [`Boolean!`](#boolean) | Whether this user can update this merge request. |
| <a id="usermergerequestinteractionreviewstate"></a>`reviewState` | [`MergeRequestReviewState`](#mergerequestreviewstate) | The state of the review by this user. |
| <a id="usermergerequestinteractionreviewstate"></a>`reviewState` | [`MergeRequestReviewState`](#mergerequestreviewstate) | State of the review by this user. |
| <a id="usermergerequestinteractionreviewed"></a>`reviewed` | [`Boolean!`](#boolean) | Whether this user has provided a review for this merge request. |
### `UserPermissions`
......@@ -16002,19 +16002,19 @@ State of a test report.
| Value | Description |
| ----- | ----------- |
| <a id="todostateenumdone"></a>`done` | The state of the todo is done. |
| <a id="todostateenumpending"></a>`pending` | The state of the todo is pending. |
| <a id="todostateenumdone"></a>`done` | State of the todo is done. |
| <a id="todostateenumpending"></a>`pending` | State of the todo is pending. |
### `TodoTargetEnum`
| Value | Description |
| ----- | ----------- |
| <a id="todotargetenumalert"></a>`ALERT` | An Alert. |
| <a id="todotargetenumcommit"></a>`COMMIT` | A Commit. |
| <a id="todotargetenumdesign"></a>`DESIGN` | A Design. |
| <a id="todotargetenumalert"></a>`ALERT` | Alert. |
| <a id="todotargetenumcommit"></a>`COMMIT` | Commit. |
| <a id="todotargetenumdesign"></a>`DESIGN` | Design. |
| <a id="todotargetenumepic"></a>`EPIC` | An Epic. |
| <a id="todotargetenumissue"></a>`ISSUE` | An Issue. |
| <a id="todotargetenummergerequest"></a>`MERGEREQUEST` | A MergeRequest. |
| <a id="todotargetenumissue"></a>`ISSUE` | Issue. |
| <a id="todotargetenummergerequest"></a>`MERGEREQUEST` | Merge request. |
### `TypeEnum`
......@@ -16069,9 +16069,9 @@ Possible states of a user.
| Value | Description |
| ----- | ----------- |
| <a id="userstateactive"></a>`active` | The user is active and is able to use the system. |
| <a id="userstateblocked"></a>`blocked` | The user has been blocked and is prevented from using the system. |
| <a id="userstatedeactivated"></a>`deactivated` | The user is no longer active and is unable to use the system. |
| <a id="userstateactive"></a>`active` | User is active and is able to use the system. |
| <a id="userstateblocked"></a>`blocked` | User has been blocked and is prevented from using the system. |
| <a id="userstatedeactivated"></a>`deactivated` | User is no longer active and is unable to use the system. |
### `VisibilityLevelsEnum`
......@@ -16085,9 +16085,9 @@ Possible states of a user.
| Value | Description |
| ----- | ----------- |
| <a id="visibilityscopesenuminternal"></a>`internal` | The snippet is visible for any logged in user except external users. |
| <a id="visibilityscopesenumprivate"></a>`private` | The snippet is visible only to the snippet creator. |
| <a id="visibilityscopesenumpublic"></a>`public` | The snippet can be accessed without any authentication. |
| <a id="visibilityscopesenuminternal"></a>`internal` | Snippet is visible for any logged in user except external users. |
| <a id="visibilityscopesenumprivate"></a>`private` | Snippet is visible only to the snippet creator. |
| <a id="visibilityscopesenumpublic"></a>`public` | Snippet can be accessed without any authentication. |
### `VulnerabilityConfidence`
......@@ -17032,7 +17032,7 @@ Implementations:
| <a id="usergroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. |
| <a id="usergroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
| <a id="userid"></a>`id` | [`ID!`](#id) | ID of the user. |
| <a id="userlocation"></a>`location` | [`String`](#string) | The location of the user. |
| <a id="userlocation"></a>`location` | [`String`](#string) | Location of the user. |
| <a id="username"></a>`name` | [`String!`](#string) | Human-readable name of the user. |
| <a id="usernamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="userprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
......
......@@ -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
# 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 'Issue Boards', :js do
include BoardHelpers
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :public, group: group) }
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:development) { create(:label, project: project, name: 'Development') }
let_it_be(:stretch) { create(:label, project: project, name: 'Stretch') }
let_it_be(:issue1) { create(:labeled_issue, project: project, assignees: [user], milestone: milestone, labels: [development], weight: 3, relative_position: 2) }
let_it_be(:issue2) { create(:labeled_issue, project: project, labels: [development, stretch], relative_position: 1) }
let_it_be(:board) { create(:board, project: project) }
let_it_be(:list) { create(:list, board: board, label: development, position: 0) }
let_it_be(:scoped_label_1) { create(:label, project: project, name: 'Scoped1::Label1') }
let_it_be(:scoped_label_2) { create(:label, project: project, name: 'Scoped2::Label2') }
let(:card1) { find('.board:nth-child(2)').find('.board-card:nth-child(2)') }
let(:card2) { find('.board:nth-child(2)').find('.board-card:nth-child(1)') }
before do
stub_feature_flags(graphql_board_lists: false)
stub_licensed_features(multiple_issue_assignees: true)
project.add_maintainer(user)
project.team.add_developer(user2)
sign_in user
visit project_board_path(project, board)
wait_for_requests
end
context 'assignee', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332078' do
it 'updates the issues assignee' do
click_card(card2)
page.within('.assignee') do
click_button('Edit')
wait_for_requests
assignee = first('.gl-avatar-labeled').find('.gl-avatar-labeled-label').text
page.within('.dropdown-menu-user') do
first('.gl-avatar-labeled').click
end
click_button('Apply')
wait_for_requests
expect(page).to have_content(assignee)
end
expect(card2).to have_selector('.avatar')
end
it 'adds multiple assignees' do
click_card(card1)
page.within('.assignee') do
click_button('Edit')
wait_for_requests
assignee = all('.gl-avatar-labeled')[1].find('.gl-avatar-labeled-label').text
page.within('.dropdown-menu-user') do
find('[data-testid="unassign"]').click
all('.gl-avatar-labeled')[0].click
all('.gl-avatar-labeled')[1].click
end
click_button('Apply')
wait_for_requests
aggregate_failures do
expect(page).to have_link(nil, title: user.name)
expect(page).to have_link(nil, title: assignee)
end
end
expect(card1.all('.avatar').length).to eq(2)
end
it 'removes the assignee' do
click_card(card1)
page.within('.assignee') do
click_button('Edit')
page.within('.dropdown-menu-user') do
find('[data-testid="unassign"]').click
end
click_button('Apply')
expect(page).to have_content('None')
end
expect(card1).not_to have_selector('.avatar')
end
it 'assignees to current user' do
click_card(card2)
page.within('.assignee') do
expect(page).to have_content('None')
click_button 'assign yourself'
wait_for_requests
expect(page).to have_content(user.name)
end
expect(card2).to have_selector('.avatar')
end
it 'updates assignee dropdown' do
click_card(card2)
page.within('.assignee') do
click_button('Edit')
wait_for_requests
assignee = first('.gl-avatar-labeled').find('.gl-avatar-labeled-label').text
page.within('.dropdown-menu-user') do
first('.gl-avatar-labeled').click
end
click_button('Apply')
wait_for_requests
expect(page).to have_content(assignee)
end
click_card(card1)
page.within('.assignee') do
click_button('Edit')
expect(find('.dropdown-menu')).to have_selector('.gl-new-dropdown-item-check-icon')
end
end
end
context 'epic' do
before do
stub_licensed_features(epics: true)
group.add_owner(user)
visit project_board_path(project, board)
wait_for_requests
end
context 'when the issue is not associated with an epic' do
it 'displays `None` for value of epic' do
click_card(card1)
expect(find('.js-epic-label').text).to have_content('None')
end
end
context 'when the issue is associated with an epic' do
let(:epic1) { create(:epic, group: group, title: 'Foo') }
let!(:epic2) { create(:epic, group: group, title: 'Bar') }
let!(:epic_issue) { create(:epic_issue, issue: issue1, epic: epic1) }
it 'displays name of epic and links to it' do
click_card(card1)
expect(find('.js-epic-label')).to have_link(epic1.title, href: epic_path(epic1))
end
it 'updates the epic associated with the issue' do
click_card(card1)
page.within('.js-epic-block') do
page.find('.sidebar-dropdown-toggle').click
wait_for_requests
find('.gl-new-dropdown-item', text: epic2.title).click
wait_for_requests
expect(page.find('.value')).to have_content(epic2.title)
end
# Ensure that boards_store is also updated the epic associated with the issue.
click_card(card1)
click_card(card1)
expect(find('.js-epic-label')).to have_content(epic2.title)
end
end
end
context 'weight' do
it 'displays weight async' do
click_card(card1)
expect(find('.js-weight-weight-label').text).to have_content(issue1.weight)
end
it 'updates weight in sidebar to 1' do
click_card(card1)
page.within '.weight' do
click_link 'Edit'
find('.block.weight input').send_keys 1, :enter
page.within '.value' do
expect(page).to have_content '1'
end
end
# Ensure the request was sent and things are persisted
visit project_board_path(project, board)
wait_for_requests
click_card(card1)
page.within '.weight .value' do
expect(page).to have_content '1'
end
end
it 'updates weight in sidebar to no weight' do
click_card(card1)
page.within '.weight' do
click_link 'remove weight'
page.within '.value' do
expect(page).to have_content 'None'
end
end
# Ensure the request was sent and things are persisted
visit project_board_path(project, board)
wait_for_requests
click_card(card1)
page.within '.weight .value' do
expect(page).to have_content 'None'
end
end
context 'unlicensed' do
before do
stub_licensed_features(issue_weights: false)
visit project_board_path(project, board)
wait_for_requests
end
it 'hides weight' do
click_card(card1)
expect(page).not_to have_selector('.js-weight-weight-label')
end
end
end
context 'scoped labels' do
before do
stub_licensed_features(scoped_labels: true)
visit project_board_path(project, board)
wait_for_requests
end
it 'adds multiple scoped labels' do
click_card(card1)
page.within('.labels') do
click_link 'Edit'
wait_for_requests
click_link scoped_label_1.title
wait_for_requests
click_link scoped_label_2.title
wait_for_requests
find('.dropdown-menu-close-icon').click
page.within('.value') do
aggregate_failures do
expect(page).to have_selector('.gl-label-scoped', count: 2)
expect(page).to have_content(scoped_label_1.scoped_label_key)
expect(page).to have_content(scoped_label_1.scoped_label_value)
expect(page).to have_content(scoped_label_2.scoped_label_key)
expect(page).to have_content(scoped_label_2.scoped_label_value)
end
end
end
end
context 'with scoped label assigned' do
let!(:issue3) { create(:labeled_issue, project: project, labels: [development, scoped_label_1, scoped_label_2], relative_position: 3) }
let(:card3) { find('.board:nth-child(2)').find('.board-card:nth-child(1)') }
before do
stub_licensed_features(scoped_labels: true)
visit project_board_path(project, board)
wait_for_requests
end
it 'removes existing scoped label' do
click_card(card3)
page.within('.labels') do
click_link 'Edit'
wait_for_requests
click_link scoped_label_2.title
wait_for_requests
find('.dropdown-menu-close-icon').click
page.within('.value') do
aggregate_failures do
expect(page).to have_selector('.gl-label-scoped', count: 1)
expect(page).not_to have_content(scoped_label_1.scoped_label_value)
expect(page).to have_content(scoped_label_2.scoped_label_key)
expect(page).to have_content(scoped_label_2.scoped_label_value)
end
end
end
aggregate_failures do
expect(card3).to have_selector('.gl-label-scoped', count: 1)
expect(card3).not_to have_content(scoped_label_1.scoped_label_key)
expect(card3).not_to have_content(scoped_label_1.scoped_label_value)
expect(card3).to have_content(scoped_label_2.scoped_label_key)
expect(card3).to have_content(scoped_label_2.scoped_label_value)
end
end
end
end
context 'when opening sidebars' do
let(:settings_button) { find('.js-board-settings-button') }
it 'closes card sidebar when opening settings sidebar' do
click_card(card1)
expect(page).to have_selector('.right-sidebar')
settings_button.click
expect(page).to have_selector('.js-board-settings-sidebar')
expect(page).not_to have_selector('.right-sidebar')
end
it 'closes settings sidebar when opening card sidebar' do
settings_button.click
expect(page).to have_selector('.js-board-settings-sidebar')
click_card(card1)
expect(page).to have_selector('.right-sidebar')
expect(page).not_to have_selector('.js-board-settings-sidebar')
end
end
end
......@@ -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