diff --git a/apps/deploy-web/src/components/LocalNoteManager/DeploymentNameModal.spec.tsx b/apps/deploy-web/src/components/LocalNoteManager/DeploymentNameModal.spec.tsx index 079137edf7..40907473dc 100644 --- a/apps/deploy-web/src/components/LocalNoteManager/DeploymentNameModal.spec.tsx +++ b/apps/deploy-web/src/components/LocalNoteManager/DeploymentNameModal.spec.tsx @@ -1,12 +1,9 @@ -import { createStore, Provider as JotaiStoreProvider } from "jotai"; import { describe, expect, it, vi } from "vitest"; -import type { MockProxy } from "vitest-mock-extended"; import { mock, mockDeep } from "vitest-mock-extended"; import { MAX_DEPLOYMENT_NAME_LENGTH } from "@src/config/deploy.config"; import type { AppDIContainer } from "@src/context/ServicesProvider/ServicesProvider"; import type { DeploymentStorageService } from "@src/services/deployment-storage/deployment-storage.service"; -import { settingsIdAtom } from "@src/store/settingsStore"; import type { DEPENDENCIES } from "./DeploymentNameModal"; import { DeploymentNameModal } from "./DeploymentNameModal"; @@ -39,12 +36,13 @@ describe("DeploymentNameModal", () => { await waitFor(() => expect(queryClient.invalidateQueries).toHaveBeenCalledWith({ queryKey: api.v1.listDeploymentNames.getKey() })); }); - it("records the new name in this browser too, which the deployments list still reads", async () => { - const { deploymentLocalStorage } = setup({}); + it("records nothing in this browser, because the api is now where the name lives", async () => { + const { deploymentLocalStorage, onSaved } = setup({}); await rename("my-app"); - await waitFor(() => expect(deploymentLocalStorage.update).toHaveBeenCalledWith("akash1abc", "12345", { name: "my-app" })); + await waitFor(() => expect(onSaved).toHaveBeenCalled()); + expect(deploymentLocalStorage.update).not.toHaveBeenCalled(); }); it("reports the rename as saved once the api accepted it", async () => { @@ -107,19 +105,6 @@ describe("DeploymentNameModal", () => { expect(patchMutate).not.toHaveBeenCalled(); }); - it("still refreshes and closes when this browser cannot record the new name", async () => { - const deploymentLocalStorage = mock(); - deploymentLocalStorage.update.mockImplementation(() => { - throw new Error("QuotaExceededError"); - }); - const { queryClient, onSaved } = setup({ deploymentLocalStorage }); - - await rename("my-app"); - - await waitFor(() => expect(onSaved).toHaveBeenCalled()); - expect(queryClient.invalidateQueries).toHaveBeenCalled(); - }); - it.each(["", " "])("refuses a name of %p, which the api rejects rather than reading as unnamed", async typed => { const { patchMutate, deploymentLocalStorage, onSaved } = setup({ resolvedName: "old-name" }); @@ -197,7 +182,6 @@ describe("DeploymentNameModal", () => { resolvedName?: string; patchMutate?: ReturnType; isPending?: boolean; - deploymentLocalStorage?: MockProxy; }) { const dseq = input.dseq === undefined ? "12345" : input.dseq; const patchMutate = input.patchMutate ?? vi.fn((_variables, options) => options?.onSuccess?.()); @@ -209,7 +193,7 @@ describe("DeploymentNameModal", () => { mock>({ mutate: patchMutate as never, isPending: input.isPending ?? false }) ); - const deploymentLocalStorage = input.deploymentLocalStorage ?? mock(); + const deploymentLocalStorage = mock(); const queryClient = mock>(); const enqueueSnackbar = vi.fn(); const onSaved = vi.fn(); @@ -221,15 +205,10 @@ describe("DeploymentNameModal", () => { useResolvedDeploymentName: () => resolvedName }; - const store = createStore(); - store.set(settingsIdAtom, "akash1abc"); - const modalFor = (shownDseq: string | number | null) => ( - - api, deploymentLocalStorage: () => deploymentLocalStorage }}> - - - + api, deploymentLocalStorage: () => deploymentLocalStorage }}> + + ); const { rerender } = render(modalFor(dseq)); diff --git a/apps/deploy-web/src/components/LocalNoteManager/DeploymentNameModal.tsx b/apps/deploy-web/src/components/LocalNoteManager/DeploymentNameModal.tsx index 0391fe5e01..6d0858df30 100644 --- a/apps/deploy-web/src/components/LocalNoteManager/DeploymentNameModal.tsx +++ b/apps/deploy-web/src/components/LocalNoteManager/DeploymentNameModal.tsx @@ -4,14 +4,12 @@ import { useForm } from "react-hook-form"; import { Form, FormField, FormInput, Popup, Snackbar } from "@akashnetwork/ui/components"; import { zodResolver } from "@hookform/resolvers/zod"; import { useQueryClient } from "@tanstack/react-query"; -import { useAtom } from "jotai"; import { useSnackbar } from "notistack"; import { z } from "zod"; import { MAX_DEPLOYMENT_NAME_LENGTH } from "@src/config/deploy.config"; import { useServices } from "@src/context/ServicesProvider"; import { useResolvedDeploymentName } from "@src/hooks/useResolvedDeploymentName/useResolvedDeploymentName"; -import { settingsIdAtom } from "@src/store/settingsStore"; export const DEPENDENCIES = { useSnackbar, useQueryClient, useResolvedDeploymentName }; @@ -27,8 +25,7 @@ type Props = { }; export const DeploymentNameModal: React.FC = ({ dseq, onClose, onSaved, dependencies: d = DEPENDENCIES }) => { - const { api, deploymentLocalStorage } = useServices(); - const [address] = useAtom(settingsIdAtom); + const { api } = useServices(); const formRef = useRef(null); const { enqueueSnackbar } = d.useSnackbar(); const queryClient = d.useQueryClient(); @@ -66,15 +63,6 @@ export const DeploymentNameModal: React.FC = ({ dseq, onClose, onSaved, d formRef.current?.dispatchEvent(new Event("submit", { cancelable: true, bubbles: true })); }; - /** Kept in step until the browser stops recording names altogether, and a full or blocked store must not strand a rename the api has already accepted. */ - function recordNameInThisBrowser(name: string) { - try { - deploymentLocalStorage.update(address, dseq, { name }); - } catch { - return; - } - } - function onSubmit({ name }: z.infer) { if (!dseq || renameDeployment.isPending) return; const renamedDseq = String(dseq); @@ -82,8 +70,7 @@ export const DeploymentNameModal: React.FC = ({ dseq, onClose, onSaved, d renameDeployment.mutate( { dseq: renamedDseq, data: { name } }, { - onSuccess: function recordRename() { - recordNameInThisBrowser(name); + onSuccess: function reportRenameSaved() { queryClient.invalidateQueries({ queryKey: api.v1.getDeployment.getKey({ dseq: renamedDseq }) }); queryClient.invalidateQueries({ queryKey: api.v1.listDeploymentNames.getKey() }); enqueueSnackbar(, { variant: "success", autoHideDuration: 1000 }); diff --git a/apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentFlow/useDeploymentFlow.spec.tsx b/apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentFlow/useDeploymentFlow.spec.tsx index 8b5d9713e6..ae779c10c2 100644 --- a/apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentFlow/useDeploymentFlow.spec.tsx +++ b/apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentFlow/useDeploymentFlow.spec.tsx @@ -320,6 +320,16 @@ describe(useDeploymentFlow.name, () => { } }); + it("records no name in this browser, because the create request is what carries it", () => { + const createDeployment = mockMutation(); + createDeployment.mutate.mockImplementation((_i, o) => o.onSuccess({ data: { dseq: "555", manifest: "M" } })); + const { result, deploymentLocalStorage } = renderFlow({ createDeployment }); + + act(() => result.current.actions.requestQuotes("SDL_AT_CREATE", "my-app")); + + expect(deploymentLocalStorage.update).not.toHaveBeenCalledWith(expect.anything(), expect.anything(), expect.objectContaining({ name: expect.anything() })); + }); + it("caches the created SDL by the settings id and dseq at create time so an in-progress deployment can be resumed after a reload", () => { const createDeployment = mockMutation(); createDeployment.mutate.mockImplementation((_i, o) => o.onSuccess({ data: { dseq: "555", manifest: "M" } })); diff --git a/apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentName/useDeploymentName.spec.tsx b/apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentName/useDeploymentName.spec.tsx index 71f1f11f92..bfe5289003 100644 --- a/apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentName/useDeploymentName.spec.tsx +++ b/apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentName/useDeploymentName.spec.tsx @@ -1,11 +1,6 @@ -import type { PropsWithChildren } from "react"; -import { createStore, Provider as JotaiStoreProvider } from "jotai"; import { describe, expect, it } from "vitest"; -import { mock } from "vitest-mock-extended"; import { MAX_DEPLOYMENT_NAME_LENGTH } from "@src/config/deploy.config"; -import type { DeploymentStorageService } from "@src/services/deployment-storage/deployment-storage.service"; -import { settingsIdAtom } from "@src/store/settingsStore"; import type { DEPENDENCIES } from "./useDeploymentName"; import { useDeploymentName } from "./useDeploymentName"; @@ -73,54 +68,11 @@ describe(useDeploymentName.name, () => { expect(result.current.name).toBe("renamed"); }); - it("writes the name to the settings-scoped record when a dseq is first assigned", () => { - const { rerender, deploymentLocalStorage } = setup({ initialName: "my-app", dseq: null, settingsId: "akash1abc" }); - - rerender({ initialName: "my-app", dseq: "12345" }); - - expect(deploymentLocalStorage.update).toHaveBeenCalledWith("akash1abc", "12345", { name: "my-app" }); - }); - - it("does not write before a dseq exists", () => { - const { deploymentLocalStorage } = setup({ initialName: "my-app", dseq: null }); - - expect(deploymentLocalStorage.update).not.toHaveBeenCalled(); - }); - - it("does not write when the session resumed already carrying a dseq", () => { - const { deploymentLocalStorage } = setup({ initialName: "my-app", dseq: "12345" }); - - expect(deploymentLocalStorage.update).not.toHaveBeenCalled(); - }); - - it("defers the write until settingsId is available instead of dropping it", () => { - const { rerender, store, deploymentLocalStorage } = setup({ initialName: "my-app", dseq: null, settingsId: null }); - - rerender({ initialName: "my-app", dseq: "12345" }); - expect(deploymentLocalStorage.update).not.toHaveBeenCalled(); - - act(() => store.set(settingsIdAtom, "akash1abc")); - - expect(deploymentLocalStorage.update).toHaveBeenCalledWith("akash1abc", "12345", { name: "my-app" }); - }); - - function setup(input: { initialName?: string; dseq?: string | null; settingsId?: string | null; apiName?: string }) { - const deploymentLocalStorage = mock(); - const useServices: typeof DEPENDENCIES.useServices = () => mock>({ deploymentLocalStorage }); + function setup(input: { initialName?: string; dseq?: string | null; apiName?: string }) { const useResolvedDeploymentName: typeof DEPENDENCIES.useResolvedDeploymentName = dseq => (dseq ? input.apiName : undefined); - const store = createStore(); - store.set(settingsIdAtom, input.settingsId ?? null); - const wrapper = ({ children }: PropsWithChildren) => {children}; - const initialProps = { initialName: input.initialName, dseq: input.dseq ?? null }; - - return { - ...renderHook((props: { initialName?: string; dseq: string | null }) => useDeploymentName(props, { useServices, useResolvedDeploymentName }), { - wrapper, - initialProps - }), - deploymentLocalStorage, - store - }; + return renderHook((props: { initialName?: string; dseq: string | null }) => useDeploymentName(props, { useResolvedDeploymentName }), { + initialProps: { initialName: input.initialName, dseq: input.dseq ?? null } + }); } }); diff --git a/apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentName/useDeploymentName.ts b/apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentName/useDeploymentName.ts index aab3798c9a..a755838bf2 100644 --- a/apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentName/useDeploymentName.ts +++ b/apps/deploy-web/src/components/deployments/ConfigureDeployment/useDeploymentName/useDeploymentName.ts @@ -1,12 +1,9 @@ -import { useEffect, useRef, useState } from "react"; -import { useAtomValue } from "jotai"; +import { useState } from "react"; import { MAX_DEPLOYMENT_NAME_LENGTH } from "@src/config/deploy.config"; -import { useServices } from "@src/context/ServicesProvider"; import { useResolvedDeploymentName } from "@src/hooks/useResolvedDeploymentName/useResolvedDeploymentName"; -import { settingsIdAtom } from "@src/store/settingsStore"; -export const DEPENDENCIES = { useServices, useResolvedDeploymentName }; +export const DEPENDENCIES = { useResolvedDeploymentName }; export interface DeploymentName { /** The name to show: the one typed in this session, and the api's own only where this session has none. */ @@ -23,24 +20,9 @@ interface UseDeploymentNameInput { dseq: string | null; } -/** Owns the configure session's deployment name: the api's own once the deployment exists, the typed one before that, and the write of the typed one to the wallet-scoped local record `settingsId` keys. */ +/** Owns the configure session's deployment name: the api's own once the deployment exists, and the typed one before that. */ export function useDeploymentName({ initialName, dseq }: UseDeploymentNameInput, dependencies = DEPENDENCIES): DeploymentName { - const { deploymentLocalStorage } = dependencies.useServices(); - const settingsId = useAtomValue(settingsIdAtom); const [typedName, setTypedName] = useState(() => (initialName ?? "").slice(0, MAX_DEPLOYMENT_NAME_LENGTH)); - const nameRef = useRef(typedName); - nameRef.current = typedName; const resolvedName = dependencies.useResolvedDeploymentName(dseq); - /** Seeded with the mounting `dseq`, so a session resumed already carrying one is treated as written and never clobbers a name edited since on the deployment page. */ - const writtenDseqRef = useRef(dseq); - useEffect( - function persistNameOnCreate() { - if (dseq && settingsId && dseq !== writtenDseqRef.current) { - writtenDseqRef.current = dseq; - deploymentLocalStorage.update(settingsId, dseq, { name: nameRef.current }); - } - }, - [dseq, settingsId, deploymentLocalStorage] - ); return { name: typedName || resolvedName || "", typedName, setName: setTypedName }; }