Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
145bf02
feat: FIT-450: Improve empty state for data manager
ricardoantoniocm Aug 11, 2025
a307038
Implements empty state on Data Manager surfacing Connect Storage, dra…
ricardoantoniocm Aug 12, 2025
688c1f2
Adds docs link.
ricardoantoniocm Aug 12, 2025
0b90c18
Linter fixes.
ricardoantoniocm Aug 12, 2025
1807e59
Improves confirmation/destructive action dialogs.
ricardoantoniocm Aug 12, 2025
0a4cf6a
Linter fixes.
ricardoantoniocm Aug 12, 2025
6ba0d35
Increases padding for empty state.
ricardoantoniocm Aug 12, 2025
8c2d953
Adds accessibility improvements and tests.
ricardoantoniocm Aug 12, 2025
12cc3d6
Improvements.
ricardoantoniocm Aug 13, 2025
465a155
Introduces role-based handling for the empty state, including improve…
ricardoantoniocm Aug 13, 2025
b4aa364
Refactors to follow react rules.
ricardoantoniocm Aug 13, 2025
b981980
Fix linting issues: convert EmptyState to TypeScript and fix ARIA roles
ricardoantoniocm Aug 13, 2025
28c169e
Apply auto-formatting fixes
ricardoantoniocm Aug 13, 2025
62718c5
Fix EmptyState role prop usage in Table component
ricardoantoniocm Aug 13, 2025
dd85ba7
Fix EmptyState logic: prioritize role-specific states over filter states
ricardoantoniocm Aug 13, 2025
9daee6d
Add debug logging for hasFilters calculation
ricardoantoniocm Aug 13, 2025
8d7602b
Revert "Add debug logging for hasFilters calculation"
ricardoantoniocm Aug 13, 2025
e8ad312
Removes legacy check for filters. Fixes order of filters empty state.
ricardoantoniocm Aug 13, 2025
5d3fb48
Fix tests.
ricardoantoniocm Aug 13, 2025
43ff8a4
Merge branch 'develop' into 'fb-fit-450/improve-empty-state-data-mana…
ricardoantoniocm Aug 13, 2025
36094e0
Adds data-testids to buttons on destructive/confirmation data manager…
ricardoantoniocm Aug 14, 2025
0c0d0e5
Adds data-testids to Import and Export buttons on Data Manager.
ricardoantoniocm Aug 14, 2025
2c78268
Merge branch 'develop' into 'fb-fit-450/improve-empty-state-data-mana…
ricardoantoniocm Aug 15, 2025
456b17f
Merge branch 'develop' into 'fb-fit-450/improve-empty-state-data-mana…
ricardoantoniocm Aug 18, 2025
64285c1
Remove interactive upload in empty state. Removes leftover empty stat…
ricardoantoniocm Aug 18, 2025
007acb6
Reverts changes to Import.
ricardoantoniocm Aug 18, 2025
227be8d
Revert changes to Data Manager page related to processing import from…
ricardoantoniocm Aug 18, 2025
9a7527b
Refactors comment on StorageSet.
ricardoantoniocm Aug 18, 2025
8e51e02
Refactors EmptyState component to reduce repeated code.
ricardoantoniocm Aug 18, 2025
64d6244
Sync Follow Merge dependencies
robot-ci-heartex Aug 18, 2025
058bfa5
Merge branch 'develop' into 'fb-fit-450/improve-empty-state-data-mana…
robot-ci-heartex Aug 18, 2025
5319257
Slightly copy adjustment to avoid repetition.
ricardoantoniocm Aug 19, 2025
ac12620
Merge branch 'develop' into 'fb-fit-450/improve-empty-state-data-mana…
ricardoantoniocm Aug 20, 2025
7d8e9cf
Fixes typo.
ricardoantoniocm Aug 22, 2025
6964da7
Merge branch 'develop' into 'fb-fit-450/improve-empty-state-data-mana…
ricardoantoniocm Aug 22, 2025
f8f4a44
Sync Follow Merge dependencies
robot-ci-heartex Aug 22, 2025
9966bce
Sync Follow Merge dependencies
robot-ci-heartex Aug 22, 2025
d211a93
Merge branch 'develop' into 'fb-fit-450/improve-empty-state-data-mana…
robot-ci-heartex Aug 22, 2025
f6caa15
Sync Follow Merge dependencies
robot-ci-heartex Aug 22, 2025
aa2a3fb
Updates test with correct string.
ricardoantoniocm Aug 22, 2025
3e841ce
Linter fixes.
ricardoantoniocm Aug 22, 2025
49f5dc2
Sync Follow Merge dependencies
robot-ci-heartex Aug 22, 2025
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
6 changes: 3 additions & 3 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ dependencies = [
"djangorestframework-simplejwt[crypto] (>=5.4.0,<6.0.0)",
"tldextract (>=5.1.3)",
## HumanSignal repo dependencies :start
"label-studio-sdk @ https://github.com/HumanSignal/label-studio-sdk/archive/a80479402d230f5e097a3052f4fe39647e05250a.zip",
"label-studio-sdk @ https://github.com/HumanSignal/label-studio-sdk/archive/2868d5981a9398edef1141dc1ddb0d3c24411d92.zip",
## HumanSignal repo dependencies :end
]

Expand Down
16 changes: 13 additions & 3 deletions web/apps/labelstudio/src/pages/DataManager/DataManager.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Button, buttonVariant, ToastContext, ToastType } from "@humansignal/ui";
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { generatePath, useHistory } from "react-router";
import { Link, NavLink } from "react-router-dom";
import { Spinner } from "../../components";
import { Button, buttonVariant } from "@humansignal/ui";
import { modal } from "../../components/Modal/Modal";
import { Space } from "../../components/Space/Space";
import { useAPI } from "../../providers/ApiProvider";
Expand All @@ -14,7 +14,6 @@ import { isDefined } from "../../utils/helpers";
import { ImportModal } from "../CreateProject/Import/ImportModal";
import { ExportPage } from "../ExportPage/ExportPage";
import { APIConfig } from "./api-config";
import { ToastContext, ToastType } from "@humansignal/ui";

import "./DataManager.scss";

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

// Navigate to Storage Settings and auto-open Add Source Storage modal
dataManager.on("openSourceStorageModal", () => {
history.push(buildLink("/settings/storage?open=source", { id: params.id }));
});

dataManager.on("exportClicked", () => {
history.push(buildLink("/data/export", { id: params.id }));
});
Expand Down Expand Up @@ -289,7 +293,13 @@ DataManagerPage.context = ({ dmRef }) => {
onClick={() => {
modal({
title: "Instructions",
body: () => <div dangerouslySetInnerHTML={{ __html: project.expert_instruction }} />,
body: () => (
<div
dangerouslySetInnerHTML={{
__html: project.expert_instruction,
}}
/>
),
});
}}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,30 @@
import { useCallback, useContext } from "react";
import { Columns } from "../../../components";
import { StorageProviderForm } from "@humansignal/app-common/blocks/StorageProviderForm";
import { ff } from "@humansignal/core";
import { Button } from "@humansignal/ui";
import { useAtomValue } from "jotai";
import { forwardRef, useCallback, useContext, useImperativeHandle } from "react";
import { Columns } from "../../../components";
import { confirm, modal } from "../../../components/Modal/Modal";
import { Spinner } from "../../../components/Spinner/Spinner";
import { ApiContext } from "../../../providers/ApiProvider";
import { projectAtom } from "../../../providers/ProjectProvider";
import { StorageCard } from "./StorageCard";
import { StorageForm } from "./StorageForm";
import { useAtomValue } from "jotai";
import { useStorageCard } from "./hooks/useStorageCard";
import { ff } from "@humansignal/core";
import { StorageProviderForm } from "@humansignal/app-common/blocks/StorageProviderForm";
import { providers } from "./providers";
import { StorageCard } from "./StorageCard";
import { StorageForm } from "./StorageForm";

export const StorageSet = ({ title, target, rootClass, buttonLabel }) => {
export const StorageSet = forwardRef(({ title, target, rootClass, buttonLabel }, ref) => {
const api = useContext(ApiContext);
const project = useAtomValue(projectAtom);
const storageTypesQueryKey = ["storage-types", target];
const storagesQueryKey = ["storages", target, project?.id];
// The useStorageCard hook now consolidates this
// logic providing only the essential state needed by this component/

const useNewStorageScreen = ff.isActive(ff.FF_NEW_STORAGES);

const {
storageTypes,
storageTypesLoading,
storageTypesLoaded,
reloadStorageTypes,
storages,
storagesLoading,
storagesLoaded,
reloadStoragesList,
loading,
loaded,
fetchStorages,
} = useStorageCard(target, project?.id);
const { storageTypes, storages, storagesLoaded, loading, loaded, fetchStorages } = useStorageCard(
target,
project?.id,
);

const showStorageFormModal = useCallback(
(storage) => {
Expand Down Expand Up @@ -92,12 +84,21 @@ export const StorageSet = ({ title, target, rootClass, buttonLabel }) => {
[showStorageFormModal],
);

// Expose showStorageFormModal to parent via ref
useImperativeHandle(
ref,
() => ({
openAddModal: () => showStorageFormModal(),
}),
[showStorageFormModal],
);

const onDeleteStorage = useCallback(
async (storage) => {
confirm({
title: "Deleting storage",
body: "This action cannot be undone. Are you sure?",
buttonLook: "destructive",
buttonLook: "negative",
onOk: async () => {
const response = await api.callApi("deleteStorage", {
params: {
Expand Down Expand Up @@ -141,4 +142,4 @@ export const StorageSet = ({ title, target, rootClass, buttonLabel }) => {
)}
</Columns.Column>
);
};
});
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
import { Typography } from "@humansignal/ui";
import { useEffect, useRef } from "react";
import { useHistory, useLocation } from "react-router-dom";
import { cn } from "../../../utils/bem";
import { StorageSet } from "./StorageSet";
import { isInLicense, LF_CLOUD_STORAGE_FOR_MANAGERS } from "../../../utils/license-flags";
import { Typography } from "@humansignal/ui";
import { StorageSet } from "./StorageSet";

const isAllowCloudStorage = !isInLicense(LF_CLOUD_STORAGE_FOR_MANAGERS);

export const StorageSettings = () => {
const rootClass = cn("storage-settings"); // TODO: Remove in the next BEM cleanup
const history = useHistory();
const location = useLocation();
const sourceStorageRef = useRef();

// Handle auto-open query parameter
useEffect(() => {
const urlParams = new URLSearchParams(location.search);
if (urlParams.get("open") === "source") {
// Auto-trigger "Add Source Storage" modal
setTimeout(() => {
sourceStorageRef.current?.openAddModal();
}, 100); // Small delay to ensure component is mounted

// Clean URL by removing the query parameter
history.replace(location.pathname);
}
}, [location, history]);

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

<div className="grid grid-cols-2 gap-8">
<StorageSet title="Source Cloud Storage" buttonLabel="Add Source Storage" rootClass={rootClass} />
<StorageSet
ref={sourceStorageRef}
title="Source Cloud Storage"
buttonLabel="Add Source Storage"
rootClass={rootClass}
/>

<StorageSet
title="Target Cloud Storage"
Expand Down
12 changes: 9 additions & 3 deletions web/libs/datamanager/src/components/Common/Menu/MenuItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export const MenuItem = ({
to,
className,
href,
danger,
danger, // deprecated, use variant="negative" instead
variant,
exact = false,
forceReload = false,
active = false,
Expand Down Expand Up @@ -39,19 +40,24 @@ export const MenuItem = ({
</>
);

// Support both deprecated danger prop and new variant prop
const isNegative = danger || variant === "negative";

const linkAttributes = {
className: rootClass
.mod({
active: isActive || active,
look: danger && "danger",
look: isNegative && "danger", // Keep existing CSS class for compatibility
})
.mix(className),
onClick,
...rest,
};

if (forceReload) {
linkAttributes.onClick = () => (window.location.href = to ?? href);
linkAttributes.onClick = () => {
window.location.href = to ?? href;
};
}

return (
Expand Down
9 changes: 5 additions & 4 deletions web/libs/datamanager/src/components/Common/Modal/Modal.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button } from "@humansignal/ui";
import { createRef } from "react";
import { render } from "react-dom";
import { cn } from "../../../utils/bem";
import { Button } from "@humansignal/ui";
import { Space } from "../Space/Space";
import { Modal } from "./ModalPopup";

Expand Down Expand Up @@ -51,10 +51,10 @@ export const confirm = ({ okText, onOk, cancelText, onCancel, buttonLook, ...pro
onCancel?.();
modal.close();
}}
size="small"
look="outlined"
autoFocus
aria-label="Cancel"
data-testid="dialog-cancel-button"
>
{cancelText ?? "Cancel"}
</Button>
Expand All @@ -64,8 +64,9 @@ export const confirm = ({ okText, onOk, cancelText, onCancel, buttonLook, ...pro
onOk?.();
modal.close();
}}
size="small"
variant={buttonLook === "negative" ? "negative" : "primary"}
aria-label={okText ?? "OK"}
data-testid="dialog-ok-button"
>
{okText ?? "OK"}
</Button>
Expand All @@ -86,8 +87,8 @@ export const info = ({ okText, onOkPress, ...props }) => {
onOkPress?.();
modal.close();
}}
size="small"
aria-label="OK"
data-testid="dialog-ok-button"
>
{okText ?? "OK"}
</Button>
Expand Down
4 changes: 2 additions & 2 deletions web/libs/datamanager/src/components/Common/Modal/Modal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
}

&__content {
width: 400px;
min-width: 400px;
width: 600px;
min-width: 600px;
min-height: 100px;
margin: 0 auto;
background-color: var(--color-neutral-background);
Expand Down
7 changes: 4 additions & 3 deletions web/libs/datamanager/src/components/Common/SDKButtons.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useSDK } from "../../providers/SDKProvider";
import { Button } from "@humansignal/ui";

const SDKButton = ({ eventName, ...props }) => {
const SDKButton = ({ eventName, testId, ...props }) => {
const sdk = useSDK();

return sdk.hasHandler(eventName) ? (
Expand All @@ -11,6 +11,7 @@ const SDKButton = ({ eventName, ...props }) => {
look={props.look ?? "outlined"}
variant={props.variant ?? "neutral"}
aria-label={`${eventName.replace("Clicked", "")} button`}
data-testid={testId}
onClick={() => {
sdk.invoke(eventName);
}}
Expand All @@ -23,9 +24,9 @@ export const SettingsButton = ({ ...props }) => {
};

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

export const ExportButton = ({ ...props }) => {
return <SDKButton {...props} eventName="exportClicked" />;
return <SDKButton {...props} eventName="exportClicked" testId="dm-export-button" />;
};
Loading
Loading