mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: Refactor log details table (#21044)
This commit is contained in:
parent
a187500c0e
commit
cd39c2bd25
@ -37,7 +37,7 @@ describe('LogDetails', () => {
|
|||||||
describe('when labels are present', () => {
|
describe('when labels are present', () => {
|
||||||
it('should render heading', () => {
|
it('should render heading', () => {
|
||||||
const wrapper = setup(undefined, { labels: { key1: 'label1', key2: 'label2' } });
|
const wrapper = setup(undefined, { labels: { key1: 'label1', key2: 'label2' } });
|
||||||
expect(wrapper.find({ 'aria-label': 'Log labels' })).toHaveLength(1);
|
expect(wrapper.find({ 'aria-label': 'Log Labels' })).toHaveLength(1);
|
||||||
});
|
});
|
||||||
it('should render labels', () => {
|
it('should render labels', () => {
|
||||||
const wrapper = setup(undefined, { labels: { key1: 'label1', key2: 'label2' } });
|
const wrapper = setup(undefined, { labels: { key1: 'label1', key2: 'label2' } });
|
||||||
@ -47,7 +47,7 @@ describe('LogDetails', () => {
|
|||||||
describe('when row entry has parsable fields', () => {
|
describe('when row entry has parsable fields', () => {
|
||||||
it('should render heading ', () => {
|
it('should render heading ', () => {
|
||||||
const wrapper = setup(undefined, { entry: 'test=successful' });
|
const wrapper = setup(undefined, { entry: 'test=successful' });
|
||||||
expect(wrapper.find({ 'aria-label': 'Parsed fields' })).toHaveLength(1);
|
expect(wrapper.find({ title: 'Ad-hoc statistics' })).toHaveLength(1);
|
||||||
});
|
});
|
||||||
it('should render parsed fields', () => {
|
it('should render parsed fields', () => {
|
||||||
const wrapper = setup(undefined, { entry: 'test=successful' });
|
const wrapper = setup(undefined, { entry: 'test=successful' });
|
||||||
@ -57,8 +57,8 @@ describe('LogDetails', () => {
|
|||||||
describe('when row entry have parsable fields and labels are present', () => {
|
describe('when row entry have parsable fields and labels are present', () => {
|
||||||
it('should render all headings', () => {
|
it('should render all headings', () => {
|
||||||
const wrapper = setup(undefined, { entry: 'test=successful', labels: { key: 'label' } });
|
const wrapper = setup(undefined, { entry: 'test=successful', labels: { key: 'label' } });
|
||||||
expect(wrapper.find({ 'aria-label': 'Log labels' })).toHaveLength(1);
|
expect(wrapper.find({ 'aria-label': 'Log Labels' })).toHaveLength(1);
|
||||||
expect(wrapper.find({ 'aria-label': 'Parsed fields' })).toHaveLength(1);
|
expect(wrapper.find({ 'aria-label': 'Parsed Fields' })).toHaveLength(1);
|
||||||
});
|
});
|
||||||
it('should render all labels and parsed fields', () => {
|
it('should render all labels and parsed fields', () => {
|
||||||
const wrapper = setup(undefined, {
|
const wrapper = setup(undefined, {
|
||||||
|
@ -106,18 +106,20 @@ class UnThemedLogDetails extends PureComponent<Props> {
|
|||||||
const style = getLogRowStyles(theme, row.logLevel);
|
const style = getLogRowStyles(theme, row.logLevel);
|
||||||
const labels = row.labels ? row.labels : {};
|
const labels = row.labels ? row.labels : {};
|
||||||
const labelsAvailable = Object.keys(labels).length > 0;
|
const labelsAvailable = Object.keys(labels).length > 0;
|
||||||
|
|
||||||
const fields = this.getAllFields(row);
|
const fields = this.getAllFields(row);
|
||||||
|
|
||||||
const parsedFieldsAvailable = fields && fields.length > 0;
|
const parsedFieldsAvailable = fields && fields.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.logsRowDetailsTable}>
|
<div className={style.logDetailsContainer}>
|
||||||
{labelsAvailable && (
|
<table className={style.logDetailsTable}>
|
||||||
<div className={style.logsRowDetailsSectionTable}>
|
<tbody>
|
||||||
<div className={style.logsRowDetailsHeading} aria-label="Log labels">
|
{labelsAvailable && (
|
||||||
Log Labels:
|
<tr>
|
||||||
</div>
|
<td colSpan={5} className={style.logDetailsHeading} aria-label="Log Labels">
|
||||||
|
Log Labels:
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
{Object.keys(labels).map(key => {
|
{Object.keys(labels).map(key => {
|
||||||
const value = labels[key];
|
const value = labels[key];
|
||||||
return (
|
return (
|
||||||
@ -132,14 +134,14 @@ class UnThemedLogDetails extends PureComponent<Props> {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{parsedFieldsAvailable && (
|
{parsedFieldsAvailable && (
|
||||||
<div className={style.logsRowDetailsSectionTable}>
|
<tr>
|
||||||
<div className={style.logsRowDetailsHeading} aria-label="Parsed fields">
|
<td colSpan={5} className={style.logDetailsHeading} aria-label="Parsed Fields">
|
||||||
Parsed fields:
|
Parsed Fields:
|
||||||
</div>
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
{fields.map(field => {
|
{fields.map(field => {
|
||||||
const { key, value, links, fieldIndex } = field;
|
const { key, value, links, fieldIndex } = field;
|
||||||
return (
|
return (
|
||||||
@ -156,9 +158,15 @@ class UnThemedLogDetails extends PureComponent<Props> {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
{!parsedFieldsAvailable && !labelsAvailable && (
|
||||||
)}
|
<tr>
|
||||||
{!parsedFieldsAvailable && !labelsAvailable && <div aria-label="No details">No details available</div>}
|
<td colSpan={5} aria-label="No details">
|
||||||
|
No details available
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ describe('LogDetailsRow', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(wrapper.find(LogLabelStats).length).toBe(0);
|
expect(wrapper.find(LogLabelStats).length).toBe(0);
|
||||||
wrapper.find('[aria-label="Field stats"]').simulate('click');
|
wrapper.find({ title: 'Ad-hoc statistics' }).simulate('click');
|
||||||
expect(wrapper.find(LogLabelStats).length).toBe(1);
|
expect(wrapper.find(LogLabelStats).length).toBe(1);
|
||||||
expect(wrapper.find(LogLabelStats).contains('another value')).toBeTruthy();
|
expect(wrapper.find(LogLabelStats).contains('another value')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
@ -28,12 +28,16 @@ interface State {
|
|||||||
|
|
||||||
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
return {
|
return {
|
||||||
noHoverEffect: css`
|
noHoverBackground: css`
|
||||||
label: noHoverEffect;
|
label: noHoverBackground;
|
||||||
:hover {
|
:hover {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
hoverCursor: css`
|
||||||
|
label: hoverCursor;
|
||||||
|
cursor: pointer;
|
||||||
|
`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -82,37 +86,24 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
|||||||
const styles = getStyles(theme);
|
const styles = getStyles(theme);
|
||||||
const style = getLogRowStyles(theme);
|
const style = getLogRowStyles(theme);
|
||||||
return (
|
return (
|
||||||
<div className={cx(style.logsRowDetailsValue, { [styles.noHoverEffect]: showFieldsStats })}>
|
<tr className={cx(style.logDetailsValue, { [styles.noHoverBackground]: showFieldsStats })}>
|
||||||
{/* Action buttons - show stats/filter results */}
|
{/* Action buttons - show stats/filter results */}
|
||||||
<div
|
<td title="Ad-hoc statistics" onClick={this.showStats} className={style.logsDetailsIcon}>
|
||||||
title="Ad-hoc statistics"
|
<i className={`fa fa-signal ${styles.hoverCursor}`} />
|
||||||
onClick={this.showStats}
|
</td>
|
||||||
aria-label={'Field stats'}
|
|
||||||
className={style.logsRowDetailsIcon}
|
<td title="Filter for value" onClick={() => isLabel && this.filterLabel()} className={style.logsDetailsIcon}>
|
||||||
>
|
{isLabel && <i className={`fa fa-search-plus ${styles.hoverCursor}`} />}
|
||||||
<i className={'fa fa-signal'} />
|
</td>
|
||||||
</div>
|
|
||||||
{isLabel ? (
|
<td title="Filter out value" onClick={() => isLabel && this.filterOutLabel()} className={style.logsDetailsIcon}>
|
||||||
<div title="Filter for value" onClick={() => this.filterLabel()} className={style.logsRowDetailsIcon}>
|
{isLabel && <i className={`fa fa-search-minus ${styles.hoverCursor}`} />}
|
||||||
<i className={'fa fa-search-plus'} />
|
</td>
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className={style.logsRowDetailsIcon} />
|
|
||||||
)}
|
|
||||||
{isLabel ? (
|
|
||||||
<div title="Filter out value" onClick={() => this.filterOutLabel()} className={style.logsRowDetailsIcon}>
|
|
||||||
<i className={'fa fa-search-minus'} />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className={style.logsRowDetailsIcon} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Key - value columns */}
|
{/* Key - value columns */}
|
||||||
<div className={style.logsRowDetailsLabel}>
|
<td className={style.logDetailsLabel}>{parsedKey}</td>
|
||||||
<span>{parsedKey}</span>
|
<td className={style.logsRowCell}>
|
||||||
</div>
|
{parsedValue}
|
||||||
<div className={style.logsRowCell}>
|
|
||||||
<span>{parsedValue}</span>
|
|
||||||
{links &&
|
{links &&
|
||||||
links.map(link => {
|
links.map(link => {
|
||||||
return (
|
return (
|
||||||
@ -125,18 +116,16 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{showFieldsStats && (
|
{showFieldsStats && (
|
||||||
<div className={style.logsRowCell}>
|
<LogLabelStats
|
||||||
<LogLabelStats
|
stats={fieldStats!}
|
||||||
stats={fieldStats!}
|
label={parsedKey}
|
||||||
label={parsedKey}
|
value={parsedValue}
|
||||||
value={parsedValue}
|
rowCount={fieldCount}
|
||||||
rowCount={fieldCount}
|
isLabel={isLabel}
|
||||||
isLabel={isLabel}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
return {
|
return {
|
||||||
logsStats: css`
|
logsStats: css`
|
||||||
label: logs-stats;
|
label: logs-stats;
|
||||||
display: table-cell;
|
|
||||||
column-span: 2;
|
column-span: 2;
|
||||||
background: inherit;
|
background: inherit;
|
||||||
color: ${theme.colors.text};
|
color: ${theme.colors.text};
|
||||||
|
word-break: break-all;
|
||||||
`,
|
`,
|
||||||
logsStatsHeader: css`
|
logsStatsHeader: css`
|
||||||
label: logs-stats__header;
|
label: logs-stats__header;
|
||||||
@ -82,7 +82,7 @@ class UnThemedLogLabelStats extends PureComponent<Props> {
|
|||||||
const otherProportion = otherCount / total;
|
const otherProportion = otherCount / total;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.logsStats}>
|
<td className={style.logsStats}>
|
||||||
<div className={style.logsStatsHeader}>
|
<div className={style.logsStatsHeader}>
|
||||||
<div className={style.logsStatsTitle}>
|
<div className={style.logsStatsTitle}>
|
||||||
{label}: {total} of {rowCount} rows have that {isLabel ? 'label' : 'field'}
|
{label}: {total} of {rowCount} rows have that {isLabel ? 'label' : 'field'}
|
||||||
@ -97,7 +97,7 @@ class UnThemedLogLabelStats extends PureComponent<Props> {
|
|||||||
<LogLabelStatsRow key="__OTHERS__" count={otherCount} value="Other" proportion={otherProportion} />
|
<LogLabelStatsRow key="__OTHERS__" count={otherCount} value="Other" proportion={otherProportion} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</td>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,6 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
|
|||||||
label: logs-row__match-highlight;
|
label: logs-row__match-highlight;
|
||||||
background: inherit;
|
background: inherit;
|
||||||
padding: inherit;
|
padding: inherit;
|
||||||
|
|
||||||
color: ${theme.colors.yellow};
|
color: ${theme.colors.yellow};
|
||||||
background-color: rgba(${theme.colors.yellow}, 0.1);
|
background-color: rgba(${theme.colors.yellow}, 0.1);
|
||||||
`,
|
`,
|
||||||
@ -119,14 +118,13 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
|
|||||||
`,
|
`,
|
||||||
logsRowCell: css`
|
logsRowCell: css`
|
||||||
label: logs-row-cell;
|
label: logs-row-cell;
|
||||||
display: table-cell;
|
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
padding-right: ${theme.spacing.sm};
|
||||||
`,
|
`,
|
||||||
logsRowToggleDetails: css`
|
logsRowToggleDetails: css`
|
||||||
label: logs-row-toggle-details__level;
|
label: logs-row-toggle-details__level;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 15px;
|
width: 15px;
|
||||||
padding-right: ${theme.spacing.sm};
|
|
||||||
font-size: 9px;
|
font-size: 9px;
|
||||||
`,
|
`,
|
||||||
logsRowLocalTime: css`
|
logsRowLocalTime: css`
|
||||||
@ -153,59 +151,43 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
|
|||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
`,
|
`,
|
||||||
//Log details sepcific CSS
|
//Log details sepcific CSS
|
||||||
logsRowDetailsTable: css`
|
logDetailsContainer: css`
|
||||||
label: logs-row-details-table;
|
label: logs-row-details-table;
|
||||||
display: table;
|
|
||||||
border: 1px solid ${borderColor};
|
border: 1px solid ${borderColor};
|
||||||
|
padding: 0 ${theme.spacing.sm} ${theme.spacing.sm};
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
padding: ${theme.spacing.sm};
|
|
||||||
padding-top: 0;
|
|
||||||
width: 100%;
|
|
||||||
cursor: default;
|
cursor: default;
|
||||||
`,
|
`,
|
||||||
logsRowDetailsSectionTable: css`
|
logDetailsTable: css`
|
||||||
label: logs-row-details-table__section;
|
label: logs-row-details-table;
|
||||||
display: table;
|
|
||||||
table-layout: fixed;
|
|
||||||
margin: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
&:first-of-type {
|
|
||||||
margin-bottom: ${theme.spacing.xs};
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
logsRowDetailsIcon: css`
|
logsDetailsIcon: css`
|
||||||
label: logs-row-details__icon;
|
label: logs-row-details__icon;
|
||||||
display: table-cell;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 22px;
|
|
||||||
padding-right: ${theme.spacing.sm};
|
padding-right: ${theme.spacing.sm};
|
||||||
color: ${theme.colors.gray3};
|
color: ${theme.colors.gray3};
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
logsRowDetailsLabel: css`
|
logDetailsLabel: css`
|
||||||
label: logs-row-details__label;
|
label: logs-row-details__label;
|
||||||
display: table-cell;
|
max-width: 25em;
|
||||||
padding: 0 ${theme.spacing.md} 0 ${theme.spacing.md};
|
min-width: 12em;
|
||||||
width: 14em;
|
padding: 0 ${theme.spacing.sm};
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
`,
|
`,
|
||||||
logsRowDetailsHeading: css`
|
logDetailsHeading: css`
|
||||||
label: logs-row-details__heading;
|
label: logs-row-details__heading;
|
||||||
display: table-caption;
|
|
||||||
margin: ${theme.spacing.sm} 0 ${theme.spacing.xs};
|
|
||||||
font-weight: ${theme.typography.weight.bold};
|
font-weight: ${theme.typography.weight.bold};
|
||||||
|
padding: ${theme.spacing.sm} 0 ${theme.spacing.xs};
|
||||||
`,
|
`,
|
||||||
logsRowDetailsValue: css`
|
logDetailsValue: css`
|
||||||
label: logs-row-details__row;
|
label: logs-row-details__row;
|
||||||
display: table-row;
|
|
||||||
line-height: 2;
|
line-height: 2;
|
||||||
padding: 0 ${theme.spacing.xl} 0 ${theme.spacing.md};
|
padding: ${theme.spacing.sm};
|
||||||
position: relative;
|
position: relative;
|
||||||
|
vertical-align: top;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: ${bgColor};
|
background-color: ${bgColor};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user