diff --git a/enclave-manager/web/cypress/e2e/enclaveList.cy.ts b/enclave-manager/web/cypress/e2e/enclaveList.cy.ts index 99a7a993ba..998e4a16ee 100644 --- a/enclave-manager/web/cypress/e2e/enclaveList.cy.ts +++ b/enclave-manager/web/cypress/e2e/enclaveList.cy.ts @@ -26,7 +26,7 @@ describe("Enclave List", () => { cy.contains("Script completed", { timeout: 10 * 1000 }); }); - it.only("Shows a new enclave in the list", () => { + it("Shows a new enclave in the list", () => { cy.goToEnclaveList(); cy.contains("tr", enclaveName).should("not.exist"); diff --git a/enclave-manager/web/cypress/e2e/enclave/service/logging.cy.ts b/enclave-manager/web/cypress/e2e/enclaves/enclave/service/logging.cy.ts similarity index 100% rename from enclave-manager/web/cypress/e2e/enclave/service/logging.cy.ts rename to enclave-manager/web/cypress/e2e/enclaves/enclave/service/logging.cy.ts diff --git a/enclave-manager/web/cypress/e2e/enclaves/packageSearch.cy.ts b/enclave-manager/web/cypress/e2e/enclaves/packageSearch.cy.ts new file mode 100644 index 0000000000..d47a032f57 --- /dev/null +++ b/enclave-manager/web/cypress/e2e/enclaves/packageSearch.cy.ts @@ -0,0 +1,9 @@ +describe("New Enclave package search", () => { + it("Can find an enclave by exact name", () => { + cy.goToEnclaveList(); + cy.contains("New Enclave").click(); + cy.contains("Package Template Repo").should("not.exist"); + cy.focused().type("github.com/kurtosis-tech/package-template-repo"); + cy.contains("Package Template Repo"); + }); +}); diff --git a/enclave-manager/web/packages/app/src/emui/catalog/CatalogContext.tsx b/enclave-manager/web/packages/app/src/emui/catalog/CatalogContext.tsx index 2e2a7b253e..577f13d542 100644 --- a/enclave-manager/web/packages/app/src/emui/catalog/CatalogContext.tsx +++ b/enclave-manager/web/packages/app/src/emui/catalog/CatalogContext.tsx @@ -1,5 +1,6 @@ import { Flex, Heading, Spinner } from "@chakra-ui/react"; import { GetPackagesResponse, KurtosisPackage } from "kurtosis-cloud-indexer-sdk"; +import { ReadPackageResponse } from "kurtosis-cloud-indexer-sdk/build/kurtosis_package_indexer_pb"; import { isDefined, SavedPackagesProvider } from "kurtosis-ui-components"; import { createContext, PropsWithChildren, useCallback, useContext, useEffect, useState } from "react"; import { Result } from "true-myth"; @@ -8,6 +9,7 @@ import { loadSavedPackageNames, storeSavedPackages } from "./storage"; export type CatalogState = { catalog: Result; + getSinglePackage: (packageName: string) => Promise>; refreshCatalog: () => Promise>; }; @@ -42,6 +44,13 @@ export const CatalogContextProvider = ({ children }: PropsWithChildren) => { }); }, []); + const getSinglePackage = useCallback( + async (packageName: string) => { + return await packageIndexerClient.readPackage(packageName); + }, + [packageIndexerClient], + ); + useEffect(() => { refreshCatalog(); }, [refreshCatalog]); @@ -58,7 +67,7 @@ export const CatalogContextProvider = ({ children }: PropsWithChildren) => { } return ( - + {children} diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/drawer/bodies/PackageSelectBody.tsx b/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/drawer/bodies/PackageSelectBody.tsx index c613da79ab..474a9ebf3c 100644 --- a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/drawer/bodies/PackageSelectBody.tsx +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/drawer/bodies/PackageSelectBody.tsx @@ -10,16 +10,30 @@ import { InputGroup, InputLeftElement, InputRightElement, + Spinner, Text, } from "@chakra-ui/react"; import { KurtosisPackage } from "kurtosis-cloud-indexer-sdk"; -import { FindCommand, KurtosisAlert, KurtosisPackageCardHorizontal, useSavedPackages } from "kurtosis-ui-components"; -import { ChangeEvent, useMemo, useRef, useState } from "react"; +import { + FindCommand, + isDefined, + KurtosisAlert, + KurtosisPackageCardHorizontal, + parsePackageUrl, + useSavedPackages, +} from "kurtosis-ui-components"; +import { debounce } from "lodash"; +import { ChangeEvent, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { FiSearch } from "react-icons/fi"; import { useCatalogContext } from "../../../../../catalog/CatalogContext"; import { DrawerExpandCollapseButton } from "../DrawerExpandCollapseButton"; import { DrawerSizes } from "../types"; +type ExactMatchState = + | { type: "loading"; url: string } + | { type: "loaded"; package: KurtosisPackage } + | { type: "error"; error: string }; + type PackageSelectBodyProps = { onPackageSelected: (kurtosisPackage: KurtosisPackage) => void; onClose: () => void; @@ -35,7 +49,46 @@ export const PackageSelectBody = ({ const searchRef = useRef(null); const [searchTerm, setSearchTerm] = useState(""); - const { catalog } = useCatalogContext(); + const [exactMatch, setExactMatch] = useState(); + const { catalog, getSinglePackage } = useCatalogContext(); + + const checkSinglePackageDebounced = useMemo( + () => + debounce( + async (packageName: string) => { + const singlePackageResult = await getSinglePackage(packageName); + if (singlePackageResult.isErr) { + setExactMatch({ type: "error", error: singlePackageResult.error }); + return; + } + if (isDefined(singlePackageResult.value.package)) { + setExactMatch({ type: "loaded", package: singlePackageResult.value.package }); + } + }, + 1000, + { trailing: true, leading: false }, + ), + [getSinglePackage], + ); + + const startCheckSinglePackage = useCallback( + async (packageName: string) => { + let isKurtosisPackageName = false; + try { + parsePackageUrl(packageName); + isKurtosisPackageName = true; + } catch (error: any) { + // This packageName isn't a kurtosis package url + } + if (isKurtosisPackageName) { + setExactMatch({ type: "loading", url: packageName }); + checkSinglePackageDebounced(packageName); + } else { + setExactMatch(undefined); + } + }, + [checkSinglePackageDebounced], + ); const searchResults = useMemo( () => @@ -53,6 +106,10 @@ export const PackageSelectBody = ({ setSearchTerm(e.target.value); }; + useEffect(() => { + startCheckSinglePackage(searchTerm); + }, [startCheckSinglePackage, searchTerm]); + if (searchResults.isErr) { return ( @@ -92,6 +149,28 @@ export const PackageSelectBody = ({ )} + {isDefined(exactMatch) && ( + + + Exact Match + + {exactMatch.type === "loading" && ( + + + Looking for a Kurtosis Package at {exactMatch.url} + + )} + {exactMatch.type === "loaded" && ( + onPackageSelected(exactMatch.package)} + /> + )} + {exactMatch.type === "error" && ( + + )} + + )} {(searchTerm.length > 0 || savedPackages.length === 0) && ( diff --git a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/utils.ts b/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/utils.ts index 0902c061e0..90ab3d0626 100644 --- a/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/utils.ts +++ b/enclave-manager/web/packages/app/src/emui/enclaves/components/configuration/utils.ts @@ -63,9 +63,12 @@ export function transformFormArgsToKurtosisArgs(data: Record, kurto case ArgumentValueType.STRING: return value; case ArgumentValueType.JSON: - return YAML.parse(value); default: - return value; + try { + return YAML.parse(value); + } catch (error: any) { + return value; + } } };