mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
feat: updated auth queries with db data, updated typing, updated auth layout
This commit is contained in:
1 parent
2a7c7871e6
commit
dc43b8cbab
10 files changed
+126
-164
No files matched your search
@@ -16,3 +16,5 @@ window.matchMedia = jest.fn().mockReturnValue({
|
||||
removeEventListener: jest.fn(),
|
||||
})
|
||||
export const StoreProvider = StoreProvider
|
||||
|
||||
export const withAuth = jest.fn()
|
||||
@@ -1 +1,2 @@
|
||||
export const get = jest.fn()
|
||||
export const post = jest.fn()
|
||||
@@ -180,6 +180,18 @@ order by
|
||||
cumulativeUsers: {
|
||||
queryType: 'db',
|
||||
sql: `
|
||||
|
||||
select
|
||||
date_trunc('day', u.created_at) as timestamp,
|
||||
sum(count(u.id)) over (order by date_trunc('day', u.created_at)) as count
|
||||
from auth.users as u
|
||||
group by timestamp
|
||||
order by timestamp desc
|
||||
`,
|
||||
},
|
||||
newUsers: {
|
||||
queryType: 'db',
|
||||
sql: `
|
||||
select
|
||||
date_trunc('day', u.created_at) as timestamp,
|
||||
count(u.id) as count
|
||||
@@ -189,24 +201,6 @@ group by
|
||||
timestamp
|
||||
order by
|
||||
timestamp desc
|
||||
`,
|
||||
},
|
||||
newUsers: {
|
||||
queryType: 'logs',
|
||||
sql: `
|
||||
select
|
||||
TIMESTAMP_TRUNC(f.timestamp, DAY) as timestamp,
|
||||
count(id) as count
|
||||
from
|
||||
gotrue_logs f
|
||||
cross join unnest(f.metadata) as m
|
||||
where f.timestamp > "2022-11-20"
|
||||
and m.project = "tzljvicuuxegpzwuwife"
|
||||
and JSON_VALUE(f.event_message, "$.path") = "/signup"
|
||||
and JSON_VALUE(f.event_message, "$.auth_event.actor_id") is not null
|
||||
and JSON_VALUE(f.event_message, "$.auth_event.action") != "user_repeated_signup"
|
||||
group by
|
||||
timestamp
|
||||
`,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { BarChart } from 'components/to-be-cleaned/Charts/ChartRenderer'
|
||||
import { first } from 'lodash'
|
||||
import { DATETIME_FORMAT } from '../Reports.constants'
|
||||
import { ReportWidgetProps } from '../ReportWidget'
|
||||
|
||||
@@ -23,27 +22,6 @@ export const renderCumulativeUsers = (
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export const renderDailyActiveUsers = (
|
||||
props: ReportWidgetProps<{
|
||||
count: number
|
||||
timestamp: string
|
||||
}>
|
||||
) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex w-full flex-col">
|
||||
<BarChart
|
||||
data={props.data}
|
||||
attribute="count"
|
||||
label=""
|
||||
minimalHeader
|
||||
customDateFormat={DATETIME_FORMAT}
|
||||
noDataMessage={'No authenticated users during this time period'}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const renderNewUsers = (
|
||||
props: ReportWidgetProps<{
|
||||
@@ -51,18 +29,37 @@ export const renderNewUsers = (
|
||||
timestamp: string
|
||||
}>
|
||||
) => {
|
||||
const sum = (props.data || []).reduce((acc, datum) => datum.count + acc, 0)
|
||||
return (
|
||||
<>
|
||||
<div className="flex w-full flex-col">
|
||||
<BarChart
|
||||
data={props.data}
|
||||
attribute="count"
|
||||
label=""
|
||||
minimalHeader
|
||||
customDateFormat={DATETIME_FORMAT}
|
||||
noDataMessage={'No new user signups during this time period'}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
<div className="flex w-full flex-col">
|
||||
<BarChart
|
||||
data={props.data}
|
||||
attribute="count"
|
||||
label="New users"
|
||||
highlightedValue={sum}
|
||||
customDateFormat={DATETIME_FORMAT}
|
||||
noDataMessage={'No new user signups during this time period'}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const renderDailyActiveUsers = (
|
||||
props: ReportWidgetProps<{
|
||||
count: number
|
||||
timestamp: string
|
||||
}>
|
||||
) => {
|
||||
return (
|
||||
<div className="flex w-full flex-col">
|
||||
<BarChart
|
||||
data={props.data}
|
||||
attribute="count"
|
||||
label=""
|
||||
minimalHeader
|
||||
customDateFormat={DATETIME_FORMAT}
|
||||
noDataMessage={'No authenticated users during this time period'}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -14,7 +14,6 @@ const useDbQuery: UseDbQuery = (sql, params = DEFAULT_QUERY_PARAMS) => {
|
||||
const { meta } = useStore()
|
||||
|
||||
const resolvedSql = typeof sql === 'function' ? sql(params) : sql
|
||||
console.log('useDbQuery', 'sql', resolvedSql)
|
||||
const {
|
||||
data,
|
||||
error: swrError,
|
||||
@@ -23,8 +22,6 @@ const useDbQuery: UseDbQuery = (sql, params = DEFAULT_QUERY_PARAMS) => {
|
||||
} = useSWR<MetaQueryResponse>(resolvedSql, async () => await meta.query(resolvedSql), {
|
||||
revalidateOnFocus: false,
|
||||
})
|
||||
console.log('useDbQuery', 'data', data)
|
||||
console.log('useDbQuery', 'isLoading', isLoading)
|
||||
|
||||
const error = swrError || (typeof data === 'object' ? data?.error : '')
|
||||
return [{ error, data, isLoading, params }, { runQuery: () => mutate() }]
|
||||
|
||||
@@ -9,7 +9,7 @@ import { PRESET_CONFIG } from 'components/interfaces/Reports/Reports.constants'
|
||||
import { renderBotScores, renderUserAgents } from 'components/interfaces/Reports/renderers/ApiBotsRenderers'
|
||||
import ReportWidget from 'components/interfaces/Reports/ReportWidget'
|
||||
|
||||
export const ApiBotsPage: NextPageWithLayout = () => {
|
||||
export const ApiBotsReport: NextPageWithLayout = () => {
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
const config = PRESET_CONFIG[Presets.API_BOTS]
|
||||
@@ -28,7 +28,7 @@ export const ApiBotsPage: NextPageWithLayout = () => {
|
||||
className="col-span-4 col-start-1"
|
||||
title="Top User Agents"
|
||||
description="The types of user agents using the API and their organization source."
|
||||
data={userAgents[0].logData}
|
||||
data={userAgents[0].logData || []}
|
||||
renderer={renderUserAgents}
|
||||
/>
|
||||
<ReportWidget
|
||||
@@ -37,7 +37,7 @@ export const ApiBotsPage: NextPageWithLayout = () => {
|
||||
className="col-span-4 col-start-1"
|
||||
title="Bot-like API Requests"
|
||||
description="Suspicious bot-like requests, as flagged by Cloudflare"
|
||||
data={botScores[0].logData}
|
||||
data={botScores[0].logData || []}
|
||||
renderer={renderBotScores}
|
||||
/>
|
||||
</div>
|
||||
@@ -45,6 +45,6 @@ export const ApiBotsPage: NextPageWithLayout = () => {
|
||||
)
|
||||
}
|
||||
|
||||
ApiBotsPage.getLayout = (page) => <ReportsLayout>{page}</ReportsLayout>
|
||||
ApiBotsReport.getLayout = (page) => <ReportsLayout>{page}</ReportsLayout>
|
||||
|
||||
export default observer(ApiBotsPage)
|
||||
export default observer(ApiBotsReport)
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
renderStatusCodesChart,
|
||||
} from 'components/interfaces/Reports/renderers/OverviewRenderers'
|
||||
|
||||
export const ApiOverviewPage: NextPageWithLayout = () => {
|
||||
export const ApiOverviewReport: NextPageWithLayout = () => {
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
const config = PRESET_CONFIG[Presets.API_OVERVIEW]
|
||||
@@ -31,7 +31,7 @@ export const ApiOverviewPage: NextPageWithLayout = () => {
|
||||
className="col-span-4 col-start-1"
|
||||
title="API Status Codes"
|
||||
description="Distribution of API responses by status codes."
|
||||
data={statusCodes[0].logData}
|
||||
data={statusCodes[0].logData || []}
|
||||
renderer={renderStatusCodesChart}
|
||||
/>
|
||||
<ReportWidget
|
||||
@@ -40,7 +40,7 @@ export const ApiOverviewPage: NextPageWithLayout = () => {
|
||||
className="col-span-4 col-start-1"
|
||||
title="Error Rate"
|
||||
description="Percentage of API 5XX and 4XX error responses."
|
||||
data={errorRates[0].logData}
|
||||
data={errorRates[0].logData || []}
|
||||
renderer={renderErrorRateChart}
|
||||
/>
|
||||
<ReportWidget
|
||||
@@ -49,7 +49,7 @@ export const ApiOverviewPage: NextPageWithLayout = () => {
|
||||
className="col-span-4 col-start-1"
|
||||
title="Slow API Requests"
|
||||
description="Most frequently used requests, sorted by total querying time"
|
||||
data={requestPaths[0].logData}
|
||||
data={requestPaths[0].logData || []}
|
||||
renderer={renderRequestsPathsTable}
|
||||
/>
|
||||
</div>
|
||||
@@ -57,6 +57,6 @@ export const ApiOverviewPage: NextPageWithLayout = () => {
|
||||
)
|
||||
}
|
||||
|
||||
ApiOverviewPage.getLayout = (page) => <ReportsLayout>{page}</ReportsLayout>
|
||||
ApiOverviewReport.getLayout = (page) => <ReportsLayout>{page}</ReportsLayout>
|
||||
|
||||
export default observer(ApiOverviewPage)
|
||||
export default observer(ApiOverviewReport)
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
renderCumulativeUsers,
|
||||
} from 'components/interfaces/Reports/renderers/AuthRenderers'
|
||||
|
||||
export const ApiBotsPage: NextPageWithLayout = () => {
|
||||
export const AuthReport: NextPageWithLayout = () => {
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
const config = PRESET_CONFIG[Presets.AUTH]
|
||||
@@ -31,33 +31,33 @@ export const ApiBotsPage: NextPageWithLayout = () => {
|
||||
params={cumulativeUsers[0].params}
|
||||
className="col-span-2 col-start-1"
|
||||
title="Users Over Time"
|
||||
description="Daily count of confirmed users"
|
||||
description="Cumulative count of confirmed users"
|
||||
data={cumulativeUsers[0].data!}
|
||||
renderer={renderCumulativeUsers}
|
||||
/>
|
||||
<ReportWidget
|
||||
isLoading={isLoading}
|
||||
params={dailyActiveUsers[0].params}
|
||||
params={newUsers[0].params}
|
||||
className="col-span-2 col-start-3"
|
||||
title="Daily New Users"
|
||||
description="The daily count of new user signups"
|
||||
data={newUsers[0].data!}
|
||||
renderer={renderNewUsers}
|
||||
/>
|
||||
<ReportWidget
|
||||
isLoading={isLoading}
|
||||
params={dailyActiveUsers[0].params}
|
||||
className="col-span-4 col-start-1"
|
||||
title="Daily Active Users"
|
||||
description="The daily count of active authenticated users"
|
||||
data={dailyActiveUsers[0].logData!}
|
||||
renderer={renderDailyActiveUsers}
|
||||
/>
|
||||
<ReportWidget
|
||||
isLoading={isLoading}
|
||||
params={newUsers[0].params}
|
||||
className="col-span-4 col-start-1"
|
||||
title="Daily New Users"
|
||||
description="The daily count of new user signups"
|
||||
data={newUsers[0].logData!}
|
||||
renderer={renderNewUsers}
|
||||
/>
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
ApiBotsPage.getLayout = (page) => <ReportsLayout>{page}</ReportsLayout>
|
||||
AuthReport.getLayout = (page) => <ReportsLayout>{page}</ReportsLayout>
|
||||
|
||||
export default observer(ApiBotsPage)
|
||||
export default observer(AuthReport)
|
||||
@@ -1,84 +0,0 @@
|
||||
import dayjs from 'dayjs'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
// mock the fetch function
|
||||
jest.mock('lib/common/fetch')
|
||||
import { get } from 'lib/common/fetch'
|
||||
|
||||
// mock mobx
|
||||
jest.mock('mobx-react-lite')
|
||||
import { observer } from 'mobx-react-lite'
|
||||
observer.mockImplementation((v) => v)
|
||||
|
||||
// mock the router
|
||||
jest.mock('next/router')
|
||||
import { useRouter } from 'next/router'
|
||||
const defaultRouterMock = () => {
|
||||
const router = jest.fn()
|
||||
router.query = {}
|
||||
router.push = jest.fn()
|
||||
router.pathname = 'logs/path'
|
||||
return router
|
||||
}
|
||||
useRouter.mockReturnValue(defaultRouterMock())
|
||||
// mock usage flags
|
||||
jest.mock('components/ui/Flag/Flag')
|
||||
import Flag from 'components/ui/Flag/Flag'
|
||||
Flag.mockImplementation(({ children }) => <>{children}</>)
|
||||
jest.mock('hooks')
|
||||
import { useFlag } from 'hooks'
|
||||
useFlag.mockReturnValue(true)
|
||||
|
||||
import { SWRConfig } from 'swr'
|
||||
jest.mock('components/interfaces/Reports/PresetReport')
|
||||
import PresetReport from 'components/interfaces/Reports/PresetReport'
|
||||
PresetReport.mockImplementation((props) => {
|
||||
const Comp = jest.requireActual('components/interfaces/Reports/PresetReport').default
|
||||
// wrap with SWR to reset the cache each time
|
||||
return (
|
||||
<SWRConfig
|
||||
value={{
|
||||
provider: () => new Map(),
|
||||
shouldRetryOnError: false,
|
||||
}}
|
||||
>
|
||||
<Comp {...props} />
|
||||
</SWRConfig>
|
||||
)
|
||||
})
|
||||
|
||||
jest.mock('hooks')
|
||||
import { useProjectSubscription } from 'hooks'
|
||||
useProjectSubscription = jest.fn((ref) => ({
|
||||
subscription: {
|
||||
tier: {
|
||||
supabase_prod_id: 'tier_free',
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
import { render, fireEvent, waitFor, screen, act } from '@testing-library/react'
|
||||
import { Presets } from 'components/interfaces/Reports/Reports.types'
|
||||
beforeEach(() => {
|
||||
// reset mocks between tests
|
||||
get.mockReset()
|
||||
useRouter.mockReset()
|
||||
useRouter.mockReturnValue(defaultRouterMock())
|
||||
})
|
||||
|
||||
test('static elements', async () => {
|
||||
render(<PresetReport preset={Presets.OVERVIEW} />)
|
||||
await screen.findByText(/Last 7 days/)
|
||||
await screen.findAllByText(/Refresh/)
|
||||
})
|
||||
|
||||
test('changing date range triggers query refresh', async () => {
|
||||
render(<PresetReport preset={Presets.OVERVIEW} />)
|
||||
await waitFor(() => expect(get).toBeCalled())
|
||||
get.mockReset()
|
||||
const refresh = await screen.findByText(/Refresh/)
|
||||
fireEvent.click(refresh)
|
||||
await waitFor(() => expect(get).toBeCalled())
|
||||
})
|
||||
@@ -0,0 +1,55 @@
|
||||
import dayjs from 'dayjs'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
dayjs.extend(utc)
|
||||
|
||||
import { get, post } from 'lib/common/fetch'
|
||||
|
||||
import { useRouter } from 'next/router'
|
||||
import { render } from '../../../helpers'
|
||||
import { fireEvent, waitFor, screen } from '@testing-library/react'
|
||||
import { ApiOverviewReport } from 'pages/project/[ref]/reports/api-overview'
|
||||
import { ApiBotsReport } from 'pages/project/[ref]/reports/api-bots'
|
||||
import { AuthReport } from 'pages/project/[ref]/reports/auth'
|
||||
|
||||
beforeEach(() => {
|
||||
// reset mocks between tests
|
||||
get.mockReset()
|
||||
useRouter.mockReset()
|
||||
useRouter.mockReturnValue({
|
||||
query: { ref: '123' },
|
||||
})
|
||||
get.mockImplementation(async (url) => {
|
||||
return [{ data: [] }]
|
||||
})
|
||||
post.mockResolvedValue([])
|
||||
})
|
||||
|
||||
describe.each([
|
||||
{ Page: ApiOverviewReport, contains: ['API Overview'] },
|
||||
{ Page: ApiBotsReport, contains: ['API Bots'] },
|
||||
{ Page: AuthReport, contains: ['Auth'] },
|
||||
])('$Page rendering', ({ Page, contains }) => {
|
||||
test(`contains ${contains}`, async () => {
|
||||
render(<Page />)
|
||||
|
||||
contains.forEach((word) => {
|
||||
expect(screen.findByText(word)).resolves.toBeTruthy()
|
||||
})
|
||||
})
|
||||
test('static elements', async () => {
|
||||
render(<Page />)
|
||||
await screen.findByText(/Last 7 days/)
|
||||
await screen.findAllByText(/Refresh/)
|
||||
})
|
||||
|
||||
test('changing date range triggers query refresh', async () => {
|
||||
render(<Page />)
|
||||
await waitFor(() => expect(get).toBeCalled())
|
||||
get.mockReset()
|
||||
const refresh = await screen.findByText(/Refresh/)
|
||||
fireEvent.click(refresh)
|
||||
|
||||
const calls = get.mock.calls.concat(post.mock.calls)
|
||||
expect(calls.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user