Skip to content

Commit 33e35dc

Browse files
authored
Merge pull request #493 from UN-OCHA/env/stage
🚀 Release `v4.11.3` and deploy to production
2 parents 73171a0 + 0b52e48 commit 33e35dc

9 files changed

Lines changed: 233 additions & 266 deletions

File tree

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hpc-api",
3-
"version": "4.11.2-hotfix-1",
3+
"version": "4.11.3",
44
"description": "api for HPC applications",
55
"main": "src/server.ts",
66
"license": "MIT",
@@ -36,15 +36,15 @@
3636
"@types/bunyan": "^1.8.11",
3737
"@types/hapi__hapi": "^20.0.9",
3838
"@types/jest": "^30.0.0",
39-
"@types/node": "^24.6.2",
40-
"@types/pg": "^8.15.5",
39+
"@types/node": "^24.10.0",
40+
"@types/pg": "^8.15.6",
4141
"@unocha/hpc-repo-tools": "^8.0.0",
42-
"eslint": "9.36.0",
42+
"eslint": "9.39.1",
4343
"husky": "^9.1.7",
4444
"jest": "^30.2.0",
45-
"lint-staged": "^16.2.3",
45+
"lint-staged": "^16.2.6",
4646
"prettier": "3.6.2",
47-
"ts-jest": "^29.4.4",
47+
"ts-jest": "^29.4.5",
4848
"ts-node-dev": "^2.0.0"
4949
},
5050
"engines": {

src/domain-services/flow-object/flow-object-service.ts

Lines changed: 25 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@ import type {
99
FieldsOfModel,
1010
InstanceOfModel,
1111
} from '@unocha/hpc-api-core/src/db/util/types';
12-
import { createBrandedValue } from '@unocha/hpc-api-core/src/util/types';
12+
import { groupObjectsByProperty } from '@unocha/hpc-api-core/src/util';
1313
import { Service } from 'typedi';
1414
import { type UniqueFlowEntity } from '../flows/model';
15-
import { type FlowObjectFilterGrouped } from './model';
16-
import { buildWhereConditionsForFlowObjectFilters } from './utils';
1715

1816
// Local types definition to increase readability
1917
type FlowObjectModel = Database['flowObject'];
@@ -23,36 +21,36 @@ export type FlowObjectOrderByCond = OrderByCond<FlowObjectsFieldsDefinition>;
2321
export type FlowObjectWhere = Condition<FlowObjectInstance>;
2422
@Service()
2523
export class FlowObjectService {
26-
// Merge with getFlowsObjectsByFlows
27-
async getFlowIdsFromFlowObjects(
28-
models: Database,
29-
where: FlowObjectWhere
30-
): Promise<FlowId[]> {
31-
const flowObjects = await models.flowObject.find({
32-
where,
33-
});
34-
// Keep only not duplicated flowIDs
35-
return [...new Set(flowObjects.map((flowObject) => flowObject.flowID))];
36-
}
37-
24+
/**
25+
* Get flows given flowObjects `OR` conditions and the number of conditions.
26+
* This will return only flows that match all the conditions.
27+
*/
3828
async getFlowFromFlowObjects(
3929
models: Database,
40-
where: FlowObjectWhere
30+
where: FlowObjectWhere,
31+
numberOfConditions: number
4132
): Promise<UniqueFlowEntity[]> {
4233
const flowObjects = await models.flowObject.find({
4334
where,
4435
});
45-
// Keep only not duplicated flowIDs
46-
return [
47-
...new Set(
48-
flowObjects.map((flowObject) => {
49-
return {
50-
id: createBrandedValue(flowObject.flowID),
51-
versionID: flowObject.versionID,
52-
};
53-
})
54-
),
55-
];
36+
const uniqueFlows: UniqueFlowEntity[] = [];
37+
38+
// Group by flowID
39+
const flowIDGroups = groupObjectsByProperty(flowObjects, 'flowID');
40+
41+
for (const [flowID, flowGroup] of flowIDGroups.entries()) {
42+
// Group each flowGroup by versionID
43+
const versionIDGroups = groupObjectsByProperty(flowGroup, 'versionID');
44+
45+
for (const [versionID, objs] of versionIDGroups.entries()) {
46+
if (objs.length === numberOfConditions) {
47+
// Only add the flowID+versionID if all conditions are met
48+
uniqueFlows.push({ id: flowID, versionID });
49+
}
50+
}
51+
}
52+
53+
return uniqueFlows;
5654
}
5755

5856
async getFlowObjectByFlowId(
@@ -72,39 +70,4 @@ export class FlowObjectService {
7270
},
7371
});
7472
}
75-
76-
async getFlowObjectsByFlowObjectConditions(
77-
models: Database,
78-
flowObjectFilterGrouped: FlowObjectFilterGrouped
79-
): Promise<FlowObjectInstance[]> {
80-
const whereClause = buildWhereConditionsForFlowObjectFilters(
81-
flowObjectFilterGrouped
82-
);
83-
84-
return await models.flowObject.find({ where: whereClause });
85-
}
86-
87-
async getFlowsObjectsByFlows(
88-
models: Database,
89-
whereClauses: FlowObjectWhere,
90-
orderBy?: FlowObjectOrderByCond
91-
): Promise<FlowObjectInstance[]> {
92-
const distinctColumns: Array<keyof FlowObjectInstance> = [
93-
'flowID',
94-
'versionID',
95-
];
96-
97-
if (orderBy) {
98-
distinctColumns.push(orderBy.column);
99-
distinctColumns.reverse();
100-
}
101-
102-
const flowsObjects: FlowObjectInstance[] = await models.flowObject.find({
103-
orderBy,
104-
where: whereClauses,
105-
distinct: distinctColumns,
106-
});
107-
108-
return flowsObjects;
109-
}
11073
}

src/domain-services/flow-object/utils.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@ import { type FlowObjectWhere } from './flow-object-service';
33
import { type FlowObjectFilterGrouped } from './model';
44

55
/**
6-
* This alg iterates over the flowObjectFilters and creates a join for each flowObjectType
7-
* and refDirection allowing to filter the flowObjects by the flowObjectType and refDirection
8-
* inclusivelly for each
9-
* @param flowObjectFiltersGrouped
10-
* @returns FlowObjectWhere
6+
* Build where conditions for flow object filters as `OR` conditions.
7+
* (This is done because we cannot have `AND` conditions on different values of the same column
8+
* or use joiners)
119
*/
1210
export function buildWhereConditionsForFlowObjectFilters(
1311
flowObjectFiltersGrouped: FlowObjectFilterGrouped
@@ -16,18 +14,14 @@ export function buildWhereConditionsForFlowObjectFilters(
1614
for (const [flowObjectType, group] of flowObjectFiltersGrouped.entries()) {
1715
for (const [direction, ids] of group.entries()) {
1816
const condition = {
19-
[Cond.AND]: [
20-
{
21-
objectType: flowObjectType,
22-
refDirection: direction,
23-
objectID: { [Op.IN]: ids },
24-
},
25-
],
17+
objectType: flowObjectType,
18+
refDirection: direction,
19+
objectID: { [Op.IN]: ids },
2620
};
2721

2822
ANDConditions.push(condition);
2923
}
3024
}
3125

32-
return { [Cond.AND]: ANDConditions };
26+
return { [Cond.OR]: ANDConditions };
3327
}

src/domain-services/flows/flow-service.ts

Lines changed: 32 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -490,96 +490,43 @@ export class FlowService {
490490
return mappedParkedParentOrganizations;
491491
}
492492

493-
async getParkedParentFlowsByFlowObjectFilter(
493+
/**
494+
* (All parents are considered `parked`, if they are not parked, it means there is corruption in the data)
495+
*/
496+
async getParkedParentsChildrenByFlowObjectFilter(
494497
models: Database,
495498
flowObjectFilters: FlowObjectFilterGrouped
496499
): Promise<UniqueFlowEntity[]> {
497-
// 1. Retrieve the parked category
498-
const parkedCategory = await models.category.findOne({
499-
where: {
500-
name: 'Parked',
501-
group: 'flowType',
502-
},
503-
});
504-
if (!parkedCategory) {
505-
throw new Error('Parked category not found');
506-
}
500+
// 1. Create where conditions from flow object filters
501+
const flowObjectsWhere =
502+
buildWhereConditionsForFlowObjectFilters(flowObjectFilters);
507503

508-
// 2. Get all category references for parked flows
509-
const categoryRefs = await models.categoryRef.find({
510-
where: {
511-
categoryID: parkedCategory.id,
512-
objectType: 'flow',
513-
},
514-
distinct: ['objectID', 'versionID'],
515-
});
504+
// 2. Extract number of conditions from flow object filters
505+
const numberOfConditions = flowObjectFilters
506+
.values()
507+
.flatMap((m) => [...m.values()])
508+
.toArray()
509+
.flat().length;
516510

517-
// Build list of parent IDs from categoryRefs
518-
const parentIDs: FlowId[] = categoryRefs.map((ref) =>
519-
createBrandedValue(ref.objectID)
511+
// 3. Retrieve flow objects matching the conditions
512+
const flowObjects = await this.flowObjectService.getFlowFromFlowObjects(
513+
models,
514+
flowObjectsWhere,
515+
numberOfConditions
520516
);
521517

522-
// 3. Retrieve flow links where the parent is among those references and depth > 0
518+
// 4. Retrieve flow links where the parent is among those references and depth > 0
523519
const flowLinks = await models.flowLink.find({
524520
where: {
525521
depth: { [Op.GT]: 0 },
526-
parentID: { [Op.IN]: parentIDs },
522+
parentID: {
523+
[Op.IN]: flowObjects.map((fo) => createBrandedValue(fo.id)),
524+
},
527525
},
528526
distinct: ['parentID', 'childID'],
529527
});
530-
531-
// Create a reference list of parent flows from the flow links
532-
const parentFlowsRef: UniqueFlowEntity[] = flowLinks.map((flowLink) => ({
533-
id: flowLink.parentID,
534-
versionID: null,
535-
}));
536-
537-
// 4. Query parent flows progressively in chunks
538-
const parentFlows = await this.progresiveSearch(
539-
models,
540-
parentFlowsRef,
541-
1000,
542-
0,
543-
false, // Do not stop on batch size
544-
[],
545-
{ activeStatus: true }
546-
);
547-
548-
// 5. Retrieve flow objects using the flow object filters
549-
const flowObjectsWhere =
550-
buildWhereConditionsForFlowObjectFilters(flowObjectFilters);
551-
const flowObjects = await this.flowObjectService.getFlowFromFlowObjects(
552-
models,
553-
flowObjectsWhere
554-
);
555-
556-
// 6. Build a Set for flowObjects for fast lookup (using a composite key of id and versionID)
557-
const flowObjectsSet = new Set(
558-
flowObjects.map(
559-
(flowObject) => `${flowObject.id}|${flowObject.versionID}`
560-
)
561-
);
562-
563-
// 7. Filter parent flows that are present in the flowObjects list
564-
const filteredParentFlows = parentFlows.filter((parentFlow) => {
565-
const key = `${parentFlow.id}|${parentFlow.versionID}`;
566-
return flowObjectsSet.has(key);
567-
});
568-
569-
// 8. Build a Set of filtered parent flow IDs for quick membership checking
570-
const filteredParentFlowIds = new Set(
571-
filteredParentFlows.map((flow) => flow.id)
572-
);
573-
574-
// 9. Extract child flow IDs from flowLinks where the parent is in the filtered set
575-
const childFlowsIDsSet = new Set<FlowId>();
576-
for (const flowLink of flowLinks) {
577-
if (filteredParentFlowIds.has(flowLink.parentID)) {
578-
childFlowsIDsSet.add(flowLink.childID);
579-
}
580-
}
581-
582-
// 10. Retrieve child flows
528+
const childFlowsIDsSet = new Set<FlowId>(flowLinks.map((fl) => fl.childID));
529+
// 5. Retrieve child flows that are active
583530
const childFlows = await models.flow.find({
584531
where: {
585532
activeStatus: true,
@@ -588,13 +535,14 @@ export class FlowService {
588535
distinct: ['id', 'versionID'],
589536
});
590537

591-
// 11. Map child flows to UniqueFlowEntity and return the result
592-
const result = childFlows.map((ref) => ({
593-
id: ref.id,
594-
versionID: ref.versionID,
595-
}));
596-
597-
return result;
538+
// 6. Map child flows to UniqueFlowEntity and return the result
539+
return childFlows.map(
540+
(ref) =>
541+
({
542+
id: ref.id,
543+
versionID: ref.versionID,
544+
}) satisfies UniqueFlowEntity
545+
);
598546
}
599547

600548
/**

0 commit comments

Comments
 (0)