mirror of https://github.com/grafana/grafana.git
LogsView + TraceView: Add time range to resource attributes extension range (#111171)
This commit is contained in:
parent
3cc2fb3728
commit
2669e0a770
|
@ -6,7 +6,7 @@ import { ScopedVars } from './ScopedVars';
|
|||
import { DataSourcePluginMeta, DataSourceSettings } from './datasource';
|
||||
import { IconName } from './icon';
|
||||
import { PanelData } from './panel';
|
||||
import { RawTimeRange, TimeZone } from './time';
|
||||
import { AbsoluteTimeRange, RawTimeRange, TimeZone } from './time';
|
||||
|
||||
// Plugin Extensions types
|
||||
// ---------------------------------------
|
||||
|
@ -273,6 +273,7 @@ export type PluginExtensionResourceAttributesContext = {
|
|||
// Key-value pairs of resource attributes, attribute name is the key
|
||||
attributes: Record<string, string[]>;
|
||||
spanAttributes?: Record<string, string[]>;
|
||||
timeRange: AbsoluteTimeRange;
|
||||
datasource: {
|
||||
type: string;
|
||||
uid: string;
|
||||
|
|
|
@ -1056,6 +1056,7 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
|||
logOptionsStorageKey={SETTING_KEY_ROOT}
|
||||
onLogOptionsChange={onLogOptionsChange}
|
||||
filterLevels={filterLevels}
|
||||
timeRange={props.range}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
@ -1113,6 +1114,7 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
|||
onPinLine={onPinToContentOutlineClick}
|
||||
pinLineButtonTooltipTitle={pinLineButtonTooltipTitle}
|
||||
renderPreview
|
||||
timeRange={props.range}
|
||||
/>
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
|
|
|
@ -138,6 +138,7 @@ export function LogsSamplePanel(props: Props) {
|
|||
timeZone={timeZone}
|
||||
enableLogDetails
|
||||
scrollElement={null}
|
||||
timeRange={props.timeRange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@ import userEvent from '@testing-library/user-event';
|
|||
import { createRef } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { DataFrame, MutableDataFrame, TimeRange } from '@grafana/data';
|
||||
import { DataFrame, MutableDataFrame } from '@grafana/data';
|
||||
import { mockTimeRange } from '@grafana/plugin-ui';
|
||||
import { DataSourceSrv, setDataSourceSrv, setPluginLinksHook, setPluginComponentsHook } from '@grafana/runtime';
|
||||
|
||||
import { configureStore } from '../../../store/configureStore';
|
||||
|
@ -24,7 +25,7 @@ function getTraceView(frames: DataFrame[]) {
|
|||
traceProp={transformDataFrames(frames[0])!}
|
||||
datasource={undefined}
|
||||
topOfViewRef={topOfViewRef}
|
||||
timeRange={{} as TimeRange}
|
||||
timeRange={mockTimeRange()}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
|
|
|
@ -17,7 +17,7 @@ jest.mock('../utils');
|
|||
import { act, render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { createDataFrame, DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { createDataFrame, DataSourceInstanceSettings, dateTime } from '@grafana/data';
|
||||
import { data } from '@grafana/flamegraph';
|
||||
import { DataSourceSrv, setDataSourceSrv, setPluginLinksHook } from '@grafana/runtime';
|
||||
|
||||
|
@ -71,6 +71,8 @@ describe('<SpanDetail>', () => {
|
|||
traceFlameGraphs: { [span.spanID]: createDataFrame(data) },
|
||||
setRedrawListView: jest.fn(),
|
||||
timeRange: {
|
||||
from: dateTime(0),
|
||||
to: dateTime(1000000000000),
|
||||
raw: {
|
||||
from: 0,
|
||||
to: 1000000000000,
|
||||
|
@ -273,6 +275,10 @@ describe('<SpanDetail>', () => {
|
|||
attributes: expect.objectContaining({
|
||||
'http.url': expect.arrayContaining([expect.any(String)]),
|
||||
}),
|
||||
timeRange: {
|
||||
from: 0,
|
||||
to: 1000000000000,
|
||||
},
|
||||
datasource: {
|
||||
type: 'tempo',
|
||||
uid: 'grafanacloud-traces',
|
||||
|
|
|
@ -51,12 +51,19 @@ import { ShareSpanButton } from './ShareSpanButton';
|
|||
import { getSpanDetailLinkButtons } from './SpanDetailLinkButtons';
|
||||
import SpanFlameGraph from './SpanFlameGraph';
|
||||
|
||||
const useResourceAttributesExtensionLinks = (
|
||||
process: TraceProcess,
|
||||
spanTags: TraceKeyValuePair[],
|
||||
datasourceType: string,
|
||||
datasourceUid: string
|
||||
) => {
|
||||
const useResourceAttributesExtensionLinks = ({
|
||||
process,
|
||||
spanTags,
|
||||
datasourceType,
|
||||
datasourceUid,
|
||||
timeRange,
|
||||
}: {
|
||||
process: TraceProcess;
|
||||
spanTags: TraceKeyValuePair[];
|
||||
datasourceType: string;
|
||||
datasourceUid: string;
|
||||
timeRange: TimeRange;
|
||||
}) => {
|
||||
// Stable context for useMemo inside usePluginLinks
|
||||
const context: PluginExtensionResourceAttributesContext = useMemo(() => {
|
||||
const attributes = (process.tags ?? []).reduce<Record<string, string[]>>((acc, tag) => {
|
||||
|
@ -80,12 +87,13 @@ const useResourceAttributesExtensionLinks = (
|
|||
return {
|
||||
attributes,
|
||||
spanAttributes,
|
||||
timeRange: { from: timeRange.from.valueOf(), to: timeRange.to.valueOf() },
|
||||
datasource: {
|
||||
type: datasourceType,
|
||||
uid: datasourceUid,
|
||||
},
|
||||
};
|
||||
}, [process.tags, spanTags, datasourceType, datasourceUid]);
|
||||
}, [process.tags, spanTags, datasourceType, datasourceUid, timeRange]);
|
||||
|
||||
const { links } = usePluginLinks({
|
||||
extensionPointId: PluginExtensionPoints.TraceViewResourceAttributes,
|
||||
|
@ -343,7 +351,13 @@ export default function SpanDetail(props: SpanDetailProps) {
|
|||
});
|
||||
|
||||
const focusSpanLink = createFocusSpanLink(traceID, spanID);
|
||||
const resourceLinksGetter = useResourceAttributesExtensionLinks(process, tags, datasourceType, datasourceUid);
|
||||
const resourceLinksGetter = useResourceAttributesExtensionLinks({
|
||||
process,
|
||||
spanTags: tags,
|
||||
datasourceType,
|
||||
datasourceUid,
|
||||
timeRange,
|
||||
});
|
||||
|
||||
return (
|
||||
<div data-testid="span-detail-component">
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import { createTheme } from '@grafana/data';
|
||||
import { createTheme, dateTime } from '@grafana/data';
|
||||
import { setPluginLinksHook } from '@grafana/runtime';
|
||||
|
||||
import DetailState from './SpanDetail/DetailState';
|
||||
|
@ -48,6 +48,8 @@ const setup = (propOverrides?: SpanDetailRowProps) => {
|
|||
theme: createTheme(),
|
||||
traceFlameGraphs: {},
|
||||
timeRange: {
|
||||
from: dateTime(0),
|
||||
to: dateTime(1000000000000),
|
||||
raw: {
|
||||
from: 0,
|
||||
to: 1000000000000,
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
DataFrameType,
|
||||
CoreApp,
|
||||
PluginExtensionPoints,
|
||||
dateTime,
|
||||
} from '@grafana/data';
|
||||
import { setPluginLinksHook } from '@grafana/runtime';
|
||||
|
||||
|
@ -43,6 +44,14 @@ const setup = (propOverrides?: Partial<Props>, rowOverrides?: Partial<LogRowMode
|
|||
theme,
|
||||
styles,
|
||||
app: CoreApp.Explore,
|
||||
timeRange: {
|
||||
from: dateTime(1757937009041),
|
||||
to: dateTime(1757940609041),
|
||||
raw: {
|
||||
from: 'now-1h',
|
||||
to: 'now',
|
||||
},
|
||||
},
|
||||
...(propOverrides || {}),
|
||||
};
|
||||
|
||||
|
@ -325,6 +334,10 @@ describe('LogDetails', () => {
|
|||
type: 'loki',
|
||||
uid: 'grafanacloud-logs',
|
||||
},
|
||||
timeRange: {
|
||||
from: 1757937009041,
|
||||
to: 1757940609041,
|
||||
},
|
||||
attributes: { key1: ['label1'], key2: ['label2'] },
|
||||
},
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@ import { cx } from '@emotion/css';
|
|||
import { PureComponent, useMemo } from 'react';
|
||||
|
||||
import {
|
||||
TimeRange,
|
||||
CoreApp,
|
||||
DataFrame,
|
||||
DataFrameType,
|
||||
|
@ -44,23 +45,25 @@ export interface Props extends Themeable2 {
|
|||
onPinLine?: (row: LogRowModel) => void;
|
||||
pinLineButtonTooltipTitle?: PopoverContent;
|
||||
links?: Record<string, LinkModel[]>;
|
||||
timeRange: TimeRange;
|
||||
}
|
||||
|
||||
interface LinkModelWithIcon extends LinkModel {
|
||||
icon?: IconName;
|
||||
}
|
||||
|
||||
export const useAttributesExtensionLinks = (row: LogRowModel) => {
|
||||
export const useAttributesExtensionLinks = (row: LogRowModel, timeRange: TimeRange) => {
|
||||
// Stable context for useMemo inside usePluginLinks
|
||||
const context: PluginExtensionResourceAttributesContext = useMemo(() => {
|
||||
return {
|
||||
attributes: Object.fromEntries(Object.entries(row.labels).map(([key, value]) => [key, [value]])),
|
||||
timeRange: { from: timeRange.from.valueOf(), to: timeRange.to.valueOf() },
|
||||
datasource: {
|
||||
type: row.datasourceType ?? '',
|
||||
uid: row.datasourceUid ?? '',
|
||||
},
|
||||
};
|
||||
}, [row.labels, row.datasourceType, row.datasourceUid]);
|
||||
}, [row.labels, row.datasourceType, row.datasourceUid, timeRange]);
|
||||
|
||||
const { links } = usePluginLinks({
|
||||
extensionPointId: PluginExtensionPoints.LogsViewResourceAttributes,
|
||||
|
@ -93,7 +96,7 @@ export const useAttributesExtensionLinks = (row: LogRowModel) => {
|
|||
|
||||
const withAttributesExtensionLinks = (Component: React.ComponentType<Props>) => {
|
||||
function ComponentWithLinks(props: Props) {
|
||||
const labelLinks = useAttributesExtensionLinks(props.row);
|
||||
const labelLinks = useAttributesExtensionLinks(props.row, props.timeRange);
|
||||
return <Component {...props} links={labelLinks} />;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { ComponentProps } from 'react';
|
|||
import tinycolor from 'tinycolor2';
|
||||
|
||||
import { CoreApp, createTheme, LogLevel, LogRowModel } from '@grafana/data';
|
||||
import { mockTimeRange } from '@grafana/plugin-ui';
|
||||
|
||||
import { LogRow } from './LogRow';
|
||||
import { getLogRowStyles } from './getLogRowStyles';
|
||||
|
@ -40,6 +41,7 @@ const setup = (propOverrides?: Partial<ComponentProps<typeof LogRow>>, rowOverri
|
|||
wrapLogMessage: false,
|
||||
timeZone: 'utc',
|
||||
styles,
|
||||
timeRange: mockTimeRange(),
|
||||
...(propOverrides || {}),
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
import { debounce } from 'lodash';
|
||||
import { MouseEvent, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { CoreApp, DataFrame, dateTimeFormat, LogRowContextOptions, LogRowModel, LogsSortOrder } from '@grafana/data';
|
||||
import {
|
||||
CoreApp,
|
||||
DataFrame,
|
||||
dateTimeFormat,
|
||||
LogRowContextOptions,
|
||||
LogRowModel,
|
||||
LogsSortOrder,
|
||||
TimeRange,
|
||||
} from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { DataQuery, TimeZone } from '@grafana/schema';
|
||||
|
@ -56,6 +64,7 @@ export interface Props {
|
|||
handleTextSelection?: (e: MouseEvent<HTMLTableRowElement>, row: LogRowModel) => boolean;
|
||||
logRowMenuIconsBefore?: ReactNode[];
|
||||
logRowMenuIconsAfter?: ReactNode[];
|
||||
timeRange: TimeRange;
|
||||
}
|
||||
|
||||
export const LogRow = ({
|
||||
|
@ -314,6 +323,7 @@ export const LogRow = ({
|
|||
styles={styles}
|
||||
isFilterLabelActive={props.isFilterLabelActive}
|
||||
pinLineButtonTooltipTitle={props.pinLineButtonTooltipTitle}
|
||||
timeRange={props.timeRange}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react';
|
|||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { LogRowModel, LogsDedupStrategy, LogsSortOrder } from '@grafana/data';
|
||||
import { mockTimeRange } from '@grafana/plugin-ui';
|
||||
|
||||
import { disablePopoverMenu, enablePopoverMenu, isPopoverMenuDisabled } from '../utils';
|
||||
|
||||
|
@ -46,6 +47,7 @@ describe('LogRows', () => {
|
|||
onClickHideField={() => {}}
|
||||
onClickShowField={() => {}}
|
||||
scrollElement={null}
|
||||
timeRange={mockTimeRange()}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -75,6 +77,7 @@ describe('LogRows', () => {
|
|||
onClickHideField={() => {}}
|
||||
onClickShowField={() => {}}
|
||||
scrollElement={null}
|
||||
timeRange={mockTimeRange()}
|
||||
/>
|
||||
);
|
||||
expect(screen.queryAllByRole('row')).toHaveLength(2);
|
||||
|
@ -105,6 +108,7 @@ describe('LogRows', () => {
|
|||
onClickHideField={() => {}}
|
||||
onClickShowField={() => {}}
|
||||
scrollElement={null}
|
||||
timeRange={mockTimeRange()}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -135,6 +139,7 @@ describe('LogRows', () => {
|
|||
onClickHideField={() => {}}
|
||||
onClickShowField={() => {}}
|
||||
scrollElement={null}
|
||||
timeRange={mockTimeRange()}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -162,6 +167,7 @@ describe('Popover menu', () => {
|
|||
onClickFilterOutString={() => {}}
|
||||
onClickFilterString={() => {}}
|
||||
scrollElement={null}
|
||||
timeRange={mockTimeRange()}
|
||||
{...overrides}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
CoreApp,
|
||||
DataFrame,
|
||||
LogRowContextOptions,
|
||||
TimeRange,
|
||||
} from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { config } from '@grafana/runtime';
|
||||
|
@ -61,6 +62,7 @@ export interface Props {
|
|||
scrollIntoView?: (element: HTMLElement) => void;
|
||||
isFilterLabelActive?: (key: string, value: string, refId?: string) => Promise<boolean>;
|
||||
pinnedLogs?: string[];
|
||||
timeRange: TimeRange;
|
||||
/**
|
||||
* If false or undefined, the `contain:strict` css property will be added to the wrapping `<table>` for performance reasons.
|
||||
* Any overflowing content will be clipped at the table boundary.
|
||||
|
|
|
@ -492,6 +492,7 @@ export const LogRowContextModal: React.FunctionComponent<LogRowContextModalProps
|
|||
|
||||
const loadingStateAbove = context.above.loadingState;
|
||||
const loadingStateBelow = context.below.loadingState;
|
||||
const timeRange = getFullTimeRange();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
@ -552,6 +553,7 @@ export const LogRowContextModal: React.FunctionComponent<LogRowContextModalProps
|
|||
onClickShowField={showField}
|
||||
onClickHideField={hideField}
|
||||
scrollElement={null}
|
||||
timeRange={timeRange}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -575,6 +577,7 @@ export const LogRowContextModal: React.FunctionComponent<LogRowContextModalProps
|
|||
pinnedLogs={sticky ? [row.uid] : undefined}
|
||||
overflowingContent={true}
|
||||
scrollElement={null}
|
||||
timeRange={timeRange}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -594,6 +597,7 @@ export const LogRowContextModal: React.FunctionComponent<LogRowContextModalProps
|
|||
onClickShowField={showField}
|
||||
onClickHideField={hideField}
|
||||
scrollElement={null}
|
||||
timeRange={timeRange}
|
||||
/>
|
||||
</>
|
||||
</td>
|
||||
|
@ -637,7 +641,7 @@ export const LogRowContextModal: React.FunctionComponent<LogRowContextModalProps
|
|||
dispatch(
|
||||
splitOpen({
|
||||
queries: [contextQuery],
|
||||
range: getFullTimeRange(),
|
||||
range: timeRange,
|
||||
datasourceUid: contextQuery.datasource!.uid!,
|
||||
panelsState: {
|
||||
logs: {
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
LogsSortOrder,
|
||||
DataFrame,
|
||||
ScopedVars,
|
||||
dateTime,
|
||||
getDefaultTimeRange,
|
||||
} from '@grafana/data';
|
||||
import { setPluginLinksHook } from '@grafana/runtime';
|
||||
|
@ -64,7 +65,14 @@ const setup = (
|
|||
containerElement: document.createElement('div'),
|
||||
focusLogLine: jest.fn(),
|
||||
logs,
|
||||
timeRange: getDefaultTimeRange(),
|
||||
timeRange: {
|
||||
from: dateTime(1757937009041),
|
||||
to: dateTime(1757940609041),
|
||||
raw: {
|
||||
from: 'now-1h',
|
||||
to: 'now',
|
||||
},
|
||||
},
|
||||
timeZone: 'browser',
|
||||
showControls: true,
|
||||
...(propOverrides || {}),
|
||||
|
@ -372,6 +380,10 @@ describe('LogLineDetails', () => {
|
|||
type: 'loki',
|
||||
uid: 'grafanacloud-logs',
|
||||
},
|
||||
timeRange: {
|
||||
from: 1757937009041,
|
||||
to: 1757940609041,
|
||||
},
|
||||
attributes: { key1: ['label1'], key2: ['label2'] },
|
||||
},
|
||||
});
|
||||
|
|
|
@ -37,7 +37,7 @@ export const LogLineDetailsComponent = memo(
|
|||
const inputRef = useRef('');
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const extensionLinks = useAttributesExtensionLinks(log);
|
||||
const extensionLinks = useAttributesExtensionLinks(log, timeRange);
|
||||
|
||||
const fieldsWithLinks = useMemo(() => {
|
||||
const fieldsWithLinks = log.fields.filter((f) => f.links?.length);
|
||||
|
|
|
@ -674,6 +674,7 @@ export const LogsPanel = ({
|
|||
logRowMenuIconsAfter={isReactNodeArray(logRowMenuIconsAfter) ? logRowMenuIconsAfter : undefined}
|
||||
// Ascending order causes scroll to stick to the bottom, so previewing is futile
|
||||
renderPreview={isAscending ? false : true}
|
||||
timeRange={data.timeRange}
|
||||
/>
|
||||
</InfiniteScroll>
|
||||
{showCommonLabels && isAscending && renderCommonLabels()}
|
||||
|
@ -727,6 +728,7 @@ export const LogsPanel = ({
|
|||
logOptionsStorageKey={controlsStorageKey}
|
||||
// Ascending order causes scroll to stick to the bottom, so previewing is futile
|
||||
renderPreview={isAscending ? false : true}
|
||||
timeRange={data.timeRange}
|
||||
/>
|
||||
{showCommonLabels && isAscending && renderCommonLabels()}
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue