mirror of
https://github.com/directus/directus.git
synced 2026-02-11 17:46:02 -05:00
Fix insights filtering (#16139)
* Parse string filter as JSON * Parse content as JSON when toggling raw editor * Refactor missing Dashboard type * Convert filter to gql format * Disable alias field filter selection in insights * Use parseJSON util * Refactor to allow selectivity of relational field for GraphQL filters * Emit variables * Skip emitting invalid JSON * Add unit tests
This commit is contained in:
134
app/src/utils/query-to-gql-string.test.ts
Normal file
134
app/src/utils/query-to-gql-string.test.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { beforeEach, describe, expect, test } from 'vitest';
|
||||
|
||||
import { formatQuery } from '@/utils/query-to-gql-string';
|
||||
|
||||
import { setActivePinia } from 'pinia';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { Query } from '@directus/shared/types';
|
||||
|
||||
const collectionName = 'users';
|
||||
const primaryKeyField = 'id';
|
||||
const key = 'query_abcde';
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(
|
||||
createTestingPinia({
|
||||
createSpy: () => (collection) => {
|
||||
return { collection, field: primaryKeyField };
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
describe('Empty query returns the primary key', () => {
|
||||
test.each([true, false])(`System collection: %o`, (isSystemCollection) => {
|
||||
const query: Query = {};
|
||||
|
||||
const collection = isSystemCollection ? `directus_${collectionName}` : collectionName;
|
||||
const formatted = formatQuery({ collection, key, query });
|
||||
|
||||
expect(formatted).toStrictEqual({
|
||||
__aliasFor: collectionName,
|
||||
__args: query,
|
||||
[primaryKeyField]: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Defined fields are requested', () => {
|
||||
test.each([true, false])(`System collection: %o`, (isSystemCollection) => {
|
||||
const query: Query = { fields: ['aaa', 'bbb', 'ccc'] };
|
||||
|
||||
const collection = isSystemCollection ? `directus_${collectionName}` : collectionName;
|
||||
const formatted = formatQuery({ collection, key, query });
|
||||
|
||||
expect(formatted).toStrictEqual({
|
||||
__aliasFor: collectionName,
|
||||
__args: {},
|
||||
aaa: true,
|
||||
bbb: true,
|
||||
ccc: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Aggregation query without group', () => {
|
||||
test.each([true, false])(`System collection: %o`, (isSystemCollection) => {
|
||||
const query: Query = { aggregate: { count: ['aaa'], sum: ['bbb', 'ccc'] } };
|
||||
|
||||
const collection = isSystemCollection ? `directus_${collectionName}` : collectionName;
|
||||
const formatted = formatQuery({ collection, key, query });
|
||||
|
||||
expect(formatted).toStrictEqual({
|
||||
__aliasFor: `${collectionName}_aggregated`,
|
||||
__args: {},
|
||||
count: {
|
||||
aaa: true,
|
||||
},
|
||||
sum: {
|
||||
bbb: true,
|
||||
ccc: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Aggregation query with group', () => {
|
||||
test.each([true, false])(`System collection: %o`, (isSystemCollection) => {
|
||||
const query: Query = { aggregate: { count: ['aaa'], sum: ['bbb', 'ccc'] }, group: ['ddd', 'eee'] };
|
||||
|
||||
const collection = isSystemCollection ? `directus_${collectionName}` : collectionName;
|
||||
const formatted = formatQuery({ collection, key, query });
|
||||
|
||||
expect(formatted).toStrictEqual({
|
||||
__aliasFor: `${collectionName}_aggregated`,
|
||||
__args: {
|
||||
groupBy: ['ddd', 'eee'],
|
||||
},
|
||||
count: {
|
||||
aaa: true,
|
||||
},
|
||||
sum: {
|
||||
bbb: true,
|
||||
ccc: true,
|
||||
},
|
||||
group: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Filter query without functions', () => {
|
||||
test.each([true, false])(`System collection: %o`, (isSystemCollection) => {
|
||||
const query: Query = { filter: { _and: [{ aaa: { _eq: '111' } }, { bbb: { ccc: { _eq: '222' } } }] } };
|
||||
|
||||
const collection = isSystemCollection ? `directus_${collectionName}` : collectionName;
|
||||
const formatted = formatQuery({ collection, key, query });
|
||||
|
||||
expect(formatted).toStrictEqual({
|
||||
__aliasFor: collectionName,
|
||||
__args: {
|
||||
filter: { _and: [{ aaa: { _eq: '111' } }, { bbb: { ccc: { _eq: '222' } } }] },
|
||||
},
|
||||
[primaryKeyField]: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Filter query with functions', () => {
|
||||
test.each([true, false])(`System collection: %o`, (isSystemCollection) => {
|
||||
const query: Query = {
|
||||
filter: { _and: [{ 'count(aaa)': { _eq: '111' } }, { bbb: { 'sum(ccc)': { _eq: '222' } } }] },
|
||||
};
|
||||
|
||||
const collection = isSystemCollection ? `directus_${collectionName}` : collectionName;
|
||||
const formatted = formatQuery({ collection, key, query });
|
||||
|
||||
expect(formatted).toStrictEqual({
|
||||
__aliasFor: collectionName,
|
||||
__args: {
|
||||
filter: { _and: [{ aaa_func: { count: { _eq: '111' } } }, { bbb: { ccc_func: { sum: { _eq: '222' } } } }] },
|
||||
},
|
||||
[primaryKeyField]: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useFieldsStore } from '@/stores/fields';
|
||||
import { Query } from '@directus/shared/types';
|
||||
import { toArray } from '@directus/shared/utils';
|
||||
import { Filter, Query } from '@directus/shared/types';
|
||||
import { parseJSON, toArray } from '@directus/shared/utils';
|
||||
import { jsonToGraphQLQuery } from 'json-to-graphql-query';
|
||||
import { isEmpty, pick, set, omitBy, isUndefined } from 'lodash';
|
||||
import { isEmpty, pick, set, omitBy, isUndefined, transform } from 'lodash';
|
||||
import { extractFieldFromFunction } from './extract-field-from-function';
|
||||
|
||||
type QueryInfo = { collection: string; key: string; query: Query };
|
||||
|
||||
@@ -61,8 +62,8 @@ export function formatQuery({ collection, query }: QueryInfo): Record<string, an
|
||||
|
||||
if (query.filter) {
|
||||
try {
|
||||
const json = String(query.filter);
|
||||
formattedQuery.__args.filter = JSON.parse(json);
|
||||
const filterValue = typeof query.filter === 'object' ? query.filter : parseJSON(String(query.filter));
|
||||
formattedQuery.__args.filter = replaceFuncs(filterValue);
|
||||
} catch {
|
||||
// Keep current value there
|
||||
}
|
||||
@@ -70,3 +71,30 @@ export function formatQuery({ collection, query }: QueryInfo): Record<string, an
|
||||
|
||||
return formattedQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace functions from Directus-Filter format to GraphQL format
|
||||
*/
|
||||
function replaceFuncs(filter?: Filter | null): null | undefined | Filter {
|
||||
if (!filter) return filter;
|
||||
|
||||
return replaceFuncDeep(filter);
|
||||
|
||||
function replaceFuncDeep(filter: Record<string, any>) {
|
||||
return transform(filter, (result: Record<string, any>, value, key) => {
|
||||
if (typeof key === 'string' && key.includes('(') && key.includes(')')) {
|
||||
const { fn, field } = extractFieldFromFunction(key);
|
||||
|
||||
if (fn) {
|
||||
result[`${field}_func`] = {
|
||||
[fn]: value,
|
||||
};
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
} else {
|
||||
result[key] = value?.constructor === Object || value?.constructor === Array ? replaceFuncDeep(value) : value;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user