Skip to content

Commit 9bb0f67

Browse files
committed
feat!: introduce data connectors (#3315) (#3330) (#3334) (#3346) (#3359)
* 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) * minor: cosmetic improvements to data connectors (#3359) BREAKING CHANGE: Requires renku-data-services version >= 0.xx.0 BREAKING CHANGE: Requires renku-notebooks version >= 0.xx.0
1 parent ced56b6 commit 9bb0f67

File tree

79 files changed

+7641
-4219
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+7641
-4219
lines changed

client/.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
*.svg
33
# Generated files should not be linted
44
src/features/projectsV2/api/storagesV2.api.ts
5+
src/features/dataConnectorsV2/api/data-connectors.api.ts

client/.eslintrc.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
"codemirror",
122122
"compat",
123123
"craco",
124+
"crosshair",
124125
"dagre",
125126
"dataset",
126127
"datasets",
@@ -263,6 +264,7 @@
263264
"ulid",
264265
"uncompress",
265266
"unicode",
267+
"unlink",
266268
"unmount",
267269
"unschedulable",
268270
"unstar",

client/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@
2222
"storybook-wait-server": "wait-on http://127.0.0.1:6006",
2323
"storybook-test": "test-storybook",
2424
"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'",
25-
"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",
25+
"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",
26+
"generate-api:data-connectors": "rtk-query-codegen-openapi src/features/dataConnectorsV2/api/data-connectors.api-config.ts",
2627
"generate-api:dataServicesUser": "rtk-query-codegen-openapi src/features/user/dataServicesUser.api/dataServicesUser.api-config.ts",
2728
"generate-api:namespaceV2": "rtk-query-codegen-openapi src/features/projectsV2/api/namespace.api-config.ts",
2829
"generate-api:projectV2": "rtk-query-codegen-openapi src/features/projectsV2/api/projectV2.api-config.ts",
2930
"generate-api:platform": "rtk-query-codegen-openapi src/features/platform/api/platform.api-config.ts",
30-
"generate-api:searchV2": "rtk-query-codegen-openapi src/features/searchV2/api/searchV2.api-config.ts",
31-
"generate-api:storages": "rtk-query-codegen-openapi src/features/projectsV2/api/storages.api-config.ts"
31+
"generate-api:searchV2": "rtk-query-codegen-openapi src/features/searchV2/api/searchV2.api-config.ts"
3232
},
3333
"type": "module",
3434
"dependencies": {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*!
2+
* Copyright 2024 - Swiss Data Science Center (SDSC)
3+
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
4+
* Eidgenössische Technische Hochschule Zürich (ETHZ).
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
interface RenkuIconProps {
20+
className?: string;
21+
id?: string;
22+
}
23+
24+
export default function CrosshairIcon({ className, id }: RenkuIconProps) {
25+
return (
26+
<svg
27+
aria-hidden
28+
className={className}
29+
id={id}
30+
role="img"
31+
width="16"
32+
height="16"
33+
fill="currentColor"
34+
viewBox="0 0 16 16"
35+
xmlns="http://www.w3.org/2000/svg"
36+
>
37+
{/* eslint-disable spellcheck/spell-checker */}
38+
<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" />
39+
{/* eslint-enable spellcheck/spell-checker */}
40+
</svg>
41+
);
42+
}
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
/*!
2+
* Copyright 2024 - Swiss Data Science Center (SDSC)
3+
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
4+
* Eidgenössische Technische Hochschule Zürich (ETHZ).
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License
17+
*/
18+
19+
import cx from "classnames";
20+
import { useCallback, useEffect, useState } from "react";
21+
import { Database, NodePlus, PlusLg, XLg } from "react-bootstrap-icons";
22+
import { Controller, useForm } from "react-hook-form";
23+
import {
24+
Button,
25+
ButtonGroup,
26+
Form,
27+
Input,
28+
Label,
29+
Modal,
30+
ModalBody,
31+
ModalHeader,
32+
ModalFooter,
33+
} from "reactstrap";
34+
35+
import { RtkOrNotebooksError } from "../../../../components/errors/RtkErrorAlert";
36+
import { Loader } from "../../../../components/Loader";
37+
import useAppDispatch from "../../../../utils/customHooks/useAppDispatch.hook";
38+
39+
import {
40+
dataConnectorsApi,
41+
usePostDataConnectorsByDataConnectorIdProjectLinksMutation,
42+
} from "../../../dataConnectorsV2/api/data-connectors.enhanced-api";
43+
import DataConnectorModal, {
44+
DataConnectorModalBodyAndFooter,
45+
} from "../../../dataConnectorsV2/components/DataConnectorModal/index";
46+
import styles from "../../../dataConnectorsV2/components/DataConnectorModal/DataConnectorModal.module.scss";
47+
48+
import type { Project } from "../../../projectsV2/api/projectV2.api";
49+
import { projectV2Api } from "../../../projectsV2/api/projectV2.enhanced-api";
50+
51+
interface ProjectConnectDataConnectorsModalProps
52+
extends Omit<
53+
Parameters<typeof DataConnectorModal>[0],
54+
"dataConnector" | "projectId"
55+
> {
56+
project: Project;
57+
}
58+
59+
type ProjectConnectDataConnectorMode = "create" | "link";
60+
61+
export default function ProjectConnectDataConnectorsModal({
62+
isOpen,
63+
namespace,
64+
project,
65+
toggle,
66+
}: ProjectConnectDataConnectorsModalProps) {
67+
const [mode, setMode] = useState<ProjectConnectDataConnectorMode>("link");
68+
return (
69+
<Modal
70+
backdrop="static"
71+
centered
72+
className={styles.modal}
73+
data-cy="project-data-connector-connect-modal"
74+
fullscreen="lg"
75+
id={"connect-project-data-connector"}
76+
isOpen={isOpen}
77+
scrollable
78+
size="lg"
79+
unmountOnClose={false}
80+
toggle={toggle}
81+
>
82+
<ModalHeader
83+
toggle={toggle}
84+
data-cy="project-data-connector-connect-header"
85+
>
86+
<ProjectConnectDataConnectorModalHeader mode={mode} setMode={setMode} />
87+
</ModalHeader>
88+
{mode === "create" ? (
89+
<ProjectCreateDataConnectorBodyAndFooter
90+
{...{
91+
isOpen,
92+
namespace,
93+
project,
94+
toggle,
95+
}}
96+
/>
97+
) : (
98+
<ProjectLinkDataConnectorBodyAndFooter
99+
{...{
100+
isOpen,
101+
namespace,
102+
project,
103+
toggle,
104+
}}
105+
/>
106+
)}
107+
</Modal>
108+
);
109+
}
110+
111+
function ProjectConnectDataConnectorModalHeader({
112+
mode,
113+
setMode,
114+
}: {
115+
mode: ProjectConnectDataConnectorMode;
116+
setMode: (mode: ProjectConnectDataConnectorMode) => void;
117+
}) {
118+
return (
119+
<>
120+
<div>
121+
<Database className={cx("bi", "me-1")} /> Link or create data connector
122+
</div>
123+
<div className="mt-3">
124+
<ButtonGroup>
125+
<Input
126+
type="radio"
127+
className="btn-check"
128+
id="project-data-controller-mode-link"
129+
value="link"
130+
checked={mode === "link"}
131+
onChange={() => {
132+
setMode("link");
133+
}}
134+
/>
135+
<Label
136+
data-cy="project-data-controller-mode-link"
137+
for="project-data-controller-mode-link"
138+
className={cx("btn", "btn-outline-primary")}
139+
>
140+
<NodePlus className={cx("bi", "me-1")} />
141+
Link a data connector
142+
</Label>
143+
<Input
144+
type="radio"
145+
className="btn-check"
146+
id="project-data-controller-mode-create"
147+
value="create"
148+
checked={mode === "create"}
149+
onChange={() => {
150+
setMode("create");
151+
}}
152+
/>
153+
<Label
154+
data-cy="project-data-controller-mode-create"
155+
for="project-data-controller-mode-create"
156+
className={cx("btn", "btn-outline-primary")}
157+
>
158+
<PlusLg className={cx("bi", "me-1")} />
159+
Create a data connector
160+
</Label>
161+
</ButtonGroup>
162+
</div>
163+
</>
164+
);
165+
}
166+
167+
function ProjectCreateDataConnectorBodyAndFooter({
168+
isOpen,
169+
namespace,
170+
project,
171+
toggle,
172+
}: ProjectConnectDataConnectorsModalProps) {
173+
return (
174+
<DataConnectorModalBodyAndFooter
175+
dataConnector={null}
176+
{...{
177+
isOpen,
178+
namespace,
179+
project,
180+
toggle,
181+
}}
182+
/>
183+
);
184+
}
185+
186+
interface DataConnectorLinkFormFields {
187+
dataConnectorIdentifier: string;
188+
}
189+
190+
function ProjectLinkDataConnectorBodyAndFooter({
191+
project,
192+
toggle,
193+
}: ProjectConnectDataConnectorsModalProps) {
194+
const dispatch = useAppDispatch();
195+
const [
196+
linkDataConnector,
197+
{ error: linkDataConnectorError, isLoading, isSuccess },
198+
] = usePostDataConnectorsByDataConnectorIdProjectLinksMutation();
199+
const {
200+
control,
201+
formState: { errors },
202+
handleSubmit,
203+
setError,
204+
} = useForm<DataConnectorLinkFormFields>({
205+
defaultValues: {
206+
dataConnectorIdentifier: "",
207+
},
208+
});
209+
210+
const onSubmit = useCallback(
211+
async (values: DataConnectorLinkFormFields) => {
212+
const [namespace, slug] = values.dataConnectorIdentifier.split("/");
213+
const dataConnectorPromise = dispatch(
214+
dataConnectorsApi.endpoints.getNamespacesByNamespaceDataConnectorsAndSlug.initiate(
215+
{ namespace, slug }
216+
)
217+
);
218+
const { data: dataConnector, isSuccess } = await dataConnectorPromise;
219+
dataConnectorPromise.unsubscribe();
220+
if (!isSuccess || dataConnector == null) {
221+
setError("dataConnectorIdentifier", {
222+
type: "manual",
223+
message: "Data connector not found",
224+
});
225+
return false;
226+
}
227+
linkDataConnector({
228+
dataConnectorId: dataConnector.id,
229+
dataConnectorToProjectLinkPost: {
230+
project_id: project.id,
231+
},
232+
});
233+
},
234+
[dispatch, linkDataConnector, project.id, setError]
235+
);
236+
237+
useEffect(() => {
238+
if (isSuccess) {
239+
dispatch(projectV2Api.util.invalidateTags(["DataConnectors"]));
240+
toggle();
241+
}
242+
}, [dispatch, isSuccess, toggle]);
243+
244+
return (
245+
<Form noValidate onSubmit={handleSubmit(onSubmit)}>
246+
<ModalBody data-cy="data-connector-edit-body">
247+
<div className="mb-3">
248+
<Label className="form-label" for="data-connector-identifier">
249+
Data connector identifier
250+
</Label>
251+
<Controller
252+
control={control}
253+
name="dataConnectorIdentifier"
254+
render={({ field }) => (
255+
<Input
256+
className={cx(
257+
"form-control",
258+
errors.dataConnectorIdentifier && "is-invalid"
259+
)}
260+
id="data-connector-identifier"
261+
placeholder="namespace/slug"
262+
type="text"
263+
{...field}
264+
/>
265+
)}
266+
rules={{
267+
required: true,
268+
pattern: /^(.+)\/(.+)$/,
269+
}}
270+
/>
271+
<div className="form-text">
272+
The the info sidebar for a data connector shows the identifier.
273+
</div>
274+
<div className="invalid-feedback">
275+
Please provide an identifier (namespace/group) for the data
276+
connector
277+
</div>
278+
</div>
279+
{isSuccess != null && !isSuccess && (
280+
<RtkOrNotebooksError error={linkDataConnectorError} />
281+
)}
282+
</ModalBody>
283+
284+
<ModalFooter className="border-top" data-cy="data-connector-edit-footer">
285+
<Button color="outline-danger" onClick={toggle}>
286+
<XLg className={cx("bi", "me-1")} />
287+
Cancel
288+
</Button>
289+
<Button
290+
color="primary"
291+
data-cy="link-data-connector-button"
292+
disabled={isLoading}
293+
type="submit"
294+
>
295+
{isLoading ? (
296+
<Loader className="me-1" inline size={16} />
297+
) : (
298+
<NodePlus className={cx("bi", "me-1")} />
299+
)}
300+
Link data
301+
</Button>
302+
</ModalFooter>
303+
</Form>
304+
);
305+
}

0 commit comments

Comments
 (0)