Skip to content

Commit fe24cb3

Browse files
authored
Merge pull request #448 from codex-team/update-notification-rule-types
feat(notification-rule): implement new notification pattern
2 parents ac7755a + f1ea748 commit fe24cb3

File tree

6 files changed

+160
-26
lines changed

6 files changed

+160
-26
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hawk.api",
3-
"version": "1.1.11",
3+
"version": "1.1.12",
44
"main": "index.ts",
55
"license": "UNLICENSED",
66
"scripts": {
@@ -37,7 +37,7 @@
3737
"@graphql-tools/schema": "^8.5.1",
3838
"@graphql-tools/utils": "^8.9.0",
3939
"@hawk.so/nodejs": "^3.1.1",
40-
"@hawk.so/types": "0.1.23",
40+
"@hawk.so/types": "^0.1.26",
4141
"@types/amqp-connection-manager": "^2.0.4",
4242
"@types/bson": "^4.0.5",
4343
"@types/debug": "^4.1.5",

src/models/project.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export interface ProjectNotificationsRuleDBScheme {
2424
uidAdded: ObjectId;
2525

2626
/**
27-
* Receive type: 'ALL' or 'ONLY_NEW'
27+
* Receive type: 'SEEN_MORE' or 'ONLY_NEW'
2828
*/
2929
whatToReceive: ReceiveTypes;
3030

@@ -42,6 +42,16 @@ export interface ProjectNotificationsRuleDBScheme {
4242
* Available channels to receive
4343
*/
4444
channels: NotificationsChannelsDBScheme;
45+
46+
/**
47+
* If this number of events is reached in the eventThresholdPeriod, the rule will be triggered
48+
*/
49+
threshold?: number;
50+
51+
/**
52+
* Size of period (in milliseconds) to count events to compare to rule threshold
53+
*/
54+
thresholdPeriod?: number;
4555
}
4656

4757
/**
@@ -51,7 +61,7 @@ export enum ReceiveTypes {
5161
/**
5262
* All notifications
5363
*/
54-
ALL = 'ALL',
64+
SEEN_MORE = 'SEEN_MORE',
5565

5666
/**
5767
* Only first occurrence
@@ -69,7 +79,7 @@ export interface CreateProjectNotificationsRulePayload {
6979
isEnabled: true;
7080

7181
/**
72-
* Receive type: 'ALL' or 'ONLY_NEW'
82+
* Receive type: 'SEEN_MORE' or 'ONLY_NEW'
7383
*/
7484
whatToReceive: ReceiveTypes;
7585

@@ -92,6 +102,16 @@ export interface CreateProjectNotificationsRulePayload {
92102
* Available channels to receive
93103
*/
94104
channels: NotificationsChannelsDBScheme;
105+
106+
/**
107+
* If this number of events is reached in the eventThresholdPeriod, the rule will be triggered
108+
*/
109+
threshold?: number;
110+
111+
/**
112+
* Size of period (in milliseconds) to count events to compare to rule threshold
113+
*/
114+
thresholdPeriod?: number;
95115
}
96116

97117
/**
@@ -127,6 +147,16 @@ interface UpdateProjectNotificationsRulePayload {
127147
* Available channels to receive
128148
*/
129149
channels: NotificationsChannelsDBScheme;
150+
151+
/**
152+
* If this number of events is reached in the eventThresholdPeriod, the rule will be triggered
153+
*/
154+
threshold?: number;
155+
156+
/**
157+
* Size of period (in milliseconds) to count events to compare to rule threshold
158+
*/
159+
thresholdPeriod?: number;
130160
}
131161

132162
/**
@@ -232,6 +262,11 @@ export default class ProjectModel extends AbstractModel<ProjectDBScheme> impleme
232262
excluding: payload.excluding,
233263
};
234264

265+
if (rule.whatToReceive === ReceiveTypes.SEEN_MORE) {
266+
rule.threshold = payload.threshold;
267+
rule.thresholdPeriod = payload.thresholdPeriod;
268+
}
269+
235270
await this.collection.updateOne({
236271
_id: this._id,
237272
},
@@ -261,6 +296,11 @@ export default class ProjectModel extends AbstractModel<ProjectDBScheme> impleme
261296
excluding: payload.excluding,
262297
};
263298

299+
if (rule.whatToReceive === ReceiveTypes.SEEN_MORE) {
300+
rule.threshold = payload.threshold;
301+
rule.thresholdPeriod = payload.thresholdPeriod;
302+
}
303+
264304
const result = await this.collection.findOneAndUpdate(
265305
{
266306
_id: this._id,

src/resolvers/projectNotifications.ts

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ interface CreateProjectNotificationsRuleMutationPayload {
3939
* Available channels to receive
4040
*/
4141
channels: NotificationsChannelsDBScheme;
42+
43+
/**
44+
* Threshold to receive notification
45+
*/
46+
threshold: number;
47+
48+
/**
49+
* Period to receive notification
50+
*/
51+
thresholdPeriod: number;
4252
}
4353

4454
/**
@@ -67,16 +77,50 @@ interface ProjectNotificationsRulePointer {
6777
}
6878

6979
/**
70-
* Return true if all passed channels are empty
71-
* @param channels - project notifications channels
80+
* Returns true is threshold and threshold period are valid
81+
* @param threshold - threshold of the notification rule to be checked
82+
* @param thresholdPeriod - threshold period of the notification rule to be checked
7283
*/
73-
function isChannelsEmpty(channels: NotificationsChannelsDBScheme): boolean {
74-
const notEmptyChannels = Object.entries(channels)
75-
.filter(([_, channel]) => {
76-
return (channel as NotificationsChannelSettingsDBScheme).endpoint.replace(/\s+/, '').trim().length !== 0;
77-
});
84+
function validateNotificationsRuleTresholdAndPeriod(
85+
threshold: ProjectNotificationsRuleDBScheme['threshold'],
86+
thresholdPeriod: ProjectNotificationsRuleDBScheme['thresholdPeriod']
87+
): string | null {
88+
const validThresholdPeriods = [60_000, 3_600_000, 86_400_000, 604_800_000];
89+
90+
if (thresholdPeriod === undefined || !validThresholdPeriods.includes(thresholdPeriod)) {
91+
return 'Threshold period should be one of the following: 60000, 3600000, 86400000, 604800000';
92+
}
93+
94+
if (threshold === undefined || threshold < 1) {
95+
return 'Threshold should be greater than 0';
96+
}
97+
98+
return null;
99+
}
78100

79-
return notEmptyChannels.length === 0;
101+
/**
102+
* Return true if all passed channels are filled with correct endpoints
103+
*/
104+
function validateNotificationsRuleChannels(channels: NotificationsChannelsDBScheme): string | null {
105+
if (channels.email!.isEnabled) {
106+
if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(channels.email!.endpoint)) {
107+
return 'Invalid email endpoint passed';
108+
}
109+
}
110+
111+
if (channels.slack!.isEnabled) {
112+
if (!/^https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9]+\/[A-Za-z0-9]+\/[A-Za-z0-9]+$/.test(channels.slack!.endpoint)) {
113+
return 'Invalid slack endpoint passed';
114+
}
115+
}
116+
117+
if (channels.telegram!.isEnabled) {
118+
if (!/^https:\/\/notify\.bot\.codex\.so\/u\/[A-Za-z0-9]+$/.test(channels.telegram!.endpoint)) {
119+
return 'Invalid telegram endpoint passed';
120+
}
121+
}
122+
123+
return null;
80124
}
81125

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

105-
if (isChannelsEmpty(input.channels)) {
106-
throw new UserInputError('At least one channel is required');
149+
const channelsValidationResult = validateNotificationsRuleChannels(input.channels);
150+
151+
if (channelsValidationResult !== null) {
152+
throw new UserInputError(channelsValidationResult);
153+
}
154+
155+
if (input.whatToReceive === ReceiveTypes.SEEN_MORE) {
156+
const thresholdValidationResult = validateNotificationsRuleTresholdAndPeriod(input.threshold, input.thresholdPeriod);
157+
158+
if (thresholdValidationResult !== null) {
159+
throw new UserInputError(thresholdValidationResult);
160+
}
107161
}
108162

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

133-
if (isChannelsEmpty(input.channels)) {
134-
throw new UserInputError('At least one channel is required');
187+
const channelsValidationResult = validateNotificationsRuleChannels(input.channels);
188+
189+
if (channelsValidationResult !== null) {
190+
throw new UserInputError(channelsValidationResult);
191+
}
192+
193+
if (input.whatToReceive === ReceiveTypes.SEEN_MORE) {
194+
const thresholdValidationResult = validateNotificationsRuleTresholdAndPeriod(input.threshold, input.thresholdPeriod);
195+
196+
if (thresholdValidationResult !== null) {
197+
throw new UserInputError(thresholdValidationResult);
198+
}
135199
}
136200

137201
return project.updateNotificationsRule(input);

src/typeDefs/projectNotifications.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ export default gql`
1111
ONLY_NEW
1212
1313
"""
14-
Receive all events
14+
Receive all events that reached threshold in period
1515
"""
16-
ALL
16+
SEEN_MORE
1717
}
1818
1919
"""
@@ -49,5 +49,15 @@ export default gql`
4949
Notification channels to recieve events
5050
"""
5151
channels: NotificationsChannels
52+
53+
"""
54+
Threshold to receive notification
55+
"""
56+
threshold: Int
57+
58+
"""
59+
Period to receive notification
60+
"""
61+
thresholdPeriod: Int
5262
}
5363
`;

src/typeDefs/projectNotificationsMutations.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ export default gql`
3434
Notification channels to recieve events
3535
"""
3636
channels: NotificationsChannelsInput!
37+
38+
"""
39+
Threshold to receive notification
40+
"""
41+
threshold: Int
42+
43+
"""
44+
Period to receive notification
45+
"""
46+
thresholdPeriod: Int
3747
}
3848
3949
"""
@@ -74,6 +84,16 @@ export default gql`
7484
Notification channels to recieve events
7585
"""
7686
channels: NotificationsChannelsInput!
87+
88+
"""
89+
Threshold to receive notification
90+
"""
91+
threshold: Int
92+
93+
"""
94+
Period to receive notification
95+
"""
96+
thresholdPeriod: Int
7797
}
7898
7999
"""

yarn.lock

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -451,20 +451,20 @@
451451
axios "^0.21.1"
452452
stack-trace "^0.0.10"
453453

454-
"@hawk.so/[email protected]":
455-
version "0.1.23"
456-
resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.1.23.tgz#45dae057fd29d4735a51baa5f00d8e0d245075f4"
457-
integrity sha512-b9W8TZJj6kBh3rVS4tKCmVbM44XJ/Ya8kwXY12QNf5/U4O2iuegIIEcFwr6N10SJI1VbuPLYFrckxt/8ymQScw==
458-
dependencies:
459-
"@types/mongodb" "^3.5.34"
460-
461454
"@hawk.so/types@^0.1.15":
462455
version "0.1.18"
463456
resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.1.18.tgz#746537634756825f066182737429d11ea124d5c5"
464457
integrity sha512-SvECLGmLb5t90OSpk3n8DCjJsUoyjrq/Z6Ioil80tVkbMXRdGjaHZpn/0w1gBqtgNWBfW2cSbsQPqmyDj1NsqQ==
465458
dependencies:
466459
"@types/mongodb" "^3.5.34"
467460

461+
"@hawk.so/types@^0.1.26":
462+
version "0.1.26"
463+
resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.1.26.tgz#780d68c317024cd918011f1edfee4ef4001c4ad6"
464+
integrity sha512-7WYhvfGgb3Q9pj3cWjpIFdcoxKNVsK+iqt1LgFdFqfCyLVLZXo9qxujaoTHB6OlC2IJ7WNjeTDUvb6yD4k+oIw==
465+
dependencies:
466+
"@types/mongodb" "^3.5.34"
467+
468468
"@istanbuljs/load-nyc-config@^1.0.0":
469469
version "1.1.0"
470470
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"

0 commit comments

Comments
 (0)