From 3b34e37056adf431042b0fa0e08b6f9ed851f915 Mon Sep 17 00:00:00 2001 From: Shibani Shankar Dash Date: Sun, 23 Jul 2023 16:17:36 +0000 Subject: [PATCH 1/3] feat: add option to sort by a column --- src/@types/parseable/api/query.ts | 9 +++++++ src/hooks/useQueryLogs.ts | 26 ++++++++++++++++--- src/pages/Logs/Column.tsx | 43 ++++++++++++++++++++++++++++--- src/pages/Logs/LogTable.tsx | 24 ++++++++++++++++- 4 files changed, 95 insertions(+), 7 deletions(-) diff --git a/src/@types/parseable/api/query.ts b/src/@types/parseable/api/query.ts index 65ea03bf..e008bc94 100644 --- a/src/@types/parseable/api/query.ts +++ b/src/@types/parseable/api/query.ts @@ -4,9 +4,18 @@ export type LogsQuery = { endTime: Date; }; +export enum SortOrder { + ASCENDING = 1, + DESCENDING = -1 +} + export type LogsSearch = { search: string; filters: Record; + sort: { + field: string, + order: SortOrder + } }; export type LogsData = { diff --git a/src/hooks/useQueryLogs.ts b/src/hooks/useQueryLogs.ts index 561fd786..f288a6e5 100644 --- a/src/hooks/useQueryLogs.ts +++ b/src/hooks/useQueryLogs.ts @@ -1,4 +1,4 @@ -import type { Log, LogsData, LogsQuery, LogsSearch } from '@/@types/parseable/api/query'; +import { SortOrder, type Log, type LogsData, type LogsQuery, type LogsSearch } from '@/@types/parseable/api/query'; import { getQueryLogs } from '@/api/query'; import { StatusCodes } from 'http-status-codes'; import useMountedState from './useMountedState'; @@ -15,14 +15,21 @@ export const useQueryLogs = () => { const [error, setError] = useMountedState(null); const [loading, setLoading] = useMountedState(true); const [pageLogData, setPageLogData] = useMountedState(null); - const [querySearch, setQuerySearch] = useMountedState({ search: '', filters: {} }); + const [querySearch, setQuerySearch] = useMountedState({ + search: '', + filters: {}, + sort: { + field: 'p_timestamp', + order: SortOrder.DESCENDING + } + }); const [isPending, startTransition] = useTransition(); const data: Log[] | null = useMemo(() => { if (_dataRef.current) { const logs = _dataRef.current; const temp = []; - const { search, filters } = querySearch; + const { search, filters, sort } = querySearch; const searchText = search.trim().toLowerCase(); const filteredKeys = Object.keys(filters); @@ -51,6 +58,18 @@ export const useQueryLogs = () => { } } + const { field, order } = sort; + + temp.sort(({[field]: aData}, {[field]: bData}) => { + let res = 0 + if (aData === bData) res = 0; + else if (aData == null) res = -1; + else if (bData == null) res = 1; + else res = aData > bData ? 1 : -1; + + return res*order; + }) + return temp; } @@ -139,6 +158,7 @@ export const useQueryLogs = () => { pageLogData, setQuerySearch, getColumnFilters, + sort: querySearch.sort, error, loading: loading || isPending, getQueryData, diff --git a/src/pages/Logs/Column.tsx b/src/pages/Logs/Column.tsx index fdfc004b..d225567b 100644 --- a/src/pages/Logs/Column.tsx +++ b/src/pages/Logs/Column.tsx @@ -1,7 +1,7 @@ -import type { Log } from '@/@types/parseable/api/query'; +import { Log, SortOrder } from '@/@types/parseable/api/query'; import { Box, Checkbox, Popover, Text, TextInput, Tooltip, UnstyledButton, px } from '@mantine/core'; import { type ChangeEvent, type FC, Fragment, useTransition, useRef, useCallback, useMemo } from 'react'; -import { IconFilter, IconSearch } from '@tabler/icons-react'; +import { IconFilter, IconSearch, IconSortAscending, IconSortDescending } from '@tabler/icons-react'; import useMountedState from '@/hooks/useMountedState'; import { useTableColumnStyle } from './styles'; import EmptyBox from '@/components/Empty'; @@ -17,10 +17,12 @@ type Column = { getColumnFilters: (columnName: string) => Log[number][] | null; appliedFilter: (columnName: string) => string[]; applyFilter: (columnName: string, value: string[]) => void; + setSorting: (order: SortOrder | null) => void; + fieldSortOrder: SortOrder | null }; const Column: FC = (props) => { - const { columnName, getColumnFilters, appliedFilter, applyFilter } = props; + const { columnName, getColumnFilters, appliedFilter, applyFilter, setSorting, fieldSortOrder } = props; // columnValues ref will always have the unfiltered data. const _columnValuesRef = useRef(null); @@ -87,6 +89,7 @@ const Column: FC = (props) => { + Filter by values: = (props) => { ); }; +type SortWidgetProps = { + setSortOrder: (order: SortOrder | null) => void; + fieldSortOrder: SortOrder | null; +} + +const SortWidget: FC = (props) => { + const { setSortOrder, fieldSortOrder } = props; + + return <> + + setSortOrder( + fieldSortOrder === SortOrder.ASCENDING ? + null + : + SortOrder.ASCENDING + )} + stroke={fieldSortOrder === SortOrder.ASCENDING ? 2 : 1} + /> + + setSortOrder( + fieldSortOrder === SortOrder.DESCENDING ? + null + : + SortOrder.DESCENDING + )} + stroke={fieldSortOrder === SortOrder.DESCENDING ? 2 : 1} + /> + +} + export default Column; diff --git a/src/pages/Logs/LogTable.tsx b/src/pages/Logs/LogTable.tsx index d9d16f40..d268695f 100644 --- a/src/pages/Logs/LogTable.tsx +++ b/src/pages/Logs/LogTable.tsx @@ -18,6 +18,7 @@ import Column from './Column'; import FilterPills from './FilterPills'; import { useHeaderContext } from '@/layouts/MainLayout/Context'; import dayjs from 'dayjs'; +import { SortOrder } from '@/@types/parseable/api/query'; const skipFields = ['p_metadata', 'p_tags']; @@ -50,6 +51,7 @@ const LogTable: FC = () => { loading: logsLoading, error: logsError, resetData: resetLogsData, + sort } = useQueryLogs(); const appliedFilter = (key: string) => { @@ -79,6 +81,24 @@ const LogTable: FC = () => { setColumnToggles(new Map(columnToggles.set(columnName, value))); }; + const sortingSetter = (columName: string) => { + return (order: SortOrder | null) => { + setQuerySearch((prev) => { + if (order === null) { + prev.sort.field = 'p_timestamp'; + prev.sort.order = -1; + } else { + prev.sort.field = columName; + prev.sort.order = order; + } + + return { + ...prev + } + }) + } + } + const onRetry = () => { const query = subLogQuery.get(); @@ -157,13 +177,15 @@ const LogTable: FC = () => { appliedFilter={appliedFilter} applyFilter={applyFilter} getColumnFilters={getColumnFilters} + setSorting={sortingSetter(field.name)} + fieldSortOrder={sort.field === field.name ? sort.order : null} /> ); }); } return null; - }, [logsSchema, columnToggles, logs]); + }, [logsSchema, columnToggles, logs, sort]); const { classes } = useLogTableStyles(); From 4e5de810b264d5eb8c26f07fbd1b0092fa789d06 Mon Sep 17 00:00:00 2001 From: Shibani Shankar Dash Date: Mon, 24 Jul 2023 18:21:53 +0000 Subject: [PATCH 2/3] fix: fix error on stream change initial state was being set in two places, but sort was missing from header context initial state, added the same --- src/layouts/MainLayout/Context.tsx | 6 +++++- src/pages/Logs/LogTable.tsx | 16 +++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/layouts/MainLayout/Context.tsx b/src/layouts/MainLayout/Context.tsx index d1e33507..2eb27387 100644 --- a/src/layouts/MainLayout/Context.tsx +++ b/src/layouts/MainLayout/Context.tsx @@ -1,4 +1,4 @@ -import type { LogsQuery, LogsSearch } from '@/@types/parseable/api/query'; +import { SortOrder, type LogsQuery, type LogsSearch } from '@/@types/parseable/api/query'; import useSubscribeState, { SubData } from '@/hooks/useSubscribeState'; import dayjs from 'dayjs'; import type { FC } from 'react'; @@ -72,6 +72,10 @@ const MainLayoutPageProvider: FC = ({ children }) => { const subLogSearch = useSubscribeState({ search: '', filters: {}, + sort: { + field: 'p_timestamp', + order: SortOrder.DESCENDING + } }); const subLogSelectedTimeRange = useSubscribeState(DEFAULT_FIXED_DURATIONS.name); const subRefreshInterval = useSubscribeState(null); diff --git a/src/pages/Logs/LogTable.tsx b/src/pages/Logs/LogTable.tsx index d268695f..a372cdf2 100644 --- a/src/pages/Logs/LogTable.tsx +++ b/src/pages/Logs/LogTable.tsx @@ -84,16 +84,18 @@ const LogTable: FC = () => { const sortingSetter = (columName: string) => { return (order: SortOrder | null) => { setQuerySearch((prev) => { - if (order === null) { - prev.sort.field = 'p_timestamp'; - prev.sort.order = -1; - } else { - prev.sort.field = columName; - prev.sort.order = order; + const sort = { + field: 'p_timestamp', + order: SortOrder.DESCENDING + } + if (order !== null) { + sort.field = columName; + sort.order = order; } return { - ...prev + ...prev, + sort } }) } From d4401cf0cbaf065ff837e55c6e3653dd50f9426c Mon Sep 17 00:00:00 2001 From: Shibani Shankar Dash Date: Mon, 24 Jul 2023 18:43:17 +0000 Subject: [PATCH 3/3] chore: address deepsource comments --- src/hooks/useQueryLogs.ts | 4 +- src/pages/Logs/Column.tsx | 75 ++++++++++++++++++++----------------- src/pages/Logs/LogTable.tsx | 3 ++ 3 files changed, 46 insertions(+), 36 deletions(-) diff --git a/src/hooks/useQueryLogs.ts b/src/hooks/useQueryLogs.ts index f288a6e5..2d7e665e 100644 --- a/src/hooks/useQueryLogs.ts +++ b/src/hooks/useQueryLogs.ts @@ -63,8 +63,8 @@ export const useQueryLogs = () => { temp.sort(({[field]: aData}, {[field]: bData}) => { let res = 0 if (aData === bData) res = 0; - else if (aData == null) res = -1; - else if (bData == null) res = 1; + else if (aData === null) res = -1; + else if (bData === null) res = 1; else res = aData > bData ? 1 : -1; return res*order; diff --git a/src/pages/Logs/Column.tsx b/src/pages/Logs/Column.tsx index d225567b..04aec556 100644 --- a/src/pages/Logs/Column.tsx +++ b/src/pages/Logs/Column.tsx @@ -12,6 +12,47 @@ import compare from 'just-compare'; import { parseLogData } from '@/utils'; import { useDisclosure } from '@mantine/hooks'; +type SortWidgetProps = { + setSortOrder: (order: SortOrder | null) => void; + fieldSortOrder: SortOrder | null; +} + +/** + * Component that allows selecting sorting by a given field + */ +const SortWidget: FC = (props) => { + const { setSortOrder, fieldSortOrder } = props; + const toggleAscending = () => { + setSortOrder( + fieldSortOrder === SortOrder.ASCENDING ? + null + : + SortOrder.ASCENDING + ) + } + const toggleDescending = () => { + setSortOrder( + fieldSortOrder === SortOrder.DESCENDING ? + null + : + SortOrder.DESCENDING + ) + } + + return <> + + + +} + type Column = { columnName: string; getColumnFilters: (columnName: string) => Log[number][] | null; @@ -173,38 +214,4 @@ const CheckboxRow: FC = (props) => { ); }; -type SortWidgetProps = { - setSortOrder: (order: SortOrder | null) => void; - fieldSortOrder: SortOrder | null; -} - -const SortWidget: FC = (props) => { - const { setSortOrder, fieldSortOrder } = props; - - return <> - - setSortOrder( - fieldSortOrder === SortOrder.ASCENDING ? - null - : - SortOrder.ASCENDING - )} - stroke={fieldSortOrder === SortOrder.ASCENDING ? 2 : 1} - /> - - setSortOrder( - fieldSortOrder === SortOrder.DESCENDING ? - null - : - SortOrder.DESCENDING - )} - stroke={fieldSortOrder === SortOrder.DESCENDING ? 2 : 1} - /> - -} - export default Column; diff --git a/src/pages/Logs/LogTable.tsx b/src/pages/Logs/LogTable.tsx index a372cdf2..3a372c35 100644 --- a/src/pages/Logs/LogTable.tsx +++ b/src/pages/Logs/LogTable.tsx @@ -81,6 +81,9 @@ const LogTable: FC = () => { setColumnToggles(new Map(columnToggles.set(columnName, value))); }; + /** + * Function to get a setter to set sort order on a given field + */ const sortingSetter = (columName: string) => { return (order: SortOrder | null) => { setQuerySearch((prev) => {