Skip to content

Commit

Permalink
Merge pull request #25 from bcgov/feature/drafts-support
Browse files Browse the repository at this point in the history
Added support for draft and in review rules
  • Loading branch information
timwekkenbc authored Jul 31, 2024
2 parents 649dcd6 + 8cc8f5c commit 8676d2c
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 21 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Before running your application locally, you'll need some environment variables.

- MONGODB_URL: The URL for connecting to the MongoDB instance you created in the previous step. Set it to something like mongodb://localhost/nest.
- FRONTEND_URI: The URI for the frontend application. Set it to http://localhost:8080.
- GITHUB_APP_CLIENT_ID
- GITHUB_APP_CLIENT_SECRET

### Including Rules from the Rules Repository

Expand Down
9 changes: 9 additions & 0 deletions src/api/ruleData/ruleData.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ describe('RuleDataController', () => {
expect(await controller.getAllRulesData()).toBe(result);
});

it('should return a rule draft for a given ruleId', async () => {
const ruleId = 'testRuleId';
const mockRuleDraft = { _id: 'sadasd', content: {} };
jest.spyOn(service, 'getRuleDataWithDraft').mockImplementation(() => Promise.resolve(mockRuleDraft));

expect(await controller.getRuleDraft(ruleId)).toBe(mockRuleDraft);
expect(service.getRuleDataWithDraft).toHaveBeenCalledWith(ruleId);
});

it('should return a rule data', async () => {
const ruleId = 'testId';
jest.spyOn(service, 'getRuleData').mockImplementation(() => Promise.resolve(mockRuleData));
Expand Down
12 changes: 11 additions & 1 deletion src/api/ruleData/ruleData.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Controller, Get, Param, Post, Body, Put, Delete, HttpException, HttpStatus } from '@nestjs/common';
import { RuleDataService } from './ruleData.service';
import { RuleData } from './ruleData.schema'; // assuming you have this interface
import { RuleData } from './ruleData.schema';
import { RuleDraft } from './ruleDraft.schema';

@Controller('api/ruleData')
export class RuleDataController {
Expand All @@ -15,6 +16,15 @@ export class RuleDataController {
}
}

@Get('/draft/:ruleId')
async getRuleDraft(@Param('ruleId') ruleId: string): Promise<RuleDraft> {
try {
return await this.ruleDataService.getRuleDataWithDraft(ruleId);
} catch (error) {
throw new HttpException('Error getting draft data', HttpStatus.INTERNAL_SERVER_ERROR);
}
}

@Get('/:ruleId')
async getRuleData(@Param('ruleId') ruleId: string): Promise<RuleData> {
try {
Expand Down
19 changes: 19 additions & 0 deletions src/api/ruleData/ruleData.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { RuleData, RuleDataSchema } from './ruleData.schema';
import { RuleDraft, RuleDraftSchema } from './ruleDraft.schema';
import { RuleDataController } from './ruleData.controller';
import { RuleDataService } from './ruleData.service';
import { DocumentsService } from '../documents/documents.service';

@Module({
imports: [
MongooseModule.forFeature([
{ name: RuleData.name, schema: RuleDataSchema },
{ name: RuleDraft.name, schema: RuleDraftSchema },
]),
],
controllers: [RuleDataController],
providers: [RuleDataService, DocumentsService],
})
export class RuleDataModule {}
13 changes: 10 additions & 3 deletions src/api/ruleData/ruleData.schema.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type RuleDataDocument = RuleData & Document;
import { Document, Types } from 'mongoose';
import { RuleDraftDocument } from './ruleDraft.schema';

@Schema()
export class RuleData {
Expand All @@ -13,6 +12,14 @@ export class RuleData {

@Prop({ required: true, description: 'The filename of the JSON file containing the rule' })
goRulesJSONFilename: string;

@Prop({ type: Types.ObjectId, description: 'Draft of updated rule content', ref: 'RuleDraft' })
ruleDraft?: RuleDraftDocument | Types.ObjectId;

@Prop({ description: 'The name of the branch on github associated with this file' })
reviewBranch?: string;
}

export type RuleDataDocument = RuleData & Document;

export const RuleDataSchema = SchemaFactory.createForClass(RuleData);
26 changes: 25 additions & 1 deletion src/api/ruleData/ruleData.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@ import { getModelToken } from '@nestjs/mongoose';
import { DocumentsService } from '../documents/documents.service';
import { RuleDataService } from './ruleData.service';
import { RuleData } from './ruleData.schema';
import { RuleDraft } from './ruleDraft.schema';

export const mockRuleData = {
export const mockRuleData: RuleData = {
_id: 'testId',
title: 'Title',
goRulesJSONFilename: 'filename.json',
};

const mockRuleDraft = { content: { nodes: [], edges: [] } };

export const mockServiceProviders = [
RuleDataService,
{
provide: getModelToken(RuleDraft.name),
useFactory: () => ({}),
},
{
provide: getModelToken(RuleData.name),
useFactory: () => ({
Expand All @@ -21,6 +28,11 @@ export const mockServiceProviders = [
findOne: jest.fn().mockImplementation(() => ({
exec: jest.fn().mockResolvedValue(mockRuleData),
})),
findById: jest.fn().mockReturnValue({
populate: jest.fn().mockReturnValue({
exec: jest.fn().mockResolvedValue({ ...mockRuleData, ruleDraft: mockRuleDraft }),
}),
}),
}),
},
{
Expand Down Expand Up @@ -53,10 +65,22 @@ describe('RuleDataService', () => {
expect(await service.getAllRuleData()).toEqual(result);
});

it('should return a rule draft for a given ruleId', async () => {
expect(await service.getRuleDataWithDraft(mockRuleData._id)).toEqual(mockRuleDraft);
});

it('should get data for a rule', async () => {
expect(await service.getRuleData(mockRuleData._id)).toEqual(mockRuleData);
});

it('should update rule data', async () => {
// TODO: Implement
});

it('should delete rule data', async () => {
// TODO: Implement
});

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'];
Expand Down
28 changes: 27 additions & 1 deletion src/api/ruleData/ruleData.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { DocumentsService } from '../documents/documents.service';
import { RuleData, RuleDataDocument } from './ruleData.schema';
import { RuleDraft, RuleDraftDocument } from './ruleDraft.schema';

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

Expand All @@ -26,6 +28,15 @@ export class RuleDataService {
}
}

async getRuleDataWithDraft(ruleId: string): Promise<RuleDraft> {
try {
const { ruleDraft } = await this.ruleDataModel.findById(ruleId).populate('ruleDraft').exec();
return ruleDraft as RuleDraft;
} catch (error) {
throw new Error(`Error getting draft for ${ruleId}: ${error.message}`);
}
}

async getRuleData(ruleId: string): Promise<RuleData> {
try {
const ruleData = await this.ruleDataModel.findOne({ _id: ruleId }).exec();
Expand All @@ -34,8 +45,20 @@ export class RuleDataService {
}
return ruleData;
} catch (error) {
throw new Error(`Error getting all rule data: ${error.message}`);
throw new Error(`Error getting all rule data for ${ruleId}: ${error.message}`);
}
}

async _addOrUpdateDraft(ruleData: Partial<RuleData>): Promise<Partial<RuleData>> {
// If there is a rule draft, update that document specifically
// This is necessary because we don't store the draft on the ruleData object directly
// Instead it is stored elsewhere and linked to the ruleData via its id
if (ruleData?.ruleDraft) {
const newDraft = new this.ruleDraftModel(ruleData.ruleDraft);
const savedDraft = await newDraft.save();
ruleData.ruleDraft = savedDraft._id;
}
return ruleData;
}

async createRuleData(ruleData: Partial<RuleData>): Promise<RuleData> {
Expand All @@ -44,6 +67,7 @@ export class RuleDataService {
const newRuleID = new ObjectId();
ruleData._id = newRuleID.toHexString();
}
ruleData = await this._addOrUpdateDraft(ruleData);
const newRuleData = new this.ruleDataModel(ruleData);
const response = await newRuleData.save();
return response;
Expand All @@ -59,9 +83,11 @@ export class RuleDataService {
if (!existingRuleData) {
throw new Error('Rule data not found');
}
updatedData = await this._addOrUpdateDraft(updatedData);
Object.assign(existingRuleData, updatedData);
return await existingRuleData.save();
} catch (error) {
console.error('Error updating rule', error.message);
throw new Error(`Failed to update rule data: ${error.message}`);
}
}
Expand Down
12 changes: 12 additions & 0 deletions src/api/ruleData/ruleDraft.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Schema as MongooseSchema } from 'mongoose';

@Schema()
export class RuleDraft {
@Prop({ type: MongooseSchema.Types.Mixed, description: 'Draft of updated rule content' })
content: object;
}

export type RuleDraftDocument = RuleDraft & Document;

export const RuleDraftSchema = SchemaFactory.createForClass(RuleDraft);
22 changes: 7 additions & 15 deletions src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';
import { RuleData, RuleDataSchema } from './api/ruleData/ruleData.schema';
import { RuleDataController } from './api/ruleData/ruleData.controller';
import { RuleDataService } from './api/ruleData/ruleData.service';
import { GithubAuthModule } from './auth/github-auth/github-auth.module';
import { RuleDataModule } from './api/ruleData/ruleData.module';
import { DecisionsController } from './api/decisions/decisions.controller';
import { DecisionsService } from './api/decisions/decisions.service';
import { DocumentsController } from './api/documents/documents.controller';
Expand All @@ -25,18 +24,11 @@ import { ScenarioDataService } from './api/scenarioData/scenarioData.service';
],
}),
MongooseModule.forRoot(process.env.MONGODB_URL),
MongooseModule.forFeature([
{ name: RuleData.name, schema: RuleDataSchema },
{ name: ScenarioData.name, schema: ScenarioDataSchema },
]),
MongooseModule.forFeature([{ name: ScenarioData.name, schema: ScenarioDataSchema }]),
GithubAuthModule,
RuleDataModule,
],
controllers: [
RuleDataController,
DecisionsController,
DocumentsController,
RuleMappingController,
ScenarioDataController,
],
providers: [RuleDataService, DecisionsService, DocumentsService, RuleMappingService, ScenarioDataService],
controllers: [DecisionsController, DocumentsController, RuleMappingController, ScenarioDataController],
providers: [DecisionsService, DocumentsService, RuleMappingService, ScenarioDataService],
})
export class AppModule {}

0 comments on commit 8676d2c

Please sign in to comment.