Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 18:44:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 18:44:42 +0300
commit4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch)
tree5423a1c7516cffe36384133ade12572cf709398d /app/assets/javascripts/boards
parente570267f2f6b326480d284e0164a6464ba4081bc (diff)
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'app/assets/javascripts/boards')
-rw-r--r--app/assets/javascripts/boards/boards_util.js31
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue5
-rw-r--r--app/assets/javascripts/boards/components/board_card_inner.vue1
-rw-r--r--app/assets/javascripts/boards/components/board_content.vue11
-rw-r--r--app/assets/javascripts/boards/components/board_content_sidebar.vue44
-rw-r--r--app/assets/javascripts/boards/components/board_filtered_search.vue154
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_settings_sidebar.vue8
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue46
-rw-r--r--app/assets/javascripts/boards/constants.js42
-rw-r--r--app/assets/javascripts/boards/filtered_search_boards.js4
-rw-r--r--app/assets/javascripts/boards/graphql/group_projects.query.graphql2
-rw-r--r--app/assets/javascripts/boards/graphql/issue.fragment.graphql1
-rw-r--r--app/assets/javascripts/boards/index.js24
-rw-r--r--app/assets/javascripts/boards/stores/actions.js113
-rw-r--r--app/assets/javascripts/boards/stores/getters.js2
-rw-r--r--app/assets/javascripts/boards/stores/mutation_types.js10
-rw-r--r--app/assets/javascripts/boards/stores/mutations.js48
18 files changed, 385 insertions, 163 deletions
diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js
index a8b870f9b8e..f53d41dd0f4 100644
--- a/app/assets/javascripts/boards/boards_util.js
+++ b/app/assets/javascripts/boards/boards_util.js
@@ -1,6 +1,6 @@
import { sortBy, cloneDeep } from 'lodash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { ListType, NOT_FILTER } from './constants';
+import { ListType, NOT_FILTER, AssigneeIdParamValues } from './constants';
export function getMilestone() {
return null;
@@ -186,6 +186,35 @@ export function transformNotFilters(filters) {
}, {});
}
+export function getSupportedParams(filters, supportedFilters) {
+ return supportedFilters.reduce((acc, f) => {
+ /**
+ * TODO the API endpoint for the classic boards
+ * accepts assignee wildcard value as 'assigneeId' param -
+ * while the GraphQL query accepts the value in 'assigneWildcardId' field.
+ * Once we deprecate the classics boards,
+ * we should change the filtered search bar to use 'asssigneeWildcardId' as a token name.
+ */
+ if (f === 'assigneeId' && filters[f]) {
+ return AssigneeIdParamValues.includes(filters[f])
+ ? {
+ ...acc,
+ assigneeWildcardId: filters[f].toUpperCase(),
+ }
+ : acc;
+ }
+
+ if (filters[f]) {
+ return {
+ ...acc,
+ [f]: filters[f],
+ };
+ }
+
+ return acc;
+ }, {});
+}
+
// EE-specific feature. Find the implementation in the `ee/`-folder
export function transformBoardConfig() {
return '';
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index aacea0b970c..2821b799cef 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -1,5 +1,5 @@
<script>
-import { mapActions, mapGetters, mapState } from 'vuex';
+import { mapActions, mapState } from 'vuex';
import BoardCardInner from './board_card_inner.vue';
export default {
@@ -31,7 +31,6 @@ export default {
},
computed: {
...mapState(['selectedBoardItems', 'activeId']),
- ...mapGetters(['isSwimlanesOn']),
isActive() {
return this.item.id === this.activeId;
},
@@ -46,7 +45,7 @@ export default {
...mapActions(['toggleBoardItemMultiSelection', 'toggleBoardItem']),
toggleIssue(e) {
// Don't do anything if this happened on a no trigger element
- if (e.target.classList.contains('js-no-trigger')) return;
+ if (e.target.closest('.js-no-trigger')) return;
const isMultiSelect = e.ctrlKey || e.metaKey;
if (isMultiSelect) {
diff --git a/app/assets/javascripts/boards/components/board_card_inner.vue b/app/assets/javascripts/boards/components/board_card_inner.vue
index 9ff2cdd76d0..0cb2e64042e 100644
--- a/app/assets/javascripts/boards/components/board_card_inner.vue
+++ b/app/assets/javascripts/boards/components/board_card_inner.vue
@@ -190,6 +190,7 @@ export default {
<template v-for="label in orderedLabels">
<gl-label
:key="label.id"
+ class="js-no-trigger"
:background-color="label.color"
:title="label.title"
:description="label.description"
diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue
index a4b1e6adacf..b8a38d833ad 100644
--- a/app/assets/javascripts/boards/components/board_content.vue
+++ b/app/assets/javascripts/boards/components/board_content.vue
@@ -4,7 +4,6 @@ import { sortBy } from 'lodash';
import Draggable from 'vuedraggable';
import { mapState, mapGetters, mapActions } from 'vuex';
import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column.vue';
-import { sortableEnd, sortableStart } from '~/boards/mixins/sortable_default_options';
import defaultSortableConfig from '~/sortable/sortable_config';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import BoardColumn from './board_column.vue';
@@ -48,7 +47,7 @@ export default {
: this.lists;
},
canDragColumns() {
- return !this.isEpicBoard && this.glFeatures.graphqlBoardLists && this.canAdminList;
+ return (this.isEpicBoard || this.glFeatures.graphqlBoardLists) && this.canAdminList;
},
boardColumnWrapper() {
return this.canDragColumns ? Draggable : 'div';
@@ -73,14 +72,7 @@ export default {
const el = this.canDragColumns ? this.$refs.list.$el : this.$refs.list;
el.scrollTo({ left: el.scrollWidth, behavior: 'smooth' });
},
- handleDragOnStart() {
- sortableStart();
- },
-
handleDragOnEnd(params) {
- sortableEnd();
- if (this.isEpicBoard) return;
-
const { item, newIndex, oldIndex, to } = params;
const listId = item.dataset.id;
@@ -108,7 +100,6 @@ export default {
ref="list"
v-bind="draggableOptions"
class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
- @start="handleDragOnStart"
@end="handleDragOnEnd"
>
<board-column
diff --git a/app/assets/javascripts/boards/components/board_content_sidebar.vue b/app/assets/javascripts/boards/components/board_content_sidebar.vue
index 46359cc2bca..e1f8457c0e2 100644
--- a/app/assets/javascripts/boards/components/board_content_sidebar.vue
+++ b/app/assets/javascripts/boards/components/board_content_sidebar.vue
@@ -4,13 +4,13 @@ import { mapState, mapActions, mapGetters } from 'vuex';
import BoardSidebarDueDate from '~/boards/components/sidebar/board_sidebar_due_date.vue';
import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import BoardSidebarMilestoneSelect from '~/boards/components/sidebar/board_sidebar_milestone_select.vue';
-import BoardSidebarSubscription from '~/boards/components/sidebar/board_sidebar_subscription.vue';
import BoardSidebarTimeTracker from '~/boards/components/sidebar/board_sidebar_time_tracker.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
import { ISSUABLE } from '~/boards/constants';
import { contentTop } from '~/lib/utils/common_utils';
import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue';
+import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue';
export default {
headerHeight: `${contentTop()}px`,
@@ -18,10 +18,11 @@ export default {
GlDrawer,
BoardSidebarTitle,
SidebarAssigneesWidget,
+ SidebarConfidentialityWidget,
BoardSidebarTimeTracker,
BoardSidebarLabelsSelect,
BoardSidebarDueDate,
- BoardSidebarSubscription,
+ SidebarSubscriptionsWidget,
BoardSidebarMilestoneSelect,
BoardSidebarEpicSelect: () =>
import('ee_component/boards/components/sidebar/board_sidebar_epic_select.vue'),
@@ -30,7 +31,20 @@ export default {
SidebarIterationWidget: () =>
import('ee_component/sidebar/components/sidebar_iteration_widget.vue'),
},
- mixins: [glFeatureFlagsMixin()],
+ inject: {
+ multipleAssigneesFeatureAvailable: {
+ default: false,
+ },
+ epicFeatureAvailable: {
+ default: false,
+ },
+ iterationFeatureAvailable: {
+ default: false,
+ },
+ weightFeatureAvailable: {
+ default: false,
+ },
+ },
computed: {
...mapGetters([
'isSidebarOpen',
@@ -50,7 +64,7 @@ export default {
},
},
methods: {
- ...mapActions(['toggleBoardItem', 'setAssignees']),
+ ...mapActions(['toggleBoardItem', 'setAssignees', 'setActiveItemConfidential']),
handleClose() {
this.toggleBoardItem({ boardItem: this.activeBoardItem, sidebarType: this.sidebarType });
},
@@ -72,13 +86,14 @@ export default {
:iid="activeBoardItem.iid"
:full-path="fullPath"
:initial-assignees="activeBoardItem.assignees"
- class="assignee"
+ :allow-multiple-assignees="multipleAssigneesFeatureAvailable"
@assignees-updated="setAssignees"
/>
- <board-sidebar-epic-select class="epic" />
+ <board-sidebar-epic-select v-if="epicFeatureAvailable" class="epic" />
<div>
<board-sidebar-milestone-select />
<sidebar-iteration-widget
+ v-if="iterationFeatureAvailable"
:iid="activeBoardItem.iid"
:workspace-path="projectPathForActiveIssue"
:iterations-workspace-path="groupPathForActiveIssue"
@@ -89,8 +104,19 @@ export default {
<board-sidebar-time-tracker class="swimlanes-sidebar-time-tracker" />
<board-sidebar-due-date />
<board-sidebar-labels-select class="labels" />
- <board-sidebar-weight-input v-if="glFeatures.issueWeights" class="weight" />
- <board-sidebar-subscription class="subscriptions" />
+ <board-sidebar-weight-input v-if="weightFeatureAvailable" class="weight" />
+ <sidebar-confidentiality-widget
+ :iid="activeBoardItem.iid"
+ :full-path="fullPath"
+ :issuable-type="issuableType"
+ @confidentialityUpdated="setActiveItemConfidential($event)"
+ />
+ <sidebar-subscriptions-widget
+ :iid="activeBoardItem.iid"
+ :full-path="fullPath"
+ :issuable-type="issuableType"
+ data-testid="sidebar-notifications"
+ />
</template>
</gl-drawer>
</template>
diff --git a/app/assets/javascripts/boards/components/board_filtered_search.vue b/app/assets/javascripts/boards/components/board_filtered_search.vue
new file mode 100644
index 00000000000..e564af0c353
--- /dev/null
+++ b/app/assets/javascripts/boards/components/board_filtered_search.vue
@@ -0,0 +1,154 @@
+<script>
+import { pickBy } from 'lodash';
+import { mapActions } from 'vuex';
+import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
+import { __ } from '~/locale';
+import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
+
+export default {
+ i18n: {
+ search: __('Search'),
+ label: __('Label'),
+ author: __('Author'),
+ },
+ components: { FilteredSearch },
+ inject: ['initialFilterParams'],
+ props: {
+ tokens: {
+ type: Array,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ filterParams: this.initialFilterParams,
+ };
+ },
+ computed: {
+ urlParams() {
+ const { authorUsername, labelName, search } = this.filterParams;
+ let notParams = {};
+
+ if (Object.prototype.hasOwnProperty.call(this.filterParams, 'not')) {
+ notParams = pickBy(
+ {
+ 'not[label_name][]': this.filterParams.not.labelName,
+ 'not[author_username]': this.filterParams.not.authorUsername,
+ },
+ undefined,
+ );
+ }
+
+ return {
+ ...notParams,
+ author_username: authorUsername,
+ 'label_name[]': labelName,
+ search,
+ };
+ },
+ },
+ methods: {
+ ...mapActions(['performSearch']),
+ handleFilter(filters) {
+ this.filterParams = this.getFilterParams(filters);
+
+ updateHistory({
+ url: setUrlParams(this.urlParams, window.location.href, true, false, true),
+ title: document.title,
+ replace: true,
+ });
+
+ this.performSearch();
+ },
+ getFilteredSearchValue() {
+ const { authorUsername, labelName, search } = this.filterParams;
+ const filteredSearchValue = [];
+
+ if (authorUsername) {
+ filteredSearchValue.push({
+ type: 'author_username',
+ value: { data: authorUsername, operator: '=' },
+ });
+ }
+
+ if (labelName?.length) {
+ filteredSearchValue.push(
+ ...labelName.map((label) => ({
+ type: 'label_name',
+ value: { data: label, operator: '=' },
+ })),
+ );
+ }
+
+ if (this.filterParams['not[authorUsername]']) {
+ filteredSearchValue.push({
+ type: 'author_username',
+ value: { data: this.filterParams['not[authorUsername]'], operator: '!=' },
+ });
+ }
+
+ if (this.filterParams['not[labelName]']) {
+ filteredSearchValue.push(
+ ...this.filterParams['not[labelName]'].map((label) => ({
+ type: 'label_name',
+ value: { data: label, operator: '!=' },
+ })),
+ );
+ }
+
+ if (search) {
+ filteredSearchValue.push(search);
+ }
+
+ return filteredSearchValue;
+ },
+ getFilterParams(filters = []) {
+ const notFilters = filters.filter((item) => item.value.operator === '!=');
+ const equalsFilters = filters.filter((item) => item.value.operator === '=');
+
+ return { ...this.generateParams(equalsFilters), not: { ...this.generateParams(notFilters) } };
+ },
+ generateParams(filters = []) {
+ const filterParams = {};
+ const labels = [];
+ const plainText = [];
+
+ filters.forEach((filter) => {
+ switch (filter.type) {
+ case 'author_username':
+ filterParams.authorUsername = filter.value.data;
+ break;
+ case 'label_name':
+ labels.push(filter.value.data);
+ break;
+ case 'filtered-search-term':
+ if (filter.value.data) plainText.push(filter.value.data);
+ break;
+ default:
+ break;
+ }
+ });
+
+ if (labels.length) {
+ filterParams.labelName = labels;
+ }
+
+ if (plainText.length) {
+ filterParams.search = plainText.join(' ');
+ }
+ return filterParams;
+ },
+ },
+};
+</script>
+
+<template>
+ <filtered-search
+ class="gl-w-full"
+ namespace=""
+ :tokens="tokens"
+ :search-input-placeholder="$options.i18n.search"
+ :initial-filter-value="getFilteredSearchValue()"
+ @onFilter="handleFilter"
+ />
+</template>
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index ca66ad6934a..f94697172ac 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -161,7 +161,7 @@ export default {
const collapsed = !this.list.collapsed;
this.toggleListCollapsed({ listId: this.list.id, collapsed });
- if (!this.isLoggedIn || this.isEpicBoard) {
+ if (!this.isLoggedIn) {
this.addToLocalStorage();
} else {
this.updateListFunction();
diff --git a/app/assets/javascripts/boards/components/board_settings_sidebar.vue b/app/assets/javascripts/boards/components/board_settings_sidebar.vue
index 997655c346a..3d7f1f38a34 100644
--- a/app/assets/javascripts/boards/components/board_settings_sidebar.vue
+++ b/app/assets/javascripts/boards/components/board_settings_sidebar.vue
@@ -29,17 +29,17 @@ export default {
};
},
computed: {
- ...mapGetters(['isSidebarOpen', 'shouldUseGraphQL']),
+ ...mapGetters(['isSidebarOpen', 'shouldUseGraphQL', 'isEpicBoard']),
...mapState(['activeId', 'sidebarType', 'boardLists']),
isWipLimitsOn() {
- return this.glFeatures.wipLimits;
+ 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) {
+ if (this.shouldUseGraphQL || this.isEpicBoard) {
return this.boardLists[this.activeId];
}
return boardsStore.state.lists.find(({ id }) => id === this.activeId);
@@ -71,7 +71,7 @@ export default {
deleteBoard() {
// eslint-disable-next-line no-alert
if (window.confirm(__('Are you sure you want to remove this list?'))) {
- if (this.shouldUseGraphQL) {
+ if (this.shouldUseGraphQL || this.isEpicBoard) {
this.removeList(this.activeId);
} else {
this.activeList.destroy();
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue
index f78be83cd82..919ef0d3783 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue
@@ -1,10 +1,12 @@
<script>
import { GlLabel } from '@gitlab/ui';
import { mapGetters, mapActions } from 'vuex';
+import Api from '~/api';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
import createFlash from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { isScopedLabel } from '~/lib/utils/common_utils';
+import { mergeUrlParams } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
@@ -14,7 +16,13 @@ export default {
LabelsSelect,
GlLabel,
},
- inject: ['labelsFetchPath', 'labelsManagePath', 'labelsFilterBasePath'],
+ inject: {
+ labelsFetchPath: {
+ default: null,
+ },
+ labelsManagePath: {},
+ labelsFilterBasePath: {},
+ },
data() {
return {
loading: false,
@@ -38,6 +46,32 @@ export default {
scoped: isScopedLabel(label),
}));
},
+ fetchPath() {
+ /*
+ Labels fetched in epic boards are always group-level labels
+ and the correct path are passed from the backend (injected through labelsFetchPath)
+
+ For issue boards, we should always include project-level labels and use a different endpoint.
+ (it requires knowing the project path of a selected issue.)
+
+ Note 1. that we will be using GraphQL to fetch labels when we create a labels select widget.
+ And this component will be removed _wholesale_ https://gitlab.com/gitlab-org/gitlab/-/issues/300653.
+
+ Note 2. Moreover, 'fetchPath' needs to be used as a key for 'labels-select' component to force updates.
+ 'labels-select' has its own vuex store and initializes the passed props as states
+ and these states aren't reactively bound to the passed props.
+ */
+
+ const projectLabelsFetchPath = mergeUrlParams(
+ { include_ancestor_groups: true },
+ Api.buildUrl(Api.projectLabelsPath).replace(
+ ':namespace_path/:project_path',
+ this.projectPathForActiveIssue,
+ ),
+ );
+
+ return this.labelsFetchPath || projectLabelsFetchPath;
+ },
},
methods: {
...mapActions(['setActiveBoardItemLabels']),
@@ -77,7 +111,12 @@ export default {
</script>
<template>
- <board-editable-item ref="sidebarItem" :title="__('Labels')" :loading="loading">
+ <board-editable-item
+ ref="sidebarItem"
+ :title="__('Labels')"
+ :loading="loading"
+ data-testid="sidebar-labels"
+ >
<template #collapsed>
<gl-label
v-for="label in issueLabels"
@@ -95,12 +134,13 @@ export default {
<template #default="{ edit }">
<labels-select
ref="labelsSelect"
+ :key="fetchPath"
:allow-label-edit="false"
:allow-label-create="false"
:allow-multiselect="true"
:allow-scoped-labels="true"
:selected-labels="selectedLabels"
- :labels-fetch-path="labelsFetchPath"
+ :labels-fetch-path="fetchPath"
:labels-manage-path="labelsManagePath"
:labels-filter-base-path="labelsFilterBasePath"
:labels-list-title="__('Select label')"
diff --git a/app/assets/javascripts/boards/constants.js b/app/assets/javascripts/boards/constants.js
index 4ebd30fe67b..d88774d11c1 100644
--- a/app/assets/javascripts/boards/constants.js
+++ b/app/assets/javascripts/boards/constants.js
@@ -1,10 +1,28 @@
+import boardListsQuery from 'ee_else_ce/boards/graphql/board_lists.query.graphql';
import { __ } from '~/locale';
import updateEpicSubscriptionMutation from '~/sidebar/queries/update_epic_subscription.mutation.graphql';
import updateEpicTitleMutation from '~/sidebar/queries/update_epic_title.mutation.graphql';
import boardBlockingIssuesQuery from './graphql/board_blocking_issues.query.graphql';
+import destroyBoardListMutation from './graphql/board_list_destroy.mutation.graphql';
+import updateBoardListMutation from './graphql/board_list_update.mutation.graphql';
+
import issueSetSubscriptionMutation from './graphql/issue_set_subscription.mutation.graphql';
import issueSetTitleMutation from './graphql/issue_set_title.mutation.graphql';
+export const SupportedFilters = [
+ 'assigneeUsername',
+ 'authorUsername',
+ 'labelName',
+ 'milestoneTitle',
+ 'releaseTag',
+ 'search',
+ 'myReactionEmoji',
+ 'assigneeId',
+];
+
+/* eslint-disable-next-line @gitlab/require-i18n-strings */
+export const AssigneeIdParamValues = ['Any', 'None'];
+
export const issuableTypes = {
issue: 'issue',
epic: 'epic',
@@ -46,9 +64,10 @@ export const NOT_FILTER = 'not[';
export const flashAnimationDuration = 2000;
-export default {
- BoardType,
- ListType,
+export const listsQuery = {
+ [issuableTypes.issue]: {
+ query: boardListsQuery,
+ },
};
export const blockingIssuablesQueries = {
@@ -57,6 +76,18 @@ export const blockingIssuablesQueries = {
},
};
+export const updateListQueries = {
+ [issuableTypes.issue]: {
+ mutation: updateBoardListMutation,
+ },
+};
+
+export const deleteListQueries = {
+ [issuableTypes.issue]: {
+ mutation: destroyBoardListMutation,
+ },
+};
+
export const titleQueries = {
[issuableTypes.issue]: {
mutation: issueSetTitleMutation,
@@ -74,3 +105,8 @@ export const subscriptionQueries = {
mutation: updateEpicSubscriptionMutation,
},
};
+
+export default {
+ BoardType,
+ ListType,
+};
diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js
index 66580bdd30f..c6040f1e4aa 100644
--- a/app/assets/javascripts/boards/filtered_search_boards.js
+++ b/app/assets/javascripts/boards/filtered_search_boards.js
@@ -28,6 +28,10 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
if (vuexstore.getters.shouldUseGraphQL && 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
+ // only to unpack the params and do another validation inside
+ // 'performSearch' and 'setFilter' vuex actions.
if (boardConfigPath !== '') {
const filterPath = window.location.search ? `${window.location.search}&` : '?';
updateHistory({
diff --git a/app/assets/javascripts/boards/graphql/group_projects.query.graphql b/app/assets/javascripts/boards/graphql/group_projects.query.graphql
index 80a37c9943d..3218c06357c 100644
--- a/app/assets/javascripts/boards/graphql/group_projects.query.graphql
+++ b/app/assets/javascripts/boards/graphql/group_projects.query.graphql
@@ -2,7 +2,7 @@
query getGroupProjects($fullPath: ID!, $search: String, $after: String) {
group(fullPath: $fullPath) {
- projects(search: $search, after: $after, first: 100) {
+ projects(search: $search, after: $after, first: 100, includeSubgroups: true) {
nodes {
id
name
diff --git a/app/assets/javascripts/boards/graphql/issue.fragment.graphql b/app/assets/javascripts/boards/graphql/issue.fragment.graphql
index 7ecf9261214..47ecb55c72b 100644
--- a/app/assets/javascripts/boards/graphql/issue.fragment.graphql
+++ b/app/assets/javascripts/boards/graphql/issue.fragment.graphql
@@ -13,7 +13,6 @@ fragment IssueNode on Issue {
emailsDisabled
confidential
webUrl
- subscribed
relativePosition
milestone {
id
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index e3f9d2f24c2..1888645ef78 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -1,3 +1,4 @@
+import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { mapActions, mapGetters } from 'vuex';
@@ -35,13 +36,27 @@ import {
} from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import sidebarEventHub from '~/sidebar/event_hub';
+import introspectionQueryResultData from '~/sidebar/fragmentTypes.json';
+import { fullBoardId } from './boards_util';
import boardConfigToggle from './config_toggle';
import mountMultipleBoardsSwitcher from './mount_multiple_boards_switcher';
Vue.use(VueApollo);
+const fragmentMatcher = new IntrospectionFragmentMatcher({
+ introspectionQueryResultData,
+});
+
const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(),
+ defaultClient: createDefaultClient(
+ {},
+ {
+ cacheConfig: {
+ fragmentMatcher,
+ },
+ assumeImmutableResults: true,
+ },
+ ),
});
let issueBoardsApp;
@@ -82,10 +97,14 @@ export default () => {
currentUserId: gon.current_user_id || null,
canUpdate: parseBoolean($boardApp.dataset.canUpdate),
canAdminList: parseBoolean($boardApp.dataset.canAdminList),
- labelsFetchPath: $boardApp.dataset.labelsFetchPath,
labelsManagePath: $boardApp.dataset.labelsManagePath,
labelsFilterBasePath: $boardApp.dataset.labelsFilterBasePath,
timeTrackingLimitToHours: parseBoolean($boardApp.dataset.timeTrackingLimitToHours),
+ multipleAssigneesFeatureAvailable: parseBoolean(
+ $boardApp.dataset.multipleAssigneesFeatureAvailable,
+ ),
+ epicFeatureAvailable: parseBoolean($boardApp.dataset.epicFeatureAvailable),
+ iterationFeatureAvailable: parseBoolean($boardApp.dataset.iterationFeatureAvailable),
weightFeatureAvailable: parseBoolean($boardApp.dataset.weightFeatureAvailable),
boardWeight: $boardApp.dataset.boardWeight
? parseInt($boardApp.dataset.boardWeight, 10)
@@ -121,6 +140,7 @@ export default () => {
created() {
this.setInitialBoardData({
boardId: $boardApp.dataset.boardId,
+ fullBoardId: fullBoardId($boardApp.dataset.boardId),
fullPath: $boardApp.dataset.fullPath,
boardType: this.parent,
disabled: this.disabled,
diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js
index 8005414962c..5158e82c320 100644
--- a/app/assets/javascripts/boards/stores/actions.js
+++ b/app/assets/javascripts/boards/stores/actions.js
@@ -1,8 +1,4 @@
import * as Sentry from '@sentry/browser';
-import { pick } from 'lodash';
-import createBoardListMutation from 'ee_else_ce/boards/graphql/board_list_create.mutation.graphql';
-import boardListsQuery from 'ee_else_ce/boards/graphql/board_lists.query.graphql';
-import issueMoveListMutation from 'ee_else_ce/boards/graphql/issue_move_list.mutation.graphql';
import {
BoardType,
ListType,
@@ -11,7 +7,14 @@ import {
ISSUABLE,
titleQueries,
subscriptionQueries,
-} from '~/boards/constants';
+ SupportedFilters,
+ deleteListQueries,
+ listsQuery,
+ updateListQueries,
+ issuableTypes,
+} from 'ee_else_ce/boards/constants';
+import createBoardListMutation from 'ee_else_ce/boards/graphql/board_list_create.mutation.graphql';
+import issueMoveListMutation from 'ee_else_ce/boards/graphql/issue_move_list.mutation.graphql';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import createGqClient, { fetchPolicies } from '~/lib/graphql';
import { convertObjectPropsToCamelCase, urlParamsToObject } from '~/lib/utils/common_utils';
@@ -19,7 +22,6 @@ import { s__ } from '~/locale';
import {
formatBoardLists,
formatListIssues,
- fullBoardId,
formatListsPageInfo,
formatIssue,
formatIssueInput,
@@ -27,10 +29,9 @@ import {
transformNotFilters,
moveItemListHelper,
getMoveData,
+ getSupportedParams,
} from '../boards_util';
import boardLabelsQuery from '../graphql/board_labels.query.graphql';
-import destroyBoardListMutation from '../graphql/board_list_destroy.mutation.graphql';
-import updateBoardListMutation from '../graphql/board_list_update.mutation.graphql';
import groupProjectsQuery from '../graphql/group_projects.query.graphql';
import issueCreateMutation from '../graphql/issue_create.mutation.graphql';
import issueSetDueDateMutation from '../graphql/issue_set_due_date.mutation.graphql';
@@ -39,11 +40,6 @@ import issueSetMilestoneMutation from '../graphql/issue_set_milestone.mutation.g
import listsIssuesQuery from '../graphql/lists_issues.query.graphql';
import * as types from './mutation_types';
-const notImplemented = () => {
- /* eslint-disable-next-line @gitlab/require-i18n-strings */
- throw new Error('Not implemented!');
-};
-
export const gqlClient = createGqClient(
{},
{
@@ -65,16 +61,11 @@ export default {
},
setFilters: ({ commit }, filters) => {
- const filterParams = pick(filters, [
- 'assigneeUsername',
- 'authorUsername',
- 'labelName',
- 'milestoneTitle',
- 'releaseTag',
- 'search',
- 'myReactionEmoji',
- ]);
- filterParams.not = transformNotFilters(filters);
+ const filterParams = {
+ ...getSupportedParams(filters, SupportedFilters),
+ not: transformNotFilters(filters),
+ };
+
commit(types.SET_FILTERS, filterParams);
},
@@ -90,24 +81,22 @@ export default {
}
},
- fetchLists: ({ dispatch }) => {
- dispatch('fetchIssueLists');
- },
-
- fetchIssueLists: ({ commit, state, dispatch }) => {
- const { boardType, filterParams, fullPath, boardId } = state;
+ fetchLists: ({ commit, state, dispatch }) => {
+ const { boardType, filterParams, fullPath, fullBoardId, issuableType } = state;
const variables = {
fullPath,
- boardId: fullBoardId(boardId),
+ boardId: fullBoardId,
filters: filterParams,
- isGroup: boardType === BoardType.group,
- isProject: boardType === BoardType.project,
+ ...(issuableType === issuableTypes.issue && {
+ isGroup: boardType === BoardType.group,
+ isProject: boardType === BoardType.project,
+ }),
};
return gqlClient
.query({
- query: boardListsQuery,
+ query: listsQuery[issuableType].query,
variables,
})
.then(({ data }) => {
@@ -141,7 +130,7 @@ export default {
{ state, commit, dispatch, getters },
{ backlog, labelId, milestoneId, assigneeId, iterationId },
) => {
- const { boardId } = state;
+ const { fullBoardId } = state;
const existingList = getters.getListByLabelId(labelId);
@@ -154,7 +143,7 @@ export default {
.mutate({
mutation: createBoardListMutation,
variables: {
- boardId: fullBoardId(boardId),
+ boardId: fullBoardId,
backlog,
labelId,
milestoneId,
@@ -242,10 +231,13 @@ export default {
dispatch('updateList', { listId, position: newPosition, backupList });
},
- updateList: ({ commit }, { listId, position, collapsed, backupList }) => {
+ updateList: (
+ { commit, state: { issuableType } },
+ { listId, position, collapsed, backupList },
+ ) => {
gqlClient
.mutate({
- mutation: updateBoardListMutation,
+ mutation: updateListQueries[issuableType].mutation,
variables: {
listId,
position,
@@ -266,14 +258,14 @@ export default {
commit(types.TOGGLE_LIST_COLLAPSED, { listId, collapsed });
},
- removeList: ({ state, commit }, listId) => {
- const listsBackup = { ...state.boardLists };
+ removeList: ({ state: { issuableType, boardLists }, commit }, listId) => {
+ const listsBackup = { ...boardLists };
commit(types.REMOVE_LIST, listId);
return gqlClient
.mutate({
- mutation: destroyBoardListMutation,
+ mutation: deleteListQueries[issuableType].mutation,
variables: {
listId,
},
@@ -297,11 +289,11 @@ export default {
fetchItemsForList: ({ state, commit }, { listId, fetchNext = false }) => {
commit(types.REQUEST_ITEMS_FOR_LIST, { listId, fetchNext });
- const { fullPath, boardId, boardType, filterParams } = state;
+ const { fullPath, fullBoardId, boardType, filterParams } = state;
const variables = {
fullPath,
- boardId: fullBoardId(boardId),
+ boardId: fullBoardId,
id: listId,
filters: filterParams,
isGroup: boardType === BoardType.group,
@@ -430,7 +422,7 @@ export default {
try {
const { itemId, fromListId, toListId, moveBeforeId, moveAfterId } = moveData;
const {
- boardId,
+ fullBoardId,
boardItems: {
[itemId]: { iid, referencePath },
},
@@ -441,7 +433,7 @@ export default {
variables: {
iid,
projectPath: referencePath.split(/[#]/)[0],
- boardId: fullBoardId(boardId),
+ boardId: fullBoardId,
fromListId: getIdFromGraphQLId(fromListId),
toListId: getIdFromGraphQLId(toListId),
moveBeforeId,
@@ -653,6 +645,15 @@ export default {
});
},
+ setActiveItemConfidential: ({ commit, getters }, confidential) => {
+ const { activeBoardItem } = getters;
+ commit(types.UPDATE_BOARD_ITEM_BY_ID, {
+ itemId: activeBoardItem.id,
+ prop: 'confidential',
+ value: confidential,
+ });
+ },
+
fetchGroupProjects: ({ commit, state }, { search = '', fetchNext = false }) => {
commit(types.REQUEST_GROUP_PROJECTS, fetchNext);
@@ -731,28 +732,4 @@ export default {
unsetError: ({ commit }) => {
commit(types.SET_ERROR, undefined);
},
-
- fetchBacklog: () => {
- notImplemented();
- },
-
- bulkUpdateIssues: () => {
- notImplemented();
- },
-
- fetchIssue: () => {
- notImplemented();
- },
-
- toggleIssueSubscription: () => {
- notImplemented();
- },
-
- showPage: () => {
- notImplemented();
- },
-
- toggleEmptyState: () => {
- notImplemented();
- },
};
diff --git a/app/assets/javascripts/boards/stores/getters.js b/app/assets/javascripts/boards/stores/getters.js
index 0589851c658..b61ecc5ccb6 100644
--- a/app/assets/javascripts/boards/stores/getters.js
+++ b/app/assets/javascripts/boards/stores/getters.js
@@ -21,7 +21,7 @@ export default {
groupPathForActiveIssue: (_, getters) => {
const { referencePath = '' } = getters.activeBoardItem;
- return referencePath.slice(0, referencePath.indexOf('/'));
+ return referencePath.slice(0, referencePath.lastIndexOf('/'));
},
projectPathForActiveIssue: (_, getters) => {
diff --git a/app/assets/javascripts/boards/stores/mutation_types.js b/app/assets/javascripts/boards/stores/mutation_types.js
index 22b9905ee62..ccea2917c2c 100644
--- a/app/assets/javascripts/boards/stores/mutation_types.js
+++ b/app/assets/javascripts/boards/stores/mutation_types.js
@@ -9,9 +9,7 @@ export const GENERATE_DEFAULT_LISTS_FAILURE = 'GENERATE_DEFAULT_LISTS_FAILURE';
export const RECEIVE_BOARD_LISTS_SUCCESS = 'RECEIVE_BOARD_LISTS_SUCCESS';
export const RECEIVE_BOARD_LISTS_FAILURE = 'RECEIVE_BOARD_LISTS_FAILURE';
export const SHOW_PROMOTION_LIST = 'SHOW_PROMOTION_LIST';
-export const REQUEST_ADD_LIST = 'REQUEST_ADD_LIST';
export const RECEIVE_ADD_LIST_SUCCESS = 'RECEIVE_ADD_LIST_SUCCESS';
-export const RECEIVE_ADD_LIST_ERROR = 'RECEIVE_ADD_LIST_ERROR';
export const MOVE_LIST = 'MOVE_LIST';
export const UPDATE_LIST_FAILURE = 'UPDATE_LIST_FAILURE';
export const TOGGLE_LIST_COLLAPSED = 'TOGGLE_LIST_COLLAPSED';
@@ -20,19 +18,11 @@ export const REMOVE_LIST_FAILURE = 'REMOVE_LIST_FAILURE';
export const REQUEST_ITEMS_FOR_LIST = 'REQUEST_ITEMS_FOR_LIST';
export const RECEIVE_ITEMS_FOR_LIST_FAILURE = 'RECEIVE_ITEMS_FOR_LIST_FAILURE';
export const RECEIVE_ITEMS_FOR_LIST_SUCCESS = 'RECEIVE_ITEMS_FOR_LIST_SUCCESS';
-export const REQUEST_ADD_ISSUE = 'REQUEST_ADD_ISSUE';
-export const RECEIVE_ADD_ISSUE_SUCCESS = 'RECEIVE_ADD_ISSUE_SUCCESS';
-export const RECEIVE_ADD_ISSUE_ERROR = 'RECEIVE_ADD_ISSUE_ERROR';
export const UPDATE_BOARD_ITEM = 'UPDATE_BOARD_ITEM';
export const REMOVE_BOARD_ITEM = 'REMOVE_BOARD_ITEM';
-export const REQUEST_UPDATE_ISSUE = 'REQUEST_UPDATE_ISSUE';
export const MUTATE_ISSUE_SUCCESS = 'MUTATE_ISSUE_SUCCESS';
-export const RECEIVE_UPDATE_ISSUE_SUCCESS = 'RECEIVE_UPDATE_ISSUE_SUCCESS';
-export const RECEIVE_UPDATE_ISSUE_ERROR = 'RECEIVE_UPDATE_ISSUE_ERROR';
export const ADD_BOARD_ITEM_TO_LIST = 'ADD_BOARD_ITEM_TO_LIST';
export const REMOVE_BOARD_ITEM_FROM_LIST = 'REMOVE_BOARD_ITEM_FROM_LIST';
-export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE';
-export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE';
export const SET_ACTIVE_ID = 'SET_ACTIVE_ID';
export const UPDATE_BOARD_ITEM_BY_ID = 'UPDATE_BOARD_ITEM_BY_ID';
export const SET_ASSIGNEE_LOADING = 'SET_ASSIGNEE_LOADING';
diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js
index 561c21b78c1..667628b2998 100644
--- a/app/assets/javascripts/boards/stores/mutations.js
+++ b/app/assets/javascripts/boards/stores/mutations.js
@@ -6,11 +6,6 @@ import { formatIssue } from '../boards_util';
import { issuableTypes } from '../constants';
import * as mutationTypes from './mutation_types';
-const notImplemented = () => {
- /* eslint-disable-next-line @gitlab/require-i18n-strings */
- throw new Error('Not implemented!');
-};
-
const updateListItemsCount = ({ state, listId, value }) => {
const list = state.boardLists[listId];
if (state.issuableType === issuableTypes.epic) {
@@ -40,8 +35,9 @@ export const addItemToList = ({ state, listId, itemId, moveBeforeId, moveAfterId
export default {
[mutationTypes.SET_INITIAL_BOARD_DATA](state, data) {
- const { boardType, disabled, boardId, fullPath, boardConfig, issuableType } = data;
+ const { boardType, disabled, boardId, fullBoardId, fullPath, boardConfig, issuableType } = data;
state.boardId = boardId;
+ state.fullBoardId = fullBoardId;
state.fullPath = fullPath;
state.boardType = boardType;
state.disabled = disabled;
@@ -93,18 +89,10 @@ export default {
state.error = s__('Boards|An error occurred while generating lists. Please reload the page.');
},
- [mutationTypes.REQUEST_ADD_LIST]: () => {
- notImplemented();
- },
-
[mutationTypes.RECEIVE_ADD_LIST_SUCCESS]: (state, list) => {
Vue.set(state.boardLists, list.id, list);
},
- [mutationTypes.RECEIVE_ADD_LIST_ERROR]: () => {
- notImplemented();
- },
-
[mutationTypes.MOVE_LIST]: (state, { movedList, listAtNewIndex }) => {
const { boardLists } = state;
Vue.set(boardLists, movedList.id, movedList);
@@ -171,35 +159,11 @@ export default {
state.isSettingAssignees = isLoading;
},
- [mutationTypes.REQUEST_ADD_ISSUE]: () => {
- notImplemented();
- },
-
- [mutationTypes.RECEIVE_ADD_ISSUE_SUCCESS]: () => {
- notImplemented();
- },
-
- [mutationTypes.RECEIVE_ADD_ISSUE_ERROR]: () => {
- notImplemented();
- },
-
[mutationTypes.MUTATE_ISSUE_SUCCESS]: (state, { issue }) => {
const issueId = getIdFromGraphQLId(issue.id);
Vue.set(state.boardItems, issueId, formatIssue({ ...issue, id: issueId }));
},
- [mutationTypes.REQUEST_UPDATE_ISSUE]: () => {
- notImplemented();
- },
-
- [mutationTypes.RECEIVE_UPDATE_ISSUE_SUCCESS]: () => {
- notImplemented();
- },
-
- [mutationTypes.RECEIVE_UPDATE_ISSUE_ERROR]: () => {
- notImplemented();
- },
-
[mutationTypes.ADD_BOARD_ITEM_TO_LIST]: (
state,
{ itemId, listId, moveBeforeId, moveAfterId, atIndex },
@@ -219,14 +183,6 @@ export default {
Vue.delete(state.boardItems, itemId);
},
- [mutationTypes.SET_CURRENT_PAGE]: () => {
- notImplemented();
- },
-
- [mutationTypes.TOGGLE_EMPTY_STATE]: () => {
- notImplemented();
- },
-
[mutationTypes.REQUEST_GROUP_PROJECTS]: (state, fetchNext) => {
Vue.set(state, 'groupProjectsFlags', {
[fetchNext ? 'isLoadingMore' : 'isLoading']: true,