mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 02:10:45 -06:00
Trace View: Add Session for this span button (#89656)
* Add ability to show Session for this span button in trace view * Add session link when fe o11y ids are available * Add tests for creating session links * Also check for session id written following otel semantic convention
This commit is contained in:
parent
0759c98504
commit
df4b280134
@ -114,6 +114,11 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
LinkIcon: css`
|
||||
font-size: 1.5em;
|
||||
`,
|
||||
linkList: css({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '10px',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
@ -303,6 +308,7 @@ export default function SpanDetail(props: SpanDetailProps) {
|
||||
|
||||
let logLinkButton: JSX.Element | null = null;
|
||||
let profileLinkButton: JSX.Element | null = null;
|
||||
let sessionLinkButton: JSX.Element | null = null;
|
||||
if (createSpanLink) {
|
||||
const links = createSpanLink(span);
|
||||
const logsLink = links?.filter((link) => link.type === SpanLinkType.Logs);
|
||||
@ -315,6 +321,15 @@ export default function SpanDetail(props: SpanDetailProps) {
|
||||
if (links && profilesLink && profilesLink.length > 0) {
|
||||
profileLinkButton = createLinkButton(profilesLink[0], SpanLinkType.Profiles, 'Profiles for this span', 'link');
|
||||
}
|
||||
const sessionLink = links?.filter((link) => link.type === SpanLinkType.Session);
|
||||
if (links && sessionLink && sessionLink.length > 0) {
|
||||
sessionLinkButton = createLinkButton(
|
||||
sessionLink[0],
|
||||
SpanLinkType.Session,
|
||||
'Session for this span',
|
||||
'frontend-observability'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const focusSpanLink = createFocusSpanLink(traceID, spanID);
|
||||
@ -326,8 +341,11 @@ export default function SpanDetail(props: SpanDetailProps) {
|
||||
<LabeledList className={styles.list} divider={true} items={overviewItems} />
|
||||
</div>
|
||||
</div>
|
||||
<span style={{ marginRight: '10px' }}>{logLinkButton}</span>
|
||||
{profileLinkButton}
|
||||
<div className={styles.linkList}>
|
||||
{logLinkButton}
|
||||
{profileLinkButton}
|
||||
{sessionLinkButton}
|
||||
</div>
|
||||
<Divider spacing={1} />
|
||||
<div>
|
||||
<div>
|
||||
|
@ -9,6 +9,7 @@ export enum SpanLinkType {
|
||||
Traces = 'trace',
|
||||
Metrics = 'metric',
|
||||
Profiles = 'profile',
|
||||
Session = 'session',
|
||||
Unknown = 'unknown',
|
||||
}
|
||||
|
||||
|
@ -1266,6 +1266,57 @@ describe('createSpanLinkFactory', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('should return session link', () => {
|
||||
beforeAll(() => {
|
||||
setLinkSrv(new LinkSrv());
|
||||
setTemplateSrv(new TemplateSrv());
|
||||
});
|
||||
|
||||
it('does not add link when no ids are present', () => {
|
||||
const createLink = setupSpanLinkFactory();
|
||||
expect(createLink).toBeDefined();
|
||||
const links = createLink!(createTraceSpan());
|
||||
const sessionLink = links?.find((link) => link.type === SpanLinkType.Session);
|
||||
expect(sessionLink).toBe(undefined);
|
||||
});
|
||||
|
||||
it('adds link when fe o11y ids are present', () => {
|
||||
const createLink = setupSpanLinkFactory();
|
||||
expect(createLink).toBeDefined();
|
||||
const links = createLink!(
|
||||
createTraceSpan({
|
||||
process: {
|
||||
serviceName: 'feo11y-service',
|
||||
tags: [{ key: 'gf.feo11y.app.id', value: 'appId' }],
|
||||
},
|
||||
tags: [{ key: 'session_id', value: 'the-session-id' }],
|
||||
})
|
||||
);
|
||||
expect(links).toHaveLength(1);
|
||||
const sessionLink = links?.find((link) => link.type === SpanLinkType.Session);
|
||||
expect(sessionLink).toBeDefined();
|
||||
expect(sessionLink!.href).toBe('/a/grafana-kowalski-app/apps/appId/sessions/the-session-id');
|
||||
});
|
||||
|
||||
it('adds link when session id is defined following otel semantic convention', () => {
|
||||
const createLink = setupSpanLinkFactory();
|
||||
expect(createLink).toBeDefined();
|
||||
const links = createLink!(
|
||||
createTraceSpan({
|
||||
process: {
|
||||
serviceName: 'feo11y-service',
|
||||
tags: [{ key: 'gf.feo11y.app.id', value: 'appId' }],
|
||||
},
|
||||
tags: [{ key: 'session.id', value: 'the-otel-session-id' }],
|
||||
})
|
||||
);
|
||||
expect(links).toHaveLength(1);
|
||||
const sessionLink = links?.find((link) => link.type === SpanLinkType.Session);
|
||||
expect(sessionLink).toBeDefined();
|
||||
expect(sessionLink!.href).toBe('/a/grafana-kowalski-app/apps/appId/sessions/the-otel-session-id');
|
||||
});
|
||||
});
|
||||
|
||||
describe('should return pyroscope link', () => {
|
||||
beforeAll(() => {
|
||||
setDataSourceSrv({
|
||||
|
@ -137,6 +137,7 @@ const formatDefaultKeys = (keys: string[]) => {
|
||||
const defaultKeys = formatDefaultKeys(['cluster', 'hostname', 'namespace', 'pod', 'service.name', 'service.namespace']);
|
||||
export const defaultProfilingKeys = formatDefaultKeys(['service.name', 'service.namespace']);
|
||||
export const pyroscopeProfileIdTagKey = 'pyroscope.profile.id';
|
||||
export const feO11yTagKey = 'gf.feo11y.app.id';
|
||||
|
||||
function legacyCreateSpanLinkFactory(
|
||||
splitOpenFn: SplitOpen,
|
||||
@ -348,6 +349,18 @@ function legacyCreateSpanLinkFactory(
|
||||
}
|
||||
}
|
||||
|
||||
// Get session links
|
||||
const feO11yLink = getLinkForFeO11y(span);
|
||||
if (feO11yLink) {
|
||||
links.push({
|
||||
title: 'Session for this span',
|
||||
href: feO11yLink,
|
||||
content: <Icon name="frontend-observability" title="Session for this span" />,
|
||||
field,
|
||||
type: SpanLinkType.Session,
|
||||
});
|
||||
}
|
||||
|
||||
return links;
|
||||
};
|
||||
}
|
||||
@ -382,6 +395,15 @@ function getQueryForLoki(
|
||||
};
|
||||
}
|
||||
|
||||
function getLinkForFeO11y(span: TraceSpan): string | undefined {
|
||||
const feO11yAppId = span.process.tags.find((tag) => tag.key === feO11yTagKey)?.value;
|
||||
const feO11ySessionId = span.tags.find((tag) => tag.key === 'session_id' || tag.key === 'session.id')?.value;
|
||||
|
||||
return feO11yAppId && feO11ySessionId
|
||||
? `/a/grafana-kowalski-app/apps/${feO11yAppId}/sessions/${feO11ySessionId}`
|
||||
: undefined;
|
||||
}
|
||||
|
||||
// we do not have access to the dataquery type for opensearch,
|
||||
// so here is a minimal interface that handles both elasticsearch and opensearch.
|
||||
interface ElasticsearchOrOpensearchQuery extends DataQuery {
|
||||
|
Loading…
Reference in New Issue
Block a user