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
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,11 @@ export function CostInsightsOverviewClient({
: alert.type === 'threshold_7d'
? 'spend-threshold-7d'
: 'spend-threshold-24h';
router.push(`${basePath}/config#${thresholdAnchor}`);
router.push(
dashboard?.enabled
? `${basePath}/config#${thresholdAnchor}`
: `${basePath}/config#spend-alert-options-hidden`
);
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,11 @@ function CostInsightsSettingsForm({
threshold7DayUsd: validateThresholdUsd(form.threshold7DayUsd),
threshold30DayUsd: validateThresholdUsd(form.threshold30DayUsd),
};
const hasValidationError = Boolean(
validations.thresholdUsd || validations.threshold7DayUsd || validations.threshold30DayUsd
);
const hasValidationError =
form.enabled &&
Boolean(
validations.thresholdUsd || validations.threshold7DayUsd || validations.threshold30DayUsd
);
const dirty =
form.enabled !== settings.enabled ||
form.anomalyAlertsEnabled !== settings.anomalyAlertsEnabled ||
Expand Down Expand Up @@ -266,15 +268,18 @@ function CostInsightsSettingsForm({

const handleSave = () => {
if (!dirty || hasValidationError || settings.readOnly) return;
const thresholdUsd = form.enabled ? form.thresholdUsd : settings.thresholdUsd;
Comment thread
jeanduplessis marked this conversation as resolved.
const threshold7DayUsd = form.enabled
? form.threshold7DayUsd
: (settings.threshold7DayUsd ?? '');
const threshold30DayUsd = form.enabled ? form.threshold30DayUsd : settings.threshold30DayUsd;
onSave({
spendAlertsEnabled: form.enabled,
anomalyAlertsEnabled: form.anomalyAlertsEnabled,
costSuggestionsEnabled: form.suggestionsEnabled,
spendThresholdUsd: form.thresholdUsd.trim() === '' ? null : form.thresholdUsd.trim(),
spend7DayThresholdUsd:
form.threshold7DayUsd.trim() === '' ? null : form.threshold7DayUsd.trim(),
spend30DayThresholdUsd:
form.threshold30DayUsd.trim() === '' ? null : form.threshold30DayUsd.trim(),
spendThresholdUsd: thresholdUsd.trim() === '' ? null : thresholdUsd.trim(),
spend7DayThresholdUsd: threshold7DayUsd.trim() === '' ? null : threshold7DayUsd.trim(),
spend30DayThresholdUsd: threshold30DayUsd.trim() === '' ? null : threshold30DayUsd.trim(),
});
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function SpendEvidenceCard({
'90d': 'Last 90 days',
}[range];
const highest = evidence
.filter(point => point.coverage !== 'unavailable')
.filter(point => point.coverage !== 'unavailable' && point.variableUsd + point.scheduledUsd > 0)
.reduce<(typeof evidence)[number] | undefined>((currentHighest, point) => {
if (!currentHighest) return point;
const currentTotal = currentHighest.variableUsd + currentHighest.scheduledUsd;
Expand Down Expand Up @@ -121,7 +121,7 @@ export function SpendEvidenceCard({
>
<legend className="sr-only">{rangeLabel} spend by period</legend>
<div
className="pointer-events-none absolute inset-x-0 top-7 h-44"
className="pointer-events-none absolute inset-x-0 top-0 h-44"
aria-hidden="true"
>
<ChartGridLine position="top-0" label={money(axisMax)} />
Expand All @@ -132,12 +132,15 @@ export function SpendEvidenceCard({
const pointTotal = point.variableUsd + point.scheduledUsd;
const totalHeight = spendBarHeightPercent(pointTotal, axisMax);
const scheduledShare = percentOf(point.scheduledUsd, pointTotal);
const isPeak = highest !== undefined && point.periodStart === highest.periodStart;
const showTick = index % tickStride === 0 || index === evidence.length - 1;
const accessibilityLabel =
point.coverage === 'unavailable'
? `${point.label}: spend data unavailable, ${point.coveredHours} of ${point.totalHours} hours covered`
: `${point.label}: ${point.coverage === 'partial' ? 'at least ' : ''}${money(pointTotal)} total, ${money(point.variableUsd)} usage-based, ${money(point.scheduledUsd)} scheduled${point.coverage === 'partial' ? `, ${point.coveredHours} of ${point.totalHours} hours covered` : ''}`;
: point.coverage === 'partial'
? `${point.label}: at least ${money(pointTotal)} total, ${money(point.variableUsd)} usage-based, ${money(point.scheduledUsd)} scheduled, ${point.coveredHours} of ${point.totalHours} hours covered`
: pointTotal === 0
? `${point.label}: no spend`
: `${point.label}: ${money(pointTotal)} total, ${money(point.variableUsd)} usage-based, ${money(point.scheduledUsd)} scheduled`;
return (
<Tooltip key={point.periodStart}>
<TooltipTrigger asChild>
Expand All @@ -152,13 +155,6 @@ export function SpendEvidenceCard({
onFocus={() => setActiveBarIndex(index)}
onKeyDown={event => handleBarKeyDown(event, index)}
>
<span className="flex h-5 w-full items-end justify-center">
{isPeak && point.coverage !== 'unavailable' && (
<span className="type-label font-mono tabular-nums whitespace-nowrap">
{money(pointTotal)}
</span>
)}
</span>
<span className="flex h-44 w-full items-end">
<span
className={cn(
Expand Down Expand Up @@ -200,7 +196,14 @@ export function SpendEvidenceCard({
</TooltipTrigger>
<TooltipContent side="top" sideOffset={8} className="min-w-44 p-3">
<div className="type-label font-medium">{point.label}</div>
{point.coverage !== 'unavailable' ? (
{point.coverage === 'unavailable' ? (
<p className="type-label text-muted-foreground mt-2">
Spend data unavailable. Covered {point.coveredHours} of{' '}
{point.totalHours} hours.
</p>
) : point.coverage === 'complete' && pointTotal === 0 ? (
<p className="type-label text-muted-foreground mt-2">No spend.</p>
) : (
<dl className="mt-2 grid grid-cols-[1fr_auto] gap-x-4 gap-y-1 type-label">
<dt className="text-muted-foreground">
{point.coverage === 'partial' ? 'Known spend' : 'Total'}
Expand Down Expand Up @@ -228,11 +231,6 @@ export function SpendEvidenceCard({
</dt>
)}
</dl>
) : (
<p className="type-label text-muted-foreground mt-2">
Spend data unavailable. Covered {point.coveredHours} of{' '}
{point.totalHours} hours.
</p>
)}
</TooltipContent>
</Tooltip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,75 +108,79 @@ export function CostInsightsSettingsView({
</div>
</section>

<div className="border-border ml-6 divide-y border-l sm:ml-10">
{!data.enabled && (
<p className="type-label text-muted-foreground px-6 py-4" role="status">
Spend Alerts are off. Saved anomaly and threshold settings apply when you turn
Spend Alerts on again.
</p>
)}
<section
className="grid gap-5 px-6 py-5 md:grid-cols-[minmax(0,20rem)_minmax(0,1fr)]"
aria-labelledby="anomaly-setting-title"
>
<div>
<h3 id="anomaly-setting-title" className="type-body font-semibold">
Spend anomalies
</h3>
<p className="type-label text-muted-foreground mt-1">
Compare current-hour usage-based spend with your recent hourly pattern.
</p>
</div>
<div className="flex min-h-control-touch items-center gap-3 md:justify-end">
<span className="type-label text-muted-foreground" aria-hidden="true">
{data.anomalyAlertsEnabled ? 'On' : 'Off'}
</span>
<Label htmlFor="spend-anomalies-enabled" className="sr-only">
Spend anomalies
</Label>
<Switch
id="spend-anomalies-enabled"
className="relative before:absolute before:inset-x-0 before:-inset-y-2.5"
checked={data.anomalyAlertsEnabled}
disabled={disabled}
onCheckedChange={anomalyAlertsEnabled => onChange?.({ anomalyAlertsEnabled })}
/>
</div>
</section>
{data.enabled ? (
<div className="border-border ml-6 divide-y border-l sm:ml-10">
<section
className="grid gap-5 px-6 py-5 md:grid-cols-[minmax(0,20rem)_minmax(0,1fr)]"
aria-labelledby="anomaly-setting-title"
>
<div>
<h3 id="anomaly-setting-title" className="type-body font-semibold">
Spend anomalies
</h3>
<p className="type-label text-muted-foreground mt-1">
Compare current-hour usage-based spend with your recent hourly pattern.
</p>
</div>
<div className="flex min-h-control-touch items-center gap-3 md:justify-end">
<span className="type-label text-muted-foreground" aria-hidden="true">
{data.anomalyAlertsEnabled ? 'On' : 'Off'}
</span>
<Label htmlFor="spend-anomalies-enabled" className="sr-only">
Spend anomalies
</Label>
<Switch
id="spend-anomalies-enabled"
className="relative before:absolute before:inset-x-0 before:-inset-y-2.5"
checked={data.anomalyAlertsEnabled}
disabled={disabled}
onCheckedChange={anomalyAlertsEnabled => onChange?.({ anomalyAlertsEnabled })}
/>
</div>
</section>

<ThresholdOption
id="spend-threshold-24h"
title="24-hour spend threshold"
description="Optional. Includes all Credit spend in a rolling 24-hour period."
value={data.thresholdUsd}
validation={thresholdValidation}
disabled={disabled}
readOnly={data.readOnly}
onChange={thresholdUsd => onChange?.({ thresholdUsd })}
/>
<ThresholdOption
id="spend-threshold-24h"
Comment thread
jeanduplessis marked this conversation as resolved.
title="24-hour spend threshold"
description="Optional. Includes all Credit spend in a rolling 24-hour period."
value={data.thresholdUsd}
validation={thresholdValidation}
disabled={disabled}
readOnly={data.readOnly}
onChange={thresholdUsd => onChange?.({ thresholdUsd })}
/>

<ThresholdOption
id="spend-threshold-7d"
title="7-day spend threshold"
description="Optional. Includes all Credit spend in a rolling 7-day period."
value={data.threshold7DayUsd ?? ''}
validation={threshold7DayValidation}
disabled={disabled}
readOnly={data.readOnly}
onChange={threshold7DayUsd => onChange?.({ threshold7DayUsd })}
/>
<ThresholdOption
id="spend-threshold-7d"
title="7-day spend threshold"
description="Optional. Includes all Credit spend in a rolling 7-day period."
value={data.threshold7DayUsd ?? ''}
validation={threshold7DayValidation}
disabled={disabled}
readOnly={data.readOnly}
onChange={threshold7DayUsd => onChange?.({ threshold7DayUsd })}
/>

<ThresholdOption
id="spend-threshold-30d"
title="30-day spend threshold"
description="Optional. Includes all Credit spend in a rolling 30-day period."
value={data.threshold30DayUsd}
validation={threshold30DayValidation}
disabled={disabled}
readOnly={data.readOnly}
onChange={threshold30DayUsd => onChange?.({ threshold30DayUsd })}
/>
</div>
<ThresholdOption
id="spend-threshold-30d"
title="30-day spend threshold"
description="Optional. Includes all Credit spend in a rolling 30-day period."
value={data.threshold30DayUsd}
validation={threshold30DayValidation}
disabled={disabled}
readOnly={data.readOnly}
onChange={threshold30DayUsd => onChange?.({ threshold30DayUsd })}
/>
</div>
) : (
<p
id="spend-alert-options-hidden"
className="type-label text-muted-foreground px-6 pb-6"
role="status"
>
Turn on Spend Alerts to manage anomaly and threshold settings.
</p>
)}
</div>
</CardContent>
</Card>
Expand Down