-
Notifications
You must be signed in to change notification settings - Fork 556
[WIP]: feat(usage-dashboard): usage dashboard prototype: show every state on fixture data #8192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
636390a
20c827c
b9c0c4b
2e6c914
ff0b745
e4c9894
8be59da
baa55aa
b18f235
cca71c1
eeac13f
71e9e1c
db1d6c0
9accc85
2e2b4e3
44ba60f
af5cd21
dae918b
9c01b88
282cd65
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| .stat-item { | ||
| flex: 1; | ||
| min-width: 180px; | ||
| padding: 16px; | ||
| border: 1px solid var(--color-border-default); | ||
| border-radius: var(--radius-md); | ||
| background: var(--color-surface-default); | ||
|
|
||
| &__head { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 6px; | ||
| margin-bottom: 8px; | ||
| } | ||
|
|
||
| &__icon { | ||
| flex-shrink: 0; | ||
| } | ||
|
|
||
| &__label { | ||
| font-size: 12px; | ||
| color: var(--color-text-secondary); | ||
| } | ||
|
|
||
| // Pushes the badge to the right of the label, whatever the label's length. | ||
| &__badge { | ||
| margin-left: auto; | ||
| } | ||
|
|
||
| &__value { | ||
| font-weight: var(--font-weight-bold); | ||
| line-height: 1.1; | ||
| // Long text values (emails, ids) wrap instead of pushing the card wide. | ||
| overflow-wrap: anywhere; | ||
|
|
||
| &--default { | ||
| font-size: 28px; | ||
| } | ||
|
|
||
| &--sm { | ||
| font-size: 16px; | ||
| } | ||
| } | ||
|
|
||
| &__limit { | ||
| font-size: 12px; | ||
| font-weight: var(--font-weight-regular); | ||
| color: var(--color-text-secondary); | ||
| } | ||
|
|
||
| &__sub { | ||
| margin-top: 2px; | ||
| font-size: 11px; | ||
| color: var(--color-text-secondary); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,8 @@ | ||
| import React, { FC, KeyboardEvent } from 'react' | ||
| import React, { FC, KeyboardEvent, ReactNode } from 'react' | ||
| import { colorIconDefault } from 'common/theme/tokens' | ||
| import Icon, { IconName } from './icons/Icon' | ||
| import Tooltip from './Tooltip' | ||
|
Comment on lines
3
to
4
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Replace the relative component imports. The frontend import rule does not permit relative imports in this file. Import As per coding guidelines: Source: Coding guidelines |
||
| import './StatItem.scss' | ||
|
|
||
| type VisibilityToggleProps = { | ||
| colour: string | ||
|
|
@@ -10,9 +11,15 @@ type VisibilityToggleProps = { | |
| } | ||
|
|
||
| export type StatItemProps = { | ||
| icon: IconName | ||
| label: string | ||
| value: string | number | ||
| /** Qualifier under the value, e.g. "of 2M plan limit". */ | ||
| sub?: ReactNode | ||
| /** State on the right of the label, e.g. a status badge. */ | ||
| badge?: ReactNode | ||
| icon?: IconName | ||
| /** 'sm' for text values like emails, which overflow at the default size. */ | ||
| size?: 'default' | 'sm' | ||
|
Comment on lines
+20
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Extract the inline union types into named types. Define named types for these unions, then use the names at each declaration.
As per coding guidelines: extract inline union types into named types. 📍 Affects 2 files
Source: Coding guidelines |
||
| // Optional: for displaying limits (e.g., "1,000 / 10,000") | ||
| limit?: number | null | ||
| // Optional: hover tooltip on the label | ||
|
|
@@ -22,9 +29,12 @@ export type StatItemProps = { | |
| } | ||
|
|
||
| const StatItem: FC<StatItemProps> = ({ | ||
| badge, | ||
| icon, | ||
| label, | ||
| limit, | ||
| size = 'default', | ||
| sub, | ||
| tooltip, | ||
| value, | ||
| visibilityToggle, | ||
|
|
@@ -40,45 +50,49 @@ const StatItem: FC<StatItemProps> = ({ | |
| } | ||
|
|
||
| return ( | ||
| <div className='d-flex flex-row align-items-start gap-2'> | ||
| <div className='plan-icon flex-shrink-0'> | ||
| <Icon name={icon} width={32} fill={colorIconDefault} /> | ||
| </div> | ||
| <div> | ||
| <p className='fs-small lh-sm mb-0'> | ||
| <div className='stat-item'> | ||
| <div className='stat-item__head'> | ||
| {icon && ( | ||
| <Icon | ||
| name={icon} | ||
| width={16} | ||
| fill={colorIconDefault} | ||
| className='stat-item__icon' | ||
| /> | ||
| )} | ||
| <span className='stat-item__label'> | ||
| {tooltip ? <Tooltip title={label}>{tooltip}</Tooltip> : label} | ||
| </p> | ||
| <h4 className='mb-0'> | ||
| {formattedValue} | ||
| {limit !== null && limit !== undefined && ( | ||
| <span className='text-muted fs-small fw-normal'> | ||
| {' '} | ||
| / {formatNumber(limit)} | ||
| </span> | ||
| )} | ||
| </h4> | ||
| {visibilityToggle && ( | ||
| </span> | ||
| {badge && <span className='stat-item__badge'>{badge}</span>} | ||
| </div> | ||
| <div className={`stat-item__value stat-item__value--${size}`}> | ||
| {formattedValue} | ||
| {limit !== null && limit !== undefined && ( | ||
| <span className='stat-item__limit'> / {formatNumber(limit)}</span> | ||
| )} | ||
| </div> | ||
| {sub && <div className='stat-item__sub'>{sub}</div>} | ||
| {visibilityToggle && ( | ||
| <div | ||
| role='checkbox' | ||
| aria-checked={visibilityToggle.isVisible} | ||
| aria-label={`Toggle ${label} visibility`} | ||
| tabIndex={0} | ||
| className='cursor-pointer d-flex align-items-center gap-2 mt-2' | ||
| onClick={visibilityToggle.onToggle} | ||
| onKeyDown={handleKeyDown} | ||
| > | ||
| <div | ||
| role='checkbox' | ||
| aria-checked={visibilityToggle.isVisible} | ||
| aria-label={`Toggle ${label} visibility`} | ||
| tabIndex={0} | ||
| className='cursor-pointer d-flex align-items-center gap-2 mt-1' | ||
| onClick={visibilityToggle.onToggle} | ||
| onKeyDown={handleKeyDown} | ||
| className='visibility-checkbox' | ||
| style={{ backgroundColor: visibilityToggle.colour }} | ||
| > | ||
| <div | ||
| className='visibility-checkbox' | ||
| style={{ backgroundColor: visibilityToggle.colour }} | ||
| > | ||
| {visibilityToggle.isVisible && ( | ||
| <Icon name='checkmark' width={10} fill='white' /> | ||
| )} | ||
| </div> | ||
| <span className='text-muted fs-small'>Visible</span> | ||
| {visibilityToggle.isVisible && ( | ||
| <Icon name='checkmark' width={10} fill='white' /> | ||
| )} | ||
| </div> | ||
| )} | ||
| </div> | ||
| <span className='text-muted fs-small'>Visible</span> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import { FC } from 'react' | ||
| import Tooltip from 'components/Tooltip' | ||
| import UsageBadge, { BadgeTone } from './UsageBadge' | ||
| import { GraceState } from './types' | ||
|
|
||
| type GraceChipProps = { | ||
| grace: GraceState | ||
| daysLeft?: number | ||
| } | ||
|
|
||
| const TONE: Record<GraceState, BadgeTone> = { | ||
| available: 'success', | ||
| countdown: 'warning', | ||
| covering: 'info', | ||
| restricted: 'danger', | ||
| used: 'danger', | ||
| } | ||
|
|
||
| const LABEL: Record<GraceState, string> = { | ||
| available: 'Grace period: available', | ||
| countdown: 'Grace period: ending', | ||
| covering: 'Grace period: covering this period', | ||
| restricted: 'Restricted', | ||
| used: 'Grace period: used', | ||
| } | ||
|
|
||
| const EXPLANATION: Record<GraceState, string> = { | ||
| available: | ||
| 'Your first month over the limit is covered. We never cut off your API without warning.', | ||
| countdown: | ||
| 'You are over your limit. Flag serving pauses when the grace window ends, unless usage drops back under.', | ||
| covering: | ||
| 'You are over your limit, but this month is covered by your grace period, so there is no overage charge.', | ||
| restricted: | ||
| 'The grace window has passed. Flag serving and admin access are paused, but this page stays readable.', | ||
| used: 'Your grace period has already been used, so usage above the limit is charged as overage.', | ||
| } | ||
|
|
||
| /** PROTOTYPE (#8184). Grace period status, per the "Grace period states" design. */ | ||
| const GraceChip: FC<GraceChipProps> = ({ daysLeft, grace }) => { | ||
| const label = | ||
| grace === 'countdown' && daysLeft ? `${daysLeft} days left` : LABEL[grace] | ||
|
Comment on lines
+41
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Day counts read "1 days" in three messages. All three sites interpolate a day count into a hard-coded plural noun. One shared helper, for example
📍 Affects 3 files
|
||
|
|
||
| return ( | ||
| <Tooltip title={<UsageBadge tone={TONE[grace]}>{label}</UsageBadge>}> | ||
| {EXPLANATION[grace]} | ||
| </Tooltip> | ||
| ) | ||
| } | ||
|
|
||
| export default GraceChip | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| // Mirrors experiments/StatusBadge so the two read as one pattern. | ||
| .usage-badge { | ||
| display: inline-flex; | ||
| align-items: center; | ||
| gap: 4px; | ||
| padding: 3px 10px; | ||
| border-radius: var(--radius-full); | ||
| font-size: 11px; | ||
| font-weight: var(--font-weight-medium); | ||
| white-space: nowrap; | ||
|
|
||
| &__dot { | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: var(--radius-full); | ||
| } | ||
|
|
||
| &--success { | ||
| background: var(--color-surface-success); | ||
| color: var(--color-text-success); | ||
|
|
||
| .usage-badge__dot { | ||
| background: var(--color-text-success); | ||
| } | ||
| } | ||
|
|
||
| &--warning { | ||
| background: var(--color-surface-warning); | ||
| color: var(--color-text-warning); | ||
|
|
||
| .usage-badge__dot { | ||
| background: var(--color-text-warning); | ||
| } | ||
| } | ||
|
|
||
| &--danger { | ||
| background: var(--color-surface-danger); | ||
| color: var(--color-text-danger); | ||
|
|
||
| .usage-badge__dot { | ||
| background: var(--color-text-danger); | ||
| } | ||
| } | ||
|
|
||
| &--info { | ||
| background: var(--color-surface-info); | ||
| color: var(--color-text-info); | ||
|
|
||
| .usage-badge__dot { | ||
| background: var(--color-text-info); | ||
| } | ||
| } | ||
|
|
||
| &--neutral { | ||
| background: var(--color-surface-muted); | ||
| color: var(--color-text-secondary); | ||
|
|
||
| .usage-badge__dot { | ||
| background: var(--color-text-secondary); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Prevent the dashboard metric row from overflowing.
InstanceMetricsCardsrenders fiveStatItemelements in a non-wrapping row. These minimum widths require at least 996px, including its gaps. At narrower content widths, the row overflows instead of reflowing. Add wrapping in that consumer or make this minimum width layout-specific.