Skip to content

Commit

Permalink
Merge pull request #2336 from IDEMSInternational/feat/task-group-comp…
Browse files Browse the repository at this point in the history
…letion-action

Feat/task group completion action
  • Loading branch information
chrismclarke authored Aug 28, 2024
2 parents 4baab6f + 232467a commit 29ab322
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 35 deletions.
1 change: 1 addition & 0 deletions packages/data-models/flowTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ export namespace FlowTypes {
"share",
"style",
"start_tour",
"task",
"task_group_set_highlighted",
"toggle_field",
"track_event",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,7 @@ export class TmplTaskCardComponent extends TemplateBaseComponent implements OnIn

ngOnInit() {
this.getParams();
this.highlighted =
this.taskGroupId && !this.taskId
? this.taskService.checkHighlightedTaskGroup(this.taskGroupId)
: false;
this.highlighted = this.checkGroupHighlighted();
this.checkProgressStatus();
}

Expand Down Expand Up @@ -161,4 +158,11 @@ export class TmplTaskCardComponent extends TemplateBaseComponent implements OnIn
this.triggerActions("completed");
}
}

private checkGroupHighlighted() {
if (this.taskGroupId && !this.taskId) {
return this.taskService.checkHighlightedTaskGroup(this.taskGroupId);
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { DBSyncService } from "src/app/shared/services/db/db-sync.service";
import { AuthService } from "src/app/shared/services/auth/auth.service";
import { SkinService } from "src/app/shared/services/skin/skin.service";
import { ThemeService } from "src/app/feature/theme/services/theme.service";
import { TaskService } from "src/app/shared/services/task/task.service";
import { getGlobalService } from "src/app/shared/services/global.service";
import { SyncServiceBase } from "src/app/shared/services/syncService.base";
import { TemplateActionRegistry } from "./template-action.registry";
Expand All @@ -35,7 +34,10 @@ export class TemplateActionService extends SyncServiceBase {
private actionsQueue: FlowTypes.TemplateRowAction[] = [];
private actionsQueueProcessing$ = new BehaviorSubject<boolean>(false);

constructor(private injector: Injector, public container?: TemplateContainerComponent) {
constructor(
private injector: Injector,
public container?: TemplateContainerComponent
) {
super("TemplateAction");
}
// Retrive all services on demand from global injector
Expand Down Expand Up @@ -72,9 +74,7 @@ export class TemplateActionService extends SyncServiceBase {
private get themeService() {
return getGlobalService(this.injector, ThemeService);
}
private get taskService() {
return getGlobalService(this.injector, TaskService);
}

private get campaignService() {
return getGlobalService(this.injector, CampaignService);
}
Expand All @@ -84,11 +84,7 @@ export class TemplateActionService extends SyncServiceBase {
}

private async ensurePublicServicesReady() {
await this.ensureAsyncServicesReady([
this.templateTranslateService,
this.dbSyncService,
this.taskService,
]);
await this.ensureAsyncServicesReady([this.templateTranslateService, this.dbSyncService]);
this.ensureSyncServicesReady([
this.serverService,
this.templateNavService,
Expand Down
4 changes: 2 additions & 2 deletions src/app/shared/services/dynamic-data/dynamic-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ export class DynamicDataService extends AsyncServiceBase {
}

/** Take a snapshot of the current state of a table */
public async snapshot(flow_type: FlowTypes.FlowType, flow_name: string) {
const obs = await this.query$(flow_type, flow_name);
public async snapshot<T extends IDocWithMeta>(flow_type: FlowTypes.FlowType, flow_name: string) {
const obs = await this.query$<T>(flow_type, flow_name);
return firstValueFrom(obs);
}

Expand Down
87 changes: 76 additions & 11 deletions src/app/shared/services/task/task.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ let mockTemplateFieldService: MockTemplateFieldService;
describe("TaskService", () => {
let service: TaskService;
let scheduleCampaignNotificationsSpy: jasmine.Spy<jasmine.Func>;
let fetchTaskRowSpy: jasmine.Spy<jasmine.Func>;
let fetchTaskRowsSpy: jasmine.Spy<jasmine.Func>;

beforeEach(async () => {
scheduleCampaignNotificationsSpy = jasmine.createSpy();
Expand Down Expand Up @@ -107,6 +109,30 @@ describe("TaskService", () => {
],
});
service = TestBed.inject(TaskService);

fetchTaskRowSpy = spyOn<TaskService, any>(service, "fetchTaskRow").and.callFake(
(dataListName, rowId) => {
if (rowId === "validRowId") {
return Promise.resolve({
completed: false,
task_child: "childDataList",
completed_field: "completed_field",
});
}
return Promise.resolve(null);
}
);

fetchTaskRowsSpy = spyOn<TaskService, any>(service, "fetchTaskRows").and.callFake(
(dataListName) => {
if (dataListName === "childDataList") {
return Promise.resolve([{ completed: true }, { completed: true }]);
}
return Promise.resolve([]);
}
);

spyOn<TaskService, any>(service, "setTaskCompletion").and.resolveTo(true);
});

it("should be created", () => {
Expand Down Expand Up @@ -134,10 +160,10 @@ describe("TaskService", () => {
});
it("evaluates highlighted task group correctly after init", async () => {
await service.ready();
expect(service.evaluateHighlightedTaskGroup().previousHighlightedTaskGroup).toBe(
expect(service["evaluateHighlightedTaskGroup"]().previousHighlightedTaskGroup).toBe(
MOCK_DATA.data_list[taskGroupsListName].rows[0].id
);
expect(service.evaluateHighlightedTaskGroup().newHighlightedTaskGroup).toBe(
expect(service["evaluateHighlightedTaskGroup"]().newHighlightedTaskGroup).toBe(
MOCK_DATA.data_list[taskGroupsListName].rows[0].id
);
});
Expand All @@ -152,7 +178,7 @@ describe("TaskService", () => {
});
it("can set a task group's completed status", async () => {
await service.ready();
await service.setTaskGroupCompletedStatus(
await service["setTaskGroupCompletedField"](
MOCK_DATA.data_list[taskGroupsListName].rows[0].completed_field,
true
);
Expand All @@ -165,43 +191,82 @@ describe("TaskService", () => {
it("completing the highlighted task causes the next highest priority task to be highlighted upon re-evaluation", async () => {
await service.ready();
// Complete highlighted task
await service.setTaskGroupCompletedStatus(
await service["setTaskGroupCompletedField"](
MOCK_DATA.data_list[taskGroupsListName].rows[0].completed_field,
true
);
const { previousHighlightedTaskGroup, newHighlightedTaskGroup } =
service.evaluateHighlightedTaskGroup();
service["evaluateHighlightedTaskGroup"]();
expect(previousHighlightedTaskGroup).toBe(MOCK_DATA.data_list[taskGroupsListName].rows[0].id);
expect(newHighlightedTaskGroup).toBe(MOCK_DATA.data_list[taskGroupsListName].rows[2].id);
});
it("when all tasks are completed, the highlighted task group is set to ''", async () => {
await service.ready();
// Complete all tasks
await service.setTaskGroupCompletedStatus(
await service["setTaskGroupCompletedField"](
MOCK_DATA.data_list[taskGroupsListName].rows[0].completed_field,
true
);
await service.setTaskGroupCompletedStatus(
await service["setTaskGroupCompletedField"](
MOCK_DATA.data_list[taskGroupsListName].rows[1].completed_field,
true
);
await service.setTaskGroupCompletedStatus(
await service["setTaskGroupCompletedField"](
MOCK_DATA.data_list[taskGroupsListName].rows[2].completed_field,
true
);
expect(service.evaluateHighlightedTaskGroup().newHighlightedTaskGroup).toBe("");
expect(service["evaluateHighlightedTaskGroup"]().newHighlightedTaskGroup).toBe("");
});
it("schedules campaign notifications on change of highlighted task", async () => {
await service.ready();
// Complete highlighted task
await service.setTaskGroupCompletedStatus(
await service["setTaskGroupCompletedField"](
MOCK_DATA.data_list[taskGroupsListName].rows[0].completed_field,
true
);
service.evaluateHighlightedTaskGroup();
service["evaluateHighlightedTaskGroup"]();
await _wait(50);
// scheduleCampaignNotifications() should be called once on init (since the highlighted task group changes),
// and again on the evaluation called above
expect(scheduleCampaignNotificationsSpy).toHaveBeenCalledTimes(2);
});

it("evaluate task completion: should return null if taskRow is not found", async () => {
const result = await service["evaluateTaskCompletion"]("dataList", "invalidRowId");
expect(result).toBeNull();
});
it("evaluate task completion: should set parent task completion to true if all child tasks are completed", async () => {
const result = await service["evaluateTaskCompletion"]("dataList", "validRowId");
expect(service["setTaskCompletion"]).toHaveBeenCalledWith(
"dataList",
"validRowId",
true,
"completed_field"
);
expect(result).toBeTrue();
});
it("evaluate task completion: should set parent task completion to false if not all child tasks are completed", async () => {
fetchTaskRowsSpy.and.resolveTo([
{ id: "a", completed: true },
{ id: "b", completed: false },
]);
const result = await service["evaluateTaskCompletion"]("dataList", "validRowId");
expect(service["setTaskCompletion"]).toHaveBeenCalledWith(
"dataList",
"validRowId",
false,
"completed_field"
);
expect(result).toBeFalse();
});
it("evaluate task completion: should log a warning if task row does not have a 'task_child' property", async () => {
spyOn(console, "warn");
fetchTaskRowSpy.and.resolveTo({
completed: false,
});
await service["evaluateTaskCompletion"]("dataList", "validRowId");
expect(console.warn).toHaveBeenCalledWith(
'[TASK] evaluate - row "validRowId" in "dataList" has no child tasks to evaluate'
);
});
});
Loading

0 comments on commit 29ab322

Please sign in to comment.