Skip to content

Commit

Permalink
Merge branch 'master' into bug/VIH-11132-grey-out-screened-participants
Browse files Browse the repository at this point in the history
  • Loading branch information
oliver-scott authored Jan 8, 2025
2 parents e8a9565 + 0e3fc61 commit 5eeb35f
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,9 @@ export class ConferenceTestData {
description: 'Spanish',
type: InterpreterType.Verbal
}),
linked_participants: []
linked_participants: [],
external_reference_id: 'B505FA9D-8072-4F96-8CA6-4F0489DD6E08',
protect_from: []
});

const participant2 = new ParticipantResponse({
Expand All @@ -498,7 +500,9 @@ export class ConferenceTestData {
tiled_display_name: 'CIVILIAN;James Green;9F681318-4955-49AF-A887-DED64554429J',
hearing_role: HearingRole.REPRESENTATIVE,
current_room: new RoomSummaryResponse(),
linked_participants: []
linked_participants: [],
external_reference_id: '072D80ED-6816-42AF-A0C0-2FAE0F65E17A',
protect_from: []
});

const participant3 = new ParticipantResponse({
Expand All @@ -511,7 +515,9 @@ export class ConferenceTestData {
tiled_display_name: 'JUDGE;Judge Fudge;9F681318-4955-49AF-A887-DED64554429T',
hearing_role: HearingRole.JUDGE,
current_room: new RoomSummaryResponse(),
linked_participants: []
linked_participants: [],
external_reference_id: '9B4737C9-5D8A-4B67-8569-EF8185FFE6E3',
protect_from: []
});

const participant4 = new ParticipantResponse({
Expand All @@ -524,7 +530,9 @@ export class ConferenceTestData {
tiled_display_name: 'Staff Member;Staff Member;9F681318-4965-49AF-A887-DED64554429T',
hearing_role: HearingRole.STAFF_MEMBER,
current_room: new RoomSummaryResponse({ label: 'ParticipantConsultationRoom1' }),
linked_participants: []
linked_participants: [],
external_reference_id: '9B4737C9-5D8A-4B67-8569-EF8185FFE6E3',
protect_from: []
});

participants.push(participant1);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { VideoWebService } from 'src/app/services/api/video-web.service';
import { translateServiceSpy } from 'src/app/testing/mocks/mock-translation.service';

import {
ConferenceResponse,
LoggedParticipantResponse,
ParticipantStatus,
Role,
RoomSummaryResponse
} from 'src/app/services/clients/api-client';
import { ConferenceResponse, ParticipantStatus, Role, RoomSummaryResponse } from 'src/app/services/clients/api-client';
import { Logger } from 'src/app/services/logging/logger-base';
import { ConferenceTestData } from 'src/app/testing/mocks/data/conference-test-data';
import { globalConference, globalEndpoint, globalParticipant } from '../../waiting-room-shared/tests/waiting-room-base-setup';
Expand All @@ -21,7 +15,6 @@ describe('JoinPrivateConsultationComponent', () => {
let logger: jasmine.SpyObj<Logger>;
let videoWebService: jasmine.SpyObj<VideoWebService>;

let logged: LoggedParticipantResponse;
const translateService = translateServiceSpy;

beforeAll(() => {
Expand All @@ -38,12 +31,6 @@ describe('JoinPrivateConsultationComponent', () => {
});
const judge = conference.participants.find(x => x.role === Role.Judge);

logged = new LoggedParticipantResponse({
participant_id: judge.id,
display_name: judge.display_name,
role: Role.Judge
});

component = new JoinPrivateConsultationComponent(logger, translateService);
});

Expand Down Expand Up @@ -91,7 +78,8 @@ describe('JoinPrivateConsultationComponent', () => {
it('should return participant hearing role text', () => {
const expectedText = 'hearing-role.litigant-in-person';
translateService.instant.calls.reset();
expect(component.getParticipantHearingRoleText(globalParticipant)).toEqual(expectedText);
const vhParticipant = mapParticipantToVHParticipant(globalParticipant);
expect(component.getParticipantHearingRoleText(vhParticipant)).toEqual(expectedText);
});

it('should return rooms available', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { ParticipantResponse } from 'src/app/services/clients/api-client';
import { Logger } from 'src/app/services/logging/logger-base';
import { VHEndpoint, VHParticipant } from '../../store/models/vh-conference';

Expand Down Expand Up @@ -113,11 +112,9 @@ export class JoinPrivateConsultationComponent {
this.selectedRoomLabel = roomLabel;
}

getParticipantHearingRoleText(participant: ParticipantResponse) {
getParticipantHearingRoleText(participant: VHParticipant) {
const translatedtext = this.translateService.instant('join-private-consultation.for');
const hearingRoleText = this.translateService.instant(
'hearing-role.' + participant.hearing_role.toLowerCase().split(' ').join('-')
);
const hearingRoleText = this.translateService.instant('hearing-role.' + participant.hearingRole.toLowerCase().split(' ').join('-'));
return participant.representee ? `${hearingRoleText} ${translatedtext} ${participant.representee}` : hearingRoleText;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,40 @@
<div class="language-icon"
*ngIf="interpreter">
<fa-icon icon="language"></fa-icon>
<div class="language-icon" *ngIf="interpreter">
<fa-icon icon="language"></fa-icon>
</div>
<div [ngClass]="{'participant-endpoint-row': interpreter == null ,
'individual-endpoint-row': interpreter != null }">
<div class="header-left" [class]="getRowClasses(participant)">
<app-invite-participant
*ngIf="!isParticipantInCurrentRoom(participant) && isParticipantAvailable(participant) && isInterpreterAvailable() && canInvite"
[participantId]="participant.id"
[conferenceId]="conferenceId"
[roomLabel]="roomLabel">
</app-invite-participant>
<fa-icon *ngIf="isParticipantInCurrentRoom(participant)"
icon="check"
aria-hidden="true"></fa-icon>
</div>
<app-private-consultation-participant-display-name
[displayName]="participant?.displayName"
[isInCurrentRoom]="isParticipantInCurrentRoom(participant)"
[isAvailable]="isParticipantAvailable(participant)">
</app-private-consultation-participant-display-name>
<app-private-consultation-participant-status
[entity]="participant"
[status]="status"
[roomLabel]="roomLabel">
</app-private-consultation-participant-status>
<div [ngClass]="{ 'participant-endpoint-row': interpreter == null, 'individual-endpoint-row': interpreter != null }">
<div class="header-left" [class]="getRowClasses(participant)">
<app-invite-participant
*ngIf="
!isParticipantInCurrentRoom(participant) &&
isParticipantAvailable(participant) &&
isInterpreterAvailable() &&
!isProtected() &&
canInvite
"
[participantId]="participant.id"
[conferenceId]="conferenceId"
[roomLabel]="roomLabel"
>
</app-invite-participant>
<fa-icon *ngIf="isParticipantInCurrentRoom(participant)" icon="check" aria-hidden="true"></fa-icon>
</div>
<app-private-consultation-participant-display-name
[displayName]="participant?.displayName"
[isInCurrentRoom]="isParticipantInCurrentRoom(participant)"
[isAvailable]="isParticipantAvailable(participant)"
>
</app-private-consultation-participant-display-name>
<app-private-consultation-participant-status [entity]="participant" [status]="status" [roomLabel]="roomLabel">
</app-private-consultation-participant-status>
</div>
<div *ngIf="interpreter"
class="individual-endpoint-row">
<div class="header-left" [class]="getRowClasses(interpreter)"> </div>
<app-private-consultation-participant-display-name
[displayName]="interpreter?.displayName"
[isInCurrentRoom]="isParticipantInCurrentRoom(interpreter)"
[isAvailable]="isParticipantAvailable(interpreter)">
</app-private-consultation-participant-display-name>
<app-private-consultation-participant-status
[entity]="interpreter"
[status]="status"
[roomLabel]="roomLabel">
</app-private-consultation-participant-status>
<div *ngIf="interpreter" class="individual-endpoint-row">
<div class="header-left" [class]="getRowClasses(interpreter)"></div>
<app-private-consultation-participant-display-name
[displayName]="interpreter?.displayName"
[isInCurrentRoom]="isParticipantInCurrentRoom(interpreter)"
[isAvailable]="isParticipantAvailable(interpreter)"
>
</app-private-consultation-participant-display-name>
<app-private-consultation-participant-status [entity]="interpreter" [status]="status" [roomLabel]="roomLabel">
</app-private-consultation-participant-status>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,22 @@ describe('ParticipantItemComponent', () => {
expect(result).toBeFalse();
});
});

describe('isProtected', () => {
it('should return true when participant is protected', () => {
const participant = mapParticipantToVHParticipant(conference.participants[0]);
component.participant = participant;
component.participantCallStatuses[participant.id] = 'Protected';
const result = component.isProtected();
expect(result).toBeTrue();
});

it('should return false when participant is not protected', () => {
const participant = mapParticipantToVHParticipant(conference.participants[0]);
component.participant = participant;
component.participantCallStatuses[participant.id] = null;
const result = component.isProtected();
expect(result).toBeFalse();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,8 @@ export class ParticipantItemComponent {

return this.isParticipantAvailable(this.interpreter);
}

isProtected(): boolean {
return this.participantCallStatuses[this.participant.id] === 'Protected';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,13 @@ describe('PrivateConsultationParticipantsComponent', () => {

it('should set answer on response message', () => {
component.roomLabel = 'Room1';
const participantId1 = conference.participants.find(x => x.role === Role.Individual).id;
consultationRequestResponseMessageSubjectMock.next(
new ConsultationRequestResponseMessage(conference.id, invitationId, 'Room1', 'Participant1', ConsultationAnswer.Rejected)
new ConsultationRequestResponseMessage(conference.id, invitationId, 'Room1', participantId1, ConsultationAnswer.Rejected)
);

// Assert
expect(component.participantCallStatuses['Participant1']).toBe('Rejected');
expect(component.participantCallStatuses[participantId1]).toBe('Rejected');
});

it('should not set answer if different room', () => {
Expand Down Expand Up @@ -227,31 +228,34 @@ describe('PrivateConsultationParticipantsComponent', () => {

it('should set answer on response message then reset after timeout', fakeAsync(() => {
component.roomLabel = 'Room1';
const participantid = conference.participants.find(x => x.role === Role.Individual).id;
consultationRequestResponseMessageSubjectMock.next(
new ConsultationRequestResponseMessage(conference.id, invitationId, 'Room1', 'Participant1', ConsultationAnswer.Rejected)
new ConsultationRequestResponseMessage(conference.id, invitationId, 'Room1', participantid, ConsultationAnswer.Rejected)
);
flushMicrotasks();

// Assert
expect(component.participantCallStatuses['Participant1']).toBe('Rejected');
expect(component.participantCallStatuses[participantid]).toBe('Rejected');
tick(10000);
expect(component.participantCallStatuses['Participant1']).toBeNull();
expect(component.participantCallStatuses[participantid]).toBeNull();
}));

it('should a 2nd call after answering should prevent timeout call', fakeAsync(() => {
component.roomLabel = 'Room1';
const participantid1 = conference.participants.find(x => x.role === Role.Individual).id;
const participantid2 = conference.participants.find(x => x.role === Role.Representative).id;
consultationRequestResponseMessageSubjectMock.next(
new ConsultationRequestResponseMessage(conference.id, invitationId, 'Room1', 'Participant1', ConsultationAnswer.Rejected)
new ConsultationRequestResponseMessage(conference.id, invitationId, 'Room1', participantid1, ConsultationAnswer.Rejected)
);
flushMicrotasks();
tick(2000);
requestedConsultationMessageSubjectMock.next(
new RequestedConsultationMessage(conference.id, invitationId, 'Room1', 'Participant2', 'Participant1')
new RequestedConsultationMessage(conference.id, invitationId, 'Room1', participantid2, participantid1)
);
tick(9000);

// Assert
expect(component.participantCallStatuses['Participant1']).toBe('Calling');
expect(component.participantCallStatuses[participantid1]).toBe('Calling');
}));

it('should not set calling if different room', () => {
Expand All @@ -276,15 +280,17 @@ describe('PrivateConsultationParticipantsComponent', () => {

it('should reset participant call status on status message', () => {
component.roomLabel = 'Room1';
const participantid1 = conference.participants.find(x => x.role === Role.Individual).id;
const participantid2 = conference.participants.find(x => x.role === Role.Representative).id;
requestedConsultationMessageSubjectMock.next(
new RequestedConsultationMessage(conference.id, invitationId, 'Room1', 'Participant2', 'Participant1')
new RequestedConsultationMessage(conference.id, invitationId, 'Room1', participantid2, participantid1)
);
participantStatusSubjectMock.next(
new ParticipantStatusMessage('Participant1', 'Username', conference.id, ParticipantStatus.Disconnected)
new ParticipantStatusMessage(participantid1, 'Username', conference.id, ParticipantStatus.Disconnected)
);

// Assert
expect(component.participantCallStatuses['Participant1']).toBeNull();
expect(component.participantCallStatuses[participantid1]).toBeNull();
});

it('should get participant status', () => {
Expand Down Expand Up @@ -785,4 +791,28 @@ describe('PrivateConsultationParticipantsComponent', () => {
expect(result).toBeFalse();
});
});

describe('setParticipantCallStatus', () => {
it('should set the participant call status and that of participants protected by the participant', () => {
const participant = conference.participants.find(x => x.role === Role.Individual);
const protectedParticipant = conference.participants.find(x => x.role === Role.Representative);
participant.protectedFrom = [protectedParticipant.externalReferenceId];

component.setParticipantCallStatus(participant.id, 'Calling', 'Restricted');

expect(component.participantCallStatuses[participant.id]).toBe('Calling');
expect(component.participantCallStatuses[protectedParticipant.id]).toBe('Restricted');
});

it('should set the participant call status and that of participants protected by the participant', () => {
const participant = conference.participants.find(x => x.role === Role.Individual);
const protectedParticipant = conference.participants.find(x => x.role === Role.Representative);
protectedParticipant.protectedFrom = [participant.externalReferenceId];

component.setParticipantCallStatus(participant.id, 'Calling', 'Protected');

expect(component.participantCallStatuses[participant.id]).toBe('Calling');
expect(component.participantCallStatuses[protectedParticipant.id]).toBe('Protected');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ export class PrivateConsultationParticipantsComponent extends WRParticipantStatu
this.eventHubSubscriptions$.add(
this.eventService.getConsultationRequestResponseMessage().subscribe(message => {
if (message.roomLabel === this.roomLabel && message.conferenceId === this.conference.id) {
this.participantCallStatuses[message.requestedFor] = message.answer;
this.setParticipantCallStatus(message.requestedFor, message.answer, null);
setTimeout(() => {
if (this.participantCallStatuses[message.requestedFor] === message.answer) {
this.participantCallStatuses[message.requestedFor] = null;
this.setParticipantCallStatus(message.requestedFor, null, null);
}
}, 10000);
}
Expand All @@ -74,7 +74,7 @@ export class PrivateConsultationParticipantsComponent extends WRParticipantStatu
// Set 'Calling...'
// No need to timeout here the text because when the notification times out it will send another event.
if (message.roomLabel === this.roomLabel && message.conferenceId === this.conference.id) {
this.participantCallStatuses[message.requestedFor] = 'Calling';
this.setParticipantCallStatus(message.requestedFor, 'Calling', 'Protected');
}
})
);
Expand All @@ -83,11 +83,28 @@ export class PrivateConsultationParticipantsComponent extends WRParticipantStatu
this.eventHubSubscriptions$.add(
this.eventService.getParticipantStatusMessage().subscribe(message => {
// If the participant state changes reset the state.
this.participantCallStatuses[message.participantId] = null;
this.setParticipantCallStatus(message.participantId, null, null);
})
);
}

setParticipantCallStatus(participantId: string, status, protectedFromStatus): void {
// Update the call status for the given participant
this.participantCallStatuses[participantId] = status;

// Find the participant with the given ID
const participant = this.nonJudgeParticipants.find(p => p.id === participantId);
// for each non-judge participant, if the participant is on their protected from list, disable the call button
this.nonJudgeParticipants.forEach(p => {
if (p.protectedFrom.includes(participant?.externalReferenceId)) {
this.participantCallStatuses[p.id] = protectedFromStatus;
}
if (participant.protectedFrom.includes(p?.externalReferenceId)) {
this.participantCallStatuses[p.id] = protectedFromStatus;
}
});
}

canCallEndpoint(endpoint: VHEndpoint): boolean {
return (
!this.isParticipantInCurrentRoom(endpoint) &&
Expand Down

0 comments on commit 5eeb35f

Please sign in to comment.