Skip to content

Commit 269b1c5

Browse files
authored
refactor: get rid of templateVariablesXService (#9763)
1 parent 530dd9d commit 269b1c5

File tree

7 files changed

+173
-361
lines changed

7 files changed

+173
-361
lines changed

site/src/api/errors.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,15 @@ export const mapApiErrorToFieldErrors = (
6161
export const getErrorMessage = (
6262
error: unknown,
6363
defaultMessage: string,
64-
): string => (isApiError(error) ? error.response.data.message : defaultMessage);
64+
): string => {
65+
if (isApiError(error)) {
66+
return error.response.data.message;
67+
}
68+
if (typeof error === "string") {
69+
return error;
70+
}
71+
return defaultMessage;
72+
};
6573

6674
/**
6775
*

site/src/api/queries/templates.ts

+59-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import * as API from "api/api";
2-
import { type Template, type AuthorizationResponse } from "api/typesGenerated";
3-
import { type QueryOptions } from "@tanstack/react-query";
2+
import {
3+
type Template,
4+
type AuthorizationResponse,
5+
type CreateTemplateVersionRequest,
6+
type ProvisionerJobStatus,
7+
type TemplateVersion,
8+
} from "api/typesGenerated";
9+
import { type QueryClient, type QueryOptions } from "@tanstack/react-query";
10+
import { delay } from "utils/delay";
411

512
export const templateByNameKey = (orgId: string, name: string) => [
613
orgId,
@@ -63,3 +70,53 @@ export const templateVersions = (templateId: string) => {
6370
queryFn: () => API.getTemplateVersions(templateId),
6471
};
6572
};
73+
74+
export const templateVersionVariables = (versionId: string) => {
75+
return {
76+
queryKey: ["templateVersion", versionId, "variables"],
77+
queryFn: () => API.getTemplateVersionVariables(versionId),
78+
};
79+
};
80+
81+
export const createAndBuildTemplateVersion = (orgId: string) => {
82+
return {
83+
mutationFn: async (
84+
request: CreateTemplateVersionRequest,
85+
): Promise<string> => {
86+
const newVersion = await API.createTemplateVersion(orgId, request);
87+
88+
let data: TemplateVersion;
89+
let jobStatus: ProvisionerJobStatus;
90+
do {
91+
await delay(1000);
92+
data = await API.getTemplateVersion(newVersion.id);
93+
jobStatus = data.job.status;
94+
95+
if (jobStatus === "succeeded") {
96+
return newVersion.id;
97+
}
98+
} while (jobStatus === "pending" || jobStatus === "running");
99+
100+
// No longer pending/running, but didn't succeed
101+
throw data.job.error;
102+
},
103+
};
104+
};
105+
106+
export const updateActiveTemplateVersion = (
107+
template: Template,
108+
queryClient: QueryClient,
109+
) => {
110+
return {
111+
mutationFn: (versionId: string) =>
112+
API.updateActiveTemplateVersion(template.id, {
113+
id: versionId,
114+
}),
115+
onSuccess: async () => {
116+
// invalidated because of `active_version_id`
117+
await queryClient.invalidateQueries(
118+
templateByNameKey(template.organization_id, template.name),
119+
);
120+
},
121+
};
122+
};

site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.test.tsx

+4-46
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,14 @@ import {
1313
MockTemplateVersionVariable1,
1414
MockTemplateVersionVariable2,
1515
MockTemplateVersion2,
16-
MockTemplateVersionVariable5,
1716
} from "testHelpers/entities";
17+
import { delay } from "utils/delay";
1818

1919
const validFormValues = {
2020
first_variable: "Hello world",
2121
second_variable: "123",
2222
};
2323

24-
const validationRequiredField = "Variable is required.";
25-
2624
const renderTemplateVariablesPage = async () => {
2725
renderWithTemplateSettingsLayout(<TemplateVariablesPage />, {
2826
route: `/templates/${MockTemplate.name}/variables`,
@@ -62,7 +60,7 @@ describe("TemplateVariablesPage", () => {
6260
jest.spyOn(API, "getTemplateByName").mockResolvedValueOnce(MockTemplate);
6361
jest
6462
.spyOn(API, "getTemplateVersion")
65-
.mockResolvedValueOnce(MockTemplateVersion);
63+
.mockResolvedValue(MockTemplateVersion);
6664
jest
6765
.spyOn(API, "getTemplateVersionVariables")
6866
.mockResolvedValueOnce([
@@ -106,49 +104,9 @@ describe("TemplateVariablesPage", () => {
106104
FooterFormLanguage.defaultSubmitLabel,
107105
);
108106
await userEvent.click(submitButton);
109-
110107
// Wait for the success message
111-
await screen.findByText("Template updated successfully");
112-
});
108+
await delay(1500);
113109

114-
it("user forgets to fill the required field", async () => {
115-
jest.spyOn(API, "getTemplateByName").mockResolvedValueOnce(MockTemplate);
116-
jest
117-
.spyOn(API, "getTemplateVersion")
118-
.mockResolvedValueOnce(MockTemplateVersion);
119-
jest
120-
.spyOn(API, "getTemplateVersionVariables")
121-
.mockResolvedValueOnce([
122-
MockTemplateVersionVariable1,
123-
MockTemplateVersionVariable5,
124-
]);
125-
jest
126-
.spyOn(API, "createTemplateVersion")
127-
.mockResolvedValueOnce(MockTemplateVersion2);
128-
jest.spyOn(API, "updateActiveTemplateVersion").mockResolvedValueOnce({
129-
message: "done",
130-
});
131-
132-
await renderTemplateVariablesPage();
133-
134-
const firstVariable = await screen.findByLabelText(
135-
MockTemplateVersionVariable1.name,
136-
);
137-
expect(firstVariable).toBeDefined();
138-
139-
const fifthVariable = await screen.findByLabelText(
140-
MockTemplateVersionVariable5.name,
141-
);
142-
expect(fifthVariable).toBeDefined();
143-
144-
// Submit the form
145-
const submitButton = await screen.findByText(
146-
FooterFormLanguage.defaultSubmitLabel,
147-
);
148-
await userEvent.click(submitButton);
149-
150-
// Check validation error
151-
const validationError = await screen.findByText(validationRequiredField);
152-
expect(validationError).toBeDefined();
110+
await screen.findByText("Template updated successfully");
153111
});
154112
});

site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.tsx

+72-33
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,88 @@
1-
import { useMachine } from "@xstate/react";
21
import {
32
CreateTemplateVersionRequest,
43
TemplateVersionVariable,
54
VariableValue,
65
} from "api/typesGenerated";
76
import { displaySuccess } from "components/GlobalSnackbar/utils";
87
import { useOrganizationId } from "hooks/useOrganizationId";
9-
import { FC } from "react";
8+
import { useCallback, type FC } from "react";
109
import { Helmet } from "react-helmet-async";
1110
import { useNavigate, useParams } from "react-router-dom";
12-
import { templateVariablesMachine } from "xServices/template/templateVariablesXService";
13-
import { pageTitle } from "../../../utils/page";
11+
import { pageTitle } from "utils/page";
1412
import { useTemplateSettings } from "../TemplateSettingsLayout";
1513
import { TemplateVariablesPageView } from "./TemplateVariablesPageView";
14+
import {
15+
createAndBuildTemplateVersion,
16+
templateVersion,
17+
templateVersionVariables,
18+
updateActiveTemplateVersion,
19+
} from "api/queries/templates";
20+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
21+
import { ErrorAlert } from "components/Alert/ErrorAlert";
22+
import { Loader } from "components/Loader/Loader";
1623

1724
export const TemplateVariablesPage: FC = () => {
1825
const { template: templateName } = useParams() as {
1926
organization: string;
2027
template: string;
2128
};
22-
const organizationId = useOrganizationId();
29+
const orgId = useOrganizationId();
2330
const { template } = useTemplateSettings();
2431
const navigate = useNavigate();
25-
const [state, send] = useMachine(templateVariablesMachine, {
26-
context: {
27-
organizationId,
28-
template,
29-
},
30-
actions: {
31-
onUpdateTemplate: () => {
32-
displaySuccess("Template updated successfully");
33-
},
34-
},
32+
const queryClient = useQueryClient();
33+
const versionId = template.active_version_id;
34+
35+
const {
36+
data: version,
37+
error: versionError,
38+
isLoading: isVersionLoading,
39+
} = useQuery({ ...templateVersion(versionId), keepPreviousData: true });
40+
const {
41+
data: variables,
42+
error: variablesError,
43+
isLoading: isVariablesLoading,
44+
} = useQuery({
45+
...templateVersionVariables(versionId),
46+
keepPreviousData: true,
3547
});
48+
49+
const {
50+
mutateAsync: sendCreateAndBuildTemplateVersion,
51+
error: buildError,
52+
isLoading: isBuilding,
53+
} = useMutation(createAndBuildTemplateVersion(orgId));
3654
const {
37-
activeTemplateVersion,
38-
templateVariables,
39-
getTemplateDataError,
40-
updateTemplateError,
41-
jobError,
42-
} = state.context;
55+
mutateAsync: sendUpdateActiveTemplateVersion,
56+
error: publishError,
57+
isLoading: isPublishing,
58+
} = useMutation(updateActiveTemplateVersion(template, queryClient));
59+
60+
const publishVersion = useCallback(
61+
async (versionId: string) => {
62+
await sendUpdateActiveTemplateVersion(versionId);
63+
displaySuccess("Template updated successfully");
64+
},
65+
[sendUpdateActiveTemplateVersion],
66+
);
67+
68+
const buildVersion = useCallback(
69+
async (req: CreateTemplateVersionRequest) => {
70+
const newVersionId = await sendCreateAndBuildTemplateVersion(req);
71+
await publishVersion(newVersionId);
72+
},
73+
[sendCreateAndBuildTemplateVersion, publishVersion],
74+
);
75+
76+
const isSubmitting = Boolean(isBuilding || isPublishing);
77+
78+
const error = versionError ?? variablesError;
79+
if (error) {
80+
return <ErrorAlert error={error} />;
81+
}
82+
83+
if (isVersionLoading || isVariablesLoading) {
84+
return <Loader />;
85+
}
4386

4487
return (
4588
<>
@@ -48,23 +91,19 @@ export const TemplateVariablesPage: FC = () => {
4891
</Helmet>
4992

5093
<TemplateVariablesPageView
51-
isSubmitting={state.hasTag("submitting")}
52-
templateVersion={activeTemplateVersion}
53-
templateVariables={templateVariables}
94+
isSubmitting={isSubmitting}
95+
templateVersion={version}
96+
templateVariables={variables}
5497
errors={{
55-
getTemplateDataError,
56-
updateTemplateError,
57-
jobError,
98+
buildError,
99+
publishError,
58100
}}
59101
onCancel={() => {
60102
navigate(`/templates/${templateName}`);
61103
}}
62-
onSubmit={(formData) => {
63-
const request = filterEmptySensitiveVariables(
64-
formData,
65-
templateVariables,
66-
);
67-
send({ type: "UPDATE_TEMPLATE_EVENT", request: request });
104+
onSubmit={async (formData) => {
105+
const request = filterEmptySensitiveVariables(formData, variables);
106+
await buildVersion(request);
68107
}}
69108
/>
70109
</>

site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPageView.stories.tsx

+12-17
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export const RequiredVariable: Story = {
4949
},
5050
};
5151

52-
export const WithUpdateTemplateError: Story = {
52+
export const WithErrors: Story = {
5353
args: {
5454
templateVersion: MockTemplateVersion,
5555
templateVariables: [
@@ -59,25 +59,20 @@ export const WithUpdateTemplateError: Story = {
5959
MockTemplateVersionVariable4,
6060
],
6161
errors: {
62-
updateTemplateError: mockApiError({
63-
message: "Something went wrong.",
62+
buildError: mockApiError({
63+
message: "buildError",
64+
validations: [
65+
{
66+
field: `user_variable_values[0].value`,
67+
detail: "Variable is required.",
68+
},
69+
],
6470
}),
71+
publishError: mockApiError({ message: "publishError" }),
6572
},
66-
},
67-
};
6873

69-
export const WithJobError: Story = {
70-
args: {
71-
templateVersion: MockTemplateVersion,
72-
templateVariables: [
73-
MockTemplateVersionVariable1,
74-
MockTemplateVersionVariable2,
75-
MockTemplateVersionVariable3,
76-
MockTemplateVersionVariable4,
77-
],
78-
errors: {
79-
jobError:
80-
"template import provision for start: recv import provision: plan terraform: terraform plan: exit status 1",
74+
initialTouched: {
75+
user_variable_values: true,
8176
},
8277
},
8378
};

0 commit comments

Comments
 (0)