Skip to content

Commit d6ade46

Browse files
committed
Add 'search-flow-by-filters-strategy' implementation
Nested strategies to find flowIDs from different filters
1 parent 5937712 commit d6ade46

5 files changed

+622
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { type Database } from '@unocha/hpc-api-core/src/db';
2+
import type Knex from 'knex';
3+
import { type FlowObjectFilterGrouped } from '../../flow-object/model';
4+
import { type FlowCategory, type NestedFlowFilters } from '../graphql/args';
5+
import { type FlowShortcutFilter } from '../graphql/types';
6+
import { type UniqueFlowEntity } from '../model';
7+
8+
export interface FlowIdSearchStrategyResponse {
9+
flows: UniqueFlowEntity[];
10+
}
11+
12+
export interface FlowIdSearchStrategyArgs {
13+
databaseConnection: Knex;
14+
models: Database;
15+
flowObjectFilterGrouped?: FlowObjectFilterGrouped;
16+
flowCategoryConditions?: FlowCategory[];
17+
nestedFlowFilters?: NestedFlowFilters;
18+
shortcutFilter?: FlowShortcutFilter;
19+
}
20+
21+
export interface FlowIDSearchStrategy {
22+
search(args: FlowIdSearchStrategyArgs): Promise<FlowIdSearchStrategyResponse>;
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { type CategoryId } from '@unocha/hpc-api-core/src/db/models/category';
2+
import { Op } from '@unocha/hpc-api-core/src/db/util/conditions';
3+
import { createBrandedValue } from '@unocha/hpc-api-core/src/util/types';
4+
import { Service } from 'typedi';
5+
import { CategoryService } from '../../../categories/category-service';
6+
import { type UniqueFlowEntity } from '../../model';
7+
import {
8+
type FlowIDSearchStrategy,
9+
type FlowIdSearchStrategyArgs,
10+
type FlowIdSearchStrategyResponse,
11+
} from '../flowID-search-strategy';
12+
import { mapFlowCategoryConditionsToWhereClause } from './utils';
13+
14+
@Service()
15+
export class GetFlowIdsFromCategoryConditionsStrategyImpl
16+
implements FlowIDSearchStrategy
17+
{
18+
constructor(private readonly categoryService: CategoryService) {}
19+
20+
async search(
21+
args: FlowIdSearchStrategyArgs
22+
): Promise<FlowIdSearchStrategyResponse> {
23+
const {
24+
models,
25+
flowCategoryConditions,
26+
shortcutFilter,
27+
databaseConnection,
28+
} = args;
29+
30+
let categoriesIds: CategoryId[] = [];
31+
32+
let whereClause = null;
33+
if (flowCategoryConditions) {
34+
whereClause = mapFlowCategoryConditionsToWhereClause(
35+
flowCategoryConditions
36+
);
37+
}
38+
if (whereClause) {
39+
const categories = await this.categoryService.findCategories(
40+
models,
41+
whereClause
42+
);
43+
44+
categoriesIds = categories.map((category) => category.id);
45+
}
46+
47+
// Add category IDs from shortcut filter
48+
// to the list of category IDs IN or NOT_IN
49+
const categoriesIdsFromShortcutFilterIN: CategoryId[] = [];
50+
const categoriesIdsFromShortcutFilterNOTIN: CategoryId[] = [];
51+
52+
if (shortcutFilter) {
53+
for (const shortcut of shortcutFilter) {
54+
if (shortcut.operation === Op.IN) {
55+
categoriesIdsFromShortcutFilterIN.push(
56+
createBrandedValue(shortcut.id)
57+
);
58+
} else {
59+
categoriesIdsFromShortcutFilterNOTIN.push(
60+
createBrandedValue(shortcut.id)
61+
);
62+
}
63+
}
64+
}
65+
66+
let joinQuery = databaseConnection!
67+
.queryBuilder()
68+
.distinct('flow.id', 'flow.versionID')
69+
.from('flow')
70+
.where('flow.deletedAt', null)
71+
.join('categoryRef', function () {
72+
this.on('flow.id', '=', 'categoryRef.objectID').andOn(
73+
'flow.versionID',
74+
'=',
75+
'categoryRef.versionID'
76+
);
77+
});
78+
79+
if (categoriesIds.length > 0) {
80+
joinQuery = joinQuery.andWhere(function () {
81+
this.where('categoryRef.categoryID', 'IN', categoriesIds).andWhere(
82+
'categoryRef.objectType',
83+
'flow'
84+
);
85+
});
86+
}
87+
88+
if (categoriesIdsFromShortcutFilterIN.length > 0) {
89+
joinQuery = joinQuery.andWhere(function () {
90+
this.where(
91+
'categoryRef.categoryID',
92+
'IN',
93+
categoriesIdsFromShortcutFilterIN
94+
).andWhere('categoryRef.objectType', 'flow');
95+
});
96+
}
97+
98+
if (categoriesIdsFromShortcutFilterNOTIN.length > 0) {
99+
joinQuery = joinQuery.andWhere(function () {
100+
this.where(
101+
'categoryRef.categoryID',
102+
'NOT IN',
103+
categoriesIdsFromShortcutFilterNOTIN
104+
).andWhere('categoryRef.objectType', 'flow');
105+
});
106+
}
107+
108+
const flows = await joinQuery;
109+
110+
const mapFlows: UniqueFlowEntity[] = flows.map(
111+
(flow) =>
112+
({
113+
id: flow.id,
114+
versionID: flow.versionID,
115+
}) as UniqueFlowEntity
116+
);
117+
118+
return { flows: mapFlows };
119+
}
120+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { Service } from 'typedi';
2+
import { ExternalReferenceService } from '../../../external-reference/external-reference-service';
3+
import { LegacyService } from '../../../legacy/legacy-service';
4+
import { ReportDetailService } from '../../../report-details/report-detail-service';
5+
import { FlowService } from '../../flow-service';
6+
import { type UniqueFlowEntity } from '../../model';
7+
import {
8+
type FlowIDSearchStrategy,
9+
type FlowIdSearchStrategyArgs,
10+
type FlowIdSearchStrategyResponse,
11+
} from '../flowID-search-strategy';
12+
import {
13+
buildSearchFlowsConditions,
14+
defaultFlowOrderBy,
15+
intersectUniqueFlowEntities,
16+
} from './utils';
17+
18+
@Service()
19+
export class GetFlowIdsFromNestedFlowFiltersStrategyImpl
20+
implements FlowIDSearchStrategy
21+
{
22+
constructor(
23+
private readonly reportDetailService: ReportDetailService,
24+
private readonly legacyService: LegacyService,
25+
private readonly externalRefenceService: ExternalReferenceService,
26+
private readonly flowService: FlowService
27+
) {}
28+
29+
async search(
30+
args: FlowIdSearchStrategyArgs
31+
): Promise<FlowIdSearchStrategyResponse> {
32+
const { databaseConnection, models, nestedFlowFilters } = args;
33+
34+
let flowsReporterReferenceCode: UniqueFlowEntity[] = [];
35+
let flowsSourceSystemId: UniqueFlowEntity[] = [];
36+
let flowsSystemId: UniqueFlowEntity[] = [];
37+
const flowsLegacyId: UniqueFlowEntity[] = [];
38+
39+
// Get the flowIDs using 'reporterReferenceCode'
40+
if (nestedFlowFilters?.reporterRefCode) {
41+
flowsReporterReferenceCode =
42+
await this.reportDetailService.getUniqueFlowIDsFromReportDetailsByReporterReferenceCode(
43+
models,
44+
nestedFlowFilters.reporterRefCode
45+
);
46+
}
47+
48+
// Get the flowIDs using 'sourceSystemID' from 'reportDetail'
49+
if (nestedFlowFilters?.sourceSystemID) {
50+
flowsSourceSystemId =
51+
await this.reportDetailService.getUniqueFlowIDsFromReportDetailsBySourceSystemID(
52+
models,
53+
nestedFlowFilters.sourceSystemID
54+
);
55+
}
56+
57+
// Get the flowIDs using 'systemID' from 'externalRefecence'
58+
if (nestedFlowFilters?.systemID) {
59+
flowsSystemId =
60+
await this.externalRefenceService.getUniqueFlowIDsBySystemID(
61+
models,
62+
nestedFlowFilters.systemID
63+
);
64+
}
65+
66+
// Get the flowIDs using 'legacyID'
67+
if (nestedFlowFilters?.legacyID) {
68+
const flowID = await this.legacyService.getFlowIdFromLegacyId(
69+
models,
70+
nestedFlowFilters.legacyID
71+
);
72+
73+
if (flowID) {
74+
flowsLegacyId.push({
75+
id: flowID,
76+
versionID: 1,
77+
});
78+
}
79+
}
80+
81+
// Intersect the flowIDs from the nestedFlowFilters
82+
const flowIDsFromNestedFlowFilters: UniqueFlowEntity[] =
83+
intersectUniqueFlowEntities(
84+
flowsReporterReferenceCode,
85+
flowsSourceSystemId,
86+
flowsSystemId,
87+
flowsLegacyId
88+
);
89+
90+
if (flowIDsFromNestedFlowFilters.length === 0) {
91+
return { flows: [] };
92+
}
93+
// Once gathered and disjoined the flowIDs from the nestedFlowFilters
94+
// Look after this uniqueFlows in the flow table
95+
// To verify the flow is not deleted
96+
const uniqueFlowEntitiesNotDeleted = [];
97+
98+
// Slice the flowIDs in chunks of 1000 to avoid the SQL query limit
99+
for (let i = 0; i < flowIDsFromNestedFlowFilters.length; i += 1000) {
100+
const getFlowArgs = {
101+
databaseConnection,
102+
orderBy: defaultFlowOrderBy(),
103+
whereClauses: buildSearchFlowsConditions(
104+
flowIDsFromNestedFlowFilters.slice(i, i + 1000)
105+
),
106+
};
107+
const uniqueFlowsNotDeleted =
108+
await this.flowService.getFlowsAsUniqueFlowEntity(getFlowArgs);
109+
uniqueFlowEntitiesNotDeleted.push(...uniqueFlowsNotDeleted);
110+
}
111+
return { flows: uniqueFlowEntitiesNotDeleted };
112+
}
113+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Service } from 'typedi';
2+
import { FlowObjectService } from '../../../flow-object/flow-object-service';
3+
import { FlowService } from '../../flow-service';
4+
import {
5+
type FlowIDSearchStrategy,
6+
type FlowIdSearchStrategyArgs,
7+
type FlowIdSearchStrategyResponse,
8+
} from '../flowID-search-strategy';
9+
10+
@Service()
11+
export class GetFlowIdsFromObjectConditionsStrategyImpl
12+
implements FlowIDSearchStrategy
13+
{
14+
constructor(
15+
private readonly flowObjectService: FlowObjectService,
16+
private readonly flowService: FlowService
17+
) {}
18+
19+
async search(
20+
args: FlowIdSearchStrategyArgs
21+
): Promise<FlowIdSearchStrategyResponse> {
22+
const { flowObjectFilterGrouped, databaseConnection } = args;
23+
24+
if (!flowObjectFilterGrouped) {
25+
return { flows: [] };
26+
}
27+
28+
const flowObjects =
29+
await this.flowObjectService.getFlowObjectsByFlowObjectConditions(
30+
databaseConnection,
31+
flowObjectFilterGrouped
32+
);
33+
34+
return { flows: flowObjects };
35+
}
36+
}

0 commit comments

Comments
 (0)