Explore/Logs: Update Parsed fields to Detected fields (#28881)

* Change all parsed fields and functions to detected fields

* Update

* Sneak in UI improvement, add tooltip

* Update docs/sources/whatsnew/whats-new-in-v6-5.md

Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>

* Update docs/sources/whatsnew/whats-new-in-v6-5.md

Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>

* Remove changes from old Whats new

* Rename LogMessageParsed

Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>
This commit is contained in:
Ivana Huckova 2020-11-23 20:10:09 +01:00 committed by GitHub
parent 7e21863982
commit 83329ae7e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 117 additions and 101 deletions

View File

@ -39,7 +39,7 @@ The Derived Fields configuration allows you to:
- Add fields parsed from the log message.
- Add a link that uses the value of the field.
You can use this functionality to link to your tracing backend directly from your logs, or link to a user profile page if a userId is present in the log line. These links appear in the [log details]({{< relref "../explore/#labels-and-parsed-fields" >}}).
You can use this functionality to link to your tracing backend directly from your logs, or link to a user profile page if a userId is present in the log line. These links appear in the [log details](/explore/#labels-and-detected-fields).
{{< docs-imagebox img="/img/docs/v65/loki_derived_fields.png" class="docs-image--no-shadow" caption="Screenshot of the derived fields configuration" >}}
Each derived field consists of:

View File

@ -71,11 +71,11 @@ frame.add({ time: 1589189388597, content: 'user registered' });
frame.add({ time: 1589189406480, content: 'user logged in' });
```
## Extract parsed fields from your logs
## Extract detected fields from your logs
You can add additional information about each log line by adding more data frame fields.
If a data frame has more than one text field, then Grafana assumes the first field in the data frame to be the actual log line. Any subsequent text fields are treated as [parsed fields]({{< relref "../../explore/index.md#labels-and-parsed-fields" >}}).
If a data frame has more than one text field, then Grafana assumes the first field in the data frame to be the actual log line. Any subsequent text fields are treated as [detected fields]({{< relref "../../explore/index.md#labels-and-detected-fields" >}}).
While you can add any number of custom fields to your data frame, Grafana comes with a couple of dedicated fields: `levels` and `id`. Let's have a closer look at each one.

View File

@ -210,17 +210,17 @@ Log data can be very repetitive and Explore can help by hiding duplicate log lin
You can change the order of received logs from the default descending order (newest first) to ascending order (oldest first).
### Labels and parsed fields
### Labels and detected fields
Each log row has an extendable area with its labels and parsed fields, for more robust interaction. For all labels we have added the ability to filter for (positive filter) and filter out (negative filter) selected labels. Each field or label also has a stats icon to display ad-hoc statistics in relation to all displayed logs.
Each log row has an extendable area with its labels and detected fields, for more robust interaction. For all labels we have added the ability to filter for (positive filter) and filter out (negative filter) selected labels. Each field or label also has a stats icon to display ad-hoc statistics in relation to all displayed logs.
### Toggle parsed fields
### Toggle detected fields
> **Note:** This feature is only available in Grafana 7.2+.
If your logs are structured in `json` or `logfmt`, then you can show or hide parsed fields. Expand a log line and then click the eye icon to show or hide fields.
If your logs are structured in `json` or `logfmt`, then you can show or hide detected fields. Expand a log line and then click the eye icon to show or hide fields.
{{< docs-imagebox img="/img/docs/explore/parsed-fields-7-2.gif" max-width="800px" caption="Toggling parsed fields in Explore" >}}
{{< docs-imagebox img="/img/docs/explore/parsed-fields-7-2.gif" max-width="800px" caption="Toggling detected fields in Explore" >}}
### Loki-specific features

View File

@ -12,7 +12,6 @@ list = false
This topic includes the release notes for the Grafana v7.2. For all details, read the full [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md).
The main highlights are:
- [**New date formatting options added**]({{< relref "#new=date-formatting-options-added" >}})
@ -108,7 +107,7 @@ With this awesome contribution from one of our community members, you can now to
{{< docs-imagebox img="/img/docs/v72/explore-toggle-parsed-fields.gif" max-width="800px" caption="Toggling parsed fields in Explore" >}}
The [Toggle parsed fields]({{< relref "../explore/index.md#toggle-parsed-fields" >}}) section has been added to [Explore]({{< relref "../explore/index.md" >}}) as a result of this feature.
The [Toggle parsed fields]({{< relref "../explore/index.md#toggle-detected-fields" >}}) section has been added to [Explore]({{< relref "../explore/index.md" >}}) as a result of this feature.
## Sensitive alert channel settings are now encrypted

View File

@ -116,7 +116,7 @@ describe('LogsParsers', () => {
expect(parser.test('foo=bar')).toBeTruthy();
});
test('should return parsed fields', () => {
test('should return detected fields', () => {
expect(
parser.getFields(
'foo=bar baz="42 + 1" msg="[resolver] received A record \\"127.0.0.1\\" for \\"localhost.\\" from udp:192.168.65.1" time(ms)=50 label{foo}=bar'
@ -168,11 +168,11 @@ describe('LogsParsers', () => {
expect(parser.test('{"foo":"bar"}')).toBeTruthy();
});
test('should return parsed fields', () => {
test('should return detected fields', () => {
expect(parser.getFields('{ "foo" : "bar", "baz" : 42 }')).toEqual(['"foo":"bar"', '"baz":42']);
});
test('should return parsed fields for nested quotes', () => {
test('should return detected fields for nested quotes', () => {
expect(parser.getFields(`{"foo":"bar: '[value=\\"42\\"]'"}`)).toEqual([`"foo":"bar: '[value=\\"42\\"]'"`]);
});

View File

@ -38,7 +38,7 @@ describe('LogDetails', () => {
describe('when labels are present', () => {
it('should render heading', () => {
const wrapper = setup(undefined, { labels: { key1: 'label1', key2: 'label2' } });
expect(wrapper.find({ 'aria-label': 'Log Labels' }).hostNodes()).toHaveLength(1);
expect(wrapper.find({ 'aria-label': 'Log labels' }).hostNodes()).toHaveLength(1);
});
it('should render labels', () => {
const wrapper = setup(undefined, { labels: { key1: 'label1', key2: 'label2' } });
@ -56,7 +56,7 @@ describe('LogDetails', () => {
const wrapper = setup(undefined, { entry: 'test=successful' });
expect(wrapper.find({ title: 'Ad-hoc statistics' }).hostNodes()).toHaveLength(1);
});
it('should render parsed fields', () => {
it('should render detected fields', () => {
const wrapper = setup(undefined, { entry: 'test=successful' });
expect(wrapper.text().includes('testsuccessful')).toBe(true);
});
@ -64,10 +64,10 @@ describe('LogDetails', () => {
describe('when row entry have parsable fields and labels are present', () => {
it('should render all headings', () => {
const wrapper = setup(undefined, { entry: 'test=successful', labels: { key: 'label' } });
expect(wrapper.find({ 'aria-label': 'Log Labels' })).toHaveLength(1);
expect(wrapper.find({ 'aria-label': 'Parsed Fields' })).toHaveLength(1);
expect(wrapper.find({ 'aria-label': 'Log labels' })).toHaveLength(1);
expect(wrapper.find({ 'aria-label': 'Detected fields' })).toHaveLength(1);
});
it('should render all labels and parsed fields', () => {
it('should render all labels and detected fields', () => {
const wrapper = setup(undefined, {
entry: 'test=successful',
labels: { key: 'label' },
@ -84,7 +84,7 @@ describe('LogDetails', () => {
it('should not render headings', () => {
const wrapper = setup(undefined, { entry: '' });
expect(wrapper.find({ 'aria-label': 'Log labels' })).toHaveLength(0);
expect(wrapper.find({ 'aria-label': 'Parsed fields' })).toHaveLength(0);
expect(wrapper.find({ 'aria-label': 'Detected fields' })).toHaveLength(0);
});
});

View File

@ -22,6 +22,8 @@ import { getAllFields } from './logParser';
//Components
import { LogDetailsRow } from './LogDetailsRow';
import { Tooltip } from '../Tooltip/Tooltip';
import { Icon } from '../Icon/Icon';
export interface Props extends Themeable {
row: LogRowModel;
@ -34,9 +36,9 @@ export interface Props extends Themeable {
onClickFilterLabel?: (key: string, value: string) => void;
onClickFilterOutLabel?: (key: string, value: string) => void;
getFieldLinks?: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
showParsedFields?: string[];
onClickShowParsedField?: (key: string) => void;
onClickHideParsedField?: (key: string) => void;
showDetectedFields?: string[];
onClickShowDetectedField?: (key: string) => void;
onClickHideDetectedField?: (key: string) => void;
}
const getStyles = stylesFactory((theme: GrafanaTheme) => {
@ -62,7 +64,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
class UnThemedLogDetails extends PureComponent<Props> {
getParser = memoizeOne(getParser);
getStatsForParsedField = (key: string) => {
getStatsForDetectedField = (key: string) => {
const matcher = this.getParser(this.props.row.entry)!.buildMatcher(key);
return calculateFieldStats(this.props.getRows(), matcher);
};
@ -79,9 +81,9 @@ class UnThemedLogDetails extends PureComponent<Props> {
className,
onMouseEnter,
onMouseLeave,
onClickShowParsedField,
onClickHideParsedField,
showParsedFields,
onClickShowDetectedField,
onClickHideDetectedField,
showDetectedFields,
getFieldLinks,
} = this.props;
const style = getLogRowStyles(theme, row.logLevel);
@ -89,7 +91,7 @@ class UnThemedLogDetails extends PureComponent<Props> {
const labels = row.labels ? row.labels : {};
const labelsAvailable = Object.keys(labels).length > 0;
const fields = getAllFields(row, getFieldLinks);
const parsedFieldsAvailable = fields && fields.length > 0;
const detectedFieldsAvailable = fields && fields.length > 0;
// If logs with error, we are not showing the level color
const levelClassName = cx(!hasError && [style.logsRowLevel, styles.logsRowLevelDetails]);
@ -107,8 +109,8 @@ class UnThemedLogDetails extends PureComponent<Props> {
<tbody>
{labelsAvailable && (
<tr>
<td colSpan={5} className={style.logDetailsHeading} aria-label="Log Labels">
Log Labels:
<td colSpan={5} className={style.logDetailsHeading} aria-label="Log labels">
Log labels
</td>
</tr>
)}
@ -129,10 +131,19 @@ class UnThemedLogDetails extends PureComponent<Props> {
);
})}
{parsedFieldsAvailable && (
{detectedFieldsAvailable && (
<tr>
<td colSpan={5} className={style.logDetailsHeading} aria-label="Parsed Fields">
Parsed Fields:
<td colSpan={5} className={style.logDetailsHeading} aria-label="Detected fields">
Detected fields
<Tooltip content="Fields that are parsed from log message and detected by Grafana.">
<Icon
name="question-circle"
size="xs"
className={css`
margin-left: 4px;
`}
/>
</Tooltip>
</td>
</tr>
)}
@ -144,18 +155,18 @@ class UnThemedLogDetails extends PureComponent<Props> {
parsedKey={key}
parsedValue={value}
links={links}
onClickShowParsedField={onClickShowParsedField}
onClickHideParsedField={onClickHideParsedField}
onClickShowDetectedField={onClickShowDetectedField}
onClickHideDetectedField={onClickHideDetectedField}
getStats={() =>
fieldIndex === undefined
? this.getStatsForParsedField(key)
? this.getStatsForDetectedField(key)
: calculateStats(row.dataFrame.fields[fieldIndex].values.toArray())
}
showParsedFields={showParsedFields}
showDetectedFields={showDetectedFields}
/>
);
})}
{!parsedFieldsAvailable && !labelsAvailable && (
{!detectedFieldsAvailable && !labelsAvailable && (
<tr>
<td colSpan={5} aria-label="No details">
No details available

View File

@ -20,9 +20,9 @@ export interface Props extends Themeable {
onClickFilterOutLabel?: (key: string, value: string) => void;
links?: Array<LinkModel<Field>>;
getStats: () => LogLabelStatsModel[] | null;
showParsedFields?: string[];
onClickShowParsedField?: (key: string) => void;
onClickHideParsedField?: (key: string) => void;
showDetectedFields?: string[];
onClickShowDetectedField?: (key: string) => void;
onClickHideDetectedField?: (key: string) => void;
}
interface State {
@ -61,16 +61,16 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
};
showField = () => {
const { onClickShowParsedField, parsedKey } = this.props;
if (onClickShowParsedField) {
onClickShowParsedField(parsedKey);
const { onClickShowDetectedField, parsedKey } = this.props;
if (onClickShowDetectedField) {
onClickShowDetectedField(parsedKey);
}
};
hideField = () => {
const { onClickHideParsedField, parsedKey } = this.props;
if (onClickHideParsedField) {
onClickHideParsedField(parsedKey);
const { onClickHideDetectedField, parsedKey } = this.props;
if (onClickHideDetectedField) {
onClickHideDetectedField(parsedKey);
}
};
@ -107,12 +107,12 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
}
render() {
const { theme, parsedKey, parsedValue, isLabel, links, showParsedFields } = this.props;
const { theme, parsedKey, parsedValue, isLabel, links, showDetectedFields } = this.props;
const { showFieldsStats, fieldStats, fieldCount } = this.state;
const styles = getStyles(theme);
const style = getLogRowStyles(theme);
const toggleFieldButton =
!isLabel && showParsedFields && showParsedFields.includes(parsedKey) ? (
!isLabel && showDetectedFields && showDetectedFields.includes(parsedKey) ? (
<IconButton name="eye" className={styles.showingField} title="Hide this field" onClick={this.hideField} />
) : (
<IconButton name="eye" title="Show this field instead of the message" onClick={this.showField} />
@ -121,7 +121,7 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
return (
<tr className={cx(style.logDetailsValue, { [styles.noHoverBackground]: showFieldsStats })}>
{/* Action buttons - show stats/filter results */}
<td className={style.logsDetailsIcon} colSpan={isLabel ? undefined : 2}>
<td className={style.logsDetailsIcon}>
<IconButton name="signal" title={'Ad-hoc statistics'} onClick={this.showStats} />
</td>
@ -138,7 +138,9 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
{!isLabel && (
<>
<td className={style.logsDetailsIcon}>{toggleFieldButton}</td>
<td className={style.logsDetailsIcon} colSpan={2}>
{toggleFieldButton}
</td>
</>
)}

View File

@ -29,7 +29,7 @@ import { selectThemeVariant } from '../../themes/selectThemeVariant';
//Components
import { LogDetails } from './LogDetails';
import { LogRowMessageParsed } from './LogRowMessageParsed';
import { LogRowMessageDetectedFields } from './LogRowMessageDetectedFields';
import { LogRowMessage } from './LogRowMessage';
import { LogLabels } from './LogLabels';
@ -50,9 +50,9 @@ interface Props extends Themeable {
getRowContext: (row: LogRowModel, options?: RowContextOptions) => Promise<DataQueryResponse>;
getFieldLinks?: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
showContextToggle?: (row?: LogRowModel) => boolean;
showParsedFields?: string[];
onClickShowParsedField?: (key: string) => void;
onClickHideParsedField?: (key: string) => void;
showDetectedFields?: string[];
onClickShowDetectedField?: (key: string) => void;
onClickHideDetectedField?: (key: string) => void;
}
interface State {
@ -149,8 +149,8 @@ class UnThemedLogRow extends PureComponent<Props, State> {
getRows,
onClickFilterLabel,
onClickFilterOutLabel,
onClickShowParsedField,
onClickHideParsedField,
onClickShowDetectedField,
onClickHideDetectedField,
highlighterExpressions,
allowDetails,
row,
@ -158,7 +158,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
showContextToggle,
showLabels,
showTime,
showParsedFields,
showDetectedFields,
wrapLogMessage,
theme,
getFieldLinks,
@ -198,8 +198,12 @@ class UnThemedLogRow extends PureComponent<Props, State> {
<LogLabels labels={row.uniqueLabels} />
</td>
)}
{showParsedFields && showParsedFields.length > 0 ? (
<LogRowMessageParsed row={row} showParsedFields={showParsedFields!} getFieldLinks={getFieldLinks} />
{showDetectedFields && showDetectedFields.length > 0 ? (
<LogRowMessageDetectedFields
row={row}
showDetectedFields={showDetectedFields!}
getFieldLinks={getFieldLinks}
/>
) : (
<LogRowMessage
highlighterExpressions={highlighterExpressions}
@ -225,12 +229,12 @@ class UnThemedLogRow extends PureComponent<Props, State> {
getFieldLinks={getFieldLinks}
onClickFilterLabel={onClickFilterLabel}
onClickFilterOutLabel={onClickFilterOutLabel}
onClickShowParsedField={onClickShowParsedField}
onClickHideParsedField={onClickHideParsedField}
onClickShowDetectedField={onClickShowDetectedField}
onClickHideDetectedField={onClickHideDetectedField}
getRows={getRows}
row={row}
hasError={hasError}
showParsedFields={showParsedFields}
showDetectedFields={showDetectedFields}
/>
)}
</>

View File

@ -8,16 +8,16 @@ import { getAllFields } from './logParser';
export interface Props extends Themeable {
row: LogRowModel;
showParsedFields: string[];
showDetectedFields: string[];
getFieldLinks?: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
}
class UnThemedLogRowMessageParsed extends PureComponent<Props> {
class UnThemedLogRowMessageDetectedFields extends PureComponent<Props> {
render() {
const { row, showParsedFields, getFieldLinks } = this.props;
const { row, showDetectedFields, getFieldLinks } = this.props;
const fields = getAllFields(row, getFieldLinks);
const line = showParsedFields
const line = showDetectedFields
.map(parsedKey => {
const field = fields.find(field => {
const { key } = field;
@ -37,5 +37,5 @@ class UnThemedLogRowMessageParsed extends PureComponent<Props> {
}
}
export const LogRowMessageParsed = withTheme(UnThemedLogRowMessageParsed);
LogRowMessageParsed.displayName = 'LogRowMessageParsed';
export const LogRowMessageDetectedFields = withTheme(UnThemedLogRowMessageDetectedFields);
LogRowMessageDetectedFields.displayName = 'LogRowMessageDetectedFields';

View File

@ -34,9 +34,9 @@ export interface Props extends Themeable {
onClickFilterOutLabel?: (key: string, value: string) => void;
getRowContext?: (row: LogRowModel, options?: RowContextOptions) => Promise<any>;
getFieldLinks?: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
showParsedFields?: string[];
onClickShowParsedField?: (key: string) => void;
onClickHideParsedField?: (key: string) => void;
showDetectedFields?: string[];
onClickShowDetectedField?: (key: string) => void;
onClickHideDetectedField?: (key: string) => void;
}
interface State {
@ -102,9 +102,9 @@ class UnThemedLogRows extends PureComponent<Props, State> {
getFieldLinks,
disableCustomHorizontalScroll,
logsSortOrder,
showParsedFields,
onClickShowParsedField,
onClickHideParsedField,
showDetectedFields,
onClickShowDetectedField,
onClickHideDetectedField,
} = this.props;
const { renderAll } = this.state;
const { logsRowsTable, logsRowsHorizontalScroll } = getLogRowStyles(theme);
@ -146,14 +146,14 @@ class UnThemedLogRows extends PureComponent<Props, State> {
showDuplicates={showDuplicates}
showLabels={showLabels}
showTime={showTime}
showParsedFields={showParsedFields}
showDetectedFields={showDetectedFields}
wrapLogMessage={wrapLogMessage}
timeZone={timeZone}
allowDetails={allowDetails}
onClickFilterLabel={onClickFilterLabel}
onClickFilterOutLabel={onClickFilterOutLabel}
onClickShowParsedField={onClickShowParsedField}
onClickHideParsedField={onClickHideParsedField}
onClickShowDetectedField={onClickShowDetectedField}
onClickHideDetectedField={onClickHideDetectedField}
getFieldLinks={getFieldLinks}
logsSortOrder={logsSortOrder}
/>
@ -170,14 +170,14 @@ class UnThemedLogRows extends PureComponent<Props, State> {
showDuplicates={showDuplicates}
showLabels={showLabels}
showTime={showTime}
showParsedFields={showParsedFields}
showDetectedFields={showDetectedFields}
wrapLogMessage={wrapLogMessage}
timeZone={timeZone}
allowDetails={allowDetails}
onClickFilterLabel={onClickFilterLabel}
onClickFilterOutLabel={onClickFilterOutLabel}
onClickShowParsedField={onClickShowParsedField}
onClickHideParsedField={onClickHideParsedField}
onClickShowDetectedField={onClickShowDetectedField}
onClickHideDetectedField={onClickHideDetectedField}
getFieldLinks={getFieldLinks}
logsSortOrder={logsSortOrder}
/>

View File

@ -49,8 +49,8 @@ const parseMessage = memoizeOne((rowEntry): FieldDef[] => {
return [];
}
// Use parser to highlight detected fields
const parsedFields = parser.getFields(rowEntry);
const fields = parsedFields.map(field => {
const detectedFields = parser.getFields(rowEntry);
const fields = detectedFields.map(field => {
const key = parser.getLabelFromField(field);
const value = parser.getValueFromField(field);
return { key, value };

View File

@ -79,7 +79,7 @@ interface State {
wrapLogMessage: boolean;
logsSortOrder: LogsSortOrder | null;
isFlipping: boolean;
showParsedFields: string[];
showDetectedFields: string[];
}
export class Logs extends PureComponent<Props, State> {
@ -92,7 +92,7 @@ export class Logs extends PureComponent<Props, State> {
wrapLogMessage: store.getBool(SETTINGS_KEYS.wrapLogMessage, true),
logsSortOrder: null,
isFlipping: false,
showParsedFields: [],
showDetectedFields: [],
};
componentWillUnmount() {
@ -174,33 +174,33 @@ export class Logs extends PureComponent<Props, State> {
}
};
showParsedField = (key: string) => {
const index = this.state.showParsedFields.indexOf(key);
showDetectedField = (key: string) => {
const index = this.state.showDetectedFields.indexOf(key);
if (index === -1) {
this.setState(state => {
return {
showParsedFields: state.showParsedFields.concat(key),
showDetectedFields: state.showDetectedFields.concat(key),
};
});
}
};
hideParsedField = (key: string) => {
const index = this.state.showParsedFields.indexOf(key);
hideDetectedField = (key: string) => {
const index = this.state.showDetectedFields.indexOf(key);
if (index > -1) {
this.setState(state => {
return {
showParsedFields: state.showParsedFields.filter(k => key !== k),
showDetectedFields: state.showDetectedFields.filter(k => key !== k),
};
});
}
};
clearParsedFields = () => {
clearDetectedFields = () => {
this.setState(state => {
return {
showParsedFields: [],
showDetectedFields: [],
};
});
};
@ -230,7 +230,7 @@ export class Logs extends PureComponent<Props, State> {
return null;
}
const { showLabels, showTime, wrapLogMessage, logsSortOrder, isFlipping, showParsedFields } = this.state;
const { showLabels, showTime, wrapLogMessage, logsSortOrder, isFlipping, showDetectedFields } = this.state;
const { dedupStrategy } = this.props;
const hasData = logRows && logRows.length > 0;
const dedupCount = dedupedRows
@ -323,18 +323,18 @@ export class Logs extends PureComponent<Props, State> {
/>
)}
{showParsedFields && showParsedFields.length > 0 && (
{showDetectedFields && showDetectedFields.length > 0 && (
<MetaInfoText
metaItems={[
{
label: 'Showing only parsed fields',
value: renderMetaItem(showParsedFields, LogsMetaKind.LabelsMap),
label: 'Showing only detected fields',
value: renderMetaItem(showDetectedFields, LogsMetaKind.LabelsMap),
},
{
label: '',
value: (
<Button variant="secondary" size="sm" onClick={this.clearParsedFields}>
Show all parsed fields
<Button variant="secondary" size="sm" onClick={this.clearDetectedFields}>
Show all detected fields
</Button>
),
},
@ -358,9 +358,9 @@ export class Logs extends PureComponent<Props, State> {
timeZone={timeZone}
getFieldLinks={getFieldLinks}
logsSortOrder={logsSortOrder}
showParsedFields={showParsedFields}
onClickShowParsedField={this.showParsedField}
onClickHideParsedField={this.hideParsedField}
showDetectedFields={showDetectedFields}
onClickShowDetectedField={this.showDetectedField}
onClickHideDetectedField={this.hideDetectedField}
/>
{!loading && !hasData && !scanning && (