Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
542352c
types(notification-rule): respect new notification model
e11sy Jan 8, 2025
517f057
Bump version up to 1.1.2
github-actions[bot] Jan 8, 2025
626f848
chore(dependencies): update hawk.so/types
e11sy Jan 24, 2025
180f61c
update from master
e11sy Jan 24, 2025
b60695d
Bump version up to 1.1.7
github-actions[bot] Jan 24, 2025
175f109
chore (yarn) update yarn lock file
e11sy Jan 24, 2025
5b0cd5e
Merge branch 'update-notification-rule-types' of https://github.com/c…
e11sy Jan 24, 2025
50c0c57
types(notifications): update typeDefs
e11sy Jan 25, 2025
3824ced
types(resolvers): update projectNotifications types for resolvers
e11sy Jan 25, 2025
a89de19
feat(models): add extra fields for update and create notification rul…
e11sy Jan 25, 2025
8aaf9c7
types(notifications): improve notifications graphql schemas
e11sy Jan 27, 2025
b09de06
imp(resolver): improved notification rules resolver
e11sy Jan 27, 2025
a18a198
fix(resolvers): improve create rule data validation
e11sy Feb 1, 2025
71a362c
imp(resolvers): add channels validation for rules in api resolvers
e11sy Feb 1, 2025
a35446e
clean(resolvers): remove redundant validation
e11sy Feb 1, 2025
988ae1b
Merge branch 'master' into update-notification-rule-types
e11sy Feb 1, 2025
257b519
chore(): lint fix
e11sy Feb 1, 2025
64a4dc5
Merge branch 'update-notification-rule-types' of https://github.com/c…
e11sy Feb 1, 2025
a295403
bump package version
e11sy Feb 2, 2025
4ce6c91
update from main
e11sy Feb 2, 2025
f1ea748
fix(): build fix
e11sy Feb 2, 2025
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hawk.api",
"version": "1.1.11",
"version": "1.1.12",
"main": "index.ts",
"license": "UNLICENSED",
"scripts": {
Expand Down Expand Up @@ -37,7 +37,7 @@
"@graphql-tools/schema": "^8.5.1",
"@graphql-tools/utils": "^8.9.0",
"@hawk.so/nodejs": "^3.1.1",
"@hawk.so/types": "0.1.23",
"@hawk.so/types": "^0.1.26",
"@types/amqp-connection-manager": "^2.0.4",
"@types/bson": "^4.0.5",
"@types/debug": "^4.1.5",
Expand Down
46 changes: 43 additions & 3 deletions src/models/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export interface ProjectNotificationsRuleDBScheme {
uidAdded: ObjectId;

/**
* Receive type: 'ALL' or 'ONLY_NEW'
* Receive type: 'SEEN_MORE' or 'ONLY_NEW'
*/
whatToReceive: ReceiveTypes;

Expand All @@ -42,6 +42,16 @@ export interface ProjectNotificationsRuleDBScheme {
* Available channels to receive
*/
channels: NotificationsChannelsDBScheme;

/**
* If this number of events is reached in the eventThresholdPeriod, the rule will be triggered
*/
threshold?: number;

/**
* Size of period (in milliseconds) to count events to compare to rule threshold
*/
thresholdPeriod?: number;
}

/**
Expand All @@ -51,7 +61,7 @@ export enum ReceiveTypes {
/**
* All notifications
*/
ALL = 'ALL',
SEEN_MORE = 'SEEN_MORE',

/**
* Only first occurrence
Expand All @@ -69,7 +79,7 @@ export interface CreateProjectNotificationsRulePayload {
isEnabled: true;

/**
* Receive type: 'ALL' or 'ONLY_NEW'
* Receive type: 'SEEN_MORE' or 'ONLY_NEW'
*/
whatToReceive: ReceiveTypes;

Expand All @@ -92,6 +102,16 @@ export interface CreateProjectNotificationsRulePayload {
* Available channels to receive
*/
channels: NotificationsChannelsDBScheme;

/**
* If this number of events is reached in the eventThresholdPeriod, the rule will be triggered
*/
threshold?: number;

/**
* Size of period (in milliseconds) to count events to compare to rule threshold
*/
thresholdPeriod?: number;
}

/**
Expand Down Expand Up @@ -127,6 +147,16 @@ interface UpdateProjectNotificationsRulePayload {
* Available channels to receive
*/
channels: NotificationsChannelsDBScheme;

/**
* If this number of events is reached in the eventThresholdPeriod, the rule will be triggered
*/
threshold?: number;

/**
* Size of period (in milliseconds) to count events to compare to rule threshold
*/
thresholdPeriod?: number;
}

/**
Expand Down Expand Up @@ -232,6 +262,11 @@ export default class ProjectModel extends AbstractModel<ProjectDBScheme> impleme
excluding: payload.excluding,
};

if (rule.whatToReceive === ReceiveTypes.SEEN_MORE) {
rule.threshold = payload.threshold;
rule.thresholdPeriod = payload.thresholdPeriod;
}

await this.collection.updateOne({
_id: this._id,
},
Expand Down Expand Up @@ -261,6 +296,11 @@ export default class ProjectModel extends AbstractModel<ProjectDBScheme> impleme
excluding: payload.excluding,
};

if (rule.whatToReceive === ReceiveTypes.SEEN_MORE) {
rule.threshold = payload.threshold;
rule.thresholdPeriod = payload.thresholdPeriod;
}

const result = await this.collection.findOneAndUpdate(
{
_id: this._id,
Expand Down
88 changes: 76 additions & 12 deletions src/resolvers/projectNotifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ interface CreateProjectNotificationsRuleMutationPayload {
* Available channels to receive
*/
channels: NotificationsChannelsDBScheme;

/**
* Threshold to receive notification
*/
threshold: number;

/**
* Period to receive notification
*/
thresholdPeriod: number;
}

/**
Expand Down Expand Up @@ -67,16 +77,50 @@ interface ProjectNotificationsRulePointer {
}

/**
* Return true if all passed channels are empty
* @param channels - project notifications channels
* Returns true is threshold and threshold period are valid
* @param threshold - threshold of the notification rule to be checked
* @param thresholdPeriod - threshold period of the notification rule to be checked
*/
function isChannelsEmpty(channels: NotificationsChannelsDBScheme): boolean {
const notEmptyChannels = Object.entries(channels)
.filter(([_, channel]) => {
return (channel as NotificationsChannelSettingsDBScheme).endpoint.replace(/\s+/, '').trim().length !== 0;
});
function validateNotificationsRuleTresholdAndPeriod(
threshold: ProjectNotificationsRuleDBScheme['threshold'],
thresholdPeriod: ProjectNotificationsRuleDBScheme['thresholdPeriod']
): string | null {
const validThresholdPeriods = [60_000, 3_600_000, 86_400_000, 604_800_000];

if (thresholdPeriod === undefined || !validThresholdPeriods.includes(thresholdPeriod)) {
return 'Threshold period should be one of the following: 60000, 3600000, 86400000, 604800000';
}

if (threshold === undefined || threshold < 1) {
return 'Threshold should be greater than 0';
}

return null;
}

return notEmptyChannels.length === 0;
/**
* Return true if all passed channels are filled with correct endpoints
*/
function validateNotificationsRuleChannels(channels: NotificationsChannelsDBScheme): string | null {
if (channels.email!.isEnabled) {
if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(channels.email!.endpoint)) {
return 'Invalid email endpoint passed';
}
}

if (channels.slack!.isEnabled) {
if (!/^https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9]+\/[A-Za-z0-9]+\/[A-Za-z0-9]+$/.test(channels.slack!.endpoint)) {
return 'Invalid slack endpoint passed';
}
}

if (channels.telegram!.isEnabled) {
if (!/^https:\/\/notify\.bot\.codex\.so\/u\/[A-Za-z0-9]+$/.test(channels.telegram!.endpoint)) {
return 'Invalid telegram endpoint passed';
}
}

return null;
}

/**
Expand All @@ -102,8 +146,18 @@ export default {
throw new ApolloError('No project with such id');
}

if (isChannelsEmpty(input.channels)) {
throw new UserInputError('At least one channel is required');
const channelsValidationResult = validateNotificationsRuleChannels(input.channels);

if (channelsValidationResult !== null) {
throw new UserInputError(channelsValidationResult);
}

if (input.whatToReceive === ReceiveTypes.SEEN_MORE) {
const thresholdValidationResult = validateNotificationsRuleTresholdAndPeriod(input.threshold, input.thresholdPeriod);

if (thresholdValidationResult !== null) {
throw new UserInputError(thresholdValidationResult);
}
}

return project.createNotificationsRule({
Expand All @@ -130,8 +184,18 @@ export default {
throw new ApolloError('No project with such id');
}

if (isChannelsEmpty(input.channels)) {
throw new UserInputError('At least one channel is required');
const channelsValidationResult = validateNotificationsRuleChannels(input.channels);

if (channelsValidationResult !== null) {
throw new UserInputError(channelsValidationResult);
}

if (input.whatToReceive === ReceiveTypes.SEEN_MORE) {
const thresholdValidationResult = validateNotificationsRuleTresholdAndPeriod(input.threshold, input.thresholdPeriod);

if (thresholdValidationResult !== null) {
throw new UserInputError(thresholdValidationResult);
}
}

return project.updateNotificationsRule(input);
Expand Down
14 changes: 12 additions & 2 deletions src/typeDefs/projectNotifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ export default gql`
ONLY_NEW

"""
Receive all events
Receive all events that reached threshold in period
"""
ALL
SEEN_MORE
}

"""
Expand Down Expand Up @@ -49,5 +49,15 @@ export default gql`
Notification channels to recieve events
"""
channels: NotificationsChannels

"""
Threshold to receive notification
"""
threshold: Int

"""
Period to receive notification
"""
thresholdPeriod: Int
}
`;
20 changes: 20 additions & 0 deletions src/typeDefs/projectNotificationsMutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ export default gql`
Notification channels to recieve events
"""
channels: NotificationsChannelsInput!

"""
Threshold to receive notification
"""
threshold: Int

"""
Period to receive notification
"""
thresholdPeriod: Int
}

"""
Expand Down Expand Up @@ -74,6 +84,16 @@ export default gql`
Notification channels to recieve events
"""
channels: NotificationsChannelsInput!

"""
Threshold to receive notification
"""
threshold: Int

"""
Period to receive notification
"""
thresholdPeriod: Int
}

"""
Expand Down
14 changes: 7 additions & 7 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -451,20 +451,20 @@
axios "^0.21.1"
stack-trace "^0.0.10"

"@hawk.so/[email protected]":
version "0.1.23"
resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.1.23.tgz#45dae057fd29d4735a51baa5f00d8e0d245075f4"
integrity sha512-b9W8TZJj6kBh3rVS4tKCmVbM44XJ/Ya8kwXY12QNf5/U4O2iuegIIEcFwr6N10SJI1VbuPLYFrckxt/8ymQScw==
dependencies:
"@types/mongodb" "^3.5.34"

"@hawk.so/types@^0.1.15":
version "0.1.18"
resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.1.18.tgz#746537634756825f066182737429d11ea124d5c5"
integrity sha512-SvECLGmLb5t90OSpk3n8DCjJsUoyjrq/Z6Ioil80tVkbMXRdGjaHZpn/0w1gBqtgNWBfW2cSbsQPqmyDj1NsqQ==
dependencies:
"@types/mongodb" "^3.5.34"

"@hawk.so/types@^0.1.26":
version "0.1.26"
resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.1.26.tgz#780d68c317024cd918011f1edfee4ef4001c4ad6"
integrity sha512-7WYhvfGgb3Q9pj3cWjpIFdcoxKNVsK+iqt1LgFdFqfCyLVLZXo9qxujaoTHB6OlC2IJ7WNjeTDUvb6yD4k+oIw==
dependencies:
"@types/mongodb" "^3.5.34"

"@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
Expand Down
Loading