Skip to content

Commit a4cfb76

Browse files
author
GitLab Bot
committed
Add latest changes from gitlab-org/gitlab@master
1 parent 3088254 commit a4cfb76

File tree

118 files changed

+1961
-439
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

118 files changed

+1961
-439
lines changed

.eslintrc.yml

+8
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,11 @@ overrides:
244244
- 'ee/app/assets/javascripts/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql'
245245
rules:
246246
'@graphql-eslint/require-id-when-available': off
247+
- files:
248+
- '{,spec/}tooling/**/*'
249+
rules:
250+
'no-undef': off
251+
'import/no-commonjs': off
252+
'import/no-extraneous-dependencies': off
253+
'no-restricted-syntax': off
254+
'@gitlab/require-i18n-strings': off

.rubocop_todo/rspec/context_wording.yml

-1
Original file line numberDiff line numberDiff line change
@@ -2078,7 +2078,6 @@ RSpec/ContextWording:
20782078
- 'spec/models/packages/dependency_spec.rb'
20792079
- 'spec/models/packages/package_file_spec.rb'
20802080
- 'spec/models/packages/package_spec.rb'
2081-
- 'spec/models/pages_domain_spec.rb'
20822081
- 'spec/models/personal_access_token_spec.rb'
20832082
- 'spec/models/plan_limits_spec.rb'
20842083
- 'spec/models/preloaders/labels_preloader_spec.rb'

.rubocop_todo/rspec/named_subject.yml

-1
Original file line numberDiff line numberDiff line change
@@ -2648,7 +2648,6 @@ RSpec/NamedSubject:
26482648
- 'spec/models/packages/package_file_spec.rb'
26492649
- 'spec/models/packages/package_spec.rb'
26502650
- 'spec/models/packages/rpm/repository_file_spec.rb'
2651-
- 'spec/models/pages_domain_spec.rb'
26522651
- 'spec/models/personal_access_token_spec.rb'
26532652
- 'spec/models/plan_limits_spec.rb'
26542653
- 'spec/models/project_authorization_spec.rb'

.rubocop_todo/rspec/return_from_stub.yml

-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,6 @@ RSpec/ReturnFromStub:
143143
- 'spec/models/internal_id_spec.rb'
144144
- 'spec/models/issue_spec.rb'
145145
- 'spec/models/merge_request_spec.rb'
146-
- 'spec/models/pages_domain_spec.rb'
147146
- 'spec/models/project_spec.rb'
148147
- 'spec/models/project_statistics_spec.rb'
149148
- 'spec/models/snippet_statistics_spec.rb'

.stylelintrc

+24-10
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
11
{
2-
"extends": ["@gitlab/stylelint-config"],
3-
"ignoreFiles": [
4-
"app/assets/stylesheets/pages/emojis.scss",
5-
"app/assets/stylesheets/startup/startup-*.scss",
6-
"ee/app/assets/stylesheets/startup/startup-*.scss",
7-
"app/assets/stylesheets/highlight/themes/*.scss",
8-
"app/assets/stylesheets/lazy_bundles/cropper.css"
9-
],
10-
"overrides": [
2+
"extends": ["@gitlab/stylelint-config"],
3+
"plugins": ["./tooling/stylelint/gitlab_no_gl_class.plugin.js"],
4+
"rules": {
5+
"gitlab/no-gl-class": true
6+
},
7+
"ignoreFiles": [
8+
"app/assets/stylesheets/pages/emojis.scss",
9+
"app/assets/stylesheets/startup/startup-*.scss",
10+
"ee/app/assets/stylesheets/startup/startup-*.scss",
11+
"app/assets/stylesheets/highlight/themes/*.scss",
12+
"app/assets/stylesheets/lazy_bundles/cropper.css"
13+
],
14+
"overrides": [
1115
{
1216
"files": ["app/assets/stylesheets/mailers/mailer.scss"],
1317
"rules": {
1418
"color-hex-length": "long"
1519
}
20+
},
21+
{
22+
"files": [
23+
"app/assets/stylesheets/framework/**/*.scss",
24+
"app/assets/stylesheets/themes/dark_mode_overrides.scss",
25+
"app/assets/stylesheets/page_bundles/_ide_theme_overrides.scss"
26+
],
27+
"rules": {
28+
"gitlab/no-gl-class": null
29+
}
1630
}
17-
]
31+
]
1832
}

app/assets/javascripts/boards/components/board_content.vue

+66-2
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,50 @@ import produce from 'immer';
55
import Draggable from 'vuedraggable';
66
import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column.vue';
77
import BoardAddNewColumnTrigger from '~/boards/components/board_add_new_column_trigger.vue';
8+
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
9+
import WorkItemDrawer from '~/work_items/components/work_item_drawer.vue';
810
import { s__ } from '~/locale';
911
import { defaultSortableOptions, DRAG_DELAY } from '~/sortable/constants';
12+
import { mapWorkItemWidgetsToIssueFields } from '~/issues/list/utils';
1013
import {
1114
DraggableItemTypes,
1215
flashAnimationDuration,
1316
listsQuery,
1417
updateListQueries,
1518
ListType,
19+
listIssuablesQueries,
20+
DEFAULT_BOARD_LIST_ITEMS_SIZE,
1621
} from 'ee_else_ce/boards/constants';
1722
import { calculateNewPosition } from 'ee_else_ce/boards/boards_util';
1823
import { setError } from '../graphql/cache_updates';
1924
import BoardColumn from './board_column.vue';
25+
import BoardDrawerWrapper from './board_drawer_wrapper.vue';
2026
2127
export default {
2228
draggableItemTypes: DraggableItemTypes,
2329
components: {
2430
BoardAddNewColumn,
2531
BoardAddNewColumnTrigger,
2632
BoardColumn,
33+
BoardDrawerWrapper,
2734
BoardContentSidebar: () => import('~/boards/components/board_content_sidebar.vue'),
2835
EpicBoardContentSidebar: () =>
2936
import('ee_component/boards/components/epic_board_content_sidebar.vue'),
3037
EpicsSwimlanes: () => import('ee_component/boards/components/epics_swimlanes.vue'),
3138
GlAlert,
39+
WorkItemDrawer,
3240
},
33-
inject: ['boardType', 'canAdminList', 'isIssueBoard', 'isEpicBoard', 'disabled', 'issuableType'],
41+
mixins: [glFeatureFlagsMixin()],
42+
inject: [
43+
'boardType',
44+
'canAdminList',
45+
'isIssueBoard',
46+
'isEpicBoard',
47+
'disabled',
48+
'issuableType',
49+
'isGroupBoard',
50+
'fullPath',
51+
],
3452
props: {
3553
boardId: {
3654
type: String,
@@ -108,6 +126,9 @@ export default {
108126
const closedList = this.boardListsToUse.find((list) => list.listType === ListType.closed);
109127
return closedList?.id || '';
110128
},
129+
issuesDrawerEnabled() {
130+
return this.glFeatures.issuesListDrawer;
131+
},
111132
},
112133
methods: {
113134
afterFormEnters() {
@@ -193,6 +214,24 @@ export default {
193214
});
194215
}
195216
},
217+
updateBoardCard(workItem, activeCard) {
218+
const { cache } = this.$apollo.provider.clients.defaultClient;
219+
220+
const variables = {
221+
id: activeCard.listId,
222+
filters: this.filterParams,
223+
fullPath: this.fullPath,
224+
boardId: this.boardId,
225+
isGroup: this.isGroupBoard,
226+
isProject: !this.isGroupBoard,
227+
first: DEFAULT_BOARD_LIST_ITEMS_SIZE,
228+
};
229+
230+
cache.updateQuery(
231+
{ query: listIssuablesQueries[this.issuableType].query, variables },
232+
(boardList) => mapWorkItemWidgetsToIssueFields(boardList, workItem, true),
233+
);
234+
},
196235
},
197236
};
198237
</script>
@@ -283,9 +322,34 @@ export default {
283322
/>
284323
</div>
285324
</epics-swimlanes>
325+
<board-drawer-wrapper
326+
v-if="issuesDrawerEnabled"
327+
:backlog-list-id="backlogListId"
328+
:closed-list-id="closedListId"
329+
>
330+
<template
331+
#default="{
332+
activeIssuable,
333+
onDrawerClosed,
334+
onAttributeUpdated,
335+
onIssuableDeleted,
336+
onStateUpdated,
337+
}"
338+
>
339+
<work-item-drawer
340+
:open="Boolean(activeIssuable && activeIssuable.iid)"
341+
:active-item="activeIssuable"
342+
@close="onDrawerClosed"
343+
@work-item-updated="updateBoardCard($event, activeIssuable)"
344+
@workItemDeleted="onIssuableDeleted(activeIssuable)"
345+
@attributesUpdated="onAttributeUpdated"
346+
@workItemStateUpdated="onStateUpdated"
347+
/>
348+
</template>
349+
</board-drawer-wrapper>
286350
287351
<board-content-sidebar
288-
v-if="isIssueBoard"
352+
v-if="isIssueBoard && !issuesDrawerEnabled"
289353
:backlog-list-id="backlogListId"
290354
:closed-list-id="closedListId"
291355
data-testid="issue-boards-sidebar"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<script>
2+
import { union } from 'lodash';
3+
import activeBoardItemQuery from 'ee_else_ce/boards/graphql/client/active_board_item.query.graphql';
4+
import setActiveBoardItemMutation from 'ee_else_ce/boards/graphql/client/set_active_board_item.mutation.graphql';
5+
import { TYPE_ISSUE } from '~/issues/constants';
6+
import { ListType } from 'ee_else_ce/boards/constants';
7+
import { identifyAffectedLists } from '../graphql/cache_updates';
8+
9+
export default {
10+
name: 'BoardDrawerWrapper',
11+
inject: {
12+
issuableType: {
13+
default: TYPE_ISSUE,
14+
},
15+
},
16+
props: {
17+
backlogListId: {
18+
type: String,
19+
required: true,
20+
},
21+
closedListId: {
22+
type: String,
23+
required: true,
24+
},
25+
},
26+
data() {
27+
return {
28+
affectedListTypes: [],
29+
updatedAttributeIds: [],
30+
};
31+
},
32+
apollo: {
33+
activeBoardItem: {
34+
query: activeBoardItemQuery,
35+
variables: {
36+
isIssue: true,
37+
},
38+
},
39+
},
40+
computed: {
41+
apolloClient() {
42+
return this.$apollo.getClient();
43+
},
44+
},
45+
methods: {
46+
async onDrawerClosed() {
47+
const item = this.activeBoardItem;
48+
49+
await this.$apollo.mutate({
50+
mutation: setActiveBoardItemMutation,
51+
variables: {
52+
boardItem: null,
53+
listId: null,
54+
},
55+
});
56+
57+
if (item.listId !== this.closedListId || this.affectedListTypes.includes(ListType.closed)) {
58+
await this.refetchAffectedLists(item);
59+
}
60+
this.affectedListTypes = [];
61+
this.updatedAttributeIds = [];
62+
},
63+
onAttributeUpdated({ ids, type }) {
64+
if (!this.affectedListTypes.includes(type)) {
65+
this.affectedListTypes.push(type);
66+
}
67+
this.updatedAttributeIds = union(this.updatedAttributeIds, ids);
68+
},
69+
refetchAffectedLists(item) {
70+
if (!this.affectedListTypes.length) {
71+
return;
72+
}
73+
74+
const affectedLists = identifyAffectedLists({
75+
client: this.apolloClient,
76+
item,
77+
issuableType: TYPE_ISSUE,
78+
affectedListTypes: this.affectedListTypes,
79+
updatedAttributeIds: this.updatedAttributeIds,
80+
});
81+
82+
if (this.backlogListId && !affectedLists.includes(this.backlogListId)) {
83+
affectedLists.push(this.backlogListId);
84+
}
85+
86+
if (this.closedListId && this.affectedListTypes.includes(ListType.closed)) {
87+
affectedLists.push(this.closedListId);
88+
}
89+
90+
this.refetchActiveIssuableLists(item);
91+
92+
this.apolloClient.refetchQueries({
93+
updateCache(cache) {
94+
affectedLists.forEach((listId) => {
95+
cache.evict({
96+
id: cache.identify({
97+
__typename: 'BoardList',
98+
id: listId,
99+
}),
100+
fieldName: 'issues',
101+
});
102+
cache.evict({
103+
id: cache.identify({
104+
__typename: 'BoardList',
105+
id: listId,
106+
}),
107+
fieldName: 'issuesCount',
108+
});
109+
});
110+
},
111+
});
112+
},
113+
refetchActiveIssuableLists(item) {
114+
this.apolloClient.refetchQueries({
115+
updateCache(cache) {
116+
cache.evict({ id: cache.identify(item) });
117+
},
118+
});
119+
},
120+
onStateUpdated() {
121+
this.affectedListTypes.push(ListType.closed);
122+
},
123+
},
124+
render() {
125+
return this.$scopedSlots.default({
126+
activeIssuable: this.activeBoardItem,
127+
onDrawerClosed: this.onDrawerClosed,
128+
onAttributeUpdated: this.onAttributeUpdated,
129+
onIssuableDeleted: this.refetchActiveIssuableLists,
130+
onStateUpdated: this.onStateUpdated,
131+
});
132+
},
133+
};
134+
</script>

app/assets/javascripts/boards/index.js

+18-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,15 @@ const apolloProvider = new VueApollo({
2222
});
2323

2424
function mountBoardApp(el) {
25-
const { boardId, groupId, fullPath, rootPath, hasScopedLabelsFeature } = el.dataset;
25+
const {
26+
boardId,
27+
groupId,
28+
fullPath,
29+
rootPath,
30+
hasScopedLabelsFeature,
31+
wiGroupPath,
32+
wiCanAdminLabel,
33+
} = el.dataset;
2634

2735
const rawFilterParams = queryToObject(window.location.search, { gatherArrays: true });
2836

@@ -43,9 +51,11 @@ function mountBoardApp(el) {
4351
groupId: Number(groupId),
4452
rootPath,
4553
fullPath,
54+
groupPath: wiGroupPath,
4655
initialFilterParams,
4756
boardBaseUrl: el.dataset.boardBaseUrl,
4857
boardType,
58+
isGroup: boardType === WORKSPACE_GROUP,
4959
isGroupBoard: boardType === WORKSPACE_GROUP,
5060
isProjectBoard: boardType === WORKSPACE_PROJECT,
5161
currentUserId: gon.current_user_id || null,
@@ -60,13 +70,16 @@ function mountBoardApp(el) {
6070
weights: el.dataset.weights ? JSON.parse(el.dataset.weights) : [],
6171
isIssueBoard: true,
6272
isEpicBoard: false,
73+
reportAbusePath: el.dataset.wiReportAbusePath,
74+
issuesListPath: el.dataset.wiIssuesListPath,
6375
// Permissions
6476
canUpdate: parseBoolean(el.dataset.canUpdate),
6577
canAdminList: parseBoolean(el.dataset.canAdminList),
6678
canAdminBoard: parseBoolean(el.dataset.canAdminBoard),
6779
allowLabelCreate: parseBoolean(el.dataset.canUpdate),
6880
allowLabelEdit: parseBoolean(el.dataset.canUpdate),
6981
isSignedIn: isLoggedIn(),
82+
canAdminLabel: parseBoolean(wiCanAdminLabel),
7083
// Features
7184
multipleAssigneesFeatureAvailable: parseBoolean(el.dataset.multipleAssigneesFeatureAvailable),
7285
epicFeatureAvailable: parseBoolean(el.dataset.epicFeatureAvailable),
@@ -83,6 +96,10 @@ function mountBoardApp(el) {
8396
scopedIssueBoardFeatureEnabled: parseBoolean(el.dataset.scopedIssueBoardFeatureEnabled),
8497
allowSubEpics: false,
8598
hasScopedLabelsFeature: parseBoolean(hasScopedLabelsFeature),
99+
hasIterationsFeature: parseBoolean(el.dataset.iterationFeatureAvailable),
100+
hasIssueWeightsFeature: parseBoolean(el.dataset.weightFeatureAvailable),
101+
hasIssuableHealthStatusFeature: parseBoolean(el.dataset.healthStatusFeatureAvailable),
102+
hasSubepicsFeature: parseBoolean(el.dataset.subEpicsFeatureAvailable),
86103
},
87104
render: (createComponent) => createComponent(BoardApp),
88105
});

0 commit comments

Comments
 (0)