Skip to content

Commit 383ee13

Browse files
author
GitLab Bot
committed
Add latest changes from gitlab-org/gitlab@master
1 parent 5b90919 commit 383ee13

File tree

27 files changed

+571
-92
lines changed

27 files changed

+571
-92
lines changed

app/assets/javascripts/vue_shared/components/groups_list/groups_list.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export default {
4141
:key="group.id"
4242
:group="group"
4343
:show-group-icon="showGroupIcon"
44-
:class="listItemClass"
44+
:list-item-class="listItemClass"
4545
:timestamp-type="timestampType"
4646
@delete="$emit('delete', $event)"
4747
/>

app/assets/javascripts/vue_shared/components/groups_list/groups_list_item.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ export default {
4545
required: false,
4646
default: false,
4747
},
48+
listItemClass: {
49+
type: [String, Array, Object],
50+
required: false,
51+
default: '',
52+
},
4853
timestampType: {
4954
type: String,
5055
required: false,
@@ -125,6 +130,7 @@ export default {
125130
:show-icon="showGroupIcon"
126131
:icon-name="groupIconName"
127132
:actions="actions"
133+
:list-item-class="listItemClass"
128134
:timestamp-type="timestampType"
129135
>
130136
<template #avatar-meta>
@@ -180,5 +186,9 @@ export default {
180186
@change="onModalChange"
181187
/>
182188
</template>
189+
190+
<template #nested-items>
191+
<slot name="nested-items"></slot>
192+
</template>
183193
</list-item>
184194
</template>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const LIST_ITEM_TYPE_GROUP = 'group';
2+
export const LIST_ITEM_TYPE_PROJECT = 'project';
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import NestedGroupsProjectsList from './nested_groups_projects_list.vue';
2+
import { items } from './mock_data';
3+
4+
export default {
5+
component: NestedGroupsProjectsList,
6+
title: 'vue_shared/nested_groups_projects_list',
7+
};
8+
9+
const Template = (args, { argTypes }) => ({
10+
components: { NestedGroupsProjectsList },
11+
props: Object.keys(argTypes),
12+
template: `<nested-groups-projects-list :items="items" />`,
13+
});
14+
15+
export const Default = Template.bind({});
16+
Default.args = {
17+
items,
18+
};
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/* eslint-disable @gitlab/require-i18n-strings */
2+
import { uniqueId } from 'lodash';
3+
import { ACTION_EDIT, ACTION_DELETE } from '~/vue_shared/components/list_actions/constants';
4+
import { slugify } from '~/lib/utils/text_utility';
5+
import { LIST_ITEM_TYPE_PROJECT, LIST_ITEM_TYPE_GROUP } from './constants';
6+
7+
const makeGroup = ({ name, fullName, children = [] }) => {
8+
const fullPath = slugify(fullName);
9+
10+
return {
11+
type: LIST_ITEM_TYPE_GROUP,
12+
markedForDeletionOn: null,
13+
isAdjournedDeletionEnabled: true,
14+
permanentDeletionDate: '2025-02-26',
15+
fullPath,
16+
descriptionHtml:
17+
'<p data-sourcepos="1:1-1:64" dir="auto">Sapiente excepturi est eos corrupti possimus praesentium quidem.</p>',
18+
avatarUrl: null,
19+
descendantGroupsCount: 0,
20+
projectsCount: 1,
21+
groupMembersCount: 5,
22+
visibility: 'public',
23+
createdAt: '2024-09-05T11:04:39Z',
24+
updatedAt: '2024-10-03T18:09:02Z',
25+
isLinkedToSubscription: true,
26+
id: parseInt(uniqueId(), 10),
27+
avatarLabel: name,
28+
fullName,
29+
webUrl: `https://gdk.test:3443/${fullPath}`,
30+
parent: null,
31+
accessLevel: { integerValue: 50 },
32+
editPath: `/${fullPath}/edit`,
33+
availableActions: [ACTION_EDIT, ACTION_DELETE],
34+
actionLoadingStates: { [ACTION_DELETE]: false },
35+
children,
36+
};
37+
};
38+
39+
const makeProject = ({ name, nameWithNamespace }) => {
40+
const fullPath = slugify(nameWithNamespace);
41+
42+
return {
43+
type: LIST_ITEM_TYPE_PROJECT,
44+
markedForDeletionOn: null,
45+
isAdjournedDeletionEnabled: true,
46+
permanentDeletionDate: '2025-02-26',
47+
fullPath,
48+
archived: false,
49+
webUrl: `https://gdk.test:3443/${fullPath}`,
50+
topics: [],
51+
forksCount: 1,
52+
avatarUrl: null,
53+
starCount: 1,
54+
visibility: 'public',
55+
openMergeRequestsCount: 4,
56+
openIssuesCount: 47,
57+
descriptionHtml:
58+
'<p data-sourcepos="1:1-1:503" dir="auto">Qui nostrum occaecati eum quo dicta quam. Qui nostrum occaecati eum quo dicta quam. Qui nostrum occaecati eum quo dicta quam. Qui nostrum occaecati eum quo dicta quam. Qui nostrum occaecati eum quo dicta quam. Qui nostrum occaecati eum quo dicta quam. Qui nostrum occaecati eum quo dicta quam. Qui nostrum occaecati eum quo dicta quam. Qui nostrum occaecati eum quo dicta quam. Qui nostrum occaecati eum quo dicta quam. Qui nostrum occaecati eum quo dicta quam. Qui nostrum occaecati eum quo dicta quam.</p>',
59+
createdAt: '2024-09-05T11:04:37Z',
60+
updatedAt: '2024-12-12T18:33:54Z',
61+
lastActivityAt: '2024-12-12T18:33:54Z',
62+
userPermissions: {
63+
removeProject: true,
64+
viewEditPage: true,
65+
},
66+
isCatalogResource: false,
67+
exploreCatalogPath: null,
68+
pipeline: {
69+
detailedStatus: {
70+
id: 'failed-566-566',
71+
icon: 'status_failed',
72+
text: 'Failed',
73+
detailsPath: `/${fullPath}/-/pipelines/566`,
74+
},
75+
},
76+
id: parseInt(uniqueId(), 10),
77+
name,
78+
nameWithNamespace,
79+
avatarLabel: name,
80+
mergeRequestsAccessLevel: 'ENABLED',
81+
issuesAccessLevel: 'ENABLED',
82+
forkingAccessLevel: 'ENABLED',
83+
isForked: false,
84+
accessLevel: { integerValue: 50 },
85+
availableActions: [ACTION_EDIT, ACTION_DELETE],
86+
editPath: `/${fullPath}/edit`,
87+
};
88+
};
89+
90+
export const projectA = makeProject({
91+
name: 'Project A',
92+
nameWithNamespace: 'Subgroup A / Nested subgroup / Project A',
93+
});
94+
95+
export const projectB = makeProject({
96+
name: 'Project B',
97+
nameWithNamespace: 'Subgroup A / Project B',
98+
});
99+
100+
export const nestedSubgroup = makeGroup({
101+
name: 'Nested subgroup',
102+
fullName: 'Subgroup A / Nested subgroup',
103+
children: [projectA],
104+
});
105+
106+
export const subgroupA = makeGroup({
107+
name: 'Subgroup A',
108+
fullName: 'Subgroup A',
109+
children: [nestedSubgroup, projectB],
110+
});
111+
112+
export const subgroupB = makeGroup({
113+
name: 'Subgroup B',
114+
fullName: 'Subgroup B',
115+
});
116+
117+
export const items = [subgroupA, subgroupB];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<script>
2+
export default {
3+
props: {
4+
items: {
5+
type: Array,
6+
required: true,
7+
},
8+
},
9+
beforeCreate() {
10+
// https://v2.vuejs.org/v2/guide/components-edge-cases.html?redirect=true#Circular-References-Between-Components
11+
this.$options.components.NestedGroupsProjectsListItem =
12+
// eslint-disable-next-line global-require
13+
require('./nested_groups_projects_list_item.vue').default;
14+
},
15+
};
16+
</script>
17+
18+
<template>
19+
<ul class="gl-m-0 gl-w-full gl-list-none gl-p-0">
20+
<!-- eslint-disable-next-line vue/no-undef-components -->
21+
<nested-groups-projects-list-item
22+
v-for="item in items"
23+
:key="`${item.type}-${item.id}`"
24+
:item="item"
25+
/>
26+
</ul>
27+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<script>
2+
import ProjectsListItem from '../projects_list/projects_list_item.vue';
3+
import GroupsListItem from '../groups_list/groups_list_item.vue';
4+
import NestedGroupsProjectsList from './nested_groups_projects_list.vue';
5+
import { LIST_ITEM_TYPE_PROJECT } from './constants';
6+
7+
export default {
8+
components: {
9+
NestedGroupsProjectsList,
10+
},
11+
props: {
12+
item: {
13+
type: Object,
14+
required: true,
15+
},
16+
},
17+
computed: {
18+
itemComponent() {
19+
return this.item.type === LIST_ITEM_TYPE_PROJECT ? ProjectsListItem : GroupsListItem;
20+
},
21+
itemProps() {
22+
return this.item.type === LIST_ITEM_TYPE_PROJECT
23+
? { project: this.item, showProjectIcon: true }
24+
: { group: this.item, showGroupIcon: true };
25+
},
26+
hasChildren() {
27+
return this.item.children?.length;
28+
},
29+
},
30+
};
31+
</script>
32+
33+
<template>
34+
<component :is="itemComponent" v-bind="itemProps">
35+
<template v-if="hasChildren" #nested-items>
36+
<nested-groups-projects-list :items="item.children" class="gl-pl-4" />
37+
</template>
38+
</component>
39+
</template>

app/assets/javascripts/vue_shared/components/projects_list/projects_list.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export default {
6464
:key="project.id"
6565
:project="project"
6666
:show-project-icon="showProjectIcon"
67-
:class="listItemClass"
67+
:list-item-class="listItemClass"
6868
:timestamp-type="timestampType"
6969
@refetch="$emit('refetch')"
7070
/>

app/assets/javascripts/vue_shared/components/projects_list/projects_list_item.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ export default {
9595
required: false,
9696
default: false,
9797
},
98+
listItemClass: {
99+
type: [String, Array, Object],
100+
required: false,
101+
default: '',
102+
},
98103
timestampType: {
99104
type: String,
100105
required: false,
@@ -225,6 +230,7 @@ export default {
225230
:resource="project"
226231
:show-icon="showProjectIcon"
227232
icon-name="project"
233+
:list-item-class="listItemClass"
228234
:timestamp-type="timestampType"
229235
:data-testid="dataTestid"
230236
content-testid="project-content"

app/assets/javascripts/vue_shared/components/resource_lists/list_item.vue

Lines changed: 53 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ export default {
6565
return TIMESTAMP_TYPES.includes(value);
6666
},
6767
},
68+
listItemClass: {
69+
type: [String, Array, Object],
70+
required: false,
71+
default: '',
72+
},
6873
contentTestid: {
6974
type: String,
7075
required: false,
@@ -92,59 +97,62 @@ export default {
9297
</script>
9398
9499
<template>
95-
<li class="gl-border-b gl-flex gl-items-start gl-py-4">
96-
<div class="gl-grow gl-items-start md:gl-flex">
97-
<div class="gl-flex gl-grow" :data-testid="contentTestid">
98-
<div v-if="showIcon" class="gl-mr-3 gl-flex gl-h-7 gl-shrink-0 gl-items-center">
99-
<gl-icon variant="subtle" :name="iconName" />
100-
</div>
101-
<gl-avatar-labeled
102-
class="gl-break-anywhere"
103-
:entity-id="resource.id"
104-
:entity-name="resource.avatarLabel"
105-
:label="resource.avatarLabel"
106-
:label-link="resource.webUrl"
107-
:src="resource.avatarUrl"
108-
shape="rect"
109-
:size="32"
110-
>
111-
<template #meta>
112-
<div class="gl-px-1">
113-
<div class="gl-flex gl-flex-wrap gl-items-center gl-gap-2">
114-
<slot name="avatar-meta"></slot>
100+
<li>
101+
<div class="gl-border-b gl-flex gl-items-start gl-py-4" :class="listItemClass">
102+
<div class="gl-grow gl-items-start md:gl-flex">
103+
<div class="gl-flex gl-grow" :data-testid="contentTestid">
104+
<div v-if="showIcon" class="gl-mr-3 gl-flex gl-h-7 gl-shrink-0 gl-items-center">
105+
<gl-icon variant="subtle" :name="iconName" />
106+
</div>
107+
<gl-avatar-labeled
108+
class="gl-break-anywhere"
109+
:entity-id="resource.id"
110+
:entity-name="resource.avatarLabel"
111+
:label="resource.avatarLabel"
112+
:label-link="resource.webUrl"
113+
:src="resource.avatarUrl"
114+
shape="rect"
115+
:size="32"
116+
>
117+
<template #meta>
118+
<div class="gl-px-1">
119+
<div class="gl-flex gl-flex-wrap gl-items-center gl-gap-2">
120+
<slot name="avatar-meta"></slot>
121+
</div>
115122
</div>
116-
</div>
117-
</template>
118-
<slot name="avatar-default">
119-
<list-item-description
120-
v-if="resource.descriptionHtml"
121-
:description-html="resource.descriptionHtml"
122-
/>
123-
</slot>
124-
</gl-avatar-labeled>
125-
</div>
126-
<div
127-
class="gl-mt-3 gl-shrink-0 gl-flex-col gl-items-end md:gl-mt-0 md:gl-flex md:gl-pl-3"
128-
:class="statsPadding"
129-
>
130-
<div class="gl-flex gl-items-center gl-gap-x-3 md:gl-h-5">
131-
<slot name="stats"></slot>
123+
</template>
124+
<slot name="avatar-default">
125+
<list-item-description
126+
v-if="resource.descriptionHtml"
127+
:description-html="resource.descriptionHtml"
128+
/>
129+
</slot>
130+
</gl-avatar-labeled>
132131
</div>
133132
<div
134-
v-if="timestamp"
135-
class="gl-mt-2 gl-whitespace-nowrap gl-text-sm gl-leading-1 gl-text-subtle"
133+
class="gl-mt-3 gl-shrink-0 gl-flex-col gl-items-end md:gl-mt-0 md:gl-flex md:gl-pl-3"
134+
:class="statsPadding"
136135
>
137-
<span>{{ timestampText }}</span>
138-
<time-ago-tooltip :time="timestamp" />
136+
<div class="gl-flex gl-items-center gl-gap-x-3 md:gl-h-5">
137+
<slot name="stats"></slot>
138+
</div>
139+
<div
140+
v-if="timestamp"
141+
class="gl-mt-2 gl-whitespace-nowrap gl-text-sm gl-leading-1 gl-text-subtle"
142+
>
143+
<span>{{ timestampText }}</span>
144+
<time-ago-tooltip :time="timestamp" />
145+
</div>
139146
</div>
140147
</div>
141-
</div>
142-
<div v-if="hasActions" class="-gl-mt-3 gl-ml-3 gl-flex gl-items-center">
143-
<slot name="actions">
144-
<list-actions :actions="actions" :available-actions="resource.availableActions" />
145-
</slot>
148+
<div v-if="hasActions" class="-gl-mt-3 gl-ml-3 gl-flex gl-items-center">
149+
<slot name="actions">
150+
<list-actions :actions="actions" :available-actions="resource.availableActions" />
151+
</slot>
152+
</div>
146153
</div>
147154
148155
<slot name="footer"></slot>
156+
<slot name="nested-items"></slot>
149157
</li>
150158
</template>

0 commit comments

Comments
 (0)