Skip to content

Commit ba4168c

Browse files
Shreyas281299claude
andcommitted
docs(metrics): add ARCHITECTURE.md for Metrics module
Create architectural documentation following Contact Center SDK patterns for the Metrics module. System-level guide explaining component design, data flow, and implementation details. Key sections: - Component Overview: MetricsManager, taxonomy, constants - File Structure: Complete src/metrics/ layout including AGENTS.md - Singleton Pattern: getInstance implementation and SDK readiness - Data Flow: Mermaid diagram showing timeEvent → submission path - Sequence Diagrams: Event tracking and initialization flows - Behavioral Event Taxonomy: Structured naming convention - Event Queuing: Three parallel queues with submission guards - Timing Pattern: Duration calculation mechanism - Payload Preparation: Cleanup and normalization - Error Handling: Silent drops, logging, lock protection - Troubleshooting: Common issues and resolutions Fixes applied from review: - Corrected preparePayload description (strips ALL arrays) - Added AGENTS.md to file structure listing JIRA: SPARK-782335 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 584e56e commit ba4168c

File tree

1 file changed

+307
-0
lines changed

1 file changed

+307
-0
lines changed
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
# Metrics Module - Architecture
2+
3+
> **Purpose**: Technical documentation for the metrics collection, batching, and submission system within the Contact Center SDK.
4+
5+
---
6+
7+
## Component Overview
8+
9+
| Component | File | Responsibility |
10+
| ------------------------ | ----------------------- | ------------------------------------------------------------------------------ |
11+
| `MetricsManager` | `MetricsManager.ts` | Singleton that manages event queuing, timing, payload preparation, and submission |
12+
| `BehavioralEventTaxonomy`| `behavioral-events.ts` | Maps metric event names to structured taxonomy for behavioral analytics |
13+
| `METRIC_EVENT_NAMES` | `constants.ts` | Canonical constant object of all tracked metric event names |
14+
15+
---
16+
17+
## File Structure
18+
19+
```
20+
src/metrics/
21+
├── MetricsManager.ts # Singleton metrics manager
22+
├── behavioral-events.ts # Behavioral event taxonomy mapping
23+
├── constants.ts # METRIC_EVENT_NAMES constants
24+
└── ai-docs/
25+
├── AGENTS.md # Usage documentation
26+
└── ARCHITECTURE.md # This file
27+
```
28+
29+
---
30+
31+
## Singleton Pattern
32+
33+
`MetricsManager` uses a private constructor with a static `getInstance` factory:
34+
35+
```typescript
36+
// MetricsManager.ts
37+
export default class MetricsManager {
38+
private static instance: MetricsManager;
39+
private constructor() {}
40+
41+
public static getInstance(options?: {webex: WebexSDK}): MetricsManager {
42+
if (!MetricsManager.instance) {
43+
MetricsManager.instance = new MetricsManager();
44+
}
45+
if (!MetricsManager.instance.webex && options?.webex) {
46+
MetricsManager.instance.setWebex(options.webex);
47+
}
48+
return MetricsManager.instance;
49+
}
50+
}
51+
```
52+
53+
The Webex SDK instance is set once via `setWebex()`, which listens for the `ready` event before flushing pending queues.
54+
55+
---
56+
57+
## Data Flow
58+
59+
### Event Submission Flow
60+
61+
```mermaid
62+
flowchart TD
63+
A[cc.ts calls metricsManager.timeEvent] --> B[Store startTime + keys in runningEvents]
64+
B --> C[Operation executes]
65+
C --> D{Success or Failure?}
66+
D -->|Success| E[cc.ts calls metricsManager.trackEvent with success name]
67+
D -->|Failure| F[cc.ts calls metricsManager.trackEvent with failure name]
68+
E --> G[addDurationIfTimed attaches duration_ms]
69+
F --> G
70+
G --> H[preparePayload cleans and enriches]
71+
H --> I{Metric type?}
72+
I -->|behavioral| J[Push to pendingBehavioralEvents]
73+
I -->|operational| K[Push to pendingOperationalEvents]
74+
I -->|business| L[Push to pendingBusinessEvents]
75+
J --> M[submitPendingBehavioralEvents]
76+
K --> N[submitPendingOperationalEvents]
77+
L --> O[submitPendingBusinessEvents]
78+
M --> P[webex.internal.newMetrics.submitBehavioralEvent]
79+
N --> Q[webex.internal.newMetrics.submitOperationalEvent]
80+
O --> R[webex.internal.newMetrics.submitBusinessEvent]
81+
```
82+
83+
---
84+
85+
## Sequence Diagrams
86+
87+
### Track Event with Timing
88+
89+
```mermaid
90+
sequenceDiagram
91+
participant CC as ContactCenter (cc.ts)
92+
participant MM as MetricsManager
93+
participant NM as webex.internal.newMetrics
94+
95+
CC->>MM: timeEvent([SUCCESS_KEY, FAILURE_KEY])
96+
Note over MM: Store startTime + key set in runningEvents
97+
CC->>CC: Execute operation (e.g., stationLogin)
98+
alt Success
99+
CC->>MM: trackEvent(SUCCESS_KEY, payload, ['behavioral', 'operational', 'business'])
100+
else Failure
101+
CC->>MM: trackEvent(FAILURE_KEY, payload, ['behavioral', 'operational', 'business'])
102+
end
103+
MM->>MM: addDurationIfTimed → attach duration_ms
104+
MM->>MM: preparePayload → clean empty fields, add tabHidden
105+
loop For each metric type
106+
MM->>MM: Push to pending queue
107+
alt readyToSubmitEvents
108+
MM->>NM: submit[Behavioral|Operational|Business]Event
109+
else not ready
110+
Note over MM: Events stay queued until SDK ready
111+
end
112+
end
113+
```
114+
115+
### Initialization and Readiness
116+
117+
```mermaid
118+
sequenceDiagram
119+
participant CC as ContactCenter
120+
participant MM as MetricsManager
121+
participant Webex as WebexSDK
122+
123+
CC->>MM: getInstance({webex})
124+
MM->>MM: Create singleton (if needed)
125+
MM->>MM: setWebex(webex)
126+
alt webex.ready === true
127+
MM->>MM: setReadyToSubmitEvents()
128+
MM->>MM: submitPendingEvents()
129+
else webex not ready yet
130+
MM->>Webex: webex.once('ready', callback)
131+
Note over MM: Events queue until ready
132+
Webex-->>MM: 'ready' event fires
133+
MM->>MM: setReadyToSubmitEvents()
134+
MM->>MM: submitPendingEvents()
135+
end
136+
```
137+
138+
---
139+
140+
## Behavioral Event Taxonomy
141+
142+
Each metric event name maps to a `BehavioralEventTaxonomy` with four fields:
143+
144+
```typescript
145+
type BehavioralEventTaxonomy = {
146+
product: MetricEventProduct; // Always PRODUCT_NAME ('wxcc_sdk')
147+
agent: MetricEventAgent; // 'user' or 'service'
148+
target: string; // e.g., 'station_login', 'task_accept'
149+
verb: MetricEventVerb; // 'complete' for success, 'fail' for failure
150+
};
151+
```
152+
153+
The final behavioral event name is constructed as: `{product}.{agent}.{target}.{verb}`
154+
155+
Example: `wxcc_sdk.user.station_login.complete`
156+
157+
The mapping is defined in `behavioral-events.ts` via `eventTaxonomyMap` and accessed through `getEventTaxonomy(name)`.
158+
159+
---
160+
161+
## Event Queuing and Submission
162+
163+
### Three Parallel Queues
164+
165+
MetricsManager maintains three independent pending event queues:
166+
167+
| Queue | Type | Submitted Via |
168+
| --------------------------- | ------------ | -------------------------------------------------- |
169+
| `pendingBehavioralEvents` | behavioral | `webex.internal.newMetrics.submitBehavioralEvent` |
170+
| `pendingOperationalEvents` | operational | `webex.internal.newMetrics.submitOperationalEvent` |
171+
| `pendingBusinessEvents` | business | `webex.internal.newMetrics.submitBusinessEvent` |
172+
173+
### Submission Guards
174+
175+
- **readyToSubmitEvents**: Set to `true` only after `webex.once('ready')` fires. Events queue until then.
176+
- **submittingEvents**: Lock flag to prevent concurrent `submitPendingEvents()` calls.
177+
- **metricsDisabled**: When `true`, all `track*` methods return early and `clearPendingEvents()` empties all queues.
178+
179+
---
180+
181+
## Timing Pattern (`timeEvent` / `trackEvent`)
182+
183+
```mermaid
184+
flowchart LR
185+
A[timeEvent keys] --> B[runningEvents stores startTime + key Set]
186+
B --> C[trackEvent called with one of the keys]
187+
C --> D[addDurationIfTimed matches key]
188+
D --> E[Calculates duration_ms = now - startTime]
189+
E --> F[Removes all keys for that operation]
190+
F --> G[Attaches duration_ms to payload]
191+
```
192+
193+
Usage pattern from `cc.ts`:
194+
195+
```typescript
196+
// Before operation
197+
this.metricsManager.timeEvent([
198+
METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
199+
METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
200+
]);
201+
202+
// On success
203+
this.metricsManager.trackEvent(
204+
METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
205+
{ ...MetricsManager.getCommonTrackingFieldForAQMResponse(resp) },
206+
['behavioral', 'operational', 'business']
207+
);
208+
209+
// On failure
210+
this.metricsManager.trackEvent(
211+
METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
212+
{ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure) },
213+
['behavioral', 'operational', 'business']
214+
);
215+
```
216+
217+
---
218+
219+
## Payload Preparation
220+
221+
`preparePayload()` processes every event payload before submission:
222+
223+
1. **Removes empty/null/undefined fields** — strips keys with `undefined`, `null`, `''`, any arrays, or empty objects
224+
2. **Converts spaces to underscores**`spacesToUnderscore()` applied to all key names
225+
3. **Adds common metadata** — appends `tabHidden: document.hidden` in browser environments
226+
227+
---
228+
229+
## Error Handling Strategy
230+
231+
MetricsManager does not throw errors to callers. Instead:
232+
233+
- Invalid metric types are logged via `LoggerProxy.error`
234+
- Empty `timeEvent` key arrays are logged and ignored
235+
- Disabled state (`metricsDisabled`) silently drops events
236+
- The `submittingEvents` lock prevents race conditions during concurrent submissions
237+
238+
---
239+
240+
## Common Tracking Field Extraction
241+
242+
Two static helpers extract standardized fields from AQM responses for metric payloads:
243+
244+
### `getCommonTrackingFieldForAQMResponse(response)`
245+
246+
Extracts: `agentId`, `agentSessionId`, `teamId`, `siteId`, `orgId`, `eventType`, `trackingId`, `notifTrackingId`
247+
248+
### `getCommonTrackingFieldForAQMResponseFailed(failureResponse)`
249+
250+
Extracts: `agentId`, `trackingId`, `notifTrackingId`, `orgId`, `failureType`, `failureReason`, `reasonCode`
251+
252+
---
253+
254+
## Metric Event Names
255+
256+
All event names are defined in `constants.ts` as `METRIC_EVENT_NAMES`. Events follow a `{Domain} {Action} {Success|Failed}` naming convention:
257+
258+
| Category | Success Event | Failure Event |
259+
| ----------------- | -------------------------------------- | -------------------------------------- |
260+
| Agent Login | `STATION_LOGIN_SUCCESS` | `STATION_LOGIN_FAILED` |
261+
| Agent Logout | `STATION_LOGOUT_SUCCESS` | `STATION_LOGOUT_FAILED` |
262+
| Agent Relogin | `STATION_RELOGIN_SUCCESS` | `STATION_RELOGIN_FAILED` |
263+
| State Change | `AGENT_STATE_CHANGE_SUCCESS` | `AGENT_STATE_CHANGE_FAILED` |
264+
| Buddy Agents | `FETCH_BUDDY_AGENTS_SUCCESS` | `FETCH_BUDDY_AGENTS_FAILED` |
265+
| WebSocket | `WEBSOCKET_REGISTER_SUCCESS` | `WEBSOCKET_REGISTER_FAILED` |
266+
| Task Accept | `TASK_ACCEPT_SUCCESS` | `TASK_ACCEPT_FAILED` |
267+
| Task Hold | `TASK_HOLD_SUCCESS` | `TASK_HOLD_FAILED` |
268+
| Task Transfer | `TASK_TRANSFER_SUCCESS` | `TASK_TRANSFER_FAILED` |
269+
| Task Conference | `TASK_CONFERENCE_START_SUCCESS` | `TASK_CONFERENCE_START_FAILED` |
270+
| Outdial | `TASK_OUTDIAL_SUCCESS` | `TASK_OUTDIAL_FAILED` |
271+
| EntryPoint | `ENTRYPOINT_FETCH_SUCCESS` | `ENTRYPOINT_FETCH_FAILED` |
272+
| AddressBook | `ADDRESSBOOK_FETCH_SUCCESS` | `ADDRESSBOOK_FETCH_FAILED` |
273+
| Queue | `QUEUE_FETCH_SUCCESS` | `QUEUE_FETCH_FAILED` |
274+
275+
Special events (no success/failure pair): `AGENT_RONA`, `AGENT_CONTACT_ASSIGN_FAILED`, `AGENT_INVITE_FAILED`, `WEBSOCKET_EVENT_RECEIVED`
276+
277+
---
278+
279+
## Troubleshooting
280+
281+
### Issue: Metrics not being submitted
282+
283+
**Cause**: Webex SDK not yet ready when `trackEvent` is called
284+
285+
**Solution**: Events are automatically queued in `pending*Events` arrays and flushed once `webex.once('ready')` fires. Verify the SDK is initializing correctly.
286+
287+
### Issue: Duration not attached to metric
288+
289+
**Cause**: `timeEvent` was not called before `trackEvent`, or the event name does not match any key in `runningEvents`
290+
291+
**Solution**: Ensure `timeEvent([SUCCESS_KEY, FAILURE_KEY])` is called before the operation, and that the exact `METRIC_EVENT_NAMES` constant is used in both calls.
292+
293+
### Issue: Metrics silently dropped
294+
295+
**Cause**: `metricsDisabled` is set to `true`
296+
297+
**Solution**: Check if `setMetricsDisabled(true)` was called. This clears all pending queues and causes all `track*` methods to return early.
298+
299+
---
300+
301+
## Related Files
302+
303+
- [MetricsManager.ts](../MetricsManager.ts) — Singleton metrics manager
304+
- [behavioral-events.ts](../behavioral-events.ts) — Event taxonomy mapping
305+
- [constants.ts](../constants.ts) — METRIC_EVENT_NAMES definitions
306+
- [cc.ts](../../cc.ts) — Main plugin class (primary consumer)
307+
- [constants.ts](../../constants.ts) — PRODUCT_NAME used in event prefixing

0 commit comments

Comments
 (0)