- Report Center Filters Version 1 retargeted to Master
Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -1,14 +1,16 @@
|
||||
import { gql } from "@apollo/client";
|
||||
import {gql} from "@apollo/client";
|
||||
import jsreport from "@jsreport/browser-client";
|
||||
import { notification } from "antd";
|
||||
import {notification} from "antd";
|
||||
import axios from "axios";
|
||||
import _ from "lodash";
|
||||
import { auth } from "../firebase/firebase.utils";
|
||||
import { setEmailOptions } from "../redux/email/email.actions";
|
||||
import { store } from "../redux/store";
|
||||
import {auth} from "../firebase/firebase.utils";
|
||||
import {setEmailOptions} from "../redux/email/email.actions";
|
||||
import {store} from "../redux/store";
|
||||
import client from "../utils/GraphQLClient";
|
||||
import cleanAxios from "./CleanAxios";
|
||||
import { TemplateList } from "./TemplateConstants";
|
||||
import {TemplateList} from "./TemplateConstants";
|
||||
import {applyFilters, applySorters, parseQuery, printQuery, wrapFiltersInAnd} from "./graphQLmodifier";
|
||||
|
||||
const server = process.env.REACT_APP_REPORTS_SERVER_URL;
|
||||
|
||||
jsreport.serverUrl = server;
|
||||
@@ -16,11 +18,11 @@ jsreport.serverUrl = server;
|
||||
const Templates = TemplateList();
|
||||
|
||||
export default async function RenderTemplate(
|
||||
templateObject,
|
||||
bodyshop,
|
||||
renderAsHtml = false,
|
||||
renderAsExcel = false,
|
||||
renderAsText = false
|
||||
templateObject,
|
||||
bodyshop,
|
||||
renderAsHtml = false,
|
||||
renderAsExcel = false,
|
||||
renderAsText = false
|
||||
) {
|
||||
if (window.jsr3) {
|
||||
jsreport.serverUrl = "https://reports3.test.imex.online/";
|
||||
@@ -30,41 +32,41 @@ export default async function RenderTemplate(
|
||||
jsreport.headers["Authorization"] = jsrAuth;
|
||||
|
||||
//Query assets that match the template name. Must be in format <<templateName>>.query
|
||||
let { contextData, useShopSpecificTemplate } = await fetchContextData(
|
||||
templateObject,
|
||||
jsrAuth
|
||||
let {contextData, useShopSpecificTemplate} = await fetchContextData(
|
||||
templateObject,
|
||||
jsrAuth
|
||||
);
|
||||
|
||||
const { ignoreCustomMargins } = Templates[templateObject.name];
|
||||
const {ignoreCustomMargins} = Templates[templateObject.name];
|
||||
|
||||
let reportRequest = {
|
||||
template: {
|
||||
name: useShopSpecificTemplate
|
||||
? `/${bodyshop.imexshopid}/${templateObject.name}`
|
||||
: `/${templateObject.name}`,
|
||||
? `/${bodyshop.imexshopid}/${templateObject.name}`
|
||||
: `/${templateObject.name}`,
|
||||
...(renderAsHtml
|
||||
? {}
|
||||
: {
|
||||
? {}
|
||||
: {
|
||||
recipe: "chrome-pdf",
|
||||
...(!ignoreCustomMargins && {
|
||||
chrome: {
|
||||
marginTop:
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.headerMargin &&
|
||||
bodyshop.logo_img_path.headerMargin > 36
|
||||
? bodyshop.logo_img_path.headerMargin
|
||||
: "36px",
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.headerMargin &&
|
||||
bodyshop.logo_img_path.headerMargin > 36
|
||||
? bodyshop.logo_img_path.headerMargin
|
||||
: "36px",
|
||||
marginBottom:
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.footerMargin &&
|
||||
bodyshop.logo_img_path.footerMargin > 50
|
||||
? bodyshop.logo_img_path.footerMargin
|
||||
: "50px",
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.footerMargin &&
|
||||
bodyshop.logo_img_path.footerMargin > 50
|
||||
? bodyshop.logo_img_path.footerMargin
|
||||
: "50px",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
...(renderAsExcel ? { recipe: "html-to-xlsx" } : {}),
|
||||
...(renderAsText ? { recipe: "text" } : {}),
|
||||
...(renderAsExcel ? {recipe: "html-to-xlsx"} : {}),
|
||||
...(renderAsText ? {recipe: "text"} : {}),
|
||||
},
|
||||
data: {
|
||||
...contextData,
|
||||
@@ -73,7 +75,7 @@ export default async function RenderTemplate(
|
||||
headerpath: `/${bodyshop.imexshopid}/header.html`,
|
||||
footerpath: `/${bodyshop.imexshopid}/footer.html`,
|
||||
bodyshop: bodyshop,
|
||||
offset: bodyshop.timezone, //moment().utcOffset(),
|
||||
offset: bodyshop.timezone, //dayjs().utcOffset(),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -82,8 +84,8 @@ export default async function RenderTemplate(
|
||||
|
||||
if (!renderAsHtml) {
|
||||
render.download(
|
||||
(Templates[templateObject.name] &&
|
||||
Templates[templateObject.name].title) ||
|
||||
(Templates[templateObject.name] &&
|
||||
Templates[templateObject.name].title) ||
|
||||
""
|
||||
);
|
||||
} else {
|
||||
@@ -97,17 +99,17 @@ export default async function RenderTemplate(
|
||||
...(!ignoreCustomMargins && {
|
||||
chrome: {
|
||||
marginTop:
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.headerMargin &&
|
||||
bodyshop.logo_img_path.headerMargin > 36
|
||||
? bodyshop.logo_img_path.headerMargin
|
||||
: "36px",
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.headerMargin &&
|
||||
bodyshop.logo_img_path.headerMargin > 36
|
||||
? bodyshop.logo_img_path.headerMargin
|
||||
: "36px",
|
||||
marginBottom:
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.footerMargin &&
|
||||
bodyshop.logo_img_path.footerMargin > 50
|
||||
? bodyshop.logo_img_path.footerMargin
|
||||
: "50px",
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.footerMargin &&
|
||||
bodyshop.logo_img_path.footerMargin > 50
|
||||
? bodyshop.logo_img_path.footerMargin
|
||||
: "50px",
|
||||
},
|
||||
}),
|
||||
},
|
||||
@@ -121,21 +123,21 @@ export default async function RenderTemplate(
|
||||
resolve({
|
||||
pdf,
|
||||
filename:
|
||||
Templates[templateObject.name] &&
|
||||
Templates[templateObject.name].title,
|
||||
Templates[templateObject.name] &&
|
||||
Templates[templateObject.name].title,
|
||||
html,
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
notification["error"]({ message: JSON.stringify(error) });
|
||||
notification["error"]({message: JSON.stringify(error)});
|
||||
}
|
||||
}
|
||||
|
||||
export async function RenderTemplates(
|
||||
templateObjects,
|
||||
bodyshop,
|
||||
renderAsHtml = false
|
||||
templateObjects,
|
||||
bodyshop,
|
||||
renderAsHtml = false
|
||||
) {
|
||||
//Query assets that match the template name. Must be in format <<templateName>>.query
|
||||
let unsortedTemplatesAndData = [];
|
||||
@@ -145,17 +147,18 @@ export async function RenderTemplates(
|
||||
|
||||
templateObjects.forEach((template) => {
|
||||
proms.push(
|
||||
(async () => {
|
||||
let { contextData, useShopSpecificTemplate } = await fetchContextData(
|
||||
template,
|
||||
jsrAuth
|
||||
);
|
||||
unsortedTemplatesAndData.push({
|
||||
templateObject: template,
|
||||
contextData,
|
||||
useShopSpecificTemplate,
|
||||
});
|
||||
})()
|
||||
(async () => {
|
||||
console.log(' RENDER TEMPLATE 2 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
|
||||
let {contextData, useShopSpecificTemplate} = await fetchContextData(
|
||||
template,
|
||||
jsrAuth
|
||||
);
|
||||
unsortedTemplatesAndData.push({
|
||||
templateObject: template,
|
||||
contextData,
|
||||
useShopSpecificTemplate,
|
||||
});
|
||||
})()
|
||||
);
|
||||
});
|
||||
await Promise.all(proms);
|
||||
@@ -172,8 +175,8 @@ export async function RenderTemplates(
|
||||
|
||||
unsortedTemplatesAndData.sort(function (a, b) {
|
||||
return (
|
||||
templateObjects.findIndex((x) => x.name === a.templateObject.name) -
|
||||
templateObjects.findIndex((x) => x.name === b.templateObject.name)
|
||||
templateObjects.findIndex((x) => x.name === a.templateObject.name) -
|
||||
templateObjects.findIndex((x) => x.name === b.templateObject.name)
|
||||
);
|
||||
});
|
||||
const templateAndData = unsortedTemplatesAndData;
|
||||
@@ -183,25 +186,25 @@ export async function RenderTemplates(
|
||||
let reportRequest = {
|
||||
template: {
|
||||
name: rootTemplate.useShopSpecificTemplate
|
||||
? `/${bodyshop.imexshopid}/${rootTemplate.templateObject.name}`
|
||||
: `/${rootTemplate.templateObject.name}`,
|
||||
? `/${bodyshop.imexshopid}/${rootTemplate.templateObject.name}`
|
||||
: `/${rootTemplate.templateObject.name}`,
|
||||
...(renderAsHtml
|
||||
? {}
|
||||
: {
|
||||
? {}
|
||||
: {
|
||||
recipe: "chrome-pdf",
|
||||
chrome: {
|
||||
marginTop:
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.headerMargin &&
|
||||
bodyshop.logo_img_path.headerMargin > 36
|
||||
? bodyshop.logo_img_path.headerMargin
|
||||
: "36px",
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.headerMargin &&
|
||||
bodyshop.logo_img_path.headerMargin > 36
|
||||
? bodyshop.logo_img_path.headerMargin
|
||||
: "36px",
|
||||
marginBottom:
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.footerMargin &&
|
||||
bodyshop.logo_img_path.footerMargin > 50
|
||||
? bodyshop.logo_img_path.footerMargin
|
||||
: "50px",
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.footerMargin &&
|
||||
bodyshop.logo_img_path.footerMargin > 50
|
||||
? bodyshop.logo_img_path.footerMargin
|
||||
: "50px",
|
||||
},
|
||||
}),
|
||||
pdfOperations: [
|
||||
@@ -218,22 +221,22 @@ export async function RenderTemplates(
|
||||
template: {
|
||||
chrome: {
|
||||
marginTop:
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.headerMargin &&
|
||||
bodyshop.logo_img_path.headerMargin > 36
|
||||
? bodyshop.logo_img_path.headerMargin
|
||||
: "36px",
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.headerMargin &&
|
||||
bodyshop.logo_img_path.headerMargin > 36
|
||||
? bodyshop.logo_img_path.headerMargin
|
||||
: "36px",
|
||||
marginBottom:
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.footerMargin &&
|
||||
bodyshop.logo_img_path.footerMargin > 50
|
||||
? bodyshop.logo_img_path.footerMargin
|
||||
: "50px",
|
||||
bodyshop.logo_img_path &&
|
||||
bodyshop.logo_img_path.footerMargin &&
|
||||
bodyshop.logo_img_path.footerMargin > 50
|
||||
? bodyshop.logo_img_path.footerMargin
|
||||
: "50px",
|
||||
},
|
||||
name: template.useShopSpecificTemplate
|
||||
? `/${bodyshop.imexshopid}/${template.templateObject.name}`
|
||||
: `/${template.templateObject.name}`,
|
||||
...(renderAsHtml ? {} : { recipe: "chrome-pdf" }),
|
||||
? `/${bodyshop.imexshopid}/${template.templateObject.name}`
|
||||
: `/${template.templateObject.name}`,
|
||||
...(renderAsHtml ? {} : {recipe: "chrome-pdf"}),
|
||||
},
|
||||
type: "append",
|
||||
|
||||
@@ -245,8 +248,8 @@ export async function RenderTemplates(
|
||||
},
|
||||
data: {
|
||||
...extend(
|
||||
rootTemplate.contextData,
|
||||
...templateAndData.map((temp) => temp.contextData)
|
||||
rootTemplate.contextData,
|
||||
...templateAndData.map((temp) => temp.contextData)
|
||||
),
|
||||
|
||||
// ...rootTemplate.templateObject.variables,
|
||||
@@ -266,29 +269,29 @@ export async function RenderTemplates(
|
||||
return render.toString();
|
||||
}
|
||||
} catch (error) {
|
||||
notification["error"]({ message: JSON.stringify(error) });
|
||||
notification["error"]({message: JSON.stringify(error)});
|
||||
}
|
||||
}
|
||||
|
||||
export const GenerateDocument = async (
|
||||
template,
|
||||
messageOptions,
|
||||
sendType,
|
||||
jobid
|
||||
template,
|
||||
messageOptions,
|
||||
sendType,
|
||||
jobid
|
||||
) => {
|
||||
const bodyshop = store.getState().user.bodyshop;
|
||||
if (sendType === "e") {
|
||||
store.dispatch(
|
||||
setEmailOptions({
|
||||
jobid,
|
||||
messageOptions: {
|
||||
...messageOptions,
|
||||
to: Array.isArray(messageOptions.to)
|
||||
? messageOptions.to
|
||||
: [messageOptions.to],
|
||||
},
|
||||
template,
|
||||
})
|
||||
setEmailOptions({
|
||||
jobid,
|
||||
messageOptions: {
|
||||
...messageOptions,
|
||||
to: Array.isArray(messageOptions.to)
|
||||
? messageOptions.to
|
||||
: [messageOptions.to],
|
||||
},
|
||||
template,
|
||||
})
|
||||
);
|
||||
} else if (sendType === "x") {
|
||||
console.log("excel");
|
||||
@@ -305,22 +308,75 @@ export const GenerateDocuments = async (templates) => {
|
||||
await RenderTemplates(templates, bodyshop);
|
||||
};
|
||||
|
||||
export const fetchFilterData = async ({name}) => {
|
||||
try {
|
||||
const bodyshop = store.getState().user.bodyshop;
|
||||
const jsrAuth = (await axios.post("/utils/jsr")).data;
|
||||
jsreport.headers["FirebaseAuthorization"] =
|
||||
"Bearer " + (await auth.currentUser.getIdToken());
|
||||
|
||||
const folders = await cleanAxios.get(`${server}/odata/folders`, {
|
||||
headers: {Authorization: jsrAuth},
|
||||
});
|
||||
const shopSpecificFolder = folders.data.value.find(
|
||||
(f) => f.name === bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const jsReportFilters = await cleanAxios.get(
|
||||
`${server}/odata/assets?$filter=name eq '${name}.filters'`,
|
||||
{headers: {Authorization: jsrAuth}}
|
||||
);
|
||||
console.log("🚀 ~ fetchFilterData ~ jsReportFilters:", jsReportFilters);
|
||||
|
||||
let parsedFilterData;
|
||||
let useShopSpecificTemplate = false;
|
||||
// let shopSpecificTemplate;
|
||||
|
||||
if (shopSpecificFolder) {
|
||||
let shopSpecificTemplate = jsReportFilters.data.value.find(
|
||||
(f) => f?.folder?.shortid === shopSpecificFolder.shortid
|
||||
);
|
||||
if (shopSpecificTemplate) {
|
||||
useShopSpecificTemplate = true;
|
||||
parsedFilterData = atob(shopSpecificTemplate.content);
|
||||
}
|
||||
}
|
||||
|
||||
if (!parsedFilterData) {
|
||||
const generalTemplate = jsReportFilters.data.value.find((f) => !f.folder);
|
||||
useShopSpecificTemplate = false;
|
||||
if (generalTemplate) parsedFilterData = atob(generalTemplate.content);
|
||||
}
|
||||
const data = JSON.parse(parsedFilterData);
|
||||
return {
|
||||
data,
|
||||
useShopSpecificTemplate,
|
||||
success: true,
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const fetchContextData = async (templateObject, jsrAuth) => {
|
||||
console.log(' FETCH CONTEXT DATA !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
|
||||
const bodyshop = store.getState().user.bodyshop;
|
||||
|
||||
jsreport.headers["FirebaseAuthorization"] =
|
||||
"Bearer " + (await auth.currentUser.getIdToken());
|
||||
"Bearer " + (await auth.currentUser.getIdToken());
|
||||
|
||||
const folders = await cleanAxios.get(`${server}/odata/folders`, {
|
||||
headers: { Authorization: jsrAuth },
|
||||
headers: {Authorization: jsrAuth},
|
||||
});
|
||||
const shopSpecificFolder = folders.data.value.find(
|
||||
(f) => f.name === bodyshop.imexshopid
|
||||
(f) => f.name === bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const jsReportQueries = await cleanAxios.get(
|
||||
`${server}/odata/assets?$filter=name eq '${templateObject.name}.query'`,
|
||||
{ headers: { Authorization: jsrAuth } }
|
||||
`${server}/odata/assets?$filter=name eq '${templateObject.name}.query'`,
|
||||
{headers: {Authorization: jsrAuth}}
|
||||
);
|
||||
|
||||
let templateQueryToExecute;
|
||||
@@ -329,7 +385,7 @@ const fetchContextData = async (templateObject, jsrAuth) => {
|
||||
|
||||
if (shopSpecificFolder) {
|
||||
let shopSpecificTemplate = jsReportQueries.data.value.find(
|
||||
(f) => f?.folder?.shortid === shopSpecificFolder.shortid
|
||||
(f) => f?.folder?.shortid === shopSpecificFolder.shortid
|
||||
);
|
||||
if (shopSpecificTemplate) {
|
||||
useShopSpecificTemplate = true;
|
||||
@@ -343,16 +399,58 @@ const fetchContextData = async (templateObject, jsrAuth) => {
|
||||
templateQueryToExecute = atob(generalTemplate.content);
|
||||
}
|
||||
|
||||
console.log('Template Object');
|
||||
console.dir(templateObject);
|
||||
console.log('Unmodified Query');
|
||||
console.dir(templateQueryToExecute);
|
||||
|
||||
|
||||
// We have no template filters or sorters, so we can just execute the query and return the data
|
||||
if ((!templateObject?.filters && !templateObject?.filters?.length && !templateObject?.sorters && !templateObject?.sorters?.length)) {
|
||||
console.log('No filters or sorters');
|
||||
let contextData = {};
|
||||
if (templateQueryToExecute) {
|
||||
const {data} = await client.query({
|
||||
query: gql(templateQueryToExecute),
|
||||
variables: {...templateObject.variables},
|
||||
});
|
||||
contextData = data;
|
||||
}
|
||||
|
||||
return {contextData, useShopSpecificTemplate};
|
||||
}
|
||||
|
||||
// Parse the query and apply the filters and sorters
|
||||
const ast = parseQuery(templateQueryToExecute);
|
||||
|
||||
let filterFields = [];
|
||||
|
||||
if (templateObject?.filters && templateObject?.filters?.length) {
|
||||
console.log('Applying filters')
|
||||
applyFilters(ast, templateObject.filters, filterFields);
|
||||
wrapFiltersInAnd(ast, filterFields);
|
||||
}
|
||||
|
||||
if (templateObject?.sorters && templateObject?.sorters?.length) {
|
||||
console.log('Applying sorters')
|
||||
applySorters(ast, templateObject.sorters);
|
||||
}
|
||||
|
||||
const finalQuery = printQuery(ast);
|
||||
|
||||
console.log('Modified Query');
|
||||
console.log(finalQuery);
|
||||
|
||||
let contextData = {};
|
||||
if (templateQueryToExecute) {
|
||||
const { data } = await client.query({
|
||||
query: gql(templateQueryToExecute),
|
||||
variables: { ...templateObject.variables },
|
||||
const {data} = await client.query({
|
||||
query: gql(finalQuery),
|
||||
variables: {...templateObject.variables},
|
||||
});
|
||||
contextData = data;
|
||||
}
|
||||
|
||||
return { contextData, useShopSpecificTemplate };
|
||||
return {contextData, useShopSpecificTemplate};
|
||||
};
|
||||
|
||||
//export const displayTemplateInWindow = (html) => {
|
||||
@@ -389,7 +487,7 @@ const fetchContextData = async (templateObject, jsrAuth) => {
|
||||
|
||||
function extend(o1, o2, o3) {
|
||||
var result = {},
|
||||
obj;
|
||||
obj;
|
||||
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
obj = arguments[i];
|
||||
@@ -405,4 +503,4 @@ function extend(o1, o2, o3) {
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
309
client/src/utils/graphQLmodifier.js
Normal file
309
client/src/utils/graphQLmodifier.js
Normal file
@@ -0,0 +1,309 @@
|
||||
import {Kind, parse, print, visit} from "graphql";
|
||||
|
||||
const STRING_OPERATORS = [
|
||||
{value: "_eq", label: "equals"},
|
||||
{value: "_neq", label: "does not equal"},
|
||||
{value: "_like", label: "contains"},
|
||||
{value: "_nlike", label: "does not contain"},
|
||||
{value: "_ilike", label: "contains case-insensitive"},
|
||||
{value: "_nilike", label: "does not contain case-insensitive"}
|
||||
];
|
||||
const NUMBER_OPERATORS = [
|
||||
{value: "_eq", label: "equals"},
|
||||
{value: "_neq", label: "does not equal"},
|
||||
{value: "_gt", label: "greater than"},
|
||||
{value: "_lt", label: "less than"},
|
||||
{value: "_gte", label: "greater than or equal"},
|
||||
{value: "_lte", label: "less than or equal"}
|
||||
];
|
||||
|
||||
export function getOperatorsByType(type = 'string') {
|
||||
const operators = {
|
||||
string: STRING_OPERATORS,
|
||||
number: NUMBER_OPERATORS
|
||||
};
|
||||
return operators[type];
|
||||
}
|
||||
|
||||
/* eslint-disable no-loop-func */
|
||||
|
||||
/**
|
||||
* Parse a GraphQL query into an AST
|
||||
* @param query
|
||||
* @returns {DocumentNode}
|
||||
*/
|
||||
export function parseQuery(query) {
|
||||
return parse(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print an AST back into a GraphQL query
|
||||
* @param query
|
||||
* @returns {string}
|
||||
*/
|
||||
export function printQuery(query) {
|
||||
return print(query);
|
||||
}
|
||||
/**
|
||||
* Apply sorters to the AST
|
||||
* @param ast
|
||||
* @param sorters
|
||||
*/
|
||||
export function applySorters(ast, sorters) {
|
||||
sorters.forEach((sorter) => {
|
||||
const fieldPath = sorter.field.split('.');
|
||||
visit(ast, {
|
||||
OperationDefinition: {
|
||||
enter(node) {
|
||||
// Loop through each sorter to apply it
|
||||
// noinspection DuplicatedCode
|
||||
|
||||
let currentSelection = node; // Start with the root operation
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Apply the sorter at the correct level
|
||||
if (currentSelection) {
|
||||
const targetFieldName = fieldPath[fieldPath.length - 1];
|
||||
let orderByArg = currentSelection.arguments.find(arg => arg.name.value === 'order_by');
|
||||
if (!orderByArg) {
|
||||
orderByArg = {
|
||||
kind: Kind.ARGUMENT,
|
||||
name: { kind: Kind.NAME, value: 'order_by' },
|
||||
value: { kind: Kind.OBJECT, fields: [] },
|
||||
};
|
||||
currentSelection.arguments.push(orderByArg);
|
||||
}
|
||||
|
||||
const sorterField = {
|
||||
kind: Kind.OBJECT_FIELD,
|
||||
name: { kind: Kind.NAME, value: targetFieldName },
|
||||
value: { kind: Kind.ENUM, value: sorter.direction }, // Adjust if your schema uses a different type for sorting directions
|
||||
};
|
||||
|
||||
// Add the new sorter condition
|
||||
orderByArg.value.fields.push(sorterField);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filters to the AST
|
||||
* @param ast
|
||||
* @param filters
|
||||
*/
|
||||
export function applyFilters(ast, filters) {
|
||||
return visit(ast, {
|
||||
OperationDefinition: {
|
||||
enter(node) {
|
||||
filters.forEach(filter => {
|
||||
const fieldPath = filter.field.split('.');
|
||||
let topLevel = false;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (topLevel) {
|
||||
// Construct the filter for a top-level application
|
||||
const targetFieldName = fieldPath[fieldPath.length - 1];
|
||||
const filterValue = {
|
||||
kind: getGraphQLKind(filter.value),
|
||||
value: filter.value,
|
||||
};
|
||||
|
||||
const nestedFilter = {
|
||||
kind: Kind.OBJECT_FIELD,
|
||||
name: { kind: Kind.NAME, value: targetFieldName },
|
||||
value: {
|
||||
kind: Kind.OBJECT,
|
||||
fields: [{
|
||||
kind: Kind.OBJECT_FIELD,
|
||||
name: { kind: Kind.NAME, value: filter.operator },
|
||||
value: filterValue,
|
||||
}],
|
||||
},
|
||||
};
|
||||
|
||||
// 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
|
||||
* @returns {Kind|Kind.INT}
|
||||
*/
|
||||
function getGraphQLKind(value) {
|
||||
if (typeof value === 'number') {
|
||||
return value % 1 === 0 ? Kind.INT : Kind.FLOAT;
|
||||
} else if (typeof value === 'boolean') {
|
||||
return Kind.BOOLEAN;
|
||||
} else if (typeof value === 'string') {
|
||||
return Kind.STRING;
|
||||
}
|
||||
// Extend with more types as needed
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap filters in an 'and' object
|
||||
* @param ast
|
||||
* @param filterFields
|
||||
*/
|
||||
export function wrapFiltersInAnd(ast, filterFields) {
|
||||
visit(ast, {
|
||||
OperationDefinition: {
|
||||
enter(node) {
|
||||
node.selectionSet.selections.forEach((selection) => {
|
||||
let whereArg = selection.arguments.find(arg => arg.name.value === 'where');
|
||||
if (filterFields.length > 1) {
|
||||
const andFilter = {
|
||||
kind: Kind.OBJECT_FIELD,
|
||||
name: {kind: Kind.NAME, value: '_and'},
|
||||
value: {kind: Kind.LIST, values: filterFields}
|
||||
};
|
||||
whereArg.value.fields.push(andFilter);
|
||||
} else if (filterFields.length === 1) {
|
||||
whereArg.value.fields.push(filterFields[0].fields[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* eslint-enable no-loop-func */
|
||||
Reference in New Issue
Block a user