Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EXUI-2551: case view not fully expanded (#1833) #1840

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
7 changes: 6 additions & 1 deletion RELEASE-NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## RELEASE NOTES

### Version 7.1.32-rc1
**EXUI-2551** Case File view too small not loading with correct size
**EXUI-2104** Event History Summary - ExUI Changes
**EXUI-2668** when Update Referral event completed (incorrect)

### Version 7.1.32
**EXUI-2729** Query Management - Callback error handling.

Expand Down Expand Up @@ -34,7 +39,7 @@ Taken by PR
**EXUI-2389** PED and Media Viewer

### Version 7.0.75-exui-2315
**EXUI-2315** etrieve current user language selection
**EXUI-2315** Retrieve current user language selection


### Version 7.0.75-exui-2515
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hmcts/ccd-case-ui-toolkit",
"version": "7.1.32",
"version": "7.1.32-srt-fix-task-completion-2",
"engines": {
"node": ">=18.19.0"
},
Expand Down
2 changes: 1 addition & 1 deletion projects/ccd-case-ui-toolkit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hmcts/ccd-case-ui-toolkit",
"version": "7.1.32",
"version": "7.1.32-srt-fix-task-completion-2",
"engines": {
"node": ">=18.19.0"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CaseEditUtils, convertNonASCIICharacter } from "./case-edit.utils";
import { SessionStorageService } from "../../../services";
import { CaseEditUtils, convertNonASCIICharacter, removeTaskFromClientContext } from "./case-edit.utils";

describe('CaseEditUtils', () => {
const caseUtils: CaseEditUtils = new CaseEditUtils();
Expand Down Expand Up @@ -61,4 +62,34 @@ describe('CaseEditUtils', () => {
});
});

describe('removeTaskFromClientContext', () => {
const fullMockClientContext = { client_context: { user_task: 'task', user_language: 'language' } };
const partialMockClientContext = { client_context: { user_language: 'language' } };

let mockSessionStorageService;

beforeEach(() => {
mockSessionStorageService = jasmine.createSpyObj<SessionStorageService>('SessionStorageService', ['getItem', 'removeItem', 'setItem']);
})

it('should correctly remove task from client context', () => {
mockSessionStorageService.getItem.and.returnValue(JSON.stringify(fullMockClientContext));
removeTaskFromClientContext(mockSessionStorageService);
expect(mockSessionStorageService.setItem).toHaveBeenCalledWith('clientContext', JSON.stringify(partialMockClientContext));
});

it('should do nothing if there is no session storage service', () => {
removeTaskFromClientContext(null);
mockSessionStorageService.getItem.and.returnValue(null);
removeTaskFromClientContext(mockSessionStorageService);
expect(mockSessionStorageService.setItem).not.toHaveBeenCalled();
});

it('should do nothing if there is no user_task', () => {
mockSessionStorageService.getItem.and.returnValue(JSON.stringify(partialMockClientContext));
removeTaskFromClientContext(mockSessionStorageService);
expect(mockSessionStorageService.setItem).not.toHaveBeenCalled();
});
});

});
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { SessionStorageService } from "../../../services";
import { CaseEditComponent } from "../case-edit/case-edit.component";

export function convertNonASCIICharacter(character: string): string {
if (character === '£') {
Expand Down Expand Up @@ -36,3 +38,15 @@ export class CaseEditUtils {
return rawString;
}
}

export function removeTaskFromClientContext(sessionStorageService: SessionStorageService): void {
if (!sessionStorageService) {
return;
}
const clientContextString = sessionStorageService.getItem(CaseEditComponent.CLIENT_CONTEXT);
const clientContext = clientContextString ? JSON.parse(clientContextString) : null;
if (clientContext?.client_context?.user_task) {
delete clientContext.client_context.user_task;
sessionStorageService.setItem(CaseEditComponent.CLIENT_CONTEXT, JSON.stringify(clientContext));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { CaseEventTrigger } from '../../../domain/case-view/case-event-trigger.m
import { CaseField } from '../../../domain/definition/case-field.model';
import { createCaseEventTrigger } from '../../../fixture/shared.test.fixture';
import { FieldsFilterPipe } from '../../../pipes/complex/fields-filter.pipe';
import { AlertService, FieldsPurger, FieldsUtils, LoadingService, SessionStorageService, WindowService } from '../../../services';
import { AlertService, FieldsPurger, FieldsUtils, LoadingService, ReadCookieService, SessionStorageService, WindowService } from '../../../services';
import { FormErrorService, FormValueService } from '../../../services/form';
import { PaletteUtilsModule } from '../../palette';
import { Confirmation, Wizard, WizardPage, WizardPageField } from '../domain';
Expand Down Expand Up @@ -227,6 +227,7 @@ describe('CaseEditComponent', () => {
let mockSessionStorageService: jasmine.SpyObj<SessionStorageService>;
let mockWorkAllocationService: jasmine.SpyObj<WorkAllocationService>;
let mockAlertService: jasmine.SpyObj<AlertService>;
let mockCookieService: jasmine.SpyObj<ReadCookieService>;
let mockabstractConfig: jasmine.SpyObj<AbstractAppConfig>;
const validPageListCaseFieldsService = new ValidPageListCaseFieldsService(fieldsUtils);

Expand Down Expand Up @@ -289,6 +290,7 @@ describe('CaseEditComponent', () => {
mockSessionStorageService = createSpyObj<SessionStorageService>('SessionStorageService', ['getItem', 'removeItem', 'setItem']);
mockWorkAllocationService = createSpyObj<WorkAllocationService>('WorkAllocationService', ['assignAndCompleteTask', 'completeTask']);
mockAlertService = createSpyObj<AlertService>('AlertService', ['error', 'setPreserveAlerts']);
mockCookieService = createSpyObj<ReadCookieService>('ReadCookieService', ['getCookie']);
mockabstractConfig = createSpyObj<AbstractAppConfig>('AbstractAppConfig', ['logMessage']);
spyOn(validPageListCaseFieldsService, 'deleteNonValidatedFields');
spyOn(validPageListCaseFieldsService, 'validPageListCaseFields');
Expand Down Expand Up @@ -342,6 +344,7 @@ describe('CaseEditComponent', () => {
{ provide: WorkAllocationService, useValue: mockWorkAllocationService},
{ provide: SessionStorageService, useValue: mockSessionStorageService},
{ provide: AlertService, useValue: mockAlertService },
{ provide: ReadCookieService, useValue: mockCookieService },
WindowService,
{ provide: LoadingService, loadingServiceMock },
{ provide: ValidPageListCaseFieldsService, useValue: validPageListCaseFieldsService},
Expand Down Expand Up @@ -1242,8 +1245,8 @@ describe('CaseEditComponent', () => {
expect(validPageListCaseFieldsService.validPageListCaseFields).toHaveBeenCalled();
expect(formValueService.removeUnnecessaryFields).toHaveBeenCalled();
// check that tasks removed from session storage once event has been completed
expect(mockSessionStorageService.removeItem).toHaveBeenCalledWith('clientContext');
expect(mockSessionStorageService.removeItem).toHaveBeenCalledWith('taskEventCompletionInfo');
expect(mockSessionStorageService.removeItem).toHaveBeenCalledWith(CaseEditComponent.CLIENT_CONTEXT);
expect(mockSessionStorageService.removeItem).toHaveBeenCalledWith(CaseEditComponent.TASK_EVENT_COMPLETION_INFO);
});

it('should submit the case for a Case Flags submission', () => {
Expand Down Expand Up @@ -1526,7 +1529,7 @@ describe('CaseEditComponent', () => {
});

it('should return true when there is a task present that matches the current case when there is no event in session storage', () => {
const mockTask = {id: '123', case_id: '123456789'};
const mockTask = {id: '123', case_id: '123456789', description: 'test test test [testEvent]'};
expect(component.taskExistsForThisEvent(mockTask as Task, null, mockEventDetails)).toBe(true);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ import { EventDetails, Task, TaskEventCompletionInfo } from '../../../domain/wor
import {
AlertService,
FieldsPurger, FieldsUtils, FormErrorService, FormValueService, LoadingService,
ReadCookieService,
SessionStorageService, WindowService
} from '../../../services';
import { Confirmation, Wizard, WizardPage } from '../domain';
import { EventCompletionParams } from '../domain/event-completion-params.model';
import { CaseNotifier, WizardFactoryService, WorkAllocationService } from '../services';
import { ValidPageListCaseFieldsService } from '../services/valid-page-list-caseFields.service';
import { removeTaskFromClientContext } from '../case-edit-utils/case-edit.utils';

@Component({
selector: 'ccd-case-edit',
Expand All @@ -35,6 +37,8 @@ import { ValidPageListCaseFieldsService } from '../services/valid-page-list-case
export class CaseEditComponent implements OnInit, OnDestroy {
public static readonly ORIGIN_QUERY_PARAM = 'origin';
public static readonly ALERT_MESSAGE = 'Page is being refreshed so you will be redirected to the first page of this event.';
public static readonly CLIENT_CONTEXT = 'clientContext';
public static readonly TASK_EVENT_COMPLETION_INFO = 'taskEventCompletionInfo';

@Input()
public eventTrigger: CaseEventTrigger;
Expand Down Expand Up @@ -106,7 +110,8 @@ export class CaseEditComponent implements OnInit, OnDestroy {
private readonly validPageListCaseFieldsService: ValidPageListCaseFieldsService,
private readonly workAllocationService: WorkAllocationService,
private readonly alertService: AlertService,
private readonly abstractConfig: AbstractAppConfig
private readonly abstractConfig: AbstractAppConfig,
private readonly cookieService: ReadCookieService
) {}

public ngOnInit(): void {
Expand Down Expand Up @@ -238,12 +243,12 @@ export class CaseEditComponent implements OnInit, OnDestroy {
this.isSubmitting = true;
// We have to run the event completion checks if task in session storage
// and if the task is in session storage, then is it associated to the case
const clientContextStr = this.sessionStorageService.getItem('clientContext');
const clientContextStr = this.sessionStorageService.getItem(CaseEditComponent.CLIENT_CONTEXT);
const userTask = FieldsUtils.getUserTaskFromClientContext(clientContextStr);
const taskInSessionStorage = userTask ? userTask.task_data : null;
let taskEventCompletionInfo: TaskEventCompletionInfo;
let userInfo: UserInfo;
const taskEventCompletionStr = this.sessionStorageService.getItem('taskEventCompletionInfo');
const taskEventCompletionStr = this.sessionStorageService.getItem(CaseEditComponent.TASK_EVENT_COMPLETION_INFO);
const userInfoStr = this.sessionStorageService.getItem('userDetails');
const assignNeeded = this.sessionStorageService.getItem('assignNeeded');
if (taskEventCompletionStr) {
Expand Down Expand Up @@ -272,7 +277,7 @@ export class CaseEditComponent implements OnInit, OnDestroy {
userId,
taskId: taskInSessionStorage.id,
createdTimestamp: Date.now()};
this.sessionStorageService.setItem('taskEventCompletionInfo', JSON.stringify(taskEventCompletionInfo));
this.sessionStorageService.setItem(CaseEditComponent.TASK_EVENT_COMPLETION_INFO, JSON.stringify(taskEventCompletionInfo));
this.isEventCompletionChecksRequired = true;
} else {
// Task not in session storage, proceed to submit
Expand Down Expand Up @@ -460,8 +465,9 @@ export class CaseEditComponent implements OnInit, OnDestroy {
}),finalize(() => {
this.loadingService.unregister(loadingSpinnerToken);
// on event completion ensure the previous event clientContext/taskEventCompletionInfo removed
this.sessionStorageService.removeItem('clientContext');
this.sessionStorageService.removeItem('taskEventCompletionInfo')
// Note - Not removeTaskFromClientContext because could interfere with other logic
this.sessionStorageService.removeItem(CaseEditComponent.CLIENT_CONTEXT);
this.sessionStorageService.removeItem(CaseEditComponent.TASK_EVENT_COMPLETION_INFO)
this.isSubmitting = false;
}))
.subscribe(
Expand Down Expand Up @@ -492,7 +498,7 @@ export class CaseEditComponent implements OnInit, OnDestroy {
}

private postCompleteTaskIfRequired(): Observable<any> {
const clientContextStr = this.sessionStorageService.getItem('clientContext');
const clientContextStr = this.sessionStorageService.getItem(CaseEditComponent.CLIENT_CONTEXT);
const userTask = FieldsUtils.getUserTaskFromClientContext(clientContextStr);
const [task, taskToBeCompleted] = userTask ? [userTask.task_data, userTask.complete_task] : [null, false];
const assignNeeded = this.sessionStorageService.getItem('assignNeeded') === 'true';
Expand Down Expand Up @@ -537,7 +543,14 @@ export class CaseEditComponent implements OnInit, OnDestroy {
}
if (!taskEventCompletionInfo) {
// if no task event present then there is no task to complete from previous event present
return true;
// EXUI-2668 - Add additional logic to confirm the task is relevant to the event
if (this.taskIsForEvent(taskInSessionStorage, eventDetails)) {
return true;
} else {
// client context still needed for language
removeTaskFromClientContext(this.sessionStorageService);
return false;
}
} else {
if (taskEventCompletionInfo.taskId !== taskInSessionStorage.id) {
return true;
Expand All @@ -546,8 +559,8 @@ export class CaseEditComponent implements OnInit, OnDestroy {
|| this.eventMoreThanDayAgo(taskEventCompletionInfo.createdTimestamp)
) {
// if the session storage not related to event, ignore it and remove
this.sessionStorageService.removeItem('clientContext');
this.sessionStorageService.removeItem('taskEventCompletionInfo');
removeTaskFromClientContext(this.sessionStorageService);
this.sessionStorageService.removeItem(CaseEditComponent.TASK_EVENT_COMPLETION_INFO);
return false;
}
if (eventDetails.assignNeeded === 'false' && eventDetails.userId !== taskInSessionStorage.assignee) {
Expand Down Expand Up @@ -593,4 +606,12 @@ export class CaseEditComponent implements OnInit, OnDestroy {
}
return false;
}

private taskIsForEvent(task: Task, eventDetails: EventDetails): boolean {
// EXUI-2668 - Ensure description for task includes event ID
// Note - This is a failsafe for an edge case that may never occur again
// Description may not include eventId in some cases which may mean task not completed (however this will be easy to check)
this.abstractConfig.logMessage(`checking taskIsForEvent: task ID ${task.id}, task description ${task.description}, event name ${eventDetails.eventId}`);
return task.case_id === eventDetails.caseId && task.description?.includes(eventDetails.eventId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Component, Input, ViewChild } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { MockRpxTranslatePipe } from '../../../../../test/mock-rpx-translate.pipe';
import { CaseEditComponent } from '../../../case-edit';
import { EventCompletionStateMachineContext } from '../../../domain';
import { CaseEventCompletionTaskCancelledComponent } from './case-event-completion-task-cancelled.component';

Expand Down Expand Up @@ -46,7 +47,7 @@ describe('TaskCancelledComponent', () => {
it('should emit event can be completed true when clicked on continue button', () => {
spyOn(component.notifyEventCompletionCancelled, 'emit');
component.onContinue();
expect(component.context.sessionStorageService.removeItem).toHaveBeenCalledWith('clientContext')
expect(component.context.sessionStorageService.removeItem).toHaveBeenCalledWith(CaseEditComponent.CLIENT_CONTEXT);
expect(component.notifyEventCompletionCancelled.emit).toHaveBeenCalledWith(true);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core';
import { COMPONENT_PORTAL_INJECTION_TOKEN, CaseEventCompletionComponent } from '../../case-event-completion.component';
import { EventCompletionStateMachineContext } from '../../../domain';
import { CaseEditComponent } from '../../../case-edit';

@Component({
selector: 'app-case-event-completion-task-cancelled',
Expand All @@ -20,7 +20,7 @@ export class CaseEventCompletionTaskCancelledComponent implements OnInit {

public onContinue(): void {
// Removes task to complete so event completes without task
this.context.sessionStorageService.removeItem('clientContext');
this.context.sessionStorageService.removeItem(CaseEditComponent.CLIENT_CONTEXT);
// may be able to remove this call below since it is now unneccesary
this.notifyEventCompletionCancelled.emit(true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { EventCompletionStateMachineContext } from '../../../domain';
import { CaseworkerService } from '../../../services/case-worker.service';
import { JudicialworkerService } from '../../../services/judicial-worker.service';
import { CaseEditComponent } from '../../../case-edit';

@Component({
selector: 'app-case-event-completion-task-reassigned',
Expand Down Expand Up @@ -77,7 +78,7 @@ export class CaseEventCompletionTaskReassignedComponent implements OnInit, OnDes

public onContinue(): void {
// Get task details
const clientContextStr = this.sessionStorageService.getItem('clientContext');
const clientContextStr = this.sessionStorageService.getItem(CaseEditComponent.CLIENT_CONTEXT);
const userTask = FieldsUtils.getUserTaskFromClientContext(clientContextStr);
const task = userTask ? userTask.task_data : null;
// not complete_task not utilised here as related to event completion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { CaseAccessUtils } from '../case-access-utils';
import { WizardPage } from '../domain';
import { WizardPageFieldToCaseFieldMapper } from './wizard-page-field-to-case-field.mapper';
import { CaseEditUtils } from '../case-edit-utils/case-edit.utils';
import { CaseEditComponent } from '../case-edit';

@Injectable()
export class CasesService {
Expand Down Expand Up @@ -395,7 +396,7 @@ export class CasesService {
}

private addClientContextHeader(headers: HttpHeaders): HttpHeaders {
const clientContextDetails = this.sessionStorageService.getItem('clientContext');
const clientContextDetails = this.sessionStorageService.getItem(CaseEditComponent.CLIENT_CONTEXT);
if (clientContextDetails) {
const caseEditUtils = new CaseEditUtils();
// below changes non-ASCII characters
Expand All @@ -414,7 +415,7 @@ export class CasesService {
const clientContextString = window.atob(headers.get('Client-Context'));
// below reverts non-ASCII characters
const editedClientContextString = caseEditUtils.convertHTMLEntities(clientContextString);
this.sessionStorageService.setItem('clientContext', editedClientContextString);
this.sessionStorageService.setItem(CaseEditComponent.CLIENT_CONTEXT, editedClientContextString);
}
}

Expand Down
Loading
Loading