Skip to content

Commit

Permalink
Dev to Main Sync (#2363)
Browse files Browse the repository at this point in the history
* chore: remove duplicate function call for onboarding extension request (#2361)

* feat: Implement Pagination for GET /progreses API (#2325)

* feat: add pagination to GET /progreses API

* fix returning 404 on a page with no data

* fix joi validator logic

* refactor getting totalcount logic

* fix dev true

* fix merge conflicts

* using constant

* maked the JsDoc more concise

---------

Co-authored-by: Vinit khandal <[email protected]>

* test : add tests for pagination of progresses api (#2328)

* added test for the get progresses pagination

* added test for dev=false

* minor fix

* added unit test for utils/progresses functions

* added progressed model unit tests

* added test for 500

* added 500 message from constant

* fix test naming

* feat: Add an API to edit onboarding extension request details before approval or rejection (#2334)

* feat: Add feature to update request before approval or rejection
- Add common validator to redirect request based on type of extension
- Add type field in onboarding extension validator
- Import addLog from services to make it available for stubbing while testing
- Moved response messages to constants file
- Reuse single instance of current date in request and log model for consistent data

- Change controller name

- Remove unused variables

- Add authorization check for superuser or request ownership

- Change authorization condition

- Remove unnecessary changes

* fix: add logs for failure cases and fix check for same old and new deadline

* refactor: separate validation and update logic in service file

* chore: fix jsDoc

* fix: send id instead of while request doc while updating it

* chore: fix lint issue

* fix: change validation response condition and fix jsDoc

* fix: add strict checking

* fix: change constant message

* feat: Add tests for PATCH /requests/:id API for onboarding extension requests (#2335)

* feat: Add test cases for controller and validator

- Remove failing tests and fix existing tests

- Add tests to check success and unexpected behaviour and fix existing tests

- Replace actual messages with constants for easily maintenance

- Add test for super user and request owner authorization check and fix existing failing tests

- Remove un-necessary changes

- Remove separate file for validator tests

* feat: add tests for onboarding update and validate service

* Merge pull request #2369 from pankajjs/fix/flaky-test

Fix flaky test present in PR[2335]

* fix: handle id query parameter in Get Requests API (#2367)

Co-authored-by: Vikas Singh <[email protected]>
Co-authored-by: Achintya Chatterjee <[email protected]>

---------

Co-authored-by: Yash Raj <[email protected]>
Co-authored-by: Pankaj <[email protected]>
Co-authored-by: Anuj Chhikara <[email protected]>
Co-authored-by: Vinit khandal <[email protected]>
Co-authored-by: Vikas Singh <[email protected]>
  • Loading branch information
6 people authored Jan 26, 2025
2 parents ba7a9e9 + 2db248c commit 4336a62
Show file tree
Hide file tree
Showing 22 changed files with 1,564 additions and 65 deletions.
7 changes: 6 additions & 1 deletion constants/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ export const logType = {
EXTENSION_REQUESTS: "extensionRequests",
TASK: "task",
TASK_REQUESTS: "taskRequests",
USER_DETAILS_UPDATED: "USER_DETAILS_UPDATED",
USER_DETAILS_UPDATED: "USER_DETAILS_UPDATED",
REQUEST_DOES_NOT_EXIST:"REQUEST_DOES_NOT_EXIST",
UNAUTHORIZED_TO_UPDATE_REQUEST: "UNAUTHORIZED_TO_UPDATE_REQUEST",
INVALID_REQUEST_TYPE: "INVALID_REQUEST_TYPE",
PENDING_REQUEST_CAN_BE_UPDATED: "PENDING_REQUEST_CAN_BE_UPDATED",
INVALID_REQUEST_DEADLINE: "INVALID_REQUEST_DEADLINE",
...REQUEST_LOG_TYPE,
};

Expand Down
5 changes: 4 additions & 1 deletion constants/progresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const TYPE_MAP = {
task: "taskId",
};
const PROGRESS_VALID_SORT_FIELDS = ["date", "-date"];

const PROGRESSES_SIZE = 20;
const PROGRESSES_PAGE_SIZE = 0;
const VALID_PROGRESS_TYPES = ["task", "user"];

module.exports = {
Expand All @@ -28,4 +29,6 @@ module.exports = {
TYPE_MAP,
VALID_PROGRESS_TYPES,
PROGRESS_VALID_SORT_FIELDS,
PROGRESSES_SIZE,
PROGRESSES_PAGE_SIZE,
};
9 changes: 8 additions & 1 deletion constants/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const REQUEST_LOG_TYPE = {
REQUEST_REJECTED: "REQUEST_REJECTED",
REQUEST_BLOCKED: "REQUEST_BLOCKED",
REQUEST_CANCELLED: "REQUEST_CANCELLED",
REQUEST_UPDATED: "REQUEST_UPDATED",
};

export const REQUEST_CREATED_SUCCESSFULLY = "Request created successfully";
Expand Down Expand Up @@ -56,4 +57,10 @@ export const TASK_REQUEST_MESSAGES = {
};

export const ONBOARDING_REQUEST_CREATED_SUCCESSFULLY = "Onboarding extension request created successfully"
export const UNAUTHORIZED_TO_CREATE_ONBOARDING_EXTENSION_REQUEST = "Only super user and onboarding user are authorized to create an onboarding extension request"
export const UNAUTHORIZED_TO_CREATE_ONBOARDING_EXTENSION_REQUEST = "Only super user and onboarding user are authorized to create an onboarding extension request"

export const PENDING_REQUEST_UPDATED = "Only pending extension request can be updated";
export const INVALID_REQUEST_TYPE = "Invalid request type";
export const INVALID_REQUEST_DEADLINE = "New deadline of the request must be greater than old deadline";
export const REQUEST_UPDATED_SUCCESSFULLY = "Request updated successfully";
export const UNAUTHORIZED_TO_UPDATE_REQUEST = "Unauthorized to update request";
68 changes: 67 additions & 1 deletion controllers/onboardingExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import {
REQUEST_REJECTED_SUCCESSFULLY,
REQUEST_STATE,
REQUEST_TYPE,
REQUEST_UPDATED_SUCCESSFULLY,
UNAUTHORIZED_TO_CREATE_ONBOARDING_EXTENSION_REQUEST,
UNAUTHORIZED_TO_UPDATE_REQUEST,
} from "../constants/requests";
import { userState } from "../constants/userStatus";
import { addLog } from "../services/logService";
Expand All @@ -24,10 +26,15 @@ import {
OnboardingExtensionCreateRequest,
OnboardingExtensionResponse,
UpdateOnboardingExtensionStateRequest,
UpdateOnboardingExtensionStateRequestBody
UpdateOnboardingExtensionStateRequestBody,
UpdateOnboardingExtensionRequest,
UpdateOnboardingExtensionRequestBody
} from "../types/onboardingExtension";
import { convertDateStringToMilliseconds, getNewDeadline } from "../utils/requests";
import { convertDaysToMilliseconds } from "../utils/time";
import firestore from "../utils/firestore";
import { updateOnboardingExtensionRequest, validateOnboardingExtensionUpdateRequest } from "../services/onboardingExtension";
const requestModel = firestore.collection("requests");

/**
* Controller to handle the creation of onboarding extension requests.
Expand Down Expand Up @@ -200,3 +207,62 @@ export const updateOnboardingExtensionRequestState = async (
return res.boom.badImplementation(ERROR_WHILE_UPDATING_REQUEST);
}
}

/**
* Updates an onboarding extension request.
*
* @param {UpdateOnboardingExtensionRequest} req - The request object.
* @param {OnboardingExtensionResponse} res - The response object.
* @returns {Promise<OnboardingExtensionResponse>} Resolves with success or failure.
*/
export const updateOnboardingExtensionRequestController = async (
req: UpdateOnboardingExtensionRequest,
res: OnboardingExtensionResponse): Promise<OnboardingExtensionResponse> =>
{

const body = req.body as UpdateOnboardingExtensionRequestBody;
const id = req.params.id;
const lastModifiedBy = req?.userData?.id;
const isSuperuser = req?.userData?.roles?.super_user === true;
const dev = req.query.dev === "true";

if(!dev) return res.boom.notImplemented("Feature not implemented");

try{
const extensionRequestDoc = await requestModel.doc(id).get();
const validationResponse = await validateOnboardingExtensionUpdateRequest(
extensionRequestDoc,
id,
isSuperuser,
lastModifiedBy,
body.newEndsOn,
)

if (validationResponse){
if(validationResponse.error === REQUEST_DOES_NOT_EXIST){
return res.boom.notFound(validationResponse.error);
}
if(validationResponse.error === UNAUTHORIZED_TO_UPDATE_REQUEST){
return res.boom.forbidden(UNAUTHORIZED_TO_UPDATE_REQUEST);
}
return res.boom.badRequest(validationResponse.error);
}

const requestBody = await updateOnboardingExtensionRequest(
id,
body,
lastModifiedBy,
)

return res.status(200).json({
message: REQUEST_UPDATED_SUCCESSFULLY,
data: {
id: extensionRequestDoc.id,
...requestBody
}
})
}catch(error){
logger.error(ERROR_WHILE_UPDATING_REQUEST, error);
return res.boom.badImplementation(ERROR_WHILE_UPDATING_REQUEST);
}
}
47 changes: 37 additions & 10 deletions controllers/progresses.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const { Conflict, NotFound } = require("http-errors");
const progressesModel = require("../models/progresses");
const {
createProgressDocument,
getProgressDocument,
getRangeProgressData,
getProgressByDate,
} = require("../models/progresses");
const { PROGRESSES_RESPONSE_MESSAGES, INTERNAL_SERVER_ERROR_MESSAGE } = require("../constants/progresses");
PROGRESSES_RESPONSE_MESSAGES,
INTERNAL_SERVER_ERROR_MESSAGE,
PROGRESSES_SIZE,
PROGRESSES_PAGE_SIZE,
} = require("../constants/progresses");
const { sendTaskUpdate } = require("../utils/sendTaskUpdate");
const { PROGRESS_DOCUMENT_RETRIEVAL_SUCCEEDED, PROGRESS_DOCUMENT_CREATED_SUCCEEDED } = PROGRESSES_RESPONSE_MESSAGES;

Expand Down Expand Up @@ -49,7 +49,7 @@ const createProgress = async (req, res) => {
body: { type, completed, planned, blockers, taskId },
} = req;
try {
const { data, taskTitle } = await createProgressDocument({ ...req.body, userId: req.userData.id });
const { data, taskTitle } = await progressesModel.createProgressDocument({ ...req.body, userId: req.userData.id });
await sendTaskUpdate(completed, blockers, planned, req.userData.username, taskId, taskTitle);
return res.status(201).json({
data,
Expand Down Expand Up @@ -107,8 +107,35 @@ const createProgress = async (req, res) => {
*/

const getProgress = async (req, res) => {
const { dev, page = PROGRESSES_PAGE_SIZE, size = PROGRESSES_SIZE, type, userId, taskId } = req.query;
try {
const data = await getProgressDocument(req.query);
if (dev === "true") {
const { progressDocs, totalProgressCount } = await progressesModel.getPaginatedProgressDocument(req.query);
const limit = parseInt(size, 10);
const offset = parseInt(page, 10) * limit;
const nextPage = offset + limit < totalProgressCount ? parseInt(page, 10) + 1 : null;
const prevPage = page > 0 ? parseInt(page, 10) - 1 : null;
let baseUrl = `${req.baseUrl}`;
if (type) {
baseUrl += `?type=${type}`;
} else if (userId) {
baseUrl += `?userId=${userId}`;
} else if (taskId) {
baseUrl += `?taskId=${taskId}`;
}
const nextLink = nextPage !== null ? `${baseUrl}&page=${nextPage}&size=${size}&dev=${dev}` : null;
const prevLink = prevPage !== null ? `${baseUrl}&page=${prevPage}&size=${size}&dev=${dev}` : null;
return res.json({
message: PROGRESS_DOCUMENT_RETRIEVAL_SUCCEEDED,
count: progressDocs.length,
data: progressDocs,
links: {
prev: prevLink,
next: nextLink,
},
});
}
const data = await progressesModel.getProgressDocument(req.query);
return res.json({
message: PROGRESS_DOCUMENT_RETRIEVAL_SUCCEEDED,
count: data.length,
Expand Down Expand Up @@ -163,7 +190,7 @@ const getProgress = async (req, res) => {

const getProgressRangeData = async (req, res) => {
try {
const data = await getRangeProgressData(req.query);
const data = await progressesModel.getRangeProgressData(req.query);
return res.json({
message: PROGRESS_DOCUMENT_RETRIEVAL_SUCCEEDED,
data,
Expand Down Expand Up @@ -217,7 +244,7 @@ const getProgressRangeData = async (req, res) => {

const getProgressBydDateController = async (req, res) => {
try {
const data = await getProgressByDate(req.params, req.query);
const data = await progressesModel.getProgressByDate(req.params, req.query);
return res.json({
message: PROGRESS_DOCUMENT_RETRIEVAL_SUCCEEDED,
data,
Expand Down
32 changes: 29 additions & 3 deletions controllers/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import { UpdateRequest } from "../types/requests";
import { TaskRequestRequest } from "../types/taskRequests";
import { createTaskRequestController } from "./taskRequestsv2";
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOnboardingExtensionStateRequest } from "../types/onboardingExtension";
import { createOnboardingExtensionRequestController, updateOnboardingExtensionRequestState } from "./onboardingExtension";
import { createOnboardingExtensionRequestController, updateOnboardingExtensionRequestController, updateOnboardingExtensionRequestState } from "./onboardingExtension";
import { UpdateOnboardingExtensionRequest } from "../types/onboardingExtension";

import { Request } from "express";

export const createRequestController = async (
req: OooRequestCreateRequest | ExtensionRequestRequest | TaskRequestRequest | OnboardingExtensionCreateRequest,
Expand All @@ -30,8 +33,6 @@ export const createRequestController = async (
return await createTaskRequestController(req as TaskRequestRequest, res as CustomResponse);
case REQUEST_TYPE.ONBOARDING:
return await createOnboardingExtensionRequestController(req as OnboardingExtensionCreateRequest, res as OnboardingExtensionResponse);
case REQUEST_TYPE.ONBOARDING:
return await createOnboardingExtensionRequestController(req as OnboardingExtensionCreateRequest, res as OnboardingExtensionResponse);
default:
return res.boom.badRequest("Invalid request type");
}
Expand Down Expand Up @@ -59,6 +60,13 @@ export const getRequestsController = async (req: any, res: any) => {
return res.status(204).send();
}

if (query.id) {
return res.status(200).json({
message: REQUEST_FETCHED_SUCCESSFULLY,
data: requests,
});
}

const { allRequests, next, prev, page } = requests;
if (allRequests.length === 0) {
return res.status(204).send();
Expand Down Expand Up @@ -105,3 +113,21 @@ export const getRequestsController = async (req: any, res: any) => {
return res.boom.badImplementation(ERROR_WHILE_FETCHING_REQUEST);
}
};

/**
* Processes update requests before acknowledgment based on type.
*
* @param {Request} req - The request object.
* @param {CustomResponse} res - The response object.
* @returns {Promise<void>} Resolves or sends an error for invalid types.
*/
export const updateRequestBeforeAcknowledgedController = async (req: Request, res: CustomResponse) => {
const type = req.body.type;
switch(type){
case REQUEST_TYPE.ONBOARDING:
await updateOnboardingExtensionRequestController(req as UpdateOnboardingExtensionRequest, res as OnboardingExtensionResponse);
break;
default:
return res.boom.badRequest("Invalid request");
}
}
40 changes: 39 additions & 1 deletion middlewares/validators/onboardingExtensionRequest.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import joi from "joi";
import { NextFunction } from "express";
import { REQUEST_TYPE } from "../../constants/requests";
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse } from "../../types/onboardingExtension";
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOnboardingExtensionRequest } from "../../types/onboardingExtension";

export const createOnboardingExtensionRequestValidator = async (
req: OnboardingExtensionCreateRequest,
Expand Down Expand Up @@ -40,3 +40,41 @@ export const createOnboardingExtensionRequestValidator = async (
throw error;
}
};

/**
* Validates onboarding extension request payload.
*
* @param {UpdateOnboardingExtensionRequest} req - Request object.
* @param {OnboardingExtensionResponse} res - Response object.
* @param {NextFunction} next - Next middleware if valid.
* @returns {Promise<void>} Resolves or sends errors.
*/
export const updateOnboardingExtensionRequestValidator = async (
req: UpdateOnboardingExtensionRequest,
res: OnboardingExtensionResponse,
next: NextFunction): Promise<void> => {
const schema = joi
.object()
.strict()
.keys({
reason: joi.string().optional(),
newEndsOn: joi.number().positive().min(Date.now()).required().messages({
'number.any': 'newEndsOn is required',
'number.base': 'newEndsOn must be a number',
'number.positive': 'newEndsOn must be positive',
'number.greater': 'newEndsOn must be greater than current date',
}),
type: joi.string().equal(REQUEST_TYPE.ONBOARDING).required().messages({
"type.any": "type is required",
})
});

try {
await schema.validateAsync(req.body, { abortEarly: false });
next();
} catch (error) {
const errorMessages = error.details.map((detail:{message: string}) => detail.message);
logger.error(`Error while validating request payload : ${errorMessages}`);
return res.boom.badRequest(errorMessages);
}
}
9 changes: 9 additions & 0 deletions middlewares/validators/progresses.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ const validateGetProgressRecordsQuery = async (req, res, next) => {
.messages({
"string.base": "orderBy must be a string",
}),
size: joi.number().optional().min(1).max(100).messages({
"number.base": "size must be a number",
"number.min": "size must be in the range 1-100",
"number.max": "size must be in the range 1-100",
}),
page: joi.number().optional().min(0).messages({
"number.base": "page must be a number",
"number.min": "page must be a positive number or zero",
}),
})
.xor("type", "userId", "taskId")
.messages({
Expand Down
29 changes: 27 additions & 2 deletions middlewares/validators/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { ExtensionRequestRequest, ExtensionRequestResponse } from "../../types/e
import { CustomResponse } from "../../typeDefinitions/global";
import { UpdateRequest } from "../../types/requests";
import { TaskRequestRequest, TaskRequestResponse } from "../../types/taskRequests";
import { createOnboardingExtensionRequestValidator } from "./onboardingExtensionRequest";
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse } from "../../types/onboardingExtension";
import { createOnboardingExtensionRequestValidator, updateOnboardingExtensionRequestValidator } from "./onboardingExtensionRequest";
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOnboardingExtensionRequest } from "../../types/onboardingExtension";

export const createRequestsMiddleware = async (
req: OooRequestCreateRequest|ExtensionRequestRequest | TaskRequestRequest | OnboardingExtensionCreateRequest,
Expand Down Expand Up @@ -121,3 +121,28 @@ export const getRequestsMiddleware = async (req: OooRequestCreateRequest, res: O
res.boom.badRequest(errorMessages);
}
};

/**
* Validates update requests based on their type.
*
* @param {UpdateOnboardingExtensionRequest} req - Request object.
* @param {CustomResponse} res - Response object.
* @param {NextFunction} next - Next middleware if valid.
* @returns {Promise<void>} Resolves or sends errors.
*/
export const updateRequestValidator = async (
req: UpdateOnboardingExtensionRequest,
res: CustomResponse,
next: NextFunction
): Promise<void> => {
const type = req.body.type;
switch (type) {
case REQUEST_TYPE.ONBOARDING:
await updateOnboardingExtensionRequestValidator(
req,
res as OnboardingExtensionResponse, next);
break;
default:
return res.boom.badRequest("Invalid type");
}
};
Loading

0 comments on commit 4336a62

Please sign in to comment.