Skip to content

Commit 37e436d

Browse files
committed
fix(webapp): only load env var values for displayed environments
The environment variables page loaded every value in the project, including rows left behind by archived preview-branch environments, which made the loader extremely slow for projects that churn branches. The values relation is now filtered to the environments the page actually displays.
1 parent cc9eabd commit 37e436d

3 files changed

Lines changed: 130 additions & 7 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
Speed up the environment variables page for projects with many archived preview branches. The page now only loads variable values for the environments it displays instead of every value ever created, including those left behind by archived branches.

apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ export class EnvironmentVariablesPresenter {
4141
throw new Error("Project not found");
4242
}
4343

44+
const { environments: sortedEnvironments, hasStaging } =
45+
await loadEnvironmentVariablesEnvironments(
46+
this.#prismaClient,
47+
{ userId, projectId: project.id },
48+
{ skipProjectAccessCheck: true }
49+
);
50+
51+
// Only load values for the environments we display. Projects can accumulate
52+
// values in archived branch environments, which would otherwise all be loaded here.
53+
const environmentIds = sortedEnvironments.map((env) => env.id);
54+
4455
const environmentVariables = await this.#prismaClient.environmentVariable.findMany({
4556
select: {
4657
id: true,
@@ -59,6 +70,11 @@ export class EnvironmentVariablesPresenter {
5970
},
6071
isSecret: true,
6172
},
73+
where: {
74+
environmentId: {
75+
in: environmentIds,
76+
},
77+
},
6278
},
6379
},
6480
where: {
@@ -102,13 +118,6 @@ export class EnvironmentVariablesPresenter {
102118
const usersRecord: Record<string, { id: string; name: string | null; displayName: string | null; avatarUrl: string | null }> =
103119
Object.fromEntries(users.map((u) => [u.id, u]));
104120

105-
const { environments: sortedEnvironments, hasStaging } =
106-
await loadEnvironmentVariablesEnvironments(
107-
this.#prismaClient,
108-
{ userId, projectId: project.id },
109-
{ skipProjectAccessCheck: true }
110-
);
111-
112121
const repository = new EnvironmentVariablesRepository(this.#prismaClient);
113122

114123
const nonSecretItems: Array<{ environmentId: string; key: string }> = [];

apps/webapp/test/EnvironmentVariablesPresenter.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,112 @@ describe("EnvironmentVariablesPresenter", () => {
6969
expect(secretVariable!.value).toBe("");
7070
expect(nonSecretVariable!.value).toBe("plain-value");
7171
});
72+
73+
postgresTest(
74+
"returns values for active environments (including branch environments) and excludes archived branch environments",
75+
async ({ prisma }) => {
76+
const { user, organization, project, projectSlug } =
77+
await createTestOrgProjectWithMember(prisma);
78+
79+
const prodEnvironment = await createRuntimeEnvironment(prisma, {
80+
projectId: project.id,
81+
organizationId: organization.id,
82+
type: "PRODUCTION",
83+
});
84+
85+
const parentPreviewEnvironment = await createRuntimeEnvironment(prisma, {
86+
projectId: project.id,
87+
organizationId: organization.id,
88+
type: "PREVIEW",
89+
});
90+
await prisma.runtimeEnvironment.update({
91+
where: { id: parentPreviewEnvironment.id },
92+
data: { isBranchableEnvironment: true },
93+
});
94+
95+
const activeBranchEnvironment = await createRuntimeEnvironment(prisma, {
96+
projectId: project.id,
97+
organizationId: organization.id,
98+
type: "PREVIEW",
99+
});
100+
await prisma.runtimeEnvironment.update({
101+
where: { id: activeBranchEnvironment.id },
102+
data: {
103+
parentEnvironmentId: parentPreviewEnvironment.id,
104+
branchName: "feature/active",
105+
},
106+
});
107+
108+
const archivedBranchEnvironment = await createRuntimeEnvironment(prisma, {
109+
projectId: project.id,
110+
organizationId: organization.id,
111+
type: "PREVIEW",
112+
});
113+
await prisma.runtimeEnvironment.update({
114+
where: { id: archivedBranchEnvironment.id },
115+
data: {
116+
parentEnvironmentId: parentPreviewEnvironment.id,
117+
branchName: "feature/archived",
118+
},
119+
});
120+
121+
const repository = new EnvironmentVariablesRepository(prisma, prisma);
122+
123+
await createEnvironmentVariable(repository, project.id, {
124+
environmentId: prodEnvironment.id,
125+
key: "MY_VAR",
126+
value: "prod-value",
127+
userId: user.id,
128+
});
129+
await createEnvironmentVariable(repository, project.id, {
130+
environmentId: activeBranchEnvironment.id,
131+
key: "MY_VAR",
132+
value: "active-branch-value",
133+
userId: user.id,
134+
});
135+
await createEnvironmentVariable(repository, project.id, {
136+
environmentId: archivedBranchEnvironment.id,
137+
key: "MY_VAR",
138+
value: "archived-branch-value",
139+
userId: user.id,
140+
});
141+
142+
// Archive the branch after it accumulated values (archiving does not
143+
// delete its EnvironmentVariableValue rows).
144+
await prisma.runtimeEnvironment.update({
145+
where: { id: archivedBranchEnvironment.id },
146+
data: { archivedAt: new Date() },
147+
});
148+
149+
const result = await new EnvironmentVariablesPresenter(prisma).call({
150+
userId: user.id,
151+
projectSlug,
152+
});
153+
154+
const environmentIds = result.environments.map((environment) => environment.id);
155+
expect(environmentIds).toContain(prodEnvironment.id);
156+
expect(environmentIds).toContain(activeBranchEnvironment.id);
157+
expect(environmentIds).not.toContain(archivedBranchEnvironment.id);
158+
159+
const myVarValues = result.environmentVariables.filter(
160+
(variable) => variable.key === "MY_VAR"
161+
);
162+
expect(myVarValues).toHaveLength(2);
163+
164+
const prodValue = myVarValues.find(
165+
(variable) => variable.environment.id === prodEnvironment.id
166+
);
167+
expect(prodValue?.value).toBe("prod-value");
168+
169+
const activeBranchValue = myVarValues.find(
170+
(variable) => variable.environment.id === activeBranchEnvironment.id
171+
);
172+
expect(activeBranchValue?.value).toBe("active-branch-value");
173+
expect(activeBranchValue?.environment.branchName).toBe("feature/active");
174+
175+
expect(
176+
myVarValues.some((variable) => variable.environment.id === archivedBranchEnvironment.id)
177+
).toBe(false);
178+
}
179+
);
72180
});

0 commit comments

Comments
 (0)