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

feat/HIT-343-Add-triggers-for-email-notifications #59

Open
wants to merge 15 commits into
base: next-oort
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/const/enumTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,17 @@ export const customNotificationStatus = {
export const customNotificationRecipientsType = {
email: 'email',
userField: 'userField',
emailField: 'userField',
distributionList: 'distributionList',
channel: 'channel',
};

/**
* Enum of custom notification type.
*/
export const customNotificationType = {
email: 'email',
notification: 'notification',
};

/**
Expand Down
1 change: 1 addition & 0 deletions src/const/placeholders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export enum Placeholder {
DATASET = '{{dataset}}',
NOW = '{{now}}',
LAST_UPDATE = '{{lastUpdate}}',
RECORD_ID = '{{recordId}}',
}

/** Regex to detect placeholder usage. */
Expand Down
18 changes: 17 additions & 1 deletion src/models/customNotification.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ export const customNotificationSchema = new Schema(
default: customNotificationLastExecutionStatus.pending,
required: true,
},
onRecordCreation: Boolean,
onRecordUpdate: Boolean,
applicationTrigger: Boolean,
redirect: mongoose.Schema.Types.Mixed,
filter: mongoose.Schema.Types.Mixed,
},
{
timestamps: { createdAt: 'createdAt', updatedAt: 'modifiedAt' },
Expand All @@ -72,7 +77,7 @@ export interface CustomNotification extends Document {
kind: 'CustomNotification';
name: string;
description: string;
schedule: string;
schedule?: string;
notificationType: string;
resource: mongoose.Types.ObjectId;
layout: mongoose.Types.ObjectId;
Expand All @@ -84,4 +89,15 @@ export interface CustomNotification extends Document {
lastExecutionStatus: string;
createdAt?: Date;
modifiedAt?: Date;
onRecordCreation?: boolean;
onRecordUpdate?: boolean; // record deletion counts as an update
applicationTrigger?: boolean;
filter?: any;
redirect?: any;
// redirect?: {
// active: boolean;
// type: string; // 'url' | 'recordIds'
// url?: string;
// recordIds?: string[];
// };
}
25 changes: 24 additions & 1 deletion src/models/notification.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ const notificationSchema = new Schema(
channel: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Channel',
required: true,
},
seenBy: {
type: [mongoose.Schema.Types.ObjectId],
ref: 'User',
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
redirect: mongoose.Schema.Types.Mixed,
},
{
timestamps: { createdAt: 'createdAt', updatedAt: 'modifiedAt' },
Expand All @@ -34,8 +38,27 @@ export interface Notification extends Document {
createdAt: Date;
channel: any;
seenBy: any[];
user?: any;
redirect?: any;
// redirect?: {
// active: boolean;
// type: string; // 'url' | 'recordIds'
// url?: string;
// recordIds?: string[];
// layout?: string;
// resource?: string;
// };
}

notificationSchema.pre('validate', function (next) {
if (!this.channel && !this.user) {
return next(
new Error('At least only field (channels, user) should be populated')
);
}
next();
});

notificationSchema.plugin(accessibleRecordsPlugin);

/** Mongoose notification model definition */
Expand Down
2 changes: 1 addition & 1 deletion src/models/template.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const templateSchema = new Schema(
/** template documents interface declaration */
export interface Template extends Document {
kind: 'Template';
type?: 'email'; // In the case we add other types of templates in the future
type?: 'email' | 'notification'; // In the case we add other types of templates in the future
name?: string;
content?: any;
createdAt?: Date;
Expand Down
24 changes: 19 additions & 5 deletions src/schema/inputs/customNotification.input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,32 @@ import {
GraphQLNonNull,
GraphQLString,
GraphQLID,
GraphQLBoolean,
} from 'graphql';
import GraphQLJSON from 'graphql-type-json';
import { Types } from 'mongoose';

/** Custom Notification type for queries/mutations argument */
export type CustomNotificationArgs = {
name: string;
description?: string;
schedule: string;
schedule?: string;
notificationType: string;
resource: string | Types.ObjectId;
layout: string | Types.ObjectId;
template: string | Types.ObjectId;
recipients: string;
recipientsType: string;
// eslint-disable-next-line @typescript-eslint/naming-convention
notification_status?: string;
onRecordCreation?: boolean;
onRecordUpdate?: boolean;
applicationTrigger?: boolean;
status?: string;
redirect?: {
active: boolean;
type: string; // 'url' | 'recordIds'
url?: string;
recordIds?: string[];
};
};

/** GraphQL custom notification query input type definition */
Expand All @@ -28,13 +38,17 @@ export const CustomNotificationInputType = new GraphQLInputObjectType({
fields: () => ({
name: { type: new GraphQLNonNull(GraphQLString) },
description: { type: GraphQLString },
schedule: { type: new GraphQLNonNull(GraphQLString) },
schedule: { type: GraphQLString },
notificationType: { type: new GraphQLNonNull(GraphQLString) },
resource: { type: new GraphQLNonNull(GraphQLID) },
layout: { type: new GraphQLNonNull(GraphQLID) },
template: { type: new GraphQLNonNull(GraphQLID) },
recipients: { type: new GraphQLNonNull(GraphQLString) },
recipientsType: { type: new GraphQLNonNull(GraphQLString) },
// notification_status: { type: new GraphQLNonNull(GraphQLString) },
onRecordCreation: { type: GraphQLBoolean },
onRecordUpdate: { type: GraphQLBoolean },
applicationTrigger: { type: GraphQLBoolean },
status: { type: new GraphQLNonNull(GraphQLString) },
redirect: { type: GraphQLJSON },
}),
});
10 changes: 7 additions & 3 deletions src/schema/mutation/addCustomNotification.mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,12 @@ export default {
layout: args.notification.layout,
template: args.notification.template,
recipients: args.notification.recipients,
status: args.notification.notification_status,
status: args.notification.status,
recipientsType: args.notification.recipientsType,
onRecordCreation: args.notification.onRecordCreation,
onRecordUpdate: args.notification.onRecordUpdate,
applicationTrigger: args.notification.applicationTrigger,
redirect: args.notification.redirect,
},
},
};
Expand All @@ -78,8 +82,8 @@ export default {
);
const notificationDetail = application.customNotifications.pop();
if (
args.notification.notification_status ===
customNotificationStatus.active
args.notification.schedule &&
args.notification.status === customNotificationStatus.active
) {
scheduleCustomNotificationJob(notificationDetail, application);
}
Expand Down
61 changes: 41 additions & 20 deletions src/schema/mutation/editCustomNotification.mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ import { logger } from '@services/logger.service';
import { graphQLAuthCheck } from '@schema/shared';
import { Types } from 'mongoose';
import { Context } from '@server/apollo/context';
import GraphQLJSON from 'graphql-type-json';

/** Arguments for the editCustomNotification mutation */
type EditCustomNotificationArgs = {
id: string | Types.ObjectId;
application: string;
notification: CustomNotificationArgs;
notification?: CustomNotificationArgs;
triggersFilters?: any;
};

/**
Expand All @@ -32,7 +34,8 @@ export default {
args: {
id: { type: new GraphQLNonNull(GraphQLID) },
application: { type: new GraphQLNonNull(GraphQLID) },
notification: { type: new GraphQLNonNull(CustomNotificationInputType) },
notification: { type: CustomNotificationInputType },
triggersFilters: { type: GraphQLJSON },
},
async resolve(_, args: EditCustomNotificationArgs, context: Context) {
graphQLAuthCheck(context);
Expand Down Expand Up @@ -60,22 +63,38 @@ export default {
}
}
// Save custom notification in application
const update = {
$set: {
'customNotifications.$.name': args.notification.name,
'customNotifications.$.description': args.notification.description,
'customNotifications.$.schedule': args.notification.schedule,
'customNotifications.$.notificationType':
args.notification.notificationType,
'customNotifications.$.resource': args.notification.resource,
'customNotifications.$.layout': args.notification.layout,
'customNotifications.$.template': args.notification.template,
'customNotifications.$.recipients': args.notification.recipients,
'customNotifications.$.status': args.notification.notification_status,
'customNotifications.$.recipientsType':
args.notification.recipientsType,
},
};
let update = {};
if (args.triggersFilters) {
update = {
$set: {
'customNotifications.$.filter': args.triggersFilters,
},
};
} else {
update = {
$set: {
'customNotifications.$.name': args.notification.name,
'customNotifications.$.description': args.notification.description,
'customNotifications.$.schedule': args.notification.schedule,
'customNotifications.$.notificationType':
args.notification.notificationType,
'customNotifications.$.resource': args.notification.resource,
'customNotifications.$.layout': args.notification.layout,
'customNotifications.$.template': args.notification.template,
'customNotifications.$.recipients': args.notification.recipients,
'customNotifications.$.status': args.notification.status,
'customNotifications.$.recipientsType':
args.notification.recipientsType,
'customNotifications.$.onRecordCreation':
args.notification.onRecordCreation,
'customNotifications.$.onRecordUpdate':
args.notification.onRecordUpdate,
'customNotifications.$.applicationTrigger':
args.notification.applicationTrigger,
'customNotifications.$.redirect': args.notification.redirect,
},
};
}

const application = await Application.findOneAndUpdate(
{ _id: args.application, 'customNotifications._id': args.id },
Expand All @@ -87,8 +106,10 @@ export default {
(customNotification) => customNotification.id.toString() === args.id
);
if (
args.notification.notification_status ===
customNotificationStatus.active
(args.notification?.schedule &&
args.notification?.status === customNotificationStatus.active) ||
(args.triggersFilters &&
notificationDetail.status === customNotificationStatus.active)
) {
scheduleCustomNotificationJob(notificationDetail, application);
} else {
Expand Down
3 changes: 2 additions & 1 deletion src/schema/mutation/editResource.mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -852,11 +852,12 @@ export default {
await updateIncrementalIds(resource, args.idShape);
}

return await Resource.findByIdAndUpdate(
const updatedResource = await Resource.findByIdAndUpdate(
args.id,
update.$addToSet ? { $addToSet: update.$addToSet } : {},
{ new: true }
);
return updatedResource;
} catch (err) {
logger.error(err.message, { stack: err.stack });
if (err instanceof GraphQLError) throw err;
Expand Down
1 change: 1 addition & 0 deletions src/schema/mutation/editTemplate.mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default {
$set: {
'templates.$.name': args.template.name,
'templates.$.content': args.template.content,
'templates.$.type': args.template.type,
},
};

Expand Down
6 changes: 4 additions & 2 deletions src/schema/query/notifications.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export default {
: {};

let items: any[] = await Notification.find({
$and: [cursorFilters, ...filters],
$or: [{ $and: [cursorFilters, ...filters] }, { user: context.user }],
})
.sort({ createdAt: -1 })
.limit(first + 1);
Expand All @@ -74,7 +74,9 @@ export default {
endCursor: edges.length > 0 ? edges[edges.length - 1].cursor : null,
},
edges,
totalCount: await Notification.countDocuments({ $and: filters }),
totalCount: await Notification.countDocuments({
$or: [{ $and: filters }, { user: context.user }],
}),
};
} catch (err) {
logger.error(err.message, { stack: err.stack });
Expand Down
5 changes: 5 additions & 0 deletions src/schema/types/customNotification.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ export const CustomNotificationType = new GraphQLObjectType({
modifiedAt: { type: GraphQLString },
status: { type: GraphQLString },
recipientsType: { type: GraphQLString },
onRecordCreation: { type: GraphQLBoolean },
onRecordUpdate: { type: GraphQLBoolean },
applicationTrigger: { type: GraphQLBoolean },
filter: { type: GraphQLJSON },
redirect: { type: GraphQLJSON },
}),
});

Expand Down
2 changes: 2 additions & 0 deletions src/schema/types/notification.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export const NotificationType = new GraphQLObjectType({
return users;
},
},
user: { type: UserType },
redirect: { type: GraphQLJSON },
}),
});

Expand Down
8 changes: 8 additions & 0 deletions src/schema/types/record.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ export const RecordType = new GraphQLObjectType({
}
},
},
userCanEdit: {
type: GraphQLBoolean,
async resolve(parent, args, context) {
const parentForm: Form = await Form.findById(parent.form);
const ability = await extendAbilityForRecords(context.user, parentForm);
return ability.can('update', parent);
},
},
validationErrors: {
type: new GraphQLList(
new GraphQLObjectType({
Expand Down
Loading