mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-06-21 21:03:20 +08:00
feat: use selected columns as pinned attributes (#5601)
* feat: use selected columns as pinned attributes * chore: handle nested json structs * chore: refactor and fix build issues * feat: handle changes for dashboard list panel * chore: remove console logs
This commit is contained in:
parent
187927403a
commit
f3d73f6d44
@ -1,6 +1,7 @@
|
|||||||
import { DrawerProps } from 'antd';
|
import { DrawerProps } from 'antd';
|
||||||
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
|
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
|
||||||
import { ActionItemProps } from 'container/LogDetailedView/ActionItem';
|
import { ActionItemProps } from 'container/LogDetailedView/ActionItem';
|
||||||
|
import { IField } from 'types/api/logs/fields';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
import { VIEWS } from './constants';
|
import { VIEWS } from './constants';
|
||||||
@ -9,6 +10,7 @@ export type LogDetailProps = {
|
|||||||
log: ILog | null;
|
log: ILog | null;
|
||||||
selectedTab: VIEWS;
|
selectedTab: VIEWS;
|
||||||
isListViewPanel?: boolean;
|
isListViewPanel?: boolean;
|
||||||
|
listViewPanelSelectedFields?: IField[] | null;
|
||||||
} & Pick<AddToQueryHOCProps, 'onAddToQuery'> &
|
} & Pick<AddToQueryHOCProps, 'onAddToQuery'> &
|
||||||
Partial<Pick<ActionItemProps, 'onClickActionItem'>> &
|
Partial<Pick<ActionItemProps, 'onClickActionItem'>> &
|
||||||
Pick<DrawerProps, 'onClose'>;
|
Pick<DrawerProps, 'onClose'>;
|
||||||
|
@ -6,10 +6,13 @@ import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
|||||||
import { RadioChangeEvent } from 'antd/lib';
|
import { RadioChangeEvent } from 'antd/lib';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
|
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
|
||||||
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
import ContextView from 'container/LogDetailedView/ContextView/ContextView';
|
import ContextView from 'container/LogDetailedView/ContextView/ContextView';
|
||||||
import JSONView from 'container/LogDetailedView/JsonView';
|
import JSONView from 'container/LogDetailedView/JsonView';
|
||||||
import Overview from 'container/LogDetailedView/Overview';
|
import Overview from 'container/LogDetailedView/Overview';
|
||||||
import { aggregateAttributesResourcesToString } from 'container/LogDetailedView/utils';
|
import { aggregateAttributesResourcesToString } from 'container/LogDetailedView/utils';
|
||||||
|
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||||
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import {
|
import {
|
||||||
@ -21,9 +24,10 @@ import {
|
|||||||
TextSelect,
|
TextSelect,
|
||||||
X,
|
X,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useCopyToClipboard } from 'react-use';
|
import { useCopyToClipboard } from 'react-use';
|
||||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
import { VIEW_TYPES, VIEWS } from './constants';
|
import { VIEW_TYPES, VIEWS } from './constants';
|
||||||
import { LogDetailProps } from './LogDetail.interfaces';
|
import { LogDetailProps } from './LogDetail.interfaces';
|
||||||
@ -36,6 +40,7 @@ function LogDetail({
|
|||||||
onClickActionItem,
|
onClickActionItem,
|
||||||
selectedTab,
|
selectedTab,
|
||||||
isListViewPanel = false,
|
isListViewPanel = false,
|
||||||
|
listViewPanelSelectedFields,
|
||||||
}: LogDetailProps): JSX.Element {
|
}: LogDetailProps): JSX.Element {
|
||||||
const [, copyToClipboard] = useCopyToClipboard();
|
const [, copyToClipboard] = useCopyToClipboard();
|
||||||
const [selectedView, setSelectedView] = useState<VIEWS>(selectedTab);
|
const [selectedView, setSelectedView] = useState<VIEWS>(selectedTab);
|
||||||
@ -45,6 +50,19 @@ function LogDetail({
|
|||||||
const [contextQuery, setContextQuery] = useState<Query | undefined>();
|
const [contextQuery, setContextQuery] = useState<Query | undefined>();
|
||||||
const [filters, setFilters] = useState<TagFilter | null>(null);
|
const [filters, setFilters] = useState<TagFilter | null>(null);
|
||||||
const [isEdit, setIsEdit] = useState<boolean>(false);
|
const [isEdit, setIsEdit] = useState<boolean>(false);
|
||||||
|
const { initialDataSource, stagedQuery } = useQueryBuilder();
|
||||||
|
|
||||||
|
const listQuery = useMemo(() => {
|
||||||
|
if (!stagedQuery || stagedQuery.builder.queryData.length < 1) return null;
|
||||||
|
|
||||||
|
return stagedQuery.builder.queryData.find((item) => !item.disabled) || null;
|
||||||
|
}, [stagedQuery]);
|
||||||
|
|
||||||
|
const { options } = useOptionsMenu({
|
||||||
|
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
|
||||||
|
dataSource: initialDataSource || DataSource.LOGS,
|
||||||
|
aggregateOperator: listQuery?.aggregateOperator || StringOperators.NOOP,
|
||||||
|
});
|
||||||
|
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
@ -192,6 +210,8 @@ function LogDetail({
|
|||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
onClickActionItem={onClickActionItem}
|
onClickActionItem={onClickActionItem}
|
||||||
isListViewPanel={isListViewPanel}
|
isListViewPanel={isListViewPanel}
|
||||||
|
selectedOptions={options}
|
||||||
|
listViewPanelSelectedFields={listViewPanelSelectedFields}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{selectedView === VIEW_TYPES.JSON && <JSONView logData={log} />}
|
{selectedView === VIEW_TYPES.JSON && <JSONView logData={log} />}
|
||||||
|
@ -12,9 +12,11 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
|
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
|
||||||
|
import { OptionsQuery } from 'container/OptionsMenu/types';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { ChevronDown, ChevronRight, Search } from 'lucide-react';
|
import { ChevronDown, ChevronRight, Search } from 'lucide-react';
|
||||||
import { ReactNode, useState } from 'react';
|
import { ReactNode, useState } from 'react';
|
||||||
|
import { IField } from 'types/api/logs/fields';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
import { ActionItemProps } from './ActionItem';
|
import { ActionItemProps } from './ActionItem';
|
||||||
@ -23,6 +25,8 @@ import TableView from './TableView';
|
|||||||
interface OverviewProps {
|
interface OverviewProps {
|
||||||
logData: ILog;
|
logData: ILog;
|
||||||
isListViewPanel?: boolean;
|
isListViewPanel?: boolean;
|
||||||
|
selectedOptions: OptionsQuery;
|
||||||
|
listViewPanelSelectedFields?: IField[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = OverviewProps &
|
type Props = OverviewProps &
|
||||||
@ -34,6 +38,8 @@ function Overview({
|
|||||||
onAddToQuery,
|
onAddToQuery,
|
||||||
onClickActionItem,
|
onClickActionItem,
|
||||||
isListViewPanel = false,
|
isListViewPanel = false,
|
||||||
|
selectedOptions,
|
||||||
|
listViewPanelSelectedFields,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const [isWrapWord, setIsWrapWord] = useState<boolean>(true);
|
const [isWrapWord, setIsWrapWord] = useState<boolean>(true);
|
||||||
const [isSearchVisible, setIsSearchVisible] = useState<boolean>(false);
|
const [isSearchVisible, setIsSearchVisible] = useState<boolean>(false);
|
||||||
@ -200,6 +206,8 @@ function Overview({
|
|||||||
fieldSearchInput={fieldSearchInput}
|
fieldSearchInput={fieldSearchInput}
|
||||||
onClickActionItem={onClickActionItem}
|
onClickActionItem={onClickActionItem}
|
||||||
isListViewPanel={isListViewPanel}
|
isListViewPanel={isListViewPanel}
|
||||||
|
selectedOptions={selectedOptions}
|
||||||
|
listViewPanelSelectedFields={listViewPanelSelectedFields}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
@ -213,6 +221,7 @@ function Overview({
|
|||||||
|
|
||||||
Overview.defaultProps = {
|
Overview.defaultProps = {
|
||||||
isListViewPanel: false,
|
isListViewPanel: false,
|
||||||
|
listViewPanelSelectedFields: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Overview;
|
export default Overview;
|
||||||
|
@ -6,17 +6,15 @@ 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 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 { OptionsQuery } from 'container/OptionsMenu/types';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
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';
|
||||||
@ -29,12 +27,14 @@ import { generatePath } from 'react-router-dom';
|
|||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
||||||
|
import { IField } from 'types/api/logs/fields';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
import { ActionItemProps } from './ActionItem';
|
import { ActionItemProps } from './ActionItem';
|
||||||
import FieldRenderer from './FieldRenderer';
|
import FieldRenderer from './FieldRenderer';
|
||||||
import {
|
import {
|
||||||
filterKeyForField,
|
filterKeyForField,
|
||||||
|
findKeyPath,
|
||||||
flattenObject,
|
flattenObject,
|
||||||
jsonToDataNodes,
|
jsonToDataNodes,
|
||||||
recursiveParseJSON,
|
recursiveParseJSON,
|
||||||
@ -47,7 +47,9 @@ const RESTRICTED_FIELDS = ['timestamp'];
|
|||||||
interface TableViewProps {
|
interface TableViewProps {
|
||||||
logData: ILog;
|
logData: ILog;
|
||||||
fieldSearchInput: string;
|
fieldSearchInput: string;
|
||||||
|
selectedOptions: OptionsQuery;
|
||||||
isListViewPanel?: boolean;
|
isListViewPanel?: boolean;
|
||||||
|
listViewPanelSelectedFields?: IField[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = TableViewProps &
|
type Props = TableViewProps &
|
||||||
@ -60,6 +62,8 @@ function TableView({
|
|||||||
onAddToQuery,
|
onAddToQuery,
|
||||||
onClickActionItem,
|
onClickActionItem,
|
||||||
isListViewPanel = false,
|
isListViewPanel = false,
|
||||||
|
selectedOptions,
|
||||||
|
listViewPanelSelectedFields,
|
||||||
}: Props): JSX.Element | null {
|
}: Props): JSX.Element | null {
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
const [isfilterInLoading, setIsFilterInLoading] = useState<boolean>(false);
|
const [isfilterInLoading, setIsFilterInLoading] = useState<boolean>(false);
|
||||||
@ -71,21 +75,31 @@ function TableView({
|
|||||||
>({});
|
>({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const pinnedAttributesFromLocalStorage = getLocalStorageApi(
|
const pinnedAttributes: Record<string, boolean> = {};
|
||||||
LOCALSTORAGE.PINNED_ATTRIBUTES,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (pinnedAttributesFromLocalStorage) {
|
if (isListViewPanel) {
|
||||||
try {
|
listViewPanelSelectedFields?.forEach((val) => {
|
||||||
const parsedPinnedAttributes = JSON.parse(pinnedAttributesFromLocalStorage);
|
const path = findKeyPath(logData, val.name, '');
|
||||||
setPinnedAttributes(parsedPinnedAttributes);
|
if (path) {
|
||||||
} catch (e) {
|
pinnedAttributes[path] = true;
|
||||||
console.error('Error parsing pinned attributes from local storgage');
|
}
|
||||||
}
|
});
|
||||||
} else {
|
} else {
|
||||||
setPinnedAttributes({});
|
selectedOptions.selectColumns.forEach((val) => {
|
||||||
|
const path = findKeyPath(logData, val.key, '');
|
||||||
|
if (path) {
|
||||||
|
pinnedAttributes[path] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
|
||||||
|
setPinnedAttributes(pinnedAttributes);
|
||||||
|
}, [
|
||||||
|
logData,
|
||||||
|
selectedOptions.selectColumns,
|
||||||
|
listViewPanelSelectedFields,
|
||||||
|
isListViewPanel,
|
||||||
|
]);
|
||||||
|
|
||||||
const flattenLogData: Record<string, string> | null = useMemo(
|
const flattenLogData: Record<string, string> | null = useMemo(
|
||||||
() => (logData ? flattenObject(logData) : null),
|
() => (logData ? flattenObject(logData) : null),
|
||||||
@ -103,19 +117,6 @@ 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,
|
||||||
@ -201,11 +202,8 @@ function TableView({
|
|||||||
'pin-attribute-icon',
|
'pin-attribute-icon',
|
||||||
pinnedAttributes[record?.key] ? 'pinned' : '',
|
pinnedAttributes[record?.key] ? 'pinned' : '',
|
||||||
)}
|
)}
|
||||||
onClick={(): void => {
|
|
||||||
togglePinAttribute(record);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Pin size={14} color={pinColor} />
|
{pinnedAttributes[record?.key] && <Pin size={14} color={pinColor} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -380,6 +378,7 @@ function TableView({
|
|||||||
|
|
||||||
TableView.defaultProps = {
|
TableView.defaultProps = {
|
||||||
isListViewPanel: false,
|
isListViewPanel: false,
|
||||||
|
listViewPanelSelectedFields: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
interface DataType {
|
interface DataType {
|
||||||
|
@ -267,3 +267,27 @@ export const removeEscapeCharacters = (str: string): string =>
|
|||||||
export function removeExtraSpaces(input: string): string {
|
export function removeExtraSpaces(input: string): string {
|
||||||
return input.replace(/\s+/g, ' ').trim();
|
return input.replace(/\s+/g, ' ').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findKeyPath(
|
||||||
|
obj: AnyObject,
|
||||||
|
targetKey: string,
|
||||||
|
currentPath = '',
|
||||||
|
): string | null {
|
||||||
|
let finalPath = null;
|
||||||
|
Object.keys(obj).forEach((key) => {
|
||||||
|
const value = obj[key];
|
||||||
|
const newPath = currentPath ? `${currentPath}.${key}` : key;
|
||||||
|
|
||||||
|
if (key === targetKey) {
|
||||||
|
finalPath = newPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
const result = findKeyPath(value, targetKey, newPath);
|
||||||
|
if (result) {
|
||||||
|
finalPath = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return finalPath;
|
||||||
|
}
|
||||||
|
@ -245,6 +245,7 @@ function LogsPanelComponent({
|
|||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
onClickActionItem={onAddToQuery}
|
onClickActionItem={onAddToQuery}
|
||||||
isListViewPanel
|
isListViewPanel
|
||||||
|
listViewPanelSelectedFields={widget?.selectedLogFields}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user