Skip to content

Commit 3b3a752

Browse files
authored
feat: add auth reports FE-1569 (supabase#36555)
* feat: add auth reports * fix reports dates, fix missing queries * add flag * fix errorbystatus chart * fix sign in by grant type * move formatting to auth report query file * fix signup by provider, add ratelimitted reqs * fix sign ups query * cleanup unused stuff * cleanup * rm console logs * undo logs.utils * add status code lib, use edge_logs for query * fix auth error chart footer * rm redundant rate limited requests chart * fix db report breaking due to useAuthReport throwing * move state up, reduce conditional logic in composed chart handler * reorder reports nav * rm bg * fix type err * fix useChartData * empty * fix report header bg * move to utils * add AnalyticsInterval import to report.utils for granularity conversion
1 parent 90d355b commit 3b3a752

File tree

15 files changed

+2194
-230
lines changed

15 files changed

+2194
-230
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* ReportChart
3+
*
4+
* A wrapper component that uses the useChartData hook to fetch data for a chart
5+
* and then passes the data and loading state to the ComposedChartHandler.
6+
*
7+
* This component acts as a bridge between the data-fetching logic and the
8+
* presentational chart component.
9+
*/
10+
import ComposedChartHandler from 'components/ui/Charts/ComposedChartHandler'
11+
import { useChartData } from 'hooks/useChartData'
12+
import type { UpdateDateRange } from 'pages/project/[ref]/reports/database'
13+
import type { MultiAttribute } from 'components/ui/Charts/ComposedChart.utils'
14+
15+
const ReportChart = ({
16+
chart,
17+
startDate,
18+
endDate,
19+
interval,
20+
updateDateRange,
21+
}: {
22+
chart: any
23+
startDate: string
24+
endDate: string
25+
interval: string
26+
updateDateRange: UpdateDateRange
27+
}) => {
28+
const {
29+
data,
30+
isLoading: isLoading,
31+
chartAttributes,
32+
highlightedValue,
33+
} = useChartData({
34+
attributes: chart.attributes,
35+
startDate,
36+
endDate,
37+
interval,
38+
data: undefined,
39+
highlightedValue:
40+
chart.id === 'client-connections' || chart.id === 'pgbouncer-connections'
41+
? true
42+
: chart.showMaxValue,
43+
})
44+
45+
return (
46+
<ComposedChartHandler
47+
{...chart}
48+
attributes={
49+
(chartAttributes.length > 0 ? chartAttributes : chart.attributes) as MultiAttribute[]
50+
}
51+
data={data}
52+
isLoading={isLoading}
53+
highlightedValue={highlightedValue as any}
54+
updateDateRange={updateDateRange}
55+
/>
56+
)
57+
}
58+
export default ReportChart

apps/studio/components/layouts/ReportsLayout/ReportsMenu.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ import { useProfile } from 'lib/profile'
1818
import { Menu, cn } from 'ui'
1919
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
2020
import { ReportMenuItem } from './ReportMenuItem'
21+
import { useFlag } from 'hooks/ui/useFlag'
2122

2223
const ReportsMenu = () => {
2324
const router = useRouter()
2425
const { profile } = useProfile()
2526
const { ref, id } = useParams()
2627
const pageKey = (id || router.pathname.split('/')[4]) as string
2728
const storageEnabled = useIsFeatureEnabled('project_storage:all')
29+
const authEnabled = useFlag('authreportv2')
2830

2931
const canCreateCustomReport = useCheckPermissions(PermissionAction.CREATE, 'user_content', {
3032
resource: { type: 'report', owner_id: profile?.id },
@@ -97,21 +99,29 @@ const ReportsMenu = () => {
9799
key: 'api-overview',
98100
url: `/project/${ref}/reports/api-overview`,
99101
},
100-
...(storageEnabled
102+
...(authEnabled
101103
? [
102104
{
103-
name: 'Storage',
104-
key: 'storage',
105-
url: `/project/${ref}/reports/storage`,
105+
name: 'Auth',
106+
key: 'auth',
107+
url: `/project/${ref}/reports/auth`,
106108
},
107109
]
108110
: []),
109-
110111
{
111112
name: 'Database',
112113
key: 'database',
113114
url: `/project/${ref}/reports/database`,
114115
},
116+
...(storageEnabled
117+
? [
118+
{
119+
name: 'Storage',
120+
key: 'storage',
121+
url: `/project/${ref}/reports/storage`,
122+
},
123+
]
124+
: []),
115125
],
116126
},
117127
]

apps/studio/components/ui/Charts/ComposedChart.tsx

Lines changed: 65 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,9 @@ import {
2828
} from './Charts.constants'
2929
import { CommonChartProps, Datum } from './Charts.types'
3030
import { numberFormatter, useChartSize } from './Charts.utils'
31-
import {
32-
calculateTotalChartAggregate,
33-
CustomLabel,
34-
CustomTooltip,
35-
type MultiAttribute,
36-
} from './ComposedChart.utils'
31+
import { calculateTotalChartAggregate, CustomLabel, CustomTooltip } from './ComposedChart.utils'
3732
import NoDataPlaceholder from './NoDataPlaceholder'
33+
import { MultiAttribute } from './ComposedChart.utils'
3834
import { ChartHighlight } from './useChartHighlight'
3935
import { formatBytes } from 'lib/helpers'
4036

@@ -58,6 +54,7 @@ export interface ComposedChartProps<D = Datum> extends CommonChartProps<D> {
5854
chartStyle?: string
5955
onChartStyleChange?: (style: string) => void
6056
updateDateRange: any
57+
titleTooltip?: string
6158
hideYAxis?: boolean
6259
hideHighlightedValue?: boolean
6360
syncId?: string
@@ -114,6 +111,22 @@ export default function ComposedChart({
114111

115112
const { Container } = useChartSize(size)
116113

114+
const day = (value: number | string) => (displayDateInUtc ? dayjs(value).utc() : dayjs(value))
115+
116+
const formatTimestamp = (ts: unknown) => {
117+
if (typeof ts !== 'number' && typeof ts !== 'string') {
118+
return ''
119+
}
120+
121+
// Timestamps from auth logs can be in microseconds
122+
if (typeof ts === 'number' && ts > 1e14) {
123+
return day(ts / 1000).format(customDateFormat)
124+
}
125+
126+
// dayjs can handle ISO strings and millisecond numbers
127+
return day(ts).format(customDateFormat)
128+
}
129+
117130
// Default props
118131
const _XAxisProps = XAxisProps || {
119132
interval: data.length - 2,
@@ -127,18 +140,19 @@ export default function ComposedChart({
127140
width: 0,
128141
}
129142

130-
const day = (value: number | string) => (displayDateInUtc ? dayjs(value).utc() : dayjs(value))
131-
132143
function getHeaderLabel() {
133144
if (!xAxisIsDate) {
134145
if (!focusDataIndex) return highlightedLabel
135-
return data[focusDataIndex]?.timestamp
146+
return data[focusDataIndex]?.[xAxisKey]
136147
}
137148
return (
138149
(focusDataIndex !== null &&
139150
data &&
140151
data[focusDataIndex] !== undefined &&
141-
day(data[focusDataIndex].timestamp).format(customDateFormat)) ||
152+
(() => {
153+
const ts = data[focusDataIndex][xAxisKey]
154+
return formatTimestamp(ts)
155+
})()) ||
142156
highlightedLabel
143157
)
144158
}
@@ -149,13 +163,45 @@ export default function ComposedChart({
149163
color: CHART_COLORS.REFERENCE_LINE,
150164
}
151165

166+
const chartData =
167+
data && !!data[0]
168+
? Object.entries(data[0])
169+
?.map(([key, value]) => ({
170+
name: key,
171+
value: value,
172+
}))
173+
.filter(
174+
(att) =>
175+
att.name !== 'timestamp' &&
176+
att.name !== 'period_start' &&
177+
att.name !== maxAttribute?.attribute &&
178+
attributes.some((attr) => attr.attribute === att.name && attr.enabled !== false)
179+
)
180+
.map((att, index) => {
181+
const attribute = attributes.find((attr) => attr.attribute === att.name)
182+
return {
183+
...att,
184+
color: attribute?.color
185+
? resolvedTheme?.includes('dark')
186+
? attribute.color.dark
187+
: attribute.color.light
188+
: STACKED_CHART_COLORS[index % STACKED_CHART_COLORS.length],
189+
}
190+
})
191+
: []
192+
152193
const lastDataPoint = !!data[data.length - 1]
153194
? Object.entries(data[data.length - 1])
154195
.map(([key, value]) => ({
155196
dataKey: key,
156197
value: value as number,
157198
}))
158-
.filter((entry) => entry.dataKey !== 'timestamp')
199+
.filter(
200+
(entry) =>
201+
entry.dataKey !== 'timestamp' &&
202+
entry.dataKey !== 'period_start' &&
203+
attributes.some((attr) => attr.attribute === entry.dataKey && attr.enabled !== false)
204+
)
159205
: undefined
160206
const referenceLines = attributes.filter((attribute) => attribute?.provider === 'reference-line')
161207

@@ -184,32 +230,17 @@ export default function ComposedChart({
184230
chartHighlight?.coordinates.right &&
185231
chartHighlight?.coordinates.left !== chartHighlight?.coordinates.right
186232

187-
const chartData =
188-
data && !!data[0]
189-
? Object.entries(data[0])
190-
?.map(([key, value], index) => ({
191-
name: key,
192-
value: value,
193-
color: STACKED_CHART_COLORS[index - (1 % STACKED_CHART_COLORS.length)],
194-
}))
195-
.filter(
196-
(att) =>
197-
att.name !== 'timestamp' &&
198-
att.name !== maxAttribute?.attribute &&
199-
!referenceLines.map((a) => a.attribute).includes(att.name)
200-
)
201-
: []
202-
203233
const stackedAttributes = chartData.filter((att) => !att.name.includes('max'))
204234
const isPercentage = format === '%'
205235
const isRamChart = chartData?.some((att: any) => att.name.toLowerCase().includes('ram_'))
206236
const isDiskSpaceChart = chartData?.some((att: any) =>
207237
att.name.toLowerCase().includes('disk_space_')
208238
)
209-
const isDiskSizeChart = chartData?.some((att: any) => att.name.toLowerCase().includes('disk_fs_'))
239+
const isDBSizeChart = chartData?.some((att: any) =>
240+
att.name.toLowerCase().includes('pg_database_size')
241+
)
210242
const isNetworkChart = chartData?.some((att: any) => att.name.toLowerCase().includes('network_'))
211-
const shouldFormatBytes = isRamChart || isDiskSpaceChart || isDiskSizeChart || isNetworkChart
212-
243+
const shouldFormatBytes = isRamChart || isDiskSpaceChart || isDBSizeChart || isNetworkChart
213244
//*
214245
// Set the y-axis domain
215246
// to the highest value in the chart data for percentage charts
@@ -387,6 +418,7 @@ export default function ComposedChart({
387418
y={line.value}
388419
strokeWidth={1}
389420
{...line}
421+
color={line.color?.dark}
390422
strokeDasharray={line.strokeDasharray ?? '3 3'}
391423
label={undefined}
392424
>
@@ -418,13 +450,11 @@ export default function ComposedChart({
418450
className="text-foreground-lighter -mt-9 flex items-center justify-between text-xs"
419451
style={{ marginLeft: YAxisProps?.width }}
420452
>
421-
<span>
422-
{xAxisIsDate ? day(data[0]?.timestamp).format(customDateFormat) : data[0]?.timestamp}
423-
</span>
453+
<span>{xAxisIsDate ? formatTimestamp(data[0]?.[xAxisKey]) : data[0]?.[xAxisKey]}</span>
424454
<span>
425455
{xAxisIsDate
426-
? day(data[data?.length - 1]?.timestamp).format(customDateFormat)
427-
: data[data?.length - 1]?.timestamp}
456+
? formatTimestamp(data[data.length - 1]?.[xAxisKey])
457+
: data[data.length - 1]?.[xAxisKey]}
428458
</span>
429459
</div>
430460
)}

apps/studio/components/ui/Charts/ComposedChart.utils.tsx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,19 @@ export interface ReportAttributes {
3131
hideHighlightedValue?: boolean
3232
}
3333

34-
type Provider = 'infra-monitoring' | 'daily-stats' | 'reference-line' | 'combine'
34+
export type Provider = 'infra-monitoring' | 'daily-stats' | 'mock' | 'reference-line' | 'logs'
3535

3636
export type MultiAttribute = {
3737
attribute: string
3838
provider: Provider
3939
label?: string
40-
color?: string
40+
color?: {
41+
light: string
42+
dark: string
43+
}
44+
statusCode?: string
45+
grantType?: string
46+
providerType?: string
4147
stackId?: string
4248
format?: string
4349
description?: string
@@ -65,6 +71,7 @@ export type MultiAttribute = {
6571
strokeDasharray?: string
6672
className?: string
6773
hide?: boolean
74+
enabled?: boolean
6875
}
6976

7077
interface CustomIconProps {
@@ -167,7 +174,7 @@ const CustomTooltip = ({
167174
return (
168175
<div key={entry.name} className="flex items-center w-full">
169176
{getIcon(entry.color, isMax)}
170-
<span className="text-foreground-lighter ml-1 flex-grow">
177+
<span className="text-foreground-lighter ml-1 flex-grow cursor-default select-none">
171178
{attribute?.label || entry.name}
172179
</span>
173180
<span className="ml-3.5 flex items-end gap-1">
@@ -194,9 +201,12 @@ const CustomTooltip = ({
194201
>
195202
<p className="font-medium">{dayjs(timestamp).format(DateTimeFormats.FULL_SECONDS)}</p>
196203
<div className="grid gap-0">
197-
{payload.reverse().map((entry: any, index: number) => (
198-
<LabelItem key={`${entry.name}-${index}`} entry={entry} />
199-
))}
204+
{payload
205+
.reverse()
206+
.filter((entry: any) => entry.value !== 0)
207+
.map((entry: any, index: number) => (
208+
<LabelItem key={`${entry.name}-${index}`} entry={entry} />
209+
))}
200210
{active && showTotal && (
201211
<div className="flex md:flex-col gap-1 md:gap-0 text-foreground mt-1">
202212
<span className="flex-grow text-foreground-lighter">Total</span>
@@ -270,7 +280,7 @@ const CustomLabel = ({ payload, attributes, showMaxValue, onLabelHover }: Custom
270280
{getIcon(entry.name, entry.color)}
271281
<span
272282
className={cn(
273-
'text-nowrap text-foreground-lighter',
283+
'text-nowrap text-foreground-lighter cursor-default select-none',
274284
hoveredLabel && !isHovered && 'opacity-50'
275285
)}
276286
>

0 commit comments

Comments
 (0)