Skip to content

feat: Add API key creation and management support in dashboard#269

Merged
prajjwalkumar17 merged 42 commits intomainfrom
feature/api-key-dashboard-support
Apr 29, 2026
Merged

feat: Add API key creation and management support in dashboard#269
prajjwalkumar17 merged 42 commits intomainfrom
feature/api-key-dashboard-support

Conversation

@jagan-jaya
Copy link
Copy Markdown
Collaborator

  • Add ApiKeysPage component for managing API keys (create, list, revoke)
  • Add sidebar navigation link to API Keys page
  • Add Cypress tests for API keys UI flows
  • Add Cypress commands for API key operations (create, list, revoke, use)
  • Update App.tsx to include API keys route
  • Add .mintlify-dev.log to gitignore

- Build Decision Engine once from PR source and share as artifact

- Run 10 Cypress spec files in parallel across matrix jobs

- Each job starts isolated stack with API + deps

- Upload screenshots on failure with 7-day retention
Add explicit permissions block to address CodeQL security findings:

- contents: read (for checkout and file operations)

- actions: read (for artifact upload/download)
- Change health check from /health to /health/ready for proper readiness

- Add both website/package-lock.json and root package-lock.json to cache

- Configure preview.proxy in vite.config.ts for API routing

- Change VITE_API_BASE_URL to VITE_API_BASE_PATH for CI builds
- Use docker/build-push-action@v5 with GHA cache

- Enable BuildKit layer caching via cache-from/cache-to

- Optimize Dockerfile.postgres for dependency caching

- Cache Cargo dependencies before copying source code
Project doesn't use workspace structure with crates/ directory
- Remove double compilation (dummy + real build)

- Remove CARGO_INCREMENTAL=0 to enable incremental builds

- Docker layer caching via GHA is sufficient optimization
- Add ApiKeysPage component for managing API keys (create, list, revoke)
- Add sidebar navigation link to API Keys page
- Add Cypress tests for API keys UI flows
- Add Cypress commands for API key operations (create, list, revoke, use)
- Update App.tsx to include API keys route
- Add .mintlify-dev.log to gitignore
@jagan-jaya jagan-jaya self-assigned this Apr 28, 2026
@jagan-jaya jagan-jaya changed the base branch from feature/add-cypress-ci-workflow to main April 29, 2026 10:46
Copilot AI review requested due to automatic review settings April 29, 2026 12:02
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds dashboard UI + test/CI coverage for creating and managing API keys, and improves the Euclid rule builder UX by introducing a searchable dropdown for condition key/value selection.

Changes:

  • Add ApiKeysPage with create/list/revoke flows and wire it into the app route + sidebar.
  • Introduce SearchableSelect and replace condition-row <select> controls in the Euclid rules builder.
  • Add Cypress UI tests + commands for API key workflows, and add a dedicated GitHub Actions Cypress workflow.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
website/src/pages/ApiKeysPage.tsx New dashboard page for API key creation, listing, and revocation.
website/src/components/ui/SearchableSelect.tsx New reusable searchable dropdown component.
website/src/components/pages/EuclidRulesPage.tsx Switch condition key/value selects to SearchableSelect.
website/src/components/layout/Sidebar.tsx Add sidebar navigation link to API Keys page.
website/src/App.tsx Register /api-keys route.
cypress/support/commands.js Add Cypress commands for API key lifecycle + using API keys for requests.
cypress/e2e/ui/api-keys-page.cy.js Add UI coverage for API keys page flows.
cypress/e2e/ui/volume-split-page.cy.js Adjust assertions around rule creation success.
cypress/e2e/ui/euclid-rules-volume-split.cy.js Make routing-keys loading synchronization explicit.
cypress/e2e/ui/euclid-rules-volume-split-priority.cy.js Make routing-keys loading synchronization explicit.
cypress/e2e/ui/euclid-rules-nested-branches.cy.js Make routing-keys loading synchronization explicit (but selectors need updating).
cypress/e2e/ui/euclid-rules-enum-operators.cy.js Make routing-keys loading synchronization explicit (but selectors need updating).
cypress/e2e/ui/euclid-rules-e2e.cy.js Make routing-keys loading synchronization explicit (but selectors need updating).
cypress/e2e/ui/analytics-page.cy.js Strengthen assertion to require visibility.
.gitignore Ignore .mintlify-dev.log.
.github/workflows/cypress.yml Add CI workflow to run API + UI Cypress suites.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +61 to +64
const { data: keys, mutate } = useSWR<ApiKeyListItem[]>(
merchantId ? `/api-key/list/${merchantId}` : null,
fetcher,
)
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

useSWR result isn't handling the list fetch error/loading state. With keys undefined (initial load or network error), the UI currently shows "No active API keys" which is misleading and hides failures. Consider using const { data, error, isLoading } = useSWR(...) and rendering a loading indicator and/or ErrorMessage for the list request error separately from revoke/create errors.

Suggested change
const { data: keys, mutate } = useSWR<ApiKeyListItem[]>(
merchantId ? `/api-key/list/${merchantId}` : null,
fetcher,
)
const {
data: keys,
error: listError,
isLoading: isKeysLoading,
mutate,
} = useSWR<ApiKeyListItem[]>(
merchantId ? `/api-key/list/${merchantId}` : null,
fetcher,
)
const hasKeysLoadError = Boolean(listError)
const listErrorMessage =
listError instanceof Error ? listError.message : 'Failed to load API keys'
const hasLoadedKeys = !isKeysLoading && !hasKeysLoadError && Array.isArray(keys)

Copilot uses AI. Check for mistakes.
Comment on lines +124 to +132
<input
value={description}
onChange={(e) => setDescription(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleCreate()}
placeholder="Description (optional)"
className="flex-1 rounded-lg border border-slate-200 bg-transparent px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-brand-500 dark:border-[#222226]"
/>
<Button onClick={handleCreate} disabled={creating || !merchantId}>
{creating ? 'Creating…' : 'Create API Key'}
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

onKeyDown triggers handleCreate() on Enter even when a create is already in progress. Because handleCreate() doesn't guard on creating, pressing Enter repeatedly (or holding it) can fire multiple POSTs while the button is disabled. Add a creating guard inside handleCreate and/or check !creating && merchantId before calling it from onKeyDown.

Copilot uses AI. Check for mistakes.
<Button
size="sm"
variant="danger"
disabled={revokingId === key.key_id}
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

revokingId tracks only a single key, but the UI only disables the matching row. If a user clicks "Revoke" on a second key while the first request is still in flight, the first row becomes enabled again and multiple revokes can run concurrently with confusing UI state. Consider tracking a Set of revoking IDs or disabling all revoke buttons while any revoke is in progress.

Suggested change
disabled={revokingId === key.key_id}
disabled={Boolean(revokingId)}

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +76
<button
type="button"
disabled={disabled}
onClick={() => setOpen(o => !o)}
className="cond-select flex items-center gap-1 pr-2"
style={{ backgroundImage: 'none', display: 'flex', alignItems: 'center' }}
>
<span className="truncate max-w-[10rem]">{selectedLabel || <span className="text-slate-400">select...</span>}</span>
<ChevronDown
size={11}
className={`shrink-0 text-slate-400 transition-transform duration-150 ${open ? 'rotate-180' : ''}`}
/>
</button>
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

This custom select replaces a native <select> but doesn't expose listbox semantics (e.g., aria-haspopup="listbox", aria-expanded, roles for options) and has limited keyboard support (no ArrowUp/ArrowDown/Enter to navigate/select). This is an accessibility regression compared to the native control; consider implementing proper ARIA roles + keyboard interactions, or rendering a visually-customized native <select> for a11y.

Copilot uses AI. Check for mistakes.
<option key={k} value={k}>{toLabel(k)}</option>
))}
</select>
options={Object.keys(routingKeys).map((k) => ({ value: k, label: toLabel(k) }))}
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

Swapping the condition LHS/value controls from native <select> to SearchableSelect changes the DOM (it's now a <button class="cond-select"> + popup), but many Cypress specs still use cy.get('select.cond-select').select(...). Those tests will fail once this lands; either update the Cypress helpers/selectors to work with the new component (ideally via a stable data-testid) or add a compatibility layer (e.g., hidden native <select>).

Suggested change
options={Object.keys(routingKeys).map((k) => ({ value: k, label: toLabel(k) }))}
options={Object.keys(routingKeys).map((k) => ({ value: k, label: toLabel(k) }))}
aria-label="Condition field"
data-testid="condition-lhs-select"
className="cond-select min-w-[9.5rem]"

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +37
const selectedLabel = options.find(o => o.value === value)?.label ?? value

const filtered = (query
? options.filter(o =>
o.label.toLowerCase().includes(query.toLowerCase()) ||
o.value.toLowerCase().includes(query.toLowerCase())
)
: options
).slice().sort((a, b) => a.label.localeCompare(b.label))
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

filtered does a full filter + sort() on every render. With large option sets (e.g., routing keys) this can add noticeable overhead because the component re-renders on each keystroke/open toggle. Consider memoizing the derived list with useMemo (keyed on options + query) and only sorting when needed.

Copilot uses AI. Check for mistakes.
@prajjwalkumar17 prajjwalkumar17 merged commit 8e8c219 into main Apr 29, 2026
15 checks passed
@prajjwalkumar17 prajjwalkumar17 deleted the feature/api-key-dashboard-support branch April 29, 2026 12:15
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.

3 participants