Skip to content

Commit 2eb3dba

Browse files
Rework Query Client for Datasets and Images, Add dataset delete (#19)
* adding start function to task Signed-off-by: charlie.rogers <charlie.rogers@fearnworks.com> * added env-file flag for task stop Signed-off-by: charlie.rogers <charlie.rogers@fearnworks.com> * added database configuration to env file during set up Signed-off-by: charlie.rogers <charlie.rogers@fearnworks.com> * Enable query caching for datasets and image CRUD, add context menu to tree, further decouple editor Signed-off-by: jphillips <josh.phillips@fearnworks.com> * Lint updates, Deleting old impl Signed-off-by: jphillips <josh.phillips@fearnworks.com> * lint tweaks Signed-off-by: jphillips <josh.phillips@fearnworks.com> --------- Signed-off-by: charlie.rogers <charlie.rogers@fearnworks.com> Signed-off-by: jphillips <josh.phillips@fearnworks.com> Co-authored-by: charlie.rogers <charlie.rogers@fearnworks.com>
1 parent b193937 commit 2eb3dba

71 files changed

Lines changed: 2730 additions & 1386 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.cursor/rules/code_style.mdc

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,79 @@ globs:
44
alwaysApply: true
55
---
66
<Warning>
7-
Below are list of constraints to adhere to based on project style requirements:
8-
- useEffect, useRef, useCallback and other hook related items should always be implemented in custom hook files instead of inside components
9-
- Utilize readonly and immutable inputs to all ui components
10-
- Non-interactive elements should not be assigned mouse or keyboard event listeners.sonarqube(typescript:S6847) !important
11-
- Use <hr> instead of the "separator" role to ensure accessibility across all devices.
12-
- Do not utilize fixed pixel heights and widths that will not respond to variable screen size and resolution
7+
1. **Constants:**
8+
- Extract magic numbers/strings into constants.
9+
- Centralize in a dedicated constants file.
10+
11+
2. **Folder Structure:**
12+
- Organize by feature/purpose (components, hooks, utilities, constants, types).
13+
- Be consistent and intuitive.
14+
15+
3. **Reusable & Dumb Components:**
16+
- Split large components into small, focused pieces.
17+
- Keep business logic out of presentational components.
18+
- Use the children pattern to avoid prop drilling.
19+
20+
4. **Minimal Markup:**
21+
- Avoid unnecessary wrappers; use React fragments (`<>…</>`).
22+
- If layout styling is needed, wrap with a one-off container rather than embedding in a reusable component.
23+
24+
5. **No Layout in Reusable Components:**
25+
- Reusable components (e.g., buttons, headings) should accept a `className` prop for one-off styling.
26+
- Do not hardcode layout styles inside them.
27+
28+
6. **TypeScript:**
29+
- Use strict TypeScript types for props, state, and functions.
30+
- Use union types for limited values (e.g., `'primary' | 'secondary'`).
31+
32+
7. **Event Handlers:**
33+
- Name props using the “onEvent” convention (e.g., `onAddTodo`).
34+
- Use internal handler names like `handleAddTodo`.
35+
36+
8. **State Updates:**
37+
- Wrap state updates in dedicated event handler functions.
38+
- Use updater functions (`setState(prev => …)`) when new state depends on previous state.
39+
40+
9. **Single Source of Truth:**
41+
- Track selected/active items by ID instead of the entire object.
42+
43+
10. **URL as State:**
44+
- Store shareable state (filters, pagination) in the URL, not in component state.
45+
46+
11. **useEffect Discipline:**
47+
- Keep each `useEffect` focused on one concern.
48+
- Split effects if they manage unrelated tasks.
49+
50+
12. **Data Fetching:**
51+
- Prefer React Query/SWR or Next.js data fetching methods over manual useEffect fetching.
52+
53+
13. **Performance:**
54+
- Use `useMemo` for expensive calculations/objects.
55+
- Use `useCallback` for functions passed as props.
56+
- Wrap components in `React.memo` to avoid unnecessary renders.
57+
58+
14. **Consolidate Related State:**
59+
- Use a single state object for related pieces of state where possible.
60+
61+
15. **Custom Hooks & Utilities:**
62+
- Extract shared logic into custom hooks.
63+
- Write utility functions for common tasks (e.g., string formatting).
64+
65+
16. **Avoid Prop Drilling:**
66+
- Use context or children patterns instead of passing raw setter functions deeply.
67+
68+
17. **Naming for Function Props:**
69+
- Expose custom events using “onEvent” props (e.g., `onAddTodo`), and use descriptive handler names internally (e.g., `handleAddTodo`).
70+
71+
### Project Constraints (Reminders)
72+
73+
- **Hooks:** Place all hook logic (useEffect, useRef, useCallback, etc.) in custom hook files.
74+
- **Immutability:** All UI component inputs must be readonly/immutable.
75+
- **Event Listeners:** Do not assign mouse or keyboard listeners to non-interactive elements.
76+
- **Accessibility:** Use `<hr>` instead of the "separator" role.
77+
- **Responsive Design:** Avoid fixed pixel heights/widths; design for responsiveness.
78+
79+
---
80+
81+
All new code must comply with these guidelines to ensure a modular, maintainable, and high-performing React codebase.
1382
</Warning>

Taskfile.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,15 @@ tasks:
4242
cmds:
4343
- docker compose up --watch --build
4444

45+
start:
46+
desc: Start the Docker Compose services
47+
cmds:
48+
- docker compose --env-file ./workspace/config/.env up -d
49+
4550
stop:
4651
desc: Stop the Docker Compose services
4752
cmds:
48-
- docker compose down
53+
- docker compose --env-file ./workspace/config/.env down
4954

5055
setup:
5156
desc: Setup the project

doc/getting_started/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Quick Start
2929

3030
.. code-block:: bash
3131
32-
docker compose up
32+
task start
3333
3434
4. Visit ``localhost:32300`` to access the web interface
3535

docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ services:
8585
- VITE_API_URL=http://localhost:32100/api
8686
- VITE_WORKSPACE_PATH=/workspace/.local
8787
- VITE_MEDIA_SERVER_URL=http://localhost:32400
88+
- VITE_DATASETS_PATH=/workspace/datasets
8889
networks:
8990
- graphcap
9091
depends_on:
@@ -103,6 +104,7 @@ services:
103104
- NODE_ENV=${NODE_ENV:-development}
104105
- PORT=32400
105106
- WORKSPACE_PATH=/workspace
107+
- DATASETS_PATH=/workspace/datasets
106108
develop:
107109
watch:
108110
- action: sync

graphcap_studio/media_server/routes/datasets.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
const express = require('express');
1111
const router = express.Router();
1212
const { logInfo, logError } = require('../utils/logger');
13-
const { listDatasetImages, createDataset, addImageToDataset } = require('../services/dataset-service');
13+
const { listDatasetImages, createDataset, addImageToDataset, deleteDataset } = require('../services/dataset-service');
1414

1515
/**
1616
* List all images in the datasets directory recursively
@@ -88,4 +88,32 @@ router.post('/add-image', async (req, res) => {
8888
}
8989
});
9090

91+
/**
92+
* Delete a dataset
93+
*
94+
* @param {string} req.params.name - Name of the dataset to delete
95+
* @returns {Object} Success status and message
96+
*/
97+
router.delete('/:name', async (req, res) => {
98+
try {
99+
const { name } = req.params;
100+
101+
if (!name) {
102+
return res.status(400).json({ error: 'Dataset name is required' });
103+
}
104+
105+
const result = await deleteDataset(name);
106+
res.json(result);
107+
} catch (error) {
108+
logError('Error deleting dataset', error);
109+
110+
// Handle specific errors with appropriate status codes
111+
if (error.message.includes('Dataset not found')) {
112+
return res.status(404).json({ error: error.message });
113+
}
114+
115+
res.status(500).json({ error: 'Failed to delete dataset' });
116+
}
117+
});
118+
91119
module.exports = router;

graphcap_studio/media_server/services/dataset-service.js

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ async function createDataset(name) {
251251
const sanitizedName = nameResult.sanitized;
252252

253253
// Create the dataset path
254-
const datasetPathResult = securePath(`datasets/local${sanitizedName}`, WORKSPACE_PATH, { createIfNotExist: true });
254+
const datasetPathResult = securePath(`datasets/local/${sanitizedName}`, WORKSPACE_PATH, { createIfNotExist: true });
255255

256256
if (!datasetPathResult.isValid) {
257257
throw new Error(`Failed to create dataset path: ${datasetPathResult.error}`);
@@ -260,6 +260,7 @@ async function createDataset(name) {
260260
logInfo(`Dataset created: ${sanitizedName} at ${datasetPathResult.path}`);
261261

262262
return {
263+
success: true,
263264
name: sanitizedName,
264265
path: datasetPathResult.relativePath,
265266
images: []
@@ -294,7 +295,7 @@ async function addImageToDataset(imagePath, datasetName) {
294295
const sanitizedDatasetName = datasetNameResult.sanitized;
295296

296297
// Check if the dataset exists
297-
const datasetPathResult = securePath(`datasets/local${sanitizedDatasetName}`, WORKSPACE_PATH, { mustExist: false });
298+
const datasetPathResult = securePath(`datasets/local/${sanitizedDatasetName}`, WORKSPACE_PATH, { mustExist: false });
298299
if (!datasetPathResult.isValid) {
299300
throw new Error(`Invalid dataset path: ${datasetPathResult.error}`);
300301
}
@@ -323,6 +324,7 @@ async function addImageToDataset(imagePath, datasetName) {
323324
logInfo(`Image added to dataset: ${imageName} to ${sanitizedDatasetName}`);
324325

325326
return {
327+
success: true,
326328
name: imageName,
327329
path: newImagePathResult.relativePath,
328330
url: `/api/images/view${newImagePathResult.relativePath}`
@@ -333,8 +335,57 @@ async function addImageToDataset(imagePath, datasetName) {
333335
}
334336
}
335337

338+
/**
339+
* Delete a dataset
340+
*
341+
* @param {string} name - Name of the dataset to delete
342+
* @returns {Promise<Object>} Success status and message
343+
*/
344+
async function deleteDataset(name) {
345+
try {
346+
// Validate dataset name
347+
if (!name || typeof name !== 'string') {
348+
throw new Error('Dataset name is required');
349+
}
350+
351+
// Sanitize dataset name
352+
const nameResult = validateFilename(name);
353+
if (!nameResult.isValid) {
354+
throw new Error(`Invalid dataset name: ${nameResult.error}`);
355+
}
356+
357+
const sanitizedName = nameResult.sanitized;
358+
359+
// Get the dataset path
360+
const datasetPathResult = securePath(`datasets/local/${sanitizedName}`, WORKSPACE_PATH, { mustExist: true });
361+
362+
if (!datasetPathResult.isValid) {
363+
throw new Error(`Invalid dataset path: ${datasetPathResult.error}`);
364+
}
365+
366+
// Check if the dataset exists
367+
if (!fs.existsSync(datasetPathResult.path)) {
368+
throw new Error(`Dataset not found: ${sanitizedName}`);
369+
}
370+
371+
// Delete the dataset directory and all its contents
372+
fs.rmSync(datasetPathResult.path, { recursive: true, force: true });
373+
374+
logInfo(`Dataset deleted: ${sanitizedName} at ${datasetPathResult.path}`);
375+
376+
return {
377+
success: true,
378+
message: `Dataset "${sanitizedName}" deleted successfully`
379+
};
380+
} catch (error) {
381+
logError('Error deleting dataset', error);
382+
throw error;
383+
}
384+
}
385+
336386
module.exports = {
337387
listDatasetImages,
338388
createDataset,
339-
addImageToDataset
389+
addImageToDataset,
390+
deleteDataset
340391
};

graphcap_studio/src/app/main.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ import './App.css'
1212
import { routeTree } from '../routeTree.gen'
1313
import App from './App'
1414
import { AppContextProvider, useFeatureFlag } from '../common/providers'
15+
import { getQueryClient } from '@/common/utils/queryClient'
1516

1617
// Create a new router instance
1718
const router = createRouter({ routeTree })
1819

1920
// Create Query Client
20-
const queryClient = new QueryClient()
21+
const queryClient = getQueryClient()
2122

2223
// Register the router instance for type safety
2324
declare module '@tanstack/react-router' {

0 commit comments

Comments
 (0)