Skip to content

Commit dc2ea8c

Browse files
authored
fix: GraphQL API date range filter (#6138)
1 parent 3bb85e4 commit dc2ea8c

File tree

7 files changed

+651
-54
lines changed

7 files changed

+651
-54
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist

packages/cubejs-api-gateway/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
"build": "rm -rf dist && npm run tsc",
2020
"tsc": "tsc",
2121
"watch": "tsc -w",
22-
"lint": "eslint src/* test/* --ext .ts,.js",
23-
"lint:fix": "eslint --fix src/* test/* --ext .ts,.js"
22+
"lint": "eslint \"**/*.{ts,tsx}\"",
23+
"lint:fix": "eslint --fix \"**/*.{ts,tsx}\""
2424
},
2525
"files": [
2626
"README.md",
@@ -79,6 +79,7 @@
7979
],
8080
"coveragePathIgnorePatterns": [
8181
".*\\.d\\.ts"
82-
]
82+
],
83+
"snapshotResolver": "<rootDir>/test/snapshotResolver.js"
8384
}
8485
}

packages/cubejs-api-gateway/src/graphql.ts

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ const DateTimeScalar = asNexusMethod(DateTimeResolver, 'date');
3737
const FloatFilter = inputObjectType({
3838
name: 'FloatFilter',
3939
definition(t) {
40-
t.float('equals');
41-
t.float('notEquals');
40+
t.list.float('equals');
41+
t.list.float('notEquals');
4242
t.list.float('in');
4343
t.list.float('notIn');
4444
t.boolean('set');
@@ -52,21 +52,21 @@ const FloatFilter = inputObjectType({
5252
const StringFilter = inputObjectType({
5353
name: 'StringFilter',
5454
definition(t) {
55-
t.string('equals');
56-
t.string('notEquals');
55+
t.list.string('equals');
56+
t.list.string('notEquals');
5757
t.list.string('in');
5858
t.list.string('notIn');
59-
t.string('contains');
60-
t.string('notContains');
59+
t.list.string('contains');
60+
t.list.string('notContains');
6161
t.boolean('set');
6262
}
6363
});
6464

6565
const DateTimeFilter = inputObjectType({
6666
name: 'DateTimeFilter',
6767
definition(t) {
68-
t.string('equals');
69-
t.string('notEquals');
68+
t.list.string('equals');
69+
t.list.string('notEquals');
7070
t.list.string('in');
7171
t.list.string('notIn');
7272
t.list.string('inDateRange');
@@ -152,7 +152,7 @@ function mapWhereValue(operator: string, value: any) {
152152
case 'notInDateRange':
153153
// This is a hack for named date ranges (e.g. "This year", "Today")
154154
// We should use enums in the future
155-
if (value.length === 1 && !/^[0-9]/.test(value[0])) {
155+
if (value.length === 1 && !/^\d\d\d\d-\d\d-\d\d/.test(value[0])) {
156156
return value[0].toString();
157157
}
158158

@@ -517,7 +517,7 @@ export function makeSchema(metaConfig: any): GraphQLSchema {
517517
const timeDimensions: any[] = [];
518518
let filters: any[] = [];
519519
const order: [string, 'asc' | 'desc'][] = [];
520-
520+
521521
if (where) {
522522
filters = whereArgToQueryFilters(where);
523523
}
@@ -544,11 +544,13 @@ export function makeSchema(metaConfig: any): GraphQLSchema {
544544
if (whereArg) {
545545
filters = whereArgToQueryFilters(whereArg, cubeName).concat(filters);
546546
}
547-
548-
const inDateRangeFilters = {};
547+
548+
// Relative date ranges such as "last quarter" can only be used in
549+
// timeDimensions dateRange filter
550+
const dateRangeFilters = {};
549551
filters = filters.filter((f) => {
550-
if (f.operator === 'inDateRange') {
551-
inDateRangeFilters[f.member] = f.values;
552+
if (f.operator === 'inDateRange' && (typeof f.values === 'string' || f.values?.length === 1)) {
553+
dateRangeFilters[f.member] = f.values;
552554
return false;
553555
}
554556

@@ -573,8 +575,8 @@ export function makeSchema(metaConfig: any): GraphQLSchema {
573575
timeDimensions.push({
574576
dimension: key,
575577
granularity: granularityName,
576-
...(inDateRangeFilters[key] ? {
577-
dateRange: inDateRangeFilters[key],
578+
...(dateRangeFilters[key] ? {
579+
dateRange: dateRangeFilters[key],
578580
} : null)
579581
});
580582
}
@@ -584,6 +586,15 @@ export function makeSchema(metaConfig: any): GraphQLSchema {
584586
}
585587
}
586588
});
589+
590+
if (Object.keys(dateRangeFilters).length && !timeDimensions.length) {
591+
Object.entries(dateRangeFilters).forEach(([dimension, dateRange]) => {
592+
timeDimensions.push({
593+
dimension,
594+
dateRange
595+
});
596+
});
597+
}
587598
});
588599

589600
const query = {
@@ -598,26 +609,21 @@ export function makeSchema(metaConfig: any): GraphQLSchema {
598609
...(renewQuery && { renewQuery }),
599610
};
600611

601-
// eslint-disable-next-line no-async-promise-executor
602-
const results = await (new Promise<any>(async (resolve, reject) => {
603-
try {
604-
await apiGateway.load({
605-
query,
606-
queryType: QueryType.REGULAR_QUERY,
607-
context: req.context,
608-
res: (message) => {
609-
if (message.error) {
610-
reject(new Error(message.error));
611-
}
612-
resolve(message);
613-
},
614-
apiType: 'graphql',
615-
});
616-
} catch (e) {
617-
reject(e);
618-
}
619-
}));
620-
612+
const results = await new Promise<any>((resolve, reject) => {
613+
apiGateway.load({
614+
query,
615+
queryType: QueryType.REGULAR_QUERY,
616+
context: req.context,
617+
res: (message) => {
618+
if (message.error) {
619+
reject(new Error(message.error));
620+
}
621+
resolve(message);
622+
},
623+
apiType: 'graphql',
624+
}).catch(reject);
625+
});
626+
621627
parseDates(results);
622628

623629
return results.data.map(entry => R.toPairs(entry)

0 commit comments

Comments
 (0)