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:
ian
2023-01-19 03:31:59 +08:00
committed by GitHub
parent d42de82bcd
commit 025bb7c053
12 changed files with 211 additions and 15 deletions

View 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,
});
});
});

View File

@@ -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;
}
});
}
}