mirror of
https://github.com/grafana/grafana.git
synced 2025-02-16 18:34:52 -06:00
* Datasource test: fix describe nesting * Parsing: export handleQuotes function * Modify query: add functions to detect the presence of a label and remove it * Loki: add support to toggle filters if already present * Datasource test: fix describe nesting * Loki: add support to toggle filter out if present * Remove label: handle escaped values * Datasource: add test case for escaped label values * Loki: remove = filter when applying != * Remove selector: add support for Selector node being far from Matcher * Modify query: add unit tests * Elasticsearch: create modifyQuery for elastic * Elastic modify query: implement functions * Elasticsearch: implement modifyQuery functions in datasource * Elasticsearch: update datasource test * Loki modify query: check for streamSelectorPositions length * Elasticsearch query has filter: escape filter value in regex * Remove unused type * Modify query: add functions to detect the presence of a label and remove it * Remove label: handle escaped values * Logs: create props to check for label filters in the query * Log Details Row: use label state props to show visual feedback * Make isCallbacks async * Explore: add placeholder for checking for filter in query * Datasource: define new API method * Inspect query: add base implementation * Remove isFilterOutLabelActive as it will not be needed * Check for "isActive" on every render Otherwise the active state will be out of sync * Elasticsearch: implement inspectQuery in the datasource * Logs: update test * Log details: update test * Datasources: update tests * Inspect query: rename to analize query to prevent confusion * Datasource types: mark method as alpha * Explore: add comment to log-specific functions * Remove duplicated code from bad rebase * Remove label filter: check node type * getMatchersWithFilter: rename argument * Fix bad rebase * Create DataSourceWithQueryManipulationSupport interface * Implement type guard for DataSourceWithQueryManipulationSupport * DataSourceWithQueryManipulationSupport: move to logs module * hasQueryManipulationSupport: change implementation `modifyQuery` comes from the prototype. * DataSourceWithQueryManipulationSupport: expand code comments * AnalyzeQueryOptions: move to logs module * DataSourceWithQueryManipulationSupport: add support for more return types * Fix merge error * Update packages/grafana-data/src/types/logs.ts Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com> * DatasourceAPI: deprecate modifyQuery * Explore: refactor isFilterLabelActive * DataSourceWithQueryModificationSupport: rename interface * Split interfaces into Analyze and Modify * Query analysis: better name for interface * Fix guard * Create feature flag for active state * Use new feature flag in Explore * DataSourceToggleableQueryFiltersSupport: create a specific interface for this feature * Rename feature flag * De-deprecate modifyQuery * DataSourceToggleableQueryFiltersSupport: Rethink types and methods * Explore: adjust modifyQuery and isFilterLabelActive to new methods * Loki: implement new interface and revert modifyQuery * DataSourceToggleableQueryFiltersSupport: better name for arguments * Elasticsearch: implement new interface and revert modifyQuery * Loki: better name for arguments * Explore: document current limitation on isFilterLabelActive * Explore: place toggleable filters under feature flag * Loki: add tests for the new methods * Loki: add legacy modifyQuery tests * Elasticsearch: add tests for the new methods * Elasticsearch: add legacy modifyQuery tests * Toggle filter action: improve type values * Logs types: update interface description * DataSourceWithToggleableQueryFiltersSupport: update interface name * Update feature flag description * Explore: add todo comment for isFilterLabelActive --------- Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>
197 lines
7.8 KiB
TypeScript
197 lines
7.8 KiB
TypeScript
import { cx } from '@emotion/css';
|
|
import React, { PureComponent } from 'react';
|
|
|
|
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, LogRowStyles } from './getLogRowStyles';
|
|
import { getAllFields, createLogLineLinks } from './logParser';
|
|
|
|
export interface Props extends Themeable2 {
|
|
row: LogRowModel;
|
|
showDuplicates: boolean;
|
|
getRows: () => LogRowModel[];
|
|
wrapLogMessage: boolean;
|
|
className?: string;
|
|
hasError?: boolean;
|
|
app?: CoreApp;
|
|
styles: LogRowStyles;
|
|
|
|
onClickFilterLabel?: (key: string, value: string) => void;
|
|
onClickFilterOutLabel?: (key: string, value: string) => void;
|
|
getFieldLinks?: (field: Field, rowIndex: number, dataFrame: DataFrame) => Array<LinkModel<Field>>;
|
|
displayedFields?: string[];
|
|
onClickShowField?: (key: string) => void;
|
|
onClickHideField?: (key: string) => void;
|
|
isFilterLabelActive?: (key: string, value: string) => Promise<boolean>;
|
|
}
|
|
|
|
class UnThemedLogDetails extends PureComponent<Props> {
|
|
render() {
|
|
const {
|
|
app,
|
|
row,
|
|
theme,
|
|
hasError,
|
|
onClickFilterOutLabel,
|
|
onClickFilterLabel,
|
|
getRows,
|
|
showDuplicates,
|
|
className,
|
|
onClickShowField,
|
|
onClickHideField,
|
|
displayedFields,
|
|
getFieldLinks,
|
|
wrapLogMessage,
|
|
styles,
|
|
} = this.props;
|
|
const levelStyles = getLogLevelStyles(theme, row.logLevel);
|
|
const labels = row.labels ? row.labels : {};
|
|
const labelsAvailable = Object.keys(labels).length > 0;
|
|
const fieldsAndLinks = getAllFields(row, getFieldLinks);
|
|
let fieldsWithLinks = fieldsAndLinks.filter((f) => f.links?.length);
|
|
const displayedFieldsWithLinks = fieldsWithLinks.filter((f) => f.fieldIndex !== row.entryFieldIndex).sort();
|
|
const hiddenFieldsWithLinks = fieldsWithLinks.filter((f) => f.fieldIndex === row.entryFieldIndex).sort();
|
|
const fieldsWithLinksFromVariableMap = createLogLineLinks(hiddenFieldsWithLinks);
|
|
|
|
// do not show the log message unless there is a link attached
|
|
const fields = fieldsAndLinks.filter((f) => f.links?.length === 0 && f.fieldIndex !== row.entryFieldIndex).sort();
|
|
const fieldsAvailable = fields && fields.length > 0;
|
|
const fieldsWithLinksAvailable =
|
|
(displayedFieldsWithLinks && displayedFieldsWithLinks.length > 0) ||
|
|
(fieldsWithLinksFromVariableMap && fieldsWithLinksFromVariableMap.length > 0);
|
|
|
|
// If logs with error, we are not showing the level color
|
|
const levelClassName = hasError
|
|
? ''
|
|
: `${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={styles.logDetailsContainer}>
|
|
<table className={styles.logDetailsTable}>
|
|
<tbody>
|
|
{(labelsAvailable || fieldsAvailable) && (
|
|
<tr>
|
|
<td colSpan={100} className={styles.logDetailsHeading} aria-label="Fields">
|
|
Fields
|
|
</td>
|
|
</tr>
|
|
)}
|
|
{Object.keys(labels)
|
|
.sort()
|
|
.map((key, i) => {
|
|
const value = labels[key];
|
|
return (
|
|
<LogDetailsRow
|
|
key={`${key}=${value}-${i}`}
|
|
parsedKeys={[key]}
|
|
parsedValues={[value]}
|
|
isLabel={true}
|
|
getStats={() => calculateLogsLabelStats(getRows(), key)}
|
|
onClickFilterOutLabel={onClickFilterOutLabel}
|
|
onClickFilterLabel={onClickFilterLabel}
|
|
onClickShowField={onClickShowField}
|
|
onClickHideField={onClickHideField}
|
|
row={row}
|
|
app={app}
|
|
wrapLogMessage={wrapLogMessage}
|
|
displayedFields={displayedFields}
|
|
disableActions={false}
|
|
isFilterLabelActive={this.props.isFilterLabelActive}
|
|
/>
|
|
);
|
|
})}
|
|
{fields.map((field, i) => {
|
|
const { keys, values, fieldIndex } = field;
|
|
return (
|
|
<LogDetailsRow
|
|
key={`${keys[0]}=${values[0]}-${i}`}
|
|
parsedKeys={keys}
|
|
parsedValues={values}
|
|
onClickShowField={onClickShowField}
|
|
onClickHideField={onClickHideField}
|
|
onClickFilterOutLabel={onClickFilterOutLabel}
|
|
onClickFilterLabel={onClickFilterLabel}
|
|
getStats={() => calculateStats(row.dataFrame.fields[fieldIndex].values)}
|
|
displayedFields={displayedFields}
|
|
wrapLogMessage={wrapLogMessage}
|
|
row={row}
|
|
app={app}
|
|
disableActions={false}
|
|
isFilterLabelActive={this.props.isFilterLabelActive}
|
|
/>
|
|
);
|
|
})}
|
|
|
|
{fieldsWithLinksAvailable && (
|
|
<tr>
|
|
<td colSpan={100} className={styles.logDetailsHeading} aria-label="Data Links">
|
|
Links
|
|
</td>
|
|
</tr>
|
|
)}
|
|
{displayedFieldsWithLinks.map((field, i) => {
|
|
const { keys, values, links, fieldIndex } = field;
|
|
return (
|
|
<LogDetailsRow
|
|
key={`${keys[0]}=${values[0]}-${i}`}
|
|
parsedKeys={keys}
|
|
parsedValues={values}
|
|
links={links}
|
|
onClickShowField={onClickShowField}
|
|
onClickHideField={onClickHideField}
|
|
getStats={() => calculateStats(row.dataFrame.fields[fieldIndex].values)}
|
|
displayedFields={displayedFields}
|
|
wrapLogMessage={wrapLogMessage}
|
|
row={row}
|
|
app={app}
|
|
disableActions={false}
|
|
/>
|
|
);
|
|
})}
|
|
{fieldsWithLinksFromVariableMap?.map((field, i) => {
|
|
const { keys, values, links, fieldIndex } = field;
|
|
return (
|
|
<LogDetailsRow
|
|
key={`${keys[0]}=${values[0]}-${i}`}
|
|
parsedKeys={keys}
|
|
parsedValues={values}
|
|
links={links}
|
|
onClickShowField={onClickShowField}
|
|
onClickHideField={onClickHideField}
|
|
getStats={() => calculateStats(row.dataFrame.fields[fieldIndex].values)}
|
|
displayedFields={displayedFields}
|
|
wrapLogMessage={wrapLogMessage}
|
|
row={row}
|
|
app={app}
|
|
disableActions={true}
|
|
/>
|
|
);
|
|
})}
|
|
|
|
{!fieldsAvailable && !labelsAvailable && !fieldsWithLinksAvailable && (
|
|
<tr>
|
|
<td colSpan={100} aria-label="No details">
|
|
No details available
|
|
</td>
|
|
</tr>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
);
|
|
}
|
|
}
|
|
|
|
export const LogDetails = withTheme2(UnThemedLogDetails);
|
|
LogDetails.displayName = 'LogDetails';
|