Repository: webex/webex-js-sdk
Pull request #4173: feat(plugin-cc): metrics tracking for cc.ts
Author: login: bhabalan
Labels: name: validated
description: If the pull request is validated for automation.
Created: 2025-03-28T04:33:06Z | Merged: 2025-04-09T15:06:08Z

<!--
Hey there,\
Thank you for taking the time to improve our code! 🙂\
Please let us know why this change is necessary and what testing you have performed. \
This ensures our reviewers understand the impact of your change. \

**IMPORTANT**
FAILING TO FILL OUT THIS TEMPLATE WILL RESULT IN REJECTION OF YOUR PULL REQUEST
This is for compliance purposes with FedRAMP program.
-->

# COMPLETES #[SPARK-635169](https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-635169)

## This pull request addresses

Add metrics to cc.ts

## by making the following changes

Using MetricsManager to track events described in the confluence

<!-- You may include screenshots -->

### Change Type

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation update
- [ ] Tooling change
- [x] Internal code refactor

## The following scenarios were tested

- Features that are being tracked were tested

### I certified that

- [x] I have read and followed [contributing guidelines](https://github.com/webex/webex-js-sdk/blob/master/CONTRIBUTING.md#submitting-a-pull-request)
- [x] I discussed changes with code owners prior to submitting this pull request

- [x] I have not skipped any automated checks
- [] All existing and new tests passed
- [x] I have updated the documentation accordingly

---

Make sure to have followed the [contributing guidelines](https://github.com/webex/webex-js-sdk/blob/master/CONTRIBUTING.md#submitting-a-pull-request) before submitting.



Diff:
diff --git a/packages/@webex/internal-plugin-metrics/src/metrics.types.ts b/packages/@webex/internal-plugin-metrics/src/metrics.types.ts
index 62ff578d3c5..994f0f27922 100644
--- a/packages/@webex/internal-plugin-metrics/src/metrics.types.ts
+++ b/packages/@webex/internal-plugin-metrics/src/metrics.types.ts
@@ -290,6 +290,8 @@ export type SubmitOperationalEvent = (args: {
 export type SubmitBusinessEvent = (args: {
   name: OperationalEvent['metricName'];
   payload: EventPayload;
+  metadata?: EventPayload;
+  table?: Table;
 }) => void;
 
 export type SubmitMQE = (args: {
diff --git a/packages/@webex/plugin-cc/src/cc.ts b/packages/@webex/plugin-cc/src/cc.ts
index 6fc6308743e..a3801614ef3 100644
--- a/packages/@webex/plugin-cc/src/cc.ts
+++ b/packages/@webex/plugin-cc/src/cc.ts
@@ -50,6 +50,7 @@ import WebCallingService from './services/WebCallingService';
 import {ITask, TASK_EVENTS, TaskResponse, DialerPayload} from './services/task/types';
 import MetricsManager from './metrics/MetricsManager';
 import {METRIC_EVENT_NAMES} from './metrics/constants';
+import {Failure} from './services/core/GlobalTypes';
 
 export default class ContactCenter extends WebexPlugin implements IContactCenter {
   namespace = 'cc';
@@ -125,10 +126,31 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
    */
   public async register(): Promise<Profile> {
     try {
+      this.metricsManager.timeEvent([
+        METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_SUCCESS,
+        METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED,
+      ]);
       this.setupEventListeners();
 
-      return await this.connectWebsocket();
+      const resp = await this.connectWebsocket();
+      this.metricsManager.trackEvent(
+        METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_SUCCESS,
+        {
+          ...MetricsManager.getCommonTrackingFieldForAQMResponse(resp),
+          deviceType: resp.deviceType || EMPTY_STRING,
+        },
+        ['operational']
+      );
+
+      return resp;
     } catch (error) {
+      this.metricsManager.trackEvent(
+        METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED,
+        {
+          orgId: error.orgId,
+        },
+        ['operational']
+      );
       LoggerProxy.error(`Error during register: ${error}`, {
         module: CC_FILE,
         method: this.register.name,
@@ -148,10 +170,38 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
    */
   public async getBuddyAgents(data: BuddyAgents): Promise<BuddyAgentsResponse> {
     try {
-      return await this.services.agent.buddyAgents({
+      this.metricsManager.timeEvent([
+        METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_SUCCESS,
+        METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_FAILED,
+      ]);
+      const resp = await this.services.agent.buddyAgents({
         data: {agentProfileId: this.agentConfig.agentProfileID, ...data},
       });
+
+      this.metricsManager.trackEvent(
+        METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_SUCCESS,
+        {
+          ...MetricsManager.getCommonTrackingFieldForAQMResponse(resp),
+          mediaType: data.mediaType,
+          buddyAgentState: data.state,
+          buddyAgentCount: resp.data.agentList.length,
+        },
+        ['operational']
+      );
+
+      return resp;
     } catch (error) {
+      const failureResp = error.details as Failure;
+
+      this.metricsManager.trackEvent(
+        METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_FAILED,
+        {
+          ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failureResp),
+          mediaType: data.mediaType,
+          buddyAgentState: data.state,
+        },
+        ['operational']
+      );
       const {error: detailedError} = getErrorDetails(error, 'getBuddyAgents', CC_FILE);
       throw detailedError;
     }
@@ -224,7 +274,10 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
    */
   public async stationLogin(data: AgentLogin): Promise<StationLoginResponse> {
     try {
-      this.metricsManager.timeEvent(METRIC_EVENT_NAMES.STATION_LOGIN);
+      this.metricsManager.timeEvent([
+        METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
+        METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
+      ]);
       const loginResponse = this.services.agent.stationLogin({
         data: {
           dialNumber:
@@ -246,24 +299,16 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
         await this.webCallingService.registerWebCallingLine();
       }
 
-      this.webCallingService.setLoginOption(data.loginOption);
-
       const resp = await loginResponse;
 
       this.metricsManager.trackEvent(
-        METRIC_EVENT_NAMES.STATION_LOGIN,
+        METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
         {
-          isSuccess: true,
+          ...MetricsManager.getCommonTrackingFieldForAQMResponse(resp),
           loginType: data.loginOption,
-          agentId: resp.data.agentId,
-          teamId: resp.data.teamId,
-          siteId: resp.data.siteId,
-          roles: resp.data.roles.join(','),
-          trackingId: resp.data.trackingId,
-          notifTrackingId: resp.trackingId,
-          orgId: resp.orgId,
-          dataEventType: resp.data.eventType,
-          eventType: resp.type,
+          status: resp.data.status, // 'LoggedIn'
+          type: resp.data.type, // 'AgentStationLoginSuccess'
+          roles: resp.data.roles?.join(',') || EMPTY_STRING,
         },
         ['behavioral', 'business', 'operational']
       );
@@ -274,6 +319,15 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
 
       return resp;
     } catch (error) {
+      const failure = error.details as Failure;
+      this.metricsManager.trackEvent(
+        METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
+        {
+          ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
+          loginType: data.loginOption,
+        },
+        ['behavioral', 'business', 'operational']
+      );
       const {error: detailedError} = getErrorDetails(error, 'stationLogin', CC_FILE);
       throw detailedError;
     }
@@ -286,11 +340,24 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
    */
   public async stationLogout(data: Logout): Promise<StationLogoutResponse> {
     try {
+      this.metricsManager.timeEvent([
+        METRIC_EVENT_NAMES.STATION_LOGOUT_SUCCESS,
+        METRIC_EVENT_NAMES.STATION_LOGOUT_FAILED,
+      ]);
       const logoutResponse = this.services.agent.logout({
         data,
       });
 
-      await logoutResponse;
+      const resp = await logoutResponse;
+
+      this.metricsManager.trackEvent(
+        METRIC_EVENT_NAMES.STATION_LOGOUT_SUCCESS,
+        {
+          ...MetricsManager.getCommonTrackingFieldForAQMResponse(resp),
+          logoutReason: data.logoutReason,
+        },
+        ['behavioral', 'business', 'operational']
+      );
 
       if (this.webCallingService) {
         this.webCallingService.deregisterWebCallingLine();
@@ -302,8 +369,17 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
       // this.taskManager.off(TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
       // this.taskManager.off(TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
 
-      return logoutResponse;
+      return resp;
     } catch (error) {
+      const failure = error.details as Failure;
+      this.metricsManager.trackEvent(
+        METRIC_EVENT_NAMES.STATION_LOGOUT_FAILED,
+        {
+          ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
+          logoutReason: data.logoutReason,
+        },
+        ['behavioral', 'business', 'operational']
+      );
       const {error: detailedError} = getErrorDetails(error, 'stationLogout', CC_FILE);
       throw detailedError;
     }
@@ -315,10 +391,29 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
    */
   public async stationReLogin(): Promise<StationReLoginResponse> {
     try {
+      this.metricsManager.timeEvent([
+        METRIC_EVENT_NAMES.STATION_RELOGIN_SUCCESS,
+        METRIC_EVENT_NAMES.STATION_RELOGIN_FAILED,
+      ]);
       const reLoginResponse = await this.services.agent.reload();
+      this.metricsManager.trackEvent(
+        METRIC_EVENT_NAMES.STATION_RELOGIN_SUCCESS,
+        {
+          ...MetricsManager.getCommonTrackingFieldForAQMResponse(reLoginResponse),
+        },
+        ['behavioral', 'business', 'operational']
+      );
 
       return reLoginResponse;
     } catch (error) {
+      const failure = error.details as Failure;
+      this.metricsManager.trackEvent(
+        METRIC_EVENT_NAMES.STATION_RELOGIN_FAILED,
+        {
+          ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
+        },
+        ['behavioral', 'business', 'operational']
+      );
       const {error: detailedError} = getErrorDetails(error, 'stationReLogin', CC_FILE);
       throw detailedError;
     }
@@ -341,10 +436,28 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
 
   public async setAgentState(data: StateChange): Promise<SetStateResponse> {
     try {
+      this.metricsManager.timeEvent([
+        METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_SUCCESS,
+        METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_FAILED,
+      ]);
       const agentStatusResponse = await this.services.agent.stateChange({
         data: {...data, agentId: data.agentId || this.agentConfig.agentId},
       });
 
+      this.metricsManager.trackEvent(
+        METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_SUCCESS,
+        {
+          ...MetricsManager.getCommonTrackingFieldForAQMResponse(agentStatusResponse),
+          requestedState: data.state,
+          teamId: this.agentConfig?.teams[0]?.teamId ?? EMPTY_STRING,
+          status: agentStatusResponse.data?.status,
+          subStatus: agentStatusResponse.data?.subStatus,
+          auxCodeId: data.auxCodeId,
+          lastStateChangeReason: data.lastStateChangeReason || EMPTY_STRING,
+        },
+        ['behavioral', 'business', 'operational']
+      );
+
       LoggerProxy.log(`SET AGENT STATUS API SUCCESS`, {
         module: CC_FILE,
         method: this.setAgentState.name,
@@ -352,6 +465,17 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
 
       return agentStatusResponse;
     } catch (error) {
+      const failure = error.details as Failure;
+      this.metricsManager.trackEvent(
+        METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_FAILED,
+        {
+          ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
+          state: data.state,
+          auxCodeId: data.auxCodeId,
+          lastStateChangeReason: data.lastStateChangeReason || EMPTY_STRING,
+        },
+        ['behavioral', 'business', 'operational']
+      );
       const {error: detailedError} = getErrorDetails(error, 'setAgentState', CC_FILE);
       throw detailedError;
     }
diff --git a/packages/@webex/plugin-cc/src/metrics/MetricsManager.ts b/packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
index b3574ae8584..5b6ea8938bf 100644
--- a/packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
+++ b/packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
@@ -9,6 +9,8 @@ import {WebexSDK} from '../types';
 import {BehavioralEventTaxonomy, getEventTaxonomy} from './behavioral-events';
 import LoggerProxy from '../logger-proxy';
 import {METRIC_EVENT_NAMES} from './constants';
+import {Failure} from '../services/core/GlobalTypes';
+import {PRODUCT_NAME} from '../constants';
 
 type BehavioralEvent = {
   taxonomy: BehavioralEventTaxonomy;
@@ -22,9 +24,10 @@ type GenericEvent = {
 
 export type MetricsType = 'behavioral' | 'operational' | 'business';
 
+const PRODUCT_NAME_UPPER = PRODUCT_NAME.toUpperCase();
 export default class MetricsManager {
   private webex: WebexSDK;
-  private readonly runningEvents: Record<string, number> = {};
+  private readonly runningEvents: Record<string, {startTime: number; keys: Set<string>}> = {};
   private pendingBehavioralEvents: BehavioralEvent[] = [];
   private pendingOperationalEvents: GenericEvent[] = [];
   private pendingBusinessEvents: GenericEvent[] = [];
@@ -33,7 +36,7 @@ export default class MetricsManager {
 
   // eslint-disable-next-line no-use-before-define
   private static instance: MetricsManager;
-  private metricsDisabled = false;
+  private metricsDisabled = false; // TODO: SPARK-637285
 
   // eslint-disable-next-line no-useless-constructor
   private constructor() {}
@@ -85,7 +88,7 @@ export default class MetricsManager {
       this.pendingOperationalEvents.length = 0;
       eventsToSubmit.forEach((event) => {
         this.webex.internal.newMetrics.submitOperationalEvent({
-          name: event.name,
+          name: `${PRODUCT_NAME_UPPER}_${event.name}`,
           payload: event.payload,
         });
       });
@@ -101,47 +104,51 @@ export default class MetricsManager {
       this.pendingBusinessEvents.length = 0;
       eventsToSubmit.forEach((event) => {
         this.webex.internal.newMetrics.submitBusinessEvent({
-          name: event.name,
+          name: `${PRODUCT_NAME_UPPER}_${event.name}`,
           payload: event.payload,
+          metadata: {
+            appType: PRODUCT_NAME,
+          },
         });
       });
     }
   }
 
-  private addDurationIfTimed(name: string, options?: EventPayload): EventPayload {
+  private addDurationIfTimed(eventName: string, options?: EventPayload): EventPayload {
     const durationKey = 'duration_ms';
-    if (name in this.runningEvents) {
-      const startTime = this.runningEvents[name];
-      delete this.runningEvents[name];
-      if (startTime && options) {
+    for (const [genericKey, timing] of Object.entries(this.runningEvents)) {
+      if (timing.keys.has(eventName)) {
+        const startTime = timing.startTime;
+        // Remove all keys for this operation.
+        delete this.runningEvents[genericKey];
+        options = options || {};
         options[durationKey] = Date.now() - startTime;
 
         return options;
       }
-      if (startTime) {
-        const payload: EventPayload = {};
-        payload[durationKey] = Date.now() - startTime;
-
-        return payload;
-      }
-    }
-    if (options) {
-      return options;
     }
 
-    return {};
+    return options || {};
   }
 
   static spacesToUnderscore(str: string): string {
     return str.replace(/ /g, '_');
   }
 
-  private static preparePayload(options: EventPayload): EventPayload {
+  private static preparePayload(obj: EventPayload): EventPayload {
     const payload: EventPayload = {};
 
-    for (const [key, value] of Object.entries(options)) {
-      payload[MetricsManager.spacesToUnderscore(key)] = value; // Replace spaces with underscores
-    }
+    Object.keys(obj).forEach((key) => {
+      if (
+        obj[key] !== undefined &&
+        obj[key] !== null &&
+        obj[key] !== '' &&
+        !Array.isArray(obj[key]) &&
+        !(typeof obj[key] === 'object' && Object.keys(obj[key]).length === 0)
+      ) {
+        payload[MetricsManager.spacesToUnderscore(key)] = obj[key];
+      }
+    });
 
     if (typeof window === 'undefined') {
       return payload;
@@ -236,12 +243,19 @@ export default class MetricsManager {
     }
   }
 
-  public timeEvent(_name: string) {
+  public timeEvent(keys: string | string[]) {
     if (this.isMetricsDisabled()) {
       return;
     }
+    const keyArray = Array.isArray(keys) ? keys : [keys];
+    // Use the first key as the tracking key.
+    if (keyArray.length === 0) {
+      LoggerProxy.error('[MetricsManager] No keys provided for timeEvent');
 
-    this.runningEvents[_name] = Date.now();
+      return;
+    }
+    const genericKey = keyArray[0];
+    this.runningEvents[genericKey] = {startTime: Date.now(), keys: new Set(keyArray)};
   }
 
   private setWebex(webex: WebexSDK) {
@@ -270,4 +284,41 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
+    // This method is used to extract common tracking fields from the AQM response
+    // and return them as an object. The fields are extracted from the response
+    // object and its data property.
+    const fields = {
+      agentId: response?.data?.agentId || response?.agentId,
+      agentSessionId: response?.data?.agentSessionId || response?.agentSessionId,
+      teamId: response?.teamId ?? response?.data?.teamId ?? undefined,
+      siteId: response?.data?.siteId || response?.siteId,
+      orgId: response?.data?.orgId || response?.orgId,
+      eventType: response?.type,
+      trackingId: response?.data?.trackingId,
+      notifTrackingId: response?.trackingId,
+    };
+
+    return fields;
+  }
+
+  public static getCommonTrackingFieldForAQMResponseFailed(
+    failureResponse: Failure
+  ): Record<string, any> {
+    // This method is used to extract common tracking fields from the AQM response failure
+    // and return them as an object. The fields are extracted from the response
+    // object and its data property.
+    const fields = {
+      agentId: failureResponse?.data?.agentId,
+      trackingId: failureResponse?.trackingId,
+      notifTrackingId: failureResponse?.trackingId,
+      orgId: failureResponse?.orgId,
+      failureType: failureResponse?.type,
+      failureReason: failureResponse?.data?.reason,
+      reasonCode: failureResponse?.data?.reasonCode,
+    };
+
+    return fields;
+  }
 }
diff --git a/packages/@webex/plugin-cc/src/metrics/behavioral-events.ts b/packages/@webex/plugin-cc/src/metrics/behavioral-events.ts
index 26b64ee0d8c..a2c0f85433d 100644
--- a/packages/@webex/plugin-cc/src/metrics/behavioral-events.ts
+++ b/packages/@webex/plugin-cc/src/metrics/behavioral-events.ts
@@ -21,55 +21,97 @@ export function getEventTaxonomy(name: METRIC_EVENT_NAMES): BehavioralEventTaxon
   const product: MetricEventProduct = PRODUCT_NAME;
 
   switch (name) {
-    case METRIC_EVENT_NAMES.STATION_LOGIN:
+    case METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS:
       return {
         product,
         agent: 'user',
-        target: 'station',
-        verb: 'login',
+        target: 'station_login',
+        verb: 'complete',
+      };
+    case METRIC_EVENT_NAMES.STATION_LOGIN_FAILED:
+      return {
+        product,
+        agent: 'user',
+        target: 'station_login',
+        verb: 'fail',
       };
-    case METRIC_EVENT_NAMES.CALL_COMPLETED:
+    case METRIC_EVENT_NAMES.STATION_LOGOUT_SUCCESS:
       return {
         product,
         agent: 'user',
-        target: 'call',
+        target: 'station_logout',
         verb: 'complete',
       };
-    case METRIC_EVENT_NAMES.CALL_CONSULT_ACTIVATE:
+    case METRIC_EVENT_NAMES.STATION_LOGOUT_FAILED:
       return {
         product,
         agent: 'user',
-        target: 'call_consult',
-        verb: 'activate',
+        target: 'station_logout',
+        verb: 'fail',
       };
-    case METRIC_EVENT_NAMES.CALL_TRANSFER_ACTIVATE:
+    case METRIC_EVENT_NAMES.STATION_RELOGIN_SUCCESS:
       return {
         product,
         agent: 'user',
-        target: 'call_transfer',
-        verb: 'activate',
+        target: 'station_relogin',
+        verb: 'complete',
       };
-    case METRIC_EVENT_NAMES.CALL_ANSWERED:
+    case METRIC_EVENT_NAMES.STATION_RELOGIN_FAILED:
       return {
         product,
         agent: 'user',
-        target: 'call',
-        verb: 'answer',
+        target: 'station_relogin',
+        verb: 'fail',
       };
-    case METRIC_EVENT_NAMES.AGENT_RONA:
+    case METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_SUCCESS:
       return {
         product,
-        agent: 'service',
-        target: 'agent_rona',
-        verb: 'set',
+        agent: 'user',
+        target: 'state_change',
+        verb: 'complete',
+      };
+    case METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_FAILED:
+      return {
+        product,
+        agent: 'user',
+        target: 'state_change',
+        verb: 'fail',
+      };
+    case METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_SUCCESS:
+      return {
+        product,
+        agent: 'user',
+        target: 'buddy_agents_fetch',
+        verb: 'complete',
       };
-    case METRIC_EVENT_NAMES.TASK_END:
+    case METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_FAILED:
       return {
         product,
         agent: 'user',
-        target: 'task',
+        target: 'buddy_agents_fetch',
+        verb: 'fail',
+      };
+    case METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_SUCCESS:
+      return {
+        product,
+        agent: 'user',
+        target: 'websocket_register',
         verb: 'complete',
       };
+    case METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED:
+      return {
+        product,
+        agent: 'user',
+        target: 'websocket_register',
+        verb: 'fail',
+      };
+    case METRIC_EVENT_NAMES.AGENT_RONA:
+      return {
+        product,
+        agent: 'service',
+        target: 'agent_rona',
+        verb: 'set',
+      };
     default:
       break;
   }
diff --git a/packages/@webex/plugin-cc/src/metrics/constants.ts b/packages/@webex/plugin-cc/src/metrics/constants.ts
index 4f96d62579d..271a8269c4c 100644
--- a/packages/@webex/plugin-cc/src/metrics/constants.ts
+++ b/packages/@webex/plugin-cc/src/metrics/constants.ts
@@ -1,12 +1,18 @@
 type Enum<T extends Record<string, unknown>> = T[keyof T];
 
 export const METRIC_EVENT_NAMES = {
-  STATION_LOGIN: 'Station Login',
-  CALL_COMPLETED: 'Call Completed',
-  TASK_END: 'Task End',
-  CALL_CONSULT_ACTIVATE: 'Call Consult Activate',
-  CALL_TRANSFER_ACTIVATE: 'Call Transfer Activated',
-  CALL_ANSWERED: 'Call Answered',
+  STATION_LOGIN_SUCCESS: 'Station Login Success',
+  STATION_LOGIN_FAILED: 'Station Login Failed',
+  STATION_LOGOUT_SUCCESS: 'Station Logout Success',
+  STATION_LOGOUT_FAILED: 'Station Logout Failed',
+  STATION_RELOGIN_SUCCESS: 'Station Relogin Success',
+  STATION_RELOGIN_FAILED: 'Station Relogin Failed',
+  AGENT_STATE_CHANGE_SUCCESS: 'Agent State Change Success',
+  AGENT_STATE_CHANGE_FAILED: 'Agent State Change Failed',
+  FETCH_BUDDY_AGENTS_SUCCESS: 'Fetch Buddy Agents Success',
+  FETCH_BUDDY_AGENTS_FAILED: 'Fetch Buddy Agents Failed',
+  WEBSOCKET_REGISTER_SUCCESS: 'Websocket Register Success',
+  WEBSOCKET_REGISTER_FAILED: 'Websocket Register Failed',
   AGENT_RONA: 'Agent RONA',
 } as const;
 
diff --git a/packages/@webex/plugin-cc/src/services/agent/index.ts b/packages/@webex/plugin-cc/src/services/agent/index.ts
index c0672cd3682..97d9ae36fbb 100644
--- a/packages/@webex/plugin-cc/src/services/agent/index.ts
+++ b/packages/@webex/plugin-cc/src/services/agent/index.ts
@@ -5,8 +5,6 @@ import AqmReqs from '../core/aqm-reqs';
 import {HTTP_METHODS} from '../../types';
 import {WCC_API_GATEWAY} from '../constants';
 import {CC_EVENTS} from '../config/types';
-import MetricsManager from '../../metrics/MetricsManager';
-import {METRIC_EVENT_NAMES} from '../../metrics/constants';
 
 /*
  * routingAgent
@@ -15,8 +13,6 @@ import {METRIC_EVENT_NAMES} from '../../metrics/constants';
  */
 
 export default function routingAgent(routing: AqmReqs) {
-  const metricsManager = MetricsManager.getInstance();
-
   return {
     reload: routing.reqEmpty(() => ({
       host: WCC_API_GATEWAY,
@@ -63,17 +59,6 @@ export default function routingAgent(routing: AqmReqs) {
       host: WCC_API_GATEWAY,
       data: p.data,
       err: /* istanbul ignore next */ (e: any) => {
-        metricsManager.trackEvent(
-          METRIC_EVENT_NAMES.STATION_LOGIN,
-          {
-            ...p.data,
-            isSuccess: false,
-            trackingId: e.response?.headers?.trackingid?.split('_')[1],
-            roles: Array.isArray(p.data.roles) ? p.data.roles.join(',') : '',
-          },
-          ['operational', 'behavioral', 'business']
-        );
-
         return new Err.Details('Service.aqm.agent.stationLogin', {
           status: e.response?.status ?? 0,
           type: e.response?.data?.errorType,
diff --git a/packages/@webex/plugin-cc/test/unit/spec/cc.ts b/packages/@webex/plugin-cc/test/unit/spec/cc.ts
index b0f2bee2838..9e97076909d 100644
--- a/packages/@webex/plugin-cc/test/unit/spec/cc.ts
+++ b/packages/@webex/plugin-cc/test/unit/spec/cc.ts
@@ -32,8 +32,10 @@ import {Profile} from '../../../src/services/config/types';
 import TaskManager from '../../../src/services/task/TaskManager';
 import {AgentContact, TASK_EVENTS} from '../../../src/services/task/types';
 import MetricsManager from '../../../src/metrics/MetricsManager';
+import { METRIC_EVENT_NAMES } from '../../../src/metrics/constants';
 import Mercury from '@webex/internal-plugin-mercury';
 
+
 jest.mock('../../../src/logger-proxy', () => ({
   __esModule: true,
   default: {
@@ -294,6 +296,10 @@ describe('webex.cc', () => {
       });
       expect(reloadSpy).toHaveBeenCalled();
       expect(result).toEqual(mockAgentProfile);
+      expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
+        METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_SUCCESS,
+        METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED
+      ]);
     });
 
     it('should not register when config is undefined', async () => {
@@ -333,6 +339,10 @@ describe('webex.cc', () => {
       });
       expect(reloadSpy).not.toHaveBeenCalled();
       expect(result).toEqual(mockAgentProfile);
+      expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
+        METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_SUCCESS,
+        METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED
+      ]);
     });
 
     it('should log error and reject if registration fails', async () => {
@@ -501,6 +511,7 @@ describe('webex.cc', () => {
           auxCodeId: '',
         },
       });
+      expect(mockMetricsManager.timeEvent).toBeCalledWith([METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS, METRIC_EVENT_NAMES.STATION_LOGIN_FAILED]);
       expect(result).toEqual(mockData);
 
       const onSpy = jest.spyOn(mockTaskManager, 'on');
@@ -697,6 +708,10 @@ describe('webex.cc', () => {
       // );
       // expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
       expect(result).toEqual(response);
+      expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
+        METRIC_EVENT_NAMES.STATION_LOGOUT_SUCCESS,
+        METRIC_EVENT_NAMES.STATION_LOGOUT_FAILED
+      ]);
     });
 
     it('should handle error during stationLogout', async () => {
@@ -733,6 +748,10 @@ describe('webex.cc', () => {
 
       expect(stationLoginMock).toHaveBeenCalled();
       expect(result).toEqual(response);
+      expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
+        METRIC_EVENT_NAMES.STATION_RELOGIN_SUCCESS,
+        METRIC_EVENT_NAMES.STATION_RELOGIN_FAILED
+      ]);
     });
 
     it('should handle error during relogin', async () => {
@@ -786,6 +805,10 @@ describe('webex.cc', () => {
         module: CC_FILE,
         method: 'setAgentState',
       });
+      expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
+        METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_SUCCESS,
+        METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_FAILED
+      ]);
     });
 
     it('should set agent status successfully when status is Meeting', async () => {
@@ -808,6 +831,10 @@ describe('webex.cc', () => {
         module: CC_FILE,
         method: 'setAgentState',
       });
+      expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
+        METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_SUCCESS,
+        METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_FAILED
+      ]);
     });
 
     it('should handle error during setAgentStatus when status is Meeting', async () => {
@@ -907,6 +934,10 @@ describe('webex.cc', () => {
       });
 
       expect(result).toEqual(buddyAgentsResponse);
+      expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
+        METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_SUCCESS,
+        METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_FAILED
+      ]);
     });
 
     it('should handle error', async () => {
diff --git a/packages/@webex/plugin-cc/test/unit/spec/metrics/MetricsManager.ts b/packages/@webex/plugin-cc/test/unit/spec/metrics/MetricsManager.ts
index c4b856cab17..a35edbd2c66 100644
--- a/packages/@webex/plugin-cc/test/unit/spec/metrics/MetricsManager.ts
+++ b/packages/@webex/plugin-cc/test/unit/spec/metrics/MetricsManager.ts
@@ -34,7 +34,7 @@ describe('MetricsManagerImplementation', () => {
 
   describe('trackEvent', () => {
     it('should submit a behavioral, operational and business event when newMetrics is available', () => {
-      const eventName = METRIC_EVENT_NAMES.STATION_LOGIN;
+      const eventName = METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS;
       const data: EventPayload = {key: 'value'};
 
       metricsManager.trackEvent(eventName, data, ['behavioral', 'operational', 'business']);
@@ -46,24 +46,27 @@ describe('MetricsManagerImplementation', () => {
       expect(webex.internal.newMetrics.submitBehavioralEvent).toHaveBeenCalledWith({
         product: PRODUCT_NAME,
         agent: 'user',
-        target: 'station',
-        verb: 'login',
+        target: 'station_login',
+        verb: 'complete',
         payload: data,
       });
 
       expect(webex.internal.newMetrics.submitOperationalEvent).toHaveBeenCalledWith({
-        name: METRIC_EVENT_NAMES.STATION_LOGIN.replace(/ /g, '_').toUpperCase(),
+        name: PRODUCT_NAME.toUpperCase() + "_" + METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS.replace(/ /g, '_').toUpperCase(),
         payload: data,
       });
 
       expect(webex.internal.newMetrics.submitBusinessEvent).toHaveBeenCalledWith({
-        name: METRIC_EVENT_NAMES.STATION_LOGIN.replace(/ /g, '_').toUpperCase(),
+        name: PRODUCT_NAME.toUpperCase() + "_" + METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS.replace(/ /g, '_').toUpperCase(),
         payload: data,
+        metadata: {
+          appType: PRODUCT_NAME,
+        }
       });
     });
 
     it('should not submit a behavioral, operational and business event if array is invalid', () => {
-      const eventName = METRIC_EVENT_NAMES.STATION_LOGIN;
+      const eventName = METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS;
       const data: EventPayload = {key: 'value'};
 
       metricsManager.trackEvent(eventName, data, ['nonexistent']);
@@ -74,7 +77,7 @@ describe('MetricsManagerImplementation', () => {
     });
 
     it('should not submit a behavioral, operational and business event if metricsDisabled is true', () => {
-      const eventName = METRIC_EVENT_NAMES.STATION_LOGIN;
+      const eventName = METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS;
       const data: EventPayload = {key: 'value'};
 
       metricsManager.setMetricsDisabled(true);
@@ -86,7 +89,7 @@ describe('MetricsManagerImplementation', () => {
     });
 
     it('should not submit a behavioral event if metricsDisabled is true', () => {
-      const eventName = METRIC_EVENT_NAMES.STATION_LOGIN;
+      const eventName = METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS;
       const data: EventPayload = {key: 'value'};
 
       metricsManager.setMetricsDisabled(true);
@@ -96,7 +99,7 @@ describe('MetricsManagerImplementation', () => {
     });
 
     it('should not submit a operational event if metricsDisabled is true', () => {
-      const eventName = METRIC_EVENT_NAMES.STATION_LOGIN;
+      const eventName = METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS;
       const data: EventPayload = {key: 'value'};
 
       metricsManager.setMetricsDisabled(true);
@@ -106,7 +109,7 @@ describe('MetricsManagerImplementation', () => {
     });
 
     it('should not submit a business event if metricsDisabled is true', () => {
-      const eventName = METRIC_EVENT_NAMES.STATION_LOGIN;
+      const eventName = METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS;
       const data: EventPayload = {key: 'value'};
 
       metricsManager.setMetricsDisabled(true);
@@ -219,7 +222,34 @@ describe('MetricsManagerImplementation', () => {
 
       metricsManager.timeEvent(eventName);
 
-      expect(metricsManager['runningEvents'][eventName]).toBe(mockTimestamp);
+      expect(metricsManager['runningEvents'][eventName]['startTime']).toBe(mockTimestamp);
+    });
+
+    it('should set the event with the current timestamp if a single event name is provided and metrics are enabled', () => {
+      jest.spyOn(MetricsManager.prototype as any, 'isMetricsDisabled').mockReturnValue(false);
+      const eventName = 'testEvent';
+      const mockTimestamp = 1234567890;
+      jest.spyOn(Date, 'now').mockReturnValue(mockTimestamp);
+      metricsManager.timeEvent(eventName);
+
+      expect(metricsManager['runningEvents'][eventName]).toEqual({
+        startTime: mockTimestamp,
+        keys: new Set([eventName]),
+      });
+    });
+
+    it('should set the event with an array of keys when metrics are enabled', () => {
+      jest.spyOn(MetricsManager.prototype as any, 'isMetricsDisabled').mockReturnValue(false);
+      const eventArray = ['eventSuccess', 'eventFailed'];
+      const mockTimestamp = 987654321;
+      jest.spyOn(Date, 'now').mockReturnValue(mockTimestamp);
+      metricsManager.timeEvent(eventArray);
+
+      // The generic key is the first element of the array.
+      expect(metricsManager['runningEvents'][eventArray[0]]).toEqual({
+        startTime: mockTimestamp,
+        keys: new Set(eventArray),
+      });
     });
   });
 
@@ -231,7 +261,10 @@ describe('MetricsManagerImplementation', () => {
     });
 
     it('should add duration to options when event name is in runningEvents and options is provided', () => {
-      metricsManager['runningEvents']['event1'] = 500;
+      metricsManager['runningEvents']['event1'] = {
+        startTime: 500,
+        keys: new Set(['event1']),
+      };
       jest.spyOn(Date, 'now').mockImplementation(() => 1000);
       const options: EventPayload = {key: 'value'};
       const result = metricsManager['addDurationIfTimed']('event1', options);
@@ -239,7 +272,10 @@ describe('MetricsManagerImplementation', () => {
     });
 
     it('should create a new payload with duration when event name is in runningEvents and options is not provided', () => {
-      metricsManager['runningEvents']['event1'] = 500;
+      metricsManager['runningEvents']['event1'] = {
+        startTime: 500,
+        keys: new Set(['event1']),
+      };
       jest.spyOn(Date, 'now').mockImplementation(() => 1000);
       const result = metricsManager['addDurationIfTimed']('event1');
       expect(result).toEqual({duration_ms: 500});
@@ -322,7 +358,7 @@ describe('MetricsManagerInstantiation', () => {
     const instance = MetricsManager.getInstance();
     expect(instance).toBeDefined();
     expect(instance['pendingBehavioralEvents'].length).toBe(0);
-    instance.trackEvent(METRIC_EVENT_NAMES.STATION_LOGIN, {key: 'value'}, ['behavioral']);
+    instance.trackEvent(METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS, {key: 'value'}, ['behavioral']);
     expect(instance['pendingBehavioralEvents'].length).toBe(1);
 
     const setReadyToSubmitEventsSpy = jest.spyOn(
@@ -338,3 +374,118 @@ describe('MetricsManagerInstantiation', () => {
     expect(setReadyToSubmitEventsSpy).toHaveBeenCalled();
   });
 });
+
+describe('getCommonTrackingFieldForAQMResponse', () => {
+  it('should extract common tracking fields from a response with nested data', () => {
+    const response = {
+      data: {
+        agentId: 'agent1',
+        agentSessionId: 'session1',
+        teamId: 'team1',
+        siteId: 'site1',
+        orgId: 'org1',
+        trackingId: 'track1'
+      },
+      trackingId: 'notifTrack1',
+      type: 'some_event'
+    };
+    const expected = {
+      agentId: 'agent1',
+      agentSessionId: 'session1',
+      teamId: 'team1',
+      siteId: 'site1',
+      orgId: 'org1',
+      eventType: 'some_event',
+      trackingId: 'track1',
+      notifTrackingId: 'notifTrack1'
+    };
+    const result = MetricsManager.getCommonTrackingFieldForAQMResponse(response);
+    expect(result).toEqual(expected);
+  });
+
+  it('should extract common tracking fields from a flat response', () => {
+    const response = {
+      agentId: 'agent2',
+      agentSessionId: 'session2',
+      teamId: 'team2',
+      siteId: 'site2',
+      orgId: 'org2',
+      type: 'event_type',
+      trackingId: 'notifTrack2'
+    };
+    const expected = {
+      agentId: 'agent2',
+      agentSessionId: 'session2',
+      teamId: 'team2',
+      siteId: 'site2',
+      orgId: 'org2',
+      eventType: 'event_type',
+      trackingId: undefined,
+      notifTrackingId: 'notifTrack2'
+    };
+    const result = MetricsManager.getCommonTrackingFieldForAQMResponse(response);
+    expect(result).toEqual(expected);
+  });
+
+  it('should handle responses with missing fields gracefully', () => {
+    const response = {};
+    const expected = {
+      agentId: undefined,
+      agentSessionId: undefined,
+      teamId: undefined,
+      siteId: undefined,
+      orgId: undefined,
+      eventType: undefined,
+      trackingId: undefined,
+      notifTrackingId: undefined
+    };
+    const result = MetricsManager.getCommonTrackingFieldForAQMResponse(response);
+    expect(result).toEqual(expected);
+  });
+});
+
+describe('getCommonTrackingFieldForAQMResponseFailed', () => {
+  it('should extract common tracking fields from a failure response with complete fields', () => {
+    const failureResponse = {
+      data: {
+        agentId: 'agent1',
+        reason: 'Some reason',
+        reasonCode: 'R001'
+      },
+      trackingId: 'notifTrack1',
+      orgId: 'org1',
+      type: 'failure_event'
+    };
+    const expected = {
+      agentId: 'agent1',
+      trackingId: 'notifTrack1',
+      notifTrackingId: 'notifTrack1',
+      orgId: 'org1',
+      failureType: 'failure_event',
+      failureReason: 'Some reason',
+      reasonCode: 'R001'
+    };
+    const result = MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failureResponse);
+    expect(result).toEqual(expected);
+  });
+
+  it('should handle failure responses with missing fields gracefully', () => {
+    const failureResponse = {
+      data: {},
+      trackingId: undefined,
+      orgId: 'org2',
+      type: undefined
+    };
+    const expected = {
+      agentId: undefined,
+      trackingId: undefined,
+      notifTrackingId: undefined,
+      orgId: 'org2',
+      failureType: undefined,
+      failureReason: undefined,
+      reasonCode: undefined
+    };
+    const result = MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failureResponse);
+    expect(result).toEqual(expected);
+  });
+});
diff --git a/packages/@webex/plugin-cc/test/unit/spec/metrics/behavioral-events.ts b/packages/@webex/plugin-cc/test/unit/spec/metrics/behavioral-events.ts
index e63b8f3ddb1..125d38cb5f5 100644
--- a/packages/@webex/plugin-cc/test/unit/spec/metrics/behavioral-events.ts
+++ b/packages/@webex/plugin-cc/test/unit/spec/metrics/behavioral-events.ts
@@ -12,50 +12,91 @@ describe('metrics/behavioral-events', () => {
         verb: 'set',
       });
 
-      expect(getEventTaxonomy(METRIC_EVENT_NAMES.STATION_LOGIN)).toEqual({
+      expect(getEventTaxonomy(METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS)).toEqual({
         product,
         agent: 'user',
-        target: 'station',
-        verb: 'login',
+        target: 'station_login',
+        verb: 'complete',
+      });
+
+      expect(getEventTaxonomy(METRIC_EVENT_NAMES.STATION_LOGIN_FAILED)).toEqual({
+        product,
+        agent: 'user',
+        target: 'station_login',
+        verb: 'fail',
       });
 
-      expect(getEventTaxonomy(METRIC_EVENT_NAMES.CALL_COMPLETED)).toEqual({
+      expect(getEventTaxonomy(METRIC_EVENT_NAMES.STATION_LOGOUT_SUCCESS)).toEqual({
         product,
         agent: 'user',
-        target: 'call',
+        target: 'station_logout',
         verb: 'complete',
       });
 
-      expect(getEventTaxonomy(METRIC_EVENT_NAMES.CALL_CONSULT_ACTIVATE)).toEqual({
+      expect(getEventTaxonomy(METRIC_EVENT_NAMES.STATION_LOGOUT_FAILED)).toEqual({
         product,
         agent: 'user',
-        target: 'call_consult',
-        verb: 'activate',
+        target: 'station_logout',
+        verb: 'fail',
       });
 
-      expect(getEventTaxonomy(METRIC_EVENT_NAMES.CALL_TRANSFER_ACTIVATE)).toEqual({
+      expect(getEventTaxonomy(METRIC_EVENT_NAMES.STATION_RELOGIN_SUCCESS)).toEqual({
         product,
         agent: 'user',
-        target: 'call_transfer',
-        verb: 'activate',
+        target: 'station_relogin',
+        verb: 'complete',
       });
 
-      expect(getEventTaxonomy(METRIC_EVENT_NAMES.CALL_ANSWERED)).toEqual({
+      expect(getEventTaxonomy(METRIC_EVENT_NAMES.STATION_RELOGIN_FAILED)).toEqual({
         product,
         agent: 'user',
-        target: 'call',
-        verb: 'answer',
+        target: 'station_relogin',
+        verb: 'fail',
       });
 
-      expect(getEventTaxonomy(METRIC_EVENT_NAMES.TASK_END)).toEqual({
+      expect(getEventTaxonomy(METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_SUCCESS)).toEqual({
         product,
         agent: 'user',
-        target: 'task',
+        target: 'state_change',
         verb: 'complete',
       });
 
-      expect(getEventTaxonomy('' as METRIC_EVENT_NAMES)).toEqual(undefined);
+      expect(getEventTaxonomy(METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_FAILED)).toEqual({
+        product,
+        agent: 'user',
+        target: 'state_change',
+        verb: 'fail',
+      });
+
+      expect(getEventTaxonomy(METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_SUCCESS)).toEqual({
+        product,
+        agent: 'user',
+        target: 'buddy_agents_fetch',
+        verb: 'complete',
+      });
 
+      expect(getEventTaxonomy(METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_FAILED)).toEqual({
+        product,
+        agent: 'user',
+        target: 'buddy_agents_fetch',
+        verb: 'fail',
+      });
+
+      expect(getEventTaxonomy(METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_SUCCESS)).toEqual({
+        product,
+        agent: 'user',
+        target: 'websocket_register',
+        verb: 'complete',
+      });
+
+      expect(getEventTaxonomy(METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED)).toEqual({
+        product,
+        agent: 'user',
+        target: 'websocket_register',
+        verb: 'fail',
+      });
+
+      expect(getEventTaxonomy('' as METRIC_EVENT_NAMES)).toEqual(undefined);
     });
   });
 });


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>packages/@webex/plugin-cc/src/metrics/constants.ts (1)</summary><blockquote>

`18-18`: _⚠️ Potential issue_

**Fix missing Enum type**

The TypeScript compilation is failing because the `Enum` utility type is not defined or imported.


Replace with a standard TypeScript approach:

```diff
-export type METRIC_EVENT_NAMES = Enum<typeof METRIC_EVENT_NAMES>;
+export type METRIC_EVENT_NAMES = typeof METRIC_EVENT_NAMES[keyof typeof METRIC_EVENT_NAMES];
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: bhabalan is running Pull Request CI</summary>

[error] 18-18: error TS2304: Cannot find name 'Enum'.

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (7)</summary><blockquote>

<details>
<summary>packages/@webex/plugin-cc/src/cc.ts (3)</summary><blockquote>

`244-283`: **Consider simplifying the stationLogin response handling**

While the metrics implementation is good, the pattern of storing the response and awaiting it later is slightly inconsistent.


Consider simplifying for better code flow:

```diff
-const loginResponse = this.services.agent.stationLogin({
+const resp = await this.services.agent.stationLogin({
  data: {
    dialNumber:
      data.loginOption === LoginOption.BROWSER ? this.agentConfig.agentId : data.dialNumber,
    teamId: data.teamId,
    deviceType: data.loginOption,
    isExtension: data.loginOption === LoginOption.EXTENSION,
    deviceId: this.getDeviceId(data.loginOption, data.dialNumber),
    roles: [AGENT],
    // TODO: The public API should not have the following properties so filling them with empty values for now. If needed, we can add them in the future.
    teamName: EMPTY_STRING,
    siteId: EMPTY_STRING,
    usesOtherDN: false,
    auxCodeId: EMPTY_STRING,
  },
});

if (data.loginOption === LoginOption.BROWSER) {
  await this.webCallingService.registerWebCallingLine();
}

-const resp = await loginResponse;
```

---

`310-330`: **Consider simplifying the stationLogout response handling**

Similar to stationLogin, the pattern of storing and awaiting responses could be simplified.


```diff
-const logoutResponse = this.services.agent.logout({
+const resp = await this.services.agent.logout({
  data,
});

-const resp = await logoutResponse;
```

---

`116-445`: **Consider extracting a helper method for failure tracking**

There's a repeating pattern in all catch blocks for tracking failure events.


Consider creating a helper method to reduce duplication:

```typescript
private trackFailureEvent(
  eventName: METRIC_EVENT_NAMES, 
  failure: Failure, 
  additionalData?: Record<string, any>
) {
  this.metricsManager.trackEvent(
    eventName,
    {
      ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
      ...additionalData
    },
    ['behavioral', 'business', 'operational']
  );
}
```

This would simplify each catch block and make the code more maintainable.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 219-219: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</blockquote></details>
<details>
<summary>packages/@webex/plugin-cc/src/metrics/MetricsManager.ts (4)</summary><blockquote>

`39-39`: **Metrics disabled by default.**

You’ve added a `metricsDisabled` default of `true` with a TODO. Consider tying this into an environment check to selectively enable metrics for production vs. development, ensuring you don’t accidentally block legitimate usage.  

Would you like me to open a new issue or propose a follow-up PR implementing that logic?

---

`237-249`: **Potential confusion in multi-key usage.**

`timeEvent` accepts multiple keys, but only the first key is used as the top-level `genericKey`. Future lookups for additional keys will rely on the same entry, which gets removed once any single key’s duration is retrieved. Consider storing each key individually or returning an identifier to avoid accidental collisions.

---

`279-297`: **Type safety for response fields.**

`getCommonTrackingFieldForAQMResponse` filters out empty or undefined props, which is good. However, consider introducing a more specific response type instead of `any`, for better type-safety and clarity about expected fields in AQM responses.

---

`299-319`: **Type safety for failure handling.**

`getCommonTrackingFieldForAQMResponseFailed` mirrors the logic of the success method. Similarly, a typed interface (instead of `Failure` as a broad shape) can boost maintainability and clarity regarding which fields are optional or mandatory.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 0a2965f6be64a96f342116a2c71a85030433303e and 8cbc11789502daef4200df7b58342f801fb01cfa.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `packages/@webex/internal-plugin-metrics/src/metrics.types.ts` (2 hunks)
* `packages/@webex/plugin-cc/src/cc.ts` (10 hunks)
* `packages/@webex/plugin-cc/src/metrics/MetricsManager.ts` (6 hunks)
* `packages/@webex/plugin-cc/src/metrics/behavioral-events.ts` (1 hunks)
* `packages/@webex/plugin-cc/src/metrics/constants.ts` (1 hunks)
* `packages/@webex/plugin-cc/test/unit/spec/metrics/MetricsManager.ts` (9 hunks)
* `packages/@webex/plugin-cc/test/unit/spec/metrics/behavioral-events.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (5)</summary>

<details>
<summary>packages/@webex/plugin-cc/test/unit/spec/metrics/MetricsManager.ts (4)</summary><blockquote>

<details>
<summary>packages/@webex/plugin-cc/src/metrics/constants.ts (2)</summary>

* `METRIC_EVENT_NAMES` (1-15)
* `METRIC_EVENT_NAMES` (18-18)

</details>
<details>
<summary>packages/@webex/plugin-cc/src/constants.ts (1)</summary>

* `PRODUCT_NAME` (5-5)

</details>
<details>
<summary>packages/@webex/plugin-cc/src/metrics/MetricsManager.ts (1)</summary>

* `MetricsManager` (27-320)

</details>
<details>
<summary>packages/@webex/internal-plugin-metrics/src/metrics.types.ts (1)</summary>

* `EventPayload` (188-188)

</details>

</blockquote></details>
<details>
<summary>packages/@webex/plugin-cc/src/metrics/MetricsManager.ts (2)</summary><blockquote>

<details>
<summary>packages/@webex/plugin-cc/src/constants.ts (2)</summary>

* `PRODUCT_NAME` (5-5)
* `EMPTY_STRING` (4-4)

</details>
<details>
<summary>packages/@webex/internal-plugin-metrics/src/metrics.types.ts (1)</summary>

* `EventPayload` (188-188)

</details>

</blockquote></details>
<details>
<summary>packages/@webex/plugin-cc/src/cc.ts (4)</summary><blockquote>

<details>
<summary>packages/@webex/plugin-cc/src/metrics/constants.ts (2)</summary>

* `METRIC_EVENT_NAMES` (1-15)
* `METRIC_EVENT_NAMES` (18-18)

</details>
<details>
<summary>packages/@webex/plugin-cc/src/metrics/MetricsManager.ts (1)</summary>

* `MetricsManager` (27-320)

</details>
<details>
<summary>packages/@webex/plugin-cc/src/constants.ts (2)</summary>

* `EMPTY_STRING` (4-4)
* `CC_FILE` (10-10)

</details>
<details>
<summary>packages/@webex/plugin-cc/src/services/core/GlobalTypes.ts (1)</summary>

* `Failure` (8-14)

</details>

</blockquote></details>
<details>
<summary>packages/@webex/plugin-cc/test/unit/spec/metrics/behavioral-events.ts (2)</summary><blockquote>

<details>
<summary>packages/@webex/plugin-cc/src/metrics/behavioral-events.ts (1)</summary>

* `getEventTaxonomy` (17-120)

</details>
<details>
<summary>packages/@webex/plugin-cc/src/metrics/constants.ts (2)</summary>

* `METRIC_EVENT_NAMES` (1-15)
* `METRIC_EVENT_NAMES` (18-18)

</details>

</blockquote></details>
<details>
<summary>packages/@webex/plugin-cc/src/metrics/behavioral-events.ts (1)</summary><blockquote>

<details>
<summary>packages/@webex/plugin-cc/src/metrics/constants.ts (2)</summary>

* `METRIC_EVENT_NAMES` (1-15)
* `METRIC_EVENT_NAMES` (18-18)

</details>

</blockquote></details>

</details><details>
<summary>🪛 GitHub Actions: bhabalan is running Pull Request CI</summary>

<details>
<summary>packages/@webex/plugin-cc/src/metrics/constants.ts</summary>

[error] 18-18: error TS2304: Cannot find name 'Enum'.

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (42)</summary><blockquote>

<details>
<summary>packages/@webex/internal-plugin-metrics/src/metrics.types.ts (2)</summary>

`76-76`: **Good addition for tracking relogin events**

The new 'relogin' verb aligns well with the enhanced metrics tracking in the Contact Center module and provides more granular event classification.

---

`294-295`: **Well-structured enhancement to business event metrics**

Adding optional metadata and table properties to SubmitBusinessEvent increases flexibility for metric reporting, particularly useful for FedRAMP compliance requirements.

</details>
<details>
<summary>packages/@webex/plugin-cc/src/metrics/constants.ts (1)</summary>

`1-15`: **Good refactoring of event names with success/failure pattern**

The revised event naming structure provides clearer success/failure states, which improves observability and error tracking.

</details>
<details>
<summary>packages/@webex/plugin-cc/test/unit/spec/metrics/MetricsManager.ts (4)</summary>

`37-37`: **Updated tests align with new event taxonomy**

The event name updates in the tests properly reflect the new naming strategy in the constants file.



Also applies to: 49-51

---

`59-64`: **Added metadata field in business events**

The updated business event submission now includes metadata with appType, which aligns with the type changes in metrics.types.ts.

---

`225-253`: **Enhanced timeEvent tests for improved event tracking**

The new tests for timeEvent method validate the enhanced tracking of events, including the ability to track multiple keys with a shared timestamp. This is a good improvement for accurate metrics collection.

---

`264-268`: **Updated runningEvents structure correctly tested**

The updated structure for runningEvents (with startTime and keys Set) is properly tested, ensuring consistent behavior across the implementation.



Also applies to: 275-278

</details>
<details>
<summary>packages/@webex/plugin-cc/src/cc.ts (6)</summary>

`42-42`: **Good addition of Failure type import**

The explicit import of the Failure type for error handling improves type safety and code readability.

---

`116-135`: **Comprehensive metrics added for websocket registration**

The registration process now tracks both success and failure metrics with detailed context, significantly improving observability.

---

`160-194`: **Well-structured metrics for buddy agents retrieval**

The implementation properly captures success and failure metrics with relevant context. Good use of timeEvent to track duration.

---

`291-299`: **Good error tracking with detailed context**

Properly captures failure details for metrics, improving debugging and monitoring capabilities.

---

`361-385`: **Good implementation of metrics for station relogin**

The implementation accurately tracks both success and failure scenarios with appropriate context.

---

`406-445`: **Well-structured metrics for agent state change**

The implementation provides comprehensive tracking of state changes with detailed context information.

</details>
<details>
<summary>packages/@webex/plugin-cc/src/metrics/MetricsManager.ts (3)</summary>

`12-13`: **Imports look appropriate.**

These new imports (`Failure`, `EMPTY_STRING`, `PRODUCT_NAME`) are properly used below for handling AQM response fields.

---

`29-29`: **Multiple-key tracking structure.**

Transitioning `runningEvents` to map each key to `{startTime, keys}` allows for timing multiple correlated event names. This is sensible but can be more complex if there are numerous secondary keys. Keep an eye on potential memory usage if those sets grow large.

---

`116-130`: **Confirm intended removal of all keyed events.**

When `addDurationIfTimed` finds a match in `timing.keys`, it deletes the entire record from `runningEvents`. That removal also discards timing data for the other keys in the same set. If you intend to track multiple keys independently, you may need to remove only the matched key from `timing.keys` instead.

</details>
<details>
<summary>packages/@webex/plugin-cc/test/unit/spec/metrics/behavioral-events.ts (13)</summary>

`15-20`: **STATION_LOGIN_SUCCESS test coverage looks correct.**

---

`22-27`: **STATION_LOGIN_FAILED test coverage looks correct.**

---

`29-35`: **STATION_LOGOUT_SUCCESS test coverage looks correct.**

---

`36-41`: **STATION_LOGOUT_FAILED test coverage looks correct.**

---

`43-49`: **STATION_RELOGIN_SUCCESS test coverage looks correct.**

---

`50-56`: **STATION_RELOGIN_FAILED test coverage looks correct.**

---

`57-63`: **AGENT_STATE_CHANGE_SUCCESS test coverage looks correct.**

---

`64-70`: **AGENT_STATE_CHANGE_FAILED test coverage looks correct.**

---

`71-77`: **FETCH_BUDDY_AGENTS_SUCCESS test coverage looks correct.**

---

`78-84`: **FETCH_BUDDY_AGENTS_FAILED test coverage looks correct.**

---

`85-91`: **WEBSOCKET_REGISTER_SUCCESS test coverage looks correct.**

---

`92-98`: **WEBSOCKET_REGISTER_FAILED test coverage looks correct.**

---

`99-99`: **Fallback scenario verified.**

The test expecting `undefined` for an empty string event name ensures graceful handling of unrecognized event names.

</details>
<details>
<summary>packages/@webex/plugin-cc/src/metrics/behavioral-events.ts (13)</summary>

`24-30`: **STATION_LOGIN_SUCCESS taxonomy.**

Switching `target` to `'station_login'` and `verb` to `'complete'` aligns with the new event naming convention.

---

`31-37`: **STATION_LOGIN_FAILED taxonomy.**

Setting `verb` to `'fail'` is consistent with the failed login scenario.

---

`38-45`: **STATION_LOGOUT_SUCCESS taxonomy.**

Mapping `'station_logout'` + `'complete'` matches the success pattern.

---

`46-51`: **STATION_LOGOUT_FAILED taxonomy.**

Use of `'fail'` for a logout failure is consistent.

---

`52-59`: **STATION_RELOGIN_SUCCESS taxonomy.**

Using `'station_relogin'` + `'complete'` clarifies the success path for re-login events.

---

`60-65`: **STATION_RELOGIN_FAILED taxonomy.**

`'station_relogin'` + `'fail'` properly indicates a failed re-login attempt.

---

`66-72`: **AGENT_STATE_CHANGE_SUCCESS taxonomy.**

`target: 'state_change', verb: 'complete'` is a clear approach to successful agent state changes.

---

`73-79`: **AGENT_STATE_CHANGE_FAILED taxonomy.**

Using `'fail'` for an agent state change failure aligns well with the rest of the naming scheme.

---

`80-86`: **FETCH_BUDDY_AGENTS_SUCCESS taxonomy.**

`'buddy_agents_fetch'` + `'complete'` is self-descriptive of success in retrieving buddy agents.

---

`87-93`: **FETCH_BUDDY_AGENTS_FAILED taxonomy.**

`'buddy_agents_fetch'` + `'fail'` is consistent with the naming for a failed fetch.

---

`94-100`: **WEBSOCKET_REGISTER_SUCCESS taxonomy.**

`'websocket_register'` + `'complete'` clarifies a successful WebSocket registration scenario.

---

`101-107`: **WEBSOCKET_REGISTER_FAILED taxonomy.**

`'websocket_register'` + `'fail'` covers the failure path.

---

`108-114`: **AGENT_RONA taxonomy.**

`agent: 'service'` for `'agent_rona'` with a `'set'` verb accurately captures the system-driven nature of RONA.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T03:49:29Z
user: login: bhabalan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T03:52:37Z
user: login: mkesavan13
body: Looks good to me mostly. Just a few comments. Also let us have the title reflecting the component like this:
`feat(plugin-cc): metrics tracking for cc.ts`
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T15:18:16Z
user: login: bhabalan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T05:13:44Z
user: login: bhabalan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T05:15:48Z
user: login: sreenara
body: As discussed, what do you think about removing the check for sending metrics in dev mode? Considering we have a beta starting next week, isn't everyone in dev mode?
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T13:28:12Z
user: login: bhabalan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T16:19:39Z
user: login: bhabalan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T16:20:47Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>packages/@webex/plugin-cc/src/metrics/MetricsManager.ts (1)</summary><blockquote>

`116-131`: **Enhanced duration calculation for multiple keys.**  
Currently, deleting the entire entry after finding one matching key might remove timing data for additional keys. Consider removing only the matched key from the set, then removing the record only if the set is empty.  

```diff
- delete this.runningEvents[genericKey];
...
+ timing.keys.delete(eventName);
+ if (timing.keys.size === 0) {
+   delete this.runningEvents[genericKey];
+ }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 8cbc11789502daef4200df7b58342f801fb01cfa and 277c985402607b222e4aaada4f084b046e962a87.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `packages/@webex/plugin-cc/src/cc.ts` (10 hunks)
* `packages/@webex/plugin-cc/src/metrics/MetricsManager.ts` (6 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (2)</summary>

<details>
<summary>packages/@webex/plugin-cc/src/cc.ts (4)</summary><blockquote>

<details>
<summary>packages/@webex/plugin-cc/src/metrics/constants.ts (2)</summary>

* `METRIC_EVENT_NAMES` (1-15)
* `METRIC_EVENT_NAMES` (18-18)

</details>
<details>
<summary>packages/@webex/plugin-cc/src/metrics/MetricsManager.ts (1)</summary>

* `MetricsManager` (27-332)

</details>
<details>
<summary>packages/@webex/plugin-cc/src/constants.ts (2)</summary>

* `EMPTY_STRING` (4-4)
* `CC_FILE` (10-10)

</details>
<details>
<summary>packages/@webex/plugin-cc/src/services/core/GlobalTypes.ts (1)</summary>

* `Failure` (8-14)

</details>

</blockquote></details>
<details>
<summary>packages/@webex/plugin-cc/src/metrics/MetricsManager.ts (3)</summary><blockquote>

<details>
<summary>packages/@webex/plugin-cc/src/constants.ts (1)</summary>

* `PRODUCT_NAME` (5-5)

</details>
<details>
<summary>packages/@webex/internal-plugin-metrics/src/metrics.types.ts (1)</summary>

* `EventPayload` (188-188)

</details>
<details>
<summary>packages/@webex/plugin-cc/src/services/core/GlobalTypes.ts (1)</summary>

* `Failure` (8-14)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (40)</summary><blockquote>

<details>
<summary>packages/@webex/plugin-cc/src/cc.ts (32)</summary>

`53-53`: **New typed error import.**  
Looks good.

---

`129-132`: **Time event instrumentation.**  
Leveraging multiple event keys is aligned with the updated `MetricsManager`. No issues found.

---

`135-143`: **Successful websocket registration event tracking.**  
No concerns.

---

`147-153`: **Failed websocket registration event tracking.**  
Consider verifying whether `error.orgId` might be undefined; fallback to an empty string if needed.

---

`173-176`: **Time event for buddy agents retrieval.**  
Implementation looks consistent.

---

`177-177`: **Buddy agents fetch call.**  
No issues found.

---

`181-190`: **Successful buddy agents fetch metrics.**  
Ensure `resp.data.agentList` is always defined before accessing `length`.

---

`194-194`: **Casting error details to Failure.**  
Consider confirming `error.details` is always defined.

---

`196-204`: **Failed buddy agents fetch metrics.**  
No issues found.

---

`257-260`: **Time event for station login.**  
No concerns.

---

`285-285`: **Station login success tracking.**  
Implementation is consistent.

---

`287-287`: **Common tracking fields usage.**  
No issues found.

---

`290-290`: **Capturing success status.**  
Looks good.

---

`291-291`: **Capturing success type.**  
No issues found.

---

`302-302`: **Casting error details for station login failure.**  
Implementation is aligned with prior usage.

---

`303-310`: **Failed station login event tracking.**  
Confirm that `error.details` always matches `Failure`.

---

`323-326`: **Time event for station logout.**  
No concerns.

---

`331-331`: **Awaiting logout response.**  
Implementation looks fine.

---

`333-340`: **Successful logout event tracking.**  
Appears correct.

---

`352-352`: **Returning station logout response.**  
No issues found.

---

`354-354`: **Casting error details for station logout failure.**  
Implementation is consistent.

---

`355-362`: **Failed station logout event tracking.**  
No issues found.

---

`374-377`: **Time event for station relogin.**  
Implementation is consistent with existing usage.

---

`379-379`: **Successful station relogin event tracking.**  
No issues found.

---

`380-385`: **Extracting common tracking fields for relogin success.**  
Looks good.

---

`389-389`: **Casting error details for station relogin failure.**  
Implementation is aligned with prior usage.

---

`390-396`: **Failed station relogin event tracking.**  
No issues found.

---

`419-422`: **Time event for agent state change.**  
Implementation is consistent.

---

`427-427`: **Agent state change success tracking.**  
Implementation looks fine.

---

`428-439`: **Capturing details for agent state change success.**  
No issues found.

---

`448-448`: **Casting error details for agent state change failure.**  
Implementation is consistent with prior usage.

---

`449-458`: **Failed agent state change event tracking.**  
No issues found.

</details>
<details>
<summary>packages/@webex/plugin-cc/src/metrics/MetricsManager.ts (8)</summary>

`12-13`: **New imports for Failure and PRODUCT_NAME.**  
No issues found.

---

`29-29`: **Refined type for runningEvents.**  
Storing multiple keys and a startTime is a good approach for multi-event tracking.

---

`38-38`: **Handling metricsDisabled.**  
No issues found.

---

`238-249`: **Updated timeEvent method to accept multiple keys.**  
Implementation for storing a set of keys is sensible and consistent.

---

`279-279`: **Utility function for common success fields.**  
No immediate concerns.

---

`280-309`: **Extracting fields from success response.**  
Implementation looks correct; ensure unknown properties are handled gracefully.

---

`311-311`: **Utility function for common failure fields.**  
No issues found.

---

`312-331`: **Extracting fields from failure response.**  
Implementation appears consistent; no concerns.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T05:10:01Z
user: login: bhabalan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T11:15:48Z
user: login: sreenara
body: I only have comments about the UT. Other things look fine.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T04:05:44Z
user: login: bhabalan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T11:15:05Z
user: login: bhabalan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T11:15:16Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>packages/@webex/plugin-cc/src/metrics/MetricsManager.ts (1)</summary><blockquote>

`288-315`: **Add descriptive comments to explain complex extraction logic.**

The utility method for extracting tracking fields from successful responses could benefit from more detailed comments explaining the different paths for extracting `teamId`.

```diff
  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
    // This method is used to extract common tracking fields from the AQM response
    // and return them as an object. The fields are extracted from the response
    // object and its data property.
+   // The teamId can be extracted from multiple possible locations:
+   // 1. Directly from response.data.teamId
+   // 2. Directly from response.teamId
+   // 3. From the first team in response.data.teams array
+   // 4. From the first team in response.teams array
    let teamId: string | undefined;
    if (response?.data?.teamId) {
      teamId = response.data.teamId;
    } else if (response?.teamId) {
      teamId = response.teamId;
    } else if (Array.isArray(response?.data?.teams) && response.data.teams.length > 0) {
      teamId = response.data.teams[0].id;
    } else if (Array.isArray(response?.teams) && response.teams.length > 0) {
      teamId = response.teams[0].id;
    }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 592d33238261ee4bc92a3f9b8bc8f6b9c2ec8200 and ea36722d31bed91a4799cb51be9ad27e56753409.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `packages/@webex/plugin-cc/src/cc.ts` (10 hunks)
* `packages/@webex/plugin-cc/src/metrics/MetricsManager.ts` (7 hunks)
* `packages/@webex/plugin-cc/test/unit/spec/cc.ts` (9 hunks)
* `packages/@webex/plugin-cc/test/unit/spec/metrics/MetricsManager.ts` (10 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* packages/@webex/plugin-cc/src/cc.ts
* packages/@webex/plugin-cc/test/unit/spec/metrics/MetricsManager.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>packages/@webex/plugin-cc/test/unit/spec/cc.ts (1)</summary><blockquote>

<details>
<summary>packages/@webex/plugin-cc/src/metrics/constants.ts (2)</summary>

* `METRIC_EVENT_NAMES` (3-17)
* `METRIC_EVENT_NAMES` (20-20)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (6)</summary>

* GitHub Check: Test - Syntax
* GitHub Check: Test - Unit
* GitHub Check: Test - Integration
* GitHub Check: Test - Style
* GitHub Check: Test - Browser
* GitHub Check: Test - Coverage

</details>

<details>
<summary>🔇 Additional comments (19)</summary><blockquote>

<details>
<summary>packages/@webex/plugin-cc/test/unit/spec/cc.ts (9)</summary>

`35-35`: **Good addition for consistent metric event names.**

Adding the import for `METRIC_EVENT_NAMES` ensures consistent usage of event name constants across the test file.

---

`299-302`: **Proper metrics tracking for register success/failure.**

The test now verifies that the `timeEvent` method is called with both success and failure event names for WebSocket registration, which ensures complete metrics coverage.

---

`342-345`: **Consistent metrics verification.**

This assertion matches the previous test case, maintaining consistency in how metrics are verified throughout the test suite.

---

`514-514`: **Verifying station login metrics.**

Good test coverage to ensure that both success and failure metrics are tracked for the station login operation.

---

`711-714`: **Proper metric tracking verification for station logout.**

The test now ensures that both success and failure metrics are tracked when calling stationLogout.

---

`751-754`: **Complete station relogin metrics verification.**

This assertion properly verifies that both success and failure metrics are being tracked for station relogin operations.

---

`808-811`: **Comprehensive agent state change metrics.**

The test appropriately verifies metrics tracking for agent state changes, ensuring both success and failure events are monitored.

---

`834-837`: **Consistent agent state change metrics verification.**

The test consistently verifies metrics tracking across different agent state change scenarios, maintaining the same pattern established earlier.

---

`937-940`: **Complete buddy agents metrics verification.**

The test correctly verifies that metrics are tracked for both successful and failed attempts to fetch buddy agents.

</details>
<details>
<summary>packages/@webex/plugin-cc/src/metrics/MetricsManager.ts (10)</summary>

`12-13`: **Appropriate imports for enhanced functionality.**

Adding imports for `Failure` and `PRODUCT_NAME` supports the new functionality for tracking metrics with improved context.

---

`27-27`: **Good performance optimization.**

Creating a constant for the uppercase product name avoids repeatedly calling `toUpperCase()` throughout the code, which is a small but valuable optimization.

---

`30-30`: **Enhanced event tracking structure.**

The redesigned `runningEvents` structure now efficiently supports tracking multiple events with the same start time, which is necessary for the paired success/failure events approach.

---

`39-39`: **Remember to address the TODO.**

The `metricsDisabled` flag is introduced with a TODO referencing SPARK-637285. Ensure this is properly addressed in future work.

Is there a timeline for completing SPARK-637285 to handle metrics in development mode? This would help ensure metrics aren't accidentally sent during testing.

---

`91-92`: **Consistent event naming.**

Using the uppercase product name as a prefix for operational events provides better categorization and consistency.

---

`107-112`: **Enhanced business events with metadata.**

Adding the product name to both the event name and metadata improves event context and categorization for business events.

---

`117-132`: **Improved duration tracking for multiple events.**

The refactored `addDurationIfTimed` method now properly handles multiple event keys sharing the same start time, which supports the paired success/failure event structure.

---

`138-161`: **Robust payload preparation.**

The enhanced `preparePayload` method now properly filters out various types of empty values (undefined, null, empty strings, empty arrays, empty objects) for cleaner metrics data.

---

`246-259`: **Flexible event timing support.**

The updated `timeEvent` method now accepts either a single string or an array of strings as keys, supporting the pattern of tracking both success and failure events with a single timing call.


I see good error handling for empty keys and the correct implementation for storing multiple events with the same timing.

---

`317-334`: **Comprehensive failure tracking.**

The utility method for extracting tracking fields from failed responses provides useful context for diagnosing issues, capturing important fields like failure reason and reason code.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T11:21:31Z
user: login: bhabalan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T11:38:09Z
user: login: mkesavan13
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T11:49:46Z
user: login: mkesavan13
body: Added a few comments out of this review as well. Please check
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T11:53:13Z
user: login: bhabalan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T11:55:10Z
user: login: bhabalan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T11:55:53Z
user: login: bhabalan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T11:56:05Z
user: login: bhabalan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T11:57:47Z
user: login: mkesavan13
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T11:58:07Z
user: login: mkesavan13
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T12:01:41Z
user: login: bhabalan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T12:03:22Z
user: login: bhabalan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T12:04:15Z
user: login: mkesavan13
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T12:04:52Z
user: login: bhabalan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T12:11:38Z
user: login: mkesavan13
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T12:52:42Z
user: login: mkesavan13
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T12:52:51Z
user: login: sreenara
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T13:15:36Z
user: login: sreenara
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T13:15:55Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>packages/@webex/plugin-cc/src/metrics/MetricsManager.ts (5)</summary><blockquote>

`292-301`: **Consider additional validation for AQM response field extraction.**

When extracting fields from responses, there's a potential for accessing properties on undefined objects. Consider adding null checks or using optional chaining more consistently.

```diff
 const fields = {
-  agentId: response?.data?.agentId || response?.agentId,
-  agentSessionId: response?.data?.agentSessionId || response?.agentSessionId,
-  teamId: response?.teamId ?? response?.data?.teamId ?? undefined,
-  siteId: response?.data?.siteId || response?.siteId,
-  orgId: response?.data?.orgId || response?.orgId,
+  agentId: response?.data?.agentId ?? response?.agentId ?? undefined,
+  agentSessionId: response?.data?.agentSessionId ?? response?.agentSessionId ?? undefined,
+  teamId: response?.teamId ?? response?.data?.teamId ?? undefined,
+  siteId: response?.data?.siteId ?? response?.siteId ?? undefined,
+  orgId: response?.data?.orgId ?? response?.orgId ?? undefined,
   eventType: response?.type,
-  trackingId: response?.data?.trackingId,
-  notifTrackingId: response?.trackingId,
+  trackingId: response?.data?.trackingId ?? undefined,
+  notifTrackingId: response?.trackingId ?? undefined,
 };
```

Similar changes could be applied to the failure response fields.


Also applies to: 312-320

---

`149-151`: **Consider simplifying the null checks in preparePayload.**

The current implementation has multiple nested conditions. Consider using optional chaining and nullish coalescing operators for more readable code.

```diff
-      if (
-        obj[key] !== undefined &&
-        obj[key] !== null &&
-        obj[key] !== '' &&
-        !Array.isArray(obj[key]) &&
-        !(typeof obj[key] === 'object' && Object.keys(obj[key]).length === 0)
-      ) {
-        payload[MetricsManager.spacesToUnderscore(key)] = obj[key];
-      }
+      const value = obj[key];
+      if (
+        value !== undefined &&
+        value !== null &&
+        value !== '' &&
+        !Array.isArray(value) &&
+        !(typeof value === 'object' && Object.keys(value ?? {}).length === 0)
+      ) {
+        payload[MetricsManager.spacesToUnderscore(key)] = value;
+      }
```

---

`246-259`: **Consider extracting key validation to a separate method.**

The validation logic in `timeEvent` could be extracted to a separate method for better reusability and testability.

```diff
 public timeEvent(keys: string | string[]) {
   if (this.isMetricsDisabled()) {
     return;
   }
+  const keyArray = this.validateAndNormalizeKeys(keys);
+  if (keyArray.length === 0) {
+    return;
+  }
+  const genericKey = keyArray[0];
+  this.runningEvents[genericKey] = {startTime: Date.now(), keys: new Set(keyArray)};
+ }
+
+ private validateAndNormalizeKeys(keys: string | string[]): string[] {
   const keyArray = Array.isArray(keys) ? keys : [keys];
   // Use the first key as the tracking key.
   if (keyArray.length === 0) {
     LoggerProxy.error('[MetricsManager] No keys provided for timeEvent');
-
-    return;
+    return [];
   }
-  const genericKey = keyArray[0];
-  this.runningEvents[genericKey] = {startTime: Date.now(), keys: new Set(keyArray)};
+  return keyArray;
 }
```

---

`288-323`: **Add JSDoc comments for the new utility methods.**

The utility methods have descriptive inline comments, but adding proper JSDoc comments would improve documentation and IDE support.

```diff
+  /**
+   * Extracts common tracking fields from an AQM response.
+   * @param response - The AQM response object
+   * @returns An object containing extracted tracking fields
+   */
   public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
     // This method is used to extract common tracking fields from the AQM response
     // and return them as an object. The fields are extracted from the response
     // object and its data property.
     const fields = {
       agentId: response?.data?.agentId || response?.agentId,
       agentSessionId: response?.data?.agentSessionId || response?.agentSessionId,
       teamId: response?.teamId ?? response?.data?.teamId ?? undefined,
       siteId: response?.data?.siteId || response?.siteId,
       orgId: response?.data?.orgId || response?.orgId,
       eventType: response?.type,
       trackingId: response?.data?.trackingId,
       notifTrackingId: response?.trackingId,
     };

     return fields;
   }

+  /**
+   * Extracts common tracking fields from a failed AQM response.
+   * @param failureResponse - The failure response object
+   * @returns An object containing extracted tracking fields
+   */
   public static getCommonTrackingFieldForAQMResponseFailed(
     failureResponse: Failure
   ): Record<string, any> {
     // This method is used to extract common tracking fields from the AQM response failure
     // and return them as an object. The fields are extracted from the response
     // object and its data property.
     const fields = {
       agentId: failureResponse?.data?.agentId,
       trackingId: failureResponse?.trackingId,
       notifTrackingId: failureResponse?.trackingId,
       orgId: failureResponse?.orgId,
       failureType: failureResponse?.type,
       failureReason: failureResponse?.data?.reason,
       reasonCode: failureResponse?.data?.reasonCode,
     };

     return fields;
   }
```

---

`288-288`: **Consider typing the response parameters more specifically.**

The `getCommonTrackingFieldForAQMResponse` method uses `any` for the response parameter, while `getCommonTrackingFieldForAQMResponseFailed` uses a specific `Failure` type. Consider creating a specific type for the success response as well.

```typescript
// Add this interface near the top of the file
interface AQMResponse {
  data?: {
    agentId?: string;
    agentSessionId?: string;
    teamId?: string;
    siteId?: string;
    orgId?: string;
    trackingId?: string;
    reason?: string;
    reasonCode?: string;
  };
  agentId?: string;
  agentSessionId?: string;
  teamId?: string;
  siteId?: string;
  orgId?: string;
  type?: string;
  trackingId?: string;
}

// Then update the method signature
public static getCommonTrackingFieldForAQMResponse(response: AQMResponse): Record<string, any> {
  // Method implementation remains the same
}
```


Also applies to: 306-306

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between ea36722d31bed91a4799cb51be9ad27e56753409 and b63d9834d02b25d05e0392cce24aedfa6ad3ca9b.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `packages/@webex/plugin-cc/src/metrics/MetricsManager.ts` (7 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (2)</summary>

* GitHub Check: Build Packages
* GitHub Check: AWS Amplify Console Web Preview

</details>

<details>
<summary>🔇 Additional comments (8)</summary><blockquote>

<details>
<summary>packages/@webex/plugin-cc/src/metrics/MetricsManager.ts (8)</summary>

`12-13`: **LGTM: Good addition of constants for consistency.**

The addition of imports and the constant `PRODUCT_NAME_UPPER` provides a consistent way to reference the product name in uppercase format throughout the code.



Also applies to: 27-27

---

`30-30`: **Improved data structure for tracking multiple event keys.**

The updated `runningEvents` structure now supports multiple keys for a single event, allowing for more flexible metric tracking scenarios.

---

`91-91`: **Good standardization of event names and metadata.**

The addition of product name prefix to event names ensures consistency across the metrics system. The metadata addition to business events provides valuable context for analytics.



Also applies to: 107-111

---

`117-132`: **Enhanced duration tracking with support for multiple keys.**

The `addDurationIfTimed` method has been refactored to support the new data structure that tracks multiple keys per event. This implementation correctly deletes all keys for an operation when the duration is calculated.

---

`138-161`: **Improved payload cleanup for better data quality.**

The `preparePayload` method now performs more comprehensive checks to filter out undefined, null, empty strings, arrays, and empty objects before sending metrics. This helps maintain clean, meaningful data in the metrics system.

---

`246-259`: **Enhanced timeEvent with support for multiple keys and error handling.**

The `timeEvent` method now accepts either a string or an array of strings as keys, which aligns with the PR objective of enhancing metrics tracking capabilities. The error logging for empty keys is a good defensive programming practice.

---

`288-304`: **Good utility method for extracting common tracking fields.**

The `getCommonTrackingFieldForAQMResponse` method extracts common tracking fields from AQM responses with fallbacks for different property paths. This helps standardize metrics data collection.

Based on the past comment from @bhabalan, these utility functions help extract common tracking fields for success cases from AQM based requests.

---

`306-323`: **Good utility method for failure tracking fields.**

The `getCommonTrackingFieldForAQMResponseFailed` method extracts relevant tracking fields from failure responses, which is essential for error analysis and debugging.

Based on the past comment from @bhabalan, these utility functions help extract common tracking fields for failure cases from AQM based requests.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T13:23:37Z

Review comments:
diff_hunk: @@ -73,6 +73,7 @@ export type MetricEventVerb =
   | 'list'
   | 'load'
   | 'login'
+  | 'relogin'
path: packages/@webex/internal-plugin-metrics/src/metrics.types.ts
user: login: bhabalan
body: Not needed anymore. Will remove
created_at: 2025-04-03T03:52:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -33,7 +35,7 @@ export default class MetricsManager {
 
   // eslint-disable-next-line no-use-before-define
   private static instance: MetricsManager;
-  private metricsDisabled = false;
+  private metricsDisabled = true; // TODO: SPARK-637285
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: mkesavan13
body: So, merging this PR won't send the metrics is it?
created_at: 2025-04-03T14:58:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,4 +275,46 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
+    const fields = {
+      agentId: response?.data?.agentId || EMPTY_STRING,
+      teamId: response?.data?.teamId || EMPTY_STRING, // TODO: handle multiple teams
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: mkesavan13
body: Is there a Jira to address this? If today there are multiple teams, would this break somehow?
created_at: 2025-04-03T15:00:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,12 +1,16 @@
-type Enum<T extends Record<string, unknown>> = T[keyof T];
-
 export const METRIC_EVENT_NAMES = {
-  STATION_LOGIN: 'Station Login',
-  CALL_COMPLETED: 'Call Completed',
-  TASK_END: 'Task End',
-  CALL_CONSULT_ACTIVATE: 'Call Consult Activate',
-  CALL_TRANSFER_ACTIVATE: 'Call Transfer Activated',
-  CALL_ANSWERED: 'Call Answered',
+  STATION_LOGIN_SUCCESS: 'Station Login Success',
path: packages/@webex/plugin-cc/src/metrics/constants.ts
user: login: mkesavan13
body: These are the names that finally land in the metrics dashboards is it? If so, would it be more efficient to not have spaces?

Considering we would share filter based URLs/permalinks around?
created_at: 2025-04-03T15:13:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -114,10 +115,31 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
    */
   public async register(): Promise<Profile> {
     try {
+      this.metricsManager.timeEvent([
path: packages/@webex/plugin-cc/src/cc.ts
user: login: mkesavan13
body: Should we at least have all the UTs in these APIs to check if timeEvent is being called? Just to ensure we do metrics on all the APIs?
created_at: 2025-04-03T15:17:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -33,7 +35,7 @@ export default class MetricsManager {
 
   // eslint-disable-next-line no-use-before-define
   private static instance: MetricsManager;
-  private metricsDisabled = false;
+  private metricsDisabled = true; // TODO: SPARK-637285
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: bhabalan
body: It won't. This is to avoid send metrics during dev/testing
created_at: 2025-04-04T05:13:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,12 +1,16 @@
-type Enum<T extends Record<string, unknown>> = T[keyof T];
-
 export const METRIC_EVENT_NAMES = {
-  STATION_LOGIN: 'Station Login',
-  CALL_COMPLETED: 'Call Completed',
-  TASK_END: 'Task End',
-  CALL_CONSULT_ACTIVATE: 'Call Consult Activate',
-  CALL_TRANSFER_ACTIVATE: 'Call Transfer Activated',
-  CALL_ANSWERED: 'Call Answered',
+  STATION_LOGIN_SUCCESS: 'Station Login Success',
path: packages/@webex/plugin-cc/src/metrics/constants.ts
user: login: bhabalan
body: These are not the final names.
For behavioral, The names would come from getEventTaxonomy() in packages/@webex/plugin-cc/src/metrics/behavioral-events.ts
For business and operational, these would be uppercased and snakecased
created_at: 2025-04-04T05:15:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -73,6 +73,7 @@ export type MetricEventVerb =
   | 'list'
   | 'load'
   | 'login'
+  | 'relogin'
path: packages/@webex/internal-plugin-metrics/src/metrics.types.ts
user: login: sreenara
body: Did we add this or was it already there? The agent desktop also uses this plugin.
created_at: 2025-04-04T10:36:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,4 +275,46 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: sreenara
body: Can you add more description to the PR about what these new functions are doing?
created_at: 2025-04-04T13:23:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -103,33 +105,29 @@ export default class MetricsManager {
         this.webex.internal.newMetrics.submitBusinessEvent({
           name: event.name,
           payload: event.payload,
+          metadata: {
+            appType: PRODUCT_NAME,
+          },
         });
       });
     }
   }
 
-  private addDurationIfTimed(name: string, options?: EventPayload): EventPayload {
+  private addDurationIfTimed(eventName: string, options?: EventPayload): EventPayload {
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: sreenara
body: Why did we change this function?
created_at: 2025-04-04T13:25:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -103,33 +105,29 @@ export default class MetricsManager {
         this.webex.internal.newMetrics.submitBusinessEvent({
           name: event.name,
           payload: event.payload,
+          metadata: {
+            appType: PRODUCT_NAME,
+          },
         });
       });
     }
   }
 
-  private addDurationIfTimed(name: string, options?: EventPayload): EventPayload {
+  private addDurationIfTimed(eventName: string, options?: EventPayload): EventPayload {
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: bhabalan
body: Earlier we had one name for each event type.
Eg: Station Login, So we were sending just one event name for timing. 
Now we have two names per event. i.e: Station Login Success, Station Login Failed
So needed to add ability to support this as the events were being used as keys
created_at: 2025-04-04T16:19:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,4 +275,46 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: bhabalan
body: I'll add more comments to these functions.
Basically these are just utility functions to get common tracking fields for success and failure cases for AQM based requests
created_at: 2025-04-04T16:20:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,4 +275,46 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
+    const fields = {
+      agentId: response?.data?.agentId || EMPTY_STRING,
+      teamId: response?.data?.teamId || EMPTY_STRING, // TODO: handle multiple teams
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: bhabalan
body: Addressed this in this PR itself
created_at: 2025-04-07T11:15:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,4 +275,58 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
+    let teamId;
+    if (response?.data?.teamId) {
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: sreenara
body: I'm not seeing any UT that covers all the conditions here. Could you please point me to that?
created_at: 2025-04-09T04:04:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,4 +275,58 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
+    let teamId;
+    if (response?.data?.teamId) {
+      teamId = response.data.teamId;
+    } else if (response?.teamId) {
+      teamId = response.teamId;
+    } else if (Array.isArray(response?.data?.teams) && response.data.teams.length > 0) {
+      teamId = response.data.teams[0].id;
+    } else if (Array.isArray(response?.teams) && response.teams.length > 0) {
+      teamId = response.teams[0].id;
+    }
+
+    const fields = {
+      agentId: response?.data?.agentId || response?.agentId,
+      agentSessionId: response?.data?.agentSessionId || response?.agentSessionId,
+      teamId, // multiple teams is returned only when doing websocket register
+      siteId: response?.data?.siteId || response?.siteId,
+      orgId: response?.data?.orgId || response?.orgId,
+      eventType: response?.type,
+      trackingId: response?.data?.trackingId,
+      notifTrackingId: response?.trackingId,
+    };
+
+    Object.keys(fields).forEach((key) => {
+      if (typeof fields[key] === 'undefined' || fields[key] === null) {
+        delete fields[key];
+      }
+    });
+
+    return fields;
+  }
+
+  public static getCommonTrackingFieldForAQMResponseFailed(
+    failureResponse: Failure
+  ): Record<string, any> {
+    const fields = {
+      agentId: failureResponse?.data?.agentId,
+      trackingId: failureResponse?.trackingId,
+      notifTrackingId: failureResponse?.trackingId,
+      orgId: failureResponse?.orgId,
+      failureType: failureResponse?.type,
+      failureReason: failureResponse?.data?.reason,
+      reasonCode: failureResponse?.data?.reasonCode,
+    };
+
+    Object.keys(fields).forEach((key) => {
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: sreenara
body: Can you point me to the UT that tests these conditions?
created_at: 2025-04-09T04:04:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,4 +275,58 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
+    let teamId;
+    if (response?.data?.teamId) {
+      teamId = response.data.teamId;
+    } else if (response?.teamId) {
+      teamId = response.teamId;
+    } else if (Array.isArray(response?.data?.teams) && response.data.teams.length > 0) {
+      teamId = response.data.teams[0].id;
+    } else if (Array.isArray(response?.teams) && response.teams.length > 0) {
+      teamId = response.teams[0].id;
+    }
+
+    const fields = {
+      agentId: response?.data?.agentId || response?.agentId,
+      agentSessionId: response?.data?.agentSessionId || response?.agentSessionId,
+      teamId, // multiple teams is returned only when doing websocket register
+      siteId: response?.data?.siteId || response?.siteId,
+      orgId: response?.data?.orgId || response?.orgId,
+      eventType: response?.type,
+      trackingId: response?.data?.trackingId,
+      notifTrackingId: response?.trackingId,
+    };
+
+    Object.keys(fields).forEach((key) => {
+      if (typeof fields[key] === 'undefined' || fields[key] === null) {
+        delete fields[key];
+      }
+    });
+
+    return fields;
+  }
+
+  public static getCommonTrackingFieldForAQMResponseFailed(
+    failureResponse: Failure
+  ): Record<string, any> {
+    const fields = {
+      agentId: failureResponse?.data?.agentId,
+      trackingId: failureResponse?.trackingId,
+      notifTrackingId: failureResponse?.trackingId,
+      orgId: failureResponse?.orgId,
+      failureType: failureResponse?.type,
+      failureReason: failureResponse?.data?.reason,
+      reasonCode: failureResponse?.data?.reasonCode,
+    };
+
+    Object.keys(fields).forEach((key) => {
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: bhabalan
body: I've added the tests for these. I have also moved the removal of emptyish fields to the preparePayload method.
created_at: 2025-04-09T11:15:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,4 +275,58 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
+    let teamId;
+    if (response?.data?.teamId) {
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: bhabalan
body: Added now
created_at: 2025-04-09T11:15:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -114,10 +115,31 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
    */
   public async register(): Promise<Profile> {
     try {
+      this.metricsManager.timeEvent([
path: packages/@webex/plugin-cc/src/cc.ts
user: login: bhabalan
body: Added UTs
created_at: 2025-04-09T11:38:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,12 +1,16 @@
-type Enum<T extends Record<string, unknown>> = T[keyof T];
-
 export const METRIC_EVENT_NAMES = {
-  STATION_LOGIN: 'Station Login',
-  CALL_COMPLETED: 'Call Completed',
-  TASK_END: 'Task End',
-  CALL_CONSULT_ACTIVATE: 'Call Consult Activate',
-  CALL_TRANSFER_ACTIVATE: 'Call Transfer Activated',
-  CALL_ANSWERED: 'Call Answered',
+  STATION_LOGIN_SUCCESS: 'Station Login Success',
path: packages/@webex/plugin-cc/src/metrics/constants.ts
user: login: mkesavan13
body: In that case why do we have these space separated values? What's the purpose there?
created_at: 2025-04-09T11:49:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,4 +284,52 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
+    // This method is used to extract common tracking fields from the AQM response
+    // and return them as an object. The fields are extracted from the response
+    // object and its data property.
+    let teamId: string | undefined;
+    if (response?.data?.teamId) {
+      teamId = response.data.teamId;
+    } else if (response?.teamId) {
+      teamId = response.teamId;
+    } else if (Array.isArray(response?.data?.teams) && response.data.teams.length > 0) {
+      teamId = response.data.teams[0].id;
+    } else if (Array.isArray(response?.teams) && response.teams.length > 0) {
+      teamId = response.teams[0].id;
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: mkesavan13
body: On what grounds are we choosing zero-th team always? What if the agent chose to login with teams[1] or teams[2] or any random team[n]?
created_at: 2025-04-09T11:52:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,4 +284,52 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
+    // This method is used to extract common tracking fields from the AQM response
+    // and return them as an object. The fields are extracted from the response
+    // object and its data property.
+    let teamId: string | undefined;
+    if (response?.data?.teamId) {
+      teamId = response.data.teamId;
+    } else if (response?.teamId) {
+      teamId = response.teamId;
+    } else if (Array.isArray(response?.data?.teams) && response.data.teams.length > 0) {
+      teamId = response.data.teams[0].id;
+    } else if (Array.isArray(response?.teams) && response.teams.length > 0) {
+      teamId = response.teams[0].id;
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: bhabalan
body: The metrics-A api accepts only boolean, string or number. Cannot send array. The other option is to concat the teamIds
created_at: 2025-04-09T11:55:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,12 +1,16 @@
-type Enum<T extends Record<string, unknown>> = T[keyof T];
-
 export const METRIC_EVENT_NAMES = {
-  STATION_LOGIN: 'Station Login',
-  CALL_COMPLETED: 'Call Completed',
-  TASK_END: 'Task End',
-  CALL_CONSULT_ACTIVATE: 'Call Consult Activate',
-  CALL_TRANSFER_ACTIVATE: 'Call Transfer Activated',
-  CALL_ANSWERED: 'Call Answered',
+  STATION_LOGIN_SUCCESS: 'Station Login Success',
path: packages/@webex/plugin-cc/src/metrics/constants.ts
user: login: bhabalan
body: These are Enum values. Just for the sake of readability
created_at: 2025-04-09T11:55:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -73,6 +73,7 @@ export type MetricEventVerb =
   | 'list'
   | 'load'
   | 'login'
+  | 'relogin'
path: packages/@webex/internal-plugin-metrics/src/metrics.types.ts
user: login: bhabalan
body: We added it. Now removed
created_at: 2025-04-09T11:56:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,4 +284,52 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
+    // This method is used to extract common tracking fields from the AQM response
+    // and return them as an object. The fields are extracted from the response
+    // object and its data property.
+    let teamId: string | undefined;
+    if (response?.data?.teamId) {
+      teamId = response.data.teamId;
+    } else if (response?.teamId) {
+      teamId = response.teamId;
+    } else if (Array.isArray(response?.data?.teams) && response.data.teams.length > 0) {
+      teamId = response.data.teams[0].id;
+    } else if (Array.isArray(response?.teams) && response.teams.length > 0) {
+      teamId = response.teams[0].id;
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: bhabalan
body: Also, I have seen the teams as an array only while we do websocket register. After that we get only one teamId instead of an array after StationLogin. The if..else.. ladder takes care of this. This picking of zero'th element is just a failsafe
created_at: 2025-04-09T11:57:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,4 +284,52 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
+    // This method is used to extract common tracking fields from the AQM response
+    // and return them as an object. The fields are extracted from the response
+    // object and its data property.
+    let teamId: string | undefined;
+    if (response?.data?.teamId) {
+      teamId = response.data.teamId;
+    } else if (response?.teamId) {
+      teamId = response.teamId;
+    } else if (Array.isArray(response?.data?.teams) && response.data.teams.length > 0) {
+      teamId = response.data.teams[0].id;
+    } else if (Array.isArray(response?.teams) && response.teams.length > 0) {
+      teamId = response.teams[0].id;
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: mkesavan13
body: So, while doing station login, the user would have chosen a team before logging in right? Shouldn't we be using that teamId here?
created_at: 2025-04-09T11:58:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,12 +1,16 @@
-type Enum<T extends Record<string, unknown>> = T[keyof T];
-
 export const METRIC_EVENT_NAMES = {
-  STATION_LOGIN: 'Station Login',
-  CALL_COMPLETED: 'Call Completed',
-  TASK_END: 'Task End',
-  CALL_CONSULT_ACTIVATE: 'Call Consult Activate',
-  CALL_TRANSFER_ACTIVATE: 'Call Transfer Activated',
-  CALL_ANSWERED: 'Call Answered',
+  STATION_LOGIN_SUCCESS: 'Station Login Success',
path: packages/@webex/plugin-cc/src/metrics/constants.ts
user: login: mkesavan13
body: In code, we just use these underscore separated variable names and in metrics dashboards, the space separated values won't show up. So, where are they being read is what am curious about?
created_at: 2025-04-09T12:01:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,4 +284,52 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
+    // This method is used to extract common tracking fields from the AQM response
+    // and return them as an object. The fields are extracted from the response
+    // object and its data property.
+    let teamId: string | undefined;
+    if (response?.data?.teamId) {
+      teamId = response.data.teamId;
+    } else if (response?.teamId) {
+      teamId = response.teamId;
+    } else if (Array.isArray(response?.data?.teams) && response.data.teams.length > 0) {
+      teamId = response.data.teams[0].id;
+    } else if (Array.isArray(response?.teams) && response.teams.length > 0) {
+      teamId = response.teams[0].id;
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: bhabalan
body: Handled higher up in the if..else.. ladder
created_at: 2025-04-09T12:03:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,12 +1,16 @@
-type Enum<T extends Record<string, unknown>> = T[keyof T];
-
 export const METRIC_EVENT_NAMES = {
-  STATION_LOGIN: 'Station Login',
-  CALL_COMPLETED: 'Call Completed',
-  TASK_END: 'Task End',
-  CALL_CONSULT_ACTIVATE: 'Call Consult Activate',
-  CALL_TRANSFER_ACTIVATE: 'Call Transfer Activated',
-  CALL_ANSWERED: 'Call Answered',
+  STATION_LOGIN_SUCCESS: 'Station Login Success',
path: packages/@webex/plugin-cc/src/metrics/constants.ts
user: login: bhabalan
body: Readable by humans. Eventually they will be snake cased on the dashboard.
created_at: 2025-04-09T12:04:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,4 +284,52 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
+    // This method is used to extract common tracking fields from the AQM response
+    // and return them as an object. The fields are extracted from the response
+    // object and its data property.
+    let teamId: string | undefined;
+    if (response?.data?.teamId) {
+      teamId = response.data.teamId;
+    } else if (response?.teamId) {
+      teamId = response.teamId;
+    } else if (Array.isArray(response?.data?.teams) && response.data.teams.length > 0) {
+      teamId = response.data.teams[0].id;
+    } else if (Array.isArray(response?.teams) && response.teams.length > 0) {
+      teamId = response.teams[0].id;
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: mkesavan13
body: > Also, I have seen the teams as an array only while we do websocket register. After that we get only one teamId instead of an array after StationLogin. The if..else.. ladder takes care of this. This picking of zero'th element is just a failsafe

If that is the case, am curious if those metrics would somehow mislead us. The metrics we record until station login could possibly have a teamId that is not chosen by the agent after station login. Is my interpretation correct here?
created_at: 2025-04-09T12:04:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,4 +284,52 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
+    // This method is used to extract common tracking fields from the AQM response
+    // and return them as an object. The fields are extracted from the response
+    // object and its data property.
+    let teamId: string | undefined;
+    if (response?.data?.teamId) {
+      teamId = response.data.teamId;
+    } else if (response?.teamId) {
+      teamId = response.teamId;
+    } else if (Array.isArray(response?.data?.teams) && response.data.teams.length > 0) {
+      teamId = response.data.teams[0].id;
+    } else if (Array.isArray(response?.teams) && response.teams.length > 0) {
+      teamId = response.teams[0].id;
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: bhabalan
body: Only one event before station login -> Websocket register. This returns an array of teams.
After StationLogin -> We get direct teamId. No array of teams
created_at: 2025-04-09T12:11:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,4 +284,52 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
+    // This method is used to extract common tracking fields from the AQM response
+    // and return them as an object. The fields are extracted from the response
+    // object and its data property.
+    let teamId: string | undefined;
+    if (response?.data?.teamId) {
+      teamId = response.data.teamId;
+    } else if (response?.teamId) {
+      teamId = response.teamId;
+    } else if (Array.isArray(response?.data?.teams) && response.data.teams.length > 0) {
+      teamId = response.data.teams[0].id;
+    } else if (Array.isArray(response?.teams) && response.teams.length > 0) {
+      teamId = response.teams[0].id;
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: mkesavan13
body: As discussed on Webex, we need to take a call how are we going to do this. Please have a discussion with @sreenara before merging the PR
created_at: 2025-04-09T12:52:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,4 +284,52 @@ export default class MetricsManager {
   public static resetInstance() {
     MetricsManager.instance = undefined;
   }
+
+  public static getCommonTrackingFieldForAQMResponse(response: any): Record<string, any> {
+    // This method is used to extract common tracking fields from the AQM response
+    // and return them as an object. The fields are extracted from the response
+    // object and its data property.
+    let teamId: string | undefined;
+    if (response?.data?.teamId) {
+      teamId = response.data.teamId;
+    } else if (response?.teamId) {
+      teamId = response.teamId;
+    } else if (Array.isArray(response?.data?.teams) && response.data.teams.length > 0) {
+      teamId = response.data.teams[0].id;
+    } else if (Array.isArray(response?.teams) && response.teams.length > 0) {
+      teamId = response.teams[0].id;
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: sreenara
body: Let's not track the teams in this event. We will have the required information during station login anyway.
created_at: 2025-04-09T13:15:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -33,7 +36,7 @@ export default class MetricsManager {
 
   // eslint-disable-next-line no-use-before-define
   private static instance: MetricsManager;
-  private metricsDisabled = false;
+  private metricsDisabled = false; // TODO: SPARK-637285
path: packages/@webex/plugin-cc/src/metrics/MetricsManager.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**TODO comment needs to be addressed.**

There's a TODO comment with a Jira ticket reference (SPARK-637285) for the `metricsDisabled` flag. Make sure this is addressed in a future PR if not addressed in this one.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if the SPARK-637285 ticket is mentioned elsewhere in the codebase
rg "SPARK-637285" --type ts
```

Length of output: 304

---



</details>

**Action Required: Address the SPARK-637285 TODO Comments**  
In `packages/@webex/plugin-cc/src/metrics/MetricsManager.ts`, there are two instances referencing SPARK-637285:  
- The initialization of `metricsDisabled` with a comment (`private metricsDisabled = false; // TODO: SPARK-637285`).  
- An inline comment further down noting that, in development mode, metrics should be disabled (i.e., “Need to return true if in development mode to avoid sending metrics to the server”).  

Please confirm whether you plan to implement the intended behavior in this PR or track it with the Jira ticket for a follow-up PR. Ensure that the ticket details are current and that the code reflects the desired behavior for development mode to avoid unintended metric reporting.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-09T13:23:36Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-28T04:33:13Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Walkthrough
The changes update the metrics tracking system across several components. In the metrics types file, two optional properties (`metadata` and `table`) are added to the `SubmitBusinessEvent` type. In the Contact Center module, multiple methods now record detailed metrics events for both success and failure scenarios, enhancing error handling. The `MetricsManager` class has been refactored to support tracking multiple keys with a revised `runningEvents` structure and updated method signatures, along with two new static methods for extracting common tracking fields from responses. The event taxonomy in behavioral events has been revised, introducing new success and failure event cases, while the constants have been updated to reflect these new event names. Additionally, the test files have been updated to validate these structural and naming changes.

## Possibly related PRs
- **webex/webex-js-sdk#4000:** The changes in the main PR, which add optional properties `metadata` and `table` to the `SubmitBusinessEvent` type, are directly related to the modifications in the retrieved PR that also introduce a `metadata` parameter to the `submitBusinessEvent` method, indicating a shared focus on enhancing the event submission structure.
- **webex/webex-js-sdk#4127:** The changes in the main PR, which introduce optional properties to the `SubmitBusinessEvent` type, are related to the modifications in the retrieved PR that involve the addition of the `SubmitBusinessEvent` type and its integration into the metrics handling, as both directly involve the same type definition.

</details>

<!-- walkthrough_end -->
<!-- This is an auto-generated comment: all tool run failures by coderabbit.ai -->

> [!WARNING]
> There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.
> 
> <details>
> <summary>🔧 ESLint</summary>
> 
> > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.
> 
> yarn install v1.22.22
> [1/4] Resolving packages...
> warning eslint@8.57.1: This version is no longer supported. Please see https://eslint.org/version-support for other options.
> warning eslint > @humanwhocodes/config-array@0.13.0: Use @eslint/config-array instead
> warning eslint > @humanwhocodes/config-array > @humanwhocodes/object-schema@2.0.3: Use @eslint/object-schema instead
> warning eslint > file-entry-cache > flat-cache > rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported
> warning eslint > file-entry-cache > flat-cache > rimraf > glob@7.2.3: Glob versions prior to v9 are no longer supported
> warning eslint > file-entry-cache > flat-cache > rimraf > glob > inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
> warning eslint-import-resolver-typescript > glob@7.2.3: Glob versions prior to v9 are no longer supported
> warning glob@7.2.3: Glob versions prior to v9 are no longer supported
> warning intern > glob@7.1.7: Glob versions prior to v9 are no longer supported
> warning intern > glob > inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
> warning jasmine > glob@7.2.3: Glob versions prior to v9 are no longer supported
> warning jest > @jest/core > jest-config > glob@7.2.3: Glob versions prior to v9 are no longer supported
> warning jest > @jest/core > jest-runtime > glob@7.2.3: Glob versions prior to v9 are no longer supported
> warning jest > @jest/core > @jest/reporters > glob@7.2.3: Glob versions prior to v9 are no longer supported
> warning jest > @jest/core > @jest/transform > babel-plugin-istanbul > test-exclude > glob@7.2.3: Glob versions prior to v9 are no longer supported
> warning mocha > glob@7.2.0: Glob versions prior to v9 are no longer supported
> warning mocha > glob > inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
> warning sinon@9.2.4: 16.1.1
> warning sinon > @sinonjs/samsam > lodash.get@4.4.2: This package is deprecated. Use the optional chaining (?.) operator instead.
> warning wd > archiver > glob@7.2.3: Glob versions prior to v9 are no longer supported
> warning wd > request@2.88.0: request has been deprecated, see https://github.com/request/request/issues/3142
> warning wd > q@1.5.1: You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.
> 
> (For a CapTP with native promises, see @endo/eventual-send and @endo/captp)
> warning wd > request > uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
> warning wd > archiver > archiver-utils > glob@7.2.3: Glob versions prior to v9 are no longer supported
> warning wd > request > har-validator@5.1.5: this library is no longer supported
> warning @babel/cli > glob@7.2.3: Glob versions prior to v9 are no longer supported
> warning @babel/plugin-proposal-async-generator-functions@7.20.7: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.
> warning @babel/plugin-proposal-class-properties@7.18.6: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.
> warning @babel/plugin-proposal-export-namespace-from@7.18.9: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.
> warning @babel/plugin-proposal-nullish-coalescing-operator@7.18.6: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.
> warning @babel/plugin-proposal-object-rest-spread@7.20.7: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.
> warning @babel/plugin-proposal-optional-chaining@7.21.0: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.
> warning @babel/polyfill@7.12.1: 🚨 This package has been deprecated in favor of separate inclusion of a polyfill and regenerator-runtime (when needed). See the @babel/polyfill docs (https://babeljs.io/docs/en/babel-polyfill) for more information.
> warning @babel/polyfill > core-js@2.6.12: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
> warning @babel/runtime-corejs2 > core-js@2.6.12: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
> warning babel-plugin-lodash > glob@7.2.3: Glob versions prior to v9 are no longer supported
> warning workspace-aggregator-412e4e95-8773-4b18-8335-6d5493559d6a > eslint@8.57.1: This version is no longer supported. Please see https://eslint.org/version-support for other options.
> warning workspace-aggregator-412e4e95-8773-4b18-8335-6d5493559d6a > glob@7.2.3: Glob versions prior to v9 are no longer supported
> warning workspace-aggregator-412e4e95-8773-4b18-8335-6d5493559d6a > sinon@9.2.4: 16.1.1
> warning workspace-aggregator-412e4e95-8773-4b18-8335-6d5493559d6a > @babel/plugin-proposal-async-generator-functions@7.20.7: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.
> warning workspace-aggregator-412e4e95-8773-4b18-8335-6d5493559d6a > @babel/plugin-proposal-class-properties@7.18.6: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.
> warning workspace-aggregator-412e4e95-8773-4b18-8335-6d5493559d6a > @babel/plugin-proposal-export-namespace-from@7.18.9: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.
> warning workspace-aggregator-412e4e95-8773-4b18-8335-6d5493559d6a > @babel/plugin-proposal-nullish-coalescing-operator@7.18.6: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.
> warning workspace-aggregator-412e4e95-8773-4b18-8335-6d5493559d6a > @babel/plugin-proposal-object-rest-spread@7.20.7: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.
> warning workspace-aggregator-412e4e95-8773-4b18-8335-6d5493559d6a > @babel/plugin-proposal-optional-chaining@7.21.0: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.
> warning workspace-aggregator-412e4e95-8773-4b18-8335-6d5493559d6a > @babel/polyfill@7.12.1: 🚨 This package has been deprecated in favor of separate inclusion of a polyfill and regenerator-runtime (when needed). See the @babel/polyfill docs (https://babeljs.io/docs/en/babel-polyfill) for more information.
> [2/4] Fetching packages...
> error eslint-plugin-jsdoc@40.3.0: The engine "node" is incompatible with this module. Expected version "^14 || ^16 || ^17 || ^18 || ^19". Got "22.9.0"
> error Found incompatible module.
> info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.
> 
> 
> 
> </details>

<!-- end of auto-generated comment: all tool run failures by coderabbit.ai -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=webex/webex-js-sdk&utm_content=4173):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: bhabalan
created_at: 2025-04-03T03:40:19Z
body: @coderabbitai review
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-04-03T03:40:26Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>:white_check_mark: Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: CONTRIBUTOR
user: login: bhabalan
created_at: 2025-04-03T03:45:56Z
body: [all cc.ts metrics.har.zip](https://github.com/user-attachments/files/19579394/all.cc.ts.metrics.har.zip)
<img width="1312" alt="image" src="https://github.com/user-attachments/assets/1585f3bd-ffae-4939-ab9c-7fbfda546809" />

author_association: CONTRIBUTOR
user: login: aws-amplify-us-east-2[bot]
created_at: 2025-04-07T14:06:47Z
body: This pull request is automatically being deployed by Amplify Hosting ([learn more](https://aws.amazon.com/amplify/hosting/)).<br/><br/>Access this pull request here: https://pr-4173.d3m3l2kee0btzx.amplifyapp.com
author_association: NONE