-
Notifications
You must be signed in to change notification settings - Fork 15
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
Bugfix/bai 862 request reviews on release creation #733
Conversation
backend/package.json
Outdated
@@ -106,7 +106,7 @@ | |||
"eslint-plugin-simple-import-sort": "^10.0.0", | |||
"mongodb-memory-server": "^8.13.0", | |||
"nodemon": "^3.0.1", | |||
"prettier": "3.0.3", | |||
"prettier": "2.8.8", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should just update everything to 3.0.3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm just avoiding the extra styling while I'm still deving, I'll add it back once I'm done :)
} | ||
|
||
async getGroupMembers(entity: string): Promise<string[]> { | ||
if (fromEntity(entity).kind !== EntityKind.User) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong, surely the entity HAS to not be a user to get the members of it. I think you want the inverse of this conditional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy and paste error, good spot
modelId?: string | ||
kind: ReviewKindKeys | ||
|
||
entity: CollaboratorEntry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be a collaborator entry. It should just be a string I think. There should be another entry on this for the type of review it is (MSRO, MTR, etc). A collaborator entry could have multiple roles on it, including roles that don't have approvals.
|
||
const ReviewRequestSchema = new Schema<ReviewRequestInterface>( | ||
{ | ||
model: { type: String, required: true }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have both model and model id on this model. Pick one! Probably modelId
.
}, | ||
kind: { type: String, enum: Object.values(ReviewKind), required: true }, | ||
|
||
entity: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Talked about this over call. Entities should likely be an array, roles should be singular.
const text = email.getBody(release.name, reviewRequest.kind, release.modelId, appBaseUrl, 'unknown') | ||
const html = email.getHtml(release.name, reviewRequest.kind, release.modelId, appBaseUrl, 'unknown') | ||
|
||
if (!config.smtp.enabled) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should do this check at the start of the function, before we've tried to get every users email, the list of users to send to, created the email, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you look in the if statement, you'll see, as with the original implementation, we log out the subject and the address. But I do think the subject and the address is pretty irrelevant if smtp has been disabled, so I'll update this
return | ||
} | ||
|
||
if (!transporter) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating a transport is not async. There's no reason we need to do this inside the function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if it were async, we have top level await now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is so we only need to create a transporter once as a pose to every time we call this function?
`) | ||
} | ||
|
||
getBody(releaseName: string, reviewKind: ReviewKindKeys, modelId: string, baseUrl: string, author: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be called getText
, as it's the text mime. Both this and the HTML are technically the body.
backend/src/utils/v2/config.ts
Outdated
log: { | ||
level: bunyan.LogLevel | ||
} | ||
} | ||
|
||
const config: Config = _config.util.toObject() | ||
deepFreeze(config) | ||
export default config |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: export default deepFreeze(config)
might be clearer.
backend/src/utils/v2/entity.ts
Outdated
export function toEntity(kind: string, value: string) { | ||
export const EntityKind = { | ||
User: 'user', | ||
Group: 'group', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this can be an enum, necessarily. Systems can generate their own entity kinds, e.g. we'll have ones specific to our internal names.
…irements-on-release-creation
backend/src/models/v2/Schema.ts
Outdated
@@ -14,7 +14,7 @@ export interface SchemaInterface { | |||
hidden: boolean | |||
|
|||
kind: SchemaKindKeys | |||
jsonSchema: unknown | |||
jsonSchema: object |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
object
is really not a good type in TypeScript. Here, we'll want a partially typed interface, something like:
interface UploadSchema {
properties: {
highLevelDetails: {
title: string
description: string
type: string
properties: {
name: {
title: string,
description: string,
[x: string]: unknown
},
[x: string]: unknown
}
[x: string]: unknown
}
[x: string]: unknown
}
[x: string]: unknown
}
It's not particularly nice, but we've run into some issues with schemas
being any
in v1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the above interface is bad, you can probably use some types from 'json-schema' to help out (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/json-schema/index.d.ts)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is out of scope for this PR. I'll revert back to unknown
@@ -13,16 +13,18 @@ export const getReviewRequestsSchema = z.object({ | |||
}) | |||
|
|||
interface GetReviewRequestsResponse { | |||
approvals: Array<ReviewRequestInterface> | |||
reviewRequests: Array<ReviewRequestInterface> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch!
@@ -11,3 +12,10 @@ export function isMongoServerError(err: unknown): err is MongoServerError { | |||
|
|||
return false | |||
} | |||
|
|||
export function handleDuplicateKeys(error: unknown) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably more reusable as:
if (isMongoServerError(error) && error.code == 11000) {
throw BadReq(`The following is not unique: ${JSON.stringify(error.keyValue)}`)
}
Without handling the case of it not being a duplicate key, and immediately throwing the error. It's rarely useful to return errors.
await release.save() | ||
} catch(error) { | ||
handleDuplicateKeys(error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're not throwing anything here. I've suggested some changes to the utility, but this should still throw other errors, e.g:
catch(error) {
handleDuplicateKeys(error)
throw error
}
backend/src/services/v2/release.ts
Outdated
} | ||
|
||
try { | ||
await createReleaseReviewRequests(model, release) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this valid indentation? I can't believe it doesn't want await create...
to be indented twice more.
semver?: string | ||
modelId?: string | ||
kind: ReviewKindKeys | ||
|
||
entity: CollaboratorEntry | ||
role: string | ||
entities: Array<string> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in chat, we don't want entities
to be on the review request.
backend/src/services/v2/review.ts
Outdated
} | ||
function getEntitiesForRole(collaborators: Array<CollaboratorEntry>, role: string): string[] { | ||
const roleEntities: string[] = collaborators | ||
.filter((collaborator) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.filter((collaborator) => collaborator.roles.includes(role)
is better.
backend/src/services/v2/schema.ts
Outdated
throw BadReq(`The following is not unique: ${JSON.stringify(error.keyValue)}`) | ||
} | ||
throw error | ||
throw handleDuplicateKeys(error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With other suggested change to handleDuplicateKeys
this becomes:
handleDuplicateKeys(error)
throw error
getSubject(resourceName: string): string | ||
getHtml(releaseName: string, reviewKind: ReviewKindKeys, modelId: string, baseUrl: string, author: string): string | ||
getBody(releaseName: string, reviewKind: ReviewKindKeys, modelId: string, baseUrl: string, author: string): string | ||
to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should be typed, e.g. as string
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you need this interface for anything. You can use the class
to automatically generate these types. Just add these as abstract functions to your class, and add the types in. Then you can just use the class and not this interface. See our v2 authorization class as an example.
getSubject(resourceName: string): string { | ||
return dedent(` | ||
You have been requested to review '${resourceName}' on Bailo | ||
export class ReleaseReviewRequestEmail extends BaseEmailTemplate implements IEmailTemplate { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems over complicated. I think I now understand why you're using both extends
and implements
at the same time.
interface Email {
subject: string
text: string
html: string
to: string
}
function emailWrapper(body: string) {
...
}
function sendEmail(email: Email) {
...
}
function sendReviewRequestNotifications(to: string, model: ModelDoc, ...) {
const subject = `${reviewerRole.toUpperCase()}: You have been requested to review '${resourceName}' on Bailo`
const text = dedent(`
// some text
`)
const html = emailWrapper(dedent(`
// some html
`))
await sendEmail({ to, subject, html, text })
}
Seems much more understandable, without losing any of our type safety.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may be misunderstanding what you're suggesting here but it seems like you're wanting to include all of the static email content like the HTML inside sendReviewRequestNotifications
which is in the smtp
service? I think this is a bad idea and the static content specific to our different emails should not be included in the generic smtp
service
No description provided.