-
Notifications
You must be signed in to change notification settings - Fork 2.1k
[Feature]: Select/deselect all sources in a Notebook #223 #231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@lfnovo pls have a look |
|
can you please add hacktoberfest label in the pull req |
| useEffect(() => { | ||
| if (!open) return | ||
|
|
||
| const loadSources = async () => { | ||
| setIsLoading(true) | ||
| try { | ||
| if (operation === 'add') { | ||
| // For add: fetch all sources and filter out ones already in notebook | ||
| const allSources = await sourcesApi.list({ | ||
| limit: 100, | ||
| offset: 0, | ||
| sort_by: 'created', | ||
| sort_order: 'desc', | ||
| }) | ||
| const sourcesToAdd = allSources.filter(s => !currentSourceIds.has(s.id)) | ||
| setAvailableSources(sourcesToAdd) | ||
| } else { | ||
| // For remove: use sources already in the notebook | ||
| setAvailableSources(currentNotebookSources || []) | ||
| } | ||
| } catch (error) { | ||
| console.error('Error loading sources:', error) | ||
| setAvailableSources([]) | ||
| } finally { | ||
| setIsLoading(false) | ||
| } | ||
| } | ||
|
|
||
| loadSources() | ||
| }, [open, operation, currentSourceIds, currentNotebookSources]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟠 HIGH - Race condition in async useEffect without cancellation
Category: bug
Description:
Async fetch operation in useEffect lacks cancellation. If dialog reopens or dependencies change rapidly, stale responses can overwrite current state.
Suggestion:
Add a cleanup function with a cancelled flag or AbortController to prevent stale responses from updating state after unmount or dependency change.
Confidence: 88%
Rule: react_async_race_condition
| for source_id in request.source_ids: | ||
| try: | ||
| if request.operation == "add": | ||
| # Check if source exists | ||
| source = await Source.get(source_id) | ||
| if not source: | ||
| results.append({ | ||
| "source_id": source_id, | ||
| "success": False, | ||
| "error": "Source not found" | ||
| }) | ||
| continue | ||
|
|
||
| # Check if reference already exists (idempotency) | ||
| existing_ref = await repo_query( | ||
| "SELECT * FROM reference WHERE out = $source_id AND in = $notebook_id", | ||
| { | ||
| "notebook_id": ensure_record_id(notebook_id), | ||
| "source_id": ensure_record_id(source_id), | ||
| }, | ||
| ) | ||
|
|
||
| # If reference doesn't exist, create it | ||
| if not existing_ref: | ||
| await repo_query( | ||
| "RELATE $source_id->reference->$notebook_id", | ||
| { | ||
| "notebook_id": ensure_record_id(notebook_id), | ||
| "source_id": ensure_record_id(source_id), | ||
| }, | ||
| ) | ||
|
|
||
| results.append({ | ||
| "source_id": source_id, | ||
| "success": True, | ||
| "message": "Source added to notebook successfully" | ||
| }) | ||
|
|
||
| elif request.operation == "remove": | ||
| # Delete the reference record linking source to notebook | ||
| await repo_query( | ||
| "DELETE FROM reference WHERE out = $notebook_id AND in = $source_id", | ||
| { | ||
| "notebook_id": ensure_record_id(notebook_id), | ||
| "source_id": ensure_record_id(source_id), | ||
| }, | ||
| ) | ||
|
|
||
| results.append({ | ||
| "source_id": source_id, | ||
| "success": True, | ||
| "message": "Source removed from notebook successfully" | ||
| }) | ||
|
|
||
| except Exception as e: | ||
| logger.error( | ||
| f"Error processing source {source_id} for notebook {notebook_id}: {str(e)}" | ||
| ) | ||
| results.append({ | ||
| "source_id": source_id, | ||
| "success": False, | ||
| "error": str(e) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 CRITICAL - N+1 database queries in bulk operation loop
Category: performance
Description:
Loop executes Source.get() and multiple repo_query() calls for each source_id, causing severe performance degradation at scale
Suggestion:
Batch fetch all sources with a single query: sources = await Source.get_many(request.source_ids), then process in-memory. For reference operations, consider bulk RELATE query or batched operations.
Confidence: 92%
Rule: perf_n_plus_one_queries
| for source_id in request.source_ids: | ||
| try: | ||
| if request.operation == "add": | ||
| # Check if source exists | ||
| source = await Source.get(source_id) | ||
| if not source: | ||
| results.append({ | ||
| "source_id": source_id, | ||
| "success": False, | ||
| "error": "Source not found" | ||
| }) | ||
| continue | ||
|
|
||
| # Check if reference already exists (idempotency) | ||
| existing_ref = await repo_query( | ||
| "SELECT * FROM reference WHERE out = $source_id AND in = $notebook_id", | ||
| { | ||
| "notebook_id": ensure_record_id(notebook_id), | ||
| "source_id": ensure_record_id(source_id), | ||
| }, | ||
| ) | ||
|
|
||
| # If reference doesn't exist, create it | ||
| if not existing_ref: | ||
| await repo_query( | ||
| "RELATE $source_id->reference->$notebook_id", | ||
| { | ||
| "notebook_id": ensure_record_id(notebook_id), | ||
| "source_id": ensure_record_id(source_id), | ||
| }, | ||
| ) | ||
|
|
||
| results.append({ | ||
| "source_id": source_id, | ||
| "success": True, | ||
| "message": "Source added to notebook successfully" | ||
| }) | ||
|
|
||
| elif request.operation == "remove": | ||
| # Delete the reference record linking source to notebook | ||
| await repo_query( | ||
| "DELETE FROM reference WHERE out = $notebook_id AND in = $source_id", | ||
| { | ||
| "notebook_id": ensure_record_id(notebook_id), | ||
| "source_id": ensure_record_id(source_id), | ||
| }, | ||
| ) | ||
|
|
||
| results.append({ | ||
| "source_id": source_id, | ||
| "success": True, | ||
| "message": "Source removed from notebook successfully" | ||
| }) | ||
|
|
||
| except Exception as e: | ||
| logger.error( | ||
| f"Error processing source {source_id} for notebook {notebook_id}: {str(e)}" | ||
| ) | ||
| results.append({ | ||
| "source_id": source_id, | ||
| "success": False, | ||
| "error": str(e) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟠 HIGH - Missing transaction handling with rollback
Category: performance
Description:
Bulk operation with multiple database statements lacks transaction wrapping, risking partial failures and data inconsistency
Suggestion:
Wrap the loop in a database transaction with try/except to commit on success and rollback on failure. Use transaction context manager or begin/commit/rollback pattern.
Confidence: 87%
Rule: py_add_transaction_handling_with_rollback
|
Description
Summary
Why this helps
Implementation details
API (generic shape)
UI/UX
How to test
Affected areas (indicative)
Notes
Checklist
Demo Video
2025-10-27.12-08-56.mp4
Linked issues