From 97fee229d715128a7ef12743190818ea69e8e770 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Thu, 16 Feb 2023 18:59:16 +0800 Subject: [PATCH] feat: move filters to be in a line, fix filtering matching bug --- .../interfaces/Reports/ReportFilterBar.tsx | 175 ++++++++++++------ .../interfaces/Reports/ReportHeader.tsx | 52 ++---- .../interfaces/Reports/Reports.constants.ts | 8 +- .../project/[ref]/reports/api-overview.tsx | 10 +- 4 files changed, 143 insertions(+), 102 deletions(-) diff --git a/studio/components/interfaces/Reports/ReportFilterBar.tsx b/studio/components/interfaces/Reports/ReportFilterBar.tsx index 376842161e8..2b575078a0e 100644 --- a/studio/components/interfaces/Reports/ReportFilterBar.tsx +++ b/studio/components/interfaces/Reports/ReportFilterBar.tsx @@ -1,4 +1,4 @@ -import { keyBy } from 'lodash' +import { IconBox, IconCode, IconDatabase, IconKey, IconZap, IconZapOff } from '@supabase/ui' import React from 'react' import { useState } from 'react' import { Dropdown, Popover, Button, IconPlus, IconChevronDown, Select, Input, IconX } from 'ui' @@ -10,11 +10,76 @@ interface Props { filters: ReportFilterItem[] onAddFilter: (filter: ReportFilterItem) => void onRemoveFilter: (filter: ReportFilterItem) => void + onDatepickerChange: React.ComponentProps['onChange'] + datepickerTo?: string + datepickerFrom?: string } -const ReportFilterBar: React.FC = ({ filters, onAddFilter, onRemoveFilter }) => { - const filterKeys = ['request.path', 'request.host' , 'response.status_code'] +const PRODUCT_FILTERS = [ + { + key: 'rest', + filterKey: 'request.path', + filterValue: '/rest', + label: 'REST', + description: 'Requests made to PostgREST', + icon: IconDatabase, + }, + { + key: 'auth', + filterKey: 'request.path', + filterValue: '/auth', + label: 'Auth', + description: 'Authentication and authorization requests', + icon: IconKey, + }, + { + key: 'storage', + filterKey: 'request.path', + filterValue: '/storage', + label: 'Storage', + description: 'Storage asset requests', + icon: IconBox, + }, + { + key: 'realtime', + filterKey: 'request.path', + filterValue: '/realtime', + label: 'Realtime', + description: 'Realtime connection requests', + icon: IconZapOff, + }, + // TODO: support functions once union parsing is fixed + // { + // key: 'functions', + // filterKey: 'request.host', + // filterValue: '.functions.', + // label: 'Edge Functions', + // description: 'Edge function calls', + // icon: IconCode, + // }, + { + key: 'graphql', + filterKey: 'request.path', + filterValue: '/graphql', + label: 'GraphQL', + description: 'Requests made to pg_graphql', + icon: IconCode, + }, +] + +const ReportFilterBar: React.FC = ({ + filters, + onAddFilter, + onRemoveFilter, + onDatepickerChange, + datepickerTo = '', + datepickerFrom = '', +}) => { + const filterKeys = ['request.path', 'request.host', 'response.status_code'] const [showAdder, setShowAdder] = useState(false) + const [currentProductFilter, setCurrentProductFilter] = useState< + null | typeof PRODUCT_FILTERS[number] + >(null) const [addFilterValues, setAddFilterValues] = useState({ key: filterKeys[0], compare: 'is', @@ -30,67 +95,68 @@ const ReportFilterBar: React.FC = ({ filters, onAddFilter, onRemoveFilter } const handleProductFilterChange = async ( - filterToRemove: ReportFilterItem | null, - key: string + productFilter: null | typeof PRODUCT_FILTERS[number] ) => { - if (filterToRemove) { - await onRemoveFilter(filterToRemove) - } - if (key !== 'all') { - await onAddFilter({ - key: 'request.path', + setCurrentProductFilter(productFilter) + if (productFilter) { + await onRemoveFilter({ + key: productFilter.filterKey, compare: 'matches', - value: `/${key}/`, + value: productFilter.filterValue, + }) + } + if (productFilter !== null) { + await onAddFilter({ + key: productFilter.filterKey, + compare: 'matches', + value: productFilter.filterValue, }) } } - const getPathFilterMatchValue = (filter: ReportFilterItem) => { - return [...String(filter.value).matchAll(/\/(storage|realtime|auth|functions)\//g)] - } - const currentProductFilter = - filters.find((filter) => { - const matches = getPathFilterMatchValue(filter) - if (filter.key == 'request.path' && matches.length > 0) { - return true - } else { - return false - } - }) || null - const currentProductFilterKey = currentProductFilter - ? getPathFilterMatchValue(currentProductFilter)[0][1] - : 'all' return (
+ - handleProductFilterChange(currentProductFilter, 'all')}> - All + handleProductFilterChange(null)}> + All Requests - {[ - { key: 'rest', label: 'REST' }, - { key: 'auth', label: 'Auth' }, - { key: 'storage', label: 'Storage' }, - { key: 'realtime', label: 'Realtime' }, - { key: 'functions', label: 'Functions' }, - { key: 'graphql', label: 'GraphQL' }, - ].map(({ key, label }) => ( - handleProductFilterChange(currentProductFilter, key)} - > - - {label} - - - ))} + {PRODUCT_FILTERS.map((productFilter) => { + const Icon = productFilter.icon + return ( + handleProductFilterChange(productFilter)} + className="hover:bg-scale-600" + icon={} + > + + {productFilter.label} + + + {productFilter.description} + + + ) + })} } > @@ -100,18 +166,15 @@ const ReportFilterBar: React.FC = ({ filters, onAddFilter, onRemoveFilter className="inline-flex flex-row gap-2" iconRight={} > - {currentProductFilterKey === 'all' - ? 'All' - : `${currentProductFilterKey - .slice(0, 1) - .toUpperCase()}${currentProductFilterKey.slice( - 1, - currentProductFilterKey.length - )}`} + {currentProductFilter === null ? 'All Requests' : currentProductFilter.label} {filters - .filter((filter) => filter !== currentProductFilter) + .filter( + (filter) => + filter.value !== currentProductFilter?.filterValue && + filter.key !== currentProductFilter?.filterKey + ) .map((filter) => (
{filter.key} {filter.compare} {filter.value} diff --git a/studio/components/interfaces/Reports/ReportHeader.tsx b/studio/components/interfaces/Reports/ReportHeader.tsx index da3cdc35b1c..d04a1423bad 100644 --- a/studio/components/interfaces/Reports/ReportHeader.tsx +++ b/studio/components/interfaces/Reports/ReportHeader.tsx @@ -1,48 +1,28 @@ import { Button, IconRefreshCw } from 'ui' -import DatePickers from '../Settings/Logs/Logs.DatePickers' -import { REPORTS_DATEPICKER_HELPERS } from './Reports.constants' interface Props { title: string - onDatepickerChange: React.ComponentProps['onChange'] - datepickerTo?: string - datepickerFrom?: string onRefresh: () => void isLoading: boolean } -const ReportHeader: React.FC = ({ - title, - onDatepickerChange, - datepickerTo = '', - datepickerFrom = '', - onRefresh, - isLoading, -}) => ( -
+const ReportHeader: React.FC = ({ title, onRefresh, isLoading }) => ( +

{title}

-
- - -
+
) export default ReportHeader diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index fd5a45c8f56..18808f3a730 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -62,8 +62,7 @@ export const PRESET_CONFIG: Record = { select cast(timestamp_trunc(t.timestamp, hour) as datetime) as timestamp, count(t.id) as count - FROM - edge_logs t + FROM edge_logs t cross join unnest(metadata) as m cross join unnest(m.response) as response cross join unnest(m.request) as request @@ -79,13 +78,12 @@ export const PRESET_CONFIG: Record = { select cast(timestamp_trunc(t.timestamp, hour) as datetime) as timestamp, count(t.id) as count - FROM - edge_logs t + FROM edge_logs t cross join unnest(metadata) as m cross join unnest(m.response) as response cross join unnest(m.request) as request WHERE - f3.status_code >= 400 + response.status_code >= 400 ${generateRexepWhere(filters, false)} GROUP BY timestamp diff --git a/studio/pages/project/[ref]/reports/api-overview.tsx b/studio/pages/project/[ref]/reports/api-overview.tsx index c78616ae249..cd1e2bdab85 100644 --- a/studio/pages/project/[ref]/reports/api-overview.tsx +++ b/studio/pages/project/[ref]/reports/api-overview.tsx @@ -14,24 +14,24 @@ import { } from 'components/interfaces/Reports/renderers/ApiRenderers' import { useState, useEffect } from 'react' import ReportHeader from 'components/interfaces/Reports/ReportHeader' -import { LogsEndpointParams } from 'components/interfaces/Settings/Logs' +import { DatePickerToFrom, LogsEndpointParams } from 'components/interfaces/Settings/Logs' import ReportFilterBar from 'components/interfaces/Reports/ReportFilterBar' - export const ApiReport: NextPageWithLayout = () => { const report = useApiReport() + const handleDatepickerChange = (value: DatePickerToFrom) => {} return (
console.log(value)} - datepickerFrom={report.params.totalRequests.iso_timestamp_start} - datepickerTo={report.params.totalRequests.iso_timestamp_end} isLoading={report.isLoading} onRefresh={report.refresh} />