From 27e35d9412199c4f5ca2627cee7aeb4889b898c8 Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 14 Jun 2024 14:27:59 +0100 Subject: [PATCH 01/61] Add initial filtered questionnaires list --- .../db/datastore/datastore-firestore.js | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-firestore.js b/eq-author-api/db/datastore/datastore-firestore.js index a99a958298..007468e242 100644 --- a/eq-author-api/db/datastore/datastore-firestore.js +++ b/eq-author-api/db/datastore/datastore-firestore.js @@ -374,6 +374,64 @@ const listQuestionnaires = async () => { } }; +const listFilteredQuestionnaires = async (limit, currentPage) => { + try { + // TODO: Replace with limit and current page from GraphQL query's input + const limit = 10; + const currentPage = 2; + // Orders questionnaires by when they were created + let questionnairesQuery = db + .collection("questionnaires") + .orderBy("createdAt"); + + // Gets the number of questionnaire documents to skip based on the current page + const questionnairesToSkip = (currentPage - 1) * limit; + + if (questionnairesToSkip > 0) { + // Based on the number of questionnaires to skip, gets questionnaire documents that need to be skipped to get to the first questionnaire document of the current page + const startAtSnapshot = await questionnairesQuery + .limit(questionnairesToSkip) + .get(); + + // Gets the last questionnaire document on the previous page (from the questionnaires to skip) + const lastDocumentSnapshot = + startAtSnapshot.docs[startAtSnapshot.docs.length - 1]; + + // Starts the query after the last questionnaire document on the previous page + questionnairesQuery = + questionnairesQuery.startAfter(lastDocumentSnapshot); + } + + if (limit) { + questionnairesQuery = questionnairesQuery.limit(limit); + } + + const questionnairesSnapshot = await questionnairesQuery.get(); + + if (questionnairesSnapshot.empty) { + logger.info("No questionnaires found (from listQuestionnaires)"); + return []; + } + + const questionnaires = questionnairesSnapshot.docs + .map((doc) => ({ + ...doc.data(), + editors: doc.data().editors || [], + createdAt: doc.data().createdAt.toDate(), + updatedAt: doc.data().updatedAt.toDate(), + })) + .sort((a, b) => (a.createdAt > b.createdAt ? -1 : 1)); + + return questionnaires || []; + } catch (error) { + logger.error( + error, + "Unable to retrieve questionnaires (from listQuestionnaires)" + ); + return; + } +}; + const deleteQuestionnaire = async (id) => { try { await db.collection("questionnaires").doc(id).delete(); @@ -666,6 +724,7 @@ module.exports = { saveQuestionnaire, deleteQuestionnaire, listQuestionnaires, + listFilteredQuestionnaires, getQuestionnaire, getQuestionnaireMetaById, getQuestionnaireByVersionId, From 6a8ace793b45653a73eb4d6030337dca96e84f3d Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 18 Jun 2024 13:07:40 +0100 Subject: [PATCH 02/61] Add query for questionnaires on first page --- .../db/datastore/datastore-firestore.js | 31 +++---------------- .../schema/resolvers/homepage/index.js | 17 ++++++++++ eq-author-api/schema/resolvers/index.js | 2 ++ eq-author-api/schema/typeDefs.js | 5 +++ 4 files changed, 29 insertions(+), 26 deletions(-) create mode 100644 eq-author-api/schema/resolvers/homepage/index.js diff --git a/eq-author-api/db/datastore/datastore-firestore.js b/eq-author-api/db/datastore/datastore-firestore.js index 007468e242..1025a926aa 100644 --- a/eq-author-api/db/datastore/datastore-firestore.js +++ b/eq-author-api/db/datastore/datastore-firestore.js @@ -374,37 +374,16 @@ const listQuestionnaires = async () => { } }; -const listFilteredQuestionnaires = async (limit, currentPage) => { +const listFirstPageQuestionnaires = async (input) => { try { - // TODO: Replace with limit and current page from GraphQL query's input - const limit = 10; - const currentPage = 2; + const { limit } = input; + // Orders questionnaires by when they were created let questionnairesQuery = db .collection("questionnaires") .orderBy("createdAt"); - // Gets the number of questionnaire documents to skip based on the current page - const questionnairesToSkip = (currentPage - 1) * limit; - - if (questionnairesToSkip > 0) { - // Based on the number of questionnaires to skip, gets questionnaire documents that need to be skipped to get to the first questionnaire document of the current page - const startAtSnapshot = await questionnairesQuery - .limit(questionnairesToSkip) - .get(); - - // Gets the last questionnaire document on the previous page (from the questionnaires to skip) - const lastDocumentSnapshot = - startAtSnapshot.docs[startAtSnapshot.docs.length - 1]; - - // Starts the query after the last questionnaire document on the previous page - questionnairesQuery = - questionnairesQuery.startAfter(lastDocumentSnapshot); - } - - if (limit) { - questionnairesQuery = questionnairesQuery.limit(limit); - } + questionnairesQuery = questionnairesQuery.limit(limit); const questionnairesSnapshot = await questionnairesQuery.get(); @@ -724,7 +703,7 @@ module.exports = { saveQuestionnaire, deleteQuestionnaire, listQuestionnaires, - listFilteredQuestionnaires, + listFirstPageQuestionnaires, getQuestionnaire, getQuestionnaireMetaById, getQuestionnaireByVersionId, diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/homepage/index.js new file mode 100644 index 0000000000..3e387cb25d --- /dev/null +++ b/eq-author-api/schema/resolvers/homepage/index.js @@ -0,0 +1,17 @@ +const { listFirstPageQuestionnaires } = require("../../../db/datastore"); + +const Resolvers = { + Query: { + firstPageQuestionnaires: async (_, { input = {} }, ctx) => { + const { limit = 10 } = input; + + const questionnaires = await listFirstPageQuestionnaires({ + limit, + }); + + return questionnaires; + }, + }, +}; + +module.exports = Resolvers; diff --git a/eq-author-api/schema/resolvers/index.js b/eq-author-api/schema/resolvers/index.js index b41c34e181..c6a62adefd 100644 --- a/eq-author-api/schema/resolvers/index.js +++ b/eq-author-api/schema/resolvers/index.js @@ -4,6 +4,7 @@ const binaryExpression2 = require("./logic/binaryExpression2"); const page = require("./pages"); const questionnaireIntroduction = require("./questionnaireIntroduction"); const importing = require("./importing"); +const homepage = require("./homepage"); module.exports = [ base, @@ -12,4 +13,5 @@ module.exports = [ ...page, ...questionnaireIntroduction, importing, + homepage, ]; diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index 1b445c28f1..19c56adbc2 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -895,6 +895,7 @@ type PublishHistoryEvent { type Query { questionnaires(input: QuestionnairesInput): [Questionnaire] + firstPageQuestionnaires(input: FirstPageQuestionnairesInput): [Questionnaire] questionnaire(input: QueryInput!): Questionnaire history(input: QueryInput!): [History] section(input: QueryInput!): Section @@ -935,6 +936,10 @@ input QuestionnairesInput { filter: QuestionnairesFilter } +input FirstPageQuestionnairesInput { + limit: Int +} + input QueryInput { id: ID questionnaireId: ID From 2237b9969f85403cc679fe80eef5eea2b2c973cc Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 19 Jun 2024 11:32:58 +0100 Subject: [PATCH 03/61] Add query for questionnaires on next page --- .../db/datastore/datastore-firestore.js | 64 +++++++++++++++---- .../schema/resolvers/homepage/index.js | 16 ++++- eq-author-api/schema/typeDefs.js | 6 ++ 3 files changed, 74 insertions(+), 12 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-firestore.js b/eq-author-api/db/datastore/datastore-firestore.js index 1025a926aa..143392f5e1 100644 --- a/eq-author-api/db/datastore/datastore-firestore.js +++ b/eq-author-api/db/datastore/datastore-firestore.js @@ -378,10 +378,10 @@ const listFirstPageQuestionnaires = async (input) => { try { const { limit } = input; - // Orders questionnaires by when they were created + // Orders questionnaires by when they were created, starting with the newest let questionnairesQuery = db .collection("questionnaires") - .orderBy("createdAt"); + .orderBy("createdAt", "desc"); questionnairesQuery = questionnairesQuery.limit(limit); @@ -392,20 +392,61 @@ const listFirstPageQuestionnaires = async (input) => { return []; } - const questionnaires = questionnairesSnapshot.docs - .map((doc) => ({ - ...doc.data(), - editors: doc.data().editors || [], - createdAt: doc.data().createdAt.toDate(), - updatedAt: doc.data().updatedAt.toDate(), - })) - .sort((a, b) => (a.createdAt > b.createdAt ? -1 : 1)); + const questionnaires = questionnairesSnapshot.docs.map((doc) => ({ + ...doc.data(), + editors: doc.data().editors || [], + createdAt: doc.data().createdAt.toDate(), + updatedAt: doc.data().updatedAt.toDate(), + })); + return questionnaires || []; + } catch (error) { + logger.error( + error, + "Unable to retrieve questionnaires (from listFirstPageQuestionnaires)" + ); + return; + } +}; + +const listNextPageQuestionnaires = async (input) => { + try { + const { limit, lastQuestionnaireIdOnPage } = input; + + // Orders questionnaires by when they were created, starting with the newest + let questionnairesQuery = db + .collection("questionnaires") + .orderBy("createdAt", "desc"); + + // Gets last questionnaire on current page based on lastQuestionnaireIdOnPage + const lastQuestionnaireOnPage = await db + .collection("questionnaires") + .doc(lastQuestionnaireIdOnPage) + .get(); + + // Gets next questionnaires after lastQuestionnaireOnPage, limiting the number of questionnaires to limit + questionnairesQuery = questionnairesQuery + .startAfter(lastQuestionnaireOnPage) + .limit(limit); + + const questionnairesSnapshot = await questionnairesQuery.get(); + + if (questionnairesSnapshot.empty) { + logger.info("No questionnaires found (from listQuestionnaires)"); + return []; + } + + const questionnaires = questionnairesSnapshot.docs.map((doc) => ({ + ...doc.data(), + editors: doc.data().editors || [], + createdAt: doc.data().createdAt.toDate(), + updatedAt: doc.data().updatedAt.toDate(), + })); return questionnaires || []; } catch (error) { logger.error( error, - "Unable to retrieve questionnaires (from listQuestionnaires)" + "Unable to retrieve questionnaires (from listNextPageQuestionnaires)" ); return; } @@ -704,6 +745,7 @@ module.exports = { deleteQuestionnaire, listQuestionnaires, listFirstPageQuestionnaires, + listNextPageQuestionnaires, getQuestionnaire, getQuestionnaireMetaById, getQuestionnaireByVersionId, diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/homepage/index.js index 3e387cb25d..a530c2f6c0 100644 --- a/eq-author-api/schema/resolvers/homepage/index.js +++ b/eq-author-api/schema/resolvers/homepage/index.js @@ -1,4 +1,7 @@ -const { listFirstPageQuestionnaires } = require("../../../db/datastore"); +const { + listFirstPageQuestionnaires, + listNextPageQuestionnaires, +} = require("../../../db/datastore"); const Resolvers = { Query: { @@ -11,6 +14,17 @@ const Resolvers = { return questionnaires; }, + + nextPageQuestionnaires: async (_, { input = {} }, ctx) => { + const { limit = 10, lastQuestionnaireIdOnPage } = input; + + const nextQuestionnaires = await listNextPageQuestionnaires({ + limit, + lastQuestionnaireIdOnPage, + }); + + return nextQuestionnaires; + }, }, }; diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index 19c56adbc2..d41c1abff4 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -896,6 +896,7 @@ type PublishHistoryEvent { type Query { questionnaires(input: QuestionnairesInput): [Questionnaire] firstPageQuestionnaires(input: FirstPageQuestionnairesInput): [Questionnaire] + nextPageQuestionnaires(input: NextPageQuestionnairesInput): [Questionnaire] questionnaire(input: QueryInput!): Questionnaire history(input: QueryInput!): [History] section(input: QueryInput!): Section @@ -940,6 +941,11 @@ input FirstPageQuestionnairesInput { limit: Int } +input NextPageQuestionnairesInput { + limit: Int + lastQuestionnaireIdOnPage: ID! +} + input QueryInput { id: ID questionnaireId: ID From f86faa86162e86545082d2291c27ecdb73a3c534 Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 20 Jun 2024 12:06:50 +0100 Subject: [PATCH 04/61] Update to use filteredQuestionnaires --- .../db/datastore/datastore-firestore.js | 91 +++++++++---------- .../schema/resolvers/homepage/index.js | 28 ++---- eq-author-api/schema/typeDefs.js | 12 +-- 3 files changed, 55 insertions(+), 76 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-firestore.js b/eq-author-api/db/datastore/datastore-firestore.js index 143392f5e1..65be0567ff 100644 --- a/eq-author-api/db/datastore/datastore-firestore.js +++ b/eq-author-api/db/datastore/datastore-firestore.js @@ -374,64 +374,57 @@ const listQuestionnaires = async () => { } }; -const listFirstPageQuestionnaires = async (input) => { +const listFilteredQuestionnaires = async (input) => { try { - const { limit } = input; + const { limit, firstQuestionnaireIdOnPage, lastQuestionnaireIdOnPage } = + input; // Orders questionnaires by when they were created, starting with the newest let questionnairesQuery = db .collection("questionnaires") .orderBy("createdAt", "desc"); - questionnairesQuery = questionnairesQuery.limit(limit); - - const questionnairesSnapshot = await questionnairesQuery.get(); - - if (questionnairesSnapshot.empty) { - logger.info("No questionnaires found (from listQuestionnaires)"); - return []; + // Gets questionnaires on first page when firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage are not provided + if (!firstQuestionnaireIdOnPage && !lastQuestionnaireIdOnPage) { + questionnairesQuery = questionnairesQuery.limit(limit); + } + // Gets questionnaires on previous page when firstQuestionnaireIdOnPage is provided without lastQuestionnaireIdOnPage + else if (firstQuestionnaireIdOnPage && !lastQuestionnaireIdOnPage) { + // Gets first questionnaire on current page based on firstQuestionnaireIdOnPage + const firstQuestionnaireOnPage = await db + .collection("questionnaires") + .doc(firstQuestionnaireIdOnPage) + .get(); + + // Gets previous questionnaires before firstQuestionnaireOnPage, limiting the number of questionnaires to `limit` + questionnairesQuery = questionnairesQuery + .endBefore(firstQuestionnaireOnPage) + .limitToLast(limit); + } + // Gets questionnaires on next page when lastQuestionnaireIdOnPage is provided without firstQuestionnaireIdOnPage + else if (lastQuestionnaireIdOnPage && !firstQuestionnaireIdOnPage) { + // Gets last questionnaire on current page based on lastQuestionnaireIdOnPage + const lastQuestionnaireOnPage = await db + .collection("questionnaires") + .doc(lastQuestionnaireIdOnPage) + .get(); + + // Gets next questionnaires after lastQuestionnaireOnPage, limiting the number of questionnaires to `limit` + questionnairesQuery = questionnairesQuery + .startAfter(lastQuestionnaireOnPage) + .limit(limit); + } + // Throws an error when both firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage are provided + else { + logger.error( + "Invalid input - both firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage have been provided (from listFilteredQuestionnaires)" + ); } - - const questionnaires = questionnairesSnapshot.docs.map((doc) => ({ - ...doc.data(), - editors: doc.data().editors || [], - createdAt: doc.data().createdAt.toDate(), - updatedAt: doc.data().updatedAt.toDate(), - })); - return questionnaires || []; - } catch (error) { - logger.error( - error, - "Unable to retrieve questionnaires (from listFirstPageQuestionnaires)" - ); - return; - } -}; - -const listNextPageQuestionnaires = async (input) => { - try { - const { limit, lastQuestionnaireIdOnPage } = input; - - // Orders questionnaires by when they were created, starting with the newest - let questionnairesQuery = db - .collection("questionnaires") - .orderBy("createdAt", "desc"); - - // Gets last questionnaire on current page based on lastQuestionnaireIdOnPage - const lastQuestionnaireOnPage = await db - .collection("questionnaires") - .doc(lastQuestionnaireIdOnPage) - .get(); - - // Gets next questionnaires after lastQuestionnaireOnPage, limiting the number of questionnaires to limit - questionnairesQuery = questionnairesQuery - .startAfter(lastQuestionnaireOnPage) - .limit(limit); const questionnairesSnapshot = await questionnairesQuery.get(); if (questionnairesSnapshot.empty) { - logger.info("No questionnaires found (from listQuestionnaires)"); + logger.info("No questionnaires found (from listFilteredQuestionnaires)"); return []; } @@ -441,12 +434,11 @@ const listNextPageQuestionnaires = async (input) => { createdAt: doc.data().createdAt.toDate(), updatedAt: doc.data().updatedAt.toDate(), })); - return questionnaires || []; } catch (error) { logger.error( error, - "Unable to retrieve questionnaires (from listNextPageQuestionnaires)" + "Unable to retrieve questionnaires (from listQuestionnaires)" ); return; } @@ -744,8 +736,7 @@ module.exports = { saveQuestionnaire, deleteQuestionnaire, listQuestionnaires, - listFirstPageQuestionnaires, - listNextPageQuestionnaires, + listFilteredQuestionnaires, getQuestionnaire, getQuestionnaireMetaById, getQuestionnaireByVersionId, diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/homepage/index.js index a530c2f6c0..8d47b5dd0b 100644 --- a/eq-author-api/schema/resolvers/homepage/index.js +++ b/eq-author-api/schema/resolvers/homepage/index.js @@ -1,29 +1,21 @@ -const { - listFirstPageQuestionnaires, - listNextPageQuestionnaires, -} = require("../../../db/datastore"); +const { listFilteredQuestionnaires } = require("../../../db/datastore"); const Resolvers = { Query: { - firstPageQuestionnaires: async (_, { input = {} }, ctx) => { - const { limit = 10 } = input; - - const questionnaires = await listFirstPageQuestionnaires({ - limit, - }); - - return questionnaires; - }, - - nextPageQuestionnaires: async (_, { input = {} }, ctx) => { - const { limit = 10, lastQuestionnaireIdOnPage } = input; + filteredQuestionnaires: async (_, { input = {} }, ctx) => { + const { + limit = 10, + firstQuestionnaireIdOnPage, + lastQuestionnaireIdOnPage, + } = input; - const nextQuestionnaires = await listNextPageQuestionnaires({ + const questionnaires = await listFilteredQuestionnaires({ limit, + firstQuestionnaireIdOnPage, lastQuestionnaireIdOnPage, }); - return nextQuestionnaires; + return questionnaires; }, }, }; diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index d41c1abff4..9ee9045429 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -895,8 +895,7 @@ type PublishHistoryEvent { type Query { questionnaires(input: QuestionnairesInput): [Questionnaire] - firstPageQuestionnaires(input: FirstPageQuestionnairesInput): [Questionnaire] - nextPageQuestionnaires(input: NextPageQuestionnairesInput): [Questionnaire] + filteredQuestionnaires(input: FilteredQuestionnairesInput): [Questionnaire] questionnaire(input: QueryInput!): Questionnaire history(input: QueryInput!): [History] section(input: QueryInput!): Section @@ -937,13 +936,10 @@ input QuestionnairesInput { filter: QuestionnairesFilter } -input FirstPageQuestionnairesInput { +input FilteredQuestionnairesInput { limit: Int -} - -input NextPageQuestionnairesInput { - limit: Int - lastQuestionnaireIdOnPage: ID! + firstQuestionnaireIdOnPage: ID + lastQuestionnaireIdOnPage: ID } input QueryInput { From 4e1c7e6b076ed53af39ab7c0711f8d037c261d9a Mon Sep 17 00:00:00 2001 From: farres1 Date: Mon, 24 Jun 2024 13:15:24 +0100 Subject: [PATCH 05/61] Filter questionnaires by page for MongoDB --- .../db/datastore/datastore-mongodb.js | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index 0d05bbc69a..8528b19332 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -290,6 +290,93 @@ const listQuestionnaires = async () => { } }; +const listFilteredQuestionnaires = async (input) => { + try { + const { limit, firstQuestionnaireIdOnPage, lastQuestionnaireIdOnPage } = + input; + + // Gets the questionnaires collection + const questionnairesCollection = dbo.collection("questionnaires"); + let questionnairesQuery; + + // Gets questionnaires on first page when firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage are not provided + if (!firstQuestionnaireIdOnPage && !lastQuestionnaireIdOnPage) { + questionnairesQuery = questionnairesCollection + .find() + .sort({ createdAt: -1 }) + .limit(limit); + } + // Gets questionnaires on previous page when firstQuestionnaireIdOnPage is provided without lastQuestionnaireIdOnPage + else if (firstQuestionnaireIdOnPage && !lastQuestionnaireIdOnPage) { + // Gets first questionnaire on current page based on firstQuestionnaireIdOnPage + const firstQuestionnaireOnPage = await questionnairesCollection.findOne({ + id: firstQuestionnaireIdOnPage, + }); + + /* + Gets questionnaires on previous page based on firstQuestionnaireOnPage + Uses `gt` (greater than) to find questionnaires created after firstQuestionnaireOnPage, sorts from earliest created first, and limits to `limit` number of questionnaires + */ + questionnairesQuery = questionnairesCollection + .find({ createdAt: { $gt: firstQuestionnaireOnPage.createdAt } }) + .sort({ createdAt: 1 }) + .limit(limit); + } + // Gets questionnaires on next page when lastQuestionnaireIdOnPage is provided without firstQuestionnaireIdOnPage + else if (!firstQuestionnaireIdOnPage && lastQuestionnaireIdOnPage) { + // Gets last questionnaire on current page based on lastQuestionnaireIdOnPage + const lastQuestionnaireOnPage = await questionnairesCollection.findOne({ + id: lastQuestionnaireIdOnPage, + }); + + /* + Gets questionnaires on next page based on lastQuestionnaireOnPage + Uses `lt` (less than) to find questionnaires created before lastQuestionnaireOnPage, sorts from most recently created first, and limits to `limit` number of questionnaires + */ + questionnairesQuery = questionnairesCollection + .find({ createdAt: { $lt: lastQuestionnaireOnPage.createdAt } }) + .sort({ createdAt: -1 }) + .limit(limit); + } else { + logger.error( + "Invalid input - both firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage have been provided (from listFilteredQuestionnaires)" + ); + } + + const questionnaires = await questionnairesQuery.toArray(); + + if (questionnaires.length === 0) { + logger.info("No questionnaires found (from listFilteredQuestionnaires)"); + return []; + } + + // Adds empty `editors` to each questionnaire if it does not already have `editors`, otherwise uses existing `editors` + let transformedQuestionnaires = questionnaires.map((questionnaire) => ({ + ...questionnaire, + editors: questionnaire.editors || [], + })); + + /* + Sorts questionnaires by most recently created first if firstQuestionnaireIdOnPage is provided without lastQuestionnaireIdOnPage + This condition's query previously sorted by earliest created questionnaire first to get the `limit` number of questionnaires created after firstQuestionnaireOnPage + This ensures questionnaires are displayed in order from most recently created first in this condition + */ + if (firstQuestionnaireIdOnPage && !lastQuestionnaireIdOnPage) { + transformedQuestionnaires = transformedQuestionnaires.sort((a, b) => + a.createdAt > b.createdAt ? -1 : 1 + ); + } + + return transformedQuestionnaires; + } catch (error) { + logger.error( + error, + "Unable to retrieve questionnaires (from listFilteredQuestionnaires)" + ); + return; + } +}; + const createComments = async (questionnaireId) => { try { if (!questionnaireId) { @@ -486,6 +573,7 @@ module.exports = { saveQuestionnaire, deleteQuestionnaire, listQuestionnaires, + listFilteredQuestionnaires, getQuestionnaire, getQuestionnaireMetaById, createComments, From 3dfbda48480704b6ff6b424520af088749c4d8f5 Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 25 Jun 2024 09:58:14 +0100 Subject: [PATCH 06/61] Rename limit to resultsPerPage --- .../db/datastore/datastore-firestore.js | 17 ++++++++++------- .../db/datastore/datastore-mongodb.js | 19 +++++++++++-------- .../schema/resolvers/homepage/index.js | 4 ++-- eq-author-api/schema/typeDefs.js | 2 +- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-firestore.js b/eq-author-api/db/datastore/datastore-firestore.js index 65be0567ff..4560144801 100644 --- a/eq-author-api/db/datastore/datastore-firestore.js +++ b/eq-author-api/db/datastore/datastore-firestore.js @@ -376,8 +376,11 @@ const listQuestionnaires = async () => { const listFilteredQuestionnaires = async (input) => { try { - const { limit, firstQuestionnaireIdOnPage, lastQuestionnaireIdOnPage } = - input; + const { + resultsPerPage, + firstQuestionnaireIdOnPage, + lastQuestionnaireIdOnPage, + } = input; // Orders questionnaires by when they were created, starting with the newest let questionnairesQuery = db @@ -386,7 +389,7 @@ const listFilteredQuestionnaires = async (input) => { // Gets questionnaires on first page when firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage are not provided if (!firstQuestionnaireIdOnPage && !lastQuestionnaireIdOnPage) { - questionnairesQuery = questionnairesQuery.limit(limit); + questionnairesQuery = questionnairesQuery.limit(resultsPerPage); } // Gets questionnaires on previous page when firstQuestionnaireIdOnPage is provided without lastQuestionnaireIdOnPage else if (firstQuestionnaireIdOnPage && !lastQuestionnaireIdOnPage) { @@ -396,10 +399,10 @@ const listFilteredQuestionnaires = async (input) => { .doc(firstQuestionnaireIdOnPage) .get(); - // Gets previous questionnaires before firstQuestionnaireOnPage, limiting the number of questionnaires to `limit` + // Gets previous questionnaires before firstQuestionnaireOnPage, limiting the number of questionnaires to `resultsPerPage` questionnairesQuery = questionnairesQuery .endBefore(firstQuestionnaireOnPage) - .limitToLast(limit); + .limitToLast(resultsPerPage); } // Gets questionnaires on next page when lastQuestionnaireIdOnPage is provided without firstQuestionnaireIdOnPage else if (lastQuestionnaireIdOnPage && !firstQuestionnaireIdOnPage) { @@ -409,10 +412,10 @@ const listFilteredQuestionnaires = async (input) => { .doc(lastQuestionnaireIdOnPage) .get(); - // Gets next questionnaires after lastQuestionnaireOnPage, limiting the number of questionnaires to `limit` + // Gets next questionnaires after lastQuestionnaireOnPage, limiting the number of questionnaires to `resultsPerPage` questionnairesQuery = questionnairesQuery .startAfter(lastQuestionnaireOnPage) - .limit(limit); + .limit(resultsPerPage); } // Throws an error when both firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage are provided else { diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index 8528b19332..bebfc5b013 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -292,8 +292,11 @@ const listQuestionnaires = async () => { const listFilteredQuestionnaires = async (input) => { try { - const { limit, firstQuestionnaireIdOnPage, lastQuestionnaireIdOnPage } = - input; + const { + resultsPerPage, + firstQuestionnaireIdOnPage, + lastQuestionnaireIdOnPage, + } = input; // Gets the questionnaires collection const questionnairesCollection = dbo.collection("questionnaires"); @@ -304,7 +307,7 @@ const listFilteredQuestionnaires = async (input) => { questionnairesQuery = questionnairesCollection .find() .sort({ createdAt: -1 }) - .limit(limit); + .limit(resultsPerPage); } // Gets questionnaires on previous page when firstQuestionnaireIdOnPage is provided without lastQuestionnaireIdOnPage else if (firstQuestionnaireIdOnPage && !lastQuestionnaireIdOnPage) { @@ -315,12 +318,12 @@ const listFilteredQuestionnaires = async (input) => { /* Gets questionnaires on previous page based on firstQuestionnaireOnPage - Uses `gt` (greater than) to find questionnaires created after firstQuestionnaireOnPage, sorts from earliest created first, and limits to `limit` number of questionnaires + Uses `gt` (greater than) to find questionnaires created after firstQuestionnaireOnPage, sorts from earliest created first, and limits to `resultsPerPage` number of questionnaires */ questionnairesQuery = questionnairesCollection .find({ createdAt: { $gt: firstQuestionnaireOnPage.createdAt } }) .sort({ createdAt: 1 }) - .limit(limit); + .limit(resultsPerPage); } // Gets questionnaires on next page when lastQuestionnaireIdOnPage is provided without firstQuestionnaireIdOnPage else if (!firstQuestionnaireIdOnPage && lastQuestionnaireIdOnPage) { @@ -331,12 +334,12 @@ const listFilteredQuestionnaires = async (input) => { /* Gets questionnaires on next page based on lastQuestionnaireOnPage - Uses `lt` (less than) to find questionnaires created before lastQuestionnaireOnPage, sorts from most recently created first, and limits to `limit` number of questionnaires + Uses `lt` (less than) to find questionnaires created before lastQuestionnaireOnPage, sorts from most recently created first, and limits to `resultsPerPage` number of questionnaires */ questionnairesQuery = questionnairesCollection .find({ createdAt: { $lt: lastQuestionnaireOnPage.createdAt } }) .sort({ createdAt: -1 }) - .limit(limit); + .limit(resultsPerPage); } else { logger.error( "Invalid input - both firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage have been provided (from listFilteredQuestionnaires)" @@ -358,7 +361,7 @@ const listFilteredQuestionnaires = async (input) => { /* Sorts questionnaires by most recently created first if firstQuestionnaireIdOnPage is provided without lastQuestionnaireIdOnPage - This condition's query previously sorted by earliest created questionnaire first to get the `limit` number of questionnaires created after firstQuestionnaireOnPage + This condition's query previously sorted by earliest created questionnaire first to get the `resultsPerPage` number of questionnaires created after firstQuestionnaireOnPage This ensures questionnaires are displayed in order from most recently created first in this condition */ if (firstQuestionnaireIdOnPage && !lastQuestionnaireIdOnPage) { diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/homepage/index.js index 8d47b5dd0b..0c76265033 100644 --- a/eq-author-api/schema/resolvers/homepage/index.js +++ b/eq-author-api/schema/resolvers/homepage/index.js @@ -4,13 +4,13 @@ const Resolvers = { Query: { filteredQuestionnaires: async (_, { input = {} }, ctx) => { const { - limit = 10, + resultsPerPage = 10, firstQuestionnaireIdOnPage, lastQuestionnaireIdOnPage, } = input; const questionnaires = await listFilteredQuestionnaires({ - limit, + resultsPerPage, firstQuestionnaireIdOnPage, lastQuestionnaireIdOnPage, }); diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index 9ee9045429..1c8f1e21ce 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -937,7 +937,7 @@ input QuestionnairesInput { } input FilteredQuestionnairesInput { - limit: Int + resultsPerPage: Int firstQuestionnaireIdOnPage: ID lastQuestionnaireIdOnPage: ID } From 43a20859c4b7234459fd1cc2453a1b0b143278b6 Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 27 Jun 2024 11:51:00 +0100 Subject: [PATCH 07/61] Update listFilteredQuestionnaires to search by title and short code for MongoDB --- .../db/datastore/datastore-mongodb.js | 37 +++++++++++++++++-- .../schema/resolvers/homepage/index.js | 2 + eq-author-api/schema/typeDefs.js | 1 + 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index bebfc5b013..f3a8efd816 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -296,6 +296,7 @@ const listFilteredQuestionnaires = async (input) => { resultsPerPage, firstQuestionnaireIdOnPage, lastQuestionnaireIdOnPage, + search, } = input; // Gets the questionnaires collection @@ -305,7 +306,13 @@ const listFilteredQuestionnaires = async (input) => { // Gets questionnaires on first page when firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage are not provided if (!firstQuestionnaireIdOnPage && !lastQuestionnaireIdOnPage) { questionnairesQuery = questionnairesCollection - .find() + .find({ + // Searches for questionnaires with title or shortTitle (short code) containing the search term + $or: [ + { title: { $regex: search, $options: "i" } }, + { shortTitle: { $regex: search, $options: "i" } }, + ], + }) .sort({ createdAt: -1 }) .limit(resultsPerPage); } @@ -321,7 +328,19 @@ const listFilteredQuestionnaires = async (input) => { Uses `gt` (greater than) to find questionnaires created after firstQuestionnaireOnPage, sorts from earliest created first, and limits to `resultsPerPage` number of questionnaires */ questionnairesQuery = questionnairesCollection - .find({ createdAt: { $gt: firstQuestionnaireOnPage.createdAt } }) + .find({ + // Searches for questionnaires created after firstQuestionnaireOnPage AND with title or shortTitle (short code) containing the search term + $and: [ + { createdAt: { $gt: firstQuestionnaireOnPage.createdAt } }, + // Searches for questionnaires with title or shortTitle (short code) containing the search term + { + $or: [ + { title: { $regex: search, $options: "i" } }, + { shortTitle: { $regex: search, $options: "i" } }, + ], + }, + ], + }) .sort({ createdAt: 1 }) .limit(resultsPerPage); } @@ -337,7 +356,19 @@ const listFilteredQuestionnaires = async (input) => { Uses `lt` (less than) to find questionnaires created before lastQuestionnaireOnPage, sorts from most recently created first, and limits to `resultsPerPage` number of questionnaires */ questionnairesQuery = questionnairesCollection - .find({ createdAt: { $lt: lastQuestionnaireOnPage.createdAt } }) + .find({ + // Searches for questionnaires created before lastQuestionnaireOnPage AND with title or shortTitle (short code) containing the search term + $and: [ + { createdAt: { $lt: lastQuestionnaireOnPage.createdAt } }, + // Searches for questionnaires with title or shortTitle (short code) containing the search term + { + $or: [ + { title: { $regex: search, $options: "i" } }, + { shortTitle: { $regex: search, $options: "i" } }, + ], + }, + ], + }) .sort({ createdAt: -1 }) .limit(resultsPerPage); } else { diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/homepage/index.js index 0c76265033..ec70e2f2ed 100644 --- a/eq-author-api/schema/resolvers/homepage/index.js +++ b/eq-author-api/schema/resolvers/homepage/index.js @@ -7,12 +7,14 @@ const Resolvers = { resultsPerPage = 10, firstQuestionnaireIdOnPage, lastQuestionnaireIdOnPage, + search, } = input; const questionnaires = await listFilteredQuestionnaires({ resultsPerPage, firstQuestionnaireIdOnPage, lastQuestionnaireIdOnPage, + search, }); return questionnaires; diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index 1c8f1e21ce..55aea21a3f 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -940,6 +940,7 @@ input FilteredQuestionnairesInput { resultsPerPage: Int firstQuestionnaireIdOnPage: ID lastQuestionnaireIdOnPage: ID + search: String } input QueryInput { From dea12a3395fd0e7e8bf4b61fcfbe70923e9381e2 Mon Sep 17 00:00:00 2001 From: farres1 Date: Mon, 1 Jul 2024 08:39:58 +0100 Subject: [PATCH 08/61] Update listFilteredQuestionnaires to search by owner name for MongoDB --- .../db/datastore/datastore-mongodb.js | 147 ++++++++++++------ .../schema/resolvers/homepage/index.js | 2 + eq-author-api/schema/typeDefs.js | 1 + 3 files changed, 105 insertions(+), 45 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index f3a8efd816..f1ab213740 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -297,6 +297,7 @@ const listFilteredQuestionnaires = async (input) => { firstQuestionnaireIdOnPage, lastQuestionnaireIdOnPage, search, + owner, } = input; // Gets the questionnaires collection @@ -305,16 +306,34 @@ const listFilteredQuestionnaires = async (input) => { // Gets questionnaires on first page when firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage are not provided if (!firstQuestionnaireIdOnPage && !lastQuestionnaireIdOnPage) { - questionnairesQuery = questionnairesCollection - .find({ - // Searches for questionnaires with title or shortTitle (short code) containing the search term - $or: [ - { title: { $regex: search, $options: "i" } }, - { shortTitle: { $regex: search, $options: "i" } }, - ], - }) - .sort({ createdAt: -1 }) - .limit(resultsPerPage); + questionnairesQuery = questionnairesCollection.aggregate([ + { + // From the `users` collection, gets the owner (based on `createdBy`) of each questionnaire by performing a join to match questionnaire `createdBy` with user `id` + $lookup: { + from: "users", + localField: "createdBy", + foreignField: "id", + as: "owner", + }, + }, + { + $match: { + // Searches for questionnaires with owner name (based on `createdBy`) containing the search term + "owner.name": { $regex: owner, $options: "i" }, + // Searches for questionnaires with `title` or `shortTitle` (short code) containing the search term + $or: [ + { title: { $regex: search, $options: "i" } }, + { shortTitle: { $regex: search, $options: "i" } }, + ], + }, + }, + { + $sort: { createdAt: -1 }, + }, + { + $limit: resultsPerPage, + }, + ]); } // Gets questionnaires on previous page when firstQuestionnaireIdOnPage is provided without lastQuestionnaireIdOnPage else if (firstQuestionnaireIdOnPage && !lastQuestionnaireIdOnPage) { @@ -323,26 +342,45 @@ const listFilteredQuestionnaires = async (input) => { id: firstQuestionnaireIdOnPage, }); - /* + /* Gets questionnaires on previous page based on firstQuestionnaireOnPage - Uses `gt` (greater than) to find questionnaires created after firstQuestionnaireOnPage, sorts from earliest created first, and limits to `resultsPerPage` number of questionnaires + Only finds questionnaires that meet the search conditions (e.g. owner name matching `owner` search field) + Uses `gt` (greater than) to find questionnaires meeting the conditions created after firstQuestionnaireOnPage, sorts from earliest created first, and limits to `resultsPerPage` number of questionnaires */ - questionnairesQuery = questionnairesCollection - .find({ - // Searches for questionnaires created after firstQuestionnaireOnPage AND with title or shortTitle (short code) containing the search term - $and: [ - { createdAt: { $gt: firstQuestionnaireOnPage.createdAt } }, - // Searches for questionnaires with title or shortTitle (short code) containing the search term - { - $or: [ - { title: { $regex: search, $options: "i" } }, - { shortTitle: { $regex: search, $options: "i" } }, - ], - }, - ], - }) - .sort({ createdAt: 1 }) - .limit(resultsPerPage); + questionnairesQuery = questionnairesCollection.aggregate([ + // From the `users` collection, gets the owner (based on `createdBy`) of each questionnaire by performing a join to match questionnaire `createdBy` with user `id` + { + $lookup: { + from: "users", + localField: "createdBy", + foreignField: "id", + as: "owner", + }, + }, + { + $match: { + // Searches for questionnaires created after firstQuestionnaireOnPage AND meeting all the search conditions + $and: [ + { createdAt: { $gt: firstQuestionnaireOnPage.createdAt } }, + // Searches for questionnaires with owner name (based on `createdBy`) containing the search term + { "owner.name": { $regex: owner, $options: "i" } }, + { + // Searches for questionnaires with `title` or `shortTitle` (short code) containing the search term + $or: [ + { title: { $regex: search, $options: "i" } }, + { shortTitle: { $regex: search, $options: "i" } }, + ], + }, + ], + }, + }, + { + $sort: { createdAt: 1 }, + }, + { + $limit: resultsPerPage, + }, + ]); } // Gets questionnaires on next page when lastQuestionnaireIdOnPage is provided without firstQuestionnaireIdOnPage else if (!firstQuestionnaireIdOnPage && lastQuestionnaireIdOnPage) { @@ -353,24 +391,43 @@ const listFilteredQuestionnaires = async (input) => { /* Gets questionnaires on next page based on lastQuestionnaireOnPage - Uses `lt` (less than) to find questionnaires created before lastQuestionnaireOnPage, sorts from most recently created first, and limits to `resultsPerPage` number of questionnaires + Only finds questionnaires that meet the search conditions (e.g. owner name matching `owner` search field) + Uses `lt` (less than) to find questionnaires meeting the conditions created before lastQuestionnaireOnPage, sorts from most recently created first, and limits to `resultsPerPage` number of questionnaires */ - questionnairesQuery = questionnairesCollection - .find({ - // Searches for questionnaires created before lastQuestionnaireOnPage AND with title or shortTitle (short code) containing the search term - $and: [ - { createdAt: { $lt: lastQuestionnaireOnPage.createdAt } }, - // Searches for questionnaires with title or shortTitle (short code) containing the search term - { - $or: [ - { title: { $regex: search, $options: "i" } }, - { shortTitle: { $regex: search, $options: "i" } }, - ], - }, - ], - }) - .sort({ createdAt: -1 }) - .limit(resultsPerPage); + questionnairesQuery = questionnairesCollection.aggregate([ + // From the `users` collection, gets the owner (based on `createdBy`) of each questionnaire by performing a join to match questionnaire `createdBy` with user `id` + { + $lookup: { + from: "users", + localField: "createdBy", + foreignField: "id", + as: "owner", + }, + }, + { + $match: { + // Searches for questionnaires created before lastQuestionnaireOnPage AND meeting all the search conditions + $and: [ + { createdAt: { $lt: lastQuestionnaireOnPage.createdAt } }, + // Searches for questionnaires with owner name (based on `createdBy`) containing the search term + { "owner.name": { $regex: owner, $options: "i" } }, + { + // Searches for questionnaires with `title` or `shortTitle` (short code) containing the search term + $or: [ + { title: { $regex: search, $options: "i" } }, + { shortTitle: { $regex: search, $options: "i" } }, + ], + }, + ], + }, + }, + { + $sort: { createdAt: -1 }, + }, + { + $limit: resultsPerPage, + }, + ]); } else { logger.error( "Invalid input - both firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage have been provided (from listFilteredQuestionnaires)" diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/homepage/index.js index ec70e2f2ed..5ddb72d4c8 100644 --- a/eq-author-api/schema/resolvers/homepage/index.js +++ b/eq-author-api/schema/resolvers/homepage/index.js @@ -8,6 +8,7 @@ const Resolvers = { firstQuestionnaireIdOnPage, lastQuestionnaireIdOnPage, search, + owner, } = input; const questionnaires = await listFilteredQuestionnaires({ @@ -15,6 +16,7 @@ const Resolvers = { firstQuestionnaireIdOnPage, lastQuestionnaireIdOnPage, search, + owner, }); return questionnaires; diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index 55aea21a3f..73a74d73d7 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -941,6 +941,7 @@ input FilteredQuestionnairesInput { firstQuestionnaireIdOnPage: ID lastQuestionnaireIdOnPage: ID search: String + owner: String } input QueryInput { From 7aeaf64057a59b7e63cfd26f3ca190d4b43e4347 Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 3 Jul 2024 11:28:43 +0100 Subject: [PATCH 09/61] Add filters for created after and created before --- .../db/datastore/datastore-mongodb.js | 59 ++++++++++--------- .../schema/resolvers/homepage/index.js | 4 ++ eq-author-api/schema/typeDefs.js | 2 + 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index f1ab213740..47c0f3ab80 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -298,12 +298,37 @@ const listFilteredQuestionnaires = async (input) => { lastQuestionnaireIdOnPage, search, owner, + createdAfter, + createdBefore, } = input; // Gets the questionnaires collection const questionnairesCollection = dbo.collection("questionnaires"); let questionnairesQuery; + const matchQuery = { + // Searches for questionnaires with `title` or `shortTitle` (short code) containing the search term + $or: [ + { title: { $regex: search, $options: "i" } }, + { shortTitle: { $regex: search, $options: "i" } }, + ], + // Searches for questionnaires with owner name (based on `createdBy`) containing the search term + "owner.name": { $regex: owner, $options: "i" }, + }; + + // If both `createdAfter` and `createdBefore` are provided, searches for questionnaires created between `createdAfter` and `createdBefore` + if (createdAfter && createdBefore) { + matchQuery.createdAt = { $gt: createdAfter, $lt: createdBefore }; + } + // If `createdAfter` is provided without `createdBefore`, searches for questionnaires created after `createdAfter` + else if (createdAfter) { + matchQuery.createdAt = { $gt: createdAfter }; + } + // If `createdBefore` is provided without `createdAfter`, searches for questionnaires created before `createdBefore` + else if (createdBefore) { + matchQuery.createdAt = { $lt: createdBefore }; + } + // Gets questionnaires on first page when firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage are not provided if (!firstQuestionnaireIdOnPage && !lastQuestionnaireIdOnPage) { questionnairesQuery = questionnairesCollection.aggregate([ @@ -317,15 +342,7 @@ const listFilteredQuestionnaires = async (input) => { }, }, { - $match: { - // Searches for questionnaires with owner name (based on `createdBy`) containing the search term - "owner.name": { $regex: owner, $options: "i" }, - // Searches for questionnaires with `title` or `shortTitle` (short code) containing the search term - $or: [ - { title: { $regex: search, $options: "i" } }, - { shortTitle: { $regex: search, $options: "i" } }, - ], - }, + $match: matchQuery, }, { $sort: { createdAt: -1 }, @@ -359,18 +376,10 @@ const listFilteredQuestionnaires = async (input) => { }, { $match: { - // Searches for questionnaires created after firstQuestionnaireOnPage AND meeting all the search conditions + // Searches for questionnaires created after firstQuestionnaireOnPage AND meeting all the search conditions from `matchQuery` $and: [ { createdAt: { $gt: firstQuestionnaireOnPage.createdAt } }, - // Searches for questionnaires with owner name (based on `createdBy`) containing the search term - { "owner.name": { $regex: owner, $options: "i" } }, - { - // Searches for questionnaires with `title` or `shortTitle` (short code) containing the search term - $or: [ - { title: { $regex: search, $options: "i" } }, - { shortTitle: { $regex: search, $options: "i" } }, - ], - }, + matchQuery, ], }, }, @@ -406,18 +415,10 @@ const listFilteredQuestionnaires = async (input) => { }, { $match: { - // Searches for questionnaires created before lastQuestionnaireOnPage AND meeting all the search conditions + // Searches for questionnaires created before lastQuestionnaireOnPage AND meeting all the search conditions from `matchQuery` $and: [ { createdAt: { $lt: lastQuestionnaireOnPage.createdAt } }, - // Searches for questionnaires with owner name (based on `createdBy`) containing the search term - { "owner.name": { $regex: owner, $options: "i" } }, - { - // Searches for questionnaires with `title` or `shortTitle` (short code) containing the search term - $or: [ - { title: { $regex: search, $options: "i" } }, - { shortTitle: { $regex: search, $options: "i" } }, - ], - }, + matchQuery, ], }, }, diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/homepage/index.js index 5ddb72d4c8..2118b372e9 100644 --- a/eq-author-api/schema/resolvers/homepage/index.js +++ b/eq-author-api/schema/resolvers/homepage/index.js @@ -9,6 +9,8 @@ const Resolvers = { lastQuestionnaireIdOnPage, search, owner, + createdAfter, + createdBefore, } = input; const questionnaires = await listFilteredQuestionnaires({ @@ -17,6 +19,8 @@ const Resolvers = { lastQuestionnaireIdOnPage, search, owner, + createdAfter, + createdBefore, }); return questionnaires; diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index 73a74d73d7..b978cc30f9 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -942,6 +942,8 @@ input FilteredQuestionnairesInput { lastQuestionnaireIdOnPage: ID search: String owner: String + createdAfter: DateTime + createdBefore: DateTime } input QueryInput { From 8301eaa08967867e422534fbfc1dfa642ffc0416 Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 5 Jul 2024 07:53:50 +0100 Subject: [PATCH 10/61] Update listFilteredQuestionnaires to search by questionnaire access --- .../db/datastore/datastore-mongodb.js | 25 ++++++++++++++++++- .../schema/resolvers/homepage/index.js | 23 ++++++++++------- eq-author-api/schema/typeDefs.js | 2 ++ 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index 47c0f3ab80..45b95be7eb 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -290,7 +290,7 @@ const listQuestionnaires = async () => { } }; -const listFilteredQuestionnaires = async (input) => { +const listFilteredQuestionnaires = async (input, ctx) => { try { const { resultsPerPage, @@ -300,8 +300,11 @@ const listFilteredQuestionnaires = async (input) => { owner, createdAfter, createdBefore, + access, } = input; + const { id: userId } = ctx.user; + // Gets the questionnaires collection const questionnairesCollection = dbo.collection("questionnaires"); let questionnairesQuery; @@ -329,6 +332,26 @@ const listFilteredQuestionnaires = async (input) => { matchQuery.createdAt = { $lt: createdBefore }; } + // TODO: Implement "Read-only for editors" code + if (access === "Write") { + if (!matchQuery.$and) { + matchQuery.$and = []; + } + matchQuery.$and.push({ + $or: [{ editors: { $in: [userId] } }, { createdBy: userId }], + }); + } else if (access === "Read") { + if (!matchQuery.$and) { + matchQuery.$and = []; + } + matchQuery.$and.push( + { + editors: { $nin: [userId] }, + }, + { createdBy: { $ne: userId } } + ); + } + // Gets questionnaires on first page when firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage are not provided if (!firstQuestionnaireIdOnPage && !lastQuestionnaireIdOnPage) { questionnairesQuery = questionnairesCollection.aggregate([ diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/homepage/index.js index 2118b372e9..fb2e83ef55 100644 --- a/eq-author-api/schema/resolvers/homepage/index.js +++ b/eq-author-api/schema/resolvers/homepage/index.js @@ -11,17 +11,22 @@ const Resolvers = { owner, createdAfter, createdBefore, + access, } = input; - const questionnaires = await listFilteredQuestionnaires({ - resultsPerPage, - firstQuestionnaireIdOnPage, - lastQuestionnaireIdOnPage, - search, - owner, - createdAfter, - createdBefore, - }); + const questionnaires = await listFilteredQuestionnaires( + { + resultsPerPage, + firstQuestionnaireIdOnPage, + lastQuestionnaireIdOnPage, + search, + owner, + createdAfter, + createdBefore, + access, + }, + ctx + ); return questionnaires; }, diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index b978cc30f9..2711950646 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -44,6 +44,7 @@ enum QuestionnaireType { enum Permission { Read Write + ReadOnlyEditor } type Questionnaire { @@ -944,6 +945,7 @@ input FilteredQuestionnairesInput { owner: String createdAfter: DateTime createdBefore: DateTime + access: Permission } input QueryInput { From 3325a85bfee2c9e564bed32e3b60bcec321bca29 Mon Sep 17 00:00:00 2001 From: farres1 Date: Mon, 8 Jul 2024 11:38:45 +0100 Subject: [PATCH 11/61] Add initial filters for my questionnaires --- eq-author-api/db/datastore/datastore-mongodb.js | 11 +++++++++++ eq-author-api/schema/resolvers/homepage/index.js | 2 ++ eq-author-api/schema/typeDefs.js | 1 + 3 files changed, 14 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index 45b95be7eb..120e1412db 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -301,6 +301,7 @@ const listFilteredQuestionnaires = async (input, ctx) => { createdAfter, createdBefore, access, + myQuestionnaires, } = input; const { id: userId } = ctx.user; @@ -352,6 +353,16 @@ const listFilteredQuestionnaires = async (input, ctx) => { ); } + // TODO: When "My questionnaires" feature is implemented, implement code to filter questionnaires based on questionnaires marked as "My questionnaires" + if (myQuestionnaires) { + if (!matchQuery.$and) { + matchQuery.$and = []; + } + matchQuery.$and.push({ + $or: [{ editors: { $in: [userId] } }, { createdBy: userId }], + }); + } + // Gets questionnaires on first page when firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage are not provided if (!firstQuestionnaireIdOnPage && !lastQuestionnaireIdOnPage) { questionnairesQuery = questionnairesCollection.aggregate([ diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/homepage/index.js index fb2e83ef55..7341560044 100644 --- a/eq-author-api/schema/resolvers/homepage/index.js +++ b/eq-author-api/schema/resolvers/homepage/index.js @@ -12,6 +12,7 @@ const Resolvers = { createdAfter, createdBefore, access, + myQuestionnaires, } = input; const questionnaires = await listFilteredQuestionnaires( @@ -24,6 +25,7 @@ const Resolvers = { createdAfter, createdBefore, access, + myQuestionnaires, }, ctx ); diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index 2711950646..2d2ea54965 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -946,6 +946,7 @@ input FilteredQuestionnairesInput { createdAfter: DateTime createdBefore: DateTime access: Permission + myQuestionnaires: Boolean } input QueryInput { From b030ac78085b01df293ee39e2ef1cd8a6ab86ad5 Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 9 Jul 2024 08:08:38 +0100 Subject: [PATCH 12/61] Add sortBy to listFilteredQuestionnaires --- .../db/datastore/datastore-mongodb.js | 52 ++++++++++++++----- .../schema/resolvers/homepage/index.js | 2 + eq-author-api/schema/typeDefs.js | 1 + 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index 120e1412db..1350d69f4d 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -302,6 +302,7 @@ const listFilteredQuestionnaires = async (input, ctx) => { createdBefore, access, myQuestionnaires, + sortBy, } = input; const { id: userId } = ctx.user; @@ -379,7 +380,7 @@ const listFilteredQuestionnaires = async (input, ctx) => { $match: matchQuery, }, { - $sort: { createdAt: -1 }, + $sort: { createdAt: sortBy === "createdDateDesc" ? -1 : 1 }, // Sorts by either most recently created first or earliest created first based on `sortBy` }, { $limit: resultsPerPage, @@ -396,7 +397,8 @@ const listFilteredQuestionnaires = async (input, ctx) => { /* Gets questionnaires on previous page based on firstQuestionnaireOnPage Only finds questionnaires that meet the search conditions (e.g. owner name matching `owner` search field) - Uses `gt` (greater than) to find questionnaires meeting the conditions created after firstQuestionnaireOnPage, sorts from earliest created first, and limits to `resultsPerPage` number of questionnaires + Uses `gt` (greater than) or `lt` (less than) to find questionnaires created after or before firstQuestionnaireOnPage (based on `sortBy`) meeting the conditions, + sorts from earliest created or most recently created first (based on `sortBy`), and limits to `resultsPerPage` number of questionnaires */ questionnairesQuery = questionnairesCollection.aggregate([ // From the `users` collection, gets the owner (based on `createdBy`) of each questionnaire by performing a join to match questionnaire `createdBy` with user `id` @@ -410,15 +412,25 @@ const listFilteredQuestionnaires = async (input, ctx) => { }, { $match: { - // Searches for questionnaires created after firstQuestionnaireOnPage AND meeting all the search conditions from `matchQuery` + // Searches for questionnaires created after or before firstQuestionnaireOnPage (based on `sortBy`) AND meeting all the search conditions from `matchQuery` $and: [ - { createdAt: { $gt: firstQuestionnaireOnPage.createdAt } }, + { + createdAt: + sortBy === "createdDateDesc" + ? { $gt: firstQuestionnaireOnPage.createdAt } + : { $lt: firstQuestionnaireOnPage.createdAt }, + }, matchQuery, ], }, }, { - $sort: { createdAt: 1 }, + /* + Sorts by either earliest created first or most recently created first based on `sortBy` + (previous page, so to get the previous questionnaires, sorts by earliest created first when `sortBy` is `createdDateDesc` + as otherwise previous page's questionnaires would be the most recently created ones) + */ + $sort: { createdAt: sortBy === "createdDateDesc" ? 1 : -1 }, }, { $limit: resultsPerPage, @@ -435,7 +447,8 @@ const listFilteredQuestionnaires = async (input, ctx) => { /* Gets questionnaires on next page based on lastQuestionnaireOnPage Only finds questionnaires that meet the search conditions (e.g. owner name matching `owner` search field) - Uses `lt` (less than) to find questionnaires meeting the conditions created before lastQuestionnaireOnPage, sorts from most recently created first, and limits to `resultsPerPage` number of questionnaires + Uses `lt` (less than) or `gt` (greater than) to find questionnaires created before or after lastQuestionnaireOnPage (based on `sortBy`) meeting the conditions, + sorts from most recently created or earliest created first (based on `sortBy`), and limits to `resultsPerPage` number of questionnaires */ questionnairesQuery = questionnairesCollection.aggregate([ // From the `users` collection, gets the owner (based on `createdBy`) of each questionnaire by performing a join to match questionnaire `createdBy` with user `id` @@ -449,15 +462,20 @@ const listFilteredQuestionnaires = async (input, ctx) => { }, { $match: { - // Searches for questionnaires created before lastQuestionnaireOnPage AND meeting all the search conditions from `matchQuery` + // Searches for questionnaires created before or after lastQuestionnaireOnPage (based on `sortBy`) AND meeting all the search conditions from `matchQuery` $and: [ - { createdAt: { $lt: lastQuestionnaireOnPage.createdAt } }, + { + createdAt: + sortBy === "createdDateDesc" + ? { $lt: lastQuestionnaireOnPage.createdAt } + : { $gt: lastQuestionnaireOnPage.createdAt }, + }, matchQuery, ], }, }, { - $sort: { createdAt: -1 }, + $sort: { createdAt: sortBy === "createdDateDesc" ? -1 : 1 }, // Sorts by either most recently created first or earliest created first based on `sortBy` }, { $limit: resultsPerPage, @@ -484,13 +502,19 @@ const listFilteredQuestionnaires = async (input, ctx) => { /* Sorts questionnaires by most recently created first if firstQuestionnaireIdOnPage is provided without lastQuestionnaireIdOnPage - This condition's query previously sorted by earliest created questionnaire first to get the `resultsPerPage` number of questionnaires created after firstQuestionnaireOnPage - This ensures questionnaires are displayed in order from most recently created first in this condition + This condition's query previously sorted in reverse order to get the `resultsPerPage` number of questionnaires created after or before firstQuestionnaireOnPage + This ensures questionnaires are displayed in the correct order (based on `sortBy`) in this condition */ if (firstQuestionnaireIdOnPage && !lastQuestionnaireIdOnPage) { - transformedQuestionnaires = transformedQuestionnaires.sort((a, b) => - a.createdAt > b.createdAt ? -1 : 1 - ); + if (sortBy === "createdDateDesc") { + transformedQuestionnaires = transformedQuestionnaires.sort((a, b) => + a.createdAt > b.createdAt ? -1 : 1 + ); + } else { + transformedQuestionnaires = transformedQuestionnaires.sort((a, b) => + a.createdAt > b.createdAt ? 1 : -1 + ); + } } return transformedQuestionnaires; diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/homepage/index.js index 7341560044..70300bf9ec 100644 --- a/eq-author-api/schema/resolvers/homepage/index.js +++ b/eq-author-api/schema/resolvers/homepage/index.js @@ -13,6 +13,7 @@ const Resolvers = { createdBefore, access, myQuestionnaires, + sortBy, } = input; const questionnaires = await listFilteredQuestionnaires( @@ -26,6 +27,7 @@ const Resolvers = { createdBefore, access, myQuestionnaires, + sortBy, }, ctx ); diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index 2d2ea54965..616da94b00 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -947,6 +947,7 @@ input FilteredQuestionnairesInput { createdBefore: DateTime access: Permission myQuestionnaires: Boolean + sortBy: String } input QueryInput { From 1d5cdd8a6f1d9811b05178205da47bb4a2bac479 Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 9 Jul 2024 12:52:09 +0100 Subject: [PATCH 13/61] Add total pages resolver --- eq-author-api/db/datastore/datastore-mongodb.js | 12 ++++++++++++ eq-author-api/schema/resolvers/homepage/index.js | 11 ++++++++++- eq-author-api/schema/typeDefs.js | 5 +++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index 1350d69f4d..55dd6b8ac7 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -527,6 +527,17 @@ const listFilteredQuestionnaires = async (input, ctx) => { } }; +const getTotalPages = async (resultsPerPage) => { + try { + const collection = dbo.collection("questionnaires"); + const totalQuestionnaires = await collection.countDocuments(); + return Math.ceil(totalQuestionnaires / resultsPerPage) - 1; + } catch (error) { + logger.error(error, "Unable to get total pages"); + return; + } +}; + const createComments = async (questionnaireId) => { try { if (!questionnaireId) { @@ -724,6 +735,7 @@ module.exports = { deleteQuestionnaire, listQuestionnaires, listFilteredQuestionnaires, + getTotalPages, getQuestionnaire, getQuestionnaireMetaById, createComments, diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/homepage/index.js index 70300bf9ec..107aa3ddd1 100644 --- a/eq-author-api/schema/resolvers/homepage/index.js +++ b/eq-author-api/schema/resolvers/homepage/index.js @@ -1,4 +1,7 @@ -const { listFilteredQuestionnaires } = require("../../../db/datastore"); +const { + listFilteredQuestionnaires, + getTotalPages, +} = require("../../../db/datastore"); const Resolvers = { Query: { @@ -34,6 +37,12 @@ const Resolvers = { return questionnaires; }, + + totalPages: async (_, { input = {} }) => { + const { resultsPerPage = 10 } = input; + + return getTotalPages(resultsPerPage); + }, }, }; diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index 616da94b00..41e7e7cf9b 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -923,6 +923,7 @@ type Query { listNames: [ListName] collectionListNames: [ListName] supplementaryDataListNames: [ListName] + totalPages(input: TotalPagesInput): Int } input CommonFilters { @@ -950,6 +951,10 @@ input FilteredQuestionnairesInput { sortBy: String } +input TotalPagesInput { + resultsPerPage: Int +} + input QueryInput { id: ID questionnaireId: ID From b73fdc3ba169642a848016a28d30c37786d9d426 Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 9 Jul 2024 13:36:58 +0100 Subject: [PATCH 14/61] Add all access questionnaires filter --- eq-author-api/db/datastore/datastore-mongodb.js | 5 +++++ eq-author-api/schema/typeDefs.js | 1 + 2 files changed, 6 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index 55dd6b8ac7..5f499f3474 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -335,6 +335,11 @@ const listFilteredQuestionnaires = async (input, ctx) => { } // TODO: Implement "Read-only for editors" code + if (access === "All") { + if (matchQuery.$and) { + delete matchQuery.$and; + } + } if (access === "Write") { if (!matchQuery.$and) { matchQuery.$and = []; diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index 41e7e7cf9b..2e5abe6086 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -42,6 +42,7 @@ enum QuestionnaireType { } enum Permission { + All Read Write ReadOnlyEditor From c1dfb59971923fbb1b13db61d64fbde36b7bfa26 Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 9 Jul 2024 14:48:57 +0100 Subject: [PATCH 15/61] Add private questionnaires filter --- eq-author-api/db/datastore/datastore-mongodb.js | 12 ++++++++++-- eq-author-api/schema/typeDefs.js | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index 5f499f3474..fb5ceffd79 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -334,7 +334,6 @@ const listFilteredQuestionnaires = async (input, ctx) => { matchQuery.createdAt = { $lt: createdBefore }; } - // TODO: Implement "Read-only for editors" code if (access === "All") { if (matchQuery.$and) { delete matchQuery.$and; @@ -347,7 +346,8 @@ const listFilteredQuestionnaires = async (input, ctx) => { matchQuery.$and.push({ $or: [{ editors: { $in: [userId] } }, { createdBy: userId }], }); - } else if (access === "Read") { + } + if (access === "Read") { if (!matchQuery.$and) { matchQuery.$and = []; } @@ -358,6 +358,14 @@ const listFilteredQuestionnaires = async (input, ctx) => { { createdBy: { $ne: userId } } ); } + if (access === "PrivateQuestionnaires") { + if (!matchQuery.$and) { + matchQuery.$and = []; + } + matchQuery.$and.push({ + isPublic: false, + }); + } // TODO: When "My questionnaires" feature is implemented, implement code to filter questionnaires based on questionnaires marked as "My questionnaires" if (myQuestionnaires) { diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index 2e5abe6086..e441e67916 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -45,7 +45,7 @@ enum Permission { All Read Write - ReadOnlyEditor + PrivateQuestionnaires } type Questionnaire { From 40c135b0a005c17ce1d5c4b39b8bd4b78bd13465 Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 10 Jul 2024 16:20:54 +0100 Subject: [PATCH 16/61] Fix incorrect total pages when searching for questionnaires --- .../db/datastore/datastore-mongodb.js | 95 +++++++++++++++---- .../schema/resolvers/homepage/index.js | 26 ++++- eq-author-api/schema/typeDefs.js | 6 ++ 3 files changed, 107 insertions(+), 20 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index fb5ceffd79..2c5193d4b7 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -290,27 +290,19 @@ const listQuestionnaires = async () => { } }; -const listFilteredQuestionnaires = async (input, ctx) => { +const getMatchQuery = async (input, ctx) => { try { const { - resultsPerPage, - firstQuestionnaireIdOnPage, - lastQuestionnaireIdOnPage, search, owner, createdAfter, createdBefore, access, myQuestionnaires, - sortBy, } = input; const { id: userId } = ctx.user; - // Gets the questionnaires collection - const questionnairesCollection = dbo.collection("questionnaires"); - let questionnairesQuery; - const matchQuery = { // Searches for questionnaires with `title` or `shortTitle` (short code) containing the search term $or: [ @@ -335,10 +327,19 @@ const listFilteredQuestionnaires = async (input, ctx) => { } if (access === "All") { - if (matchQuery.$and) { - delete matchQuery.$and; + if (!matchQuery.$and) { + matchQuery.$and = []; } + // Searches for all questionnaires that are public, the user is an editor of, or the user created (all questionnaires the user has access to) + matchQuery.$and.push({ + $or: [ + { isPublic: true }, + { editors: { $in: [userId] } }, + { createdBy: userId }, + ], + }); } + // Searches for all questionnaires the user can edit (all questionnaires the user is an editor of or the user created) if (access === "Write") { if (!matchQuery.$and) { matchQuery.$and = []; @@ -347,6 +348,7 @@ const listFilteredQuestionnaires = async (input, ctx) => { $or: [{ editors: { $in: [userId] } }, { createdBy: userId }], }); } + // Searches for all questionnaires the user can view but not edit (all public questionnaires the user is not an editor of and did not create) if (access === "Read") { if (!matchQuery.$and) { matchQuery.$and = []; @@ -355,14 +357,17 @@ const listFilteredQuestionnaires = async (input, ctx) => { { editors: { $nin: [userId] }, }, - { createdBy: { $ne: userId } } + { createdBy: { $ne: userId } }, + { isPublic: true } ); } + // Searches for all non-public questionnaires the user can edit (all questionnaires the user is an editor of or the user created that are not public) if (access === "PrivateQuestionnaires") { if (!matchQuery.$and) { matchQuery.$and = []; } matchQuery.$and.push({ + $or: [{ editors: { $in: [userId] } }, { createdBy: userId }], isPublic: false, }); } @@ -377,6 +382,30 @@ const listFilteredQuestionnaires = async (input, ctx) => { }); } + return matchQuery; + } catch (error) { + logger.error( + { error, input }, + "Unable to get match query for filtering questionnaires (from getMatchQuery)" + ); + } +}; + +const listFilteredQuestionnaires = async (input, ctx) => { + try { + const { + resultsPerPage, + firstQuestionnaireIdOnPage, + lastQuestionnaireIdOnPage, + sortBy, + } = input; + + // Gets the questionnaires collection + const questionnairesCollection = dbo.collection("questionnaires"); + let questionnairesQuery; + + const matchQuery = await getMatchQuery(input, ctx); + // Gets questionnaires on first page when firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage are not provided if (!firstQuestionnaireIdOnPage && !lastQuestionnaireIdOnPage) { questionnairesQuery = questionnairesCollection.aggregate([ @@ -503,7 +532,11 @@ const listFilteredQuestionnaires = async (input, ctx) => { const questionnaires = await questionnairesQuery.toArray(); if (questionnaires.length === 0) { - logger.info("No questionnaires found (from listFilteredQuestionnaires)"); + logger.debug( + `No questionnaires found with input: ${JSON.stringify( + input + )} (from listFilteredQuestionnaires)` + ); return []; } @@ -540,11 +573,39 @@ const listFilteredQuestionnaires = async (input, ctx) => { } }; -const getTotalPages = async (resultsPerPage) => { +const getTotalPages = async (input, ctx) => { try { - const collection = dbo.collection("questionnaires"); - const totalQuestionnaires = await collection.countDocuments(); - return Math.ceil(totalQuestionnaires / resultsPerPage) - 1; + const { resultsPerPage } = input; + + // Gets the questionnaires collection + const questionnairesCollection = dbo.collection("questionnaires"); + + const matchQuery = await getMatchQuery(input, ctx); + + // Gets the total number of questionnaires that meet the search conditions + const { totalFilteredQuestionnaires } = await questionnairesCollection + .aggregate([ + { + $lookup: { + from: "users", + localField: "createdBy", + foreignField: "id", + as: "owner", + }, + }, + { + $match: matchQuery, + }, + { + $count: "totalFilteredQuestionnaires", + }, + ]) + .next(); + + // Calculates the total number of pages by dividing the total number of filtered questionnaires by the number of results per page, and rounding up + const totalPages = Math.ceil(totalFilteredQuestionnaires / resultsPerPage); + + return totalPages; } catch (error) { logger.error(error, "Unable to get total pages"); return; diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/homepage/index.js index 107aa3ddd1..0cbaff6fd6 100644 --- a/eq-author-api/schema/resolvers/homepage/index.js +++ b/eq-author-api/schema/resolvers/homepage/index.js @@ -38,10 +38,30 @@ const Resolvers = { return questionnaires; }, - totalPages: async (_, { input = {} }) => { - const { resultsPerPage = 10 } = input; + totalPages: async (_, { input = {} }, ctx) => { + const { + resultsPerPage = 10, + search, + owner, + createdAfter, + createdBefore, + access, + myQuestionnaires, + } = input; - return getTotalPages(resultsPerPage); + const totalPages = await getTotalPages( + { + resultsPerPage, + search, + owner, + createdAfter, + createdBefore, + access, + myQuestionnaires, + }, + ctx + ); + return totalPages; }, }, }; diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index e441e67916..b02b6b38b9 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -954,6 +954,12 @@ input FilteredQuestionnairesInput { input TotalPagesInput { resultsPerPage: Int + search: String + owner: String + createdAfter: DateTime + createdBefore: DateTime + access: Permission + myQuestionnaires: Boolean } input QueryInput { From 44d369f295f105bb147340c8c503751a2cea0cec Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 11 Jul 2024 09:31:38 +0100 Subject: [PATCH 17/61] Update created after and created before to include created on --- .../db/datastore/datastore-mongodb.js | 34 +++++++++++-------- .../schema/resolvers/homepage/index.js | 16 ++++----- eq-author-api/schema/typeDefs.js | 8 ++--- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index 2c5193d4b7..ab3c910171 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -295,8 +295,8 @@ const getMatchQuery = async (input, ctx) => { const { search, owner, - createdAfter, - createdBefore, + createdOnOrAfter, + createdOnOrBefore, access, myQuestionnaires, } = input; @@ -313,17 +313,20 @@ const getMatchQuery = async (input, ctx) => { "owner.name": { $regex: owner, $options: "i" }, }; - // If both `createdAfter` and `createdBefore` are provided, searches for questionnaires created between `createdAfter` and `createdBefore` - if (createdAfter && createdBefore) { - matchQuery.createdAt = { $gt: createdAfter, $lt: createdBefore }; + // If both `createdOnOrAfter` and `createdOnOrBefore` are provided, searches for questionnaires created between `createdOnOrAfter` and `createdOnOrBefore` inclusive + if (createdOnOrAfter && createdOnOrBefore) { + matchQuery.createdAt = { + $gte: createdOnOrAfter, // gte: Greater than or equal to + $lte: createdOnOrBefore, // lte: Less than or equal to + }; } - // If `createdAfter` is provided without `createdBefore`, searches for questionnaires created after `createdAfter` - else if (createdAfter) { - matchQuery.createdAt = { $gt: createdAfter }; + // If `createdOnOrAfter` is provided without `createdOnOrBefore`, searches for questionnaires created on or after `createdOnOrAfter` + else if (createdOnOrAfter) { + matchQuery.createdAt = { $gte: createdOnOrAfter }; // gte: Greater than or equal to } - // If `createdBefore` is provided without `createdAfter`, searches for questionnaires created before `createdBefore` - else if (createdBefore) { - matchQuery.createdAt = { $lt: createdBefore }; + // If `createdOnOrBefore` is provided without `createdOnOrAfter`, searches for questionnaires created on or before `createdOnOrBefore` + else if (createdOnOrBefore) { + matchQuery.createdAt = { $lte: createdOnOrBefore }; // lte: Less than or equal to } if (access === "All") { @@ -385,7 +388,7 @@ const getMatchQuery = async (input, ctx) => { return matchQuery; } catch (error) { logger.error( - { error, input }, + { error: error.stack, input }, "Unable to get match query for filtering questionnaires (from getMatchQuery)" ); } @@ -566,7 +569,7 @@ const listFilteredQuestionnaires = async (input, ctx) => { return transformedQuestionnaires; } catch (error) { logger.error( - error, + { error: error.stack, input }, "Unable to retrieve questionnaires (from listFilteredQuestionnaires)" ); return; @@ -607,7 +610,10 @@ const getTotalPages = async (input, ctx) => { return totalPages; } catch (error) { - logger.error(error, "Unable to get total pages"); + logger.error( + { error: error.stack, input }, + "Unable to get total pages (from getTotalPages)" + ); return; } }; diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/homepage/index.js index 0cbaff6fd6..7a6aa692a0 100644 --- a/eq-author-api/schema/resolvers/homepage/index.js +++ b/eq-author-api/schema/resolvers/homepage/index.js @@ -12,8 +12,8 @@ const Resolvers = { lastQuestionnaireIdOnPage, search, owner, - createdAfter, - createdBefore, + createdOnOrAfter, + createdOnOrBefore, access, myQuestionnaires, sortBy, @@ -26,8 +26,8 @@ const Resolvers = { lastQuestionnaireIdOnPage, search, owner, - createdAfter, - createdBefore, + createdOnOrAfter, + createdOnOrBefore, access, myQuestionnaires, sortBy, @@ -43,8 +43,8 @@ const Resolvers = { resultsPerPage = 10, search, owner, - createdAfter, - createdBefore, + createdOnOrAfter, + createdOnOrBefore, access, myQuestionnaires, } = input; @@ -54,8 +54,8 @@ const Resolvers = { resultsPerPage, search, owner, - createdAfter, - createdBefore, + createdOnOrAfter, + createdOnOrBefore, access, myQuestionnaires, }, diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index b02b6b38b9..ead3db9fdb 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -945,8 +945,8 @@ input FilteredQuestionnairesInput { lastQuestionnaireIdOnPage: ID search: String owner: String - createdAfter: DateTime - createdBefore: DateTime + createdOnOrAfter: DateTime + createdOnOrBefore: DateTime access: Permission myQuestionnaires: Boolean sortBy: String @@ -956,8 +956,8 @@ input TotalPagesInput { resultsPerPage: Int search: String owner: String - createdAfter: DateTime - createdBefore: DateTime + createdOnOrAfter: DateTime + createdOnOrBefore: DateTime access: Permission myQuestionnaires: Boolean } From 013549ad89c3d9917e6165b2b561b5a7bce490b9 Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 16 Jul 2024 12:50:49 +0100 Subject: [PATCH 18/61] Add tests for search by questionnaire title and owner --- .../db/datastore/datastore-mongodb.test.js | 89 ++++++++++++++++++- .../db/datastore/mock-questionnaire.js | 5 +- 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 3de432fc7d..7f9541e74a 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -1,4 +1,4 @@ -const mockQuestionnnaire = require("./mock-questionnaire"); +const mockQuestionnaire = require("./mock-questionnaire"); const { noteCreationEvent } = require("../../utils/questionnaireEvents"); const { v4: uuidv4 } = require("uuid"); @@ -13,7 +13,7 @@ describe("MongoDB Datastore", () => { }); beforeEach(() => { - questionnaire = mockQuestionnnaire(); + questionnaire = mockQuestionnaire({}); ctx = { user: { id: 123, @@ -58,6 +58,10 @@ describe("MongoDB Datastore", () => { expect(() => mongoDB.listQuestionnaires()).not.toThrow(); }); + it("Should not throw error on listFilteredQuestionnaires", async () => { + expect(() => mongoDB.listFilteredQuestionnaires({}, ctx)).not.toThrow(); + }); + it("Should not throw error on createQuestionnaire", async () => { expect(() => mongoDB.createQuestionnaire(questionnaire, ctx) @@ -340,6 +344,87 @@ describe("MongoDB Datastore", () => { }); }); + describe("Getting a list of filtered questionnaires", () => { + beforeAll(async () => { + await mongoDB.createUser({ + id: "user-1", + email: "user1@example.com", + name: "Joe Bloggs", + externalId: "user-1", + picture: "", + }); + + await mongoDB.createUser({ + id: "user-2", + email: "user2@example.com", + name: "Jane Smith", + externalId: "user-2", + picture: "", + }); + + await mongoDB.createQuestionnaire( + mockQuestionnaire({ + title: "Test questionnaire 1", + ownerId: "user-1", + }), + ctx + ); + await mongoDB.createQuestionnaire( + mockQuestionnaire({ + title: "Test questionnaire 2", + ownerId: "user-1", + }), + ctx + ); + await mongoDB.createQuestionnaire( + mockQuestionnaire({ + title: "Test questionnaire 3", + ownerId: "user-2", + }), + ctx + ); + await mongoDB.createQuestionnaire( + mockQuestionnaire({ + title: "Test questionnaire 4", + ownerId: "user-2", + }), + ctx + ); + }); + + it("Should return questionnaires with title containing the search term", async () => { + const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "Test questionnaire", + owner: "", + resultsPerPage: 10, + }, + ctx + ); + + expect(listOfQuestionnaires.length).toBe(4); + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); + expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); + expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 3"); + expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 4"); + }); + + it("Should return questionnaires with owner containing the `owner` search term", async () => { + const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "Jane", + resultsPerPage: 10, + }, + ctx + ); + + expect(listOfQuestionnaires.length).toBe(2); + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 3"); + expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 4"); + }); + }); + describe("Creating a history event", () => { let mockHistoryEvent; beforeEach(() => { diff --git a/eq-author-api/db/datastore/mock-questionnaire.js b/eq-author-api/db/datastore/mock-questionnaire.js index cefbae0be5..a49e3ee69a 100644 --- a/eq-author-api/db/datastore/mock-questionnaire.js +++ b/eq-author-api/db/datastore/mock-questionnaire.js @@ -1,8 +1,8 @@ const { v4: uuidv4 } = require("uuid"); -const mockQuestionnaire = () => { +const mockQuestionnaire = ({ title, ownerId }) => { const questionnaire = { - title: "Working from home", + title: title || "Working from home", theme: "business", legalBasis: "Voluntary", navigation: false, @@ -36,6 +36,7 @@ const mockQuestionnaire = () => { editors: [], isPublic: true, publishStatus: "Unpublished", + createdBy: ownerId || "user-1", }; return questionnaire; }; From 90b9cc154ed13dd7438ace1d5e40dade57b5157b Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 16 Jul 2024 14:25:20 +0100 Subject: [PATCH 19/61] Add test for questionnaires created on or after date --- .../db/datastore/datastore-mongodb.test.js | 31 +++++++++++++++++++ .../db/datastore/mock-questionnaire.js | 5 +-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 7f9541e74a..cb31af4dc3 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -366,6 +366,7 @@ describe("MongoDB Datastore", () => { mockQuestionnaire({ title: "Test questionnaire 1", ownerId: "user-1", + createdAt: new Date(2021, 2, 5, 5, 0, 0, 0), }), ctx ); @@ -373,6 +374,7 @@ describe("MongoDB Datastore", () => { mockQuestionnaire({ title: "Test questionnaire 2", ownerId: "user-1", + createdAt: new Date(2021, 2, 10, 5, 0, 0, 0), }), ctx ); @@ -380,6 +382,7 @@ describe("MongoDB Datastore", () => { mockQuestionnaire({ title: "Test questionnaire 3", ownerId: "user-2", + createdAt: new Date(2021, 2, 15, 5, 0, 0, 0), }), ctx ); @@ -387,6 +390,7 @@ describe("MongoDB Datastore", () => { mockQuestionnaire({ title: "Test questionnaire 4", ownerId: "user-2", + createdAt: new Date(2021, 2, 20, 5, 0, 0, 0), }), ctx ); @@ -423,6 +427,33 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 3"); expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 4"); }); + + it("Should return questionnaires created on or after the searched date", async () => { + const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "", + createdOnOrAfter: new Date(2021, 2, 10), + resultsPerPage: 10, + }, + ctx + ); + + expect(listOfQuestionnaires.length).toBe(6); + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 2"); + expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 3"); + expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 4"); + // Questionnaires created in previous tests + expect(listOfQuestionnaires[3].title).toEqual( + "Default questionnaire title" + ); + expect(listOfQuestionnaires[4].title).toEqual( + "Default questionnaire title" + ); + expect(listOfQuestionnaires[5].title).toEqual( + "Default questionnaire title" + ); + }); }); describe("Creating a history event", () => { diff --git a/eq-author-api/db/datastore/mock-questionnaire.js b/eq-author-api/db/datastore/mock-questionnaire.js index a49e3ee69a..0efe17bcaf 100644 --- a/eq-author-api/db/datastore/mock-questionnaire.js +++ b/eq-author-api/db/datastore/mock-questionnaire.js @@ -1,8 +1,8 @@ const { v4: uuidv4 } = require("uuid"); -const mockQuestionnaire = ({ title, ownerId }) => { +const mockQuestionnaire = ({ title, ownerId, createdAt }) => { const questionnaire = { - title: title || "Working from home", + title: title || "Default questionnaire title", theme: "business", legalBasis: "Voluntary", navigation: false, @@ -37,6 +37,7 @@ const mockQuestionnaire = ({ title, ownerId }) => { isPublic: true, publishStatus: "Unpublished", createdBy: ownerId || "user-1", + createdAt, }; return questionnaire; }; From b974554476b8f81c2d75385ba0478369e14f6baa Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 16 Jul 2024 14:47:44 +0100 Subject: [PATCH 20/61] Add test for questionnaires created on or before date --- eq-author-api/db/datastore/datastore-mongodb.js | 3 +++ .../db/datastore/datastore-mongodb.test.js | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index ab3c910171..aea1453be3 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -302,6 +302,9 @@ const getMatchQuery = async (input, ctx) => { } = input; const { id: userId } = ctx.user; + if (createdOnOrBefore) { + createdOnOrBefore.setHours(23, 59, 59, 999); // Sets `createdOnOrBefore` time to 23:59:59.999 to include all questionnaires created on that day + } const matchQuery = { // Searches for questionnaires with `title` or `shortTitle` (short code) containing the search term diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index cb31af4dc3..ba949413f8 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -454,6 +454,22 @@ describe("MongoDB Datastore", () => { "Default questionnaire title" ); }); + + it("Should return questionnaires created on or before the searched date", async () => { + const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "", + createdOnOrBefore: new Date(2021, 2, 10), + resultsPerPage: 10, + }, + ctx + ); + + expect(listOfQuestionnaires.length).toBe(2); + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); + expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); + }); }); describe("Creating a history event", () => { From d0449ef9476bdefdd7799979668aac09e625fd0c Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 16 Jul 2024 14:50:56 +0100 Subject: [PATCH 21/61] Add test for questionnaires created between specified dates --- .../db/datastore/datastore-mongodb.test.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index ba949413f8..5545bf5fcd 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -470,6 +470,24 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); }); + + it("Should return questionnaires created between the searched dates", async () => { + const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "", + createdOnOrAfter: new Date(2021, 2, 10), + createdOnOrBefore: new Date(2021, 2, 20), + resultsPerPage: 10, + }, + ctx + ); + + expect(listOfQuestionnaires.length).toBe(3); + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 2"); + expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 3"); + expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 4"); + }); }); describe("Creating a history event", () => { From cfe91fbc24094bd2303fb2b9f6566348b9fc4b4a Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 17 Jul 2024 09:29:05 +0100 Subject: [PATCH 22/61] Add test to search questionnaires by "All" access --- .../db/datastore/datastore-mongodb.test.js | 44 ++++++++++++++++++- .../db/datastore/mock-questionnaire.js | 4 +- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 5545bf5fcd..d4e25581e2 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -16,7 +16,7 @@ describe("MongoDB Datastore", () => { questionnaire = mockQuestionnaire({}); ctx = { user: { - id: 123, + id: "user-1", }, }; user = { @@ -394,6 +394,15 @@ describe("MongoDB Datastore", () => { }), ctx ); + await mongoDB.createQuestionnaire( + mockQuestionnaire({ + title: "Test questionnaire 5", + ownerId: "user-2", + createdAt: new Date(2021, 2, 25, 5, 0, 0, 0), + isPublic: false, + }), + ctx + ); }); it("Should return questionnaires with title containing the search term", async () => { @@ -401,6 +410,7 @@ describe("MongoDB Datastore", () => { { search: "Test questionnaire", owner: "", + access: "All", resultsPerPage: 10, }, ctx @@ -418,6 +428,7 @@ describe("MongoDB Datastore", () => { { search: "", owner: "Jane", + access: "All", resultsPerPage: 10, }, ctx @@ -434,6 +445,7 @@ describe("MongoDB Datastore", () => { search: "", owner: "", createdOnOrAfter: new Date(2021, 2, 10), + access: "All", resultsPerPage: 10, }, ctx @@ -461,6 +473,7 @@ describe("MongoDB Datastore", () => { search: "", owner: "", createdOnOrBefore: new Date(2021, 2, 10), + access: "All", resultsPerPage: 10, }, ctx @@ -478,6 +491,7 @@ describe("MongoDB Datastore", () => { owner: "", createdOnOrAfter: new Date(2021, 2, 10), createdOnOrBefore: new Date(2021, 2, 20), + access: "All", resultsPerPage: 10, }, ctx @@ -488,6 +502,34 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 3"); expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 4"); }); + + it("Should return relevant questionnaires when searching by access `All`", async () => { + const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "", + access: "All", + resultsPerPage: 10, + }, + ctx + ); + + expect(listOfQuestionnaires.length).toBe(7); + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); + expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); + expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 3"); + expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 4"); + // Questionnaires created in previous tests + expect(listOfQuestionnaires[4].title).toEqual( + "Default questionnaire title" + ); + expect(listOfQuestionnaires[5].title).toEqual( + "Default questionnaire title" + ); + expect(listOfQuestionnaires[6].title).toEqual( + "Default questionnaire title" + ); + }); }); describe("Creating a history event", () => { diff --git a/eq-author-api/db/datastore/mock-questionnaire.js b/eq-author-api/db/datastore/mock-questionnaire.js index 0efe17bcaf..2f60c1acd6 100644 --- a/eq-author-api/db/datastore/mock-questionnaire.js +++ b/eq-author-api/db/datastore/mock-questionnaire.js @@ -1,6 +1,6 @@ const { v4: uuidv4 } = require("uuid"); -const mockQuestionnaire = ({ title, ownerId, createdAt }) => { +const mockQuestionnaire = ({ title, ownerId, createdAt, isPublic }) => { const questionnaire = { title: title || "Default questionnaire title", theme: "business", @@ -34,7 +34,7 @@ const mockQuestionnaire = ({ title, ownerId, createdAt }) => { version: 13, surveyVersion: 1, editors: [], - isPublic: true, + isPublic: isPublic ?? true, publishStatus: "Unpublished", createdBy: ownerId || "user-1", createdAt, From b0695d236890a942646bfb4eccd21f6f2a8da025 Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 17 Jul 2024 09:54:03 +0100 Subject: [PATCH 23/61] Add test to search questionnaires by editor access --- .../db/datastore/datastore-mongodb.test.js | 27 +++++++++++++++++++ .../db/datastore/mock-questionnaire.js | 12 ++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index d4e25581e2..eafeddd253 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -362,6 +362,14 @@ describe("MongoDB Datastore", () => { picture: "", }); + await mongoDB.createUser({ + id: "test-user", + email: "test-user@example.com", + name: "Test User", + externalId: "test-user", + picture: "", + }); + await mongoDB.createQuestionnaire( mockQuestionnaire({ title: "Test questionnaire 1", @@ -382,6 +390,7 @@ describe("MongoDB Datastore", () => { mockQuestionnaire({ title: "Test questionnaire 3", ownerId: "user-2", + editors: ["user-1"], createdAt: new Date(2021, 2, 15, 5, 0, 0, 0), }), ctx @@ -530,6 +539,24 @@ describe("MongoDB Datastore", () => { "Default questionnaire title" ); }); + + it("Should return relevant questionnaires when searching by access `Write`", async () => { + const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "", + access: "Write", + resultsPerPage: 10, + }, + ctx + ); + + // Expects all questionnaires where `ctx.user` is the owner (`ctx.user` created the questionnaire) or an editor + expect(listOfQuestionnaires.length).toBe(3); + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); // "user-1" created the questionnaire + expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); // "user-1" created the questionnaire + expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 3"); // "user-1" is an editor + }); }); describe("Creating a history event", () => { diff --git a/eq-author-api/db/datastore/mock-questionnaire.js b/eq-author-api/db/datastore/mock-questionnaire.js index 2f60c1acd6..ffd7089bff 100644 --- a/eq-author-api/db/datastore/mock-questionnaire.js +++ b/eq-author-api/db/datastore/mock-questionnaire.js @@ -1,6 +1,12 @@ const { v4: uuidv4 } = require("uuid"); -const mockQuestionnaire = ({ title, ownerId, createdAt, isPublic }) => { +const mockQuestionnaire = ({ + title, + ownerId, + createdAt, + isPublic, + editors, +}) => { const questionnaire = { title: title || "Default questionnaire title", theme: "business", @@ -33,10 +39,10 @@ const mockQuestionnaire = ({ title, ownerId, createdAt, isPublic }) => { summary: false, version: 13, surveyVersion: 1, - editors: [], + editors: editors || [], isPublic: isPublic ?? true, publishStatus: "Unpublished", - createdBy: ownerId || "user-1", + createdBy: ownerId || "test-user", createdAt, }; return questionnaire; From ee2328cdde7a2d2e03b7dbc9218a9f970f766b38 Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 17 Jul 2024 11:15:49 +0100 Subject: [PATCH 24/61] Add test to search questionnaires by read access --- .../db/datastore/datastore-mongodb.test.js | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index eafeddd253..8b8878bbab 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -557,6 +557,31 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); // "user-1" created the questionnaire expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 3"); // "user-1" is an editor }); + + it("Should return relevant questionnaires when searching by access `Read`", async () => { + const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "", + access: "Read", + resultsPerPage: 10, + }, + ctx + ); + + expect(listOfQuestionnaires.length).toBe(4); + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 4"); + // Questionnaires created in previous tests + expect(listOfQuestionnaires[1].title).toEqual( + "Default questionnaire title" + ); + expect(listOfQuestionnaires[2].title).toEqual( + "Default questionnaire title" + ); + expect(listOfQuestionnaires[3].title).toEqual( + "Default questionnaire title" + ); + }); }); describe("Creating a history event", () => { From 9ac4539096288af45be417c564485dc3572c657e Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 17 Jul 2024 12:56:23 +0100 Subject: [PATCH 25/61] Add test to search for non-public questionnaires --- .../db/datastore/datastore-mongodb.test.js | 49 +++++++++++++++---- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 8b8878bbab..8f720f2d1c 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -403,6 +403,7 @@ describe("MongoDB Datastore", () => { }), ctx ); + // ** "Test questionnaire 5" is not included in several test assertions as it is not public and `ctx.user` is not owner/editor await mongoDB.createQuestionnaire( mockQuestionnaire({ title: "Test questionnaire 5", @@ -412,6 +413,15 @@ describe("MongoDB Datastore", () => { }), ctx ); + await mongoDB.createQuestionnaire( + mockQuestionnaire({ + title: "Test questionnaire 6", + ownerId: "user-1", + createdAt: new Date(2021, 2, 30, 5, 0, 0, 0), + isPublic: false, + }), + ctx + ); }); it("Should return questionnaires with title containing the search term", async () => { @@ -425,11 +435,12 @@ describe("MongoDB Datastore", () => { ctx ); - expect(listOfQuestionnaires.length).toBe(4); + expect(listOfQuestionnaires.length).toBe(5); expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 3"); expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 4"); + expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 6"); }); it("Should return questionnaires with owner containing the `owner` search term", async () => { @@ -460,20 +471,21 @@ describe("MongoDB Datastore", () => { ctx ); - expect(listOfQuestionnaires.length).toBe(6); + expect(listOfQuestionnaires.length).toBe(7); expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 2"); expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 3"); expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 4"); + expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 6"); // Questionnaires created in previous tests - expect(listOfQuestionnaires[3].title).toEqual( - "Default questionnaire title" - ); expect(listOfQuestionnaires[4].title).toEqual( "Default questionnaire title" ); expect(listOfQuestionnaires[5].title).toEqual( "Default questionnaire title" ); + expect(listOfQuestionnaires[6].title).toEqual( + "Default questionnaire title" + ); }); it("Should return questionnaires created on or before the searched date", async () => { @@ -523,21 +535,22 @@ describe("MongoDB Datastore", () => { ctx ); - expect(listOfQuestionnaires.length).toBe(7); + expect(listOfQuestionnaires.length).toBe(8); expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 3"); expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 4"); + expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 6"); // Questionnaires created in previous tests - expect(listOfQuestionnaires[4].title).toEqual( - "Default questionnaire title" - ); expect(listOfQuestionnaires[5].title).toEqual( "Default questionnaire title" ); expect(listOfQuestionnaires[6].title).toEqual( "Default questionnaire title" ); + expect(listOfQuestionnaires[7].title).toEqual( + "Default questionnaire title" + ); }); it("Should return relevant questionnaires when searching by access `Write`", async () => { @@ -552,10 +565,11 @@ describe("MongoDB Datastore", () => { ); // Expects all questionnaires where `ctx.user` is the owner (`ctx.user` created the questionnaire) or an editor - expect(listOfQuestionnaires.length).toBe(3); + expect(listOfQuestionnaires.length).toBe(4); expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); // "user-1" created the questionnaire expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); // "user-1" created the questionnaire expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 3"); // "user-1" is an editor + expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 6"); // "user-1" created the questionnaire }); it("Should return relevant questionnaires when searching by access `Read`", async () => { @@ -582,6 +596,21 @@ describe("MongoDB Datastore", () => { "Default questionnaire title" ); }); + + it("Should return relevant questionnaires when searching by access `PrivateQuestionnaires`", async () => { + const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "", + access: "PrivateQuestionnaires", + resultsPerPage: 10, + }, + ctx + ); + + expect(listOfQuestionnaires.length).toBe(1); + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 6"); + }); }); describe("Creating a history event", () => { From 1fb16676511e424327f52f287b4c1a6b38bef5bd Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 17 Jul 2024 13:06:38 +0100 Subject: [PATCH 26/61] Add test to filter my questionnaires --- .../db/datastore/datastore-mongodb.test.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 8f720f2d1c..5577556876 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -611,6 +611,25 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires.length).toBe(1); expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 6"); }); + + it("Should return relevant questionnaires when `myQuestionnaires` is true", async () => { + const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "", + access: "All", + resultsPerPage: 10, + myQuestionnaires: true, + }, + ctx + ); + + expect(listOfQuestionnaires.length).toBe(4); + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); + expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); + expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 3"); + expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 6"); + }); }); describe("Creating a history event", () => { From 80ee25e4bbcd5e82e1e904a89ff81299c4c74bbd Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 17 Jul 2024 13:19:36 +0100 Subject: [PATCH 27/61] Add test for questionnaires on previous page --- .../db/datastore/datastore-mongodb.test.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 5577556876..8f276fc8f5 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -630,6 +630,39 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 3"); expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 6"); }); + + it("Should return questionnaires on previous page when `firstQuestionnaireIdOnPage` is provided without `lastQuestionnaireIdOnPage`", async () => { + // Gets questionnaires with "All" access to get a questionnaire ID to use as `firstQuestionnaireIdOnPage` + const allQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "", + access: "All", + resultsPerPage: 10, + }, + ctx + ); + + const listOfPreviousPageQuestionnaires = + await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "", + access: "All", + resultsPerPage: 2, // Limits to 2 questionnaires per page to test a small number of questionnaires on previous page + firstQuestionnaireIdOnPage: allQuestionnaires[2].id, + }, + ctx + ); + + expect(listOfPreviousPageQuestionnaires.length).toBe(2); + expect(listOfPreviousPageQuestionnaires[0].title).toEqual( + "Test questionnaire 1" + ); + expect(listOfPreviousPageQuestionnaires[1].title).toEqual( + "Test questionnaire 2" + ); + }); }); describe("Creating a history event", () => { From 97c3b69eb4e1b80966770261b549bb0ac563c296 Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 17 Jul 2024 13:26:20 +0100 Subject: [PATCH 28/61] Add test for questionnaires on next page --- .../db/datastore/datastore-mongodb.test.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 8f276fc8f5..644eacaa62 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -656,6 +656,7 @@ describe("MongoDB Datastore", () => { ); expect(listOfPreviousPageQuestionnaires.length).toBe(2); + // The two questionnaires before the first questionnaire on the page (based on firstQuestionnaireIdOnPage) expect(listOfPreviousPageQuestionnaires[0].title).toEqual( "Test questionnaire 1" ); @@ -663,6 +664,40 @@ describe("MongoDB Datastore", () => { "Test questionnaire 2" ); }); + + it("Should return questionnaires on next page when `lastQuestionnaireIdOnPage` is provided without `firstQuestionnaireIdOnPage`", async () => { + // Gets questionnaires with "All" access to get a questionnaire ID to use as `lastQuestionnaireIdOnPage` + const allQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "", + access: "All", + resultsPerPage: 10, + }, + ctx + ); + + const listOfNextPageQuestionnaires = + await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "", + access: "All", + resultsPerPage: 2, // Limits to 2 questionnaires per page to test a small number of questionnaires on next page + lastQuestionnaireIdOnPage: allQuestionnaires[3].id, + }, + ctx + ); + + expect(listOfNextPageQuestionnaires.length).toBe(2); + // The two questionnaires after the last questionnaire on the page (based on lastQuestionnaireIdOnPage) + expect(listOfNextPageQuestionnaires[0].title).toEqual( + "Test questionnaire 6" + ); + expect(listOfNextPageQuestionnaires[1].title).toEqual( + "Default questionnaire title" + ); + }); }); describe("Creating a history event", () => { From f3e6b93b308ec046a3455df1593027f6bceb6dbd Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 17 Jul 2024 15:00:10 +0100 Subject: [PATCH 29/61] Add test for error log when both firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage are provided --- .../db/datastore/datastore-mongodb.js | 4 +- .../db/datastore/datastore-mongodb.test.js | 40 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index aea1453be3..930ca914ec 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -531,8 +531,10 @@ const listFilteredQuestionnaires = async (input, ctx) => { ]); } else { logger.error( - "Invalid input - both firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage have been provided (from listFilteredQuestionnaires)" + { input }, + "Invalid input - received both firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage, expected only one of these values or neither (from listFilteredQuestionnaires)" ); + return; } const questionnaires = await questionnairesQuery.toArray(); diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 644eacaa62..9f08ed0d60 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -2,6 +2,10 @@ const mockQuestionnaire = require("./mock-questionnaire"); const { noteCreationEvent } = require("../../utils/questionnaireEvents"); const { v4: uuidv4 } = require("uuid"); +const mockLoggerDebug = jest.fn(); +const mockLoggerInfo = jest.fn(); +const mockLoggerError = jest.fn(); + describe("MongoDB Datastore", () => { let questionnaire, user, firstUser, mockComment, ctx; let mongoDB; @@ -12,6 +16,18 @@ describe("MongoDB Datastore", () => { jest.resetModules(); }); + jest.mock("../../utils/logger", () => ({ + logger: { + debug: mockLoggerDebug, + info: mockLoggerInfo, + error: mockLoggerError, + }, + })); + + afterEach(() => { + jest.clearAllMocks(); + }); + beforeEach(() => { questionnaire = mockQuestionnaire({}); ctx = { @@ -698,6 +714,30 @@ describe("MongoDB Datastore", () => { "Default questionnaire title" ); }); + + it("Should log an error when both `firstQuestionnaireIdOnPage` and `lastQuestionnaireIdOnPage` are provided", async () => { + const listFilteredQuestionnairesInput = { + search: "", + owner: "", + access: "All", + resultsPerPage: 10, + firstQuestionnaireIdOnPage: "123", + lastQuestionnaireIdOnPage: "456", + }; + + await mongoDB.listFilteredQuestionnaires( + listFilteredQuestionnairesInput, + ctx + ); + + expect(mockLoggerError).toHaveBeenCalledTimes(1); + expect(mockLoggerError).toHaveBeenCalledWith( + { + input: listFilteredQuestionnairesInput, + }, + "Invalid input - received both firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage, expected only one of these values or neither (from listFilteredQuestionnaires)" + ); + }); }); describe("Creating a history event", () => { From 79abc37c502252ea893b4c43487f86933245dc06 Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 17 Jul 2024 15:12:24 +0100 Subject: [PATCH 30/61] Add test for no questionnaires found --- .../db/datastore/datastore-mongodb.test.js | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 9f08ed0d60..fca7a6f6ce 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -715,7 +715,7 @@ describe("MongoDB Datastore", () => { ); }); - it("Should log an error when both `firstQuestionnaireIdOnPage` and `lastQuestionnaireIdOnPage` are provided", async () => { + it("Should log an error message when both `firstQuestionnaireIdOnPage` and `lastQuestionnaireIdOnPage` are provided", async () => { const listFilteredQuestionnairesInput = { search: "", owner: "", @@ -738,6 +738,29 @@ describe("MongoDB Datastore", () => { "Invalid input - received both firstQuestionnaireIdOnPage and lastQuestionnaireIdOnPage, expected only one of these values or neither (from listFilteredQuestionnaires)" ); }); + + it("Should log a debug message when no questionnaires are found", async () => { + const listFilteredQuestionnairesInput = { + search: "Lorem ipsum", // Search term contained in no questionnaires + owner: "", + access: "All", + resultsPerPage: 10, + }; + + // `listOfQuestionnaires` should be an empty array as no questionnaires contain the search term + const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( + listFilteredQuestionnairesInput, + ctx + ); + + expect(mockLoggerDebug).toHaveBeenCalledTimes(1); + expect(mockLoggerDebug).toHaveBeenCalledWith( + `No questionnaires found with input: ${JSON.stringify( + listFilteredQuestionnairesInput + )} (from listFilteredQuestionnaires)` + ); + expect(listOfQuestionnaires).toEqual([]); + }); }); describe("Creating a history event", () => { From 4c8ba12f826afd2b7d833c5601315b7781f3dd18 Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 18 Jul 2024 09:24:47 +0100 Subject: [PATCH 31/61] Add test for sorting questionnaires on first page --- .../db/datastore/datastore-mongodb.test.js | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index fca7a6f6ce..026617daca 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -12,9 +12,6 @@ describe("MongoDB Datastore", () => { jest.isolateModules(() => { mongoDB = require("./datastore-mongodb"); }); - beforeAll(() => { - jest.resetModules(); - }); jest.mock("../../utils/logger", () => ({ logger: { @@ -24,6 +21,10 @@ describe("MongoDB Datastore", () => { }, })); + beforeAll(() => { + jest.resetModules(); + }); + afterEach(() => { jest.clearAllMocks(); }); @@ -761,6 +762,37 @@ describe("MongoDB Datastore", () => { ); expect(listOfQuestionnaires).toEqual([]); }); + + it("Should sort questionnaires on first page from newest to oldest when `sortBy` is `createdDateDesc`", async () => { + const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "", + access: "All", + resultsPerPage: 10, + sortBy: "createdDateDesc", + }, + ctx + ); + + expect(listOfQuestionnaires.length).toBe(8); + // First created are questionnaires created in previous tests as all `Test questionnaires` have `createdAt` dates in the past + expect(listOfQuestionnaires[0].title).toEqual( + "Default questionnaire title" + ); + expect(listOfQuestionnaires[1].title).toEqual( + "Default questionnaire title" + ); + expect(listOfQuestionnaires[2].title).toEqual( + "Default questionnaire title" + ); + // `Test questionnaire 6` has the most recent `createdAt` date of the `Test questionnaires` + expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 6"); + expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 4"); + expect(listOfQuestionnaires[5].title).toEqual("Test questionnaire 3"); + expect(listOfQuestionnaires[6].title).toEqual("Test questionnaire 2"); + expect(listOfQuestionnaires[7].title).toEqual("Test questionnaire 1"); + }); }); describe("Creating a history event", () => { From 06e76993f8d8c7c820e7eb93ae4dc2c690f30c3c Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 18 Jul 2024 09:43:55 +0100 Subject: [PATCH 32/61] Add test for sorting questionnaires on next page --- .../db/datastore/datastore-mongodb.test.js | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 026617daca..c670db8602 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -793,6 +793,42 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[6].title).toEqual("Test questionnaire 2"); expect(listOfQuestionnaires[7].title).toEqual("Test questionnaire 1"); }); + + it("Should sort questionnaires on next page from newest to oldest when `sortBy` is `createdDateDesc`", async () => { + // Gets questionnaires with "All" access to get a questionnaire ID to use as `lastQuestionnaireIdOnPage` + const allQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "", + access: "All", + resultsPerPage: 10, + sortBy: "createdDateDesc", + }, + ctx + ); + + const listOfNextPageQuestionnaires = + await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "", + access: "All", + resultsPerPage: 2, + lastQuestionnaireIdOnPage: allQuestionnaires[3].id, + sortBy: "createdDateDesc", + }, + ctx + ); + + expect(listOfNextPageQuestionnaires.length).toBe(2); + // The two questionnaires after the last questionnaire on the page (based on lastQuestionnaireIdOnPage) + expect(listOfNextPageQuestionnaires[0].title).toEqual( + "Test questionnaire 4" + ); + expect(listOfNextPageQuestionnaires[1].title).toEqual( + "Test questionnaire 3" + ); + }); }); describe("Creating a history event", () => { From 52b40cfe99e81cfe24c033e63d8f42dbf3232c3b Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 18 Jul 2024 09:55:36 +0100 Subject: [PATCH 33/61] Add test for sorting questionnaires on previous page --- .../db/datastore/datastore-mongodb.test.js | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index c670db8602..3b7392def8 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -794,6 +794,42 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[7].title).toEqual("Test questionnaire 1"); }); + it("Should sort questionnaires on previous page from newest to oldest when `sortBy` is `createdDateDesc`", async () => { + // Gets questionnaires with "All" access to get a questionnaire ID to use as `firstQuestionnaireIdOnPage` + const allQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "", + access: "All", + resultsPerPage: 10, + sortBy: "createdDateDesc", + }, + ctx + ); + + const listOfPreviousPageQuestionnaires = + await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "", + access: "All", + resultsPerPage: 2, + firstQuestionnaireIdOnPage: allQuestionnaires[6].id, + sortBy: "createdDateDesc", + }, + ctx + ); + + expect(listOfPreviousPageQuestionnaires.length).toBe(2); + // The two questionnaires before the first questionnaire on the page (based on firstQuestionnaireIdOnPage) + expect(listOfPreviousPageQuestionnaires[0].title).toEqual( + "Test questionnaire 4" + ); + expect(listOfPreviousPageQuestionnaires[1].title).toEqual( + "Test questionnaire 3" + ); + }); + it("Should sort questionnaires on next page from newest to oldest when `sortBy` is `createdDateDesc`", async () => { // Gets questionnaires with "All" access to get a questionnaire ID to use as `lastQuestionnaireIdOnPage` const allQuestionnaires = await mongoDB.listFilteredQuestionnaires( From 74dec59f15a85f04e73a0177d946895cf44ac102 Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 18 Jul 2024 11:49:20 +0100 Subject: [PATCH 34/61] Add test for getting total page count --- .../db/datastore/datastore-mongodb.test.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 3b7392def8..5e4f1c5938 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -867,6 +867,22 @@ describe("MongoDB Datastore", () => { }); }); + describe("Getting total page count", () => { + it("Should get the total number of pages based on the number of questionnaires and results per page", async () => { + const totalPageCount = await mongoDB.getTotalPages( + { + resultsPerPage: 3, // As 8 questionnaires should be returned (from previously created questionnaires), uses 3 questionnaires per page to test total page count is rounded up + search: "", + owner: "", + access: "All", + }, + ctx + ); + + expect(totalPageCount).toBe(3); // (8 questionnaires) / (3 results per page) gives 3 total pages after rounding up + }); + }); + describe("Creating a history event", () => { let mockHistoryEvent; beforeEach(() => { From 75c2cb2bdb85b0eb308997bc782eb2ddc3bcf6d1 Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 18 Jul 2024 13:49:09 +0100 Subject: [PATCH 35/61] Add test for getTotalPages error --- .../db/datastore/datastore-mongodb.test.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 5e4f1c5938..a734fa462d 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -881,6 +881,19 @@ describe("MongoDB Datastore", () => { expect(totalPageCount).toBe(3); // (8 questionnaires) / (3 results per page) gives 3 total pages after rounding up }); + + it("Should log an error message on exception", async () => { + await mongoDB.getTotalPages(); // No arguments to trigger exception + + expect(mockLoggerError).toHaveBeenCalledTimes(1); + expect(mockLoggerError).toHaveBeenCalledWith( + { + input: undefined, + error: expect.any(String), + }, + "Unable to get total pages (from getTotalPages)" + ); + }); }); describe("Creating a history event", () => { From c86257e2956886383818e006c4ad842a0545ed8b Mon Sep 17 00:00:00 2001 From: farres1 Date: Mon, 22 Jul 2024 11:04:12 +0100 Subject: [PATCH 36/61] Add test for filtered questionnaires resolver --- .../schema/resolvers/homepage/index.test.js | 65 +++++++++++++++++++ .../utils/contextBuilder/homepage/index.js | 3 + .../homepage/queryFilteredQuestionnaires.js | 37 +++++++++++ 3 files changed, 105 insertions(+) create mode 100644 eq-author-api/schema/resolvers/homepage/index.test.js create mode 100644 eq-author-api/tests/utils/contextBuilder/homepage/index.js create mode 100644 eq-author-api/tests/utils/contextBuilder/homepage/queryFilteredQuestionnaires.js diff --git a/eq-author-api/schema/resolvers/homepage/index.test.js b/eq-author-api/schema/resolvers/homepage/index.test.js new file mode 100644 index 0000000000..910c751e18 --- /dev/null +++ b/eq-author-api/schema/resolvers/homepage/index.test.js @@ -0,0 +1,65 @@ +const { + createQuestionnaire, +} = require("../../../tests/utils/contextBuilder/questionnaire"); +const { + queryFilteredQuestionnaires, +} = require("../../../tests/utils/contextBuilder/homepage"); + +const { buildContext } = require("../../../tests/utils/contextBuilder"); + +describe("filteredQuestionnaires", () => { + let ctx; + + beforeEach(async () => { + ctx = await buildContext(); + + await createQuestionnaire(ctx, { + title: "Test Questionnaire 1", + theme: "business", + surveyId: "", + }); + await createQuestionnaire(ctx, { + title: "Test Questionnaire 2", + theme: "business", + surveyId: "", + }); + await createQuestionnaire(ctx, { + title: "Test Questionnaire 10", + theme: "business", + surveyId: "", + }); + await createQuestionnaire(ctx, { + title: "Test Questionnaire 11", + theme: "business", + surveyId: "", + }); + }); + + it("should return filtered questionnaires", async () => { + const user = { + id: "user-1", + }; + + const input = { + search: "Test Questionnaire 1", + owner: "", + }; + + const filteredQuestionnaires = await queryFilteredQuestionnaires( + user, + input + ); + + expect(filteredQuestionnaires).toEqual([ + expect.objectContaining({ + title: "Test Questionnaire 1", + }), + expect.objectContaining({ + title: "Test Questionnaire 10", + }), + expect.objectContaining({ + title: "Test Questionnaire 11", + }), + ]); + }); +}); diff --git a/eq-author-api/tests/utils/contextBuilder/homepage/index.js b/eq-author-api/tests/utils/contextBuilder/homepage/index.js new file mode 100644 index 0000000000..7e37a00f7e --- /dev/null +++ b/eq-author-api/tests/utils/contextBuilder/homepage/index.js @@ -0,0 +1,3 @@ +module.exports = { + ...require("./queryFilteredQuestionnaires"), +}; diff --git a/eq-author-api/tests/utils/contextBuilder/homepage/queryFilteredQuestionnaires.js b/eq-author-api/tests/utils/contextBuilder/homepage/queryFilteredQuestionnaires.js new file mode 100644 index 0000000000..df75382b16 --- /dev/null +++ b/eq-author-api/tests/utils/contextBuilder/homepage/queryFilteredQuestionnaires.js @@ -0,0 +1,37 @@ +const executeQuery = require("../../executeQuery"); + +const getFilteredQuestionnairesQuery = ` + query GetFilteredQuestionnaires($input: FilteredQuestionnairesInput) { + filteredQuestionnaires(input: $input) { + id + title + shortTitle + createdBy { + id + name + } + createdAt + editors { + id + } + permission + } + } +`; + +const queryFilteredQuestionnaires = async (user, input) => { + const result = await executeQuery( + getFilteredQuestionnairesQuery, + { input }, + { + user, + } + ); + + return result.data.filteredQuestionnaires; +}; + +module.exports = { + getFilteredQuestionnairesQuery, + queryFilteredQuestionnaires, +}; From 74a5d98a48d1eac4b99541e0893eea3364a47203 Mon Sep 17 00:00:00 2001 From: farres1 Date: Mon, 22 Jul 2024 14:48:04 +0100 Subject: [PATCH 37/61] Add test for total pages resolver --- .../schema/resolvers/homepage/index.test.js | 71 ++++++++++++------- .../utils/contextBuilder/homepage/index.js | 1 + .../homepage/queryTotalPages.js | 24 +++++++ 3 files changed, 71 insertions(+), 25 deletions(-) create mode 100644 eq-author-api/tests/utils/contextBuilder/homepage/queryTotalPages.js diff --git a/eq-author-api/schema/resolvers/homepage/index.test.js b/eq-author-api/schema/resolvers/homepage/index.test.js index 910c751e18..461374f004 100644 --- a/eq-author-api/schema/resolvers/homepage/index.test.js +++ b/eq-author-api/schema/resolvers/homepage/index.test.js @@ -3,14 +3,15 @@ const { } = require("../../../tests/utils/contextBuilder/questionnaire"); const { queryFilteredQuestionnaires, + queryTotalPages, } = require("../../../tests/utils/contextBuilder/homepage"); const { buildContext } = require("../../../tests/utils/contextBuilder"); -describe("filteredQuestionnaires", () => { +describe("homepage", () => { let ctx; - beforeEach(async () => { + beforeAll(async () => { ctx = await buildContext(); await createQuestionnaire(ctx, { @@ -35,31 +36,51 @@ describe("filteredQuestionnaires", () => { }); }); - it("should return filtered questionnaires", async () => { - const user = { - id: "user-1", - }; + describe("filteredQuestionnaires", () => { + it("should return filtered questionnaires", async () => { + const user = { + id: "user-1", + }; - const input = { - search: "Test Questionnaire 1", - owner: "", - }; + const input = { + search: "Test Questionnaire 1", + owner: "", + }; - const filteredQuestionnaires = await queryFilteredQuestionnaires( - user, - input - ); + const filteredQuestionnaires = await queryFilteredQuestionnaires( + user, + input + ); - expect(filteredQuestionnaires).toEqual([ - expect.objectContaining({ - title: "Test Questionnaire 1", - }), - expect.objectContaining({ - title: "Test Questionnaire 10", - }), - expect.objectContaining({ - title: "Test Questionnaire 11", - }), - ]); + expect(filteredQuestionnaires).toEqual([ + expect.objectContaining({ + title: "Test Questionnaire 1", + }), + expect.objectContaining({ + title: "Test Questionnaire 10", + }), + expect.objectContaining({ + title: "Test Questionnaire 11", + }), + ]); + }); + }); + + describe("totalPages", () => { + it("should return total pages", async () => { + const user = { + id: "user-1", + }; + + const input = { + resultsPerPage: 2, + search: "Test Questionnaire", + owner: "", + }; + + const totalPages = await queryTotalPages(user, input); + + expect(totalPages).toEqual(2); + }); }); }); diff --git a/eq-author-api/tests/utils/contextBuilder/homepage/index.js b/eq-author-api/tests/utils/contextBuilder/homepage/index.js index 7e37a00f7e..b8c2601a13 100644 --- a/eq-author-api/tests/utils/contextBuilder/homepage/index.js +++ b/eq-author-api/tests/utils/contextBuilder/homepage/index.js @@ -1,3 +1,4 @@ module.exports = { ...require("./queryFilteredQuestionnaires"), + ...require("./queryTotalPages"), }; diff --git a/eq-author-api/tests/utils/contextBuilder/homepage/queryTotalPages.js b/eq-author-api/tests/utils/contextBuilder/homepage/queryTotalPages.js new file mode 100644 index 0000000000..65345fb1d1 --- /dev/null +++ b/eq-author-api/tests/utils/contextBuilder/homepage/queryTotalPages.js @@ -0,0 +1,24 @@ +const executeQuery = require("../../executeQuery"); + +const getTotalPagesQuery = ` + query GetTotalPages($input: TotalPagesInput) { + totalPages(input: $input) + } +`; + +const queryTotalPages = async (user, input) => { + const result = await executeQuery( + getTotalPagesQuery, + { input }, + { + user, + } + ); + + return result.data.totalPages; +}; + +module.exports = { + getTotalPagesQuery, + queryTotalPages, +}; From 010aa33fd0fd2ebae7f5d35150e467b0f2c6a747 Mon Sep 17 00:00:00 2001 From: farres1 Date: Mon, 22 Jul 2024 15:15:45 +0100 Subject: [PATCH 38/61] Add filtered questionnaires test for when input is not provided --- .../schema/resolvers/homepage/index.js | 4 +-- .../schema/resolvers/homepage/index.test.js | 25 ++++++++++++++++++- .../homepage/queryFilteredQuestionnaires.js | 2 +- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/homepage/index.js index 7a6aa692a0..596b3aa1f7 100644 --- a/eq-author-api/schema/resolvers/homepage/index.js +++ b/eq-author-api/schema/resolvers/homepage/index.js @@ -10,8 +10,8 @@ const Resolvers = { resultsPerPage = 10, firstQuestionnaireIdOnPage, lastQuestionnaireIdOnPage, - search, - owner, + search = "", + owner = "", createdOnOrAfter, createdOnOrBefore, access, diff --git a/eq-author-api/schema/resolvers/homepage/index.test.js b/eq-author-api/schema/resolvers/homepage/index.test.js index 461374f004..e60283b755 100644 --- a/eq-author-api/schema/resolvers/homepage/index.test.js +++ b/eq-author-api/schema/resolvers/homepage/index.test.js @@ -37,7 +37,30 @@ describe("homepage", () => { }); describe("filteredQuestionnaires", () => { - it("should return filtered questionnaires", async () => { + it("should return all questionnaires when input is not provided", async () => { + const user = { + id: "user-1", + }; + + const filteredQuestionnaires = await queryFilteredQuestionnaires(user); + + expect(filteredQuestionnaires).toEqual([ + expect.objectContaining({ + title: "Test Questionnaire 1", + }), + expect.objectContaining({ + title: "Test Questionnaire 2", + }), + expect.objectContaining({ + title: "Test Questionnaire 10", + }), + expect.objectContaining({ + title: "Test Questionnaire 11", + }), + ]); + }); + + it("should filter questionnaires when input is provided", async () => { const user = { id: "user-1", }; diff --git a/eq-author-api/tests/utils/contextBuilder/homepage/queryFilteredQuestionnaires.js b/eq-author-api/tests/utils/contextBuilder/homepage/queryFilteredQuestionnaires.js index df75382b16..2b134d8532 100644 --- a/eq-author-api/tests/utils/contextBuilder/homepage/queryFilteredQuestionnaires.js +++ b/eq-author-api/tests/utils/contextBuilder/homepage/queryFilteredQuestionnaires.js @@ -22,7 +22,7 @@ const getFilteredQuestionnairesQuery = ` const queryFilteredQuestionnaires = async (user, input) => { const result = await executeQuery( getFilteredQuestionnairesQuery, - { input }, + input ? { input } : undefined, // Passes `undefined` when `input` is falsy to test when `input` is not provided { user, } From b75c93ed9ea5b730bc856f07433f1bfeb247b7fa Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 23 Jul 2024 07:48:39 +0100 Subject: [PATCH 39/61] Add total pages test for when input is not provided --- eq-author-api/schema/resolvers/homepage/index.js | 4 ++-- .../schema/resolvers/homepage/index.test.js | 15 +++++++++++++++ .../contextBuilder/homepage/queryTotalPages.js | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/homepage/index.js index 596b3aa1f7..3e182b56e7 100644 --- a/eq-author-api/schema/resolvers/homepage/index.js +++ b/eq-author-api/schema/resolvers/homepage/index.js @@ -41,8 +41,8 @@ const Resolvers = { totalPages: async (_, { input = {} }, ctx) => { const { resultsPerPage = 10, - search, - owner, + search = "", + owner = "", createdOnOrAfter, createdOnOrBefore, access, diff --git a/eq-author-api/schema/resolvers/homepage/index.test.js b/eq-author-api/schema/resolvers/homepage/index.test.js index e60283b755..4b1ed01496 100644 --- a/eq-author-api/schema/resolvers/homepage/index.test.js +++ b/eq-author-api/schema/resolvers/homepage/index.test.js @@ -86,10 +86,25 @@ describe("homepage", () => { title: "Test Questionnaire 11", }), ]); + expect(filteredQuestionnaires).not.toContainEqual( + expect.objectContaining({ + title: "Test Questionnaire 2", + }) + ); }); }); describe("totalPages", () => { + it("should return total pages for all questionnaires when input is not provided", async () => { + const user = { + id: "user-1", + }; + + const totalPages = await queryTotalPages(user); + + expect(totalPages).toEqual(1); + }); + it("should return total pages", async () => { const user = { id: "user-1", diff --git a/eq-author-api/tests/utils/contextBuilder/homepage/queryTotalPages.js b/eq-author-api/tests/utils/contextBuilder/homepage/queryTotalPages.js index 65345fb1d1..c14e09a28a 100644 --- a/eq-author-api/tests/utils/contextBuilder/homepage/queryTotalPages.js +++ b/eq-author-api/tests/utils/contextBuilder/homepage/queryTotalPages.js @@ -9,7 +9,7 @@ const getTotalPagesQuery = ` const queryTotalPages = async (user, input) => { const result = await executeQuery( getTotalPagesQuery, - { input }, + input ? { input } : undefined, // Passes `undefined` when `input` is falsy to test when `input` is not provided { user, } From 5a883d41672a44d7c1a51eed4f6a00ccba8d79ce Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 26 Jul 2024 10:36:27 +0100 Subject: [PATCH 40/61] Fix error thrown by getTotalPages when no questionnaires returned --- eq-author-api/db/datastore/datastore-mongodb.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index 930ca914ec..93d2723caa 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -591,7 +591,7 @@ const getTotalPages = async (input, ctx) => { const matchQuery = await getMatchQuery(input, ctx); // Gets the total number of questionnaires that meet the search conditions - const { totalFilteredQuestionnaires } = await questionnairesCollection + const aggregationResult = await questionnairesCollection .aggregate([ { $lookup: { @@ -610,6 +610,9 @@ const getTotalPages = async (input, ctx) => { ]) .next(); + // Sets default `totalFilteredQuestionnaires` to 0 if no questionnaires are returned - prevents error when destructuring `totalFilteredQuestionnaires` with no results + const { totalFilteredQuestionnaires = 0 } = aggregationResult || {}; + // Calculates the total number of pages by dividing the total number of filtered questionnaires by the number of results per page, and rounding up const totalPages = Math.ceil(totalFilteredQuestionnaires / resultsPerPage); From 624d3ce1d2c1ca53477734123c47e547db1efc06 Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 26 Jul 2024 10:46:26 +0100 Subject: [PATCH 41/61] Add test for total pages when no questionnaires returned --- .../db/datastore/datastore-mongodb.test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index a734fa462d..c3ca3bec3e 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -882,6 +882,21 @@ describe("MongoDB Datastore", () => { expect(totalPageCount).toBe(3); // (8 questionnaires) / (3 results per page) gives 3 total pages after rounding up }); + it("Should return 0 when no questionnaires are found", async () => { + const totalPageCount = await mongoDB.getTotalPages( + { + resultsPerPage: 10, + search: "Lorem ipsum", // Search term contained in no questionnaires + owner: "", + access: "All", + }, + ctx + ); + + expect(mockLoggerError).not.toHaveBeenCalled(); + expect(totalPageCount).toBe(0); + }); + it("Should log an error message on exception", async () => { await mongoDB.getTotalPages(); // No arguments to trigger exception From 508d7f077080d4dfc46babe5473e700b7207048c Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 31 Jul 2024 11:32:51 +0100 Subject: [PATCH 42/61] Refactor filteredQuestionnaires resolver --- .../db/datastore/datastore-mongodb.js | 10 +++---- .../schema/resolvers/homepage/index.js | 29 +------------------ 2 files changed, 6 insertions(+), 33 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index 93d2723caa..4e76664f63 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -290,11 +290,11 @@ const listQuestionnaires = async () => { } }; -const getMatchQuery = async (input, ctx) => { +const getMatchQuery = async (input = {}, ctx) => { try { const { - search, - owner, + search = "", + owner = "", createdOnOrAfter, createdOnOrBefore, access, @@ -397,10 +397,10 @@ const getMatchQuery = async (input, ctx) => { } }; -const listFilteredQuestionnaires = async (input, ctx) => { +const listFilteredQuestionnaires = async (input = {}, ctx) => { try { const { - resultsPerPage, + resultsPerPage = 10, firstQuestionnaireIdOnPage, lastQuestionnaireIdOnPage, sortBy, diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/homepage/index.js index 3e182b56e7..7fa2a98045 100644 --- a/eq-author-api/schema/resolvers/homepage/index.js +++ b/eq-author-api/schema/resolvers/homepage/index.js @@ -6,34 +6,7 @@ const { const Resolvers = { Query: { filteredQuestionnaires: async (_, { input = {} }, ctx) => { - const { - resultsPerPage = 10, - firstQuestionnaireIdOnPage, - lastQuestionnaireIdOnPage, - search = "", - owner = "", - createdOnOrAfter, - createdOnOrBefore, - access, - myQuestionnaires, - sortBy, - } = input; - - const questionnaires = await listFilteredQuestionnaires( - { - resultsPerPage, - firstQuestionnaireIdOnPage, - lastQuestionnaireIdOnPage, - search, - owner, - createdOnOrAfter, - createdOnOrBefore, - access, - myQuestionnaires, - sortBy, - }, - ctx - ); + const questionnaires = await listFilteredQuestionnaires(input, ctx); return questionnaires; }, From b1d78af8153c3979a409b422ce576f27cfe8ff0b Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 31 Jul 2024 14:14:23 +0100 Subject: [PATCH 43/61] Refactor getTotalPages resolver --- .../db/datastore/datastore-mongodb.js | 4 +-- .../db/datastore/datastore-mongodb.test.js | 12 +++++++-- .../schema/resolvers/homepage/index.js | 26 +++---------------- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index 4e76664f63..6b3901194c 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -581,9 +581,9 @@ const listFilteredQuestionnaires = async (input = {}, ctx) => { } }; -const getTotalPages = async (input, ctx) => { +const getTotalPages = async (input = {}, ctx) => { try { - const { resultsPerPage } = input; + const { resultsPerPage = 10 } = input; // Gets the questionnaires collection const questionnairesCollection = dbo.collection("questionnaires"); diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index c3ca3bec3e..d8a5b6c10c 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -900,10 +900,18 @@ describe("MongoDB Datastore", () => { it("Should log an error message on exception", async () => { await mongoDB.getTotalPages(); // No arguments to trigger exception - expect(mockLoggerError).toHaveBeenCalledTimes(1); + // Two calls as `getMatchQuery` also throws an error due to no context object + expect(mockLoggerError).toHaveBeenCalledTimes(2); + expect(mockLoggerError).toHaveBeenCalledWith( + { + input: {}, + error: expect.any(String), + }, + "Unable to get match query for filtering questionnaires (from getMatchQuery)" + ); expect(mockLoggerError).toHaveBeenCalledWith( { - input: undefined, + input: {}, error: expect.any(String), }, "Unable to get total pages (from getTotalPages)" diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/homepage/index.js index 7fa2a98045..72d8d27497 100644 --- a/eq-author-api/schema/resolvers/homepage/index.js +++ b/eq-author-api/schema/resolvers/homepage/index.js @@ -5,35 +5,15 @@ const { const Resolvers = { Query: { - filteredQuestionnaires: async (_, { input = {} }, ctx) => { + filteredQuestionnaires: async (_, { input }, ctx) => { const questionnaires = await listFilteredQuestionnaires(input, ctx); return questionnaires; }, - totalPages: async (_, { input = {} }, ctx) => { - const { - resultsPerPage = 10, - search = "", - owner = "", - createdOnOrAfter, - createdOnOrBefore, - access, - myQuestionnaires, - } = input; + totalPages: async (_, { input }, ctx) => { + const totalPages = await getTotalPages(input, ctx); - const totalPages = await getTotalPages( - { - resultsPerPage, - search, - owner, - createdOnOrAfter, - createdOnOrBefore, - access, - myQuestionnaires, - }, - ctx - ); return totalPages; }, }, From db6dd8ab78b3369a5de26bb1241e2066c1bfcc93 Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 31 Jul 2024 14:48:31 +0100 Subject: [PATCH 44/61] Refactor access if statements to switch statement --- .../db/datastore/datastore-mongodb.js | 84 +++++++++---------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index 6b3901194c..0bf6c12339 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -332,50 +332,48 @@ const getMatchQuery = async (input = {}, ctx) => { matchQuery.createdAt = { $lte: createdOnOrBefore }; // lte: Less than or equal to } - if (access === "All") { - if (!matchQuery.$and) { - matchQuery.$and = []; - } + switch (access) { // Searches for all questionnaires that are public, the user is an editor of, or the user created (all questionnaires the user has access to) - matchQuery.$and.push({ - $or: [ - { isPublic: true }, - { editors: { $in: [userId] } }, - { createdBy: userId }, - ], - }); - } - // Searches for all questionnaires the user can edit (all questionnaires the user is an editor of or the user created) - if (access === "Write") { - if (!matchQuery.$and) { - matchQuery.$and = []; - } - matchQuery.$and.push({ - $or: [{ editors: { $in: [userId] } }, { createdBy: userId }], - }); - } - // Searches for all questionnaires the user can view but not edit (all public questionnaires the user is not an editor of and did not create) - if (access === "Read") { - if (!matchQuery.$and) { - matchQuery.$and = []; - } - matchQuery.$and.push( - { - editors: { $nin: [userId] }, - }, - { createdBy: { $ne: userId } }, - { isPublic: true } - ); - } - // Searches for all non-public questionnaires the user can edit (all questionnaires the user is an editor of or the user created that are not public) - if (access === "PrivateQuestionnaires") { - if (!matchQuery.$and) { - matchQuery.$and = []; - } - matchQuery.$and.push({ - $or: [{ editors: { $in: [userId] } }, { createdBy: userId }], - isPublic: false, - }); + case "All": + matchQuery.$and = matchQuery.$and || []; + matchQuery.$and.push({ + $or: [ + { isPublic: true }, + { editors: { $in: [userId] } }, + { createdBy: userId }, + ], + }); + + break; + // Searches for all questionnaires the user can edit (all questionnaires the user is an editor of or the user created) + case "Write": + matchQuery.$and = matchQuery.$and || []; + matchQuery.$and.push({ + $or: [{ editors: { $in: [userId] } }, { createdBy: userId }], + }); + + break; + // Searches for all questionnaires the user can view but not edit (all public questionnaires the user is not an editor of and did not create) + case "Read": + matchQuery.$and = matchQuery.$and || []; + matchQuery.$and.push( + { + editors: { $nin: [userId] }, + }, + { createdBy: { $ne: userId } }, + { isPublic: true } + ); + + break; + // Searches for all non-public questionnaires the user can edit (all questionnaires the user is an editor of or the user created that are not public) + case "PrivateQuestionnaires": + matchQuery.$and = matchQuery.$and || []; + matchQuery.$and.push({ + $or: [{ editors: { $in: [userId] } }, { createdBy: userId }], + isPublic: false, + }); + + break; } // TODO: When "My questionnaires" feature is implemented, implement code to filter questionnaires based on questionnaires marked as "My questionnaires" From 1da97cfe455cffd9be73c325a292ac6b27f43ba2 Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 31 Jul 2024 15:59:58 +0100 Subject: [PATCH 45/61] Update default sort for listFilteredQuestionnaires to "createdDateDesc" --- .../db/datastore/datastore-mongodb.js | 2 +- .../db/datastore/datastore-mongodb.test.js | 150 ++++++++++-------- .../schema/resolvers/homepage/index.test.js | 15 +- 3 files changed, 90 insertions(+), 77 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index 0bf6c12339..026d05ef0e 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -401,7 +401,7 @@ const listFilteredQuestionnaires = async (input = {}, ctx) => { resultsPerPage = 10, firstQuestionnaireIdOnPage, lastQuestionnaireIdOnPage, - sortBy, + sortBy = "createdDateDesc", } = input; // Gets the questionnaires collection diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index d8a5b6c10c..805e859078 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -453,11 +453,12 @@ describe("MongoDB Datastore", () => { ); expect(listOfQuestionnaires.length).toBe(5); - expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); - expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); + // "Test questionnaire 6" is first as default sort is newest to oldest + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 6"); + expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 4"); expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 3"); - expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 4"); - expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 6"); + expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 2"); + expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 1"); }); it("Should return questionnaires with owner containing the `owner` search term", async () => { @@ -472,8 +473,8 @@ describe("MongoDB Datastore", () => { ); expect(listOfQuestionnaires.length).toBe(2); - expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 3"); - expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 4"); + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 4"); + expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 3"); }); it("Should return questionnaires created on or after the searched date", async () => { @@ -489,20 +490,23 @@ describe("MongoDB Datastore", () => { ); expect(listOfQuestionnaires.length).toBe(7); - expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 2"); - expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 3"); - expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 4"); - expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 6"); - // Questionnaires created in previous tests - expect(listOfQuestionnaires[4].title).toEqual( + /* + Questionnaires with titles "Default questionnaire title" are created in previous tests. + These appear first when sorted by newest to oldest as their `createdAt` dates are most recent. + */ + expect(listOfQuestionnaires[0].title).toEqual( "Default questionnaire title" ); - expect(listOfQuestionnaires[5].title).toEqual( + expect(listOfQuestionnaires[1].title).toEqual( "Default questionnaire title" ); - expect(listOfQuestionnaires[6].title).toEqual( + expect(listOfQuestionnaires[2].title).toEqual( "Default questionnaire title" ); + expect(listOfQuestionnaires[6].title).toEqual("Test questionnaire 2"); + expect(listOfQuestionnaires[5].title).toEqual("Test questionnaire 3"); + expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 4"); + expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 6"); }); it("Should return questionnaires created on or before the searched date", async () => { @@ -518,8 +522,8 @@ describe("MongoDB Datastore", () => { ); expect(listOfQuestionnaires.length).toBe(2); - expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); - expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 2"); + expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 1"); }); it("Should return questionnaires created between the searched dates", async () => { @@ -536,9 +540,9 @@ describe("MongoDB Datastore", () => { ); expect(listOfQuestionnaires.length).toBe(3); - expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 2"); + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 4"); expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 3"); - expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 4"); + expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 2"); }); it("Should return relevant questionnaires when searching by access `All`", async () => { @@ -553,21 +557,24 @@ describe("MongoDB Datastore", () => { ); expect(listOfQuestionnaires.length).toBe(8); - expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); - expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); - expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 3"); - expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 4"); - expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 6"); - // Questionnaires created in previous tests - expect(listOfQuestionnaires[5].title).toEqual( + /* + Questionnaires with titles "Default questionnaire title" are created in previous tests. + These appear first when sorted by newest to oldest as their `createdAt` dates are most recent. + */ + expect(listOfQuestionnaires[0].title).toEqual( "Default questionnaire title" ); - expect(listOfQuestionnaires[6].title).toEqual( + expect(listOfQuestionnaires[1].title).toEqual( "Default questionnaire title" ); - expect(listOfQuestionnaires[7].title).toEqual( + expect(listOfQuestionnaires[2].title).toEqual( "Default questionnaire title" ); + expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 6"); + expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 4"); + expect(listOfQuestionnaires[5].title).toEqual("Test questionnaire 3"); + expect(listOfQuestionnaires[6].title).toEqual("Test questionnaire 2"); + expect(listOfQuestionnaires[7].title).toEqual("Test questionnaire 1"); }); it("Should return relevant questionnaires when searching by access `Write`", async () => { @@ -583,10 +590,10 @@ describe("MongoDB Datastore", () => { // Expects all questionnaires where `ctx.user` is the owner (`ctx.user` created the questionnaire) or an editor expect(listOfQuestionnaires.length).toBe(4); - expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); // "user-1" created the questionnaire - expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); // "user-1" created the questionnaire - expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 3"); // "user-1" is an editor - expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 6"); // "user-1" created the questionnaire + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 6"); // "user-1" created the questionnaire + expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 3"); // "user-1" is an editor + expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 2"); // "user-1" created the questionnaire + expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 1"); // "user-1" created the questionnaire }); it("Should return relevant questionnaires when searching by access `Read`", async () => { @@ -601,17 +608,20 @@ describe("MongoDB Datastore", () => { ); expect(listOfQuestionnaires.length).toBe(4); - expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 4"); - // Questionnaires created in previous tests - expect(listOfQuestionnaires[1].title).toEqual( + /* + Questionnaires with titles "Default questionnaire title" are created in previous tests. + These appear first when sorted by newest to oldest as their `createdAt` dates are most recent. + */ + expect(listOfQuestionnaires[0].title).toEqual( "Default questionnaire title" ); - expect(listOfQuestionnaires[2].title).toEqual( + expect(listOfQuestionnaires[1].title).toEqual( "Default questionnaire title" ); - expect(listOfQuestionnaires[3].title).toEqual( + expect(listOfQuestionnaires[2].title).toEqual( "Default questionnaire title" ); + expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 4"); }); it("Should return relevant questionnaires when searching by access `PrivateQuestionnaires`", async () => { @@ -642,10 +652,10 @@ describe("MongoDB Datastore", () => { ); expect(listOfQuestionnaires.length).toBe(4); - expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); - expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); - expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 3"); - expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 6"); + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 6"); + expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 3"); + expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 2"); + expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 1"); }); it("Should return questionnaires on previous page when `firstQuestionnaireIdOnPage` is provided without `lastQuestionnaireIdOnPage`", async () => { @@ -667,7 +677,7 @@ describe("MongoDB Datastore", () => { owner: "", access: "All", resultsPerPage: 2, // Limits to 2 questionnaires per page to test a small number of questionnaires on previous page - firstQuestionnaireIdOnPage: allQuestionnaires[2].id, + firstQuestionnaireIdOnPage: allQuestionnaires[6].id, }, ctx ); @@ -675,10 +685,10 @@ describe("MongoDB Datastore", () => { expect(listOfPreviousPageQuestionnaires.length).toBe(2); // The two questionnaires before the first questionnaire on the page (based on firstQuestionnaireIdOnPage) expect(listOfPreviousPageQuestionnaires[0].title).toEqual( - "Test questionnaire 1" + "Test questionnaire 4" ); expect(listOfPreviousPageQuestionnaires[1].title).toEqual( - "Test questionnaire 2" + "Test questionnaire 3" ); }); @@ -709,10 +719,10 @@ describe("MongoDB Datastore", () => { expect(listOfNextPageQuestionnaires.length).toBe(2); // The two questionnaires after the last questionnaire on the page (based on lastQuestionnaireIdOnPage) expect(listOfNextPageQuestionnaires[0].title).toEqual( - "Test questionnaire 6" + "Test questionnaire 4" ); expect(listOfNextPageQuestionnaires[1].title).toEqual( - "Default questionnaire title" + "Test questionnaire 3" ); }); @@ -763,38 +773,40 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires).toEqual([]); }); - it("Should sort questionnaires on first page from newest to oldest when `sortBy` is `createdDateDesc`", async () => { + it("Should sort questionnaires on first page from oldest to newest when `sortBy` is `createdDateAsc`", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "", owner: "", access: "All", resultsPerPage: 10, - sortBy: "createdDateDesc", + sortBy: "createdDateAsc", }, ctx ); expect(listOfQuestionnaires.length).toBe(8); - // First created are questionnaires created in previous tests as all `Test questionnaires` have `createdAt` dates in the past - expect(listOfQuestionnaires[0].title).toEqual( + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); + expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); + expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 3"); + expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 4"); + expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 6"); + /* + Questionnaires with titles "Default questionnaire title" are created in previous tests. + These appear last when sorted by oldest to newest as their `createdAt` dates are most recent. + */ + expect(listOfQuestionnaires[5].title).toEqual( "Default questionnaire title" ); - expect(listOfQuestionnaires[1].title).toEqual( + expect(listOfQuestionnaires[6].title).toEqual( "Default questionnaire title" ); - expect(listOfQuestionnaires[2].title).toEqual( + expect(listOfQuestionnaires[7].title).toEqual( "Default questionnaire title" ); - // `Test questionnaire 6` has the most recent `createdAt` date of the `Test questionnaires` - expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 6"); - expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 4"); - expect(listOfQuestionnaires[5].title).toEqual("Test questionnaire 3"); - expect(listOfQuestionnaires[6].title).toEqual("Test questionnaire 2"); - expect(listOfQuestionnaires[7].title).toEqual("Test questionnaire 1"); }); - it("Should sort questionnaires on previous page from newest to oldest when `sortBy` is `createdDateDesc`", async () => { + it("Should sort questionnaires on previous page from oldest to newest when `sortBy` is `createdDateAsc`", async () => { // Gets questionnaires with "All" access to get a questionnaire ID to use as `firstQuestionnaireIdOnPage` const allQuestionnaires = await mongoDB.listFilteredQuestionnaires( { @@ -802,7 +814,7 @@ describe("MongoDB Datastore", () => { owner: "", access: "All", resultsPerPage: 10, - sortBy: "createdDateDesc", + sortBy: "createdDateAsc", }, ctx ); @@ -814,8 +826,8 @@ describe("MongoDB Datastore", () => { owner: "", access: "All", resultsPerPage: 2, - firstQuestionnaireIdOnPage: allQuestionnaires[6].id, - sortBy: "createdDateDesc", + firstQuestionnaireIdOnPage: allQuestionnaires[4].id, + sortBy: "createdDateAsc", }, ctx ); @@ -823,14 +835,14 @@ describe("MongoDB Datastore", () => { expect(listOfPreviousPageQuestionnaires.length).toBe(2); // The two questionnaires before the first questionnaire on the page (based on firstQuestionnaireIdOnPage) expect(listOfPreviousPageQuestionnaires[0].title).toEqual( - "Test questionnaire 4" + "Test questionnaire 3" ); expect(listOfPreviousPageQuestionnaires[1].title).toEqual( - "Test questionnaire 3" + "Test questionnaire 4" ); }); - it("Should sort questionnaires on next page from newest to oldest when `sortBy` is `createdDateDesc`", async () => { + it("Should sort questionnaires on next page from oldest to newest when `sortBy` is `createdDateAsc`", async () => { // Gets questionnaires with "All" access to get a questionnaire ID to use as `lastQuestionnaireIdOnPage` const allQuestionnaires = await mongoDB.listFilteredQuestionnaires( { @@ -838,7 +850,7 @@ describe("MongoDB Datastore", () => { owner: "", access: "All", resultsPerPage: 10, - sortBy: "createdDateDesc", + sortBy: "createdDateAsc", }, ctx ); @@ -850,8 +862,8 @@ describe("MongoDB Datastore", () => { owner: "", access: "All", resultsPerPage: 2, - lastQuestionnaireIdOnPage: allQuestionnaires[3].id, - sortBy: "createdDateDesc", + lastQuestionnaireIdOnPage: allQuestionnaires[1].id, + sortBy: "createdDateAsc", }, ctx ); @@ -859,10 +871,10 @@ describe("MongoDB Datastore", () => { expect(listOfNextPageQuestionnaires.length).toBe(2); // The two questionnaires after the last questionnaire on the page (based on lastQuestionnaireIdOnPage) expect(listOfNextPageQuestionnaires[0].title).toEqual( - "Test questionnaire 4" + "Test questionnaire 3" ); expect(listOfNextPageQuestionnaires[1].title).toEqual( - "Test questionnaire 3" + "Test questionnaire 4" ); }); }); diff --git a/eq-author-api/schema/resolvers/homepage/index.test.js b/eq-author-api/schema/resolvers/homepage/index.test.js index 7700cb6bfe..9cee8b13c6 100644 --- a/eq-author-api/schema/resolvers/homepage/index.test.js +++ b/eq-author-api/schema/resolvers/homepage/index.test.js @@ -49,21 +49,22 @@ describe("homepage", () => { const filteredQuestionnaires = await queryFilteredQuestionnaires(user); + // Sorted from newest created first to oldest created last as `listFilteredQuestionnaires` is sorted in this order by default expect(filteredQuestionnaires).toEqual([ expect.objectContaining({ - title: "Test Questionnaire 1", + title: "Test Questionnaire 11", }), expect.objectContaining({ - title: "Test Questionnaire 2", + title: "Test Questionnaire 10", }), expect.objectContaining({ title: "Test Questionnaire 3", }), expect.objectContaining({ - title: "Test Questionnaire 10", + title: "Test Questionnaire 2", }), expect.objectContaining({ - title: "Test Questionnaire 11", + title: "Test Questionnaire 1", }), ]); }); @@ -84,15 +85,15 @@ describe("homepage", () => { ); expect(filteredQuestionnaires).toEqual([ + // `filteredQuestionnaires` should contain `Test Questionnaire 11` and `Test Questionnaire 10` as these contain the string `Test Questionnaire 1` expect.objectContaining({ - title: "Test Questionnaire 1", + title: "Test Questionnaire 11", }), - // `filteredQuestionnaires` should contain `Test Questionnaire 10` and `Test Questionnaire 11` as these contain the string `Test Questionnaire 1` expect.objectContaining({ title: "Test Questionnaire 10", }), expect.objectContaining({ - title: "Test Questionnaire 11", + title: "Test Questionnaire 1", }), ]); // `filteredQuestionnaires` should not contain `Test Questionnaire 2` and `Test Questionnaire 3` as these do not contain the string `Test Questionnaire 1` From 2dad7110d73ada33bc44a2c70c9c623777f040b1 Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 1 Aug 2024 15:48:29 +0100 Subject: [PATCH 46/61] Separate Access from Permission --- eq-author-api/db/datastore/datastore-mongodb.js | 4 ++-- eq-author-api/db/datastore/datastore-mongodb.test.js | 8 ++++---- eq-author-api/schema/resolvers/homepage/index.test.js | 2 ++ eq-author-api/schema/typeDefs.js | 11 ++++++++--- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index 026d05ef0e..bf33ad327a 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -346,7 +346,7 @@ const getMatchQuery = async (input = {}, ctx) => { break; // Searches for all questionnaires the user can edit (all questionnaires the user is an editor of or the user created) - case "Write": + case "Editor": matchQuery.$and = matchQuery.$and || []; matchQuery.$and.push({ $or: [{ editors: { $in: [userId] } }, { createdBy: userId }], @@ -354,7 +354,7 @@ const getMatchQuery = async (input = {}, ctx) => { break; // Searches for all questionnaires the user can view but not edit (all public questionnaires the user is not an editor of and did not create) - case "Read": + case "ViewOnly": matchQuery.$and = matchQuery.$and || []; matchQuery.$and.push( { diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 805e859078..0fc5fa97e5 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -577,12 +577,12 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[7].title).toEqual("Test questionnaire 1"); }); - it("Should return relevant questionnaires when searching by access `Write`", async () => { + it("Should return relevant questionnaires when searching by access `Editor`", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "", owner: "", - access: "Write", + access: "Editor", resultsPerPage: 10, }, ctx @@ -596,12 +596,12 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 1"); // "user-1" created the questionnaire }); - it("Should return relevant questionnaires when searching by access `Read`", async () => { + it("Should return relevant questionnaires when searching by access `ViewOnly`", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "", owner: "", - access: "Read", + access: "ViewOnly", resultsPerPage: 10, }, ctx diff --git a/eq-author-api/schema/resolvers/homepage/index.test.js b/eq-author-api/schema/resolvers/homepage/index.test.js index 9cee8b13c6..1c7a3997ca 100644 --- a/eq-author-api/schema/resolvers/homepage/index.test.js +++ b/eq-author-api/schema/resolvers/homepage/index.test.js @@ -77,6 +77,7 @@ describe("homepage", () => { const input = { search: "Test Questionnaire 1", owner: "", + access: "All", }; const filteredQuestionnaires = await queryFilteredQuestionnaires( @@ -131,6 +132,7 @@ describe("homepage", () => { resultsPerPage: 2, search: "Test Questionnaire", owner: "", + access: "All", }; const totalPages = await queryTotalPages(user, input); diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index ead3db9fdb..c2094dd391 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -42,9 +42,14 @@ enum QuestionnaireType { } enum Permission { - All Read Write +} + +enum Access { + All + Editor + ViewOnly PrivateQuestionnaires } @@ -947,7 +952,7 @@ input FilteredQuestionnairesInput { owner: String createdOnOrAfter: DateTime createdOnOrBefore: DateTime - access: Permission + access: Access! myQuestionnaires: Boolean sortBy: String } @@ -958,7 +963,7 @@ input TotalPagesInput { owner: String createdOnOrAfter: DateTime createdOnOrBefore: DateTime - access: Permission + access: Access! myQuestionnaires: Boolean } From d98a4233c17a62532a2d676fa83da0810836d67f Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 2 Aug 2024 09:13:30 +0100 Subject: [PATCH 47/61] Update datastore error tests --- .../db/datastore/datastore-mongodb.test.js | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 0fc5fa97e5..6e6456c7dd 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -75,8 +75,26 @@ describe("MongoDB Datastore", () => { expect(() => mongoDB.listQuestionnaires()).not.toThrow(); }); - it("Should not throw error on listFilteredQuestionnaires", async () => { + it("Should log error message without throwing error on listFilteredQuestionnaires", async () => { expect(() => mongoDB.listFilteredQuestionnaires({}, ctx)).not.toThrow(); + expect(mockLoggerError).toHaveBeenCalledWith( + { + error: expect.any(String), + input: {}, + }, + "Unable to retrieve questionnaires (from listFilteredQuestionnaires)" + ); + }); + + it("Should log error message without throwing error on getTotalPages", async () => { + expect(() => mongoDB.getTotalPages({}, ctx)).not.toThrow(); + expect(mockLoggerError).toHaveBeenCalledWith( + { + error: expect.any(String), + input: {}, + }, + "Unable to get total pages (from getTotalPages)" + ); }); it("Should not throw error on createQuestionnaire", async () => { From a7e41a4ea181ac1178214dca3d39fda80a1e4265 Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 9 Aug 2024 08:44:36 +0100 Subject: [PATCH 48/61] Add search for questionnaire owner email --- .../db/datastore/datastore-mongodb.js | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index bf33ad327a..9ac22354dd 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -307,13 +307,22 @@ const getMatchQuery = async (input = {}, ctx) => { } const matchQuery = { - // Searches for questionnaires with `title` or `shortTitle` (short code) containing the search term - $or: [ - { title: { $regex: search, $options: "i" } }, - { shortTitle: { $regex: search, $options: "i" } }, + $and: [ + // Searches for questionnaires with `title` or `shortTitle` (short code) containing the search term + { + $or: [ + { title: { $regex: search, $options: "i" } }, + { shortTitle: { $regex: search, $options: "i" } }, + ], + }, + // Searches for questionnaires with owner name OR email containing the search term - email also handles owner name being null + { + $or: [ + { "owner.name": { $regex: owner, $options: "i" } }, + { "owner.email": { $regex: owner, $options: "i" } }, + ], + }, ], - // Searches for questionnaires with owner name (based on `createdBy`) containing the search term - "owner.name": { $regex: owner, $options: "i" }, }; // If both `createdOnOrAfter` and `createdOnOrBefore` are provided, searches for questionnaires created between `createdOnOrAfter` and `createdOnOrBefore` inclusive From 68c6afe4acb41ef2083d7d7c633abc478dc9b8f1 Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 9 Aug 2024 09:16:31 +0100 Subject: [PATCH 49/61] Add questionnaire created by user with null name to tests --- .../db/datastore/datastore-mongodb.test.js | 77 ++++++++++++------- 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 6e6456c7dd..90bde9628b 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -405,6 +405,14 @@ describe("MongoDB Datastore", () => { picture: "", }); + await mongoDB.createUser({ + id: "user-4", + email: "null@example.com", + name: null, // `name` is null to test questionnaires created by users with null `name` are returned + externalId: "user-4", + picture: "", + }); + await mongoDB.createQuestionnaire( mockQuestionnaire({ title: "Test questionnaire 1", @@ -457,6 +465,14 @@ describe("MongoDB Datastore", () => { }), ctx ); + await mongoDB.createQuestionnaire( + mockQuestionnaire({ + title: "Test questionnaire 7", + ownerId: "user-4", + createdAt: new Date(2021, 4, 10, 5, 0, 0, 0), + }), + ctx + ); }); it("Should return questionnaires with title containing the search term", async () => { @@ -470,13 +486,14 @@ describe("MongoDB Datastore", () => { ctx ); - expect(listOfQuestionnaires.length).toBe(5); - // "Test questionnaire 6" is first as default sort is newest to oldest - expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 6"); - expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 4"); - expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 3"); - expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 2"); - expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 1"); + expect(listOfQuestionnaires.length).toBe(6); + // "Test questionnaire 7" is first as default sort is newest to oldest + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 7"); + expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 6"); + expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 4"); + expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 3"); + expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 2"); + expect(listOfQuestionnaires[5].title).toEqual("Test questionnaire 1"); }); it("Should return questionnaires with owner containing the `owner` search term", async () => { @@ -507,7 +524,7 @@ describe("MongoDB Datastore", () => { ctx ); - expect(listOfQuestionnaires.length).toBe(7); + expect(listOfQuestionnaires.length).toBe(8); /* Questionnaires with titles "Default questionnaire title" are created in previous tests. These appear first when sorted by newest to oldest as their `createdAt` dates are most recent. @@ -521,10 +538,11 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[2].title).toEqual( "Default questionnaire title" ); - expect(listOfQuestionnaires[6].title).toEqual("Test questionnaire 2"); - expect(listOfQuestionnaires[5].title).toEqual("Test questionnaire 3"); - expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 4"); - expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 6"); + expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 7"); + expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 6"); + expect(listOfQuestionnaires[5].title).toEqual("Test questionnaire 4"); + expect(listOfQuestionnaires[6].title).toEqual("Test questionnaire 3"); + expect(listOfQuestionnaires[7].title).toEqual("Test questionnaire 2"); }); it("Should return questionnaires created on or before the searched date", async () => { @@ -574,7 +592,7 @@ describe("MongoDB Datastore", () => { ctx ); - expect(listOfQuestionnaires.length).toBe(8); + expect(listOfQuestionnaires.length).toBe(9); /* Questionnaires with titles "Default questionnaire title" are created in previous tests. These appear first when sorted by newest to oldest as their `createdAt` dates are most recent. @@ -588,11 +606,12 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[2].title).toEqual( "Default questionnaire title" ); - expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 6"); - expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 4"); - expect(listOfQuestionnaires[5].title).toEqual("Test questionnaire 3"); - expect(listOfQuestionnaires[6].title).toEqual("Test questionnaire 2"); - expect(listOfQuestionnaires[7].title).toEqual("Test questionnaire 1"); + expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 7"); + expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 6"); + expect(listOfQuestionnaires[5].title).toEqual("Test questionnaire 4"); + expect(listOfQuestionnaires[6].title).toEqual("Test questionnaire 3"); + expect(listOfQuestionnaires[7].title).toEqual("Test questionnaire 2"); + expect(listOfQuestionnaires[8].title).toEqual("Test questionnaire 1"); }); it("Should return relevant questionnaires when searching by access `Editor`", async () => { @@ -625,7 +644,7 @@ describe("MongoDB Datastore", () => { ctx ); - expect(listOfQuestionnaires.length).toBe(4); + expect(listOfQuestionnaires.length).toBe(5); /* Questionnaires with titles "Default questionnaire title" are created in previous tests. These appear first when sorted by newest to oldest as their `createdAt` dates are most recent. @@ -639,7 +658,8 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[2].title).toEqual( "Default questionnaire title" ); - expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 4"); + expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 7"); + expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 4"); }); it("Should return relevant questionnaires when searching by access `PrivateQuestionnaires`", async () => { @@ -703,10 +723,10 @@ describe("MongoDB Datastore", () => { expect(listOfPreviousPageQuestionnaires.length).toBe(2); // The two questionnaires before the first questionnaire on the page (based on firstQuestionnaireIdOnPage) expect(listOfPreviousPageQuestionnaires[0].title).toEqual( - "Test questionnaire 4" + "Test questionnaire 6" ); expect(listOfPreviousPageQuestionnaires[1].title).toEqual( - "Test questionnaire 3" + "Test questionnaire 4" ); }); @@ -737,10 +757,10 @@ describe("MongoDB Datastore", () => { expect(listOfNextPageQuestionnaires.length).toBe(2); // The two questionnaires after the last questionnaire on the page (based on lastQuestionnaireIdOnPage) expect(listOfNextPageQuestionnaires[0].title).toEqual( - "Test questionnaire 4" + "Test questionnaire 6" ); expect(listOfNextPageQuestionnaires[1].title).toEqual( - "Test questionnaire 3" + "Test questionnaire 4" ); }); @@ -803,25 +823,26 @@ describe("MongoDB Datastore", () => { ctx ); - expect(listOfQuestionnaires.length).toBe(8); + expect(listOfQuestionnaires.length).toBe(9); expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 3"); expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 4"); expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 6"); + expect(listOfQuestionnaires[5].title).toEqual("Test questionnaire 7"); /* Questionnaires with titles "Default questionnaire title" are created in previous tests. These appear last when sorted by oldest to newest as their `createdAt` dates are most recent. */ - expect(listOfQuestionnaires[5].title).toEqual( - "Default questionnaire title" - ); expect(listOfQuestionnaires[6].title).toEqual( "Default questionnaire title" ); expect(listOfQuestionnaires[7].title).toEqual( "Default questionnaire title" ); + expect(listOfQuestionnaires[8].title).toEqual( + "Default questionnaire title" + ); }); it("Should sort questionnaires on previous page from oldest to newest when `sortBy` is `createdDateAsc`", async () => { From 44fb53d3fc4b0d32e050d06aa44c7a18495d767f Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 9 Aug 2024 09:26:20 +0100 Subject: [PATCH 50/61] Add test for owner email --- .../db/datastore/datastore-mongodb.test.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 90bde9628b..d474c5865f 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -496,7 +496,7 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[5].title).toEqual("Test questionnaire 1"); }); - it("Should return questionnaires with owner containing the `owner` search term", async () => { + it("Should return questionnaires with owner name containing the `owner` search term", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "", @@ -512,6 +512,22 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 3"); }); + it("Should return questionnaires with owner email containing the `owner` search term", async () => { + const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "", + owner: "user2@example.com", + access: "All", + resultsPerPage: 10, + }, + ctx + ); + + expect(listOfQuestionnaires.length).toBe(2); + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 4"); + expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 3"); + }); + it("Should return questionnaires created on or after the searched date", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { From f5981069bdfd95ee0e0308f45ebec2c134ee5ea2 Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 9 Aug 2024 09:33:55 +0100 Subject: [PATCH 51/61] Add test for short title search --- .../db/datastore/datastore-mongodb.test.js | 16 ++++++++++++++++ eq-author-api/db/datastore/mock-questionnaire.js | 2 ++ 2 files changed, 18 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index d474c5865f..95fb274962 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -417,6 +417,7 @@ describe("MongoDB Datastore", () => { mockQuestionnaire({ title: "Test questionnaire 1", ownerId: "user-1", + shortTitle: "Alias 1", createdAt: new Date(2021, 2, 5, 5, 0, 0, 0), }), ctx @@ -496,6 +497,21 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[5].title).toEqual("Test questionnaire 1"); }); + it("Should return questionnaires with shortTitle containing the search term", async () => { + const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "Alias", + owner: "", + access: "All", + resultsPerPage: 10, + }, + ctx + ); + + expect(listOfQuestionnaires.length).toBe(1); + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); // "Test questionnaire 1" has `shortTitle` "Alias 1" - this `shortTitle` contains the search term + }); + it("Should return questionnaires with owner name containing the `owner` search term", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { diff --git a/eq-author-api/db/datastore/mock-questionnaire.js b/eq-author-api/db/datastore/mock-questionnaire.js index ffd7089bff..7099d850e8 100644 --- a/eq-author-api/db/datastore/mock-questionnaire.js +++ b/eq-author-api/db/datastore/mock-questionnaire.js @@ -6,9 +6,11 @@ const mockQuestionnaire = ({ createdAt, isPublic, editors, + shortTitle, }) => { const questionnaire = { title: title || "Default questionnaire title", + shortTitle, theme: "business", legalBasis: "Voluntary", navigation: false, From 7684d893c755c4a9d265de4f43de2ff9850dca15 Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 9 Aug 2024 09:55:39 +0100 Subject: [PATCH 52/61] Add test for multiple filters --- .../db/datastore/datastore-mongodb.test.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 95fb274962..522e205702 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -948,6 +948,26 @@ describe("MongoDB Datastore", () => { "Test questionnaire 4" ); }); + + it("Should return relevant questionnaires when searching with multiple filters", async () => { + const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( + { + search: "Test questionnaire", + owner: "Joe", + access: "Editor", + createdOnOrBefore: new Date(2021, 2, 15), + createdOnOrAfter: new Date(2021, 2, 5), + resultsPerPage: 10, + sortBy: "createdDateAsc", + }, + ctx + ); + + expect(listOfQuestionnaires.length).toBe(2); + // These questionnaires meet all of the filter conditions + expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); + expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 2"); + }); }); describe("Getting total page count", () => { From ca0f14d09402f8933259c4435672cb432273cdef Mon Sep 17 00:00:00 2001 From: farres1 Date: Mon, 12 Aug 2024 15:52:11 +0100 Subject: [PATCH 53/61] Rename homepage to questionnaires --- eq-author-api/schema/resolvers/index.js | 4 ++-- .../schema/resolvers/{homepage => questionnaires}/index.js | 0 .../resolvers/{homepage => questionnaires}/index.test.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename eq-author-api/schema/resolvers/{homepage => questionnaires}/index.js (100%) rename eq-author-api/schema/resolvers/{homepage => questionnaires}/index.test.js (97%) diff --git a/eq-author-api/schema/resolvers/index.js b/eq-author-api/schema/resolvers/index.js index c6a62adefd..e09458ef1b 100644 --- a/eq-author-api/schema/resolvers/index.js +++ b/eq-author-api/schema/resolvers/index.js @@ -4,7 +4,7 @@ const binaryExpression2 = require("./logic/binaryExpression2"); const page = require("./pages"); const questionnaireIntroduction = require("./questionnaireIntroduction"); const importing = require("./importing"); -const homepage = require("./homepage"); +const questionnaires = require("./questionnaires"); module.exports = [ base, @@ -13,5 +13,5 @@ module.exports = [ ...page, ...questionnaireIntroduction, importing, - homepage, + questionnaires, ]; diff --git a/eq-author-api/schema/resolvers/homepage/index.js b/eq-author-api/schema/resolvers/questionnaires/index.js similarity index 100% rename from eq-author-api/schema/resolvers/homepage/index.js rename to eq-author-api/schema/resolvers/questionnaires/index.js diff --git a/eq-author-api/schema/resolvers/homepage/index.test.js b/eq-author-api/schema/resolvers/questionnaires/index.test.js similarity index 97% rename from eq-author-api/schema/resolvers/homepage/index.test.js rename to eq-author-api/schema/resolvers/questionnaires/index.test.js index 1c7a3997ca..c35d48194a 100644 --- a/eq-author-api/schema/resolvers/homepage/index.test.js +++ b/eq-author-api/schema/resolvers/questionnaires/index.test.js @@ -4,11 +4,11 @@ const { const { queryFilteredQuestionnaires, queryTotalPages, -} = require("../../../tests/utils/contextBuilder/homepage"); +} = require("../../../tests/utils/contextBuilder/questionnaires"); const { buildContext } = require("../../../tests/utils/contextBuilder"); -describe("homepage", () => { +describe("questionnaires", () => { let ctx; beforeAll(async () => { From b9e5f8e3ef469e6aa8514349bded46e41649ac8f Mon Sep 17 00:00:00 2001 From: farres1 Date: Mon, 12 Aug 2024 16:18:30 +0100 Subject: [PATCH 54/61] Rename "homepage" to "questionnaires" for context builder --- .../utils/contextBuilder/{homepage => questionnaires}/index.js | 0 .../{homepage => questionnaires}/queryFilteredQuestionnaires.js | 0 .../{homepage => questionnaires}/queryTotalPages.js | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename eq-author-api/tests/utils/contextBuilder/{homepage => questionnaires}/index.js (100%) rename eq-author-api/tests/utils/contextBuilder/{homepage => questionnaires}/queryFilteredQuestionnaires.js (100%) rename eq-author-api/tests/utils/contextBuilder/{homepage => questionnaires}/queryTotalPages.js (100%) diff --git a/eq-author-api/tests/utils/contextBuilder/homepage/index.js b/eq-author-api/tests/utils/contextBuilder/questionnaires/index.js similarity index 100% rename from eq-author-api/tests/utils/contextBuilder/homepage/index.js rename to eq-author-api/tests/utils/contextBuilder/questionnaires/index.js diff --git a/eq-author-api/tests/utils/contextBuilder/homepage/queryFilteredQuestionnaires.js b/eq-author-api/tests/utils/contextBuilder/questionnaires/queryFilteredQuestionnaires.js similarity index 100% rename from eq-author-api/tests/utils/contextBuilder/homepage/queryFilteredQuestionnaires.js rename to eq-author-api/tests/utils/contextBuilder/questionnaires/queryFilteredQuestionnaires.js diff --git a/eq-author-api/tests/utils/contextBuilder/homepage/queryTotalPages.js b/eq-author-api/tests/utils/contextBuilder/questionnaires/queryTotalPages.js similarity index 100% rename from eq-author-api/tests/utils/contextBuilder/homepage/queryTotalPages.js rename to eq-author-api/tests/utils/contextBuilder/questionnaires/queryTotalPages.js From ed8a55acf0412c77df28cead355030ed3bb63b33 Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 13 Aug 2024 13:13:24 +0100 Subject: [PATCH 55/61] Update format to "should" in datastore tests --- .../db/datastore/datastore-mongodb.test.js | 162 +++++++++--------- 1 file changed, 81 insertions(+), 81 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 522e205702..c572604c3f 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -65,17 +65,17 @@ describe("MongoDB Datastore", () => { }); describe("Error handling for failed DB connection", () => { - it("Should throw error on connect to db", async () => { + it("should throw error on connect to db", async () => { expect(() => mongoDB.connectDB("BrokenConnectionString") ).rejects.toThrow(); }); - it("Should not throw error on listQuestionnaires", async () => { + it("should not throw error on listQuestionnaires", async () => { expect(() => mongoDB.listQuestionnaires()).not.toThrow(); }); - it("Should log error message without throwing error on listFilteredQuestionnaires", async () => { + it("should log error message without throwing error on listFilteredQuestionnaires", async () => { expect(() => mongoDB.listFilteredQuestionnaires({}, ctx)).not.toThrow(); expect(mockLoggerError).toHaveBeenCalledWith( { @@ -86,7 +86,7 @@ describe("MongoDB Datastore", () => { ); }); - it("Should log error message without throwing error on getTotalPages", async () => { + it("should log error message without throwing error on getTotalPages", async () => { expect(() => mongoDB.getTotalPages({}, ctx)).not.toThrow(); expect(mockLoggerError).toHaveBeenCalledWith( { @@ -97,68 +97,68 @@ describe("MongoDB Datastore", () => { ); }); - it("Should not throw error on createQuestionnaire", async () => { + it("should not throw error on createQuestionnaire", async () => { expect(() => mongoDB.createQuestionnaire(questionnaire, ctx) ).not.toThrow(); }); - it("Should not throw error on getQuestionnaire", async () => { + it("should not throw error on getQuestionnaire", async () => { expect(() => mongoDB.getQuestionnaire("567")).not.toThrow(); }); - it("Should not throw error on getQuestionnaireMetaById ", async () => { + it("should not throw error on getQuestionnaireMetaById ", async () => { expect(() => mongoDB.getQuestionnaireMetaById("567")).not.toThrow(); }); - it("Should not throw error on saveQuestionnaire", async () => { + it("should not throw error on saveQuestionnaire", async () => { expect(() => mongoDB.saveQuestionnaire(questionnaire)).not.toThrow(); }); - it("Should not throw error on saveMetadata", async () => { + it("should not throw error on saveMetadata", async () => { questionnaire.id = "567"; expect(() => mongoDB.saveMetadata(questionnaire)).not.toThrow(); }); - it("Should not throw error on deleteQuestionnaire", async () => { + it("should not throw error on deleteQuestionnaire", async () => { expect(() => mongoDB.deleteQuestionnaire("567")).not.toThrow(); }); - it("Should not throw error on listUsers", async () => { + it("should not throw error on listUsers", async () => { expect(() => mongoDB.listUsers()).not.toThrow(); }); - it("Should not throw error on createUser", async () => { + it("should not throw error on createUser", async () => { expect(() => mongoDB.createUser(user)).not.toThrow(); }); - it("Should not throw error on getUserByExternalId", async () => { + it("should not throw error on getUserByExternalId", async () => { expect(() => mongoDB.deleteQuestionnaire("567")).not.toThrow(); }); - it("Should not throw error on getUserById", async () => { + it("should not throw error on getUserById", async () => { expect(() => mongoDB.getUserById("567")).not.toThrow(); }); - it("Should not throw error on updateUser", async () => { + it("should not throw error on updateUser", async () => { expect(() => mongoDB.updateUser(user)).not.toThrow(); }); - it("Should not throw error on createComments", async () => { + it("should not throw error on createComments", async () => { expect(() => mongoDB.createComments("567")).not.toThrow(); }); - it("Should not throw error on saveComments", async () => { + it("should not throw error on saveComments", async () => { expect(() => mongoDB.saveComments({ questionnaireId: "567" }) ).not.toThrow(); }); - it("Should not throw error on getCommentsForQuestionnaire", async () => { + it("should not throw error on getCommentsForQuestionnaire", async () => { expect(() => mongoDB.getCommentsForQuestionnaire("567")).not.toThrow(); }); - it("Should not throw error on createHistoryEvent", async () => { + it("should not throw error on createHistoryEvent", async () => { expect(() => mongoDB.createHistoryEvent("567", {})).not.toThrow(); }); }); @@ -169,7 +169,7 @@ describe("MongoDB Datastore", () => { }); describe("Getting a list of questionnaires when empty", () => { - it("Should return an empty array if no questionnaires are found", async () => { + it("should return an empty array if no questionnaires are found", async () => { const listOfQuestionnaires = await mongoDB.listQuestionnaires(); expect(listOfQuestionnaires.length).toBe(0); expect(Array.isArray(listOfQuestionnaires)).toBeTruthy(); @@ -177,7 +177,7 @@ describe("MongoDB Datastore", () => { }); describe("Creating a questionnaire", () => { - it("Should give the questionnaire an ID if one is not given", async () => { + it("should give the questionnaire an ID if one is not given", async () => { const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; expect(questionnaire.id).toBeFalsy(); @@ -189,7 +189,7 @@ describe("MongoDB Datastore", () => { expect(questionnaireFromDb.id).toMatch(uuidRegex); }); - it("Should leave the questionnaire ID as is if one is given", async () => { + it("should leave the questionnaire ID as is if one is given", async () => { expect(questionnaire.id).toBeFalsy(); questionnaire.id = "123"; expect(questionnaire.id).toBeTruthy(); @@ -202,23 +202,23 @@ describe("MongoDB Datastore", () => { }); describe("Getting the latest questionnaire version", () => { - it("Should should handle when an ID is not provided", () => { + it("should should handle when an ID is not provided", () => { expect(() => mongoDB.getQuestionnaire()).not.toThrow(); }); - it("Should return null when it cannot find the questionnaire", async () => { + it("should return null when it cannot find the questionnaire", async () => { const questionnaireFromDb = await mongoDB.getQuestionnaire("567"); expect(questionnaireFromDb).toBeNull(); expect(mongoDB.getQuestionnaire("567")).resolves.toBeNull(); }); - it("Should transform Timestamps into JS Date objects", async () => { + it("should transform Timestamps into JS Date objects", async () => { const questionnaireFromDb = await mongoDB.getQuestionnaire("123"); expect(questionnaireFromDb.createdAt instanceof Date).toBeTruthy(); expect(questionnaireFromDb.updatedAt instanceof Date).toBeTruthy(); }); - it("Should get a questionnaire with missing section, metadata and editors", async () => { + it("should get a questionnaire with missing section, metadata and editors", async () => { delete questionnaire.sections; delete questionnaire.metadata; delete questionnaire.editors; @@ -230,11 +230,11 @@ describe("MongoDB Datastore", () => { }); describe("Getting the base questionnaire", () => { - it("Should handle when an ID is not provided", () => { + it("should handle when an ID is not provided", () => { expect(() => mongoDB.getQuestionnaireMetaById()).not.toThrow(); }); - it("Should return null when it cannot find the questionnaire", async () => { + it("should return null when it cannot find the questionnaire", async () => { const baseQuestionnaireFromDb = await mongoDB.getQuestionnaireMetaById( "567" ); @@ -242,7 +242,7 @@ describe("MongoDB Datastore", () => { expect(mongoDB.getQuestionnaire("567")).resolves.toBeNull(); }); - it("Should transform Timestamps into JS Data objects", async () => { + it("should transform Timestamps into JS Data objects", async () => { const baseQuestionnaireFromDb = await mongoDB.getQuestionnaireMetaById( "123" ); @@ -256,11 +256,11 @@ describe("MongoDB Datastore", () => { }); describe("Saving a questionnaire", () => { - it("Should handle when an ID cannot be found within the given questionnaire", () => { + it("should handle when an ID cannot be found within the given questionnaire", () => { expect(() => mongoDB.saveQuestionnaire(questionnaire)).not.toThrow(); }); - it("Should update the 'updatedAt' property", async () => { + it("should update the 'updatedAt' property", async () => { const updatedAt = new Date(); const savedQuestionnaire = await mongoDB.saveQuestionnaire({ id: "123", @@ -272,7 +272,7 @@ describe("MongoDB Datastore", () => { }); describe("Getting a list of questionnaires", () => { - it("Should transform Timestamps into JS Date objects", async () => { + it("should transform Timestamps into JS Date objects", async () => { const listOfQuestionnaires = await mongoDB.listQuestionnaires(); expect(listOfQuestionnaires[0].updatedAt instanceof Date).toBeTruthy(); @@ -281,13 +281,13 @@ describe("MongoDB Datastore", () => { }); describe("Deleting a questionnaire", () => { - it("Should handle when an ID has not been given", () => { + it("should handle when an ID has not been given", () => { expect(() => mongoDB.deleteQuestionnaire()).not.toThrow(); }); }); describe("Getting a list of users", () => { - it("Should return an empty array if no users are found", async () => { + it("should return an empty array if no users are found", async () => { const listOfUsers = await mongoDB.listUsers(); expect(listOfUsers.length).toBe(0); expect(Array.isArray(listOfUsers)).toBeTruthy(); @@ -295,14 +295,14 @@ describe("MongoDB Datastore", () => { }); describe("Creating a user", () => { - it("Should create a user with a provided id", async () => { + it("should create a user with a provided id", async () => { const userFromDb = await mongoDB.createUser(firstUser); expect(userFromDb.id).toBeTruthy(); expect(userFromDb.id).toMatch("999-999"); expect(userFromDb.updatedAt instanceof Date).toBeTruthy(); }); - it("Should give the user an ID if one is not given", async () => { + it("should give the user an ID if one is not given", async () => { const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; const userFromDb = await mongoDB.createUser(user); @@ -310,30 +310,30 @@ describe("MongoDB Datastore", () => { expect(userFromDb.id).toMatch(uuidRegex); }); - it("Should use the email as the users name if one is not given", async () => { + it("should use the email as the users name if one is not given", async () => { delete user.name; const userFromDb = await mongoDB.createUser(user); expect(userFromDb.name).toBeTruthy(); expect(userFromDb.name).toMatch(userFromDb.email); }); - it("Should handle any errors that may occur", () => { + it("should handle any errors that may occur", () => { delete user.email; expect(() => mongoDB.createUser(user)).not.toThrow(); }); }); describe("Getting a user by their external ID", () => { - it("Should handle when an ID is not provided", () => { + it("should handle when an ID is not provided", () => { expect(() => mongoDB.getUserByExternalId()).not.toThrow(); }); - it("Should return nothing if the user cannot be found", async () => { + it("should return nothing if the user cannot be found", async () => { const user = await mongoDB.getUserByExternalId("123"); expect(user).toBeUndefined(); }); - it("Should return the user from the externalId", async () => { + it("should return the user from the externalId", async () => { const userFromDb = await mongoDB.getUserByExternalId( firstUser.externalId ); @@ -342,31 +342,31 @@ describe("MongoDB Datastore", () => { }); describe("Getting a user by their ID", () => { - it("Should handle when an ID is not provided", () => { + it("should handle when an ID is not provided", () => { expect(() => mongoDB.getUserById()).not.toThrow(); }); - it("Should return nothing if the user cannot be found", async () => { + it("should return nothing if the user cannot be found", async () => { const user = await mongoDB.getUserById("123"); expect(user).toBeUndefined(); }); - it("Should return user from the ID", async () => { + it("should return user from the ID", async () => { const userFromDb = await mongoDB.getUserById(firstUser.id); expect(userFromDb.id).toBe(firstUser.id); }); }); describe("Getting a list of users 2", () => { - it("Should use the Firestore document ID as the ID for each user", async () => { + it("should use the Firestore document ID as the ID for each user", async () => { const usersFromDb = await mongoDB.listUsers(); expect(usersFromDb[0].id).toBe(firstUser.id); }); }); describe("Updating a user", () => { - it("Should handle not finding an ID within the given user object", () => { + it("should handle not finding an ID within the given user object", () => { expect(() => mongoDB.updateUser(user)).not.toThrow(); }); - it("Should return the updated user object", async () => { + it("should return the updated user object", async () => { const changedUser = { ...user, name: "Harry James Potter", @@ -476,7 +476,7 @@ describe("MongoDB Datastore", () => { ); }); - it("Should return questionnaires with title containing the search term", async () => { + it("should return questionnaires with title containing the search term", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "Test questionnaire", @@ -497,7 +497,7 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[5].title).toEqual("Test questionnaire 1"); }); - it("Should return questionnaires with shortTitle containing the search term", async () => { + it("should return questionnaires with shortTitle containing the search term", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "Alias", @@ -512,7 +512,7 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 1"); // "Test questionnaire 1" has `shortTitle` "Alias 1" - this `shortTitle` contains the search term }); - it("Should return questionnaires with owner name containing the `owner` search term", async () => { + it("should return questionnaires with owner name containing the `owner` search term", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "", @@ -528,7 +528,7 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 3"); }); - it("Should return questionnaires with owner email containing the `owner` search term", async () => { + it("should return questionnaires with owner email containing the `owner` search term", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "", @@ -544,7 +544,7 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 3"); }); - it("Should return questionnaires created on or after the searched date", async () => { + it("should return questionnaires created on or after the searched date", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "", @@ -577,7 +577,7 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[7].title).toEqual("Test questionnaire 2"); }); - it("Should return questionnaires created on or before the searched date", async () => { + it("should return questionnaires created on or before the searched date", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "", @@ -594,7 +594,7 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[1].title).toEqual("Test questionnaire 1"); }); - it("Should return questionnaires created between the searched dates", async () => { + it("should return questionnaires created between the searched dates", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "", @@ -613,7 +613,7 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[2].title).toEqual("Test questionnaire 2"); }); - it("Should return relevant questionnaires when searching by access `All`", async () => { + it("should return relevant questionnaires when searching by access `All`", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "", @@ -646,7 +646,7 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[8].title).toEqual("Test questionnaire 1"); }); - it("Should return relevant questionnaires when searching by access `Editor`", async () => { + it("should return relevant questionnaires when searching by access `Editor`", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "", @@ -665,7 +665,7 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 1"); // "user-1" created the questionnaire }); - it("Should return relevant questionnaires when searching by access `ViewOnly`", async () => { + it("should return relevant questionnaires when searching by access `ViewOnly`", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "", @@ -694,7 +694,7 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[4].title).toEqual("Test questionnaire 4"); }); - it("Should return relevant questionnaires when searching by access `PrivateQuestionnaires`", async () => { + it("should return relevant questionnaires when searching by access `PrivateQuestionnaires`", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "", @@ -709,7 +709,7 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[0].title).toEqual("Test questionnaire 6"); }); - it("Should return relevant questionnaires when `myQuestionnaires` is true", async () => { + it("should return relevant questionnaires when `myQuestionnaires` is true", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "", @@ -728,7 +728,7 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[3].title).toEqual("Test questionnaire 1"); }); - it("Should return questionnaires on previous page when `firstQuestionnaireIdOnPage` is provided without `lastQuestionnaireIdOnPage`", async () => { + it("should return questionnaires on previous page when `firstQuestionnaireIdOnPage` is provided without `lastQuestionnaireIdOnPage`", async () => { // Gets questionnaires with "All" access to get a questionnaire ID to use as `firstQuestionnaireIdOnPage` const allQuestionnaires = await mongoDB.listFilteredQuestionnaires( { @@ -762,7 +762,7 @@ describe("MongoDB Datastore", () => { ); }); - it("Should return questionnaires on next page when `lastQuestionnaireIdOnPage` is provided without `firstQuestionnaireIdOnPage`", async () => { + it("should return questionnaires on next page when `lastQuestionnaireIdOnPage` is provided without `firstQuestionnaireIdOnPage`", async () => { // Gets questionnaires with "All" access to get a questionnaire ID to use as `lastQuestionnaireIdOnPage` const allQuestionnaires = await mongoDB.listFilteredQuestionnaires( { @@ -796,7 +796,7 @@ describe("MongoDB Datastore", () => { ); }); - it("Should log an error message when both `firstQuestionnaireIdOnPage` and `lastQuestionnaireIdOnPage` are provided", async () => { + it("should log an error message when both `firstQuestionnaireIdOnPage` and `lastQuestionnaireIdOnPage` are provided", async () => { const listFilteredQuestionnairesInput = { search: "", owner: "", @@ -820,7 +820,7 @@ describe("MongoDB Datastore", () => { ); }); - it("Should log a debug message when no questionnaires are found", async () => { + it("should log a debug message when no questionnaires are found", async () => { const listFilteredQuestionnairesInput = { search: "Lorem ipsum", // Search term contained in no questionnaires owner: "", @@ -843,7 +843,7 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires).toEqual([]); }); - it("Should sort questionnaires on first page from oldest to newest when `sortBy` is `createdDateAsc`", async () => { + it("should sort questionnaires on first page from oldest to newest when `sortBy` is `createdDateAsc`", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "", @@ -877,7 +877,7 @@ describe("MongoDB Datastore", () => { ); }); - it("Should sort questionnaires on previous page from oldest to newest when `sortBy` is `createdDateAsc`", async () => { + it("should sort questionnaires on previous page from oldest to newest when `sortBy` is `createdDateAsc`", async () => { // Gets questionnaires with "All" access to get a questionnaire ID to use as `firstQuestionnaireIdOnPage` const allQuestionnaires = await mongoDB.listFilteredQuestionnaires( { @@ -913,7 +913,7 @@ describe("MongoDB Datastore", () => { ); }); - it("Should sort questionnaires on next page from oldest to newest when `sortBy` is `createdDateAsc`", async () => { + it("should sort questionnaires on next page from oldest to newest when `sortBy` is `createdDateAsc`", async () => { // Gets questionnaires with "All" access to get a questionnaire ID to use as `lastQuestionnaireIdOnPage` const allQuestionnaires = await mongoDB.listFilteredQuestionnaires( { @@ -949,7 +949,7 @@ describe("MongoDB Datastore", () => { ); }); - it("Should return relevant questionnaires when searching with multiple filters", async () => { + it("should return relevant questionnaires when searching with multiple filters", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { search: "Test questionnaire", @@ -971,7 +971,7 @@ describe("MongoDB Datastore", () => { }); describe("Getting total page count", () => { - it("Should get the total number of pages based on the number of questionnaires and results per page", async () => { + it("should get the total number of pages based on the number of questionnaires and results per page", async () => { const totalPageCount = await mongoDB.getTotalPages( { resultsPerPage: 3, // As 8 questionnaires should be returned (from previously created questionnaires), uses 3 questionnaires per page to test total page count is rounded up @@ -985,7 +985,7 @@ describe("MongoDB Datastore", () => { expect(totalPageCount).toBe(3); // (8 questionnaires) / (3 results per page) gives 3 total pages after rounding up }); - it("Should return 0 when no questionnaires are found", async () => { + it("should return 0 when no questionnaires are found", async () => { const totalPageCount = await mongoDB.getTotalPages( { resultsPerPage: 10, @@ -1000,7 +1000,7 @@ describe("MongoDB Datastore", () => { expect(totalPageCount).toBe(0); }); - it("Should log an error message on exception", async () => { + it("should log an error message on exception", async () => { await mongoDB.getTotalPages(); // No arguments to trigger exception // Two calls as `getMatchQuery` also throws an error due to no context object @@ -1034,15 +1034,15 @@ describe("MongoDB Datastore", () => { "He defeated the dark lord!" ); }); - it("Should handle when a qid has not been given", () => { + it("should handle when a qid has not been given", () => { expect(() => mongoDB.createHistoryEvent(null, mockHistoryEvent) ).not.toThrow(); }); - it("Should handle when an event has not been given", () => { + it("should handle when an event has not been given", () => { expect(() => mongoDB.createHistoryEvent("123", null)).not.toThrow(); }); - it("Should put the new history event at the front of the list", async () => { + it("should put the new history event at the front of the list", async () => { const questionnaireHistory = await mongoDB.createHistoryEvent( "123", mockHistoryEvent @@ -1053,11 +1053,11 @@ describe("MongoDB Datastore", () => { }); describe("Saving a base questionnaire", () => { - it("Should handle when an ID cannot be found within the given base questionnaire", async () => { + it("should handle when an ID cannot be found within the given base questionnaire", async () => { await expect(mongoDB.saveMetadata({})).rejects.toThrow(); }); - it("Should update the 'updatedAt' property", async () => { + it("should update the 'updatedAt' property", async () => { const baseQuestionnaire = await mongoDB.getQuestionnaireMetaById("123"); baseQuestionnaire.updatedAt = new Date(); const updatedBaseQuestionnaire = await mongoDB.saveMetadata( @@ -1071,10 +1071,10 @@ describe("MongoDB Datastore", () => { }); describe("Creating default comments", () => { - it("Should handle when a questionnaireId has not been given", () => { + it("should handle when a questionnaireId has not been given", () => { expect(() => mongoDB.createComments()).not.toThrow(); }); - it("Should return a default comments object", async () => { + it("should return a default comments object", async () => { const commentsFromDb = await mongoDB.createComments("123"); expect(commentsFromDb).toMatchObject({ comments: {}, @@ -1084,12 +1084,12 @@ describe("MongoDB Datastore", () => { }); describe("Saving a comment", () => { - it("Should handle a questionnaireId not being found within the given comments object", () => { + it("should handle a questionnaireId not being found within the given comments object", () => { expect(() => mongoDB.saveComments({ comments: [mockComment] }) ).not.toThrow(); }); - it("Should return the questionnaire comments object", async () => { + it("should return the questionnaire comments object", async () => { const mockCommentObj = { "123-456-789": [mockComment], }; @@ -1103,10 +1103,10 @@ describe("MongoDB Datastore", () => { }); describe("Getting the comments for a questionnaire", () => { - it("Should handle when a questionnareId has not been given", () => { + it("should handle when a questionnareId has not been given", () => { expect(() => mongoDB.getCommentsForQuestionnaire()).not.toThrow(); }); - it("Should transform Firestore Timestamps into JS Date objects", async () => { + it("should transform Firestore Timestamps into JS Date objects", async () => { const listOfComments = await mongoDB.getCommentsForQuestionnaire("123"); expect( listOfComments.comments["123-456-789"][0].createdTime instanceof Date From c9e5fcdea832a2e0961cb375a972829bf8953e0a Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 13 Aug 2024 17:10:11 +0100 Subject: [PATCH 56/61] Rename search to searchByTitleOrShortCode --- .../db/datastore/datastore-mongodb.js | 6 +- .../db/datastore/datastore-mongodb.test.js | 56 +++++++++---------- .../resolvers/questionnaires/index.test.js | 4 +- eq-author-api/schema/typeDefs.js | 4 +- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index 9ac22354dd..5bc70b17b0 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -293,7 +293,7 @@ const listQuestionnaires = async () => { const getMatchQuery = async (input = {}, ctx) => { try { const { - search = "", + searchByTitleOrShortCode = "", owner = "", createdOnOrAfter, createdOnOrBefore, @@ -311,8 +311,8 @@ const getMatchQuery = async (input = {}, ctx) => { // Searches for questionnaires with `title` or `shortTitle` (short code) containing the search term { $or: [ - { title: { $regex: search, $options: "i" } }, - { shortTitle: { $regex: search, $options: "i" } }, + { title: { $regex: searchByTitleOrShortCode, $options: "i" } }, + { shortTitle: { $regex: searchByTitleOrShortCode, $options: "i" } }, ], }, // Searches for questionnaires with owner name OR email containing the search term - email also handles owner name being null diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index c572604c3f..70fa523b68 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -476,10 +476,10 @@ describe("MongoDB Datastore", () => { ); }); - it("should return questionnaires with title containing the search term", async () => { + it("should return questionnaires with title containing the `searchByTitleOrShortCode` search term", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "Test questionnaire", + searchByTitleOrShortCode: "Test questionnaire", owner: "", access: "All", resultsPerPage: 10, @@ -497,10 +497,10 @@ describe("MongoDB Datastore", () => { expect(listOfQuestionnaires[5].title).toEqual("Test questionnaire 1"); }); - it("should return questionnaires with shortTitle containing the search term", async () => { + it("should return questionnaires with shortTitle containing the `searchByTitleOrShortCode` search term", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "Alias", + searchByTitleOrShortCode: "Alias", owner: "", access: "All", resultsPerPage: 10, @@ -515,7 +515,7 @@ describe("MongoDB Datastore", () => { it("should return questionnaires with owner name containing the `owner` search term", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "Jane", access: "All", resultsPerPage: 10, @@ -531,7 +531,7 @@ describe("MongoDB Datastore", () => { it("should return questionnaires with owner email containing the `owner` search term", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "user2@example.com", access: "All", resultsPerPage: 10, @@ -547,7 +547,7 @@ describe("MongoDB Datastore", () => { it("should return questionnaires created on or after the searched date", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "", createdOnOrAfter: new Date(2021, 2, 10), access: "All", @@ -580,7 +580,7 @@ describe("MongoDB Datastore", () => { it("should return questionnaires created on or before the searched date", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "", createdOnOrBefore: new Date(2021, 2, 10), access: "All", @@ -597,7 +597,7 @@ describe("MongoDB Datastore", () => { it("should return questionnaires created between the searched dates", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "", createdOnOrAfter: new Date(2021, 2, 10), createdOnOrBefore: new Date(2021, 2, 20), @@ -616,7 +616,7 @@ describe("MongoDB Datastore", () => { it("should return relevant questionnaires when searching by access `All`", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "", access: "All", resultsPerPage: 10, @@ -649,7 +649,7 @@ describe("MongoDB Datastore", () => { it("should return relevant questionnaires when searching by access `Editor`", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "", access: "Editor", resultsPerPage: 10, @@ -668,7 +668,7 @@ describe("MongoDB Datastore", () => { it("should return relevant questionnaires when searching by access `ViewOnly`", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "", access: "ViewOnly", resultsPerPage: 10, @@ -697,7 +697,7 @@ describe("MongoDB Datastore", () => { it("should return relevant questionnaires when searching by access `PrivateQuestionnaires`", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "", access: "PrivateQuestionnaires", resultsPerPage: 10, @@ -712,7 +712,7 @@ describe("MongoDB Datastore", () => { it("should return relevant questionnaires when `myQuestionnaires` is true", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "", access: "All", resultsPerPage: 10, @@ -732,7 +732,7 @@ describe("MongoDB Datastore", () => { // Gets questionnaires with "All" access to get a questionnaire ID to use as `firstQuestionnaireIdOnPage` const allQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "", access: "All", resultsPerPage: 10, @@ -743,7 +743,7 @@ describe("MongoDB Datastore", () => { const listOfPreviousPageQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "", access: "All", resultsPerPage: 2, // Limits to 2 questionnaires per page to test a small number of questionnaires on previous page @@ -766,7 +766,7 @@ describe("MongoDB Datastore", () => { // Gets questionnaires with "All" access to get a questionnaire ID to use as `lastQuestionnaireIdOnPage` const allQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "", access: "All", resultsPerPage: 10, @@ -777,7 +777,7 @@ describe("MongoDB Datastore", () => { const listOfNextPageQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "", access: "All", resultsPerPage: 2, // Limits to 2 questionnaires per page to test a small number of questionnaires on next page @@ -798,7 +798,7 @@ describe("MongoDB Datastore", () => { it("should log an error message when both `firstQuestionnaireIdOnPage` and `lastQuestionnaireIdOnPage` are provided", async () => { const listFilteredQuestionnairesInput = { - search: "", + searchByTitleOrShortCode: "", owner: "", access: "All", resultsPerPage: 10, @@ -822,7 +822,7 @@ describe("MongoDB Datastore", () => { it("should log a debug message when no questionnaires are found", async () => { const listFilteredQuestionnairesInput = { - search: "Lorem ipsum", // Search term contained in no questionnaires + searchByTitleOrShortCode: "Lorem ipsum", // Search term contained in no questionnaires owner: "", access: "All", resultsPerPage: 10, @@ -846,7 +846,7 @@ describe("MongoDB Datastore", () => { it("should sort questionnaires on first page from oldest to newest when `sortBy` is `createdDateAsc`", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "", access: "All", resultsPerPage: 10, @@ -881,7 +881,7 @@ describe("MongoDB Datastore", () => { // Gets questionnaires with "All" access to get a questionnaire ID to use as `firstQuestionnaireIdOnPage` const allQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "", access: "All", resultsPerPage: 10, @@ -893,7 +893,7 @@ describe("MongoDB Datastore", () => { const listOfPreviousPageQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "", access: "All", resultsPerPage: 2, @@ -917,7 +917,7 @@ describe("MongoDB Datastore", () => { // Gets questionnaires with "All" access to get a questionnaire ID to use as `lastQuestionnaireIdOnPage` const allQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "", access: "All", resultsPerPage: 10, @@ -929,7 +929,7 @@ describe("MongoDB Datastore", () => { const listOfNextPageQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "", + searchByTitleOrShortCode: "", owner: "", access: "All", resultsPerPage: 2, @@ -952,7 +952,7 @@ describe("MongoDB Datastore", () => { it("should return relevant questionnaires when searching with multiple filters", async () => { const listOfQuestionnaires = await mongoDB.listFilteredQuestionnaires( { - search: "Test questionnaire", + searchByTitleOrShortCode: "Test questionnaire", owner: "Joe", access: "Editor", createdOnOrBefore: new Date(2021, 2, 15), @@ -975,7 +975,7 @@ describe("MongoDB Datastore", () => { const totalPageCount = await mongoDB.getTotalPages( { resultsPerPage: 3, // As 8 questionnaires should be returned (from previously created questionnaires), uses 3 questionnaires per page to test total page count is rounded up - search: "", + searchByTitleOrShortCode: "", owner: "", access: "All", }, @@ -989,7 +989,7 @@ describe("MongoDB Datastore", () => { const totalPageCount = await mongoDB.getTotalPages( { resultsPerPage: 10, - search: "Lorem ipsum", // Search term contained in no questionnaires + searchByTitleOrShortCode: "Lorem ipsum", // Search term contained in no questionnaires owner: "", access: "All", }, diff --git a/eq-author-api/schema/resolvers/questionnaires/index.test.js b/eq-author-api/schema/resolvers/questionnaires/index.test.js index c35d48194a..99f40eb8a4 100644 --- a/eq-author-api/schema/resolvers/questionnaires/index.test.js +++ b/eq-author-api/schema/resolvers/questionnaires/index.test.js @@ -75,7 +75,7 @@ describe("questionnaires", () => { }; const input = { - search: "Test Questionnaire 1", + searchByTitleOrShortCode: "Test Questionnaire 1", owner: "", access: "All", }; @@ -130,7 +130,7 @@ describe("questionnaires", () => { const input = { resultsPerPage: 2, - search: "Test Questionnaire", + searchByTitleOrShortCode: "Test Questionnaire", owner: "", access: "All", }; diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index c2094dd391..93c4b0a1a1 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -948,7 +948,7 @@ input FilteredQuestionnairesInput { resultsPerPage: Int firstQuestionnaireIdOnPage: ID lastQuestionnaireIdOnPage: ID - search: String + searchByTitleOrShortCode: String owner: String createdOnOrAfter: DateTime createdOnOrBefore: DateTime @@ -959,7 +959,7 @@ input FilteredQuestionnairesInput { input TotalPagesInput { resultsPerPage: Int - search: String + searchByTitleOrShortCode: String owner: String createdOnOrAfter: DateTime createdOnOrBefore: DateTime From 93da657b52cd494f2ee2ae522af9d619e87b890a Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 13 Aug 2024 17:45:16 +0100 Subject: [PATCH 57/61] Add total filtered questionnaires --- .../db/datastore/datastore-mongodb.js | 24 ++++++++++++++++--- .../schema/resolvers/questionnaires/index.js | 10 ++++++++ eq-author-api/schema/typeDefs.js | 10 ++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.js b/eq-author-api/db/datastore/datastore-mongodb.js index 5bc70b17b0..7297802a82 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.js +++ b/eq-author-api/db/datastore/datastore-mongodb.js @@ -588,10 +588,8 @@ const listFilteredQuestionnaires = async (input = {}, ctx) => { } }; -const getTotalPages = async (input = {}, ctx) => { +const getTotalFilteredQuestionnaires = async (input = {}, ctx) => { try { - const { resultsPerPage = 10 } = input; - // Gets the questionnaires collection const questionnairesCollection = dbo.collection("questionnaires"); @@ -620,6 +618,25 @@ const getTotalPages = async (input = {}, ctx) => { // Sets default `totalFilteredQuestionnaires` to 0 if no questionnaires are returned - prevents error when destructuring `totalFilteredQuestionnaires` with no results const { totalFilteredQuestionnaires = 0 } = aggregationResult || {}; + return totalFilteredQuestionnaires; + } catch (error) { + logger.error( + { error: error.stack, input }, + "Unable to get total filtered questionnaires (from getTotalFilteredQuestionnaires)" + ); + return; + } +}; + +const getTotalPages = async (input = {}, ctx) => { + try { + const { resultsPerPage = 10 } = input; + + const totalFilteredQuestionnaires = await getTotalFilteredQuestionnaires( + input, + ctx + ); + // Calculates the total number of pages by dividing the total number of filtered questionnaires by the number of results per page, and rounding up const totalPages = Math.ceil(totalFilteredQuestionnaires / resultsPerPage); @@ -830,6 +847,7 @@ module.exports = { deleteQuestionnaire, listQuestionnaires, listFilteredQuestionnaires, + getTotalFilteredQuestionnaires, getTotalPages, getQuestionnaire, getQuestionnaireMetaById, diff --git a/eq-author-api/schema/resolvers/questionnaires/index.js b/eq-author-api/schema/resolvers/questionnaires/index.js index 72d8d27497..67fd6ac272 100644 --- a/eq-author-api/schema/resolvers/questionnaires/index.js +++ b/eq-author-api/schema/resolvers/questionnaires/index.js @@ -1,5 +1,6 @@ const { listFilteredQuestionnaires, + getTotalFilteredQuestionnaires, getTotalPages, } = require("../../../db/datastore"); @@ -11,6 +12,15 @@ const Resolvers = { return questionnaires; }, + totalFilteredQuestionnaires: async (_, { input }, ctx) => { + const totalFilteredQuestionnaires = await getTotalFilteredQuestionnaires( + input, + ctx + ); + + return totalFilteredQuestionnaires; + }, + totalPages: async (_, { input }, ctx) => { const totalPages = await getTotalPages(input, ctx); diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index 93c4b0a1a1..81c410b989 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -929,6 +929,7 @@ type Query { listNames: [ListName] collectionListNames: [ListName] supplementaryDataListNames: [ListName] + totalFilteredQuestionnaires(input: TotalFilteredQuestionnairesInput): Int totalPages(input: TotalPagesInput): Int } @@ -957,6 +958,15 @@ input FilteredQuestionnairesInput { sortBy: String } +input TotalFilteredQuestionnairesInput { + searchByTitleOrShortCode: String + owner: String + createdOnOrAfter: DateTime + createdOnOrBefore: DateTime + access: Access! + myQuestionnaires: Boolean +} + input TotalPagesInput { resultsPerPage: Int searchByTitleOrShortCode: String From 6a8c3237ed24ef0c32e63c9b05a3daadbcbc512f Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 14 Aug 2024 13:02:06 +0100 Subject: [PATCH 58/61] Fix failing tests --- eq-author-api/db/datastore/datastore-mongodb.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 70fa523b68..608a600fc3 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -93,7 +93,7 @@ describe("MongoDB Datastore", () => { error: expect.any(String), input: {}, }, - "Unable to get total pages (from getTotalPages)" + "Unable to get total filtered questionnaires (from getTotalFilteredQuestionnaires)" // The error from `getTotalFilteredQuestionnaires` is triggered by `getTotalPages` ); }); @@ -1017,7 +1017,7 @@ describe("MongoDB Datastore", () => { input: {}, error: expect.any(String), }, - "Unable to get total pages (from getTotalPages)" + "Unable to get total filtered questionnaires (from getTotalFilteredQuestionnaires)" // The error from `getTotalFilteredQuestionnaires` is triggered by `getTotalPages` ); }); }); From 2bec23b5e80ee70e3e4a594b9d506d5cc29609d8 Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 15 Aug 2024 11:47:10 +0100 Subject: [PATCH 59/61] Add datastore tests for total filtered questionnaires --- .../db/datastore/datastore-mongodb.test.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 608a600fc3..89fdc3988a 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -970,6 +970,41 @@ describe("MongoDB Datastore", () => { }); }); + describe("Getting total filtered questionnaires", () => { + it("should get the total number of questionnaires with no filters applied", async () => { + const totalFilteredQuestionnaires = + await mongoDB.getTotalFilteredQuestionnaires( + { + searchByTitleOrShortCode: "", + owner: "", + access: "All", + resultsPerPage: 2, // Limits to 2 questionnaires per page to test the total includes questionnaires on other pages + }, + ctx + ); + + expect(totalFilteredQuestionnaires).toBe(9); // 9 questionnaires created in previous tests + }); + + it("should get the total number of questionnaires with filters applied", async () => { + const totalFilteredQuestionnaires = + await mongoDB.getTotalFilteredQuestionnaires( + { + searchByTitleOrShortCode: "Test questionnaire", + owner: "Joe", + access: "Editor", + createdOnOrBefore: new Date(2021, 2, 15), + createdOnOrAfter: new Date(2021, 2, 5), + resultsPerPage: 1, // Limits to 1 questionnaire per page to test the total includes questionnaires on other pages + sortBy: "createdDateAsc", + }, + ctx + ); + + expect(totalFilteredQuestionnaires).toBe(2); + }); + }); + describe("Getting total page count", () => { it("should get the total number of pages based on the number of questionnaires and results per page", async () => { const totalPageCount = await mongoDB.getTotalPages( From 90a6c0b9be49801f9b366d1e072fce437527e45f Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 15 Aug 2024 11:51:03 +0100 Subject: [PATCH 60/61] Replace numbers for testing total page count --- eq-author-api/db/datastore/datastore-mongodb.test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/eq-author-api/db/datastore/datastore-mongodb.test.js b/eq-author-api/db/datastore/datastore-mongodb.test.js index 89fdc3988a..eb23f7c130 100644 --- a/eq-author-api/db/datastore/datastore-mongodb.test.js +++ b/eq-author-api/db/datastore/datastore-mongodb.test.js @@ -978,7 +978,6 @@ describe("MongoDB Datastore", () => { searchByTitleOrShortCode: "", owner: "", access: "All", - resultsPerPage: 2, // Limits to 2 questionnaires per page to test the total includes questionnaires on other pages }, ctx ); @@ -995,7 +994,6 @@ describe("MongoDB Datastore", () => { access: "Editor", createdOnOrBefore: new Date(2021, 2, 15), createdOnOrAfter: new Date(2021, 2, 5), - resultsPerPage: 1, // Limits to 1 questionnaire per page to test the total includes questionnaires on other pages sortBy: "createdDateAsc", }, ctx @@ -1009,7 +1007,7 @@ describe("MongoDB Datastore", () => { it("should get the total number of pages based on the number of questionnaires and results per page", async () => { const totalPageCount = await mongoDB.getTotalPages( { - resultsPerPage: 3, // As 8 questionnaires should be returned (from previously created questionnaires), uses 3 questionnaires per page to test total page count is rounded up + resultsPerPage: 2, // As 9 questionnaires should be returned (from previously created questionnaires), uses 2 questionnaires per page to test total page count is rounded up searchByTitleOrShortCode: "", owner: "", access: "All", @@ -1017,7 +1015,7 @@ describe("MongoDB Datastore", () => { ctx ); - expect(totalPageCount).toBe(3); // (8 questionnaires) / (3 results per page) gives 3 total pages after rounding up + expect(totalPageCount).toBe(5); // (9 questionnaires) / (2 results per page) gives 5 total pages after rounding up }); it("should return 0 when no questionnaires are found", async () => { From 79b5fd3c5b77f98e07321002b0f51fa6b2b2f02e Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 15 Aug 2024 12:16:21 +0100 Subject: [PATCH 61/61] Add totalFilteredQuestionnaires resolver test --- .../resolvers/questionnaires/index.test.js | 34 ++++++++++++++++++- .../contextBuilder/questionnaires/index.js | 1 + .../queryTotalFilteredQuestionnaires.js | 24 +++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 eq-author-api/tests/utils/contextBuilder/questionnaires/queryTotalFilteredQuestionnaires.js diff --git a/eq-author-api/schema/resolvers/questionnaires/index.test.js b/eq-author-api/schema/resolvers/questionnaires/index.test.js index 99f40eb8a4..a8a0bf8228 100644 --- a/eq-author-api/schema/resolvers/questionnaires/index.test.js +++ b/eq-author-api/schema/resolvers/questionnaires/index.test.js @@ -3,6 +3,7 @@ const { } = require("../../../tests/utils/contextBuilder/questionnaire"); const { queryFilteredQuestionnaires, + queryTotalFilteredQuestionnaires, queryTotalPages, } = require("../../../tests/utils/contextBuilder/questionnaires"); @@ -111,6 +112,37 @@ describe("questionnaires", () => { }); }); + describe("totalFilteredQuestionnaires", () => { + it("should return total questionnaires when input is not provided", async () => { + const user = { + id: "user-1", + }; + + const totalFilteredQuestionnaires = + await queryTotalFilteredQuestionnaires(user); + + expect(totalFilteredQuestionnaires).toEqual(5); + }); + + it("should return total filtered questionnaires", async () => { + const user = { + id: "user-1", + }; + + const input = { + searchByTitleOrShortCode: "Test Questionnaire 1", + owner: "", + access: "All", + }; + + const totalFilteredQuestionnaires = + await queryTotalFilteredQuestionnaires(user, input); + + // `totalFilteredQuestionnaires` should be 3 as there are 3 questionnaires containing the string `Test Questionnaire 1` + expect(totalFilteredQuestionnaires).toEqual(3); + }); + }); + describe("totalPages", () => { it("should return total pages for all questionnaires when input is not provided", async () => { const user = { @@ -137,7 +169,7 @@ describe("questionnaires", () => { const totalPages = await queryTotalPages(user, input); - // `totalPages` should be 3 - (5 questionnaires) / (2 resultsPerPage) gives 3 total pages after rounding up + // `totalPages` should be 3: (5 questionnaires) / (2 resultsPerPage) gives 3 total pages after rounding up expect(totalPages).toEqual(3); }); }); diff --git a/eq-author-api/tests/utils/contextBuilder/questionnaires/index.js b/eq-author-api/tests/utils/contextBuilder/questionnaires/index.js index b8c2601a13..76bbb7350f 100644 --- a/eq-author-api/tests/utils/contextBuilder/questionnaires/index.js +++ b/eq-author-api/tests/utils/contextBuilder/questionnaires/index.js @@ -1,4 +1,5 @@ module.exports = { ...require("./queryFilteredQuestionnaires"), ...require("./queryTotalPages"), + ...require("./queryTotalFilteredQuestionnaires"), }; diff --git a/eq-author-api/tests/utils/contextBuilder/questionnaires/queryTotalFilteredQuestionnaires.js b/eq-author-api/tests/utils/contextBuilder/questionnaires/queryTotalFilteredQuestionnaires.js new file mode 100644 index 0000000000..af348dacb9 --- /dev/null +++ b/eq-author-api/tests/utils/contextBuilder/questionnaires/queryTotalFilteredQuestionnaires.js @@ -0,0 +1,24 @@ +const executeQuery = require("../../executeQuery"); + +const getTotalFilteredQuestionnairesQuery = ` + query GetTotalFilteredQuestionnaires($input: TotalFilteredQuestionnairesInput) { + totalFilteredQuestionnaires(input: $input) + } +`; + +const queryTotalFilteredQuestionnaires = async (user, input) => { + const result = await executeQuery( + getTotalFilteredQuestionnairesQuery, + input ? { input } : undefined, // Passes `undefined` when `input` is falsy to test when `input` is not provided + { + user, + } + ); + + return result.data.totalFilteredQuestionnaires; +}; + +module.exports = { + getTotalFilteredQuestionnairesQuery, + queryTotalFilteredQuestionnaires, +};