diff --git a/frontend/src/constants/localStorage.ts b/frontend/src/constants/localStorage.ts index 0ba6cac302..85f46ab892 100644 --- a/frontend/src/constants/localStorage.ts +++ b/frontend/src/constants/localStorage.ts @@ -17,4 +17,5 @@ export enum LOCALSTORAGE { IS_IDENTIFIED_USER = 'IS_IDENTIFIED_USER', DASHBOARD_VARIABLES = 'DASHBOARD_VARIABLES', SHOW_EXPLORER_TOOLBAR = 'SHOW_EXPLORER_TOOLBAR', + PINNED_ATTRIBUTES = 'PINNED_ATTRIBUTES', } diff --git a/frontend/src/container/LogDetailedView/Overview.styles.scss b/frontend/src/container/LogDetailedView/Overview.styles.scss index bc63db442c..8e1726a0a9 100644 --- a/frontend/src/container/LogDetailedView/Overview.styles.scss +++ b/frontend/src/container/LogDetailedView/Overview.styles.scss @@ -53,6 +53,19 @@ background: rgba(171, 189, 255, 0.04); padding: 8px; + + .ant-collapse-extra { + display: flex; + align-items: center; + + .action-btn { + display: flex; + + .ant-btn { + background: rgba(113, 144, 249, 0.08); + } + } + } } .ant-collapse-content { diff --git a/frontend/src/container/LogDetailedView/TableView.styles.scss b/frontend/src/container/LogDetailedView/TableView.styles.scss index 89101bf7b2..d9cbdcabbb 100644 --- a/frontend/src/container/LogDetailedView/TableView.styles.scss +++ b/frontend/src/container/LogDetailedView/TableView.styles.scss @@ -5,12 +5,13 @@ .ant-table-row:hover { .ant-table-cell { .value-field { - display: flex; - justify-content: space-between; - align-items: center; .action-btn { display: flex; - gap: 4px; + position: absolute; + top: 50%; + right: 16px; + transform: translateY(-50%); + gap: 8px; } } } @@ -28,6 +29,30 @@ } } + .attribute-pin { + cursor: pointer; + + padding: 0; + vertical-align: middle; + text-align: center; + + .log-attribute-pin { + padding: 8px; + + display: flex; + justify-content: center; + align-items: center; + + .pin-attribute-icon { + border: none; + + &.pinned svg { + fill: var(--bg-robin-500); + } + } + } + } + .value-field-container { background: rgba(22, 25, 34, 0.4); @@ -70,6 +95,10 @@ .value-field-container { background: var(--bg-vanilla-300); + &.attribute-pin { + background: var(--bg-vanilla-100); + } + .action-btn { .filter-btn { background: var(--bg-vanilla-300); diff --git a/frontend/src/container/LogDetailedView/TableView.tsx b/frontend/src/container/LogDetailedView/TableView.tsx index 593519404d..a69fba6441 100644 --- a/frontend/src/container/LogDetailedView/TableView.tsx +++ b/frontend/src/container/LogDetailedView/TableView.tsx @@ -1,22 +1,29 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import './TableView.styles.scss'; import { LinkOutlined } from '@ant-design/icons'; import { Color } from '@signozhq/design-tokens'; import { Button, Space, Spin, Tooltip, Tree, Typography } from 'antd'; import { ColumnsType } from 'antd/es/table'; +import getLocalStorageApi from 'api/browser/localstorage/get'; +import setLocalStorageApi from 'api/browser/localstorage/set'; +import cx from 'classnames'; import AddToQueryHOC, { AddToQueryHOCProps, } from 'components/Logs/AddToQueryHOC'; import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC'; import { ResizeTable } from 'components/ResizeTable'; +import { LOCALSTORAGE } from 'constants/localStorage'; import { OPERATORS } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; +import { useIsDarkMode } from 'hooks/useDarkMode'; import history from 'lib/history'; import { fieldSearchFilter } from 'lib/logs/fieldSearch'; import { removeJSONStringifyQuotes } from 'lib/removeJSONStringifyQuotes'; import { isEmpty } from 'lodash-es'; -import { ArrowDownToDot, ArrowUpFromDot } from 'lucide-react'; -import { useMemo, useState } from 'react'; +import { ArrowDownToDot, ArrowUpFromDot, Pin } from 'lucide-react'; +import { useEffect, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import { generatePath } from 'react-router-dom'; import { Dispatch } from 'redux'; @@ -57,6 +64,28 @@ function TableView({ const dispatch = useDispatch>(); const [isfilterInLoading, setIsFilterInLoading] = useState(false); const [isfilterOutLoading, setIsFilterOutLoading] = useState(false); + const isDarkMode = useIsDarkMode(); + + const [pinnedAttributes, setPinnedAttributes] = useState< + Record + >({}); + + useEffect(() => { + const pinnedAttributesFromLocalStorage = getLocalStorageApi( + LOCALSTORAGE.PINNED_ATTRIBUTES, + ); + + if (pinnedAttributesFromLocalStorage) { + try { + const parsedPinnedAttributes = JSON.parse(pinnedAttributesFromLocalStorage); + setPinnedAttributes(parsedPinnedAttributes); + } catch (e) { + console.error('Error parsing pinned attributes from local storgage'); + } + } else { + setPinnedAttributes({}); + } + }, []); const flattenLogData: Record | null = useMemo( () => (logData ? flattenObject(logData) : null), @@ -74,6 +103,19 @@ function TableView({ } }; + const togglePinAttribute = (record: DataType): void => { + if (record) { + const newPinnedAttributes = { ...pinnedAttributes }; + newPinnedAttributes[record.key] = !newPinnedAttributes[record.key]; + setPinnedAttributes(newPinnedAttributes); + + setLocalStorageApi( + LOCALSTORAGE.PINNED_ATTRIBUTES, + JSON.stringify(newPinnedAttributes), + ); + } + }; + const onClickHandler = ( operator: string, fieldKey: string, @@ -138,6 +180,37 @@ function TableView({ } const columns: ColumnsType = [ + { + title: '', + dataIndex: 'pin', + key: 'pin', + width: 5, + align: 'left', + className: 'attribute-pin value-field-container', + render: (fieldData: Record, record): JSX.Element => { + let pinColor = isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_500; + + if (pinnedAttributes[record?.key]) { + pinColor = Color.BG_ROBIN_500; + } + + return ( +
+
{ + togglePinAttribute(record); + }} + > + +
+
+ ); + }, + }, { title: 'Field', dataIndex: 'field', @@ -264,12 +337,34 @@ function TableView({ }, }, ]; + function sortPinnedAttributes( + data: Record[], + sortingObj: Record, + ): Record[] { + const sortingKeys = Object.keys(sortingObj); + return data.sort((a, b) => { + const aKey = a.key; + const bKey = b.key; + const aSortIndex = sortingKeys.indexOf(aKey); + const bSortIndex = sortingKeys.indexOf(bKey); + + if (sortingObj[aKey] && !sortingObj[bKey]) { + return -1; + } + if (!sortingObj[aKey] && sortingObj[bKey]) { + return 1; + } + return aSortIndex - bSortIndex; + }); + } + + const sortedAttributes = sortPinnedAttributes(dataSource, pinnedAttributes); return (