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:
Vikrant Gupta 2024-08-12 16:34:43 +05:30 committed by GitHub
parent 187927403a
commit f3d73f6d44
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 88 additions and 33 deletions

View File

@ -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'>;

View File

@ -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} />}

View File

@ -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;

View File

@ -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 {

View File

@ -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;
}

View File

@ -245,6 +245,7 @@ function LogsPanelComponent({
onAddToQuery={onAddToQuery} onAddToQuery={onAddToQuery}
onClickActionItem={onAddToQuery} onClickActionItem={onAddToQuery}
isListViewPanel isListViewPanel
listViewPanelSelectedFields={widget?.selectedLogFields}
/> />
</> </>
); );