Trace View: Copy Trace ID action button (#64416)
* Added button to trace view to copy trace ID * Added dummy Export button
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2, IconName } from '@grafana/data';
|
||||
import { Button, useStyles2 } from '@grafana/ui';
|
||||
|
||||
export const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
ActionButton: css`
|
||||
label: ActionButton;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 110px;
|
||||
justify-content: center;
|
||||
&:after {
|
||||
content: '';
|
||||
background: ${theme.colors.primary.main};
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
transition: all 0.8s;
|
||||
}
|
||||
&:active:after {
|
||||
margin: 0;
|
||||
opacity: 0.3;
|
||||
transition: 0s;
|
||||
}
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
||||
export type ActionButtonProps = {
|
||||
onClick: () => void;
|
||||
ariaLabel: string;
|
||||
label: string;
|
||||
icon: IconName;
|
||||
};
|
||||
|
||||
export default function ActionButton(props: ActionButtonProps) {
|
||||
const { onClick, ariaLabel, label, icon } = props;
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={styles.ActionButton}
|
||||
variant="secondary"
|
||||
fill={'outline'}
|
||||
type="button"
|
||||
icon={icon}
|
||||
aria-label={ariaLabel}
|
||||
onClick={onClick}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
|
||||
import ActionButton from './ActionButton';
|
||||
|
||||
export const getStyles = () => {
|
||||
return {
|
||||
TracePageActions: css`
|
||||
label: TracePageActions;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
||||
export type TracePageActionsProps = {
|
||||
traceId: string;
|
||||
};
|
||||
|
||||
export default function TracePageActions(props: TracePageActionsProps) {
|
||||
const { traceId } = props;
|
||||
const styles = useStyles2(getStyles);
|
||||
const [copyTraceIdClicked, setCopyTraceIdClicked] = useState(false);
|
||||
|
||||
const copyTraceId = () => {
|
||||
navigator.clipboard.writeText(traceId);
|
||||
setCopyTraceIdClicked(true);
|
||||
setTimeout(() => {
|
||||
setCopyTraceIdClicked(false);
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.TracePageActions}>
|
||||
<ActionButton
|
||||
onClick={copyTraceId}
|
||||
ariaLabel={'Copy Trace ID'}
|
||||
label={copyTraceIdClicked ? 'Copied!' : 'Trace ID'}
|
||||
icon={'copy'}
|
||||
/>
|
||||
<ActionButton
|
||||
onClick={() => alert('not implemented')}
|
||||
ariaLabel={'Export Trace'}
|
||||
label={'Export'}
|
||||
icon={'save'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+19
-4
@@ -26,15 +26,29 @@ import { getTraceLinks } from '../model/link-patterns';
|
||||
import { getHeaderTags, getTraceName } from '../model/trace-viewer';
|
||||
import { formatDuration } from '../utils/date';
|
||||
|
||||
import TracePageActions from './Actions/TracePageActions';
|
||||
import SpanGraph from './SpanGraph';
|
||||
import { TracePageHeaderEmbedProps, timestamp, getStyles } from './TracePageHeader';
|
||||
|
||||
const getNewStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
titleRow: css`
|
||||
label: TracePageHeaderTitleRow;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
padding: 0 0.5em 0 0.5em;
|
||||
`,
|
||||
title: css`
|
||||
label: TracePageHeaderTitle;
|
||||
color: inherit;
|
||||
flex: 1;
|
||||
font-size: 1.7em;
|
||||
line-height: 1em;
|
||||
`,
|
||||
subtitle: css`
|
||||
flex: 1;
|
||||
line-height: 1em;
|
||||
margin: -0.5em 0 1.5em 0.5em;
|
||||
margin: -0.5em 0.5em 1.5em 0.5em;
|
||||
`,
|
||||
tag: css`
|
||||
margin: 0 0.5em 0 0;
|
||||
@@ -56,7 +70,7 @@ const getNewStyles = (theme: GrafanaTheme2) => {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 5;
|
||||
padding: 10px 5px 0 5px;
|
||||
padding: 0.5em 0.25em 0 0.25em;
|
||||
& > :last-child {
|
||||
border-bottom: 1px solid ${autoColor(theme, '#ccc')};
|
||||
}
|
||||
@@ -82,7 +96,7 @@ export function NewTracePageHeader(props: TracePageHeaderEmbedProps) {
|
||||
const { method, status, url } = getHeaderTags(trace.spans);
|
||||
|
||||
const title = (
|
||||
<h1 className={cx(styles.TracePageHeaderTitle)}>
|
||||
<h1 className={cx(styles.title)}>
|
||||
<TraceName traceName={getTraceName(trace.spans)} />
|
||||
<small>
|
||||
<span className={styles.divider}>|</span>
|
||||
@@ -102,9 +116,10 @@ export function NewTracePageHeader(props: TracePageHeaderEmbedProps) {
|
||||
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<div className={styles.TracePageHeaderTitleRow}>
|
||||
<div className={styles.titleRow}>
|
||||
{links && links.length > 0 && <ExternalLinks links={links} className={styles.TracePageHeaderBack} />}
|
||||
{title}
|
||||
<TracePageActions traceId={trace.traceID} />
|
||||
</div>
|
||||
|
||||
<div className={styles.subtitle}>
|
||||
|
||||
Reference in New Issue
Block a user