Skip to content

Refactor data store workflow#352

Merged
brucetony merged 17 commits intodevelopfrom
refactor-data-store
Apr 7, 2026
Merged

Refactor data store workflow#352
brucetony merged 17 commits intodevelopfrom
refactor-data-store

Conversation

@brucetony
Copy link
Copy Markdown
Collaborator

@brucetony brucetony commented Apr 7, 2026

Update the data store components to use computed() where needed, refactor certain services as composables, and remove orphaned components

Summary by CodeRabbit

  • Refactor

    • Data store listing and creation now use a centralized data flow; project selection is populated from the API and selection behavior is more reliable.
    • Help content reorganized into reusable help panels with a consistent header and close control for clearer guidance.
  • New Features

    • Added a “Data Stores” option to the Cleanup dialog; cleanup feedback now reports the number of resources removed.
  • Tests

    • Added tests for the data-store composable and updated component tests to exercise new async/project behaviors.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 7, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Centralized DataStore listing into a new composable, moved project fetching into the initializer, extracted a help-panel component, added datastore cleanup API and UI handling, extended types/utilities, updated tests/fixtures, and added docs/ to .gitignore.

Changes

Cohort / File(s) Summary
Data Store List & Composable
app/components/data-stores/DataStoreList.vue, app/composables/useDataStoreList.ts
Moved data fetching/formatting/state from the component into useDataStoreList; DataStoreList now awaits and consumes { dataStores, projectNameMap, loading, refresh }.
Help Panel Components
app/components/data-stores/create/DataStoreHelpBox.vue, app/components/data-stores/create/DataStoreHelpPanel.vue
Added DataStoreHelpPanel.vue and refactored DataStoreHelpBox to delegate rendering and emit closeHelp.
Project Initialization & Page
app/components/data-stores/create/DataStoreProjectInitializer.vue, app/components/data-stores/create/ResourceManagerTabs.vue, app/pages/data-stores/create.vue, app/components/data-stores/create/index.ts
Removed ResourceManagerTabs; DataStoreProjectInitializer now fetches projects internally and tightened typing; added exported AvailableProject interface.
API & Cleanup Dialog
app/services/Api.ts, app/components/header/CleanupDialog.vue
Added DeleteService type and DELETE /kong/datastore API method; CleanupDialog supports datastore cleanup and adjusts toast messaging; changed HttpClient.baseUrl default.
Types / Utilities
app/utils/format-data-row.ts
Extended formatDataRow input types to include DetailedService and adjusted related typings.
Tests & Fixtures
test/components/data-stores/create/DataStoreProjectInitializer.spec.ts, test/components/analysis/AnalysisControlButtons.spec.ts, test/components/data-stores/constants.ts, test/composables/useDataStoreList.test.ts
Updated tests to wrap initializer in Suspense, mock getProjectNodes, change fixtures (remove dropdown, use null), add tests for useDataStoreList helpers, and add required analysisDistributionStatus prop in mounts.
Repository Config
.gitignore
Added docs/ to ignored paths.

Sequence Diagram(s)

sequenceDiagram
  participant UI as "DataStoreList UI"
  participant Composable as "useDataStoreList"
  participant API as "Api / Services"

  UI->>Composable: await useDataStoreList()
  Composable->>API: GET /services (getDataStores lazy)
  Composable->>API: GET /projects (getProjects lazy)
  API-->>Composable: services payload
  API-->>Composable: projects payload
  Composable->>Composable: format rows, filter kong-admin-service, enrich routes (projectId)
  Composable-->>UI: { dataStores, projectNameMap, loading=false, refresh }
  UI->>Composable: refresh() (on user action)
  Composable->>API: repeat fetch/update
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A nibble of code, new composables in sight,
Projects fetched tidy, panels shining light.
Tests hopped in order, APIs trimmed and neat,
Docs tucked away, the repo feels complete. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Refactor data store workflow' accurately summarizes the main change: refactoring data store-related components and services into a cleaner composable-based architecture.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor-data-store

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@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: 9

🧹 Nitpick comments (2)
app/components/data-stores/create/DataStoreHelpBox.vue (1)

5-5: Narrow helpField prop type to HelpTextField | undefined for type safety.

Line 5 currently accepts any string, which weakens compile-time guarantees for the HelpTextField-based branches. Prefer the enum type directly.

Suggested fix
-const props = defineProps<{ helpField: string | undefined }>();
+const props = defineProps<{ helpField: HelpTextField | undefined }>();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/data-stores/create/DataStoreHelpBox.vue` at line 5, Change the
helpField prop type from a generic string to the specific enum type
HelpTextField by modifying the defineProps call (replace `{ helpField: string |
undefined }` with `{ helpField: HelpTextField | undefined }`) and ensure the
HelpTextField enum is imported or referenced in this file so the compiler
recognizes it; update any usages relying on string behavior to use the enum
values if necessary.
app/components/data-stores/create/DataStoreProjectInitializer.vue (1)

138-142: Non-null assertion relies on implicit validation.

The ! assertion on selectedProject.value!.id depends on prior validation failing if no project is selected. Consider adding explicit validation for selectedProject for clarity and safety against future refactoring.

♻️ Proposed fix to add explicit validation
   const configSettings: BodyKongInitializeKongInitializePost = {
     datastore: datastoreSettings,
-    project_id: selectedProject.value!.id,
+    project_id: selectedProject.value?.id ?? "",
     ds_type: selectedDataStoreType.value,
   };

Or add explicit validation before constructing configSettings:

if (!selectedProject.value) {
  toast.add({
    severity: "error",
    summary: "Value missing",
    detail: "Project is not selected!",
    life: 5000,
  });
  return;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/data-stores/create/DataStoreProjectInitializer.vue` around
lines 138 - 142, The code currently uses a non-null assertion
selectedProject.value!.id when building configSettings
(BodyKongInitializeKongInitializePost); add an explicit check for
selectedProject.value before constructing configSettings (e.g., if
(!selectedProject.value) { show an error toast and return }) so you avoid
relying on implicit validation and prevent runtime crashes—place this check just
before the configSettings assignment (the block that references
datastoreSettings, project_id, and ds_type).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/components/data-stores/create/DataStoreHelpPanel.vue`:
- Around line 13-18: The icon-only close Button in DataStoreHelpPanel.vue lacks
an accessible name; add an explicit accessible label by updating the Button (the
one with class "help-box-close-btn" and `@click`="emit('closeHelp')") to include
aria-label="Close help box" (or aria-labelledby pointing to a visible label) so
screen readers get a clear name instead of relying solely on v-tooltip.top; keep
the tooltip but ensure aria-label matches its text.

In `@app/components/data-stores/create/DataStoreProjectInitializer.vue`:
- Around line 33-43: The code calling getProjectNodes() lacks handling for
non-success or thrown errors, leaving availableProjects empty and the user
uninformed; wrap the call in try/catch and handle the projStatus !== "success"
case by setting an error state (e.g., projectLoadError or similar reactive ref)
and logging the error, and populate that error/ref so the template (near the
project dropdown) can show a user-facing message; update places referencing
projects, projStatus, and availableProjects to use this error state when the
fetch fails.

In `@app/components/data-stores/DataStoreList.vue`:
- Around line 8-9: You are optimistically removing an item by filtering
dataStores.value then immediately calling refresh(), which repopulates
dataStores from the server and can reintroduce the just-deleted store; instead,
either delay calling refresh() until the delete API confirms success (call
refresh() in the delete promise's success handler) or maintain a
pending-deletions set (e.g., track dsName in a pendingDeletes collection used to
filter incoming data in useDataStoreList) so that refresh() respects optimistic
removals until the server acknowledges deletion; update the code paths around
dataStores.value, refresh(), useDataStoreList(), and the delete handler to
implement one of these approaches.

In `@app/composables/useDataStoreList.ts`:
- Around line 19-20: extractProjectIdFromPath dereferences paths[0]! without
guarding against undefined/empty arrays which can throw at runtime; update the
function to safely handle missing or empty paths by using checks or optional
chaining (e.g., verify paths && paths.length>0 or use paths?.[0]) and return
undefined if the first path or the second segment is absent, instead of using
the non-null assertion; keep the function name extractProjectIdFromPath and
ensure it returns the segment at index 1 only when present.
- Around line 43-47: The code is unsafely asserting formatDataRow(...) as
ModifiedDetailedService[] before timestamps are normalized; fix by removing the
direct cast and explicitly normalize/add created_at and updated_at on the
formatted rows (use formatDataRow and then map over its result) so each item has
both timestamp objects (converted to the modifiedTimestamp shape used by
ModifiedDetailedService) before casting or returning them; reference
formatDataRow, ModifiedDetailedService, and the optional
DetailedService.created_at/updated_at fields (format-data-row.ts currently only
rewrites keys that exist) and ensure you fill or transform missing timestamp
fields in the mapping step so no .created_at.short or .updated_at.short access
can fail at runtime.
- Around line 53-60: Remove the unsafe cast "as Route[]" when assigning the
result of formatDataRow to store.routes; formatDataRow mutates timestamp fields
and adds an expand property so you must either update the type of store.routes
to a proper transformed Route shape (create a TransformedRoute/ExpandedRoute
interface including created_at/updated_at objects and expand) or assign to a
correctly typed temporary variable (e.g. const transformed = formatDataRow(...))
and then set store.routes = transformed; also update the subsequent forEach to
use the transformed type and keep the call to extractProjectIdFromPath as-is to
populate projectId.

In `@app/utils/format-data-row.ts`:
- Around line 17-20: The exported formatDataRow signature accepts rowEntries:
ProjectNode[] | DetailedService[] but the .map() callback and
parseUnixTimestamp() types exclude DetailedService; create a single union type
(e.g., FormattableRow = ProjectNode | DetailedService) and replace the separate
unions in the formatDataRow parameter, the .map() callback's input type, and the
parseUnixTimestamp() parameter type with this FormattableRow to ensure
consistency and remove the need for downstream type assertions; update any
references to ProjectNode/DetailedService in those function signatures
(formatDataRow, parseUnixTimestamp, and the anonymous map callback) to use
FormattableRow.

In `@test/components/data-stores/create/DataStoreProjectInitializer.spec.ts`:
- Around line 1-3: The test imports defineComponent from Vue but uses ref() in
the mock, causing a runtime error; update the import statement in
DataStoreProjectInitializer.spec.ts to also import ref from "vue" (add ref
alongside defineComponent) so the mock that calls ref() resolves correctly and
the test can execute.

In `@test/composables/useDataStoreList.test.ts`:
- Line 13: Tests are using "as any" to bypass types when calling
buildProjectNameMap; remove those casts and either (A) supply properly typed
Project fixtures in the test (create objects matching the Project interface,
using explicit name: undefined or id: undefined where needed) for the calls in
the useDataStoreList.test.ts test, or (B) if the function truly accepts looser
shapes, narrow the function signature of buildProjectNameMap to accept Array<{
id?: any; name?: any }> so the existing test data is valid; update the test
cases referencing buildProjectNameMap to use the chosen approach and remove all
"as any" casts.

---

Nitpick comments:
In `@app/components/data-stores/create/DataStoreHelpBox.vue`:
- Line 5: Change the helpField prop type from a generic string to the specific
enum type HelpTextField by modifying the defineProps call (replace `{ helpField:
string | undefined }` with `{ helpField: HelpTextField | undefined }`) and
ensure the HelpTextField enum is imported or referenced in this file so the
compiler recognizes it; update any usages relying on string behavior to use the
enum values if necessary.

In `@app/components/data-stores/create/DataStoreProjectInitializer.vue`:
- Around line 138-142: The code currently uses a non-null assertion
selectedProject.value!.id when building configSettings
(BodyKongInitializeKongInitializePost); add an explicit check for
selectedProject.value before constructing configSettings (e.g., if
(!selectedProject.value) { show an error toast and return }) so you avoid
relying on implicit validation and prevent runtime crashes—place this check just
before the configSettings assignment (the block that references
datastoreSettings, project_id, and ds_type).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 199658db-125f-4d70-b457-6d7fd46ccce1

📥 Commits

Reviewing files that changed from the base of the PR and between a91115e and e7c5472.

📒 Files selected for processing (14)
  • .gitignore
  • app/components/data-stores/DataStoreList.vue
  • app/components/data-stores/create/DataStoreHelpBox.vue
  • app/components/data-stores/create/DataStoreHelpPanel.vue
  • app/components/data-stores/create/DataStoreProjectInitializer.vue
  • app/components/data-stores/create/ResourceManagerTabs.vue
  • app/components/data-stores/create/index.ts
  • app/composables/useDataStoreList.ts
  • app/pages/data-stores/create.vue
  • app/utils/format-data-row.ts
  • test/components/analysis/AnalysisControlButtons.spec.ts
  • test/components/data-stores/constants.ts
  • test/components/data-stores/create/DataStoreProjectInitializer.spec.ts
  • test/composables/useDataStoreList.test.ts
💤 Files with no reviewable changes (2)
  • test/components/data-stores/constants.ts
  • app/components/data-stores/create/ResourceManagerTabs.vue

Comment on lines +8 to +9
dataStores.value = dataStores.value.filter((store) => store.name !== dsName);
refresh();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid overwriting the optimistic delete with an immediate refresh.

refresh() causes useDataStoreList() to repopulate dataStores.value from the server. If the deletion has not propagated yet, the just-filtered row gets written back and the table shows the deleted store again. Refresh only after the delete is confirmed, or keep a pending-deletions filter when applying fresh data.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/data-stores/DataStoreList.vue` around lines 8 - 9, You are
optimistically removing an item by filtering dataStores.value then immediately
calling refresh(), which repopulates dataStores from the server and can
reintroduce the just-deleted store; instead, either delay calling refresh()
until the delete API confirms success (call refresh() in the delete promise's
success handler) or maintain a pending-deletions set (e.g., track dsName in a
pendingDeletes collection used to filter incoming data in useDataStoreList) so
that refresh() respects optimistic removals until the server acknowledges
deletion; update the code paths around dataStores.value, refresh(),
useDataStoreList(), and the delete handler to implement one of these approaches.

{ id: "abc-123", name: "My Project" },
{ id: "def-456", name: "Other Project" },
];
const map = buildProjectNameMap(projects as any);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

fd -t f useDataStoreList

Repository: PrivateAIM/node-ui

Length of output: 139


🏁 Script executed:

cat -n test/composables/useDataStoreList.test.ts

Repository: PrivateAIM/node-ui

Length of output: 1818


🏁 Script executed:

cat -n app/composables/useDataStoreList.ts

Repository: PrivateAIM/node-ui

Length of output: 2935


🏁 Script executed:

cat -n app/composables/useDataStoreList.ts | head -20

Repository: PrivateAIM/node-ui

Length of output: 936


🏁 Script executed:

fd -t f Api.ts

Repository: PrivateAIM/node-ui

Length of output: 81


🏁 Script executed:

rg "export (type|interface) (Project|DetailedAnalysis)" app/services/Api.ts -A 10

Repository: PrivateAIM/node-ui

Length of output: 650


🏁 Script executed:

rg "export interface Project\b" app/services/Api.ts -A 20

Repository: PrivateAIM/node-ui

Length of output: 438


🏁 Script executed:

rg "no-explicit-any" . -t ts --type-add 'ts:*.ts' | head -20

Repository: PrivateAIM/node-ui

Length of output: 142


🏁 Script executed:

fd -t f "eslint" -E node_modules

Repository: PrivateAIM/node-ui

Length of output: 78


🏁 Script executed:

cat eslint.config.js

Repository: PrivateAIM/node-ui

Length of output: 892


Remove the as any casts from test fixtures.

The test fixtures at these lines don't match the declared Project[] | DetailedAnalysis[] parameter type and use as any to bypass TypeScript checking. Replace with properly typed test data that matches the actual function contract:

  • Line 13: valid Project shape
  • Line 20: Project with undefined name (use name: undefined without casting, or adjust type)
  • Line 26: Project with undefined id (use id: undefined without casting, or adjust type)

Alternatively, narrow the function's input type to Array<{ id?: any; name?: any }> to match what it actually reads.

Also applies to: 20-20, 26-26

🧰 Tools
🪛 ESLint

[error] 13-13: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/composables/useDataStoreList.test.ts` at line 13, Tests are using "as
any" to bypass types when calling buildProjectNameMap; remove those casts and
either (A) supply properly typed Project fixtures in the test (create objects
matching the Project interface, using explicit name: undefined or id: undefined
where needed) for the calls in the useDataStoreList.test.ts test, or (B) if the
function truly accepts looser shapes, narrow the function signature of
buildProjectNameMap to accept Array<{ id?: any; name?: any }> so the existing
test data is valid; update the test cases referencing buildProjectNameMap to use
the chosen approach and remove all "as any" casts.

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (2)
app/components/header/CleanupDialog.vue (2)

130-134: Edge case: count of 0 won't be displayed.

If the API returns count: 0 or zombies: "0", the condition if (modifiedResources) evaluates to falsy, so the message won't include the count. If displaying "0 modified resources" is meaningful feedback, consider using an explicit check.

♻️ Optional: Show count even when zero
   if (cleanupResponse) {
     let detail = "Cleanup request successfully submitted";
-    if (modifiedResources) {
+    if (modifiedResources !== 0 && modifiedResources !== "") {
       detail += ", number of modified resources: " + modifiedResources;
     }

Or to always show it:

-    if (modifiedResources) {
-      detail += ", number of modified resources: " + modifiedResources;
-    }
+    detail += ", number of modified resources: " + modifiedResources;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/header/CleanupDialog.vue` around lines 130 - 134, The
message-building logic around cleanupResponse uses a truthy check on
modifiedResources which hides legitimate zero counts; update the condition in
the CleanupDialog.vue block that appends to the detail string (the variables
detail, modifiedResources and cleanupResponse) to explicitly check for
null/undefined (e.g., modifiedResources != null or typeof modifiedResources !==
"undefined") so that a value of 0 or "0" is still included in the message;
ensure the same explicit check is used for any similar zombies/count fields so
zero is displayed.

94-128: Error handling pattern could swallow the actual error details.

The .catch() handlers show a generic error toast but discard the actual error. Consider logging the error or displaying more specific error information to aid debugging.

Additionally, the type assertion after .catch() is technically incorrect since .catch() returns void (making the expression potentially undefined), though it's safely guarded by the if (cleanupResponse) check at line 130.

♻️ Optional: Capture and log error details
   if (endpoint === "datastore") {
     cleanupResponse = (await useNuxtApp()
       .$hubApi(`/kong/${endpoint}`, {
         method: "DELETE",
       })
-      .catch(() => {
+      .catch((error) => {
+        console.error("Datastore cleanup failed:", error);
         toast.add({
           severity: "error",
           summary: "Unable to perform cleanup",
           detail:
             "There was an error while deleting or restarting the resource.",
           life: 5000,
         });
       })) as DeleteService;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/header/CleanupDialog.vue` around lines 94 - 128, The current
.catch() swallows the error and the inline type assertions after the chained
.catch() are unsafe; wrap the await useNuxtApp().$hubApi(...) calls in a proper
try/catch instead of chaining .catch(), e.g., in CleanupDialog.vue call the API
inside a try block and assign the result to a typed variable (cleanupResponse
typed as DeleteService | CleanupPodResponse | null), and in the catch(err) log
the full error (console.error or processLogger) and include meaningful info in
the toast (err.message or a parsed error detail) so you don't lose diagnostics;
update both branches that call useNuxtApp().$hubApi and remove the post-.catch()
type assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/components/header/CleanupDialog.vue`:
- Around line 130-134: The message-building logic around cleanupResponse uses a
truthy check on modifiedResources which hides legitimate zero counts; update the
condition in the CleanupDialog.vue block that appends to the detail string (the
variables detail, modifiedResources and cleanupResponse) to explicitly check for
null/undefined (e.g., modifiedResources != null or typeof modifiedResources !==
"undefined") so that a value of 0 or "0" is still included in the message;
ensure the same explicit check is used for any similar zombies/count fields so
zero is displayed.
- Around line 94-128: The current .catch() swallows the error and the inline
type assertions after the chained .catch() are unsafe; wrap the await
useNuxtApp().$hubApi(...) calls in a proper try/catch instead of chaining
.catch(), e.g., in CleanupDialog.vue call the API inside a try block and assign
the result to a typed variable (cleanupResponse typed as DeleteService |
CleanupPodResponse | null), and in the catch(err) log the full error
(console.error or processLogger) and include meaningful info in the toast
(err.message or a parsed error detail) so you don't lose diagnostics; update
both branches that call useNuxtApp().$hubApi and remove the post-.catch() type
assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d132a740-9559-45d0-8bed-03332b8595ae

📥 Commits

Reviewing files that changed from the base of the PR and between e7c5472 and df6089f.

📒 Files selected for processing (3)
  • app/components/header/CleanupDialog.vue
  • app/services/Api.ts
  • app/services/hub_adapter_swagger.json

@brucetony brucetony merged commit 668edce into develop Apr 7, 2026
4 of 5 checks passed
@brucetony brucetony deleted the refactor-data-store branch April 8, 2026 06:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant