New Log Details: Add support to sort displayed fields (#107635)
* Create LogLineDetailsDisplayedFields * Log labels: properly implement plurals * Pluralize * LogListContext: pass setDisplayedFields * LogLineDetailsDisplayedFields: update displayed fields * LogLineDetails: scroll to item after opening details * LogListContext: serve logOptionsStorageKey * Update test * Missing translations * LogLineDetailsDisplayedFields: update styles * LogLineDetails: update text selectors * Update tests * Reorganize log details: improve discoverability * Update test
This commit is contained in:
@@ -1144,6 +1144,7 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
|||||||
permalinkedLogId={panelState?.logs?.id}
|
permalinkedLogId={panelState?.logs?.id}
|
||||||
pinLineButtonTooltipTitle={pinLineButtonTooltipTitle}
|
pinLineButtonTooltipTitle={pinLineButtonTooltipTitle}
|
||||||
pinnedLogs={pinnedLogs}
|
pinnedLogs={pinnedLogs}
|
||||||
|
setDisplayedFields={setDisplayedFields}
|
||||||
showControls
|
showControls
|
||||||
showTime={showTime}
|
showTime={showTime}
|
||||||
sortOrder={logsSortOrder}
|
sortOrder={logsSortOrder}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ const setup = (
|
|||||||
|
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
containerElement: document.createElement('div'),
|
containerElement: document.createElement('div'),
|
||||||
|
focusLogLine: jest.fn(),
|
||||||
logs,
|
logs,
|
||||||
onResize: jest.fn(),
|
onResize: jest.fn(),
|
||||||
...(propOverrides || {}),
|
...(propOverrides || {}),
|
||||||
@@ -198,8 +199,8 @@ describe('LogLineDetails', () => {
|
|||||||
setup(undefined, { entry: '' });
|
setup(undefined, { entry: '' });
|
||||||
expect(screen.queryByText('Fields')).not.toBeInTheDocument();
|
expect(screen.queryByText('Fields')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText('Links')).not.toBeInTheDocument();
|
expect(screen.queryByText('Links')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText('Indexed labels')).not.toBeInTheDocument();
|
expect(screen.queryByText(/Indexed label/)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText('Parsed fields')).not.toBeInTheDocument();
|
expect(screen.queryByText(/Parsed field/)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText('Structured metadata')).not.toBeInTheDocument();
|
expect(screen.queryByText('Structured metadata')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -400,8 +401,8 @@ describe('LogLineDetails', () => {
|
|||||||
expect(screen.getByText('value2')).toBeInTheDocument();
|
expect(screen.getByText('value2')).toBeInTheDocument();
|
||||||
expect(screen.getByText('label3')).toBeInTheDocument();
|
expect(screen.getByText('label3')).toBeInTheDocument();
|
||||||
expect(screen.getByText('value3')).toBeInTheDocument();
|
expect(screen.getByText('value3')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Indexed labels')).toBeInTheDocument();
|
expect(screen.getByText(/Indexed label/)).toBeInTheDocument();
|
||||||
expect(screen.getByText('Parsed fields')).toBeInTheDocument();
|
expect(screen.getByText(/Parsed field/)).toBeInTheDocument();
|
||||||
expect(screen.getByText('Structured metadata')).toBeInTheDocument();
|
expect(screen.getByText('Structured metadata')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
test('should not show label types if they are unavailable or not supported', () => {
|
test('should not show label types if they are unavailable or not supported', () => {
|
||||||
@@ -427,8 +428,8 @@ describe('LogLineDetails', () => {
|
|||||||
expect(screen.getByText('value3')).toBeInTheDocument();
|
expect(screen.getByText('value3')).toBeInTheDocument();
|
||||||
|
|
||||||
expect(screen.getByText('Fields')).toBeInTheDocument();
|
expect(screen.getByText('Fields')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Indexed labels')).not.toBeInTheDocument();
|
expect(screen.queryByText(/Indexed label/)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText('Parsed fields')).not.toBeInTheDocument();
|
expect(screen.queryByText(/Parsed field/)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText('Structured metadata')).not.toBeInTheDocument();
|
expect(screen.queryByText('Structured metadata')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -457,4 +458,37 @@ describe('LogLineDetails', () => {
|
|||||||
expect(screen.getAllByText('No results to display.')).toHaveLength(3);
|
expect(screen.getAllByText('No results to display.')).toHaveLength(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Label types', () => {
|
||||||
|
test('Does not show displayed fields controls if not present', () => {
|
||||||
|
setup(undefined, { labels: { key1: 'label1', key2: 'label2' } });
|
||||||
|
expect(screen.queryByText('Displayed fields')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Does not show displayed fields controls if required props are not present', () => {
|
||||||
|
setup(undefined, { labels: { key1: 'label1', key2: 'label2' } }, { displayedFields: ['key1', 'key2'] });
|
||||||
|
expect(screen.queryByText('Displayed fields')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Shows displayed fields controls if required props are present', async () => {
|
||||||
|
const setDisplayedFields = jest.fn();
|
||||||
|
const onClickHideField = jest.fn();
|
||||||
|
setup(
|
||||||
|
undefined,
|
||||||
|
{ labels: { key1: 'label1', key2: 'label2' } },
|
||||||
|
{ displayedFields: ['key1', 'key2'], setDisplayedFields, onClickHideField }
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Organize displayed fields')).toBeInTheDocument();
|
||||||
|
expect(screen.queryAllByLabelText('Remove field')).toHaveLength(0);
|
||||||
|
|
||||||
|
await userEvent.click(screen.getByText('Organize displayed fields'));
|
||||||
|
|
||||||
|
expect(screen.getAllByLabelText('Remove field')).toHaveLength(2);
|
||||||
|
|
||||||
|
await userEvent.click(screen.getAllByLabelText('Remove field')[0]);
|
||||||
|
|
||||||
|
expect(onClickHideField).toHaveBeenCalledWith('key1');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { Resizable } from 're-resizable';
|
import { Resizable } from 're-resizable';
|
||||||
import { useCallback, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { getDragStyles, useStyles2 } from '@grafana/ui';
|
import { getDragStyles, useStyles2 } from '@grafana/ui';
|
||||||
@@ -12,17 +12,23 @@ import { LOG_LIST_MIN_WIDTH } from './virtualization';
|
|||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
containerElement: HTMLDivElement;
|
containerElement: HTMLDivElement;
|
||||||
logOptionsStorageKey?: string;
|
focusLogLine: (log: LogListModel) => void;
|
||||||
logs: LogListModel[];
|
logs: LogListModel[];
|
||||||
onResize(): void;
|
onResize(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LogLineDetails = ({ containerElement, logOptionsStorageKey, logs, onResize }: Props) => {
|
export const LogLineDetails = ({ containerElement, focusLogLine, logs, onResize }: Props) => {
|
||||||
const { detailsWidth, setDetailsWidth, showDetails } = useLogListContext();
|
const { detailsWidth, logOptionsStorageKey, setDetailsWidth, showDetails } = useLogListContext();
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const dragStyles = useStyles2(getDragStyles);
|
const dragStyles = useStyles2(getDragStyles);
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
focusLogLine(showDetails[0]);
|
||||||
|
// Just once
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleResize = useCallback(() => {
|
const handleResize = useCallback(() => {
|
||||||
if (containerRef.current) {
|
if (containerRef.current) {
|
||||||
setDetailsWidth(containerRef.current.clientWidth);
|
setDetailsWidth(containerRef.current.clientWidth);
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import { getLabelTypeFromRow } from '../../utils';
|
|||||||
import { useAttributesExtensionLinks } from '../LogDetails';
|
import { useAttributesExtensionLinks } from '../LogDetails';
|
||||||
import { createLogLineLinks } from '../logParser';
|
import { createLogLineLinks } from '../logParser';
|
||||||
|
|
||||||
|
import { LogLineDetailsDisplayedFields } from './LogLineDetailsDisplayedFields';
|
||||||
import { LabelWithLinks, LogLineDetailsFields, LogLineDetailsLabelFields } from './LogLineDetailsFields';
|
import { LabelWithLinks, LogLineDetailsFields, LogLineDetailsLabelFields } from './LogLineDetailsFields';
|
||||||
import { LogLineDetailsHeader } from './LogLineDetailsHeader';
|
import { LogLineDetailsHeader } from './LogLineDetailsHeader';
|
||||||
|
import { useLogListContext } from './LogListContext';
|
||||||
import { LogListModel } from './processing';
|
import { LogListModel } from './processing';
|
||||||
|
|
||||||
interface LogLineDetailsComponentProps {
|
interface LogLineDetailsComponentProps {
|
||||||
@@ -21,6 +23,7 @@ interface LogLineDetailsComponentProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const LogLineDetailsComponent = ({ log, logOptionsStorageKey, logs }: LogLineDetailsComponentProps) => {
|
export const LogLineDetailsComponent = ({ log, logOptionsStorageKey, logs }: LogLineDetailsComponentProps) => {
|
||||||
|
const { displayedFields, setDisplayedFields } = useLogListContext();
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const inputRef = useRef('');
|
const inputRef = useRef('');
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
@@ -65,10 +68,12 @@ export const LogLineDetailsComponent = ({ log, logOptionsStorageKey, logs }: Log
|
|||||||
const fieldsOpen = logOptionsStorageKey
|
const fieldsOpen = logOptionsStorageKey
|
||||||
? store.getBool(`${logOptionsStorageKey}.log-details.fieldsOpen`, true)
|
? store.getBool(`${logOptionsStorageKey}.log-details.fieldsOpen`, true)
|
||||||
: true;
|
: true;
|
||||||
|
const displayedFieldsOpen = logOptionsStorageKey
|
||||||
|
? store.getBool(`${logOptionsStorageKey}.log-details.displayedFieldsOpen`, false)
|
||||||
|
: false;
|
||||||
|
|
||||||
const handleToggle = useCallback(
|
const handleToggle = useCallback(
|
||||||
(option: string, isOpen: boolean) => {
|
(option: string, isOpen: boolean) => {
|
||||||
console.log(option, isOpen);
|
|
||||||
store.set(`${logOptionsStorageKey}.log-details.${option}`, isOpen);
|
store.set(`${logOptionsStorageKey}.log-details.${option}`, isOpen);
|
||||||
},
|
},
|
||||||
[logOptionsStorageKey]
|
[logOptionsStorageKey]
|
||||||
@@ -100,6 +105,16 @@ export const LogLineDetailsComponent = ({ log, logOptionsStorageKey, logs }: Log
|
|||||||
>
|
>
|
||||||
<div className={styles.logLineWrapper}>{log.raw}</div>
|
<div className={styles.logLineWrapper}>{log.raw}</div>
|
||||||
</ControlledCollapse>
|
</ControlledCollapse>
|
||||||
|
{displayedFields.length > 0 && setDisplayedFields && (
|
||||||
|
<ControlledCollapse
|
||||||
|
label={t('logs.log-line-details.displayed-fields-section', 'Organize displayed fields')}
|
||||||
|
collapsible
|
||||||
|
isOpen={displayedFieldsOpen}
|
||||||
|
onToggle={(isOpen: boolean) => handleToggle('displayedFieldsOpen', isOpen)}
|
||||||
|
>
|
||||||
|
<LogLineDetailsDisplayedFields />
|
||||||
|
</ControlledCollapse>
|
||||||
|
)}
|
||||||
{fieldsWithLinks.links.length > 0 && (
|
{fieldsWithLinks.links.length > 0 && (
|
||||||
<ControlledCollapse
|
<ControlledCollapse
|
||||||
className={styles.collapsable}
|
className={styles.collapsable}
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { t } from '@grafana/i18n';
|
||||||
|
import { Card, IconButton, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { LOG_LINE_BODY_FIELD_NAME } from '../LogDetailsBody';
|
||||||
|
|
||||||
|
import { useLogListContext } from './LogListContext';
|
||||||
|
|
||||||
|
export const LogLineDetailsDisplayedFields = () => {
|
||||||
|
const { displayedFields, setDisplayedFields } = useLogListContext();
|
||||||
|
|
||||||
|
const onDragEnd = useCallback(
|
||||||
|
(result: DropResult) => {
|
||||||
|
if (result.destination == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newDisplayedFields = [...displayedFields];
|
||||||
|
const element = displayedFields[result.source.index];
|
||||||
|
newDisplayedFields.splice(result.source.index, 1);
|
||||||
|
newDisplayedFields.splice(result.destination.index, 0, element);
|
||||||
|
|
||||||
|
setDisplayedFields?.(newDisplayedFields);
|
||||||
|
},
|
||||||
|
[displayedFields, setDisplayedFields]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
|
<Droppable ignoreContainerClipping={true} droppableId="displayed-fields" direction="vertical">
|
||||||
|
{(provided) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div ref={provided.innerRef} {...provided.droppableProps}>
|
||||||
|
{displayedFields.map((field, index) => (
|
||||||
|
<DraggableDisplayedField key={field} field={field} index={index} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{provided.placeholder}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Droppable>
|
||||||
|
</DragDropContext>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface DraggableDisplayedFieldProps {
|
||||||
|
field: string;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DraggableDisplayedField = ({ field, index }: DraggableDisplayedFieldProps) => {
|
||||||
|
const { onClickHideField } = useLogListContext();
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
return (
|
||||||
|
<Draggable draggableId={field} index={index}>
|
||||||
|
{(provided) => (
|
||||||
|
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
|
||||||
|
<Card noMargin className={styles.fieldCard}>
|
||||||
|
<div>
|
||||||
|
{field === LOG_LINE_BODY_FIELD_NAME ? t('logs.log-line-details.log-line-field', 'Log line') : field}
|
||||||
|
</div>
|
||||||
|
{onClickHideField && (
|
||||||
|
<IconButton
|
||||||
|
name="times"
|
||||||
|
onClick={() => onClickHideField(field)}
|
||||||
|
tooltip={t('logs.log-line-details.remove-displayed-field', 'Remove field')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
|
fieldCard: css({
|
||||||
|
cursor: 'move',
|
||||||
|
padding: theme.spacing(1),
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -2,7 +2,7 @@ import { css } from '@emotion/css';
|
|||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { Grammar } from 'prismjs';
|
import { Grammar } from 'prismjs';
|
||||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, MouseEvent } from 'react';
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, MouseEvent } from 'react';
|
||||||
import { VariableSizeList } from 'react-window';
|
import { Align, VariableSizeList } from 'react-window';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AbsoluteTimeRange,
|
AbsoluteTimeRange,
|
||||||
@@ -73,6 +73,7 @@ export interface Props {
|
|||||||
permalinkedLogId?: string;
|
permalinkedLogId?: string;
|
||||||
pinLineButtonTooltipTitle?: PopoverContent;
|
pinLineButtonTooltipTitle?: PopoverContent;
|
||||||
pinnedLogs?: string[];
|
pinnedLogs?: string[];
|
||||||
|
setDisplayedFields?: (displayedFields: string[]) => void;
|
||||||
showControls: boolean;
|
showControls: boolean;
|
||||||
showTime: boolean;
|
showTime: boolean;
|
||||||
sortOrder: LogsSortOrder;
|
sortOrder: LogsSortOrder;
|
||||||
@@ -92,6 +93,7 @@ type LogListComponentProps = Omit<
|
|||||||
| 'dedupStrategy'
|
| 'dedupStrategy'
|
||||||
| 'displayedFields'
|
| 'displayedFields'
|
||||||
| 'enableLogDetails'
|
| 'enableLogDetails'
|
||||||
|
| 'logOptionsStorageKey'
|
||||||
| 'permalinkedLogId'
|
| 'permalinkedLogId'
|
||||||
| 'showTime'
|
| 'showTime'
|
||||||
| 'sortOrder'
|
| 'sortOrder'
|
||||||
@@ -135,6 +137,7 @@ export const LogList = ({
|
|||||||
permalinkedLogId,
|
permalinkedLogId,
|
||||||
pinLineButtonTooltipTitle,
|
pinLineButtonTooltipTitle,
|
||||||
pinnedLogs,
|
pinnedLogs,
|
||||||
|
setDisplayedFields,
|
||||||
showControls,
|
showControls,
|
||||||
showTime,
|
showTime,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
@@ -176,6 +179,7 @@ export const LogList = ({
|
|||||||
permalinkedLogId={permalinkedLogId}
|
permalinkedLogId={permalinkedLogId}
|
||||||
pinLineButtonTooltipTitle={pinLineButtonTooltipTitle}
|
pinLineButtonTooltipTitle={pinLineButtonTooltipTitle}
|
||||||
pinnedLogs={pinnedLogs}
|
pinnedLogs={pinnedLogs}
|
||||||
|
setDisplayedFields={setDisplayedFields}
|
||||||
showControls={showControls}
|
showControls={showControls}
|
||||||
showTime={showTime}
|
showTime={showTime}
|
||||||
sortOrder={sortOrder}
|
sortOrder={sortOrder}
|
||||||
@@ -191,7 +195,6 @@ export const LogList = ({
|
|||||||
initialScrollPosition={initialScrollPosition}
|
initialScrollPosition={initialScrollPosition}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
loadMore={loadMore}
|
loadMore={loadMore}
|
||||||
logOptionsStorageKey={logOptionsStorageKey}
|
|
||||||
logs={logs}
|
logs={logs}
|
||||||
showControls={showControls}
|
showControls={showControls}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
@@ -210,7 +213,6 @@ const LogListComponent = ({
|
|||||||
initialScrollPosition = 'top',
|
initialScrollPosition = 'top',
|
||||||
loading,
|
loading,
|
||||||
loadMore,
|
loadMore,
|
||||||
logOptionsStorageKey,
|
|
||||||
logs,
|
logs,
|
||||||
showControls,
|
showControls,
|
||||||
timeRange,
|
timeRange,
|
||||||
@@ -271,6 +273,12 @@ const LogListComponent = ({
|
|||||||
}, 25);
|
}, 25);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const debouncedScrollToItem = useMemo(() => {
|
||||||
|
return debounce((index: number, align?: Align) => {
|
||||||
|
listRef.current?.scrollToItem(index, align);
|
||||||
|
}, 250);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = eventBus.subscribe(ScrollToLogsEvent, (e: ScrollToLogsEvent) =>
|
const subscription = eventBus.subscribe(ScrollToLogsEvent, (e: ScrollToLogsEvent) =>
|
||||||
handleScrollToEvent(e, logs.length, listRef.current)
|
handleScrollToEvent(e, logs.length, listRef.current)
|
||||||
@@ -380,6 +388,16 @@ const LogListComponent = ({
|
|||||||
[filterLogs, levelFilteredLogs, matchingUids]
|
[filterLogs, levelFilteredLogs, matchingUids]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const focusLogLine = useCallback(
|
||||||
|
(log: LogListModel) => {
|
||||||
|
const index = filteredLogs.indexOf(log);
|
||||||
|
if (index >= 0) {
|
||||||
|
debouncedScrollToItem(index, 'start');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[debouncedScrollToItem, filteredLogs]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.logListContainer}>
|
<div className={styles.logListContainer}>
|
||||||
<div className={styles.logListWrapper} ref={wrapperRef}>
|
<div className={styles.logListWrapper} ref={wrapperRef}>
|
||||||
@@ -461,7 +479,7 @@ const LogListComponent = ({
|
|||||||
{showDetails.length > 0 && (
|
{showDetails.length > 0 && (
|
||||||
<LogLineDetails
|
<LogLineDetails
|
||||||
containerElement={containerElement}
|
containerElement={containerElement}
|
||||||
logOptionsStorageKey={logOptionsStorageKey}
|
focusLogLine={focusLogLine}
|
||||||
logs={filteredLogs}
|
logs={filteredLogs}
|
||||||
onResize={handleLogDetailsResize}
|
onResize={handleLogDetailsResize}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ export interface Props {
|
|||||||
pinLineButtonTooltipTitle?: PopoverContent;
|
pinLineButtonTooltipTitle?: PopoverContent;
|
||||||
pinnedLogs?: string[];
|
pinnedLogs?: string[];
|
||||||
prettifyJSON?: boolean;
|
prettifyJSON?: boolean;
|
||||||
|
setDisplayedFields?: (displayedFields: string[]) => void;
|
||||||
showControls: boolean;
|
showControls: boolean;
|
||||||
showUniqueLabels?: boolean;
|
showUniqueLabels?: boolean;
|
||||||
showTime: boolean;
|
showTime: boolean;
|
||||||
@@ -204,6 +205,7 @@ export const LogListContextProvider = ({
|
|||||||
pinLineButtonTooltipTitle,
|
pinLineButtonTooltipTitle,
|
||||||
pinnedLogs,
|
pinnedLogs,
|
||||||
prettifyJSON,
|
prettifyJSON,
|
||||||
|
setDisplayedFields,
|
||||||
showControls,
|
showControls,
|
||||||
showTime,
|
showTime,
|
||||||
showUniqueLabels,
|
showUniqueLabels,
|
||||||
@@ -492,6 +494,7 @@ export const LogListContextProvider = ({
|
|||||||
getRowContextQuery,
|
getRowContextQuery,
|
||||||
logSupportsContext,
|
logSupportsContext,
|
||||||
logLineMenuCustomItems,
|
logLineMenuCustomItems,
|
||||||
|
logOptionsStorageKey,
|
||||||
onClickFilterLabel,
|
onClickFilterLabel,
|
||||||
onClickFilterOutLabel,
|
onClickFilterOutLabel,
|
||||||
onClickFilterString,
|
onClickFilterString,
|
||||||
@@ -509,6 +512,7 @@ export const LogListContextProvider = ({
|
|||||||
prettifyJSON: logListState.prettifyJSON,
|
prettifyJSON: logListState.prettifyJSON,
|
||||||
setDedupStrategy,
|
setDedupStrategy,
|
||||||
setDetailsWidth,
|
setDetailsWidth,
|
||||||
|
setDisplayedFields,
|
||||||
setFilterLevels,
|
setFilterLevels,
|
||||||
setFontSize,
|
setFontSize,
|
||||||
setForceEscape,
|
setForceEscape,
|
||||||
|
|||||||
@@ -408,17 +408,11 @@ function getDataSourceLabelType(labelType: string, datasourceType: string, plura
|
|||||||
case 'loki':
|
case 'loki':
|
||||||
switch (labelType) {
|
switch (labelType) {
|
||||||
case 'I':
|
case 'I':
|
||||||
return plural
|
return t('logs.fields.type.loki.indexed-label', 'Indexed label', { count: plural ? 2 : 1 });
|
||||||
? t('logs.fields.type.loki.indexed-label-plural', 'Indexed labels')
|
|
||||||
: t('logs.fields.type.loki.indexed-label', 'Indexed label');
|
|
||||||
case 'S':
|
case 'S':
|
||||||
return plural
|
return t('logs.fields.type.loki.structured-metadata', 'Structured metadata', { count: plural ? 2 : 1 });
|
||||||
? t('logs.fields.type.loki.structured-metadata-plural', 'Structured metadata')
|
|
||||||
: t('logs.fields.type.loki.structured-metadata', 'Structured metadata');
|
|
||||||
case 'P':
|
case 'P':
|
||||||
return plural
|
return t('logs.fields.type.loki.parsedl-label', 'Parsed field', { count: plural ? 2 : 1 });
|
||||||
? t('logs.fields.type.loki.parsed-label-plural', 'Parsed fields')
|
|
||||||
: t('logs.fields.type.loki.parsedl-label', 'Parsed field');
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -565,6 +565,7 @@ export const LogsPanel = ({
|
|||||||
onOpenContext={onOpenContext}
|
onOpenContext={onOpenContext}
|
||||||
onPermalinkClick={showPermaLink() ? onPermalinkClick : undefined}
|
onPermalinkClick={showPermaLink() ? onPermalinkClick : undefined}
|
||||||
permalinkedLogId={getLogsPanelState()?.logs?.id ?? undefined}
|
permalinkedLogId={getLogsPanelState()?.logs?.id ?? undefined}
|
||||||
|
setDisplayedFields={setDisplayedFields}
|
||||||
showControls={Boolean(showControls)}
|
showControls={Boolean(showControls)}
|
||||||
showTime={showTime}
|
showTime={showTime}
|
||||||
sortOrder={sortOrder}
|
sortOrder={sortOrder}
|
||||||
|
|||||||
@@ -8644,12 +8644,12 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"type": {
|
"type": {
|
||||||
"loki": {
|
"loki": {
|
||||||
"indexed-label": "Indexed label",
|
"indexed-label_one": "Indexed label",
|
||||||
"indexed-label-plural": "Indexed labels",
|
"indexed-label_other": "Indexed labels",
|
||||||
"parsed-label-plural": "Parsed fields",
|
"parsedl-label_one": "Parsed field",
|
||||||
"parsedl-label": "Parsed field",
|
"parsedl-label_other": "Parsed fields",
|
||||||
"structured-metadata": "Structured metadata",
|
"structured-metadata_one": "Structured metadata",
|
||||||
"structured-metadata-plural": "Structured metadata"
|
"structured-metadata_other": "Structured metadata"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -8706,6 +8706,7 @@
|
|||||||
"close": "Close log details",
|
"close": "Close log details",
|
||||||
"copy-shortlink": "Copy shortlink",
|
"copy-shortlink": "Copy shortlink",
|
||||||
"copy-to-clipboard": "Copy to clipboard",
|
"copy-to-clipboard": "Copy to clipboard",
|
||||||
|
"displayed-fields-section": "Organize displayed fields",
|
||||||
"fields": {
|
"fields": {
|
||||||
"adhoc-statistics": "Ad-hoc statistics",
|
"adhoc-statistics": "Ad-hoc statistics",
|
||||||
"copy-value-to-clipboard": "Copy value to clipboard",
|
"copy-value-to-clipboard": "Copy value to clipboard",
|
||||||
@@ -8719,9 +8720,11 @@
|
|||||||
"fields-section": "Fields",
|
"fields-section": "Fields",
|
||||||
"hide-log-line": "Hide log line",
|
"hide-log-line": "Hide log line",
|
||||||
"links-section": "Links",
|
"links-section": "Links",
|
||||||
|
"log-line-field": "Log line",
|
||||||
"log-line-section": "Log line",
|
"log-line-section": "Log line",
|
||||||
"no-details": "No fields to display.",
|
"no-details": "No fields to display.",
|
||||||
"pin-line": "Pin log",
|
"pin-line": "Pin log",
|
||||||
|
"remove-displayed-field": "Remove field",
|
||||||
"search": {
|
"search": {
|
||||||
"no-results": "No results to display."
|
"no-results": "No results to display."
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user