Skip to content

Commit

Permalink
Add 'search-flow-by-filters-strategy' implementation
Browse files Browse the repository at this point in the history
Nested strategies to find flowIDs from different filters
  • Loading branch information
manelcecs committed Apr 24, 2024
1 parent 2a66796 commit 08020f9
Show file tree
Hide file tree
Showing 5 changed files with 652 additions and 0 deletions.
22 changes: 22 additions & 0 deletions src/domain-services/flows/strategy/flowID-search-strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { type Database } from '@unocha/hpc-api-core/src/db';
import type Knex from 'knex';
import { type FlowCategory, type NestedFlowFilters } from '../graphql/args';
import { type FlowShortcutFilter } from '../graphql/types';
import { type UniqueFlowEntity } from '../model';

export interface FlowIdSearchStrategyResponse {
flows: UniqueFlowEntity[];
}

export interface FlowIdSearchStrategyArgs {
databaseConnection: Knex;
models: Database;
flowObjectsConditions?: any;
flowCategoryConditions?: FlowCategory[];
nestedFlowFilters?: NestedFlowFilters;
shortcutFilter?: FlowShortcutFilter;
}

export interface FlowIDSearchStrategy {
search(args: FlowIdSearchStrategyArgs): Promise<FlowIdSearchStrategyResponse>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { type CategoryId } from '@unocha/hpc-api-core/src/db/models/category';
import { Op } from '@unocha/hpc-api-core/src/db/util/conditions';
import { createBrandedValue } from '@unocha/hpc-api-core/src/util/types';
import { Service } from 'typedi';
import { CategoryService } from '../../../categories/category-service';
import { type UniqueFlowEntity } from '../../model';
import {
type FlowIDSearchStrategy,
type FlowIdSearchStrategyArgs,
type FlowIdSearchStrategyResponse,
} from '../flowID-search-strategy';
import { mapFlowCategoryConditionsToWhereClause } from './utils';

@Service()
export class GetFlowIdsFromCategoryConditionsStrategyImpl
implements FlowIDSearchStrategy
{
constructor(private readonly categoryService: CategoryService) {}

async search(
args: FlowIdSearchStrategyArgs
): Promise<FlowIdSearchStrategyResponse> {
const {
models,
flowCategoryConditions,
shortcutFilter,
databaseConnection,
} = args;

let categoriesIds: CategoryId[] = [];

let whereClause = null;
if (flowCategoryConditions) {
whereClause = mapFlowCategoryConditionsToWhereClause(
flowCategoryConditions
);
}
if (whereClause) {
const categories = await this.categoryService.findCategories(
models,
whereClause
);

categoriesIds = categories.map((category) => category.id);
}

// Add category IDs from shortcut filter
// to the list of category IDs IN or NOT_IN
const categoriesIdsFromShortcutFilterIN: CategoryId[] = [];
const categoriesIdsFromShortcutFilterNOTIN: CategoryId[] = [];

if (shortcutFilter) {
for (const shortcut of shortcutFilter) {
if (shortcut.operation === Op.IN) {
categoriesIdsFromShortcutFilterIN.push(
createBrandedValue(shortcut.id)
);
} else {
categoriesIdsFromShortcutFilterNOTIN.push(
createBrandedValue(shortcut.id)
);
}
}
}

let joinQuery = databaseConnection!
.queryBuilder()
.distinct('flow.id', 'flow.versionID')
.from('flow')
.join('categoryRef', function () {
this.on('flow.id', '=', 'categoryRef.objectID').andOn(
'flow.versionID',
'=',
'categoryRef.versionID'
);
});

if (categoriesIds.length > 0) {
joinQuery = joinQuery.andWhere(function () {
this.where('categoryRef.categoryID', 'IN', categoriesIds)
.andWhere('categoryRef.objectType', 'flow')
.andWhere('flow.deletedAt', null);
});
}

if (categoriesIdsFromShortcutFilterIN.length > 0) {
joinQuery = joinQuery.andWhere(function () {
this.where(
'categoryRef.categoryID',
'IN',
categoriesIdsFromShortcutFilterIN
)
.andWhere('categoryRef.objectType', 'flow')
.andWhere('flow.deletedAt', null);
});
}

if (categoriesIdsFromShortcutFilterNOTIN.length > 0) {
joinQuery = joinQuery.andWhere(function () {
this.where(
'categoryRef.categoryID',
'NOT IN',
categoriesIdsFromShortcutFilterNOTIN
)
.andWhere('categoryRef.objectType', 'flow')
.andWhere('flow.deletedAt', null);
});
}

const flows = await joinQuery;

const mapFlows: UniqueFlowEntity[] = flows.map(
(flow) =>
({
id: flow.id,
versionID: flow.versionID,
}) as UniqueFlowEntity
);

return { flows: mapFlows };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { Service } from 'typedi';
import { ExternalReferenceService } from '../../../external-reference/external-reference-service';
import { LegacyService } from '../../../legacy/legacy-service';
import { ReportDetailService } from '../../../report-details/report-detail-service';
import { FlowService } from '../../flow-service';
import { type UniqueFlowEntity } from '../../model';
import {
type FlowIDSearchStrategy,
type FlowIdSearchStrategyArgs,
type FlowIdSearchStrategyResponse,
} from '../flowID-search-strategy';
import {
buildSearchFlowsConditions,
defaultFlowOrderBy,
intersectUniqueFlowEntities,
} from './utils';

@Service()
export class GetFlowIdsFromNestedFlowFiltersStrategyImpl
implements FlowIDSearchStrategy
{
constructor(
private readonly reportDetailService: ReportDetailService,
private readonly legacyService: LegacyService,
private readonly externalRefenceService: ExternalReferenceService,
private readonly flowService: FlowService
) {}

async search(
args: FlowIdSearchStrategyArgs
): Promise<FlowIdSearchStrategyResponse> {
const { databaseConnection, models, nestedFlowFilters } = args;

let flowsReporterReferenceCode: UniqueFlowEntity[] = [];
let flowsSourceSystemId: UniqueFlowEntity[] = [];
let flowsSystemId: UniqueFlowEntity[] = [];
const flowsLegacyId: UniqueFlowEntity[] = [];

// Get the flowIDs using 'reporterReferenceCode'
if (nestedFlowFilters?.reporterRefCode) {
flowsReporterReferenceCode =
await this.reportDetailService.getUniqueFlowIDsFromReportDetailsByReporterReferenceCode(
models,
nestedFlowFilters.reporterRefCode
);
}

// Get the flowIDs using 'sourceSystemID' from 'reportDetail'
if (nestedFlowFilters?.sourceSystemID) {
flowsSourceSystemId =
await this.reportDetailService.getUniqueFlowIDsFromReportDetailsBySourceSystemID(
models,
nestedFlowFilters.sourceSystemID
);
}

// Get the flowIDs using 'systemID' from 'externalRefecence'
if (nestedFlowFilters?.systemID) {
flowsSystemId =
await this.externalRefenceService.getUniqueFlowIDsBySystemID(
models,
nestedFlowFilters.systemID
);
}

// Get the flowIDs using 'legacyID'
if (nestedFlowFilters?.legacyID) {
const flowID = await this.legacyService.getFlowIdFromLegacyId(
models,
nestedFlowFilters.legacyID
);

if (flowID) {
flowsLegacyId.push({
id: flowID,
versionID: 1,
});
}
}

// Intersect the flowIDs from the nestedFlowFilters
const flowIDsFromNestedFlowFilters: UniqueFlowEntity[] =
intersectUniqueFlowEntities(
flowsReporterReferenceCode,
flowsSourceSystemId,
flowsSystemId,
flowsLegacyId
);

if (flowIDsFromNestedFlowFilters.length === 0) {
return { flows: [] };
}
// Once gathered and disjoined the flowIDs from the nestedFlowFilters
// Look after this uniqueFlows in the flow table
// To verify the flow is not deleted
const uniqueFlowEntitiesNotDeleted = [];

// TEMP fix
for (let i = 0; i < flowIDsFromNestedFlowFilters.length; i += 1000) {
const getFlowArgs = {
databaseConnection,
orderBy: defaultFlowOrderBy(),
whereClauses: buildSearchFlowsConditions(
flowIDsFromNestedFlowFilters.slice(i, i + 1000)
),
};
const uniqueFlowsNotDeleted =
await this.flowService.getFlowsAsUniqueFlowEntity(getFlowArgs);
uniqueFlowEntitiesNotDeleted.push(...uniqueFlowsNotDeleted);
}
return { flows: uniqueFlowEntitiesNotDeleted };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Service } from 'typedi';
import { FlowObjectService } from '../../../flow-object/flow-object-service';
import { FlowService } from '../../flow-service';
import { type UniqueFlowEntity } from '../../model';
import {
type FlowIDSearchStrategy,
type FlowIdSearchStrategyArgs,
type FlowIdSearchStrategyResponse,
} from '../flowID-search-strategy';
import { buildSearchFlowsConditions, defaultFlowOrderBy } from './utils';

@Service()
export class GetFlowIdsFromObjectConditionsStrategyImpl
implements FlowIDSearchStrategy
{
constructor(
private readonly flowObjectService: FlowObjectService,
private readonly flowService: FlowService
) {}

async search(
args: FlowIdSearchStrategyArgs
): Promise<FlowIdSearchStrategyResponse> {
const { flowObjectsConditions, databaseConnection } = args;

// 1. Obtain flowIDs from flowObjects
const flowsFromFilteredFlowObjects: UniqueFlowEntity[] = [];
const flowObjects =
await this.flowObjectService.getFlowObjectsByFlowObjectConditions(
databaseConnection,
flowObjectsConditions
);

// 1.1. Check if flowObjects is undefined
if (!flowObjects) {
return { flows: [] };
}

// 2. Map flowObjects to UniqueFlowEntity and store in flowsFromFilteredFlowObjects
for (const flowObject of flowObjects) {
flowsFromFilteredFlowObjects.push({
id: flowObject.flowID,
versionID: flowObject.versionID,
});
}

// 3. Once we have the flowIDs
// Search in the Flow table for the flowIDs and versionIDs
// To verify that the flows are not deleted
const uniqueFlowsNotDeleted: UniqueFlowEntity[] = [];
// TEMP fix
for (let i = 0; i < flowsFromFilteredFlowObjects.length; i += 1000) {
const getFlowArgs = {
databaseConnection,
orderBy: defaultFlowOrderBy(),
whereClauses: buildSearchFlowsConditions(
flowsFromFilteredFlowObjects.slice(i, i + 1000)
),
};
const uniqueFlowsNotDeletedSlice =
await this.flowService.getFlowsAsUniqueFlowEntity(getFlowArgs);
uniqueFlowsNotDeleted.push(...uniqueFlowsNotDeletedSlice);
}

return { flows: uniqueFlowsNotDeleted };
}
}
Loading

0 comments on commit 08020f9

Please sign in to comment.