Logs: migration of columns in the url, update tests
This commit is contained in:
@@ -30,7 +30,6 @@ import {
|
||||
serializeStateToUrlParam,
|
||||
urlUtil,
|
||||
LogLevel,
|
||||
shallowCompare,
|
||||
} from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { config, reportInteraction } from '@grafana/runtime';
|
||||
@@ -47,10 +46,12 @@ import {
|
||||
Themeable2,
|
||||
withTheme2,
|
||||
} from '@grafana/ui';
|
||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||
import store from 'app/core/store';
|
||||
import { createAndCopyShortLink, getLogsPermalinkRange } from 'app/core/utils/shortLinks';
|
||||
import { ControlledLogRows } from 'app/features/logs/components/ControlledLogRows';
|
||||
import { InfiniteScroll } from 'app/features/logs/components/InfiniteScroll';
|
||||
import { LOG_LINE_BODY_FIELD_NAME, TABLE_LINE_FIELD_NAME } from 'app/features/logs/components/LogDetailsBody';
|
||||
import { LogRows } from 'app/features/logs/components/LogRows';
|
||||
import { LogRowContextModal } from 'app/features/logs/components/log-context/LogRowContextModal';
|
||||
import { LogLineContext } from 'app/features/logs/components/panel/LogLineContext';
|
||||
@@ -74,6 +75,7 @@ import {
|
||||
} from '../ContentOutline/ContentOutlineAnalyticEvents';
|
||||
import { useContentOutlineContext } from '../ContentOutline/ContentOutlineContext';
|
||||
import { getUrlStateFromPaneState } from '../hooks/useStateSync';
|
||||
import { parseURL } from '../hooks/useStateSync/parseURL';
|
||||
import { changePanelState } from '../state/explorePane';
|
||||
import { changeQueries, runQueries } from '../state/query';
|
||||
|
||||
@@ -213,6 +215,7 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
const logsContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const dispatch = useDispatch();
|
||||
const previousLoading = usePrevious(loading);
|
||||
const { location } = useGrafana();
|
||||
|
||||
const logsVolumeEventBus = eventBus.newScopedBus('logsvolume', { onlyLocal: false });
|
||||
const { register, unregister, outlineItems, updateItem } = useContentOutlineContext() ?? {};
|
||||
@@ -344,7 +347,122 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
]
|
||||
);
|
||||
|
||||
// No longer needed - displayedFields is read directly from Redux state
|
||||
// Migration: Convert legacy 'columns' parameter from URL to 'displayedFields'
|
||||
useEffect(() => {
|
||||
// Parse URL to check for legacy columns
|
||||
const urlParams = location.getSearchObject();
|
||||
const [urlState] = parseURL(urlParams);
|
||||
const urlPane = urlState.panes[exploreId];
|
||||
|
||||
if (!urlPane?.panelsState?.logs) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for legacy columns in URL panelsState
|
||||
const logsState = urlPane.panelsState.logs;
|
||||
const hasColumns = 'columns' in logsState;
|
||||
if (!hasColumns) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use Object.getOwnPropertyDescriptor to safely access the property
|
||||
const columnsDescriptor = Object.getOwnPropertyDescriptor(logsState, 'columns');
|
||||
const columnsValue = columnsDescriptor?.value;
|
||||
|
||||
// Type guard to validate columns format
|
||||
let legacyColumns: Record<string, string> | string[] | undefined = undefined;
|
||||
if (Array.isArray(columnsValue)) {
|
||||
legacyColumns = columnsValue;
|
||||
} else if (typeof columnsValue === 'object' && columnsValue !== null && !Array.isArray(columnsValue)) {
|
||||
const values = Object.values(columnsValue);
|
||||
if (values.length > 0 && values.every((v) => typeof v === 'string')) {
|
||||
// Create a new object to avoid type assertion
|
||||
const record: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(columnsValue)) {
|
||||
if (typeof value === 'string') {
|
||||
record[key] = value;
|
||||
}
|
||||
}
|
||||
legacyColumns = record;
|
||||
}
|
||||
}
|
||||
|
||||
if (!legacyColumns) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert columns to array format
|
||||
let columnsArray: string[] = [];
|
||||
if (Array.isArray(legacyColumns)) {
|
||||
columnsArray = legacyColumns;
|
||||
} else if (typeof legacyColumns === 'object' && legacyColumns !== null) {
|
||||
// Handle object format like {0: 'Time', 1: 'Line'}
|
||||
columnsArray = Object.values(legacyColumns);
|
||||
}
|
||||
|
||||
// Map legacy field names to new field names
|
||||
const mappedColumns = columnsArray.map((column) => {
|
||||
// Map 'Line' to LOG_LINE_BODY_FIELD_NAME
|
||||
if (column === TABLE_LINE_FIELD_NAME) {
|
||||
return LOG_LINE_BODY_FIELD_NAME;
|
||||
}
|
||||
// Other fields like 'Time' stay the same
|
||||
return column;
|
||||
});
|
||||
|
||||
// Merge with defaultDisplayedFields: defaults first, then migrated columns (avoid duplicates)
|
||||
const mergedFields: string[] = [...defaultDisplayedFields];
|
||||
mappedColumns.forEach((column) => {
|
||||
if (!mergedFields.includes(column)) {
|
||||
mergedFields.push(column);
|
||||
}
|
||||
});
|
||||
|
||||
// Update displayedFields in state
|
||||
const state: ExploreItemState | undefined = getState().explore.panes[exploreId];
|
||||
if (state?.panelsState?.logs) {
|
||||
// Create new logs state without columns property
|
||||
// Explicitly exclude 'columns' by copying only the properties we want
|
||||
const logsState = state.panelsState.logs;
|
||||
const newLogsState: ExploreLogsPanelState = {
|
||||
visualisationType: logsState.visualisationType,
|
||||
displayedFields: mergedFields,
|
||||
...(logsState.labelFieldName && { labelFieldName: logsState.labelFieldName }),
|
||||
...(logsState.refId && { refId: logsState.refId }),
|
||||
...(logsState.id && { id: logsState.id }),
|
||||
};
|
||||
|
||||
dispatch(changePanelState(exploreId, 'logs', newLogsState));
|
||||
|
||||
// Remove columns from URL by re-serializing all panes
|
||||
// Use the updated state we just created (without columns) instead of reading from Redux
|
||||
const allPanes = getState().explore.panes;
|
||||
const panesObj = Object.entries(allPanes).reduce((acc, [id, paneState]) => {
|
||||
if (!paneState) {
|
||||
return acc;
|
||||
}
|
||||
// For the current exploreId, use the updated state without columns
|
||||
let stateToSerialize = paneState;
|
||||
if (id === exploreId) {
|
||||
// Create updated pane state with the new logs state (without columns)
|
||||
stateToSerialize = {
|
||||
...paneState,
|
||||
panelsState: {
|
||||
...paneState.panelsState,
|
||||
logs: newLogsState,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
...acc,
|
||||
[id]: getUrlStateFromPaneState(stateToSerialize),
|
||||
};
|
||||
}, {});
|
||||
|
||||
// Update URL - columns will be excluded since it's not in the type definition
|
||||
location.partial({ panes: JSON.stringify(panesObj) }, false);
|
||||
}
|
||||
}, [exploreId, displayedFields, defaultDisplayedFields, dispatch, location]);
|
||||
|
||||
// actions
|
||||
const onLogRowHover = useCallback(
|
||||
|
||||
@@ -67,7 +67,7 @@ describe('LogsTableWrap', () => {
|
||||
setup({
|
||||
panelState: {
|
||||
visualisationType: 'table',
|
||||
columns: undefined,
|
||||
displayedFields: undefined,
|
||||
},
|
||||
updatePanelState: updatePanelState,
|
||||
});
|
||||
@@ -109,7 +109,7 @@ describe('LogsTableWrap', () => {
|
||||
setup({
|
||||
panelState: {
|
||||
visualisationType: 'table',
|
||||
columns: undefined,
|
||||
displayedFields: undefined,
|
||||
},
|
||||
updatePanelState: updatePanelState,
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ function getLogsTableTransformations(
|
||||
options: ExploreToDashboardPanelOptions
|
||||
): DataTransformerConfig[] {
|
||||
let transformations: DataTransformerConfig[] = [];
|
||||
if (panelType === 'table' && options.panelState?.logs?.columns) {
|
||||
if (panelType === 'table' && options.panelState?.logs?.displayedFields) {
|
||||
// If we have a labels column, we need to extract the fields from it
|
||||
if (options.panelState.logs?.labelFieldName) {
|
||||
transformations.push({
|
||||
@@ -34,19 +34,18 @@ function getLogsTableTransformations(
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Show the columns that the user selected in explore
|
||||
transformations.push({
|
||||
id: 'organize',
|
||||
options: {
|
||||
indexByName: Object.values(options.panelState.logs.columns).reduce(
|
||||
indexByName: options.panelState.logs.displayedFields.reduce(
|
||||
(acc: Record<string, number>, value: string, idx) => ({
|
||||
...acc,
|
||||
[value]: idx,
|
||||
}),
|
||||
{}
|
||||
),
|
||||
includeByName: Object.values(options.panelState.logs.columns).reduce(
|
||||
includeByName: options.panelState.logs.displayedFields.reduce(
|
||||
(acc: Record<string, boolean>, value: string) => ({
|
||||
...acc,
|
||||
[value]: true,
|
||||
|
||||
Reference in New Issue
Block a user