From dc43b8cbab01463e66834d2f72315d44fd4ac010 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Mon, 12 Dec 2022 18:56:05 +0800 Subject: [PATCH] feat: updated auth queries with db data, updated typing, updated auth layout --- studio/__mocks__/hooks.js | 2 + studio/__mocks__/lib/common/fetch.js | 1 + .../interfaces/Reports/Reports.constants.ts | 30 +++---- .../Reports/renderers/AuthRenderers.tsx | 65 +++++++------- studio/hooks/analytics/useDbQuery.tsx | 3 - .../pages/project/[ref]/reports/api-bots.tsx | 10 +-- .../project/[ref]/reports/api-overview.tsx | 12 +-- studio/pages/project/[ref]/reports/auth.tsx | 28 +++---- .../components/Reports/PresetReport.test.js | 84 ------------------- .../projects/reports/product-reports.test.js | 55 ++++++++++++ 10 files changed, 126 insertions(+), 164 deletions(-) delete mode 100644 studio/tests/components/Reports/PresetReport.test.js create mode 100644 studio/tests/pages/projects/reports/product-reports.test.js diff --git a/studio/__mocks__/hooks.js b/studio/__mocks__/hooks.js index bfacf3748e5..e6d89720b13 100644 --- a/studio/__mocks__/hooks.js +++ b/studio/__mocks__/hooks.js @@ -16,3 +16,5 @@ window.matchMedia = jest.fn().mockReturnValue({ removeEventListener: jest.fn(), }) export const StoreProvider = StoreProvider + +export const withAuth = jest.fn() \ No newline at end of file diff --git a/studio/__mocks__/lib/common/fetch.js b/studio/__mocks__/lib/common/fetch.js index b957643e53b..ab231111a61 100644 --- a/studio/__mocks__/lib/common/fetch.js +++ b/studio/__mocks__/lib/common/fetch.js @@ -1 +1,2 @@ export const get = jest.fn() +export const post = jest.fn() diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index 797ee40d311..59ef87fd261 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -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 `, }, }, diff --git a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx index f719a91f1da..f85ddabeece 100644 --- a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx @@ -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 = ( ) } -export const renderDailyActiveUsers = ( - props: ReportWidgetProps<{ - count: number - timestamp: string - }> -) => { - return ( - <> -
- -
- - ) -} 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 ( - <> -
- -
- +
+ +
+ ) +} + +export const renderDailyActiveUsers = ( + props: ReportWidgetProps<{ + count: number + timestamp: string + }> +) => { + return ( +
+ +
) } diff --git a/studio/hooks/analytics/useDbQuery.tsx b/studio/hooks/analytics/useDbQuery.tsx index 979f30a1e5d..9dbbf902440 100644 --- a/studio/hooks/analytics/useDbQuery.tsx +++ b/studio/hooks/analytics/useDbQuery.tsx @@ -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(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() }] diff --git a/studio/pages/project/[ref]/reports/api-bots.tsx b/studio/pages/project/[ref]/reports/api-bots.tsx index 5f4198ac55a..5da674c7a09 100644 --- a/studio/pages/project/[ref]/reports/api-bots.tsx +++ b/studio/pages/project/[ref]/reports/api-bots.tsx @@ -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} /> { 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} /> @@ -45,6 +45,6 @@ export const ApiBotsPage: NextPageWithLayout = () => { ) } -ApiBotsPage.getLayout = (page) => {page} +ApiBotsReport.getLayout = (page) => {page} -export default observer(ApiBotsPage) +export default observer(ApiBotsReport) diff --git a/studio/pages/project/[ref]/reports/api-overview.tsx b/studio/pages/project/[ref]/reports/api-overview.tsx index 7f2881877bb..ad9c4d2ecdc 100644 --- a/studio/pages/project/[ref]/reports/api-overview.tsx +++ b/studio/pages/project/[ref]/reports/api-overview.tsx @@ -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} /> { 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} /> { 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} /> @@ -57,6 +57,6 @@ export const ApiOverviewPage: NextPageWithLayout = () => { ) } -ApiOverviewPage.getLayout = (page) => {page} +ApiOverviewReport.getLayout = (page) => {page} -export default observer(ApiOverviewPage) +export default observer(ApiOverviewReport) diff --git a/studio/pages/project/[ref]/reports/auth.tsx b/studio/pages/project/[ref]/reports/auth.tsx index 1d1557f2444..5e6c6781a79 100644 --- a/studio/pages/project/[ref]/reports/auth.tsx +++ b/studio/pages/project/[ref]/reports/auth.tsx @@ -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} /> + - ) } -ApiBotsPage.getLayout = (page) => {page} +AuthReport.getLayout = (page) => {page} -export default observer(ApiBotsPage) +export default observer(AuthReport) diff --git a/studio/tests/components/Reports/PresetReport.test.js b/studio/tests/components/Reports/PresetReport.test.js deleted file mode 100644 index 7813c376821..00000000000 --- a/studio/tests/components/Reports/PresetReport.test.js +++ /dev/null @@ -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 ( - new Map(), - shouldRetryOnError: false, - }} - > - - - ) -}) - -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() - await screen.findByText(/Last 7 days/) - await screen.findAllByText(/Refresh/) -}) - -test('changing date range triggers query refresh', async () => { - render() - await waitFor(() => expect(get).toBeCalled()) - get.mockReset() - const refresh = await screen.findByText(/Refresh/) - fireEvent.click(refresh) - await waitFor(() => expect(get).toBeCalled()) -}) diff --git a/studio/tests/pages/projects/reports/product-reports.test.js b/studio/tests/pages/projects/reports/product-reports.test.js new file mode 100644 index 00000000000..a30e33c1aea --- /dev/null +++ b/studio/tests/pages/projects/reports/product-reports.test.js @@ -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() + + contains.forEach((word) => { + expect(screen.findByText(word)).resolves.toBeTruthy() + }) + }) + test('static elements', async () => { + render() + await screen.findByText(/Last 7 days/) + await screen.findAllByText(/Refresh/) + }) + + test('changing date range triggers query refresh', async () => { + render() + 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) + }) +})