Skip to content

Commit d59be2e

Browse files
authored
feat: add ux enhancements (#944)
* feat: add ux enhancements * feat: fix error card * feat: fix comments, refactor * feat: add proper tooltip for json * feat: fix json tooltip
1 parent 747c76c commit d59be2e

File tree

14 files changed

+495
-287
lines changed

14 files changed

+495
-287
lines changed
Lines changed: 33 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { and, desc, eq, inArray } from 'drizzle-orm';
2-
import { NextRequest } from 'next/server';
1+
import { and, desc, eq, inArray } from "drizzle-orm";
2+
import { NextRequest } from "next/server";
33

4-
import { clickhouseClient } from '@/lib/clickhouse/client';
5-
import { DatasetInfo } from '@/lib/dataset/types';
6-
import { db } from '@/lib/db/drizzle';
7-
import { datasets } from '@/lib/db/migrations/schema';
8-
import { paginatedGet } from '@/lib/db/utils';
4+
import { clickhouseClient } from "@/lib/clickhouse/client";
5+
import { DatasetInfo } from "@/lib/dataset/types";
6+
import { db } from "@/lib/db/drizzle";
7+
import { datasets } from "@/lib/db/migrations/schema";
8+
import { paginatedGet } from "@/lib/db/utils";
9+
import { PaginatedResponse } from "@/lib/types";
910

1011
export async function POST(req: Request, props: { params: Promise<{ projectId: string }> }): Promise<Response> {
1112
const params = await props.params;
@@ -17,14 +18,14 @@ export async function POST(req: Request, props: { params: Promise<{ projectId: s
1718
.insert(datasets)
1819
.values({
1920
name,
20-
projectId
21+
projectId,
2122
})
2223
.returning()
2324
.then((res) => res[0]);
2425

2526
if (!dataset) {
26-
return new Response(JSON.stringify({ error: 'Failed to create dataset' }), {
27-
status: 500
27+
return new Response(JSON.stringify({ error: "Failed to create dataset" }), {
28+
status: 500,
2829
});
2930
}
3031

@@ -35,21 +36,19 @@ export async function GET(req: NextRequest, props: { params: Promise<{ projectId
3536
const params = await props.params;
3637
const projectId = params.projectId;
3738

38-
const pageNumber =
39-
parseInt(req.nextUrl.searchParams.get('pageNumber') ?? '0') || 0;
40-
const pageSize =
41-
parseInt(req.nextUrl.searchParams.get('pageSize') ?? '50') || 50;
39+
const pageNumber = parseInt(req.nextUrl.searchParams.get("pageNumber") ?? "0") || 0;
40+
const pageSize = parseInt(req.nextUrl.searchParams.get("pageSize") ?? "50") || 50;
4241
const filters = [eq(datasets.projectId, projectId)];
4342

44-
const datasetsData = await paginatedGet({
43+
const datasetsData: PaginatedResponse<DatasetInfo> = await paginatedGet({
4544
table: datasets,
4645
pageNumber,
4746
pageSize,
4847
filters,
4948
orderBy: [desc(datasets.createdAt)],
5049
});
5150

52-
const datasetIds = datasetsData.items.map(dataset => (dataset as DatasetInfo).id);
51+
const datasetIds = datasetsData.items.map((dataset) => (dataset as DatasetInfo).id);
5352

5453
const chResult = await clickhouseClient.query({
5554
query: `
@@ -59,54 +58,47 @@ export async function GET(req: NextRequest, props: { params: Promise<{ projectId
5958
AND dataset_id IN {datasetIds: Array(UUID)}
6059
GROUP BY dataset_id
6160
`,
62-
format: 'JSONEachRow',
61+
format: "JSONEachRow",
6362
query_params: {
6463
projectId,
65-
datasetIds
66-
}
64+
datasetIds,
65+
},
6766
});
6867

6968
const chResultJson = await chResult.json();
7069

71-
const datapointCounts = Object.fromEntries(
72-
chResultJson.map((row: any) => [row.dataset_id, row.count])
73-
);
70+
const datapointCounts = Object.fromEntries(chResultJson.map((row: any) => [row.dataset_id, row.count]));
7471

7572
const items = datasetsData.items.map((dataset: any) => ({
7673
...dataset,
77-
datapointsCount: parseInt(datapointCounts[dataset.id] ?? '0')
74+
datapointsCount: parseInt(datapointCounts[dataset.id] ?? "0"),
7875
})) as DatasetInfo[];
7976

80-
return new Response(JSON.stringify({
81-
...datasetsData,
82-
items
83-
}), { status: 200 });
77+
const response: PaginatedResponse<DatasetInfo> = {
78+
items,
79+
totalCount: datasetsData.totalCount,
80+
};
81+
82+
return new Response(JSON.stringify(response));
8483
}
8584

86-
export async function DELETE(
87-
req: Request,
88-
props: { params: Promise<{ projectId: string }> }
89-
): Promise<Response> {
85+
export async function DELETE(req: Request, props: { params: Promise<{ projectId: string }> }): Promise<Response> {
9086
const params = await props.params;
9187
const projectId = params.projectId;
9288

9389
const { searchParams } = new URL(req.url);
94-
const datasetIds = searchParams.get('datasetIds')?.split(',');
90+
const datasetIds = searchParams.get("datasetIds")?.split(",");
9591

9692
if (!datasetIds) {
97-
return new Response('At least one Dataset ID is required', { status: 400 });
93+
return new Response("At least one Dataset ID is required", { status: 400 });
9894
}
9995

10096
try {
101-
await db
102-
.delete(datasets)
103-
.where(
104-
and(inArray(datasets.id, datasetIds), eq(datasets.projectId, projectId))
105-
);
97+
await db.delete(datasets).where(and(inArray(datasets.id, datasetIds), eq(datasets.projectId, projectId)));
10698

107-
return new Response('datasets deleted successfully', { status: 200 });
99+
return new Response("datasets deleted successfully", { status: 200 });
108100
} catch (error) {
109-
console.error('Error deleting datasets:', error);
110-
return new Response('Error deleting datasets', { status: 500 });
101+
console.error("Error deleting datasets:", error);
102+
return new Response("Error deleting datasets", { status: 500 });
111103
}
112104
}

frontend/components/dataset/dataset-panel.tsx

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { Skeleton } from "../ui/skeleton";
1717
interface DatasetPanelProps {
1818
datasetId: string;
1919
datapointId: string;
20-
onClose: () => void;
20+
onClose: (updatedDatapoint?: Datapoint) => void;
2121
}
2222

2323
// Helper function to safely parse JSON strings
@@ -60,11 +60,13 @@ export default function DatasetPanel({ datasetId, datapointId, onClose }: Datase
6060
const originalMetadataRef = useRef<Record<string, any>>({});
6161

6262
// Check if current values differ from original values
63-
const hasChanges = useCallback(() => (
64-
JSON.stringify(newData) !== JSON.stringify(originalDataRef.current) ||
65-
JSON.stringify(newTarget) !== JSON.stringify(originalTargetRef.current) ||
66-
JSON.stringify(newMetadata) !== JSON.stringify(originalMetadataRef.current)
67-
), [newData, newTarget, newMetadata]);
63+
const hasChanges = useCallback(
64+
() =>
65+
JSON.stringify(newData) !== JSON.stringify(originalDataRef.current) ||
66+
JSON.stringify(newTarget) !== JSON.stringify(originalTargetRef.current) ||
67+
JSON.stringify(newMetadata) !== JSON.stringify(originalMetadataRef.current),
68+
[newData, newTarget, newMetadata]
69+
);
6870

6971
const saveChanges = useCallback(async () => {
7072
// don't do anything if no changes or invalid jsons
@@ -97,7 +99,19 @@ export default function DatasetPanel({ datasetId, datapointId, onClose }: Datase
9799
originalDataRef.current = newData;
98100
originalTargetRef.current = newTarget;
99101
originalMetadataRef.current = newMetadata;
100-
}, [hasChanges, isValidJsonData, isValidJsonTarget, isValidJsonMetadata, newData, newTarget, newMetadata, projectId, datasetId, datapointId, toast]);
102+
}, [
103+
hasChanges,
104+
isValidJsonData,
105+
isValidJsonTarget,
106+
isValidJsonMetadata,
107+
newData,
108+
newTarget,
109+
newMetadata,
110+
projectId,
111+
datasetId,
112+
datapointId,
113+
toast,
114+
]);
101115

102116
useEffect(() => {
103117
if (!datapoint) return;
@@ -117,6 +131,20 @@ export default function DatasetPanel({ datasetId, datapointId, onClose }: Datase
117131
originalMetadataRef.current = parsedMetadata;
118132
}, [datapoint]);
119133

134+
const handleClose = useCallback(() => {
135+
if (datapoint) {
136+
const updatedDatapoint: Datapoint = {
137+
...datapoint,
138+
data: JSON.stringify(newData),
139+
target: JSON.stringify(newTarget),
140+
metadata: JSON.stringify(newMetadata),
141+
};
142+
onClose(updatedDatapoint);
143+
} else {
144+
onClose();
145+
}
146+
}, [onClose, datapoint, newData, newTarget, newMetadata]);
147+
120148
// Debounced auto-save effect
121149
useEffect(() => {
122150
// Skip if datapoint is not loaded yet or if values are invalid
@@ -142,7 +170,17 @@ export default function DatasetPanel({ datasetId, datapointId, onClose }: Datase
142170
clearTimeout(autoSaveFuncTimeoutId.current);
143171
}
144172
};
145-
}, [newData, newTarget, newMetadata, hasChanges, saveChanges, datapoint, isValidJsonData, isValidJsonTarget, isValidJsonMetadata]);
173+
}, [
174+
newData,
175+
newTarget,
176+
newMetadata,
177+
hasChanges,
178+
saveChanges,
179+
datapoint,
180+
isValidJsonData,
181+
isValidJsonTarget,
182+
isValidJsonMetadata,
183+
]);
146184

147185
if (isLoading) {
148186
return (
@@ -158,11 +196,7 @@ export default function DatasetPanel({ datasetId, datapointId, onClose }: Datase
158196
return (
159197
<div className="flex flex-col h-full w-full">
160198
<div className="h-12 flex flex-none space-x-2 px-3 items-center border-b">
161-
<Button
162-
variant="ghost"
163-
className="px-1"
164-
onClick={onClose}
165-
>
199+
<Button variant="ghost" className="px-1" onClick={handleClose}>
166200
<ChevronsRight />
167201
</Button>
168202
<div>Row</div>
@@ -181,7 +215,7 @@ export default function DatasetPanel({ datasetId, datapointId, onClose }: Datase
181215
payload: {
182216
data: safeParseJSON(datapoint.data, {}),
183217
target: safeParseJSON(datapoint.target, {}),
184-
metadata: safeParseJSON(datapoint.metadata, {})
218+
metadata: safeParseJSON(datapoint.metadata, {}),
185219
},
186220
metadata: { source: "datapoint", id: datapoint.id, datasetId: datasetId },
187221
},
@@ -195,6 +229,7 @@ export default function DatasetPanel({ datasetId, datapointId, onClose }: Datase
195229
<div className="flex flex-col space-y-2">
196230
<Label className="font-medium">Data</Label>
197231
<CodeHighlighter
232+
presetKey={`dataset-data-${datasetId}`}
198233
className="max-h-[400px] rounded"
199234
value={JSON.stringify(newData, null, 2)}
200235
defaultMode="json"
@@ -225,10 +260,10 @@ export default function DatasetPanel({ datasetId, datapointId, onClose }: Datase
225260
<div className="flex flex-col space-y-2">
226261
<Label className="font-medium">Target</Label>
227262
<CodeHighlighter
263+
presetKey={`dataset-target-${datasetId}`}
228264
className="max-h-[400px] rounded w-full"
229265
value={JSON.stringify(newTarget, null, 2)}
230266
defaultMode="json"
231-
readOnly={false}
232267
onChange={(s) => {
233268
try {
234269
setNewTarget(JSON.parse(s));
@@ -243,10 +278,10 @@ export default function DatasetPanel({ datasetId, datapointId, onClose }: Datase
243278
<div className="flex flex-col space-y-2 pb-4">
244279
<Label className="font-medium">Metadata</Label>
245280
<CodeHighlighter
281+
presetKey={`dataset-metadata-${datasetId}`}
246282
className="rounded max-h-[400px]"
247283
value={JSON.stringify(newMetadata, null, 2)}
248284
defaultMode="json"
249-
readOnly={false}
250285
onChange={(s: string) => {
251286
try {
252287
if (s === "") {

0 commit comments

Comments
 (0)