Skip to content

Commit 8296ee5

Browse files
joanagmaiaclaude
andcommitted
perf: defer subproject fetch and eliminate duplicate activity calls on load
- Fix router guard forEach async bug: replace forEach with for...of so auth middleware actually awaits before segment lookup runs, eliminating the pre-token /segment/{id} request - Replace resolveProjectGroupForSelection (findSegment call) with flat /segment/subproject/query filtered by grandparentSlug; store result in selectedProjectGroupSubprojects state - activity-timeline and lf-banners now read from selectedProjectGroupSubprojects instead of walking projectGroup.projects[].subprojects[] - utils/segments: getSegmentsFromProjectGroup reads subproject IDs from store - Remove duplicate fetchActivityTypes/fetchActivityChannels from App.vue tenant watcher; menu.vue watcher already handles these correctly once a project group is selected Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org>
1 parent 1bc70e4 commit 8296ee5

9 files changed

Lines changed: 64 additions & 90 deletions

File tree

frontend/src/app.vue

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@
2323
<script>
2424
import { mapActions } from 'vuex';
2525
import AppResizePage from '@/modules/layout/pages/resize-page.vue';
26-
import { mapActions as piniaMapActions, storeToRefs } from 'pinia';
27-
import { useActivityStore } from '@/modules/activity/store/pinia';
28-
import { useActivityTypeStore } from '@/modules/activity/store/type';
26+
import { storeToRefs } from 'pinia';
2927
import { useAuthStore } from '@/modules/auth/store/auth.store';
3028
import useSessionTracking from '@/shared/modules/monitoring/useSessionTracking';
3129
import { useLfSegmentsStore } from '@/modules/lf/segments/store';
@@ -58,8 +56,6 @@ export default {
5856
tenant: {
5957
handler(tenant, oldTenant) {
6058
if (tenant?.id && tenant.id !== oldTenant?.id) {
61-
this.fetchActivityTypes();
62-
this.fetchActivityChannels();
6359
this.listProjectGroups({
6460
limit: 20,
6561
reset: true,
@@ -91,12 +87,6 @@ export default {
9187
...mapActions({
9288
resize: 'layout/resize',
9389
}),
94-
...piniaMapActions(useActivityStore, {
95-
fetchActivityChannels: 'fetchActivityChannels',
96-
}),
97-
...piniaMapActions(useActivityTypeStore, {
98-
fetchActivityTypes: 'fetchActivityTypes',
99-
}),
10090
10191
handleResize() {
10292
this.resize({

frontend/src/modules/activity/components/activity-timeline.vue

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ const props = defineProps({
257257
});
258258
259259
const lsSegmentsStore = useLfSegmentsStore();
260-
const { projectGroups, selectedProjectGroup } = storeToRefs(lsSegmentsStore);
260+
const { selectedProjectGroup, selectedProjectGroupSubprojects } = storeToRefs(lsSegmentsStore);
261261
262262
const enabledPlatforms: IdentityConfig[] = Object.values(lfIdentities);
263263
@@ -274,16 +274,8 @@ const selectedSegment = ref(props.selectedSegment || null);
274274
275275
const isMemberEntity = computed(() => props.entityType === 'member');
276276
277-
const subprojects = computed(() => projectGroups.value.list.reduce((acc, projectGroup) => {
278-
(projectGroup.projects || []).forEach((project) => {
279-
(project.subprojects || []).forEach((subproject) => {
280-
acc[subproject.id] = {
281-
id: subproject.id,
282-
name: subproject.name,
283-
};
284-
});
285-
});
286-
277+
const subprojects = computed(() => selectedProjectGroupSubprojects.value.reduce((acc, sp) => {
278+
acc[sp.id] = { id: sp.id, name: sp.name };
287279
return acc;
288280
}, {}));
289281

frontend/src/modules/lf/layout/components/lf-banners.vue

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,11 @@ import LfButton from '@/ui-kit/button/Button.vue';
158158
const ERROR_BANNER_WORKING_DAYS_DISPLAY = 3;
159159
160160
const lsSegmentsStore = useLfSegmentsStore();
161-
const { selectedProjectGroup } = storeToRefs(lsSegmentsStore);
161+
const { selectedProjectGroup, selectedProjectGroupSubprojects } = storeToRefs(lsSegmentsStore);
162162
const integrations = ref([]);
163163
const fetchIntegrationTimer = ref(null);
164164
const loading = ref(true);
165-
const subProjects = ref([]);
165+
const subProjects = computed(() => selectedProjectGroupSubprojects.value);
166166
167167
const route = useRoute();
168168
@@ -252,21 +252,6 @@ watch(selectedProjectGroup, (updatedProjectGroup, previousProjectGroup) => {
252252
loading.value = true;
253253
fetchIntegrations(updatedProjectGroup);
254254
}
255-
256-
if (!updatedProjectGroup) {
257-
subProjects.value = [];
258-
} else {
259-
subProjects.value = updatedProjectGroup.projects
260-
.reduce((acc, project) => {
261-
project.subprojects.forEach((subproject) => {
262-
if (subproject) {
263-
acc.push(subproject);
264-
}
265-
});
266-
267-
return acc;
268-
}, []);
269-
}
270255
}, {
271256
deep: true,
272257
immediate: true,

frontend/src/modules/lf/segments/lf-segments-service.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,18 @@ export class LfService {
8989
return response.data;
9090
}
9191

92+
static async querySubprojects(body) {
93+
const response = await authAxios.post(
94+
'/segment/subproject/query',
95+
{
96+
...body,
97+
excludeSegments: true,
98+
},
99+
);
100+
101+
return response.data;
102+
}
103+
92104
// Sub-projects
93105

94106
static async createSubProject(body) {

frontend/src/modules/lf/segments/store/actions.js

Lines changed: 33 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,6 @@ const isAdminOnly = () => {
1414
return roles.value.includes(LfRole.projectAdmin);
1515
};
1616

17-
const hasNestedProjects = (projectGroup) => Array.isArray(projectGroup?.projects)
18-
&& projectGroup.projects.some((p) => Array.isArray(p.subprojects) && p.subprojects.length > 0);
19-
20-
const resolveProjectGroupForSelection = async (projectGroupOrId, list) => {
21-
if (!projectGroupOrId) {
22-
return null;
23-
}
24-
25-
const id = typeof projectGroupOrId === 'string' ? projectGroupOrId : projectGroupOrId.id;
26-
if (!id) {
27-
return null;
28-
}
29-
30-
const fromArg = typeof projectGroupOrId === 'object' ? projectGroupOrId : null;
31-
const fromList = list.find((p) => p.id === id) || null;
32-
33-
if (hasNestedProjects(fromArg)) {
34-
return fromArg;
35-
}
36-
if (hasNestedProjects(fromList)) {
37-
return fromList;
38-
}
39-
40-
return LfService.findSegment(id);
41-
};
4217

4318
export default {
4419
// Project Groups
@@ -296,35 +271,53 @@ export default {
296271

297272
this.listProjects();
298273
},
274+
async fetchSubprojectsForProjectGroup(grandparentSlug) {
275+
if (!grandparentSlug) {
276+
this.selectedProjectGroupSubprojects = [];
277+
return;
278+
}
279+
try {
280+
const response = await LfService.querySubprojects({
281+
limit: null,
282+
offset: 0,
283+
filter: { grandparentSlug },
284+
});
285+
this.selectedProjectGroupSubprojects = response.rows || [];
286+
} catch (e) {
287+
this.selectedProjectGroupSubprojects = [];
288+
}
289+
},
290+
299291
async updateSelectedProjectGroup(projectGroupOrId, sendToDashboard = true) {
300292
if (!projectGroupOrId) {
301293
this.selectedProjectGroup = null;
294+
this.selectedProjectGroupSubprojects = [];
295+
return;
296+
}
297+
298+
const id = typeof projectGroupOrId === 'string' ? projectGroupOrId : projectGroupOrId.id;
299+
if (!id) {
300+
this.selectedProjectGroup = null;
301+
this.selectedProjectGroupSubprojects = [];
302302
return;
303303
}
304304

305305
try {
306-
const projectGroup = await resolveProjectGroupForSelection(
307-
projectGroupOrId,
308-
this.projectGroups.list,
309-
);
306+
let projectGroup = this.projectGroups.list.find((p) => p.id === id) || null;
307+
if (!projectGroup) {
308+
await this.listProjectGroups({ limit: null, reset: true });
309+
projectGroup = this.projectGroups.list.find((p) => p.id === id) || null;
310+
}
310311

311312
if (!projectGroup?.id) {
312313
this.selectedProjectGroup = null;
314+
this.selectedProjectGroupSubprojects = [];
313315
ToastStore.error('Project group could not be loaded');
314316
return;
315317
}
316318

317319
this.selectedProjectGroup = projectGroup;
318-
319-
const idx = this.projectGroups.list.findIndex((p) => p.id === projectGroup.id);
320-
if (idx >= 0) {
321-
this.projectGroups.list[idx] = {
322-
...this.projectGroups.list[idx],
323-
...projectGroup,
324-
};
325-
} else {
326-
this.projectGroups.list = [...this.projectGroups.list, projectGroup];
327-
}
320+
this.fetchSubprojectsForProjectGroup(projectGroup.slug);
328321

329322
if (sendToDashboard) {
330323
router.push({
@@ -333,6 +326,7 @@ export default {
333326
}
334327
} catch (e) {
335328
this.selectedProjectGroup = null;
329+
this.selectedProjectGroupSubprojects = [];
336330
ToastStore.error('Something went wrong while selecting the project group');
337331
}
338332
},

frontend/src/modules/lf/segments/store/state.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { ProjectGroup, Project } from '@/modules/lf/segments/types/Segments';
1+
import { ProjectGroup, Project, SubProject } from '@/modules/lf/segments/types/Segments';
22

33
export interface SegmentsState {
44
selectedProjectGroup: ProjectGroup | null
5+
selectedProjectGroupSubprojects: SubProject[]
56
adminProjectGroups: {
67
list: ProjectGroup[]
78
}
@@ -32,6 +33,7 @@ export interface SegmentsState {
3233

3334
const state: SegmentsState = {
3435
selectedProjectGroup: null,
36+
selectedProjectGroupSubprojects: [],
3537
adminProjectGroups: {
3638
list: [],
3739
},

frontend/src/modules/lf/segments/types/Segments.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ export interface ProjectGroup {
5050
activityChannels: object;
5151
createdAt: string;
5252
updatedAt: string;
53-
projects: Project[];
53+
projects?: Project[];
54+
projectCount?: number;
5455
isLF: boolean;
5556
}
5657

frontend/src/router/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,10 @@ export const createRouter = () => {
8585
store,
8686
};
8787

88-
await middlewareArray.forEach(async (middleware) => {
88+
for (const middleware of middlewareArray) {
89+
// eslint-disable-next-line no-await-in-loop
8990
await middleware(context);
90-
});
91+
}
9192

9293
// Redirect to project group landing pages if routes that require a selected project group
9394
// And no project group is selected

frontend/src/utils/segments.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,14 @@ export const getSegmentsFromProjectGroup = (
1919
return [projectGroup.id];
2020
}
2121

22-
if (!projectGroup.projects?.length) {
22+
const lsSegmentsStore = useLfSegmentsStore();
23+
const { selectedProjectGroupSubprojects } = storeToRefs(lsSegmentsStore);
24+
25+
if (!selectedProjectGroupSubprojects.value.length) {
2326
return [projectGroup.id];
2427
}
2528

26-
return projectGroup.projects.reduce((acc, project) => {
27-
(project.subprojects || []).forEach((subproject) => {
28-
acc.push(subproject.id);
29-
});
30-
31-
return acc;
32-
}, []);
29+
return selectedProjectGroupSubprojects.value.map((sp) => sp.id);
3330
};
3431

3532
export const getProjectGroupsThroughSegments = (segments) => {

0 commit comments

Comments
 (0)