Skip to content

Commit

Permalink
Merge pull request #19 from bcgov/dev
Browse files Browse the repository at this point in the history
Production Release
  • Loading branch information
timwekkenbc authored Jul 10, 2024
2 parents 8012a2d + f3de579 commit 4c899af
Show file tree
Hide file tree
Showing 22 changed files with 1,840 additions and 49 deletions.
5 changes: 1 addition & 4 deletions .github/workflows/eslint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,7 @@ jobs:
continue-on-error: true

- name: Upload ESLint report
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: eslint-report
path: eslint-report.html

- name: Display ESLint report link
run: echo "::set-output name=eslint-report::${{ steps.upload.outputs.artifact_path }}"
57 changes: 50 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@
"@nestjs/config": "^3.2.0",
"@nestjs/core": "^10.0.0",
"@nestjs/mongoose": "^10.0.5",
"@nestjs/platform-express": "^10.3.7",
"@nestjs/platform-express": "^10.3.9",
"axios": "^1.6.8",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"csv-parser": "^3.0.0",
"mongoose": "^8.3.0",
"multer": "^1.4.5-lts.1",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1"
},
Expand All @@ -40,6 +42,7 @@
"@nestjs/testing": "^10.0.0",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/multer": "^1.4.11",
"@types/node": "^20.3.1",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
Expand Down
4 changes: 3 additions & 1 deletion src/api/decisions/decisions.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ export class DecisionsService {
rulesDirectory: string;

constructor(private configService: ConfigService) {
this.engine = new ZenEngine();
this.rulesDirectory = this.configService.get<string>('RULES_DIRECTORY');
const loader = async (key: string) => readFileSafely(this.rulesDirectory, key);
this.engine = new ZenEngine({ loader });
}

async runDecision(content: object, context: object, options: ZenEvaluateOptions) {
try {
const decision = this.engine.createDecision(content);
return await decision.evaluate(context, options);
} catch (error) {
console.error(error.message);
throw new Error(`Failed to run decision: ${error.message}`);
}
}
Expand Down
3 changes: 0 additions & 3 deletions src/api/documents/documents.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { HttpException, HttpStatus } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import * as fs from 'fs';
import * as util from 'util';
import * as path from 'path';
import { DocumentsService } from './documents.service';
import { readFileSafely } from '../../utils/readFile';

Expand Down
23 changes: 23 additions & 0 deletions src/api/ruleData/ruleData.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getModelToken } from '@nestjs/mongoose';
import { DocumentsService } from '../documents/documents.service';
import { RuleDataService } from './ruleData.service';
import { RuleData } from './ruleData.schema';

Expand All @@ -24,17 +25,25 @@ export const mockServiceProviders = [
})),
}),
},
{
provide: DocumentsService,
useValue: {
getAllJSONFiles: jest.fn().mockResolvedValue(['doc1.json', 'doc2.json']),
},
},
];

describe('RuleDataService', () => {
let service: RuleDataService;
let documentsService: DocumentsService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: mockServiceProviders,
}).compile();

service = module.get<RuleDataService>(RuleDataService);
documentsService = module.get<DocumentsService>(DocumentsService);
});

it('should be defined', () => {
Expand All @@ -49,4 +58,18 @@ describe('RuleDataService', () => {
it('should get data for a rule', async () => {
expect(await service.getRuleData(mockRuleData._id)).toEqual(mockRuleData);
});

it('should add unsynced files correctly', async () => {
// Mock the expected behavior getting db files, getting repo files, and adding to db
const unsyncedFiles = ['file1.txt', 'file2.txt'];
jest.spyOn(service, 'getAllRuleData').mockResolvedValue([mockRuleData]);
jest.spyOn(documentsService, 'getAllJSONFiles').mockResolvedValue(unsyncedFiles);
jest.spyOn(service, 'createRuleData').mockImplementation((file: RuleData) => Promise.resolve(file));

await service.addUnsyncedFiles();

expect(service.createRuleData).toHaveBeenCalled();
expect(documentsService.getAllJSONFiles).toHaveBeenCalled();
expect(service.createRuleData).toHaveBeenCalledTimes(unsyncedFiles.length);
});
});
29 changes: 27 additions & 2 deletions src/api/ruleData/ruleData.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@ import { ObjectId } from 'mongodb';
import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { DocumentsService } from '../documents/documents.service';
import { RuleData, RuleDataDocument } from './ruleData.schema';

@Injectable()
export class RuleDataService {
constructor(@InjectModel(RuleData.name) private ruleDataModel: Model<RuleDataDocument>) {}
constructor(
@InjectModel(RuleData.name) private ruleDataModel: Model<RuleDataDocument>,
private documentsService: DocumentsService,
) {}

async onModuleInit() {
console.info('Syncing existing rules with any updates to the rules repository');
this.addUnsyncedFiles();
}

async getAllRuleData(): Promise<RuleData[]> {
try {
Expand All @@ -29,7 +38,7 @@ export class RuleDataService {
}
}

async createRuleData(ruleData: RuleData): Promise<RuleData> {
async createRuleData(ruleData: Partial<RuleData>): Promise<RuleData> {
try {
if (!ruleData._id) {
const newRuleID = new ObjectId();
Expand Down Expand Up @@ -75,4 +84,20 @@ export class RuleDataService {
}
return ruleData.chefsFormAPIKey;
}

/**
* Add rules to the db that exist in the repo, but not yet the db
*/
async addUnsyncedFiles() {
const existingRules = await this.getAllRuleData();
const jsonRuleDocuments = await this.documentsService.getAllJSONFiles();
// Find rules not yet defined in db (but with an exisitng JSON file) and add them
jsonRuleDocuments
.filter((goRulesJSONFilename: string) => {
return !existingRules.find((rule) => rule.goRulesJSONFilename === goRulesJSONFilename);
})
.forEach((goRulesJSONFilename: string) => {
this.createRuleData({ goRulesJSONFilename });
});
}
}
2 changes: 1 addition & 1 deletion src/api/ruleMapping/ruleMapping.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe('RuleMappingController', () => {
it('should return the evaluated rule map', async () => {
const nodes = [{ id: '1', type: 'someType', content: { inputs: [], outputs: [] } }];
const edges = [{ id: '2', type: 'someType', targetId: '1', sourceId: '1' }];
const result = { inputs: [], outputs: [], finalOutputs: [] };
const result = { inputs: [], outputs: [], resultOutputs: [] };
jest.spyOn(service, 'ruleSchema').mockReturnValue(result);

const dto: EvaluateRuleMappingDto = { nodes, edges };
Expand Down
11 changes: 6 additions & 5 deletions src/api/ruleMapping/ruleMapping.controller.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { Controller, Get, Param, Res, Post, Body, HttpException, HttpStatus } from '@nestjs/common';
import { Controller, Query, Res, Post, Body, HttpException, HttpStatus } from '@nestjs/common';
import { RuleMappingService } from './ruleMapping.service';
import { Response } from 'express';
import { EvaluateRuleRunSchemaDto, EvaluateRuleMappingDto } from './dto/evaluate-rulemapping.dto';

@Controller('api/rulemap')
export class RuleMappingController {
constructor(private ruleMappingService: RuleMappingService) {}

// Map a rule file to its unique inputs, and all outputs
@Get('/:ruleFileName')
async getRuleFile(@Param('ruleFileName') ruleFileName: string, @Res() res: Response) {
const rulemap = await this.ruleMappingService.ruleSchemaFile(ruleFileName);
@Post('/')
async getRuleFile(@Query('goRulesJSONFilename') goRulesJSONFilename: string, @Res() res: Response) {
const rulemap = await this.ruleMappingService.ruleSchemaFile(goRulesJSONFilename);

try {
res.setHeader('Content-Type', 'application/json');
res.setHeader('Content-Disposition', `attachment; filename=${ruleFileName}`);
res.setHeader('Content-Disposition', `attachment; filename=${goRulesJSONFilename}`);
res.send(rulemap);
} catch (error) {
throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
Expand Down
22 changes: 11 additions & 11 deletions src/api/ruleMapping/ruleMapping.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ describe('RuleMappingService', () => {
});
});

describe('extractfinalOutputs', () => {
it('should extract final outputs correctly when there is one output node and corresponding edges', () => {
describe('extractResultOutputs', () => {
it('should extract result outputs correctly when there is one output node and corresponding edges', () => {
const nodes: Node[] = [
{
id: '1',
Expand Down Expand Up @@ -129,9 +129,9 @@ describe('RuleMappingService', () => {
],
});

const result = service.extractfinalOutputs(nodes, edges);
const result = service.extractResultOutputs(nodes, edges);
expect(result).toEqual({
finalOutputs: [
resultOutputs: [
{ id: '1', name: 'Output1', type: 'string', property: 'field2' },
{ id: '2', name: 'Output2', type: 'number', property: 'field3' },
],
Expand All @@ -151,7 +151,7 @@ describe('RuleMappingService', () => {

const edges: Edge[] = [{ id: '1', type: 'someType', sourceId: '1', targetId: '2' }];

expect(() => service.extractfinalOutputs(nodes, edges)).toThrow('No outputNode found in the nodes array');
expect(() => service.extractResultOutputs(nodes, edges)).toThrow('No outputNode found in the nodes array');
});

it('should return an empty array if no target edges are found for the output node', () => {
Expand All @@ -178,9 +178,9 @@ describe('RuleMappingService', () => {
outputs: [],
});

const result = service.extractfinalOutputs(nodes, edges);
const result = service.extractResultOutputs(nodes, edges);
expect(result).toEqual({
finalOutputs: [],
resultOutputs: [],
});
});

Expand Down Expand Up @@ -221,9 +221,9 @@ describe('RuleMappingService', () => {
],
});

const result = service.extractfinalOutputs(nodes, edges);
const result = service.extractResultOutputs(nodes, edges);
expect(result).toEqual({
finalOutputs: [
resultOutputs: [
{ id: '1', name: 'Output1', type: 'string', property: 'field2' },
{ id: '2', name: 'Output2', type: 'number', property: 'field3' },
],
Expand Down Expand Up @@ -367,7 +367,7 @@ describe('RuleMappingService', () => {
{ id: '1', name: 'Output1', type: 'string', property: 'field2' },
{ id: '2', name: 'Output2', type: 'number', property: 'field3' },
],
finalOutputs: [],
resultOutputs: [],
});
});
});
Expand Down Expand Up @@ -524,7 +524,7 @@ describe('RuleMappingService', () => {

expect(mockGetFileContent).toHaveBeenCalledWith(filePath);
expect(result).toEqual({
finalOutputs: [],
resultOutputs: [],
inputs: [{ id: '1', name: 'Input1', type: 'string', property: 'field1' }],
outputs: [
{ id: '3', name: 'Output1', type: 'string', property: 'field2' },
Expand Down
Loading

0 comments on commit 4c899af

Please sign in to comment.