From eb5641bb96b1d4f9db4fc43bb9230102b6ac9f8e Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 20 May 2026 18:53:02 +0200 Subject: [PATCH 1/2] feat(settings): Azure and GCP create-override modal (closes #109) Extend the per-account override create/edit flow to Azure and GCP accounts (previously AWS-only, tracked as follow-up to issue #104). - Drop `canCreate = provider === 'aws'` gate: all providers now render the "Add override" button and auto-open the create modal on empty state - Add AZURE_OVERRIDE_SERVICES and GCP_OVERRIDE_SERVICES lists; dispatch via getOverrideServicesForProvider so the service dropdown is provider-aware - Remove `o.provider === 'aws'` guards on term/payment/coverage table cells: all three get inline editing selects/inputs for every provider - Remove the disabled+tooltip guard on the enabled checkbox for non-AWS rows - buildPaymentOverrideSelect and syncOverridePaymentOptions now derive the fallback payment list from getCommitmentConfig(provider) instead of a hardcoded AWS-only constant, so Azure shows upfront/monthly and GCP shows monthly-only - Remove now-unused AWS_PAYMENT_OPTIONS constant (superseded by getCommitmentConfig which already owns the same data) - Tests: update three tests that asserted the old read-only behavior; add coverage for Azure payment options, Azure inline editing, and Azure empty-state modal auto-open with provider-correct service list --- .../src/__tests__/settings-accounts.test.ts | 59 ++++++--- frontend/src/settings.ts | 117 +++++++++--------- 2 files changed, 97 insertions(+), 79 deletions(-) diff --git a/frontend/src/__tests__/settings-accounts.test.ts b/frontend/src/__tests__/settings-accounts.test.ts index 5fa990a68..f32f79275 100644 --- a/frontend/src/__tests__/settings-accounts.test.ts +++ b/frontend/src/__tests__/settings-accounts.test.ts @@ -650,14 +650,22 @@ describe('Overrides panel — AWS payment selector', () => { expect(api.saveAccountServiceOverride).not.toHaveBeenCalled(); }); - test('non-AWS rows render the existing read-only payment cell, no for the overrides panel. Issue #23. @@ -629,15 +645,16 @@ function buildPaymentOverrideSelect( select.appendChild(inheritOpt); // Filter the payment dropdown to only the (term, payment) combinations - // AWS actually supports for THIS service. e.g. RDS rejects 3yr no-upfront, - // so an RDS override with term=3 must not list no-upfront. Per #107. - // Falls back to the full AWS list when term is missing (override row was - // saved without a term) — the global default's term will resolve at + // the provider/service actually supports. e.g. RDS rejects 3yr no-upfront; + // Azure has upfront/monthly only; GCP has monthly only. Per #107. + // Falls back to the full provider payment list when term is missing (override + // row was saved without a term) — the global default's term will resolve at // recommendation time, and we can't pre-validate without it. const overrideTerm = override.term ?? 0; + const providerPayments = getCommitmentConfig(override.provider).payments; const validPayments = overrideTerm > 0 ? getValidPaymentOptions(override.provider, override.service, overrideTerm) - : AWS_PAYMENT_OPTIONS.map(p => ({ value: p.value, label: p.label })); + : providerPayments.map(p => ({ value: p.value, label: p.label })); for (const { value, label } of validPayments) { const opt = document.createElement('option'); @@ -705,9 +722,6 @@ async function handlePaymentOverrideChange( message: `Payment override updated for ${override.provider}/${override.service}.`, kind: 'success', }); - // The inline payment selector only renders for AWS rows (per the - // o.provider === 'aws' guard in loadOverridesPanel's row loop), so - // override.provider is always 'aws' at this call site — cast is safe. await loadOverridesPanel(accountId, panel, override.provider as AccountProvider); await refreshRecommendationsAfterOverrideChange(); } catch (err) { @@ -969,12 +983,6 @@ function buildEnabledOverrideCheckbox( `Enable ${override.provider}/${override.service} override`, ); - if (override.provider !== 'aws') { - input.disabled = true; - input.title = 'Per-provider override editing is AWS-only — Azure/GCP coming with the provider-aware modal'; - return input; - } - input.addEventListener('change', () => { void handleEnabledOverrideChange(accountId, override, input, panel, row); }); @@ -1031,9 +1039,10 @@ async function handleEnabledOverrideChange( * Populated state: render the existing table + an "Add override" button at * the top so users can add another override for a different service. * - * The Add Override flow is AWS-only for now; Azure/GCP keep the read-only - * empty-state text since their per-product term/payment semantics differ - * (issue #104 follow-up tracks Azure/GCP modal support). + * Issue #109: Azure and GCP accounts now also get the "Add override" button + * and full inline editing (term, payment, coverage, enabled) on existing rows. + * Provider-specific payment options are derived via getCommitmentConfig so + * GCP shows "Monthly only" and Azure shows "Upfront / Monthly". */ /** * Open the per-account overrides modal (issue #122). Replaces the inline @@ -1080,14 +1089,13 @@ export async function loadOverridesPanel(accountId: string, panel: HTMLElement, const overrides = await api.listAccountServiceOverrides(accountId); panel.textContent = ''; - const canCreate = provider === 'aws'; + // Issue #109: all providers can now create overrides. + const canCreate = true; if (!overrides || overrides.length === 0) { const msg = document.createElement('p'); msg.className = 'help-text'; - msg.textContent = canCreate - ? 'No service overrides yet for this account.' - : 'No service overrides set. All services use global defaults.'; + msg.textContent = 'No service overrides yet for this account.'; panel.appendChild(msg); if (canCreate) { // No overrides yet \u2014 auto-open the modal so the user lands directly @@ -1143,33 +1151,21 @@ export async function loadOverridesPanel(accountId: string, panel: HTMLElement, const serviceTd = tr.insertCell(); serviceTd.textContent = `${o.provider}/${o.service}`; - // Term cell: editable for all providers (issue #109 extends + // the AWS-only #110 inline editing to Azure/GCP). const termTd = tr.insertCell(); - if (o.provider === 'aws') { - termTd.appendChild(buildTermOverrideSelect(accountId, o, panel)); - } else { - termTd.textContent = o.term !== undefined ? `${o.term}yr` : '\u2014'; - } + termTd.appendChild(buildTermOverrideSelect(accountId, o, panel)); - // Payment cell: editable for all providers. GCP's payment + // dropdown only shows "Monthly"; Azure shows upfront/monthly. The + // underlying getValidPaymentOptions already handles per-provider + // filtering, so the same builder works for all three clouds. const paymentTd = tr.insertCell(); - if (o.provider === 'aws') { - paymentTd.appendChild(buildPaymentOverrideSelect(accountId, o, panel)); - } else { - paymentTd.textContent = o.payment ?? '\u2014'; - } + paymentTd.appendChild(buildPaymentOverrideSelect(accountId, o, panel)); - // Coverage cell: editable numeric for AWS (issue #110); - // read-only text for Azure/GCP. + // Coverage cell: editable numeric for all providers (issue #109). const coverageTd = tr.insertCell(); - if (o.provider === 'aws') { - coverageTd.appendChild(buildCoverageOverrideInput(accountId, o, panel)); - } else { - coverageTd.textContent = o.coverage !== undefined ? `${o.coverage}%` : '\u2014'; - } + coverageTd.appendChild(buildCoverageOverrideInput(accountId, o, panel)); // Enabled cell: per-row checkbox toggling account_service_overrides.enabled. // AWS-editable, disabled (with tooltip) for non-AWS to keep the column @@ -1311,9 +1307,9 @@ export function openOverrideModal( const errEl = document.getElementById('override-form-error'); if (errEl) errEl.textContent = ''; - // Populate the service dropdown, excluding services this account already - // has an override for. AWS-only for now (issue #104 follow-up tracks - // Azure/GCP). + // Populate the service dropdown with provider-specific services, excluding + // any that already have an override for this account. Issue #109 extends + // this to Azure and GCP (previously AWS-only per issue #104 follow-up). const used = new Set( existingOverrides .filter(o => o.provider === provider) @@ -1322,7 +1318,7 @@ export function openOverrideModal( const select = document.getElementById('override-service') as HTMLSelectElement | null; if (select) { select.replaceChildren(); - const available = AWS_OVERRIDE_SERVICES.filter(s => !used.has(s.value)); + const available = getOverrideServicesForProvider(provider).filter(s => !used.has(s.value)); if (available.length === 0) { const opt = document.createElement('option'); opt.value = ''; @@ -1382,11 +1378,12 @@ function syncOverridePaymentOptions(provider: string): void { paymentSel.appendChild(inheritOpt); // When service or term is unset, can't run the validity check yet — - // show the full AWS payment list so the user can choose; the + // show the full provider payment list so the user can choose; the // submit-side guard will catch any invalid combo on Save. + const provPayments = getCommitmentConfig(provider).payments; const candidates = (service && Number.isFinite(term) && term > 0) ? getValidPaymentOptions(provider, service, term).map(p => ({ value: p.value, label: p.label })) - : AWS_PAYMENT_OPTIONS.map(p => ({ value: p.value, label: p.label })); + : provPayments.map(p => ({ value: p.value, label: p.label })); for (const { value, label } of candidates) { const opt = document.createElement('option'); From 95d13847b348f899b88d6fee4d1dc4e4e71231a9 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 27 May 2026 23:58:19 +0200 Subject: [PATCH 2/2] docs(frontend/settings): update stale Enabled-cell comment to reflect all-providers editability The Enabled-cell comment described the pre-#109 behaviour (AWS-only editable, disabled-with-tooltip for non-AWS). Since #109 made the checkbox editable for Azure and GCP as well, the comment was stale. Updated to CR's suggested wording per PR #588 review. --- frontend/src/settings.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/settings.ts b/frontend/src/settings.ts index 918b12514..703d50a25 100644 --- a/frontend/src/settings.ts +++ b/frontend/src/settings.ts @@ -1168,8 +1168,7 @@ export async function loadOverridesPanel(accountId: string, panel: HTMLElement, coverageTd.appendChild(buildCoverageOverrideInput(accountId, o, panel)); // Enabled cell: per-row checkbox toggling account_service_overrides.enabled. - // AWS-editable, disabled (with tooltip) for non-AWS to keep the column - // alignment consistent across provider types. Issue #110. + // Editable for all providers. Issue `#110`, extended to Azure/GCP in `#109`. const enabledTd = tr.insertCell(); enabledTd.appendChild(buildEnabledOverrideCheckbox(accountId, o, panel, tr));