From 10baaf44b13b15c3f088684a9ada75f1aec5a8e3 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Fri, 31 Jan 2025 10:55:51 +0100 Subject: [PATCH 1/4] feat: create data connector in project --- .../DataConnectorModalBody.tsx | 32 +++++++++++++++++-- .../components/DataConnectorModal/index.tsx | 5 ++- .../components/DataConnectorsBox.tsx | 2 +- .../cloudStorage/AddOrEditCloudStorage.tsx | 5 +++ .../cloudStorage/CloudStorageModal.tsx | 1 + .../cloudStorageModalComponents.tsx | 3 ++ .../projectsV2/api/namespace.enhanced-api.ts | 12 +++++++ .../projectsV2/api/projectV2.enhanced-api.ts | 6 ++-- .../fields/ProjectNamespaceFormField.tsx | 30 +++++++++++++++-- .../projectsV2/list/ProjectV2ListDisplay.tsx | 2 +- 10 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 client/src/features/projectsV2/api/namespace.enhanced-api.ts diff --git a/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalBody.tsx b/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalBody.tsx index 040dc1468a..3d8b04f5c0 100644 --- a/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalBody.tsx +++ b/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalBody.tsx @@ -47,15 +47,18 @@ import dataConnectorFormSlice from "../../state/dataConnectors.slice"; import DataConnectorModalResult from "./DataConnectorModalResult"; import DataConnectorSaveCredentialsInfo from "./DataConnectorSaveCredentialsInfo"; +import type { Project } from "../../../projectsV2/api/projectV2.api"; interface AddOrEditDataConnectorProps { storageSecrets: DataConnectorSecret[]; + project?: Project; } type DataConnectorModalBodyProps = AddOrEditDataConnectorProps; export default function DataConnectorModalBody({ storageSecrets, + project, }: DataConnectorModalBodyProps) { const { flatDataConnector, schemata, success } = useAppSelector( (state) => state.dataConnectorFormSlice @@ -76,13 +79,17 @@ export default function DataConnectorModalBody({ Or, connect to cloud storage to read and write custom data.

)} - + ); } function AddOrEditDataConnector({ storageSecrets, + project, }: AddOrEditDataConnectorProps) { const { cloudStorageState, flatDataConnector, schemata, validationResult } = useAppSelector((state) => state.dataConnectorFormSlice); @@ -150,7 +157,10 @@ function AddOrEditDataConnector({ setState={setState} /> - + ); return

Error - not implemented yet

; @@ -173,7 +183,7 @@ type DataConnectorMountFormFields = | "mountPoint" | "readOnly" | "saveCredentials"; -export function DataConnectorMount() { +export function DataConnectorMount({ project }: AddOrEditDataConnectorProps) { const dispatch = useAppDispatch(); const { cloudStorageState, flatDataConnector, schemata } = useAppSelector( (state) => state.dataConnectorFormSlice @@ -305,6 +315,18 @@ export function DataConnectorMount() { const fields: Partial = { ...field }; delete fields?.ref; return ( + // { + // field.onChange(e); + // onFieldValueChange("namespace", e?.value || ""); + // }} + ///> ); }} diff --git a/client/src/features/dataConnectorsV2/components/DataConnectorModal/index.tsx b/client/src/features/dataConnectorsV2/components/DataConnectorModal/index.tsx index 8b79effa3a..8d163d8f34 100644 --- a/client/src/features/dataConnectorsV2/components/DataConnectorModal/index.tsx +++ b/client/src/features/dataConnectorsV2/components/DataConnectorModal/index.tsx @@ -102,7 +102,10 @@ export function DataConnectorModalBodyAndFooter({ ) : schemaQueryResult.error ? ( ) : ( - + )} diff --git a/client/src/features/dataConnectorsV2/components/DataConnectorsBox.tsx b/client/src/features/dataConnectorsV2/components/DataConnectorsBox.tsx index e67a53f56d..7fabeff587 100644 --- a/client/src/features/dataConnectorsV2/components/DataConnectorsBox.tsx +++ b/client/src/features/dataConnectorsV2/components/DataConnectorsBox.tsx @@ -34,7 +34,7 @@ import Pagination from "../../../components/Pagination"; import { RtkOrNotebooksError } from "../../../components/errors/RtkErrorAlert"; import useGroupPermissions from "../../groupsV2/utils/useGroupPermissions.hook"; import PermissionsGuard from "../../permissionsV2/PermissionsGuard"; -import type { NamespaceKind } from "../../projectsV2/api/namespace.api"; +import type { NamespaceKind } from "../../projectsV2/api/namespace.enhanced-api"; import { useGetUserQuery } from "../../usersV2/api/users.api"; import { useGetDataConnectorsQuery, diff --git a/client/src/features/project/components/cloudStorage/AddOrEditCloudStorage.tsx b/client/src/features/project/components/cloudStorage/AddOrEditCloudStorage.tsx index b2a43f873e..fba792befb 100644 --- a/client/src/features/project/components/cloudStorage/AddOrEditCloudStorage.tsx +++ b/client/src/features/project/components/cloudStorage/AddOrEditCloudStorage.tsx @@ -80,6 +80,7 @@ interface AddOrEditCloudStorageProps { state: AddCloudStorageState; storage: CloudStorageDetails; storageSecrets: CloudStorageSecretGet[]; + projectId?: string; } export default function AddOrEditCloudStorage({ @@ -88,6 +89,7 @@ export default function AddOrEditCloudStorage({ setState, state, storage, + projectId, }: AddOrEditCloudStorageProps) { const ContentByStep = state.step >= 0 && state.step <= CLOUD_STORAGE_TOTAL_STEPS @@ -107,6 +109,7 @@ export default function AddOrEditCloudStorage({ setStorage={setStorage} storageSecrets={[]} validationSucceeded={false} + projectId={projectId} /> ); @@ -171,6 +174,7 @@ export interface AddStorageStepProps { storageSecrets: CloudStorageSecretGet[]; isV2?: boolean; validationSucceeded: boolean; + projectId?: string; } const mapStepToElement: { @@ -1035,6 +1039,7 @@ export function AddStorageMount({ storage, state, validationSucceeded, + projectId, }: AddStorageStepProps) { const { control, diff --git a/client/src/features/project/components/cloudStorage/CloudStorageModal.tsx b/client/src/features/project/components/cloudStorage/CloudStorageModal.tsx index 7c82f886b6..2aa60873fd 100644 --- a/client/src/features/project/components/cloudStorage/CloudStorageModal.tsx +++ b/client/src/features/project/components/cloudStorage/CloudStorageModal.tsx @@ -403,6 +403,7 @@ export default function CloudStorageModal({ storageId={storageId} success={success} validationSucceeded={validationSucceeded} + projectId={projectId} /> diff --git a/client/src/features/project/components/cloudStorage/cloudStorageModalComponents.tsx b/client/src/features/project/components/cloudStorage/cloudStorageModalComponents.tsx index 40231aeeae..3e0b177ddf 100644 --- a/client/src/features/project/components/cloudStorage/cloudStorageModalComponents.tsx +++ b/client/src/features/project/components/cloudStorage/cloudStorageModalComponents.tsx @@ -107,6 +107,7 @@ export interface AddCloudStorageBodyContentProps storageDetails: CloudStorageDetails; success: boolean; validationSucceeded: boolean; + projectId?: string; } export function AddCloudStorageBodyContent({ addResultStorageName, @@ -121,6 +122,7 @@ export function AddCloudStorageBodyContent({ storageDetails, storageId, success, + projectId, }: AddCloudStorageBodyContentProps) { if (redraw) return ; if (success) { @@ -140,6 +142,7 @@ export function AddCloudStorageBodyContent({ state={state} storage={storageDetails} storageSecrets={[]} + projectId={projectId} /> ); } diff --git a/client/src/features/projectsV2/api/namespace.enhanced-api.ts b/client/src/features/projectsV2/api/namespace.enhanced-api.ts new file mode 100644 index 0000000000..5a248ab5f2 --- /dev/null +++ b/client/src/features/projectsV2/api/namespace.enhanced-api.ts @@ -0,0 +1,12 @@ +import { + NamespaceKind as NamespaceKindOrig, + NamespaceResponse as NamespaceResponseOrig, +} from "./namespace.api.ts"; + +export type NamespaceKind = NamespaceKindOrig | "project"; +export interface NamespaceResponse + extends Omit { + namespace_kind: NamespaceKind; +} +export type NamespaceResponseList = NamespaceResponse[]; +export type GetNamespacesApiResponse = NamespaceResponseList; diff --git a/client/src/features/projectsV2/api/projectV2.enhanced-api.ts b/client/src/features/projectsV2/api/projectV2.enhanced-api.ts index 56440923f2..4b385f0b85 100644 --- a/client/src/features/projectsV2/api/projectV2.enhanced-api.ts +++ b/client/src/features/projectsV2/api/projectV2.enhanced-api.ts @@ -17,10 +17,12 @@ import type { GetGroupsApiArg, GetGroupsApiResponse as GetGroupsApiResponseOrig, GetNamespacesApiArg, - GetNamespacesApiResponse as GetNamespacesApiResponseOrig, GroupResponseList, - NamespaceResponseList, } from "./namespace.api"; +import type { + GetNamespacesApiResponse as GetNamespacesApiResponseOrig, + NamespaceResponseList, +} from "./namespace.enhanced-api"; export interface GetGroupsApiResponse extends AbstractKgPaginatedResponse { groups: GetGroupsApiResponseOrig; diff --git a/client/src/features/projectsV2/fields/ProjectNamespaceFormField.tsx b/client/src/features/projectsV2/fields/ProjectNamespaceFormField.tsx index 4afff294a1..fc42c0e8b4 100644 --- a/client/src/features/projectsV2/fields/ProjectNamespaceFormField.tsx +++ b/client/src/features/projectsV2/fields/ProjectNamespaceFormField.tsx @@ -38,6 +38,8 @@ import { ErrorAlert } from "../../../components/Alert"; import { Loader } from "../../../components/Loader"; import type { PaginatedState } from "../../session/components/options/fetchMore.types"; import type { GetNamespacesApiResponse } from "../api/projectV2.enhanced-api"; +import type { Project } from "../api/projectV2.api"; +import type { NamespaceResponse } from "../api/namespace.enhanced-api"; import { useGetNamespacesByNamespaceSlugQuery, useGetNamespacesQuery, @@ -136,6 +138,7 @@ interface NamespaceSelectorProps { namespaces: ResponseNamespaces; onChange?: (newValue: SingleValue) => void; onFetchMore?: () => void; + project?: Project; } function NamespaceSelector({ @@ -146,10 +149,27 @@ function NamespaceSelector({ namespaces, onChange, onFetchMore, + project, }: NamespaceSelectorProps) { + const namespaceOptions = useMemo(() => { + if (!project) { + return namespaces; + } + return [ + { + id: project.id, + name: project.name, + slug: `${project.namespace}/${project.slug}`, + created_by: project.created_by, + creation_date: project.creation_date, + namespace_kind: "project", + } as NamespaceResponse, + ...namespaces, + ]; + }, [namespaces, project]); const currentValue = useMemo( - () => namespaces.find(({ slug }) => slug === currentNamespace), - [namespaces, currentNamespace] + () => namespaceOptions.find(({ slug }) => slug === currentNamespace), + [namespaceOptions, currentNamespace] ); const components = useMemo( @@ -163,7 +183,7 @@ function NamespaceSelector({ return ( option.id} @@ -415,6 +397,23 @@ export function ProjectNamespaceControl({ }); }, [allNamespaces, specificNamespace, specificNamespaceRequestId]); + const allNamespacesWithProject = useMemo(() => { + if (!project) { + return allNamespaces || []; + } + return [ + { + id: project.id, + slug: `${project.namespace}/${project.slug}`, + creation_date: project.creation_date, + created_by: project.created_by, + namespace_kind: "project", + name: `${project.namespace}/${project.slug}`, + } as NamespaceResponse, + ...(allNamespaces || []), + ]; + }, [allNamespaces, project]); + if (isFetching) { return (
@@ -441,10 +440,9 @@ export function ProjectNamespaceControl({ hasMore={hasMore} inputId={inputId} isFetchingMore={namespacesPageResult.isFetching} - namespaces={allNamespaces} + namespaces={allNamespacesWithProject} onChange={onChange} onFetchMore={onFetchMore} - project={project} />
); From 7b25397ec2aeb90b8222031a1a46ad933c2e3151 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Thu, 6 Feb 2025 14:44:47 +0100 Subject: [PATCH 3/4] squashme: apply code suggestion Co-authored-by: Lorenzo Cavazzi <43481553+lorenzo-cavazzi@users.noreply.github.com> --- .../src/features/projectsV2/fields/ProjectNamespaceFormField.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/features/projectsV2/fields/ProjectNamespaceFormField.tsx b/client/src/features/projectsV2/fields/ProjectNamespaceFormField.tsx index 379111ee95..21fdce654d 100644 --- a/client/src/features/projectsV2/fields/ProjectNamespaceFormField.tsx +++ b/client/src/features/projectsV2/fields/ProjectNamespaceFormField.tsx @@ -251,7 +251,6 @@ export default function ProjectNamespaceFormField({ id={`${entityName}-namespace`} inputId={`${entityName}-namespace-input`} onChange={(newValue) => field.onChange(newValue?.slug)} - project={undefined} /> ); }} From 3af32099f75017dc8eb5dcdd92383c80da1d582c Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Fri, 14 Feb 2025 11:55:11 +0100 Subject: [PATCH 4/4] squashme: fix e2e tests --- .../e2e/projectV2DataConnectorCredentials.spec.ts | 14 ++++++++++---- tests/cypress/e2e/projectV2setup.spec.ts | 14 ++++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/tests/cypress/e2e/projectV2DataConnectorCredentials.spec.ts b/tests/cypress/e2e/projectV2DataConnectorCredentials.spec.ts index e158846825..dedb744906 100644 --- a/tests/cypress/e2e/projectV2DataConnectorCredentials.spec.ts +++ b/tests/cypress/e2e/projectV2DataConnectorCredentials.spec.ts @@ -45,7 +45,10 @@ describe("Set up data connectors with credentials", () => { it("set up data connector with failed connection test", () => { fixtures .testCloudStorage({ success: false }) - .postDataConnector({ namespace: "user1-uuid", visibility: "public" }) + .postDataConnector({ + namespace: "user1-uuid/test-2-v2-project", + visibility: "public", + }) .postDataConnectorProjectLink({ dataConnectorId: "ULID-5", }) @@ -85,7 +88,7 @@ describe("Set up data connectors with credentials", () => { cy.wait("@postDataConnector"); cy.getDataCy("data-connector-edit-body").should( "contain.text", - "The data connector user1-uuid/example-storage-without-credentials has been successfully added" + "The data connector user1-uuid/test-2-v2-project/example-storage-without-credentials has been successfully added" ); cy.getDataCy("data-connector-edit-body").should( "contain.text", @@ -109,7 +112,10 @@ describe("Set up data connectors with credentials", () => { it("set up data connector with credentials", () => { fixtures .testCloudStorage({ success: true }) - .postDataConnector({ namespace: "user1-uuid", visibility: "public" }) + .postDataConnector({ + namespace: "user1-uuid/test-2-v2-project", + visibility: "public", + }) .postDataConnectorProjectLink({ dataConnectorId: "ULID-5" }) .patchDataConnectorSecrets({ dataConnectorId: "ULID-5", @@ -161,7 +167,7 @@ describe("Set up data connectors with credentials", () => { cy.wait("@patchDataConnectorSecrets"); cy.getDataCy("data-connector-edit-body").should( "contain.text", - "The data connector user1-uuid/example-storage-with-credentials has been successfully added" + "The data connector user1-uuid/test-2-v2-project/example-storage-with-credentials has been successfully added" ); cy.getDataCy("data-connector-edit-body").should( "contain.text", diff --git a/tests/cypress/e2e/projectV2setup.spec.ts b/tests/cypress/e2e/projectV2setup.spec.ts index ec8bd6abf1..64e1182c2f 100644 --- a/tests/cypress/e2e/projectV2setup.spec.ts +++ b/tests/cypress/e2e/projectV2setup.spec.ts @@ -191,7 +191,10 @@ describe("Set up data connectors", () => { .getDataConnector() .getStorageSchema({ fixture: "cloudStorage/storage-schema-s3.json" }) .testCloudStorage({ success: false }) - .postDataConnector({ namespace: "user1-uuid", visibility: "public" }) + .postDataConnector({ + namespace: "user1-uuid/test-2-v2-project", + visibility: "public", + }) .postDataConnectorProjectLink({ dataConnectorId: "ULID-5" }); cy.visit("/v2/projects/user1-uuid/test-2-v2-project"); cy.wait("@readProjectV2"); @@ -239,7 +242,7 @@ describe("Set up data connectors", () => { cy.wait("@postDataConnector"); cy.getDataCy("data-connector-edit-body").should( "contain.text", - "The data connector user1-uuid/example-storage-without-credentials has been successfully added" + "The data connector user1-uuid/test-2-v2-project/example-storage-without-credentials has been successfully added" ); cy.getDataCy("data-connector-edit-body").should( "contain.text", @@ -342,7 +345,10 @@ describe("Set up data connectors", () => { .getDataConnector() .getStorageSchema({ fixture: "cloudStorage/storage-schema-s3.json" }) .testCloudStorage({ success: false }) - .postDataConnector({ namespace: "user1-uuid", visibility: "public" }) + .postDataConnector({ + namespace: "user1-uuid/test-2-v2-project", + visibility: "public", + }) .postDataConnectorProjectLink({ dataConnectorId: "ULID-5" }); cy.visit("/v2/projects/user1-uuid/test-2-v2-project"); cy.wait("@readProjectV2"); @@ -375,7 +381,7 @@ describe("Set up data connectors", () => { cy.wait("@postDataConnector"); cy.getDataCy("data-connector-edit-body").should( "contain.text", - "The data connector user1-uuid/example-storage-without-credentials has been successfully added" + "The data connector user1-uuid/test-2-v2-project/example-storage-without-credentials has been successfully added" ); cy.getDataCy("data-connector-edit-body").should( "contain.text",