TraceView: Add support for plugin components in trace view header (#110524)

* Add support for plugin components in trace view header

* Fix lint error

* Remove as

* Fix tests

* Prettier

* better eslint
This commit is contained in:
Joey
2025-09-04 09:58:21 +01:00
committed by GitHub
parent 9272a1c19a
commit 600137919e
4 changed files with 46 additions and 21 deletions
+1 -2
View File
@@ -2662,8 +2662,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "1"]
],
"public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx:5381": [
[0, 0, 0, "React Hook \\"useMemo\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "0"],
[0, 0, 0, "React Hook \\"usePluginLinks\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "1"]
[0, 0, 0, "React Hook \\"useMemo\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "0"]
],
"public/app/features/explore/TraceView/components/TracePageHeader/index.tsx:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./TracePageHeader\`)", "0"]
@@ -4,7 +4,7 @@ import { createRef } from 'react';
import { Provider } from 'react-redux';
import { DataFrame, MutableDataFrame, TimeRange } from '@grafana/data';
import { DataSourceSrv, setDataSourceSrv, setPluginLinksHook } from '@grafana/runtime';
import { DataSourceSrv, setDataSourceSrv, setPluginLinksHook, setPluginComponentsHook } from '@grafana/runtime';
import { configureStore } from '../../../store/configureStore';
@@ -52,6 +52,11 @@ describe('TraceView', () => {
links: [],
}));
setPluginComponentsHook(() => ({
isLoading: false,
components: [],
}));
setDataSourceSrv({
getInstanceSettings() {
return undefined;
@@ -22,7 +22,7 @@ import {
PluginExtensionPoints,
PluginExtensionTypes,
} from '@grafana/data';
import { usePluginLinks } from '@grafana/runtime';
import { usePluginLinks, usePluginComponents } from '@grafana/runtime';
import { DEFAULT_SPAN_FILTERS } from 'app/features/explore/state/constants';
import { TraceViewPluginExtensionContext } from '../types/trace';
@@ -34,6 +34,7 @@ import { trace } from './mocks';
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
usePluginLinks: jest.fn(),
usePluginComponents: jest.fn(),
reportInteraction: jest.fn(),
}));
@@ -92,6 +93,9 @@ const setup = (pluginLinks: { links: PluginExtensionLink[]; isLoading: boolean }
const mockUsePluginLinks = usePluginLinks as jest.MockedFunction<typeof usePluginLinks>;
mockUsePluginLinks.mockReturnValue(pluginLinks);
const mockUsePluginComponents = usePluginComponents as jest.MockedFunction<typeof usePluginComponents>;
mockUsePluginComponents.mockReturnValue({ components: [], isLoading: false });
const defaultProps = {
trace,
timeZone: '',
@@ -115,6 +119,7 @@ const setup = (pluginLinks: { links: PluginExtensionLink[]; isLoading: boolean }
return {
...render(<TracePageHeader {...defaultProps} />),
mockUsePluginLinks,
mockUsePluginComponents,
};
};
@@ -26,7 +26,7 @@ import {
PluginExtensionPoints,
} from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { reportInteraction, usePluginLinks } from '@grafana/runtime';
import { reportInteraction, renderLimitedComponents, usePluginComponents, usePluginLinks } from '@grafana/runtime';
import { TimeZone } from '@grafana/schema';
import {
Badge,
@@ -95,6 +95,28 @@ export const TracePageHeader = memo((props: TracePageHeaderProps) => {
setHeaderHeight(document.querySelector('.' + styles.header)?.scrollHeight ?? 0);
}, [setHeaderHeight, showSpanFilters, styles.header]);
// Build context for plugin extensions if trace is available
const traceContext: TraceViewPluginExtensionContext | undefined = trace
? {
...trace,
datasource: {
name: datasourceName,
uid: datasourceUid,
type: datasourceType,
},
}
: undefined;
const { links: extensionLinks } = usePluginLinks({
extensionPointId: PluginExtensionPoints.TraceViewHeaderActions,
context: traceContext,
limitPerPlugin: 2,
});
const { components: extensionComponents } = usePluginComponents<TraceViewPluginExtensionContext>({
extensionPointId: PluginExtensionPoints.TraceViewHeaderActions,
});
if (!trace) {
return null;
}
@@ -110,22 +132,6 @@ export const TracePageHeader = memo((props: TracePageHeaderProps) => {
return new Set(trace.spans.map((span) => span.process?.serviceName)).size;
}, [trace.spans]);
// Get plugin extensions for trace view header actions
const traceContext: TraceViewPluginExtensionContext = {
...trace,
datasource: {
name: datasourceName,
uid: datasourceUid,
type: datasourceType,
},
};
const { links: extensionLinks } = usePluginLinks({
extensionPointId: PluginExtensionPoints.TraceViewHeaderActions,
context: traceContext,
limitPerPlugin: 2,
});
let statusColor: BadgeColor = 'green';
if (status && status.length > 0) {
if (status[0].value.toString().charAt(0) === '4') {
@@ -212,6 +218,16 @@ export const TracePageHeader = memo((props: TracePageHeaderProps) => {
</div>
)}
<div className={styles.actions}>
{traceContext
? renderLimitedComponents<TraceViewPluginExtensionContext>({
props: traceContext,
components: extensionComponents,
limit: 2,
})
: null}
</div>
{config.feedbackLinksEnabled && (
<Tooltip
content={t(