diff --git a/client/src/utils/graphQLmodifier.js b/client/src/utils/graphQLmodifier.js index 3ff134a8a..805e62215 100644 --- a/client/src/utils/graphQLmodifier.js +++ b/client/src/utils/graphQLmodifier.js @@ -113,46 +113,25 @@ export function applyFilters(ast, filters) { return visit(ast, { OperationDefinition: { enter(node) { - // Loop through each filter to apply it filters.forEach(filter => { const fieldPath = filter.field.split('.'); - let currentSelection = node; // Start with the root operation + let topLevel = false; - // Navigate down the field path to the correct location - for (let i = 0; i < fieldPath.length - 1; i++) { - let found = false; - visit(currentSelection, { - Field: { - enter(node) { - if (node.name.value === fieldPath[i]) { - currentSelection = node; // Move down to the next level - found = true; - } - } - } - }); - if (!found) break; // Stop if we can't find the next field in the path + // Determine if the filter should be applied at the top level + if (fieldPath[0].startsWith('[') && fieldPath[0].endsWith(']')) { + fieldPath[0] = fieldPath[0].substring(1, fieldPath[0].length - 1); // Strip the brackets + topLevel = true; } - // Apply the filter at the correct level - if (currentSelection) { + if (topLevel) { + // Construct the filter for a top-level application const targetFieldName = fieldPath[fieldPath.length - 1]; - let whereArg = currentSelection.arguments.find(arg => arg.name.value === 'where'); - if (!whereArg) { - whereArg = { - kind: Kind.ARGUMENT, - name: { kind: Kind.NAME, value: 'where' }, - value: { kind: Kind.OBJECT, fields: [] }, - }; - currentSelection.arguments.push(whereArg); - } - const filterValue = { kind: getGraphQLKind(filter.value), value: filter.value, }; - const filterField = { + const nestedFilter = { kind: Kind.OBJECT_FIELD, name: { kind: Kind.NAME, value: targetFieldName }, value: { @@ -165,15 +144,125 @@ export function applyFilters(ast, filters) { }, }; - // Add the new filter condition - whereArg.value.fields.push(filterField); + // Find or create the where argument for the top-level field + let whereArg = node.selectionSet.selections + .find(selection => selection.name.value === fieldPath[0]) + ?.arguments.find(arg => arg.name.value === 'where'); + + if (!whereArg) { + whereArg = { + kind: Kind.ARGUMENT, + name: { kind: Kind.NAME, value: 'where' }, + value: { kind: Kind.OBJECT, fields: [] }, + }; + const topLevelSelection = node.selectionSet.selections.find(selection => + selection.name.value === fieldPath[0] + ); + if (topLevelSelection) { + topLevelSelection.arguments = topLevelSelection.arguments || []; + topLevelSelection.arguments.push(whereArg); + } + } + + // Correctly position the nested filter without an extra 'where' + if (fieldPath.length > 2) { // More than one level deep + let currentField = whereArg.value; + fieldPath.slice(1, -1).forEach((path, index) => { + let existingField = currentField.fields.find(f => f.name.value === path); + if (!existingField) { + existingField = { + kind: Kind.OBJECT_FIELD, + name: { kind: Kind.NAME, value: path }, + value: { kind: Kind.OBJECT, fields: [] } + }; + currentField.fields.push(existingField); + } + currentField = existingField.value; + }); + currentField.fields.push(nestedFilter); + } else { // Directly under the top level + whereArg.value.fields.push(nestedFilter); + } + } else { + // Initialize a reference to the current selection to traverse down the AST + let currentSelection = node; + let whereArgFound = false; + + // Iterate over the fieldPath, except for the last entry, to navigate the structure + for (let i = 0; i < fieldPath.length - 1; i++) { + const fieldName = fieldPath[i]; + let fieldFound = false; + + // Check if the current selection has a selectionSet and selections + if (currentSelection.selectionSet && currentSelection.selectionSet.selections) { + // Look for the field in the current selection's selections + const selection = currentSelection.selectionSet.selections.find(sel => sel.name.value === fieldName); + if (selection) { + // Move down the AST to the found selection + currentSelection = selection; + fieldFound = true; + } + } + + // If the field was not found in the current path, it's an issue + if (!fieldFound) { + console.error(`Field ${fieldName} not found in the current selection.`); + return; // Exit the loop and function due to error + } + } + + // At this point, currentSelection should be the parent field where the filter needs to be applied + // Check if the 'where' argument already exists in the current selection + const whereArg = currentSelection.arguments.find(arg => arg.name.value === 'where'); + if (whereArg) { + whereArgFound = true; + } else { + // If not found, create a new 'where' argument for the current selection + currentSelection.arguments.push({ + kind: Kind.ARGUMENT, + name: { kind: Kind.NAME, value: 'where' }, + value: { kind: Kind.OBJECT, fields: [] } // Empty fields array to be populated with the filter + }); + } + + // Assuming the last entry in fieldPath is the field to apply the filter on + const targetField = fieldPath[fieldPath.length - 1]; + const filterValue = { + kind: getGraphQLKind(filter.value), + value: filter.value, + }; + + // Construct the filter field object + const filterField = { + kind: Kind.OBJECT_FIELD, + name: { kind: Kind.NAME, value: targetField }, + value: { + kind: Kind.OBJECT, + fields: [{ + kind: Kind.OBJECT_FIELD, + name: { kind: Kind.NAME, value: filter.operator }, + value: filterValue, + }], + }, + }; + + // Add the filter field to the 'where' clause of the current selection + if (whereArgFound) { + whereArg.value.fields.push(filterField); + } else { + // If the whereArg was newly created, find it again (since we didn't store its reference) and add the filter + currentSelection.arguments.find(arg => arg.name.value === 'where').value.fields.push(filterField); + } } + }); } } }); } + + /** * Get the GraphQL kind for a value * @param value