@juice-js/observability 0.0.0-stage → 1.0.0-alpha.1

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.
Files changed (34) hide show
  1. package/README.md +31 -2
  2. package/fesm2022/juice-js-observability-alert.page-D2OL0zTT.mjs +405 -0
  3. package/fesm2022/juice-js-observability-alert.page-D2OL0zTT.mjs.map +1 -0
  4. package/fesm2022/juice-js-observability-alerts.page-BLJvD_op.mjs +208 -0
  5. package/fesm2022/juice-js-observability-alerts.page-BLJvD_op.mjs.map +1 -0
  6. package/fesm2022/juice-js-observability-logs.page-BWFLleyX.mjs +414 -0
  7. package/fesm2022/juice-js-observability-logs.page-BWFLleyX.mjs.map +1 -0
  8. package/fesm2022/juice-js-observability-metrics.page-D1caPbQq.mjs +268 -0
  9. package/fesm2022/juice-js-observability-metrics.page-D1caPbQq.mjs.map +1 -0
  10. package/fesm2022/juice-js-observability-overview.page-UBwHjlyq.mjs +213 -0
  11. package/fesm2022/juice-js-observability-overview.page-UBwHjlyq.mjs.map +1 -0
  12. package/fesm2022/juice-js-observability-resource.page-BZwLK_vU.mjs +275 -0
  13. package/fesm2022/juice-js-observability-resource.page-BZwLK_vU.mjs.map +1 -0
  14. package/fesm2022/juice-js-observability-resources.page-C0xWOb5I.mjs +210 -0
  15. package/fesm2022/juice-js-observability-resources.page-C0xWOb5I.mjs.map +1 -0
  16. package/fesm2022/juice-js-observability-rules.page-Db_U5rRu.mjs +176 -0
  17. package/fesm2022/juice-js-observability-rules.page-Db_U5rRu.mjs.map +1 -0
  18. package/fesm2022/juice-js-observability-service.page-B-SWJ9I5.mjs +727 -0
  19. package/fesm2022/juice-js-observability-service.page-B-SWJ9I5.mjs.map +1 -0
  20. package/fesm2022/juice-js-observability-services.page-BmWgFnC8.mjs +252 -0
  21. package/fesm2022/juice-js-observability-services.page-BmWgFnC8.mjs.map +1 -0
  22. package/fesm2022/juice-js-observability-telemetry-VA4_3nzr.mjs +1748 -0
  23. package/fesm2022/juice-js-observability-telemetry-VA4_3nzr.mjs.map +1 -0
  24. package/fesm2022/juice-js-observability-topology.page-DEUNp10U.mjs +458 -0
  25. package/fesm2022/juice-js-observability-topology.page-DEUNp10U.mjs.map +1 -0
  26. package/fesm2022/juice-js-observability-trace.page-gBhMG2eQ.mjs +530 -0
  27. package/fesm2022/juice-js-observability-trace.page-gBhMG2eQ.mjs.map +1 -0
  28. package/fesm2022/juice-js-observability-traces.page-BCQg4V4o.mjs +521 -0
  29. package/fesm2022/juice-js-observability-traces.page-BCQg4V4o.mjs.map +1 -0
  30. package/fesm2022/juice-js-observability.mjs +396 -0
  31. package/fesm2022/juice-js-observability.mjs.map +1 -0
  32. package/package.json +48 -4
  33. package/styles/_observability.scss +133 -0
  34. package/types/juice-js-observability.d.ts +387 -0
@@ -0,0 +1,530 @@
1
+ import { ApiService, ContextService, LinkPipe } from "./juice-js-observability.mjs";
2
+ import { _ as plainTraceId, a as problemText, d as KeyValueTable, g as formatDuration, l as ContextBar, p as DurationPipe, t as LogTable, v as preciseTime } from "./juice-js-observability-telemetry-VA4_3nzr.mjs";
3
+ import * as i0 from "@angular/core";
4
+ import { ChangeDetectionStrategy, Component, computed, inject, input, signal } from "@angular/core";
5
+ import { rxResource } from "@angular/core/rxjs-interop";
6
+ import { Router, RouterLink } from "@angular/router";
7
+ import { DatePipe } from "@angular/common";
8
+ import * as i1 from "@angular/material/button";
9
+ import { MatButtonModule } from "@angular/material/button";
10
+ import * as i3 from "@angular/material/progress-bar";
11
+ import { MatProgressBarModule } from "@angular/material/progress-bar";
12
+ import * as i2 from "@angular/material/icon";
13
+ import { MatIconModule } from "@angular/material/icon";
14
+ const PALETTE = [
15
+ "#3b82f6",
16
+ "#8b5cf6",
17
+ "#14b8a6",
18
+ "#f59e0b",
19
+ "#ec4899",
20
+ "#22c55e",
21
+ "#06b6d4",
22
+ "#a855f7",
23
+ "#84cc16",
24
+ "#f97316"
25
+ ];
26
+ var TracePage = class TracePage {
27
+ ctx = inject(ContextService);
28
+ api = inject(ApiService);
29
+ router = inject(Router);
30
+ traceId = input.required(...ngDevMode ? [{ debugName: "traceId" }] : /* istanbul ignore next */ []);
31
+ span = input(...ngDevMode ? [void 0, { debugName: "span" }] : /* istanbul ignore next */ []);
32
+ id = computed(() => plainTraceId(this.traceId()), ...ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []);
33
+ trace = rxResource({
34
+ params: () => this.id(),
35
+ stream: ({ params }) => this.api.trace(params)
36
+ });
37
+ error = computed(() => problemText(this.trace.error()), ...ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []);
38
+ collapsed = signal(/* @__PURE__ */ new Set(), ...ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []);
39
+ showErrorsOnly = signal(false, ...ngDevMode ? [{ debugName: "showErrorsOnly" }] : /* istanbul ignore next */ []);
40
+ selectedId = computed(() => this.span() ?? null, ...ngDevMode ? [{ debugName: "selectedId" }] : /* istanbul ignore next */ []);
41
+ spans = computed(() => this.trace.value()?.spans ?? [], ...ngDevMode ? [{ debugName: "spans" }] : /* istanbul ignore next */ []);
42
+ start = computed(() => Math.min(...this.spans().map((s) => preciseTime(s.startedAt))), ...ngDevMode ? [{ debugName: "start" }] : /* istanbul ignore next */ []);
43
+ total = computed(() => Math.max(.001, Math.max(...this.spans().map((s) => preciseTime(s.startedAt) + s.durationMs)) - this.start()), ...ngDevMode ? [{ debugName: "total" }] : /* istanbul ignore next */ []);
44
+ root = computed(() => this.spans().find((s) => !s.parentSpanId) ?? this.spans()[0], ...ngDevMode ? [{ debugName: "root" }] : /* istanbul ignore next */ []);
45
+ services = computed(() => [...new Set(this.spans().map((s) => s.service))], ...ngDevMode ? [{ debugName: "services" }] : /* istanbul ignore next */ []);
46
+ errors = computed(() => this.spans().filter((s) => s.status === "Error").length, ...ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []);
47
+ selected = computed(() => this.spans().find((s) => s.spanId === this.selectedId()) ?? null, ...ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []);
48
+ spanLogQuery = computed(() => {
49
+ const s = this.selected();
50
+ return s ? {
51
+ spanId: s.spanId,
52
+ ...this.windowAround(s)
53
+ } : {};
54
+ }, ...ngDevMode ? [{ debugName: "spanLogQuery" }] : /* istanbul ignore next */ []);
55
+ rows = computed(() => {
56
+ const spans = this.spans();
57
+ const ids = new Set(spans.map((s) => s.spanId));
58
+ const byParent = /* @__PURE__ */ new Map();
59
+ for (const s of spans) {
60
+ const parent = s.parentSpanId && ids.has(s.parentSpanId) ? s.parentSpanId : null;
61
+ byParent.set(parent, [...byParent.get(parent) ?? [], s]);
62
+ }
63
+ const slowest = new Set([...spans].sort((a, b) => b.durationMs - a.durationMs).slice(0, Math.max(1, Math.ceil(spans.length * .05))).map((s) => s.spanId));
64
+ const rows = [];
65
+ const walk = (parent, depth) => {
66
+ for (const s of (byParent.get(parent) ?? []).sort((a, b) => a.startedAt.localeCompare(b.startedAt))) {
67
+ rows.push({
68
+ span: s,
69
+ depth,
70
+ children: byParent.get(s.spanId)?.length ?? 0,
71
+ offset: 100 * (preciseTime(s.startedAt) - this.start()) / this.total(),
72
+ selfError: s.status === "Error",
73
+ slowest: slowest.has(s.spanId) && spans.length > 3
74
+ });
75
+ walk(s.spanId, depth + 1);
76
+ }
77
+ };
78
+ walk(null, 0);
79
+ return rows;
80
+ }, ...ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []);
81
+ visible = computed(() => {
82
+ const rows = this.rows();
83
+ const parentOf = new Map(this.spans().map((s) => [s.spanId, s.parentSpanId]));
84
+ let keep = null;
85
+ if (this.showErrorsOnly()) {
86
+ keep = /* @__PURE__ */ new Set();
87
+ for (const r of rows.filter((r) => r.selfError)) for (let id = r.span.spanId; id; id = parentOf.get(id)) keep.add(id);
88
+ }
89
+ const out = [];
90
+ let hiddenBelow = null;
91
+ for (const r of rows) {
92
+ if (hiddenBelow !== null && r.depth > hiddenBelow) continue;
93
+ hiddenBelow = null;
94
+ if (keep && !keep.has(r.span.spanId)) continue;
95
+ out.push(r);
96
+ if (this.collapsed().has(r.span.spanId)) hiddenBelow = r.depth;
97
+ }
98
+ return out;
99
+ }, ...ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []);
100
+ ticks = computed(() => [
101
+ 0,
102
+ 25,
103
+ 50,
104
+ 75
105
+ ].map((at) => ({
106
+ at,
107
+ label: formatDuration(this.total() * at / 100)
108
+ })), ...ngDevMode ? [{ debugName: "ticks" }] : /* istanbul ignore next */ []);
109
+ color(service) {
110
+ return PALETTE[this.services().indexOf(service) % PALETTE.length];
111
+ }
112
+ width(span) {
113
+ return Math.max(.3, 100 * span.durationMs / this.total());
114
+ }
115
+ offsetMs(span) {
116
+ return preciseTime(span.startedAt) - this.start();
117
+ }
118
+ eventOffset(span, at) {
119
+ return preciseTime(at) - preciseTime(span.startedAt);
120
+ }
121
+ toggle(spanId, event) {
122
+ event.stopPropagation();
123
+ const next = new Set(this.collapsed());
124
+ if (!next.delete(spanId)) next.add(spanId);
125
+ this.collapsed.set(next);
126
+ }
127
+ select(spanId) {
128
+ this.router.navigate([], {
129
+ queryParams: { span: spanId },
130
+ queryParamsHandling: "merge",
131
+ replaceUrl: true
132
+ });
133
+ }
134
+ windowAround(span) {
135
+ const start = new Date(span.startedAt).getTime();
136
+ return {
137
+ from: (/* @__PURE__ */ new Date(start - 6e4)).toISOString(),
138
+ to: new Date(start + span.durationMs + 6e4).toISOString()
139
+ };
140
+ }
141
+ aroundSpan(span, extra) {
142
+ return {
143
+ ...this.ctx.around(span.startedAt, span.durationMs),
144
+ ...extra
145
+ };
146
+ }
147
+ static ɵfac = i0.ɵɵngDeclareFactory({
148
+ minVersion: "12.0.0",
149
+ version: "22.2.4",
150
+ ngImport: i0,
151
+ type: TracePage,
152
+ deps: [],
153
+ target: i0.ɵɵFactoryTarget.Component
154
+ });
155
+ static ɵcmp = i0.ɵɵngDeclareComponent({
156
+ minVersion: "17.0.0",
157
+ version: "22.2.4",
158
+ type: TracePage,
159
+ isStandalone: true,
160
+ selector: "ng-component",
161
+ inputs: {
162
+ traceId: {
163
+ classPropertyName: "traceId",
164
+ publicName: "traceId",
165
+ isSignal: true,
166
+ isRequired: true,
167
+ transformFunction: null
168
+ },
169
+ span: {
170
+ classPropertyName: "span",
171
+ publicName: "span",
172
+ isSignal: true,
173
+ isRequired: false,
174
+ transformFunction: null
175
+ }
176
+ },
177
+ host: { classAttribute: "jc-fill-page" },
178
+ ngImport: i0,
179
+ template: `
180
+ <jc-context-bar>
181
+ <a [routerLink]="['traces'] | jcLink" [queryParams]="ctx.keep()" class="jc-crumb">Traces</a>
182
+ <span class="jc-crumb-sep">›</span>
183
+ <h1 class="jc-mono">{{ id() }}</h1>
184
+ </jc-context-bar>
185
+ @if (trace.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
186
+ @if (trace.error()) { <p class="jc-error">{{ error() }}</p> }
187
+
188
+ @if (trace.value(); as t) {
189
+ <div class="jc-summary">
190
+ <span><strong>{{ root()?.service }}</strong> · {{ root()?.name }}</span>
191
+ <span>{{ start() | date: 'MMM d, HH:mm:ss.SSS' }}</span>
192
+ <span>{{ total() | duration }}</span>
193
+ <span>{{ t.spans.length }} spans · {{ services().length }} services</span>
194
+ @if (errors() > 0) { <span class="jc-badge jc-health-unhealthy">{{ errors() }} failed</span> }
195
+ <button mat-button (click)="showErrorsOnly.set(!showErrorsOnly())">{{ showErrorsOnly() ? 'Show all spans' : 'Error paths only' }}</button>
196
+ <a mat-button [routerLink]="['logs'] | jcLink" [queryParams]="ctx.keep({ trace: id() })">Logs of this trace</a>
197
+ @if (t.truncated) { <span class="jc-muted">Showing the first {{ t.spans.length }} spans.</span> }
198
+ </div>
199
+ <div class="jc-legend">@for (s of services(); track s) { <span><i [style.background]="color(s)"></i>{{ s }}</span> }</div>
200
+
201
+ <div class="jc-trace" [class.jc-with-detail]="selected()">
202
+ <div class="jc-waterfall" role="tree">
203
+ <div class="jc-wf-head"><span>Span</span><span class="jc-wf-scale">
204
+ @for (tick of ticks(); track tick.at) { <span [style.left.%]="tick.at">{{ tick.label }}</span> }</span></div>
205
+ @for (r of visible(); track r.span.spanId) {
206
+ <div class="jc-wf-row" role="treeitem" [class.jc-selected]="r.span.spanId === selectedId()"
207
+ [class.jc-wf-error]="r.selfError" (click)="select(r.span.spanId)" tabindex="0" (keydown.enter)="select(r.span.spanId)">
208
+ <span class="jc-wf-name" [style.padding-left.px]="r.depth * 14">
209
+ @if (r.children > 0) {
210
+ <button class="jc-wf-toggle" (click)="toggle(r.span.spanId, $event)" [attr.aria-label]="collapsed().has(r.span.spanId) ? 'Expand' : 'Collapse'">
211
+ {{ collapsed().has(r.span.spanId) ? '▸' : '▾' }}</button>
212
+ } @else { <span class="jc-wf-toggle"></span> }
213
+ <i class="jc-dot" [style.background]="color(r.span.service)"></i>
214
+ <span class="jc-wf-service">{{ r.span.service }}</span>&nbsp;<span>{{ r.span.name }}</span>
215
+ @if (r.selfError) { <mat-icon class="jc-wf-icon jc-bad" title="Failed">error</mat-icon> }
216
+ @if (r.slowest) { <mat-icon class="jc-wf-icon" title="Among the slowest spans">schedule</mat-icon> }
217
+ @if (collapsed().has(r.span.spanId)) { <span class="jc-muted jc-small">+{{ r.children }}</span> }
218
+ </span>
219
+ <span class="jc-wf-track">
220
+ <span class="jc-wf-bar" [style.left.%]="r.offset" [style.width.%]="width(r.span)" [style.background]="color(r.span.service)"></span>
221
+ <span class="jc-wf-time" [style.left.%]="r.offset">{{ r.span.durationMs | duration }}</span>
222
+ </span>
223
+ </div>
224
+ }
225
+ </div>
226
+
227
+ @if (selected(); as s) {
228
+ <aside class="jc-card jc-span-detail">
229
+ <div class="jc-row"><h2>{{ s.name }}</h2><button mat-icon-button (click)="select(null)" aria-label="Close"><mat-icon>close</mat-icon></button></div>
230
+ <div class="jc-span-facts">
231
+ <span class="jc-badge" [class.jc-health-unhealthy]="s.status === 'Error'" [class.jc-health-healthy]="s.status === 'Ok'">{{ s.status }}</span>
232
+ <span>{{ s.kind }}</span><span>{{ s.durationMs | duration }}</span>
233
+ <span>at +{{ offsetMs(s) | duration }}</span>
234
+ </div>
235
+ @if (s.statusMessage) { <p class="jc-error">{{ s.statusMessage }}</p> }
236
+
237
+ <nav class="jc-actions">
238
+ @if (s.serviceId) { <a mat-stroked-button [routerLink]="['services', s.serviceId] | jcLink" [queryParams]="ctx.keep()">Service {{ s.service }}</a> }
239
+ @if (s.target; as target) {
240
+ <a mat-stroked-button [routerLink]="(target.kind === 'service' ? ['services', target.id] : ['resources', target.id]) | jcLink" [queryParams]="ctx.keep()">
241
+ {{ target.kind === 'service' ? 'Calls' : 'Uses' }} {{ target.name }}</a>
242
+ }
243
+ @if (s.serviceId) {
244
+ <a mat-stroked-button [routerLink]="['services', s.serviceId] | jcLink" [queryParams]="aroundSpan(s, { tab: 'metrics' })">Metrics around</a>
245
+ <a mat-stroked-button [routerLink]="['services', s.serviceId] | jcLink" [queryParams]="aroundSpan(s, { tab: 'logs' })">Service logs around</a>
246
+ <a mat-stroked-button [routerLink]="['services', s.serviceId] | jcLink" [queryParams]="ctx.keep({ tab: 'traces', op: s.name })">Other traces of {{ s.name }}</a>
247
+ }
248
+ </nav>
249
+
250
+ <h3>Logs of this span</h3>
251
+ <jc-log-table [query]="spanLogQuery()" [compact]="true" />
252
+
253
+ <h3>Span</h3>
254
+ <jc-kv [values]="{ 'service': s.service, 'environment': s.environment, 'instance': s.instance, 'host': s.host,
255
+ 'trace id': id(), 'span id': s.spanId, 'parent span id': s.parentSpanId, 'started': s.startedAt,
256
+ 'instrumentation scope': s.scope ? s.scope + (s.scopeVersion ? ' ' + s.scopeVersion : '') : null }" />
257
+ <h3>Attributes</h3>
258
+ <jc-kv [values]="s.attributes" empty="No attributes." />
259
+ @if (s.events.length) {
260
+ <h3>Events</h3>
261
+ @for (e of s.events; track $index) {
262
+ <div class="jc-event" [class.jc-wf-error]="e.name === 'exception'">
263
+ <strong>{{ e.name }}</strong> <span class="jc-muted">+{{ eventOffset(s, e.at) | duration }}</span>
264
+ <jc-kv [values]="e.attributes" empty="" />
265
+ </div>
266
+ }
267
+ }
268
+ @if (s.links.length) {
269
+ <h3>Links</h3>
270
+ @for (l of s.links; track l.spanId) {
271
+ <a [routerLink]="['traces', l.traceId] | jcLink" [queryParams]="ctx.keep({ span: l.spanId })" class="jc-mono">{{ l.traceId }} / {{ l.spanId }}</a>
272
+ }
273
+ }
274
+ </aside>
275
+ }
276
+ </div>
277
+ }`,
278
+ isInline: true,
279
+ dependencies: [
280
+ {
281
+ kind: "directive",
282
+ type: RouterLink,
283
+ selector: "[routerLink]",
284
+ inputs: [
285
+ "target",
286
+ "queryParams",
287
+ "fragment",
288
+ "queryParamsHandling",
289
+ "state",
290
+ "info",
291
+ "relativeTo",
292
+ "preserveFragment",
293
+ "skipLocationChange",
294
+ "replaceUrl",
295
+ "browserUrl",
296
+ "routerLink"
297
+ ]
298
+ },
299
+ {
300
+ kind: "ngmodule",
301
+ type: MatButtonModule
302
+ },
303
+ {
304
+ kind: "component",
305
+ type: i1.MatButton,
306
+ selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ",
307
+ inputs: ["matButton"],
308
+ exportAs: ["matButton", "matAnchor"]
309
+ },
310
+ {
311
+ kind: "component",
312
+ type: i1.MatIconButton,
313
+ selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]",
314
+ exportAs: ["matButton", "matAnchor"]
315
+ },
316
+ {
317
+ kind: "ngmodule",
318
+ type: MatIconModule
319
+ },
320
+ {
321
+ kind: "component",
322
+ type: i2.MatIcon,
323
+ selector: "mat-icon",
324
+ inputs: [
325
+ "color",
326
+ "inline",
327
+ "svgIcon",
328
+ "fontSet",
329
+ "fontIcon"
330
+ ],
331
+ exportAs: ["matIcon"]
332
+ },
333
+ {
334
+ kind: "ngmodule",
335
+ type: MatProgressBarModule
336
+ },
337
+ {
338
+ kind: "component",
339
+ type: i3.MatProgressBar,
340
+ selector: "mat-progress-bar",
341
+ inputs: [
342
+ "color",
343
+ "value",
344
+ "bufferValue",
345
+ "mode"
346
+ ],
347
+ outputs: ["animationEnd"],
348
+ exportAs: ["matProgressBar"]
349
+ },
350
+ {
351
+ kind: "component",
352
+ type: ContextBar,
353
+ selector: "jc-context-bar"
354
+ },
355
+ {
356
+ kind: "component",
357
+ type: KeyValueTable,
358
+ selector: "jc-kv",
359
+ inputs: ["values", "empty"]
360
+ },
361
+ {
362
+ kind: "component",
363
+ type: LogTable,
364
+ selector: "jc-log-table",
365
+ inputs: ["query", "compact"]
366
+ },
367
+ {
368
+ kind: "pipe",
369
+ type: LinkPipe,
370
+ name: "jcLink"
371
+ },
372
+ {
373
+ kind: "pipe",
374
+ type: DatePipe,
375
+ name: "date"
376
+ },
377
+ {
378
+ kind: "pipe",
379
+ type: DurationPipe,
380
+ name: "duration"
381
+ }
382
+ ],
383
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
384
+ });
385
+ };
386
+ i0.ɵɵngDeclareClassMetadata({
387
+ minVersion: "12.0.0",
388
+ version: "22.2.4",
389
+ ngImport: i0,
390
+ type: TracePage,
391
+ decorators: [{
392
+ type: Component,
393
+ args: [{
394
+ changeDetection: ChangeDetectionStrategy.OnPush,
395
+ host: { class: "jc-fill-page" },
396
+ imports: [
397
+ LinkPipe,
398
+ RouterLink,
399
+ DatePipe,
400
+ MatButtonModule,
401
+ MatIconModule,
402
+ MatProgressBarModule,
403
+ ContextBar,
404
+ KeyValueTable,
405
+ LogTable,
406
+ DurationPipe
407
+ ],
408
+ template: `
409
+ <jc-context-bar>
410
+ <a [routerLink]="['traces'] | jcLink" [queryParams]="ctx.keep()" class="jc-crumb">Traces</a>
411
+ <span class="jc-crumb-sep">›</span>
412
+ <h1 class="jc-mono">{{ id() }}</h1>
413
+ </jc-context-bar>
414
+ @if (trace.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
415
+ @if (trace.error()) { <p class="jc-error">{{ error() }}</p> }
416
+
417
+ @if (trace.value(); as t) {
418
+ <div class="jc-summary">
419
+ <span><strong>{{ root()?.service }}</strong> · {{ root()?.name }}</span>
420
+ <span>{{ start() | date: 'MMM d, HH:mm:ss.SSS' }}</span>
421
+ <span>{{ total() | duration }}</span>
422
+ <span>{{ t.spans.length }} spans · {{ services().length }} services</span>
423
+ @if (errors() > 0) { <span class="jc-badge jc-health-unhealthy">{{ errors() }} failed</span> }
424
+ <button mat-button (click)="showErrorsOnly.set(!showErrorsOnly())">{{ showErrorsOnly() ? 'Show all spans' : 'Error paths only' }}</button>
425
+ <a mat-button [routerLink]="['logs'] | jcLink" [queryParams]="ctx.keep({ trace: id() })">Logs of this trace</a>
426
+ @if (t.truncated) { <span class="jc-muted">Showing the first {{ t.spans.length }} spans.</span> }
427
+ </div>
428
+ <div class="jc-legend">@for (s of services(); track s) { <span><i [style.background]="color(s)"></i>{{ s }}</span> }</div>
429
+
430
+ <div class="jc-trace" [class.jc-with-detail]="selected()">
431
+ <div class="jc-waterfall" role="tree">
432
+ <div class="jc-wf-head"><span>Span</span><span class="jc-wf-scale">
433
+ @for (tick of ticks(); track tick.at) { <span [style.left.%]="tick.at">{{ tick.label }}</span> }</span></div>
434
+ @for (r of visible(); track r.span.spanId) {
435
+ <div class="jc-wf-row" role="treeitem" [class.jc-selected]="r.span.spanId === selectedId()"
436
+ [class.jc-wf-error]="r.selfError" (click)="select(r.span.spanId)" tabindex="0" (keydown.enter)="select(r.span.spanId)">
437
+ <span class="jc-wf-name" [style.padding-left.px]="r.depth * 14">
438
+ @if (r.children > 0) {
439
+ <button class="jc-wf-toggle" (click)="toggle(r.span.spanId, $event)" [attr.aria-label]="collapsed().has(r.span.spanId) ? 'Expand' : 'Collapse'">
440
+ {{ collapsed().has(r.span.spanId) ? '▸' : '▾' }}</button>
441
+ } @else { <span class="jc-wf-toggle"></span> }
442
+ <i class="jc-dot" [style.background]="color(r.span.service)"></i>
443
+ <span class="jc-wf-service">{{ r.span.service }}</span>&nbsp;<span>{{ r.span.name }}</span>
444
+ @if (r.selfError) { <mat-icon class="jc-wf-icon jc-bad" title="Failed">error</mat-icon> }
445
+ @if (r.slowest) { <mat-icon class="jc-wf-icon" title="Among the slowest spans">schedule</mat-icon> }
446
+ @if (collapsed().has(r.span.spanId)) { <span class="jc-muted jc-small">+{{ r.children }}</span> }
447
+ </span>
448
+ <span class="jc-wf-track">
449
+ <span class="jc-wf-bar" [style.left.%]="r.offset" [style.width.%]="width(r.span)" [style.background]="color(r.span.service)"></span>
450
+ <span class="jc-wf-time" [style.left.%]="r.offset">{{ r.span.durationMs | duration }}</span>
451
+ </span>
452
+ </div>
453
+ }
454
+ </div>
455
+
456
+ @if (selected(); as s) {
457
+ <aside class="jc-card jc-span-detail">
458
+ <div class="jc-row"><h2>{{ s.name }}</h2><button mat-icon-button (click)="select(null)" aria-label="Close"><mat-icon>close</mat-icon></button></div>
459
+ <div class="jc-span-facts">
460
+ <span class="jc-badge" [class.jc-health-unhealthy]="s.status === 'Error'" [class.jc-health-healthy]="s.status === 'Ok'">{{ s.status }}</span>
461
+ <span>{{ s.kind }}</span><span>{{ s.durationMs | duration }}</span>
462
+ <span>at +{{ offsetMs(s) | duration }}</span>
463
+ </div>
464
+ @if (s.statusMessage) { <p class="jc-error">{{ s.statusMessage }}</p> }
465
+
466
+ <nav class="jc-actions">
467
+ @if (s.serviceId) { <a mat-stroked-button [routerLink]="['services', s.serviceId] | jcLink" [queryParams]="ctx.keep()">Service {{ s.service }}</a> }
468
+ @if (s.target; as target) {
469
+ <a mat-stroked-button [routerLink]="(target.kind === 'service' ? ['services', target.id] : ['resources', target.id]) | jcLink" [queryParams]="ctx.keep()">
470
+ {{ target.kind === 'service' ? 'Calls' : 'Uses' }} {{ target.name }}</a>
471
+ }
472
+ @if (s.serviceId) {
473
+ <a mat-stroked-button [routerLink]="['services', s.serviceId] | jcLink" [queryParams]="aroundSpan(s, { tab: 'metrics' })">Metrics around</a>
474
+ <a mat-stroked-button [routerLink]="['services', s.serviceId] | jcLink" [queryParams]="aroundSpan(s, { tab: 'logs' })">Service logs around</a>
475
+ <a mat-stroked-button [routerLink]="['services', s.serviceId] | jcLink" [queryParams]="ctx.keep({ tab: 'traces', op: s.name })">Other traces of {{ s.name }}</a>
476
+ }
477
+ </nav>
478
+
479
+ <h3>Logs of this span</h3>
480
+ <jc-log-table [query]="spanLogQuery()" [compact]="true" />
481
+
482
+ <h3>Span</h3>
483
+ <jc-kv [values]="{ 'service': s.service, 'environment': s.environment, 'instance': s.instance, 'host': s.host,
484
+ 'trace id': id(), 'span id': s.spanId, 'parent span id': s.parentSpanId, 'started': s.startedAt,
485
+ 'instrumentation scope': s.scope ? s.scope + (s.scopeVersion ? ' ' + s.scopeVersion : '') : null }" />
486
+ <h3>Attributes</h3>
487
+ <jc-kv [values]="s.attributes" empty="No attributes." />
488
+ @if (s.events.length) {
489
+ <h3>Events</h3>
490
+ @for (e of s.events; track $index) {
491
+ <div class="jc-event" [class.jc-wf-error]="e.name === 'exception'">
492
+ <strong>{{ e.name }}</strong> <span class="jc-muted">+{{ eventOffset(s, e.at) | duration }}</span>
493
+ <jc-kv [values]="e.attributes" empty="" />
494
+ </div>
495
+ }
496
+ }
497
+ @if (s.links.length) {
498
+ <h3>Links</h3>
499
+ @for (l of s.links; track l.spanId) {
500
+ <a [routerLink]="['traces', l.traceId] | jcLink" [queryParams]="ctx.keep({ span: l.spanId })" class="jc-mono">{{ l.traceId }} / {{ l.spanId }}</a>
501
+ }
502
+ }
503
+ </aside>
504
+ }
505
+ </div>
506
+ }`
507
+ }]
508
+ }],
509
+ propDecorators: {
510
+ traceId: [{
511
+ type: i0.Input,
512
+ args: [{
513
+ isSignal: true,
514
+ alias: "traceId",
515
+ required: true
516
+ }]
517
+ }],
518
+ span: [{
519
+ type: i0.Input,
520
+ args: [{
521
+ isSignal: true,
522
+ alias: "span",
523
+ required: false
524
+ }]
525
+ }]
526
+ }
527
+ });
528
+ export { TracePage };
529
+
530
+ //# sourceMappingURL=juice-js-observability-trace.page-gBhMG2eQ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"juice-js-observability-trace.page-gBhMG2eQ.mjs","names":[],"sources":["../../../../projects/juice-js/observability/src/lib/pages/trace.page.ts"],"sourcesContent":["import { LinkPipe } from '../core/options';\nimport { ChangeDetectionStrategy, Component, computed, inject, input, signal } from '@angular/core';\nimport { rxResource } from '@angular/core/rxjs-interop';\nimport { DatePipe } from '@angular/common';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressBarModule } from '@angular/material/progress-bar';\nimport { Router, RouterLink } from '@angular/router';\nimport { ApiService } from '../core/api.service';\nimport { ContextService } from '../core/context.service';\nimport { Span } from '../core/models';\nimport { DurationPipe, formatDuration, plainTraceId, preciseTime } from '../shared/format';\nimport { ContextBar, KeyValueTable } from '../shared/ui';\nimport { LogTable, problemText } from '../shared/telemetry';\n\ninterface Row { span: Span; depth: number; children: number; offset: number; selfError: boolean; slowest: boolean; }\n\n/** A stable colour per service, from a small palette that reads in both themes. */\nconst PALETTE = ['#3b82f6', '#8b5cf6', '#14b8a6', '#f59e0b', '#ec4899', '#22c55e', '#06b6d4', '#a855f7', '#84cc16', '#f97316'];\n\n/**\n * A trace as a waterfall (Console requirements §6): the span hierarchy with timing relative to the trace start, so\n * parallel calls show side by side; errors and the slowest spans marked; subtrees collapsible. The selected span is\n * in the URL (`span`), with its details, its logs, and links to its service, its dependency, and the metrics and logs\n * around it (§7).\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n // Fills the window; the waterfall and the span scroll on their own (styles.scss).\n host: { class: 'jc-fill-page' },\n imports: [LinkPipe, RouterLink, DatePipe, MatButtonModule, MatIconModule, MatProgressBarModule, ContextBar, KeyValueTable, LogTable, DurationPipe],\n template: `\n <jc-context-bar>\n <a [routerLink]=\"['traces'] | jcLink\" [queryParams]=\"ctx.keep()\" class=\"jc-crumb\">Traces</a>\n <span class=\"jc-crumb-sep\">›</span>\n <h1 class=\"jc-mono\">{{ id() }}</h1>\n </jc-context-bar>\n @if (trace.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (trace.error()) { <p class=\"jc-error\">{{ error() }}</p> }\n\n @if (trace.value(); as t) {\n <div class=\"jc-summary\">\n <span><strong>{{ root()?.service }}</strong> · {{ root()?.name }}</span>\n <span>{{ start() | date: 'MMM d, HH:mm:ss.SSS' }}</span>\n <span>{{ total() | duration }}</span>\n <span>{{ t.spans.length }} spans · {{ services().length }} services</span>\n @if (errors() > 0) { <span class=\"jc-badge jc-health-unhealthy\">{{ errors() }} failed</span> }\n <button mat-button (click)=\"showErrorsOnly.set(!showErrorsOnly())\">{{ showErrorsOnly() ? 'Show all spans' : 'Error paths only' }}</button>\n <a mat-button [routerLink]=\"['logs'] | jcLink\" [queryParams]=\"ctx.keep({ trace: id() })\">Logs of this trace</a>\n @if (t.truncated) { <span class=\"jc-muted\">Showing the first {{ t.spans.length }} spans.</span> }\n </div>\n <div class=\"jc-legend\">@for (s of services(); track s) { <span><i [style.background]=\"color(s)\"></i>{{ s }}</span> }</div>\n\n <div class=\"jc-trace\" [class.jc-with-detail]=\"selected()\">\n <div class=\"jc-waterfall\" role=\"tree\">\n <div class=\"jc-wf-head\"><span>Span</span><span class=\"jc-wf-scale\">\n @for (tick of ticks(); track tick.at) { <span [style.left.%]=\"tick.at\">{{ tick.label }}</span> }</span></div>\n @for (r of visible(); track r.span.spanId) {\n <div class=\"jc-wf-row\" role=\"treeitem\" [class.jc-selected]=\"r.span.spanId === selectedId()\"\n [class.jc-wf-error]=\"r.selfError\" (click)=\"select(r.span.spanId)\" tabindex=\"0\" (keydown.enter)=\"select(r.span.spanId)\">\n <span class=\"jc-wf-name\" [style.padding-left.px]=\"r.depth * 14\">\n @if (r.children > 0) {\n <button class=\"jc-wf-toggle\" (click)=\"toggle(r.span.spanId, $event)\" [attr.aria-label]=\"collapsed().has(r.span.spanId) ? 'Expand' : 'Collapse'\">\n {{ collapsed().has(r.span.spanId) ? '▸' : '▾' }}</button>\n } @else { <span class=\"jc-wf-toggle\"></span> }\n <i class=\"jc-dot\" [style.background]=\"color(r.span.service)\"></i>\n <span class=\"jc-wf-service\">{{ r.span.service }}</span>&nbsp;<span>{{ r.span.name }}</span>\n @if (r.selfError) { <mat-icon class=\"jc-wf-icon jc-bad\" title=\"Failed\">error</mat-icon> }\n @if (r.slowest) { <mat-icon class=\"jc-wf-icon\" title=\"Among the slowest spans\">schedule</mat-icon> }\n @if (collapsed().has(r.span.spanId)) { <span class=\"jc-muted jc-small\">+{{ r.children }}</span> }\n </span>\n <span class=\"jc-wf-track\">\n <span class=\"jc-wf-bar\" [style.left.%]=\"r.offset\" [style.width.%]=\"width(r.span)\" [style.background]=\"color(r.span.service)\"></span>\n <span class=\"jc-wf-time\" [style.left.%]=\"r.offset\">{{ r.span.durationMs | duration }}</span>\n </span>\n </div>\n }\n </div>\n\n @if (selected(); as s) {\n <aside class=\"jc-card jc-span-detail\">\n <div class=\"jc-row\"><h2>{{ s.name }}</h2><button mat-icon-button (click)=\"select(null)\" aria-label=\"Close\"><mat-icon>close</mat-icon></button></div>\n <div class=\"jc-span-facts\">\n <span class=\"jc-badge\" [class.jc-health-unhealthy]=\"s.status === 'Error'\" [class.jc-health-healthy]=\"s.status === 'Ok'\">{{ s.status }}</span>\n <span>{{ s.kind }}</span><span>{{ s.durationMs | duration }}</span>\n <span>at +{{ offsetMs(s) | duration }}</span>\n </div>\n @if (s.statusMessage) { <p class=\"jc-error\">{{ s.statusMessage }}</p> }\n\n <nav class=\"jc-actions\">\n @if (s.serviceId) { <a mat-stroked-button [routerLink]=\"['services', s.serviceId] | jcLink\" [queryParams]=\"ctx.keep()\">Service {{ s.service }}</a> }\n @if (s.target; as target) {\n <a mat-stroked-button [routerLink]=\"(target.kind === 'service' ? ['services', target.id] : ['resources', target.id]) | jcLink\" [queryParams]=\"ctx.keep()\">\n {{ target.kind === 'service' ? 'Calls' : 'Uses' }} {{ target.name }}</a>\n }\n @if (s.serviceId) {\n <a mat-stroked-button [routerLink]=\"['services', s.serviceId] | jcLink\" [queryParams]=\"aroundSpan(s, { tab: 'metrics' })\">Metrics around</a>\n <a mat-stroked-button [routerLink]=\"['services', s.serviceId] | jcLink\" [queryParams]=\"aroundSpan(s, { tab: 'logs' })\">Service logs around</a>\n <a mat-stroked-button [routerLink]=\"['services', s.serviceId] | jcLink\" [queryParams]=\"ctx.keep({ tab: 'traces', op: s.name })\">Other traces of {{ s.name }}</a>\n }\n </nav>\n\n <h3>Logs of this span</h3>\n <jc-log-table [query]=\"spanLogQuery()\" [compact]=\"true\" />\n\n <h3>Span</h3>\n <jc-kv [values]=\"{ 'service': s.service, 'environment': s.environment, 'instance': s.instance, 'host': s.host,\n 'trace id': id(), 'span id': s.spanId, 'parent span id': s.parentSpanId, 'started': s.startedAt,\n 'instrumentation scope': s.scope ? s.scope + (s.scopeVersion ? ' ' + s.scopeVersion : '') : null }\" />\n <h3>Attributes</h3>\n <jc-kv [values]=\"s.attributes\" empty=\"No attributes.\" />\n @if (s.events.length) {\n <h3>Events</h3>\n @for (e of s.events; track $index) {\n <div class=\"jc-event\" [class.jc-wf-error]=\"e.name === 'exception'\">\n <strong>{{ e.name }}</strong> <span class=\"jc-muted\">+{{ eventOffset(s, e.at) | duration }}</span>\n <jc-kv [values]=\"e.attributes\" empty=\"\" />\n </div>\n }\n }\n @if (s.links.length) {\n <h3>Links</h3>\n @for (l of s.links; track l.spanId) {\n <a [routerLink]=\"['traces', l.traceId] | jcLink\" [queryParams]=\"ctx.keep({ span: l.spanId })\" class=\"jc-mono\">{{ l.traceId }} / {{ l.spanId }}</a>\n }\n }\n </aside>\n }\n </div>\n }`,\n})\nexport class TracePage {\n protected readonly ctx = inject(ContextService);\n private readonly api = inject(ApiService);\n private readonly router = inject(Router);\n\n /** A trace id, or a W3C traceparent such as a Juice.Audit record before 10.4 holds; links from either open the trace. */\n readonly traceId = input.required<string>();\n readonly span = input<string>();\n\n protected readonly id = computed(() => plainTraceId(this.traceId()));\n protected readonly trace = rxResource({ params: () => this.id(), stream: ({ params }) => this.api.trace(params) });\n protected readonly error = computed(() => problemText(this.trace.error()));\n protected readonly collapsed = signal(new Set<string>());\n protected readonly showErrorsOnly = signal(false);\n protected readonly selectedId = computed(() => this.span() ?? null);\n\n private readonly spans = computed(() => this.trace.value()?.spans ?? []);\n // Sub-millisecond precision: spans of a fast in-process flow start microseconds apart.\n protected readonly start = computed(() => Math.min(...this.spans().map(s => preciseTime(s.startedAt))));\n protected readonly total = computed(() =>\n Math.max(0.001, Math.max(...this.spans().map(s => preciseTime(s.startedAt) + s.durationMs)) - this.start()));\n protected readonly root = computed(() => this.spans().find(s => !s.parentSpanId) ?? this.spans()[0]);\n protected readonly services = computed(() => [...new Set(this.spans().map(s => s.service))]);\n protected readonly errors = computed(() => this.spans().filter(s => s.status === 'Error').length);\n protected readonly selected = computed(() => this.spans().find(s => s.spanId === this.selectedId()) ?? null);\n protected readonly spanLogQuery = computed(() => {\n const s = this.selected();\n return s ? { spanId: s.spanId, ...this.windowAround(s) } : {};\n });\n\n /** Depth-first order with depth; spans whose parent is missing are roots. Children start-ordered. */\n private readonly rows = computed<Row[]>(() => {\n const spans = this.spans();\n const ids = new Set(spans.map(s => s.spanId));\n const byParent = new Map<string | null, Span[]>();\n for (const s of spans) {\n const parent = s.parentSpanId && ids.has(s.parentSpanId) ? s.parentSpanId : null;\n byParent.set(parent, [...(byParent.get(parent) ?? []), s]);\n }\n const slowest = new Set([...spans].sort((a, b) => b.durationMs - a.durationMs).slice(0, Math.max(1, Math.ceil(spans.length * 0.05))).map(s => s.spanId));\n const rows: Row[] = [];\n const walk = (parent: string | null, depth: number) => {\n for (const s of (byParent.get(parent) ?? []).sort((a, b) => a.startedAt.localeCompare(b.startedAt))) {\n rows.push({\n span: s, depth, children: byParent.get(s.spanId)?.length ?? 0,\n offset: 100 * (preciseTime(s.startedAt) - this.start()) / this.total(),\n selfError: s.status === 'Error', slowest: slowest.has(s.spanId) && spans.length > 3,\n });\n walk(s.spanId, depth + 1);\n }\n };\n walk(null, 0);\n return rows;\n });\n\n /** Hides collapsed subtrees; \"error paths only\" keeps failed spans and their ancestors. */\n protected readonly visible = computed(() => {\n const rows = this.rows();\n const parentOf = new Map(this.spans().map(s => [s.spanId, s.parentSpanId]));\n let keep: Set<string> | null = null;\n if (this.showErrorsOnly()) {\n keep = new Set<string>();\n for (const r of rows.filter(r => r.selfError)) {\n for (let id: string | null | undefined = r.span.spanId; id; id = parentOf.get(id)) keep.add(id);\n }\n }\n const out: Row[] = [];\n let hiddenBelow: number | null = null;\n for (const r of rows) {\n if (hiddenBelow !== null && r.depth > hiddenBelow) continue;\n hiddenBelow = null;\n if (keep && !keep.has(r.span.spanId)) continue;\n out.push(r);\n if (this.collapsed().has(r.span.spanId)) hiddenBelow = r.depth;\n }\n return out;\n });\n\n protected readonly ticks = computed(() => [0, 25, 50, 75].map(at => ({ at, label: formatDuration(this.total() * at / 100) })));\n\n color(service: string) {\n return PALETTE[this.services().indexOf(service) % PALETTE.length];\n }\n\n width(span: Span) {\n return Math.max(0.3, 100 * span.durationMs / this.total());\n }\n\n offsetMs(span: Span) {\n return preciseTime(span.startedAt) - this.start();\n }\n\n eventOffset(span: Span, at: string) {\n return preciseTime(at) - preciseTime(span.startedAt);\n }\n\n toggle(spanId: string, event: Event) {\n event.stopPropagation();\n const next = new Set(this.collapsed());\n if (!next.delete(spanId)) next.add(spanId);\n this.collapsed.set(next);\n }\n\n select(spanId: string | null) {\n void this.router.navigate([], { queryParams: { span: spanId }, queryParamsHandling: 'merge', replaceUrl: true });\n }\n\n private windowAround(span: Span) {\n const start = new Date(span.startedAt).getTime();\n return { from: new Date(start - 60_000).toISOString(), to: new Date(start + span.durationMs + 60_000).toISOString() };\n }\n\n /** The service page over a window around this span, so its metrics and logs line up with it. */\n aroundSpan(span: Span, extra: Record<string, string>) {\n return { ...this.ctx.around(span.startedAt, span.durationMs), ...extra };\n }\n}\n"],"mappings":"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"}