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
44 changes: 44 additions & 0 deletions frontend/src/__tests__/history.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,50 @@ describe('History Module', () => {
expect(list?.innerHTML).toContain('table');
expect(list?.innerHTML).toContain('ec2');
expect(list?.innerHTML).toContain('us-east-1');
// Both Monthly Cost and Monthly Savings columns must be present (issue #788).
expect(list?.innerHTML).toContain('Monthly Cost');
expect(list?.innerHTML).toContain('Monthly Savings');
});

test('renders monthly_cost cell with value when present, dash when absent (issue #788)', async () => {
(api.getHistory as jest.Mock).mockResolvedValue({
summary: {},
purchases: [
{
timestamp: '2024-01-15T00:00:00Z',
provider: 'aws',
service: 'ec2',
resource_type: 't3.medium',
region: 'us-east-1',
count: 1,
term: 1,
upfront_cost: 500,
monthly_cost: 42.5,
estimated_savings: 20,
},
{
timestamp: '2024-01-16T00:00:00Z',
provider: 'aws',
service: 'rds',
resource_type: 'db.m5.large',
region: 'us-east-1',
count: 1,
term: 1,
upfront_cost: 0,
// monthly_cost intentionally absent
estimated_savings: 10,
},
]
});

await loadHistory();

const list = document.getElementById('history-list');
const html = list?.innerHTML || '';
// Row with monthly_cost=42.5: formatCurrency mock returns "$42.5".
expect(html).toContain('$42.5');
// Row without monthly_cost: must render the muted dash, not "$0".
expect(html).toContain('class="muted"');
});

test('shows empty message when no purchases', async () => {
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/__tests__/inventory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,20 @@ describe('loadActiveCommitments — fetch + render flow', () => {
// (or drops) a column trips the test.
const headers = Array.from(list.querySelectorAll('thead th')).map(th => th.textContent);
expect(headers).toEqual([
'Provider', 'Account', 'Service', 'Resource type', 'Region', 'Count', 'Term', 'Payment', 'Monthly cost', 'Expires',
'Provider', 'Account', 'Service', 'Resource type', 'Region', 'Count', 'Term', 'Payment', 'Monthly cost', 'Monthly savings', 'Expires',
]);

const rows = list.querySelectorAll('tbody tr');
expect(rows.length).toBe(2);
// Account cell contains the account name AND its monospaced ID.
expect(rows[0]!.textContent).toContain('Prod');
expect(rows[0]!.textContent).toContain('ec2');

// Monthly Savings column (10th, 0-indexed 9) must render the estimated_savings value.
// makeCommitment defaults estimated_savings to 80.0; formatCurrency mock returns "$80".
const savingsCell = rows[0]!.querySelector('td:nth-child(10)');
expect(savingsCell).not.toBeNull();
expect(savingsCell!.textContent).toContain('80');
});

test('renders an empty paragraph when no commitments are returned', async () => {
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,9 @@ function renderHistoryList(purchases: HistoryPurchase[]): void {
})();
const execIdAttr = p.purchase_id ? ` data-execution-id="${escapeHtml(p.purchase_id)}"` : '';
const planCellContent = renderActionCell(p);
const monthlyCostCell = p.monthly_cost != null
? formatCurrency(p.monthly_cost)
: '<span class="muted">-</span>';
return `
<tr${execIdAttr}>
<td>${statusCell}</td>
Expand All @@ -634,6 +637,7 @@ function renderHistoryList(purchases: HistoryPurchase[]): void {
<td>${p.count}</td>
<td>${formatTerm(p.term)}</td>
<td>${formatCurrency(p.upfront_cost)}</td>
<td>${monthlyCostCell}</td>
<td class="savings">${formatCurrency(p.estimated_savings)}</td>
<td>${planCellContent}</td>
</tr>
Expand All @@ -654,6 +658,7 @@ function renderHistoryList(purchases: HistoryPurchase[]): void {
<th>Count</th>
<th>Term</th>
<th>Upfront Cost</th>
<th>Monthly Cost</th>
<th>Monthly Savings</th>
<th>Plan</th>
</tr>
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/inventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export function switchInventorySubSection(name: string): void {

const ACTIVE_COMMITMENTS_LIST_ID = 'active-commitments-list';
const ACTIVE_COMMITMENTS_REFRESH_BTN_ID = 'active-commitments-refresh-btn';
const ACTIVE_COMMITMENTS_COLS = 10;
const ACTIVE_COMMITMENTS_COLS = 11;

/**
* Fetch and render the active-commitments table. Replaces #active-commitments-list
Expand Down Expand Up @@ -153,7 +153,7 @@ function renderActiveCommitmentsTable(container: HTMLElement, commitments: api.I

const thead = document.createElement('thead');
const headerRow = document.createElement('tr');
const headers = ['Provider', 'Account', 'Service', 'Resource type', 'Region', 'Count', 'Term', 'Payment', 'Monthly cost', 'Expires'];
const headers = ['Provider', 'Account', 'Service', 'Resource type', 'Region', 'Count', 'Term', 'Payment', 'Monthly cost', 'Monthly savings', 'Expires'];
for (const label of headers) {
const th = document.createElement('th');
th.textContent = label;
Expand Down Expand Up @@ -183,6 +183,7 @@ function buildCommitmentRow(c: api.InventoryCommitment): HTMLTableRowElement {
appendCell(tr, `${c.term_years}y`);
appendCell(tr, c.payment_option ?? '');
appendCell(tr, formatCurrency(c.monthly_cost));
appendCell(tr, formatCurrency(c.estimated_savings));
appendCell(tr, formatDate(c.end_date));

return tr;
Expand Down
Loading