Skip to content

#4469 prisma query optimization - #4582

Open
getheobald wants to merge 15 commits into
developfrom
#4469-prisma-query-optimization
Open

getheobald wants to merge 15 commits into
developfrom
#4469-prisma-query-optimization

Conversation

@getheobald

@getheobald getheobald commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Changes

  • Gantt base types. Added GanttChartProject, GanttChartWorkPackage, GanttChartTask and GanttChartEvent (shared), with a matching query and transformer (gantt-chart.query-args.ts, gantt-chart.transformer.ts). GET /projects/all-gantt now fetches and returns only what the chart reads. Change history, description bullets, links, team types, blockers, event titles and creators, user emails and roles, and task notes, labels and priority are no longer loaded. Retrospective, team pages and the project-detail Gantt keep using the full types; the shared Gantt helpers accept both.
  • Index on Schedule_Slot.eventId (new migration).
  • Deterministic ordering for projects, work packages and tasks. Event markers use the earliest scheduled slot, fetching only that slot.
  • Event creators in the shared Gantt args now use the basic user shape, which removes private user fields from the team, change-request and project-template responses.
  • Fixes in the Gantt save flow:
    • Work-package edits were silently dropped (an early return left over from [Descoping CR] - Schema Changes #4168). They now create one standard change request per edited work package, using that work package's own dates and its other fields from the freshly loaded project.
    • Task edits send the Gantt's dates plus every other field from the freshly loaded project.
    • Assignee changes are compared regardless of order.
    • The save modal now renders above the chart's highlighted bar (z-index).
  • Seed: events now link to work packages (event.workPackageLinkWeights).
  • Result (local, same data: 204 projects, 948 work packages, 2,912 tasks): median 1,882 → 173 ms, response 7.68 → 1.85 MB.

Notes

  • relationLoadStrategy stays on the default (join). query was faster locally, but it's on hold pending the prod connection and latency discussion.
  • User-visible behavior changes:
    • Gantt rows now come back in a fixed order.
    • Event markers show the earliest slot, so markers for recurring events may move.
    • Saving a work-package drag now creates change requests. It previously did nothing.
  • Security: event creators previously included private fields such as icsToken, googleAuthId, personal Gmail and availability in the all-gantt, team, change-request and template responses. Those are no longer sent.
  • Migration added.
  • The base types are meant to be extended by retro mode later in the epic.
  • Review comment about unused changes is addressed by the base type.

Test Cases

  • Viewing: projects grouped by team; car, team, team-type, overdue and hide-tasks filters and search work; tooltips show lead/manager (projects, work packages) and creator/assignees (tasks); event markers and dependency arrows render.
  • Case A, work package: drag or resize a work package, enter an explanation, save. One change request is created per edited work package; the work package doesn't change until the change request is approved.
  • Case B, task: drag or resize an existing task, save. Only its start date and deadline change; title, notes, priority, labels, blockers and assignees are preserved.
  • Case C, add a task: add a task on the Gantt, save. It's created with the entered fields.
  • Case D, create a project: create a project from the Gantt. It's created on the selected team.
  • Edge cases:
    • Submit is disabled for work-package edits until an explanation is entered.
    • The save modal is fully visible above the highlighted bar.
    • Dragging and then cancelling saves nothing.
    • Changes made to a task elsewhere after the Gantt loaded aren't overwritten by a Gantt save.
    • Reordered assignees don't count as an edit.
  • Automated: backend and frontend tests pass.

Screenshots

If you made UI changes you must post a screenshot of the whole page for each change in 1) a normal sized window and 2) the smallest possible window

If you did any manual testing (e.g., with Postman), put screenshots of the http request and before and after of the db

If none of this applies, you can delete this section

To Do

Any remaining things that need to get done

  • item 1
  • ...

Checklist

It can be helpful to check the Checks and Files changed tabs.
Please review the contributor guide and reach out to your Tech Lead if anything is unclear.
Please request reviewers and ping on slack only after you've gone through this whole checklist.

  • All commits are tagged with the ticket number
  • No linting errors / newline at end of file warnings
  • All code follows repository-configured prettier formatting
  • No merge conflicts
  • All checks passing
  • Screenshots of UI changes (see Screenshots section)
  • Remove any non-applicable sections of this template
  • Assign the PR to yourself
  • No yarn.lock changes (unless dependencies have changed)
  • Request reviewers & ping on Slack
  • PR is linked to the ticket (fill in the closes line below)

Closes #4469
Closes #4477

@getheobald
getheobald requested a review from wavehassman October 5, 2026 04:04
@getheobald

Copy link
Copy Markdown
Contributor Author

getProjectGanttQueryArgs analysis
• One big query rather than n+1 which is good
○ Everything is expressed as nested includes in in a single Prisma.validator, so prisma will generate one query with multi-le joins rather than separate queries per project
• But nesting depth and breadth still makes this query very expensive
○ favoritedBy pulls every user who favorited this project, each fetched with the full getUserQueryArgs, which may pull a bunch of data per user
• getUserQueryArgs is called 3 times in this one query (lead, manager, favoritedBy)
• No 'where' filtering at the top level

getWorkPackageQueryArgs
• Includes project, but we're already inside project.workPackages in the original query, so each WP is re-fetching its own parent project, which is redundant
• Also, crazy depth for each WP
• Across the whole query, the full user shape is being requested a ton of times
For every project, for every WP in that project, for every CR on that WP, we're pulling a full nested user record plus further nested relations

title: true,
status: true,
initialDateScheduled: true,
scheduledTimes: true,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need all slots?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah nope, switching to just the first bc the preview only reads the first slot's startTime

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note about this: I'm having it always select the earliest slot, which seems like the most stable and logical choice. However, if we want to show recurring events differently, we could switch it to one marker per slot but that could get super crowded

const projects = await prisma.project.findMany({
where: { wbsElement: { dateDeleted: null, organizationId: organization.organizationId }, ...(carId && { carId }) },
orderBy: [{ wbsElement: { carNumber: 'asc' } }, { wbsElement: { projectNumber: 'asc' } }],
relationLoadStrategy: 'query',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flagging this

@getheobald

Copy link
Copy Markdown
Contributor Author

Step 0: Tried relationLoadStrategy: 'query' on getAllProjectsGantt in ProjectsService, which made a big speed improvement. Before doing that, because of the confusion with the order not matching, I decided to implement ordering, so projects are now sorted by car number then project number. Work packages are sorted by orderInProject, then WP number. Tasks are sorted by dateCreated, then taskId. Changes sort by date, then changeId. Description bullets sort by dateAdded, then descriptionId. This is about 5x faster than before (1700ms to 340ms).
Step 1: Dropped favoritedBy on the query which didn't impact speed but was just general code cleanup because it wasn't used anywhere.
Step 2: Added the missing eventId index.
Step 3: New getEventPreviewQueryArgs selects only the fields the event preview uses (eventId, title, status, initialDateScheduled, scheduledTimes, userCreated). Previously each event loaded every member list, teams, shops, machinery, its work packages and their projects, documents and Slack threads. Applied only on the Gantt path. The shared work-package and event query args are unchanged. From 470 to 387ms median.

@getheobald

Copy link
Copy Markdown
Contributor Author

Also fixed an early return editing error, which was blocking #4477

  1. The early return: it now bails out only if work packages were edited and there's no explanation, which was the original intent. The submit button already enforced that.
  2. Duplicate work packages: the old code sent one project-level change request listing the edited work packages. When a project change request is approved, the backend creates those as new work packages (applyWorkPackageProposedChanges → createWorkPackage), so approval would have duplicated them. The fix sends one change request per edited work package, scoped to that work package, the same way the work-package edit form does. Approval then edits the existing work package.
  3. Wrong start date: the old code set every edited work package's start to the project's new start. The fix uses each work package's own dragged start date and duration. Name, stage, blockers, description bullets and lead/manager come from the full project the modal already loads, not from the Gantt's copy.
  4. Cleanup: removed the dangling "Reason for Change" label left over from [Descoping CR] - Schema Changes #4168, plus an unused link helper.
  5. Errors: if one work package's change request fails (for example, it already has an unreviewed one open), that work package gets an error toast and the others still go through.

@getheobald

Copy link
Copy Markdown
Contributor Author

That last commit made it so a task now counts as edited only if its set of assignees actually changed. Previously it also depended on index but they weren't returned in any consistent order so it was unnecessarily re-saving

@getheobald
getheobald force-pushed the #4469-prisma-query-optimization branch from f677ecd to e28e468 Compare October 15, 2026 22:12
@getheobald
getheobald marked this pull request as ready for review October 15, 2026 23:20
@getheobald getheobald self-assigned this Oct 15, 2026
@wavehassman
wavehassman self-requested a review October 15, 2026 23:55

@wavehassman wavehassman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to rethink how we think about this and focus on optimizing the query args we have and not making a ton new ones. Check if the previews can be taken down or if there are other query args that are extremely close to the ones you made

export type GanttChartWorkPackageQueryArgs = ReturnType<typeof getGanttChartWorkPackageQueryArgs>;
export type GanttChartProjectQueryArgs = ReturnType<typeof getGanttChartProjectQueryArgs>;

const wbsNumberSelect = { carNumber: true, projectNumber: true, workPackageNumber: true } as const;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new query arg


// same output as getTaskQueryArgs through taskTransformer for the gantt's tasks, which are non-deleted (so deletedBy is
// skipped) and project-level (a project's wbs element has no work package, so its blocking work packages are skipped)
export const getTaskGanttQueryArgs = () =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is different enough from the preview or potentially this could just replace the preview

const wbsNumberSelect = { carNumber: true, projectNumber: true, workPackageNumber: true } as const;

// a task bar and its tooltip; saving an edited task reads its other fields from the full project
export const getGanttChartTaskQueryArgs = () =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this and event preview are nearly identical

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Gantt Chart] - 1.5.3 Drag-to-edit snap-to-release [Gantt Chart] - 1.3 Prisma query optimization

2 participants