Skip to content

Commit 1ae5191

Browse files
asizikovCopilot
andcommitted
fix: address additional PR feedback
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4ca0af0 commit 1ae5191

8 files changed

Lines changed: 47 additions & 89 deletions

File tree

src/App.tsx

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,44 +15,11 @@ import { CostCenterAggregator, type CostCenterResult } from './pipeline/aggregat
1515
import { OrganizationAggregator, type OrganizationResult } from './pipeline/aggregators/organizationAggregator'
1616
import { UserUsageAggregator, type UserUsageResult } from './pipeline/aggregators/userUsageAggregator'
1717
import { runPipeline } from './pipeline/runPipeline'
18-
import { formatUsd } from './utils/format'
18+
import { fillDataForRange } from './utils/fillDataForRange'
19+
import { formatAiu, formatUsd } from './utils/format'
1920

2021
type Status = 'idle' | 'processing' | 'done'
2122

22-
type DatedMetric = {
23-
date: string
24-
}
25-
26-
function formatAiu(n: number): string {
27-
return n.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 3 })
28-
}
29-
30-
function fillDataForRange<T extends DatedMetric>(
31-
data: T[],
32-
startDate: string | null,
33-
endDate: string | null,
34-
createEmpty: (date: string) => T,
35-
): T[] {
36-
if (!startDate || !endDate) return data
37-
38-
const start = new Date(`${startDate}T00:00:00Z`)
39-
const end = new Date(`${endDate}T00:00:00Z`)
40-
if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime()) || start > end) return data
41-
42-
const byDate = new Map<string, T>()
43-
for (const item of data) {
44-
byDate.set(item.date, item)
45-
}
46-
47-
const out: T[] = []
48-
for (let current = start; current <= end; current = new Date(current.getTime() + 24 * 60 * 60 * 1000)) {
49-
const isoDate = current.toISOString().slice(0, 10)
50-
out.push(byDate.get(isoDate) ?? createEmpty(isoDate))
51-
}
52-
53-
return out
54-
}
55-
5623
function createEmptyDailyUsage(date: string): DailyUsageData {
5724
return {
5825
date,
@@ -454,7 +421,7 @@ function App() {
454421
href="https://docs.github.com/billing/managing-your-copilot-usage"
455422
className="entitlement-banner__link"
456423
target="_blank"
457-
rel="noreferrer"
424+
rel="noopener noreferrer"
458425
>
459426
Learn more &rarr;
460427
</a>
@@ -466,22 +433,22 @@ function App() {
466433
<div className="next-steps__card">
467434
<h3 className="next-steps__card-title">Set an account-level budget</h3>
468435
<p className="next-steps__card-body">Control your total Copilot costs and prevent overages across your account. A top-level budget ensures spending stays within the limits you set.</p>
469-
<a href="https://github.com/settings/billing/budgets/new" className="next-steps__cta next-steps__cta--primary" target="_blank" rel="noreferrer">Set account budget &rarr;</a>
436+
<a href="https://github.com/settings/billing/budgets/new" className="next-steps__cta next-steps__cta--primary" target="_blank" rel="noopener noreferrer">Set account budget &rarr;</a>
470437
</div>
471438
<div className="next-steps__card">
472439
<h3 className="next-steps__card-title">Set per-user budgets</h3>
473440
<p className="next-steps__card-body">Ensure every user gets the access they need while you control how entitlements are distributed. User-level budgets prevent any single person from consuming an outsized share of the pool.</p>
474-
<a href="https://github.com/settings/billing/budgets" className="next-steps__cta" target="_blank" rel="noreferrer">Manage user budgets &rarr;</a>
441+
<a href="https://github.com/settings/billing/budgets" className="next-steps__cta" target="_blank" rel="noopener noreferrer">Manage user budgets &rarr;</a>
475442
</div>
476443
<div className="next-steps__card">
477444
<h3 className="next-steps__card-title">Set product-level budgets</h3>
478445
<p className="next-steps__card-body">Your usage breakdown shows cost by product. Set budgets per product (e.g., Coding Agent, Copilot Chat) to control spending where it matters most.</p>
479-
<a href="https://github.com/settings/billing/budgets" className="next-steps__cta" target="_blank" rel="noreferrer">Set product budgets &rarr;</a>
446+
<a href="https://github.com/settings/billing/budgets" className="next-steps__cta" target="_blank" rel="noopener noreferrer">Set product budgets &rarr;</a>
480447
</div>
481448
<div className="next-steps__card">
482449
<h3 className="next-steps__card-title">Read the docs</h3>
483450
<p className="next-steps__card-body">Learn how token-based billing works and how to plan for the transition.</p>
484-
<a href="https://docs.github.com/billing" className="next-steps__cta" target="_blank" rel="noreferrer">View documentation &rarr;</a>
451+
<a href="https://docs.github.com/billing" className="next-steps__cta" target="_blank" rel="noopener noreferrer">View documentation &rarr;</a>
485452
</div>
486453
</div>
487454
</section>

src/utils/fillDataForRange.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export type DatedMetric = {
2+
date: string
3+
}
4+
5+
export function fillDataForRange<T extends DatedMetric>(
6+
data: T[],
7+
startDate: string | null,
8+
endDate: string | null,
9+
createEmpty: (date: string) => T,
10+
): T[] {
11+
if (!startDate || !endDate) return data
12+
13+
const start = new Date(`${startDate}T00:00:00Z`)
14+
const end = new Date(`${endDate}T00:00:00Z`)
15+
if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime()) || start > end) return data
16+
17+
const byDate = new Map<string, T>()
18+
for (const item of data) {
19+
byDate.set(item.date, item)
20+
}
21+
22+
const out: T[] = []
23+
for (let current = start; current <= end; current = new Date(current.getTime() + 24 * 60 * 60 * 1000)) {
24+
const isoDate = current.toISOString().slice(0, 10)
25+
out.push(byDate.get(isoDate) ?? createEmpty(isoDate))
26+
}
27+
28+
return out
29+
}

src/utils/format.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
export function formatUsd(n: number): string {
22
return `$ ${n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
33
}
4+
5+
export function formatAiu(n: number): string {
6+
return n.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 3 })
7+
}

src/views/CostCentersView.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import { useMemo, useState } from 'react'
22
import type { CostCenterResult, CostTotals } from '../pipeline/aggregators/costCenterAggregator'
33
import { CostBreakdownCard } from '../components'
4-
import { formatUsd } from '../utils/format'
5-
6-
function formatAiu(n: number): string {
7-
return n.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 3 })
8-
}
4+
import { formatAiu, formatUsd } from '../utils/format'
95

106
function discountsFrom(totals: CostTotals): number {
117
return totals.discountAmount

src/views/ModelsView.tsx

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import { useState, useMemo } from 'react'
22
import type { ModelUsageResult, ModelDailyUsageData } from '../pipeline/aggregators/modelUsageAggregator'
33
import { BillingComparisonCard, DualAxisLineChart } from '../components'
4-
import { formatUsd } from '../utils/format'
5-
6-
function formatAiu(n: number): string {
7-
return n.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 3 })
8-
}
4+
import { fillDataForRange } from '../utils/fillDataForRange'
5+
import { formatAiu, formatUsd } from '../utils/format'
96

107
function createEmptyModelDailyUsage(date: string): ModelDailyUsageData {
118
return {
@@ -19,31 +16,6 @@ function createEmptyModelDailyUsage(date: string): ModelDailyUsageData {
1916
}
2017
}
2118

22-
function fillDataForRange(
23-
data: ModelDailyUsageData[],
24-
startDate: string | null,
25-
endDate: string | null,
26-
): ModelDailyUsageData[] {
27-
if (!startDate || !endDate) return data
28-
29-
const start = new Date(`${startDate}T00:00:00Z`)
30-
const end = new Date(`${endDate}T00:00:00Z`)
31-
if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime()) || start > end) return data
32-
33-
const byDate = new Map<string, ModelDailyUsageData>()
34-
for (const item of data) {
35-
byDate.set(item.date, item)
36-
}
37-
38-
const out: ModelDailyUsageData[] = []
39-
for (let current = start; current <= end; current = new Date(current.getTime() + 24 * 60 * 60 * 1000)) {
40-
const isoDate = current.toISOString().slice(0, 10)
41-
out.push(byDate.get(isoDate) ?? createEmptyModelDailyUsage(isoDate))
42-
}
43-
44-
return out
45-
}
46-
4719
type ModelsViewProps = {
4820
modelUsage: ModelUsageResult
4921
aicDiscountRate: number
@@ -61,7 +33,7 @@ export function ModelsView({ modelUsage, aicDiscountRate, rangeStart, rangeEnd }
6133

6234
const filledPerModelDailyData = useMemo(() => {
6335
const raw = selectedModel ? (modelUsage.byModel[selectedModel] ?? []) : []
64-
return fillDataForRange(raw, rangeStart, rangeEnd)
36+
return fillDataForRange(raw, rangeStart, rangeEnd, createEmptyModelDailyUsage)
6537
}, [selectedModel, modelUsage, rangeStart, rangeEnd])
6638

6739
const selectedModelAicDiscount = selectedModelTotals ? aicDiscountRate * selectedModelTotals.aiuGrossAmount : 0

src/views/OrganizationsView.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import type { OrganizationResult } from '../pipeline/aggregators/organizationAggregator'
2-
import { formatUsd } from '../utils/format'
3-
4-
function formatAiu(n: number): string {
5-
return n.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 3 })
6-
}
2+
import { formatAiu, formatUsd } from '../utils/format'
73

84
export function OrganizationsView({ data }: { data: OrganizationResult }) {
95
if (data.organizations.length === 0) {

src/views/UserDetailsView.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useMemo } from 'react'
22
import { MultiSeriesStackedBarChart } from '../components'
33
import type { UserUsage } from '../pipeline/aggregators/userUsageAggregator'
4+
import { formatAiu } from '../utils/format'
45

56
type DailySummaryModelRow = {
67
model: string
@@ -33,10 +34,6 @@ function formatInt(n: number): string {
3334
return n.toLocaleString()
3435
}
3536

36-
function formatAiu(n: number): string {
37-
return n.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 3 })
38-
}
39-
4037
function formatCost(n: number): string {
4138
const sign = n < 0 ? '-' : ''
4239
const absValue = Math.abs(n)

src/views/UsersView.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
import { useMemo, useState } from 'react'
22
import type { ChangeEvent } from 'react'
33
import type { UserUsage } from '../pipeline/aggregators/userUsageAggregator'
4+
import { formatAiu } from '../utils/format'
45
import { Trie } from '../utils/trie'
56

67
function formatInt(n: number): string {
78
return n.toLocaleString()
89
}
910

10-
function formatAiu(n: number): string {
11-
return n.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 3 })
12-
}
13-
1411
function formatCost(n: number): string {
1512
return `$${n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
1613
}

0 commit comments

Comments
 (0)