diff --git a/client/src/features/ProjectPageV2/ProjectPageContent/DataConnectors/ProjectConnectDataConnectorsModal.tsx b/client/src/features/ProjectPageV2/ProjectPageContent/DataConnectors/ProjectConnectDataConnectorsModal.tsx index b287607777..eba07c111a 100644 --- a/client/src/features/ProjectPageV2/ProjectPageContent/DataConnectors/ProjectConnectDataConnectorsModal.tsx +++ b/client/src/features/ProjectPageV2/ProjectPageContent/DataConnectors/ProjectConnectDataConnectorsModal.tsx @@ -388,7 +388,7 @@ function ProjectLinkDataConnectorBodyAndFooter({ - @@ -541,7 +541,7 @@ function ProjectDoiDataConnectorBodyAndFooter({ - diff --git a/client/src/features/dataConnectorsV2/components/DataConnectorActions.tsx b/client/src/features/dataConnectorsV2/components/DataConnectorActions.tsx index 946c45bb9d..c3f2764f98 100644 --- a/client/src/features/dataConnectorsV2/components/DataConnectorActions.tsx +++ b/client/src/features/dataConnectorsV2/components/DataConnectorActions.tsx @@ -57,7 +57,6 @@ import { import useDataConnectorPermissions from "../utils/useDataConnectorPermissions.hook"; import DataConnectorCredentialsModal from "./DataConnectorCredentialsModal"; -import DataConnectorModal from "./DataConnectorModal"; import { getDataConnectorScope } from "./dataConnector.utils"; interface DataConnectorRemoveModalProps { @@ -366,6 +365,7 @@ function DataConnectorActionsInner({ dataConnector, dataConnectorLink, toggleView, + toggleEdit, }: DataConnectorActionsProps) { const { id: dataConnectorId } = dataConnector; const scope = getDataConnectorScope(dataConnector.namespace); @@ -394,7 +394,6 @@ function DataConnectorActionsInner({ dataConnector.storage.sensitive_fields?.length > 0; const [isCredentialsOpen, setCredentialsOpen] = useState(false); const [isDeleteOpen, setIsDeleteOpen] = useState(false); - const [isEditOpen, setIsEditOpen] = useState(false); const [isUnlinkOpen, setIsUnlinkOpen] = useState(false); const onDelete = useCallback(() => { toggleView(); @@ -410,9 +409,7 @@ function DataConnectorActionsInner({ const toggleDelete = useCallback(() => { setIsDeleteOpen((open) => !open); }, []); - const toggleEdit = useCallback(() => { - setIsEditOpen((open) => !open); - }, []); + const toggleUnlink = useCallback(() => { setIsUnlinkOpen((open) => !open); }, []); @@ -422,7 +419,7 @@ function DataConnectorActionsInner({ ? [ { key: "data-connector-edit", - onClick: toggleEdit, + onClick: () => toggleEdit(3), content: ( <> @@ -430,6 +427,16 @@ function DataConnectorActionsInner({ ), }, + { + key: "data-connector-edit-connection", + onClick: () => toggleEdit(2), + content: ( + <> + + Edit Connection Information + + ), + }, ] : []), ...(requiresCredentials @@ -516,12 +523,6 @@ function DataConnectorActionsInner({ return ( <> {actionsContent} - void; + toggleEdit: (initialStep: number) => void; } export default function DataConnectorActions(props: DataConnectorActionsProps) { diff --git a/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalBody.tsx b/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalBody.tsx index 5b0c74a858..c7dc41ad28 100644 --- a/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalBody.tsx +++ b/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalBody.tsx @@ -17,10 +17,11 @@ */ import cx from "classnames"; -import { useCallback } from "react"; +import { useCallback, useEffect, useState } from "react"; import { Globe, Lock } from "react-bootstrap-icons"; import { Controller, useForm } from "react-hook-form"; -import { ButtonGroup, FormText, Input, Label } from "reactstrap"; +import { ButtonGroup, Collapse, Input, Label } from "reactstrap"; +import ChevronFlippedIcon from "../../../../components/icons/ChevronFlippedIcon"; import { Loader } from "../../../../components/Loader"; import { WarnAlert } from "../../../../components/Alert"; @@ -42,6 +43,7 @@ import { type AddStorageStepProps, } from "../../../project/components/cloudStorage/AddOrEditCloudStorage"; import { ProjectNamespaceControl } from "../../../projectsV2/fields/ProjectNamespaceFormField"; +import SlugPreviewFormField from "../../../projectsV2/fields/SlugPreviewFormField"; import type { DataConnectorSecret } from "../../api/data-connectors.api"; import dataConnectorFormSlice from "../../state/dataConnectors.slice"; @@ -73,12 +75,6 @@ export default function DataConnectorModalBody({ if (schemata.length < 1) return ; return ( <> - {!flatDataConnector.dataConnectorId && ( -

- Add published datasets from data repositories for use in your project. - Or, connect to cloud storage to read and write custom data. -

- )} + {!flatDataConnector.dataConnectorId && cloudStorageState.step <= 1 && ( +

+ Add published datasets from data repositories for use in your + project. Or, connect to cloud storage to read and write custom data. +

+ )}
-
- -
+ {!flatDataConnector.dataConnectorId && ( +
+ +
+ )} state.dataConnectorFormSlice ); + const [isAdvancedSettingOpen, setIsAdvancedSettingsOpen] = useState(false); + const toggleIsOpen = useCallback( + () => + setIsAdvancedSettingsOpen( + (isAdvancedSettingOpen) => !isAdvancedSettingOpen + ), + [] + ); const { control, - formState: { errors, touchedFields }, + formState: { dirtyFields, errors, touchedFields }, setValue, getValues, + watch, } = useForm({ mode: "onChange", defaultValues: { @@ -268,10 +281,28 @@ export function DataConnectorMount() { (o) => flatDataConnector.options && flatDataConnector.options[o.name] ); + const currentName = watch("name"); + const currentSlug = watch("slug"); + useEffect(() => { + dispatch( + dataConnectorFormSlice.actions.setFlatDataConnector({ + flatDataConnector: { ...getValues() }, + }) + ); + }, [currentSlug, getValues, dispatch]); + const resetUrl = useCallback(() => { + setValue("slug", slugFromTitle(currentName, true, true), { + shouldValidate: true, + }); + }, [setValue, currentName]); + const dataConnectorId = flatDataConnector.dataConnectorId; + const parentPath = `/${flatDataConnector.namespace}/`; return (
-
Final details
-

We need a few more details to mount your data properly.

+ {!dataConnectorId &&
Final details
} +

+ Set how your data connector displays in Renku and who can access it. +

- - ( - { - field.onChange(e); - onFieldValueChange("slug", e.target.value); - }} - /> - )} - rules={{ - required: true, - maxLength: 99, - pattern: - /^(?!.*\.git$|.*\.atom$|.*[-._][-._].*)[a-z0-9][a-z0-9\-_.]*$/, - }} + resetFunction={resetUrl} + slug={currentSlug} + dirtyFields={dirtyFields} + label="Project URL" + entityName="data-connector" + parentPath={parentPath} /> -
- Please provide a slug consisting of lowercase letters, numbers, and - hyphens. -
- - A short, machine-readable identifier for the data connector, - restricted to lowercase letters, numbers, and hyphens. -
@@ -466,63 +464,44 @@ export function DataConnectorMount() {
- - - ( - { - field.onChange(e); - onFieldValueChange("mountPoint", e.target.value); - }} - /> - )} - rules={{ required: true }} - /> -
Please provide a mount point.
-
- This is the name of the folder where you will find your external - storage in sessions. You should pick something different from the - folders used in the projects repository, and from folders mounted by - other storage services. -
-
- -
- - ( - { - field.onChange(e); - onFieldValueChange("readOnly", e.target.checked); - }} - value="" - checked={flatDataConnector.readOnly ?? false} - /> +
+ +
+ { + field.onChange(e); + onFieldValueChange("readOnly", e.target.checked); + }} + value="" + checked={flatDataConnector.readOnly ?? false} + /> +
+
)} rules={{ required: true }} /> @@ -544,6 +523,65 @@ export function DataConnectorMount() {
+
+ +
+ + +
+ + + ( + { + field.onChange(e); + onFieldValueChange("mountPoint", e.target.value); + }} + /> + )} + rules={{ required: true }} + /> +
Please provide a mount point.
+
+ This is the name of the folder where you will find your external + storage in sessions. You should pick something different from the + folders used in the projects repository, and from folders mounted by + other storage services. +
+
+
+ {flatDataConnector.dataConnectorId == null && hasPasswordFieldWithInput && validationResult?.isSuccess && ( diff --git a/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalFooter.tsx b/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalFooter.tsx index cc6fac81b1..8c88897b1e 100644 --- a/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalFooter.tsx +++ b/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalFooter.tsx @@ -66,6 +66,7 @@ interface DataConnectorModalFooterProps { isOpen: boolean; project?: Project; toggle: () => void; + initialStep?: number; } function DataConnectorCreateFooter({ @@ -354,34 +355,36 @@ function DataConnectorCreateFooter({ setState={setStateSafe} /> - {!isResultLoading && !success && ( - - )} - {!isResultLoading && ( - - )} - {!success && ( - - )} +
+ {!isResultLoading && !success && ( + + )} + {!isResultLoading && ( + + )} + {!success && ( + + )} +
); } @@ -395,6 +398,7 @@ function DataConnectorEditFooter({ dataConnector, isOpen, toggle, + initialStep, }: DataConnectorEditFooterProps) { const dataConnectorId = dataConnector.id; const dispatch = useAppDispatch(); @@ -410,18 +414,6 @@ function DataConnectorEditFooter({ success, } = useAppSelector((state) => state.dataConnectorFormSlice); - // Enhanced setters - const setStateSafe = useCallback( - (newState: Partial) => { - dispatch( - dataConnectorFormSlice.actions.setCloudStorageState({ - cloudStorageState: newState, - }) - ); - }, - [dispatch] - ); - // Mutations const [updateDataConnector, updateResult] = usePatchDataConnectorsByDataConnectorIdMutation(); @@ -541,12 +533,13 @@ function DataConnectorEditFooter({ )} -
- -
+ )} {!isResultLoading && !success && ( ); return ( - + (!initialStep || initialStep === 2) && ( + + ) ); } @@ -172,6 +176,7 @@ export function DataConnectorModalContinueButton({ continueId="add-data-connector-continue" step={cloudStorageState.step} testId="test-data-connector" + editDataConnector={addOrEditStorage} /> {disableContinueButton && ( void; } function TestConnectionAndContinueButtons({ continueId, step, testId, + editDataConnector, }: TestConnectionAndContinueButtonsProps) { const dispatch = useAppDispatch(); const { flatDataConnector, isActionOngoing, validationResultIsCurrent } = @@ -367,22 +374,81 @@ function TestConnectionAndContinueButtons({ const buttonContinueId = `${continueId}-button`; const divContinueId = `${continueId}-div`; - const continueContent = validationResult.isSuccess ? ( - <> - Continue - - ) : validationResult.isError ? ( - <> - Skip Test - + const dataConnectorId = flatDataConnector.dataConnectorId; + const continueContent = + dataConnectorId ? null : validationResult.isSuccess ? ( + <> + Continue + + ) : validationResult.isError ? ( + <> + Skip Test + + ) : null; + + const updateContent = dataConnectorId ? ( + validationResult.isSuccess ? ( + <> + Update connector + + ) : ( + <> + Skip Test and Save + + ) ) : null; const continueColorClass = validationResult.isSuccess ? "btn-primary" : validationResult.isError ? "btn-outline-danger" : "btn-primary"; - const continueSection = - !validationResult.isError && !validationResult.isSuccess ? null : ( + const continueSection = dataConnectorId ? null : !validationResult.isError && + !validationResult.isSuccess ? null : ( +
+ + {validationResult.isError && ( + + The connection is not working as configured. You can make changes and + try again, or skip and continue. + + )} +
+ ); + + const saveSection = + !dataConnectorId || !editDataConnector ? null : !validationResult.isError && + !validationResult.isSuccess ? null : (
{validationResult.isError && ( The connection is not working as configured. You can make changes - and try again, or skip and continue. + and try again, or skip and save. )}
@@ -429,6 +473,7 @@ function TestConnectionAndContinueButtons({
{testConnectionSection} {continueSection} + {saveSection}
); } diff --git a/client/src/features/dataConnectorsV2/components/DataConnectorModal/index.tsx b/client/src/features/dataConnectorsV2/components/DataConnectorModal/index.tsx index 3ceb0fca81..d8ec3268c2 100644 --- a/client/src/features/dataConnectorsV2/components/DataConnectorModal/index.tsx +++ b/client/src/features/dataConnectorsV2/components/DataConnectorModal/index.tsx @@ -54,6 +54,7 @@ export function DataConnectorModalBodyAndFooter({ namespace, project, toggle, + initialStep, }: DataConnectorModalProps) { const dataConnectorId = dataConnector?.id ?? null; // Fetch available schema when users open the modal @@ -81,7 +82,7 @@ export function DataConnectorModalBodyAndFooter({ dataConnector != null ? { ...EMPTY_CLOUD_STORAGE_STATE, - step: 2, + step: initialStep ?? 2, completedSteps: CLOUD_STORAGE_TOTAL_STEPS, } : EMPTY_CLOUD_STORAGE_STATE; @@ -92,7 +93,7 @@ export function DataConnectorModalBodyAndFooter({ schemata: schemata ?? [], }) ); - }, [dataConnector, dispatch, namespace, project, schemata]); + }, [dataConnector, dispatch, namespace, project, schemata, initialStep]); // Visual elements return ( @@ -116,6 +117,7 @@ export function DataConnectorModalBodyAndFooter({ isOpen={isOpen} project={project} toggle={toggle} + initialStep={initialStep} />
@@ -145,7 +147,8 @@ interface DataConnectorModalProps { isOpen: boolean; namespace?: string; project?: Project; - toggle: () => void; + toggle: (initialStep?: number) => void; + initialStep?: number; } export default function DataConnectorModal({ dataConnector = null, @@ -153,6 +156,7 @@ export default function DataConnectorModal({ namespace, project, toggle: originalToggle, + initialStep, }: DataConnectorModalProps) { const dataConnectorId = dataConnector?.id ?? null; const scope = getDataConnectorScope(dataConnector?.namespace); @@ -179,7 +183,10 @@ export default function DataConnectorModal({ toggle={toggle} > - + {!isLoadingPermissions && dataConnectorId != null && @@ -194,6 +201,7 @@ export default function DataConnectorModal({ namespace, project, toggle, + initialStep, }} /> } @@ -226,14 +234,17 @@ export default function DataConnectorModal({ interface DataConnectorModalHeaderProps { dataConnectorId: string | null; + initialStep?: number; } export function DataConnectorModalHeader({ dataConnectorId, + initialStep, }: DataConnectorModalHeaderProps) { return ( <> {" "} {dataConnectorId ? "Edit" : "Add"} data connector + {!initialStep ? "" : initialStep === 2 ? " connection information" : ""} ); } diff --git a/client/src/features/dataConnectorsV2/components/DataConnectorView.tsx b/client/src/features/dataConnectorsV2/components/DataConnectorView.tsx index 55dc1933b0..e5258fa706 100644 --- a/client/src/features/dataConnectorsV2/components/DataConnectorView.tsx +++ b/client/src/features/dataConnectorsV2/components/DataConnectorView.tsx @@ -17,7 +17,7 @@ */ import { skipToken } from "@reduxjs/toolkit/query"; import cx from "classnames"; -import { useMemo, useRef } from "react"; +import { useCallback, useMemo, useRef, useState } from "react"; import { Folder, Gear, @@ -26,11 +26,13 @@ import { Journals, Key, Lock, + Pencil, PersonBadge, } from "react-bootstrap-icons"; import { Link, generatePath } from "react-router"; import { Badge, + Button, Offcanvas, OffcanvasBody, UncontrolledTooltip, @@ -41,6 +43,7 @@ import { Loader } from "../../../components/Loader"; import LazyRenkuMarkdown from "../../../components/markdown/LazyRenkuMarkdown"; import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; import { toCapitalized } from "../../../utils/helpers/HelperFunctions"; +import PermissionsGuard from "../../permissionsV2/PermissionsGuard"; import { CredentialMoreInfo } from "../../project/components/cloudStorage/CloudStorageItem"; import { CLOUD_STORAGE_SENSITIVE_FIELD_TOKEN, @@ -56,6 +59,7 @@ import type { DataConnectorToProjectLink, } from "../api/data-connectors.api"; import { useGetDataConnectorsByDataConnectorIdSecretsQuery } from "../api/data-connectors.enhanced-api"; +import useDataConnectorPermissions from "../utils/useDataConnectorPermissions.hook"; import { DATA_CONNECTORS_VISIBILITY_WARNING } from "./dataConnector.constants"; import { getDataConnectorScope, @@ -63,6 +67,7 @@ import { useGetDataConnectorSource, } from "./dataConnector.utils"; import DataConnectorActions from "./DataConnectorActions"; +import DataConnectorModal from "./DataConnectorModal"; import useDataConnectorProjects from "./useDataConnectorProjects.hook"; const SECTION_CLASSES = [ @@ -94,6 +99,7 @@ interface DataConnectorViewProps { dataConnectorLink?: DataConnectorToProjectLink; showView: boolean; toggleView: () => void; + toggleEdit: (initialStep?: number) => void; dataConnectorPotentiallyInaccessible?: boolean; } export default function DataConnectorView({ @@ -102,7 +108,15 @@ export default function DataConnectorView({ showView, toggleView, dataConnectorPotentiallyInaccessible = false, -}: DataConnectorViewProps) { +}: Omit) { + const [isEditOpen, setIsEditOpen] = useState(false); + const [initialStep, setInitialStep] = useState(2); + + const toggleEdit = useCallback((initialStep?: number) => { + if (initialStep) setInitialStep(initialStep); + setIsEditOpen((open) => !open); + }, []); + return ( - - + + + ); } @@ -187,7 +211,7 @@ function DataConnectorViewAccess({ {anySensitiveField && requiredCredentials && requiredCredentials.length > 0 && ( -
+

Required credentials

)} - - {storageDefinition.readonly - ? "Force Read-only" - : "Allow Read-Write (requires adequate privileges on the storage)"} - ); @@ -255,27 +274,84 @@ function DataConnectorViewAccess({ function DataConnectorViewConfiguration({ dataConnector, -}: Pick) { + toggleEdit, +}: Pick) { const storageDefinition = dataConnector.storage; + const { permissions } = useDataConnectorPermissions({ + dataConnectorId: dataConnector.id, + }); + const credentialFieldDefinitions = useMemo( + () => + getCredentialFieldDefinitions({ + storage: storageDefinition, + sensitive_fields: storageDefinition.sensitive_fields, + }), + [storageDefinition] + ); + const requiredCredentials = useMemo( + () => + credentialFieldDefinitions?.filter((field) => field.requiredCredential), + [credentialFieldDefinitions] + ); + const nonRequiredCredentialConfigurationKeys = Object.keys( + storageDefinition.configuration + ).filter((k) => !requiredCredentials?.some((f) => f.name === k)); + const scope = useMemo( + () => getDataConnectorScope(dataConnector.namespace), + [dataConnector.namespace] + ); + const hasAccessMode = useMemo( + () => STORAGES_WITH_ACCESS_MODE.includes(storageDefinition.storage_type), + [storageDefinition.storage_type] + ); return (
-
-

+
+

- Configuration + {scope === "global" ? "Configuration" : "Connection Information"}

+ + + + Modify connection information + + + } + requestedPermission="write" + userPermissions={permissions} + />
+ {scope !== "global" && + nonRequiredCredentialConfigurationKeys.map((key) => { + const title = + key == "provider" && hasAccessMode ? "Mode" : toCapitalized(key); + const value = storageDefinition.configuration[key]?.toString() ?? ""; + return ( + + {value} + + ); + })}
{storageDefinition.source_path} - }> - {storageDefinition.target_path} -

); @@ -285,15 +361,18 @@ function DataConnectorViewHeader({ dataConnector, dataConnectorLink, toggleView, + toggleEdit, }: Omit) { return (
+ Data connector

@@ -313,7 +392,7 @@ function DataConnectorViewProjects({ className={cx(SECTION_CLASSES)} data-cy="data-connector-projects-section" > -

+

Projects

@@ -360,22 +439,6 @@ function DataConnectorViewMetadata({ dataConnectorPotentiallyInaccessible, }: DataConnectorViewMetadataProps) { const storageDefinition = dataConnector.storage; - const credentialFieldDefinitions = useMemo( - () => - getCredentialFieldDefinitions({ - storage: storageDefinition, - sensitive_fields: storageDefinition.sensitive_fields, - }), - [storageDefinition] - ); - const requiredCredentials = useMemo( - () => - credentialFieldDefinitions?.filter((field) => field.requiredCredential), - [credentialFieldDefinitions] - ); - const nonRequiredCredentialConfigurationKeys = Object.keys( - storageDefinition.configuration - ).filter((k) => !requiredCredentials?.some((f) => f.name === k)); const { data: namespace, isLoading: isLoadingNamespace } = useGetNamespacesByNamespaceSlugQuery( dataConnector.namespace @@ -409,11 +472,6 @@ function DataConnectorViewMetadata({ [dataConnector.namespace, namespace, scope] ); - const hasAccessMode = useMemo( - () => STORAGES_WITH_ACCESS_MODE.includes(storageDefinition.storage_type), - [storageDefinition.storage_type] - ); - const identifier = useMemo( () => scope === "global" @@ -448,6 +506,12 @@ function DataConnectorViewMetadata({
+ {dataConnector.description && ( + + + + )} + {scope === "global" ? ( <> @@ -554,23 +618,14 @@ function DataConnectorViewMetadata({ )} - {dataConnector.description && ( - - - - )} - - {scope !== "global" && - nonRequiredCredentialConfigurationKeys.map((key) => { - const title = - key == "provider" && hasAccessMode ? "Mode" : toCapitalized(key); - const value = storageDefinition.configuration[key]?.toString() ?? ""; - return ( - - {value} - - ); - })} + }> + {storageDefinition.target_path} + + + {storageDefinition.readonly + ? "Force Read-only" + : "Allow Read-Write (requires adequate privileges on the storage)"} + ); } diff --git a/client/src/features/dataConnectorsV2/components/DataConnectorsBoxListDisplay.tsx b/client/src/features/dataConnectorsV2/components/DataConnectorsBoxListDisplay.tsx index 7a6b9514e0..28bdbfe50b 100644 --- a/client/src/features/dataConnectorsV2/components/DataConnectorsBoxListDisplay.tsx +++ b/client/src/features/dataConnectorsV2/components/DataConnectorsBoxListDisplay.tsx @@ -116,6 +116,7 @@ export default function DataConnectorBoxListDisplay({ action className={cx("cursor-pointer", "link-primary", "text-body")} onClick={toggleDetails} + data-cy="data-connector-item" >
diff --git a/client/src/features/project/components/cloudStorage/AddOrEditCloudStorage.tsx b/client/src/features/project/components/cloudStorage/AddOrEditCloudStorage.tsx index 8baf2b3b68..822221ab8f 100644 --- a/client/src/features/project/components/cloudStorage/AddOrEditCloudStorage.tsx +++ b/client/src/features/project/components/cloudStorage/AddOrEditCloudStorage.tsx @@ -45,8 +45,9 @@ import { import { WarnAlert } from "../../../../components/Alert"; import { ExternalLink } from "../../../../components/ExternalLinks"; +import useAppSelector from "../../../../utils/customHooks/useAppSelector.hook"; import type { DataConnectorSecret } from "../../../dataConnectorsV2/api/data-connectors.api"; -import { hasSchemaAccessMode } from "../../../dataConnectorsV2/components/dataConnector.utils.ts"; +import { hasSchemaAccessMode } from "../../../dataConnectorsV2/components/dataConnector.utils"; import { convertFromAdvancedConfig, getSchemaOptions, @@ -872,7 +873,10 @@ export function AddStorageOptions({ storage.provider ); const { control, setValue, getValues } = useForm(); - + const { flatDataConnector } = useAppSelector( + (state) => state.dataConnectorFormSlice + ); + const dataConnectorId = flatDataConnector.dataConnectorId; const onFieldValueChange = ( option: string, value: string | number | boolean @@ -1008,7 +1012,7 @@ export function AddStorageOptions({ return ( -
Options
+ {!dataConnectorId &&
Connection information
}

Please fill in all the options required to connect to your storage. Mind that the specific fields required depend on your storage configuration. diff --git a/client/src/features/project/components/cloudStorage/AddStorageBreadcrumbNavbar.tsx b/client/src/features/project/components/cloudStorage/AddStorageBreadcrumbNavbar.tsx index c996816297..a489f70775 100644 --- a/client/src/features/project/components/cloudStorage/AddStorageBreadcrumbNavbar.tsx +++ b/client/src/features/project/components/cloudStorage/AddStorageBreadcrumbNavbar.tsx @@ -48,7 +48,9 @@ export default function AddStorageBreadcrumbNavbar({ return ( {active ? ( - <>{mapStepToName[stepNumber]} + + {mapStepToName[stepNumber]} + ) : ( <>