diff --git a/ui/src/api/environments.ts b/ui/src/api/environments.ts index 4740427eb4..ab3cf09aaa 100644 --- a/ui/src/api/environments.ts +++ b/ui/src/api/environments.ts @@ -41,6 +41,10 @@ export interface EnvironmentCustomImageRollbackResult { supersededTemplate: EnvironmentCustomImageTemplate; } +function customImageCompanyQuery(companyId: string): string { + return `companyId=${encodeURIComponent(companyId)}`; +} + export const environmentsApi = { list: (companyId: string) => api.get(`/companies/${companyId}/environments`), capabilities: (companyId: string) => @@ -69,14 +73,17 @@ export const environmentsApi = { config?: Record; metadata?: Record | null; }) => api.post(`/companies/${companyId}/environments/probe-config`, body), - customImageTemplate: (environmentId: string) => - api.get(`/environments/${environmentId}/custom-image-template`), + customImageTemplate: (environmentId: string, companyId: string) => + api.get( + `/environments/${environmentId}/custom-image-template?${customImageCompanyQuery(companyId)}`, + ), startCustomImageSetupSession: ( environmentId: string, + companyId: string, body: StartEnvironmentCustomImageSetupSession = {}, ) => api.post( - `/environments/${environmentId}/custom-image-setup-sessions`, + `/environments/${environmentId}/custom-image-setup-sessions?${customImageCompanyQuery(companyId)}`, body, ), customImageSetupSession: (sessionId: string) => @@ -107,16 +114,17 @@ export const environmentsApi = { `/environment-custom-image-setup-sessions/${sessionId}/cancel`, body, ), - rollbackCustomImageTemplate: (environmentId: string) => + rollbackCustomImageTemplate: (environmentId: string, companyId: string) => api.post( - `/environments/${environmentId}/custom-image-template/rollback`, + `/environments/${environmentId}/custom-image-template/rollback?${customImageCompanyQuery(companyId)}`, {}, ), disableCustomImageTemplate: ( environmentId: string, + companyId: string, options: { deleteProviderTemplate?: boolean } = {}, ) => api.delete( - `/environments/${environmentId}/custom-image-template?deleteProviderTemplate=${options.deleteProviderTemplate === true ? "true" : "false"}`, + `/environments/${environmentId}/custom-image-template?${customImageCompanyQuery(companyId)}&deleteProviderTemplate=${options.deleteProviderTemplate === true ? "true" : "false"}`, ), }; diff --git a/ui/src/pages/CompanyEnvironments.test.tsx b/ui/src/pages/CompanyEnvironments.test.tsx index 7003642314..c787bea861 100644 --- a/ui/src/pages/CompanyEnvironments.test.tsx +++ b/ui/src/pages/CompanyEnvironments.test.tsx @@ -673,7 +673,7 @@ describe("CompanyEnvironments — test provider button", () => { await waitForAssertion(() => { expect(getOpenDialog()?.textContent).toContain("Configure image"); }); - expect(mockEnvironmentsApi.customImageTemplate).toHaveBeenCalledExactlyOnceWith("env-1"); + expect(mockEnvironmentsApi.customImageTemplate).toHaveBeenCalledExactlyOnceWith("env-1", "company-1"); await act(async () => click(findButton(document.body, "Cancel"))); await waitForAssertion(() => expect(getOpenDialog()).toBeNull()); @@ -1037,6 +1037,7 @@ describe("CompanyEnvironments — test provider button", () => { expect(mockEnvironmentsApi.startCustomImageSetupSession).toHaveBeenCalledWith( "env-1", + "company-1", { templateId: "template-active" }, ); await waitForAssertion(() => { @@ -1046,4 +1047,64 @@ describe("CompanyEnvironments — test provider button", () => { expect(FakeWebSocket.instances).toHaveLength(1); }); }); + + it("passes company context when rolling back and disabling an active template", async () => { + root = createRoot(container); + const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }); + mockEnvironmentsApi.list.mockResolvedValue([ + { id: "env-1", name: "Daytona", driver: "sandbox", description: null, config: { provider: "daytona" } }, + ]); + mockEnvironmentsApi.capabilities.mockResolvedValue({ + adapters: [], + drivers: { local: "supported", ssh: "supported", sandbox: "supported", plugin: "unsupported" }, + sandboxProviders: { + daytona: { + status: "supported", + supportsSavedProbe: true, + supportsUnsavedProbe: true, + supportsRunExecution: true, + supportsReusableLeases: true, + supportsInteractiveSetup: true, + interactiveSetupConnectionTypes: ["ssh"], + supportsTemplateCapture: true, + supportsTemplateDelete: true, + displayName: "Daytona", + }, + }, + }); + mockEnvironmentsApi.customImageTemplate.mockResolvedValue({ + activeTemplate: createTemplate({ id: "template-active" }), + activeSession: null, + latestSession: null, + }); + + await act(async () => { + root!.render( + + + + + , + ); + }); + await flushReact(); + + await act(async () => click(editButtons(container)[0])); + await waitForAssertion(() => { + const dialog = getOpenDialog(); + expect(dialog?.textContent).toContain("Active template"); + expect(findButton(dialog!, "Rollback")).toBeTruthy(); + expect(findButton(dialog!, "Disable")).toBeTruthy(); + }); + + await act(async () => click(findButton(getOpenDialog()!, "Rollback"))); + await waitForAssertion(() => { + expect(mockEnvironmentsApi.rollbackCustomImageTemplate).toHaveBeenCalledExactlyOnceWith("env-1", "company-1"); + }); + + await act(async () => click(findButton(getOpenDialog()!, "Disable"))); + await waitForAssertion(() => { + expect(mockEnvironmentsApi.disableCustomImageTemplate).toHaveBeenCalledExactlyOnceWith("env-1", "company-1"); + }); + }); }); diff --git a/ui/src/pages/CompanyEnvironments.tsx b/ui/src/pages/CompanyEnvironments.tsx index c19881491b..444db4358c 100644 --- a/ui/src/pages/CompanyEnvironments.tsx +++ b/ui/src/pages/CompanyEnvironments.tsx @@ -679,10 +679,12 @@ function sessionStatusCopy(status: EnvironmentCustomImageSetupSession["status"]) function EnvironmentImageTemplatePanel({ environment, + companyId, providerCapability, providerDisplayName, }: { environment: Environment; + companyId: string; providerCapability: EnvironmentProviderCapability | null | undefined; providerDisplayName: string; }) { @@ -693,7 +695,7 @@ function EnvironmentImageTemplatePanel({ const overviewQuery = useQuery({ queryKey: overviewKey, - queryFn: () => environmentsApi.customImageTemplate(environment.id), + queryFn: () => environmentsApi.customImageTemplate(environment.id, companyId), enabled: state.kind === "supported", retry: false, }); @@ -721,9 +723,11 @@ function EnvironmentImageTemplatePanel({ const startSetupMutation = useMutation({ mutationFn: (input: { templateId?: string | null } = {}) => - environmentsApi.startCustomImageSetupSession(environment.id, { - templateId: input.templateId ?? null, - }), + environmentsApi.startCustomImageSetupSession( + environment.id, + companyId, + { templateId: input.templateId ?? null }, + ), onSuccess: (result) => { queryClient.setQueryData(overviewKey, (current: typeof overviewQuery.data) => ({ activeTemplate: current?.activeTemplate ?? null, @@ -798,7 +802,7 @@ function EnvironmentImageTemplatePanel({ }); const rollbackTemplateMutation = useMutation({ - mutationFn: () => environmentsApi.rollbackCustomImageTemplate(environment.id), + mutationFn: () => environmentsApi.rollbackCustomImageTemplate(environment.id, companyId), onSuccess: (result) => { queryClient.setQueryData(overviewKey, (current: typeof overviewQuery.data) => ({ activeTemplate: result.activeTemplate, @@ -822,7 +826,7 @@ function EnvironmentImageTemplatePanel({ }); const disableTemplateMutation = useMutation({ - mutationFn: () => environmentsApi.disableCustomImageTemplate(environment.id), + mutationFn: () => environmentsApi.disableCustomImageTemplate(environment.id, companyId), onSuccess: (template) => { queryClient.setQueryData(overviewKey, (current: typeof overviewQuery.data) => ({ activeTemplate: null, @@ -1697,7 +1701,8 @@ export function CompanyEnvironments() { {editingEnvironment && editingEnvironment.driver === "sandbox" && - environmentForm.driver === "sandbox" ? ( + environmentForm.driver === "sandbox" && + selectedCompanyId ? (
Custom image
@@ -1706,6 +1711,7 @@ export function CompanyEnvironments() {