feat: move filters to be in a line, fix filtering matching bug

This commit is contained in:
TzeYiing authored and Ziinc committed 2023-03-01 19:37:06 +08:00
1 parent df160e8597
commit 97fee229d7
4 files changed
+143 -102

No files matched your search

@@ -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<typeof DatePickers>['onChange']
datepickerTo?: string
datepickerFrom?: string
}
const ReportFilterBar: React.FC<Props> = ({ 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<Props> = ({
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<ReportFilterItem>({
key: filterKeys[0],
compare: 'is',
@@ -30,67 +95,68 @@ const ReportFilterBar: React.FC<Props> = ({ 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 (
<div>
<div className="flex flex-row justify-start items-center flex-wrap gap-2">
<DatePickers
onChange={onDatepickerChange}
to={datepickerTo}
from={datepickerFrom}
helpers={REPORTS_DATEPICKER_HELPERS}
/>
<Dropdown
size="small"
side="bottom"
align="start"
overlay={
<>
<Dropdown.Item onClick={() => handleProductFilterChange(currentProductFilter, 'all')}>
All
<Dropdown.Item onClick={() => handleProductFilterChange(null)}>
All Requests
</Dropdown.Item>
<Dropdown.Separator />
{[
{ 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 }) => (
<Dropdown.Item
key={key}
disabled={key === currentProductFilterKey}
onClick={() => handleProductFilterChange(currentProductFilter, key)}
>
<span className={[key === currentProductFilterKey ? 'font-bold' : ''].join(' ')}>
{label}
</span>
</Dropdown.Item>
))}
{PRODUCT_FILTERS.map((productFilter) => {
const Icon = productFilter.icon
return (
<Dropdown.Item
key={productFilter.key}
disabled={productFilter.key === currentProductFilter?.key}
onClick={() => handleProductFilterChange(productFilter)}
className="hover:bg-scale-600"
icon={<Icon size={24} />}
>
<span
className={[
productFilter.key === currentProductFilter?.key ? 'font-bold' : '',
'inline-block',
].join(' ')}
>
{productFilter.label}
</span>
<span className=" text-left text-scale-1000 inline-block">
{productFilter.description}
</span>
</Dropdown.Item>
)
})}
</>
}
>
@@ -100,18 +166,15 @@ const ReportFilterBar: React.FC<Props> = ({ filters, onAddFilter, onRemoveFilter
className="inline-flex flex-row gap-2"
iconRight={<IconChevronDown size={14} />}
>
{currentProductFilterKey === 'all'
? 'All'
: `${currentProductFilterKey
.slice(0, 1)
.toUpperCase()}${currentProductFilterKey.slice(
1,
currentProductFilterKey.length
)}`}
{currentProductFilter === null ? 'All Requests' : currentProductFilter.label}
</Button>
</Dropdown>
{filters
.filter((filter) => filter !== currentProductFilter)
.filter(
(filter) =>
filter.value !== currentProductFilter?.filterValue &&
filter.key !== currentProductFilter?.filterKey
)
.map((filter) => (
<div className="text-xs rounded border border-scale-1100 bg-scale-500 px-1 h-7 flex flex-row justify-center gap-1 items-center">
{filter.key} {filter.compare} {filter.value}
@@ -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<typeof DatePickers>['onChange']
datepickerTo?: string
datepickerFrom?: string
onRefresh: () => void
isLoading: boolean
}
const ReportHeader: React.FC<Props> = ({
title,
onDatepickerChange,
datepickerTo = '',
datepickerFrom = '',
onRefresh,
isLoading,
}) => (
<div className="flex flex-col gap-4">
const ReportHeader: React.FC<Props> = ({ title, onRefresh, isLoading }) => (
<div className="flex flex-row justify-between gap-4">
<h1 className="text-2xl text-scale-1200">{title}</h1>
<div className="flex flex-row justify-start gap-4">
<DatePickers
onChange={onDatepickerChange}
to={datepickerTo}
from={datepickerFrom}
helpers={REPORTS_DATEPICKER_HELPERS}
/>
<Button
type="default"
size="tiny"
onClick={onRefresh}
disabled={isLoading ? true : false}
icon={
<IconRefreshCw
size="tiny"
className={`text-scale-1100 ${isLoading ? 'animate-spin' : ''}`}
/>
}
>
Refresh
</Button>
</div>
<Button
type="default"
size="tiny"
onClick={onRefresh}
disabled={isLoading ? true : false}
icon={
<IconRefreshCw
size="tiny"
className={`text-scale-1100 ${isLoading ? 'animate-spin' : ''}`}
/>
}
>
Refresh
</Button>
</div>
)
export default ReportHeader
@@ -62,8 +62,7 @@ export const PRESET_CONFIG: Record<Presets, PresetConfig> = {
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<Presets, PresetConfig> = {
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
@@ -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 (
<div className="flex flex-col gap-4 px-5 py-6 mx-auto 1xl:px-28 lg:px-16 xl:px-24 2xl:px-32">
<ReportHeader
title="API Performance"
onDatepickerChange={(value) => console.log(value)}
datepickerFrom={report.params.totalRequests.iso_timestamp_start}
datepickerTo={report.params.totalRequests.iso_timestamp_end}
isLoading={report.isLoading}
onRefresh={report.refresh}
/>
<ReportFilterBar
onDatepickerChange={handleDatepickerChange}
datepickerFrom={report.params.totalRequests.iso_timestamp_start}
datepickerTo={report.params.totalRequests.iso_timestamp_end}
onAddFilter={report.addFilter}
onRemoveFilter={report.removeFilter}
filters={report.filters}