From 45e79de5959622e9401cea3159a5feed8ae56b09 Mon Sep 17 00:00:00 2001 From: AnujChhikara Date: Sat, 21 Dec 2024 12:24:02 +0530 Subject: [PATCH 1/7] initial --- controllers/progresses.js | 2 +- middlewares/validators/progresses.js | 5 ++ models/progresses.js | 34 +++++++++- test/integration/progressesTasks.test.js | 57 +++++++++++++++++ test/integration/progressesUsers.test.js | 79 ++++++++++++++++++++++++ 5 files changed, 174 insertions(+), 3 deletions(-) diff --git a/controllers/progresses.js b/controllers/progresses.js index cdf1f31bc..5e1b24870 100644 --- a/controllers/progresses.js +++ b/controllers/progresses.js @@ -217,7 +217,7 @@ const getProgressRangeData = async (req, res) => { const getProgressBydDateController = async (req, res) => { try { - const data = await getProgressByDate(req.params); + const data = await getProgressByDate(req.params, req.query); return res.json({ message: PROGRESS_DOCUMENT_RETRIEVAL_SUCCEEDED, data, diff --git a/middlewares/validators/progresses.js b/middlewares/validators/progresses.js index 27af88a39..a1a17ffcc 100644 --- a/middlewares/validators/progresses.js +++ b/middlewares/validators/progresses.js @@ -63,6 +63,9 @@ const validateGetProgressRecordsQuery = async (req, res, next) => { taskId: joi.string().optional().allow("").messages({ "string.base": "taskId must be a string", }), + dev: joi.boolean().optional().messages({ + "boolean.base": "dev must be a boolean value (true or false).", + }), orderBy: joi .string() .optional() @@ -92,6 +95,7 @@ const validateGetRangeProgressRecordsParams = async (req, res, next) => { taskId: joi.string().optional(), startDate: joi.date().iso().required(), endDate: joi.date().iso().min(joi.ref("startDate")).required(), + dev: joi.boolean().optional(), }) .xor("userId", "taskId") .messages({ @@ -121,6 +125,7 @@ const validateGetDayProgressParams = async (req, res, next) => { }), typeId: joi.string().required(), date: joi.date().iso().required(), + dev: joi.boolean().optional(), }); try { await schema.validateAsync(req.params, { abortEarly: false }); diff --git a/models/progresses.js b/models/progresses.js index 8e8a622d9..9925d0bb7 100644 --- a/models/progresses.js +++ b/models/progresses.js @@ -13,6 +13,7 @@ const { getProgressDateTimestamp, buildQueryToSearchProgressByDay, } = require("../utils/progresses"); +const { fetchUser } = require("./users"); const { PROGRESS_ALREADY_CREATED, PROGRESS_DOCUMENT_NOT_FOUND } = PROGRESSES_RESPONSE_MESSAGES; /** @@ -47,9 +48,31 @@ const createProgressDocument = async (progressData) => { * @throws {Error} If the userId or taskId is invalid or does not exist. **/ const getProgressDocument = async (queryParams) => { + const { dev } = queryParams; await assertUserOrTaskExists(queryParams); const query = buildQueryToFetchDocs(queryParams); const progressDocs = await getProgressDocs(query); + + if (dev) { + try { + const uniqueUserIds = [...new Set(progressDocs.map((doc) => doc.userId))]; + const usersData = await Promise.all(uniqueUserIds.map((userId) => fetchUser({ userId }))); + + const userLookupMap = usersData.reduce((lookup, { user }) => { + if (user) lookup[user.id] = user; + return lookup; + }, {}); + + const progressDocsWithUserDetails = progressDocs.map((doc) => { + const userDetails = userLookupMap[doc.userId] || null; + return { ...doc, userData: userDetails }; + }); + + return progressDocsWithUserDetails; + } catch (err) { + return progressDocs.map((doc) => ({ ...doc, userData: null })); + } + } return progressDocs; }; @@ -77,8 +100,9 @@ const getRangeProgressData = async (queryParams) => { * @returns {Promise} A Promise that resolves with the progress records of the queried user or task. * @throws {Error} If the userId or taskId is invalid or does not exist. **/ -async function getProgressByDate(pathParams) { +async function getProgressByDate(pathParams, queryParams) { const { type, typeId, date } = pathParams; + const { dev } = queryParams; await assertUserOrTaskExists({ [TYPE_MAP[type]]: typeId }); const query = buildQueryToSearchProgressByDay({ [TYPE_MAP[type]]: typeId, date }); const result = await query.get(); @@ -86,7 +110,13 @@ async function getProgressByDate(pathParams) { throw new NotFound(PROGRESS_DOCUMENT_NOT_FOUND); } const doc = result.docs[0]; - return { id: doc.id, ...doc.data() }; + const docData = doc.data(); + if (dev) { + const { user: userData } = await fetchUser({ userId: docData.userId }); + return { id: doc.id, ...docData, userData }; + } + + return { id: doc.id, ...docData }; } module.exports = { createProgressDocument, getProgressDocument, getRangeProgressData, getProgressByDate }; diff --git a/test/integration/progressesTasks.test.js b/test/integration/progressesTasks.test.js index 704c534db..d1921b162 100644 --- a/test/integration/progressesTasks.test.js +++ b/test/integration/progressesTasks.test.js @@ -222,6 +222,34 @@ describe("Test Progress Updates API for Tasks", function () { }); }); + it("Returns the progress array for the task with userData object when dev is true", function (done) { + chai + .request(app) + .get(`/progresses?taskId=${taskId1}&dev=true`) + .end((err, res) => { + if (err) return done(err); + expect(res).to.have.status(200); + expect(res.body).to.have.keys(["message", "data", "count"]); + expect(res.body.data).to.be.an("array"); + expect(res.body.message).to.be.equal("Progress document retrieved successfully."); + res.body.data.forEach((progress) => { + expect(progress).to.have.keys([ + "id", + "taskId", + "type", + "completed", + "planned", + "blockers", + "userData", + "userId", + "createdAt", + "date", + ]); + }); + return done(); + }); + }); + it("Gives 400 status when anything other than -date or date is supplied", function (done) { chai .request(app) @@ -311,6 +339,35 @@ describe("Test Progress Updates API for Tasks", function () { }); }); + it("Returns the progress array for all the tasks with userData object when dev is true", function (done) { + chai + .request(app) + .get(`/progresses?type=task&dev=true`) + .end((err, res) => { + if (err) return done(err); + expect(res).to.have.status(200); + expect(res.body).to.have.keys(["message", "data", "count"]); + expect(res.body.data).to.be.an("array"); + expect(res.body.message).to.be.equal("Progress document retrieved successfully."); + expect(res.body.count).to.be.equal(4); + res.body.data.forEach((progress) => { + expect(progress).to.have.keys([ + "id", + "taskId", + "type", + "completed", + "planned", + "blockers", + "userData", + "userId", + "createdAt", + "date", + ]); + }); + return done(); + }); + }); + it("Returns 400 for bad request", function (done) { chai .request(app) diff --git a/test/integration/progressesUsers.test.js b/test/integration/progressesUsers.test.js index e70d5d317..f0102d6ff 100644 --- a/test/integration/progressesUsers.test.js +++ b/test/integration/progressesUsers.test.js @@ -226,6 +226,60 @@ describe("Test Progress Updates API for Users", function () { }); }); + it("Returns the progress array for a specific user with userData object when dev is true", function (done) { + chai + .request(app) + .get(`/progresses?userId=${userId1}&dev=true`) + .end((err, res) => { + if (err) return done(err); + expect(res).to.have.status(200); + expect(res.body).to.have.keys(["message", "data", "count"]); + expect(res.body.data).to.be.an("array"); + expect(res.body.message).to.be.equal("Progress document retrieved successfully."); + res.body.data.forEach((progress) => { + expect(progress).to.have.keys([ + "id", + "type", + "completed", + "planned", + "blockers", + "userId", + "userData", + "createdAt", + "date", + ]); + }); + return done(); + }); + }); + + it("Returns the progress array for all the user with userData object when dev is true", function (done) { + chai + .request(app) + .get(`/progresses?type=user&dev=true`) + .end((err, res) => { + if (err) return done(err); + expect(res).to.have.status(200); + expect(res.body).to.have.keys(["message", "data", "count"]); + expect(res.body.data).to.be.an("array"); + expect(res.body.message).to.be.equal("Progress document retrieved successfully."); + res.body.data.forEach((progress) => { + expect(progress).to.have.keys([ + "id", + "type", + "completed", + "planned", + "blockers", + "userId", + "userData", + "createdAt", + "date", + ]); + }); + return done(); + }); + }); + it("Returns 400 for bad request", function (done) { chai .request(app) @@ -370,6 +424,31 @@ describe("Test Progress Updates API for Users", function () { }); }); + it("Returns the progress data for a specific user with userDat object when dev is true", function (done) { + chai + .request(app) + .get(`/progresses/user/${userId}/date/2023-05-02?dev=true`) + .end((err, res) => { + if (err) return done(err); + expect(res).to.have.status(200); + expect(res.body).to.have.keys(["message", "data"]); + expect(res.body.data).to.be.an("object"); + expect(res.body.message).to.be.equal("Progress document retrieved successfully."); + expect(res.body.data).to.have.keys([ + "id", + "type", + "completed", + "planned", + "blockers", + "userData", + "userId", + "createdAt", + "date", + ]); + return done(); + }); + }); + it("Should return 404 No progress records found if the document doesn't exist", function (done) { chai .request(app) From 9fc6982ab98d68d435511270f533a0bedb8afe3e Mon Sep 17 00:00:00 2001 From: AnujChhikara Date: Sat, 21 Dec 2024 14:01:43 +0530 Subject: [PATCH 2/7] fix typos --- test/integration/progressesTasks.test.js | 4 ++-- test/integration/progressesUsers.test.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/integration/progressesTasks.test.js b/test/integration/progressesTasks.test.js index d1921b162..089ab1c86 100644 --- a/test/integration/progressesTasks.test.js +++ b/test/integration/progressesTasks.test.js @@ -222,7 +222,7 @@ describe("Test Progress Updates API for Tasks", function () { }); }); - it("Returns the progress array for the task with userData object when dev is true", function (done) { + it("Returns the progress array for the task with userData object", function (done) { chai .request(app) .get(`/progresses?taskId=${taskId1}&dev=true`) @@ -339,7 +339,7 @@ describe("Test Progress Updates API for Tasks", function () { }); }); - it("Returns the progress array for all the tasks with userData object when dev is true", function (done) { + it("Returns the progress array for all the tasks with userData object", function (done) { chai .request(app) .get(`/progresses?type=task&dev=true`) diff --git a/test/integration/progressesUsers.test.js b/test/integration/progressesUsers.test.js index f0102d6ff..f2458a576 100644 --- a/test/integration/progressesUsers.test.js +++ b/test/integration/progressesUsers.test.js @@ -226,7 +226,7 @@ describe("Test Progress Updates API for Users", function () { }); }); - it("Returns the progress array for a specific user with userData object when dev is true", function (done) { + it("Returns the progress array for a specific user with userData object", function (done) { chai .request(app) .get(`/progresses?userId=${userId1}&dev=true`) @@ -424,7 +424,7 @@ describe("Test Progress Updates API for Users", function () { }); }); - it("Returns the progress data for a specific user with userDat object when dev is true", function (done) { + it("Returns the progress data for a specific user with userData object", function (done) { chai .request(app) .get(`/progresses/user/${userId}/date/2023-05-02?dev=true`) From 0eb05a5c5a7e928996aba0be27ff98aea8340f1a Mon Sep 17 00:00:00 2001 From: AnujChhikara Date: Tue, 24 Dec 2024 15:22:41 +0530 Subject: [PATCH 3/7] using batches to fetch userIds --- models/progresses.js | 13 +++++++++++-- test/integration/progressesTasks.test.js | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/models/progresses.js b/models/progresses.js index 9925d0bb7..bbd9fb28a 100644 --- a/models/progresses.js +++ b/models/progresses.js @@ -56,7 +56,17 @@ const getProgressDocument = async (queryParams) => { if (dev) { try { const uniqueUserIds = [...new Set(progressDocs.map((doc) => doc.userId))]; - const usersData = await Promise.all(uniqueUserIds.map((userId) => fetchUser({ userId }))); + const batchSize = 500; + + const batches = Array.from({ length: Math.ceil(uniqueUserIds.length / batchSize) }, (_, index) => + uniqueUserIds.slice(index * batchSize, index * batchSize + batchSize) + ); + + const batchPromises = batches.map((batch) => Promise.all(batch.map((userId) => fetchUser({ userId })))); + + const usersDataBatches = await Promise.all(batchPromises); + + const usersData = usersDataBatches.flat(); const userLookupMap = usersData.reduce((lookup, { user }) => { if (user) lookup[user.id] = user; @@ -67,7 +77,6 @@ const getProgressDocument = async (queryParams) => { const userDetails = userLookupMap[doc.userId] || null; return { ...doc, userData: userDetails }; }); - return progressDocsWithUserDetails; } catch (err) { return progressDocs.map((doc) => ({ ...doc, userData: null })); diff --git a/test/integration/progressesTasks.test.js b/test/integration/progressesTasks.test.js index 089ab1c86..d9be00709 100644 --- a/test/integration/progressesTasks.test.js +++ b/test/integration/progressesTasks.test.js @@ -250,6 +250,21 @@ describe("Test Progress Updates API for Tasks", function () { }); }); + it("Returns a 404 error when the task does not exist", function (done) { + chai + .request(app) + .get(`/progresses?taskId=nonExistingTaskId&dev=true`) + .end((err, res) => { + if (err) return done(err); + + expect(res).to.have.status(404); + expect(res.body).to.have.keys(["message"]); + expect(res.body.message).to.be.equal(`Task with id nonExistingTaskId does not exist.`); + + return done(); + }); + }); + it("Gives 400 status when anything other than -date or date is supplied", function (done) { chai .request(app) From dc896b5fb0a1f8ef0c6210b0e829c8694d9277d6 Mon Sep 17 00:00:00 2001 From: AnujChhikara Date: Mon, 30 Dec 2024 10:56:39 +0530 Subject: [PATCH 4/7] refactor the function --- models/progresses.js | 67 +++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/models/progresses.js b/models/progresses.js index bbd9fb28a..18f2c6ba7 100644 --- a/models/progresses.js +++ b/models/progresses.js @@ -13,7 +13,7 @@ const { getProgressDateTimestamp, buildQueryToSearchProgressByDay, } = require("../utils/progresses"); -const { fetchUser } = require("./users"); +const { fetchUserByIds, fetchUser } = require("./users"); const { PROGRESS_ALREADY_CREATED, PROGRESS_DOCUMENT_NOT_FOUND } = PROGRESSES_RESPONSE_MESSAGES; /** @@ -54,33 +54,7 @@ const getProgressDocument = async (queryParams) => { const progressDocs = await getProgressDocs(query); if (dev) { - try { - const uniqueUserIds = [...new Set(progressDocs.map((doc) => doc.userId))]; - const batchSize = 500; - - const batches = Array.from({ length: Math.ceil(uniqueUserIds.length / batchSize) }, (_, index) => - uniqueUserIds.slice(index * batchSize, index * batchSize + batchSize) - ); - - const batchPromises = batches.map((batch) => Promise.all(batch.map((userId) => fetchUser({ userId })))); - - const usersDataBatches = await Promise.all(batchPromises); - - const usersData = usersDataBatches.flat(); - - const userLookupMap = usersData.reduce((lookup, { user }) => { - if (user) lookup[user.id] = user; - return lookup; - }, {}); - - const progressDocsWithUserDetails = progressDocs.map((doc) => { - const userDetails = userLookupMap[doc.userId] || null; - return { ...doc, userData: userDetails }; - }); - return progressDocsWithUserDetails; - } catch (err) { - return progressDocs.map((doc) => ({ ...doc, userData: null })); - } + return await addUserDetailsToProgressDocs(progressDocs); } return progressDocs; }; @@ -128,4 +102,39 @@ async function getProgressByDate(pathParams, queryParams) { return { id: doc.id, ...docData }; } -module.exports = { createProgressDocument, getProgressDocument, getRangeProgressData, getProgressByDate }; +/** + * Adds user details to progress documents by fetching unique users. + * This function retrieves user details for each user ID in the progress documents and attaches the user data to each document. + * + * @param {Array} progressDocs - An array of progress documents. Each document should include a `userId` property. + * @returns {Promise>} A Promise that resolves to an array of progress documents with the `userData` field populated. + * If an error occurs while fetching the user details, the `userData` field will be set to `null` for each document. + */ +const addUserDetailsToProgressDocs = async (progressDocs) => { + try { + const uniqueUserIds = [...new Set(progressDocs.map((doc) => doc.userId))]; + + const uniqueUsersData = await fetchUserByIds(uniqueUserIds); + + const allUsers = uniqueUsersData.flat(); + const userByIdMap = allUsers.reduce((lookup, user) => { + if (user) lookup[user.id] = user; + return lookup; + }, {}); + + return progressDocs.map((doc) => { + const userDetails = userByIdMap[doc.userId] || null; + return { ...doc, userData: userDetails }; + }); + } catch (err) { + return progressDocs.map((doc) => ({ ...doc, userData: null })); + } +}; + +module.exports = { + createProgressDocument, + getProgressDocument, + getRangeProgressData, + getProgressByDate, + addUserDetailsToProgressDocs, +}; From 3ab3358e0eb369ce5f6ebf785b845b768fb0b184 Mon Sep 17 00:00:00 2001 From: AnujChhikara Date: Tue, 31 Dec 2024 09:35:27 +0530 Subject: [PATCH 5/7] added test for dev false case --- models/progresses.js | 4 ++-- test/integration/progressesTasks.test.js | 27 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/models/progresses.js b/models/progresses.js index 18f2c6ba7..b6ffa858e 100644 --- a/models/progresses.js +++ b/models/progresses.js @@ -53,7 +53,7 @@ const getProgressDocument = async (queryParams) => { const query = buildQueryToFetchDocs(queryParams); const progressDocs = await getProgressDocs(query); - if (dev) { + if (dev === "true") { return await addUserDetailsToProgressDocs(progressDocs); } return progressDocs; @@ -94,7 +94,7 @@ async function getProgressByDate(pathParams, queryParams) { } const doc = result.docs[0]; const docData = doc.data(); - if (dev) { + if (dev === "true") { const { user: userData } = await fetchUser({ userId: docData.userId }); return { id: doc.id, ...docData, userData }; } diff --git a/test/integration/progressesTasks.test.js b/test/integration/progressesTasks.test.js index d9be00709..14a00ffe6 100644 --- a/test/integration/progressesTasks.test.js +++ b/test/integration/progressesTasks.test.js @@ -250,6 +250,33 @@ describe("Test Progress Updates API for Tasks", function () { }); }); + it("Returns the progress array for the task without userData field if dev is false", function (done) { + chai + .request(app) + .get(`/progresses?taskId=${taskId1}&dev=false`) + .end((err, res) => { + if (err) return done(err); + expect(res).to.have.status(200); + expect(res.body).to.have.keys(["message", "data", "count"]); + expect(res.body.data).to.be.an("array"); + expect(res.body.message).to.be.equal("Progress document retrieved successfully."); + res.body.data.forEach((progress) => { + expect(progress).to.have.keys([ + "id", + "taskId", + "type", + "completed", + "planned", + "blockers", + "userId", + "createdAt", + "date", + ]); + }); + return done(); + }); + }); + it("Returns a 404 error when the task does not exist", function (done) { chai .request(app) From 4d4a2f17e3efcec3dc1492bfac766f3cbc92bf9f Mon Sep 17 00:00:00 2001 From: AnujChhikara Date: Fri, 3 Jan 2025 12:12:38 +0530 Subject: [PATCH 6/7] added unit tests --- test/unit/models/progresses.test.js | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 test/unit/models/progresses.test.js diff --git a/test/unit/models/progresses.test.js b/test/unit/models/progresses.test.js new file mode 100644 index 000000000..d5c5b75f2 --- /dev/null +++ b/test/unit/models/progresses.test.js @@ -0,0 +1,44 @@ +const chai = require("chai"); +const sinon = require("sinon"); +const { expect } = chai; +const { addUserDetailsToProgressDocs } = require("../../../models/progresses"); +const cleanDb = require("../../utils/cleanDb"); +const users = require("../../../models/users"); +const userDataArray = require("../../fixtures/user/user")(); +describe("getProgressDocument", function () { + afterEach(function () { + cleanDb(); + sinon.restore(); + }); + + it("should add userData to progress documents correctly", async function () { + const userData = userDataArray[0]; + const userData2 = userDataArray[1]; + const { userId } = await users.addOrUpdate(userData); + const { userId: userId2 } = await users.addOrUpdate(userData2); + const updatedUserData = { ...userData, id: userId }; + const updatedUserData2 = { ...userData2, id: userId2 }; + const mockProgressDocs = [ + { userId: userId, taskId: 101 }, + { userId: userId2, taskId: 102 }, + ]; + + const result = await addUserDetailsToProgressDocs(mockProgressDocs); + + expect(result).to.deep.equal([ + { userId, taskId: 101, userData: updatedUserData }, + { userId: userId2, taskId: 102, userData: updatedUserData2 }, + ]); + }); + + it("should handle errors and set userData as null", async function () { + const userData = userDataArray[0]; + await users.addOrUpdate(userData); + + const mockProgressDocs = [{ userId: "userIdNotExists", taskId: 101 }]; + + const result = await addUserDetailsToProgressDocs(mockProgressDocs); + + expect(result).to.deep.equal([{ userId: "userIdNotExists", taskId: 101, userData: null }]); + }); +}); From 7d73ac7e71eaba6463dc5f36af091997559043e8 Mon Sep 17 00:00:00 2001 From: AnujChhikara Date: Sat, 4 Jan 2025 11:33:00 +0530 Subject: [PATCH 7/7] fix response body containing email --- models/progresses.js | 9 +++++---- test/unit/models/progresses.test.js | 3 +++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/models/progresses.js b/models/progresses.js index b6ffa858e..e406d9f15 100644 --- a/models/progresses.js +++ b/models/progresses.js @@ -13,7 +13,7 @@ const { getProgressDateTimestamp, buildQueryToSearchProgressByDay, } = require("../utils/progresses"); -const { fetchUserByIds, fetchUser } = require("./users"); +const { retrieveUsers } = require("../services/dataAccessLayer"); const { PROGRESS_ALREADY_CREATED, PROGRESS_DOCUMENT_NOT_FOUND } = PROGRESSES_RESPONSE_MESSAGES; /** @@ -95,7 +95,7 @@ async function getProgressByDate(pathParams, queryParams) { const doc = result.docs[0]; const docData = doc.data(); if (dev === "true") { - const { user: userData } = await fetchUser({ userId: docData.userId }); + const { user: userData } = await retrieveUsers({ id: docData.userId }); return { id: doc.id, ...docData, userData }; } @@ -114,8 +114,9 @@ const addUserDetailsToProgressDocs = async (progressDocs) => { try { const uniqueUserIds = [...new Set(progressDocs.map((doc) => doc.userId))]; - const uniqueUsersData = await fetchUserByIds(uniqueUserIds); - + const uniqueUsersData = await retrieveUsers({ + userIds: uniqueUserIds, + }); const allUsers = uniqueUsersData.flat(); const userByIdMap = allUsers.reduce((lookup, user) => { if (user) lookup[user.id] = user; diff --git a/test/unit/models/progresses.test.js b/test/unit/models/progresses.test.js index d5c5b75f2..28a57207b 100644 --- a/test/unit/models/progresses.test.js +++ b/test/unit/models/progresses.test.js @@ -5,6 +5,7 @@ const { addUserDetailsToProgressDocs } = require("../../../models/progresses"); const cleanDb = require("../../utils/cleanDb"); const users = require("../../../models/users"); const userDataArray = require("../../fixtures/user/user")(); +const { removeSensitiveInfo } = require("../../../services/dataAccessLayer"); describe("getProgressDocument", function () { afterEach(function () { cleanDb(); @@ -18,6 +19,8 @@ describe("getProgressDocument", function () { const { userId: userId2 } = await users.addOrUpdate(userData2); const updatedUserData = { ...userData, id: userId }; const updatedUserData2 = { ...userData2, id: userId2 }; + removeSensitiveInfo(updatedUserData); + removeSensitiveInfo(updatedUserData2); const mockProgressDocs = [ { userId: userId, taskId: 101 }, { userId: userId2, taskId: 102 },