Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
Bullseye, // eslint-disable-line spellcheck/spell-checker
CheckLg,
Database,
DatabaseAdd,
Globe,
Journals,
Link45deg,
Expand All @@ -49,8 +50,10 @@ import {
import RtkOrDataServicesError from "~/components/errors/RtkOrDataServicesError";
import ExternalLink from "~/components/ExternalLink";
import RenkuBadge from "~/components/renkuBadge/RenkuBadge";
import { useGetDataConnectorsStorageAllowByProjectIdQuery } from "~/features/dataConnectorsV2/api/data-connectors.api";
import {
useGetProjectsByProjectIdDataConnectorLinksQuery,
useGetProjectsByProjectIdStorageQuery,
usePostDataConnectorsByDataConnectorIdProjectLinksMutation,
usePostDataConnectorsGlobalMutation,
} from "~/features/dataConnectorsV2/api/data-connectors.enhanced-api";
Expand All @@ -70,8 +73,11 @@ import ScrollableModal from "../../../../components/modal/ScrollableModal";
import useAppDispatch from "../../../../utils/customHooks/useAppDispatch.hook";
import useAppSelector from "../../../../utils/customHooks/useAppSelector.hook";
import dataConnectorFormSlice from "../../../dataConnectorsV2/state/dataConnectors.slice";
import PermissionsGuard from "../../../permissionsV2/PermissionsGuard";
import type { Project } from "../../../projectsV2/api/projectV2.api";
import { doiFromUrl } from "../../utils/dataConnectorUtils";
import useProjectPermissions from "../../utils/useProjectPermissions.hook";
import ProjectStorageForm from "../ProjectStorage/ProjectStorageForm";
import {
DC_LIKELY_DOI_ID,
DC_SEARCH_DOI_PREFIX,
Expand All @@ -89,10 +95,13 @@ interface ProjectConnectDataConnectorsModalProps extends Omit<
"dataConnector" | "projectId"
> {
project: Project;
switchMode?: () => void;
switchMode?: (mode: ProjectConnectDataConnectorMode) => void;
}

type ProjectConnectDataConnectorMode = "create" | "search";
export type ProjectConnectDataConnectorMode =
| "create"
| "search"
| "add-storage";

export default function ProjectConnectDataConnectorsModal({
isOpen,
Expand All @@ -106,10 +115,9 @@ export default function ProjectConnectDataConnectorsModal({
dispatch(dataConnectorFormSlice.actions.resetTransientState());
originalToggle();
}, [dispatch, originalToggle]);
const switchMode = useCallback(() => {
if (mode === "create") setMode("search");
else setMode("create");
}, [mode]);
const switchMode = useCallback((mode: ProjectConnectDataConnectorMode) => {
setMode(mode);
}, []);

return (
<ScrollableModal
Expand Down Expand Up @@ -138,7 +146,7 @@ export default function ProjectConnectDataConnectorsModal({
toggle,
}}
/>
) : (
) : mode === "search" ? (
<ProjectSearchDataConnectorBodyAndFooter
{...{
isOpen,
Expand All @@ -148,7 +156,17 @@ export default function ProjectConnectDataConnectorsModal({
toggle,
}}
/>
)}
) : mode === "add-storage" ? (
<ProjectStorageDataConnectorBodyAndFooter
{...{
isOpen,
namespace,
project,
switchMode,
toggle,
}}
/>
) : null}
</ScrollableModal>
);
}
Expand Down Expand Up @@ -196,10 +214,21 @@ function ProjectCreateDataConnectorBodyAndFooter({
export function ProjectConnectDataConnectorModeSwitch({
mode,
switchMode,
project,
}: {
mode: ProjectConnectDataConnectorMode;
switchMode: () => void;
switchMode: (mode: ProjectConnectDataConnectorMode) => void;
project: Project;
}) {
const permissions = useProjectPermissions({ projectId: project.id });
const { data: storageAllowData } =
useGetDataConnectorsStorageAllowByProjectIdQuery({
projectId: project.id,
});
const { data: projectStorage } = useGetProjectsByProjectIdStorageQuery({
projectId: project.id,
});

return (
<ButtonGroup>
<Input
Expand All @@ -208,7 +237,7 @@ export function ProjectConnectDataConnectorModeSwitch({
id="project-data-controller-mode-search"
value="search"
checked={mode === "search"}
onChange={switchMode}
onChange={() => switchMode("search")}
/>
<Label
data-cy="project-data-controller-mode-search"
Expand All @@ -230,7 +259,7 @@ export function ProjectConnectDataConnectorModeSwitch({
id="project-data-controller-mode-create"
value="create"
checked={mode === "create"}
onChange={switchMode}
onChange={() => switchMode("create")}
/>
<Label
data-cy="project-data-controller-mode-create"
Expand All @@ -245,10 +274,75 @@ export function ProjectConnectDataConnectorModeSwitch({
<PlusLg className={cx("fs-3", "me-1")} />
Create a data connector
</Label>

{storageAllowData && projectStorage?.length === 0 && (
<PermissionsGuard
disabled={null}
enabled={
<>
<Input
type="radio"
className="btn-check"
id="project-data-controller-mode-add-storage"
value="add-storage"
checked={mode === "add-storage"}
onChange={() => switchMode("add-storage")}
/>
<Label
data-cy="project-data-controller-mode-add-storage"
for="project-data-controller-mode-add-storage"
className={cx(
"align-items-center",
"btn-outline-primary",
"btn",
"d-flex",
)}
>
<DatabaseAdd className={cx("fs-3", "me-1")} />
Add project storage
</Label>
</>
}
requestedPermission="delete" // User needs to be project owner
userPermissions={permissions}
/>
)}
</ButtonGroup>
);
}

function ProjectStorageDataConnectorBodyAndFooter({
isOpen,
project,
switchMode,
toggle,
}: ProjectConnectDataConnectorsModalProps) {
useEffect(() => {
if (!isOpen) {
return;
}
}, [isOpen]);

return (
<ModalBody data-cy="data-connector-add-storage-body" toggle={toggle}>
{switchMode && (
<div className="mb-3">
<ProjectConnectDataConnectorModeSwitch
mode="add-storage"
switchMode={switchMode}
project={project}
/>
</div>
)}
<ProjectStorageForm
projectId={project.id}
namespace={`${project.namespace}/${project.slug}`}
toggle={toggle}
/>
</ModalBody>
);
}

function ProjectSearchDataConnectorBodyAndFooter({
isOpen,
project,
Expand Down Expand Up @@ -517,6 +611,7 @@ function ProjectSearchDataConnectorBodyAndFooter({
<ProjectConnectDataConnectorModeSwitch
mode="search"
switchMode={switchMode}
project={project}
/>
</div>
)}
Expand Down Expand Up @@ -851,5 +946,5 @@ function DataConnectorSearchSourceBadge({
</div>
);

return <p className={cx("mb-0", "small", "text-muted")}>{badgeText}</p>;
return <div className={cx("mb-0", "small", "text-muted")}>{badgeText}</div>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ import {
import {
type DataConnectorToProjectLink,
type GetProjectsByProjectIdDataConnectorLinksApiResponse,
type ProjectStorage,
} from "~/features/dataConnectorsV2/api/data-connectors.api";
import {
useGetDataConnectorsByDataConnectorIdQuery,
useGetProjectsByProjectIdDataConnectorLinksQuery,
useGetProjectsByProjectIdInaccessibleDataConnectorLinksQuery,
useGetProjectsByProjectIdStorageQuery,
} from "~/features/dataConnectorsV2/api/data-connectors.enhanced-api";
import { ErrorAlert } from "../../../../components/Alert";
import RtkOrDataServicesError from "../../../../components/errors/RtkOrDataServicesError";
Expand All @@ -47,6 +49,7 @@ import DataConnectorBoxListDisplay, {
import PermissionsGuard from "../../../permissionsV2/PermissionsGuard";
import type { Project } from "../../../projectsV2/api/projectV2.api";
import useProjectPermissions from "../../utils/useProjectPermissions.hook";
import ProjectStorageLinkDisplay from "../ProjectStorage/ProjectStorageLinkDisplay";
import ProjectConnectDataConnectorsModal from "./ProjectConnectDataConnectorsModal";

interface DataConnectorListDisplayProps {
Expand All @@ -68,13 +71,31 @@ export default function ProjectDataConnectorsBox({
projectId: project.id,
});

if (isLoading || inaccessibleDataConnectorsIsLoading)
const {
data: projectStorageData,
error: projectStorageError,
isLoading: projectStorageIsLoading,
} = useGetProjectsByProjectIdStorageQuery({
projectId: project.id,
});

if (
isLoading ||
inaccessibleDataConnectorsIsLoading ||
projectStorageIsLoading
)
return <DataConnectorLoadingBoxContent />;

if (error) {
return <RtkOrDataServicesError error={error} dismissible={false} />;
}

if (projectStorageError) {
return (
<RtkOrDataServicesError error={projectStorageError} dismissible={false} />
);
}

Comment on lines +93 to +98

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change: I understand why you put this here, but I'm not sure it's the best place. If this fails, users won't see any other data connector, which is probably not what we want. I suggest putting this in a child component

if (data == null) {
return (
<ErrorAlert>
Expand All @@ -91,34 +112,40 @@ export default function ProjectDataConnectorsBox({
inaccessibleDataConnectorsCount={
inaccessibleDataConnectorsData?.count || 0
}
projectStorageData={projectStorageData}
/>
);
}

interface ProjectDataConnectorBoxContentProps extends DataConnectorListDisplayProps {
data: GetProjectsByProjectIdDataConnectorLinksApiResponse;
inaccessibleDataConnectorsCount: number;
projectStorageData?: ProjectStorage[];
}
function ProjectDataConnectorBoxContent({
data,
project,
inaccessibleDataConnectorsCount,
projectStorageData,
}: ProjectDataConnectorBoxContentProps) {
const [isModalOpen, setModalOpen] = useState(false);
const toggleOpen = useCallback(() => {
setModalOpen((open) => !open);
}, []);
const accessibleDataConnectorsCount =
data.length + (projectStorageData?.length ?? 0);

return (
<div className={cx("d-flex", "flex-column", "gap-3")}>
<Card className="h-100" data-cy="data-connector-box">
<ProjectDataConnectorBoxHeader
projectId={project.id}
toggleOpen={toggleOpen}
accessibleDataConnectorsCount={data.length}
accessibleDataConnectorsCount={accessibleDataConnectorsCount}
inaccessibleDataConnectorsCount={inaccessibleDataConnectorsCount}
/>
<CardBody>
{data.length === 0 && (
{accessibleDataConnectorsCount === 0 && (
<p className={cx("m-0", "text-body-secondary")}>
Add published datasets from data repositories, and connect to
cloud storage to read and write custom data.
Expand All @@ -135,6 +162,13 @@ function ProjectDataConnectorBoxContent({
))}
</ListGroup>
)}
{projectStorageData && projectStorageData.length > 0 && (
<ListGroup flush>
{projectStorageData.map((ps, index) => (
<ProjectStorageLinkDisplay key={index} projectStorage={ps} />
))}
</ListGroup>
)}
</CardBody>
</Card>
{isModalOpen && (
Expand Down
Loading
Loading