Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 45 additions & 10 deletions frontend/src/__tests__/plans.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1441,16 +1441,51 @@ describe('Plans Module', () => {
expect(chips?.textContent).not.toContain('Prod AWS');
});

test('does not prefill when snapshot has more than one commitment', () => {
const second: api.Recommendation = { ...fixture, id: 'rec-771', service: 'rds' };
openCreatePlanModal([fixture, second]);

// populateTermSelect is called by setupRampScheduleHandlers / updateCommitmentOptions
// but NOT by prefillPurchaseConfigFromCommitment (which only runs for length===1)
// The provider/service select should NOT be forced to either rec's values
// We assert that the service select was not forced to 'ec2' alone
// (a multi-commitment plan requires manual selection)
expect(api.getAccount).not.toHaveBeenCalled();
// #898: extend prefill to a homogeneous multi-commitment selection. The
// "Plan from N selected" button only enables when provider/service/term/
// payment match across the selection, so the first commitment is a valid
// representative for the Purchase Configuration fields.
describe('prefill from multiple homogeneous commitments (#898)', () => {
test('prefills provider/service/term/payment from the shared values', () => {
const second: api.Recommendation = { ...fixture, id: 'rec-771', region: 'us-west-2' };
openCreatePlanModal([fixture, second]);

expect((document.getElementById('plan-provider') as HTMLSelectElement).value).toBe('aws');
expect((document.getElementById('plan-service') as HTMLSelectElement).value).toBe('ec2');
expect((document.getElementById('plan-term') as HTMLSelectElement).value).toBe('1');
expect((document.getElementById('plan-payment') as HTMLSelectElement).value).toBe('partial-upfront');
});

test('prefills the account chip when all commitments share one account', async () => {
(api.getAccount as jest.Mock).mockResolvedValueOnce({
id: 'acct-uuid-123',
name: 'Prod AWS',
external_id: '123456789012',
});
const second: api.Recommendation = { ...fixture, id: 'rec-771', region: 'us-west-2' };
openCreatePlanModal([fixture, second]);

await Promise.resolve();
await Promise.resolve();

expect(api.getAccount).toHaveBeenCalledWith('acct-uuid-123');
const hiddenIds = (document.getElementById('plan-account-ids') as HTMLInputElement).value;
expect(hiddenIds).toContain('acct-uuid-123');
});

test('leaves the account chip empty when commitments span accounts', async () => {
const second: api.Recommendation = { ...fixture, id: 'rec-771', cloud_account_id: 'acct-uuid-999' };
openCreatePlanModal([fixture, second]);

await Promise.resolve();

// Config still prefills from the homogeneous fields, but the account
// is ambiguous so no chip is fetched.
expect((document.getElementById('plan-service') as HTMLSelectElement).value).toBe('ec2');
expect(api.getAccount).not.toHaveBeenCalled();
const hiddenIds = (document.getElementById('plan-account-ids') as HTMLInputElement).value;
expect(hiddenIds).toBe('');
});
});
});
});
Expand Down
42 changes: 30 additions & 12 deletions frontend/src/plans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -836,8 +836,15 @@ async function setupPlanAccountsSection(planId?: string): Promise<void> {

/**
* Prefill the Purchase Configuration section (provider / service / term /
* payment) from a single selected commitment. Called after form.reset() so
* the defaults are already in place; each field is still editable. (#770)
* payment) from a representative selected commitment. Called after
* form.reset() so the defaults are already in place; each field is still
* editable. (#770)
*
* For a multi-commitment selection the caller passes the first commitment as
* the representative: the "Plan from N selected" button is only enabled when
* the selection is homogeneous (same provider/service/term/payment — see
* isHomogeneousSelection in recommendations.ts), so any element shares those
* four values. (#898)
*/
function prefillPurchaseConfigFromCommitment(rec: api.Recommendation): void {
const providerSelect = document.getElementById('plan-provider') as HTMLSelectElement | null;
Expand Down Expand Up @@ -921,10 +928,13 @@ export function openCreatePlanModal(snapshot?: readonly api.Recommendation[]): v
(document.getElementById('plan-id') as HTMLInputElement).value = '';
(document.getElementById('plan-form') as HTMLFormElement | null)?.reset();

// When exactly one commitment is selected, prefill the Purchase
// Configuration fields so the user does not have to re-enter them.
// Fields are still fully editable after prefill. (#770)
if (pendingPlanRecommendations.length === 1) {
// When one or more commitments are selected, prefill the Purchase
// Configuration fields so the user does not have to re-enter them. The
// first commitment is the representative: for a multi-selection the
// "Plan from N selected" button only enables on a homogeneous selection,
// so every element shares provider/service/term/payment. Fields are still
// fully editable after prefill. (#770, #898)
if (pendingPlanRecommendations.length >= 1) {
prefillPurchaseConfigFromCommitment(pendingPlanRecommendations[0]!);
}

Expand All @@ -943,13 +953,21 @@ export function openCreatePlanModal(snapshot?: readonly api.Recommendation[]): v
planModalSession += 1;

// setupPlanAccountsSection clears planSelectedAccounts and re-renders.
// When a single commitment carries a cloud_account_id, we look up that
// account after the section has reset and add it as a pre-selected chip.
// When every selected commitment carries the SAME cloud_account_id, we look
// up that account after the section has reset and add it as a pre-selected
// chip. Homogeneity of provider/service/term/payment (enforced by the
// "Plan from N selected" gate) does not imply a single account, so a
// multi-account selection leaves the chip empty for the user to fill. (#898)
void setupPlanAccountsSection();
if (pendingPlanRecommendations.length === 1) {
const accountId = pendingPlanRecommendations[0]!.cloud_account_id;
if (accountId) {
void prefillAccountChipFromId(accountId, planModalSession);
if (pendingPlanRecommendations.length >= 1) {
const firstAccountId = pendingPlanRecommendations[0]!.cloud_account_id;
const sharedAccountId =
firstAccountId &&
pendingPlanRecommendations.every((r) => r.cloud_account_id === firstAccountId)
? firstAccountId
: undefined;
if (sharedAccountId) {
void prefillAccountChipFromId(sharedAccountId, planModalSession);
}
}

Expand Down
Loading