@localstack/appinspector-ui 1.0.185 → 1.0.187

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -13103,7 +13103,7 @@ var ResponsiveTimestampCell = (0, import_react31.memo)(({ date }) => {
13103
13103
 
13104
13104
  // src/dev-settings.tsx
13105
13105
  var React5 = __toESM(require("react"), 1);
13106
- var DEFAULT_DEV_SETTINGS = { requestTerms: "on", waterfallIssueBackgrounds: "on", waterfallLayout: "tree", waterfallStatusIcons: "first column" };
13106
+ var DEFAULT_DEV_SETTINGS = { requestTerms: "on", warningIcon: "iam shield", waterfallIssueBackgrounds: "on", waterfallLayout: "tree", waterfallStatusIcons: "first column" };
13107
13107
  var DevSettingsContext = React5.createContext(DEFAULT_DEV_SETTINGS);
13108
13108
  var useDevSettings = () => React5.useContext(DevSettingsContext);
13109
13109
 
@@ -15355,22 +15355,6 @@ ServiceCell.displayName = "ServiceCell";
15355
15355
  var import_icons_material2 = require("@mui/icons-material");
15356
15356
  var import_material5 = require("@mui/material");
15357
15357
 
15358
- // src/types/error-levels.ts
15359
- var ErrorLevel = {
15360
- ERROR: 3,
15361
- // General error
15362
- IAM_PERMISSION: 5,
15363
- // IAM permission issues
15364
- OK: 2,
15365
- // OK
15366
- // Standard OTEL status codes
15367
- UNSET: 1,
15368
- // Unset
15369
- // Extended custom levels
15370
- WARNING: 4
15371
- // Warning
15372
- };
15373
-
15374
15358
  // src/utils/iam-utils.ts
15375
15359
  function determinePermissionStatus(payload) {
15376
15360
  if (payload.explicit_deny_count && payload.explicit_deny_count > 0) {
@@ -15469,58 +15453,40 @@ var getIamSummary = (span) => {
15469
15453
  return { denials, level: void 0 };
15470
15454
  };
15471
15455
 
15472
- // src/utils/error-level.ts
15473
- var calculateHighestErrorLevel = (span) => {
15474
- if (span.operation_status) {
15475
- switch (span.operation_status) {
15476
- case "error": {
15477
- return ErrorLevel.ERROR;
15478
- }
15479
- case "iam_explicit_deny":
15480
- case "iam_explicit_warn":
15481
- case "iam_implicit_deny":
15482
- case "iam_implicit_warn": {
15483
- return ErrorLevel.IAM_PERMISSION;
15484
- }
15485
- default: {
15486
- return ErrorLevel.OK;
15487
- }
15488
- }
15456
+ // src/utils/span-issues.ts
15457
+ var firstText = (...values) => values.find((value) => !!value) ?? void 0;
15458
+ var OTEL_STATUS_ERROR = 2;
15459
+ var getIamDenials = (span) => span.events.flatMap((event) => {
15460
+ if (!event.event_type?.toLowerCase().includes("iam") || typeof event.attributes?.payload !== "string") {
15461
+ return [];
15489
15462
  }
15490
- const hasIamDenial = span.events.some((event) => {
15491
- if (!event.event_type?.toLowerCase().includes("iam")) return false;
15492
- if (typeof event.attributes?.payload !== "string") return false;
15493
- const parsed = parseIAMEvent(event.attributes.payload);
15494
- return parsed?.permission === "explicitly_denied" || parsed?.permission === "implicitly_denied";
15495
- });
15496
- if (hasIamDenial) return ErrorLevel.IAM_PERMISSION;
15497
- if (span.status_code === 2) return ErrorLevel.ERROR;
15498
- const hasErrorInErrors = span.errors?.some((error) => error.level === "error");
15499
- if (hasErrorInErrors) return ErrorLevel.ERROR;
15500
- const hasWarning = span.errors?.some((error) => error.level === "warning");
15501
- if (hasWarning) return ErrorLevel.WARNING;
15502
- return span.status_code || ErrorLevel.UNSET;
15463
+ const permission = parseIAMEvent(event.attributes.payload)?.permission;
15464
+ if (permission === "explicitly_denied") return ["explicit"];
15465
+ if (permission === "implicitly_denied") return ["implicit"];
15466
+ return [];
15467
+ });
15468
+ var getSpanIssues = (span) => {
15469
+ const status = span.operation_status;
15470
+ const iamDenied = status === "iam_explicit_deny" || status === "iam_implicit_deny";
15471
+ const iamWarned = status === "iam_explicit_warn" || status === "iam_implicit_warn";
15472
+ const errorMessages = (span.errors ?? []).filter((item) => item.level === "error");
15473
+ const warningMessages = (span.errors ?? []).filter((item) => item.level === "warning");
15474
+ const denials = getIamDenials(span);
15475
+ const unenforcedDenial = !iamDenied && (denials.length > 0 || span.attributes?.["localstack.has_iam_deny_event"] === true);
15476
+ const error = status === "error" || iamDenied || span.status_code === OTEL_STATUS_ERROR || errorMessages.length > 0;
15477
+ const warning2 = iamWarned || warningMessages.length > 0 || unenforcedDenial;
15478
+ const denialCount = unenforcedDenial ? Math.max(1, denials.length) : 0;
15479
+ return {
15480
+ error,
15481
+ errorType: error ? firstText(span.status_message, errorMessages[0]?.message) ?? (iamDenied ? "AccessDenied" : "Error") : void 0,
15482
+ warning: warning2,
15483
+ warningCount: warning2 ? Math.max(1, warningMessages.length + denialCount) : 0,
15484
+ warningType: warning2 ? firstText(warningMessages[0]?.message) ?? (unenforcedDenial ? `IAM ${denials[0] ?? "implicit"} deny` : iamWarned ? "IAM permission warning" : "Warning") : void 0
15485
+ };
15503
15486
  };
15504
15487
 
15505
15488
  // src/components/cells/status-cell.tsx
15506
15489
  var import_jsx_runtime125 = require("react/jsx-runtime");
15507
- var StatusIcon = ({ errorLevel, operationStatus, spanStatusCode }) => {
15508
- switch (errorLevel) {
15509
- case ErrorLevel.ERROR: {
15510
- return /* @__PURE__ */ (0, import_jsx_runtime125.jsx)(import_icons_material2.Cancel, { sx: { color: "red" } });
15511
- }
15512
- case ErrorLevel.IAM_PERMISSION: {
15513
- const isDenial = operationStatus ? operationStatus === "iam_explicit_deny" || operationStatus === "iam_implicit_deny" : spanStatusCode === 2;
15514
- return /* @__PURE__ */ (0, import_jsx_runtime125.jsx)(import_icons_material2.GppBad, { sx: { color: isDenial ? "red" : "warning.main" } });
15515
- }
15516
- case ErrorLevel.WARNING: {
15517
- return /* @__PURE__ */ (0, import_jsx_runtime125.jsx)(import_icons_material2.Error, { sx: { color: "orange" } });
15518
- }
15519
- default: {
15520
- return /* @__PURE__ */ (0, import_jsx_runtime125.jsx)(import_jsx_runtime125.Fragment, {});
15521
- }
15522
- }
15523
- };
15524
15490
  var SPAN_STATUS_LABELS = {
15525
15491
  0: "Unset",
15526
15492
  1: "OK",
@@ -15548,9 +15514,14 @@ var buildTooltipContent = (span) => {
15548
15514
  ] });
15549
15515
  };
15550
15516
  var StatusIconWithTooltip = ({ span }) => {
15551
- const errorLevel = calculateHighestErrorLevel(span);
15552
- if (errorLevel !== ErrorLevel.ERROR && errorLevel !== ErrorLevel.IAM_PERMISSION) return /* @__PURE__ */ (0, import_jsx_runtime125.jsx)(import_jsx_runtime125.Fragment, {});
15553
- return /* @__PURE__ */ (0, import_jsx_runtime125.jsx)(import_material5.Tooltip, { title: buildTooltipContent(span), children: /* @__PURE__ */ (0, import_jsx_runtime125.jsx)(import_material5.Box, { sx: { alignItems: "center", cursor: "default", display: "flex" }, children: /* @__PURE__ */ (0, import_jsx_runtime125.jsx)(StatusIcon, { errorLevel, operationStatus: span.operation_status, spanStatusCode: span.status_code }) }) });
15517
+ const { warningIcon } = useDevSettings();
15518
+ const { error, warning: warning2 } = getSpanIssues(span);
15519
+ if (!error && !warning2) return /* @__PURE__ */ (0, import_jsx_runtime125.jsx)(import_jsx_runtime125.Fragment, {});
15520
+ const iamLevel = getIamSummary(span).level;
15521
+ return /* @__PURE__ */ (0, import_jsx_runtime125.jsx)(import_material5.Tooltip, { title: buildTooltipContent(span), children: /* @__PURE__ */ (0, import_jsx_runtime125.jsxs)(import_material5.Box, { sx: { alignItems: "center", cursor: "default", display: "flex" }, children: [
15522
+ error && /* @__PURE__ */ (0, import_jsx_runtime125.jsx)(import_material5.Box, { "aria-label": "Error", role: "img", sx: { display: "flex" }, children: iamLevel === "error" ? /* @__PURE__ */ (0, import_jsx_runtime125.jsx)(import_icons_material2.GppBad, { sx: { color: "error.main" } }) : /* @__PURE__ */ (0, import_jsx_runtime125.jsx)(import_icons_material2.Cancel, { sx: { color: "error.main" } }) }),
15523
+ warning2 && /* @__PURE__ */ (0, import_jsx_runtime125.jsx)(import_material5.Box, { "aria-label": "Warning", role: "img", sx: { display: "flex" }, children: iamLevel === "warning" && warningIcon === "iam shield" ? /* @__PURE__ */ (0, import_jsx_runtime125.jsx)(import_icons_material2.GppBad, { sx: { color: "warning.main" } }) : /* @__PURE__ */ (0, import_jsx_runtime125.jsx)(import_icons_material2.Error, { sx: { color: "warning.main" } }) })
15524
+ ] }) });
15554
15525
  };
15555
15526
 
15556
15527
  // src/components/spans-list/span-count-badge.tsx
@@ -16635,6 +16606,94 @@ var StatusMessage = ({
16635
16606
  return /* @__PURE__ */ (0, import_jsx_runtime130.jsx)(import_jsx_runtime130.Fragment, {});
16636
16607
  };
16637
16608
 
16609
+ // src/utils/span-parents.ts
16610
+ var resolveParents = (spans) => {
16611
+ const byId = new Map(spans.map((span) => [span.span_id, span]));
16612
+ const parentOf = /* @__PURE__ */ new Map();
16613
+ for (const span of spans) {
16614
+ const parent = span.parent_span_id === null ? void 0 : byId.get(span.parent_span_id);
16615
+ if (parent && parent.span_id !== span.span_id && parent.trace_id === span.trace_id) {
16616
+ parentOf.set(span.span_id, parent.span_id);
16617
+ }
16618
+ }
16619
+ const causes = /* @__PURE__ */ new Map();
16620
+ const addCause = (spanId, causeId) => {
16621
+ const list = causes.get(spanId);
16622
+ if (list) list.push(causeId);
16623
+ else causes.set(spanId, [causeId]);
16624
+ };
16625
+ for (const span of spans) {
16626
+ for (const causeId of span.outgoing_link_span_ids ?? []) addCause(span.span_id, causeId);
16627
+ for (const effectId of span.incoming_link_span_ids ?? []) addCause(effectId, span.span_id);
16628
+ }
16629
+ const isAncestorOrSelf = (candidateId, spanId) => {
16630
+ let current = spanId;
16631
+ for (let hops = 0; current !== void 0 && hops <= spans.length; hops++) {
16632
+ if (current === candidateId) return true;
16633
+ current = parentOf.get(current);
16634
+ }
16635
+ return false;
16636
+ };
16637
+ for (const span of spans) {
16638
+ if (parentOf.has(span.span_id)) continue;
16639
+ const cause = causes.get(span.span_id)?.find((causeId) => byId.has(causeId) && !isAncestorOrSelf(span.span_id, causeId));
16640
+ if (cause !== void 0) parentOf.set(span.span_id, cause);
16641
+ }
16642
+ return parentOf;
16643
+ };
16644
+
16645
+ // src/components/trace-panel/timeline-tree.ts
16646
+ var compareNano = (left, right) => {
16647
+ const difference = BigInt(left) - BigInt(right);
16648
+ if (difference === 0n) return 0;
16649
+ return difference < 0n ? -1 : 1;
16650
+ };
16651
+ var buildTraceTree = (spans) => {
16652
+ const nodes2 = new Map(spans.map((span) => [span.span_id, { children: [], span }]));
16653
+ const parentOf = resolveParents(spans);
16654
+ const roots = [];
16655
+ for (const [spanId, node] of nodes2) {
16656
+ const parent = parentOf.get(spanId);
16657
+ if (parent === void 0) roots.push(node);
16658
+ else nodes2.get(parent)?.children.push(node);
16659
+ }
16660
+ const byStart = (left, right) => compareNano(left.span.start_time_unix_nano, right.span.start_time_unix_nano);
16661
+ roots.sort(byStart);
16662
+ for (const node of nodes2.values()) node.children.sort(byStart);
16663
+ return { parentOf, roots };
16664
+ };
16665
+ var findRootSpan = (spans, parentOf, spanId) => {
16666
+ let current = spanId;
16667
+ for (let hops = 0; hops <= spans.length; hops++) {
16668
+ const parent = parentOf.get(current);
16669
+ if (parent === void 0) break;
16670
+ current = parent;
16671
+ }
16672
+ return spans.find((span) => span.span_id === current);
16673
+ };
16674
+ var findFirstSpan = (nodes2, predicate) => {
16675
+ for (const node of nodes2) {
16676
+ if (predicate(node.span.span_id)) return node.span;
16677
+ const found = findFirstSpan(node.children, predicate);
16678
+ if (found) return found;
16679
+ }
16680
+ return void 0;
16681
+ };
16682
+ var getTraceBounds = (spans) => {
16683
+ let start;
16684
+ let end;
16685
+ for (const span of spans) {
16686
+ const spanStart = BigInt(span.start_time_unix_nano);
16687
+ const spanEnd = span.end_time_unix_nano ? BigInt(span.end_time_unix_nano) : spanStart;
16688
+ if (start === void 0 || spanStart < start) start = spanStart;
16689
+ if (end === void 0 || spanEnd > end) end = spanEnd;
16690
+ }
16691
+ if (start === void 0 || end === void 0) {
16692
+ return { durationMs: 0, startNano: 0n };
16693
+ }
16694
+ return { durationMs: Number(end - start) / 1e6, startNano: start };
16695
+ };
16696
+
16638
16697
  // src/components/trace-panel/trace-sidebar.tsx
16639
16698
  var import_icons_material15 = require("@mui/icons-material");
16640
16699
  var import_material25 = require("@mui/material");
@@ -16754,38 +16813,6 @@ var getEventGroupsByType = (events) => {
16754
16813
  return groups;
16755
16814
  };
16756
16815
 
16757
- // src/utils/span-issues.ts
16758
- var firstText = (...values) => values.find((value) => !!value) ?? void 0;
16759
- var OTEL_STATUS_ERROR = 2;
16760
- var getIamDenials = (span) => span.events.flatMap((event) => {
16761
- if (!event.event_type?.toLowerCase().includes("iam") || typeof event.attributes?.payload !== "string") {
16762
- return [];
16763
- }
16764
- const permission = parseIAMEvent(event.attributes.payload)?.permission;
16765
- if (permission === "explicitly_denied") return ["explicit"];
16766
- if (permission === "implicitly_denied") return ["implicit"];
16767
- return [];
16768
- });
16769
- var getSpanIssues = (span) => {
16770
- const status = span.operation_status;
16771
- const iamDenied = status === "iam_explicit_deny" || status === "iam_implicit_deny";
16772
- const iamWarned = status === "iam_explicit_warn" || status === "iam_implicit_warn";
16773
- const errorMessages = (span.errors ?? []).filter((item) => item.level === "error");
16774
- const warningMessages = (span.errors ?? []).filter((item) => item.level === "warning");
16775
- const denials = getIamDenials(span);
16776
- const unenforcedDenial = !iamDenied && (denials.length > 0 || span.attributes?.["localstack.has_iam_deny_event"] === true);
16777
- const error = status === "error" || iamDenied || span.status_code === OTEL_STATUS_ERROR || errorMessages.length > 0;
16778
- const warning2 = iamWarned || warningMessages.length > 0 || unenforcedDenial;
16779
- const denialCount = unenforcedDenial ? Math.max(1, denials.length) : 0;
16780
- return {
16781
- error,
16782
- errorType: error ? firstText(span.status_message, errorMessages[0]?.message) ?? (iamDenied ? "AccessDenied" : "Error") : void 0,
16783
- warning: warning2,
16784
- warningCount: warning2 ? Math.max(1, warningMessages.length + denialCount) : 0,
16785
- warningType: warning2 ? firstText(warningMessages[0]?.message) ?? (unenforcedDenial ? `IAM ${denials[0] ?? "implicit"} deny` : iamWarned ? "IAM permission warning" : "Warning") : void 0
16786
- };
16787
- };
16788
-
16789
16816
  // src/utils/time.ts
16790
16817
  function unixNanoToDate(unixNanoString) {
16791
16818
  const milliseconds = unixNanoToMilliseconds(unixNanoString);
@@ -16834,7 +16861,7 @@ var import_react44 = require("react");
16834
16861
  var import_icons_material8 = require("@mui/icons-material");
16835
16862
  var import_material12 = require("@mui/material");
16836
16863
  var import_jsx_runtime132 = require("react/jsx-runtime");
16837
- var StatusIcon2 = ({ errorLevel }) => {
16864
+ var StatusIcon = ({ errorLevel }) => {
16838
16865
  switch (errorLevel) {
16839
16866
  case EventLevel.LevelError: {
16840
16867
  return /* @__PURE__ */ (0, import_jsx_runtime132.jsx)(import_icons_material8.Cancel, { sx: { color: "red" } });
@@ -18935,7 +18962,7 @@ var EventDetail = ({
18935
18962
  const eventLevel = getEventLevelFromGroup(type);
18936
18963
  return /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)(import_material17.Box, { children: [
18937
18964
  /* @__PURE__ */ (0, import_jsx_runtime137.jsxs)(import_material17.Box, { sx: { alignItems: "center", display: "flex", mb: 0.5 }, children: [
18938
- /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(StatusIcon2, { errorLevel: eventLevel }),
18965
+ /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(StatusIcon, { errorLevel: eventLevel }),
18939
18966
  /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_material17.Typography, { color: "text.secondary", sx: { fontWeight: 600, ml: 0.5 }, variant: "caption", children: displayText })
18940
18967
  ] }),
18941
18968
  /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_material17.Box, { children: events.map((event) => /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(import_material17.Box, { sx: { maxWidth: "100%", mb: 0.5 }, children: /* @__PURE__ */ (0, import_jsx_runtime137.jsx)(EventMessage, { event }) }, `${event.span_id}-${event.event_id}`)) })
@@ -31584,8 +31611,9 @@ var handleStyle = {
31584
31611
  borderColor: "lightgray",
31585
31612
  opacity: 0
31586
31613
  };
31587
- var NodeContent = ({ event, expanded, hasPermissionEvents, isError, isWarning }) => {
31614
+ var NodeContent = ({ event, expanded, iamLevel, issues }) => {
31588
31615
  const theme = (0, import_material20.useTheme)();
31616
+ const { warningIcon } = useDevSettings();
31589
31617
  return /* @__PURE__ */ (0, import_jsx_runtime143.jsxs)(import_jsx_runtime143.Fragment, { children: [
31590
31618
  /* @__PURE__ */ (0, import_jsx_runtime143.jsx)(import_react52.Handle, { position: import_react52.Position.Left, style: handleStyle, type: "target" }),
31591
31619
  /* @__PURE__ */ (0, import_jsx_runtime143.jsx)(import_react52.Handle, { position: import_react52.Position.Right, style: handleStyle, type: "source" }),
@@ -31640,8 +31668,8 @@ var NodeContent = ({ event, expanded, hasPermissionEvents, isError, isWarning })
31640
31668
  width: "38px"
31641
31669
  },
31642
31670
  children: [
31643
- (isError || isWarning) && hasPermissionEvents && /* @__PURE__ */ (0, import_jsx_runtime143.jsx)(import_material20.Tooltip, { arrow: true, title: "Permission denied", children: /* @__PURE__ */ (0, import_jsx_runtime143.jsx)(import_icons_material12.GppBad, { sx: { color: isError ? theme.palette.error.main : theme.palette.warning.main, fontSize: "16px" } }) }),
31644
- isError && !hasPermissionEvents && /* @__PURE__ */ (0, import_jsx_runtime143.jsx)(import_material20.Tooltip, { arrow: true, title: "Error", children: /* @__PURE__ */ (0, import_jsx_runtime143.jsx)(import_icons_material12.Cancel, { sx: { color: theme.palette.error.main, fontSize: "16px" } }) })
31671
+ issues.error && /* @__PURE__ */ (0, import_jsx_runtime143.jsx)(import_material20.Tooltip, { arrow: true, title: issues.errorType, children: /* @__PURE__ */ (0, import_jsx_runtime143.jsx)(import_material20.Box, { "aria-label": "Error", role: "img", sx: { display: "flex" }, children: iamLevel === "error" ? /* @__PURE__ */ (0, import_jsx_runtime143.jsx)(import_icons_material12.GppBad, { sx: { color: theme.palette.error.main, fontSize: "16px" } }) : /* @__PURE__ */ (0, import_jsx_runtime143.jsx)(import_icons_material12.Cancel, { sx: { color: theme.palette.error.main, fontSize: "16px" } }) }) }),
31672
+ issues.warning && /* @__PURE__ */ (0, import_jsx_runtime143.jsx)(import_material20.Tooltip, { arrow: true, title: issues.warningType, children: /* @__PURE__ */ (0, import_jsx_runtime143.jsx)(import_material20.Box, { "aria-label": "Warning", role: "img", sx: { display: "flex" }, children: iamLevel === "warning" && warningIcon === "iam shield" ? /* @__PURE__ */ (0, import_jsx_runtime143.jsx)(import_icons_material12.GppBad, { sx: { color: theme.palette.warning.main, fontSize: "16px" } }) : /* @__PURE__ */ (0, import_jsx_runtime143.jsx)(import_icons_material12.Error, { sx: { color: theme.palette.warning.main, fontSize: "16px" } }) }) })
31645
31673
  ]
31646
31674
  }
31647
31675
  ),
@@ -31664,17 +31692,9 @@ var ServiceNode = (0, import_react53.memo)(({ data, selected }) => {
31664
31692
  const theme = (0, import_material20.useTheme)();
31665
31693
  const [isHovered, setIsHovered] = (0, import_react53.useState)(false);
31666
31694
  const nodeRef = (0, import_react53.useRef)(null);
31667
- const { operation_status } = data.event;
31668
- const hasPermissionEventsLegacy = (0, import_react53.useMemo)(
31669
- () => data.event.events.some(
31670
- (event) => event.event_type === "iam.policy_evaluation" && event.name === "iam.policy.denied"
31671
- ),
31672
- [data.event.events]
31673
- );
31674
- const isError = operation_status ? operation_status === "error" || operation_status === "iam_explicit_deny" || operation_status === "iam_implicit_deny" : data.event.status_code === 2;
31675
- const isWarning = operation_status ? operation_status === "iam_explicit_warn" || operation_status === "iam_implicit_warn" : !isError && data.event.status_code === 1 && hasPermissionEventsLegacy;
31676
- const hasPermissionEvents = operation_status ? operation_status.startsWith("iam_") : hasPermissionEventsLegacy;
31677
- const outlineColor = isError ? theme.palette.error.main : isWarning ? theme.palette.warning.main : void 0;
31695
+ const issues = getSpanIssues(data.event);
31696
+ const iamLevel = getIamSummary(data.event).level;
31697
+ const outlineColor = issues.error ? theme.palette.error.main : issues.warning ? theme.palette.warning.main : void 0;
31678
31698
  const sharedBoxSx = {
31679
31699
  backgroundColor: theme.palette.background.paper,
31680
31700
  borderLeft: `3px solid ${outlineColor ?? "transparent"}`,
@@ -31683,7 +31703,7 @@ var ServiceNode = (0, import_react53.memo)(({ data, selected }) => {
31683
31703
  minWidth: 140,
31684
31704
  outline: selected ? `2px solid ${outlineColor ?? theme.palette.primary.main}` : `1px solid ${theme.palette.divider}`
31685
31705
  };
31686
- const contentProps = { event: data.event, hasPermissionEvents, isError, isWarning };
31706
+ const contentProps = { event: data.event, iamLevel, issues };
31687
31707
  const handleMouseEnter = () => {
31688
31708
  setIsHovered(true);
31689
31709
  const xyNode = nodeRef.current?.closest(".react-flow__node");
@@ -32191,86 +32211,6 @@ function useLocalStorage(key, initialValue) {
32191
32211
  return [storedValue, setValue];
32192
32212
  }
32193
32213
 
32194
- // src/utils/span-parents.ts
32195
- var resolveParents = (spans) => {
32196
- const byId = new Map(spans.map((span) => [span.span_id, span]));
32197
- const parentOf = /* @__PURE__ */ new Map();
32198
- for (const span of spans) {
32199
- const parent = span.parent_span_id === null ? void 0 : byId.get(span.parent_span_id);
32200
- if (parent && parent.span_id !== span.span_id && parent.trace_id === span.trace_id) {
32201
- parentOf.set(span.span_id, parent.span_id);
32202
- }
32203
- }
32204
- const causes = /* @__PURE__ */ new Map();
32205
- const addCause = (spanId, causeId) => {
32206
- const list = causes.get(spanId);
32207
- if (list) list.push(causeId);
32208
- else causes.set(spanId, [causeId]);
32209
- };
32210
- for (const span of spans) {
32211
- for (const causeId of span.outgoing_link_span_ids ?? []) addCause(span.span_id, causeId);
32212
- for (const effectId of span.incoming_link_span_ids ?? []) addCause(effectId, span.span_id);
32213
- }
32214
- const isAncestorOrSelf = (candidateId, spanId) => {
32215
- let current = spanId;
32216
- for (let hops = 0; current !== void 0 && hops <= spans.length; hops++) {
32217
- if (current === candidateId) return true;
32218
- current = parentOf.get(current);
32219
- }
32220
- return false;
32221
- };
32222
- for (const span of spans) {
32223
- if (parentOf.has(span.span_id)) continue;
32224
- const cause = causes.get(span.span_id)?.find((causeId) => byId.has(causeId) && !isAncestorOrSelf(span.span_id, causeId));
32225
- if (cause !== void 0) parentOf.set(span.span_id, cause);
32226
- }
32227
- return parentOf;
32228
- };
32229
-
32230
- // src/components/trace-panel/timeline-tree.ts
32231
- var compareNano = (left, right) => {
32232
- const difference = BigInt(left) - BigInt(right);
32233
- if (difference === 0n) return 0;
32234
- return difference < 0n ? -1 : 1;
32235
- };
32236
- var buildTraceTree = (spans) => {
32237
- const nodes2 = new Map(spans.map((span) => [span.span_id, { children: [], span }]));
32238
- const parentOf = resolveParents(spans);
32239
- const roots = [];
32240
- for (const [spanId, node] of nodes2) {
32241
- const parent = parentOf.get(spanId);
32242
- if (parent === void 0) roots.push(node);
32243
- else nodes2.get(parent)?.children.push(node);
32244
- }
32245
- const byStart = (left, right) => compareNano(left.span.start_time_unix_nano, right.span.start_time_unix_nano);
32246
- roots.sort(byStart);
32247
- for (const node of nodes2.values()) node.children.sort(byStart);
32248
- return { parentOf, roots };
32249
- };
32250
- var findRootSpan = (spans, parentOf, spanId) => {
32251
- let current = spanId;
32252
- for (let hops = 0; hops <= spans.length; hops++) {
32253
- const parent = parentOf.get(current);
32254
- if (parent === void 0) break;
32255
- current = parent;
32256
- }
32257
- return spans.find((span) => span.span_id === current);
32258
- };
32259
- var getTraceBounds = (spans) => {
32260
- let start;
32261
- let end;
32262
- for (const span of spans) {
32263
- const spanStart = BigInt(span.start_time_unix_nano);
32264
- const spanEnd = span.end_time_unix_nano ? BigInt(span.end_time_unix_nano) : spanStart;
32265
- if (start === void 0 || spanStart < start) start = spanStart;
32266
- if (end === void 0 || spanEnd > end) end = spanEnd;
32267
- }
32268
- if (start === void 0 || end === void 0) {
32269
- return { durationMs: 0, startNano: 0n };
32270
- }
32271
- return { durationMs: Number(end - start) / 1e6, startNano: start };
32272
- };
32273
-
32274
32214
  // src/components/trace-panel/waterfall.tsx
32275
32215
  var import_icons_material14 = require("@mui/icons-material");
32276
32216
  var import_material24 = require("@mui/material");
@@ -33570,17 +33510,22 @@ var SpansListPage = () => {
33570
33510
  const [bannerDismissed, setBannerDismissed] = (0, import_react65.useState)(false);
33571
33511
  const [order2, setOrder] = useLocalStorage("spans-order", "oldest_first");
33572
33512
  const [selectedSpanId, setSelectedSpanId] = (0, import_react65.useState)();
33513
+ const getTreeSpans = (0, import_react65.useCallback)((rootSpanId2) => Object.entries(compute.rows).filter(([, row]) => row.rootSpanId === rootSpanId2).map(([spanId]) => getSpan(spanId)).filter((span) => span !== void 0), [compute.rows, getSpan]);
33514
+ const latestRef = (0, import_react65.useRef)({ compute, getTreeSpans });
33515
+ (0, import_react65.useEffect)(() => {
33516
+ latestRef.current = { compute, getTreeSpans };
33517
+ }, [compute, getTreeSpans]);
33573
33518
  const navigateToSpan = (0, import_react65.useCallback)((span) => {
33574
- setSelectedSpanId(span.span_id);
33519
+ const { compute: { queryActive, rows }, getTreeSpans: getSpans } = latestRef.current;
33520
+ const rootSpanId2 = rows[span.span_id]?.rootSpanId;
33521
+ const firstMatch = queryActive && rootSpanId2 !== void 0 ? findFirstSpan(buildTraceTree(getSpans(rootSpanId2)).roots, (spanId) => rows[spanId]?.matches ?? false) : void 0;
33522
+ setSelectedSpanId(firstMatch?.span_id ?? span.span_id);
33575
33523
  }, []);
33576
33524
  const closeSidebar = (0, import_react65.useCallback)(() => {
33577
33525
  setSelectedSpanId(void 0);
33578
33526
  }, []);
33579
33527
  const rootSpanId = selectedSpanId === void 0 ? void 0 : compute.rows[selectedSpanId]?.rootSpanId;
33580
- const treeSpans = (0, import_react65.useMemo)(() => {
33581
- if (rootSpanId === void 0) return [];
33582
- return Object.entries(compute.rows).filter(([, row]) => row.rootSpanId === rootSpanId).map(([spanId]) => getSpan(spanId)).filter((span) => span !== void 0);
33583
- }, [compute.rows, getSpan, rootSpanId]);
33528
+ const treeSpans = (0, import_react65.useMemo)(() => rootSpanId === void 0 ? [] : getTreeSpans(rootSpanId), [getTreeSpans, rootSpanId]);
33584
33529
  const spanMatches = (0, import_react65.useMemo)(
33585
33530
  () => compute.queryActive ? (spanId) => compute.rows[spanId]?.matches ?? false : void 0,
33586
33531
  [compute]