Skip to content

feat(frontend): create RBAC API keys from apikeys page#2332

Open
Dalanir wants to merge 5 commits into
mainfrom
feat/rbac-api-keys-page
Open

feat(frontend): create RBAC API keys from apikeys page#2332
Dalanir wants to merge 5 commits into
mainfrom
feat/rbac-api-keys-page

Conversation

@Dalanir
Copy link
Copy Markdown
Contributor

@Dalanir Dalanir commented May 22, 2026

Summary (AI generated)

  • Reworked the sidebar /apikeys page to create RBAC API keys instead of legacy mode keys.
  • Added RBAC role display for API keys, including per-organization role details via tooltip.
  • Updated the creation modal to select multiple organizations by default and configure app-level RBAC bindings across those organizations.

Motivation (AI generated)

Martin is migrating legacy API keys to RBAC API keys, so the global API keys page needs to support the RBAC creation model instead of the old read/upload/write/all mode flow.

Business Impact (AI generated)

This keeps API key management aligned with the RBAC migration and lets users create organization-scoped and app-scoped keys from the central API keys page without falling back to legacy permissions.

Test Plan (AI generated)

  • Ran
  • Ran
  • Verify /apikeys lists RBAC roles for migrated keys
  • Verify creating a key from /apikeys selects all orgs by default with org_member
  • Verify app-level roles can be configured across selected orgs

Generated with AI

Summary by CodeRabbit

  • New Features

    • API keys now support role-based access control with per-organization and optional per-application bindings.
  • User Interface Changes

    • API key list shows assigned role and aggregates org/app scope from role bindings.
    • Key creation flow now collects org-level role and optional per-app role bindings; dialogs support larger sizes.
  • Localization

    • Added messages for permission denial, "Role per organization", and multi-organization selection help.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

📝 Walkthrough

Walkthrough

Makes API key listing and creation RBAC-aware: new role/binding types and fetchers, RBAC-driven add-key modal (org role + optional per-app bindings), table role column and scope display from bindings, adds three i18n keys, and broadens DialogV2 size options.

Changes

RBAC-Aware API Key Management

Layer / File(s) Summary
Type imports and translations
src/pages/ApiKeys.vue, messages/en.json
Adds Role/RoleBindingRow typing and three English messages (cannot-manage-org-api-keys, role-per-org, select-multiple-organizations-help).
Reactive RBAC state & computed helpers
src/pages/ApiKeys.vue
Introduces roles, allBindings, selected orgs/org-role, pending per-app bindings, name caches, and computed helpers for role display and per-key/org tooltip data.
Data loading: roles, bindings, and name caching
src/pages/ApiKeys.vue
Adds fetchRoles and fetchAllBindings, runs them during key load, and refactors org/app name resolution for public and UUID app IDs with caching and error handling.
Add-key RBAC selection model & modal UI
src/pages/ApiKeys.vue
Replaces legacy key-type/modal flow with an RBAC selection modal: manageable org checkboxes, org-role radios, conditional app access controls, per-app role selects, and pruning logic.
Create/regenerate flows and backend payload
src/pages/ApiKeys.vue
Validates RBAC selections, builds bindings (org-level + per-app) with owner-org context, calls backend apikey with bindings, limited_to_orgs, limited_to_apps, preserves hashed-key semantics, and refreshes bindings/names.
DataTable role column & scope rendering
src/pages/ApiKeys.vue
Adds a role column showing highest-priority org role with conditional tooltip; scope cells use cached org/app names and RBAC-derived app IDs.
Selection helpers, watchers, copy/regenerate flows
src/pages/ApiKeys.vue
Adds helpers for org/app selection, per-app role handlers, watchers to prune/clear pending bindings, simplifies modal submit to createApiKey(), and aligns copy/regenerate UX for hashed keys.
Dialog size variants
src/components/DialogV2.vue, src/stores/dialogv2.ts
Extends DialogV2 sizeClasses and DialogV2Options to support 2xl and 3xl.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • riderx
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is missing critical sections from the template: test plan steps are incomplete with unchecked items, screenshots section is omitted, and most checklist items are unchecked without explanation. Complete the test plan with specific steps, add screenshots showing the new RBAC role display and creation modal, and verify code style/linting and test coverage requirements.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding RBAC API key creation support to the apikeys page, which is the core objective of this PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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


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

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 22, 2026

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing feat/rbac-api-keys-page (2ff7bdf) with main (2716be4)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Copy link
Copy Markdown
Contributor

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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pages/ApiKeys.vue`:
- Around line 349-363: The title attribute is built from unescaped org names in
displayFunction (uses getHighestOrgRole, getRolesByOrg, getRoleDisplayName and
tooltipText) which allows injection if a name contains quotes; fix by escaping
attribute values or, better, by creating the span via DOM APIs and assigning the
tooltip via element.title (or setAttribute('title', ...)) and using textContent
for the visible label so values are not interpolated into HTML strings—ensure
tooltipText is properly escaped when you must build HTML strings.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b09456b0-f518-4ee6-bf4c-5198995f5b11

📥 Commits

Reviewing files that changed from the base of the PR and between 2716be4 and 7b2e3f8.

📒 Files selected for processing (2)
  • messages/en.json
  • src/pages/ApiKeys.vue

Comment thread src/pages/ApiKeys.vue
@Dalanir Dalanir deployed to deepsec-pr May 22, 2026 17:47 — with GitHub Actions Active
Copy link
Copy Markdown
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@messages/en.json`:
- Line 1740: The i18n key "select-multiple-organizations-help" is unused and
misleading for the checkbox-based org selector; either remove this key from
messages/en.json, or update and wire it into the UI: modify ApiKeys.vue to
display a localized help string (use $t("select-multiple-organizations-help") or
equivalent) near the checkbox list and update the message text to describe
checkbox toggling (e.g., "Click checkboxes to select organizations") and ensure
toggleOrgSelection(...) remains the change handler for each checkbox; choose one
approach and remove the dead key if you opt to wire nothing.

In `@src/pages/ApiKeys.vue`:
- Around line 1084-1095: The dropdown trigger button lacks an aria-expanded
attribute; update the button element that toggles showOrgDropdown (the one using
`@click`="showOrgDropdown = !showOrgDropdown" and displaying
selectedOrgNamesForCreation/selectedOrgsForCreation) to include a bound
aria-expanded reflecting the open state (e.g., :aria-expanded="showOrgDropdown")
so assistive tech can read the dropdown state.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f5ccc0ee-2cfa-41d1-962e-3679be94bc6b

📥 Commits

Reviewing files that changed from the base of the PR and between f50a2f7 and 1e1e8df.

📒 Files selected for processing (4)
  • messages/en.json
  • src/components/DialogV2.vue
  • src/pages/ApiKeys.vue
  • src/stores/dialogv2.ts

Comment thread messages/en.json
"select-expiration-date": "Select expiration date",
"select-group": "Select a group",
"select-member": "Select member",
"select-multiple-organizations-help": "Use Cmd/Ctrl or Shift to select multiple organizations.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if this i18n key is actually used anywhere
rg -n "select-multiple-organizations-help" --type vue --type ts --type js -g '!messages/*.json'

Repository: Cap-go/capgo

Length of output: 84


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== 1) Find i18n key usage in code (exclude messages json) =="
rg -nF "select-multiple-organizations-help" -g'!messages/**/*.json' .

echo "== 2) Locate ApiKeys.vue =="
fd -a "ApiKeys.vue" . || true

echo "== 3) Search ApiKeys.vue for org selector/help i18n =="
# If ApiKeys.vue exists, search it for nearby keys/strings
api_file="$(fd -a "ApiKeys.vue" . | head -n 1 || true)"
if [ -n "${api_file:-}" ]; then
  echo "-- using $api_file"
  rg -n "organizations|organization|select-multiple|multiple-organizations|help" "$api_file" || true
else
  echo "ApiKeys.vue not found"
fi

Repository: Cap-go/capgo

Length of output: 117


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== 1) Find i18n key usage in code (exclude messages json) =="
rg -nF "select-multiple-organizations-help" -g'!messages/**/*.json' .

echo "== 2) Locate ApiKeys.vue =="
fd -a "ApiKeys.vue" . || true

echo "== 3) Search ApiKeys.vue for org selector/help i18n =="
# If ApiKeys.vue exists, search it for nearby keys/strings
api_file="$(fd -a "ApiKeys.vue" . | head -n 1 || true)"
if [ -n "${api_file:-}" ]; then
  echo "-- using $api_file"
  rg -n "organizations|organization|select-multiple|multiple-organizations|help" "$api_file" || true
else
  echo "ApiKeys.vue not found"
fi

Repository: Cap-go/capgo

Length of output: 117


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== 1) Find i18n key usage in code (exclude messages json) =="
rg -nF "select-multiple-organizations-help" -g'!messages/**/*.json' . || true

echo "== 2) Locate ApiKeys.vue =="
fd -a "ApiKeys.vue" . || true

echo "== 3) Inspect organization selector/help in ApiKeys.vue =="
api_file="$(fd -a "ApiKeys.vue" . | head -n 1 || true)"
if [ -n "${api_file:-}" ]; then
  echo "-- using $api_file"
  sed -n '1,200p' "$api_file" | cat -n
  echo "--- grep nearby i18n/help text ---"
  rg -n "select-multiple-organizations-help|organizations|organization|multiple-organizations|help" "$api_file" || true
else
  echo "ApiKeys.vue not found"
fi

Repository: Cap-go/capgo

Length of output: 9702


🏁 Script executed:

#!/bin/bash
set -euo pipefail

files="$(fd -a "ApiKeys.vue" src/pages | tr '\n' ' ')"
echo "ApiKeys.vue files: $files"

echo "== 1) Directly search for the i18n key in those files =="
for f in $(echo "$files"); do
  echo "-- $f"
  rg -nF "select-multiple-organizations-help" "$f" || true
done

echo "== 2) Search for keyboard modifier strings in those files =="
for f in $(echo "$files"); do
  echo "-- $f"
  rg -n "Cmd|Ctrl|Shift" "$f" || true
done

echo "== 3) Search for checkbox selection UI in those files =="
for f in $(echo "$files"); do
  echo "-- $f"
  rg -n "checkbox|FormKit.*checkbox|type=\"checkbox\"|v-model=.*selectedOrgs|selectedOrgsForCreation|multiple" "$f" || true
done

echo "== 4) Dump the template section around org selection in src/pages/ApiKeys.vue =="
main_file="src/pages/ApiKeys.vue"
if [ -f "$main_file" ]; then
  # Find first occurrence of selectedOrgsForCreation in template and print a window
  line="$(rg -n "selectedOrgsForCreation" "$main_file" | head -n 1 | cut -d: -f1 || true)"
  echo "selectedOrgsForCreation first line: $line"
  if [ -n "${line:-}" ]; then
    start=$((line-80))
    end=$((line+160))
    sed -n "${start},${end}p" "$main_file" | cat -n
  fi
fi

echo "== 5) Dump the template section around org selection in src/pages/settings/organization/ApiKeys.vue =="
settings_file="src/pages/settings/organization/ApiKeys.vue"
if [ -f "$settings_file" ]; then
  line="$(rg -n "selectedOrgsForCreation|organizationStore\.organizations|select-organization|organizations" "$settings_file" | head -n 1 | cut -d: -f1 || true)"
  echo "org-related first line: $line"
  if [ -n "${line:-}" ]; then
    start=$((line-80))
    end=$((line+220))
    sed -n "${start},${end}p" "$settings_file" | cat -n
  fi
fi

Repository: Cap-go/capgo

Length of output: 4202


🏁 Script executed:

#!/bin/bash
set -euo pipefail

KEY="select-multiple-organizations-help"

echo "== 1) Non-messages usage of i18n key ($KEY) =="
if rg -nF "$KEY" -g'!messages/**/*.json' . ; then
  echo "matches found"
else
  echo "NO matches found (outside messages/**/*.json)"
fi

echo
echo "== 2) Where Cmd/Ctrl/Shift appears in i18n strings =="
rg -n "Cmd|Ctrl|Shift" messages/en.json || true

echo
echo "== 3) Find any *help* i18n key usage in src/pages/ApiKeys.vue =="
rg -n "t\\(.*help|help\\b|tooltip" src/pages/ApiKeys.vue || true

echo
echo "== 4) Inspect checkbox-based org selector block in src/pages/ApiKeys.vue =="
# Print a safe window around the first checkbox for org selection
line="$(rg -n 'type="checkbox"' src/pages/ApiKeys.vue | head -n 1 | cut -d: -f1 || true)"
echo "first checkbox line: ${line:-none}"
if [ -n "${line:-}" ]; then
  start=$(( line-60 )); [ "$start" -lt 1 ] && start=1
  end=$(( line+140 ))
  sed -n "${start},${end}p" src/pages/ApiKeys.vue | cat -n
fi

echo
echo "== 5) Check whether the template references select-multiple* i18n keys =="
rg -n "select-multiple" src/pages/ApiKeys.vue || true

Repository: Cap-go/capgo

Length of output: 12547


select-multiple-organizations-help appears unused and the text doesn’t match the org selector UI.

  • The i18n key select-multiple-organizations-help only exists in messages/en.json (no references in Vue/TS/JS).
  • The organization selector in src/pages/ApiKeys.vue is checkbox-based (type="checkbox" with @change="toggleOrgSelection(...)") and doesn’t implement any Cmd/Ctrl+click or Shift multi-select behavior.

Remove the unused string, or wire it to the selector and update it to describe the checkbox interaction.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@messages/en.json` at line 1740, The i18n key
"select-multiple-organizations-help" is unused and misleading for the
checkbox-based org selector; either remove this key from messages/en.json, or
update and wire it into the UI: modify ApiKeys.vue to display a localized help
string (use $t("select-multiple-organizations-help") or equivalent) near the
checkbox list and update the message text to describe checkbox toggling (e.g.,
"Click checkboxes to select organizations") and ensure toggleOrgSelection(...)
remains the change handler for each checkbox; choose one approach and remove the
dead key if you opt to wire nothing.

Comment thread src/pages/ApiKeys.vue
Comment on lines +1084 to +1095
<button
type="button"
class="flex items-center justify-between w-full gap-3 px-3 py-2 text-sm text-left bg-white border rounded-lg border-slate-300 dark:bg-gray-800 dark:border-slate-600 focus:ring-2 focus:ring-primary-500 focus:outline-none"
@click="showOrgDropdown = !showOrgDropdown"
>
<span class="flex-1 truncate" :class="selectedOrgsForCreation.length ? 'text-slate-800 dark:text-white' : 'text-slate-500'">
{{ selectedOrgNamesForCreation || t('select-organization') }}
</span>
<svg class="w-4 h-4 text-slate-500 shrink-0" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>
</button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add aria-expanded to the dropdown trigger button.

The button toggles a dropdown but lacks an aria-expanded attribute to communicate state to assistive technologies.

♿ Proposed accessibility fix
 <button
   type="button"
   class="flex items-center justify-between w-full gap-3 px-3 py-2 text-sm text-left bg-white border rounded-lg border-slate-300 dark:bg-gray-800 dark:border-slate-600 focus:ring-2 focus:ring-primary-500 focus:outline-none"
+  :aria-expanded="showOrgDropdown"
+  aria-haspopup="listbox"
   `@click`="showOrgDropdown = !showOrgDropdown"
 >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/ApiKeys.vue` around lines 1084 - 1095, The dropdown trigger button
lacks an aria-expanded attribute; update the button element that toggles
showOrgDropdown (the one using `@click`="showOrgDropdown = !showOrgDropdown" and
displaying selectedOrgNamesForCreation/selectedOrgsForCreation) to include a
bound aria-expanded reflecting the open state (e.g.,
:aria-expanded="showOrgDropdown") so assistive tech can read the dropdown state.

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
7.5% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Copy link
Copy Markdown
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pages/ApiKeys.vue (1)

124-140: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the table role summary org-scoped.

This helper now ranks every binding, so an app-level role can replace the org role shown in the new role column. A key with org_member plus one high-rank app binding will render the app role here, which is misleading at table level.

Suggested fix
 function getHighestRole(key: Database['public']['Tables']['apikeys']['Row']): string | null {
-  const keyBindings = getBindingsForKey(key)
+  const keyBindings = getBindingsForKey(key)
+    .filter(binding => binding.scope_type === 'org')
   if (keyBindings.length === 0)
     return null
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/ApiKeys.vue` around lines 124 - 140, The current getHighestRole
function considers all bindings (including app-level) when selecting the top
role, causing app roles to override org roles in the table; update
getHighestRole (and where it calls getBindingsForKey) to first filter
keyBindings to only org-scoped bindings (e.g., binding.scope === 'org' or
binding.org_id matching the org context) and then perform the existing
priority_rank-based selection over that filtered list; if no org-scoped bindings
remain, return null so the table-level role column only reflects org-scoped
roles.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pages/ApiKeys.vue`:
- Around line 170-172: The formatDisplayApps function incorrectly deduplicates
apps by display name; update it to preserve the full list returned by
getDisplayAppIds instead of converting mapped names into a Set so different apps
with the same name and multiple unresolved apps remain distinct. Specifically,
in formatDisplayApps use getDisplayAppIds(key).map(appId =>
appCache.value.get(appId) || 'Unknown') and join the resulting array with ', '
(do not use Set), keeping reference to the function name formatDisplayApps and
the appCache and getDisplayAppIds helpers when making the change.
- Around line 149-156: getDisplayOrgIds currently only adds org ids from
bindings where scope_type === 'org', so keys that only have app-scoped bindings
miss their app's organization; modify getDisplayOrgIds to also extract and add
the org id from app-scoped bindings returned by getBindingsForKey(key) (e.g.
check b.org_id || b.app?.org_id or equivalent field on the binding/app object),
keep using the Set to dedupe, and return Array.from(orgIds) so app-binding orgs
are included in the organizations summary.

---

Outside diff comments:
In `@src/pages/ApiKeys.vue`:
- Around line 124-140: The current getHighestRole function considers all
bindings (including app-level) when selecting the top role, causing app roles to
override org roles in the table; update getHighestRole (and where it calls
getBindingsForKey) to first filter keyBindings to only org-scoped bindings
(e.g., binding.scope === 'org' or binding.org_id matching the org context) and
then perform the existing priority_rank-based selection over that filtered list;
if no org-scoped bindings remain, return null so the table-level role column
only reflects org-scoped roles.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2149345a-f259-4728-a029-ea74a2382210

📥 Commits

Reviewing files that changed from the base of the PR and between 1e1e8df and 2ff7bdf.

📒 Files selected for processing (1)
  • src/pages/ApiKeys.vue

Comment thread src/pages/ApiKeys.vue
Comment on lines +149 to +156
function getDisplayOrgIds(key: Database['public']['Tables']['apikeys']['Row']): string[] {
const orgIds = new Set<string>()
key.limited_to_orgs?.forEach(orgId => orgIds.add(orgId))
getBindingsForKey(key)
.filter(b => b.scope_type === 'org' && !!b.org_id)
.forEach(b => orgIds.add(b.org_id!))
return Array.from(orgIds)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Include app-binding orgs in the organizations summary.

uniqueOrgIds already treats any role_bindings.org_id as part of the key's org footprint, but this display helper only lifts org-scoped bindings. That leaves the organizations column blank for keys that only carry app bindings.

Suggested fix
 function getDisplayOrgIds(key: Database['public']['Tables']['apikeys']['Row']): string[] {
   const orgIds = new Set<string>()
   key.limited_to_orgs?.forEach(orgId => orgIds.add(orgId))
   getBindingsForKey(key)
-    .filter(b => b.scope_type === 'org' && !!b.org_id)
+    .filter(b => !!b.org_id)
     .forEach(b => orgIds.add(b.org_id!))
   return Array.from(orgIds)
 }
📝 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
function getDisplayOrgIds(key: Database['public']['Tables']['apikeys']['Row']): string[] {
const orgIds = new Set<string>()
key.limited_to_orgs?.forEach(orgId => orgIds.add(orgId))
getBindingsForKey(key)
.filter(b => b.scope_type === 'org' && !!b.org_id)
.forEach(b => orgIds.add(b.org_id!))
return Array.from(orgIds)
}
function getDisplayOrgIds(key: Database['public']['Tables']['apikeys']['Row']): string[] {
const orgIds = new Set<string>()
key.limited_to_orgs?.forEach(orgId => orgIds.add(orgId))
getBindingsForKey(key)
.filter(b => !!b.org_id)
.forEach(b => orgIds.add(b.org_id!))
return Array.from(orgIds)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/ApiKeys.vue` around lines 149 - 156, getDisplayOrgIds currently
only adds org ids from bindings where scope_type === 'org', so keys that only
have app-scoped bindings miss their app's organization; modify getDisplayOrgIds
to also extract and add the org id from app-scoped bindings returned by
getBindingsForKey(key) (e.g. check b.org_id || b.app?.org_id or equivalent field
on the binding/app object), keep using the Set to dedupe, and return
Array.from(orgIds) so app-binding orgs are included in the organizations
summary.

Comment thread src/pages/ApiKeys.vue
Comment on lines +170 to +172
function formatDisplayApps(key: Database['public']['Tables']['apikeys']['Row']) {
const appNames = new Set(getDisplayAppIds(key).map(appId => appCache.value.get(appId) || 'Unknown'))
return Array.from(appNames).join(', ')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't dedupe apps by display name.

getDisplayAppIds() already removes duplicate app identities. Converting the rendered labels to a Set collapses different apps that share a name, and it also merges multiple unresolved apps into one row.

Suggested fix
 function formatDisplayApps(key: Database['public']['Tables']['apikeys']['Row']) {
-  const appNames = new Set(getDisplayAppIds(key).map(appId => appCache.value.get(appId) || 'Unknown'))
-  return Array.from(appNames).join(', ')
+  return getDisplayAppIds(key)
+    .map(appId => appCache.value.get(appId) || appId)
+    .join(', ')
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/ApiKeys.vue` around lines 170 - 172, The formatDisplayApps function
incorrectly deduplicates apps by display name; update it to preserve the full
list returned by getDisplayAppIds instead of converting mapped names into a Set
so different apps with the same name and multiple unresolved apps remain
distinct. Specifically, in formatDisplayApps use getDisplayAppIds(key).map(appId
=> appCache.value.get(appId) || 'Unknown') and join the resulting array with ',
' (do not use Set), keeping reference to the function name formatDisplayApps and
the appCache and getDisplayAppIds helpers when making the change.

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