From bad80def907186b7cf67c9bb070dd24f24e9d15c Mon Sep 17 00:00:00 2001 From: volodfast Date: Wed, 15 Feb 2023 11:25:15 +0200 Subject: [PATCH] feat: add list and table views for logs (#2163) * feat: add list and table views for logs * chore: some of the changes are updated * chore: some of the refactoring is done * chore: px to updated to rem * chore: constant is moved to local storage * refactor: some of the refactoring is updated * chore: some of the changes are updated * fix: resize log table issue * chore: logs is updated * chore: resize header is updated * chore: font observer is added in package json and hook is added for same * chore: no logs text is updated * chore: no logs text is updated * chore: updated some feedback in raw logs line * chore: types is added --------- Co-authored-by: Palash Gupta Co-authored-by: Pranay Prateek Co-authored-by: Vishal Sharma Co-authored-by: Chintan Sudani --- frontend/package.json | 2 + .../Logs/{LogItem => ListLogView}/index.tsx | 13 ++- .../Logs/{LogItem => ListLogView}/styles.ts | 0 .../Logs/{LogItem => ListLogView}/util.ts | 0 .../src/components/Logs/RawLogView/config.ts | 5 + .../src/components/Logs/RawLogView/index.tsx | 48 ++++++++ .../src/components/Logs/RawLogView/styles.ts | 24 ++++ .../src/components/Logs/TableView/config.ts | 12 ++ .../src/components/Logs/TableView/index.tsx | 106 ++++++++++++++++++ .../ResizeTable/ResizableHeader.tsx | 9 +- frontend/src/constants/localStorage.ts | 2 + frontend/src/container/LogControls/index.tsx | 32 ++++-- frontend/src/container/LogsFilters/index.tsx | 8 +- frontend/src/container/LogsFilters/styles.ts | 8 +- .../src/container/LogsSearchFilter/index.tsx | 1 + frontend/src/container/LogsTable/index.tsx | 102 ++++++++++++++--- frontend/src/container/LogsTable/styles.ts | 6 +- frontend/src/hooks/useFontObserver.tsx | 69 ++++++++++++ frontend/src/index.html.ejs | 8 +- frontend/src/pages/Logs/PopoverContent.tsx | 28 +++++ frontend/src/pages/Logs/config.ts | 25 +++++ frontend/src/pages/Logs/hooks.ts | 78 +++++++++++++ frontend/src/pages/Logs/index.tsx | 99 +++++++++++++++- frontend/src/pages/Logs/types.ts | 17 +++ frontend/src/pages/Logs/utils.ts | 7 ++ .../src/store/actions/logs/setLInesPerRow.ts | 14 +++ .../src/store/actions/logs/setViewMode.ts | 15 +++ frontend/src/store/reducers/logs.ts | 18 +++ frontend/src/types/actions/logs.ts | 14 +++ frontend/src/types/reducer/logs.ts | 3 + 30 files changed, 714 insertions(+), 59 deletions(-) rename frontend/src/components/Logs/{LogItem => ListLogView}/index.tsx (95%) rename frontend/src/components/Logs/{LogItem => ListLogView}/styles.ts (100%) rename frontend/src/components/Logs/{LogItem => ListLogView}/util.ts (100%) create mode 100644 frontend/src/components/Logs/RawLogView/config.ts create mode 100644 frontend/src/components/Logs/RawLogView/index.tsx create mode 100644 frontend/src/components/Logs/RawLogView/styles.ts create mode 100644 frontend/src/components/Logs/TableView/config.ts create mode 100644 frontend/src/components/Logs/TableView/index.tsx create mode 100644 frontend/src/hooks/useFontObserver.tsx create mode 100644 frontend/src/pages/Logs/PopoverContent.tsx create mode 100644 frontend/src/pages/Logs/config.ts create mode 100644 frontend/src/pages/Logs/hooks.ts create mode 100644 frontend/src/pages/Logs/types.ts create mode 100644 frontend/src/pages/Logs/utils.ts create mode 100644 frontend/src/store/actions/logs/setLInesPerRow.ts create mode 100644 frontend/src/store/actions/logs/setViewMode.ts diff --git a/frontend/package.json b/frontend/package.json index 0486067372..ba9e1d3e05 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -55,6 +55,7 @@ "event-source-polyfill": "1.0.31", "file-loader": "6.1.1", "flat": "^5.0.2", + "fontfaceobserver": "2.3.0", "history": "4.10.1", "html-webpack-plugin": "5.1.0", "i18next": "^21.6.12", @@ -127,6 +128,7 @@ "@types/d3-tip": "^3.5.5", "@types/event-source-polyfill": "^1.0.0", "@types/flat": "^5.0.2", + "@types/fontfaceobserver": "2.1.0", "@types/jest": "^27.5.1", "@types/lodash-es": "^4.17.4", "@types/mini-css-extract-plugin": "^2.5.1", diff --git a/frontend/src/components/Logs/LogItem/index.tsx b/frontend/src/components/Logs/ListLogView/index.tsx similarity index 95% rename from frontend/src/components/Logs/LogItem/index.tsx rename to frontend/src/components/Logs/ListLogView/index.tsx index e09f6c4ba7..d8108062aa 100644 --- a/frontend/src/components/Logs/LogItem/index.tsx +++ b/frontend/src/components/Logs/ListLogView/index.tsx @@ -4,17 +4,21 @@ import { Button, Divider, Row, Typography } from 'antd'; import { map } from 'd3'; import dayjs from 'dayjs'; import { useNotifications } from 'hooks/useNotifications'; +// utils import { FlatLogData } from 'lib/logs/flatLogData'; import React, { useCallback, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useCopyToClipboard } from 'react-use'; +// interfaces import { AppState } from 'store/reducers'; import { SET_DETAILED_LOG_DATA } from 'types/actions/logs'; import { ILog } from 'types/api/logs/log'; import { ILogsReducer } from 'types/reducer/logs'; +// components import AddToQueryHOC from '../AddToQueryHOC'; import CopyClipboardHOC from '../CopyClipboardHOC'; +// styles import { Container, LogContainer, Text, TextContainer } from './styles'; import { isValidLogField } from './util'; @@ -37,6 +41,7 @@ function LogGeneralField({ fieldKey, fieldValue }: LogFieldProps): JSX.Element { ); } + function LogSelectedField({ fieldKey = '', fieldValue = '', @@ -70,15 +75,17 @@ function LogSelectedField({ ); } -interface LogItemProps { +interface ListLogViewProps { logData: ILog; } -function LogItem({ logData }: LogItemProps): JSX.Element { +function ListLogView({ logData }: ListLogViewProps): JSX.Element { const { fields: { selected }, } = useSelector((state) => state.logs); + const dispatch = useDispatch(); const flattenLogData = useMemo(() => FlatLogData(logData), [logData]); + const [, setCopy] = useCopyToClipboard(); const { notifications } = useNotifications(); @@ -152,4 +159,4 @@ function LogItem({ logData }: LogItemProps): JSX.Element { ); } -export default LogItem; +export default ListLogView; diff --git a/frontend/src/components/Logs/LogItem/styles.ts b/frontend/src/components/Logs/ListLogView/styles.ts similarity index 100% rename from frontend/src/components/Logs/LogItem/styles.ts rename to frontend/src/components/Logs/ListLogView/styles.ts diff --git a/frontend/src/components/Logs/LogItem/util.ts b/frontend/src/components/Logs/ListLogView/util.ts similarity index 100% rename from frontend/src/components/Logs/LogItem/util.ts rename to frontend/src/components/Logs/ListLogView/util.ts diff --git a/frontend/src/components/Logs/RawLogView/config.ts b/frontend/src/components/Logs/RawLogView/config.ts new file mode 100644 index 0000000000..66adeae252 --- /dev/null +++ b/frontend/src/components/Logs/RawLogView/config.ts @@ -0,0 +1,5 @@ +export const rawLineStyle: React.CSSProperties = { + marginBottom: 0, + fontFamily: "'Fira Code', monospace", + fontWeight: 300, +}; diff --git a/frontend/src/components/Logs/RawLogView/index.tsx b/frontend/src/components/Logs/RawLogView/index.tsx new file mode 100644 index 0000000000..9cfafc5bd3 --- /dev/null +++ b/frontend/src/components/Logs/RawLogView/index.tsx @@ -0,0 +1,48 @@ +import { ExpandAltOutlined } from '@ant-design/icons'; +import { Typography } from 'antd'; +import dayjs from 'dayjs'; +// hooks +import { useIsDarkMode } from 'hooks/useDarkMode'; +import React, { useCallback, useMemo } from 'react'; +// interfaces +import { ILog } from 'types/api/logs/log'; + +import { rawLineStyle } from './config'; +// styles +import { ExpandIconWrapper, RawLogViewContainer } from './styles'; + +interface RawLogViewProps { + data: ILog; + linesPerRow: number; + onClickExpand: (log: ILog) => void; +} + +function RawLogView(props: RawLogViewProps): JSX.Element { + const { data, linesPerRow, onClickExpand } = props; + + const isDarkMode = useIsDarkMode(); + + const text = useMemo( + () => `${dayjs(data.timestamp / 1e6).format()} | ${data.body}`, + [data.timestamp, data.body], + ); + + const ellipsis = useMemo(() => ({ rows: linesPerRow }), [linesPerRow]); + + const handleClickExpand = useCallback(() => { + onClickExpand(data); + }, [onClickExpand, data]); + + return ( + + + + + + {text} + + + ); +} + +export default RawLogView; diff --git a/frontend/src/components/Logs/RawLogView/styles.ts b/frontend/src/components/Logs/RawLogView/styles.ts new file mode 100644 index 0000000000..19163adc07 --- /dev/null +++ b/frontend/src/components/Logs/RawLogView/styles.ts @@ -0,0 +1,24 @@ +import { blue } from '@ant-design/colors'; +import { Col, Row } from 'antd'; +import styled from 'styled-components'; + +export const RawLogViewContainer = styled(Row)<{ $isDarkMode: boolean }>` + width: 100%; + font-weight: 700; + font-size: 0.625rem; + line-height: 1.25rem; + + transition: background-color 0.2s ease-in; + + &:hover { + background-color: ${({ $isDarkMode }): string => + $isDarkMode ? 'rgba(255,255,255,0.1)' : 'rgba(0, 0, 0, 0.1)'}; + } +`; + +export const ExpandIconWrapper = styled(Col)` + color: ${blue[6]}; + padding: 0.25rem 0.375rem; + cursor: pointer; + font-size: 12px; +`; diff --git a/frontend/src/components/Logs/TableView/config.ts b/frontend/src/components/Logs/TableView/config.ts new file mode 100644 index 0000000000..f68dcbfebf --- /dev/null +++ b/frontend/src/components/Logs/TableView/config.ts @@ -0,0 +1,12 @@ +import { TableProps } from 'antd'; + +export const defaultCellStyle: React.CSSProperties = { + paddingTop: 4, + paddingBottom: 6, + paddingRight: 8, + paddingLeft: 8, +}; + +export const tableScroll: TableProps>['scroll'] = { + x: true, +}; diff --git a/frontend/src/components/Logs/TableView/index.tsx b/frontend/src/components/Logs/TableView/index.tsx new file mode 100644 index 0000000000..3cb6d9a041 --- /dev/null +++ b/frontend/src/components/Logs/TableView/index.tsx @@ -0,0 +1,106 @@ +import { ExpandAltOutlined } from '@ant-design/icons'; +import { Table, Typography } from 'antd'; +import { ColumnsType, ColumnType } from 'antd/es/table'; +import dayjs from 'dayjs'; +// utils +import { FlatLogData } from 'lib/logs/flatLogData'; +import React, { useMemo } from 'react'; +import { IField } from 'types/api/logs/fields'; +// interfaces +import { ILog } from 'types/api/logs/log'; + +// styles +import { ExpandIconWrapper } from '../RawLogView/styles'; +// config +import { defaultCellStyle, tableScroll } from './config'; + +type ColumnTypeRender = ReturnType< + NonNullable['render']> +>; + +type LogsTableViewProps = { + logs: ILog[]; + fields: IField[]; + linesPerRow: number; + onClickExpand: (log: ILog) => void; +}; + +function LogsTableView(props: LogsTableViewProps): JSX.Element { + const { logs, fields, linesPerRow, onClickExpand } = props; + + const flattenLogData = useMemo(() => logs.map((log) => FlatLogData(log)), [ + logs, + ]); + + const columns: ColumnsType> = useMemo(() => { + const fieldColumns: ColumnsType> = fields.map( + ({ name }) => ({ + title: name, + dataIndex: name, + key: name, + render: (field): ColumnTypeRender> => ({ + props: { + style: defaultCellStyle, + }, + children: ( + + {field} + + ), + }), + }), + ); + + return [ + { + title: '', + dataIndex: 'id', + key: 'expand', + // https://github.com/ant-design/ant-design/discussions/36886 + render: (_, item): ColumnTypeRender> => ({ + props: { + style: defaultCellStyle, + }, + children: ( + { + onClickExpand((item as unknown) as ILog); + }} + > + + + ), + }), + }, + { + title: 'Timestamp', + dataIndex: 'timestamp', + key: 'timestamp', + // https://github.com/ant-design/ant-design/discussions/36886 + render: (field): ColumnTypeRender> => { + const date = dayjs(field / 1e6).format(); + return { + props: { + style: defaultCellStyle, + }, + children: {date}, + }; + }, + }, + ...fieldColumns, + ]; + }, [fields, linesPerRow, onClickExpand]); + + return ( + + ); +} + +export default LogsTableView; diff --git a/frontend/src/components/ResizeTable/ResizableHeader.tsx b/frontend/src/components/ResizeTable/ResizableHeader.tsx index 48f904b05a..b3119c0c1a 100644 --- a/frontend/src/components/ResizeTable/ResizableHeader.tsx +++ b/frontend/src/components/ResizeTable/ResizableHeader.tsx @@ -17,13 +17,6 @@ function ResizableHeader(props: ResizableHeaderProps): JSX.Element { [], ); - const draggableOpts = useMemo( - () => ({ - enableUserSelectHack, - }), - [], - ); - if (!width) { // eslint-disable-next-line react/jsx-props-no-spreading return - + ); } diff --git a/frontend/src/container/LogsFilters/styles.ts b/frontend/src/container/LogsFilters/styles.ts index 38aca7fe1e..f6e9e69bff 100644 --- a/frontend/src/container/LogsFilters/styles.ts +++ b/frontend/src/container/LogsFilters/styles.ts @@ -1,13 +1,7 @@ import { blue, grey } from '@ant-design/colors'; -import { Col, Typography } from 'antd'; +import { Typography } from 'antd'; import styled from 'styled-components'; -export const Container = styled(Col)` - padding-top: 0.3rem; - min-width: 15.625rem; - max-width: 21.875rem; -`; - export const CategoryContainer = styled.div` margin: 1rem 0; padding-left: 0.2rem; diff --git a/frontend/src/container/LogsSearchFilter/index.tsx b/frontend/src/container/LogsSearchFilter/index.tsx index 098d6c60ed..b124ab6c1a 100644 --- a/frontend/src/container/LogsSearchFilter/index.tsx +++ b/frontend/src/container/LogsSearchFilter/index.tsx @@ -48,6 +48,7 @@ function SearchFilter({ AppState, ILogsReducer >((state) => state.logs); + const globalTime = useSelector( (state) => state.globalTime, ); diff --git a/frontend/src/container/LogsTable/index.tsx b/frontend/src/container/LogsTable/index.tsx index c1f6f7c690..2b0013035d 100644 --- a/frontend/src/container/LogsTable/index.tsx +++ b/frontend/src/container/LogsTable/index.tsx @@ -1,19 +1,53 @@ -import { Typography } from 'antd'; -import LogItem from 'components/Logs/LogItem'; +import { Card, Typography } from 'antd'; +// components +import ListLogView from 'components/Logs/ListLogView'; +import RawLogView from 'components/Logs/RawLogView'; +import LogsTableView from 'components/Logs/TableView'; import Spinner from 'components/Spinner'; +import { contentStyle } from 'container/Trace/Search/config'; +import useFontFaceObserver from 'hooks/useFontObserver'; import React, { memo, useCallback, useMemo } from 'react'; import { useSelector } from 'react-redux'; import { Virtuoso } from 'react-virtuoso'; +// interfaces import { AppState } from 'store/reducers'; +import { ILog } from 'types/api/logs/log'; import { ILogsReducer } from 'types/reducer/logs'; +// styles import { Container, Heading } from './styles'; -function LogsTable(): JSX.Element { - const { logs, isLoading, liveTail } = useSelector( - (state) => state.logs, +export type LogViewMode = 'raw' | 'table' | 'list'; + +type LogsTableProps = { + viewMode: LogViewMode; + linesPerRow: number; + onClickExpand: (logData: ILog) => void; +}; + +function LogsTable(props: LogsTableProps): JSX.Element { + const { viewMode, onClickExpand, linesPerRow } = props; + + useFontFaceObserver( + [ + { + family: 'Fira Code', + weight: '300', + }, + ], + viewMode === 'raw', + { + timeout: 5000, + }, ); + const { + logs, + fields: { selected }, + isLoading, + liveTail, + } = useSelector((state) => state.logs); + const isLiveTail = useMemo(() => logs.length === 0 && liveTail === 'PLAYING', [ logs?.length, liveTail, @@ -27,29 +61,63 @@ function LogsTable(): JSX.Element { const getItemContent = useCallback( (index: number): JSX.Element => { const log = logs[index]; - return ; + + if (viewMode === 'raw') { + return ( + + ); + } + + return ; }, - [logs], + [logs, linesPerRow, viewMode, onClickExpand], ); + const renderContent = useMemo(() => { + if (viewMode === 'table') { + return ( + + ); + } + + return ( + + + + ); + }, [getItemContent, linesPerRow, logs, onClickExpand, selected, viewMode]); + if (isLoading) { return ; } return ( - - - Event - + + {viewMode !== 'table' && ( + + Event + + )} + {isLiveTail && Getting live logs...} - {isNoLogs && No log lines found} + {isNoLogs && No logs lines found} - + {renderContent} ); } diff --git a/frontend/src/container/LogsTable/styles.ts b/frontend/src/container/LogsTable/styles.ts index cf42d3df9b..a7c843fa51 100644 --- a/frontend/src/container/LogsTable/styles.ts +++ b/frontend/src/container/LogsTable/styles.ts @@ -1,14 +1,16 @@ -import { Card, Col } from 'antd'; +import { Card } from 'antd'; import styled from 'styled-components'; -export const Container = styled(Col)` +export const Container = styled.div` overflow-x: hidden; width: 100%; margin-bottom: 1rem; + margin-top: 0.5rem; `; export const Heading = styled(Card)` margin-bottom: 0.1rem; + height: 32px; .ant-card-body { padding: 0.3rem 0.5rem; } diff --git a/frontend/src/hooks/useFontObserver.tsx b/frontend/src/hooks/useFontObserver.tsx new file mode 100644 index 0000000000..7292a8ee28 --- /dev/null +++ b/frontend/src/hooks/useFontObserver.tsx @@ -0,0 +1,69 @@ +import FontFaceObserver from 'fontfaceobserver'; +import { useEffect, useState } from 'react'; + +export interface FontFace { + family: string; + weight?: + | `light` + | `normal` + | `bold` + | `bolder` + | `100` + | `200` + | `300` + | `400` + | `500` + | `600` + | `700` + | `800` + | `900`; + style?: `normal` | `italic` | `oblique`; + stretch?: + | `normal` + | `ultra-condensed` + | `extra-condensed` + | `condensed` + | `semi-condensed` + | `semi-expanded` + | `expanded` + | `extra-expanded` + | `ultra-expanded`; +} + +export interface Options { + testString?: string; + timeout?: number; +} + +export interface Config { + showErrors: boolean; +} + +function useFontFaceObserver( + fontFaces: FontFace[] = [], + isEnabled = true, + { testString, timeout }: Options = {}, + { showErrors }: Config = { showErrors: false }, +): boolean { + const [isResolved, setIsResolved] = useState(false); + const fontFacesString = JSON.stringify(fontFaces); + + useEffect(() => { + if (isEnabled) { + const promises = JSON.parse(fontFacesString).map( + ({ family, weight, style, stretch }: FontFace) => + new FontFaceObserver(family, { + weight, + style, + stretch, + }).load(testString, timeout), + ); + + Promise.all(promises).then(() => setIsResolved(true)); + } + }, [fontFacesString, testString, timeout, showErrors, isEnabled]); + + return isResolved; +} + +export default useFontFaceObserver; diff --git a/frontend/src/index.html.ejs b/frontend/src/index.html.ejs index 60deae1901..6d7c037e1f 100644 --- a/frontend/src/index.html.ejs +++ b/frontend/src/index.html.ejs @@ -50,8 +50,14 @@ + + + - +
diff --git a/frontend/src/pages/Logs/PopoverContent.tsx b/frontend/src/pages/Logs/PopoverContent.tsx new file mode 100644 index 0000000000..8dcf819d3a --- /dev/null +++ b/frontend/src/pages/Logs/PopoverContent.tsx @@ -0,0 +1,28 @@ +import { InputNumber, Row, Space, Typography } from 'antd'; +import React from 'react'; + +interface PopoverContentProps { + linesPerRow: number; + handleLinesPerRowChange: (l: unknown) => void; +} + +function PopoverContent({ + linesPerRow, + handleLinesPerRowChange, +}: PopoverContentProps): JSX.Element { + return ( + + + Max lines per Row + + + + ); +} + +export default PopoverContent; diff --git a/frontend/src/pages/Logs/config.ts b/frontend/src/pages/Logs/config.ts new file mode 100644 index 0000000000..dca2a3b937 --- /dev/null +++ b/frontend/src/pages/Logs/config.ts @@ -0,0 +1,25 @@ +import { ViewModeOption } from './types'; + +export const viewModeOptionList: ViewModeOption[] = [ + { + key: 'raw', + label: 'Raw', + value: 'raw', + }, + { + key: 'table', + label: 'Table', + value: 'table', + }, + { + key: 'list', + label: 'List', + value: 'list', + }, +]; + +export const logsOptions = ['raw', 'table']; + +export const defaultSelectStyle: React.CSSProperties = { + minWidth: '6rem', +}; diff --git a/frontend/src/pages/Logs/hooks.ts b/frontend/src/pages/Logs/hooks.ts new file mode 100644 index 0000000000..7093147ce3 --- /dev/null +++ b/frontend/src/pages/Logs/hooks.ts @@ -0,0 +1,78 @@ +// utils +import get from 'api/browser/localstorage/get'; +import { LOCALSTORAGE } from 'constants/localStorage'; +// interfaces +import { LogViewMode } from 'container/LogsTable'; +import { useCallback, useLayoutEffect, useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { setLinesPerRow } from 'store/actions/logs/setLInesPerRow'; +// actions +import { setViewMode } from 'store/actions/logs/setViewMode'; +import { AppState } from 'store/reducers'; + +import { viewModeOptionList } from './config'; +import { SelectedLogViewData } from './types'; +import { isLogViewMode } from './utils'; + +export const useSelectedLogView = (): SelectedLogViewData => { + const dispatch = useDispatch(); + const viewMode = useSelector( + (state) => state.logs.viewMode, + ); + const linesPerRow = useSelector( + (state) => state.logs.linesPerRow, + ); + + const viewModeOption = useMemo( + () => + viewModeOptionList.find( + (viewModeOption) => viewModeOption.value === viewMode, + ) ?? viewModeOptionList[0], + [viewMode], + ); + + const handleViewModeChange = useCallback( + (selectedViewMode: LogViewMode) => { + dispatch(setViewMode(selectedViewMode)); + }, + [dispatch], + ); + + const handleViewModeOptionChange = useCallback( + ({ key }: { key: string }) => { + if (isLogViewMode(key)) handleViewModeChange(key); + }, + [handleViewModeChange], + ); + + const handleLinesPerRowChange = useCallback( + (selectedLinesPerRow: unknown) => { + if (typeof selectedLinesPerRow === 'number') { + dispatch(setLinesPerRow(selectedLinesPerRow)); + } + }, + [dispatch], + ); + + useLayoutEffect(() => { + const storedViewMode = get(LOCALSTORAGE.LOGS_VIEW_MODE); + if (storedViewMode) { + handleViewModeChange(storedViewMode as LogViewMode); + } + + const storedLinesPerRow = get(LOCALSTORAGE.LOGS_LINES_PER_ROW); + if (storedLinesPerRow) { + handleLinesPerRowChange(+storedLinesPerRow); + } + }, [handleViewModeChange, handleLinesPerRowChange]); + + return { + viewModeOptionList, + viewModeOption, + viewMode, + handleViewModeChange, + handleViewModeOptionChange, + linesPerRow, + handleLinesPerRowChange, + }; +}; diff --git a/frontend/src/pages/Logs/index.tsx b/frontend/src/pages/Logs/index.tsx index 15cc757ab3..d92c45708e 100644 --- a/frontend/src/pages/Logs/index.tsx +++ b/frontend/src/pages/Logs/index.tsx @@ -1,4 +1,4 @@ -import { Divider, Row } from 'antd'; +import { Button, Col, Divider, Popover, Row, Select, Space } from 'antd'; import LogControls from 'container/LogControls'; import LogDetailedView from 'container/LogDetailedView'; import LogLiveTail from 'container/LogLiveTail'; @@ -6,11 +6,67 @@ import LogsAggregate from 'container/LogsAggregate'; import LogsFilters from 'container/LogsFilters'; import LogsSearchFilter from 'container/LogsSearchFilter'; import LogsTable from 'container/LogsTable'; -import React from 'react'; +import React, { useCallback, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; +import { Dispatch } from 'redux'; +import AppActions from 'types/actions'; +import { SET_DETAILED_LOG_DATA } from 'types/actions/logs'; +import { ILog } from 'types/api/logs/log'; +import { defaultSelectStyle, logsOptions } from './config'; +import { useSelectedLogView } from './hooks'; +import PopoverContent from './PopoverContent'; import SpaceContainer from './styles'; function Logs(): JSX.Element { + const dispatch = useDispatch>(); + + const showExpandedLog = useCallback( + (logData: ILog) => { + dispatch({ + type: SET_DETAILED_LOG_DATA, + payload: logData, + }); + }, + [dispatch], + ); + + const { + viewModeOptionList, + viewModeOption, + viewMode, + handleViewModeOptionChange, + linesPerRow, + handleLinesPerRowChange, + } = useSelectedLogView(); + + const renderPopoverContent = useCallback( + () => ( + + ), + [linesPerRow, handleLinesPerRowChange], + ); + + const isFormatButtonVisible = useMemo(() => logsOptions.includes(viewMode), [ + viewMode, + ]); + + const selectedViewModeOption = useMemo(() => viewModeOption.value.toString(), [ + viewModeOption.value, + ]); + + const onChangeVeiwMode = useCallback( + (key: string) => { + handleViewModeOptionChange({ + key, + }); + }, + [handleViewModeOptionChange], + ); + return ( <> - - + - - +
+ + + + + + {isFormatButtonVisible && ( + + + + )} + + + + + + + + + + + ); diff --git a/frontend/src/pages/Logs/types.ts b/frontend/src/pages/Logs/types.ts new file mode 100644 index 0000000000..c4bbfc6042 --- /dev/null +++ b/frontend/src/pages/Logs/types.ts @@ -0,0 +1,17 @@ +import { ItemType } from 'antd/es/menu/hooks/useItems'; +import { LogViewMode } from 'container/LogsTable'; + +export type ViewModeOption = ItemType & { + label: string; + value: LogViewMode; +}; + +export type SelectedLogViewData = { + viewModeOptionList: ViewModeOption[]; + viewModeOption: ViewModeOption; + viewMode: LogViewMode; + handleViewModeChange: (s: LogViewMode) => void; + handleViewModeOptionChange: ({ key }: { key: string }) => void; + linesPerRow: number; + handleLinesPerRowChange: (l: unknown) => void; +}; diff --git a/frontend/src/pages/Logs/utils.ts b/frontend/src/pages/Logs/utils.ts new file mode 100644 index 0000000000..2d8f317a9a --- /dev/null +++ b/frontend/src/pages/Logs/utils.ts @@ -0,0 +1,7 @@ +import { LogViewMode } from 'container/LogsTable'; + +import { viewModeOptionList } from './config'; + +export const isLogViewMode = (value: unknown): value is LogViewMode => + typeof value === 'string' && + viewModeOptionList.some((option) => option.key === value); diff --git a/frontend/src/store/actions/logs/setLInesPerRow.ts b/frontend/src/store/actions/logs/setLInesPerRow.ts new file mode 100644 index 0000000000..3931053574 --- /dev/null +++ b/frontend/src/store/actions/logs/setLInesPerRow.ts @@ -0,0 +1,14 @@ +import set from 'api/browser/localstorage/set'; +import { LOCALSTORAGE } from 'constants/localStorage'; +import { SET_LINES_PER_ROW } from 'types/actions/logs'; + +type ActionSetLinesPerRow = { type: typeof SET_LINES_PER_ROW; payload: number }; + +export function setLinesPerRow(lines: number): ActionSetLinesPerRow { + set(LOCALSTORAGE.LOGS_LINES_PER_ROW, lines.toString()); + + return { + type: SET_LINES_PER_ROW, + payload: lines, + }; +} diff --git a/frontend/src/store/actions/logs/setViewMode.ts b/frontend/src/store/actions/logs/setViewMode.ts new file mode 100644 index 0000000000..4f5bb3fb1d --- /dev/null +++ b/frontend/src/store/actions/logs/setViewMode.ts @@ -0,0 +1,15 @@ +import set from 'api/browser/localstorage/set'; +import { LOCALSTORAGE } from 'constants/localStorage'; +import { LogViewMode } from 'container/LogsTable'; +import { SET_VIEW_MODE } from 'types/actions/logs'; + +type ActionSetViewMode = { type: typeof SET_VIEW_MODE; payload: LogViewMode }; + +export function setViewMode(viewMode: LogViewMode): ActionSetViewMode { + set(LOCALSTORAGE.LOGS_VIEW_MODE, viewMode); + + return { + type: SET_VIEW_MODE, + payload: viewMode, + }; +} diff --git a/frontend/src/store/reducers/logs.ts b/frontend/src/store/reducers/logs.ts index e920d68a6e..06c6b41b70 100644 --- a/frontend/src/store/reducers/logs.ts +++ b/frontend/src/store/reducers/logs.ts @@ -10,6 +10,7 @@ import { RESET_ID_START_AND_END, SET_DETAILED_LOG_DATA, SET_FIELDS, + SET_LINES_PER_ROW, SET_LIVE_TAIL_START_TIME, SET_LOADING, SET_LOADING_AGGREGATE, @@ -18,6 +19,7 @@ import { SET_LOGS_AGGREGATE_SERIES, SET_SEARCH_QUERY_PARSED_PAYLOAD, SET_SEARCH_QUERY_STRING, + SET_VIEW_MODE, STOP_LIVE_TAIL, TOGGLE_LIVE_TAIL, UPDATE_INTERESTING_FIELDS, @@ -36,6 +38,8 @@ const initialState: ILogsReducer = { }, logs: [], logLinesPerPage: 25, + linesPerRow: 2, + viewMode: 'raw', idEnd: '', idStart: '', isLoading: false, @@ -205,6 +209,20 @@ export const LogsReducer = ( }; } + case SET_LINES_PER_ROW: { + return { + ...state, + linesPerRow: action.payload, + }; + } + + case SET_VIEW_MODE: { + return { + ...state, + viewMode: action.payload, + }; + } + case UPDATE_INTERESTING_FIELDS: { return { ...state, diff --git a/frontend/src/types/actions/logs.ts b/frontend/src/types/actions/logs.ts index b680b70233..bd400f9204 100644 --- a/frontend/src/types/actions/logs.ts +++ b/frontend/src/types/actions/logs.ts @@ -1,3 +1,4 @@ +import { LogViewMode } from 'container/LogsTable'; import { ILogQLParsedQueryItem } from 'lib/logql/types'; import { IField, IFieldMoveToSelected, IFields } from 'types/api/logs/fields'; import { TLogsLiveTailState } from 'types/api/logs/liveTail'; @@ -28,6 +29,8 @@ export const PUSH_LIVE_TAIL_EVENT = 'LOGS_PUSH_LIVE_TAIL_EVENT'; export const STOP_LIVE_TAIL = 'LOGS_STOP_LIVE_TAIL'; export const FLUSH_LOGS = 'LOGS_FLUSH_LOGS'; export const SET_LIVE_TAIL_START_TIME = 'LOGS_SET_LIVE_TAIL_START_TIME'; +export const SET_LINES_PER_ROW = 'SET_LINES_PER_ROW'; +export const SET_VIEW_MODE = 'SET_VIEW_MODE'; export const UPDATE_SELECTED_FIELDS = 'LOGS_UPDATE_SELECTED_FIELDS'; export const UPDATE_INTERESTING_FIELDS = 'LOGS_UPDATE_INTERESTING_FIELDS'; @@ -118,6 +121,15 @@ export interface SetLiveTailStartTime { payload: number; } +export interface SetLinesPerRow { + type: typeof SET_LINES_PER_ROW; + payload: number; +} + +export interface SetViewMode { + type: typeof SET_VIEW_MODE; + payload: LogViewMode; +} type IFieldType = 'interesting' | 'selected'; export interface UpdateSelectedInterestFields { @@ -149,4 +161,6 @@ export type LogsActions = | StopLiveTail | FlushLogs | SetLiveTailStartTime + | SetLinesPerRow + | SetViewMode | UpdateSelectedInterestFields; diff --git a/frontend/src/types/reducer/logs.ts b/frontend/src/types/reducer/logs.ts index 43b3c6f7fa..762afc199c 100644 --- a/frontend/src/types/reducer/logs.ts +++ b/frontend/src/types/reducer/logs.ts @@ -1,3 +1,4 @@ +import { LogViewMode } from 'container/LogsTable'; import { ILogQLParsedQueryItem } from 'lib/logql/types'; import { IFields } from 'types/api/logs/fields'; import { TLogsLiveTailState } from 'types/api/logs/liveTail'; @@ -12,6 +13,8 @@ export interface ILogsReducer { }; logs: ILog[]; logLinesPerPage: number; + linesPerRow: number; + viewMode: LogViewMode; idEnd: string; idStart: string; isLoading: boolean;
; @@ -35,7 +28,7 @@ function ResizableHeader(props: ResizableHeaderProps): JSX.Element { height={0} handle={handle} onResize={onResize} - draggableOpts={draggableOpts} + draggableOpts={enableUserSelectHack} > {/* eslint-disable-next-line react/jsx-props-no-spreading */} diff --git a/frontend/src/constants/localStorage.ts b/frontend/src/constants/localStorage.ts index ab8b01a388..d2988c9be5 100644 --- a/frontend/src/constants/localStorage.ts +++ b/frontend/src/constants/localStorage.ts @@ -4,4 +4,6 @@ export enum LOCALSTORAGE { AUTH_TOKEN = 'AUTH_TOKEN', REFRESH_AUTH_TOKEN = 'REFRESH_AUTH_TOKEN', THEME = 'THEME', + LOGS_VIEW_MODE = 'LOGS_VIEW_MODE', + LOGS_LINES_PER_ROW = 'LOGS_LINES_PER_ROW', } diff --git a/frontend/src/container/LogControls/index.tsx b/frontend/src/container/LogControls/index.tsx index db427624c3..41bfeafb53 100644 --- a/frontend/src/container/LogControls/index.tsx +++ b/frontend/src/container/LogControls/index.tsx @@ -6,9 +6,12 @@ import { import { Button, Divider, Select } from 'antd'; import { getGlobalTime } from 'container/LogsSearchFilter/utils'; import { getMinMax } from 'container/TopNav/AutoRefresh/config'; +import { defaultSelectStyle } from 'pages/Logs/config'; import React, { memo, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { Dispatch } from 'redux'; import { AppState } from 'store/reducers'; +import AppActions from 'types/actions'; import { GET_NEXT_LOG_LINES, GET_PREVIOUS_LOG_LINES, @@ -21,8 +24,6 @@ import { ILogsReducer } from 'types/reducer/logs'; import { ITEMS_PER_PAGE_OPTIONS } from './config'; import { Container } from './styles'; -const { Option } = Select; - function LogControls(): JSX.Element | null { const { logLinesPerPage, @@ -34,13 +35,14 @@ function LogControls(): JSX.Element | null { const globalTime = useSelector( (state) => state.globalTime, ); - const dispatch = useDispatch(); + + const dispatch = useDispatch>(); const handleLogLinesPerPageChange = (e: number): void => { dispatch({ type: SET_LOG_LINES_PER_PAGE, payload: { - logLinesPerPage: e, + logsLinesPerPage: e, }, }); }; @@ -52,13 +54,17 @@ function LogControls(): JSX.Element | null { globalTime.maxTime, ); - dispatch({ - type: RESET_ID_START_AND_END, - payload: getGlobalTime(globalTime.selectedTime, { - maxTime, - minTime, - }), + const updatedGlobalTime = getGlobalTime(globalTime.selectedTime, { + maxTime, + minTime, }); + + if (updatedGlobalTime) { + dispatch({ + type: RESET_ID_START_AND_END, + payload: updatedGlobalTime, + }); + } }; const handleNavigatePrevious = (): void => { @@ -117,12 +123,16 @@ function LogControls(): JSX.Element | null { Next diff --git a/frontend/src/container/LogsFilters/index.tsx b/frontend/src/container/LogsFilters/index.tsx index bede522e61..d5a673395a 100644 --- a/frontend/src/container/LogsFilters/index.tsx +++ b/frontend/src/container/LogsFilters/index.tsx @@ -1,5 +1,5 @@ import { CloseOutlined, PlusCircleFilled } from '@ant-design/icons'; -import { Input } from 'antd'; +import { Col, Input } from 'antd'; import CategoryHeading from 'components/Logs/CategoryHeading'; import { fieldSearchFilter } from 'lib/logs/fieldSearch'; import React, { useCallback, useState } from 'react'; @@ -9,7 +9,7 @@ import { ILogsReducer } from 'types/reducer/logs'; import { ICON_STYLE } from './config'; import FieldItem from './FieldItem'; -import { CategoryContainer, Container, FieldContainer } from './styles'; +import { CategoryContainer, FieldContainer } from './styles'; import { IHandleInterestProps, IHandleRemoveInterestProps } from './types'; import { onHandleAddInterest, onHandleRemoveInterest } from './utils'; @@ -58,7 +58,7 @@ function LogsFilters(): JSX.Element { ); return ( - +