-
Notifications
You must be signed in to change notification settings - Fork 6
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
aid alarms-handler testability by making the main logic a more pure function #2635
Conversation
export type AlarmMappings = { | ||
getTeams: (appName?: string) => Team[]; | ||
getTeamWebhookUrl: (team: Team) => 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.
in the previous PR I did a class as that gives you the type for free, but I wasn't sure how to do similar as function based (assuming this is even what was meant) - perhaps there's a typeof trick I should be using rather than replicating the function signatures here?
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 defining the type to be returned by the function here is good.
Technically this could be an interface though.
Regarding "replicating the function signatures" - I think what you have here is fine. It is possible to reference the type of a field as follows (though I don't think it's necessary here):
const getTeams: AlarmMappings['getTeams'] = (appName) => {...}
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.
thanks Tom for the link and I will leave it as it is then, I agree on the referencing thing.
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.
the changes in this file are simply to make the mappings parameterised rather than being a singleton.
@@ -62,6 +63,25 @@ describe('Handler', () => { | |||
expect(getAppNameTag).toHaveBeenCalledWith('mock-arn', '111111'); | |||
expect(fetch).toHaveBeenCalledWith(mockEnv.SRE_WEBHOOK, expect.any(Object)); | |||
}); | |||
it('should handle captured CloudWatch alarm message', async () => { | |||
(getAppNameTag as jest.Mock).mockResolvedValueOnce('mock-app'); |
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 still have one "mocked" function but it's more minor functionality and doesn't lend itself to parameterising as easily as the alarm mappings.
… a more pure function
8f1bc45
to
98c37b6
Compare
handlers/alarms-handler/src/index.ts
Outdated
export async function getChatMessages( | ||
record: SQSRecord, | ||
alarmMappings: AlarmMappings, | ||
) { |
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.
return type?
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.
it's Promise<{webhookUrls: string[], text: string} | undefined>
Last year someone in the team told me in typescript you are supposed to avoid specifying return types as it can break your code at run time. Someone sent me some videos about it which I watched, but I forgot the exact reasoning, and can't find them any more. Perhaps some of these? https://marcoheine.com/today-i-learned/dont-use-return-types-in-typescript
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've not heard that before...
In general if the return type isn't obvious then it's helpful to other humans to specify it. In this case it wasn't obvious to me
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.
great in that case I've added it in!
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.
aha here was the original note suggesting to avoid them which I clearly took some time to digest! #2317 (comment)
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 looks good! I left a bunch of pretty minor comments/suggestions to take or leave.
}; | ||
|
||
export const ProdAlarmMappings = buildAlarmMappings(teamToAppMappings); |
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.
Maybe just me, but I feel like it's slightly unconventional to have this be camel case with the first letter in caps (which makes me think it's a class name or even a type). I'd probably go for prodAlarmMappings
. Obviously a very minor thing so no big deal either way!
} else { | ||
return undefined; | ||
} |
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.
Totally subjective, but we don't need this else block I don't think. With no explicit return undefined
will be returned implicitly.
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.
got it, so it's like of like mapping an Option in scala
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.
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.
Oh weird sorry about that. Maybe it's the fact that the return value is wrapped in a promise? Best to leave it as is (which I think you've done!)
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.
record: SQSRecord, | ||
alarmMappings: AlarmMappings, | ||
): Promise<{ webhookUrls: string[]; text: string } | undefined> { | ||
console.log('sqsRecord', record); |
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.
Should these console.log
s be removed before merging?
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 intentionally left them as I think this is the correct way to log things in cloudwatch in a JS lambda, if that's right?
handlers/alarms-handler/src/index.ts
Outdated
|
||
let message; | ||
if (parsedMessage) { | ||
message = await handleCloudWatchAlarmMessage({ message: parsedMessage }); |
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 wonder if handle
is the right prefix for this method now? To me it kind of implies actually doing something with the message. What about buildCloudwatchAlarmMessage
?
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.
good spot, same for buildSnsPublishMessage
handlers/alarms-handler/src/index.ts
Outdated
|
||
if (message) { | ||
const teams = alarmMappings.getTeams(message.app); | ||
console.log('sending message to teams', teams); |
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 we keep this log line, what about mapping message to teams
since we aren't actually sending it here?
console.log('sending message to teams', teams); | |
console.log('mapping message to teams', teams); |
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.
thanks, went for
console.log(
app ${message.app} is assigned to teams ${teams.join(', ')});
in the end
handlers/alarms-handler/src/index.ts
Outdated
}); | ||
}), | ||
); | ||
return { app, text }; |
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.
Totally subjective, and very minor, but rather than assigning to a new const I'd probably just map from one name to the other in the return:
return { app, text }; | |
return { app, text: message }; |
Thanks this is exactly the kind of thing that's useful, I've made all your suggestions apart from the undefined one as that broke the type checker/linter. In retrospect I think it's kind of nice to be explicit, in scala I'd use an Option but I don't think that's usual TS. |
A while ago I proposed a new feature in alarms handler to send relevant log links to chat as well.
I made a PR but it got very cluttered with pre-refactoring so I will pull out individual changes to make it easier to review.
This is the first PR
Second PR is here: #2636
Third PR is here: #2641
Regarding this PR
Tested in CODE and still sends alarms