oWaiting metric not showing properly when querying

Hello,

I was hoping someone can help. I'm working on a project where we use Angular app to monitor queues and if one "breaks" we alert the team so we can fix it. I've modelled our app using the sample app here: https://developer.genesys.cloud/blueprints/angular-app-with-genesys-cloud-sdk/, but I encountered some metrics not working.

This is what it looks like when I have oWaiting metric enabled (seems like if number in oWaiting is greater than zero, it breaks):

And this is what it looks like when oWaiting is disabled and other metrics are used:

This is what I have for genesys-cloud.service.ts:

 getQueueObservations(queueId: string): Observable<platformClient.Models.QueueObservationDataContainer>{
    return from(this.analyticsApi.postAnalyticsQueuesObservationsQuery({
      filter: {
        type: 'or',
        predicates: [
         {
          type: 'dimension',
          dimension: 'queueId',
          operator: 'matches',
          value: queueId
         }
        ]
       },
       metrics: [ 'oOnQueueUsers', 'oActiveUsers', 'oOffQueueUsers', 'oWaiting' ]
    }))
    .pipe(
      map(data => {
        const result = data.results?.find(r => r.group?.queueId === queueId); 
        if(!result) throw new Error(`No results queried for ${queueId}`);
        console.log(result)

        return result;
      })
    );
  }

This is what I have for queue-details.component.ts:

 getQueueObservations(){
    if(!this.queue) throw Error('Invalid queue.');
    this.fetching = true;

    this.genesysCloudService.getQueueObservations(this.queue.id!)
      .subscribe(result => {
        if(!result.data) throw new Error('Error in getting observations.')

        this.onQueueAgents = result.data
                .filter(d => d.metric === 'oOnQueueUsers')
                .reduce((acc, d) => acc + d.stats!.count!, 0)
        this.totalAgents = result.data
                .find(d => d.metric === 'oActiveUsers')!.stats!.count || 0;
        this.offQueueAgents = result.data
                .filter(d => d.qualifier === 'ccf3c10a-aa2c-4845-8e8d-f59fa48c58e5')
                .reduce((acc, d) => acc + d.stats!.count!, 0)
        this.waiting = result.data
                .filter(d => d.metric === 'oWaiting')
                .reduce((acc, d) => acc + d.stats!.count!, 0)

        this.fetching = false;
      });
  }

Is it because oWaiting is in a different group than the rest of the metrics I'm using? How can I fix this?

Thank you!

The error Cannot read properties of undefined (reading 'stats') is a JavaScript error indicating that you need to add some additional value checking to your code. I'd guess it's coming from one of the lines in you're code where you're accessing .stats without first checking to see if .filter returned anything.

Hello Tim,

Thank you for your reply! I tried console logging the oWaiting metric and seems like it shows up as undefined when count: 0. If its greater than 0, it logs the object. Not entirely too sure what's causing this and how to go about it. Any advice would be great.

Thank you! Have a great day.
Dianne

This sounds like it's working as expected. Statistics are only returned if they have a value.

Consider the server-side logic like this: For cases where no conversations meet the criteria for the query, it had zero conversations to use in a calculation, so there wasn't any data from which to calculate a statistic. You get nothing back at all because metrics don't have default values. That's a very pedantic point to consider, but is the reason for the behavior you're seeing.

Hello @dgabriel

Your problem is not coming from the stats count equal to 0 or greater than 0.

It is because the original sample is built to request 'oOnQueueUsers' and 'oActiveUsers' metrics, and its processing in getQueueObservations (genesys-cloud.service.ts and queue-details.component.ts) is coded accordingly.
'oOnQueueUsers' and 'oActiveUsers' are queue level metrics.

So when requesting a queue observation, you will get a single entity with "group": { "queueId": "xxxxx" }.
getQueueObservations in genesys-cloud.service.ts is extracting that entity using the data.results?.find(r => r.group?.queueId === queueId);
The javascript find method (for arrays) returns the value of the first element found, matching this condition.

The modification you have made is to also request the 'oWaiting' metric in the Queue Observation request.
'oWaiting' is a (queue, mediaType) level metric.
It means you will get multiple entities with "group": { "queueId": "xxxxx", "mediaType": "yyyy" } (one per media type: chat, voice, callback, message, email).

Combining 'oOnQueueUsers', 'oActiveUsers', 'oOffQueueUsers' and 'oWaiting' means, you will get one entity with "group": { "queueId": "xxxxx" } (for the 'oOnQueueUsers', 'oActiveUsers', 'oOffQueueUsers' metrics) and multiple entities with "group": { "queueId": "xxxxx", "mediaType": "yyyy" } (for the 'oWaiting' metric - one per media type: chat, voice, callback, message, email).
The order of the these entities in the Query Observation response is not fixed (I mean they can be in any possible order in the results array attribute).

As you have not modified the code of the getQueueObservations in genesys-cloud.service.ts, it is only extracting the first element/entity in the results array which matches the condition (i.e. because of the way javascript find works).

You will need to modify this to make sure you are sending all entities in the result. And in the getQueueObservations in queue-details.component.ts, process/parse that new result accordingly.

Regards,

1 Like

Hello Jerome,

Thank you for your response. I had an idea from what you said so I modified the filler to this:

     "filter": {
        "type": "and",
        "clauses": [
           {
              "type": "or",
              "predicates": [
                 {
                    "type": "dimension",
                    "dimension": "queueId",
                    "value": queueId
                 }
              ]
           },
           { 
             "type": "or",
             "predicates": [
                {
                  "type": "dimension",
                  "dimension": "mediaType",
                  "value": "voice"
                }
              ]
           }
        ]
     },
     "metrics": ["oWaiting", "oOffQueueUsers"]
    }))

There was an improvement with the query but now it's breaking with the other metrics. I also tried different Javascript methods (filter, indexOf, etc). Would you happen to know which one would be suitable for my situation? I would really appreciate it!

Thank you,
Dianne

The rest of the code expects stats for 'oOnQueueUsers' and 'oActiveUsers' - if you have not modified it.

'oOffQueueUsers' is a queue level metric, as I explained above.
So your query is still returning multiple entities - this time 2 entities.
One entity with "group": { "queueId": "xxxxx" } (for the oOffQueueUsers' metric) and one entity with "group": { "queueId": "xxxxx", "mediaType": "voice" }

"Would you happen to know which one would be suitable for my situation?"
It depends what you are trying to achieve and what metrics you want to display.

My recommendation is to change the code (javascript side) in the getQueueObservations functions.
In genesys-cloud.service.ts, you can return data.results? (I mean: result = data.results? - or result = data.results - I am not familiar with angular). This will be an array.
And in queue-details.component.ts, change the processing after ".subscribe(result => {". In the original code, it expects a single entity. In your modified, it could be an array of entities, that you will then parse to extract the appropriate values.

Regards,

Hello Jerome,

I tried doing that prior to posting but I was met with errors, here's what I got:


Not sure if this is a TypeScript or Angular issue (honestly I have never worked with TS/Angular before). Also tried deconstructing it with const {data.result} = result to no avail.

The three metrics we want to display are oWaiting, oOnQueueUsers, and oOffQueueUsers (I somehow accidentally deleted the other metrics when I was doing more testing).

Update:

oWaiting, oOnQueueUsers, and oOffQueueUsers are now showing. The only time I get undefined is when these metric counts are 0. However, seems like oWaiting doesn't co-exist with oOnQueueUsers and oOffQueueUsers in the same queue (and I'm pretty sure its because of the entity conflict, group: xxx vs group: xxx && mediaType: voice).

We have a regular queue and a voicemail queue (I used the same colours to showcase which queues are in pairs). So both blue queues should be outputting: Waiting 3, OffQueue: 5, and OnQueue:1.

I'm hoping that both queues can print all the given metric counts but it's been quite tricky.

Any input in this would be appreciated!

Thank you,
Dianne

Hello,

As explained in my previous answer, some of the statistics are queue-level metrics: 'oOnQueueUsers', 'oActiveUsers', 'oOffQueueUsers'
When a queue level metric or (queue-level metric + qualifier) is not present in the response, it means that its value is zero.
'oOnQueueUsers' and 'oOffQueueUsers' also have another specificity: you can get 0 to n results back when requesting such metric. A value (if different from 0) will be provided per (metric, qualifier) [presence status id for 'oOffQueueUsers' metric, and routing status id for 'oOnQueueUsers' metric]

NB: That is the purpose 'reduce()' when computing this.onQueueAgents (make the sum of all 'oOnQueueUsers' metrics - i.e. Contact Center Agents who are OnQueue and in any routing status (IDLE/Waiting for an interaction, INTERACTING/Working on an interaction, ...).

'oWaiting' is (queue, mediaType) metric.
Unless you add a filter on mediaType in the request, the response will return metrics for all mediaTypes (with 'oWaiting', stats are also reported if value is equal to zero)

The following should work:

In genesys-cloud.service.ts

getQueueObservations(queueId: string): Observable<platformClient.Models.QueueObservationDataContainer[]> {
    return from(this.analyticsApi.postAnalyticsQueuesObservationsQuery({
      filter: {
        type: 'or',
        predicates: [
          {
            type: 'dimension',
            dimension: 'queueId',
            operator: 'matches',
            value: queueId
          }
        ]
      },
      metrics: ['oOnQueueUsers', 'oActiveUsers', 'oOffQueueUsers', 'oWaiting']
    }))
      .pipe(
        map(data => {
          const results = data.results?.filter(r => r.group?.queueId === queueId);
          if (!results) throw new Error(`No results queried for ${queueId}`);

          console.log("RESULT QUEUE OBS");
          console.log(results);

          return results;
        }),
      );
  }

In queue-details.component.ts

export class QueueDetailsComponent implements OnInit {
    @Input() queue?: platformClient.Models.UserQueue | platformClient.Models.Queue;
    onQueueAgents = 0;
    totalAgents = 0;
    waitingVoice = 0;
    offQueueOfflineAgents = 0;
    offQueueAgents = 0;
    fetching = true;

and

getQueueObservations() {
    if (!this.queue) throw Error('Invalid queue.');
    this.fetching = true;

    this.genesysCloudService.getQueueObservations(this.queue.id!)
      .subscribe(results => {
        if (!results || results.length === 0) throw new Error('Error in getting observations.')

        let resultVoice = results.find(od => od.group?.mediaType === 'voice');
        if (!resultVoice) throw new Error(`No results queried for ${this.queue?.id} voice media type`);
        if (resultVoice.data) {
          this.waitingVoice = resultVoice.data
            .find(d => d.metric === 'oWaiting')!.stats!.count || 0;
        }

        let resultQueue = results.find(od => !od.group?.hasOwnProperty('mediaType'));
        if (!resultQueue) throw new Error(`No results queried for ${this.queue?.id} queue level stats`);
        if (resultQueue.data) {
          this.onQueueAgents = resultQueue.data
            .filter(d => d.metric === 'oOnQueueUsers')
            .reduce((acc, d) => acc + d.stats!.count!, 0);

          this.totalAgents = resultQueue.data
            .find(d => d.metric === 'oActiveUsers')!.stats!.count || 0;

          this.offQueueOfflineAgents = resultQueue.data
            .filter(d => d.metric === 'oOffQueueUsers' && d.qualifier === 'ccf3c10a-aa2c-4845-8e8d-f59fa48c58e5')
            .reduce((acc, d) => acc + d.stats!.count!, 0);

          this.offQueueAgents = resultQueue.data
            .filter(d => d.metric === 'oOffQueueUsers')
            .reduce((acc, d) => acc + d.stats!.count!, 0);
        }

        this.fetching = false;
      });
  }

In queue-details.component.html

<div class="queue-details">
  <div class="queue-name">
    <div>{{queue?.name}}</div> 
    <div class="small">id: {{queue?.id}}</div> 
  </div>
  <div *ngIf="!fetching; else loadingIndicator" class="queue-observations">
    <div class="onqueue">Waiting (Voice): {{this.waitingVoice}}</div>
    <div class="onqueue">OffQueue (Offline): {{this.offQueueOfflineAgents}}</div>
    <div class="onqueue">OffQueue (All): {{this.offQueueAgents}}</div>
    <div class="onqueue">OnQueue: {{this.onQueueAgents}}</div>
    <div class="total">Total: {{this.totalAgents}}</div>
    <div>
      <button [disabled]="!totalAgents" class="logout-agents" (click)="logoutAgents()">
        Log Out All Agents
      </button>
    </div>
  </div>
  <ng-template #loadingIndicator>
    <div class="spinner">
      <img src="assets/loading.svg" alt="Loading..." srcset="">
    </div>
  </ng-template>
</div>

Regards,

1 Like

Thank you so much Jerome! This is exactly what we needed. I really appreciate it.

Have a great one!
Dianne

This topic was automatically closed 31 days after the last reply. New replies are no longer allowed.