Skip to content

Commit 4336a62

Browse files
iamitprakashyesyashpankajjsAnujChhikaravinit717
authored
Dev to Main Sync (#2363)
* 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]>
2 parents ba7a9e9 + 2db248c commit 4336a62

22 files changed

+1564
-65
lines changed

constants/logs.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ export const logType = {
1616
EXTENSION_REQUESTS: "extensionRequests",
1717
TASK: "task",
1818
TASK_REQUESTS: "taskRequests",
19-
USER_DETAILS_UPDATED: "USER_DETAILS_UPDATED",
19+
USER_DETAILS_UPDATED: "USER_DETAILS_UPDATED",
20+
REQUEST_DOES_NOT_EXIST:"REQUEST_DOES_NOT_EXIST",
21+
UNAUTHORIZED_TO_UPDATE_REQUEST: "UNAUTHORIZED_TO_UPDATE_REQUEST",
22+
INVALID_REQUEST_TYPE: "INVALID_REQUEST_TYPE",
23+
PENDING_REQUEST_CAN_BE_UPDATED: "PENDING_REQUEST_CAN_BE_UPDATED",
24+
INVALID_REQUEST_DEADLINE: "INVALID_REQUEST_DEADLINE",
2025
...REQUEST_LOG_TYPE,
2126
};
2227

constants/progresses.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ const TYPE_MAP = {
1818
task: "taskId",
1919
};
2020
const PROGRESS_VALID_SORT_FIELDS = ["date", "-date"];
21-
21+
const PROGRESSES_SIZE = 20;
22+
const PROGRESSES_PAGE_SIZE = 0;
2223
const VALID_PROGRESS_TYPES = ["task", "user"];
2324

2425
module.exports = {
@@ -28,4 +29,6 @@ module.exports = {
2829
TYPE_MAP,
2930
VALID_PROGRESS_TYPES,
3031
PROGRESS_VALID_SORT_FIELDS,
32+
PROGRESSES_SIZE,
33+
PROGRESSES_PAGE_SIZE,
3134
};

constants/requests.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const REQUEST_LOG_TYPE = {
2424
REQUEST_REJECTED: "REQUEST_REJECTED",
2525
REQUEST_BLOCKED: "REQUEST_BLOCKED",
2626
REQUEST_CANCELLED: "REQUEST_CANCELLED",
27+
REQUEST_UPDATED: "REQUEST_UPDATED",
2728
};
2829

2930
export const REQUEST_CREATED_SUCCESSFULLY = "Request created successfully";
@@ -56,4 +57,10 @@ export const TASK_REQUEST_MESSAGES = {
5657
};
5758

5859
export const ONBOARDING_REQUEST_CREATED_SUCCESSFULLY = "Onboarding extension request created successfully"
59-
export const UNAUTHORIZED_TO_CREATE_ONBOARDING_EXTENSION_REQUEST = "Only super user and onboarding user are authorized to create an onboarding extension request"
60+
export const UNAUTHORIZED_TO_CREATE_ONBOARDING_EXTENSION_REQUEST = "Only super user and onboarding user are authorized to create an onboarding extension request"
61+
62+
export const PENDING_REQUEST_UPDATED = "Only pending extension request can be updated";
63+
export const INVALID_REQUEST_TYPE = "Invalid request type";
64+
export const INVALID_REQUEST_DEADLINE = "New deadline of the request must be greater than old deadline";
65+
export const REQUEST_UPDATED_SUCCESSFULLY = "Request updated successfully";
66+
export const UNAUTHORIZED_TO_UPDATE_REQUEST = "Unauthorized to update request";

controllers/onboardingExtension.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import {
1010
REQUEST_REJECTED_SUCCESSFULLY,
1111
REQUEST_STATE,
1212
REQUEST_TYPE,
13+
REQUEST_UPDATED_SUCCESSFULLY,
1314
UNAUTHORIZED_TO_CREATE_ONBOARDING_EXTENSION_REQUEST,
15+
UNAUTHORIZED_TO_UPDATE_REQUEST,
1416
} from "../constants/requests";
1517
import { userState } from "../constants/userStatus";
1618
import { addLog } from "../services/logService";
@@ -24,10 +26,15 @@ import {
2426
OnboardingExtensionCreateRequest,
2527
OnboardingExtensionResponse,
2628
UpdateOnboardingExtensionStateRequest,
27-
UpdateOnboardingExtensionStateRequestBody
29+
UpdateOnboardingExtensionStateRequestBody,
30+
UpdateOnboardingExtensionRequest,
31+
UpdateOnboardingExtensionRequestBody
2832
} from "../types/onboardingExtension";
2933
import { convertDateStringToMilliseconds, getNewDeadline } from "../utils/requests";
3034
import { convertDaysToMilliseconds } from "../utils/time";
35+
import firestore from "../utils/firestore";
36+
import { updateOnboardingExtensionRequest, validateOnboardingExtensionUpdateRequest } from "../services/onboardingExtension";
37+
const requestModel = firestore.collection("requests");
3138

3239
/**
3340
* Controller to handle the creation of onboarding extension requests.
@@ -200,3 +207,62 @@ export const updateOnboardingExtensionRequestState = async (
200207
return res.boom.badImplementation(ERROR_WHILE_UPDATING_REQUEST);
201208
}
202209
}
210+
211+
/**
212+
* Updates an onboarding extension request.
213+
*
214+
* @param {UpdateOnboardingExtensionRequest} req - The request object.
215+
* @param {OnboardingExtensionResponse} res - The response object.
216+
* @returns {Promise<OnboardingExtensionResponse>} Resolves with success or failure.
217+
*/
218+
export const updateOnboardingExtensionRequestController = async (
219+
req: UpdateOnboardingExtensionRequest,
220+
res: OnboardingExtensionResponse): Promise<OnboardingExtensionResponse> =>
221+
{
222+
223+
const body = req.body as UpdateOnboardingExtensionRequestBody;
224+
const id = req.params.id;
225+
const lastModifiedBy = req?.userData?.id;
226+
const isSuperuser = req?.userData?.roles?.super_user === true;
227+
const dev = req.query.dev === "true";
228+
229+
if(!dev) return res.boom.notImplemented("Feature not implemented");
230+
231+
try{
232+
const extensionRequestDoc = await requestModel.doc(id).get();
233+
const validationResponse = await validateOnboardingExtensionUpdateRequest(
234+
extensionRequestDoc,
235+
id,
236+
isSuperuser,
237+
lastModifiedBy,
238+
body.newEndsOn,
239+
)
240+
241+
if (validationResponse){
242+
if(validationResponse.error === REQUEST_DOES_NOT_EXIST){
243+
return res.boom.notFound(validationResponse.error);
244+
}
245+
if(validationResponse.error === UNAUTHORIZED_TO_UPDATE_REQUEST){
246+
return res.boom.forbidden(UNAUTHORIZED_TO_UPDATE_REQUEST);
247+
}
248+
return res.boom.badRequest(validationResponse.error);
249+
}
250+
251+
const requestBody = await updateOnboardingExtensionRequest(
252+
id,
253+
body,
254+
lastModifiedBy,
255+
)
256+
257+
return res.status(200).json({
258+
message: REQUEST_UPDATED_SUCCESSFULLY,
259+
data: {
260+
id: extensionRequestDoc.id,
261+
...requestBody
262+
}
263+
})
264+
}catch(error){
265+
logger.error(ERROR_WHILE_UPDATING_REQUEST, error);
266+
return res.boom.badImplementation(ERROR_WHILE_UPDATING_REQUEST);
267+
}
268+
}

controllers/progresses.js

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
const { Conflict, NotFound } = require("http-errors");
2+
const progressesModel = require("../models/progresses");
23
const {
3-
createProgressDocument,
4-
getProgressDocument,
5-
getRangeProgressData,
6-
getProgressByDate,
7-
} = require("../models/progresses");
8-
const { PROGRESSES_RESPONSE_MESSAGES, INTERNAL_SERVER_ERROR_MESSAGE } = require("../constants/progresses");
4+
PROGRESSES_RESPONSE_MESSAGES,
5+
INTERNAL_SERVER_ERROR_MESSAGE,
6+
PROGRESSES_SIZE,
7+
PROGRESSES_PAGE_SIZE,
8+
} = require("../constants/progresses");
99
const { sendTaskUpdate } = require("../utils/sendTaskUpdate");
1010
const { PROGRESS_DOCUMENT_RETRIEVAL_SUCCEEDED, PROGRESS_DOCUMENT_CREATED_SUCCEEDED } = PROGRESSES_RESPONSE_MESSAGES;
1111

@@ -49,7 +49,7 @@ const createProgress = async (req, res) => {
4949
body: { type, completed, planned, blockers, taskId },
5050
} = req;
5151
try {
52-
const { data, taskTitle } = await createProgressDocument({ ...req.body, userId: req.userData.id });
52+
const { data, taskTitle } = await progressesModel.createProgressDocument({ ...req.body, userId: req.userData.id });
5353
await sendTaskUpdate(completed, blockers, planned, req.userData.username, taskId, taskTitle);
5454
return res.status(201).json({
5555
data,
@@ -107,8 +107,35 @@ const createProgress = async (req, res) => {
107107
*/
108108

109109
const getProgress = async (req, res) => {
110+
const { dev, page = PROGRESSES_PAGE_SIZE, size = PROGRESSES_SIZE, type, userId, taskId } = req.query;
110111
try {
111-
const data = await getProgressDocument(req.query);
112+
if (dev === "true") {
113+
const { progressDocs, totalProgressCount } = await progressesModel.getPaginatedProgressDocument(req.query);
114+
const limit = parseInt(size, 10);
115+
const offset = parseInt(page, 10) * limit;
116+
const nextPage = offset + limit < totalProgressCount ? parseInt(page, 10) + 1 : null;
117+
const prevPage = page > 0 ? parseInt(page, 10) - 1 : null;
118+
let baseUrl = `${req.baseUrl}`;
119+
if (type) {
120+
baseUrl += `?type=${type}`;
121+
} else if (userId) {
122+
baseUrl += `?userId=${userId}`;
123+
} else if (taskId) {
124+
baseUrl += `?taskId=${taskId}`;
125+
}
126+
const nextLink = nextPage !== null ? `${baseUrl}&page=${nextPage}&size=${size}&dev=${dev}` : null;
127+
const prevLink = prevPage !== null ? `${baseUrl}&page=${prevPage}&size=${size}&dev=${dev}` : null;
128+
return res.json({
129+
message: PROGRESS_DOCUMENT_RETRIEVAL_SUCCEEDED,
130+
count: progressDocs.length,
131+
data: progressDocs,
132+
links: {
133+
prev: prevLink,
134+
next: nextLink,
135+
},
136+
});
137+
}
138+
const data = await progressesModel.getProgressDocument(req.query);
112139
return res.json({
113140
message: PROGRESS_DOCUMENT_RETRIEVAL_SUCCEEDED,
114141
count: data.length,
@@ -163,7 +190,7 @@ const getProgress = async (req, res) => {
163190

164191
const getProgressRangeData = async (req, res) => {
165192
try {
166-
const data = await getRangeProgressData(req.query);
193+
const data = await progressesModel.getRangeProgressData(req.query);
167194
return res.json({
168195
message: PROGRESS_DOCUMENT_RETRIEVAL_SUCCEEDED,
169196
data,
@@ -217,7 +244,7 @@ const getProgressRangeData = async (req, res) => {
217244

218245
const getProgressBydDateController = async (req, res) => {
219246
try {
220-
const data = await getProgressByDate(req.params, req.query);
247+
const data = await progressesModel.getProgressByDate(req.params, req.query);
221248
return res.json({
222249
message: PROGRESS_DOCUMENT_RETRIEVAL_SUCCEEDED,
223250
data,

controllers/requests.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import { UpdateRequest } from "../types/requests";
1414
import { TaskRequestRequest } from "../types/taskRequests";
1515
import { createTaskRequestController } from "./taskRequestsv2";
1616
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOnboardingExtensionStateRequest } from "../types/onboardingExtension";
17-
import { createOnboardingExtensionRequestController, updateOnboardingExtensionRequestState } from "./onboardingExtension";
17+
import { createOnboardingExtensionRequestController, updateOnboardingExtensionRequestController, updateOnboardingExtensionRequestState } from "./onboardingExtension";
18+
import { UpdateOnboardingExtensionRequest } from "../types/onboardingExtension";
19+
20+
import { Request } from "express";
1821

1922
export const createRequestController = async (
2023
req: OooRequestCreateRequest | ExtensionRequestRequest | TaskRequestRequest | OnboardingExtensionCreateRequest,
@@ -30,8 +33,6 @@ export const createRequestController = async (
3033
return await createTaskRequestController(req as TaskRequestRequest, res as CustomResponse);
3134
case REQUEST_TYPE.ONBOARDING:
3235
return await createOnboardingExtensionRequestController(req as OnboardingExtensionCreateRequest, res as OnboardingExtensionResponse);
33-
case REQUEST_TYPE.ONBOARDING:
34-
return await createOnboardingExtensionRequestController(req as OnboardingExtensionCreateRequest, res as OnboardingExtensionResponse);
3536
default:
3637
return res.boom.badRequest("Invalid request type");
3738
}
@@ -59,6 +60,13 @@ export const getRequestsController = async (req: any, res: any) => {
5960
return res.status(204).send();
6061
}
6162

63+
if (query.id) {
64+
return res.status(200).json({
65+
message: REQUEST_FETCHED_SUCCESSFULLY,
66+
data: requests,
67+
});
68+
}
69+
6270
const { allRequests, next, prev, page } = requests;
6371
if (allRequests.length === 0) {
6472
return res.status(204).send();
@@ -105,3 +113,21 @@ export const getRequestsController = async (req: any, res: any) => {
105113
return res.boom.badImplementation(ERROR_WHILE_FETCHING_REQUEST);
106114
}
107115
};
116+
117+
/**
118+
* Processes update requests before acknowledgment based on type.
119+
*
120+
* @param {Request} req - The request object.
121+
* @param {CustomResponse} res - The response object.
122+
* @returns {Promise<void>} Resolves or sends an error for invalid types.
123+
*/
124+
export const updateRequestBeforeAcknowledgedController = async (req: Request, res: CustomResponse) => {
125+
const type = req.body.type;
126+
switch(type){
127+
case REQUEST_TYPE.ONBOARDING:
128+
await updateOnboardingExtensionRequestController(req as UpdateOnboardingExtensionRequest, res as OnboardingExtensionResponse);
129+
break;
130+
default:
131+
return res.boom.badRequest("Invalid request");
132+
}
133+
}

middlewares/validators/onboardingExtensionRequest.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import joi from "joi";
22
import { NextFunction } from "express";
33
import { REQUEST_TYPE } from "../../constants/requests";
4-
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse } from "../../types/onboardingExtension";
4+
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOnboardingExtensionRequest } from "../../types/onboardingExtension";
55

66
export const createOnboardingExtensionRequestValidator = async (
77
req: OnboardingExtensionCreateRequest,
@@ -40,3 +40,41 @@ export const createOnboardingExtensionRequestValidator = async (
4040
throw error;
4141
}
4242
};
43+
44+
/**
45+
* Validates onboarding extension request payload.
46+
*
47+
* @param {UpdateOnboardingExtensionRequest} req - Request object.
48+
* @param {OnboardingExtensionResponse} res - Response object.
49+
* @param {NextFunction} next - Next middleware if valid.
50+
* @returns {Promise<void>} Resolves or sends errors.
51+
*/
52+
export const updateOnboardingExtensionRequestValidator = async (
53+
req: UpdateOnboardingExtensionRequest,
54+
res: OnboardingExtensionResponse,
55+
next: NextFunction): Promise<void> => {
56+
const schema = joi
57+
.object()
58+
.strict()
59+
.keys({
60+
reason: joi.string().optional(),
61+
newEndsOn: joi.number().positive().min(Date.now()).required().messages({
62+
'number.any': 'newEndsOn is required',
63+
'number.base': 'newEndsOn must be a number',
64+
'number.positive': 'newEndsOn must be positive',
65+
'number.greater': 'newEndsOn must be greater than current date',
66+
}),
67+
type: joi.string().equal(REQUEST_TYPE.ONBOARDING).required().messages({
68+
"type.any": "type is required",
69+
})
70+
});
71+
72+
try {
73+
await schema.validateAsync(req.body, { abortEarly: false });
74+
next();
75+
} catch (error) {
76+
const errorMessages = error.details.map((detail:{message: string}) => detail.message);
77+
logger.error(`Error while validating request payload : ${errorMessages}`);
78+
return res.boom.badRequest(errorMessages);
79+
}
80+
}

middlewares/validators/progresses.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,15 @@ const validateGetProgressRecordsQuery = async (req, res, next) => {
7373
.messages({
7474
"string.base": "orderBy must be a string",
7575
}),
76+
size: joi.number().optional().min(1).max(100).messages({
77+
"number.base": "size must be a number",
78+
"number.min": "size must be in the range 1-100",
79+
"number.max": "size must be in the range 1-100",
80+
}),
81+
page: joi.number().optional().min(0).messages({
82+
"number.base": "page must be a number",
83+
"number.min": "page must be a positive number or zero",
84+
}),
7685
})
7786
.xor("type", "userId", "taskId")
7887
.messages({

middlewares/validators/requests.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import { ExtensionRequestRequest, ExtensionRequestResponse } from "../../types/e
99
import { CustomResponse } from "../../typeDefinitions/global";
1010
import { UpdateRequest } from "../../types/requests";
1111
import { TaskRequestRequest, TaskRequestResponse } from "../../types/taskRequests";
12-
import { createOnboardingExtensionRequestValidator } from "./onboardingExtensionRequest";
13-
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse } from "../../types/onboardingExtension";
12+
import { createOnboardingExtensionRequestValidator, updateOnboardingExtensionRequestValidator } from "./onboardingExtensionRequest";
13+
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOnboardingExtensionRequest } from "../../types/onboardingExtension";
1414

1515
export const createRequestsMiddleware = async (
1616
req: OooRequestCreateRequest|ExtensionRequestRequest | TaskRequestRequest | OnboardingExtensionCreateRequest,
@@ -121,3 +121,28 @@ export const getRequestsMiddleware = async (req: OooRequestCreateRequest, res: O
121121
res.boom.badRequest(errorMessages);
122122
}
123123
};
124+
125+
/**
126+
* Validates update requests based on their type.
127+
*
128+
* @param {UpdateOnboardingExtensionRequest} req - Request object.
129+
* @param {CustomResponse} res - Response object.
130+
* @param {NextFunction} next - Next middleware if valid.
131+
* @returns {Promise<void>} Resolves or sends errors.
132+
*/
133+
export const updateRequestValidator = async (
134+
req: UpdateOnboardingExtensionRequest,
135+
res: CustomResponse,
136+
next: NextFunction
137+
): Promise<void> => {
138+
const type = req.body.type;
139+
switch (type) {
140+
case REQUEST_TYPE.ONBOARDING:
141+
await updateOnboardingExtensionRequestValidator(
142+
req,
143+
res as OnboardingExtensionResponse, next);
144+
break;
145+
default:
146+
return res.boom.badRequest("Invalid type");
147+
}
148+
};

0 commit comments

Comments
 (0)