mirror of
				https://github.com/grafana/grafana.git
				synced 2025-02-25 18:55:37 -06:00 
			
		
		
		
	Tempo / Trace Viewer: Support Span Links in Trace Viewer (#45632)
* Support Span Links in Trace Viewer * Update ReferencesButton styles * Remove datasource prop Co-authored-by: Connor Lindsey <cblindsey3@gmail.com>
This commit is contained in:
		@@ -104,7 +104,7 @@ exports[`no enzyme tests`] = {
 | 
			
		||||
    "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBar.test.js:2127169675": [
 | 
			
		||||
      [15, 17, 13, "RegExp match", "2409514259"]
 | 
			
		||||
    ],
 | 
			
		||||
    "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js:2454947085": [
 | 
			
		||||
    "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js:814916029": [
 | 
			
		||||
      [15, 26, 13, "RegExp match", "2409514259"]
 | 
			
		||||
    ],
 | 
			
		||||
    "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianKeyValues.test.js:2200354834": [
 | 
			
		||||
@@ -113,7 +113,7 @@ exports[`no enzyme tests`] = {
 | 
			
		||||
    "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.test.js:3242453659": [
 | 
			
		||||
      [15, 19, 13, "RegExp match", "2409514259"]
 | 
			
		||||
    ],
 | 
			
		||||
    "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.test.js:3043344541": [
 | 
			
		||||
    "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.test.js:1301875390": [
 | 
			
		||||
      [15, 19, 13, "RegExp match", "2409514259"]
 | 
			
		||||
    ],
 | 
			
		||||
    "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianText.test.js:2881451220": [
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,8 @@ export enum LoadingState {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Should be kept in sync with grafana-plugin-sdk-go/data/frame_meta.go
 | 
			
		||||
export type PreferredVisualisationType = 'graph' | 'table' | 'logs' | 'trace' | 'nodeGraph';
 | 
			
		||||
export const preferredVisualizationTypes = ['graph', 'table', 'logs', 'trace', 'nodeGraph'] as const;
 | 
			
		||||
export type PreferredVisualisationType = typeof preferredVisualizationTypes[number];
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @public
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,12 @@ export type TraceLog = {
 | 
			
		||||
  fields: TraceKeyValuePair[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type TraceSpanReference = {
 | 
			
		||||
  traceID: string;
 | 
			
		||||
  spanID: string;
 | 
			
		||||
  tags?: TraceKeyValuePair[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This describes the structure of the dataframe that should be returned from a tracing data source to show trace
 | 
			
		||||
 * in a TraceView component.
 | 
			
		||||
@@ -31,7 +37,7 @@ export interface TraceSpanRow {
 | 
			
		||||
  // Milliseconds
 | 
			
		||||
  duration: number;
 | 
			
		||||
  logs?: TraceLog[];
 | 
			
		||||
 | 
			
		||||
  references?: TraceSpanReference[];
 | 
			
		||||
  // Note: To mark spen as having error add tag error: true
 | 
			
		||||
  tags?: TraceKeyValuePair[];
 | 
			
		||||
  warnings?: string[];
 | 
			
		||||
 
 | 
			
		||||
@@ -14,16 +14,15 @@
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { css } from '@emotion/css';
 | 
			
		||||
import { stylesFactory, Tooltip } from '@grafana/ui';
 | 
			
		||||
import { Tooltip, useStyles2 } from '@grafana/ui';
 | 
			
		||||
 | 
			
		||||
import { TraceSpanReference } from '../types/trace';
 | 
			
		||||
import ReferenceLink from '../url/ReferenceLink';
 | 
			
		||||
 | 
			
		||||
export const getStyles = stylesFactory(() => {
 | 
			
		||||
export const getStyles = () => {
 | 
			
		||||
  return {
 | 
			
		||||
    MultiParent: css`
 | 
			
		||||
      padding: 0 5px;
 | 
			
		||||
      color: #000;
 | 
			
		||||
      & ~ & {
 | 
			
		||||
        margin-left: 5px;
 | 
			
		||||
      }
 | 
			
		||||
@@ -39,7 +38,7 @@ export const getStyles = stylesFactory(() => {
 | 
			
		||||
      max-width: none;
 | 
			
		||||
    `,
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type TReferencesButtonProps = {
 | 
			
		||||
  references: TraceSpanReference[];
 | 
			
		||||
@@ -48,10 +47,9 @@ type TReferencesButtonProps = {
 | 
			
		||||
  focusSpan: (spanID: string) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default class ReferencesButton extends React.PureComponent<TReferencesButtonProps> {
 | 
			
		||||
  render() {
 | 
			
		||||
    const { references, children, tooltipText, focusSpan } = this.props;
 | 
			
		||||
    const styles = getStyles();
 | 
			
		||||
const ReferencesButton = (props: TReferencesButtonProps) => {
 | 
			
		||||
  const { references, children, tooltipText, focusSpan } = props;
 | 
			
		||||
  const styles = useStyles2(getStyles);
 | 
			
		||||
 | 
			
		||||
  // TODO: handle multiple items with some dropdown
 | 
			
		||||
  const ref = references[0];
 | 
			
		||||
@@ -62,5 +60,6 @@ export default class ReferencesButton extends React.PureComponent<TReferencesBut
 | 
			
		||||
      </ReferenceLink>
 | 
			
		||||
    </Tooltip>
 | 
			
		||||
  );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default ReferencesButton;
 | 
			
		||||
 
 | 
			
		||||
@@ -54,6 +54,7 @@ describe('<SpanBarRow>', () => {
 | 
			
		||||
      },
 | 
			
		||||
      spanID,
 | 
			
		||||
      logs: [],
 | 
			
		||||
      references: [],
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
@@ -84,8 +85,8 @@ describe('<SpanBarRow>', () => {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('render references button', () => {
 | 
			
		||||
    const span = Object.assign(
 | 
			
		||||
      {
 | 
			
		||||
    const newSpan = Object.assign({}, props.span);
 | 
			
		||||
    const span = Object.assign(newSpan, {
 | 
			
		||||
      references: [
 | 
			
		||||
        {
 | 
			
		||||
          refType: 'CHILD_OF',
 | 
			
		||||
@@ -104,9 +105,7 @@ describe('<SpanBarRow>', () => {
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      },
 | 
			
		||||
      props.span
 | 
			
		||||
    );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const spanRow = shallow(<SpanBarRow {...props} span={span} />)
 | 
			
		||||
      .dive()
 | 
			
		||||
 
 | 
			
		||||
@@ -13,13 +13,13 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
 | 
			
		||||
import IoAlert from 'react-icons/lib/io/alert';
 | 
			
		||||
import IoArrowRightA from 'react-icons/lib/io/arrow-right-a';
 | 
			
		||||
import IoNetwork from 'react-icons/lib/io/network';
 | 
			
		||||
import MdFileUpload from 'react-icons/lib/md/file-upload';
 | 
			
		||||
import { css, keyframes } from '@emotion/css';
 | 
			
		||||
import cx from 'classnames';
 | 
			
		||||
import { stylesFactory, withTheme2 } from '@grafana/ui';
 | 
			
		||||
import { Icon, stylesFactory, withTheme2 } from '@grafana/ui';
 | 
			
		||||
import { GrafanaTheme2 } from '@grafana/data';
 | 
			
		||||
 | 
			
		||||
import ReferencesButton from './ReferencesButton';
 | 
			
		||||
@@ -510,7 +510,7 @@ export class UnthemedSpanBarRow extends React.PureComponent<SpanBarRowProps> {
 | 
			
		||||
                tooltipText="Contains multiple references"
 | 
			
		||||
                focusSpan={focusSpan}
 | 
			
		||||
              >
 | 
			
		||||
                <IoNetwork />
 | 
			
		||||
                <Icon name="link" />
 | 
			
		||||
              </ReferencesButton>
 | 
			
		||||
            )}
 | 
			
		||||
            {span.subsidiarilyReferencedBy && span.subsidiarilyReferencedBy.length > 0 && (
 | 
			
		||||
 
 | 
			
		||||
@@ -104,7 +104,7 @@ describe('<References>', () => {
 | 
			
		||||
        expect(serviceName).toBe(span.process.serviceName);
 | 
			
		||||
        expect(endpointName).toBe(span.operationName);
 | 
			
		||||
      } else {
 | 
			
		||||
        expect(serviceName).toBe('< span in another trace >');
 | 
			
		||||
        expect(serviceName).toBe('View Linked Span ');
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 
 | 
			
		||||
@@ -14,17 +14,51 @@
 | 
			
		||||
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { css } from '@emotion/css';
 | 
			
		||||
import cx from 'classnames';
 | 
			
		||||
import { useStyles2 } from '@grafana/ui';
 | 
			
		||||
import { Icon, useStyles2 } from '@grafana/ui';
 | 
			
		||||
 | 
			
		||||
import AccordianKeyValues from './AccordianKeyValues';
 | 
			
		||||
import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down';
 | 
			
		||||
import IoIosArrowRight from 'react-icons/lib/io/ios-arrow-right';
 | 
			
		||||
import { TraceSpanReference } from '../../types/trace';
 | 
			
		||||
import ReferenceLink from '../../url/ReferenceLink';
 | 
			
		||||
import { uAlignIcon } from '../../uberUtilityStyles';
 | 
			
		||||
import { uAlignIcon, ubMb1 } from '../../uberUtilityStyles';
 | 
			
		||||
import { GrafanaTheme2 } from '@grafana/data';
 | 
			
		||||
import { autoColor } from '../../Theme';
 | 
			
		||||
 | 
			
		||||
const getStyles = () => {
 | 
			
		||||
const getStyles = (theme: GrafanaTheme2) => {
 | 
			
		||||
  return {
 | 
			
		||||
    AccordianReferenceItem: css`
 | 
			
		||||
      border-bottom: 1px solid ${autoColor(theme, '#d8d8d8')};
 | 
			
		||||
    `,
 | 
			
		||||
    AccordianKeyValues: css`
 | 
			
		||||
      margin-left: 10px;
 | 
			
		||||
    `,
 | 
			
		||||
    AccordianReferences: css`
 | 
			
		||||
      label: AccordianReferences;
 | 
			
		||||
      border: 1px solid ${autoColor(theme, '#d8d8d8')};
 | 
			
		||||
      position: relative;
 | 
			
		||||
      margin-bottom: 0.25rem;
 | 
			
		||||
    `,
 | 
			
		||||
    AccordianReferencesHeader: css`
 | 
			
		||||
      label: AccordianReferencesHeader;
 | 
			
		||||
      background: ${autoColor(theme, '#e4e4e4')};
 | 
			
		||||
      color: inherit;
 | 
			
		||||
      display: block;
 | 
			
		||||
      padding: 0.25rem 0.5rem;
 | 
			
		||||
      &:hover {
 | 
			
		||||
        background: ${autoColor(theme, '#dadada')};
 | 
			
		||||
      }
 | 
			
		||||
    `,
 | 
			
		||||
    AccordianReferencesContent: css`
 | 
			
		||||
      label: AccordianReferencesContent;
 | 
			
		||||
      background: ${autoColor(theme, '#f0f0f0')};
 | 
			
		||||
      border-top: 1px solid ${autoColor(theme, '#d8d8d8')};
 | 
			
		||||
      padding: 0.5rem 0.5rem 0.25rem 0.5rem;
 | 
			
		||||
    `,
 | 
			
		||||
    AccordianReferencesFooter: css`
 | 
			
		||||
      label: AccordianReferencesFooter;
 | 
			
		||||
      color: ${autoColor(theme, '#999')};
 | 
			
		||||
    `,
 | 
			
		||||
    ReferencesList: css`
 | 
			
		||||
      background: #fff;
 | 
			
		||||
      border: 1px solid #ddd;
 | 
			
		||||
@@ -53,6 +87,9 @@ const getStyles = () => {
 | 
			
		||||
    debugInfo: css`
 | 
			
		||||
      letter-spacing: 0.25px;
 | 
			
		||||
      margin: 0.5em 0 0;
 | 
			
		||||
      flex-wrap: wrap;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      justify-content: flex-end;
 | 
			
		||||
    `,
 | 
			
		||||
    debugLabel: css`
 | 
			
		||||
      margin: 0 5px 0 5px;
 | 
			
		||||
@@ -69,26 +106,30 @@ type AccordianReferencesProps = {
 | 
			
		||||
  highContrast?: boolean;
 | 
			
		||||
  interactive?: boolean;
 | 
			
		||||
  isOpen: boolean;
 | 
			
		||||
  openedItems?: Set<TraceSpanReference>;
 | 
			
		||||
  onItemToggle?: (reference: TraceSpanReference) => void;
 | 
			
		||||
  onToggle?: null | (() => void);
 | 
			
		||||
  focusSpan: (uiFind: string) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type ReferenceItemProps = {
 | 
			
		||||
  data: TraceSpanReference[];
 | 
			
		||||
  interactive?: boolean;
 | 
			
		||||
  openedItems?: Set<TraceSpanReference>;
 | 
			
		||||
  onItemToggle?: (reference: TraceSpanReference) => void;
 | 
			
		||||
  focusSpan: (uiFind: string) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// export for test
 | 
			
		||||
export function References(props: ReferenceItemProps) {
 | 
			
		||||
  const { data, focusSpan } = props;
 | 
			
		||||
  const { data, focusSpan, openedItems, onItemToggle, interactive } = props;
 | 
			
		||||
  const styles = useStyles2(getStyles);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={cx(styles.ReferencesList)}>
 | 
			
		||||
      <ul className={styles.list}>
 | 
			
		||||
        {data.map((reference) => {
 | 
			
		||||
          return (
 | 
			
		||||
            <li className={styles.item} key={`${reference.spanID}`}>
 | 
			
		||||
    <div className={styles.AccordianReferencesContent}>
 | 
			
		||||
      {data.map((reference, i) => (
 | 
			
		||||
        <div className={i < data.length - 1 ? styles.AccordianReferenceItem : undefined} key={reference.spanID}>
 | 
			
		||||
          <div className={styles.item} key={`${reference.spanID}`}>
 | 
			
		||||
            <ReferenceLink reference={reference} focusSpan={focusSpan}>
 | 
			
		||||
              <span className={styles.itemContent}>
 | 
			
		||||
                {reference.span ? (
 | 
			
		||||
@@ -97,11 +138,13 @@ export function References(props: ReferenceItemProps) {
 | 
			
		||||
                    <small className="endpoint-name">{reference.span.operationName}</small>
 | 
			
		||||
                  </span>
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <span className="span-svc-name">< span in another trace ></span>
 | 
			
		||||
                  <span className="span-svc-name">
 | 
			
		||||
                    View Linked Span <Icon name="external-link-alt" />
 | 
			
		||||
                  </span>
 | 
			
		||||
                )}
 | 
			
		||||
                <small className={styles.debugInfo}>
 | 
			
		||||
                    <span className={styles.debugLabel} data-label="Reference Type:">
 | 
			
		||||
                      {reference.refType}
 | 
			
		||||
                  <span className={styles.debugLabel} data-label="TraceID:">
 | 
			
		||||
                    {reference.traceID}
 | 
			
		||||
                  </span>
 | 
			
		||||
                  <span className={styles.debugLabel} data-label="SpanID:">
 | 
			
		||||
                    {reference.spanID}
 | 
			
		||||
@@ -109,46 +152,71 @@ export function References(props: ReferenceItemProps) {
 | 
			
		||||
                </small>
 | 
			
		||||
              </span>
 | 
			
		||||
            </ReferenceLink>
 | 
			
		||||
            </li>
 | 
			
		||||
          );
 | 
			
		||||
        })}
 | 
			
		||||
      </ul>
 | 
			
		||||
          </div>
 | 
			
		||||
          {!!reference.tags?.length && (
 | 
			
		||||
            <div className={styles.AccordianKeyValues}>
 | 
			
		||||
              <AccordianKeyValues
 | 
			
		||||
                className={i < data.length - 1 ? ubMb1 : null}
 | 
			
		||||
                data={reference.tags || []}
 | 
			
		||||
                highContrast
 | 
			
		||||
                interactive={interactive}
 | 
			
		||||
                isOpen={openedItems ? openedItems.has(reference) : false}
 | 
			
		||||
                label={'attributes'}
 | 
			
		||||
                linksGetter={null}
 | 
			
		||||
                onToggle={interactive && onItemToggle ? () => onItemToggle(reference) : null}
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
          )}
 | 
			
		||||
        </div>
 | 
			
		||||
      ))}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default class AccordianReferences extends React.PureComponent<AccordianReferencesProps> {
 | 
			
		||||
  static defaultProps: Partial<AccordianReferencesProps> = {
 | 
			
		||||
    highContrast: false,
 | 
			
		||||
    interactive: true,
 | 
			
		||||
    onToggle: null,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const { data, interactive, isOpen, onToggle, focusSpan } = this.props;
 | 
			
		||||
const AccordianReferences: React.FC<AccordianReferencesProps> = ({
 | 
			
		||||
  data,
 | 
			
		||||
  interactive = true,
 | 
			
		||||
  isOpen,
 | 
			
		||||
  onToggle,
 | 
			
		||||
  onItemToggle,
 | 
			
		||||
  openedItems,
 | 
			
		||||
  focusSpan,
 | 
			
		||||
}) => {
 | 
			
		||||
  const isEmpty = !Array.isArray(data) || !data.length;
 | 
			
		||||
    const iconCls = uAlignIcon;
 | 
			
		||||
  let arrow: React.ReactNode | null = null;
 | 
			
		||||
  let HeaderComponent: 'span' | 'a' = 'span';
 | 
			
		||||
  let headerProps: {} | null = null;
 | 
			
		||||
  if (interactive) {
 | 
			
		||||
      arrow = isOpen ? <IoIosArrowDown className={iconCls} /> : <IoIosArrowRight className={iconCls} />;
 | 
			
		||||
    arrow = isOpen ? <IoIosArrowDown className={uAlignIcon} /> : <IoIosArrowRight className={uAlignIcon} />;
 | 
			
		||||
    HeaderComponent = 'a';
 | 
			
		||||
    headerProps = {
 | 
			
		||||
      'aria-checked': isOpen,
 | 
			
		||||
      onClick: isEmpty ? null : onToggle,
 | 
			
		||||
      role: 'switch',
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const styles = useStyles2(getStyles);
 | 
			
		||||
  return (
 | 
			
		||||
      <div>
 | 
			
		||||
        <div {...headerProps}>
 | 
			
		||||
    <div className={styles.AccordianReferences}>
 | 
			
		||||
      <HeaderComponent className={styles.AccordianReferencesHeader} {...headerProps}>
 | 
			
		||||
        {arrow}
 | 
			
		||||
        <strong>
 | 
			
		||||
          <span>References</span>
 | 
			
		||||
        </strong>{' '}
 | 
			
		||||
        ({data.length})
 | 
			
		||||
        </div>
 | 
			
		||||
        {isOpen && <References data={data} focusSpan={focusSpan} />}
 | 
			
		||||
      </HeaderComponent>
 | 
			
		||||
      {isOpen && (
 | 
			
		||||
        <References
 | 
			
		||||
          data={data}
 | 
			
		||||
          openedItems={openedItems}
 | 
			
		||||
          focusSpan={focusSpan}
 | 
			
		||||
          onItemToggle={onItemToggle}
 | 
			
		||||
          interactive={interactive}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default React.memo(AccordianReferences);
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { TraceLog } from '../../types/trace';
 | 
			
		||||
import { TraceLog, TraceSpanReference } from '../../types/trace';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Which items of a {@link SpanDetail} component are expanded.
 | 
			
		||||
@@ -21,6 +21,7 @@ export default class DetailState {
 | 
			
		||||
  isTagsOpen: boolean;
 | 
			
		||||
  isProcessOpen: boolean;
 | 
			
		||||
  logs: { isOpen: boolean; openedItems: Set<TraceLog> };
 | 
			
		||||
  references: { isOpen: boolean; openedItems: Set<TraceSpanReference> };
 | 
			
		||||
  isWarningsOpen: boolean;
 | 
			
		||||
  isStackTracesOpen: boolean;
 | 
			
		||||
  isReferencesOpen: boolean;
 | 
			
		||||
@@ -33,6 +34,7 @@ export default class DetailState {
 | 
			
		||||
      isWarningsOpen,
 | 
			
		||||
      isStackTracesOpen,
 | 
			
		||||
      logs,
 | 
			
		||||
      references,
 | 
			
		||||
    }: DetailState | Record<string, undefined> = oldState || {};
 | 
			
		||||
    this.isTagsOpen = Boolean(isTagsOpen);
 | 
			
		||||
    this.isProcessOpen = Boolean(isProcessOpen);
 | 
			
		||||
@@ -43,6 +45,10 @@ export default class DetailState {
 | 
			
		||||
      isOpen: Boolean(logs && logs.isOpen),
 | 
			
		||||
      openedItems: logs && logs.openedItems ? new Set(logs.openedItems) : new Set(),
 | 
			
		||||
    };
 | 
			
		||||
    this.references = {
 | 
			
		||||
      isOpen: Boolean(references && references.isOpen),
 | 
			
		||||
      openedItems: references && references.openedItems ? new Set(references.openedItems) : new Set(),
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleTags() {
 | 
			
		||||
@@ -59,7 +65,17 @@ export default class DetailState {
 | 
			
		||||
 | 
			
		||||
  toggleReferences() {
 | 
			
		||||
    const next = new DetailState(this);
 | 
			
		||||
    next.isReferencesOpen = !this.isReferencesOpen;
 | 
			
		||||
    next.references.isOpen = !this.references.isOpen;
 | 
			
		||||
    return next;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleReferenceItem(reference: TraceSpanReference) {
 | 
			
		||||
    const next = new DetailState(this);
 | 
			
		||||
    if (next.references.openedItems.has(reference)) {
 | 
			
		||||
      next.references.openedItems.delete(reference);
 | 
			
		||||
    } else {
 | 
			
		||||
      next.references.openedItems.add(reference);
 | 
			
		||||
    }
 | 
			
		||||
    return next;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ import DetailState from './DetailState';
 | 
			
		||||
import { formatDuration } from '../utils';
 | 
			
		||||
import LabeledList from '../../common/LabeledList';
 | 
			
		||||
import { SpanLinkFunc, TNil } from '../../types';
 | 
			
		||||
import { TraceKeyValuePair, TraceLink, TraceLog, TraceSpan } from '../../types/trace';
 | 
			
		||||
import { TraceKeyValuePair, TraceLink, TraceLog, TraceSpan, TraceSpanReference } from '../../types/trace';
 | 
			
		||||
import AccordianReferences from './AccordianReferences';
 | 
			
		||||
import { autoColor } from '../../Theme';
 | 
			
		||||
import { Divider } from '../../common/Divider';
 | 
			
		||||
@@ -110,6 +110,7 @@ type SpanDetailProps = {
 | 
			
		||||
  traceStartTime: number;
 | 
			
		||||
  warningsToggle: (spanID: string) => void;
 | 
			
		||||
  stackTracesToggle: (spanID: string) => void;
 | 
			
		||||
  referenceItemToggle: (spanID: string, reference: TraceSpanReference) => void;
 | 
			
		||||
  referencesToggle: (spanID: string) => void;
 | 
			
		||||
  focusSpan: (uiFind: string) => void;
 | 
			
		||||
  createSpanLink?: SpanLinkFunc;
 | 
			
		||||
@@ -130,6 +131,7 @@ export default function SpanDetail(props: SpanDetailProps) {
 | 
			
		||||
    warningsToggle,
 | 
			
		||||
    stackTracesToggle,
 | 
			
		||||
    referencesToggle,
 | 
			
		||||
    referenceItemToggle,
 | 
			
		||||
    focusSpan,
 | 
			
		||||
    createSpanLink,
 | 
			
		||||
    createFocusSpanLink,
 | 
			
		||||
@@ -139,7 +141,7 @@ export default function SpanDetail(props: SpanDetailProps) {
 | 
			
		||||
    isProcessOpen,
 | 
			
		||||
    logs: logsState,
 | 
			
		||||
    isWarningsOpen,
 | 
			
		||||
    isReferencesOpen,
 | 
			
		||||
    references: referencesState,
 | 
			
		||||
    isStackTracesOpen,
 | 
			
		||||
  } = detailState;
 | 
			
		||||
  const {
 | 
			
		||||
@@ -258,8 +260,10 @@ export default function SpanDetail(props: SpanDetailProps) {
 | 
			
		||||
        {references && references.length > 0 && (references.length > 1 || references[0].refType !== 'CHILD_OF') && (
 | 
			
		||||
          <AccordianReferences
 | 
			
		||||
            data={references}
 | 
			
		||||
            isOpen={isReferencesOpen}
 | 
			
		||||
            isOpen={referencesState.isOpen}
 | 
			
		||||
            openedItems={referencesState.openedItems}
 | 
			
		||||
            onToggle={() => referencesToggle(spanID)}
 | 
			
		||||
            onItemToggle={(reference) => referenceItemToggle(spanID, reference)}
 | 
			
		||||
            focusSpan={focusSpan}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ import { autoColor } from '../Theme';
 | 
			
		||||
import { stylesFactory, withTheme2 } from '@grafana/ui';
 | 
			
		||||
import { GrafanaTheme2, LinkModel } from '@grafana/data';
 | 
			
		||||
 | 
			
		||||
import { TraceLog, TraceSpan, TraceKeyValuePair, TraceLink } from '../types/trace';
 | 
			
		||||
import { TraceLog, TraceSpan, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace';
 | 
			
		||||
import { SpanLinkFunc } from '../types';
 | 
			
		||||
 | 
			
		||||
const getStyles = stylesFactory((theme: GrafanaTheme2) => {
 | 
			
		||||
@@ -77,6 +77,7 @@ type SpanDetailRowProps = {
 | 
			
		||||
  logItemToggle: (spanID: string, log: TraceLog) => void;
 | 
			
		||||
  logsToggle: (spanID: string) => void;
 | 
			
		||||
  processToggle: (spanID: string) => void;
 | 
			
		||||
  referenceItemToggle: (spanID: string, reference: TraceSpanReference) => void;
 | 
			
		||||
  referencesToggle: (spanID: string) => void;
 | 
			
		||||
  warningsToggle: (spanID: string) => void;
 | 
			
		||||
  stackTracesToggle: (spanID: string) => void;
 | 
			
		||||
@@ -111,6 +112,7 @@ export class UnthemedSpanDetailRow extends React.PureComponent<SpanDetailRowProp
 | 
			
		||||
      logItemToggle,
 | 
			
		||||
      logsToggle,
 | 
			
		||||
      processToggle,
 | 
			
		||||
      referenceItemToggle,
 | 
			
		||||
      referencesToggle,
 | 
			
		||||
      warningsToggle,
 | 
			
		||||
      stackTracesToggle,
 | 
			
		||||
@@ -156,6 +158,7 @@ export class UnthemedSpanDetailRow extends React.PureComponent<SpanDetailRowProp
 | 
			
		||||
              logItemToggle={logItemToggle}
 | 
			
		||||
              logsToggle={logsToggle}
 | 
			
		||||
              processToggle={processToggle}
 | 
			
		||||
              referenceItemToggle={referenceItemToggle}
 | 
			
		||||
              referencesToggle={referencesToggle}
 | 
			
		||||
              warningsToggle={warningsToggle}
 | 
			
		||||
              stackTracesToggle={stackTracesToggle}
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ import {
 | 
			
		||||
import { Accessors } from '../ScrollManager';
 | 
			
		||||
import { getColorByKey } from '../utils/color-generator';
 | 
			
		||||
import { SpanLinkFunc, TNil } from '../types';
 | 
			
		||||
import { TraceLog, TraceSpan, Trace, TraceKeyValuePair, TraceLink } from '../types/trace';
 | 
			
		||||
import { TraceLog, TraceSpan, Trace, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace';
 | 
			
		||||
import TTraceTimeline from '../types/TTraceTimeline';
 | 
			
		||||
import { PEER_SERVICE } from '../constants/tag-keys';
 | 
			
		||||
 | 
			
		||||
@@ -75,6 +75,7 @@ type TVirtualizedTraceViewOwnProps = {
 | 
			
		||||
  detailWarningsToggle: (spanID: string) => void;
 | 
			
		||||
  detailStackTracesToggle: (spanID: string) => void;
 | 
			
		||||
  detailReferencesToggle: (spanID: string) => void;
 | 
			
		||||
  detailReferenceItemToggle: (spanID: string, reference: TraceSpanReference) => void;
 | 
			
		||||
  detailProcessToggle: (spanID: string) => void;
 | 
			
		||||
  detailTagsToggle: (spanID: string) => void;
 | 
			
		||||
  detailToggle: (spanID: string) => void;
 | 
			
		||||
@@ -440,6 +441,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
 | 
			
		||||
      detailLogsToggle,
 | 
			
		||||
      detailProcessToggle,
 | 
			
		||||
      detailReferencesToggle,
 | 
			
		||||
      detailReferenceItemToggle,
 | 
			
		||||
      detailWarningsToggle,
 | 
			
		||||
      detailStackTracesToggle,
 | 
			
		||||
      detailStates,
 | 
			
		||||
@@ -474,6 +476,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
 | 
			
		||||
          logItemToggle={detailLogItemToggle}
 | 
			
		||||
          logsToggle={detailLogsToggle}
 | 
			
		||||
          processToggle={detailProcessToggle}
 | 
			
		||||
          referenceItemToggle={detailReferenceItemToggle}
 | 
			
		||||
          referencesToggle={detailReferencesToggle}
 | 
			
		||||
          warningsToggle={detailWarningsToggle}
 | 
			
		||||
          stackTracesToggle={detailStackTracesToggle}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ import { merge as mergeShortcuts } from '../keyboard-shortcuts';
 | 
			
		||||
import { Accessors } from '../ScrollManager';
 | 
			
		||||
import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from './types';
 | 
			
		||||
import { SpanLinkFunc, TNil } from '../types';
 | 
			
		||||
import { TraceSpan, Trace, TraceLog, TraceKeyValuePair, TraceLink } from '../types/trace';
 | 
			
		||||
import { TraceSpan, Trace, TraceLog, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace';
 | 
			
		||||
import TTraceTimeline from '../types/TTraceTimeline';
 | 
			
		||||
import { autoColor } from '../Theme';
 | 
			
		||||
import ExternalLinkContext from '../url/externalLinkContext';
 | 
			
		||||
@@ -93,6 +93,7 @@ type TProps = TExtractUiFindFromStateReturn & {
 | 
			
		||||
  detailWarningsToggle: (spanID: string) => void;
 | 
			
		||||
  detailStackTracesToggle: (spanID: string) => void;
 | 
			
		||||
  detailReferencesToggle: (spanID: string) => void;
 | 
			
		||||
  detailReferenceItemToggle: (spanID: string, reference: TraceSpanReference) => void;
 | 
			
		||||
  detailProcessToggle: (spanID: string) => void;
 | 
			
		||||
  detailTagsToggle: (spanID: string) => void;
 | 
			
		||||
  detailToggle: (spanID: string) => void;
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,7 @@ export type TraceSpanReference = {
 | 
			
		||||
  span?: TraceSpan | null | undefined;
 | 
			
		||||
  spanID: string;
 | 
			
		||||
  traceID: string;
 | 
			
		||||
  tags?: TraceKeyValuePair[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type TraceSpanData = {
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,7 @@ export default function ReferenceLink(props: ReferenceLinkProps) {
 | 
			
		||||
        if (!createLinkToExternalSpan) {
 | 
			
		||||
          throw new Error("ExternalLinkContext does not have a value, you probably forgot to setup it's provider");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
          <a
 | 
			
		||||
            href={createLinkToExternalSpan(reference.traceID, reference.spanID)}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,12 @@ type TraceLog struct {
 | 
			
		||||
	Fields    []*KeyValue `json:"fields"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TraceReference struct {
 | 
			
		||||
	SpanID  string      `json:"spanID"`
 | 
			
		||||
	TraceID string      `json:"traceID"`
 | 
			
		||||
	Tags    []*KeyValue `json:"tags"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TraceToFrame(td pdata.Traces) (*data.Frame, error) {
 | 
			
		||||
	// In open telemetry format the spans are grouped first by resource/service they originated in and inside that
 | 
			
		||||
	// resource they are grouped by the instrumentation library which created them.
 | 
			
		||||
@@ -44,6 +50,7 @@ func TraceToFrame(td pdata.Traces) (*data.Frame, error) {
 | 
			
		||||
			data.NewField("startTime", nil, []float64{}),
 | 
			
		||||
			data.NewField("duration", nil, []float64{}),
 | 
			
		||||
			data.NewField("logs", nil, []string{}),
 | 
			
		||||
			data.NewField("references", nil, []string{}),
 | 
			
		||||
			data.NewField("tags", nil, []string{}),
 | 
			
		||||
		},
 | 
			
		||||
		Meta: &data.FrameMeta{
 | 
			
		||||
@@ -127,6 +134,13 @@ func spanToSpanRow(span pdata.Span, libraryTags pdata.InstrumentationLibrary, re
 | 
			
		||||
		return nil, fmt.Errorf("failed to marshal span logs: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	references, err := json.Marshal(spanLinksToReferences(span.Links()))
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to marshal span links: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Order matters (look at dataframe order)
 | 
			
		||||
	return []interface{}{
 | 
			
		||||
		traceID,
 | 
			
		||||
		spanID,
 | 
			
		||||
@@ -137,6 +151,7 @@ func spanToSpanRow(span pdata.Span, libraryTags pdata.InstrumentationLibrary, re
 | 
			
		||||
		startTime,
 | 
			
		||||
		float64(span.EndTimestamp()-span.StartTimestamp()) / 1_000_000,
 | 
			
		||||
		toJSONString(logs),
 | 
			
		||||
		toJSONString(references),
 | 
			
		||||
		toJSONString(spanTags),
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -321,3 +336,33 @@ func spanEventsToLogs(events pdata.SpanEventSlice) []*TraceLog {
 | 
			
		||||
 | 
			
		||||
	return logs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func spanLinksToReferences(links pdata.SpanLinkSlice) []*TraceReference {
 | 
			
		||||
	if links.Len() == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	references := make([]*TraceReference, 0, links.Len())
 | 
			
		||||
	for i := 0; i < links.Len(); i++ {
 | 
			
		||||
		link := links.At(i)
 | 
			
		||||
 | 
			
		||||
		traceId := link.TraceID().HexString()
 | 
			
		||||
		traceId = strings.TrimLeft(traceId, "0")
 | 
			
		||||
 | 
			
		||||
		spanId := link.SpanID().HexString()
 | 
			
		||||
 | 
			
		||||
		tags := make([]*KeyValue, 0, link.Attributes().Len())
 | 
			
		||||
		link.Attributes().Range(func(key string, attr pdata.AttributeValue) bool {
 | 
			
		||||
			tags = append(tags, &KeyValue{Key: key, Value: getAttributeVal(attr)})
 | 
			
		||||
			return true
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		references = append(references, &TraceReference{
 | 
			
		||||
			TraceID: traceId,
 | 
			
		||||
			SpanID:  spanId,
 | 
			
		||||
			Tags:    tags,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return references
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -106,5 +106,6 @@ var fields = []string{
 | 
			
		||||
	"startTime",
 | 
			
		||||
	"duration",
 | 
			
		||||
	"logs",
 | 
			
		||||
	"references",
 | 
			
		||||
	"tags",
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -57,6 +57,7 @@ export function TraceView(props: Props) {
 | 
			
		||||
    detailLogsToggle,
 | 
			
		||||
    detailProcessToggle,
 | 
			
		||||
    detailReferencesToggle,
 | 
			
		||||
    detailReferenceItemToggle,
 | 
			
		||||
    detailTagsToggle,
 | 
			
		||||
    detailWarningsToggle,
 | 
			
		||||
    detailStackTracesToggle,
 | 
			
		||||
@@ -87,6 +88,11 @@ export function TraceView(props: Props) {
 | 
			
		||||
    datasource,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const createLinkToExternalSpan = (traceId: string, spanId: string) => {
 | 
			
		||||
    const link = createFocusSpanLink(traceId, spanId);
 | 
			
		||||
    return link.href;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const traceTimeline: TTraceTimeline = useMemo(
 | 
			
		||||
    () => ({
 | 
			
		||||
      childrenHiddenIDs,
 | 
			
		||||
@@ -144,7 +150,7 @@ export function TraceView(props: Props) {
 | 
			
		||||
        updateViewRangeTime={updateViewRangeTime}
 | 
			
		||||
        viewRange={viewRange}
 | 
			
		||||
        focusSpan={noop}
 | 
			
		||||
        createLinkToExternalSpan={noop as any}
 | 
			
		||||
        createLinkToExternalSpan={createLinkToExternalSpan}
 | 
			
		||||
        setSpanNameColumnWidth={setSpanNameColumnWidth}
 | 
			
		||||
        collapseAll={collapseAll}
 | 
			
		||||
        collapseOne={collapseOne}
 | 
			
		||||
@@ -157,6 +163,7 @@ export function TraceView(props: Props) {
 | 
			
		||||
        detailWarningsToggle={detailWarningsToggle}
 | 
			
		||||
        detailStackTracesToggle={detailStackTracesToggle}
 | 
			
		||||
        detailReferencesToggle={detailReferencesToggle}
 | 
			
		||||
        detailReferenceItemToggle={detailReferenceItemToggle}
 | 
			
		||||
        detailProcessToggle={detailProcessToggle}
 | 
			
		||||
        detailTagsToggle={detailTagsToggle}
 | 
			
		||||
        detailToggle={toggleDetail}
 | 
			
		||||
@@ -203,13 +210,20 @@ function transformTraceDataFrame(frame: DataFrame): TraceResponse {
 | 
			
		||||
    traceID: view.get(0).traceID,
 | 
			
		||||
    processes,
 | 
			
		||||
    spans: view.toArray().map((s, index) => {
 | 
			
		||||
      const references = [];
 | 
			
		||||
      if (s.parentSpanID) {
 | 
			
		||||
        references.push({ refType: 'CHILD_OF' as const, spanID: s.parentSpanID, traceID: s.traceID });
 | 
			
		||||
      }
 | 
			
		||||
      if (s.references) {
 | 
			
		||||
        references.push(...s.references.map((reference) => ({ refType: 'FOLLOWS_FROM' as const, ...reference })));
 | 
			
		||||
      }
 | 
			
		||||
      return {
 | 
			
		||||
        ...s,
 | 
			
		||||
        duration: s.duration * 1000,
 | 
			
		||||
        startTime: s.startTime * 1000,
 | 
			
		||||
        processID: s.spanID,
 | 
			
		||||
        flags: 0,
 | 
			
		||||
        references: s.parentSpanID ? [{ refType: 'CHILD_OF', spanID: s.parentSpanID, traceID: s.traceID }] : undefined,
 | 
			
		||||
        references,
 | 
			
		||||
        logs: s.logs?.map((l) => ({ ...l, timestamp: l.timestamp * 1000 })) || [],
 | 
			
		||||
        dataFrameRowIndex: index,
 | 
			
		||||
      };
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ describe('useDetailState', () => {
 | 
			
		||||
    const { result } = renderHook(() => useDetailState(sampleFrame));
 | 
			
		||||
    act(() => result.current.toggleDetail('span1'));
 | 
			
		||||
    act(() => result.current.detailReferencesToggle('span1'));
 | 
			
		||||
    expect(result.current.detailStates.get('span1')?.isReferencesOpen).toBe(true);
 | 
			
		||||
    expect(result.current.detailStates.get('span1')?.references.isOpen).toBe(true);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('toggles processes', async () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import { useCallback, useState, useEffect } from 'react';
 | 
			
		||||
import { DataFrame } from '@grafana/data';
 | 
			
		||||
import { DetailState } from '@jaegertracing/jaeger-ui-components';
 | 
			
		||||
import { TraceLog } from '@jaegertracing/jaeger-ui-components/src/types/trace';
 | 
			
		||||
import { TraceLog, TraceSpanReference } from '@jaegertracing/jaeger-ui-components/src/types/trace';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Keeps state of the span detail. This means whether span details are open but also state of each detail subitem
 | 
			
		||||
@@ -42,6 +42,20 @@ export function useDetailState(frame: DataFrame) {
 | 
			
		||||
    [detailStates]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const detailReferenceItemToggle = useCallback(
 | 
			
		||||
    function detailReferenceItemToggle(spanID: string, reference: TraceSpanReference) {
 | 
			
		||||
      const old = detailStates.get(spanID);
 | 
			
		||||
      if (!old) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      const detailState = old.toggleReferenceItem(reference);
 | 
			
		||||
      const newDetailStates = new Map(detailStates);
 | 
			
		||||
      newDetailStates.set(spanID, detailState);
 | 
			
		||||
      return setDetailStates(newDetailStates);
 | 
			
		||||
    },
 | 
			
		||||
    [detailStates]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    detailStates,
 | 
			
		||||
    toggleDetail,
 | 
			
		||||
@@ -58,6 +72,7 @@ export function useDetailState(frame: DataFrame) {
 | 
			
		||||
      (spanID: string) => makeDetailSubsectionToggle('stackTraces', detailStates, setDetailStates)(spanID),
 | 
			
		||||
      [detailStates]
 | 
			
		||||
    ),
 | 
			
		||||
    detailReferenceItemToggle,
 | 
			
		||||
    detailReferencesToggle: useCallback(
 | 
			
		||||
      (spanID: string) => makeDetailSubsectionToggle('references', detailStates, setDetailStates)(spanID),
 | 
			
		||||
      [detailStates]
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ import {
 | 
			
		||||
  MutableDataFrame,
 | 
			
		||||
  TraceKeyValuePair,
 | 
			
		||||
  TraceLog,
 | 
			
		||||
  TraceSpanReference,
 | 
			
		||||
  TraceSpanRow,
 | 
			
		||||
  dateTimeFormat,
 | 
			
		||||
} from '@grafana/data';
 | 
			
		||||
@@ -230,6 +231,24 @@ function getSpanTags(
 | 
			
		||||
  return spanTags;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getReferences(span: collectorTypes.opentelemetryProto.trace.v1.Span) {
 | 
			
		||||
  const references: TraceSpanReference[] = [];
 | 
			
		||||
  if (span.links) {
 | 
			
		||||
    for (const link of span.links) {
 | 
			
		||||
      const { traceId, spanId } = link;
 | 
			
		||||
      const tags: TraceKeyValuePair[] = [];
 | 
			
		||||
      if (link.attributes) {
 | 
			
		||||
        for (const attribute of link.attributes) {
 | 
			
		||||
          tags.push({ key: attribute.key, value: getAttributeValue(attribute.value) });
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      references.push({ traceID: traceId, spanID: spanId, tags });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return references;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getLogs(span: collectorTypes.opentelemetryProto.trace.v1.Span) {
 | 
			
		||||
  const logs: TraceLog[] = [];
 | 
			
		||||
  if (span.events) {
 | 
			
		||||
@@ -262,6 +281,7 @@ export function transformFromOTLP(
 | 
			
		||||
      { name: 'startTime', type: FieldType.number },
 | 
			
		||||
      { name: 'duration', type: FieldType.number },
 | 
			
		||||
      { name: 'logs', type: FieldType.other },
 | 
			
		||||
      { name: 'references', type: FieldType.other },
 | 
			
		||||
      { name: 'tags', type: FieldType.other },
 | 
			
		||||
    ],
 | 
			
		||||
    meta: {
 | 
			
		||||
@@ -287,6 +307,7 @@ export function transformFromOTLP(
 | 
			
		||||
            duration: (span.endTimeUnixNano! - span.startTimeUnixNano!) / 1000000,
 | 
			
		||||
            tags: getSpanTags(span, librarySpan.instrumentationLibrary),
 | 
			
		||||
            logs: getLogs(span),
 | 
			
		||||
            references: getReferences(span),
 | 
			
		||||
          } as TraceSpanRow);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@@ -513,7 +534,7 @@ export function transformTrace(response: DataQueryResponse, nodeGraph = false):
 | 
			
		||||
 * Change fields which are json string into JS objects. Modifies the frame in place.
 | 
			
		||||
 */
 | 
			
		||||
function parseJsonFields(frame: DataFrame) {
 | 
			
		||||
  for (const fieldName of ['serviceTags', 'logs', 'tags']) {
 | 
			
		||||
  for (const fieldName of ['serviceTags', 'logs', 'tags', 'references']) {
 | 
			
		||||
    const field = frame.fields.find((f) => f.name === fieldName);
 | 
			
		||||
    if (field) {
 | 
			
		||||
      const fieldIndex = frame.fields.indexOf(field);
 | 
			
		||||
 
 | 
			
		||||
@@ -1923,6 +1923,16 @@ export const otlpDataFrameFromResponse = new MutableDataFrame({
 | 
			
		||||
        displayName: 'logs',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'references',
 | 
			
		||||
      type: 'other',
 | 
			
		||||
      config: {},
 | 
			
		||||
      labels: undefined,
 | 
			
		||||
      values: [[]],
 | 
			
		||||
      state: {
 | 
			
		||||
        displayName: 'references',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'tags',
 | 
			
		||||
      type: 'other',
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user