{"version":3,"file":"JourneyTracker.cjs","sources":["../../../src/services/JourneyTracker.ts"],"sourcesContent":["/**\n * Outcome of a tracked user journey.\n *\n * @public\n */\nexport type JourneyOutcome = 'success' | 'timeout' | 'abandoned' | 'error' | 'discarded' | 'canceled';\n\n/**\n * Metadata describing a Critical User Journey type.\n * Stored in the registry - not per-instance data.\n *\n * @public\n */\nexport interface JourneyMeta {\n  /** Unique identifier for this journey type. */\n  type: string;\n  /** Human-readable description of what this journey represents. */\n  description: string;\n  /** Team owning this journey (e.g. 'grafana-dashboards'). */\n  owner: string;\n  /** Maximum duration (ms) before the journey auto-ends with 'timeout'. */\n  timeoutMs: number;\n  /** If true (default), starting a journey of the same type cancels the previous instance. */\n  cancelOnRestart?: boolean;\n  /**\n   * Other journey types that should be treated as parents. When any of these is\n   * active at journey start, this journey's root span nests under it (same trace,\n   * native Tempo waterfall) and `parent_journey.id` / `parent_journey.type`\n   * attributes are recorded for queryability.\n   *\n   * Order matters: the first listed parent that is currently active wins. If\n   * none are active, the journey starts a new trace as usual.\n   */\n  parents?: string[];\n}\n\n/**\n * Options when starting a new journey.\n *\n * @public\n */\nexport interface JourneyOptions {\n  /** Arbitrary key-value metadata attached to the journey. */\n  attributes?: Record<string, string>;\n  /** Auto-end the journey with outcome 'timeout' after this many ms. Default 5 min. */\n  timeoutMs?: number;\n}\n\n/**\n * Extended options used by the registry when invoking the tracker. These fields\n * are sourced from {@link JourneyMeta} and must not be set by feature code -\n * splitting them off the public {@link JourneyOptions} ensures callers cannot\n * silently bypass registry intent.\n *\n * @internal\n */\nexport interface JourneyStartOptions extends JourneyOptions {\n  /** Parent journey types from {@link JourneyMeta.parents}. */\n  /** @internal - set by registry, not by callers */\n  cancelOnRestart?: boolean;\n  /** Parent journey types from {@link JourneyMeta.parents}. */\n  parents?: string[];\n}\n\n/**\n * Handle for a single step within a journey.\n *\n * @public\n */\nexport interface StepHandle {\n  /** End this step. Calling after already ended is a safe no-op. */\n  end(attributes?: Record<string, string>): void;\n}\n\n/**\n * Handle returned when starting a journey. Used to record steps and signal completion.\n *\n * @public\n */\nexport interface JourneyHandle {\n  /**\n   * Unique identifier for this journey instance. Stable across the lifetime of the journey.\n   *\n   * When OTel tracing is enabled this is the journey's root span ID, which is unique even\n   * for journeys that nest under a parent (those share the parent's traceId, not the spanId).\n   * When tracing is disabled this is a UUID generated at start time.\n   */\n  readonly journeyId: string;\n  /**\n   * OTel trace ID for the journey's root span, when tracing is enabled.\n   * Empty string when tracing is disabled. Use this (not journeyId) to deep-link to a Tempo trace.\n   * Nested journeys share their parent's traceId.\n   */\n  readonly traceId: string;\n  /** The journey type name passed to startJourney. */\n  readonly journeyType: string;\n  /** False after end() has been called. */\n  readonly isActive: boolean;\n  /**\n   * Record a pointwise event on the journey. Creates a zero-duration child span with the\n   * given attributes - no handle returned, nothing to end. Safe no-op when the journey\n   * has already ended.\n   */\n  recordEvent(name: string, attributes?: Record<string, string>): void;\n  /**\n   * Start a duration step on the journey. Returns a StepHandle - the caller MUST call\n   * `step.end()` when the measured operation completes. Safe no-op when the journey\n   * has already ended (returns a noop StepHandle).\n   */\n  startStep(name: string, attributes?: Record<string, string>): StepHandle;\n  /** End the journey with a given outcome. Idempotent - second call is a no-op. */\n  end(outcome: JourneyOutcome, attributes?: Record<string, string>): void;\n  /** Merge additional attributes into the journey. */\n  setAttributes(attributes: Record<string, string>): void;\n  /** Register a callback that fires when this journey ends (any outcome). @internal */\n  onEnd(callback: () => void): void;\n}\n\n/**\n * Service for tracking Critical User Journeys across the Grafana frontend.\n *\n * Obtain via {@link getJourneyTracker}. When the feature is disabled, all calls\n * hit a zero-overhead {@link NoopJourneyTracker}.\n *\n * @public\n */\nexport interface JourneyTracker {\n  /** Start a new journey of the given type. */\n  startJourney(journeyType: string, options?: JourneyOptions): JourneyHandle;\n  /** Get the currently active journey of a type, or null. */\n  getActiveJourney(journeyType: string): JourneyHandle | null;\n  /** Cancel all active journeys. */\n  cancelAll(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Noop implementations - truly zero overhead when feature is disabled\n// ---------------------------------------------------------------------------\n\nclass NoopStepHandle implements StepHandle {\n  end(): void {}\n}\n\nconst NOOP_STEP = new NoopStepHandle();\n\nclass NoopJourneyHandle implements JourneyHandle {\n  readonly journeyId = '';\n  readonly traceId = '';\n  readonly journeyType = '';\n  readonly isActive = false;\n\n  recordEvent(): void {}\n  startStep(): StepHandle {\n    return NOOP_STEP;\n  }\n  end(): void {}\n  setAttributes(): void {}\n  onEnd(): void {}\n}\n\nconst NOOP_HANDLE = new NoopJourneyHandle();\n\nclass NoopJourneyTracker implements JourneyTracker {\n  startJourney(_journeyType?: string, _options?: JourneyOptions): JourneyHandle {\n    return NOOP_HANDLE;\n  }\n  getActiveJourney(_journeyType?: string): JourneyHandle | null {\n    return null;\n  }\n  cancelAll(): void {}\n}\n\n// ---------------------------------------------------------------------------\n// Singleton getter / setter (mirrors EchoSrv pattern)\n// ---------------------------------------------------------------------------\n\nlet singletonInstance: JourneyTracker | undefined;\n\n/**\n * Set the global JourneyTracker implementation. Called once during app bootstrap\n * when the feature toggle is enabled.\n *\n * @internal\n */\nexport function setJourneyTracker(instance: JourneyTracker): void {\n  singletonInstance = instance;\n}\n\n/**\n * Retrieve the global {@link JourneyTracker}. Returns a zero-overhead\n * {@link NoopJourneyTracker} when the feature has not been initialised.\n *\n * @public\n */\nexport function getJourneyTracker(): JourneyTracker {\n  if (!singletonInstance) {\n    singletonInstance = new NoopJourneyTracker();\n  }\n  return singletonInstance;\n}\n\n// ---------------------------------------------------------------------------\n// JourneyRegistry: split start / end registration\n// ---------------------------------------------------------------------------\n\n/**\n * Callback for setting up journey triggers (start conditions).\n * Runs once at registration time. Sets up interaction subscriptions\n * and calls tracker.startJourney() inside them. Returns a cleanup function.\n *\n * @internal\n */\nexport type JourneyTriggersFn = (tracker: JourneyTracker) => () => void;\n\n/**\n * Callback for setting up journey end conditions.\n * Called per journey instance - receives the handle for that specific instance.\n * Sets up interaction subscriptions that call handle.end(). Returns a cleanup function.\n *\n * @internal\n */\nexport type JourneyInstanceFn = (handle: JourneyHandle) => () => void;\n\n/**\n * Interface for the journey registry that manages metadata and trigger registration.\n *\n * @internal\n */\nexport interface JourneyRegistry {\n  /** Initialize the registry with journey metadata definitions. */\n  init(metadata: JourneyMeta[]): void;\n  /** Register triggers that start a journey. Runs triggersFn immediately. Called once at bootstrap. */\n  registerTriggers(journeyType: string, triggersFn: JourneyTriggersFn): void;\n  /** Called with each journey instance's handle when a journey of this type starts. Wire up end conditions here. */\n  onInstance(journeyType: string, instanceFn: JourneyInstanceFn): void;\n  /** Clean up all subscriptions. */\n  destroy(): void;\n}\n\nlet registryInstance: JourneyRegistry | undefined;\n\n/**\n * Set the global JourneyRegistry implementation. Called once during app bootstrap.\n *\n * @internal\n */\nexport function setJourneyRegistry(instance: JourneyRegistry): void {\n  registryInstance = instance;\n}\n\n/**\n * Register triggers that start a journey type. Called ONCE at bootstrap.\n *\n * The triggersFn runs immediately - set up interaction subscriptions inside it.\n * Call tracker.startJourney() inside those subscriptions when the start condition is met.\n *\n * @public\n */\nexport function registerJourneyTriggers(journeyType: string, triggersFn: JourneyTriggersFn): void {\n  if (!registryInstance) {\n    return; // Feature disabled - silent no-op\n  }\n  registryInstance.registerTriggers(journeyType, triggersFn);\n}\n\n/**\n * Called once per journey instance when startJourney fires. Use it to wire\n * up end conditions (onInteraction listeners that call handle.end()).\n * Can be registered at bootstrap (eager) or later from feature code (lazy).\n * Buffered instances are replayed if registered late.\n *\n * @public\n */\nexport function onJourneyInstance(journeyType: string, instanceFn: JourneyInstanceFn): void {\n  if (!registryInstance) {\n    return; // Feature disabled - silent no-op\n  }\n  registryInstance.onInstance(journeyType, instanceFn);\n}\n"],"names":[],"mappings":";;;;;AA2IA,MAAM,cAAA,CAAqC;AAAA,EACzC,GAAA,GAAY;AAAA,EAAC;AACf;AAEA,MAAM,SAAA,GAAY,IAAI,cAAA,EAAe;AAErC,MAAM,iBAAA,CAA2C;AAAA,EAAjD,WAAA,GAAA;AACE,IAAA,IAAA,CAAS,SAAA,GAAY,EAAA;AACrB,IAAA,IAAA,CAAS,OAAA,GAAU,EAAA;AACnB,IAAA,IAAA,CAAS,WAAA,GAAc,EAAA;AACvB,IAAA,IAAA,CAAS,QAAA,GAAW,KAAA;AAAA,EAAA;AAAA,EAEpB,WAAA,GAAoB;AAAA,EAAC;AAAA,EACrB,SAAA,GAAwB;AACtB,IAAA,OAAO,SAAA;AAAA,EACT;AAAA,EACA,GAAA,GAAY;AAAA,EAAC;AAAA,EACb,aAAA,GAAsB;AAAA,EAAC;AAAA,EACvB,KAAA,GAAc;AAAA,EAAC;AACjB;AAEA,MAAM,WAAA,GAAc,IAAI,iBAAA,EAAkB;AAE1C,MAAM,kBAAA,CAA6C;AAAA,EACjD,YAAA,CAAa,cAAuB,QAAA,EAA0C;AAC5E,IAAA,OAAO,WAAA;AAAA,EACT;AAAA,EACA,iBAAiB,YAAA,EAA6C;AAC5D,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EACA,SAAA,GAAkB;AAAA,EAAC;AACrB;AAMA,IAAI,iBAAA;AAQG,SAAS,kBAAkB,QAAA,EAAgC;AAChE,EAAA,iBAAA,GAAoB,QAAA;AACtB;AAQO,SAAS,iBAAA,GAAoC;AAClD,EAAA,IAAI,CAAC,iBAAA,EAAmB;AACtB,IAAA,iBAAA,GAAoB,IAAI,kBAAA,EAAmB;AAAA,EAC7C;AACA,EAAA,OAAO,iBAAA;AACT;AAwCA,IAAI,gBAAA;AAOG,SAAS,mBAAmB,QAAA,EAAiC;AAClE,EAAA,gBAAA,GAAmB,QAAA;AACrB;AAUO,SAAS,uBAAA,CAAwB,aAAqB,UAAA,EAAqC;AAChG,EAAA,IAAI,CAAC,gBAAA,EAAkB;AACrB,IAAA;AAAA,EACF;AACA,EAAA,gBAAA,CAAiB,gBAAA,CAAiB,aAAa,UAAU,CAAA;AAC3D;AAUO,SAAS,iBAAA,CAAkB,aAAqB,UAAA,EAAqC;AAC1F,EAAA,IAAI,CAAC,gBAAA,EAAkB;AACrB,IAAA;AAAA,EACF;AACA,EAAA,gBAAA,CAAiB,UAAA,CAAW,aAAa,UAAU,CAAA;AACrD;;;;;;;;"}