Log Components: Optimize style computing and re-renders in Row, Details, and Context (#63728)

* Log Details: grab parsed styles from parent

* Log Details Row: better memoization of style parsing

* Log Context: less re-renders

* Log Row Styles: memoize

* Log Row: use class method instead of anonymous function
This commit is contained in:
Matias Chomicki 2023-03-03 10:19:48 +01:00 committed by GitHub
parent 9521b0d2d2
commit b731540939
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 102 additions and 100 deletions

View File

@ -5,8 +5,11 @@ import { Field, LogLevel, LogRowModel, MutableDataFrame, createTheme } from '@gr
import { LogDetails, Props } from './LogDetails';
import { createLogRow } from './__mocks__/logRow';
import { getLogRowStyles } from './getLogRowStyles';
const setup = (propOverrides?: Partial<Props>, rowOverrides?: Partial<LogRowModel>) => {
const theme = createTheme();
const styles = getLogRowStyles(theme);
const props: Props = {
displayedFields: [],
showDuplicates: false,
@ -17,7 +20,8 @@ const setup = (propOverrides?: Partial<Props>, rowOverrides?: Partial<LogRowMode
onClickFilterOutLabel: () => {},
onClickShowField: () => {},
onClickHideField: () => {},
theme: createTheme(),
theme,
styles,
...(propOverrides || {}),
};

View File

@ -1,13 +1,13 @@
import { css, cx } from '@emotion/css';
import { cx } from '@emotion/css';
import React, { PureComponent } from 'react';
import { CoreApp, DataFrame, Field, GrafanaTheme2, LinkModel, LogRowModel } from '@grafana/data';
import { CoreApp, DataFrame, Field, LinkModel, LogRowModel } from '@grafana/data';
import { Themeable2, withTheme2 } from '@grafana/ui';
import { calculateLogsLabelStats, calculateStats } from '../utils';
import { LogDetailsRow } from './LogDetailsRow';
import { getLogLevelStyles, getLogRowStyles } from './getLogRowStyles';
import { getLogLevelStyles, LogRowStyles } from './getLogRowStyles';
import { getAllFields } from './logParser';
export interface Props extends Themeable2 {
@ -18,6 +18,7 @@ export interface Props extends Themeable2 {
className?: string;
hasError?: boolean;
app?: CoreApp;
styles: LogRowStyles;
onClickFilterLabel?: (key: string, value: string) => void;
onClickFilterOutLabel?: (key: string, value: string) => void;
@ -27,25 +28,6 @@ export interface Props extends Themeable2 {
onClickHideField?: (key: string) => void;
}
const getStyles = (theme: GrafanaTheme2) => {
return {
logsRowLevelDetails: css`
label: logs-row__level_details;
&::after {
top: -3px;
}
`,
logDetails: css`
label: logDetailsDefaultCursor;
cursor: default;
&:hover {
background-color: ${theme.colors.background.primary};
}
`,
};
};
class UnThemedLogDetails extends PureComponent<Props> {
render() {
const {
@ -63,10 +45,9 @@ class UnThemedLogDetails extends PureComponent<Props> {
displayedFields,
getFieldLinks,
wrapLogMessage,
styles,
} = this.props;
const rowStyles = getLogRowStyles(theme);
const levelStyles = getLogLevelStyles(theme, row.logLevel);
const styles = getStyles(theme);
const labels = row.labels ? row.labels : {};
const labelsAvailable = Object.keys(labels).length > 0;
const fieldsAndLinks = getAllFields(row, getFieldLinks);
@ -78,19 +59,19 @@ class UnThemedLogDetails extends PureComponent<Props> {
// If logs with error, we are not showing the level color
const levelClassName = hasError
? ''
: `${levelStyles.logsRowLevelColor} ${rowStyles.logsRowLevel} ${styles.logsRowLevelDetails}`;
: `${levelStyles.logsRowLevelColor} ${styles.logsRowLevel} ${styles.logsRowLevelDetails}`;
return (
<tr className={cx(className, styles.logDetails)}>
{showDuplicates && <td />}
<td className={levelClassName} aria-label="Log level" />
<td colSpan={4}>
<div className={rowStyles.logDetailsContainer}>
<table className={rowStyles.logDetailsTable}>
<div className={styles.logDetailsContainer}>
<table className={styles.logDetailsTable}>
<tbody>
{(labelsAvailable || fieldsAvailable) && (
<tr>
<td colSpan={100} className={rowStyles.logDetailsHeading} aria-label="Fields">
<td colSpan={100} className={styles.logDetailsHeading} aria-label="Fields">
Fields
</td>
</tr>
@ -139,7 +120,7 @@ class UnThemedLogDetails extends PureComponent<Props> {
{linksAvailable && (
<tr>
<td colSpan={100} className={rowStyles.logDetailsHeading} aria-label="Data Links">
<td colSpan={100} className={styles.logDetailsHeading} aria-label="Data Links">
Links
</td>
</tr>

View File

@ -34,41 +34,7 @@ interface State {
fieldStats: LogLabelStatsModel[] | null;
}
const getStyles = memoizeOne((theme: GrafanaTheme2, activeButton: boolean) => {
// those styles come from ToolbarButton. Unfortunately this is needed because we can not control the variant of the menu-button in a ToolbarButtonRow.
const defaultOld = css`
color: ${theme.colors.text.secondary};
background-color: ${theme.colors.background.primary};
&:hover {
color: ${theme.colors.text.primary};
background: ${theme.colors.background.secondary};
}
`;
const defaultTopNav = css`
color: ${theme.colors.text.secondary};
background-color: transparent;
border-color: transparent;
&:hover {
color: ${theme.colors.text.primary};
background: ${theme.colors.background.secondary};
}
`;
const active = css`
color: ${theme.v1.palette.orangeDark};
border-color: ${theme.v1.palette.orangeDark};
background-color: transparent;
&:hover {
color: ${theme.colors.text.primary};
background: ${theme.colors.emphasize(theme.colors.background.canvas, 0.03)};
}
`;
const defaultToolbarButtonStyle = theme.flags.topnav ? defaultTopNav : defaultOld;
const getStyles = memoizeOne((theme: GrafanaTheme2) => {
return {
noHoverBackground: css`
label: noHoverBackground;
@ -130,8 +96,17 @@ const getStyles = memoizeOne((theme: GrafanaTheme2, activeButton: boolean) => {
}
}
}
`,
toolbarButtonRowActive: css`
& div:last-child > button:not(.stats-button) {
${activeButton ? active : defaultToolbarButtonStyle};
color: ${theme.v1.palette.orangeDark};
border-color: ${theme.v1.palette.orangeDark};
background-color: transparent;
&:hover {
color: ${theme.colors.text.primary};
background: ${theme.colors.emphasize(theme.colors.background.canvas, 0.03)};
}
}
`,
logDetailsStats: css`
@ -268,7 +243,7 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
} = this.props;
const { showFieldsStats, fieldStats, fieldCount } = this.state;
const activeButton = displayedFields?.includes(parsedKey) || showFieldsStats;
const styles = getStyles(theme, activeButton);
const styles = getStyles(theme);
const style = getLogRowStyles(theme);
const hasFilteringFunctionality = onClickFilterLabel && onClickFilterOutLabel;
@ -289,7 +264,10 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
<>
<tr className={cx(style.logDetailsValue)}>
<td className={style.logsDetailsIcon}>
<ToolbarButtonRow alignment="left" className={styles.toolbarButtonRow}>
<ToolbarButtonRow
alignment="left"
className={cx(styles.toolbarButtonRow, activeButton && styles.toolbarButtonRowActive)}
>
{hasFilteringFunctionality && (
<ToolbarButton
iconOnly

View File

@ -120,6 +120,18 @@ class UnThemedLogRow extends PureComponent<Props, State> {
});
}
onMouseEnter = () => {
if (this.props.onLogRowHover) {
this.props.onLogRowHover(this.props.row);
}
};
onMouseLeave = () => {
if (this.props.onLogRowHover) {
this.props.onLogRowHover(undefined);
}
};
renderLogRow(
context?: LogRowContextRows,
errors?: LogRowContextQueryErrors,
@ -148,7 +160,6 @@ class UnThemedLogRow extends PureComponent<Props, State> {
theme,
getFieldLinks,
forceEscape,
onLogRowHover,
app,
scrollElement,
styles,
@ -171,12 +182,8 @@ class UnThemedLogRow extends PureComponent<Props, State> {
<tr
className={logRowBackground}
onClick={this.toggleDetails}
onMouseEnter={() => {
onLogRowHover && onLogRowHover(row);
}}
onMouseLeave={() => {
onLogRowHover && onLogRowHover(undefined);
}}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
>
{showDuplicates && (
<td className={styles.logsRowDuplicates}>
@ -246,6 +253,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
hasError={hasError}
displayedFields={displayedFields}
app={app}
styles={styles}
/>
)}
</>

View File

@ -1,5 +1,5 @@
import { css, cx } from '@emotion/css';
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import usePrevious from 'react-use/lib/usePrevious';
import {
@ -376,9 +376,10 @@ export const LogRowContext: React.FunctionComponent<LogRowContextProps> = ({
const { afterContext, beforeContext, title, top, actions, width } = useStyles2((theme) =>
getLogRowContextStyles(theme, wrapLogMessage)
);
const handleOutsideClick = useCallback(() => onOutsideClick('close_outside_click'), [onOutsideClick]);
return (
<ClickOutsideWrapper onClick={() => onOutsideClick('close_outside_click')}>
<ClickOutsideWrapper onClick={handleOutsideClick}>
{/* e.stopPropagation is necessary so the log details doesn't open when clicked on log line in context
* and/or when context log line is being highlighted
*/}

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import useAsync from 'react-use/lib/useAsync';
import {
@ -211,31 +211,45 @@ export const LogRowContextProvider: React.FunctionComponent<LogRowContextProvide
}
}, [results]);
return children({
result: {
const updateLimit = useCallback(() => {
setLimit(limit + 10);
const { datasourceType, uid: logRowUid } = row;
reportInteraction('grafana_explore_logs_log_context_load_more_clicked', {
datasourceType,
logRowUid,
newLimit: limit + 10,
});
}, [limit, row]);
const runContextQuery = useCallback(async () => {
const results = await getRowContexts(getRowContext, row, limit, logsSortOrder);
results.doNotCheckForMore = true;
setResults(results);
}, [getRowContext, limit, logsSortOrder, row]);
const resultData = useMemo(
() => ({
before: result ? result.data[0] : [],
after: result ? result.data[1] : [],
},
errors: {
}),
[result]
);
const errorsData = useMemo(
() => ({
before: result ? result.errors[0] : undefined,
after: result ? result.errors[1] : undefined,
},
hasMoreContextRows,
updateLimit: () => {
setLimit(limit + 10);
}),
[result]
);
const { datasourceType, uid: logRowUid } = row;
reportInteraction('grafana_explore_logs_log_context_load_more_clicked', {
datasourceType,
logRowUid,
newLimit: limit + 10,
});
},
runContextQuery: async () => {
const results = await getRowContexts(getRowContext, row, limit, logsSortOrder);
results.doNotCheckForMore = true;
setResults(results);
},
return children({
result: resultData,
errors: errorsData,
hasMoreContextRows,
updateLimit,
runContextQuery,
limit,
logsSortOrder,
});

View File

@ -1,4 +1,5 @@
import { css } from '@emotion/css';
import memoizeOne from 'memoize-one';
import tinycolor from 'tinycolor2';
import { GrafanaTheme2, LogLevel } from '@grafana/data';
@ -39,7 +40,7 @@ export const getLogLevelStyles = (theme: GrafanaTheme2, logLevel?: LogLevel) =>
};
};
export const getLogRowStyles = (theme: GrafanaTheme2) => {
export const getLogRowStyles = memoizeOne((theme: GrafanaTheme2) => {
const hoverBgColor = styleMixins.hoverColor(theme.colors.background.secondary, theme);
const contextOutlineColor = tinycolor(theme.components.dashboard.background).setAlpha(0.7).toRgbString();
return {
@ -263,7 +264,22 @@ export const getLogRowStyles = (theme: GrafanaTheme2) => {
padding: 0;
user-select: text;
`,
// Log details
logsRowLevelDetails: css`
label: logs-row__level_details;
&::after {
top: -3px;
}
`,
logDetails: css`
label: logDetailsDefaultCursor;
cursor: default;
&:hover {
background-color: ${theme.colors.background.primary};
}
`,
};
};
});
export type LogRowStyles = ReturnType<typeof getLogRowStyles>;