Skip to content

Commit

Permalink
Merge branch 'master' into feat/display-grid-data-items
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismclarke authored Dec 19, 2024
2 parents f40899e + 3db1e52 commit 48f12c9
Showing 8 changed files with 87 additions and 9 deletions.
8 changes: 8 additions & 0 deletions packages/shared/src/utils/object-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import {
sortJsonKeys,
toEmptyObject,
arrayToHashmap,
filterObjectByKeys,
} from "./object-utils";

const MOCK_NESTED_OBJECT = {
@@ -22,6 +23,9 @@ const MOCK_NESTED_OBJECT = {
number: 1,
};

/**
* yarn workspace shared test --filter "Object Utils"
*/
describe("Object Utils", () => {
it("isObjectLiteral", () => {
expect(isObjectLiteral({})).toEqual(true);
@@ -144,4 +148,8 @@ describe("Object Utils", () => {
id_2_duplicate: { id: "id_2", number: 2.1 },
});
});
it("filterObjectByKeys", () => {
const res = filterObjectByKeys({ keep: 1, ignore: 2 }, ["keep"]);
expect(res).toEqual({ keep: 1 });
});
});
9 changes: 9 additions & 0 deletions packages/shared/src/utils/object-utils.ts
Original file line number Diff line number Diff line change
@@ -127,3 +127,12 @@ export function arrayToHashmap<T extends object>(
}
return hashmap;
}

/** Take an object and return a subset of keys matching list of provided keys */
export function filterObjectByKeys<T extends Record<string, any>, K extends keyof T>(
obj: T,
includeKeys: K[]
) {
const filteredEntries = Object.entries(obj).filter(([key]) => includeKeys.includes(key as K));
return Object.fromEntries(filteredEntries) as Partial<Pick<T, K>>;
}
Original file line number Diff line number Diff line change
@@ -62,7 +62,9 @@ <h1>
<plh-task-progress-bar
[dataListName]="taskGroupDataList"
[completedField]="completedField"
[completedColumnName]="completedColumnName"
[highlighted]="highlighted"
[parameterList]="_row.parameter_list"
[progressUnitsName]="progressUnitsName"
[showText]="showProgressText"
[(progressStatus)]="progressStatus"
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@
border: 1px solid rgba(black, 0.07);
border-radius: var(--ion-border-radius-secondary);
padding: var(--regular-padding);
padding-right: var(--tiny-padding);
max-width: 400px;
filter: drop-shadow(var(--ion-default-box-shadow));

@@ -34,6 +33,7 @@
}
.image-wrapper {
width: 85%;
padding-left: unset;
}
}
}
Original file line number Diff line number Diff line change
@@ -30,6 +30,12 @@ interface ITaskCardParams {
* as their completion is calculated by the completion of their constituent tasks
*/
completed_field: string;
/**
* TEMPLATE PARAMETER: completed_column_name.
* The name of the column in the referenced task_group_data data list that tracks the completed value of each subtask.
* Deafult "completed"
* */
completed_column_name: string;
/** The title to display on the task card */
title: string;
/**
@@ -97,6 +103,7 @@ export class TmplTaskCardComponent extends TemplateBaseComponent implements OnIn
taskGroupId: string | null;
taskGroupDataList: string | null;
completedField: string | null;
completedColumnName: string | null;
taskId: string | null;
title: string | null;
subtitle: string | null;
@@ -123,6 +130,11 @@ export class TmplTaskCardComponent extends TemplateBaseComponent implements OnIn
this.taskGroupId = getStringParamFromTemplateRow(this._row, "task_group_id", null);
this.taskGroupDataList = getStringParamFromTemplateRow(this._row, "task_group_data", null);
this.completedField = getStringParamFromTemplateRow(this._row, "completed_field", null);
this.completedColumnName = getStringParamFromTemplateRow(
this._row,
"completed_column_name",
"completed"
);
this.taskId = getStringParamFromTemplateRow(this._row, "task_id", null);
this.title = getStringParamFromTemplateRow(this._row, "title", null);
this.subtitle = getStringParamFromTemplateRow(this._row, "subtitle", null);
Original file line number Diff line number Diff line change
@@ -2,11 +2,13 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
computed,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
signal,
} from "@angular/core";
import { TaskService } from "src/app/shared/services/task/task.service";
import {
@@ -17,6 +19,9 @@ import { TemplateBaseComponent } from "../base";
import { IProgressStatus } from "src/app/shared/services/task/task.service";
import { Subscription, debounceTime } from "rxjs";
import { DynamicDataService } from "src/app/shared/services/dynamic-data/dynamic-data.service";
import { ITEM_PIPE_OPERATOR_NAMES } from "../../processors/itemPipe";
import { ItemProcessor } from "../../processors/item";
import { filterObjectByKeys } from "packages/shared/src/utils/object-utils";

interface ITaskProgressBarParams {
/**
@@ -76,19 +81,28 @@ export class TmplTaskProgressBarComponent
{
@Input() dataListName: string | null;
@Input() completedField: string | null;
@Input() completedColumnName: string;
@Input() highlighted: boolean | null;
@Input() progressStatus: IProgressStatus;
@Input() progressUnitsName: string;
@Input() showText: boolean;
// Pass whole parameter list from parent component to extract any item row operations
@Input() parameterList: any;
@Output() progressStatusChange = new EventEmitter<IProgressStatus>();
@Output() newlyCompleted = new EventEmitter<boolean>();
params: Partial<ITaskProgressBarParams> = {};
dataRows: any[];
dataRows = signal<any[]>([]);
processedDataRows = computed(() => {
const processedDataRows = this.processDataRows(this.dataRows());
return processedDataRows;
});
subtasksTotal: number;
subtasksCompleted: number;
standalone: boolean = false;
useDynamicData: boolean;
private dataQuery$: Subscription;
itemRowOperations: Partial<Pick<{ [param: string]: string }, string>>;
itemProcessor: ItemProcessor;

/** Progress wheel variables */
radius = 16; // Radius of the circle
@@ -139,6 +153,7 @@ export class TmplTaskProgressBarComponent
"completed_field_column_name",
"completed_field"
);
this.configureItemProcessor(this._row.parameter_list);
this.params.variant = getStringParamFromTemplateRow(this._row, "variant", "bar")
.split(",")
.join(" ") as ITaskProgressBarParams["variant"];
@@ -150,9 +165,10 @@ export class TmplTaskProgressBarComponent
this.params.completedField = this.completedField;
this.params.progressUnitsName = this.progressUnitsName;
this.params.showText = this.showText;
this.params.completedColumnName = "completed";
this.params.completedColumnName = this.completedColumnName || "completed";
this.params.completedFieldColumnName = "completed_field";
this.params.variant = "bar";
this.configureItemProcessor(this.parameterList);
}
}

@@ -166,19 +182,46 @@ export class TmplTaskProgressBarComponent
return this.circumference * (1 - (progressProportion || 0));
}

// Apply any item row operations, e.g. filter, if supplied to component via parameter list
private processDataRows(dataRows: any[]) {
if (this.itemProcessor) {
return this.itemProcessor.pipeData(dataRows, this.itemRowOperations);
}
return dataRows;
}

private configureItemProcessor(parameterList: any) {
const rawItemRowOperations = filterObjectByKeys(parameterList, ITEM_PIPE_OPERATOR_NAMES as any);
if (Object.keys(rawItemRowOperations).length > 0) {
this.itemRowOperations = this.hackParseItemRowOperationParams(rawItemRowOperations);
this.itemProcessor = new ItemProcessor();
}
}

// HACK: use `@task_item` reference in item row operations to prevent evaluation up to this point.
// Replace with `this.item` before passing to item processor for evaluation
private hackParseItemRowOperationParams(itemRowOperations: any) {
for (const [name, arg] of Object.entries(itemRowOperations)) {
if (arg && typeof arg === "string") {
itemRowOperations[name] = arg.replaceAll("@task_item", "this.item");
}
}
return itemRowOperations;
}

private async getTaskGroupDataRows() {
await this.taskService.ready();
this.dataRows = await this.taskService.getTaskGroupDataRows(this.params.dataListName);
this.dataRows.set(await this.taskService.getTaskGroupDataRows(this.params.dataListName));
}

private checkAndSetUseDynamicData() {
this.useDynamicData = this.dataRows?.[0]?.hasOwnProperty(this.params.completedColumnName);
this.useDynamicData = this.dataRows()?.[0]?.hasOwnProperty(this.params.completedColumnName);
}

private async evaluateTaskGroupData() {
const previousProgressStatus = this.progressStatus;
const { subtasksTotal, subtasksCompleted, progressStatus, newlyCompleted } =
await this.taskService.evaluateTaskGroupData(this.dataRows, {
await this.taskService.evaluateTaskGroupData(this.processedDataRows(), {
completedColumnName: this.params.completedColumnName,
completedField: this.params.completedField,
completedFieldColumnName: this.params.completedFieldColumnName,
@@ -205,7 +248,7 @@ export class TmplTaskProgressBarComponent
await this.dynamicDataService.ready();
const query = await this.dynamicDataService.query$("data_list", this.params.dataListName);
this.dataQuery$ = query.pipe(debounceTime(50)).subscribe(async (data) => {
this.dataRows = data;
this.dataRows.set(data);
await this.evaluateTaskGroupData();
});
}
2 changes: 1 addition & 1 deletion src/app/shared/components/template/processors/item.ts
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ export class ItemProcessor {
}

/** Process all item list operators, such as filter, sort and limit */
private pipeData(data: any[], parameter_list: any) {
public pipeData(data: any[], parameter_list: any) {
if (parameter_list) {
const operations = Object.entries<any>(parameter_list).map(([name, arg]) => ({
name,
6 changes: 5 additions & 1 deletion src/app/shared/components/template/processors/itemPipe.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,10 @@ import type { FlowTypes } from "packages/data-models/flowTypes";
import { JSEvaluator } from "packages/shared/src/models/jsEvaluator/jsEvaluator";
import { shuffleArray } from "src/app/shared/utils";

/** List of operations should by item pipe (exported to allow extraction from parameter_list by task-progress-bar */
export const ITEM_PIPE_OPERATOR_NAMES = ["shuffle", "sort", "filter", "reverse", "limit"] as const;
type IOperationName = (typeof ITEM_PIPE_OPERATOR_NAMES)[number];

export class ItemDataPipe {
public process(data: any[], operations: { name: string; arg?: string }[]) {
for (const { name, arg } of operations) {
@@ -15,7 +19,7 @@ export class ItemDataPipe {
return data;
}

private operations = {
private operations: Record<IOperationName, (items: any[], arg?: string) => any> = {
shuffle: (items: any[] = []) => {
return shuffleArray(items);
},

0 comments on commit 48f12c9

Please sign in to comment.