Skip to content

Commit 7d639a7

Browse files
feat: FIT-450: Improve empty state for data manager (#8149)
Co-authored-by: ricardoantoniocm <[email protected]> Co-authored-by: robot-ci-heartex <[email protected]>
1 parent def47a2 commit 7d639a7

File tree

18 files changed

+903
-92
lines changed

18 files changed

+903
-92
lines changed

poetry.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ dependencies = [
7373
"djangorestframework-simplejwt[crypto] (>=5.4.0,<6.0.0)",
7474
"tldextract (>=5.1.3)",
7575
## HumanSignal repo dependencies :start
76-
"label-studio-sdk @ https://github.com/HumanSignal/label-studio-sdk/archive/a80479402d230f5e097a3052f4fe39647e05250a.zip",
76+
"label-studio-sdk @ https://github.com/HumanSignal/label-studio-sdk/archive/2868d5981a9398edef1141dc1ddb0d3c24411d92.zip",
7777
## HumanSignal repo dependencies :end
7878
]
7979

web/apps/labelstudio/src/pages/DataManager/DataManager.jsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import { Button, buttonVariant, ToastContext, ToastType } from "@humansignal/ui";
12
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
23
import { generatePath, useHistory } from "react-router";
34
import { Link, NavLink } from "react-router-dom";
45
import { Spinner } from "../../components";
5-
import { Button, buttonVariant } from "@humansignal/ui";
66
import { modal } from "../../components/Modal/Modal";
77
import { Space } from "../../components/Space/Space";
88
import { useAPI } from "../../providers/ApiProvider";
@@ -14,7 +14,6 @@ import { isDefined } from "../../utils/helpers";
1414
import { ImportModal } from "../CreateProject/Import/ImportModal";
1515
import { ExportPage } from "../ExportPage/ExportPage";
1616
import { APIConfig } from "./api-config";
17-
import { ToastContext, ToastType } from "@humansignal/ui";
1817

1918
import "./DataManager.scss";
2019

@@ -127,6 +126,11 @@ export const DataManagerPage = ({ ...props }) => {
127126
history.push(buildLink("/data/import", { id: params.id }));
128127
});
129128

129+
// Navigate to Storage Settings and auto-open Add Source Storage modal
130+
dataManager.on("openSourceStorageModal", () => {
131+
history.push(buildLink("/settings/storage?open=source", { id: params.id }));
132+
});
133+
130134
dataManager.on("exportClicked", () => {
131135
history.push(buildLink("/data/export", { id: params.id }));
132136
});
@@ -289,7 +293,13 @@ DataManagerPage.context = ({ dmRef }) => {
289293
onClick={() => {
290294
modal({
291295
title: "Instructions",
292-
body: () => <div dangerouslySetInnerHTML={{ __html: project.expert_instruction }} />,
296+
body: () => (
297+
<div
298+
dangerouslySetInnerHTML={{
299+
__html: project.expert_instruction,
300+
}}
301+
/>
302+
),
293303
});
294304
}}
295305
>

web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageSet.jsx

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,30 @@
1-
import { useCallback, useContext } from "react";
2-
import { Columns } from "../../../components";
1+
import { StorageProviderForm } from "@humansignal/app-common/blocks/StorageProviderForm";
2+
import { ff } from "@humansignal/core";
33
import { Button } from "@humansignal/ui";
4+
import { useAtomValue } from "jotai";
5+
import { forwardRef, useCallback, useContext, useImperativeHandle } from "react";
6+
import { Columns } from "../../../components";
47
import { confirm, modal } from "../../../components/Modal/Modal";
58
import { Spinner } from "../../../components/Spinner/Spinner";
69
import { ApiContext } from "../../../providers/ApiProvider";
710
import { projectAtom } from "../../../providers/ProjectProvider";
8-
import { StorageCard } from "./StorageCard";
9-
import { StorageForm } from "./StorageForm";
10-
import { useAtomValue } from "jotai";
1111
import { useStorageCard } from "./hooks/useStorageCard";
12-
import { ff } from "@humansignal/core";
13-
import { StorageProviderForm } from "@humansignal/app-common/blocks/StorageProviderForm";
1412
import { providers } from "./providers";
13+
import { StorageCard } from "./StorageCard";
14+
import { StorageForm } from "./StorageForm";
1515

16-
export const StorageSet = ({ title, target, rootClass, buttonLabel }) => {
16+
export const StorageSet = forwardRef(({ title, target, rootClass, buttonLabel }, ref) => {
1717
const api = useContext(ApiContext);
1818
const project = useAtomValue(projectAtom);
19-
const storageTypesQueryKey = ["storage-types", target];
20-
const storagesQueryKey = ["storages", target, project?.id];
19+
// The useStorageCard hook now consolidates this
20+
// logic providing only the essential state needed by this component/
21+
2122
const useNewStorageScreen = ff.isActive(ff.FF_NEW_STORAGES);
2223

23-
const {
24-
storageTypes,
25-
storageTypesLoading,
26-
storageTypesLoaded,
27-
reloadStorageTypes,
28-
storages,
29-
storagesLoading,
30-
storagesLoaded,
31-
reloadStoragesList,
32-
loading,
33-
loaded,
34-
fetchStorages,
35-
} = useStorageCard(target, project?.id);
24+
const { storageTypes, storages, storagesLoaded, loading, loaded, fetchStorages } = useStorageCard(
25+
target,
26+
project?.id,
27+
);
3628

3729
const showStorageFormModal = useCallback(
3830
(storage) => {
@@ -92,12 +84,21 @@ export const StorageSet = ({ title, target, rootClass, buttonLabel }) => {
9284
[showStorageFormModal],
9385
);
9486

87+
// Expose showStorageFormModal to parent via ref
88+
useImperativeHandle(
89+
ref,
90+
() => ({
91+
openAddModal: () => showStorageFormModal(),
92+
}),
93+
[showStorageFormModal],
94+
);
95+
9596
const onDeleteStorage = useCallback(
9697
async (storage) => {
9798
confirm({
9899
title: "Deleting storage",
99100
body: "This action cannot be undone. Are you sure?",
100-
buttonLook: "destructive",
101+
buttonLook: "negative",
101102
onOk: async () => {
102103
const response = await api.callApi("deleteStorage", {
103104
params: {
@@ -141,4 +142,4 @@ export const StorageSet = ({ title, target, rootClass, buttonLabel }) => {
141142
)}
142143
</Columns.Column>
143144
);
144-
};
145+
});

web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageSettings.jsx

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,31 @@
1+
import { Typography } from "@humansignal/ui";
2+
import { useEffect, useRef } from "react";
3+
import { useHistory, useLocation } from "react-router-dom";
14
import { cn } from "../../../utils/bem";
2-
import { StorageSet } from "./StorageSet";
35
import { isInLicense, LF_CLOUD_STORAGE_FOR_MANAGERS } from "../../../utils/license-flags";
4-
import { Typography } from "@humansignal/ui";
6+
import { StorageSet } from "./StorageSet";
57

68
const isAllowCloudStorage = !isInLicense(LF_CLOUD_STORAGE_FOR_MANAGERS);
79

810
export const StorageSettings = () => {
911
const rootClass = cn("storage-settings"); // TODO: Remove in the next BEM cleanup
12+
const history = useHistory();
13+
const location = useLocation();
14+
const sourceStorageRef = useRef();
15+
16+
// Handle auto-open query parameter
17+
useEffect(() => {
18+
const urlParams = new URLSearchParams(location.search);
19+
if (urlParams.get("open") === "source") {
20+
// Auto-trigger "Add Source Storage" modal
21+
setTimeout(() => {
22+
sourceStorageRef.current?.openAddModal();
23+
}, 100); // Small delay to ensure component is mounted
24+
25+
// Clean URL by removing the query parameter
26+
history.replace(location.pathname);
27+
}
28+
}, [location, history]);
1029

1130
return isAllowCloudStorage ? (
1231
<section className="max-w-[680px]">
@@ -18,7 +37,12 @@ export const StorageSettings = () => {
1837
</Typography>
1938

2039
<div className="grid grid-cols-2 gap-8">
21-
<StorageSet title="Source Cloud Storage" buttonLabel="Add Source Storage" rootClass={rootClass} />
40+
<StorageSet
41+
ref={sourceStorageRef}
42+
title="Source Cloud Storage"
43+
buttonLabel="Add Source Storage"
44+
rootClass={rootClass}
45+
/>
2246

2347
<StorageSet
2448
title="Target Cloud Storage"

web/libs/datamanager/src/components/Common/Menu/MenuItem.jsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ export const MenuItem = ({
1010
to,
1111
className,
1212
href,
13-
danger,
13+
danger, // deprecated, use variant="negative" instead
14+
variant,
1415
exact = false,
1516
forceReload = false,
1617
active = false,
@@ -39,19 +40,24 @@ export const MenuItem = ({
3940
</>
4041
);
4142

43+
// Support both deprecated danger prop and new variant prop
44+
const isNegative = danger || variant === "negative";
45+
4246
const linkAttributes = {
4347
className: rootClass
4448
.mod({
4549
active: isActive || active,
46-
look: danger && "danger",
50+
look: isNegative && "danger", // Keep existing CSS class for compatibility
4751
})
4852
.mix(className),
4953
onClick,
5054
...rest,
5155
};
5256

5357
if (forceReload) {
54-
linkAttributes.onClick = () => (window.location.href = to ?? href);
58+
linkAttributes.onClick = () => {
59+
window.location.href = to ?? href;
60+
};
5561
}
5662

5763
return (

web/libs/datamanager/src/components/Common/Modal/Modal.jsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import { Button } from "@humansignal/ui";
12
import { createRef } from "react";
23
import { render } from "react-dom";
34
import { cn } from "../../../utils/bem";
4-
import { Button } from "@humansignal/ui";
55
import { Space } from "../Space/Space";
66
import { Modal } from "./ModalPopup";
77

@@ -51,10 +51,10 @@ export const confirm = ({ okText, onOk, cancelText, onCancel, buttonLook, ...pro
5151
onCancel?.();
5252
modal.close();
5353
}}
54-
size="small"
5554
look="outlined"
5655
autoFocus
5756
aria-label="Cancel"
57+
data-testid="dialog-cancel-button"
5858
>
5959
{cancelText ?? "Cancel"}
6060
</Button>
@@ -64,8 +64,9 @@ export const confirm = ({ okText, onOk, cancelText, onCancel, buttonLook, ...pro
6464
onOk?.();
6565
modal.close();
6666
}}
67-
size="small"
67+
variant={buttonLook === "negative" ? "negative" : "primary"}
6868
aria-label={okText ?? "OK"}
69+
data-testid="dialog-ok-button"
6970
>
7071
{okText ?? "OK"}
7172
</Button>
@@ -86,8 +87,8 @@ export const info = ({ okText, onOkPress, ...props }) => {
8687
onOkPress?.();
8788
modal.close();
8889
}}
89-
size="small"
9090
aria-label="OK"
91+
data-testid="dialog-ok-button"
9192
>
9293
{okText ?? "OK"}
9394
</Button>

web/libs/datamanager/src/components/Common/Modal/Modal.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
}
2525

2626
&__content {
27-
width: 400px;
28-
min-width: 400px;
27+
width: 600px;
28+
min-width: 600px;
2929
min-height: 100px;
3030
margin: 0 auto;
3131
background-color: var(--color-neutral-background);

web/libs/datamanager/src/components/Common/SDKButtons.jsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useSDK } from "../../providers/SDKProvider";
22
import { Button } from "@humansignal/ui";
33

4-
const SDKButton = ({ eventName, ...props }) => {
4+
const SDKButton = ({ eventName, testId, ...props }) => {
55
const sdk = useSDK();
66

77
return sdk.hasHandler(eventName) ? (
@@ -11,6 +11,7 @@ const SDKButton = ({ eventName, ...props }) => {
1111
look={props.look ?? "outlined"}
1212
variant={props.variant ?? "neutral"}
1313
aria-label={`${eventName.replace("Clicked", "")} button`}
14+
data-testid={testId}
1415
onClick={() => {
1516
sdk.invoke(eventName);
1617
}}
@@ -23,9 +24,9 @@ export const SettingsButton = ({ ...props }) => {
2324
};
2425

2526
export const ImportButton = ({ ...props }) => {
26-
return <SDKButton {...props} eventName="importClicked" />;
27+
return <SDKButton {...props} eventName="importClicked" testId="dm-import-button" />;
2728
};
2829

2930
export const ExportButton = ({ ...props }) => {
30-
return <SDKButton {...props} eventName="exportClicked" />;
31+
return <SDKButton {...props} eventName="exportClicked" testId="dm-export-button" />;
3132
};

0 commit comments

Comments
 (0)