diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml index 65057afdc4..bd0ecda177 100644 --- a/.github/workflows/npmpublish.yml +++ b/.github/workflows/npmpublish.yml @@ -53,7 +53,7 @@ jobs: name: code-coverage-report path: coverage - name: Analyze with SonarCloud - uses: sonarsource/sonarcloud-github-action@v3.0.0 + uses: sonarsource/sonarcloud-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index e63ff2ddac..0d6d02e97b 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,5 +1,8 @@ ## RELEASE NOTES +### Version 7.0.75-exui-2315 +**EXUI-2315** etrieve current user language selection + ### Version 7.0.75-exui-2515 **EXUI-2515** case-link-issue diff --git a/package.json b/package.json index ad572f51fa..20f7e931d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hmcts/ccd-case-ui-toolkit", - "version": "7.0.75-exui-2515", + "version": "7.0.75-exui-2315", "engines": { "node": ">=18.19.0" }, diff --git a/projects/ccd-case-ui-toolkit/package.json b/projects/ccd-case-ui-toolkit/package.json index 640f1fc5a1..08fb6f6352 100644 --- a/projects/ccd-case-ui-toolkit/package.json +++ b/projects/ccd-case-ui-toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@hmcts/ccd-case-ui-toolkit", - "version": "7.0.75-exui-2515", + "version": "7.0.75-exui-2315", "engines": { "node": ">=18.19.0" }, diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/services/cases.service.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/services/cases.service.ts index 87edcd258d..856e9f502c 100644 --- a/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/services/cases.service.ts +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/services/cases.service.ts @@ -406,59 +406,9 @@ export class CasesService { } private updateClientContextStorage(headers: HttpHeaders): void { - // for mocking - TODO: Kasi Remove/Uncomment for testing - // headers = this.setMockClientContextHeader(headers); if (headers && headers.get('Client-Context')) { const clientContextString = window.atob(headers.get('Client-Context')); this.sessionStorageService.setItem('clientContext', clientContextString); } } - - // for mocking - TODO: Kasi Remove/Uncomment for testing - /* private setMockClientContextHeader(headers: HttpHeaders): HttpHeaders { - const mockClientContext = { client_context: { - user_task: { - task_data: { - // Replace with relevant task id to complete other/current task - id: "2c7e03cc-18e8-11ef-bfd0-763319b21cea", - // Can edit other details to check they update - name: "Review the appeal - Test mocked", - assignee: "dfd4c2d1-67b1-40f9-8680-c9551632f5d9", - type: "reviewTheAppeal", - task_state: "assigned", - task_system: "SELF", - security_classification: "PUBLIC", - task_title: "Review the appeal", - created_date: "2024-05-23T09:38:12+0000", - due_date: "2024-05-28T09:39:00+0000", - location_name: "Taylor House", - location: "765324", - execution_type: "Case Management Task", - jurisdiction: "IA", - region: "1", - case_type_id: "Asylum", - case_id: "1716456926502698", - case_category: "Protection", - case_name: "Aipp Check", - auto_assigned: false, - warnings: false, - warning_list: { values: [] }, - case_management_category: "Protection", - work_type_id: "decision_making_work", - work_type_label: "Decision-making work", - permissions: { values : ["Read","Own","Manage","Execute","Cancel","Complete","Claim","Assign","Unassign"] }, - description: "[Request respondent evidence](/case/IA/Asylum/${[CASE_REFERENCE]}/trigger/requestRespondentEvidence)", - role_category: "LEGAL_OPERATIONS", - minor_priority: 500, - major_priority: 5000, - priority_date: "2024-05-28T09:39:00+0000" - }, - // determines whether task will be completed - sets default to true via EXUI - complete_task: true - } - }}; - const encodedMockedClientContext = window.btoa(JSON.stringify(mockClientContext)); - headers = headers.set('Client-Context', encodedMockedClientContext); - return headers; - } */ } diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/services/page-validation.service.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/services/page-validation.service.ts index e4d13b4780..172d7443ec 100644 --- a/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/services/page-validation.service.ts +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/services/page-validation.service.ts @@ -16,7 +16,7 @@ export class PageValidationService { page.case_fields .filter(caseField => !this.caseFieldService.isReadOnly(caseField)) .filter(caseField => !this.isHidden(caseField, editForm)) - .map(caseField => { + .forEach(caseField => { const theControl = FieldsUtils.isCaseFieldOfType(caseField, ['JudicialUser']) ? editForm.controls['data'].get(`${caseField.id}_judicialUserControl`) : editForm.controls['data'].get(caseField.id); diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/event-guard/event-start.guard.spec.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/event-guard/event-start.guard.spec.ts index 693e3b2642..a5e546cfd1 100644 --- a/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/event-guard/event-start.guard.spec.ts +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/event-guard/event-start.guard.spec.ts @@ -3,7 +3,7 @@ import { ActivatedRouteSnapshot, Router } from '@angular/router'; import { of } from 'rxjs'; import { TaskPayload } from '../../../domain/work-allocation/TaskPayload'; import { UserInfo } from '../../../domain/user/user-info.model'; -import { SessionStorageService } from '../../../services'; +import { ReadCookieService, SessionStorageService } from '../../../services'; import { WorkAllocationService } from '../../case-editor'; import { EventStartGuard } from './event-start.guard'; import { AbstractAppConfig } from '../../../../app.config'; @@ -30,12 +30,14 @@ describe('EventStartGuard', () => { let service: jasmine.SpyObj; let router: jasmine.SpyObj; let sessionStorageService: jasmine.SpyObj; + let mockCookieService: jasmine.SpyObj; let mockAbstractConfig: jasmine.SpyObj; beforeEach(() => { service = jasmine.createSpyObj('WorkAllocationService', ['getTasksByCaseIdAndEventId']); router = jasmine.createSpyObj('Router', ['navigate']); sessionStorageService = jasmine.createSpyObj('SessionStorageService', ['getItem', 'setItem', 'removeItem']); + mockCookieService = jasmine.createSpyObj('readCookieService', ['getCookie']); mockAbstractConfig = jasmine.createSpyObj('AbstractAppConfig', ['logMessage']); TestBed.configureTestingModule({ @@ -44,7 +46,8 @@ describe('EventStartGuard', () => { { provide: WorkAllocationService, useValue: service }, { provide: Router, useValue: router }, { provide: SessionStorageService, useValue: sessionStorageService }, - { provide: AbstractAppConfig, useValue: mockAbstractConfig } + { provide: AbstractAppConfig, useValue: mockAbstractConfig }, + { provide: ReadCookieService, useValue: mockCookieService } ] }); @@ -64,6 +67,19 @@ describe('EventStartGuard', () => { }); }); + it('client context should be set with language regardless whether task is attached to event', () => { + sessionStorageService.getItem.and.returnValue(null); + const mockClientContext = { client_context: { user_language: { language: 'cookieString' } } }; + mockCookieService.getCookie.and.returnValue('cookieString'); + const route = createActivatedRouteSnapshot('caseId', 'eventId'); + const result$ = guard.canActivate(route); + result$.subscribe(result => { + expect(result).toEqual(false); + // check client contesxt is set correctly + expect(sessionStorageService.setItem).toHaveBeenCalledWith('clientContext', JSON.stringify(mockClientContext)); + }); + }); + it('should log a message and not call getTasksByCaseIdAndEventId when caseInfo is not available', () => { sessionStorageService.getItem.and.returnValue(null); const route = createActivatedRouteSnapshot('caseId', 'eventId'); @@ -136,17 +152,22 @@ describe('EventStartGuard', () => { }); it('should return true and navigate to event trigger if one task is assigned to user', () => { + const mockLanguage = 'en'; const clientContext = { client_context: { user_task: { task_data: tasks[0], complete_task: true + }, + user_language: { + language: mockLanguage } } } tasks[0].assignee = '1'; const mockPayload: TaskPayload = {task_required_for_event: false, tasks}; sessionStorageService.getItem.and.returnValue(JSON.stringify(getExampleUserInfo())); + mockCookieService.getCookie.and.returnValue(mockLanguage); expect(guard.checkTaskInEventNotRequired(mockPayload, caseId, null)).toBe(true); expect(sessionStorageService.setItem).toHaveBeenCalledWith('clientContext', JSON.stringify(clientContext)); }); @@ -161,11 +182,15 @@ describe('EventStartGuard', () => { }); it('should return true and navigate to event trigger if navigated to via task next steps', () => { + const mockLanguage = 'cy'; const clientContext = { client_context: { user_task: { task_data: tasks[0], complete_task: true + }, + user_language: { + language: mockLanguage } } } @@ -173,6 +198,7 @@ describe('EventStartGuard', () => { tasks.push(tasks[0]); const mockPayload: TaskPayload = {task_required_for_event: false, tasks}; sessionStorageService.getItem.and.returnValue(JSON.stringify(getExampleUserInfo())); + mockCookieService.getCookie.and.returnValue(mockLanguage); expect(guard.checkTaskInEventNotRequired(mockPayload, caseId, '0d22d838-b25a-11eb-a18c-f2d58a9b7bc6')).toBe(true); expect(sessionStorageService.setItem).toHaveBeenCalledWith('clientContext', JSON.stringify(clientContext)); }); diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/event-guard/event-start.guard.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/event-guard/event-start.guard.ts index 81feae8093..1d6a7e0471 100644 --- a/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/event-guard/event-start.guard.ts +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/event-guard/event-start.guard.ts @@ -6,7 +6,7 @@ import { switchMap } from 'rxjs/operators'; import { AbstractAppConfig } from '../../../../app.config'; import { TaskEventCompletionInfo } from '../../../domain/work-allocation/Task'; import { TaskPayload } from '../../../domain/work-allocation/TaskPayload'; -import { SessionStorageService } from '../../../services'; +import { ReadCookieService, SessionStorageService } from '../../../services'; import { WorkAllocationService } from '../../case-editor'; @Injectable() @@ -16,7 +16,8 @@ export class EventStartGuard implements CanActivate { constructor(private readonly workAllocationService: WorkAllocationService, private readonly router: Router, private readonly sessionStorageService: SessionStorageService, - private readonly abstractConfig: AbstractAppConfig) { + private readonly abstractConfig: AbstractAppConfig, + private readonly cookieService: ReadCookieService) { } public canActivate(route: ActivatedRouteSnapshot): Observable { @@ -30,6 +31,16 @@ export class EventStartGuard implements CanActivate { userId = userInfo.id ? userInfo.id : userInfo.uid; } const caseInfoStr = this.sessionStorageService.getItem('caseInfo'); + const currentLanguage = this.cookieService.getCookie('exui-preferred-language'); + // if one task assigned to user, allow user to complete event + const storeClientContext = { + client_context: { + user_language: { + language: currentLanguage + } + } + }; + this.sessionStorageService.setItem(EventStartGuard.CLIENT_CONTEXT, JSON.stringify(storeClientContext)); if (caseInfoStr) { const caseInfo = JSON.parse(caseInfoStr); if (caseInfo && caseInfo.cid === caseId) { @@ -75,12 +86,16 @@ export class EventStartGuard implements CanActivate { } else { task = tasksAssignedToUser[0]; } + const currentLanguage = this.cookieService.getCookie('exui-preferred-language'); // if one task assigned to user, allow user to complete event const storeClientContext = { client_context: { user_task: { task_data: task, complete_task: true + }, + user_language: { + language: currentLanguage } } }; @@ -105,11 +120,15 @@ export class EventStartGuard implements CanActivate { taskId: task.id, createdTimestamp: Date.now() }; + const currentLanguage = this.cookieService.getCookie('exui-preferred-language'); const storeClientContext = { client_context: { user_task: { task_data: task, complete_task: true + }, + user_language: { + language: currentLanguage } } }; diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/event-start.component.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/event-start.component.ts index c02d551db7..a291ca8d72 100644 --- a/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/event-start.component.ts +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/event-start.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { StateMachine } from '@edium/fsm'; import { Task } from '../../domain/work-allocation/Task'; +import { ReadCookieService } from '../../services/cookie/read-cookie-service'; import { SessionStorageService } from '../../services/session/session-storage.service'; import { EventStartStateMachineContext } from './models/event-start-state-machine-context.model'; import { EventStartStateMachineService } from './services/event-start-state-machine.service'; @@ -18,7 +19,8 @@ export class EventStartComponent implements OnInit { constructor(private service: EventStartStateMachineService, private readonly router: Router, private readonly route: ActivatedRoute, - private readonly sessionStorageService: SessionStorageService) { + private readonly sessionStorageService: SessionStorageService, + private readonly cookieService: ReadCookieService) { } public ngOnInit(): void { @@ -36,7 +38,8 @@ export class EventStartComponent implements OnInit { taskId, router: this.router, route: this.route, - sessionStorageService: this.sessionStorageService + sessionStorageService: this.sessionStorageService, + cookieService: this.cookieService }; // Initialise state machine diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/models/event-start-state-machine-context.model.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/models/event-start-state-machine-context.model.ts index 0cfd149b0b..2470a68119 100644 --- a/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/models/event-start-state-machine-context.model.ts +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/models/event-start-state-machine-context.model.ts @@ -1,6 +1,6 @@ import { ActivatedRoute, Router } from '@angular/router'; import { Task } from '../../../domain/work-allocation/Task'; -import { SessionStorageService } from '../../../services'; +import { ReadCookieService, SessionStorageService } from '../../../services'; export interface EventStartStateMachineContext { tasks: Task[]; @@ -10,4 +10,5 @@ export interface EventStartStateMachineContext { router: Router; route: ActivatedRoute; sessionStorageService: SessionStorageService; + cookieService: ReadCookieService; } diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/services/event-start-state-machine.service.spec.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/services/event-start-state-machine.service.spec.ts index d0a6cdb145..d2ea4c7e81 100644 --- a/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/services/event-start-state-machine.service.spec.ts +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/services/event-start-state-machine.service.spec.ts @@ -3,7 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { StateMachine } from '@edium/fsm'; import { Task } from '../../../domain/work-allocation/Task'; -import { SessionStorageService } from '../../../services'; +import { ReadCookieService, SessionStorageService } from '../../../services'; import { EventStartStateMachineContext, EventStartStates } from '../models'; import { EventStartStateMachineService } from './event-start-state-machine.service'; import createSpyObj = jasmine.createSpyObj; @@ -11,6 +11,7 @@ import createSpyObj = jasmine.createSpyObj; describe('EventStartStateMachineService', () => { let service: EventStartStateMachineService; let stateMachine: StateMachine; + let mockReadCookieService: any; let mockSessionStorageService: any; // tslint:disable-next-line: prefer-const let mockRoute: ActivatedRoute; @@ -109,6 +110,7 @@ describe('EventStartStateMachineService', () => { ]; mockSessionStorageService = createSpyObj('sessionStorageService', ['getItem', 'setItem']); + mockReadCookieService = createSpyObj('readCookieService', ['getCookie']); mockSessionStorageService.getItem.and.returnValue(`{"id": "test-user-id", "forename": "Test", "surname": "User", "roles": ["caseworker-role1", "caseworker-role3"], "email": "test@mail.com", "token": null}`); @@ -119,7 +121,8 @@ describe('EventStartStateMachineService', () => { taskId: '1122-3344-5566-7788', router: mockRouter, route: mockRoute, - sessionStorageService: mockSessionStorageService + sessionStorageService: mockSessionStorageService, + cookieService: mockReadCookieService }; beforeEach(async () => { diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/services/event-start-state-machine.service.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/services/event-start-state-machine.service.ts index 8ab746f4b4..5b95b4cf08 100644 --- a/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/services/event-start-state-machine.service.ts +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/components/event-start/services/event-start-state-machine.service.ts @@ -184,14 +184,18 @@ export class EventStartStateMachineService { task = context.tasks[0]; } - const taskStr= JSON.stringify(task); + const taskStr = JSON.stringify(task); console.log('entryActionForStateOneTaskAssignedToUser: setting client context task_data to ' + taskStr); // Store task to session + const currentLanguage = context.cookieService.getCookie('exui-preferred-language'); const clientContext = { client_context: { user_task: { task_data: task, complete_task: true + }, + user_language: { + language: currentLanguage } } }; diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/services/cookie/index.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/services/cookie/index.ts new file mode 100644 index 0000000000..af017e92dd --- /dev/null +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/services/cookie/index.ts @@ -0,0 +1 @@ +export * from './read-cookie-service'; \ No newline at end of file diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/services/cookie/read-cookie-service.spec.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/services/cookie/read-cookie-service.spec.ts new file mode 100644 index 0000000000..d68b07870c --- /dev/null +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/services/cookie/read-cookie-service.spec.ts @@ -0,0 +1,21 @@ +import { ReadCookieService } from "./read-cookie-service"; + +describe('CookieService', () => { + const mockDocument: any = { + cookie: '' + }; + + const cookieService: ReadCookieService = new ReadCookieService(mockDocument); + + afterEach(() => { + mockDocument.cookie = ''; + }); + + it('should get a cookie', () => { + + mockDocument.cookie = 'user=dummy'; + const result = cookieService.getCookie('user'); + expect(result).toBe('dummy'); + }); + +}); diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/services/cookie/read-cookie-service.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/services/cookie/read-cookie-service.ts new file mode 100644 index 0000000000..34739d5365 --- /dev/null +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/services/cookie/read-cookie-service.ts @@ -0,0 +1,21 @@ +import { DOCUMENT } from '@angular/common'; +import { Inject, Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class ReadCookieService { + private readonly document?: Document; + + constructor(@Inject(DOCUMENT) doc?: any) { + this.document = doc as Document; + } + + public getCookie(key: string): string { + const cookieValue = this.document.cookie + .split('; ') + .find(row => row.startsWith(`${key}=`)) + .split('=')[1]; + return cookieValue; + } +} diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/services/index.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/services/index.ts index f36a406324..060ab28f09 100644 --- a/projects/ccd-case-ui-toolkit/src/lib/shared/services/index.ts +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/services/index.ts @@ -5,6 +5,7 @@ export * from './auth'; export * from './case-fields'; export * from './case-file-view'; export * from './case-flag'; +export * from './cookie'; export * from './document-management'; export * from './draft'; export * from './error'; diff --git a/sonar-project.properties b/sonar-project.properties index 9c0f09a1fd..2fd4084099 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -8,5 +8,4 @@ sonar.test.inclusions=projects/ccd-case-ui-toolkit/src/**/*.spec.ts sonar.exclusions=projects/ccd-case-ui-toolkit/src/*.spec.ts, projects/ccd-case-ui-toolkit/src/**/*.module.ts, projects/ccd-case-ui-toolkit/src/lib/shared/components/palette/datetime-picker/datetime-picker-component.spec.ts, projects/ccd-case-ui-toolkit/src/lib/shared/test/mock-rpx-translate.pipe.ts, projects/ccd-case-ui-toolkit/src/test.ts, projects/ccd-case-ui-toolkit/src/lib/shared/components/palette/case-flag/write-case-flag-field.component.ts, projects/ccd-case-ui-toolkit/src/lib/shared/components/palette/datetime-picker/datetime-picker-component.ts, projects/ccd-case-ui-toolkit/src/lib/shared/components/palette/complex/read-complex-field.component.ts, projects/ccd-case-ui-toolkit/src/lib/shared/commons/address-validation-constants.ts, projects/ccd-case-ui-toolkit/src/lib/shared/components/palette/markdown/markdown.component.ts sonar.javascript.lcov.reportPaths=/github/workspace/coverage/ccd-case-ui-toolkit/lcov-report/lcov.info -sonar.host.url= sonar.sourceEncoding=UTF-8