Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/AB#74292 People picker #725

Open
wants to merge 35 commits into
base: 2.x.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a164834
mock data
TaiKamilla Sep 4, 2023
8c5d92a
Merge branch '2.1.x' into feat/AB#74292_ABC-Create-people-picker-ques…
NathanHGit Sep 6, 2023
d486e03
Merge branch 'beta' into feat/AB#74292_ABC-Create-people-picker-question
MwanPygmay Sep 26, 2023
3647e3a
type not supported anymore, changed
MwanPygmay Sep 26, 2023
d93b78b
add support for people question: get type and metadata correctly, ali…
MwanPygmay Sep 27, 2023
54937ce
Merge branch 'beta' into feat/AB#74292_ABC-Create-people-picker-question
TaiKamilla Oct 3, 2023
a84fecf
Merge remote-tracking branch 'origin/beta' into feat/AB#74292_ABC-Cre…
TaiKamilla Oct 4, 2023
189c5a1
type
TaiKamilla Oct 5, 2023
2115ffb
missing resolver change
TaiKamilla Oct 5, 2023
3c3e00c
it works
TaiKamilla Oct 6, 2023
52ae447
compact the code a bit
TaiKamilla Oct 6, 2023
6602426
Clean 🧼 🧽
TaiKamilla Oct 9, 2023
21522f0
Merge branch '2.x.x' into feat/AB#74292_ABC-Create-people-picker-ques…
NathanHGit Mar 20, 2024
ae83129
feat: added people type
NathanHGit Mar 20, 2024
d5f2cea
feat: add get people
NathanHGit Mar 22, 2024
17c52d8
feat: handle people filtering and add people resolver
NathanHGit Mar 25, 2024
2c5088c
review people filter format + unique ids
NathanHGit Mar 25, 2024
05c65cd
add context to resolvers
NathanHGit Mar 26, 2024
8e5b164
fix people resolver
NathanHGit Mar 27, 2024
e3c999a
delete field.resource
NathanHGit Mar 27, 2024
0ea3ab0
fix empty value
NathanHGit Mar 27, 2024
716011b
Merge branch '2.x.x' into feat/AB#74292_ABC-Create-people-picker-ques…
AntoineRelief Apr 24, 2024
fe3d1fc
now getting people ten by ten
MwanPygmay Apr 30, 2024
f5d146e
update meta, and correct export
MwanPygmay May 3, 2024
d4ea0f5
add support for summary cards and text editor
MwanPygmay May 3, 2024
089a94d
avoid getting a back-to-back query
MwanPygmay May 21, 2024
acc3dee
Merge branch '2.x.x' into feat/AB#74292_ABC-Create-people-picker-ques…
AntoineRelief Jun 3, 2024
09be020
update config
AntoineRelief Jun 4, 2024
8f6eed0
Merge branch '2.x.x' into feat/AB#74292_ABC-Create-people-picker-ques…
AntoineRelief Jun 14, 2024
df67577
optimize people query
AntoineRelief Jun 14, 2024
0c7dc32
add support for graphs
MwanPygmay Jun 18, 2024
6906094
remove console log
MwanPygmay Jun 19, 2024
dcf1c7a
Merge branch '2.x.x' into feat/AB#74292_ABC-Create-people-picker-ques…
AntoineRelief Jun 26, 2024
7893c10
add support for filter, record history. Sorting does not work yet
MwanPygmay Jun 27, 2024
2031d43
add sorting for layouts
MwanPygmay Jun 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/custom-environment-variables.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ module.exports = {
clientId: 'COMMON_SERVICES_CLIENT_ID',
clientSecret: 'COMMON_SERVICES_CLIENT_SECRET',
scope: 'COMMON_SERVICES_SCOPE',
url: 'COMMON_SERVICES_URL',
},
microsoftGraph: {
tokenEndpoint: 'MICROSOFT_GRAPH_TOKEN_ENDPOINT',
Expand Down
1 change: 1 addition & 0 deletions src/const/fieldTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const MULTISELECT_TYPES: string[] = [
'tagbox',
'owner',
'users',
'people',
];

/** List of date field types */
Expand Down
5 changes: 3 additions & 2 deletions src/routes/download/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
27 changes: 27 additions & 0 deletions src/routes/proxy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>('commonServices.url'),
settings: CryptoJS.AES.encrypt(
JSON.stringify({
authTargetUrl: config.get<string>('commonServices.tokenEndpoint'),
apiClientID: config.get<string>('commonServices.clientId'),
scope: config.get<string>('commonServices.scope'),
safeSecret: config.get<string>('commonServices.clientSecret'),
}),
config.get('encryption.key')
).toString(),
});

/**
* Proxy API request
*
Expand Down Expand Up @@ -171,6 +188,16 @@ router.post('/ping/**', async (req: Request, res: Response) => {
}
});

router.all('/common-services/**', async (req, res) => {
try {
const path = req.originalUrl.split('common-services').pop().substring(1);
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'));
}
});

/**
* Forward requests to actual API using the API Configuration
*/
Expand Down
2 changes: 2 additions & 0 deletions src/schema/query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import referenceDataAggregation from './referenceDataAggregation.query';
import dataset from './dataset.query';
import emailNotifications from './emailNotifications.query';
import types from './types.query';
import people from './people.query';

/** GraphQL query type definition */
const Query = new GraphQLObjectType({
Expand Down Expand Up @@ -88,6 +89,7 @@ const Query = new GraphQLObjectType({
layers,
layer,
draftRecords,
people,
},
});

Expand Down
67 changes: 67 additions & 0 deletions src/schema/query/people.query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { GraphQLError, GraphQLInt, 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;
offset?: number;
limitItems?: number | null;
};

/**
* Return distant users from common services
*/
export default {
type: new GraphQLList(PersonType),
args: {
filter: { type: GraphQLJSON },
offset: { type: GraphQLInt },
limitItems: { type: GraphQLInt },
},
async resolve(parent, args: PeopleArgs, context: Context) {
if (!args.filter) return [];
try {
// 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,
args.offset,
args.limitItems
);
if (people) {
return people.map((person) => {
const updatedPerson = { ...person };
updatedPerson.id = updatedPerson.userid;
delete updatedPerson.userid;
return updatedPerson;
});
}
return [];
} catch (err) {
logger.error(err.message, { stack: err.stack });
throw new GraphQLError(
context.i18next.t('common.errors.internalServerError')
);
}
},
};
3 changes: 2 additions & 1 deletion src/schema/types/draftRecord.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
14 changes: 14 additions & 0 deletions src/schema/types/person.type.ts
Original file line number Diff line number Diff line change
@@ -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 },
}),
});
3 changes: 2 additions & 1 deletion src/schema/types/record.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/schema/types/resource.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ export const ResourceType = new GraphQLObjectType({
{ id: r._id }
),
}));

return {
pageInfo: {
hasNextPage,
Expand Down
15 changes: 11 additions & 4 deletions src/utils/aggregation/setDisplayText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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<any>(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) {
Expand Down
28 changes: 27 additions & 1 deletion src/utils/files/getRows.ts
Original file line number Diff line number Diff line change
@@ -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<any[]> => {
const rows = [];
for (const record of records) {
Expand Down Expand Up @@ -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)
Expand Down
14 changes: 4 additions & 10 deletions src/utils/files/getRowsFromMeta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 || [];
Expand Down Expand Up @@ -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);
Expand Down
24 changes: 22 additions & 2 deletions src/utils/form/getDisplayText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, getPeopleFilter } from '@utils/proxy';

/**
* Gets display text from choice value.
Expand Down Expand Up @@ -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[] | string
): Promise<{ value: string; text: string }[] | string[]> => {
try {
if (field.choicesByUrl) {
Expand Down Expand Up @@ -110,6 +113,23 @@ 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 filter = getPeopleFilter(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;
}
Expand All @@ -133,7 +153,7 @@ const getDisplayText = async (
context: Context
): Promise<string | string[]> => {
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));
Expand Down
4 changes: 4 additions & 0 deletions src/utils/form/getFieldType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ export const getFieldType = async (question: {
return 'users';
case 'owner':
return 'owner';
case 'people':
return 'people';
case 'singlepeople':
return 'singlepeople';
case 'geospatial':
return 'geospatial';
default:
Expand Down
Loading
Loading