feat: updated auth queries with db data, updated typing, updated auth layout

This commit is contained in:
TzeYiing authored and Ziinc committed 2022-12-22 22:49:00 +08:00
1 parent 2a7c7871e6
commit dc43b8cbab
10 files changed
+126 -164

No files matched your search

+2
View File
@@ -16,3 +16,5 @@ window.matchMedia = jest.fn().mockReturnValue({
removeEventListener: jest.fn(),
})
export const StoreProvider = StoreProvider
export const withAuth = jest.fn()
+1
View File
@@ -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>
)
}
-3
View File
@@ -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)
+14 -14
View File
@@ -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)
})
})