Skip to content
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

perf: paginate API calls #1332

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft

Conversation

nas-tabchiche
Copy link
Contributor

@nas-tabchiche nas-tabchiche commented Jan 7, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new RemoteModelTable component with advanced table rendering capabilities
    • Enhanced pagination and search functionality across multiple components
    • Added server-side model table components with improved data handling
  • Improvements

    • Updated pagination settings to use LimitOffsetPagination
    • Improved type safety for pagination-related configurations
    • Refined search and filter mechanisms in table components
    • Enhanced data fetching and rendering logic
  • Backend Changes

    • Modified viewset ordering to allow ordering by all fields
    • Updated search fields for requirement assessments
  • Frontend Enhancements

    • Implemented debounce mechanism for search inputs
    • Added more flexible table styling and configuration options
    • Improved accessibility and interaction for table components

@ab-smith ab-smith changed the title Switch to limit offset pagination in the backend perf: switch to limit offset pagination in the backend Jan 26, 2025
@ab-smith
Copy link
Contributor

@coderabbitai full review

Copy link

coderabbitai bot commented Jan 26, 2025

✅ Actions performed

Full review triggered.

Copy link

coderabbitai bot commented Jan 26, 2025

Walkthrough

This pull request introduces significant changes across the frontend and backend of the application, focusing on enhancing pagination, table rendering, and data handling mechanisms. The modifications span multiple components and files, introducing new server-side table components, updating pagination strategies, and refining data retrieval and display logic. The changes aim to improve flexibility in data presentation, search functionality, and overall user interaction with tabular data.

Changes

File Change Summary
backend/ciso_assistant/settings.py
enterprise/backend/enterprise_core/settings.py
- Updated PAGINATE_BY to explicitly convert to integer
- Changed default pagination class to LimitOffsetPagination
backend/core/views.py - Modified ordering_fields to use "__all__"
- Updated search_fields for requirement assessments
frontend/src/lib/components/ModelTable/* - Introduced new server-side table components
- Added debounced search functionality
- Enhanced pagination and filtering mechanisms
frontend/src/routes/(app)/(internal)/[model=urlmodel]/+page.svelte - Updated ModelTable import to use server-side version

Sequence Diagram

sequenceDiagram
    participant User
    participant ModelTable
    participant DataHandler
    participant Server

    User->>ModelTable: Interact with table
    ModelTable->>DataHandler: Request data
    DataHandler->>Server: Fetch data with pagination
    Server-->>DataHandler: Return paginated results
    DataHandler-->>ModelTable: Update table state
    ModelTable->>User: Render updated table
Loading

Poem

🐰 Hop, hop, hooray! A table's new way,
Pagination dancing with server's display,
Filters and searches, now smooth as can be,
CodeRabbit's magic, for all users to see!
Data flows freely, no more delay! 🎉

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (10)
frontend/src/lib/components/ModelTable/RemoteModelTable.svelte (4)

29-38: Consider consolidating multiple boolean props into a configuration object.

Currently, there are many boolean props controlling aspects such as interactive, search, rowsPerPage, rowCount, etc. Grouping them into a single configuration object or a smaller set of logically-related props can streamline usage and reduce prop clutter.


56-77: Combine row click and keydown handlers to reduce duplication.

Both onRowClick and onRowKeydown share similar logic for navigating to detail pages. Consider merging them into a unified, reusable function (with a parameter specifying if the call originated from a mouse or keyboard event), improving maintainability.


193-220: Extract the array rendering logic into a helper component or function to improve readability.

The block that handles rendering arrays (lines 202-222) is quite extensive, containing multiple conditionals and nested loops. Moving it into a dedicated helper function or a child component will clarify the main table rendering flow and make testing easier.


329-336: Consider pagination performance for large datasets.

While the Pagination component and remote handler are used, extremely large datasets might benefit from server-side filtering and dynamic loading. Evaluate whether chunked/lazy loading is necessary to prevent performance bottlenecks and memory overhead.

frontend/src/lib/components/ModelTable/RowCount.svelte (2)

8-10: Use a more descriptive placeholder for undefined row counts.

Rendering an empty <div /> could be confusing for keyboard or screen-reader users. Instead, consider using a visually hidden accessibility message (e.g., "Loading row count" or "Row count unavailable").


12-16: Clarify the zero-count scenario to end users.

When $rowCount?.total is 0, the component displays "no entries found". In some edge cases (e.g., filtered results), ensure the end user clearly understands that there is no matching data, rather than a loading or retrieval failure.

frontend/src/lib/components/ModelTable/handler.ts (1)

33-46: Uncomment and implement search and filter functionality.

The search and filter functionality is commented out. Consider implementing these features to provide a complete table experience.

Would you like me to help implement the search and filter functionality? I can provide a complete implementation that integrates with the backend API.

frontend/src/lib/components/ModelTable/Pagination.svelte (2)

16-16: Remove debug console.log statement.

Debug console.log statements should not be committed to production code.

-$: console.log('$pages', $pages);

18-22: Consider adding loading state during navigation.

The afterNavigate hook resets the page, but there's no indication to the user that the table is reloading.

Consider adding a loading state:

 afterNavigate(() => {
     if ($page.url) {
+        handler.setLoading(true);
         handler.setPage(1);
+        handler.invalidate().finally(() => {
+            handler.setLoading(false);
+        });
     }
 });
backend/ciso_assistant/settings.py (1)

127-127: Consider reducing the default pagination size.

The default value of 5000 items per page is quite high and could impact performance. Consider reducing this to a more reasonable default (e.g., 100-500) to:

  • Reduce initial load time
  • Decrease memory usage
  • Improve response times
-PAGINATE_BY = int(os.environ.get("PAGINATE_BY", default=5000))
+PAGINATE_BY = int(os.environ.get("PAGINATE_BY", default=100))
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d590179 and 606d7a9.

📒 Files selected for processing (10)
  • backend/ciso_assistant/settings.py (2 hunks)
  • enterprise/backend/enterprise_core/settings.py (2 hunks)
  • frontend/src/lib/components/ModelTable/ModelTable.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/Pagination.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/RemoteModelTable.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/RowCount.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/RowsPerPage.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/Th.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/handler.ts (1 hunks)
  • frontend/src/routes/(app)/(internal)/[model=urlmodel]/+page.svelte (1 hunks)
🔇 Additional comments (11)
frontend/src/lib/components/ModelTable/RemoteModelTable.svelte (2)

275-310: Ensure the custom actions slot covers all key security and concurrency concerns.

The <TableRowActions> usage is flexible, but verifying that each row's action permissions and concurrency conditions (e.g., multi-user editing or row lock scenarios) are handled robustly can prevent potential data corruption or unauthorized edits.


127-128: Verify the null/undefined checks when referencing row.meta.

In preventDelete and elsewhere, references to row.meta are common. If row.meta is missing or undefined due to upstream changes, this could throw an error. Ensure that row.meta is a defined object before attempting property access.

✅ Verification successful

The code has proper null/undefined checks for row.meta access

The implementation includes multiple layers of protection:

  • Object.hasOwn() checks before accessing critical properties
  • Optional chaining (?.) for nullable fields
  • Guaranteed meta initialization with undefined fallback
  • Meta object access with row fallback
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Looking for usage of row.meta to ensure that it's checked or guaranteed to exist
rg "row\.meta" -A 3 | rg -B 3 -A 3 "if|else|Object\.hasOwn"

Length of output: 5984


Script:

#!/bin/bash
# Look for type definitions and interfaces related to row/meta
ast-grep --pattern 'interface $_ {
  meta: $_
}'

# Look for where row data is constructed
rg "row.*=.*{.*meta.*}" -A 3

Length of output: 191


Script:

#!/bin/bash
# Search for TypeScript type definitions and interfaces
fd "\.(ts|d\.ts)$" | xargs rg "type.*Row|interface.*Row" -A 5

# Look for component props and row type usage
rg "export let row" -A 2

# Look for meta object construction with proper escaping
rg "row\s*=\s*\{" -A 3

Length of output: 1750


Script:

#!/bin/bash
# Look for table data population and row structure
rg "let rows\s*=|rows\s*=" -A 3

# Check the table component implementation
fd "Table.*svelte$" | xargs cat

# Look for data fetching or row creation
rg "createRows|getRows|fetchRows" -A 3

Length of output: 33843

frontend/src/lib/components/ModelTable/RowsPerPage.svelte (1)

9-11: Verify page handling logic after commenting out pagination updates.

By disabling auto-adjustment (handler.setPage(...)), users could potentially remain on an out-of-range page. Confirm that this cannot happen or that other checks exist to reset the page in these boundary conditions.

✅ Verification successful

Pagination boundary checks are properly handled elsewhere

The commented-out code can be safely removed as the Pagination component already enforces valid page boundaries through:

  • Disabled states for previous/next buttons at page limits
  • Constrained page selection via handler.getPages()
  • Automatic reset to page 1 during navigation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Confirm no page-out-of-range logic is required in the rest of the code after removing setPage()
rg -A 3 "setPage"

Length of output: 2350


Script:

#!/bin/bash
# Check handler implementation and store management
ast-grep --pattern 'class $_ {
  $$$
  setPage($_) {
    $$$
  }
  $$$
}'

# Look for page number validation
rg -A 5 "pageNumber|pages" frontend/src/lib/components/ModelTable/

Length of output: 3193

frontend/src/lib/components/ModelTable/Th.svelte (2)

9-13: Encapsulation of sort logic improves code organization.

The introduction of the update function improves code organization by encapsulating the sorting logic and handler invalidation in a single place.


17-18: Verify sort direction indicators in the UI.

The class bindings for sort direction indicators have been updated. Please ensure that the ascending and descending arrows are displayed correctly when sorting is applied.

Run this script to check the sort direction implementation across the codebase:

Also applies to: 26-27

frontend/src/lib/components/ModelTable/Pagination.svelte (1)

12-15: LGTM: Improved page setting logic.

The new setPage function properly encapsulates the page setting logic and handler invalidation.

frontend/src/routes/(app)/(internal)/[model=urlmodel]/+page.svelte (1)

5-5: Verify RemoteModelTable compatibility with existing features.

The switch to RemoteModelTable should maintain compatibility with existing features like the add button, export functionality, and graph visualization.

Run this script to verify the component's interface:

✅ Verification successful

RemoteModelTable maintains full compatibility with existing features

The component implements all required functionality:

  • Add button support via addButton slot (respects permissions)
  • Export functionality via optButton slot
  • Custom actions and visualizations through field_component_map
  • Full pagination and search capabilities
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if RemoteModelTable implements all required props and slots
ast-grep --pattern 'export let $prop' frontend/src/lib/components/ModelTable/RemoteModelTable.svelte
echo "Checking slot definitions..."
ast-grep --pattern '<slot $$$name$$$>' frontend/src/lib/components/ModelTable/RemoteModelTable.svelte

Length of output: 273


Script:

#!/bin/bash
# First verify the file exists and show its content
echo "Checking file existence and content:"
cat frontend/src/lib/components/ModelTable/RemoteModelTable.svelte

echo -e "\nChecking exported props:"
ast-grep --pattern 'export let' 

echo -e "\nChecking slot definitions:"
ast-grep --pattern '<slot'

Length of output: 12209

backend/ciso_assistant/settings.py (1)

214-214: LGTM! Good choice of pagination strategy.

Switching to LimitOffsetPagination is a good choice for improving performance as it:

  • Provides better handling of large datasets
  • Allows more efficient database queries
  • Enables cursor-based navigation through results
enterprise/backend/enterprise_core/settings.py (2)

130-130: Consider reducing the default pagination size (enterprise).

Similar to the community version, the default value of 5000 items per page is quite high. Consider reducing this to maintain consistency with the recommended changes in the community version.


217-217: LGTM! Consistent pagination strategy with community version.

The switch to LimitOffsetPagination maintains consistency with the community version while providing the same performance benefits.

frontend/src/lib/components/ModelTable/ModelTable.svelte (1)

Line range hint 185-196: LGTM! Well-implemented filter tracking.

The implementation:

  • Properly tracks active filters using reactive statements
  • Efficiently calculates filter count
  • Uses appropriate TypeScript typing
  • Follows Svelte's reactive programming patterns

Note: While this change doesn't directly relate to the pagination changes, it complements them by providing better control over the displayed data.

Comment on lines 98 to 115
import { goto } from '$lib/utils/breadcrumbs';
import { formatDateOrDateTime } from '$lib/utils/datetime';
import { DataHandler, type Row, type State } from '@vincjo/datatables/remote';
import Pagination from './Pagination.svelte';
import RowCount from './RowCount.svelte';
import RowsPerPage from './RowsPerPage.svelte';
import Search from './Search.svelte';
import Th from './Th.svelte';
import ThFilter from './ThFilter.svelte';

const data = source.body.map((item: Record<string, any>, index: number) => {
return { ...item, meta: source.meta ? { ...source.meta[index] } : undefined };
});

const handler = new DataHandler(data, { rowsPerPage: 10 });
const rows = handler.getRows();

handler.onChange((state: State) => loadTableData(state, 'threats', '/threats') as Promise<Row[]>);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle potential data loading errors.

The handler.onChange((state: State) => loadTableData(state, 'threats', '/threats') as Promise<Row[]>) call lacks error handling. Consider wrapping this call in a try/catch or adding promise .catch() logic to handle potential network or server errors gracefully and provide user feedback.

 handler.onChange((state: State) => {
+   try {
     return loadTableData(state, 'threats', '/threats');
+   } catch (error) {
+     console.error('Failed to load data:', error);
+     // Optionally show user notification here
+     throw error;
+   }
 });

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 8 to 31
export const loadTableData = async (state: State, URLModel: urlModel, endpoint: string) => {
const response = await fetch(`${endpoint}/?${getParams(state)}`).then((res) => res.json());
state.setTotalRows(response.count);

const bodyData = tableSourceMapper(response, listViewFields[URLModel as urlModel].body);

const headData: Record<string, string> = listViewFields[URLModel as urlModel].body.reduce(
(obj, key, index) => {
obj[key] = listViewFields[URLModel as urlModel].head[index];
return obj;
},
{}
);

const table: TableSource = {
head: headData,
body: bodyData,
meta: response // metaData
};

return table.body.map((item: Record<string, any>, index: number) => {
return { ...item, meta: table.meta ? { ...table.meta[index] } : undefined };
});
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for fetch operations.

The loadTableData function should handle potential fetch errors and API response errors. Consider implementing proper error handling and loading states.

Apply this diff to add error handling:

 export const loadTableData = async (state: State, URLModel: urlModel, endpoint: string) => {
-    const response = await fetch(`${endpoint}/?${getParams(state)}`).then((res) => res.json());
+    try {
+        const res = await fetch(`${endpoint}/?${getParams(state)}`);
+        if (!res.ok) {
+            throw new Error(`HTTP error! status: ${res.status}`);
+        }
+        const response = await res.json();
+        state.setTotalRows(response.count);
 
-    state.setTotalRows(response.count);
+        const bodyData = tableSourceMapper(response, listViewFields[URLModel as urlModel].body);
 
-    const bodyData = tableSourceMapper(response, listViewFields[URLModel as urlModel].body);
+        const headData: Record<string, string> = listViewFields[URLModel as urlModel].body.reduce(
+            (obj, key, index) => {
+                obj[key] = listViewFields[URLModel as urlModel].head[index];
+                return obj;
+            },
+            {}
+        );
 
-    const headData: Record<string, string> = listViewFields[URLModel as urlModel].body.reduce(
-        (obj, key, index) => {
-            obj[key] = listViewFields[URLModel as urlModel].head[index];
-            return obj;
-        },
-        {}
-    );
+        const table: TableSource = {
+            head: headData,
+            body: bodyData,
+            meta: response
+        };
 
-    const table: TableSource = {
-        head: headData,
-        body: bodyData,
-        meta: response // metaData
-    };
-
-    return table.body.map((item: Record<string, any>, index: number) => {
-        return { ...item, meta: table.meta ? { ...table.meta[index] } : undefined };
-    });
+        return table.body.map((item: Record<string, any>, index: number) => {
+            return { ...item, meta: table.meta ? { ...table.meta[index] } : undefined };
+        });
+    } catch (error) {
+        console.error('Error loading table data:', error);
+        throw error;
+    }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const loadTableData = async (state: State, URLModel: urlModel, endpoint: string) => {
const response = await fetch(`${endpoint}/?${getParams(state)}`).then((res) => res.json());
state.setTotalRows(response.count);
const bodyData = tableSourceMapper(response, listViewFields[URLModel as urlModel].body);
const headData: Record<string, string> = listViewFields[URLModel as urlModel].body.reduce(
(obj, key, index) => {
obj[key] = listViewFields[URLModel as urlModel].head[index];
return obj;
},
{}
);
const table: TableSource = {
head: headData,
body: bodyData,
meta: response // metaData
};
return table.body.map((item: Record<string, any>, index: number) => {
return { ...item, meta: table.meta ? { ...table.meta[index] } : undefined };
});
};
export const loadTableData = async (state: State, URLModel: urlModel, endpoint: string) => {
try {
const res = await fetch(`${endpoint}/?${getParams(state)}`);
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
const response = await res.json();
state.setTotalRows(response.count);
const bodyData = tableSourceMapper(response, listViewFields[URLModel as urlModel].body);
const headData: Record<string, string> = listViewFields[URLModel as urlModel].body.reduce(
(obj, key, index) => {
obj[key] = listViewFields[URLModel as urlModel].head[index];
return obj;
},
{}
);
const table: TableSource = {
head: headData,
body: bodyData,
meta: response
};
return table.body.map((item: Record<string, any>, index: number) => {
return { ...item, meta: table.meta ? { ...table.meta[index] } : undefined };
});
} catch (error) {
console.error('Error loading table data:', error);
throw error;
}
};

@ab-smith ab-smith added the perf label Jan 27, 2025
@nas-tabchiche nas-tabchiche changed the title perf: switch to limit offset pagination in the backend perf: paginate API calls Jan 28, 2025
@nas-tabchiche
Copy link
Contributor Author

@coderabbitai full review

Copy link

coderabbitai bot commented Jan 28, 2025

✅ Actions performed

Full review triggered.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (22)
frontend/src/lib/components/ModelTable/RowCount.svelte (2)

Line range hint 1-5: Consider using a reactive declaration for rowCount.

The current implementation retrieves the row count once during initialization. To ensure the count stays in sync with the handler's state, consider using a reactive declaration.

-const rowCount = handler.getRowCount();
+$: rowCount = handler.getRowCount();

8-10: Consider removing or improving the empty div placeholder.

The empty <div /> placeholder could be either removed entirely (if the parent layout doesn't depend on it) or replaced with a more semantic element that indicates loading/processing state.

-{#if $rowCount === undefined}
-	<div />
-{:else}
+{#if $rowCount !== undefined}
	<aside class="text-sm">
		{#if $rowCount?.total > 0}
			{m.rowCount({ start: $rowCount.start, end: $rowCount.end, total: $rowCount.total })}
		{:else}
			{m.noEntriesFound()}
		{/if}
	</aside>
{/if}
frontend/src/lib/components/ModelTable/ModelTable.svelte (2)

Line range hint 185-195: Consider handling additional filter value types.

The filter counting logic assumes all filter values are arrays. Consider handling other potential filter value types for better robustness.

 function getFilterCount(filters: typeof $_filters): number {
   return Object.values(filters).reduce((acc, filter) => {
-    if (Array.isArray(filter.value) && filter.value.length > 0) {
+    if (!filter.value) return acc;
+    if (Array.isArray(filter.value)) {
+      return filter.value.length > 0 ? acc + 1 : acc;
+    }
+    // Handle non-array filter values (e.g., single value filters)
+    if (filter.value !== undefined && filter.value !== null) {
       return acc + 1;
     }
     return acc;
   }, 0);
 }

185-187: Add JSDoc documentation for the filter-related code.

Consider adding documentation to explain:

  • The structure of filter values
  • The relationship between _filters and the DataHandler
  • Expected filter value types
+/** Stores the current state of all active filters from the DataHandler */
 const _filters = handler.getFilters();

+/**
+ * Calculates the total number of active filters
+ * @param filters - The current filter state from DataHandler
+ * @returns The count of active filters
+ */
 function getFilterCount(filters: typeof $_filters): number {
frontend/src/lib/components/ModelTable/server/ThFilter.svelte (2)

1-7: Add type safety improvements and proper class handling.

Consider these improvements for better type safety and class handling:

  1. Add type for the value variable
  2. Add runtime validation for the required handler prop
  3. Consider using the Svelte class directive instead of a manual class property
 <script lang="ts">
 	import type { DataHandler } from '@vincjo/datatables';
 	export let handler: DataHandler;
 	export let filterBy = '';
-	let value = '';
-	let classProp = ''; // Replacing $$props.class
+	let value: string = '';
+	$: if (!handler) {
+		throw new Error('ThFilter component requires a handler prop');
+	}
 </script>

1-17: Consider additional features for better UX.

The filter component could benefit from these enhancements:

  1. Add a clear button to reset the filter
  2. Show visual feedback when the filter is active
  3. Handle and display filter operation errors
  4. Consider adding filter presets for common use cases

These improvements would make the filtering functionality more robust and user-friendly.

Would you like me to provide an implementation example for any of these features?

frontend/src/lib/components/ModelTable/server/Th.svelte (3)

7-7: Simplify the identifier assignment.

The optional chaining operator (?.) is unnecessary here since orderBy is initialized as a string.

-const identifier = orderBy?.toString();
+const identifier = orderBy.toString();

3-5: Add type safety for component props.

Consider adding type constraints and validation for the props to ensure type safety.

-export let handler: DataHandler;
-export let orderBy = '';
+export let handler: DataHandler;
+export let orderBy: string = '';
+
+$: if (!handler) {
+    throw new Error('DataHandler is required');
+}

32-53: Enhance sort indicator styles for better user experience.

Consider adding smooth transitions and using CSS custom properties for better maintainability.

 <style>
+    th {
+        transition: background-color 0.2s ease;
+    }
     th span:before,
     th span:after {
         border: 4px solid transparent;
         content: '';
         display: block;
         height: 0;
         width: 0;
+        transition: border-color 0.2s ease;
     }
     th span:before {
         @apply border-b-surface-200 mt-0.5;
     }
     th span:after {
         @apply border-t-surface-200 mt-0.5;
     }
     th.active span.asc:before {
         @apply border-b-surface-700;
     }
     th.active span.desc:after {
         @apply border-t-surface-700;
     }
 </style>
frontend/src/lib/components/ModelTable/server/RowsPerPage.svelte (2)

5-7: Consider making the options array configurable via props.

While the current fixed options [5, 10, 20, 50, 100] are reasonable defaults, consider making this configurable to accommodate different use cases.

 <script lang="ts">
 	import type { DataHandler } from '@vincjo/datatables';
 	import * as m from '$paraglide/messages';
 	export let handler: DataHandler;
+	export let rowsPerPageOptions = [5, 10, 20, 50, 100];
 	const rowsPerPage = handler.getRowsPerPage();
-	const options = [5, 10, 20, 50, 100];
+	const options = rowsPerPageOptions;

19-33: Add accessibility attributes to improve user experience.

While the template implementation is clean, it could benefit from additional accessibility attributes:

 <aside class="flex items-center">
 	{m.show()}
 	<select
 		class="select bg-surface-50 w-fit mx-1"
 		bind:value={$rowsPerPage}
 		on:change={setRowsPerPage}
+		aria-label={m.show()}
+		role="combobox"
 	>
 		{#each options as option}
 			<option value={option}>
 				{option}
 			</option>
 		{/each}
 	</select>
 	{m.entries()}
 </aside>
frontend/src/lib/components/ModelTable/server/RowCount.svelte (2)

1-6: Add type safety and error handling.

While the basic TypeScript setup is good, consider these improvements:

  1. Add error handling for getRowCount()
  2. Add type checking for the messages import
  3. Add initialization check for the handler prop
 <script lang="ts">
 	import type { DataHandler } from '@vincjo/datatables';
-	import * as m from '$paraglide/messages';
+	import type { Messages } from '$paraglide/messages';
+	import * as m from '$paraglide/messages';
 	export let handler: DataHandler;
+	$: if (!handler) {
+		console.error('Handler is required for RowCount component');
+	}
 	const rowCount = handler.getRowCount();
 </script>

8-18: Enhance UX and accessibility.

Consider these improvements:

  1. Add proper ARIA attributes for screen readers
  2. Include loading and error states
  3. Replace empty div with a more semantic placeholder
-{#if $rowCount === undefined}
-	<div />
+{#if $rowCount === undefined}
+	<aside class="text-sm" aria-hidden="true">
+		<span class="animate-pulse">Loading...</span>
+	</aside>
 {:else}
-	<aside class="text-sm">
+	<aside class="text-sm" role="status" aria-live="polite">
 		{#if $rowCount?.total > 0}
 			{m.rowCount({ start: $rowCount.start, end: $rowCount.end, total: $rowCount.total })}
 		{:else}
 			{m.noEntriesFound()}
 		{/if}
 	</aside>
 {/if}
frontend/src/lib/components/ModelTable/server/ModelTable.svelte (3)

70-74: Refine detailQueryParameter setting.
The current approach appends a “?” if the parameter is defined, which is fine but can lead to confusion if we need multiple query parameters or more robust query strings in the future.

Consider using a utility function or a more flexible approach (e.g., URLSearchParams) to handle a variety of query parameters consistently.


239-241: Expand date detection beyond hardcoded fields.
You are checking for ISO-8601 strings specifically in fields like created_at, updated_at, and a few others. If additional date/time fields are added later, they would be missed by this conditional.

Propose moving date detection logic into a more generic utility function that can dynamically detect or handle newly introduced date fields.


248-256: Avoid embedding domain-specific logic in a generic table.
Lines 248-256 introduce special styling for 'risk-acceptances'. While it addresses an immediate need, it couples domain logic with a generally reusable component.

Consider delegating domain-specific styling or logic to a higher-level wrapper or a custom cell renderer so that ModelTable can remain as generic as possible.

frontend/src/lib/components/ModelTable/Search.svelte (1)

7-15: Use a more precise type than any for timeout.
Using any is convenient, but it can obscure potential timing or scope issues.

- let timeout: any;
+ let timeout: ReturnType<typeof setTimeout> | undefined;

Additionally, you might want to clean up the timeout on component teardown to prevent lingering callbacks:

import { onDestroy } from 'svelte';

onDestroy(() => {
  clearTimeout(timeout);
});
frontend/src/lib/components/ModelTable/Pagination.svelte (3)

16-16: Remove debug console.log statement.

Debug logging should not be present in production code.

-$: console.log('$pages', $pages);

Line range hint 1-52: Consider consolidating duplicate pagination components.

This component shares identical code with server/Pagination.svelte. Consider consolidating these components to avoid code duplication and maintenance overhead.


12-15: Add error handling for invalid page values.

The setPage function should validate the page number to prevent potential issues with invalid values.

 const setPage = (value: 'previous' | 'next' | number) => {
+    if (typeof value === 'number' && (value < 1 || value > $pageCount)) {
+        return;
+    }
     handler.setPage(value);
     handler.invalidate();
 };
backend/core/views.py (1)

128-128: Consider security and performance implications of allowing ordering by all fields.

While ordering_fields = "__all__" provides flexibility, it could:

  1. Impact performance when ordering by fields without database indexes
  2. Expose sensitive data through ordering operations
  3. Increase database load with complex ordering queries

Consider explicitly listing only the necessary orderable fields.

-    ordering_fields = "__all__"
+    ordering_fields = [
+        "created_at",
+        "updated_at",
+        "name",
+        "description"
+        # Add other specific fields that need ordering
+    ]
frontend/src/lib/components/ModelTable/RemoteModelTable.svelte (1)

187-323: Consider performance optimization for large datasets.

The table body rendering includes multiple nested loops and conditional checks that could impact performance with large datasets.

Consider:

  1. Implementing virtual scrolling for large datasets
  2. Memoizing complex computations
  3. Using {#key} blocks to optimize re-renders

Example implementation:

// Add these reactive statements
$: memoizedRows = $rows;
$: visibleRows = memoizedRows.slice(startIndex, endIndex);

// Update the template to use virtualized rows
{#each visibleRows as row, rowIndex (row.meta[identifierField])}
  {#key row.meta[identifierField]}
    <!-- Row content -->
  {/key}
{/each}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 625e07b and 11d2da4.

📒 Files selected for processing (21)
  • backend/ciso_assistant/settings.py (2 hunks)
  • backend/core/views.py (2 hunks)
  • enterprise/backend/enterprise_core/settings.py (2 hunks)
  • frontend/src/lib/components/ModelTable/ModelTable.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/Pagination.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/RemoteModelTable.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/RowCount.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/RowsPerPage.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/Search.svelte (2 hunks)
  • frontend/src/lib/components/ModelTable/Th.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/server/ModelTable.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/server/Pagination.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/server/RowCount.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/server/RowsPerPage.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/server/Search.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/server/Th.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/server/ThFilter.svelte (1 hunks)
  • frontend/src/lib/components/ModelTable/server/handler.ts (1 hunks)
  • frontend/src/routes/(app)/(internal)/[model=urlmodel]/+page.svelte (1 hunks)
  • frontend/src/routes/(app)/(internal)/[model=urlmodel]/+server.ts (1 hunks)
  • frontend/src/routes/(app)/(internal)/analytics/ComposerSelect.svelte (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: startup-docker-compose-test
  • GitHub Check: enterprise-functional-tests (3.12, chromium)
  • GitHub Check: test (3.12)
  • GitHub Check: functional-tests (3.12, chromium)
  • GitHub Check: enterprise-startup-docker-compose-test
  • GitHub Check: build (3.12)
🔇 Additional comments (20)
frontend/src/lib/components/ModelTable/RowCount.svelte (1)

12-16: Well-structured conditional rendering with i18n support!

The implementation properly handles the row count states while maintaining internationalization support. The use of optional chaining (?.) for safe property access is a good practice.

frontend/src/lib/components/ModelTable/ModelTable.svelte (1)

Line range hint 290-294: LGTM! Clean implementation of the filter count badge.

The badge implementation follows good practices for accessibility and user experience:

  • Conditional rendering based on filterCount
  • Proper positioning relative to the filter button
  • Clear visual feedback for active filters
frontend/src/lib/components/ModelTable/server/Th.svelte (1)

1-53: Well-structured component with good separation of concerns!

The component is well-implemented with clear separation of script, template, and styles. It effectively integrates with the DataHandler for sorting functionality while maintaining a clean and focused implementation.

frontend/src/lib/components/ModelTable/RowsPerPage.svelte (3)

9-12: LGTM! Clean implementation of rows per page handling.

The setRowsPerPage function follows good practices by:

  1. Resetting to the first page when changing page size
  2. Triggering a re-render via invalidate()

21-25: LGTM! Good use of Svelte bindings and event handling.

The select element implementation correctly uses:

  • Two-way binding with bind:value
  • Event handling with on:change

6-6: Verify pagination edge cases after removing automatic page adjustment.

The removal of row count tracking and automatic page adjustment could lead to edge cases where the current page becomes invalid after changing the rows per page. Please ensure these scenarios are handled appropriately elsewhere in the codebase.

Also applies to: 14-16

frontend/src/lib/components/ModelTable/server/RowsPerPage.svelte (3)

1-4: LGTM! Clean TypeScript setup with proper type definitions.

The component follows TypeScript best practices with proper type imports and export declarations.


9-12: LGTM! Proper handler implementation.

The setRowsPerPage function correctly handles page reset and invalidation when changing the number of rows per page.


6-6: Clarify the status of the commented-out row count logic.

The commented-out row count initialization suggests incomplete or removed pagination logic. Please clarify if this is intentional or if it needs to be implemented.

✅ Verification successful

Commented-out row count logic is intentionally removed

The commented-out code was for an edge case in pagination handling. The component remains fully functional without it, as:

  • Core rows-per-page functionality is handled by the remaining code
  • Row count display is managed separately by the RowCount component
  • Basic page reset is still implemented in the setRowsPerPage function
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other components that might be handling row count logic
rg -l "rowCount" frontend/src/lib/components/ModelTable/

Length of output: 480


Script:

#!/bin/bash
# Check implementation in server components
echo "=== Server RowCount.svelte ==="
cat frontend/src/lib/components/ModelTable/server/RowCount.svelte

echo -e "\n=== Server ModelTable.svelte rowCount usage ==="
rg "rowCount" frontend/src/lib/components/ModelTable/server/ModelTable.svelte -C 2

echo -e "\n=== Server RowsPerPage.svelte full file ==="
cat frontend/src/lib/components/ModelTable/server/RowsPerPage.svelte

Length of output: 2043

frontend/src/lib/components/ModelTable/server/RowCount.svelte (1)

1-18: Verify integration with ModelTable system.

Please ensure:

  1. The component properly integrates with the parent ModelTable component
  2. The row count updates are synchronized with other pagination components
  3. The messaging format is consistent across all table components
✅ Verification successful

Integration with ModelTable system verified successfully

The RowCount component properly integrates with the ModelTable system, using consistent patterns for handler prop usage, message formatting, and data synchronization. The implementation aligns with other pagination components and follows the established patterns across the table system.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for consistent usage patterns across table components
echo "Checking component integration patterns..."

# Find all related table components
fd -e svelte . frontend/src/lib/components/ModelTable/server

# Check for consistent message usage
rg "from '\$paraglide/messages'" frontend/src/lib/components/ModelTable/server

# Verify handler prop usage
ast-grep --pattern 'export let handler: DataHandler'

Length of output: 731


Script:

#!/bin/bash
# Check handler usage and data flow patterns
echo "Checking ModelTable integration patterns..."

# Check handler prop usage in ModelTable
ast-grep --pattern 'import type { DataHandler } from "@vincjo/datatables"'

# Examine ModelTable component implementation
cat frontend/src/lib/components/ModelTable/server/ModelTable.svelte

# Check message usage in pagination components
cat frontend/src/lib/components/ModelTable/server/Pagination.svelte

Length of output: 13947


Script:

#!/bin/bash
# Final verification of RowsPerPage integration
cat frontend/src/lib/components/ModelTable/server/RowsPerPage.svelte

# Check message usage consistency
rg "import \* as m from '\$paraglide/messages'" frontend/src/lib/components/ModelTable/server/RowsPerPage.svelte

Length of output: 986

frontend/src/lib/components/ModelTable/server/ModelTable.svelte (1)

47-61: Ensure robust handling of rowMetaData.
Here, onRowClick accesses $rows[rowIndex].meta and uses fields like rowMetaData[identifierField]. Although there's a fallback in the template logic, an undefined or null meta could slip through in edge cases (e.g., unusual data states or future expansions).

Consider adding additional null checks (or a typed guard) for rowMetaData to prevent potential runtime errors if meta is ever missing.

frontend/src/routes/(app)/(internal)/[model=urlmodel]/+server.ts (1)

Line range hint 15-21: Verify that all consumers handle the full response object correctly.

The endpoint now returns the complete response object instead of extracting just the results. While this change supports pagination by preserving metadata like count, ensure all consumers are updated to handle this new format.

Run this script to find potential consumers that might expect the old format:

✅ Verification successful

The response format change is safe to proceed with

The codebase consistently handles paginated responses by explicitly extracting the .results property where needed, primarily in server-side code. The change to return the full response object aligns with proper pagination support while maintaining compatibility with existing consumers.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for direct fetch calls to this endpoint and .results access
rg -l "fetch\(['\"]\/[^'\"]*\/" | xargs rg "\.results"

Length of output: 159


Script:

#!/bin/bash
# Search for fetch calls to model endpoints and response handling
rg -A 5 "fetch\(.*model.*\)" 

# Also search for any .results property access patterns
rg -A 3 "\.results\b"

Length of output: 40106

frontend/src/lib/components/ModelTable/Th.svelte (1)

9-13: LGTM! Good encapsulation of sort functionality.

The introduction of the update function improves code organization by encapsulating sort logic and ensuring proper invalidation of the handler.

frontend/src/lib/components/ModelTable/server/Pagination.svelte (1)

1-97: Duplicate component detected.

This component is identical to ModelTable/Pagination.svelte. Please refer to the comments on that file regarding consolidation.

enterprise/backend/enterprise_core/settings.py (2)

130-130: LGTM! Type safety improvement for PAGINATE_BY.

Converting the environment variable to integer prevents potential type-related issues.


217-217: Verify the impact of pagination class change.

Switching from PageNumberPagination to LimitOffsetPagination changes how clients should handle pagination. Ensure all API clients are updated accordingly.

✅ Verification successful

No impact on API clients - frontend already uses limit/offset pagination

The change from PageNumberPagination to LimitOffsetPagination is safe because:

  • Frontend code already uses limit/offset parameters for pagination
  • No page number based pagination found in API requests
  • Server-side handler properly constructs pagination using offset and limit parameters
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for pagination-related API calls in frontend code
rg -t js -t ts -t svelte 'page=' frontend/
rg -t js -t ts -t svelte 'limit=' frontend/

Length of output: 148


Script:

#!/bin/bash
# Search for pagination-related patterns in the codebase
echo "=== Searching for page-based pagination patterns ==="
rg -g '!*.{pyc,min.js}' -i 'page(_size|number|\s*=|":)' 

echo -e "\n=== Searching for limit-based pagination patterns ==="
rg -g '!*.{pyc,min.js}' -i '(limit|offset)(\s*=|":)'

echo -e "\n=== Searching for pagination response handling ==="
rg -g '!*.{pyc,min.js}' '(next_page|previous_page|results)(\s*=|":)'

echo -e "\n=== Checking API client configurations ==="
rg -g '!*.{pyc,min.js}' 'pagination' -A 2 -B 2

Length of output: 73265


Script:

#!/bin/bash
# Search for API client configurations and network requests
echo "=== Searching for API client configurations ==="
rg -g '!*.{pyc,min.js}' 'axios|fetch' -A 3 -B 3

echo -e "\n=== Searching for pagination parameters in requests ==="
rg -g '!*.{pyc,min.js}' '(page|limit|offset).*params'

echo -e "\n=== Checking pagination component implementations ==="
rg -g '!*.{pyc,min.js}' 'class="pagination|Pagination.svelte' -A 5 -B 5

echo -e "\n=== Searching for REST framework pagination imports/usage ==="
rg -g '!*.{pyc,min.js}' 'from rest_framework.pagination import|PageNumberPagination|LimitOffsetPagination'

Length of output: 67527


Script:

#!/bin/bash
# Search for pagination-related code
echo "=== Searching for pagination parameter handling ==="
rg -g '!*.{pyc,min.js}' 'params.*page|params.*limit|params.*offset'

echo -e "\n=== Searching for pagination response handling ==="
rg -g '!*.{pyc,min.js}' 'results.*pagination|next.*previous'

echo -e "\n=== Checking pagination component implementations ==="
rg -g '!*.{pyc,min.js}' -A 10 -B 10 'class.*Pagination|export.*function.*pagination'

echo -e "\n=== Checking REST framework pagination configuration ==="
rg -g '!*.{pyc,min.js}' 'DEFAULT_PAGINATION_CLASS|PAGE_SIZE|PAGINATE_BY'

Length of output: 66788

backend/ciso_assistant/settings.py (1)

130-130: LGTM! Consistent pagination configuration.

The changes maintain consistency with enterprise_core/settings.py, ensuring uniform pagination behavior across the application.

Also applies to: 217-217

backend/core/views.py (1)

3690-3690: LGTM! Improved search functionality through related fields.

The search fields now correctly reference the related requirement's name and description, enabling more intuitive search functionality.

frontend/src/lib/components/ModelTable/RemoteModelTable.svelte (2)

56-77: LGTM! Well-implemented keyboard accessibility.

The component properly handles both click and keyboard events for row interactions, following accessibility best practices.


117-117: ⚠️ Potential issue

Add error handling for data loading operations.

The data loading call lacks error handling which could lead to silent failures.

- handler.onChange((state: State) => loadTableData(state, URLModel, `/${URLModel}`));
+ handler.onChange(async (state: State) => {
+   try {
+     return await loadTableData(state, URLModel, `/${URLModel}`);
+   } catch (error) {
+     console.error('Failed to load table data:', error);
+     // Add user notification here
+     throw error;
+   }
+ });

Likely invalid or redundant comment.

Comment on lines +9 to +17
<th class="{classProp} !py-0">
<input
type="text"
class="input variant-form-material placeholder:text-xs bg-transparent p-0"
placeholder="Filter"
bind:value
on:input={() => handler.filter(value, filterBy)}
/>
</th>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance accessibility and user experience.

The input field needs accessibility improvements and performance optimization:

  1. Add proper ARIA attributes for accessibility
  2. Implement debouncing for the filter function
  3. Make the placeholder more descriptive
-<th class="{classProp} !py-0">
+<th class="!py-0">
 	<input
 		type="text"
 		class="input variant-form-material placeholder:text-xs bg-transparent p-0"
-		placeholder="Filter"
+		placeholder="Filter {filterBy}..."
+		aria-label="Filter by {filterBy}"
+		role="searchbox"
 		bind:value
-		on:input={() => handler.filter(value, filterBy)}
+		on:input={() => {
+			const debounceTimeout = setTimeout(() => {
+				handler.filter(value, filterBy);
+			}, 300);
+			return () => clearTimeout(debounceTimeout);
+		}}
 	/>
 </th>

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +16 to +30
<th
on:click={update}
class:active={$sort?.orderBy === identifier}
class={_class}
data-testid="tableheader"
>
<div class="flex items-center h-full">
<slot />
<span
class="pl-2"
class:asc={$sort?.direction === 'asc'}
class:desc={$sort?.direction === 'desc'}
/>
</div>
</th>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance accessibility for sorting functionality.

Add ARIA attributes to improve accessibility for screen readers.

 <th
 	on:click={update}
 	class:active={$sort?.orderBy === identifier}
 	class={_class}
 	data-testid="tableheader"
+	role="columnheader"
+	aria-sort={$sort?.orderBy === identifier 
+		? ($sort?.direction === 'asc' ? 'ascending' : 'descending')
+		: 'none'}
 >
 	<div class="flex items-center h-full">
 		<slot />
 		<span
 			class="pl-2"
 			class:asc={$sort?.direction === 'asc'}
 			class:desc={$sort?.direction === 'desc'}
+			aria-hidden="true"
 		/>
 	</div>
 </th>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<th
on:click={update}
class:active={$sort?.orderBy === identifier}
class={_class}
data-testid="tableheader"
>
<div class="flex items-center h-full">
<slot />
<span
class="pl-2"
class:asc={$sort?.direction === 'asc'}
class:desc={$sort?.direction === 'desc'}
/>
</div>
</th>
<th
on:click={update}
class:active={$sort?.orderBy === identifier}
class={_class}
data-testid="tableheader"
role="columnheader"
aria-sort={$sort?.orderBy === identifier
? ($sort?.direction === 'asc' ? 'ascending' : 'descending')
: 'none'}
>
<div class="flex items-center h-full">
<slot />
<span
class="pl-2"
class:asc={$sort?.direction === 'asc'}
class:desc={$sort?.direction === 'desc'}
aria-hidden="true"
/>
</div>
</th>

Comment on lines +14 to +16
// $: if ($rowCount.start >= $rowCount.total && $rowsPerPage) {
// handler.setPage(Math.ceil($rowCount.total / $rowsPerPage));
// }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Edge case for invalid page numbers is not handled elsewhere

The commented-out code was the only implementation that handled the scenario where the current page becomes invalid due to changes in the total number of rows. The current implementation only resets the page number to 1 when:

  • Changing rows per page
  • Navigation occurs

This could lead to issues where users might see empty pages if the total number of rows decreases below the current page's range.

🔗 Analysis chain

Verify that page adjustment logic is handled elsewhere.

The commented-out reactive statement appears to handle an important edge case: adjusting the current page when it becomes invalid after changing rows per page. Please verify that this logic is implemented elsewhere to prevent potential issues.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for similar page adjustment logic in other components
ast-grep --pattern 'if ($rowCount.start >= $rowCount.total) {
  $$$
}'

Length of output: 72


Script:

#!/bin/bash
# Search for any usage of rowCount.start and rowCount.total
rg "rowCount\.(start|total)" -A 2 -B 2

# Search for setPage calls with math calculations
ast-grep --pattern 'setPage(Math.$_($$$))'

# Search for any reactive statements involving rowCount
ast-grep --pattern '$: if ($rowCount.$_) {
  $$$
}'

# Look for handler.setPage calls
rg "handler\.setPage" -A 2 -B 2

Length of output: 5452

Comment on lines +269 to +299
{@const actionsURLModel = source.meta.urlmodel ?? URLModel}
<TableRowActions
{deleteForm}
{model}
URLModel={actionsURLModel}
detailURL={`/${actionsURLModel}/${row.meta[identifierField]}${detailQueryParameter}`}
editURL={!(row.meta.builtin || row.meta.urn)
? `/${actionsURLModel}/${row.meta[identifierField]}/edit?next=${encodeURIComponent($page.url.pathname + $page.url.search)}`
: undefined}
{row}
hasBody={$$slots.actionsBody}
{identifierField}
preventDelete={preventDelete(row)}
>
<svelte:fragment slot="head">
{#if $$slots.actionsHead}
<slot name="actionsHead" />
{/if}
</svelte:fragment>
<svelte:fragment slot="body">
{#if $$slots.actionsBody}
<slot name="actionsBody" />
{/if}
</svelte:fragment>
<svelte:fragment slot="tail">
<svelte:component
this={actionsComponent}
meta={row.meta ?? {}}
{actionsURLModel}
/>
</svelte:fragment>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure uniform usage for action components.
Here, you dynamically generate actionsURLModel and assign a custom component for row actions. The same logic for building URLs and “preventDelete” checks appears in multiple places.

Extract repeated logic (URL construction, preventDelete checks) into a shared helper to avoid duplication and keep the table code more maintainable.

Comment on lines +7 to +15
let timeout: any;

const search = () => {
handler.search(value);
clearTimeout(timeout);
timeout = setTimeout(() => {
handler.invalidate();
}, 400);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Prevent duplication between Search.svelte and server/Search.svelte.
This file and frontend/src/lib/components/ModelTable/Search.svelte appear nearly identical, potentially increasing maintenance overhead.

Consider consolidating them into a single component with optional SSR logic or extracting a shared logic module for both client and server usage. This helps reduce duplication and ensures consistent behavior.

Comment on lines +33 to +46
const getParams = ({ offset, rowsPerPage, search, sort, filters }: State) => {
let params = `offset=${offset}&limit=${rowsPerPage}`;
//
if (search) {
params += `&search=${search}`;
}
if (sort) {
params += `&ordering=${sort.direction === 'desc' ? '-' : ''}${sort.orderBy}`;
}
// if (filters) {
// params += filters.map(({ filterBy, value }) => `&${filterBy}=${value}`).join('');
// }
return params;
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Sanitize URL parameters and implement request optimization.

The getParams function has several areas for improvement:

  1. URL parameters are not properly encoded
  2. No debouncing for search parameter
  3. Commented-out filter implementation suggests incomplete feature

Consider this implementation:

 const getParams = ({ offset, rowsPerPage, search, sort, filters }: State) => {
-    let params = `offset=${offset}&limit=${rowsPerPage}`;
+    const params = new URLSearchParams();
+    params.set('offset', offset.toString());
+    params.set('limit', rowsPerPage.toString());
 
     if (search) {
-        params += `&search=${search}`;
+        params.set('search', search);
     }
     if (sort) {
-        params += `&ordering=${sort.direction === 'desc' ? '-' : ''}${sort.orderBy}`;
+        params.set('ordering', `${sort.direction === 'desc' ? '-' : ''}${sort.orderBy}`);
     }
-    // if (filters) {
-    // 	params += filters.map(({ filterBy, value }) => `&${filterBy}=${value}`).join('');
-    // }
-    return params;
+    return params.toString();
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getParams = ({ offset, rowsPerPage, search, sort, filters }: State) => {
let params = `offset=${offset}&limit=${rowsPerPage}`;
//
if (search) {
params += `&search=${search}`;
}
if (sort) {
params += `&ordering=${sort.direction === 'desc' ? '-' : ''}${sort.orderBy}`;
}
// if (filters) {
// params += filters.map(({ filterBy, value }) => `&${filterBy}=${value}`).join('');
// }
return params;
};
const getParams = ({ offset, rowsPerPage, search, sort, filters }: State) => {
const params = new URLSearchParams();
params.set('offset', offset.toString());
params.set('limit', rowsPerPage.toString());
if (search) {
params.set('search', search);
}
if (sort) {
params.set('ordering', `${sort.direction === 'desc' ? '-' : ''}${sort.orderBy}`);
}
return params.toString();
};

Comment on lines +8 to +31
export const loadTableData = async (state: State, URLModel: urlModel, endpoint: string) => {
const response = await fetch(`${endpoint}/?${getParams(state)}`).then((res) => res.json());
state.setTotalRows(response.count);

const bodyData = tableSourceMapper(response.results, listViewFields[URLModel as urlModel].body);

const headData: Record<string, string> = listViewFields[URLModel as urlModel].body.reduce(
(obj, key, index) => {
obj[key] = listViewFields[URLModel as urlModel].head[index];
return obj;
},
{}
);

const table: TableSource = {
head: headData,
body: bodyData,
meta: response // metaData
};

return table.body.map((item: Record<string, any>, index: number) => {
return { ...item, meta: table.meta ? { ...table.meta[index] } : undefined };
});
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling and response validation.

The loadTableData function lacks error handling for network failures and response validation. This could lead to runtime errors if the API returns unexpected data.

Consider this implementation:

 export const loadTableData = async (state: State, URLModel: urlModel, endpoint: string) => {
-    const response = await fetch(`${endpoint}/?${getParams(state)}`).then((res) => res.json());
+    try {
+        const res = await fetch(`${endpoint}/?${getParams(state)}`);
+        if (!res.ok) {
+            throw new Error(`HTTP error! status: ${res.status}`);
+        }
+        const response = await res.json();
+        if (!response || typeof response.count !== 'number' || !Array.isArray(response.results)) {
+            throw new Error('Invalid response format');
+        }
+        // ... rest of the function
+    } catch (error) {
+        console.error('Failed to load table data:', error);
+        throw error;
+    }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +17 to +19
const riskAssessments = await fetch('/risk-assessments')
.then((res) => res.json())
.then((res) => res.results);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling and use centralized endpoint configuration.

The fetch call needs improvement in several areas:

  1. Missing error handling
  2. Hard-coded endpoint path
  3. No loading state management

Consider this implementation:

+    let loading = false;
+    const RISK_ASSESSMENTS_ENDPOINT = '/risk-assessments';  // Consider moving to constants
+
     onMount(async () => {
+        loading = true;
         try {
-            const riskAssessments = await fetch('/risk-assessments')
-                .then((res) => res.json())
-                .then((res) => res.results);
+            const response = await fetch(RISK_ASSESSMENTS_ENDPOINT);
+            if (!response.ok) {
+                throw new Error(`HTTP error! status: ${response.status}`);
+            }
+            const data = await response.json();
+            const riskAssessments = data.results;
             options = getOptions({
                 objects: riskAssessments,
                 label: 'str',
                 extra_fields: [['project', 'str']]
             });
+        } catch (error) {
+            console.error('Failed to fetch risk assessments:', error);
+            // Consider adding user notification
+        } finally {
+            loading = false;
+        }
     });

Committable suggestion skipped: line range outside the PR's diff.

@@ -2,7 +2,7 @@
import { safeTranslate } from '$lib/utils/i18n';
import CreateModal from '$lib/components/Modals/CreateModal.svelte';
import MissingConstraintsModal from '$lib/components/Modals/MissingConstraintsModal.svelte';
import ModelTable from '$lib/components/ModelTable/ModelTable.svelte';
import ModelTable from '$lib/components/ModelTable/server/ModelTable.svelte';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Hold off on server-side ModelTable migration without proper coordination

This appears to be an isolated switch to the server-side component while the rest of the application (20+ files) still uses the client-side version. This inconsistency could lead to:

  • Different rendering behaviors across pages
  • Potential hydration mismatches
  • Maintenance complexity

Consider:

  • Keeping the client-side version until a coordinated migration plan is in place
  • Creating a migration plan to transition all ModelTable usage consistently
  • Documenting any behavioral differences between client and server versions
🔗 Analysis chain

Verify the impact of switching to server-side ModelTable component.

The import path change suggests a shift to server-side rendering. Ensure that:

  1. All existing table functionality works as expected
  2. Data loading patterns remain efficient
  3. Client-side interactivity is preserved where needed
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining client-side ModelTable imports that should be updated

# Search for any remaining old imports
rg "import.*ModelTable.*from.*(?<!server)/ModelTable" -g "*.svelte"

# Check for any components still using client-side specific features
rg "ModelTable.*client.*|.*client.*ModelTable" -g "*.svelte"

Length of output: 411


Script:

#!/bin/bash
# Search for all ModelTable imports
echo "=== ModelTable imports ==="
rg "import.*ModelTable.*from" --type svelte -A 1

# Search for ModelTable usage patterns
echo -e "\n=== ModelTable usage patterns ==="
rg "<ModelTable.*>" --type svelte -A 2

# Use ast-grep to find ModelTable component instances
echo -e "\n=== ModelTable component structure ==="
ast-grep --pattern '<ModelTable
  $$$
/>'

Length of output: 436


Script:

#!/bin/bash
# Find all Svelte files containing ModelTable
echo "=== Files containing ModelTable ==="
fd -e svelte -x grep -l "ModelTable"

# Search for all ModelTable imports
echo -e "\n=== ModelTable imports ==="
fd -e svelte -x grep -A 1 "import.*ModelTable.*from" {}

# Search for ModelTable usage in components
echo -e "\n=== ModelTable usage ==="
fd -e svelte -x grep -A 2 "<ModelTable" {}

Length of output: 10045

Comment on lines +65 to +69
goto(`/${URLModel}/${rowMetaData[identifierField]}${detailQueryParameter}`, {
label:
rowMetaData.str ?? rowMetaData.name ?? rowMetaData.email ?? rowMetaData[identifierField],
breadcrumbAction: 'push'
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Sanitize URL parameters before navigation.

The URL construction could be vulnerable to injection if rowMetaData contains malicious values.

+ function sanitizeUrlParam(param: string): string {
+   return encodeURIComponent(String(param).replace(/[^a-zA-Z0-9-_]/g, ''));
+ }

  goto(`/${URLModel}/${rowMetaData[identifierField]}${detailQueryParameter}`, {
    label:
-     rowMetaData.str ?? rowMetaData.name ?? rowMetaData.email ?? rowMetaData[identifierField],
+     sanitizeUrlParam(rowMetaData.str ?? rowMetaData.name ?? rowMetaData.email ?? rowMetaData[identifierField]),
    breadcrumbAction: 'push'
  });

Committable suggestion skipped: line range outside the PR's diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants