123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804 |
- import {action, computed, makeObservable, observable} from 'mobx';
- import {Client} from 'sentry/api';
- import {t} from 'sentry/locale';
- import {EventTransaction} from 'sentry/types/event';
- import {ActiveOperationFilter} from './filter';
- import {
- DescendantGroup,
- EnhancedProcessedSpanType,
- EnhancedSpan,
- FetchEmbeddedChildrenState,
- FilterSpans,
- FocusedSpanIDMap,
- OrphanTreeDepth,
- RawSpanType,
- SpanChildrenLookupType,
- SpanType,
- TraceBound,
- TreeDepthType,
- } from './types';
- import {
- generateRootSpan,
- getSiblingGroupKey,
- getSpanID,
- getSpanOperation,
- isEventFromBrowserJavaScriptSDK,
- isOrphanSpan,
- isSpanIdFocused,
- parseTrace,
- SpanBoundsType,
- SpanGeneratedBoundsType,
- } from './utils';
- export const MIN_SIBLING_GROUP_SIZE = 5;
- class SpanTreeModel {
- api: Client;
- // readonly state
- span: Readonly<SpanType>;
- children: Array<SpanTreeModel> = [];
- isRoot: boolean;
- // readable/writable state
- fetchEmbeddedChildrenState: FetchEmbeddedChildrenState = 'idle';
- showEmbeddedChildren: boolean = false;
- embeddedChildren: Array<SpanTreeModel> = [];
- isEmbeddedTransactionTimeAdjusted: boolean = false;
- // This controls if a chain of nested spans that are the only sibling to be visually grouped together or not.
- // On initial render, they're visually grouped together.
- isNestedSpanGroupExpanded: boolean = false;
- // Entries in this set will follow the format 'op.description'.
- // An entry in this set indicates that all siblings with the op and description should be left ungrouped
- expandedSiblingGroups: Set<string> = new Set();
- constructor(
- parentSpan: SpanType,
- childSpans: SpanChildrenLookupType,
- api: Client,
- isRoot: boolean = false
- ) {
- this.api = api;
- this.span = parentSpan;
- this.isRoot = isRoot;
- const spanID = getSpanID(parentSpan);
- const spanChildren: Array<RawSpanType> = childSpans?.[spanID] ?? [];
- // Mark descendents as being rendered. This is to address potential recursion issues due to malformed data.
- // For example if a span has a span_id that's identical to its parent_span_id.
- childSpans = {
- ...childSpans,
- };
- delete childSpans[spanID];
- this.children = spanChildren.map(span => {
- return new SpanTreeModel(span, childSpans, api);
- });
- makeObservable(this, {
- operationNameCounts: computed.struct,
- showEmbeddedChildren: observable,
- embeddedChildren: observable,
- fetchEmbeddedChildrenState: observable,
- toggleEmbeddedChildren: action,
- fetchEmbeddedTransactions: action,
- isNestedSpanGroupExpanded: observable,
- toggleNestedSpanGroup: action,
- expandedSiblingGroups: observable,
- toggleSiblingSpanGroup: action,
- isEmbeddedTransactionTimeAdjusted: observable,
- });
- }
- get operationNameCounts(): Map<string, number> {
- const result = new Map<string, number>();
- const operationName = this.span.op;
- if (typeof operationName === 'string' && operationName.length > 0) {
- result.set(operationName, 1);
- }
- for (const directChild of this.children) {
- const operationNameCounts = directChild.operationNameCounts;
- for (const [key, count] of operationNameCounts) {
- result.set(key, (result.get(key) ?? 0) + count);
- }
- }
- // sort alphabetically using case insensitive comparison
- return new Map(
- [...result].sort((a, b) =>
- String(a[0]).localeCompare(b[0], undefined, {sensitivity: 'base'})
- )
- );
- }
- isSpanFilteredOut = (
- props: {
- filterSpans: FilterSpans | undefined;
- operationNameFilters: ActiveOperationFilter;
- },
- spanModel: SpanTreeModel
- ): boolean => {
- const {operationNameFilters, filterSpans} = props;
- if (operationNameFilters.type === 'active_filter') {
- const operationName = getSpanOperation(spanModel.span);
- if (
- typeof operationName === 'string' &&
- !operationNameFilters.operationNames.has(operationName)
- ) {
- return true;
- }
- }
- if (!filterSpans) {
- return false;
- }
- return !filterSpans.spanIDs.has(getSpanID(spanModel.span));
- };
- generateSpanGap(
- event: Readonly<EventTransaction>,
- previousSiblingEndTimestamp: number | undefined,
- treeDepth: number,
- continuingTreeDepths: Array<TreeDepthType>
- ): EnhancedProcessedSpanType | undefined {
- // hide gap spans (i.e. "missing instrumentation" spans) for browser js transactions,
- // since they're not useful to indicate
- const shouldIncludeGap = !isEventFromBrowserJavaScriptSDK(event);
- const isValidGap =
- shouldIncludeGap &&
- typeof previousSiblingEndTimestamp === 'number' &&
- previousSiblingEndTimestamp < this.span.start_timestamp &&
- // gap is at least 100 ms
- this.span.start_timestamp - previousSiblingEndTimestamp >= 0.1;
- if (!isValidGap) {
- return undefined;
- }
- const gapSpan: EnhancedProcessedSpanType = {
- type: 'gap',
- span: {
- type: 'gap',
- start_timestamp: previousSiblingEndTimestamp || this.span.start_timestamp,
- timestamp: this.span.start_timestamp, // this is essentially end_timestamp
- description: t('Missing instrumentation'),
- isOrphan: isOrphanSpan(this.span),
- },
- numOfSpanChildren: 0,
- treeDepth,
- isLastSibling: false,
- continuingTreeDepths,
- fetchEmbeddedChildrenState: 'idle',
- showEmbeddedChildren: false,
- toggleEmbeddedChildren: undefined,
- isEmbeddedTransactionTimeAdjusted: this.isEmbeddedTransactionTimeAdjusted,
- };
- return gapSpan;
- }
- getSpansList = (props: {
- addTraceBounds: (bounds: TraceBound) => void;
- continuingTreeDepths: Array<TreeDepthType>;
- directParent: SpanTreeModel | null;
- event: Readonly<EventTransaction>;
- filterSpans: FilterSpans | undefined;
- generateBounds: (bounds: SpanBoundsType) => SpanGeneratedBoundsType;
- hiddenSpanSubTrees: Set<String>;
- isLastSibling: boolean;
- isNestedSpanGroupExpanded: boolean;
- isOnlySibling: boolean;
- operationNameFilters: ActiveOperationFilter;
- previousSiblingEndTimestamp: number | undefined;
- removeTraceBounds: (eventSlug: string) => void;
- spanAncestors: Set<String>;
- spanNestedGrouping: EnhancedSpan[] | undefined;
- toggleNestedSpanGroup: (() => void) | undefined;
- treeDepth: number;
- focusedSpanIds?: FocusedSpanIDMap;
- }): EnhancedProcessedSpanType[] => {
- const {
- operationNameFilters,
- generateBounds,
- isLastSibling,
- hiddenSpanSubTrees,
- // The set of ancestor span IDs whose sub-tree that the span belongs to
- spanAncestors,
- filterSpans,
- previousSiblingEndTimestamp,
- event,
- isOnlySibling,
- spanNestedGrouping,
- toggleNestedSpanGroup,
- isNestedSpanGroupExpanded,
- addTraceBounds,
- removeTraceBounds,
- focusedSpanIds,
- directParent,
- } = props;
- let {treeDepth, continuingTreeDepths} = props;
- const parentSpanID = getSpanID(this.span);
- const nextSpanAncestors = new Set(spanAncestors);
- nextSpanAncestors.add(parentSpanID);
- const descendantsSource = this.showEmbeddedChildren
- ? [...this.embeddedChildren, ...this.children]
- : this.children;
- if (focusedSpanIds && this.span.span_id in focusedSpanIds) {
- // Since this is a focused span, show this span's direct parent, and also its children
- directParent && focusedSpanIds[this.span.span_id].add(directParent.span.span_id);
- descendantsSource.forEach(descendant =>
- focusedSpanIds[this.span.span_id].add(descendant.span.span_id)
- );
- }
- const isNotLastSpanOfGroup =
- isOnlySibling && !this.isRoot && descendantsSource.length === 1;
- const shouldGroup = isNotLastSpanOfGroup;
- const hideSpanTree = hiddenSpanSubTrees.has(parentSpanID);
- const isLastSpanOfGroup =
- isOnlySibling && !this.isRoot && (descendantsSource.length !== 1 || hideSpanTree);
- const isFirstSpanOfGroup =
- shouldGroup &&
- (spanNestedGrouping === undefined ||
- (Array.isArray(spanNestedGrouping) && spanNestedGrouping.length === 0));
- if (
- isLastSpanOfGroup &&
- Array.isArray(spanNestedGrouping) &&
- spanNestedGrouping.length >= 1 &&
- !isNestedSpanGroupExpanded
- ) {
- // We always want to indent the last span of the span group chain
- treeDepth = treeDepth + 1;
- // For a collapsed span group chain to be useful, we prefer span groupings
- // that are two or more spans.
- // Since there is no concept of "backtracking" when constructing the span tree,
- // we will need to reconstruct the tree depth information. This is only neccessary
- // when the span group chain is hidden/collapsed.
- if (spanNestedGrouping.length === 1) {
- const treeDepthEntry = isOrphanSpan(spanNestedGrouping[0].span)
- ? ({type: 'orphan', depth: spanNestedGrouping[0].treeDepth} as OrphanTreeDepth)
- : spanNestedGrouping[0].treeDepth;
- if (!spanNestedGrouping[0].isLastSibling) {
- continuingTreeDepths = [...continuingTreeDepths, treeDepthEntry];
- }
- }
- }
- // Criteria for propagating information about the span group to the last span of the span group chain
- const spanGroupingCriteria =
- isLastSpanOfGroup &&
- Array.isArray(spanNestedGrouping) &&
- spanNestedGrouping.length > 1;
- const wrappedSpan: EnhancedSpan = {
- type: this.isRoot ? 'root_span' : 'span',
- span: this.span,
- numOfSpanChildren: descendantsSource.length,
- treeDepth,
- isLastSibling,
- continuingTreeDepths,
- fetchEmbeddedChildrenState: this.fetchEmbeddedChildrenState,
- showEmbeddedChildren: this.showEmbeddedChildren,
- toggleEmbeddedChildren: this.toggleEmbeddedChildren({
- addTraceBounds,
- removeTraceBounds,
- }),
- toggleNestedSpanGroup:
- spanGroupingCriteria && toggleNestedSpanGroup && !isNestedSpanGroupExpanded
- ? toggleNestedSpanGroup
- : isFirstSpanOfGroup && this.isNestedSpanGroupExpanded && !hideSpanTree
- ? this.toggleNestedSpanGroup
- : undefined,
- toggleSiblingSpanGroup: undefined,
- isEmbeddedTransactionTimeAdjusted: this.isEmbeddedTransactionTimeAdjusted,
- };
- if (wrappedSpan.type === 'root_span') {
- // @ts-expect-error
- delete wrappedSpan.toggleNestedSpanGroup;
- }
- const treeDepthEntry = isOrphanSpan(this.span)
- ? ({type: 'orphan', depth: treeDepth} as OrphanTreeDepth)
- : treeDepth;
- const shouldHideSpanOfGroup =
- shouldGroup &&
- !isLastSpanOfGroup &&
- ((toggleNestedSpanGroup === undefined && !this.isNestedSpanGroupExpanded) ||
- (toggleNestedSpanGroup !== undefined && !isNestedSpanGroupExpanded));
- const descendantContinuingTreeDepths =
- isLastSibling || shouldHideSpanOfGroup
- ? continuingTreeDepths
- : [...continuingTreeDepths, treeDepthEntry];
- for (const hiddenSpanSubTree of hiddenSpanSubTrees) {
- if (spanAncestors.has(hiddenSpanSubTree)) {
- // If this span is hidden, then all the descendants are hidden as well
- return [];
- }
- }
- const groupedDescendants: DescendantGroup[] = [];
- // Used to number sibling groups in case there are multiple groups with the same op and description
- const siblingGroupOccurrenceMap = {};
- const addGroupToMap = (prevSpanModel: SpanTreeModel, group: SpanTreeModel[]) => {
- if (!group.length) {
- return;
- }
- const groupKey = `${prevSpanModel.span.op}.${prevSpanModel.span.description}`;
- if (!siblingGroupOccurrenceMap[groupKey]) {
- siblingGroupOccurrenceMap[groupKey] = 1;
- } else {
- siblingGroupOccurrenceMap[groupKey] += 1;
- }
- groupedDescendants.push({
- group,
- occurrence: siblingGroupOccurrenceMap[groupKey],
- });
- };
- if (descendantsSource?.length >= MIN_SIBLING_GROUP_SIZE) {
- let prevSpanModel = descendantsSource[0];
- let currentGroup = [prevSpanModel];
- for (let i = 1; i < descendantsSource.length; i++) {
- const currSpanModel = descendantsSource[i];
- // We want to group siblings only if they share the same op and description, and if they have no children
- if (
- prevSpanModel.span.op === currSpanModel.span.op &&
- prevSpanModel.span.description === currSpanModel.span.description &&
- currSpanModel.children.length === 0
- ) {
- currentGroup.push(currSpanModel);
- } else {
- addGroupToMap(prevSpanModel, currentGroup);
- if (currSpanModel.children.length) {
- currentGroup = [currSpanModel];
- groupedDescendants.push({group: currentGroup});
- currentGroup = [];
- } else {
- currentGroup = [currSpanModel];
- }
- }
- prevSpanModel = currSpanModel;
- }
- addGroupToMap(prevSpanModel, currentGroup);
- } else if (descendantsSource.length >= 1) {
- groupedDescendants.push({group: descendantsSource});
- }
- const descendants = (hideSpanTree ? [] : groupedDescendants).reduce(
- (
- acc: {
- descendants: EnhancedProcessedSpanType[];
- previousSiblingEndTimestamp: number | undefined;
- },
- {group, occurrence},
- groupIndex
- ) => {
- // Groups less than 5 indicate that the spans should be left ungrouped
- if (group.length < MIN_SIBLING_GROUP_SIZE) {
- group.forEach((spanModel, index) => {
- acc.descendants.push(
- ...spanModel.getSpansList({
- operationNameFilters,
- generateBounds,
- treeDepth: shouldHideSpanOfGroup ? treeDepth : treeDepth + 1,
- isLastSibling:
- groupIndex === groupedDescendants.length - 1 &&
- index === group.length - 1,
- continuingTreeDepths: descendantContinuingTreeDepths,
- hiddenSpanSubTrees,
- spanAncestors: new Set(nextSpanAncestors),
- filterSpans,
- previousSiblingEndTimestamp: acc.previousSiblingEndTimestamp,
- event,
- isOnlySibling: descendantsSource.length === 1,
- spanNestedGrouping: shouldGroup
- ? [...(spanNestedGrouping ?? []), wrappedSpan]
- : undefined,
- toggleNestedSpanGroup: isNotLastSpanOfGroup
- ? toggleNestedSpanGroup === undefined
- ? this.toggleNestedSpanGroup
- : toggleNestedSpanGroup
- : undefined,
- isNestedSpanGroupExpanded: isNotLastSpanOfGroup
- ? toggleNestedSpanGroup === undefined
- ? this.isNestedSpanGroupExpanded
- : isNestedSpanGroupExpanded
- : false,
- addTraceBounds,
- removeTraceBounds,
- focusedSpanIds,
- directParent: this,
- })
- );
- acc.previousSiblingEndTimestamp = spanModel.span.timestamp;
- });
- return acc;
- }
- // NOTE: I am making the assumption here that grouped sibling spans will not have children.
- // By making this assumption, I can immediately wrap the grouped spans here without having
- // to recursively traverse them.
- // This may not be the case, and needs to be looked into later
- const key = getSiblingGroupKey(group[0].span, occurrence);
- if (this.expandedSiblingGroups.has(key)) {
- // This check is needed here, since it is possible that a user could be filtering for a specific span ID.
- // In this case, we must add only the specified span into the accumulator's descendants
- group.forEach((spanModel, index) => {
- if (
- this.isSpanFilteredOut(props, spanModel) ||
- (focusedSpanIds && !isSpanIdFocused(spanModel.span.span_id, focusedSpanIds))
- ) {
- acc.descendants.push({
- type: 'filtered_out',
- span: spanModel.span,
- });
- } else {
- const enhancedSibling: EnhancedSpan = {
- type: 'span',
- span: spanModel.span,
- numOfSpanChildren: 0,
- treeDepth: treeDepth + 1,
- isLastSibling:
- index === group.length - 1 &&
- groupIndex === groupedDescendants.length - 1,
- isFirstSiblingOfGroup: index === 0,
- groupOccurrence: occurrence,
- continuingTreeDepths: descendantContinuingTreeDepths,
- fetchEmbeddedChildrenState: spanModel.fetchEmbeddedChildrenState,
- showEmbeddedChildren: spanModel.showEmbeddedChildren,
- toggleEmbeddedChildren: spanModel.toggleEmbeddedChildren({
- addTraceBounds,
- removeTraceBounds,
- }),
- toggleNestedSpanGroup: undefined,
- toggleSiblingSpanGroup:
- index === 0 ? this.toggleSiblingSpanGroup : undefined,
- isEmbeddedTransactionTimeAdjusted:
- spanModel.isEmbeddedTransactionTimeAdjusted,
- };
- acc.previousSiblingEndTimestamp = spanModel.span.timestamp;
- acc.descendants.push(enhancedSibling);
- }
- });
- return acc;
- }
- // Since we are not recursively traversing elements in this group, need to check
- // if the spans are filtered or out of bounds here
- if (this.isSpanFilteredOut(props, group[0])) {
- group.forEach(spanModel => {
- acc.descendants.push({
- type: 'filtered_out',
- span: spanModel.span,
- });
- });
- return acc;
- }
- // TODO: Check within the group if any of the focusedSpanIDs are present
- const bounds = generateBounds({
- startTimestamp: group[0].span.start_timestamp,
- endTimestamp: group[group.length - 1].span.timestamp,
- });
- if (!bounds.isSpanVisibleInView) {
- group.forEach(spanModel =>
- acc.descendants.push({
- type: 'out_of_view',
- span: spanModel.span,
- })
- );
- return acc;
- }
- // Since the group is not expanded, return a singular grouped span bar
- const wrappedSiblings: EnhancedSpan[] = group.map((spanModel, index) => {
- const enhancedSibling: EnhancedSpan = {
- type: 'span',
- span: spanModel.span,
- numOfSpanChildren: 0,
- treeDepth: treeDepth + 1,
- isLastSibling:
- index === group.length - 1 && groupIndex === groupedDescendants.length - 1,
- isFirstSiblingOfGroup: index === 0,
- groupOccurrence: occurrence,
- continuingTreeDepths: descendantContinuingTreeDepths,
- fetchEmbeddedChildrenState: spanModel.fetchEmbeddedChildrenState,
- showEmbeddedChildren: spanModel.showEmbeddedChildren,
- toggleEmbeddedChildren: spanModel.toggleEmbeddedChildren({
- addTraceBounds,
- removeTraceBounds,
- }),
- toggleNestedSpanGroup: undefined,
- toggleSiblingSpanGroup: index === 0 ? this.toggleSiblingSpanGroup : undefined,
- isEmbeddedTransactionTimeAdjusted:
- spanModel.isEmbeddedTransactionTimeAdjusted,
- };
- return enhancedSibling;
- });
- const groupedSiblingsSpan: EnhancedProcessedSpanType = {
- type: 'span_group_siblings',
- span: this.span,
- treeDepth: treeDepth + 1,
- continuingTreeDepths: descendantContinuingTreeDepths,
- spanSiblingGrouping: wrappedSiblings,
- isLastSibling: groupIndex === groupedDescendants.length - 1,
- occurrence: occurrence ?? 0,
- toggleSiblingSpanGroup: this.toggleSiblingSpanGroup,
- };
- acc.previousSiblingEndTimestamp =
- wrappedSiblings[wrappedSiblings.length - 1].span.timestamp;
- acc.descendants.push(groupedSiblingsSpan);
- return acc;
- },
- {
- descendants: [],
- previousSiblingEndTimestamp: undefined,
- }
- ).descendants;
- if (
- this.isSpanFilteredOut(props, this) ||
- (focusedSpanIds && !isSpanIdFocused(this.span.span_id, focusedSpanIds))
- ) {
- return [
- {
- type: 'filtered_out',
- span: this.span,
- },
- ...descendants,
- ];
- }
- const bounds = generateBounds({
- startTimestamp: this.span.start_timestamp,
- endTimestamp: this.span.timestamp,
- });
- const isCurrentSpanOutOfView = !bounds.isSpanVisibleInView;
- if (isCurrentSpanOutOfView) {
- return [
- {
- type: 'out_of_view',
- span: this.span,
- },
- ...descendants,
- ];
- }
- if (shouldHideSpanOfGroup) {
- return [...descendants];
- }
- if (
- isLastSpanOfGroup &&
- Array.isArray(spanNestedGrouping) &&
- spanNestedGrouping.length > 1 &&
- !isNestedSpanGroupExpanded &&
- wrappedSpan.type === 'span'
- ) {
- const spanGroupChain: EnhancedProcessedSpanType = {
- type: 'span_group_chain',
- span: this.span,
- treeDepth: treeDepth - 1,
- continuingTreeDepths,
- spanNestedGrouping,
- isNestedSpanGroupExpanded,
- toggleNestedSpanGroup: wrappedSpan.toggleNestedSpanGroup,
- toggleSiblingSpanGroup: undefined,
- };
- return [
- spanGroupChain,
- {...wrappedSpan, toggleNestedSpanGroup: undefined},
- ...descendants,
- ];
- }
- if (
- isFirstSpanOfGroup &&
- this.isNestedSpanGroupExpanded &&
- !hideSpanTree &&
- descendants.length <= 1 &&
- wrappedSpan.type === 'span'
- ) {
- // If we know the descendants will be one span or less, we remove the "regroup" feature (therefore hide it)
- // by setting toggleNestedSpanGroup to be undefined for the first span of the group chain.
- wrappedSpan.toggleNestedSpanGroup = undefined;
- }
- // Do not autogroup groups that will only have two spans
- if (
- isLastSpanOfGroup &&
- Array.isArray(spanNestedGrouping) &&
- spanNestedGrouping.length === 1
- ) {
- if (!isNestedSpanGroupExpanded) {
- const parentSpan = spanNestedGrouping[0].span;
- const parentSpanBounds = generateBounds({
- startTimestamp: parentSpan.start_timestamp,
- endTimestamp: parentSpan.timestamp,
- });
- const isParentSpanOutOfView = !parentSpanBounds.isSpanVisibleInView;
- if (!isParentSpanOutOfView) {
- return [spanNestedGrouping[0], wrappedSpan, ...descendants];
- }
- }
- return [wrappedSpan, ...descendants];
- }
- const gapSpan = this.generateSpanGap(
- event,
- previousSiblingEndTimestamp,
- treeDepth,
- continuingTreeDepths
- );
- if (gapSpan) {
- return [gapSpan, wrappedSpan, ...descendants];
- }
- return [wrappedSpan, ...descendants];
- };
- toggleEmbeddedChildren =
- ({
- addTraceBounds,
- removeTraceBounds,
- }: {
- addTraceBounds: (bounds: TraceBound) => void;
- removeTraceBounds: (eventSlug: string) => void;
- }) =>
- (props: {eventSlug: string; orgSlug: string}) => {
- this.showEmbeddedChildren = !this.showEmbeddedChildren;
- this.fetchEmbeddedChildrenState = 'idle';
- if (!this.showEmbeddedChildren) {
- if (this.embeddedChildren.length > 0) {
- this.embeddedChildren.forEach(child => {
- removeTraceBounds(child.generateTraceBounds().spanId);
- });
- }
- }
- if (this.showEmbeddedChildren) {
- if (this.embeddedChildren.length === 0) {
- return this.fetchEmbeddedTransactions({...props, addTraceBounds});
- }
- this.embeddedChildren.forEach(child => {
- addTraceBounds(child.generateTraceBounds());
- });
- }
- return Promise.resolve(undefined);
- };
- fetchEmbeddedTransactions({
- orgSlug,
- eventSlug,
- addTraceBounds,
- }: {
- addTraceBounds: (bounds: TraceBound) => void;
- eventSlug: string;
- orgSlug: string;
- }) {
- const url = `/organizations/${orgSlug}/events/${eventSlug}/`;
- this.fetchEmbeddedChildrenState = 'loading_embedded_transactions';
- return this.api
- .requestPromise(url, {
- method: 'GET',
- query: {},
- })
- .then(
- action('fetchEmbeddedTransactionsSuccess', (event: EventTransaction) => {
- if (!event) {
- return;
- }
- const parsedTrace = parseTrace(event);
- // We need to adjust the timestamps for this embedded transaction only if it is not within the bounds of its parent span
- if (
- parsedTrace.traceStartTimestamp < this.span.start_timestamp ||
- parsedTrace.traceEndTimestamp > this.span.timestamp
- ) {
- const startTimeDelta =
- this.span.start_timestamp - parsedTrace.traceStartTimestamp;
- parsedTrace.traceStartTimestamp += startTimeDelta;
- parsedTrace.traceEndTimestamp += startTimeDelta;
- parsedTrace.spans.forEach(span => {
- span.start_timestamp += startTimeDelta;
- span.timestamp += startTimeDelta;
- });
- this.isEmbeddedTransactionTimeAdjusted = true;
- }
- const rootSpan = generateRootSpan(parsedTrace);
- const parsedRootSpan = new SpanTreeModel(
- rootSpan,
- parsedTrace.childSpans,
- this.api,
- false
- );
- this.embeddedChildren = [parsedRootSpan];
- this.fetchEmbeddedChildrenState = 'idle';
- addTraceBounds(parsedRootSpan.generateTraceBounds());
- })
- )
- .catch(
- action('fetchEmbeddedTransactionsError', () => {
- this.embeddedChildren = [];
- this.fetchEmbeddedChildrenState = 'error_fetching_embedded_transactions';
- })
- );
- }
- toggleNestedSpanGroup = () => {
- this.isNestedSpanGroupExpanded = !this.isNestedSpanGroupExpanded;
- };
- toggleSiblingSpanGroup = (span: SpanType, occurrence?: number) => {
- const key = getSiblingGroupKey(span, occurrence);
- if (this.expandedSiblingGroups.has(key)) {
- this.expandedSiblingGroups.delete(key);
- } else {
- this.expandedSiblingGroups.add(key);
- }
- };
- generateTraceBounds = (): TraceBound => {
- return {
- spanId: this.span.span_id,
- traceStartTimestamp: this.span.start_timestamp,
- traceEndTimestamp: this.span.timestamp,
- };
- };
- }
- export default SpanTreeModel;
|