Skip to content

Commit

Permalink
feat!: introduce data connectors (#3315) (#3330) (#3334) (#3346)
Browse files Browse the repository at this point in the history
* feat: support for data connectors on user and group namespaces (#3315)
* feat: set/edit secrets for data connectors (#3330)
* feat: data connectors on the project page (#3334)
* feat: support launching sessions with data connectors (#3346)

BREAKING CHANGE: Requires renku-data-services version >= 0.xx.0
BREAKING CHANGE: Requires renku-notebooks version >= 0.xx.0

build: update to latest project api spec
  • Loading branch information
ciyer committed Oct 14, 2024
1 parent 30c7172 commit e3d41c7
Show file tree
Hide file tree
Showing 75 changed files with 7,630 additions and 4,192 deletions.
1 change: 1 addition & 0 deletions client/.eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
*.svg
# Generated files should not be linted
src/features/projectsV2/api/storagesV2.api.ts
src/features/dataConnectorsV2/api/data-connectors.api.ts
2 changes: 2 additions & 0 deletions client/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
"codemirror",
"compat",
"craco",
"crosshair",
"dagre",
"dataset",
"datasets",
Expand Down Expand Up @@ -263,6 +264,7 @@
"ulid",
"uncompress",
"unicode",
"unlink",
"unmount",
"unschedulable",
"unstar",
Expand Down
6 changes: 3 additions & 3 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
"storybook-wait-server": "wait-on http://127.0.0.1:6006",
"storybook-test": "test-storybook",
"storybook-compile-and-test": "concurrently -k -s first -n 'BUILD,TEST' -c 'magenta,blue' 'npm run storybook-build && npm run storybook-start-server' 'npm run storybook-wait-server && npm run storybook-test'",
"generate-api": "npm run generate-api:dataServicesUser && npm run generate-api:namespaceV2 && npm run generate-api:projectV2 && npm run generate-api:platform && npm run generate-api:searchV2 && npm run generate-api:storages",
"generate-api": "npm run generate-api:data-connectors && npm run generate-api:dataServicesUser && npm run generate-api:namespaceV2 && npm run generate-api:projectV2 && npm run generate-api:platform && npm run generate-api:searchV2",
"generate-api:data-connectors": "rtk-query-codegen-openapi src/features/dataConnectorsV2/api/data-connectors.api-config.ts",
"generate-api:dataServicesUser": "rtk-query-codegen-openapi src/features/user/dataServicesUser.api/dataServicesUser.api-config.ts",
"generate-api:namespaceV2": "rtk-query-codegen-openapi src/features/projectsV2/api/namespace.api-config.ts",
"generate-api:projectV2": "rtk-query-codegen-openapi src/features/projectsV2/api/projectV2.api-config.ts",
"generate-api:platform": "rtk-query-codegen-openapi src/features/platform/api/platform.api-config.ts",
"generate-api:searchV2": "rtk-query-codegen-openapi src/features/searchV2/api/searchV2.api-config.ts",
"generate-api:storages": "rtk-query-codegen-openapi src/features/projectsV2/api/storages.api-config.ts"
"generate-api:searchV2": "rtk-query-codegen-openapi src/features/searchV2/api/searchV2.api-config.ts"
},
"type": "module",
"dependencies": {
Expand Down
42 changes: 42 additions & 0 deletions client/src/components/icons/CrosshairIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*!
* Copyright 2024 - Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

interface RenkuIconProps {
className?: string;
id?: string;
}

export default function CrosshairIcon({ className, id }: RenkuIconProps) {
return (
<svg
aria-hidden
className={className}
id={id}
role="img"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
>
{/* eslint-disable spellcheck/spell-checker */}
<path d="M8.5.5a.5.5 0 0 0-1 0v.518A7 7 0 0 0 1.018 7.5H.5a.5.5 0 0 0 0 1h.518A7 7 0 0 0 7.5 14.982v.518a.5.5 0 0 0 1 0v-.518A7 7 0 0 0 14.982 8.5h.518a.5.5 0 0 0 0-1h-.518A7 7 0 0 0 8.5 1.018zm-6.48 7A6 6 0 0 1 7.5 2.02v.48a.5.5 0 0 0 1 0v-.48a6 6 0 0 1 5.48 5.48h-.48a.5.5 0 0 0 0 1h.48a6 6 0 0 1-5.48 5.48v-.48a.5.5 0 0 0-1 0v.48A6 6 0 0 1 2.02 8.5h.48a.5.5 0 0 0 0-1zM8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4" />
{/* eslint-enable spellcheck/spell-checker */}
</svg>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
/*!
* Copyright 2024 - Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/

import cx from "classnames";
import { useCallback, useEffect, useState } from "react";
import { Database, NodePlus, PlusLg, XLg } from "react-bootstrap-icons";
import { Controller, useForm } from "react-hook-form";
import {
Button,
ButtonGroup,
Form,
Input,
Label,
Modal,
ModalBody,
ModalHeader,
ModalFooter,
} from "reactstrap";

import { RtkOrNotebooksError } from "../../../../components/errors/RtkErrorAlert";
import { Loader } from "../../../../components/Loader";
import useAppDispatch from "../../../../utils/customHooks/useAppDispatch.hook";

import {
dataConnectorsApi,
usePostDataConnectorsByDataConnectorIdProjectLinksMutation,
} from "../../../dataConnectorsV2/api/data-connectors.enhanced-api";
import DataConnectorModal, {
DataConnectorModalBodyAndFooter,
} from "../../../dataConnectorsV2/components/DataConnectorModal/index";
import styles from "../../../dataConnectorsV2/components/DataConnectorModal/DataConnectorModal.module.scss";

import type { Project } from "../../../projectsV2/api/projectV2.api";
import { projectV2Api } from "../../../projectsV2/api/projectV2.enhanced-api";

interface ProjectConnectDataConnectorsModalProps
extends Omit<
Parameters<typeof DataConnectorModal>[0],
"dataConnector" | "projectId"
> {
project: Project;
}

type ProjectConnectDataConnectorMode = "create" | "link";

export default function ProjectConnectDataConnectorsModal({
isOpen,
namespace,
project,
toggle,
}: ProjectConnectDataConnectorsModalProps) {
const [mode, setMode] = useState<ProjectConnectDataConnectorMode>("link");
return (
<Modal
backdrop="static"
centered
className={styles.modal}
data-cy="project-data-connector-connect-modal"
fullscreen="lg"
id={"connect-project-data-connector"}
isOpen={isOpen}
scrollable
size="lg"
unmountOnClose={false}
toggle={toggle}
>
<ModalHeader
toggle={toggle}
data-cy="project-data-connector-connect-header"
>
<ProjectConnectDataConnectorModalHeader mode={mode} setMode={setMode} />
</ModalHeader>
{mode === "create" ? (
<ProjectCreateDataConnectorBodyAndFooter
{...{
isOpen,
namespace,
project,
toggle,
}}
/>
) : (
<ProjectLinkDataConnectorBodyAndFooter
{...{
isOpen,
namespace,
project,
toggle,
}}
/>
)}
</Modal>
);
}

function ProjectConnectDataConnectorModalHeader({
mode,
setMode,
}: {
mode: ProjectConnectDataConnectorMode;
setMode: (mode: ProjectConnectDataConnectorMode) => void;
}) {
return (
<>
<div>
<Database className={cx("bi", "me-1")} /> Link or create data connector
</div>
<div className="mt-3">
<ButtonGroup>
<Input
type="radio"
className="btn-check"
id="project-data-controller-mode-link"
value="link"
checked={mode === "link"}
onChange={() => {
setMode("link");
}}
/>
<Label
data-cy="project-data-controller-mode-link"
for="project-data-controller-mode-link"
className={cx("btn", "btn-outline-primary")}
>
<NodePlus className={cx("bi", "me-1")} />
Link a data connector
</Label>
<Input
type="radio"
className="btn-check"
id="project-data-controller-mode-create"
value="create"
checked={mode === "create"}
onChange={() => {
setMode("create");
}}
/>
<Label
data-cy="project-data-controller-mode-create"
for="project-data-controller-mode-create"
className={cx("btn", "btn-outline-primary")}
>
<PlusLg className={cx("bi", "me-1")} />
Create a data connector
</Label>
</ButtonGroup>
</div>
</>
);
}

function ProjectCreateDataConnectorBodyAndFooter({
isOpen,
namespace,
project,
toggle,
}: ProjectConnectDataConnectorsModalProps) {
return (
<DataConnectorModalBodyAndFooter
dataConnector={null}
{...{
isOpen,
namespace,
project,
toggle,
}}
/>
);
}

interface DataConnectorLinkFormFields {
dataConnectorIdentifier: string;
}

function ProjectLinkDataConnectorBodyAndFooter({
project,
toggle,
}: ProjectConnectDataConnectorsModalProps) {
const dispatch = useAppDispatch();
const [
linkDataConnector,
{ error: linkDataConnectorError, isLoading, isSuccess },
] = usePostDataConnectorsByDataConnectorIdProjectLinksMutation();
const {
control,
formState: { errors },
handleSubmit,
setError,
} = useForm<DataConnectorLinkFormFields>({
defaultValues: {
dataConnectorIdentifier: "",
},
});

const onSubmit = useCallback(
async (values: DataConnectorLinkFormFields) => {
const [namespace, slug] = values.dataConnectorIdentifier.split("/");
const dataConnectorPromise = dispatch(
dataConnectorsApi.endpoints.getNamespacesByNamespaceDataConnectorsAndSlug.initiate(
{ namespace, slug }
)
);
const { data: dataConnector, isSuccess } = await dataConnectorPromise;
dataConnectorPromise.unsubscribe();
if (!isSuccess || dataConnector == null) {
setError("dataConnectorIdentifier", {
type: "manual",
message: "Data connector not found",
});
return false;
}
linkDataConnector({
dataConnectorId: dataConnector.id,
dataConnectorToProjectLinkPost: {
project_id: project.id,
},
});
},
[dispatch, linkDataConnector, project.id, setError]
);

useEffect(() => {
if (isSuccess) {
dispatch(projectV2Api.util.invalidateTags(["DataConnectors"]));
toggle();
}
}, [dispatch, isSuccess, toggle]);

return (
<Form noValidate onSubmit={handleSubmit(onSubmit)}>
<ModalBody data-cy="data-connector-edit-body">
<div className="mb-3">
<Label className="form-label" for="data-connector-identifier">
Data connector identifier
</Label>
<Controller
control={control}
name="dataConnectorIdentifier"
render={({ field }) => (
<Input
className={cx(
"form-control",
errors.dataConnectorIdentifier && "is-invalid"
)}
id="data-connector-identifier"
placeholder="namespace/slug"
type="text"
{...field}
/>
)}
rules={{
required: true,
pattern: /^(.+)\/(.+)$/,
}}
/>
<div className="invalid-feedback">
Please provide an identifier (namespace/group) for the data
connector
</div>
</div>
{isSuccess != null && !isSuccess && (
<RtkOrNotebooksError error={linkDataConnectorError} />
)}
</ModalBody>

<ModalFooter className="border-top" data-cy="data-connector-edit-footer">
<Button color="outline-danger" onClick={toggle}>
<XLg className={cx("bi", "me-1")} />
Cancel
</Button>
<Button
color="primary"
data-cy="link-data-connector-button"
disabled={isLoading}
type="submit"
>
{isLoading ? (
<Loader className="me-1" inline size={16} />
) : (
<NodePlus className={cx("bi", "me-1")} />
)}
Link data
</Button>
</ModalFooter>
</Form>
);
}
Loading

0 comments on commit e3d41c7

Please sign in to comment.