From e16449856626ce44de09f6a80b16e0121a631244 Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 3 Jan 2025 14:55:38 -0600 Subject: [PATCH 1/2] feat: add table body state to url --- src/Components/ServiceScene/LogsListScene.tsx | 15 ++++++++-- .../ServiceScene/LogsTableScene.tsx | 9 +++++- .../Table/Context/TableColumnsContext.tsx | 18 ++++++++++-- src/Components/Table/RawLogLineText.tsx | 3 +- src/Components/Table/TableProvider.tsx | 7 +++++ src/Components/Table/TableWrap.tsx | 6 +++- src/services/testIds.ts | 1 + tests/exploreServicesBreakDown.spec.ts | 29 +++++++++++++++++-- 8 files changed, 78 insertions(+), 10 deletions(-) diff --git a/src/Components/ServiceScene/LogsListScene.tsx b/src/Components/ServiceScene/LogsListScene.tsx index e9b8071ea..4e26027de 100644 --- a/src/Components/ServiceScene/LogsListScene.tsx +++ b/src/Components/ServiceScene/LogsListScene.tsx @@ -26,12 +26,14 @@ import { import { logger } from '../../services/logger'; import { Options } from '@grafana/schema/dist/esm/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen'; import { narrowLogsVisualizationType, narrowSelectedTableRow, unknownToStrings } from '../../services/narrowing'; +import { LogLineState } from '../Table/Context/TableColumnsContext'; export interface LogsListSceneState extends SceneObjectState { loading?: boolean; panel?: SceneFlexLayout; visualizationType: LogsVisualizationType; urlColumns?: string[]; + urlLogLineState?: LogLineState; selectedLine?: SelectedTableRow; $timeRange?: SceneTimeRangeLike; displayedFields: string[]; @@ -39,7 +41,7 @@ export interface LogsListSceneState extends SceneObjectState { export class LogsListScene extends SceneObjectBase { protected _urlSync = new SceneObjectUrlSyncConfig(this, { - keys: ['urlColumns', 'selectedLine', 'visualizationType', 'displayedFields'], + keys: ['urlColumns', 'selectedLine', 'visualizationType', 'displayedFields', 'urlLogLineState'], }); private lineFilterScene?: LineFilterScene = undefined; private logsPanelScene?: LogsPanelScene = undefined; @@ -63,6 +65,7 @@ export class LogsListScene extends SceneObjectBase { selectedLine: JSON.stringify(selectedLine), visualizationType: JSON.stringify(visualizationType), displayedFields: JSON.stringify(displayedFields), + urlLogLineState: JSON.stringify(this.state.urlLogLineState), }; } @@ -84,20 +87,24 @@ export class LogsListScene extends SceneObjectBase { } } } - if (typeof values.visualizationType === 'string') { const decodedVisualizationType = narrowLogsVisualizationType(JSON.parse(values.visualizationType)); if (decodedVisualizationType && decodedVisualizationType !== this.state.visualizationType) { stateUpdate.visualizationType = decodedVisualizationType; } } - if (typeof values.displayedFields === 'string') { const displayedFields = unknownToStrings(JSON.parse(values.displayedFields)); if (displayedFields && displayedFields.length) { stateUpdate.displayedFields = displayedFields; } } + if (typeof values.urlLogLineState === 'string') { + const urlLogLineState = JSON.parse(values.urlLogLineState); + if (urlLogLineState === LogLineState.labels || urlLogLineState === LogLineState.text) { + stateUpdate.urlLogLineState = urlLogLineState; + } + } } catch (e) { // URL Params can be manually changed and it will make JSON.parse() fail. logger.error(e, { msg: 'LogsListScene: updateFromUrl unexpected error' }); @@ -147,12 +154,14 @@ export class LogsListScene extends SceneObjectBase { const urlColumnsUrl = searchParams.get('urlColumns'); const vizTypeUrl = searchParams.get('visualizationType'); const displayedFieldsUrl = searchParams.get('displayedFields') ?? JSON.stringify(getDisplayedFields(this)); + const urlLogLineState = searchParams.get('urlLogLineState'); this.updateFromUrl({ selectedLine: selectedLineUrl, urlColumns: urlColumnsUrl, vizType: vizTypeUrl, displayedFields: displayedFieldsUrl, + urlLogLineState, }); } diff --git a/src/Components/ServiceScene/LogsTableScene.tsx b/src/Components/ServiceScene/LogsTableScene.tsx index 1409fae36..e2c2bb5f6 100644 --- a/src/Components/ServiceScene/LogsTableScene.tsx +++ b/src/Components/ServiceScene/LogsTableScene.tsx @@ -11,6 +11,7 @@ import { areArraysEqual } from '../../services/comparison'; import { getLogsPanelFrame } from './ServiceScene'; import { getVariableForLabel } from '../../services/fields'; import { PanelMenu } from '../Panels/PanelMenu'; +import { LogLineState } from '../Table/Context/TableColumnsContext'; interface LogsTableSceneState extends SceneObjectState { menu?: PanelMenu; @@ -32,7 +33,7 @@ export class LogsTableScene extends SceneObjectBase { // Get state from parent model const parentModel = sceneGraph.getAncestor(model, LogsListScene); const { data } = sceneGraph.getData(model).useState(); - const { selectedLine, urlColumns, visualizationType } = parentModel.useState(); + const { selectedLine, urlColumns, visualizationType, urlLogLineState } = parentModel.useState(); const { menu } = model.useState(); // Get time range @@ -57,6 +58,10 @@ export class LogsTableScene extends SceneObjectBase { } }; + const setUrlTableBodyState = (logLineState: LogLineState) => { + parentModel.setState({ urlLogLineState: logLineState }); + }; + const clearSelectedLine = () => { if (parentModel.state.selectedLine) { parentModel.clearSelectedLine(); @@ -81,6 +86,8 @@ export class LogsTableScene extends SceneObjectBase { setUrlColumns={setUrlColumns} dataFrame={dataFrame} clearSelectedLine={clearSelectedLine} + setUrlTableBodyState={setUrlTableBodyState} + urlTableBodyState={urlLogLineState} /> )} diff --git a/src/Components/Table/Context/TableColumnsContext.tsx b/src/Components/Table/Context/TableColumnsContext.tsx index 56767db29..a161523be 100644 --- a/src/Components/Table/Context/TableColumnsContext.tsx +++ b/src/Components/Table/Context/TableColumnsContext.tsx @@ -97,15 +97,19 @@ export const TableColumnContextProvider = ({ logsFrame, setUrlColumns, clearSelectedLine, + setUrlTableBodyState, + urlTableBodyState, }: { children: ReactNode; initialColumns: FieldNameMetaStore; logsFrame: LogsFrame; setUrlColumns: (columns: string[]) => void; clearSelectedLine: () => void; + setUrlTableBodyState: (logLineState: LogLineState) => void; + urlTableBodyState?: LogLineState; }) => { const [columns, setColumns] = useState(removeExtraColumns(initialColumns)); - const [bodyState, setBodyState] = useState(LogLineState.auto); + const [bodyState, setBodyState] = useState(urlTableBodyState ?? LogLineState.auto); const [filteredColumns, setFilteredColumns] = useState(undefined); const [visible, setVisible] = useState(false); const initialColumnWidths = getColumnWidthsFromLocalStorage(); @@ -145,6 +149,16 @@ export const TableColumnContextProvider = ({ [setUrlColumns] ); + const handleSetBodyState = useCallback( + (logLineState: LogLineState) => { + setBodyState(logLineState); + + // Sync change with url state + setUrlTableBodyState(logLineState); + }, + [setUrlTableBodyState] + ); + const handleClearSelectedLine = () => { clearSelectedLine(); }; @@ -182,7 +196,7 @@ export const TableColumnContextProvider = ({ setColumnWidthMap, columnWidthMap, bodyState, - setBodyState, + setBodyState: handleSetBodyState, setFilteredColumns, filteredColumns, columns, diff --git a/src/Components/Table/RawLogLineText.tsx b/src/Components/Table/RawLogLineText.tsx index 5e4a2b229..8c2ba25ab 100644 --- a/src/Components/Table/RawLogLineText.tsx +++ b/src/Components/Table/RawLogLineText.tsx @@ -2,12 +2,13 @@ import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; import { useTheme2 } from '@grafana/ui'; +import { testIds } from '../../services/testIds'; export function RawLogLineText(props: { value: unknown }) { const theme = useTheme2(); const styles = getStyles(theme); return ( -
+
<>{props.value}
); diff --git a/src/Components/Table/TableProvider.tsx b/src/Components/Table/TableProvider.tsx index 7066ba967..11311a057 100644 --- a/src/Components/Table/TableProvider.tsx +++ b/src/Components/Table/TableProvider.tsx @@ -5,6 +5,7 @@ import { AdHocVariableFilter, DataFrame, TimeRange } from '@grafana/data'; import { QueryContextProvider } from 'Components/Table/Context/QueryContext'; import { parseLogsFrame } from '../../services/logsFrame'; import { SelectedTableRow } from './LogLineCellComponent'; +import { LogLineState } from './Context/TableColumnsContext'; interface TableProviderProps { dataFrame: DataFrame; @@ -15,6 +16,8 @@ interface TableProviderProps { timeRange?: TimeRange; panelWrap: React.RefObject; clearSelectedLine: () => void; + setUrlTableBodyState: (logLineState: LogLineState) => void; + urlTableBodyState?: LogLineState; } export const TableProvider = ({ @@ -26,6 +29,8 @@ export const TableProvider = ({ timeRange, panelWrap, clearSelectedLine, + setUrlTableBodyState, + urlTableBodyState, }: TableProviderProps) => { if (!dataFrame) { return null; @@ -39,7 +44,9 @@ export const TableProvider = ({ return ( void; panelWrap: React.RefObject; clearSelectedLine: () => void; + setUrlTableBodyState: (logLineState: LogLineState) => void; } const getStyles = () => ({ @@ -110,10 +112,12 @@ export const TableWrap = (props: TableWrapProps) => { return (
{ // Switch to table view await explorePage.getTableToggleLocator().click(); - const table = page.getByTestId(testIds.table.wrapper); await page.getByTestId('data-testid Panel menu Logs').click(); await page.getByTestId('data-testid Panel menu item Explore').click(); @@ -130,6 +129,32 @@ test.describe('explore services breakdown page', () => { await expect(page.getByTestId(`data-testid Dashboard template variables submenu Label ${levelName}`)).toBeVisible(); }); + test('table log line state should persist in the url', async ({ page }) => { + explorePage.blockAllQueriesExcept({ + refIds: ['logsPanelQuery'], + }); + await explorePage.getTableToggleLocator().click(); + const table = page.getByTestId(testIds.table.wrapper); + + // assert the table doesn't contain the raw log line option by default + await expect(table.getByTestId(testIds.table.rawLogLine)).toHaveCount(0); + + // Open menu + await page.getByRole('button', { name: 'Show menu' }).nth(1).click(); + + // Show log text option should be visible by default + await expect(page.getByText('Show log text')).toBeVisible(); + + // Change the option + await page.getByText('Show log text').click(); + + // Assert the change was made to the table + await expect(table.getByTestId(testIds.table.rawLogLine).nth(0)).toBeVisible(); + + await page.reload(); + await expect(table.getByTestId(testIds.table.rawLogLine).nth(0)).toBeVisible(); + }); + test('should show inspect modal', async ({ page }) => { await explorePage.getTableToggleLocator().click(); // Expect table to be rendered @@ -449,7 +474,7 @@ test.describe('explore services breakdown page', () => { refIds: ['A'], }); await page.getByTestId(testIds.exploreServiceDetails.tabPatterns).click(); - const key = page.getByText('<_>').last().click(); + await page.getByText('<_>').last().click(); // `From a sample of` is the indicator that the underlying query perfomed successfully await expect(page.getByText(`From a sample of`)).toBeVisible(); }); From c2463ea52953656a6ff877cb4c52a4c384219465 Mon Sep 17 00:00:00 2001 From: Galen Date: Mon, 6 Jan 2025 07:11:37 -0600 Subject: [PATCH 2/2] chore: refactor url param name --- src/Components/ServiceScene/LogsListScene.tsx | 18 +++++++++--------- src/Components/ServiceScene/LogsTableScene.tsx | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Components/ServiceScene/LogsListScene.tsx b/src/Components/ServiceScene/LogsListScene.tsx index 4e26027de..4dc1da0db 100644 --- a/src/Components/ServiceScene/LogsListScene.tsx +++ b/src/Components/ServiceScene/LogsListScene.tsx @@ -33,7 +33,7 @@ export interface LogsListSceneState extends SceneObjectState { panel?: SceneFlexLayout; visualizationType: LogsVisualizationType; urlColumns?: string[]; - urlLogLineState?: LogLineState; + tableLogLineState?: LogLineState; selectedLine?: SelectedTableRow; $timeRange?: SceneTimeRangeLike; displayedFields: string[]; @@ -41,7 +41,7 @@ export interface LogsListSceneState extends SceneObjectState { export class LogsListScene extends SceneObjectBase { protected _urlSync = new SceneObjectUrlSyncConfig(this, { - keys: ['urlColumns', 'selectedLine', 'visualizationType', 'displayedFields', 'urlLogLineState'], + keys: ['urlColumns', 'selectedLine', 'visualizationType', 'displayedFields', 'tableLogLineState'], }); private lineFilterScene?: LineFilterScene = undefined; private logsPanelScene?: LogsPanelScene = undefined; @@ -65,7 +65,7 @@ export class LogsListScene extends SceneObjectBase { selectedLine: JSON.stringify(selectedLine), visualizationType: JSON.stringify(visualizationType), displayedFields: JSON.stringify(displayedFields), - urlLogLineState: JSON.stringify(this.state.urlLogLineState), + tableLogLineState: JSON.stringify(this.state.tableLogLineState), }; } @@ -99,10 +99,10 @@ export class LogsListScene extends SceneObjectBase { stateUpdate.displayedFields = displayedFields; } } - if (typeof values.urlLogLineState === 'string') { - const urlLogLineState = JSON.parse(values.urlLogLineState); - if (urlLogLineState === LogLineState.labels || urlLogLineState === LogLineState.text) { - stateUpdate.urlLogLineState = urlLogLineState; + if (typeof values.tableLogLineState === 'string') { + const tableLogLineState = JSON.parse(values.tableLogLineState); + if (tableLogLineState === LogLineState.labels || tableLogLineState === LogLineState.text) { + stateUpdate.tableLogLineState = tableLogLineState; } } } catch (e) { @@ -154,14 +154,14 @@ export class LogsListScene extends SceneObjectBase { const urlColumnsUrl = searchParams.get('urlColumns'); const vizTypeUrl = searchParams.get('visualizationType'); const displayedFieldsUrl = searchParams.get('displayedFields') ?? JSON.stringify(getDisplayedFields(this)); - const urlLogLineState = searchParams.get('urlLogLineState'); + const tableLogLineState = searchParams.get('tableLogLineState'); this.updateFromUrl({ selectedLine: selectedLineUrl, urlColumns: urlColumnsUrl, vizType: vizTypeUrl, displayedFields: displayedFieldsUrl, - urlLogLineState, + tableLogLineState, }); } diff --git a/src/Components/ServiceScene/LogsTableScene.tsx b/src/Components/ServiceScene/LogsTableScene.tsx index e2c2bb5f6..554dd5783 100644 --- a/src/Components/ServiceScene/LogsTableScene.tsx +++ b/src/Components/ServiceScene/LogsTableScene.tsx @@ -33,7 +33,7 @@ export class LogsTableScene extends SceneObjectBase { // Get state from parent model const parentModel = sceneGraph.getAncestor(model, LogsListScene); const { data } = sceneGraph.getData(model).useState(); - const { selectedLine, urlColumns, visualizationType, urlLogLineState } = parentModel.useState(); + const { selectedLine, urlColumns, visualizationType, tableLogLineState } = parentModel.useState(); const { menu } = model.useState(); // Get time range @@ -59,7 +59,7 @@ export class LogsTableScene extends SceneObjectBase { }; const setUrlTableBodyState = (logLineState: LogLineState) => { - parentModel.setState({ urlLogLineState: logLineState }); + parentModel.setState({ tableLogLineState: logLineState }); }; const clearSelectedLine = () => { @@ -87,7 +87,7 @@ export class LogsTableScene extends SceneObjectBase { dataFrame={dataFrame} clearSelectedLine={clearSelectedLine} setUrlTableBodyState={setUrlTableBodyState} - urlTableBodyState={urlLogLineState} + urlTableBodyState={tableLogLineState} /> )}