mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-16 05:36:03 +08:00
feat: add support to pin attributes in logs details view (#4692)
* feat: add support to pin attributes in logs details view * feat: add safety checks * feat: update styles * feat: update styles * feat: move json parsing in try catch block
This commit is contained in:
parent
6014bb76b6
commit
9f30bba9a8
@ -17,4 +17,5 @@ export enum LOCALSTORAGE {
|
|||||||
IS_IDENTIFIED_USER = 'IS_IDENTIFIED_USER',
|
IS_IDENTIFIED_USER = 'IS_IDENTIFIED_USER',
|
||||||
DASHBOARD_VARIABLES = 'DASHBOARD_VARIABLES',
|
DASHBOARD_VARIABLES = 'DASHBOARD_VARIABLES',
|
||||||
SHOW_EXPLORER_TOOLBAR = 'SHOW_EXPLORER_TOOLBAR',
|
SHOW_EXPLORER_TOOLBAR = 'SHOW_EXPLORER_TOOLBAR',
|
||||||
|
PINNED_ATTRIBUTES = 'PINNED_ATTRIBUTES',
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,19 @@
|
|||||||
background: rgba(171, 189, 255, 0.04);
|
background: rgba(171, 189, 255, 0.04);
|
||||||
|
|
||||||
padding: 8px;
|
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 {
|
.ant-collapse-content {
|
||||||
|
@ -5,12 +5,13 @@
|
|||||||
.ant-table-row:hover {
|
.ant-table-row:hover {
|
||||||
.ant-table-cell {
|
.ant-table-cell {
|
||||||
.value-field {
|
.value-field {
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
display: flex;
|
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 {
|
.value-field-container {
|
||||||
background: rgba(22, 25, 34, 0.4);
|
background: rgba(22, 25, 34, 0.4);
|
||||||
|
|
||||||
@ -70,6 +95,10 @@
|
|||||||
.value-field-container {
|
.value-field-container {
|
||||||
background: var(--bg-vanilla-300);
|
background: var(--bg-vanilla-300);
|
||||||
|
|
||||||
|
&.attribute-pin {
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
.filter-btn {
|
.filter-btn {
|
||||||
background: var(--bg-vanilla-300);
|
background: var(--bg-vanilla-300);
|
||||||
|
@ -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 './TableView.styles.scss';
|
||||||
|
|
||||||
import { LinkOutlined } from '@ant-design/icons';
|
import { LinkOutlined } from '@ant-design/icons';
|
||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Button, Space, Spin, Tooltip, Tree, Typography } from 'antd';
|
import { Button, Space, Spin, Tooltip, Tree, Typography } from 'antd';
|
||||||
import { ColumnsType } from 'antd/es/table';
|
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, {
|
import AddToQueryHOC, {
|
||||||
AddToQueryHOCProps,
|
AddToQueryHOCProps,
|
||||||
} from 'components/Logs/AddToQueryHOC';
|
} from 'components/Logs/AddToQueryHOC';
|
||||||
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
|
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
import { OPERATORS } from 'constants/queryBuilder';
|
import { OPERATORS } from 'constants/queryBuilder';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
||||||
import { removeJSONStringifyQuotes } from 'lib/removeJSONStringifyQuotes';
|
import { removeJSONStringifyQuotes } from 'lib/removeJSONStringifyQuotes';
|
||||||
import { isEmpty } from 'lodash-es';
|
import { isEmpty } from 'lodash-es';
|
||||||
import { ArrowDownToDot, ArrowUpFromDot } from 'lucide-react';
|
import { ArrowDownToDot, ArrowUpFromDot, Pin } from 'lucide-react';
|
||||||
import { useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { generatePath } from 'react-router-dom';
|
import { generatePath } from 'react-router-dom';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
@ -57,6 +64,28 @@ function TableView({
|
|||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
const [isfilterInLoading, setIsFilterInLoading] = useState<boolean>(false);
|
const [isfilterInLoading, setIsFilterInLoading] = useState<boolean>(false);
|
||||||
const [isfilterOutLoading, setIsFilterOutLoading] = useState<boolean>(false);
|
const [isfilterOutLoading, setIsFilterOutLoading] = useState<boolean>(false);
|
||||||
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
|
const [pinnedAttributes, setPinnedAttributes] = useState<
|
||||||
|
Record<string, boolean>
|
||||||
|
>({});
|
||||||
|
|
||||||
|
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<string, string> | null = useMemo(
|
const flattenLogData: Record<string, string> | null = useMemo(
|
||||||
() => (logData ? flattenObject(logData) : null),
|
() => (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 = (
|
const onClickHandler = (
|
||||||
operator: string,
|
operator: string,
|
||||||
fieldKey: string,
|
fieldKey: string,
|
||||||
@ -138,6 +180,37 @@ function TableView({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const columns: ColumnsType<DataType> = [
|
const columns: ColumnsType<DataType> = [
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
dataIndex: 'pin',
|
||||||
|
key: 'pin',
|
||||||
|
width: 5,
|
||||||
|
align: 'left',
|
||||||
|
className: 'attribute-pin value-field-container',
|
||||||
|
render: (fieldData: Record<string, string>, record): JSX.Element => {
|
||||||
|
let pinColor = isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_500;
|
||||||
|
|
||||||
|
if (pinnedAttributes[record?.key]) {
|
||||||
|
pinColor = Color.BG_ROBIN_500;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="log-attribute-pin value-field">
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
'pin-attribute-icon',
|
||||||
|
pinnedAttributes[record?.key] ? 'pinned' : '',
|
||||||
|
)}
|
||||||
|
onClick={(): void => {
|
||||||
|
togglePinAttribute(record);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Pin size={14} color={pinColor} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Field',
|
title: 'Field',
|
||||||
dataIndex: 'field',
|
dataIndex: 'field',
|
||||||
@ -264,12 +337,34 @@ function TableView({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
function sortPinnedAttributes(
|
||||||
|
data: Record<string, string>[],
|
||||||
|
sortingObj: Record<string, boolean>,
|
||||||
|
): Record<string, string>[] {
|
||||||
|
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 (
|
return (
|
||||||
<ResizeTable
|
<ResizeTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
tableLayout="fixed"
|
tableLayout="fixed"
|
||||||
dataSource={dataSource}
|
dataSource={sortedAttributes}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
showHeader={false}
|
showHeader={false}
|
||||||
className="attribute-table-container"
|
className="attribute-table-container"
|
||||||
|
@ -8,6 +8,10 @@
|
|||||||
|
|
||||||
.resource-attributes-selector {
|
.resource-attributes-selector {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
background-color: var(--bg-ink-400);
|
||||||
|
border: 1px solid #454c58;
|
||||||
}
|
}
|
||||||
|
|
||||||
.environment-selector {
|
.environment-selector {
|
||||||
@ -18,3 +22,12 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lightMode {
|
||||||
|
.resourceAttributesFilter-container {
|
||||||
|
.resource-attributes-selector {
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
background: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,7 +8,6 @@ export const SearchContainer = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.2rem;
|
gap: 0.2rem;
|
||||||
padding: 0 0.2rem;
|
padding: 0 0.2rem;
|
||||||
border: 1px solid #454c58;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
`;
|
`;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user