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

Conversation

JR40159
Copy link
Member

@JR40159 JR40159 commented Sep 14, 2023

No description provided.

@@ -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",
Copy link
Member

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

Copy link
Member Author

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) {
Copy link
Member

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.

Copy link
Member Author

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
Copy link
Member

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 },
Copy link
Member

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: {
Copy link
Member

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) {
Copy link
Member

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.

Copy link
Member Author

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) {
Copy link
Member

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.

Copy link
Member

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.

Copy link
Member Author

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) {
Copy link
Member

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.

log: {
level: bunyan.LogLevel
}
}

const config: Config = _config.util.toObject()
deepFreeze(config)
export default config
Copy link
Member

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.

export function toEntity(kind: string, value: string) {
export const EntityKind = {
User: 'user',
Group: 'group',
Copy link
Member

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.

@@ -14,7 +14,7 @@ export interface SchemaInterface {
hidden: boolean

kind: SchemaKindKeys
jsonSchema: unknown
jsonSchema: object
Copy link
Member

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.

Copy link
Member

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)

Copy link
Member Author

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>
Copy link
Member

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) {
Copy link
Member

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)
Copy link
Member

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
}

}

try {
await createReleaseReviewRequests(model, release)
Copy link
Member

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>
Copy link
Member

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.

}
function getEntitiesForRole(collaborators: Array<CollaboratorEntry>, role: string): string[] {
const roleEntities: string[] = collaborators
.filter((collaborator) => {
Copy link
Member

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.

throw BadReq(`The following is not unique: ${JSON.stringify(error.keyValue)}`)
}
throw error
throw handleDuplicateKeys(error)
Copy link
Member

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
Copy link
Member

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.

Copy link
Member

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 {
Copy link
Member

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.

Copy link
Member Author

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

@JR40159 JR40159 merged commit b794839 into main Sep 18, 2023
@JR40159 JR40159 deleted the bugfix/BAI-862-create-approval-requests-requirements-on-release-creation branch September 18, 2023 15:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants