From a16483486dd65bafb7cb0129262f6ce5f44e2835 Mon Sep 17 00:00:00 2001 From: Tai Kamilla Date: Mon, 4 Sep 2023 16:09:50 +0200 Subject: [PATCH 01/26] mock data --- src/schema/query/index.ts | 2 ++ src/schema/query/people.query.ts | 42 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/schema/query/people.query.ts diff --git a/src/schema/query/index.ts b/src/schema/query/index.ts index 6b0fc5925..12d78d29b 100644 --- a/src/schema/query/index.ts +++ b/src/schema/query/index.ts @@ -10,6 +10,7 @@ import recordsAggregation from './recordsAggregation.query'; import dashboards from './dashboards.query'; import dashboard from './dashboard.query'; import users from './users.query'; +import people from './people.query'; import me from './me.query'; import role from './role.query'; import roles from './roles.query'; @@ -72,6 +73,7 @@ const Query = new GraphQLObjectType({ steps, user, users, + people, workflow, workflows, positionAttributes, diff --git a/src/schema/query/people.query.ts b/src/schema/query/people.query.ts new file mode 100644 index 000000000..8ea7e8a45 --- /dev/null +++ b/src/schema/query/people.query.ts @@ -0,0 +1,42 @@ +import { GraphQLList, GraphQLError, GraphQLID } from 'graphql'; +import { UserType } from '../types'; +import { logger } from '@services/logger.service'; + +/** + * People mock query + */ +export default { + type: new GraphQLList(UserType), //this should be PersonType + args: { + applications: { type: GraphQLList(GraphQLID) }, + }, + resolve(parent, args, context) { + try { + // return a test user + return [ + { + _id: '5f9a9b9b9b9b9b9b9b9b9b9b', + username: 'John Doe', + name: 'John Doe', + email: 'JohnDoe@test.com', + oid: '5f9a9b9b9b9b9b9b9b9b9b9b', + }, + { + _id: '6f9a9b9b9b9b9b9b9b9b9b9b', + username: 'Jane Doe', + name: 'Jane Doe', + email: 'JaneDoe@test.com', + oid: '6f9a9b9b9b9b9b9b9b9b9b9b', + }, + ]; + } catch (err) { + logger.error(err.message, { stack: err.stack }); + if (err instanceof GraphQLError) { + throw new GraphQLError(err.message); + } + throw new GraphQLError( + context.i18next.t('common.errors.internalServerError') + ); + } + }, +}; From 3647e3a3879972931ff952929eeb8bf60351abe9 Mon Sep 17 00:00:00 2001 From: MwanPygmay Date: Tue, 26 Sep 2023 12:28:40 +0200 Subject: [PATCH 02/26] type not supported anymore, changed --- src/schema/query/people.query.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schema/query/people.query.ts b/src/schema/query/people.query.ts index 8ea7e8a45..08c6bc07a 100644 --- a/src/schema/query/people.query.ts +++ b/src/schema/query/people.query.ts @@ -8,7 +8,7 @@ import { logger } from '@services/logger.service'; export default { type: new GraphQLList(UserType), //this should be PersonType args: { - applications: { type: GraphQLList(GraphQLID) }, + applications: { type: new GraphQLList(GraphQLID) }, }, resolve(parent, args, context) { try { From d93b78b016adfdee9a0b87de7c94dc1d8070d70e Mon Sep 17 00:00:00 2001 From: MwanPygmay Date: Wed, 27 Sep 2023 09:49:16 +0200 Subject: [PATCH 03/26] add support for people question: get type and metadata correctly, aligned on the users question --- src/schema/query/people.query.ts | 35 ++++++++++--------- src/utils/files/getRowsFromMeta.ts | 1 + src/utils/form/getFieldType.ts | 2 ++ src/utils/form/metadata.helper.ts | 1 + .../schema/introspection/getFieldType.ts | 3 ++ .../resolvers/Meta/getMetaFieldResolver.ts | 4 +++ .../resolvers/Meta/getMetaPeopleResolver.ts | 23 ++++++++++++ 7 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts diff --git a/src/schema/query/people.query.ts b/src/schema/query/people.query.ts index 08c6bc07a..2e7110dd4 100644 --- a/src/schema/query/people.query.ts +++ b/src/schema/query/people.query.ts @@ -2,6 +2,24 @@ import { GraphQLList, GraphQLError, GraphQLID } from 'graphql'; import { UserType } from '../types'; import { logger } from '@services/logger.service'; +/** Temporary test mocked people awaiting for real data */ +export const MockedPeople = [ + { + _id: '5f9a9b9b9b9b9b9b9b9b9b9b', + username: 'John Doe', + name: 'John Doe', + email: 'JohnDoe@test.com', + oid: '5f9a9b9b9b9b9b9b9b9b9b9b', + }, + { + _id: '6f9a9b9b9b9b9b9b9b9b9b9b', + username: 'Jane Doe', + name: 'Jane Doe', + email: 'JaneDoe@test.com', + oid: '6f9a9b9b9b9b9b9b9b9b9b9b', + }, +]; + /** * People mock query */ @@ -13,22 +31,7 @@ export default { resolve(parent, args, context) { try { // return a test user - return [ - { - _id: '5f9a9b9b9b9b9b9b9b9b9b9b', - username: 'John Doe', - name: 'John Doe', - email: 'JohnDoe@test.com', - oid: '5f9a9b9b9b9b9b9b9b9b9b9b', - }, - { - _id: '6f9a9b9b9b9b9b9b9b9b9b9b', - username: 'Jane Doe', - name: 'Jane Doe', - email: 'JaneDoe@test.com', - oid: '6f9a9b9b9b9b9b9b9b9b9b9b', - }, - ]; + return MockedPeople; } catch (err) { logger.error(err.message, { stack: err.stack }); if (err instanceof GraphQLError) { diff --git a/src/utils/files/getRowsFromMeta.ts b/src/utils/files/getRowsFromMeta.ts index 8000e0840..877df12b5 100644 --- a/src/utils/files/getRowsFromMeta.ts +++ b/src/utils/files/getRowsFromMeta.ts @@ -66,6 +66,7 @@ export const getRowsFromMeta = (columns: any[], records: any[]): any[] => { set(row, column.name, Array.isArray(value) ? value.join(',') : value); break; } + case 'people': case 'users': { let value: any = get(data, column.field); const choices = column.meta.field.choices || []; diff --git a/src/utils/form/getFieldType.ts b/src/utils/form/getFieldType.ts index eadef92ea..509a325d6 100644 --- a/src/utils/form/getFieldType.ts +++ b/src/utils/form/getFieldType.ts @@ -79,6 +79,8 @@ export const getFieldType = async (question: { return 'tagbox'; case 'users': return 'users'; + case 'people': + return 'people'; case 'owner': return 'owner'; case 'geospatial': diff --git a/src/utils/form/metadata.helper.ts b/src/utils/form/metadata.helper.ts index d77b8caca..784d64625 100644 --- a/src/utils/form/metadata.helper.ts +++ b/src/utils/form/metadata.helper.ts @@ -316,6 +316,7 @@ export const getMetaData = async ( }; break; } + case 'people': case 'users': { fieldMeta.editor = 'select'; fieldMeta.multiSelect = true; diff --git a/src/utils/schema/introspection/getFieldType.ts b/src/utils/schema/introspection/getFieldType.ts index ab48c05a2..b83d50ec0 100644 --- a/src/utils/schema/introspection/getFieldType.ts +++ b/src/utils/schema/introspection/getFieldType.ts @@ -119,6 +119,9 @@ const getFieldType = ( case 'users': { return GraphQLJSON; } + case 'people': { + return GraphQLJSON; + } case 'owner': { return GraphQLJSON; } diff --git a/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts b/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts index 94e3b19c5..7d94e5bcc 100644 --- a/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts +++ b/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts @@ -1,6 +1,7 @@ import getMetaCheckboxResolver from './getMetaCheckboxResolver'; import getMetaDropdownResolver from './getMetaDropdownResolver'; import getMetaOwnerResolver from './getMetaOwnerResolver'; +import getMetaPeopleResolver from './getMetaPeopleResolver'; import getMetaUsersResolver from './getMetaUsersResolver'; import getMetaRadioResolver from './getMetaRadiogroupResolver'; import getMetaTagboxResolver from './getMetaTagboxResolver'; @@ -25,6 +26,9 @@ const getMetaFieldResolver = (field: any) => { case 'tagbox': { return getMetaTagboxResolver(field); } + case 'people': { + return getMetaPeopleResolver(field); + } case 'users': { return getMetaUsersResolver(field); } diff --git a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts new file mode 100644 index 000000000..f278d86e2 --- /dev/null +++ b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts @@ -0,0 +1,23 @@ +import { MockedPeople } from '@schema/query/people.query'; + +/** + * Return people meta resolver. + * + * @param field field definition. + * @returns people resolver. + */ +const getMetaUsersResolver = async (field: any) => { + const people = MockedPeople; + return Object.assign(field, { + choices: people + ? people.map((x) => { + return { + text: x.username, + value: x._id, + }; + }) + : [], + }); +}; + +export default getMetaUsersResolver; From 189c5a144b97d6c029699371fd8d9ab9265e66d8 Mon Sep 17 00:00:00 2001 From: Tai Kamilla Date: Thu, 5 Oct 2023 11:44:19 +0200 Subject: [PATCH 04/26] type --- src/schema/query/people.query.ts | 16 ++++++++-------- src/schema/types/people.type.ts | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 src/schema/types/people.type.ts diff --git a/src/schema/query/people.query.ts b/src/schema/query/people.query.ts index 2e7110dd4..61918bafb 100644 --- a/src/schema/query/people.query.ts +++ b/src/schema/query/people.query.ts @@ -1,21 +1,21 @@ import { GraphQLList, GraphQLError, GraphQLID } from 'graphql'; -import { UserType } from '../types'; +import { PeopleType } from '../types/people.type'; import { logger } from '@services/logger.service'; /** Temporary test mocked people awaiting for real data */ export const MockedPeople = [ { _id: '5f9a9b9b9b9b9b9b9b9b9b9b', - username: 'John Doe', - name: 'John Doe', - email: 'JohnDoe@test.com', + emailaddress: 'JohnDoe@test.com', + firstname: 'John', + lastname: 'Doe', oid: '5f9a9b9b9b9b9b9b9b9b9b9b', }, { _id: '6f9a9b9b9b9b9b9b9b9b9b9b', - username: 'Jane Doe', - name: 'Jane Doe', - email: 'JaneDoe@test.com', + emailaddress: 'JaneDoe@test.com', + firstname: 'Jane', + lastname: 'Doe', oid: '6f9a9b9b9b9b9b9b9b9b9b9b', }, ]; @@ -24,7 +24,7 @@ export const MockedPeople = [ * People mock query */ export default { - type: new GraphQLList(UserType), //this should be PersonType + type: new GraphQLList(PeopleType), args: { applications: { type: new GraphQLList(GraphQLID) }, }, diff --git a/src/schema/types/people.type.ts b/src/schema/types/people.type.ts new file mode 100644 index 000000000..b45d4cf0c --- /dev/null +++ b/src/schema/types/people.type.ts @@ -0,0 +1,24 @@ +import { GraphQLObjectType, GraphQLID, GraphQLString } from 'graphql'; +import { Connection } from './pagination.type'; + +/** + * GraphQL User type. + */ +export const PeopleType = new GraphQLObjectType({ + name: 'People', + fields: () => ({ + id: { + type: GraphQLID, + resolve(parent) { + return parent._id; + }, + }, + emailaddress: { type: GraphQLString }, + firstname: { type: GraphQLString }, + lastname: { type: GraphQLString }, + oid: { type: GraphQLString }, + }), +}); + +/** User connection type */ +export const UserConnectionType = Connection(PeopleType); From 2115ffbf4a45d270227fd7ee06983c80a68b26d8 Mon Sep 17 00:00:00 2001 From: Tai Kamilla Date: Thu, 5 Oct 2023 12:52:34 +0200 Subject: [PATCH 05/26] missing resolver change --- src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts index f278d86e2..219e085c9 100644 --- a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts +++ b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts @@ -12,7 +12,7 @@ const getMetaUsersResolver = async (field: any) => { choices: people ? people.map((x) => { return { - text: x.username, + text: x.firstname + ', ' + x.lastname, value: x._id, }; }) From 3c3e00c704c0808cc284f8a2fb03984701995a50 Mon Sep 17 00:00:00 2001 From: Tai Kamilla Date: Fri, 6 Oct 2023 09:22:19 +0200 Subject: [PATCH 06/26] it works --- config/custom-environment-variables.js | 2 ++ src/routes/proxy/index.ts | 43 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/config/custom-environment-variables.js b/config/custom-environment-variables.js index dacdb425e..f01abfcef 100644 --- a/config/custom-environment-variables.js +++ b/config/custom-environment-variables.js @@ -60,5 +60,7 @@ module.exports = { clientId: 'COMMON_SERVICES_CLIENT_ID', clientSecret: 'COMMON_SERVICES_CLIENT_SECRET', scope: 'COMMON_SERVICES_SCOPE', + authUrl: 'COMMON_SERVICES_AUTH_URL', + url: 'COMMON_SERVICES_URL', }, }; diff --git a/src/routes/proxy/index.ts b/src/routes/proxy/index.ts index 36dba6030..2121ca2e5 100644 --- a/src/routes/proxy/index.ts +++ b/src/routes/proxy/index.ts @@ -145,6 +145,48 @@ router.post('/ping/**', async (req, res) => { } }); +router.all('/common-services/**', async (req, res) => { + const commonServiceConfig = { + name: 'common-services', + status: 'active', + authType: 'service-to-service', + endpoint: config.get('commonServices.url'), + settings: { + authTargetUrl: config.get('commonServices.authUrl'), + apiClientId: config.get('commonServices.clientId'), + scope: config.get('commonServices.scope'), + secret: config.get('commonServices.clientSecret'), + }, + }; + + //encrypt settings + const settings = { + authTargetUrl: commonServiceConfig.settings.authTargetUrl, + apiClientID: commonServiceConfig.settings.apiClientId, + scope: commonServiceConfig.settings.scope, + safeSecret: commonServiceConfig.settings.secret, + }; + const encryptedSettings = CryptoJS.AES.encrypt( + JSON.stringify(settings), + config.get('encryption.key') + ).toString(); + + //save common service config + const commonServiceConfigToSave = { + ...commonServiceConfig, + settings: encryptedSettings, + }; + + try { + const api = new ApiConfiguration(commonServiceConfigToSave); + const path = req.originalUrl.split('common-services').pop().substring(1); + await proxyAPIRequest(req, res, api, path); + } catch (err) { + logger.error(err.message, { stack: err.stack }); + return res.status(500).send(req.t('common.errors.internalServerError')); + } +}); + /** * Forward requests to actual API using the API Configuration */ @@ -154,6 +196,7 @@ router.all('/:name/**', async (req, res) => { $or: [{ name: req.params.name }, { id: req.params.name }], status: 'active', }).select('name authType endpoint settings id'); + if (!api) { return res.status(404).send(i18next.t('common.errors.dataNotFound')); } From 52ae44756d3692fb0eb2bcb237216e7ce2492e30 Mon Sep 17 00:00:00 2001 From: Tai Kamilla Date: Fri, 6 Oct 2023 09:33:10 +0200 Subject: [PATCH 07/26] compact the code a bit --- src/routes/proxy/index.ts | 35 ++++++++++------------------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/src/routes/proxy/index.ts b/src/routes/proxy/index.ts index 2121ca2e5..68a7169dc 100644 --- a/src/routes/proxy/index.ts +++ b/src/routes/proxy/index.ts @@ -151,34 +151,19 @@ router.all('/common-services/**', async (req, res) => { status: 'active', authType: 'service-to-service', endpoint: config.get('commonServices.url'), - settings: { - authTargetUrl: config.get('commonServices.authUrl'), - apiClientId: config.get('commonServices.clientId'), - scope: config.get('commonServices.scope'), - secret: config.get('commonServices.clientSecret'), - }, - }; - - //encrypt settings - const settings = { - authTargetUrl: commonServiceConfig.settings.authTargetUrl, - apiClientID: commonServiceConfig.settings.apiClientId, - scope: commonServiceConfig.settings.scope, - safeSecret: commonServiceConfig.settings.secret, - }; - const encryptedSettings = CryptoJS.AES.encrypt( - JSON.stringify(settings), - config.get('encryption.key') - ).toString(); - - //save common service config - const commonServiceConfigToSave = { - ...commonServiceConfig, - settings: encryptedSettings, + settings: CryptoJS.AES.encrypt( + JSON.stringify({ + authTargetUrl: config.get('commonServices.authUrl'), + apiClientID: config.get('commonServices.clientId'), + scope: config.get('commonServices.scope'), + safeSecret: config.get('commonServices.clientSecret'), + }), + config.get('encryption.key') + ).toString(), }; try { - const api = new ApiConfiguration(commonServiceConfigToSave); + const api = new ApiConfiguration(commonServiceConfig); const path = req.originalUrl.split('common-services').pop().substring(1); await proxyAPIRequest(req, res, api, path); } catch (err) { From 66024266d6033ee10dcb9bbd96c313849ac6093d Mon Sep 17 00:00:00 2001 From: Tai Kamilla Date: Mon, 9 Oct 2023 09:38:44 +0200 Subject: [PATCH 08/26] =?UTF-8?q?Clean=20=F0=9F=A7=BC=20=F0=9F=A7=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/proxy/index.ts | 1 - src/schema/query/index.ts | 2 - src/schema/query/people.query.ts | 45 ------------------- src/schema/types/people.type.ts | 24 ---------- src/utils/files/getRowsFromMeta.ts | 1 - src/utils/form/getFieldType.ts | 2 - src/utils/form/metadata.helper.ts | 1 - .../schema/introspection/getFieldType.ts | 3 -- .../resolvers/Meta/getMetaFieldResolver.ts | 4 -- .../resolvers/Meta/getMetaPeopleResolver.ts | 23 ---------- 10 files changed, 106 deletions(-) delete mode 100644 src/schema/query/people.query.ts delete mode 100644 src/schema/types/people.type.ts delete mode 100644 src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts diff --git a/src/routes/proxy/index.ts b/src/routes/proxy/index.ts index 68a7169dc..d29b105ab 100644 --- a/src/routes/proxy/index.ts +++ b/src/routes/proxy/index.ts @@ -181,7 +181,6 @@ router.all('/:name/**', async (req, res) => { $or: [{ name: req.params.name }, { id: req.params.name }], status: 'active', }).select('name authType endpoint settings id'); - if (!api) { return res.status(404).send(i18next.t('common.errors.dataNotFound')); } diff --git a/src/schema/query/index.ts b/src/schema/query/index.ts index 12d78d29b..6b0fc5925 100644 --- a/src/schema/query/index.ts +++ b/src/schema/query/index.ts @@ -10,7 +10,6 @@ import recordsAggregation from './recordsAggregation.query'; import dashboards from './dashboards.query'; import dashboard from './dashboard.query'; import users from './users.query'; -import people from './people.query'; import me from './me.query'; import role from './role.query'; import roles from './roles.query'; @@ -73,7 +72,6 @@ const Query = new GraphQLObjectType({ steps, user, users, - people, workflow, workflows, positionAttributes, diff --git a/src/schema/query/people.query.ts b/src/schema/query/people.query.ts deleted file mode 100644 index 61918bafb..000000000 --- a/src/schema/query/people.query.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { GraphQLList, GraphQLError, GraphQLID } from 'graphql'; -import { PeopleType } from '../types/people.type'; -import { logger } from '@services/logger.service'; - -/** Temporary test mocked people awaiting for real data */ -export const MockedPeople = [ - { - _id: '5f9a9b9b9b9b9b9b9b9b9b9b', - emailaddress: 'JohnDoe@test.com', - firstname: 'John', - lastname: 'Doe', - oid: '5f9a9b9b9b9b9b9b9b9b9b9b', - }, - { - _id: '6f9a9b9b9b9b9b9b9b9b9b9b', - emailaddress: 'JaneDoe@test.com', - firstname: 'Jane', - lastname: 'Doe', - oid: '6f9a9b9b9b9b9b9b9b9b9b9b', - }, -]; - -/** - * People mock query - */ -export default { - type: new GraphQLList(PeopleType), - args: { - applications: { type: new GraphQLList(GraphQLID) }, - }, - resolve(parent, args, context) { - try { - // return a test user - return MockedPeople; - } catch (err) { - logger.error(err.message, { stack: err.stack }); - if (err instanceof GraphQLError) { - throw new GraphQLError(err.message); - } - throw new GraphQLError( - context.i18next.t('common.errors.internalServerError') - ); - } - }, -}; diff --git a/src/schema/types/people.type.ts b/src/schema/types/people.type.ts deleted file mode 100644 index b45d4cf0c..000000000 --- a/src/schema/types/people.type.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { GraphQLObjectType, GraphQLID, GraphQLString } from 'graphql'; -import { Connection } from './pagination.type'; - -/** - * GraphQL User type. - */ -export const PeopleType = new GraphQLObjectType({ - name: 'People', - fields: () => ({ - id: { - type: GraphQLID, - resolve(parent) { - return parent._id; - }, - }, - emailaddress: { type: GraphQLString }, - firstname: { type: GraphQLString }, - lastname: { type: GraphQLString }, - oid: { type: GraphQLString }, - }), -}); - -/** User connection type */ -export const UserConnectionType = Connection(PeopleType); diff --git a/src/utils/files/getRowsFromMeta.ts b/src/utils/files/getRowsFromMeta.ts index 877df12b5..8000e0840 100644 --- a/src/utils/files/getRowsFromMeta.ts +++ b/src/utils/files/getRowsFromMeta.ts @@ -66,7 +66,6 @@ export const getRowsFromMeta = (columns: any[], records: any[]): any[] => { set(row, column.name, Array.isArray(value) ? value.join(',') : value); break; } - case 'people': case 'users': { let value: any = get(data, column.field); const choices = column.meta.field.choices || []; diff --git a/src/utils/form/getFieldType.ts b/src/utils/form/getFieldType.ts index 509a325d6..eadef92ea 100644 --- a/src/utils/form/getFieldType.ts +++ b/src/utils/form/getFieldType.ts @@ -79,8 +79,6 @@ export const getFieldType = async (question: { return 'tagbox'; case 'users': return 'users'; - case 'people': - return 'people'; case 'owner': return 'owner'; case 'geospatial': diff --git a/src/utils/form/metadata.helper.ts b/src/utils/form/metadata.helper.ts index 784d64625..d77b8caca 100644 --- a/src/utils/form/metadata.helper.ts +++ b/src/utils/form/metadata.helper.ts @@ -316,7 +316,6 @@ export const getMetaData = async ( }; break; } - case 'people': case 'users': { fieldMeta.editor = 'select'; fieldMeta.multiSelect = true; diff --git a/src/utils/schema/introspection/getFieldType.ts b/src/utils/schema/introspection/getFieldType.ts index b83d50ec0..ab48c05a2 100644 --- a/src/utils/schema/introspection/getFieldType.ts +++ b/src/utils/schema/introspection/getFieldType.ts @@ -119,9 +119,6 @@ const getFieldType = ( case 'users': { return GraphQLJSON; } - case 'people': { - return GraphQLJSON; - } case 'owner': { return GraphQLJSON; } diff --git a/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts b/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts index 7d94e5bcc..94e3b19c5 100644 --- a/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts +++ b/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts @@ -1,7 +1,6 @@ import getMetaCheckboxResolver from './getMetaCheckboxResolver'; import getMetaDropdownResolver from './getMetaDropdownResolver'; import getMetaOwnerResolver from './getMetaOwnerResolver'; -import getMetaPeopleResolver from './getMetaPeopleResolver'; import getMetaUsersResolver from './getMetaUsersResolver'; import getMetaRadioResolver from './getMetaRadiogroupResolver'; import getMetaTagboxResolver from './getMetaTagboxResolver'; @@ -26,9 +25,6 @@ const getMetaFieldResolver = (field: any) => { case 'tagbox': { return getMetaTagboxResolver(field); } - case 'people': { - return getMetaPeopleResolver(field); - } case 'users': { return getMetaUsersResolver(field); } diff --git a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts deleted file mode 100644 index 219e085c9..000000000 --- a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { MockedPeople } from '@schema/query/people.query'; - -/** - * Return people meta resolver. - * - * @param field field definition. - * @returns people resolver. - */ -const getMetaUsersResolver = async (field: any) => { - const people = MockedPeople; - return Object.assign(field, { - choices: people - ? people.map((x) => { - return { - text: x.firstname + ', ' + x.lastname, - value: x._id, - }; - }) - : [], - }); -}; - -export default getMetaUsersResolver; From ae831292cbe3fb7848215a376f78ecd03244a9ab Mon Sep 17 00:00:00 2001 From: Nathan Date: Wed, 20 Mar 2024 16:44:56 +0100 Subject: [PATCH 09/26] feat: added people type --- src/const/fieldTypes.ts | 1 + src/utils/form/getFieldType.ts | 2 ++ src/utils/schema/introspection/getFieldType.ts | 3 +++ 3 files changed, 6 insertions(+) diff --git a/src/const/fieldTypes.ts b/src/const/fieldTypes.ts index e2e2d7c0c..dd138f1a8 100644 --- a/src/const/fieldTypes.ts +++ b/src/const/fieldTypes.ts @@ -5,6 +5,7 @@ export const MULTISELECT_TYPES: string[] = [ 'tagbox', 'owner', 'users', + 'people', ]; /** List of date field types */ diff --git a/src/utils/form/getFieldType.ts b/src/utils/form/getFieldType.ts index eadef92ea..bce03af36 100644 --- a/src/utils/form/getFieldType.ts +++ b/src/utils/form/getFieldType.ts @@ -81,6 +81,8 @@ export const getFieldType = async (question: { return 'users'; case 'owner': return 'owner'; + case 'people': + return 'people'; case 'geospatial': return 'geospatial'; default: diff --git a/src/utils/schema/introspection/getFieldType.ts b/src/utils/schema/introspection/getFieldType.ts index ab48c05a2..70c720bcf 100644 --- a/src/utils/schema/introspection/getFieldType.ts +++ b/src/utils/schema/introspection/getFieldType.ts @@ -122,6 +122,9 @@ const getFieldType = ( case 'owner': { return GraphQLJSON; } + case 'people': { + return GraphQLJSON; + } case 'geospatial': { return GraphQLJSON; } From d5f2ceae8cabf3d4f932ebfd523dc9baf77c0798 Mon Sep 17 00:00:00 2001 From: Nathan Date: Fri, 22 Mar 2024 17:06:46 +0100 Subject: [PATCH 10/26] feat: add get people --- src/schema/query/index.ts | 2 ++ src/schema/query/people.query.ts | 47 ++++++++++++++++++++++++++++++++ src/schema/types/person.type.ts | 14 ++++++++++ src/utils/proxy/getPeople.ts | 40 +++++++++++++++++++++++++++ src/utils/proxy/index.ts | 1 + 5 files changed, 104 insertions(+) create mode 100644 src/schema/query/people.query.ts create mode 100644 src/schema/types/person.type.ts create mode 100644 src/utils/proxy/getPeople.ts diff --git a/src/schema/query/index.ts b/src/schema/query/index.ts index 74920da62..df82de6fc 100644 --- a/src/schema/query/index.ts +++ b/src/schema/query/index.ts @@ -39,6 +39,7 @@ import layer from './layer.query'; import draftRecords from './draftRecords.query'; import referenceDataAggregation from './referenceDataAggregation.query'; import types from './types.query'; +import people from './people.query'; /** GraphQL query type definition */ const Query = new GraphQLObjectType({ @@ -84,6 +85,7 @@ const Query = new GraphQLObjectType({ layers, layer, draftRecords, + people, }, }); diff --git a/src/schema/query/people.query.ts b/src/schema/query/people.query.ts new file mode 100644 index 000000000..b064f9f9b --- /dev/null +++ b/src/schema/query/people.query.ts @@ -0,0 +1,47 @@ +import { GraphQLError, GraphQLList } from 'graphql'; +import { logger } from '@services/logger.service'; +import { Context } from '@server/apollo/context'; +import GraphQLJSON from 'graphql-type-json'; +import { PersonType } from '@schema/types/person.type'; +import { getPeople } from '@utils/proxy'; + +/** Arguments for the people query */ +type PeopleArgs = { + filter?: any; +}; + +/** + * Get people. + */ +export default { + type: new GraphQLList(PersonType), + args: { + filter: { type: GraphQLJSON }, + }, + async resolve(parent, args: PeopleArgs, context: Context) { + try { + const myFilter = `{ + OR: [ + { firstname_like: "%${args.filter.value}%" } + { lastname_like: "%${args.filter.value}%" } + { emailaddress_like: "%${args.filter.value}%" } + ] + }`; + + const people = await getPeople(context.token, myFilter); + if (people) { + return people.map((person) => { + const updatedPerson = { ...person }; + updatedPerson.id = updatedPerson.userid; + delete updatedPerson.userid; + return updatedPerson; + }); + } + } catch (err) { + logger.error(err.message, { stack: err.stack }); + throw new GraphQLError( + context.i18next.t('common.errors.internalServerError') + ); + } + }, +}; diff --git a/src/schema/types/person.type.ts b/src/schema/types/person.type.ts new file mode 100644 index 000000000..7e147f40a --- /dev/null +++ b/src/schema/types/person.type.ts @@ -0,0 +1,14 @@ +import { GraphQLObjectType, GraphQLString } from 'graphql'; + +/** + * GraphQL Person type. + */ +export const PersonType = new GraphQLObjectType({ + name: 'Person', + fields: () => ({ + id: { type: GraphQLString }, + firstname: { type: GraphQLString }, + lastname: { type: GraphQLString }, + emailaddress: { type: GraphQLString }, + }), +}); diff --git a/src/utils/proxy/getPeople.ts b/src/utils/proxy/getPeople.ts new file mode 100644 index 000000000..5e3988504 --- /dev/null +++ b/src/utils/proxy/getPeople.ts @@ -0,0 +1,40 @@ +import axios from 'axios'; + +/** + * Fetches the people + * + * @param token The authorization token + * @returns the choices + */ +export const getPeople = async (token: string, filter: any): Promise => { + const url = 'http://localhost:3000/proxy/common-services/graphql'; + const query = `query { + users( + filter: ${filter} + ) { + userid + firstname + lastname + emailaddress + } + }`; + try { + let people: any[] = []; + await axios({ + url, + method: 'post', + headers: { + Authorization: token, + 'Content-Type': 'application/json', + }, + data: { + query: query, + }, + }).then(({ data }) => { + people = data?.data?.users; + }); + return people; + } catch { + return []; + } +}; diff --git a/src/utils/proxy/index.ts b/src/utils/proxy/index.ts index 336d85a33..ce4002cc3 100644 --- a/src/utils/proxy/index.ts +++ b/src/utils/proxy/index.ts @@ -1,2 +1,3 @@ export * from './authManagement'; export * from './getChoices'; +export * from './getPeople'; From 17c52d81280f58eb52df96ee04eaf1589e876df7 Mon Sep 17 00:00:00 2001 From: Nathan Date: Mon, 25 Mar 2024 14:58:08 +0100 Subject: [PATCH 11/26] feat: handle people filtering and add people resolver --- src/schema/query/people.query.ts | 31 +++++++---- src/utils/proxy/getPeople.ts | 1 + .../resolvers/Meta/getMetaFieldResolver.ts | 4 ++ .../resolvers/Meta/getMetaPeopleResolver.ts | 51 +++++++++++++++++++ src/utils/schema/resolvers/Meta/index.ts | 3 ++ 5 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts diff --git a/src/schema/query/people.query.ts b/src/schema/query/people.query.ts index b064f9f9b..d4ef730ba 100644 --- a/src/schema/query/people.query.ts +++ b/src/schema/query/people.query.ts @@ -11,7 +11,7 @@ type PeopleArgs = { }; /** - * Get people. + * Return distant users from common services */ export default { type: new GraphQLList(PersonType), @@ -19,16 +19,26 @@ export default { filter: { type: GraphQLJSON }, }, async resolve(parent, args: PeopleArgs, context: Context) { + if (!args.filter) return []; try { - const myFilter = `{ - OR: [ - { firstname_like: "%${args.filter.value}%" } - { lastname_like: "%${args.filter.value}%" } - { emailaddress_like: "%${args.filter.value}%" } - ] - }`; - - const people = await getPeople(context.token, myFilter); + // Formatted filter used by the API + const getFormattedFilter = (filter: any) => { + const formattedFilter = `{${filter.logic.toUpperCase()}:[ + ${filter.filters.map((el: any) => { + if (el.operator === 'like') { + el.value = `"%${el.value}%"`; + } else if (el.operator === 'in') { + el.value = el.value.map((e) => `"${e}"`); + el.value = `[${el.value}]`; + } + return `{ ${el.field}_${el.operator}: ${el.value} }`; + })} + ] + }`; + return formattedFilter.replace(/\s/g, ''); + }; + const filter = getFormattedFilter(args.filter); + const people = await getPeople(context.token, filter); if (people) { return people.map((person) => { const updatedPerson = { ...person }; @@ -37,6 +47,7 @@ export default { return updatedPerson; }); } + return []; } catch (err) { logger.error(err.message, { stack: err.stack }); throw new GraphQLError( diff --git a/src/utils/proxy/getPeople.ts b/src/utils/proxy/getPeople.ts index 5e3988504..7693c37ca 100644 --- a/src/utils/proxy/getPeople.ts +++ b/src/utils/proxy/getPeople.ts @@ -4,6 +4,7 @@ import axios from 'axios'; * Fetches the people * * @param token The authorization token + * @param filter The filter used for fetching the distant users * @returns the choices */ export const getPeople = async (token: string, filter: any): Promise => { diff --git a/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts b/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts index 94e3b19c5..5bfeea824 100644 --- a/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts +++ b/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts @@ -4,6 +4,7 @@ import getMetaOwnerResolver from './getMetaOwnerResolver'; import getMetaUsersResolver from './getMetaUsersResolver'; import getMetaRadioResolver from './getMetaRadiogroupResolver'; import getMetaTagboxResolver from './getMetaTagboxResolver'; +import getMetaPeopleResolver from './getMetaPeopleResolver'; /** * Return GraphQL resolver of the field, based on its type. @@ -28,6 +29,9 @@ const getMetaFieldResolver = (field: any) => { case 'users': { return getMetaUsersResolver(field); } + case 'people': { + return getMetaPeopleResolver(field); + } case 'owner': { return getMetaOwnerResolver(field); } diff --git a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts new file mode 100644 index 000000000..37d252c91 --- /dev/null +++ b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts @@ -0,0 +1,51 @@ +import { Record } from '@models'; +// import { getPeople } from '@utils/proxy'; + +/** + * Return people meta resolver. + * + * @param field field definition. + * @returns People resolver. + */ +const getMetaPeopleResolver = async (field: any) => { + const records = await Record.find({ resource: field.resource }); + const peopleIds = []; + records.forEach((record) => { + const propertyValue = record.data[field.name]; + peopleIds.push(...propertyValue.flat()); + }); + const getFilter = (people: any) => { + const formattedFilter = `{OR:[ + userid_eq: + [${people.map((el: any) => `"${el}"`)}]`; + return formattedFilter.replace(/\s/g, ''); + }; + const filter = getFilter(peopleIds); + console.log(filter); + + // const people = await getPeople(token, filter); + // return Object.assign(field, { + // choices: people.map((x: any) => { + // const fullname = + // x.firstname && x.lastname + // ? `${x.firstname}, ${x.lastname}` + // : x.firstname || x.lastname; + // return { + // text: `${fullname} (${x.emailaddress})`, + // value: x.id, + // }; + // }), + // }); + + // Temporary solution as we don't have the token + return Object.assign(field, { + choices: peopleIds.map((x: any, id) => { + return { + text: 'User ' + id, + value: x, + }; + }), + }); +}; + +export default getMetaPeopleResolver; diff --git a/src/utils/schema/resolvers/Meta/index.ts b/src/utils/schema/resolvers/Meta/index.ts index e3121258e..c06491209 100644 --- a/src/utils/schema/resolvers/Meta/index.ts +++ b/src/utils/schema/resolvers/Meta/index.ts @@ -170,6 +170,9 @@ export const getMetaResolver = ( ) ] : parent[fieldName]; + if (field.type === 'people') { + field.resource = id; + } return getMetaFieldResolver(field); }, }), From 2c5088c2a1546ded214a6aca189909264b6daa85 Mon Sep 17 00:00:00 2001 From: Nathan Date: Mon, 25 Mar 2024 16:58:23 +0100 Subject: [PATCH 12/26] review people filter format + unique ids --- .../resolvers/Meta/getMetaPeopleResolver.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts index 37d252c91..dd318cf0f 100644 --- a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts +++ b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts @@ -12,12 +12,21 @@ const getMetaPeopleResolver = async (field: any) => { const peopleIds = []; records.forEach((record) => { const propertyValue = record.data[field.name]; - peopleIds.push(...propertyValue.flat()); + propertyValue.flat().forEach((id: string) => { + if (!peopleIds.includes(id)) { + peopleIds.push(id); + } + }); }); const getFilter = (people: any) => { - const formattedFilter = `{OR:[ - userid_eq: - [${people.map((el: any) => `"${el}"`)}]`; + const formattedFilter = `{ + OR: [ + { + userid_eq: + [${people.map((el: any) => `"${el}"`)}] + } + ] + }`; return formattedFilter.replace(/\s/g, ''); }; const filter = getFilter(peopleIds); From 05c65cdbc8059a9000dc465741cd2e3f2cf5672e Mon Sep 17 00:00:00 2001 From: Nathan Date: Tue, 26 Mar 2024 11:41:15 +0100 Subject: [PATCH 13/26] add context to resolvers --- .../resolvers/Meta/getMetaFieldResolver.ts | 6 ++- .../resolvers/Meta/getMetaPeopleResolver.ts | 42 +++++++------------ src/utils/schema/resolvers/Meta/index.ts | 4 +- 3 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts b/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts index 5bfeea824..73698a75e 100644 --- a/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts +++ b/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts @@ -5,14 +5,16 @@ import getMetaUsersResolver from './getMetaUsersResolver'; import getMetaRadioResolver from './getMetaRadiogroupResolver'; import getMetaTagboxResolver from './getMetaTagboxResolver'; import getMetaPeopleResolver from './getMetaPeopleResolver'; +import { Context } from '@server/apollo/context'; /** * Return GraphQL resolver of the field, based on its type. * * @param field field definition. + * @param context graphQL context. * @returns resolver of the field. */ -const getMetaFieldResolver = (field: any) => { +const getMetaFieldResolver = (field: any, context: Context) => { switch (field.type) { case 'dropdown': { return getMetaDropdownResolver(field); @@ -30,7 +32,7 @@ const getMetaFieldResolver = (field: any) => { return getMetaUsersResolver(field); } case 'people': { - return getMetaPeopleResolver(field); + return getMetaPeopleResolver(field, context); } case 'owner': { return getMetaOwnerResolver(field); diff --git a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts index dd318cf0f..e64d07d65 100644 --- a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts +++ b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts @@ -1,13 +1,15 @@ import { Record } from '@models'; -// import { getPeople } from '@utils/proxy'; +import { Context } from '@server/apollo/context'; +import { getPeople } from '@utils/proxy'; /** * Return people meta resolver. * * @param field field definition. + * @param context graphQL context. * @returns People resolver. */ -const getMetaPeopleResolver = async (field: any) => { +const getMetaPeopleResolver = async (field: any, context: Context) => { const records = await Record.find({ resource: field.resource }); const peopleIds = []; records.forEach((record) => { @@ -20,38 +22,26 @@ const getMetaPeopleResolver = async (field: any) => { }); const getFilter = (people: any) => { const formattedFilter = `{ - OR: [ - { - userid_eq: + userid_in: [${people.map((el: any) => `"${el}"`)}] - } - ] }`; return formattedFilter.replace(/\s/g, ''); }; const filter = getFilter(peopleIds); - console.log(filter); + const people = await getPeople(context.token, filter); + if (!people) { + return []; + } - // const people = await getPeople(token, filter); - // return Object.assign(field, { - // choices: people.map((x: any) => { - // const fullname = - // x.firstname && x.lastname - // ? `${x.firstname}, ${x.lastname}` - // : x.firstname || x.lastname; - // return { - // text: `${fullname} (${x.emailaddress})`, - // value: x.id, - // }; - // }), - // }); - - // Temporary solution as we don't have the token return Object.assign(field, { - choices: peopleIds.map((x: any, id) => { + choices: people.map((x: any) => { + const fullname = + x.firstname && x.lastname + ? `${x.firstname}, ${x.lastname}` + : x.firstname || x.lastname; return { - text: 'User ' + id, - value: x, + text: `${fullname} (${x.emailaddress})`, + value: x.userid, }; }), }); diff --git a/src/utils/schema/resolvers/Meta/index.ts b/src/utils/schema/resolvers/Meta/index.ts index c06491209..6c7b5741b 100644 --- a/src/utils/schema/resolvers/Meta/index.ts +++ b/src/utils/schema/resolvers/Meta/index.ts @@ -161,7 +161,7 @@ export const getMetaResolver = ( .reduce( (resolvers, fieldName) => Object.assign({}, resolvers, { - [fieldName]: (parent) => { + [fieldName]: (parent, args, context) => { const field = relationshipFields.includes(fieldName) ? parent[ fieldName.slice( @@ -173,7 +173,7 @@ export const getMetaResolver = ( if (field.type === 'people') { field.resource = id; } - return getMetaFieldResolver(field); + return getMetaFieldResolver(field, context); }, }), {} From 8e5b16435614483fb37217cb0b31d89190ef0c0c Mon Sep 17 00:00:00 2001 From: Nathan Date: Wed, 27 Mar 2024 12:28:23 +0100 Subject: [PATCH 14/26] fix people resolver --- src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts index e64d07d65..16ae85770 100644 --- a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts +++ b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts @@ -10,7 +10,10 @@ import { getPeople } from '@utils/proxy'; * @returns People resolver. */ const getMetaPeopleResolver = async (field: any, context: Context) => { - const records = await Record.find({ resource: field.resource }); + const records = await Record.find({ + resource: field.resource, + archived: false, + }); const peopleIds = []; records.forEach((record) => { const propertyValue = record.data[field.name]; From e3c999ae78c6f3709775bdb0bb81f2df01555a5f Mon Sep 17 00:00:00 2001 From: Nathan Date: Wed, 27 Mar 2024 14:13:40 +0100 Subject: [PATCH 15/26] delete field.resource --- src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts index 16ae85770..5dbba7593 100644 --- a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts +++ b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts @@ -36,6 +36,8 @@ const getMetaPeopleResolver = async (field: any, context: Context) => { return []; } + delete field.resource; + return Object.assign(field, { choices: people.map((x: any) => { const fullname = From 0ea3ab0c9d5d302bd69da2f6d5b8065ff592c045 Mon Sep 17 00:00:00 2001 From: Nathan Date: Wed, 27 Mar 2024 15:56:03 +0100 Subject: [PATCH 16/26] fix empty value --- src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts index 5dbba7593..ae6d67122 100644 --- a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts +++ b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts @@ -17,7 +17,7 @@ const getMetaPeopleResolver = async (field: any, context: Context) => { const peopleIds = []; records.forEach((record) => { const propertyValue = record.data[field.name]; - propertyValue.flat().forEach((id: string) => { + propertyValue?.flat().forEach((id: string) => { if (!peopleIds.includes(id)) { peopleIds.push(id); } From fe3d1fc24ecfbed21369cd6fc096c1d3f1cf1bd8 Mon Sep 17 00:00:00 2001 From: MwanPygmay Date: Tue, 30 Apr 2024 15:46:02 +0200 Subject: [PATCH 17/26] now getting people ten by ten --- src/schema/query/people.query.ts | 6 ++++-- src/utils/proxy/getPeople.ts | 9 ++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/schema/query/people.query.ts b/src/schema/query/people.query.ts index d4ef730ba..4368e4906 100644 --- a/src/schema/query/people.query.ts +++ b/src/schema/query/people.query.ts @@ -1,4 +1,4 @@ -import { GraphQLError, GraphQLList } from 'graphql'; +import { GraphQLError, GraphQLInt, GraphQLList } from 'graphql'; import { logger } from '@services/logger.service'; import { Context } from '@server/apollo/context'; import GraphQLJSON from 'graphql-type-json'; @@ -8,6 +8,7 @@ import { getPeople } from '@utils/proxy'; /** Arguments for the people query */ type PeopleArgs = { filter?: any; + offset?: number; }; /** @@ -17,6 +18,7 @@ export default { type: new GraphQLList(PersonType), args: { filter: { type: GraphQLJSON }, + offset: { type: GraphQLInt }, }, async resolve(parent, args: PeopleArgs, context: Context) { if (!args.filter) return []; @@ -38,7 +40,7 @@ export default { return formattedFilter.replace(/\s/g, ''); }; const filter = getFormattedFilter(args.filter); - const people = await getPeople(context.token, filter); + const people = await getPeople(context.token, filter, args.offset ?? 0); if (people) { return people.map((person) => { const updatedPerson = { ...person }; diff --git a/src/utils/proxy/getPeople.ts b/src/utils/proxy/getPeople.ts index 7693c37ca..7b833b4bf 100644 --- a/src/utils/proxy/getPeople.ts +++ b/src/utils/proxy/getPeople.ts @@ -5,13 +5,20 @@ import axios from 'axios'; * * @param token The authorization token * @param filter The filter used for fetching the distant users + * @param offset offset to query users * @returns the choices */ -export const getPeople = async (token: string, filter: any): Promise => { +export const getPeople = async ( + token: string, + filter: any, + offset = 0 +): Promise => { const url = 'http://localhost:3000/proxy/common-services/graphql'; const query = `query { users( filter: ${filter} + limitItems: 10 + offset: ${offset} ) { userid firstname From f5d146e139632796214fefcaa39d8b422527601f Mon Sep 17 00:00:00 2001 From: MwanPygmay Date: Fri, 3 May 2024 11:25:40 +0200 Subject: [PATCH 18/26] update meta, and correct export --- src/utils/files/getRowsFromMeta.ts | 14 ++++---------- src/utils/form/getFieldType.ts | 2 ++ src/utils/schema/introspection/getFieldType.ts | 3 +++ .../schema/resolvers/Meta/getMetaFieldResolver.ts | 3 ++- .../resolvers/Meta/getMetaPeopleResolver.ts | 15 ++++++++++----- src/utils/schema/resolvers/Meta/index.ts | 2 +- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/utils/files/getRowsFromMeta.ts b/src/utils/files/getRowsFromMeta.ts index 05c9d122b..8eae632f9 100644 --- a/src/utils/files/getRowsFromMeta.ts +++ b/src/utils/files/getRowsFromMeta.ts @@ -58,6 +58,8 @@ export const getRowsFromMeta = (columns: any[], records: any[]): any[] => { set(row, column.name, Array.isArray(value) ? value.join(',') : value); break; } + case 'people': + case 'singlepeople': case 'users': { let value: any = get(record, column.field); const choices = column.meta.field.choices || []; @@ -92,16 +94,8 @@ export const getRowsFromMeta = (columns: any[], records: any[]): any[] => { set(row, column.name, Array.isArray(value) ? value.join(',') : value); break; } - case 'multipletext': { - const value = get(record, column.name); - set(row, column.name, value); - break; - } - case 'matrix': { - const value = get(record, column.name); - set(row, column.name, value); - break; - } + case 'multipletext': + case 'matrix': case 'matrixdropdown': { const value = get(record, column.name); set(row, column.name, value); diff --git a/src/utils/form/getFieldType.ts b/src/utils/form/getFieldType.ts index bce03af36..4fdff0ac9 100644 --- a/src/utils/form/getFieldType.ts +++ b/src/utils/form/getFieldType.ts @@ -83,6 +83,8 @@ export const getFieldType = async (question: { return 'owner'; case 'people': return 'people'; + case 'singlepeople': + return 'singlepeople'; case 'geospatial': return 'geospatial'; default: diff --git a/src/utils/schema/introspection/getFieldType.ts b/src/utils/schema/introspection/getFieldType.ts index 70c720bcf..7164d82ed 100644 --- a/src/utils/schema/introspection/getFieldType.ts +++ b/src/utils/schema/introspection/getFieldType.ts @@ -125,6 +125,9 @@ const getFieldType = ( case 'people': { return GraphQLJSON; } + case 'singlepeople': { + return GraphQLString; + } case 'geospatial': { return GraphQLJSON; } diff --git a/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts b/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts index 73698a75e..a61b78b5f 100644 --- a/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts +++ b/src/utils/schema/resolvers/Meta/getMetaFieldResolver.ts @@ -31,7 +31,8 @@ const getMetaFieldResolver = (field: any, context: Context) => { case 'users': { return getMetaUsersResolver(field); } - case 'people': { + case 'people': + case 'singlepeople': { return getMetaPeopleResolver(field, context); } case 'owner': { diff --git a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts index ae6d67122..89798b8cf 100644 --- a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts +++ b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts @@ -1,6 +1,7 @@ import { Record } from '@models'; import { Context } from '@server/apollo/context'; import { getPeople } from '@utils/proxy'; +import { isArray } from 'lodash'; /** * Return people meta resolver. @@ -17,11 +18,15 @@ const getMetaPeopleResolver = async (field: any, context: Context) => { const peopleIds = []; records.forEach((record) => { const propertyValue = record.data[field.name]; - propertyValue?.flat().forEach((id: string) => { - if (!peopleIds.includes(id)) { - peopleIds.push(id); - } - }); + if (isArray(propertyValue)) + propertyValue?.flat().forEach((id: string) => { + if (!peopleIds.includes(id)) { + peopleIds.push(id); + } + }); + else { + peopleIds.push(propertyValue); + } }); const getFilter = (people: any) => { const formattedFilter = `{ diff --git a/src/utils/schema/resolvers/Meta/index.ts b/src/utils/schema/resolvers/Meta/index.ts index 6c7b5741b..bb5976d92 100644 --- a/src/utils/schema/resolvers/Meta/index.ts +++ b/src/utils/schema/resolvers/Meta/index.ts @@ -170,7 +170,7 @@ export const getMetaResolver = ( ) ] : parent[fieldName]; - if (field.type === 'people') { + if (field.type === 'people' || 'singlepeople') { field.resource = id; } return getMetaFieldResolver(field, context); From d4ea0f552b9695692d245321effe5e0708399752 Mon Sep 17 00:00:00 2001 From: MwanPygmay Date: Fri, 3 May 2024 14:18:44 +0200 Subject: [PATCH 19/26] add support for summary cards and text editor --- src/schema/query/people.query.ts | 9 ++++++++- src/utils/proxy/getPeople.ts | 6 ++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/schema/query/people.query.ts b/src/schema/query/people.query.ts index 4368e4906..abfbdaa98 100644 --- a/src/schema/query/people.query.ts +++ b/src/schema/query/people.query.ts @@ -9,6 +9,7 @@ import { getPeople } from '@utils/proxy'; type PeopleArgs = { filter?: any; offset?: number; + limitItems?: number | null; }; /** @@ -19,6 +20,7 @@ export default { args: { filter: { type: GraphQLJSON }, offset: { type: GraphQLInt }, + limitItems: { type: GraphQLInt }, }, async resolve(parent, args: PeopleArgs, context: Context) { if (!args.filter) return []; @@ -40,7 +42,12 @@ export default { return formattedFilter.replace(/\s/g, ''); }; const filter = getFormattedFilter(args.filter); - const people = await getPeople(context.token, filter, args.offset ?? 0); + const people = await getPeople( + context.token, + filter, + args.offset, + args.limitItems + ); if (people) { return people.map((person) => { const updatedPerson = { ...person }; diff --git a/src/utils/proxy/getPeople.ts b/src/utils/proxy/getPeople.ts index 7b833b4bf..10a61c483 100644 --- a/src/utils/proxy/getPeople.ts +++ b/src/utils/proxy/getPeople.ts @@ -6,19 +6,21 @@ import axios from 'axios'; * @param token The authorization token * @param filter The filter used for fetching the distant users * @param offset offset to query users + * @param limitItems number of maximum items to fetch * @returns the choices */ export const getPeople = async ( token: string, filter: any, - offset = 0 + offset = 0, + limitItems = null ): Promise => { const url = 'http://localhost:3000/proxy/common-services/graphql'; const query = `query { users( filter: ${filter} - limitItems: 10 offset: ${offset} + ${limitItems ? `limitItems: ${limitItems}` : ''} ) { userid firstname From 089a94d4cca989a80e790cbfd3867639e776fcb1 Mon Sep 17 00:00:00 2001 From: MwanPygmay Date: Tue, 21 May 2024 11:03:52 +0200 Subject: [PATCH 20/26] avoid getting a back-to-back query --- src/routes/proxy/index.ts | 36 ++++++++++++++++++------------------ src/utils/proxy/getPeople.ts | 12 +++++++----- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/routes/proxy/index.ts b/src/routes/proxy/index.ts index f85fc040e..d6bd80876 100644 --- a/src/routes/proxy/index.ts +++ b/src/routes/proxy/index.ts @@ -18,6 +18,23 @@ const router = express.Router(); /** Placeholder to hide settings in UI once saved */ const SETTING_PLACEHOLDER = '●●●●●●●●●●●●●'; +/** common services configuration */ +export const commonServicesConfig = new ApiConfiguration({ + name: 'common-services', + status: 'active', + authType: 'service-to-service', + endpoint: config.get('commonServices.url'), + settings: CryptoJS.AES.encrypt( + JSON.stringify({ + authTargetUrl: config.get('commonServices.authUrl'), + apiClientID: config.get('commonServices.clientId'), + scope: config.get('commonServices.scope'), + safeSecret: config.get('commonServices.clientSecret'), + }), + config.get('encryption.key') + ).toString(), +}); + /** * Proxy API request * @@ -172,26 +189,9 @@ router.post('/ping/**', async (req: Request, res: Response) => { }); router.all('/common-services/**', async (req, res) => { - const commonServiceConfig = { - name: 'common-services', - status: 'active', - authType: 'service-to-service', - endpoint: config.get('commonServices.url'), - settings: CryptoJS.AES.encrypt( - JSON.stringify({ - authTargetUrl: config.get('commonServices.authUrl'), - apiClientID: config.get('commonServices.clientId'), - scope: config.get('commonServices.scope'), - safeSecret: config.get('commonServices.clientSecret'), - }), - config.get('encryption.key') - ).toString(), - }; - try { - const api = new ApiConfiguration(commonServiceConfig); const path = req.originalUrl.split('common-services').pop().substring(1); - await proxyAPIRequest(req, res, api, path); + await proxyAPIRequest(req, res, commonServicesConfig, path); } catch (err) { logger.error(err.message, { stack: err.stack }); return res.status(500).send(req.t('common.errors.internalServerError')); diff --git a/src/utils/proxy/getPeople.ts b/src/utils/proxy/getPeople.ts index 10a61c483..e2ddf1dee 100644 --- a/src/utils/proxy/getPeople.ts +++ b/src/utils/proxy/getPeople.ts @@ -1,21 +1,22 @@ import axios from 'axios'; +import { getToken } from './authManagement'; +import { commonServicesConfig } from '@routes/proxy'; /** * Fetches the people * - * @param token The authorization token + * @param accessToken The authorization token * @param filter The filter used for fetching the distant users * @param offset offset to query users * @param limitItems number of maximum items to fetch * @returns the choices */ export const getPeople = async ( - token: string, + accessToken: string, filter: any, offset = 0, limitItems = null ): Promise => { - const url = 'http://localhost:3000/proxy/common-services/graphql'; const query = `query { users( filter: ${filter} @@ -29,12 +30,13 @@ export const getPeople = async ( } }`; try { + const token = await getToken(commonServicesConfig, accessToken); let people: any[] = []; await axios({ - url, + url: `${commonServicesConfig.endpoint}/graphql`, method: 'post', headers: { - Authorization: token, + Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, data: { From 09be020b74497f4fb08d416712cc957ae0a5dfd9 Mon Sep 17 00:00:00 2001 From: Antoine Hurard Date: Tue, 4 Jun 2024 09:48:54 +0200 Subject: [PATCH 21/26] update config --- config/custom-environment-variables.js | 1 - src/routes/proxy/index.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/config/custom-environment-variables.js b/config/custom-environment-variables.js index fb16fca6b..fef79b07f 100644 --- a/config/custom-environment-variables.js +++ b/config/custom-environment-variables.js @@ -63,7 +63,6 @@ module.exports = { clientId: 'COMMON_SERVICES_CLIENT_ID', clientSecret: 'COMMON_SERVICES_CLIENT_SECRET', scope: 'COMMON_SERVICES_SCOPE', - authUrl: 'COMMON_SERVICES_AUTH_URL', url: 'COMMON_SERVICES_URL', }, microsoftGraph: { diff --git a/src/routes/proxy/index.ts b/src/routes/proxy/index.ts index d6bd80876..8c0fc05f1 100644 --- a/src/routes/proxy/index.ts +++ b/src/routes/proxy/index.ts @@ -26,7 +26,7 @@ export const commonServicesConfig = new ApiConfiguration({ endpoint: config.get('commonServices.url'), settings: CryptoJS.AES.encrypt( JSON.stringify({ - authTargetUrl: config.get('commonServices.authUrl'), + authTargetUrl: config.get('commonServices.tokenEndpoint'), apiClientID: config.get('commonServices.clientId'), scope: config.get('commonServices.scope'), safeSecret: config.get('commonServices.clientSecret'), From df67577646ed0cc0582f90b084213b53c4af7c93 Mon Sep 17 00:00:00 2001 From: Antoine Hurard Date: Fri, 14 Jun 2024 09:36:47 +0200 Subject: [PATCH 22/26] optimize people query --- .../schema/resolvers/Meta/getMetaPeopleResolver.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts index 89798b8cf..1470e5f94 100644 --- a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts +++ b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts @@ -11,10 +11,14 @@ import { isArray } from 'lodash'; * @returns People resolver. */ const getMetaPeopleResolver = async (field: any, context: Context) => { - const records = await Record.find({ - resource: field.resource, - archived: false, - }); + // Optimize the query by only fetching target field + const records = await Record.find( + { + resource: field.resource, + archived: false, + }, + { [`data.${field.name}`]: 1 } + ); const peopleIds = []; records.forEach((record) => { const propertyValue = record.data[field.name]; @@ -28,6 +32,7 @@ const getMetaPeopleResolver = async (field: any, context: Context) => { peopleIds.push(propertyValue); } }); + // Generate a filter to only fetch users we need const getFilter = (people: any) => { const formattedFilter = `{ userid_in: From 0c7dc32cd18428fdd3054b4a6d88b32545f48293 Mon Sep 17 00:00:00 2001 From: MwanPygmay Date: Tue, 18 Jun 2024 14:00:17 +0200 Subject: [PATCH 23/26] add support for graphs --- src/schema/types/draftRecord.type.ts | 3 ++- src/schema/types/record.type.ts | 3 ++- src/schema/types/resource.type.ts | 1 + src/utils/aggregation/setDisplayText.ts | 15 +++++++++--- src/utils/form/getDisplayText.ts | 31 ++++++++++++++++++++++-- src/utils/schema/resolvers/Meta/index.ts | 2 +- 6 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/schema/types/draftRecord.type.ts b/src/schema/types/draftRecord.type.ts index d4d40551b..ef2dd8c28 100644 --- a/src/schema/types/draftRecord.type.ts +++ b/src/schema/types/draftRecord.type.ts @@ -50,7 +50,8 @@ export const DraftRecordType = new GraphQLObjectType({ if ( field.choices || field.choicesByUrl || - field.choicesByGraphQL + field.choicesByGraphQL || + ['people', 'singlepeople'].includes(field.type) ) { res[name] = await getDisplayText( field, diff --git a/src/schema/types/record.type.ts b/src/schema/types/record.type.ts index 992ad76a6..62ee9725b 100644 --- a/src/schema/types/record.type.ts +++ b/src/schema/types/record.type.ts @@ -75,7 +75,8 @@ export const RecordType = new GraphQLObjectType({ if ( field.choices || field.choicesByUrl || - field.choicesByGraphQL + field.choicesByGraphQL || + ['people', 'singlepeople'].includes(field.type) ) { res[name] = await getDisplayText( field, diff --git a/src/schema/types/resource.type.ts b/src/schema/types/resource.type.ts index 1b11ffc31..d8d22d4d8 100644 --- a/src/schema/types/resource.type.ts +++ b/src/schema/types/resource.type.ts @@ -218,6 +218,7 @@ export const ResourceType = new GraphQLObjectType({ { id: r._id } ), })); + console.log('mekkkkkk'); return { pageInfo: { hasNextPage, diff --git a/src/utils/aggregation/setDisplayText.ts b/src/utils/aggregation/setDisplayText.ts index eb11c2cd8..42d75f751 100644 --- a/src/utils/aggregation/setDisplayText.ts +++ b/src/utils/aggregation/setDisplayText.ts @@ -38,7 +38,10 @@ const setDisplayText = async ( const formField = lookAt.find((field: any) => { return ( lookFor === field.name && - (field.choices || field.choicesByUrl || field.choicesByGraphQL) + (field.choices || + field.choicesByUrl || + field.choicesByGraphQL || + ['people', 'singlepeople'].includes(field.type)) ); }); if (formField) { @@ -47,10 +50,14 @@ const setDisplayText = async ( return { ...(await acc) }; } }; - const fieldWithChoices = await mappedFields.reduce(reducer, {}); - for (const [key, field] of Object.entries(fieldWithChoices)) { + const fieldWithChoices: any = await mappedFields.reduce(reducer, {}); + for (const [key, field] of Object.entries(fieldWithChoices)) { // Fetch choices from source ( static / rest / graphql ) - const choices = await getFullChoices(field, context); + let peopleIds = []; + if (['people', 'singlepeople'].includes(field.type)) { + peopleIds = items.map((item) => item[key]); + } + const choices = await getFullChoices(field, context, peopleIds); for (const item of items) { const fieldValue = get(item, key, null); if (fieldValue) { diff --git a/src/utils/form/getDisplayText.ts b/src/utils/form/getDisplayText.ts index 1f89352e1..355901e2e 100644 --- a/src/utils/form/getDisplayText.ts +++ b/src/utils/form/getDisplayText.ts @@ -5,6 +5,7 @@ import { logger } from '@services/logger.service'; import axios from 'axios'; import get from 'lodash/get'; import jsonpath from 'jsonpath'; +import { getPeople } from '@utils/proxy'; /** * Gets display text from choice value. @@ -38,11 +39,13 @@ export const getText = (choices: any[], value: any): string => { * * @param field field to get value of. * @param context provides the data sources context. + * @param peopleIds ids of people to fetch * @returns Choice list of the field. */ export const getFullChoices = async ( field: any, - context: Context + context: Context, + peopleIds?: string[] ): Promise<{ value: string; text: string }[] | string[]> => { try { if (field.choicesByUrl) { @@ -110,6 +113,30 @@ export const getFullChoices = async ( choices.push({ [valueField]: 'other', [textField]: 'Other' }); } return choices; + } else if (['people', 'singlepeople'].includes(field.type)) { + // Generate a filter to only fetch users we need + const getFilter = (people: any) => { + const formattedFilter = `{ + userid_in: + [${people.map((el: any) => `"${el}"`)}] + }`; + return formattedFilter.replace(/\s/g, ''); + }; + const filter = getFilter(peopleIds); + const people = await getPeople(context.token, filter); + if (!people) { + return []; + } + return people.map((x: any) => { + const fullname = + x.firstname && x.lastname + ? `${x.firstname}, ${x.lastname}` + : x.firstname || x.lastname; + return { + text: `${fullname} (${x.emailaddress})`, + value: x.userid, + }; + }); } else { return field.choices; } @@ -133,7 +160,7 @@ const getDisplayText = async ( context: Context ): Promise => { const choices: { value: string; text: string }[] | string[] = - await getFullChoices(field, context); + await getFullChoices(field, context, [value]); if (choices && choices.length) { if (Array.isArray(value)) { return value.map((x) => getText(choices, x)); diff --git a/src/utils/schema/resolvers/Meta/index.ts b/src/utils/schema/resolvers/Meta/index.ts index bb5976d92..fcc44ccb5 100644 --- a/src/utils/schema/resolvers/Meta/index.ts +++ b/src/utils/schema/resolvers/Meta/index.ts @@ -170,7 +170,7 @@ export const getMetaResolver = ( ) ] : parent[fieldName]; - if (field.type === 'people' || 'singlepeople') { + if (['people', 'singlepeople'].includes(field.type)) { field.resource = id; } return getMetaFieldResolver(field, context); From 6906094b41d7bfa0791746ab99ca5567d25d3e28 Mon Sep 17 00:00:00 2001 From: MwanPygmay Date: Wed, 19 Jun 2024 09:04:55 +0200 Subject: [PATCH 24/26] remove console log --- src/schema/types/resource.type.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schema/types/resource.type.ts b/src/schema/types/resource.type.ts index d8d22d4d8..cbe5f4247 100644 --- a/src/schema/types/resource.type.ts +++ b/src/schema/types/resource.type.ts @@ -218,7 +218,7 @@ export const ResourceType = new GraphQLObjectType({ { id: r._id } ), })); - console.log('mekkkkkk'); + return { pageInfo: { hasNextPage, From 7893c10e5a7cd90d996fcc9cc2236520a979ac06 Mon Sep 17 00:00:00 2001 From: MwanPygmay Date: Thu, 27 Jun 2024 16:25:24 +0200 Subject: [PATCH 25/26] add support for filter, record history. Sorting does not work yet --- src/routes/download/index.ts | 5 +- src/utils/files/getRows.ts | 28 ++++++++++- src/utils/form/getDisplayText.ts | 13 ++--- src/utils/form/metadata.helper.ts | 21 ++++++++ src/utils/history/recordHistory.ts | 50 ++++++++++--------- src/utils/proxy/getPeople.ts | 16 ++++++ .../resolvers/Meta/getMetaPeopleResolver.ts | 12 +---- .../resolvers/Query/getSortAggregation.ts | 5 +- 8 files changed, 103 insertions(+), 47 deletions(-) diff --git a/src/routes/download/index.ts b/src/routes/download/index.ts index 1b5550dec..27f36827e 100644 --- a/src/routes/download/index.ts +++ b/src/routes/download/index.ts @@ -141,7 +141,8 @@ router.get('/form/records/:id', async (req, res) => { const records = await Record.find(filter); const rows = await getRows( columns, - getAccessibleFields(records, formAbility) + getAccessibleFields(records, formAbility), + req.headers.authorization ); const type = (req.query ? req.query.type : 'xlsx').toString(); const filename = formatFilename(form.name); @@ -318,7 +319,7 @@ router.get('/resource/records/:id', async (req, res) => { archived: { $ne: true }, }); } - const rows = await getRows(columns, records); + const rows = await getRows(columns, records, req.headers.authorization); const type = (req.query ? req.query.type : 'xlsx').toString(); const filename = formatFilename(resource.name); return await fileBuilder(res, filename, columns, rows, type); diff --git a/src/utils/files/getRows.ts b/src/utils/files/getRows.ts index 152f78375..af0e6ab76 100644 --- a/src/utils/files/getRows.ts +++ b/src/utils/files/getRows.ts @@ -1,17 +1,20 @@ import get from 'lodash/get'; import set from 'lodash/set'; import { getText } from '../form/getDisplayText'; +import { getPeople, getPeopleFilter } from '@utils/proxy'; /** * Transforms records into export rows, using fields definition. * * @param columns definition of export columns. * @param records list of records. + * @param token used to make graphql queries * @returns list of export rows. */ export const getRows = async ( columns: any[], - records: any[] + records: any[], + token?: string ): Promise => { const rows = []; for (const record of records) { @@ -113,6 +116,29 @@ export const getRows = async ( } break; } + case 'people': + case 'singlepeople': { + const value = get(data, column.field); + const filter = getPeopleFilter(value); + const people = await getPeople(token, filter); + if (!people) { + return; + } + set( + row, + column.name, + people + .map((x) => { + const fullname = + x.firstname && x.lastname + ? `${x.firstname}, ${x.lastname}` + : x.firstname || x.lastname; + return `${fullname} (${x.emailaddress})`; + }) + .join(', ') + ); + break; + } default: { const value = column.default ? get(record, column.field) diff --git a/src/utils/form/getDisplayText.ts b/src/utils/form/getDisplayText.ts index 355901e2e..a32753bd4 100644 --- a/src/utils/form/getDisplayText.ts +++ b/src/utils/form/getDisplayText.ts @@ -5,7 +5,7 @@ import { logger } from '@services/logger.service'; import axios from 'axios'; import get from 'lodash/get'; import jsonpath from 'jsonpath'; -import { getPeople } from '@utils/proxy'; +import { getPeople, getPeopleFilter } from '@utils/proxy'; /** * Gets display text from choice value. @@ -45,7 +45,7 @@ export const getText = (choices: any[], value: any): string => { export const getFullChoices = async ( field: any, context: Context, - peopleIds?: string[] + peopleIds?: string[] | string ): Promise<{ value: string; text: string }[] | string[]> => { try { if (field.choicesByUrl) { @@ -115,14 +115,7 @@ export const getFullChoices = async ( return choices; } else if (['people', 'singlepeople'].includes(field.type)) { // Generate a filter to only fetch users we need - const getFilter = (people: any) => { - const formattedFilter = `{ - userid_in: - [${people.map((el: any) => `"${el}"`)}] - }`; - return formattedFilter.replace(/\s/g, ''); - }; - const filter = getFilter(peopleIds); + const filter = getPeopleFilter(peopleIds); const people = await getPeople(context.token, filter); if (!people) { return []; diff --git a/src/utils/form/metadata.helper.ts b/src/utils/form/metadata.helper.ts index d77b8caca..4954a6878 100644 --- a/src/utils/form/metadata.helper.ts +++ b/src/utils/form/metadata.helper.ts @@ -328,6 +328,27 @@ export const getMetaData = async ( fieldMeta._field = field; break; } + case 'people': + fieldMeta.editor = 'people'; + fieldMeta.multiSelect = true; + fieldMeta.filter = { + operators: [ + 'isempty', + 'isnotempty', + 'eq', + 'neq', + 'contains', + 'doesnotcontain', + ], + }; + break; + case 'singlepeople': { + fieldMeta.editor = 'people'; + fieldMeta.filter = { + operators: ['isempty', 'isnotempty', 'eq', 'neq'], + }; + break; + } default: { break; } diff --git a/src/utils/history/recordHistory.ts b/src/utils/history/recordHistory.ts index 8b43b19bb..e31ed613f 100644 --- a/src/utils/history/recordHistory.ts +++ b/src/utils/history/recordHistory.ts @@ -358,32 +358,32 @@ export class RecordHistory { }); } else { // Otherwise, get the display value from choices stored in the field/choicesByUrl - const choices = await getFullChoices(field, this.options.context); + const choices = await getFullChoices( + field, + this.options.context, + [].concat(change.old).concat(change.new) + ); if (change.old !== undefined) { - if (isArray(change.old)) { - change.old = [ - ...new Set( - change.old.map((item: string) => - getOptionFromChoices(item, choices) - ) - ), - ]; - } else { - change.old = getOptionFromChoices(change.old, choices); - } + change.old = Array.isArray(change.old) + ? [ + ...new Set( + change.old.map((item: string) => + getOptionFromChoices(item, choices) + ) + ), + ] + : getOptionFromChoices(change.old, choices); } if (change.new !== undefined) { - if (isArray(change.new)) { - change.new = [ - ...new Set( - change.new.map((item: string) => - getOptionFromChoices(item, choices) - ) - ), - ]; - } else { - change.new = getOptionFromChoices(change.new, choices); - } + change.new = Array.isArray(change.new) + ? [ + ...new Set( + change.new.map((item: string) => + getOptionFromChoices(item, choices) + ) + ), + ] + : getOptionFromChoices(change.new, choices); } } }; @@ -609,6 +609,10 @@ export class RecordHistory { if (change.new !== undefined) change.new = new Date(change.new).toTimeString(); break; + case 'people': + case 'singlepeople': + await formatSelectable(field, change); + break; default: // for all other cases, keep the values break; diff --git a/src/utils/proxy/getPeople.ts b/src/utils/proxy/getPeople.ts index e2ddf1dee..b283167b6 100644 --- a/src/utils/proxy/getPeople.ts +++ b/src/utils/proxy/getPeople.ts @@ -1,6 +1,22 @@ import axios from 'axios'; import { getToken } from './authManagement'; import { commonServicesConfig } from '@routes/proxy'; +import { isArray } from 'lodash'; + +/** + * Generate a filter to only fetch users we need + * + * @param people list of people + * @returns the filter to get people + */ +export const getPeopleFilter = (people: string[] | string) => { + people = isArray(people) ? people : [people]; + const formattedFilter = `{ +userid_in: +[${people.map((el) => `"${el}"`)}] +}`; + return formattedFilter.replace(/\s/g, ''); +}; /** * Fetches the people diff --git a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts index 1470e5f94..e0ff39bde 100644 --- a/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts +++ b/src/utils/schema/resolvers/Meta/getMetaPeopleResolver.ts @@ -1,6 +1,6 @@ import { Record } from '@models'; import { Context } from '@server/apollo/context'; -import { getPeople } from '@utils/proxy'; +import { getPeople, getPeopleFilter } from '@utils/proxy'; import { isArray } from 'lodash'; /** @@ -32,15 +32,7 @@ const getMetaPeopleResolver = async (field: any, context: Context) => { peopleIds.push(propertyValue); } }); - // Generate a filter to only fetch users we need - const getFilter = (people: any) => { - const formattedFilter = `{ - userid_in: - [${people.map((el: any) => `"${el}"`)}] - }`; - return formattedFilter.replace(/\s/g, ''); - }; - const filter = getFilter(peopleIds); + const filter = getPeopleFilter(peopleIds); const people = await getPeople(context.token, filter); if (!people) { return []; diff --git a/src/utils/schema/resolvers/Query/getSortAggregation.ts b/src/utils/schema/resolvers/Query/getSortAggregation.ts index d0bbd8ccb..dae8a8b8d 100644 --- a/src/utils/schema/resolvers/Query/getSortAggregation.ts +++ b/src/utils/schema/resolvers/Query/getSortAggregation.ts @@ -27,7 +27,10 @@ const getSortAggregation = async ( // If we need to populate choices to sort on the text value if ( field && - (field.choices || field.choicesByUrl || field.choicesByGraphQL) + (field.choices || + field.choicesByUrl || + field.choicesByGraphQL || + ['people, singlepeople'].includes(field.type)) ) { const choices = (await getFullChoices(field, context)) || []; const choicesValue = choices.map((x) => x.value); From 2031d4326c1279f00d96f584bf488efe056e56e1 Mon Sep 17 00:00:00 2001 From: MwanPygmay Date: Fri, 28 Jun 2024 09:49:57 +0200 Subject: [PATCH 26/26] add sorting for layouts --- src/utils/schema/resolvers/Query/all.ts | 35 +++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/utils/schema/resolvers/Query/all.ts b/src/utils/schema/resolvers/Query/all.ts index 0b8967648..1a8946145 100644 --- a/src/utils/schema/resolvers/Query/all.ts +++ b/src/utils/schema/resolvers/Query/all.ts @@ -12,7 +12,7 @@ import getStyle from './getStyle'; import getSortAggregation from './getSortAggregation'; import mongoose from 'mongoose'; import buildReferenceDataAggregation from '@utils/aggregation/buildReferenceDataAggregation'; -import { getAccessibleFields } from '@utils/form'; +import { getAccessibleFields, getFullChoices } from '@utils/form'; import buildCalculatedFieldPipeline from '@utils/aggregation/buildCalculatedFieldPipeline'; import { logger } from '@services/logger.service'; import checkPageSize from '@utils/schema/errors/checkPageSize.util'; @@ -191,8 +191,13 @@ const getQueryFields = ( * * @param records Records array to be sorted * @param sortArgs Sort arguments + * @param sortArgs.sortField sort field + * @param sortArgs.sortOrder sort order */ -const sortRecords = (records: any[], sortArgs: any): void => { +const sortRecords = ( + records: any[], + sortArgs: { sortField: string; sortOrder: 'asc' | 'desc' } +): void => { if (sortArgs.sortField && sortArgs.sortOrder) { const sortField = FLAT_DEFAULT_FIELDS.includes(sortArgs.sortField) ? sortArgs.sortField @@ -514,6 +519,32 @@ export default (entityName: string, fieldsByName: any, idsByName: any) => totalCount = aggregation[0]?.totalCount[0]?.count || 0; } + const fullSortField = fields.find((field) => field.name === sortField); + if ( + fullSortField && + ['people', 'singlepeople'].includes(fullSortField.type) + ) { + const peopleIds = items.map((item) => item.data[sortField]); + const choices = await getFullChoices(fullSortField, context, peopleIds); + + // Assuming choices is an array of objects like [{text, value}] + const choicesMap = new Map( + (choices as { value: string; text: string }[]).map((choice) => [ + choice.value as string, + choice.text as string, + ]) + ); + + // Sort the items based on the corresponding text field in choices + items.sort((a, b) => { + const textA = choicesMap.get(a.data[sortField]); + const textB = choicesMap.get(b.data[sortField]); + return sortOrder === 'asc' + ? textA.localeCompare(textB) + : -textA.localeCompare(textB); + }); + } + // Deal with resource/resources questions on THIS form const resourcesFields: any[] = fields.reduce((arr, field) => { if (field.type === 'resource' || field.type === 'resources') {