Skip to content
2 changes: 2 additions & 0 deletions src/models/form.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ schema.index(
{ unique: true, partialFilterExpression: { core: true } }
);
schema.index({ graphQLTypeName: 1 }, { unique: true });
schema.index({ createdAt: 1 });

schema.plugin(accessibleRecordsPlugin);

/** Mongoose form model definition */
Expand Down
9 changes: 8 additions & 1 deletion src/models/record.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,14 @@ recordSchema.index(
{ incrementalId: 1, resource: 1 },
{ unique: true, partialFilterExpression: { resource: { $exists: true } } }
);
recordSchema.index({ resource: 1 });

recordSchema.index({ '$**': 'text' });
recordSchema.index({ 'data.$**': 1 });

recordSchema.index({ archived: 1, form: 1, resource: 1, createdAt: 1 });
recordSchema.index({ resource: 1, archived: 1 });
recordSchema.index({ createdAt: 1 });
recordSchema.index({ form: 1 });

// handle cascading deletion
addOnBeforeDeleteMany(recordSchema, async (records) => {
Expand Down
2 changes: 2 additions & 0 deletions src/models/resource.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ addOnBeforeDeleteMany(resourceSchema, async (resources) => {
}
});

resourceSchema.index({ createdAt: 1 });

resourceSchema.plugin(accessibleRecordsPlugin);

/** Mongoose resource model definition */
Expand Down
20 changes: 0 additions & 20 deletions src/schema/mutation/editResource.mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { Resource } from '@models';
import { AppAbility } from '@security/defineUserAbility';
import { get, has, isArray, isEqual, isNil } from 'lodash';
import { logger } from '@services/logger.service';
import buildCalculatedFieldPipeline from '@utils/aggregation/buildCalculatedFieldPipeline';
import { graphQLAuthCheck } from '@schema/shared';
import { Context } from '@server/apollo/context';

Expand Down Expand Up @@ -707,25 +706,6 @@ export default {
if (args.calculatedField) {
const calculatedField: CalculatedFieldChange = args.calculatedField;

// Check if calculated field expression is too long
if (calculatedField.add || calculatedField.update) {
const expression =
calculatedField.add?.expression ??
calculatedField.update?.expression;
const pipeline = buildCalculatedFieldPipeline(
expression,
'',
context.timeZone
);
if (pipeline[0].$facet.calcFieldFacet.length > 50) {
throw new GraphQLError(
context.i18next.t(
'mutations.resource.edit.errors.calculatedFieldTooLong'
)
);
}
}

// Add new calculated field
if (calculatedField.add) {
const expression = getExpressionFromString(
Expand Down
16 changes: 1 addition & 15 deletions src/utils/aggregation/buildCalculatedFieldPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,21 +515,7 @@ const buildCalculatedFieldPipeline = (
) => {
const operation = getExpressionFromString(expression);
const pipeline = buildPipeline(operation, name, timeZone);
return [
{
$facet: {
calcFieldFacet: pipeline,
},
} as PipelineStage.Facet,
{
$unwind: '$calcFieldFacet',
} as PipelineStage.Unwind,
{
$replaceRoot: {
newRoot: '$calcFieldFacet',
},
} as PipelineStage.ReplaceRoot,
] as const;
return pipeline;
};

export default buildCalculatedFieldPipeline;
109 changes: 68 additions & 41 deletions src/utils/schema/resolvers/Query/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import getFilter, {
FLAT_DEFAULT_FIELDS,
extractFilterFields,
} from './getFilter';
import getSearchFilter from './getSearchFilter';
import getStyle from './getStyle';
import getSortAggregation from './getSortAggregation';
import mongoose from 'mongoose';
Expand Down Expand Up @@ -65,21 +66,6 @@ const projectAggregation = [
},
];

/** Default aggregation common to all records to make lookups for default fields. */
const defaultRecordAggregation = [
{ $addFields: { id: { $toString: '$_id' } } },
{
$addFields: {
'_createdBy.user.id': { $toString: '$_createdBy.user._id' },
},
},
{
$addFields: {
'_lastUpdatedBy.user.id': { $toString: '$_lastUpdatedBy.user._id' },
},
},
];

/**
* Build At aggregation, filtering out items created after this date, and using version that matches date
*
Expand Down Expand Up @@ -140,6 +126,23 @@ const getAtAggregation = (at: Date) => {
];
};

/**
*
*/
const defaultRecordAggregation = [
{ $addFields: { id: { $toString: '$_id' } } },
{
$addFields: {
'_createdBy.user.id': { $toString: '$_createdBy.user._id' },
},
},
{
$addFields: {
'_lastUpdatedBy.user.id': { $toString: '$_lastUpdatedBy.user._id' },
},
},
];

/**
* Get queried fields from query definition
*
Expand Down Expand Up @@ -400,7 +403,7 @@ export default (entityName: string, fieldsByName: any, idsByName: any) =>
// Add the basic records filter
const basicFilters = {
$or: [{ resource: id }, { form: id }],
archived: { $ne: true },
archived: { $not: { $eq: true } },
};

// Additional filter from the user permissions
Expand All @@ -417,37 +420,55 @@ export default (entityName: string, fieldsByName: any, idsByName: any) =>

// Finally putting all filters together
const filters = {
$and: [mongooseFilter, permissionFilters],
$and: [basicFilters, mongooseFilter, permissionFilters],
};

const searchFilter = getSearchFilter(filter, fields, context);

// === RUN AGGREGATION TO FETCH ITEMS ===
let items: Record[] = [];
let totalCount = 0;

// If we're using skip parameter, include them into the aggregation
if (skip || skip === 0) {
const pipeline = [
{ $match: basicFilters },
...(at ? getAtAggregation(new Date(at)) : []),
...linkedRecordsAggregation,
...linkedReferenceDataAggregation,
...defaultRecordAggregation,
...calculatedFieldsAggregation,
{ $match: filters },
...projectAggregation,
...(await getSortAggregation(sortField, sortOrder, fields, context)),
{
$facet: {
items: [{ $skip: skip }, { $limit: first + 1 }],
totalCount: [
{
$count: 'count',
},
],
const sort = await getSortAggregation(
sortField,
sortOrder,
fields,
context
);
let pipeline = [];
if (searchFilter) {
pipeline = [
searchFilter,
...calculatedFieldsAggregation,
...defaultRecordAggregation,
{ $match: filters },
...(at ? getAtAggregation(new Date(at)) : []),
];
} else {
pipeline = [
...calculatedFieldsAggregation,
...defaultRecordAggregation,
{ $match: filters },
...(at ? getAtAggregation(new Date(at)) : []),
];
}
const aggregation = await Record.aggregate(pipeline).facet({
items: [
...linkedRecordsAggregation,
...linkedReferenceDataAggregation,
...sort,
...projectAggregation,
{ $skip: skip },
{ $limit: first + 1 },
],
totalCount: [
{
$count: 'count',
},
},
];
const aggregation = await Record.aggregate(pipeline);
],
});
items = aggregation[0].items;
totalCount = aggregation[0]?.totalCount[0]?.count || 0;
} else {
Expand All @@ -463,12 +484,18 @@ export default (entityName: string, fieldsByName: any, idsByName: any) =>
{ $match: basicFilters },
...linkedRecordsAggregation,
...linkedReferenceDataAggregation,
...defaultRecordAggregation,
...(await getSortAggregation(sortField, sortOrder, fields, context)),
{ $match: { $and: [filters, cursorFilters] } },
{
$facet: {
results: [{ $limit: first + 1 }],
results: [
...(await getSortAggregation(
sortField,
sortOrder,
fields,
context
)),
{ $limit: first + 1 },
],
totalCount: [
{
$count: 'count',
Expand Down
20 changes: 15 additions & 5 deletions src/utils/schema/resolvers/Query/getFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import mongoose from 'mongoose';
import { getDateForMongo } from '@utils/filter/getDateForMongo';
import { getTimeForMongo } from '@utils/filter/getTimeForMongo';
import { MULTISELECT_TYPES, DATE_TYPES } from '@const/fieldTypes';
import { isNumber } from 'lodash';

/** The default fields */
const DEFAULT_FIELDS = [
Expand Down Expand Up @@ -351,11 +352,19 @@ const buildMongoFilter = (
}
case 'contains': {
if (MULTISELECT_TYPES.includes(type)) {
if (Array.isArray(value)) {
return { [fieldName]: { $all: value } };
} else {
return { [fieldName]: { $all: [value] } };
}
return { [fieldName]: { $all: value } };
// Check if a number has been searched globally
// If so, perform an 'eq' search
} else if (isNumber(value?.[0]?.value)) {
const eq = value.map((v) => {
return { [`data.${v.field}`]: { $eq: v.value } };
});
return { $or: eq };
} else if (
fieldName === 'data._globalSearch' &&
(type === 'text' || type === '')
) {
return;
} else {
return { [fieldName]: { $regex: value, $options: 'i' } };
}
Expand Down Expand Up @@ -483,5 +492,6 @@ export default (
const expandedFields = fields.concat(DEFAULT_FIELDS);
const mongooseFilter =
buildMongoFilter(filter, expandedFields, context, prefix) || {};

return mongooseFilter;
};
Loading