Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix/bai 862 request reviews on release creation #733

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4e0efbd
Add smtp service and a happy test
JR40159 Sep 5, 2023
2138d58
Make config immutable
JR40159 Sep 7, 2023
7b04aa2
Add test for disabled smtp
JR40159 Sep 7, 2023
4a29659
Change to mockReturnValueOnce
JR40159 Sep 7, 2023
9c5825b
remove console logs
JR40159 Sep 7, 2023
a2c1c62
Add Review Request creation on release creation
JR40159 Sep 7, 2023
7f89020
Replace approvals with review requests
JR40159 Sep 8, 2023
1c51d10
Send email to groups
JR40159 Sep 8, 2023
f93b16e
Remove role from review respoonse object
JR40159 Sep 8, 2023
acaabf2
Fix bugs
JR40159 Sep 8, 2023
3664c9e
Add email templates
JR40159 Sep 11, 2023
1450011
revert prettier and fix existing style issues
JR40159 Sep 12, 2023
b6c1d42
Fix problems in the email
JR40159 Sep 12, 2023
8a9ec03
Rename approvals to review requests
JR40159 Sep 12, 2023
c8ed25a
Update mock
JR40159 Sep 12, 2023
a7f9a0a
Fix broken test
JR40159 Sep 14, 2023
7f1863e
Test email service
JR40159 Sep 14, 2023
f146c60
Create a review request for each role
JR40159 Sep 14, 2023
606b273
Fix filter
JR40159 Sep 14, 2023
ac537e2
Update get Review Requests by active
JR40159 Sep 14, 2023
35a5df7
Review comments
JR40159 Sep 14, 2023
5e88078
Rename methods for 'release' reviews
JR40159 Sep 14, 2023
d0d9af8
Merge branch 'main' into bugfix/BAI-862-create-approval-requests-requ…
JR40159 Sep 15, 2023
36fb5cc
Move entity logic out of smtp service
JR40159 Sep 15, 2023
d800aa9
Make entityKind internal to the auth mechanism
JR40159 Sep 15, 2023
992f9ea
Add a list of reviewer roles for a release
JR40159 Sep 15, 2023
a1ca487
PR feedback
JR40159 Sep 18, 2023
f5edf09
Remove entities from reviews
JR40159 Sep 18, 2023
045915e
Rename ReviewRequests to Reviews
JR40159 Sep 18, 2023
4deb39b
Remove throwing of error, log it instead
JR40159 Sep 18, 2023
f3a0c5c
Update tests
JR40159 Sep 18, 2023
3bf0f6e
Update prettier and style
JR40159 Sep 18, 2023
a2db73c
Merge branch 'main' into bugfix/BAI-862-create-approval-requests-requ…
JR40159 Sep 18, 2023
c891364
Fix lint issues
JR40159 Sep 18, 2023
03f36af
Update package lock
JR40159 Sep 18, 2023
3196362
Remove commented out line
JR40159 Sep 18, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backend/config/docker_compose.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ module.exports = {
},

app: {
protocol: 'http',
host: 'localhost',
port: 8080,
// Typically generated from `npm run certs`
privateKey: '/certs/key.pem',
publicKey: '/certs/cert.pem',
Expand Down
1,512 changes: 771 additions & 741 deletions backend/package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions backend/src/connectors/v2/authorisation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export abstract class BaseAuthorisationConnector {
action: ReleaseActionKeys,
): Promise<boolean>
abstract getEntities(user: UserDoc): Promise<Array<string>>
abstract getUserInformation(userEntity: string): Promise<{ email: string }>
abstract getUserInformationList(userEntity: string): Promise<Promise<{ email: string }>[]>
abstract getEntityMembers(groupEntity: string): Promise<Array<string>>
}

let authConnector: undefined | BaseAuthorisationConnector = undefined
Expand Down
34 changes: 32 additions & 2 deletions backend/src/connectors/v2/authorisation/silly.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { ModelDoc } from '../../../models/v2/Model.js'
import { ReleaseDoc } from '../../../models/v2/Release.js'
import { UserDoc } from '../../../models/v2/User.js'
import { toEntity } from '../../../utils/v2/entity.js'
import { fromEntity, toEntity } from '../../../utils/v2/entity.js'
import { BaseAuthorisationConnector, ModelActionKeys } from './index.js'

const SillyEntityKind = {
User: 'user',
Group: 'group',
} as const

export class SillyAuthorisationConnector implements BaseAuthorisationConnector {
constructor() {
// do nothing
Expand All @@ -20,6 +25,31 @@ export class SillyAuthorisationConnector implements BaseAuthorisationConnector {
}

async getEntities(user: UserDoc) {
return [toEntity('user', user.dn)]
return [toEntity(SillyEntityKind.User, user.dn)]
}

async getUserInformation(entity: string): Promise<{ email: string }> {
const entityObject = fromEntity(entity)
if (entityObject.kind !== SillyEntityKind.User) {
throw new Error(`Cannot get user information for a non-user entity: ${entity}`)
}
return {
email: `${entityObject.value}@example.com`,
}
}

async getUserInformationList(entity): Promise<Promise<{ email: string }>[]> {
const entities = await this.getEntityMembers(entity)
return entities.map((member) => this.getUserInformation(member))
}

async getEntityMembers(entity: string): Promise<string[]> {
if (fromEntity(entity).kind === SillyEntityKind.User) {
return [entity]
} else if (fromEntity(entity).kind === SillyEntityKind.Group) {
return [toEntity(SillyEntityKind.User, 'user1'), toEntity(SillyEntityKind.User, 'user2')]
} else {
throw new Error(`Unable to get Entity Members. Entity not kind recognised: ${entity}`)
}
}
}
2 changes: 0 additions & 2 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import processDeployments from './processors/processDeployments.js'
import processUploads from './processors/processUploads.js'
import { server } from './routes.js'
import { addDefaultSchemas } from './services/schema.js'
import { addDefaultApprovals } from './services/v2/approval.js'
import { addDefaultSchemas as addDefaultSchemasv2 } from './services/v2/schema.js'
import config from './utils/config.js'
import { connectToMongoose, runMigrations } from './utils/database.js'
Expand Down Expand Up @@ -39,7 +38,6 @@ createSchemaIndexes()
addDefaultSchemas()
if (config.experimental.v2) {
addDefaultSchemasv2()
addDefaultApprovals()
}

await Promise.all([processUploads(), processDeployments()])
Expand Down
46 changes: 0 additions & 46 deletions backend/src/models/v2/Approval.ts

This file was deleted.

74 changes: 74 additions & 0 deletions backend/src/models/v2/Review.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Document, model, Schema } from 'mongoose'
import MongooseDelete from 'mongoose-delete'

import { ReviewKind, ReviewKindKeys } from '../../types/v2/enums.js'

export const Decision = {
RequestChanges: 'request_changes',
Approve: 'approve',
} as const
export type DecisionKeys = (typeof Decision)[keyof typeof Decision]

interface ReviewResponse {
user: string
decision: DecisionKeys
comment: string
}

// This interface stores information about the properties on the base object.
// It should be used for plain object representations, e.g. for sending to the
// client.
export interface ReviewInterface {
semver?: string
modelId?: string
kind: ReviewKindKeys

role: string

responses: Array<ReviewResponse>

createdAt: Date
updatedAt: Date
}

// The doc type includes all values in the plain interface, as well as all the
// properties and functions that Mongoose provides. If a function takes in an
// object from Mongoose it should use this interface
export type ReviewDoc = ReviewInterface & Document<any, any, ReviewInterface>

const ReviewSchema = new Schema<ReviewInterface>(
{
semver: {
type: String,
required: function (this: ReviewInterface): boolean {
return this.kind === ReviewKind.Release
},
},
modelId: { type: String, required: true },
kind: { type: String, enum: Object.values(ReviewKind), required: true },

role: { type: String, required: true },

responses: [
{
user: { type: String, required: true },
decision: { type: String, enum: Object.values(Decision), required: true },
comment: { type: String, required: false },
},
],
},
{
timestamps: true,
collection: 'v2_reviews',
},
)

ReviewSchema.plugin(MongooseDelete, {
overrideMethods: 'all',
deletedBy: true,
deletedByType: String,
})

const ReviewModel = model<ReviewInterface>('v2_Review', ReviewSchema)

export default ReviewModel
11 changes: 4 additions & 7 deletions backend/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ import {
putUpdateLastViewed,
putVersion,
} from './routes/v1/version.js'
import { getApprovals as getApprovalsV2 } from './routes/v2/approval/getApprovals.js'
import { getApprovalsCount as getApprovalsCountV2 } from './routes/v2/approval/getApprovalsCount.js'
import { getComplianceApprovals } from './routes/v2/model/compliance/getComplianceCheckApprovals.js'
import { deleteFile } from './routes/v2/model/file/deleteFile.js'
import { getFiles } from './routes/v2/model/file/getFiles.js'
Expand All @@ -69,6 +67,8 @@ import { deleteRelease } from './routes/v2/release/deleteRelease.js'
import { getRelease } from './routes/v2/release/getRelease.js'
import { getReleases } from './routes/v2/release/getReleases.js'
import { postRelease } from './routes/v2/release/postRelease.js'
import { getReviewsCount } from './routes/v2/review/getReviewCount.js'
import { getReviews } from './routes/v2/review/getReviews.js'
import { getSchema as getSchemaV2 } from './routes/v2/schema/getSchema.js'
import { getSchemas as getSchemasV2 } from './routes/v2/schema/getSchemas.js'
import { postSchema as postSchemaV2 } from './routes/v2/schema/postSchema.js'
Expand Down Expand Up @@ -222,8 +222,8 @@ if (config.experimental.v2) {
server.get('/api/v2/schema/:schemaId', ...getSchemaV2)
server.post('/api/v2/schemas', ...postSchemaV2)

server.get('/api/v2/approvals', ...getApprovalsV2)
server.get('/api/v2/approvals/count', ...getApprovalsCountV2)
server.get('/api/v2/reviews', ...getReviews)
server.get('/api/v2/reviews/count', ...getReviewsCount)

server.get('/api/v2/model/:modelId/roles', ...getModelRoles)
server.get('/api/v2/model/:modelId/roles/mine', ...getModelCurrentUserRoles)
Expand All @@ -238,9 +238,6 @@ if (config.experimental.v2) {
server.get('/api/v2/team/:teamId', ...getTeam)
server.patch('/api/v2/team/:teamId', ...patchTeam)

server.get('/api/v2/approvals', ...getApprovalsV2)
server.get('/api/v2/approvals/count', ...getApprovalsCountV2)

// server.post('/api/v2/teams/:teamId/members', ...postTeamMember)
// server.get('/api/v2/teams/:teamId/members', ...getTeamMembers)
// server.delete('/api/v2/teams/:teamId/members/:memberId', ...deleteTeamMember)
Expand Down
28 changes: 0 additions & 28 deletions backend/src/routes/v2/approval/getApprovals.ts

This file was deleted.

18 changes: 0 additions & 18 deletions backend/src/routes/v2/approval/getApprovalsCount.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import bodyParser from 'body-parser'
import { Request, Response } from 'express'
import { z } from 'zod'

import { ApprovalInterface } from '../../../../models/v2/Approval.js'
import { testReleaseReview, testReleaseReviewWithResponses } from '../../../../../test/testUtils/testModels.js'
import { ReviewInterface } from '../../../../models/v2/Review.js'
import { parse } from '../../../../utils/validate.js'

export const getComplianceApprovalsSchema = z.object({
Expand All @@ -14,7 +15,7 @@ export const getComplianceApprovalsSchema = z.object({
})

interface GetComplianceApprovalsResponse {
approvals: Array<ApprovalInterface>
approvals: Array<ReviewInterface>
}

export const getComplianceApprovals = [
Expand All @@ -23,26 +24,7 @@ export const getComplianceApprovals = [
const _ = parse(req, getComplianceApprovalsSchema)

return res.json({
approvals: [
{
model: 'yolo',
release: '3.0.2',
role: 'owner',
kind: 'access',
active: true,
createdAt: new Date('08/13/2023'),
updatedAt: new Date('08/14/2023'),
},
{
model: 'yolo',
release: '3.0.1',
role: 'owner',
kind: 'access',
active: true,
createdAt: new Date('08/12/2023'),
updatedAt: new Date('08/12/2023'),
},
],
approvals: [testReleaseReviewWithResponses, testReleaseReview],
})
},
]
18 changes: 18 additions & 0 deletions backend/src/routes/v2/review/getReviewCount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import bodyParser from 'body-parser'
import { Request, Response } from 'express'

import { countReviews } from '../../../services/v2/review.js'

interface GetReviewsCountResponse {
count: number
}

export const getReviewsCount = [
bodyParser.json(),
async (req: Request, res: Response<GetReviewsCountResponse>) => {
const count = await countReviews(req.user)
return res.json({
count,
})
},
]
30 changes: 30 additions & 0 deletions backend/src/routes/v2/review/getReviews.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import bodyParser from 'body-parser'
import { Request, Response } from 'express'
import { z } from 'zod'

import { ReviewInterface } from '../../../models/v2/Review.js'
import { findReviewsByActive } from '../../../services/v2/review.js'
import { parse, strictCoerceBoolean } from '../../../utils/v2/validate.js'

export const getReviewSchema = z.object({
query: z.object({
active: strictCoerceBoolean(z.boolean()),
}),
})

interface GetReviewResponse {
reviews: Array<ReviewInterface>
}

export const getReviews = [
bodyParser.json(),
async (req: Request, res: Response<GetReviewResponse>) => {
const {
query: { active },
} = parse(req, getReviewSchema)
const reviews = await findReviewsByActive(req.user, active)
return res.json({
reviews,
})
},
]
Loading