From 0a5a628cf7b7779a07acbb7919dc56fc6352e3ef Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 10 Jun 2025 09:34:57 +0000 Subject: [PATCH 01/14] feat(dataset): Improve CSV file management in dataset dialog Implements drag-and-drop reordering and deletion for CSV files in the dataset creation/editing dialog. Key changes: - Uses dnd-kit for drag-and-drop functionality. - Stores the ordered list of selected file names in the dataset's `data` field. - Continues to send actual `File` objects for new/updated files to the API via the `files` field. - Updates UI to display file names, sizes for new files, and allows reordering and deletion. - Adds unit tests for new interactions and logic using Vitest. - Ensures all build, lint, and type checks pass. This addresses the issue of maintaining file order and allowing more flexible file management for CSV datasets. --- ui/package.json | 3 + ui/pnpm-lock.yaml | 56 ++ .../dialog/dataset/dataset.test.tsx | 545 +++++++++++------- ui/src/components/dialog/dataset/dataset.tsx | 240 ++++++-- 4 files changed, 601 insertions(+), 243 deletions(-) diff --git a/ui/package.json b/ui/package.json index 38ea814..671ae35 100644 --- a/ui/package.json +++ b/ui/package.json @@ -11,6 +11,9 @@ "test": "vitest" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@material-symbols/font-400": "^0.29.1", "@microsoft/fetch-event-source": "^2.0.1", "@radix-ui/react-alert-dialog": "^1.1.14", diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml index 2b320e2..b3ec406 100644 --- a/ui/pnpm-lock.yaml +++ b/ui/pnpm-lock.yaml @@ -8,6 +8,15 @@ importers: .: dependencies: + '@dnd-kit/core': + specifier: ^6.3.1 + version: 6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@dnd-kit/sortable': + specifier: ^10.0.0 + version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) + '@dnd-kit/utilities': + specifier: ^3.2.2 + version: 3.2.2(react@19.1.0) '@material-symbols/font-400': specifier: ^0.29.1 version: 0.29.3 @@ -310,6 +319,28 @@ packages: resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==} engines: {node: '>=6.9.0'} + '@dnd-kit/accessibility@3.1.1': + resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} + peerDependencies: + react: '>=16.8.0' + + '@dnd-kit/core@6.3.1': + resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@dnd-kit/sortable@10.0.0': + resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==} + peerDependencies: + '@dnd-kit/core': ^6.3.0 + react: '>=16.8.0' + + '@dnd-kit/utilities@3.2.2': + resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} + peerDependencies: + react: '>=16.8.0' + '@emotion/babel-plugin@11.13.5': resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} @@ -2898,6 +2929,31 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + '@dnd-kit/accessibility@3.1.1(react@19.1.0)': + dependencies: + react: 19.1.0 + tslib: 2.8.1 + + '@dnd-kit/core@6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@dnd-kit/accessibility': 3.1.1(react@19.1.0) + '@dnd-kit/utilities': 3.2.2(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + tslib: 2.8.1 + + '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@dnd-kit/utilities': 3.2.2(react@19.1.0) + react: 19.1.0 + tslib: 2.8.1 + + '@dnd-kit/utilities@3.2.2(react@19.1.0)': + dependencies: + react: 19.1.0 + tslib: 2.8.1 + '@emotion/babel-plugin@11.13.5': dependencies: '@babel/helper-module-imports': 7.27.1 diff --git a/ui/src/components/dialog/dataset/dataset.test.tsx b/ui/src/components/dialog/dataset/dataset.test.tsx index 0d1ee48..932eb2a 100644 --- a/ui/src/components/dialog/dataset/dataset.test.tsx +++ b/ui/src/components/dialog/dataset/dataset.test.tsx @@ -1,237 +1,390 @@ -import { TestProvider } from "@/test/helpers/test-provider"; -import "@testing-library/jest-dom"; -import { render, screen } from "@testing-library/react"; // Added waitFor -import userEvent from "@testing-library/user-event"; -import { useNavigate } from "react-router-dom"; -import { beforeEach, describe, expect, it, MockedFunction, vi } from "vitest"; -import type { CreateDatasetDialogProps } from "./dataset"; -import { CreateDatasetDialog } from "./dataset"; - -vi.mock("../generate-options-dialog", () => ({ - GenerateOptionsDialog: vi.fn((props) => { - if (!props.isOpen) return null; - return ( -
Dataset Name: {props.datasetName}
-Dataset Description: {props.datasetDescription}
-{filesError}
)} - {selectedFiles.length > 0 && ( -- {dataset - ? "Select one or more CSV files to REPLACE original data or leave it empty if you don't wan t to change." + {dataset && type === 'csv' + ? "Add new CSV files to replace existing ones. Leave empty to keep current files. You can reorder files by dragging." : "Select one or more CSV files."}
- {dataset && type === 'csv' + {dataset && type === "csv" ? "Add new CSV files to replace existing ones. Leave empty to keep current files. You can reorder files by dragging." : "Select one or more CSV files."}
From 50b8a52f0650a28fdbd8e2d28fc13d30bc142fa9 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 10 Jun 2025 12:15:21 +0000 Subject: [PATCH 04/14] feat: Add image type to dataset dialog This commit introduces a new 'image' dataset type to the dataset creation and update dialog. Key changes: - Added "image" as an option in the dataset type selection. - Implemented image file selection, allowing you to choose common image formats (PNG, JPG, GIF). - Added functionality to generate and display small image thumbnails in the file list for selected images. - Updated data structures, submission logic, and validation to support the new image type. - Included unit tests to cover the new functionality, including image selection, thumbnail display (mocked), and submission. The file input for images now shows a preview thumbnail for each selected image file in the list, enhancing your experience when working with image datasets. --- api/dataset.go | 2 +- services/dataset/dataset.go | 15 +- ui/src/actions.ts | 2 +- ui/src/components/dataset-list-page.tsx | 9 +- .../dialog/dataset/dataset.test.tsx | 579 ++++++++---------- ui/src/components/dialog/dataset/dataset.tsx | 298 +++++++-- ui/src/components/dialog/dataset/preview.tsx | 2 - 7 files changed, 494 insertions(+), 413 deletions(-) diff --git a/api/dataset.go b/api/dataset.go index 2b483bf..e2113fd 100644 --- a/api/dataset.go +++ b/api/dataset.go @@ -25,7 +25,7 @@ func (hs *HTTPServer) CreateDataset(ctx *gin.Context) { Data: apiReq.Data, } - if apiReq.Type == "csv" { + if apiReq.Type == "csv" || apiReq.Type == "image" { if len(apiReq.Files) == 0 { errorResponse(ctx, http.StatusBadRequest, errors.New("at least one file is required for CSV dataset type")) return diff --git a/services/dataset/dataset.go b/services/dataset/dataset.go index ed833e4..c158ca9 100644 --- a/services/dataset/dataset.go +++ b/services/dataset/dataset.go @@ -7,6 +7,7 @@ import ( "io" "os" "path/filepath" + "slices" "github.com/Yiling-J/tablepilot/config" "github.com/Yiling-J/tablepilot/ent" @@ -47,10 +48,6 @@ func (s DatasetServiceImpl) buildCreateDatasetReq(ctx context.Context, req *Crea return fmt.Errorf("failed to create directory: %w", err) } - if len(req.Files) == 0 { - return errors.New("dataset.Create: files should not be empty") - } - for _, file := range req.Files { outFile, err := os.Create(filepath.Join(dirPath, file.Name)) if err != nil { @@ -80,9 +77,6 @@ func (s DatasetServiceImpl) buildCreateDatasetReq(ctx context.Context, req *Crea return fmt.Errorf("failed to create directory: %w", err) } - if len(req.Files) == 0 { - return errors.New("dataset.Create: files should not be empty") - } for _, file := range req.Files { outFile, err := os.Create(filepath.Join(dirPath, file.Name)) if err != nil { @@ -178,8 +172,11 @@ func (s *DatasetServiceImpl) Update(ctx context.Context, dataset string, req *Up case "description": updater.SetDescription(req.Description) case "data", "files": - processDataRebuild = true - updater.ClearIndexer().ClearPath().SetValues(nil) + // new files or data slice change + if len(req.Files) > 0 || !slices.Equal(req.Data, ds.Values) { + processDataRebuild = true + updater.ClearIndexer().ClearPath().SetValues(nil) + } } } diff --git a/ui/src/actions.ts b/ui/src/actions.ts index 75b8e91..11849a2 100644 --- a/ui/src/actions.ts +++ b/ui/src/actions.ts @@ -699,7 +699,7 @@ export async function deleteWorkflow(id: string) { } } -export type DatasetType = "list" | "csv"; +export type DatasetType = "list" | "csv" | "image"; export interface DatasetInfo { id: string; diff --git a/ui/src/components/dataset-list-page.tsx b/ui/src/components/dataset-list-page.tsx index bf44de5..64a71e4 100644 --- a/ui/src/components/dataset-list-page.tsx +++ b/ui/src/components/dataset-list-page.tsx @@ -115,8 +115,8 @@ function DatasetList() { data: { name: string; description: string; - type: "list" | "csv"; - options?: string[]; + type: "list" | "csv" | "image"; + data?: string[]; files?: File[]; }, ) => { @@ -125,9 +125,8 @@ function DatasetList() { name: data.name, description: data.description, type: data.type, - data: data.type === "list" ? data.options || [] : [], - files: data.type === "csv" ? data.files || [] : [], - private: false, // Added private field + data: data.data ?? [], + files: data.files ?? [], }; await updateDataset(id, requestPayload); diff --git a/ui/src/components/dialog/dataset/dataset.test.tsx b/ui/src/components/dialog/dataset/dataset.test.tsx index 932eb2a..9109d96 100644 --- a/ui/src/components/dialog/dataset/dataset.test.tsx +++ b/ui/src/components/dialog/dataset/dataset.test.tsx @@ -1,390 +1,299 @@ import React from 'react'; -import { render, screen, act } from '@testing-library/react'; // Removed fireEvent -import userEvent from '@testing-library/user-event'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom'; import { CreateDatasetDialog, CreateDatasetDialogProps } from './dataset'; // Adjust path as needed import { DatasetInfo } from '@/actions'; // Adjust path as needed -import { DragEndEvent } from '@dnd-kit/core'; -import { vi } from 'vitest'; // Import vi - -// Mock dnd-kit hooks and components to simplify testing if needed, -// though many basic dnd-kit features work in JSDOM. -// For this test, we will focus on handler logic more than pixel-perfect drag simulation. - -// Mocking @dnd-kit/sortable's arrayMove as it's a utility function -vi.mock('@dnd-kit/sortable', async () => { - const actual = await vi.importActual{filesError}
+ )} + {fileItems.length > 0 && ( ++ {dataset && type === "image" + ? "Add new image files to replace existing ones. Leave empty to keep current files. You can reorder files by dragging." + : "Select one or more image files (PNG, JPG, GIF)."} +
++ {filename} +
++
{filename}