From bdf2bbce3c3d0469de209b5d3f616f5665f3d489 Mon Sep 17 00:00:00 2001 From: balaji-jr Date: Tue, 18 Jun 2024 23:35:04 +0530 Subject: [PATCH 1/9] readme - updated the demo app url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b48d8ef7..1aedc2b5 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ For complete Parseable API documentation, refer to [Parseable API workspace on P - + From 1a5a6971d163327f5fe5405a75e1d8464635b06b Mon Sep 17 00:00:00 2001 From: balaji-jr Date: Fri, 26 Jul 2024 02:53:27 +0530 Subject: [PATCH 2/9] implemented json view on explore page --- src/pages/Stream/Views/Explore/Footer.tsx | 214 ++++++++++++ src/pages/Stream/Views/Explore/JSONView.tsx | 102 ++++++ .../Stream/Views/Explore/LoadingViews.tsx | 38 +++ src/pages/Stream/Views/Explore/LogsView.tsx | 20 ++ .../Stream/Views/Explore/StaticLogRow.tsx | 4 +- .../Stream/Views/Explore/StaticLogTable.tsx | 304 +----------------- .../Stream/Views/Explore/useLogsFetcher.ts | 38 +++ src/pages/Stream/index.tsx | 4 +- src/pages/Stream/providers/LogsProvider.tsx | 5 + src/pages/Stream/styles/Footer.module.css | 44 +++ src/pages/Stream/styles/JSONView.module.css | 42 +++ src/pages/Stream/utils.ts | 12 + 12 files changed, 531 insertions(+), 296 deletions(-) create mode 100644 src/pages/Stream/Views/Explore/Footer.tsx create mode 100644 src/pages/Stream/Views/Explore/JSONView.tsx create mode 100644 src/pages/Stream/Views/Explore/LoadingViews.tsx create mode 100644 src/pages/Stream/Views/Explore/LogsView.tsx create mode 100644 src/pages/Stream/Views/Explore/useLogsFetcher.ts create mode 100644 src/pages/Stream/styles/Footer.module.css create mode 100644 src/pages/Stream/styles/JSONView.module.css diff --git a/src/pages/Stream/Views/Explore/Footer.tsx b/src/pages/Stream/Views/Explore/Footer.tsx new file mode 100644 index 00000000..17af5075 --- /dev/null +++ b/src/pages/Stream/Views/Explore/Footer.tsx @@ -0,0 +1,214 @@ +import { FC, useCallback } from "react"; +import { useLogsStore, logsStoreReducers, LOAD_LIMIT, LOG_QUERY_LIMITS } from "../../providers/LogsProvider"; +import { useAppStore } from "@/layouts/MainLayout/providers/AppProvider"; +import { usePagination } from "@mantine/hooks"; +import { downloadDataAsCSV, downloadDataAsJson } from "@/utils/exportHelpers"; +import { Box, Center, Group, Loader, Menu, Pagination, px, Stack, Tooltip } from "@mantine/core"; +import _ from "lodash"; +import { Text } from "@mantine/core"; +import { HumanizeNumber } from "@/utils/formatBytes"; +import IconButton from "@/components/Button/IconButton"; +import { IconDownload, IconSelector } from "@tabler/icons-react"; +import useMountedState from "@/hooks/useMountedState"; +import classes from '../../styles/Footer.module.css' + +const {setPageAndPageData, setCurrentPage, setCurrentOffset, makeExportData} = logsStoreReducers; + +const TotalCount = (props: { totalCount: number }) => { + return ( + + {HumanizeNumber(props.totalCount)} + + ); +}; + +const renderExportIcon = () => ; + +const TotalLogsCount = (props: { hasTableLoaded: boolean; isFetchingCount: boolean; isTableEmpty: boolean }) => { + const [{ totalCount, perPage, pageData }] = useLogsStore((store) => store.tableOpts); + const displayedCount = _.size(pageData); + const showingCount = displayedCount < perPage ? displayedCount : perPage; + if (typeof totalCount !== 'number' || typeof displayedCount !== 'number') return ; + return ( + + {props.hasTableLoaded ? ( + props.isFetchingCount ? ( + + ) : ( + <> + {`Showing ${showingCount} out of`} + + records + + ) + ) : props.isTableEmpty ? null : ( + + )} + + ); +}; + + +const LimitControl: FC = () => { + const [opened, setOpened] = useMountedState(false); + const [perPage, setLogsStore] = useLogsStore((store) => store.tableOpts.perPage); + + const toggle = () => { + setOpened(!opened); + }; + + const onSelect = (limit: number) => { + if (perPage !== limit) { + setLogsStore((store) => setPageAndPageData(store, 1, limit)); + } + }; + + return ( + + +
+ + + {perPage} + + + +
+ + {LOG_QUERY_LIMITS.map((limit) => { + return ( + onSelect(limit)}> +
+ {limit} +
+
+ ); + })} +
+
+
+ ); +}; + +const Footer = (props: { loaded: boolean; isLoading: boolean; hasNoData: boolean }) => { + const [tableOpts, setLogsStore] = useLogsStore((store) => store.tableOpts); + const [filteredData] = useLogsStore((store) => store.data.filteredData); + const { totalPages, currentOffset, currentPage, perPage, headers, totalCount } = tableOpts; + const onPageChange = useCallback((page: number) => { + setLogsStore((store) => setPageAndPageData(store, page)); + }, []); + const [currentStream] = useAppStore((store) => store.currentStream); + const pagination = usePagination({ total: totalPages ?? 1, initialPage: 1, onChange: onPageChange }); + const onChangeOffset = useCallback( + (key: 'prev' | 'next') => { + if (key === 'prev') { + const targetOffset = currentOffset - LOAD_LIMIT; + if (currentOffset < 0) return; + + if (targetOffset === 0 && currentOffset > 0) { + // hack to initiate fetch + setLogsStore((store) => setCurrentPage(store, 0)); + } + setLogsStore((store) => setCurrentOffset(store, targetOffset)); + } else { + const targetOffset = currentOffset + LOAD_LIMIT; + setLogsStore((store) => setCurrentOffset(store, targetOffset)); + } + }, + [currentOffset], + ); + + const exportHandler = useCallback( + (fileType: string | null) => { + const filename = `${currentStream}-logs`; + if (fileType === 'CSV') { + downloadDataAsCSV(makeExportData(filteredData, headers, 'CSV'), filename); + } else if (fileType === 'JSON') { + downloadDataAsJson(makeExportData(filteredData, headers, 'JSON'), filename); + } + }, + [currentStream, filteredData, headers], + ); + + return ( + + + + + + {props.loaded ? ( + { + pagination.setPage(page); + }} + size="sm"> + + { + currentOffset !== 0 && onChangeOffset('prev'); + }} + disabled={currentOffset === 0} + /> + + {pagination.range.map((page, index) => { + if (page === 'dots') { + return ; + } else { + return ( + { + pagination.setPage(page); + }}> + {(perPage ? page + currentOffset / perPage : page) ?? 1} + + ); + } + })} + + { + onChangeOffset('next'); + }} + disabled={!(currentOffset + LOAD_LIMIT < totalCount)} + /> + + + ) : null} + + + {props.loaded && ( + + +
+ +
+
+ + exportHandler('CSV')} style={{ padding: '0.5rem 2.25rem 0.5rem 0.75rem' }}> + CSV + + exportHandler('JSON')} style={{ padding: '0.5rem 2.25rem 0.5rem 0.75rem' }}> + JSON + + +
+ )} + +
+
+ ); +}; + +export default Footer; \ No newline at end of file diff --git a/src/pages/Stream/Views/Explore/JSONView.tsx b/src/pages/Stream/Views/Explore/JSONView.tsx new file mode 100644 index 00000000..2c4618b7 --- /dev/null +++ b/src/pages/Stream/Views/Explore/JSONView.tsx @@ -0,0 +1,102 @@ +import { Box, Stack, Text } from '@mantine/core'; +import { ReactNode, useEffect, useRef, useState } from 'react'; +import classes from '../../styles/JSONView.module.css'; +import EmptyBox from '@/components/Empty'; +import { ErrorView, LoadingView } from './LoadingViews'; +import Footer from './Footer'; +import { useAppStore } from '@/layouts/MainLayout/providers/AppProvider'; +import { LOGS_PRIMARY_TOOLBAR_HEIGHT, LOGS_SECONDARY_TOOLBAR_HEIGHT, PRIMARY_HEADER_HEIGHT } from '@/constants/theme'; +import { columnsToSkip, useLogsStore } from '../../providers/LogsProvider'; +import { Log } from '@/@types/parseable/api/query'; +import _ from 'lodash'; + +const Container = (props: { children: ReactNode }) => { + return ( + + {props.children} + + ); +}; + +const Item = (props: { header: string; value: number | string | Date | null }) => { + return ( + + {props.header}: + {props.value} + + ); +}; + +const Row = (props: { log: Log; headers: string[] }) => { + const { log, headers } = props; + return ( + + + {_.map(headers, (header) => (_.has(log, header) ? : null))} + + + ); +}; + +const JsonRows = () => { + const [{ pageData, headers }] = useLogsStore((store) => store.tableOpts); + const sanitizedHeaders = _.without(headers, ...columnsToSkip); + + return ( + + {_.map(pageData, (d, index) => ( + + ))} + + ); +}; + +const Content = () => { + const [maximized] = useAppStore((store) => store.maximized); + + const primaryHeaderHeight = !maximized + ? PRIMARY_HEADER_HEIGHT + LOGS_PRIMARY_TOOLBAR_HEIGHT + LOGS_SECONDARY_TOOLBAR_HEIGHT + : 0; + return ( + + + + + + ); +}; + +const JsonView = (props: { + isFetchingCount: boolean; + errorMessage: string | null; + hasNoData: boolean; + showTable: boolean; +}) => { + const { isFetchingCount, errorMessage, hasNoData, showTable } = props; + return ( + + {!errorMessage ? ( + showTable ? ( + + ) : hasNoData ? ( + + ) : ( + + ) + ) : ( + + )} +
+ + ); +}; + +export default JsonView; diff --git a/src/pages/Stream/Views/Explore/LoadingViews.tsx b/src/pages/Stream/Views/Explore/LoadingViews.tsx new file mode 100644 index 00000000..591a41bb --- /dev/null +++ b/src/pages/Stream/Views/Explore/LoadingViews.tsx @@ -0,0 +1,38 @@ +import { useCallback } from "react"; +import { useLogsStore, logsStoreReducers } from "../../providers/LogsProvider"; +import { Center, Loader, Stack, Text } from "@mantine/core"; +import { RetryBtn } from "@/components/Button/Retry"; +import classes from '../../styles/Logs.module.css' + +const { getCleanStoreForRefetch } = logsStoreReducers; + +export const ErrorView = (props: { message: string }) => { + const [, setLogsStore] = useLogsStore((_store) => null); + const { message } = props; + const onRetry = useCallback(() => { + setLogsStore((store) => getCleanStoreForRefetch(store)); + }, []); + return ( +
+ + {message || 'Error'} + + +
+ ); +}; + +export const LoadingView = () => { + return ( + + + + ); +}; \ No newline at end of file diff --git a/src/pages/Stream/Views/Explore/LogsView.tsx b/src/pages/Stream/Views/Explore/LogsView.tsx new file mode 100644 index 00000000..ae9e7623 --- /dev/null +++ b/src/pages/Stream/Views/Explore/LogsView.tsx @@ -0,0 +1,20 @@ +import { useLogsStore } from '../../providers/LogsProvider'; +import JsonView from './JSONView'; +import LogTable from './StaticLogTable'; +import useLogsFetcher from './useLogsFetcher'; + +const LogsView = (props: { schemaLoading: boolean }) => { + const { schemaLoading } = props; + const { errorMessage, isFetchingCount, hasNoData, showTable } = useLogsFetcher({ schemaLoading }); + const [viewMode] = useLogsStore((store) => store.viewMode); + + const viewOpts = { + isFetchingCount, + errorMessage, + hasNoData, + showTable, + }; + return viewMode === 'table' ? : ; +}; + +export default LogsView; diff --git a/src/pages/Stream/Views/Explore/StaticLogRow.tsx b/src/pages/Stream/Views/Explore/StaticLogRow.tsx index a1bc0f8d..cbcb7368 100644 --- a/src/pages/Stream/Views/Explore/StaticLogRow.tsx +++ b/src/pages/Stream/Views/Explore/StaticLogRow.tsx @@ -4,11 +4,9 @@ import { IconArrowNarrowRight } from '@tabler/icons-react'; import { FC, Fragment, useCallback, MouseEvent, useState, useEffect } from 'react'; import { Log } from '@/@types/parseable/api/query'; import tableStyles from '../../styles/Logs.module.css'; -import { useLogsStore, logsStoreReducers } from '../../providers/LogsProvider'; +import { useLogsStore, logsStoreReducers, columnsToSkip } from '../../providers/LogsProvider'; import { IconCopy, IconCheck } from '@tabler/icons-react'; -const columnsToSkip = ['p_metadata', 'p_tags']; - const { setSelectedLog } = logsStoreReducers; type LogRowProps = { diff --git a/src/pages/Stream/Views/Explore/StaticLogTable.tsx b/src/pages/Stream/Views/Explore/StaticLogTable.tsx index bd1345cf..eaf537fc 100644 --- a/src/pages/Stream/Views/Explore/StaticLogTable.tsx +++ b/src/pages/Stream/Views/Explore/StaticLogTable.tsx @@ -1,5 +1,4 @@ import { Tbody, Thead } from '@/components/Table'; -import { useQueryLogs } from '@/hooks/useQueryLogs'; import { Box, Center, @@ -9,88 +8,29 @@ import { Table, px, ActionIcon, - Text, Flex, Button, - Pagination, - Group, - Stack, - Tooltip, - Loader, } from '@mantine/core'; import { useCallback, useEffect, useRef, useState } from 'react'; import type { FC, MutableRefObject, ReactNode, RefObject } from 'react'; import LogRow from './StaticLogRow'; import useMountedState from '@/hooks/useMountedState'; -import { - IconSelector, - IconGripVertical, - IconPin, - IconPinFilled, - IconSettings, - IconDownload, -} from '@tabler/icons-react'; +import { IconGripVertical, IconPin, IconPinFilled, IconSettings } from '@tabler/icons-react'; import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; import EmptyBox from '@/components/Empty'; -import { RetryBtn } from '@/components/Button/Retry'; import Column from '../../components/Column'; import FilterPills from '../../components/FilterPills'; -import { usePagination } from '@mantine/hooks'; import tableStyles from '../../styles/Logs.module.css'; import { LOGS_PRIMARY_TOOLBAR_HEIGHT, LOGS_SECONDARY_TOOLBAR_HEIGHT, PRIMARY_HEADER_HEIGHT } from '@/constants/theme'; -import { HumanizeNumber } from '@/utils/formatBytes'; -import { useLogsStore, logsStoreReducers, LOG_QUERY_LIMITS, LOAD_LIMIT } from '../../providers/LogsProvider'; +import { useLogsStore, logsStoreReducers } from '../../providers/LogsProvider'; import { useAppStore } from '@/layouts/MainLayout/providers/AppProvider'; import _ from 'lodash'; -import IconButton from '@/components/Button/IconButton'; -import { downloadDataAsCSV, downloadDataAsJson } from '@/utils/exportHelpers'; +import Footer from './Footer'; +import { ErrorView, LoadingView } from './LoadingViews'; const skipFields = ['p_metadata', 'p_tags']; -const { - togglePinnedColumns, - toggleDisabledColumns, - setCurrentPage, - setCurrentOffset, - setPageAndPageData, - getCleanStoreForRefetch, - setCleanStoreForStreamChange, - makeExportData, -} = logsStoreReducers; - -const TotalCount = (props: { totalCount: number }) => { - return ( - - {HumanizeNumber(props.totalCount)} - - ); -}; - -const renderExportIcon = () => ; - -const TotalLogsCount = (props: { hasTableLoaded: boolean; isFetchingCount: boolean; isTableEmpty: boolean }) => { - const [{ totalCount, perPage, pageData }] = useLogsStore((store) => store.tableOpts); - const displayedCount = _.size(pageData); - const showingCount = displayedCount < perPage ? displayedCount : perPage; - if (typeof totalCount !== 'number' || typeof displayedCount !== 'number') return ; - return ( - - {props.hasTableLoaded ? ( - props.isFetchingCount ? ( - - ) : ( - <> - {`Showing ${showingCount} out of`} - - records - - ) - ) : props.isTableEmpty ? null : ( - - )} - - ); -}; +const { togglePinnedColumns, toggleDisabledColumns } = logsStoreReducers; const TableContainer = (props: { children: ReactNode }) => { return {props.children}; @@ -182,155 +122,6 @@ const Columns = (props: { containerRefs: SectionRefs }) => { ); }; -const ErrorView = (props: { message: string }) => { - const [, setLogsStore] = useLogsStore((_store) => null); - const { message } = props; - const onRetry = useCallback(() => { - setLogsStore((store) => getCleanStoreForRefetch(store)); - }, []); - return ( -
- - {message || 'Error'} - - -
- ); -}; - -const LoadingView = () => { - return ( - - - - ); -}; - -const Footer = (props: { loaded: boolean; isLoading: boolean; hasNoData: boolean }) => { - const [tableOpts, setLogsStore] = useLogsStore((store) => store.tableOpts); - const [filteredData] = useLogsStore((store) => store.data.filteredData); - const { totalPages, currentOffset, currentPage, perPage, headers, totalCount } = tableOpts; - const onPageChange = useCallback((page: number) => { - setLogsStore((store) => setPageAndPageData(store, page)); - }, []); - const [currentStream] = useAppStore((store) => store.currentStream); - const pagination = usePagination({ total: totalPages ?? 1, initialPage: 1, onChange: onPageChange }); - const onChangeOffset = useCallback( - (key: 'prev' | 'next') => { - if (key === 'prev') { - const targetOffset = currentOffset - LOAD_LIMIT; - if (currentOffset < 0) return; - - if (targetOffset === 0 && currentOffset > 0) { - // hack to initiate fetch - setLogsStore((store) => setCurrentPage(store, 0)); - } - setLogsStore((store) => setCurrentOffset(store, targetOffset)); - } else { - const targetOffset = currentOffset + LOAD_LIMIT; - setLogsStore((store) => setCurrentOffset(store, targetOffset)); - } - }, - [currentOffset], - ); - - const exportHandler = useCallback( - (fileType: string | null) => { - const filename = `${currentStream}-logs`; - if (fileType === 'CSV') { - downloadDataAsCSV(makeExportData(filteredData, headers, 'CSV'), filename); - } else if (fileType === 'JSON') { - downloadDataAsJson(makeExportData(filteredData, headers, 'JSON'), filename); - } - }, - [currentStream, filteredData, headers], - ); - - return ( - - - - - - {props.loaded ? ( - { - pagination.setPage(page); - }} - size="sm"> - - { - currentOffset !== 0 && onChangeOffset('prev'); - }} - disabled={currentOffset === 0} - /> - - {pagination.range.map((page, index) => { - if (page === 'dots') { - return ; - } else { - return ( - { - pagination.setPage(page); - }}> - {perPage ? page + currentOffset / perPage ?? 1 : page} - - ); - } - })} - - { - onChangeOffset('next'); - }} - disabled={!(currentOffset + LOAD_LIMIT < totalCount)} - /> - - - ) : null} - - - {props.loaded && ( - - -
- -
-
- - exportHandler('CSV')} style={{ padding: '0.5rem 2.25rem 0.5rem 0.75rem' }}> - CSV - - exportHandler('JSON')} style={{ padding: '0.5rem 2.25rem 0.5rem 0.75rem' }}> - JSON - - -
- )} - -
-
- ); -}; - const TableHeader = (props: { isPinned?: boolean }) => { const [{ headers, disabledColumns, pinnedColumns }] = useLogsStore((store) => store.tableOpts); @@ -357,7 +148,13 @@ interface SectionRefs { pinnedContainerRef: HTMLDivRef; } -const LogTable = (props: { schemaLoading: boolean }) => { +const LogTable = (props: { + isFetchingCount: boolean; + errorMessage: string | null; + hasNoData: boolean; + showTable: boolean; +}) => { + const { isFetchingCount, errorMessage, hasNoData, showTable } = props; const [containerRefs, _setContainerRefs] = useState({ activeSectionRef: useRef<'left' | 'right'>('left'), leftSectionRef: useRef(null), @@ -365,44 +162,16 @@ const LogTable = (props: { schemaLoading: boolean }) => { pinnedContainerRef: useRef(null), }); const [maximized] = useAppStore((store) => store.maximized); - const [currentStream] = useAppStore((store) => store.currentStream); const primaryHeaderHeight = !maximized ? PRIMARY_HEADER_HEIGHT + LOGS_PRIMARY_TOOLBAR_HEIGHT + LOGS_SECONDARY_TOOLBAR_HEIGHT : 0; - const { getQueryData, loading: logsLoading, error: logsError, fetchCount, isFetchingCount } = useQueryLogs(); - const [currentPage] = useLogsStore((store) => store.tableOpts.currentPage); - const [currentOffset, setLogsStore] = useLogsStore((store) => store.tableOpts.currentOffset); - - useEffect(() => { - setLogsStore(setCleanStoreForStreamChange); - }, [currentStream]); - - useEffect(() => { - if (currentPage === 0 && currentOffset === 0) { - getQueryData(); - fetchCount(); - } - }, [currentPage]); - - useEffect(() => { - if (currentOffset !== 0 && currentPage !== 0) { - getQueryData(); - } - }, [currentOffset]); - - const [pageData] = useLogsStore((store) => store.tableOpts.pageData); - const hasContentLoaded = props.schemaLoading === false && logsLoading === false; - const errorMessage = logsError; - const hasNoData = hasContentLoaded && !errorMessage && pageData.length === 0; - const showTable = hasContentLoaded && !hasNoData && !errorMessage; - return ( {!errorMessage ? ( showTable ? ( - + @@ -541,51 +310,4 @@ const ThColumnMenu: FC = () => { ); }; -const LimitControl: FC = () => { - const [opened, setOpened] = useMountedState(false); - const [perPage, setLogsStore] = useLogsStore((store) => store.tableOpts.perPage); - - const toggle = () => { - setOpened(!opened); - }; - - const onSelect = (limit: number) => { - if (perPage !== limit) { - setLogsStore((store) => setPageAndPageData(store, 1, limit)); - } - }; - - const classes = tableStyles; - const { limitBtn, limitBtnText, limitActive, limitOption } = classes; - - return ( - - -
- - - {perPage} - - - -
- - {LOG_QUERY_LIMITS.map((limit) => { - return ( - onSelect(limit)}> -
- {limit} -
-
- ); - })} -
-
-
- ); -}; - export default LogTable; diff --git a/src/pages/Stream/Views/Explore/useLogsFetcher.ts b/src/pages/Stream/Views/Explore/useLogsFetcher.ts new file mode 100644 index 00000000..027de19e --- /dev/null +++ b/src/pages/Stream/Views/Explore/useLogsFetcher.ts @@ -0,0 +1,38 @@ +import { useAppStore } from '@/layouts/MainLayout/providers/AppProvider'; +import { useEffect } from 'react'; +import { useLogsStore, logsStoreReducers } from '../../providers/LogsProvider'; +import { useQueryLogs } from '@/hooks/useQueryLogs'; + +const { setCleanStoreForStreamChange } = logsStoreReducers; + +const useLogsFetcher = (props: {schemaLoading: boolean}) => { + const {schemaLoading} = props; + const [currentStream] = useAppStore((store) => store.currentStream); + const [tableOpts, setLogsStore] = useLogsStore((store) => store.tableOpts); + const { currentOffset, currentPage, pageData } = tableOpts; + const { getQueryData, loading: logsLoading, error: errorMessage, fetchCount, isFetchingCount } = useQueryLogs(); + const hasContentLoaded = schemaLoading === false && logsLoading === false; + const hasNoData = hasContentLoaded && !errorMessage && pageData.length === 0; + const showTable = hasContentLoaded && !hasNoData && !errorMessage; + + useEffect(() => { + setLogsStore(setCleanStoreForStreamChange); + }, [currentStream]); + + useEffect(() => { + if (currentPage === 0 && currentOffset === 0) { + getQueryData(); + fetchCount(); + } + }, [currentPage]); + + useEffect(() => { + if (currentOffset !== 0 && currentPage !== 0) { + getQueryData(); + } + }, [currentOffset]); + + return { logsLoading, errorMessage, isFetchingCount, hasContentLoaded, hasNoData, showTable }; +}; + +export default useLogsFetcher; diff --git a/src/pages/Stream/index.tsx b/src/pages/Stream/index.tsx index c02a6482..09ecb937 100644 --- a/src/pages/Stream/index.tsx +++ b/src/pages/Stream/index.tsx @@ -1,7 +1,6 @@ import { Box, Stack, rem } from '@mantine/core'; import { useDocumentTitle } from '@mantine/hooks'; import { FC, useCallback, useEffect } from 'react'; -import StaticLogTable from './Views/Explore/StaticLogTable'; import LiveLogTable from './Views/LiveTail/LiveLogTable'; import ViewLog from './components/ViewLog'; import { useAppStore } from '@/layouts/MainLayout/providers/AppProvider'; @@ -17,6 +16,7 @@ import { useParams } from 'react-router-dom'; import { STREAM_VIEWS } from '@/constants/routes'; import { Text } from '@mantine/core'; import { RetryBtn } from '@/components/Button/Retry'; +import LogsView from './Views/Explore/LogsView'; const { streamChangeCleanup } = streamStoreReducers; @@ -89,7 +89,7 @@ const Logs: FC = () => { error ? ( ) : ( - + ) ) : view === 'live-tail' ? ( diff --git a/src/pages/Stream/providers/LogsProvider.tsx b/src/pages/Stream/providers/LogsProvider.tsx index e67f9e0e..d418080f 100644 --- a/src/pages/Stream/providers/LogsProvider.tsx +++ b/src/pages/Stream/providers/LogsProvider.tsx @@ -11,9 +11,12 @@ import { sanitizeCSVData } from '@/utils/exportHelpers'; export const DEFAULT_FIXED_DURATIONS = FIXED_DURATIONS[0]; export const LOG_QUERY_LIMITS = [50, 100, 150, 200]; export const LOAD_LIMIT = 1000; +export const columnsToSkip = ['p_metadata', 'p_tags']; type ReducerOutput = Partial; +export type ViewMode = 'table' | 'json' + export type ConfigType = { column: string; operator: string; @@ -182,6 +185,7 @@ type LogsStore = { selectedLog: Log | null; custQuerySearchState: CustQuerySearchState; sideBarOpen: boolean; + viewMode: ViewMode; modalOpts: { deleteModalOpen: boolean; alertsModalOpen: boolean; @@ -281,6 +285,7 @@ const initialState: LogsStore = { selectedLog: null, custQuerySearchState: defaultCustQuerySearchState, sideBarOpen: false, + viewMode: 'json', modalOpts: { deleteModalOpen: false, alertsModalOpen: false, diff --git a/src/pages/Stream/styles/Footer.module.css b/src/pages/Stream/styles/Footer.module.css new file mode 100644 index 00000000..2a89859b --- /dev/null +++ b/src/pages/Stream/styles/Footer.module.css @@ -0,0 +1,44 @@ +.footerContainer { + padding: 0.6rem 1rem; + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + border-top: 0.0625rem solid rgba(0, 0, 0, 0.1); +} + +.limitActive { + background: #1a237e; + font-weight: 700; + color: #fff; + &:hover { + background-color: #1a237e; + } +} + +.limitOption { + font-weight: 400; + &:hover { + color: #1a237e; + } +} + +.limitBtn { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 0.6rem; + background: white; + padding: 0.4rem 0.625rem; + border: 1px solid var(--mantine-color-gray-2); + border-radius: 0.425rem; + &:hover { + background: #e0e0e0; + } +} + +.limitBtnText { + font-size: 0.65rem; +} \ No newline at end of file diff --git a/src/pages/Stream/styles/JSONView.module.css b/src/pages/Stream/styles/JSONView.module.css new file mode 100644 index 00000000..69d856e5 --- /dev/null +++ b/src/pages/Stream/styles/JSONView.module.css @@ -0,0 +1,42 @@ +.container { + position: relative; +} + +.innerContainer { + position: relative; + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.rowContainer { + border-bottom: 1px solid var(--mantine-color-gray-1); + padding: 0.25rem 1rem; + cursor: pointer; + &:hover { + background-color: var(--mantine-color-gray-1); + } +} + +.itemHeader { + font-weight: 500; + font-size: 0.65rem; + word-break: normal; + word-wrap: break-word; + font-family: monospace; +} + +.itemValue { + font-weight: 400; + font-size: 0.65rem; + color: var(--mantine-color-gray-7); + word-break: normal; + word-wrap: break-word; + font-family: monospace; +} + +.itemContainer { + margin-right: 0.2rem; + +} \ No newline at end of file diff --git a/src/pages/Stream/utils.ts b/src/pages/Stream/utils.ts index 025935c7..46da868d 100644 --- a/src/pages/Stream/utils.ts +++ b/src/pages/Stream/utils.ts @@ -1,5 +1,6 @@ import { Log } from "@/@types/parseable/api/query"; import { LogStreamSchemaData } from "@/@types/parseable/api/stream"; +import { columnsToSkip } from "./providers/LogsProvider"; export const getPageSlice = (page = 1, perPage: number, data: Log[]) => { const firstPageIndex = (page - 1) * perPage; @@ -32,3 +33,14 @@ export const makeHeadersfromData = (data: Log[]): string[] => { return allKeys; }; + +export const genColumnsToShow = (opts: {disabledColumns: string[], headers: string[], isPinned: boolean, pinnedColumns: string[]}) => { + const {disabledColumns, headers, isPinned, pinnedColumns} = opts; + + const columnsToIgnore = [ + ...disabledColumns, + ...columnsToSkip, + ...headers.filter((header) => (isPinned ? !pinnedColumns.includes(header) : pinnedColumns.includes(header))), + ]; + return headers.filter((header) => !columnsToIgnore.includes(header)); +} \ No newline at end of file From 77d44a49cafdd9cc086f3ab4e1e3d0374efcf6fb Mon Sep 17 00:00:00 2001 From: balaji-jr Date: Sat, 27 Jul 2024 17:34:43 +0530 Subject: [PATCH 3/9] implemented jq search and text search --- package.json | 1 + pnpm-lock.yaml | 166 ++++++++++-------- src/constants/theme.ts | 3 +- src/hooks/useQueryLogs.ts | 8 +- src/jq-web.d.ts | 13 ++ src/pages/Stream/Views/Explore/JSONView.tsx | 145 ++++++++++++--- .../Stream/Views/Explore/StaticLogTable.tsx | 15 +- .../Stream/components/PrimaryToolbar.tsx | 46 ++++- src/pages/Stream/providers/LogsProvider.tsx | 114 +++++++++++- src/utils/jqSearch.ts | 20 +++ 10 files changed, 413 insertions(+), 118 deletions(-) create mode 100644 src/jq-web.d.ts create mode 100644 src/utils/jqSearch.ts diff --git a/package.json b/package.json index c0f3e199..aa3ec792 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "embla-carousel-react": "7.1.0", "http-status-codes": "^2.2.0", "immer": "^10.0.2", + "jq-web": "^0.5.1", "js-cookie": "^3.0.5", "just-compare": "^2.3.0", "lodash": "^4.17.21", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2b2251ce..06a90fe9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,19 +16,19 @@ importers: version: 11.11.1(@types/react@18.2.14)(react@18.2.0) '@mantine/carousel': specifier: ^7.8.1 - version: 7.8.1(@mantine/core@7.8.1)(@mantine/hooks@7.8.1)(embla-carousel-react@7.1.0)(react-dom@18.2.0)(react@18.2.0) + version: 7.8.1(@mantine/core@7.8.1(@mantine/hooks@7.8.1(react@18.2.0))(@types/react@18.2.14)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@mantine/hooks@7.8.1(react@18.2.0))(embla-carousel-react@7.1.0(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@mantine/charts': specifier: ^7.8.1 - version: 7.8.1(@mantine/core@7.8.1)(@mantine/hooks@7.8.1)(react-dom@18.2.0)(react@18.2.0)(recharts@2.12.7) + version: 7.8.1(@mantine/core@7.8.1(@mantine/hooks@7.8.1(react@18.2.0))(@types/react@18.2.14)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@mantine/hooks@7.8.1(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(recharts@2.12.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0)) '@mantine/code-highlight': specifier: ^7.8.1 - version: 7.8.1(@mantine/core@7.8.1)(@mantine/hooks@7.8.1)(react-dom@18.2.0)(react@18.2.0) + version: 7.8.1(@mantine/core@7.8.1(@mantine/hooks@7.8.1(react@18.2.0))(@types/react@18.2.14)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@mantine/hooks@7.8.1(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@mantine/core': specifier: ^7.8.1 - version: 7.8.1(@mantine/hooks@7.8.1)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) + version: 7.8.1(@mantine/hooks@7.8.1(react@18.2.0))(@types/react@18.2.14)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@mantine/dates': specifier: ^7.8.1 - version: 7.8.1(@mantine/core@7.8.1)(@mantine/hooks@7.8.1)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0) + version: 7.8.1(@mantine/core@7.8.1(@mantine/hooks@7.8.1(react@18.2.0))(@types/react@18.2.14)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@mantine/hooks@7.8.1(react@18.2.0))(dayjs@1.11.10)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@mantine/form': specifier: ^7.8.1 version: 7.8.1(react@18.2.0) @@ -37,10 +37,10 @@ importers: version: 7.8.1(react@18.2.0) '@mantine/notifications': specifier: ^7.8.1 - version: 7.8.1(@mantine/core@7.8.1)(@mantine/hooks@7.8.1)(react-dom@18.2.0)(react@18.2.0) + version: 7.8.1(@mantine/core@7.8.1(@mantine/hooks@7.8.1(react@18.2.0))(@types/react@18.2.14)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@mantine/hooks@7.8.1(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@monaco-editor/react': specifier: ^4.5.1 - version: 4.5.1(monaco-editor@0.48.0)(react-dom@18.2.0)(react@18.2.0) + version: 4.5.1(monaco-editor@0.48.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@tabler/icons-react': specifier: ^3.3.0 version: 3.3.0(react@18.2.0) @@ -62,6 +62,9 @@ importers: immer: specifier: ^10.0.2 version: 10.0.2 + jq-web: + specifier: ^0.5.1 + version: 0.5.1 js-cookie: specifier: ^3.0.5 version: 3.0.5 @@ -91,7 +94,7 @@ importers: version: 18.2.0 react-beautiful-dnd: specifier: ^13.1.1 - version: 13.1.1(react-dom@18.2.0)(react@18.2.0) + version: 13.1.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) @@ -100,19 +103,19 @@ importers: version: 4.0.10(react@18.2.0) react-query: specifier: ^3.39.3 - version: 3.39.3(react-dom@18.2.0)(react@18.2.0) + version: 3.39.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react-querybuilder: specifier: ^6.5.5 version: 6.5.5(react@18.2.0) react-resizable-panels: specifier: ^0.0.53 - version: 0.0.53(react-dom@18.2.0)(react@18.2.0) + version: 0.0.53(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react-router-dom: specifier: ^6.14.0 - version: 6.14.0(react-dom@18.2.0)(react@18.2.0) + version: 6.14.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react-window: specifier: ^1.8.9 - version: 1.8.9(react-dom@18.2.0)(react@18.2.0) + version: 1.8.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) devDependencies: '@types/lodash': specifier: ^4.17.0 @@ -137,13 +140,13 @@ importers: version: 1.8.5 '@typescript-eslint/eslint-plugin': specifier: ^5.60.1 - version: 5.60.1(@typescript-eslint/parser@5.60.1)(eslint@8.43.0)(typescript@5.1.6) + version: 5.60.1(@typescript-eslint/parser@5.60.1(eslint@8.43.0)(typescript@5.1.6))(eslint@8.43.0)(typescript@5.1.6) '@typescript-eslint/parser': specifier: ^5.60.1 version: 5.60.1(eslint@8.43.0)(typescript@5.1.6) '@vitejs/plugin-react-swc': specifier: ^3.3.2 - version: 3.3.2(vite@4.3.9) + version: 3.3.2(vite@4.3.9(@types/node@20.3.2)(sugarss@4.0.1(postcss@8.4.33))) eslint: specifier: ^8.43.0 version: 8.43.0 @@ -152,10 +155,10 @@ importers: version: 8.8.0(eslint@8.43.0) eslint-plugin-import: specifier: ^2.27.5 - version: 2.27.5(@typescript-eslint/parser@5.60.1)(eslint@8.43.0) + version: 2.27.5(@typescript-eslint/parser@5.60.1(eslint@8.43.0)(typescript@5.1.6))(eslint@8.43.0) eslint-plugin-prettier: specifier: ^4.2.1 - version: 4.2.1(eslint-config-prettier@8.8.0)(eslint@8.43.0)(prettier@2.8.8) + version: 4.2.1(eslint-config-prettier@8.8.0(eslint@8.43.0))(eslint@8.43.0)(prettier@2.8.8) eslint-plugin-react: specifier: ^7.32.2 version: 7.32.2(eslint@8.43.0) @@ -185,7 +188,7 @@ importers: version: 5.1.6 vite: specifier: ^4.3.9 - version: 4.3.9(@types/node@20.3.2) + version: 4.3.9(@types/node@20.3.2)(sugarss@4.0.1(postcss@8.4.33)) packages: @@ -1547,6 +1550,9 @@ packages: peerDependencies: ws: '*' + jq-web@0.5.1: + resolution: {integrity: sha512-3Fa3E6g3U1O1j46ljy0EM10yRr4txzILga8J7bqOG8F89gZ6Lilz82WG9z6TItWpYEO0YGa4W8yFGj+NMM1xqQ==} + js-base64@3.7.5: resolution: {integrity: sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==} @@ -2467,9 +2473,10 @@ snapshots: '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) '@emotion/utils': 1.2.1 '@emotion/weak-memoize': 0.3.1 - '@types/react': 18.2.14 hoist-non-react-statics: 3.3.2 react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.14 '@emotion/serialize@1.1.2': dependencies: @@ -2589,15 +2596,15 @@ snapshots: '@floating-ui/core': 1.6.0 '@floating-ui/utils': 0.2.1 - '@floating-ui/react-dom@2.0.8(react-dom@18.2.0)(react@18.2.0)': + '@floating-ui/react-dom@2.0.8(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@floating-ui/dom': 1.6.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - '@floating-ui/react@0.26.12(react-dom@18.2.0)(react@18.2.0)': + '@floating-ui/react@0.26.12(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: - '@floating-ui/react-dom': 2.0.8(react-dom@18.2.0)(react@18.2.0) + '@floating-ui/react-dom': 2.0.8(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@floating-ui/utils': 0.2.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -2617,48 +2624,48 @@ snapshots: '@humanwhocodes/object-schema@1.2.1': {} - '@mantine/carousel@7.8.1(@mantine/core@7.8.1)(@mantine/hooks@7.8.1)(embla-carousel-react@7.1.0)(react-dom@18.2.0)(react@18.2.0)': + '@mantine/carousel@7.8.1(@mantine/core@7.8.1(@mantine/hooks@7.8.1(react@18.2.0))(@types/react@18.2.14)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@mantine/hooks@7.8.1(react@18.2.0))(embla-carousel-react@7.1.0(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: - '@mantine/core': 7.8.1(@mantine/hooks@7.8.1)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) + '@mantine/core': 7.8.1(@mantine/hooks@7.8.1(react@18.2.0))(@types/react@18.2.14)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@mantine/hooks': 7.8.1(react@18.2.0) embla-carousel-react: 7.1.0(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - '@mantine/charts@7.8.1(@mantine/core@7.8.1)(@mantine/hooks@7.8.1)(react-dom@18.2.0)(react@18.2.0)(recharts@2.12.7)': + '@mantine/charts@7.8.1(@mantine/core@7.8.1(@mantine/hooks@7.8.1(react@18.2.0))(@types/react@18.2.14)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@mantine/hooks@7.8.1(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(recharts@2.12.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0))': dependencies: - '@mantine/core': 7.8.1(@mantine/hooks@7.8.1)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) + '@mantine/core': 7.8.1(@mantine/hooks@7.8.1(react@18.2.0))(@types/react@18.2.14)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@mantine/hooks': 7.8.1(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - recharts: 2.12.7(react-dom@18.2.0)(react@18.2.0) + recharts: 2.12.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@mantine/code-highlight@7.8.1(@mantine/core@7.8.1)(@mantine/hooks@7.8.1)(react-dom@18.2.0)(react@18.2.0)': + '@mantine/code-highlight@7.8.1(@mantine/core@7.8.1(@mantine/hooks@7.8.1(react@18.2.0))(@types/react@18.2.14)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@mantine/hooks@7.8.1(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: - '@mantine/core': 7.8.1(@mantine/hooks@7.8.1)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) + '@mantine/core': 7.8.1(@mantine/hooks@7.8.1(react@18.2.0))(@types/react@18.2.14)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@mantine/hooks': 7.8.1(react@18.2.0) clsx: 2.1.0 highlight.js: 11.9.0 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - '@mantine/core@7.8.1(@mantine/hooks@7.8.1)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)': + '@mantine/core@7.8.1(@mantine/hooks@7.8.1(react@18.2.0))(@types/react@18.2.14)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: - '@floating-ui/react': 0.26.12(react-dom@18.2.0)(react@18.2.0) + '@floating-ui/react': 0.26.12(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@mantine/hooks': 7.8.1(react@18.2.0) clsx: 2.1.0 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-number-format: 5.3.1(react-dom@18.2.0)(react@18.2.0) + react-number-format: 5.3.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react-remove-scroll: 2.5.7(@types/react@18.2.14)(react@18.2.0) react-textarea-autosize: 8.5.3(@types/react@18.2.14)(react@18.2.0) type-fest: 4.16.0 transitivePeerDependencies: - '@types/react' - '@mantine/dates@7.8.1(@mantine/core@7.8.1)(@mantine/hooks@7.8.1)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0)': + '@mantine/dates@7.8.1(@mantine/core@7.8.1(@mantine/hooks@7.8.1(react@18.2.0))(@types/react@18.2.14)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@mantine/hooks@7.8.1(react@18.2.0))(dayjs@1.11.10)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: - '@mantine/core': 7.8.1(@mantine/hooks@7.8.1)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) + '@mantine/core': 7.8.1(@mantine/hooks@7.8.1(react@18.2.0))(@types/react@18.2.14)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@mantine/hooks': 7.8.1(react@18.2.0) clsx: 2.1.0 dayjs: 1.11.10 @@ -2675,14 +2682,14 @@ snapshots: dependencies: react: 18.2.0 - '@mantine/notifications@7.8.1(@mantine/core@7.8.1)(@mantine/hooks@7.8.1)(react-dom@18.2.0)(react@18.2.0)': + '@mantine/notifications@7.8.1(@mantine/core@7.8.1(@mantine/hooks@7.8.1(react@18.2.0))(@types/react@18.2.14)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@mantine/hooks@7.8.1(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: - '@mantine/core': 7.8.1(@mantine/hooks@7.8.1)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) + '@mantine/core': 7.8.1(@mantine/hooks@7.8.1(react@18.2.0))(@types/react@18.2.14)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@mantine/hooks': 7.8.1(react@18.2.0) '@mantine/store': 7.8.1(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) + react-transition-group: 4.4.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@mantine/store@7.8.1(react@18.2.0)': dependencies: @@ -2693,7 +2700,7 @@ snapshots: monaco-editor: 0.48.0 state-local: 1.0.7 - '@monaco-editor/react@4.5.1(monaco-editor@0.48.0)(react-dom@18.2.0)(react@18.2.0)': + '@monaco-editor/react@4.5.1(monaco-editor@0.48.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@monaco-editor/loader': 1.3.3(monaco-editor@0.48.0) monaco-editor: 0.48.0 @@ -2871,7 +2878,7 @@ snapshots: '@types/semver@7.5.0': {} - '@typescript-eslint/eslint-plugin@5.60.1(@typescript-eslint/parser@5.60.1)(eslint@8.43.0)(typescript@5.1.6)': + '@typescript-eslint/eslint-plugin@5.60.1(@typescript-eslint/parser@5.60.1(eslint@8.43.0)(typescript@5.1.6))(eslint@8.43.0)(typescript@5.1.6)': dependencies: '@eslint-community/regexpp': 4.5.1 '@typescript-eslint/parser': 5.60.1(eslint@8.43.0)(typescript@5.1.6) @@ -2885,6 +2892,7 @@ snapshots: natural-compare-lite: 1.4.0 semver: 7.5.1 tsutils: 3.21.0(typescript@5.1.6) + optionalDependencies: typescript: 5.1.6 transitivePeerDependencies: - supports-color @@ -2896,6 +2904,7 @@ snapshots: '@typescript-eslint/typescript-estree': 5.60.1(typescript@5.1.6) debug: 4.3.4 eslint: 8.43.0 + optionalDependencies: typescript: 5.1.6 transitivePeerDependencies: - supports-color @@ -2912,6 +2921,7 @@ snapshots: debug: 4.3.4 eslint: 8.43.0 tsutils: 3.21.0(typescript@5.1.6) + optionalDependencies: typescript: 5.1.6 transitivePeerDependencies: - supports-color @@ -2927,6 +2937,7 @@ snapshots: is-glob: 4.0.3 semver: 7.5.1 tsutils: 3.21.0(typescript@5.1.6) + optionalDependencies: typescript: 5.1.6 transitivePeerDependencies: - supports-color @@ -2951,10 +2962,10 @@ snapshots: '@typescript-eslint/types': 5.60.1 eslint-visitor-keys: 3.4.1 - '@vitejs/plugin-react-swc@3.3.2(vite@4.3.9)': + '@vitejs/plugin-react-swc@3.3.2(vite@4.3.9(@types/node@20.3.2)(sugarss@4.0.1(postcss@8.4.33)))': dependencies: '@swc/core': 1.3.67 - vite: 4.3.9(@types/node@20.3.2) + vite: 4.3.9(@types/node@20.3.2)(sugarss@4.0.1(postcss@8.4.33)) transitivePeerDependencies: - '@swc/helpers' @@ -3354,18 +3365,18 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.0(@typescript-eslint/parser@5.60.1)(eslint-import-resolver-node@0.3.7)(eslint@8.43.0): + eslint-module-utils@2.8.0(@typescript-eslint/parser@5.60.1(eslint@8.43.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.7)(eslint@8.43.0): dependencies: - '@typescript-eslint/parser': 5.60.1(eslint@8.43.0)(typescript@5.1.6) debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 5.60.1(eslint@8.43.0)(typescript@5.1.6) eslint: 8.43.0 eslint-import-resolver-node: 0.3.7 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.60.1)(eslint@8.43.0): + eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.60.1(eslint@8.43.0)(typescript@5.1.6))(eslint@8.43.0): dependencies: - '@typescript-eslint/parser': 5.60.1(eslint@8.43.0)(typescript@5.1.6) array-includes: 3.1.6 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 @@ -3373,7 +3384,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.43.0 eslint-import-resolver-node: 0.3.7 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.60.1)(eslint-import-resolver-node@0.3.7)(eslint@8.43.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.60.1(eslint@8.43.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.7)(eslint@8.43.0) has: 1.0.3 is-core-module: 2.12.1 is-glob: 4.0.3 @@ -3382,17 +3393,20 @@ snapshots: resolve: 1.22.2 semver: 6.3.0 tsconfig-paths: 3.14.2 + optionalDependencies: + '@typescript-eslint/parser': 5.60.1(eslint@8.43.0)(typescript@5.1.6) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.8.0)(eslint@8.43.0)(prettier@2.8.8): + eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.8.0(eslint@8.43.0))(eslint@8.43.0)(prettier@2.8.8): dependencies: eslint: 8.43.0 - eslint-config-prettier: 8.8.0(eslint@8.43.0) prettier: 2.8.8 prettier-linter-helpers: 1.0.0 + optionalDependencies: + eslint-config-prettier: 8.8.0(eslint@8.43.0) eslint-plugin-react-hooks@4.6.0(eslint@8.43.0): dependencies: @@ -3798,6 +3812,8 @@ snapshots: dependencies: ws: 8.17.0 + jq-web@0.5.1: {} + js-base64@3.7.5: {} js-cookie@3.0.5: {} @@ -4132,7 +4148,7 @@ snapshots: raf-schd@4.0.3: {} - react-beautiful-dnd@13.1.1(react-dom@18.2.0)(react@18.2.0): + react-beautiful-dnd@13.1.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@babel/runtime': 7.21.5 css-box-model: 1.2.1 @@ -4140,7 +4156,7 @@ snapshots: raf-schd: 4.0.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-redux: 7.2.9(react-dom@18.2.0)(react@18.2.0) + react-redux: 7.2.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) redux: 4.2.1 use-memo-one: 1.1.3(react@18.2.0) transitivePeerDependencies: @@ -4161,18 +4177,19 @@ snapshots: react-is@17.0.2: {} - react-number-format@5.3.1(react-dom@18.2.0)(react@18.2.0): + react-number-format@5.3.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-query@3.39.3(react-dom@18.2.0)(react@18.2.0): + react-query@3.39.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@babel/runtime': 7.21.5 broadcast-channel: 3.7.0 match-sorter: 6.3.1 react: 18.2.0 + optionalDependencies: react-dom: 18.2.0(react@18.2.0) react-querybuilder@6.5.5(react@18.2.0): @@ -4181,7 +4198,7 @@ snapshots: immer: 10.0.3 react: 18.2.0 - react-redux@7.2.9(react-dom@18.2.0)(react@18.2.0): + react-redux@7.2.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@babel/runtime': 7.21.5 '@types/react-redux': 7.1.25 @@ -4189,32 +4206,35 @@ snapshots: loose-envify: 1.4.0 prop-types: 15.8.1 react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) react-is: 17.0.2 + optionalDependencies: + react-dom: 18.2.0(react@18.2.0) react-remove-scroll-bar@2.3.4(@types/react@18.2.14)(react@18.2.0): dependencies: - '@types/react': 18.2.14 react: 18.2.0 react-style-singleton: 2.2.1(@types/react@18.2.14)(react@18.2.0) tslib: 2.6.2 + optionalDependencies: + '@types/react': 18.2.14 react-remove-scroll@2.5.7(@types/react@18.2.14)(react@18.2.0): dependencies: - '@types/react': 18.2.14 react: 18.2.0 react-remove-scroll-bar: 2.3.4(@types/react@18.2.14)(react@18.2.0) react-style-singleton: 2.2.1(@types/react@18.2.14)(react@18.2.0) tslib: 2.6.2 use-callback-ref: 1.3.0(@types/react@18.2.14)(react@18.2.0) use-sidecar: 1.1.2(@types/react@18.2.14)(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.14 - react-resizable-panels@0.0.53(react-dom@18.2.0)(react@18.2.0): + react-resizable-panels@0.0.53(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-router-dom@6.14.0(react-dom@18.2.0)(react@18.2.0): + react-router-dom@6.14.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@remix-run/router': 1.7.0 react: 18.2.0 @@ -4226,21 +4246,22 @@ snapshots: '@remix-run/router': 1.7.0 react: 18.2.0 - react-smooth@4.0.1(react-dom@18.2.0)(react@18.2.0): + react-smooth@4.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: fast-equals: 5.0.1 prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) + react-transition-group: 4.4.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react-style-singleton@2.2.1(@types/react@18.2.14)(react@18.2.0): dependencies: - '@types/react': 18.2.14 get-nonce: 1.0.1 invariant: 2.2.4 react: 18.2.0 tslib: 2.6.2 + optionalDependencies: + '@types/react': 18.2.14 react-textarea-autosize@8.5.3(@types/react@18.2.14)(react@18.2.0): dependencies: @@ -4251,7 +4272,7 @@ snapshots: transitivePeerDependencies: - '@types/react' - react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): + react-transition-group@4.4.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@babel/runtime': 7.21.5 dom-helpers: 5.2.1 @@ -4260,7 +4281,7 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-window@1.8.9(react-dom@18.2.0)(react@18.2.0): + react-window@1.8.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@babel/runtime': 7.21.5 memoize-one: 5.2.1 @@ -4275,7 +4296,7 @@ snapshots: dependencies: decimal.js-light: 2.5.1 - recharts@2.12.7(react-dom@18.2.0)(react@18.2.0): + recharts@2.12.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: clsx: 2.1.1 eventemitter3: 4.0.7 @@ -4283,7 +4304,7 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) react-is: 16.13.1 - react-smooth: 4.0.1(react-dom@18.2.0)(react@18.2.0) + react-smooth: 4.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) recharts-scale: 0.4.5 tiny-invariant: 1.3.3 victory-vendor: 36.9.2 @@ -4507,9 +4528,10 @@ snapshots: use-callback-ref@1.3.0(@types/react@18.2.14)(react@18.2.0): dependencies: - '@types/react': 18.2.14 react: 18.2.0 tslib: 2.6.2 + optionalDependencies: + '@types/react': 18.2.14 use-composed-ref@1.3.0(react@18.2.0): dependencies: @@ -4517,14 +4539,16 @@ snapshots: use-isomorphic-layout-effect@1.1.2(@types/react@18.2.14)(react@18.2.0): dependencies: - '@types/react': 18.2.14 react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.14 use-latest@1.2.1(@types/react@18.2.14)(react@18.2.0): dependencies: - '@types/react': 18.2.14 react: 18.2.0 use-isomorphic-layout-effect: 1.1.2(@types/react@18.2.14)(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.14 use-memo-one@1.1.3(react@18.2.0): dependencies: @@ -4532,10 +4556,11 @@ snapshots: use-sidecar@1.1.2(@types/react@18.2.14)(react@18.2.0): dependencies: - '@types/react': 18.2.14 detect-node-es: 1.1.0 react: 18.2.0 tslib: 2.6.2 + optionalDependencies: + '@types/react': 18.2.14 util-deprecate@1.0.2: {} @@ -4556,14 +4581,15 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite@4.3.9(@types/node@20.3.2): + vite@4.3.9(@types/node@20.3.2)(sugarss@4.0.1(postcss@8.4.33)): dependencies: - '@types/node': 20.3.2 esbuild: 0.17.19 postcss: 8.4.33 rollup: 3.23.0 optionalDependencies: + '@types/node': 20.3.2 fsevents: 2.3.3 + sugarss: 4.0.1(postcss@8.4.33) which-boxed-primitive@1.0.2: dependencies: diff --git a/src/constants/theme.ts b/src/constants/theme.ts index 973f6e51..4d91ad67 100644 --- a/src/constants/theme.ts +++ b/src/constants/theme.ts @@ -7,4 +7,5 @@ export const LOGS_SECONDARY_TOOLBAR_HEIGHT = 68; export const STREAM_PRIMARY_TOOLBAR_CONTAINER_HEIGHT = 48; export const STREAM_PRIMARY_TOOLBAR_HEIGHT = 26; export const STREAM_SECONDARY_TOOLBAR_HRIGHT = 70; -export const SECONDARY_SIDEBAR_WIDTH = 50; \ No newline at end of file +export const SECONDARY_SIDEBAR_WIDTH = 50; +export const JSON_VIEW_TOOLBAR_HEIGHT = 50; \ No newline at end of file diff --git a/src/hooks/useQueryLogs.ts b/src/hooks/useQueryLogs.ts index 700a6194..11797eee 100644 --- a/src/hooks/useQueryLogs.ts +++ b/src/hooks/useQueryLogs.ts @@ -3,12 +3,13 @@ import { getQueryLogs, getQueryResult } from '@/api/query'; import { StatusCodes } from 'http-status-codes'; import useMountedState from './useMountedState'; import { useCallback, useEffect, useRef } from 'react'; -import { useLogsStore, logsStoreReducers, LOAD_LIMIT } from '@/pages/Stream/providers/LogsProvider'; +import { useLogsStore, logsStoreReducers, LOAD_LIMIT, isJqSearch } from '@/pages/Stream/providers/LogsProvider'; import { useAppStore } from '@/layouts/MainLayout/providers/AppProvider'; import { useQueryResult } from './useQueryResult'; import _ from 'lodash'; import { useStreamStore } from '@/pages/Stream/providers/StreamProvider'; import { AxiosError } from 'axios'; +import jqSearch from '@/utils/jqSearch'; const { setData, setTotalCount } = logsStoreReducers; @@ -46,7 +47,7 @@ export const useQueryLogs = () => { const [ { timeRange, - tableOpts: { currentOffset }, + tableOpts: { currentOffset, instantSearchValue }, custQuerySearchState, }, setLogsStore, @@ -93,7 +94,8 @@ export const useQueryLogs = () => { const data = logsQueryRes.data; if (logsQueryRes.status === StatusCodes.OK) { - return setLogsStore((store) => setData(store, data, schema)); + const jqFilteredData = isJqSearch(instantSearchValue) ? await jqSearch(data, instantSearchValue) : []; + return setLogsStore((store) => setData(store, data, schema, jqFilteredData)); } if (typeof data === 'string' && data.includes('Stream is not initialized yet')) { return setLogsStore((store) => setData(store, [], schema)); diff --git a/src/jq-web.d.ts b/src/jq-web.d.ts new file mode 100644 index 00000000..9e8114d0 --- /dev/null +++ b/src/jq-web.d.ts @@ -0,0 +1,13 @@ +declare module 'jq-web' { + interface JQWeb { + json( + records: { + [key: string]: any; // Use `any` if the values can be of any type. Replace `any` with a specific type if needed. + }, + filter: string, + ): Promise; + } + + const jq: JQWeb; + export default jq; +} \ No newline at end of file diff --git a/src/pages/Stream/Views/Explore/JSONView.tsx b/src/pages/Stream/Views/Explore/JSONView.tsx index 2c4618b7..dc9b21d1 100644 --- a/src/pages/Stream/Views/Explore/JSONView.tsx +++ b/src/pages/Stream/Views/Explore/JSONView.tsx @@ -1,14 +1,23 @@ -import { Box, Stack, Text } from '@mantine/core'; -import { ReactNode, useEffect, useRef, useState } from 'react'; +import { Box, Loader, Stack, Text, TextInput } from '@mantine/core'; +import { ChangeEvent, ReactNode, useCallback, useState } from 'react'; import classes from '../../styles/JSONView.module.css'; import EmptyBox from '@/components/Empty'; import { ErrorView, LoadingView } from './LoadingViews'; import Footer from './Footer'; import { useAppStore } from '@/layouts/MainLayout/providers/AppProvider'; -import { LOGS_PRIMARY_TOOLBAR_HEIGHT, LOGS_SECONDARY_TOOLBAR_HEIGHT, PRIMARY_HEADER_HEIGHT } from '@/constants/theme'; -import { columnsToSkip, useLogsStore } from '../../providers/LogsProvider'; +import { + LOGS_PRIMARY_TOOLBAR_HEIGHT, + JSON_VIEW_TOOLBAR_HEIGHT, + LOGS_SECONDARY_TOOLBAR_HEIGHT, + PRIMARY_HEADER_HEIGHT, +} from '@/constants/theme'; +import { columnsToSkip, useLogsStore, logsStoreReducers } from '../../providers/LogsProvider'; import { Log } from '@/@types/parseable/api/query'; import _ from 'lodash'; +import jqSearch from '@/utils/jqSearch'; +import { IconSearch } from '@tabler/icons-react'; + +const { setInstantSearchValue, applyInstantSearch, applyJqSearch } = logsStoreReducers; const Container = (props: { children: ReactNode }) => { return ( @@ -18,44 +27,132 @@ const Container = (props: { children: ReactNode }) => { ); }; -const Item = (props: { header: string; value: number | string | Date | null }) => { +const Item = (props: { header: string | null; value: string; highlight: boolean }) => { return ( - {props.header}: - {props.value} + {props.header && {props.header}: } + + {props.value}{' '} + ); }; -const Row = (props: { log: Log; headers: string[] }) => { - const { log, headers } = props; +const Row = (props: { + log: Log; + headers: string[]; + searchValue: string; + disableHighlight: boolean; + shouldHighlight: (val: number | string | Date | null) => boolean; +}) => { + const { log, headers, disableHighlight, shouldHighlight } = props; + return ( - {_.map(headers, (header) => (_.has(log, header) ? : null))} + {_.isObject(log) ? ( + _.map(headers, (header, index) => ( + + )) + ) : ( + + )} ); }; -const JsonRows = () => { - const [{ pageData, headers }] = useLogsStore((store) => store.tableOpts); +const JsonRows = (props: { isSearching: boolean }) => { + const [{ pageData, headers, instantSearchValue }] = useLogsStore((store) => store.tableOpts); const sanitizedHeaders = _.without(headers, ...columnsToSkip); + const disableHighlight = props.isSearching || _.isEmpty(instantSearchValue) || _.startsWith(instantSearchValue, '.'); + const regExp = disableHighlight ? null : new RegExp(instantSearchValue, 'i'); + + const shouldHighlight = useCallback( + (val: number | string | Date | null) => { + return !!regExp?.test(_.toString(val)); + }, + [regExp], + ); return ( - + {_.map(pageData, (d, index) => ( - + ))} ); }; -const Content = () => { +const Toolbar = (props: { isSearching: boolean; setSearching: React.Dispatch> }) => { + const { isSearching, setSearching } = props; + const [searchValue, setLogsStore] = useLogsStore((store) => store.tableOpts.instantSearchValue); + const [{ rawData, filteredData }] = useLogsStore((store) => store.data); + + const debouncedSearch = useCallback( + _.debounce(async (val: string) => { + const isJqSearch = _.startsWith(val, '.'); + if (isJqSearch) { + const jqResult = await jqSearch(rawData, val); + setLogsStore((store) => applyJqSearch(store, jqResult)); + } else { + setLogsStore(applyInstantSearch); + } + setSearching(false); + }, 1000), + [rawData], + ); + + const onChange = useCallback((e: ChangeEvent) => { + setLogsStore((store) => setInstantSearchValue(store, e.target.value)); + debouncedSearch(e.target.value); + setSearching(true); + }, []); + + if (_.isEmpty(rawData)) return null; + + return ( + + : } + placeholder="Search Placeholder" + value={searchValue} + onChange={onChange} + style={{ '--input-left-section-width': '2rem', '--input-right-section-width': '6rem' }} + rightSection={ + !_.isEmpty(searchValue) && + !isSearching && ( + + {_.size(filteredData)} Matches + + ) + } + /> + + ); +}; + +const Content = (props: { isSearching: boolean }) => { const [maximized] = useAppStore((store) => store.maximized); const primaryHeaderHeight = !maximized - ? PRIMARY_HEADER_HEIGHT + LOGS_PRIMARY_TOOLBAR_HEIGHT + LOGS_SECONDARY_TOOLBAR_HEIGHT + ? PRIMARY_HEADER_HEIGHT + LOGS_PRIMARY_TOOLBAR_HEIGHT + LOGS_SECONDARY_TOOLBAR_HEIGHT + JSON_VIEW_TOOLBAR_HEIGHT : 0; return ( @@ -63,12 +160,12 @@ const Content = () => { className={classes.innerContainer} style={{ display: 'flex', - flexDirection: 'row', + flexDirection: 'column', maxHeight: `calc(100vh - ${primaryHeaderHeight}px )`, flex: 1, overflowY: 'scroll', }}> - +
); @@ -81,13 +178,21 @@ const JsonView = (props: { showTable: boolean; }) => { const { isFetchingCount, errorMessage, hasNoData, showTable } = props; + const [isSearching, setSearching] = useState(false); + return ( {!errorMessage ? ( showTable ? ( - + <> + + + ) : hasNoData ? ( - + <> + + + ) : ( ) diff --git a/src/pages/Stream/Views/Explore/StaticLogTable.tsx b/src/pages/Stream/Views/Explore/StaticLogTable.tsx index eaf537fc..805794ff 100644 --- a/src/pages/Stream/Views/Explore/StaticLogTable.tsx +++ b/src/pages/Stream/Views/Explore/StaticLogTable.tsx @@ -1,16 +1,5 @@ import { Tbody, Thead } from '@/components/Table'; -import { - Box, - Center, - Checkbox, - Menu, - ScrollArea, - Table, - px, - ActionIcon, - Flex, - Button, -} from '@mantine/core'; +import { Box, Center, Checkbox, Menu, ScrollArea, Table, px, ActionIcon, Flex, Button } from '@mantine/core'; import { useCallback, useEffect, useRef, useState } from 'react'; import type { FC, MutableRefObject, ReactNode, RefObject } from 'react'; import LogRow from './StaticLogRow'; @@ -171,7 +160,7 @@ const LogTable = (props: { {!errorMessage ? ( showTable ? ( - + diff --git a/src/pages/Stream/components/PrimaryToolbar.tsx b/src/pages/Stream/components/PrimaryToolbar.tsx index 0f6378a6..b2ddd500 100644 --- a/src/pages/Stream/components/PrimaryToolbar.tsx +++ b/src/pages/Stream/components/PrimaryToolbar.tsx @@ -1,6 +1,6 @@ -import { Button, Stack, px } from '@mantine/core'; +import { Button, SegmentedControl, Stack, Tooltip, px, rem } from '@mantine/core'; import IconButton from '@/components/Button/IconButton'; -import { IconFilterHeart, IconMaximize, IconTrash } from '@tabler/icons-react'; +import { IconFilterHeart, IconList, IconMaximize, IconTable, IconTrash } from '@tabler/icons-react'; import { STREAM_PRIMARY_TOOLBAR_CONTAINER_HEIGHT, STREAM_PRIMARY_TOOLBAR_HEIGHT } from '@/constants/theme'; import TimeRange from '@/components/Header/TimeRange'; import RefreshInterval from '@/components/Header/RefreshInterval'; @@ -17,7 +17,7 @@ import { useLogsStore, logsStoreReducers } from '../providers/LogsProvider'; import { filterStoreReducers, useFilterStore } from '../providers/FilterProvider'; import classes from './styles/PrimaryToolbar.module.css' -const { toggleDeleteModal } = logsStoreReducers; +const { toggleDeleteModal, onToggleView } = logsStoreReducers; const { toggleSavedFiltersModal } = filterStoreReducers; const renderMaximizeIcon = () => ; const renderDeleteIcon = () => ; @@ -48,6 +48,45 @@ const DeleteStreamButton = () => { return ; }; +const ViewToggle = () => { + const [viewMode, setLogsStore] = useLogsStore((store) => store.viewMode); + const iconProps = { + style: { width: rem(20), height: rem(20), display: 'block' }, + stroke: 1.8, + }; + const onChange = useCallback((val: string) => { + if (_.includes(['json', 'table'], val)) { + setLogsStore((store) => onToggleView(store, val as 'json' | 'table')); + } + }, []); + return ( + + + + ), + }, + { + value: 'json', + label: ( + + + + ), + }, + ]} + /> + ); +} + const PrimaryToolbar = () => { const [maximized] = useAppStore((store) => store.maximized); const { view } = useParams(); @@ -76,6 +115,7 @@ const PrimaryToolbar = () => { + diff --git a/src/pages/Stream/providers/LogsProvider.tsx b/src/pages/Stream/providers/LogsProvider.tsx index d418080f..4774555e 100644 --- a/src/pages/Stream/providers/LogsProvider.tsx +++ b/src/pages/Stream/providers/LogsProvider.tsx @@ -15,7 +15,7 @@ export const columnsToSkip = ['p_metadata', 'p_tags']; type ReducerOutput = Partial; -export type ViewMode = 'table' | 'json' +export type ViewMode = 'table' | 'json'; export type ConfigType = { column: string; @@ -186,6 +186,7 @@ type LogsStore = { custQuerySearchState: CustQuerySearchState; sideBarOpen: boolean; viewMode: ViewMode; + modalOpts: { deleteModalOpen: boolean; alertsModalOpen: boolean; @@ -207,6 +208,7 @@ type LogsStore = { sortKey: string; sortOrder: 'asc' | 'desc'; filters: Record; + instantSearchValue: string; }; data: LogQueryData; @@ -259,7 +261,7 @@ type LogsStoreReducers = { getCleanStoreForRefetch: (store: LogsStore) => ReducerOutput; // data reducers - setData: (store: LogsStore, data: Log[], schema: LogStreamSchemaData | null) => ReducerOutput; + setData: (store: LogsStore, data: Log[], schema: LogStreamSchemaData | null, jqFilteredData?: Log[]) => ReducerOutput; setStreamSchema: (store: LogsStore, schema: LogStreamSchemaData) => ReducerOutput; applyCustomQuery: ( store: LogsStore, @@ -275,8 +277,15 @@ type LogsStoreReducers = { setCleanStoreForStreamChange: (store: LogsStore) => ReducerOutput; updateSavedFilterId: (store: LogsStore, savedFilterId: string | null) => ReducerOutput; + setInstantSearchValue: (store: LogsStore, value: string) => ReducerOutput; + applyInstantSearch: (store: LogsStore) => ReducerOutput; + applyJqSearch: (store: LogsStore, jqFilteredData: any[]) => ReducerOutput; + onToggleView: (store: LogsStore, viewMode: 'json' | 'table') => ReducerOutput; }; +const defaultSortKey = 'p_timestamp'; +const defaultSortOrder = 'desc' as 'desc'; + const initialState: LogsStore = { timeRange: getDefaultTimeRange(), quickFilters: defaultQuickFilters, @@ -285,7 +294,7 @@ const initialState: LogsStore = { selectedLog: null, custQuerySearchState: defaultCustQuerySearchState, sideBarOpen: false, - viewMode: 'json', + viewMode: 'table', modalOpts: { deleteModalOpen: false, alertsModalOpen: false, @@ -304,9 +313,10 @@ const initialState: LogsStore = { currentPage: 0, currentOffset: 0, headers: [], - sortKey: 'p_timestamp', - sortOrder: 'desc', + sortKey: defaultSortKey, + sortOrder: defaultSortOrder, filters: {}, + instantSearchValue: '', }, // data @@ -386,6 +396,10 @@ const resetQuickFilters = (_store: LogsStore) => { return { quickFilters: defaultQuickFilters }; }; +const setInstantSearchValue = (store: LogsStore, value: string) => { + return { tableOpts: { ...store.tableOpts, instantSearchValue: value } }; +}; + const setLiveTailStatus = (store: LogsStore, liveTailStatus: LiveTailStatus) => { const { liveTailConfig } = store; return { liveTailConfig: { ...liveTailConfig, liveTailStatus } }; @@ -498,6 +512,27 @@ const filterAndSortData = ( return sortedData; }; +const searchAndSortData = (opts: { searchValue: string }, data: Log[]) => { + const { searchValue } = opts; + const regExp = new RegExp(searchValue, 'i'); + const filteredData = _.isEmpty(searchValue) + ? data + : (_.reduce( + data, + (acc: Log[], d: Log) => { + const allValues = _.chain(d) + .values() + .map((e) => _.toString(e)) + .value(); + const doesMatch = _.some(allValues, (str) => regExp.test(str)); + return doesMatch ? [...acc, d] : acc; + }, + [], + ) as Log[]); + const sortedData = _.orderBy(filteredData, [defaultSortKey], [defaultSortOrder]); + return sortedData; +}; + const setTableHeaders = (store: LogsStore, schema: LogStreamSchemaData) => { const { data: existingData, custQuerySearchState, tableOpts } = store; const { filteredData } = existingData; @@ -513,14 +548,25 @@ const setTableHeaders = (store: LogsStore, schema: LogStreamSchemaData) => { }; }; -const setData = (store: LogsStore, data: Log[], schema: LogStreamSchemaData | null) => { +export const isJqSearch = (value: string) => { + return _.startsWith(value, '.'); +}; + +const setData = (store: LogsStore, data: Log[], schema: LogStreamSchemaData | null, jqFilteredData?: Log[]) => { const { data: existingData, tableOpts, custQuerySearchState: { isQuerySearchActive, activeMode }, + viewMode, } = store; + const isJsonView = viewMode === 'json'; const currentPage = tableOpts.currentPage === 0 ? 1 : tableOpts.currentPage; - const filteredData = filterAndSortData(tableOpts, data); + const filteredData = + isJsonView && !_.isEmpty(tableOpts.instantSearchValue) + ? isJqSearch(tableOpts.instantSearchValue) + ? jqFilteredData || [] + : searchAndSortData({ searchValue: tableOpts.instantSearchValue }, data) + : filterAndSortData(tableOpts, data); const newPageSlice = filteredData && getPageSlice(currentPage, tableOpts.perPage, filteredData); const newHeaders = isQuerySearchActive && activeMode === 'sql' ? makeHeadersfromData(data) : makeHeadersFromSchema(schema); @@ -533,7 +579,7 @@ const setData = (store: LogsStore, data: Log[], schema: LogStreamSchemaData | nu currentPage, totalPages: getTotalPages(filteredData, tableOpts.perPage), }, - data: { ...existingData, rawData: data, filteredData: data }, + data: { ...existingData, rawData: data, filteredData: filteredData }, }; }; @@ -748,6 +794,48 @@ const setAndFilterData = (store: LogsStore, filterKey: string, filterValues: str }; }; +const applyInstantSearch = (store: LogsStore) => { + const { data, tableOpts } = store; + const { instantSearchValue: searchValue } = tableOpts; + const filteredData = searchAndSortData({ searchValue }, data.rawData); + const currentPage = 1; + const newPageSlice = getPageSlice(currentPage, tableOpts.perPage, filteredData); + + return { + data: { + ...data, + filteredData, + }, + tableOpts: { + ...tableOpts, + filters: {}, + pageData: newPageSlice, + currentPage, + totalPages: getTotalPages(filteredData, tableOpts.perPage), + }, + }; +}; + +const applyJqSearch = (store: LogsStore, jqFilteredData: any[]) => { + const { data, tableOpts } = store; + const currentPage = 1; + const newPageSlice = getPageSlice(currentPage, tableOpts.perPage, jqFilteredData); + + return { + data: { + ...data, + filteredData: jqFilteredData, + }, + tableOpts: { + ...tableOpts, + filters: {}, + pageData: newPageSlice, + currentPage, + totalPages: getTotalPages(jqFilteredData, tableOpts.perPage), + }, + }; +}; + const getUniqueValues = (data: Log[], key: string) => { return _.chain(data) .map(key) @@ -787,6 +875,12 @@ const toggleSideBar = (store: LogsStore) => { }; }; +const onToggleView = (_store: LogsStore, viewMode: 'json' | 'table') => { + return { + viewMode, + }; +}; + const logsStoreReducers: LogsStoreReducers = { setTimeRange, setshiftInterval, @@ -828,6 +922,10 @@ const logsStoreReducers: LogsStoreReducers = { toggleSideBar, setTableHeaders, updateSavedFilterId, + setInstantSearchValue, + applyInstantSearch, + applyJqSearch, + onToggleView, }; export { LogsProvider, useLogsStore, logsStoreReducers }; diff --git a/src/utils/jqSearch.ts b/src/utils/jqSearch.ts new file mode 100644 index 00000000..f2399d90 --- /dev/null +++ b/src/utils/jqSearch.ts @@ -0,0 +1,20 @@ +import jq from 'jq-web'; +import _ from 'lodash'; + +const jqSearch = async ( + records: { + [key: string]: any; + }, + filter: string, +) => { + try { + const result = await jq.json(records, filter); + console.log(result) + return _.isArray(result) ? _.some(result, r => !_.isNull(r)) ? result : [] : [result]; + } catch (e) { + console.log(e); + return []; + } +}; + +export default jqSearch; From 5b289abd45537c0083f6bbcc5569f433266a1be3 Mon Sep 17 00:00:00 2001 From: balaji-jr Date: Sun, 28 Jul 2024 11:59:32 +0530 Subject: [PATCH 4/9] Minor review feedbacks --- src/pages/Stream/Views/Explore/JSONView.tsx | 10 ++++---- .../Stream/components/PrimaryToolbar.tsx | 8 +++---- src/pages/Stream/providers/LogsProvider.tsx | 23 ++++++++++++++++--- src/utils/jqSearch.ts | 6 ++--- 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/pages/Stream/Views/Explore/JSONView.tsx b/src/pages/Stream/Views/Explore/JSONView.tsx index dc9b21d1..88524389 100644 --- a/src/pages/Stream/Views/Explore/JSONView.tsx +++ b/src/pages/Stream/Views/Explore/JSONView.tsx @@ -11,7 +11,7 @@ import { LOGS_SECONDARY_TOOLBAR_HEIGHT, PRIMARY_HEADER_HEIGHT, } from '@/constants/theme'; -import { columnsToSkip, useLogsStore, logsStoreReducers } from '../../providers/LogsProvider'; +import { columnsToSkip, useLogsStore, logsStoreReducers, isJqSearch } from '../../providers/LogsProvider'; import { Log } from '@/@types/parseable/api/query'; import _ from 'lodash'; import jqSearch from '@/utils/jqSearch'; @@ -74,7 +74,7 @@ const Row = (props: { const JsonRows = (props: { isSearching: boolean }) => { const [{ pageData, headers, instantSearchValue }] = useLogsStore((store) => store.tableOpts); const sanitizedHeaders = _.without(headers, ...columnsToSkip); - const disableHighlight = props.isSearching || _.isEmpty(instantSearchValue) || _.startsWith(instantSearchValue, '.'); + const disableHighlight = props.isSearching || _.isEmpty(instantSearchValue) || isJqSearch(instantSearchValue); const regExp = disableHighlight ? null : new RegExp(instantSearchValue, 'i'); const shouldHighlight = useCallback( @@ -107,8 +107,8 @@ const Toolbar = (props: { isSearching: boolean; setSearching: React.Dispatch { - const isJqSearch = _.startsWith(val, '.'); - if (isJqSearch) { + const isJq = isJqSearch(val); + if (isJq) { const jqResult = await jqSearch(rawData, val); setLogsStore((store) => applyJqSearch(store, jqResult)); } else { @@ -131,7 +131,7 @@ const Toolbar = (props: { isSearching: boolean; setSearching: React.Dispatch : } - placeholder="Search Placeholder" + placeholder="Search loaded data with text or jq. For jq input try `jq .[]`" value={searchValue} onChange={onChange} style={{ '--input-left-section-width': '2rem', '--input-right-section-width': '6rem' }} diff --git a/src/pages/Stream/components/PrimaryToolbar.tsx b/src/pages/Stream/components/PrimaryToolbar.tsx index b2ddd500..75669640 100644 --- a/src/pages/Stream/components/PrimaryToolbar.tsx +++ b/src/pages/Stream/components/PrimaryToolbar.tsx @@ -1,6 +1,6 @@ import { Button, SegmentedControl, Stack, Tooltip, px, rem } from '@mantine/core'; import IconButton from '@/components/Button/IconButton'; -import { IconFilterHeart, IconList, IconMaximize, IconTable, IconTrash } from '@tabler/icons-react'; +import { IconBraces, IconFilterHeart, IconMaximize, IconTable, IconTrash } from '@tabler/icons-react'; import { STREAM_PRIMARY_TOOLBAR_CONTAINER_HEIGHT, STREAM_PRIMARY_TOOLBAR_HEIGHT } from '@/constants/theme'; import TimeRange from '@/components/Header/TimeRange'; import RefreshInterval from '@/components/Header/RefreshInterval'; @@ -77,8 +77,8 @@ const ViewToggle = () => { { value: 'json', label: ( - - + + ), }, @@ -115,8 +115,8 @@ const PrimaryToolbar = () => { - +
diff --git a/src/pages/Stream/providers/LogsProvider.tsx b/src/pages/Stream/providers/LogsProvider.tsx index 4774555e..6a6b4466 100644 --- a/src/pages/Stream/providers/LogsProvider.tsx +++ b/src/pages/Stream/providers/LogsProvider.tsx @@ -549,7 +549,7 @@ const setTableHeaders = (store: LogsStore, schema: LogStreamSchemaData) => { }; export const isJqSearch = (value: string) => { - return _.startsWith(value, '.'); + return _.startsWith(value, 'jq .'); }; const setData = (store: LogsStore, data: Log[], schema: LogStreamSchemaData | null, jqFilteredData?: Log[]) => { @@ -875,9 +875,26 @@ const toggleSideBar = (store: LogsStore) => { }; }; -const onToggleView = (_store: LogsStore, viewMode: 'json' | 'table') => { +const onToggleView = (store: LogsStore, viewMode: 'json' | 'table') => { + const { data, tableOpts } = store; + const filteredData = filterAndSortData({ sortOrder: defaultSortOrder, sortKey: defaultSortKey, filters: {} }, data.rawData); + const currentPage = tableOpts.currentPage; + const newPageSlice = getPageSlice(currentPage, tableOpts.perPage, filteredData); + return { - viewMode, + data: { + ...data, + filteredData, + }, + tableOpts: { + ...tableOpts, + filters: {}, + pageData: newPageSlice, + instantSearchValue: '', + currentPage, + totalPages: getTotalPages(filteredData, tableOpts.perPage), + }, + viewMode }; }; diff --git a/src/utils/jqSearch.ts b/src/utils/jqSearch.ts index f2399d90..afb25f8d 100644 --- a/src/utils/jqSearch.ts +++ b/src/utils/jqSearch.ts @@ -7,10 +7,10 @@ const jqSearch = async ( }, filter: string, ) => { + const sanitizedJqCommand = filter.replace(/^jq\s*/, ''); try { - const result = await jq.json(records, filter); - console.log(result) - return _.isArray(result) ? _.some(result, r => !_.isNull(r)) ? result : [] : [result]; + const result = await jq.json(records, sanitizedJqCommand); + return _.isArray(result) ? (_.some(result, (r) => !_.isNull(r)) ? result : []) : [result]; } catch (e) { console.log(e); return []; From 456e681d057d649ea393d24baa3d5b19d6ffba21 Mon Sep 17 00:00:00 2001 From: balaji-jr Date: Mon, 29 Jul 2024 13:06:58 +0530 Subject: [PATCH 5/9] fixed view toggle position and json table height --- src/pages/Stream/Views/Explore/JSONView.tsx | 61 +++++++------------ .../Stream/Views/Explore/StaticLogTable.tsx | 4 +- .../Stream/components/PrimaryToolbar.tsx | 2 +- src/pages/Stream/providers/LogsProvider.tsx | 4 +- src/pages/Stream/styles/JSONView.module.css | 4 ++ 5 files changed, 33 insertions(+), 42 deletions(-) diff --git a/src/pages/Stream/Views/Explore/JSONView.tsx b/src/pages/Stream/Views/Explore/JSONView.tsx index 88524389..6d40fcba 100644 --- a/src/pages/Stream/Views/Explore/JSONView.tsx +++ b/src/pages/Stream/Views/Explore/JSONView.tsx @@ -6,10 +6,9 @@ import { ErrorView, LoadingView } from './LoadingViews'; import Footer from './Footer'; import { useAppStore } from '@/layouts/MainLayout/providers/AppProvider'; import { - LOGS_PRIMARY_TOOLBAR_HEIGHT, - JSON_VIEW_TOOLBAR_HEIGHT, - LOGS_SECONDARY_TOOLBAR_HEIGHT, PRIMARY_HEADER_HEIGHT, + STREAM_PRIMARY_TOOLBAR_CONTAINER_HEIGHT, + STREAM_SECONDARY_TOOLBAR_HRIGHT, } from '@/constants/theme'; import { columnsToSkip, useLogsStore, logsStoreReducers, isJqSearch } from '../../providers/LogsProvider'; import { Log } from '@/@types/parseable/api/query'; @@ -19,14 +18,6 @@ import { IconSearch } from '@tabler/icons-react'; const { setInstantSearchValue, applyInstantSearch, applyJqSearch } = logsStoreReducers; -const Container = (props: { children: ReactNode }) => { - return ( - - {props.children} - - ); -}; - const Item = (props: { header: string | null; value: string; highlight: boolean }) => { return ( @@ -148,27 +139,8 @@ const Toolbar = (props: { isSearching: boolean; setSearching: React.Dispatch { - const [maximized] = useAppStore((store) => store.maximized); - - const primaryHeaderHeight = !maximized - ? PRIMARY_HEADER_HEIGHT + LOGS_PRIMARY_TOOLBAR_HEIGHT + LOGS_SECONDARY_TOOLBAR_HEIGHT + JSON_VIEW_TOOLBAR_HEIGHT - : 0; - return ( - - - - - - ); +const TableContainer = (props: { children: ReactNode }) => { + return {props.children}; }; const JsonView = (props: { @@ -177,17 +149,30 @@ const JsonView = (props: { hasNoData: boolean; showTable: boolean; }) => { + const [maximized] = useAppStore((store) => store.maximized); + const { isFetchingCount, errorMessage, hasNoData, showTable } = props; const [isSearching, setSearching] = useState(false); + const primaryHeaderHeight = !maximized + ? PRIMARY_HEADER_HEIGHT + STREAM_PRIMARY_TOOLBAR_CONTAINER_HEIGHT + STREAM_SECONDARY_TOOLBAR_HRIGHT + : 0; return ( - + {!errorMessage ? ( showTable ? ( - <> - - - + + + + + + + + + + ) : hasNoData ? ( <> @@ -200,7 +185,7 @@ const JsonView = (props: { )}
- + ); }; diff --git a/src/pages/Stream/Views/Explore/StaticLogTable.tsx b/src/pages/Stream/Views/Explore/StaticLogTable.tsx index 805794ff..81e1fc34 100644 --- a/src/pages/Stream/Views/Explore/StaticLogTable.tsx +++ b/src/pages/Stream/Views/Explore/StaticLogTable.tsx @@ -10,7 +10,7 @@ import EmptyBox from '@/components/Empty'; import Column from '../../components/Column'; import FilterPills from '../../components/FilterPills'; import tableStyles from '../../styles/Logs.module.css'; -import { LOGS_PRIMARY_TOOLBAR_HEIGHT, LOGS_SECONDARY_TOOLBAR_HEIGHT, PRIMARY_HEADER_HEIGHT } from '@/constants/theme'; +import { PRIMARY_HEADER_HEIGHT, STREAM_PRIMARY_TOOLBAR_CONTAINER_HEIGHT, STREAM_SECONDARY_TOOLBAR_HRIGHT } from '@/constants/theme'; import { useLogsStore, logsStoreReducers } from '../../providers/LogsProvider'; import { useAppStore } from '@/layouts/MainLayout/providers/AppProvider'; import _ from 'lodash'; @@ -152,7 +152,7 @@ const LogTable = (props: { }); const [maximized] = useAppStore((store) => store.maximized); const primaryHeaderHeight = !maximized - ? PRIMARY_HEADER_HEIGHT + LOGS_PRIMARY_TOOLBAR_HEIGHT + LOGS_SECONDARY_TOOLBAR_HEIGHT + ? PRIMARY_HEADER_HEIGHT + STREAM_PRIMARY_TOOLBAR_CONTAINER_HEIGHT + STREAM_SECONDARY_TOOLBAR_HRIGHT : 0; return ( diff --git a/src/pages/Stream/components/PrimaryToolbar.tsx b/src/pages/Stream/components/PrimaryToolbar.tsx index 75669640..8d7a6160 100644 --- a/src/pages/Stream/components/PrimaryToolbar.tsx +++ b/src/pages/Stream/components/PrimaryToolbar.tsx @@ -116,9 +116,9 @@ const PrimaryToolbar = () => { + - ) : view === 'live-tail' ? ( diff --git a/src/pages/Stream/providers/LogsProvider.tsx b/src/pages/Stream/providers/LogsProvider.tsx index 6a6b4466..4984ed0f 100644 --- a/src/pages/Stream/providers/LogsProvider.tsx +++ b/src/pages/Stream/providers/LogsProvider.tsx @@ -356,9 +356,11 @@ const setTimeRange = ( const { startTime, endTime, type } = payload; const label = `${startTime.format('hh:mm A DD MMM YY')} to ${endTime.format('hh:mm A DD MMM YY')}`; const interval = endTime.diff(startTime, 'milliseconds'); + const cleanStore = getCleanStoreForRefetch(store); return { - ...getCleanStoreForRefetch(store), + ...cleanStore, timeRange: { ...store.timeRange, startTime: startTime.toDate(), endTime: endTime.toDate(), label, interval, type }, + viewMode: store.viewMode }; }; diff --git a/src/pages/Stream/styles/JSONView.module.css b/src/pages/Stream/styles/JSONView.module.css index 69d856e5..84ab861e 100644 --- a/src/pages/Stream/styles/JSONView.module.css +++ b/src/pages/Stream/styles/JSONView.module.css @@ -1,5 +1,9 @@ .container { position: relative; + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; } .innerContainer { From c7a07b998c1c4a94ca03a412115723479c20bef0 Mon Sep 17 00:00:00 2001 From: balaji-jr Date: Mon, 29 Jul 2024 14:40:54 +0530 Subject: [PATCH 6/9] implemented copy icon on json tabvle row --- src/pages/Stream/Views/Explore/JSONView.tsx | 35 +++++++++++++++++-- .../Stream/components/EventTimeLineGraph.tsx | 2 +- src/pages/Stream/styles/JSONView.module.css | 14 ++++++-- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/pages/Stream/Views/Explore/JSONView.tsx b/src/pages/Stream/Views/Explore/JSONView.tsx index 6d40fcba..63116670 100644 --- a/src/pages/Stream/Views/Explore/JSONView.tsx +++ b/src/pages/Stream/Views/Explore/JSONView.tsx @@ -1,5 +1,5 @@ import { Box, Loader, Stack, Text, TextInput } from '@mantine/core'; -import { ChangeEvent, ReactNode, useCallback, useState } from 'react'; +import { ChangeEvent, ReactNode, useCallback, useRef, useState } from 'react'; import classes from '../../styles/JSONView.module.css'; import EmptyBox from '@/components/Empty'; import { ErrorView, LoadingView } from './LoadingViews'; @@ -14,7 +14,7 @@ import { columnsToSkip, useLogsStore, logsStoreReducers, isJqSearch } from '../. import { Log } from '@/@types/parseable/api/query'; import _ from 'lodash'; import jqSearch from '@/utils/jqSearch'; -import { IconSearch } from '@tabler/icons-react'; +import { IconCheck, IconCopy, IconSearch } from '@tabler/icons-react'; const { setInstantSearchValue, applyInstantSearch, applyJqSearch } = logsStoreReducers; @@ -29,6 +29,36 @@ const Item = (props: { header: string | null; value: string; highlight: boolean ); }; +const CopyIcon = (props: { log: Log }) => { + const copyIconRef = useRef(null); + const copiedIconRef = useRef(null); + + const onCopy = async () => { + if (copyIconRef.current && copiedIconRef.current) { + copyIconRef.current.style.display = 'none'; + copiedIconRef.current.style.display = 'inline-block'; + } + await navigator.clipboard.writeText(JSON.stringify(props.log, null, 2)); + setTimeout(() => { + if (copyIconRef.current && copiedIconRef.current) { + copiedIconRef.current.style.display = 'none'; + copyIconRef.current.style.display = 'inline-block'; + } + }, 1500); + }; + + return ( + + + + + + + + + ); +}; + const Row = (props: { log: Log; headers: string[]; @@ -58,6 +88,7 @@ const Row = (props: { /> )} + ); }; diff --git a/src/pages/Stream/components/EventTimeLineGraph.tsx b/src/pages/Stream/components/EventTimeLineGraph.tsx index 3053df85..abf31305 100644 --- a/src/pages/Stream/components/EventTimeLineGraph.tsx +++ b/src/pages/Stream/components/EventTimeLineGraph.tsx @@ -308,7 +308,7 @@ const EventTimeLineGraph = () => { withXAxis={false} withYAxis={hasData} yAxisProps={{ tickCount: 2, tickFormatter: (value) => `${HumanizeNumber(value)}` }} - referenceLines={[{ y: avgEventCount, color: 'red.6', label: 'Avg' }]} + referenceLines={[{ y: avgEventCount, color: 'gray.5', label: 'Avg' }]} tickLine="none" areaChartProps={{ onClick: setTimeRangeFromGraph, style: { cursor: 'pointer' } }} gridAxis="xy" diff --git a/src/pages/Stream/styles/JSONView.module.css b/src/pages/Stream/styles/JSONView.module.css index 84ab861e..a357a1d2 100644 --- a/src/pages/Stream/styles/JSONView.module.css +++ b/src/pages/Stream/styles/JSONView.module.css @@ -18,8 +18,16 @@ border-bottom: 1px solid var(--mantine-color-gray-1); padding: 0.25rem 1rem; cursor: pointer; + &:hover { background-color: var(--mantine-color-gray-1); + .copyIcon { + color: var(--mantine-color-gray-7); + } + } + + .copyIcon { + color: transparent; } } @@ -27,7 +35,7 @@ font-weight: 500; font-size: 0.65rem; word-break: normal; - word-wrap: break-word; + word-wrap: break-word; font-family: monospace; } @@ -36,11 +44,11 @@ font-size: 0.65rem; color: var(--mantine-color-gray-7); word-break: normal; - word-wrap: break-word; + word-wrap: break-word; font-family: monospace; } .itemContainer { margin-right: 0.2rem; -} \ No newline at end of file +} From 77e49465988a842b09be1c3d8d635cdc71723b34 Mon Sep 17 00:00:00 2001 From: balaji-jr Date: Mon, 29 Jul 2024 14:58:46 +0530 Subject: [PATCH 7/9] Fix - keep the focus on toolbar when there are no matches --- src/pages/Stream/Views/Explore/JSONView.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/pages/Stream/Views/Explore/JSONView.tsx b/src/pages/Stream/Views/Explore/JSONView.tsx index 63116670..9c5c0134 100644 --- a/src/pages/Stream/Views/Explore/JSONView.tsx +++ b/src/pages/Stream/Views/Explore/JSONView.tsx @@ -36,20 +36,20 @@ const CopyIcon = (props: { log: Log }) => { const onCopy = async () => { if (copyIconRef.current && copiedIconRef.current) { copyIconRef.current.style.display = 'none'; - copiedIconRef.current.style.display = 'inline-block'; + copiedIconRef.current.style.display = 'flex'; } await navigator.clipboard.writeText(JSON.stringify(props.log, null, 2)); setTimeout(() => { if (copyIconRef.current && copiedIconRef.current) { copiedIconRef.current.style.display = 'none'; - copyIconRef.current.style.display = 'inline-block'; + copyIconRef.current.style.display = 'flex'; } }, 1500); }; return ( - + @@ -190,6 +190,7 @@ const JsonView = (props: { return ( + {!errorMessage ? ( showTable ? ( @@ -197,7 +198,6 @@ const JsonView = (props: { className={classes.innerContainer} style={{ display: 'flex', flexDirection: 'row', maxHeight: `calc(100vh - ${primaryHeaderHeight}px )` }}> - @@ -206,7 +206,6 @@ const JsonView = (props: { ) : hasNoData ? ( <> - ) : ( From 6756958665f77c9c1abaf41592ecbda769c0356b Mon Sep 17 00:00:00 2001 From: balaji-jr Date: Mon, 29 Jul 2024 15:06:00 +0530 Subject: [PATCH 8/9] fixed - empty scroll bar appearing in some browsers --- src/pages/Stream/Views/Explore/JSONView.tsx | 2 +- src/pages/Stream/styles/Logs.module.css | 26 --------------------- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/src/pages/Stream/Views/Explore/JSONView.tsx b/src/pages/Stream/Views/Explore/JSONView.tsx index 9c5c0134..b6d3ff9e 100644 --- a/src/pages/Stream/Views/Explore/JSONView.tsx +++ b/src/pages/Stream/Views/Explore/JSONView.tsx @@ -198,7 +198,7 @@ const JsonView = (props: { className={classes.innerContainer} style={{ display: 'flex', flexDirection: 'row', maxHeight: `calc(100vh - ${primaryHeaderHeight}px )` }}> - + diff --git a/src/pages/Stream/styles/Logs.module.css b/src/pages/Stream/styles/Logs.module.css index 0432930d..4f7cf4bf 100644 --- a/src/pages/Stream/styles/Logs.module.css +++ b/src/pages/Stream/styles/Logs.module.css @@ -1,35 +1,9 @@ -.mycontainer { - flex-direction: column; - flex: 1 1 auto; - overflow: scroll; - align-items: flex-start; -} - .col { display: flex; flex-direction: column; width: max-content; } -.myrow { - display: flex; - flex-direction: row; - height: 48px; - background-color: #e7eff5; - border-bottom: 1px solid red; -} - -.mycell { - display: inline-flex; - justify-content: center; - align-items: center; - min-height: 48px; - background-color: #fff; - width: 300px; - background: transparent; - background-color: #f8f9fb; -} - .container { position: relative; flex: 1; From 7baf050d5eb548e63ab35e314eea393a9e2c7a58 Mon Sep 17 00:00:00 2001 From: balaji-jr Date: Mon, 29 Jul 2024 15:14:06 +0530 Subject: [PATCH 9/9] fix - extra scrollbar appearing in some browsers --- src/pages/Stream/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Stream/index.tsx b/src/pages/Stream/index.tsx index 09ecb937..ed74396f 100644 --- a/src/pages/Stream/index.tsx +++ b/src/pages/Stream/index.tsx @@ -80,7 +80,7 @@ const Logs: FC = () => { gap={0} style={{ maxHeight: `calc(100vh - ${maximized ? 0 : PRIMARY_HEADER_HEIGHT}px )`, - overflow: 'scroll', + overflowY: 'scroll', flex: 1, }}>
URLhttps://demo.parseable.iohttps://demo.parseable.com
Username