Skip to content

Commit 49b4a7b

Browse files
fix: FIT-1488: Makes search, filters and Resolve URIs controls on Task Source Viewer be saved per project (#9559)
1 parent 05f0398 commit 49b4a7b

5 files changed

Lines changed: 38 additions & 30 deletions

File tree

web/libs/datamanager/src/components/Common/Table/RowContextMenu/RowContextMenu.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Dropdown, DropdownContext, IconViewAll, IconCopyOutline, IconBraces, Ic
44
// @ts-expect-error - Menu is from JS module
55
import { Menu } from "../../Menu/Menu";
66
import { modal } from "../../Modal/Modal";
7-
import { TaskSourceViewer } from "../../TaskSourceViewer";
7+
import { TaskSourceViewer, getTaskSourceViewerStorageKey } from "../../TaskSourceViewer";
88
// @ts-expect-error - utils is JS module
99
import { getProperty } from "../utils";
1010

@@ -25,6 +25,8 @@ export interface RowContextMenuProps {
2525
cursorPosition?: { x: number; y: number };
2626
/** Callback when menu closes */
2727
onClose: () => void;
28+
/** Optional project ID for project-scoped TaskSourceViewer storage */
29+
projectId?: string | number | null;
2830
}
2931

3032
export const RowContextMenu: FC<RowContextMenuProps> = ({
@@ -36,6 +38,7 @@ export const RowContextMenu: FC<RowContextMenuProps> = ({
3638
onViewAnalytics,
3739
cursorPosition,
3840
onClose,
41+
projectId,
3942
}) => {
4043
// Columns that should not have copy cell content option
4144
const excludedColumns = [
@@ -185,7 +188,7 @@ export const RowContextMenu: FC<RowContextMenuProps> = ({
185188
content={taskData}
186189
onTaskLoad={onTaskLoad}
187190
sdkType={sdkType}
188-
storageKey="dm:tasksource"
191+
storageKey={getTaskSourceViewerStorageKey(projectId)}
189192
renderToggle={(toggle) => {
190193
// Update modal header with toggle
191194
modalInstance?.update({ header: toggle });
@@ -195,7 +198,7 @@ export const RowContextMenu: FC<RowContextMenuProps> = ({
195198
});
196199

197200
onClose();
198-
}, [row, api, sdkType, onClose]);
201+
}, [row, api, sdkType, onClose, projectId]);
199202

200203
// 5. View annotator performance (LSE-only)
201204
const handleViewAnalytics = useCallback(() => {

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { cn } from "../../../utils/bem";
1818
import { FieldsButton } from "../FieldsButton";
1919
import { FF_LOPS_E_3, isFF } from "../../../utils/feature-flags";
2020
import { DensityToggle } from "../../DataManager/Toolbar/DensityToggle";
21-
import { TaskSourceViewer } from "../TaskSourceViewer";
21+
import { TaskSourceViewer, getTaskSourceViewerStorageKey } from "../TaskSourceViewer";
2222

2323
const Decorator = (decoration) => {
2424
return {
@@ -62,7 +62,7 @@ export const Table = observer(
6262
const [colOrder, setColOrder] = useState(JSON.parse(localStorage.getItem(colOrderKey)) ?? {});
6363
const listRef = useRef();
6464
const Decoration = useMemo(() => Decorator(decoration), [decoration]);
65-
const { api, type } = useSDK();
65+
const { api, type, projectId } = useSDK();
6666
const toolbarHeight = 41;
6767
const isQuickView = view.root.isLabeling;
6868
const [toolbarVisible, setToolbarVisible] = useState(true);
@@ -194,7 +194,7 @@ export const Table = observer(
194194
content={out}
195195
onTaskLoad={onTaskLoad}
196196
sdkType={type}
197-
storageKey="dm:tasksource"
197+
storageKey={getTaskSourceViewerStorageKey(projectId)}
198198
renderToggle={(toggle) => {
199199
// Update modal header with toggle
200200
modalInstance?.update({ header: toggle });
@@ -640,7 +640,7 @@ const innerElementType = forwardRef(({ children, ...rest }, ref) => {
640640
const ContextMenuPortal = memo(
641641
({ contextMenu, view, onViewAnalytics, onViewReviewerAnalytics, onClose, RowContextMenuComponent }) => {
642642
const MenuComponent = RowContextMenuComponent || RowContextMenu;
643-
const { api, type } = useSDK();
643+
const { api, type, projectId } = useSDK();
644644

645645
return (
646646
<MenuComponent
@@ -649,6 +649,7 @@ const ContextMenuPortal = memo(
649649
view={view}
650650
api={api}
651651
sdkType={type}
652+
projectId={projectId}
652653
onViewAnalytics={onViewAnalytics}
653654
onViewReviewerAnalytics={onViewReviewerAnalytics}
654655
cursorPosition={{ x: contextMenu.x, y: contextMenu.y }}

web/libs/datamanager/src/components/Common/TaskSourceViewer/TaskSourceViewer.test.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ jest.mock("./TaskSourceViewer.module.scss", () => ({
6767
}));
6868

6969
describe("TaskSourceViewer Component", () => {
70+
// View uses global key; resolveUrls and JSON viewer use project-scoped key (storageKey prop)
71+
const GLOBAL_STORAGE_KEY = "dm:tasksource";
7072
const mockTaskData = {
7173
id: 123,
7274
data: {
@@ -133,7 +135,7 @@ describe("TaskSourceViewer Component", () => {
133135
});
134136

135137
it("should show resolve URI toggle in JsonViewer toolbar for interactive view", async () => {
136-
localStorage.setItem("test:tasksource:view", "interactive");
138+
localStorage.setItem(`${GLOBAL_STORAGE_KEY}:view`, "interactive");
137139

138140
render(<TaskSourceViewer {...defaultProps} />);
139141

@@ -145,7 +147,7 @@ describe("TaskSourceViewer Component", () => {
145147
});
146148

147149
it("should reload task data when resolve URIs toggle changes", async () => {
148-
localStorage.setItem("test:tasksource:view", "interactive");
150+
localStorage.setItem(`${GLOBAL_STORAGE_KEY}:view`, "interactive");
149151
const user = userEvent.setup();
150152
const mockOnTaskLoad = jest.fn().mockResolvedValue(mockTaskData);
151153

@@ -166,7 +168,7 @@ describe("TaskSourceViewer Component", () => {
166168
});
167169

168170
it("should save resolve URIs preference to localStorage", async () => {
169-
localStorage.setItem("test:tasksource:view", "interactive");
171+
localStorage.setItem(`${GLOBAL_STORAGE_KEY}:view`, "interactive");
170172
const user = userEvent.setup();
171173

172174
render(<TaskSourceViewer {...defaultProps} />);
@@ -193,7 +195,7 @@ describe("TaskSourceViewer Component", () => {
193195
});
194196

195197
it("should respect stored view preference from localStorage", async () => {
196-
localStorage.setItem("test:tasksource:view", "interactive");
198+
localStorage.setItem(`${GLOBAL_STORAGE_KEY}:view`, "interactive");
197199

198200
render(<TaskSourceViewer {...defaultProps} />);
199201

@@ -237,7 +239,7 @@ describe("TaskSourceViewer Component", () => {
237239
capturedOnViewChange!("interactive");
238240

239241
await waitFor(() => {
240-
expect(localStorage.getItem("test:tasksource:view")).toBe("interactive");
242+
expect(localStorage.getItem(`${GLOBAL_STORAGE_KEY}:view`)).toBe("interactive");
241243
expect(screen.getByTestId("json-viewer")).toBeInTheDocument();
242244
});
243245
});

web/libs/datamanager/src/components/Common/TaskSourceViewer/TaskSourceViewer.tsx

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ import { ViewToggle, type ViewMode } from "./ViewToggle";
77

88
export type { ViewMode };
99

10+
/** Build project-scoped localStorage key for JSON viewer search and filter state. Returns undefined when projectId is missing. */
11+
export function getTaskSourceViewerStorageKey(projectId: string | number | null | undefined): string | undefined {
12+
if (projectId == null || projectId === "") return undefined;
13+
return `dm:tasksource:${projectId}`;
14+
}
15+
16+
/** Global key for view mode (Code/Interactive) only — shared across all projects. */
17+
const TASK_SOURCE_VIEWER_GLOBAL_KEY = "dm:tasksource";
18+
1019
/** Options passed to onTaskLoad callback */
1120
export interface TaskLoadOptions {
1221
/** Whether to resolve storage URIs to proxy URLs (default: false) */
@@ -20,7 +29,7 @@ export interface TaskSourceViewerProps {
2029
onTaskLoad: (options?: TaskLoadOptions) => Promise<any>;
2130
/** SDK type (e.g., "DE" for Data Explorer) */
2231
sdkType?: string;
23-
/** Storage key for localStorage persistence */
32+
/** Storage key for project-scoped persistence (JSON viewer search/filters and Resolve URIs). View mode stays global. */
2433
storageKey?: string;
2534
/** Render toggle in external location (e.g., modal header) */
2635
renderToggle?: (toggle: React.ReactNode) => void;
@@ -64,35 +73,28 @@ export const TaskSourceViewer: FC<TaskSourceViewerProps> = ({
6473
content,
6574
onTaskLoad,
6675
sdkType,
67-
storageKey = "dm:tasksource",
76+
storageKey,
6877
renderToggle,
6978
}) => {
7079
const isInteractiveViewerEnabled = isFF(FF_INTERACTIVE_JSON_VIEWER);
7180

7281
const [taskData, setTaskData] = useState(content);
7382
const [loading, setLoading] = useState(true);
7483

75-
// Manage view state internally
76-
const [view, setView] = useState<ViewMode>(() =>
77-
storageKey ? (localStorage.getItem(`${storageKey}:view`) as ViewMode) || "code" : "code",
84+
// View mode (Code/Interactive) — global key so preference is shared across projects
85+
const [view, setView] = useState<ViewMode>(
86+
() => (localStorage.getItem(`${TASK_SOURCE_VIEWER_GLOBAL_KEY}:view`) as ViewMode) || "code",
7887
);
7988

80-
// Manage resolve URIs state - default OFF to show original storage URIs
89+
// Resolve URIs — per project when storageKey is set (same key as JSON viewer search/filters)
8190
const [resolveUrls, setResolveUrls] = useState<boolean>(() =>
8291
storageKey ? localStorage.getItem(`${storageKey}:resolveUrls`) === "true" : false,
8392
);
8493

85-
const handleViewChange = useCallback(
86-
(newView: ViewMode) => {
87-
setView(newView);
88-
89-
// Save to localStorage
90-
if (storageKey) {
91-
localStorage.setItem(`${storageKey}:view`, newView);
92-
}
93-
},
94-
[storageKey],
95-
);
94+
const handleViewChange = useCallback((newView: ViewMode) => {
95+
setView(newView);
96+
localStorage.setItem(`${TASK_SOURCE_VIEWER_GLOBAL_KEY}:view`, newView);
97+
}, []);
9698

9799
// Load full task data
98100
useEffect(() => {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export { TaskSourceViewer } from "./TaskSourceViewer";
1+
export { TaskSourceViewer, getTaskSourceViewerStorageKey } from "./TaskSourceViewer";
22
export type { TaskSourceViewerProps } from "./TaskSourceViewer";

0 commit comments

Comments
 (0)