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: Add support for Action Items #1648

Merged
merged 35 commits into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8e7f186
feat: Add support for Categories
meetulr Jan 3, 2024
7ae756d
feat: Add support for Action Items
meetulr Jan 4, 2024
dad9f93
minor corrections
meetulr Jan 5, 2024
ddb61d0
Add Organization-Category two way relationship
meetulr Jan 5, 2024
77f498b
Add Event-ActionItem two way relationship
meetulr Jan 5, 2024
7a29e65
Add cascade delete functionality on Organization deletion
meetulr Jan 5, 2024
1fe4c9f
Add tests for Categories
meetulr Jan 6, 2024
991ea31
Add tests for Action Items
meetulr Jan 7, 2024
7aa48a4
fix typos in comments
meetulr Jan 7, 2024
bc3cbc3
Merge remote-tracking branch 'upstream/develop' into develop
meetulr Jan 7, 2024
b1c9ac3
Add check for action item assignee being an organization member
meetulr Jan 8, 2024
bd6833f
update to more meaningful field names in mongoose schema
meetulr Jan 8, 2024
eddf670
remove the updatedBy field
meetulr Jan 8, 2024
385b816
Merge remote-tracking branch 'upstream/develop' into develop
meetulr Jan 10, 2024
794d15d
remove schema.graphql
meetulr Jan 10, 2024
cd02629
restore schema.graphql to upstream/develop
meetulr Jan 10, 2024
dc7180e
update field name and make resolvers nullable
meetulr Jan 11, 2024
72603ea
Merge remote-tracking branch 'upstream/develop' into develop
meetulr Jan 11, 2024
40a107e
merge upstream/develop into develop
meetulr Jan 12, 2024
a048bb4
Merge remote-tracking branch 'upstream/develop' into develop
meetulr Jan 12, 2024
24ac7fb
Merge remote-tracking branch 'upstream/develop' into develop
meetulr Jan 17, 2024
a511465
update generatedGraphqlTypes
meetulr Jan 17, 2024
4fc5fe7
change Category name to ActionItemCategory
meetulr Jan 22, 2024
cc13bcf
update field names
meetulr Jan 22, 2024
9298ace
remove redundant relationships from Organization and Event
meetulr Jan 22, 2024
a74d184
making actionItemCategory name unique for a given Organization
meetulr Jan 22, 2024
7455a81
minor correction
meetulr Jan 22, 2024
e3619b3
update Action Item inputs
meetulr Jan 26, 2024
5657fa0
add actionItemsByOrganization query
meetulr Jan 27, 2024
0476a35
add constant for milliseconds in a week
meetulr Jan 28, 2024
51ca629
Merge branch 'develop' of https://github.com/PalisadoesFoundation/tal…
meetulr Jan 28, 2024
76e6982
restore unwanted changes
meetulr Jan 28, 2024
0d56d46
Revert "add constant for milliseconds in a week"
meetulr Jan 28, 2024
256af0e
Merge branch 'develop' of https://github.com/PalisadoesFoundation/tal…
meetulr Jan 28, 2024
9577a59
add constant for milliseconds in a week
meetulr Jan 28, 2024
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
5 changes: 5 additions & 0 deletions codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ const config: CodegenConfig = {
// functionality is useful because what we retrieve from the database and what we choose to return from a graphql server
// could be completely different fields. Address to models here is relative to the location of generated types.
mappers: {
ActionItem: "../models/ActionItem#InterfaceActionItem",

ActionItemCategory:
"../models/ActionItemCategory#InterfaceActionItemCategory",

CheckIn: "../models/CheckIn#InterfaceCheckIn",

MessageChat: "../models/MessageChat#InterfaceMessageChat",
Expand Down
3 changes: 3 additions & 0 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"user.notFound": "User not found",
"user.alreadyMember": "User is already a member",
"user.profileImage.notFound": "User profile image not found",
"actionItemCategory.notFound": "Action Item Category not found",
"actionItemCategory.alreadyExists": "Action Item Category already exists",
"actionItem.notFound": "Action Item not found",
"advertisement.notFound": "Advertisement not found",
"event.notFound": "Event not found",
"organization.notFound": "Organization not found",
Expand Down
3 changes: 3 additions & 0 deletions locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"user.notFound": "Utilisateur introuvable",
"user.alreadyMember": "L'utilisateur est déjà membre",
"user.profileImage.notFound": "Image du profil utilisateur introuvable",
"actionItemCategory.notFound": "Catégorie d’élément d’action introuvable",
"actionItemCategory.alreadyExists": "La catégorie d’élément d’action existe déjà",
"actionItem.notFound": "Élément d\\’action non trouvé",
"event.notFound": "Événement non trouvé",
"organization.notFound": "Organisation introuvable",
"organization.profileImage.notFound": "Image du profil de l'organisation introuvable",
Expand Down
3 changes: 3 additions & 0 deletions locales/hi.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"user.notFound": "उपयोगकर्ता नहीं मिला",
"user.alreadyMember": "उपयोगकर्ता पहले से ही एक सदस्य है",
"user.profileImage.notFound": "उपयोगकर्ता प्रोफ़ाइल छवि नहीं मिली",
"actionItemCategory.notFound": "श्रेणी नहीं मिली",
"actionItemCategory.alreadyExists": "यह श्रेणी पहले से मौजूद है",
"actionItem.notFound": "कार्रवाई का मद नहीं मिला",
"advertisement.notFound": "विज्ञापन नहीं मिला",
"event.notFound": "घटना नहीं मिली",
"organization.notFound": "संगठन नहीं मिला",
Expand Down
3 changes: 3 additions & 0 deletions locales/sp.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"user.notFound": "Usuario no encontrado",
"user.alreadyMember": "El usuario ya es miembro",
"user.profileImage.notFound": "No se encontró la imagen de perfil de usuario",
"actionItemCategory.notFound": "No se encontró la categoría de elemento de acción",
"actionItemCategory.alreadyExists": "Ya existe una categoría de elemento de acción",
"actionItem.notFound": "Elemento de acción no encontrado",
"event.notFound": "Evento no encontrado",
"organization.notFound": "Organización no encontrada",
"organization.profileImage.notFound": "No se encontró la imagen del perfil de la organización",
Expand Down
3 changes: 3 additions & 0 deletions locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"user.notFound": "找不到用戶",
"user.alreadyMember": "用戶已經是會員",
"user.profileImage.notFound": "未找到用戶個人資料圖像",
"actionItemCategory.notFound": "未找到措施项类别",
"actionItemCategory.alreadyExists": "措施项类别已存在",
"actionItem.notFound": "找不到操作项",
"event.notFound": "未找到事件",
"organization.notFound": "未找到組織",
"organization.profileImage.notFound": "未找到組織檔案圖像",
Expand Down
23 changes: 23 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,27 @@ if (!issues) {
ENV = envSchema.parse(process.env);
}

export const ACTION_ITEM_NOT_FOUND_ERROR = {
DESC: "ActionItem not found",
CODE: "actionItem.notFound",
MESSAGE: "actionItem.notFound",
PARAM: "actionItem",
};

export const ACTION_ITEM_CATEGORY_NOT_FOUND_ERROR = {
DESC: "ActionItemCategory not found",
CODE: "actionItemCategory.notFound",
MESSAGE: "actionItemCategory.notFound",
PARAM: "actionItemCategory",
};

export const ACTION_ITEM_CATEGORY_ALREADY_EXISTS = {
DESC: "Action Item Category already exists",
CODE: "actionItemCategory.alreadyExists",
MESSAGE: "actionItemCategory.alreadyExists",
PARAM: "actionItemCategory",
};

export const CHAT_NOT_FOUND_ERROR = {
DESC: "Chat not found",
CODE: "chat.notFound",
Expand Down Expand Up @@ -486,6 +507,8 @@ export const REDIS_HOST = process.env.REDIS_HOST || "";
export const REDIS_PORT = Number(process.env.REDIS_PORT);
export const REDIS_PASSWORD = process.env.REDIS_PASSWORD;

export const MILLISECONDS_IN_A_WEEK = 7 * 24 * 60 * 60 * 1000;

export const key = ENV.ENCRYPTION_KEY as string;
export const iv = crypto.randomBytes(16).toString("hex");

Expand Down
107 changes: 107 additions & 0 deletions src/models/ActionItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import type { PopulatedDoc, Types, Document, Model } from "mongoose";
import { Schema, model, models } from "mongoose";
import type { InterfaceUser } from "./User";
import type { InterfaceEvent } from "./Event";
import type { InterfaceActionItemCategory } from "./ActionItemCategory";
import { MILLISECONDS_IN_A_WEEK } from "../constants";

/**
* This is an interface that represents a database(MongoDB) document for ActionItem.
*/

export interface InterfaceActionItem {
meetulr marked this conversation as resolved.
Show resolved Hide resolved
_id: Types.ObjectId;
assigneeId: PopulatedDoc<InterfaceUser & Document>;
assignerId: PopulatedDoc<InterfaceUser & Document>;
actionItemCategoryId: PopulatedDoc<InterfaceActionItemCategory & Document>;
preCompletionNotes: string;
postCompletionNotes: string;
assignmentDate: Date;
dueDate: Date;
completionDate: Date;
isCompleted: boolean;
eventId: PopulatedDoc<InterfaceEvent & Document>;
creatorId: PopulatedDoc<InterfaceUser & Document>;
createdAt: Date;
updatedAt: Date;
}

/**
* This describes the schema for a `ActionItem` that corresponds to `InterfaceActionItem` document.
* @param assigneeId - User to whom the ActionItem is assigned, refer to `User` model.
* @param assignerId - User who assigned the ActionItem, refer to the `User` model.
* @param actionItemCategoryId - ActionItemCategory to which the ActionItem is related, refer to the `ActionItemCategory` model.
* @param preCompletionNotes - Notes prior to completion.
* @param postCompletionNotes - Notes on completion.
* @param assignmentDate - Date of assignment.
* @param dueDate - Due date.
* @param completionDate - Completion date.
* @param isCompleted - Whether the ActionItem has been completed.
* @param eventId - Event to which the ActionItem is related, refer to the `Event` model.
* @param creatorId - User who created the ActionItem, refer to the `User` model.
* @param createdAt - Timestamp when the ActionItem was created.
* @param updatedAt - Timestamp when the ActionItem was last updated.
*/

const actionItemSchema = new Schema(
meetulr marked this conversation as resolved.
Show resolved Hide resolved
{
assigneeId: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
assignerId: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
actionItemCategoryId: {
type: Schema.Types.ObjectId,
ref: "ActionItemCategory",
required: true,
},
preCompletionNotes: {
type: String,
},
postCompletionNotes: {
type: String,
},
assignmentDate: {
type: Date,
required: true,
default: Date.now(),
},
dueDate: {
type: Date,
required: true,
default: Date.now() + MILLISECONDS_IN_A_WEEK,
},
completionDate: {
type: Date,
required: true,
default: Date.now() + MILLISECONDS_IN_A_WEEK,
},
isCompleted: {
type: Boolean,
required: true,
default: false,
},
eventId: {
type: Schema.Types.ObjectId,
ref: "Event",
},
creatorId: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
},
{ timestamps: true }
);

const actionItemModel = (): Model<InterfaceActionItem> =>
model<InterfaceActionItem>("ActionItem", actionItemSchema);

// This syntax is needed to prevent Mongoose OverwriteModelError while running tests.
export const ActionItem = (models.ActionItem ||
actionItemModel()) as ReturnType<typeof actionItemModel>;
68 changes: 68 additions & 0 deletions src/models/ActionItemCategory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { PopulatedDoc, Types, Document, Model } from "mongoose";
import { Schema, model, models } from "mongoose";
import type { InterfaceUser } from "./User";
import type { InterfaceOrganization } from "./Organization";

/**
* This is an interface that represents a database(MongoDB) document for ActionItemCategory.
*/

export interface InterfaceActionItemCategory {
_id: Types.ObjectId;
name: string;
organizationId: PopulatedDoc<InterfaceOrganization & Document>;
isDisabled: boolean;
creatorId: PopulatedDoc<InterfaceUser & Document>;
createdAt: Date;
updatedAt: Date;
}

/**
* This describes the schema for a `actionItemCategory` that corresponds to `InterfaceCategory` document.
* @param name - An actionItemCategory to be selected for ActionItems.
* @param organizationId - Organization the actionItemCategory belongs to, refer to the `Organization` model.
* @param isDisabled - Whether actionItemCategory is disabled or not.
* @param creatorId - Task creator, refer to `User` model.
* @param createdAt - Time stamp of data creation.
* @param updatedAt - Time stamp of data updation.
*/

const actionItemCategorySchema = new Schema(
{
name: {
type: String,
required: true,
},
organizationId: {
type: Schema.Types.ObjectId,
ref: "Organization",
required: true,
},
isDisabled: {
type: Boolean,
required: true,
default: false,
},
creatorId: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
},
{ timestamps: true }
);

actionItemCategorySchema.index(
{ organizationId: 1, name: 1 },
{ unique: true }
);

const actionItemCategoryModel = (): Model<InterfaceActionItemCategory> =>
model<InterfaceActionItemCategory>(
"ActionItemCategory",
actionItemCategorySchema
);

// This syntax is needed to prevent Mongoose OverwriteModelError while running tests.
export const ActionItemCategory = (models.ActionItemCategory ||
actionItemCategoryModel()) as ReturnType<typeof actionItemCategoryModel>;
2 changes: 2 additions & 0 deletions src/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from "./ActionItem";
export * from "./Advertisement";
export * from "./ActionItemCategory";
export * from "./CheckIn";
export * from "./MessageChat";
export * from "./Comment";
Expand Down
9 changes: 9 additions & 0 deletions src/resolvers/ActionItem/actionItemCategory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { ActionItemResolvers } from "../../types/generatedGraphQLTypes";
import { ActionItemCategory } from "../../models";

export const actionItemCategory: ActionItemResolvers["actionItemCategory"] =
async (parent) => {
return ActionItemCategory.findOne({
_id: parent.actionItemCategoryId,
}).lean();
};
8 changes: 8 additions & 0 deletions src/resolvers/ActionItem/assignee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ActionItemResolvers } from "../../types/generatedGraphQLTypes";
import { User } from "../../models";

export const assignee: ActionItemResolvers["assignee"] = async (parent) => {
return User.findOne({
_id: parent.assigneeId,
}).lean();
};
8 changes: 8 additions & 0 deletions src/resolvers/ActionItem/assigner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ActionItemResolvers } from "../../types/generatedGraphQLTypes";
import { User } from "../../models";

export const assigner: ActionItemResolvers["assigner"] = async (parent) => {
return User.findOne({
_id: parent.assignerId,
}).lean();
};
8 changes: 8 additions & 0 deletions src/resolvers/ActionItem/creator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ActionItemResolvers } from "../../types/generatedGraphQLTypes";
import { User } from "../../models";

export const creator: ActionItemResolvers["creator"] = async (parent) => {
return User.findOne({
_id: parent.creatorId,
}).lean();
};
8 changes: 8 additions & 0 deletions src/resolvers/ActionItem/event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ActionItemResolvers } from "../../types/generatedGraphQLTypes";
import { Event } from "../../models";

export const event: ActionItemResolvers["event"] = async (parent) => {
return Event.findOne({
_id: parent.eventId,
}).lean();
};
14 changes: 14 additions & 0 deletions src/resolvers/ActionItem/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { ActionItemResolvers } from "../../types/generatedGraphQLTypes";
import { assignee } from "./assignee";
import { assigner } from "./assigner";
import { actionItemCategory } from "./actionItemCategory";
import { event } from "./event";
import { creator } from "./creator";

export const ActionItem: ActionItemResolvers = {
assignee,
assigner,
actionItemCategory,
event,
creator,
};
10 changes: 10 additions & 0 deletions src/resolvers/ActionItemCategory/creator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { ActionItemCategoryResolvers } from "../../types/generatedGraphQLTypes";
import { User } from "../../models";

export const creator: ActionItemCategoryResolvers["creator"] = async (
parent
) => {
return User.findOne({
_id: parent.creatorId,
}).lean();
};
8 changes: 8 additions & 0 deletions src/resolvers/ActionItemCategory/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ActionItemCategoryResolvers } from "../../types/generatedGraphQLTypes";
import { organization } from "./organization";
import { creator } from "./creator";

export const ActionItemCategory: ActionItemCategoryResolvers = {
organization,
creator,
};
10 changes: 10 additions & 0 deletions src/resolvers/ActionItemCategory/organization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { ActionItemCategoryResolvers } from "../../types/generatedGraphQLTypes";
import { Organization } from "../../models";

export const organization: ActionItemCategoryResolvers["organization"] = async (
parent
) => {
return Organization.findOne({
_id: parent.organizationId,
}).lean();
};
12 changes: 12 additions & 0 deletions src/resolvers/Event/actionItems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ActionItem } from "../../models";
import type { EventResolvers } from "../../types/generatedGraphQLTypes";
/**
* This resolver function will fetch and return the action items related to the event from database.
* @param parent - An object that is the return value of the resolver for this field's parent.
* @returns An object that contains the list of all action items related to the event.
*/
export const actionItems: EventResolvers["actionItems"] = async (parent) => {
return await ActionItem.find({
eventId: parent._id,
}).lean();
};
2 changes: 2 additions & 0 deletions src/resolvers/Event/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { attendeesCheckInStatus } from "./attendeesCheckInStatus";
import { averageFeedbackScore } from "./averageFeedbackScore";
import { feedback } from "./feedback";
import { organization } from "./organization";
import { actionItems } from "./actionItems";
import { creator } from "./creator";

export const Event: EventResolvers = {
actionItems,
attendees,
attendeesCheckInStatus,
averageFeedbackScore,
Expand Down
Loading
Loading