flowerbi 3.8.3 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/QueryJson.d.ts +29 -5
- package/dist/QueryValues.d.ts +10 -10
- package/dist/QueryValues.js.map +1 -1
- package/dist/executeQuery.d.ts +8 -8
- package/dist/executeQuery.js +83 -12
- package/dist/executeQuery.js.map +1 -1
- package/dist/executeQuery.test.d.ts +1 -0
- package/dist/executeQuery.test.js +196 -22
- package/dist/executeQuery.test.js.map +1 -1
- package/dist/queryModel.d.ts +28 -8
- package/dist/queryModel.js.map +1 -1
- package/package.json +1 -1
- package/src/QueryJson.ts +32 -5
- package/src/QueryValues.ts +17 -17
- package/src/executeQuery.test.ts +263 -62
- package/src/executeQuery.ts +121 -31
- package/src/queryModel.ts +59 -34
package/dist/queryModel.d.ts
CHANGED
|
@@ -16,18 +16,22 @@ export declare type QueryColumnType<T> = T extends QueryColumn<infer C> ? C : ne
|
|
|
16
16
|
/**
|
|
17
17
|
* Defines the shape of a record returned from a query, based on its `select` object.
|
|
18
18
|
* Each selected property appears as a property in the record with the same name. For
|
|
19
|
-
* plain columns the data type depends on the column definition, but for
|
|
20
|
-
* the data type is always `number`.
|
|
19
|
+
* plain columns the data type depends on the column definition, but for aggregations
|
|
20
|
+
* and calculations the data type is always `number`.
|
|
21
21
|
*/
|
|
22
|
-
export declare type ExpandedQueryRecord<S extends QuerySelect
|
|
22
|
+
export declare type ExpandedQueryRecord<S extends QuerySelect, C extends QueryCalculations<S>> = {
|
|
23
23
|
[P in keyof S]: S[P] extends QueryColumn<any> ? QueryColumnType<S[P]> : number;
|
|
24
|
+
} & {
|
|
25
|
+
[P in keyof C]: number;
|
|
24
26
|
};
|
|
25
27
|
/**
|
|
26
28
|
* Similar to {@link ExpandedQueryRecord} but the plain columns are optional, so they
|
|
27
|
-
* may be `undefined`. Aggregations are not optional.
|
|
29
|
+
* may be `undefined`. Aggregations/calculations are not optional.
|
|
28
30
|
*/
|
|
29
|
-
export declare type ExpandedQueryRecordWithOptionalColumns<S extends QuerySelect
|
|
30
|
-
[P in keyof S]: S[P] extends QueryColumn<any> ?
|
|
31
|
+
export declare type ExpandedQueryRecordWithOptionalColumns<S extends QuerySelect, C extends QueryCalculations<S>> = {
|
|
32
|
+
[P in keyof S]: S[P] extends QueryColumn<any> ? QueryColumnType<S[P]> | undefined : number;
|
|
33
|
+
} & {
|
|
34
|
+
[P in keyof C]: number;
|
|
31
35
|
};
|
|
32
36
|
/**
|
|
33
37
|
* A helper type used in the definition of {@link AggregatePropsOnly}.
|
|
@@ -62,6 +66,9 @@ export declare function getAggregatePropsOnly<T extends QuerySelect>(select: T):
|
|
|
62
66
|
export declare type AggregateValuesOnly<T extends QuerySelect> = {
|
|
63
67
|
[K in AggregatePropsOnly<T>]: number;
|
|
64
68
|
};
|
|
69
|
+
export declare type CalculationValues<C extends QueryCalculations<any>> = {
|
|
70
|
+
[K in keyof C]: number;
|
|
71
|
+
};
|
|
65
72
|
/**
|
|
66
73
|
* A helper type used in the definition of {@link ColumnPropsOnly}.
|
|
67
74
|
*/
|
|
@@ -93,17 +100,30 @@ export declare function getColumnPropsOnly<T extends QuerySelect>(select: T): Co
|
|
|
93
100
|
* @param select the `select` object from a query
|
|
94
101
|
*/
|
|
95
102
|
export declare function getColumnsOnly(select: QuerySelect): QueryColumn<any>[];
|
|
103
|
+
export declare type Calculation<S extends QuerySelect> = number | AggregatePropsOnly<S> | [Calculation<S>, "+" | "-" | "*" | "/", Calculation<S>];
|
|
104
|
+
/**
|
|
105
|
+
* The `select` object of a query has named properties of type {@link SelectMember}.
|
|
106
|
+
*/
|
|
107
|
+
export declare type QueryCalculations<S extends QuerySelect> = Record<string, Calculation<S>>;
|
|
108
|
+
export declare type Ordering<S extends QuerySelect, C extends QueryCalculations<S>> = {
|
|
109
|
+
descending?: boolean;
|
|
110
|
+
} & ({
|
|
111
|
+
select: keyof S;
|
|
112
|
+
} | {
|
|
113
|
+
calculation: keyof C;
|
|
114
|
+
});
|
|
96
115
|
/**
|
|
97
116
|
* Strongly-typed query definition: use {@link jsonifyQuery} to convert to the JSON format
|
|
98
117
|
* and {@link expandQueryResult} to generate corresponding output records.
|
|
99
118
|
*/
|
|
100
|
-
export interface Query<S extends QuerySelect
|
|
119
|
+
export interface Query<S extends QuerySelect, C extends QueryCalculations<S>> {
|
|
101
120
|
select: S;
|
|
102
121
|
filters?: FilterJson[];
|
|
122
|
+
calculations?: C;
|
|
103
123
|
/**
|
|
104
124
|
* Ordering criteria to apply.
|
|
105
125
|
*/
|
|
106
|
-
orderBy?: OrderingJson[];
|
|
126
|
+
orderBy?: (OrderingJson | Ordering<S, C>)[];
|
|
107
127
|
/**
|
|
108
128
|
* Specifies whether to return a `totals` property containing the
|
|
109
129
|
* aggregation values across the whole dataset, e.g. if the `select`
|
package/dist/queryModel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queryModel.js","sourceRoot":"","sources":["../src/queryModel.ts"],"names":[],"mappings":";;;AAAA,+CAAwC;AACxC,6CAA4C;
|
|
1
|
+
{"version":3,"file":"queryModel.js","sourceRoot":"","sources":["../src/queryModel.ts"],"names":[],"mappings":";;;AAAA,+CAAwC;AACxC,6CAA4C;AAsD5C;;;;;;;;;;;;GAYG;AACH,SAAgB,qBAAqB,CAAwB,MAAS;IAClE,IAAM,IAAI,GAAG,IAAA,qBAAM,EAAC,MAAM,CAAC,CAAC,MAAM,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,yBAAW,CAAC,EAAnC,CAAmC,CAAC,CAAC;IAC/E,OAAO,IAA+B,CAAC;AAC3C,CAAC;AAHD,sDAGC;AA4BD;;;;;;;;;;;;GAYG;AACH,SAAgB,kBAAkB,CAAwB,MAAS;IAC/D,IAAM,IAAI,GAAG,IAAA,qBAAM,EAAC,MAAM,CAAC,CAAC,MAAM,CAAC,UAAC,CAAC,IAAK,OAAA,MAAM,CAAC,CAAC,CAAC,YAAY,yBAAW,EAAhC,CAAgC,CAAC,CAAC;IAC5E,OAAO,IAA4B,CAAC;AACxC,CAAC;AAHD,gDAGC;AAED;;;;GAIG;AACH,SAAgB,cAAc,CAAC,MAAmB;IAC9C,OAAO,IAAA,qBAAM,EAAC,MAAM,CAAC;SAChB,GAAG,CAAC,UAAC,CAAC,IAAK,OAAA,MAAM,CAAC,CAAC,CAAC,EAAT,CAAS,CAAC;SACrB,MAAM,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,YAAY,yBAAW,EAAxB,CAAwB,CAAC;SACvC,GAAG,CAAC,UAAC,CAAC,IAAK,OAAA,CAAqB,EAArB,CAAqB,CAAC,CAAC;AAC3C,CAAC;AALD,wCAKC"}
|
package/package.json
CHANGED
package/src/QueryJson.ts
CHANGED
|
@@ -37,13 +37,36 @@ export interface AggregationJson {
|
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* Specifies an ordering criteria: which column to sort by, and optionally
|
|
40
|
-
* whether it is descending (the default is ascending). The column
|
|
41
|
-
* specified by a string of the form `table.column
|
|
40
|
+
* whether it is descending (the default is ascending). The column can be
|
|
41
|
+
* specified by a string of the form `table.column`, though this can only
|
|
42
|
+
* target one of the columns specified in select. More flexibly, specify
|
|
43
|
+
* a type of column (the type 'Value' refers to aggregations) and its
|
|
44
|
+
* zero-based position.
|
|
42
45
|
*/
|
|
43
|
-
export
|
|
44
|
-
column: string;
|
|
46
|
+
export type OrderingJson = {
|
|
45
47
|
descending?: boolean;
|
|
46
|
-
}
|
|
48
|
+
} & (
|
|
49
|
+
| {
|
|
50
|
+
column: string;
|
|
51
|
+
}
|
|
52
|
+
| {
|
|
53
|
+
type: "Select" | "Value" | "Calculation";
|
|
54
|
+
index: number;
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Specifies an expression for calculating a derived value based on
|
|
60
|
+
* the values of aggregations, specified by zero-based position.
|
|
61
|
+
*/
|
|
62
|
+
export type CalculationJson =
|
|
63
|
+
| { value: number }
|
|
64
|
+
| { aggregation: number }
|
|
65
|
+
| {
|
|
66
|
+
first: CalculationJson;
|
|
67
|
+
operator: "+" | "-" | "*" | "/";
|
|
68
|
+
second: CalculationJson;
|
|
69
|
+
};
|
|
47
70
|
|
|
48
71
|
/**
|
|
49
72
|
* Specifies an entire query.
|
|
@@ -57,6 +80,10 @@ export interface QueryJson {
|
|
|
57
80
|
* The aggregated values to fetch.
|
|
58
81
|
*/
|
|
59
82
|
aggregations: AggregationJson[];
|
|
83
|
+
/**
|
|
84
|
+
* The calculations to perform.
|
|
85
|
+
*/
|
|
86
|
+
calculations?: CalculationJson[];
|
|
60
87
|
/**
|
|
61
88
|
* Filters to apply. They are always combined with AND.
|
|
62
89
|
*/
|
package/src/QueryValues.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
QuerySelect,
|
|
3
|
-
ExpandedQueryRecord,
|
|
4
|
-
AggregateValuesOnly,
|
|
5
|
-
AggregatePropsOnly,
|
|
6
|
-
ExpandedQueryRecordWithOptionalColumns
|
|
1
|
+
import {
|
|
2
|
+
QuerySelect,
|
|
3
|
+
ExpandedQueryRecord,
|
|
4
|
+
AggregateValuesOnly,
|
|
5
|
+
AggregatePropsOnly,
|
|
6
|
+
ExpandedQueryRecordWithOptionalColumns,
|
|
7
|
+
QueryCalculations,
|
|
8
|
+
CalculationValues,
|
|
7
9
|
} from "./queryModel";
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* An abstract interface representing either a row from a dataset or
|
|
11
|
-
* the {@link ExpandedQueryResult.totals} row, so that generic code can
|
|
13
|
+
* the {@link ExpandedQueryResult.totals} row, so that generic code can
|
|
12
14
|
* format either of them in a consistent way.
|
|
13
15
|
*/
|
|
14
|
-
export interface QueryValues<S extends QuerySelect
|
|
16
|
+
export interface QueryValues<S extends QuerySelect, C extends QueryCalculations<S>> {
|
|
15
17
|
/**
|
|
16
18
|
* The plain values of columns, which may be `undefined` if this
|
|
17
19
|
* refers to the {@link ExpandedQueryResult.totals} record.
|
|
18
20
|
*/
|
|
19
|
-
values: ExpandedQueryRecordWithOptionalColumns<S>;
|
|
21
|
+
values: ExpandedQueryRecordWithOptionalColumns<S, C>;
|
|
20
22
|
percentage<K extends AggregatePropsOnly<S>>(key: K): number;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
export class QueryValuesRow<S extends QuerySelect
|
|
24
|
-
constructor(
|
|
25
|
-
public readonly values: ExpandedQueryRecord<S>,
|
|
26
|
-
public readonly totals: AggregateValuesOnly<S> | undefined) {}
|
|
25
|
+
export class QueryValuesRow<S extends QuerySelect, C extends QueryCalculations<S>> implements QueryValues<S, C> {
|
|
26
|
+
constructor(public readonly values: ExpandedQueryRecord<S, C>, public readonly totals: (AggregateValuesOnly<S> & CalculationValues<C>) | undefined) {}
|
|
27
27
|
|
|
28
28
|
percentage<K extends AggregatePropsOnly<S>>(key: K) {
|
|
29
29
|
if (!this.totals) return 0;
|
|
@@ -35,11 +35,11 @@ export class QueryValuesRow<S extends QuerySelect> implements QueryValues<S> {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export class QueryValuesTotal<S extends QuerySelect
|
|
39
|
-
public readonly values: ExpandedQueryRecordWithOptionalColumns<S>;
|
|
38
|
+
export class QueryValuesTotal<S extends QuerySelect, C extends QueryCalculations<S>> implements QueryValues<S, C> {
|
|
39
|
+
public readonly values: ExpandedQueryRecordWithOptionalColumns<S, C>;
|
|
40
40
|
|
|
41
|
-
constructor(totals: AggregateValuesOnly<S>) {
|
|
42
|
-
this.values = totals as ExpandedQueryRecordWithOptionalColumns<S>;
|
|
41
|
+
constructor(totals: AggregateValuesOnly<S> & CalculationValues<C>) {
|
|
42
|
+
this.values = totals as ExpandedQueryRecordWithOptionalColumns<S, C>;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
percentage() {
|
package/src/executeQuery.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { jsonifyQuery } from "./executeQuery";
|
|
1
|
+
import { expandQueryResult, jsonifyQuery, QueryResultJson } from "./executeQuery";
|
|
2
2
|
import { QueryColumn } from "./QueryColumn";
|
|
3
|
+
import { Query, QueryCalculations, QuerySelect } from "./queryModel";
|
|
3
4
|
|
|
4
5
|
export const Customer = {
|
|
5
6
|
Id: new QueryColumn<number>("Customer.Id"),
|
|
@@ -8,22 +9,28 @@ export const Customer = {
|
|
|
8
9
|
|
|
9
10
|
export const Bug = {
|
|
10
11
|
Id: new QueryColumn<number>("Bug.Id"),
|
|
11
|
-
CustomerId: new QueryColumn<number>("Bug.CustomerId"),
|
|
12
|
+
CustomerId: new QueryColumn<number>("Bug.CustomerId"),
|
|
13
|
+
Fixed: new QueryColumn<boolean>("Bug.Fixed"),
|
|
12
14
|
};
|
|
13
15
|
|
|
14
|
-
test("jsonifies mixed columns", () => {
|
|
15
|
-
expect(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
test("jsonifies mixed columns", () => {
|
|
17
|
+
expect(
|
|
18
|
+
jsonifyQuery({
|
|
19
|
+
select: {
|
|
20
|
+
customer: Customer.CustomerName,
|
|
21
|
+
bugCount: Bug.Id.count(),
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
).toStrictEqual({
|
|
21
25
|
select: ["Customer.CustomerName"],
|
|
22
|
-
aggregations: [
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
aggregations: [
|
|
27
|
+
{
|
|
28
|
+
column: "Bug.Id",
|
|
29
|
+
function: "Count",
|
|
30
|
+
filters: undefined,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
calculations: undefined,
|
|
27
34
|
filters: [],
|
|
28
35
|
orderBy: [],
|
|
29
36
|
skip: 0,
|
|
@@ -34,14 +41,17 @@ test("jsonifies mixed columns", () => {
|
|
|
34
41
|
});
|
|
35
42
|
});
|
|
36
43
|
|
|
37
|
-
test("jsonifies select", () => {
|
|
38
|
-
expect(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
test("jsonifies select", () => {
|
|
45
|
+
expect(
|
|
46
|
+
jsonifyQuery({
|
|
47
|
+
select: {
|
|
48
|
+
customer: Customer.CustomerName,
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
).toStrictEqual({
|
|
43
52
|
select: ["Customer.CustomerName"],
|
|
44
53
|
aggregations: [],
|
|
54
|
+
calculations: undefined,
|
|
45
55
|
filters: [],
|
|
46
56
|
orderBy: [],
|
|
47
57
|
skip: 0,
|
|
@@ -53,16 +63,19 @@ test("jsonifies select", () => {
|
|
|
53
63
|
});
|
|
54
64
|
|
|
55
65
|
test("jsonifies params", () => {
|
|
56
|
-
expect(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
expect(
|
|
67
|
+
jsonifyQuery({
|
|
68
|
+
select: {},
|
|
69
|
+
skip: 3,
|
|
70
|
+
take: 42,
|
|
71
|
+
totals: true,
|
|
72
|
+
allowDuplicates: true,
|
|
73
|
+
comment: "a comment",
|
|
74
|
+
})
|
|
75
|
+
).toStrictEqual({
|
|
64
76
|
select: [],
|
|
65
77
|
aggregations: [],
|
|
78
|
+
calculations: undefined,
|
|
66
79
|
filters: [],
|
|
67
80
|
orderBy: [],
|
|
68
81
|
skip: 3,
|
|
@@ -74,32 +87,36 @@ test("jsonifies params", () => {
|
|
|
74
87
|
});
|
|
75
88
|
|
|
76
89
|
test("jsonifies filters", () => {
|
|
77
|
-
expect(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
Customer.CustomerName.lessThan("z")
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
]
|
|
86
|
-
|
|
87
|
-
})).toStrictEqual({
|
|
90
|
+
expect(
|
|
91
|
+
jsonifyQuery({
|
|
92
|
+
select: {
|
|
93
|
+
bugCount: Bug.Id.count([Customer.CustomerName.lessThan("z")]),
|
|
94
|
+
},
|
|
95
|
+
filters: [Customer.CustomerName.greaterThan("a")],
|
|
96
|
+
})
|
|
97
|
+
).toStrictEqual({
|
|
88
98
|
select: [],
|
|
89
|
-
aggregations: [
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
99
|
+
aggregations: [
|
|
100
|
+
{
|
|
101
|
+
column: "Bug.Id",
|
|
102
|
+
function: "Count",
|
|
103
|
+
filters: [
|
|
104
|
+
{
|
|
105
|
+
column: "Customer.CustomerName",
|
|
106
|
+
operator: "<",
|
|
107
|
+
value: "z",
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
calculations: undefined,
|
|
113
|
+
filters: [
|
|
114
|
+
{
|
|
93
115
|
column: "Customer.CustomerName",
|
|
94
|
-
operator: "
|
|
95
|
-
value: "
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
filters: [{
|
|
99
|
-
column: "Customer.CustomerName",
|
|
100
|
-
operator: ">",
|
|
101
|
-
value: "a",
|
|
102
|
-
}],
|
|
116
|
+
operator: ">",
|
|
117
|
+
value: "a",
|
|
118
|
+
},
|
|
119
|
+
],
|
|
103
120
|
orderBy: [],
|
|
104
121
|
skip: 0,
|
|
105
122
|
take: 100,
|
|
@@ -109,20 +126,126 @@ test("jsonifies filters", () => {
|
|
|
109
126
|
});
|
|
110
127
|
});
|
|
111
128
|
|
|
112
|
-
test("jsonifies orderBy", () => {
|
|
113
|
-
expect(
|
|
114
|
-
|
|
129
|
+
test("jsonifies orderBy", () => {
|
|
130
|
+
expect(
|
|
131
|
+
jsonifyQuery({
|
|
132
|
+
select: {},
|
|
133
|
+
orderBy: [Customer.CustomerName.descending()],
|
|
134
|
+
})
|
|
135
|
+
).toStrictEqual({
|
|
136
|
+
select: [],
|
|
137
|
+
aggregations: [],
|
|
138
|
+
calculations: undefined,
|
|
139
|
+
filters: [],
|
|
140
|
+
orderBy: [
|
|
141
|
+
{
|
|
142
|
+
column: "Customer.CustomerName",
|
|
143
|
+
descending: true,
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
skip: 0,
|
|
147
|
+
take: 100,
|
|
148
|
+
totals: false,
|
|
149
|
+
allowDuplicates: undefined,
|
|
150
|
+
comment: undefined,
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("jsonifies calculations and ordering by keys", () => {
|
|
155
|
+
expect(
|
|
156
|
+
jsonifyQuery({
|
|
157
|
+
select: {
|
|
158
|
+
customer: Customer.CustomerName,
|
|
159
|
+
bugCount: Bug.Id.count(),
|
|
160
|
+
bugsFixed: Bug.Id.count([Bug.Fixed.equalTo(true)]),
|
|
161
|
+
},
|
|
162
|
+
calculations: {
|
|
163
|
+
successRate: [100, "*", ["bugsFixed", "/", "bugCount"]],
|
|
164
|
+
failureRate: [100, "-", [100, "*", ["bugsFixed", "/", "bugCount"]]],
|
|
165
|
+
},
|
|
166
|
+
orderBy: [
|
|
167
|
+
{ select: "customer" },
|
|
168
|
+
{ select: "bugsFixed", descending: true },
|
|
169
|
+
{ calculation: "successRate" },
|
|
170
|
+
{ calculation: "failureRate", descending: true },
|
|
171
|
+
],
|
|
172
|
+
})
|
|
173
|
+
).toStrictEqual({
|
|
174
|
+
select: ["Customer.CustomerName"],
|
|
175
|
+
aggregations: [
|
|
176
|
+
{
|
|
177
|
+
column: "Bug.Id",
|
|
178
|
+
function: "Count",
|
|
179
|
+
filters: undefined,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
column: "Bug.Id",
|
|
183
|
+
function: "Count",
|
|
184
|
+
filters: [
|
|
185
|
+
{
|
|
186
|
+
column: "Bug.Fixed",
|
|
187
|
+
operator: "=",
|
|
188
|
+
value: true,
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
calculations: [
|
|
194
|
+
{
|
|
195
|
+
first: { value: 100 },
|
|
196
|
+
operator: "*",
|
|
197
|
+
second: {
|
|
198
|
+
first: { aggregation: 1 },
|
|
199
|
+
operator: "/",
|
|
200
|
+
second: { aggregation: 0 },
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
first: { value: 100 },
|
|
205
|
+
operator: "-",
|
|
206
|
+
second: {
|
|
207
|
+
first: { value: 100 },
|
|
208
|
+
operator: "*",
|
|
209
|
+
second: {
|
|
210
|
+
first: { aggregation: 1 },
|
|
211
|
+
operator: "/",
|
|
212
|
+
second: { aggregation: 0 },
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
filters: [],
|
|
115
218
|
orderBy: [
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
219
|
+
{ type: "Select", index: 0, descending: undefined },
|
|
220
|
+
{ type: "Value", index: 1, descending: true },
|
|
221
|
+
{ type: "Calculation", index: 0, descending: undefined },
|
|
222
|
+
{ type: "Calculation", index: 1, descending: true },
|
|
223
|
+
],
|
|
224
|
+
skip: 0,
|
|
225
|
+
take: 100,
|
|
226
|
+
totals: false,
|
|
227
|
+
allowDuplicates: undefined,
|
|
228
|
+
comment: undefined,
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("passes through JSON orderBy", () => {
|
|
233
|
+
expect(
|
|
234
|
+
jsonifyQuery({
|
|
235
|
+
select: {},
|
|
236
|
+
orderBy: [Customer.CustomerName.descending()],
|
|
237
|
+
})
|
|
238
|
+
).toStrictEqual({
|
|
119
239
|
select: [],
|
|
120
240
|
aggregations: [],
|
|
241
|
+
calculations: undefined,
|
|
121
242
|
filters: [],
|
|
122
|
-
orderBy: [
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
243
|
+
orderBy: [
|
|
244
|
+
{
|
|
245
|
+
column: "Customer.CustomerName",
|
|
246
|
+
descending: true,
|
|
247
|
+
},
|
|
248
|
+
],
|
|
126
249
|
skip: 0,
|
|
127
250
|
take: 100,
|
|
128
251
|
totals: false,
|
|
@@ -130,3 +253,81 @@ test("jsonifies orderBy", () => {
|
|
|
130
253
|
comment: undefined,
|
|
131
254
|
});
|
|
132
255
|
});
|
|
256
|
+
|
|
257
|
+
function formatResultsFromQuery<S extends QuerySelect, C extends QueryCalculations<S>>(query: Query<S, C>, result: QueryResultJson) {
|
|
258
|
+
return expandQueryResult<S, C>(query.select, result, query.calculations);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
test("generates typed results", () => {
|
|
262
|
+
const result = formatResultsFromQuery(
|
|
263
|
+
{
|
|
264
|
+
select: {
|
|
265
|
+
customer: Customer.CustomerName,
|
|
266
|
+
bugCount: Bug.Id.count(),
|
|
267
|
+
bugsFixed: Bug.Id.count([Bug.Fixed.equalTo(true)]),
|
|
268
|
+
},
|
|
269
|
+
calculations: {
|
|
270
|
+
successRate: [100, "*", ["bugsFixed", "/", "bugCount"]],
|
|
271
|
+
failureRate: [100, "-", [100, "*", ["bugsFixed", "/", "bugCount"]]],
|
|
272
|
+
},
|
|
273
|
+
orderBy: [
|
|
274
|
+
{ select: "customer" },
|
|
275
|
+
{ select: "bugsFixed", descending: true },
|
|
276
|
+
{ calculation: "successRate" },
|
|
277
|
+
{ calculation: "failureRate", descending: true },
|
|
278
|
+
],
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
records: [
|
|
282
|
+
{
|
|
283
|
+
selected: ["woolworths"],
|
|
284
|
+
aggregated: [3, 1, 33.33, 66.67],
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
}
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
expect(result).toStrictEqual({
|
|
291
|
+
records: [{ customer: "woolworths", bugCount: 3, bugsFixed: 1, successRate: 33.33, failureRate: 66.67 }],
|
|
292
|
+
totals: undefined,
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("generates typed results with totals", () => {
|
|
297
|
+
const result = formatResultsFromQuery(
|
|
298
|
+
{
|
|
299
|
+
select: {
|
|
300
|
+
customer: Customer.CustomerName,
|
|
301
|
+
bugCount: Bug.Id.count(),
|
|
302
|
+
bugsFixed: Bug.Id.count([Bug.Fixed.equalTo(true)]),
|
|
303
|
+
},
|
|
304
|
+
calculations: {
|
|
305
|
+
successRate: [100, "*", ["bugsFixed", "/", "bugCount"]],
|
|
306
|
+
failureRate: [100, "-", [100, "*", ["bugsFixed", "/", "bugCount"]]],
|
|
307
|
+
},
|
|
308
|
+
orderBy: [
|
|
309
|
+
{ select: "customer" },
|
|
310
|
+
{ select: "bugsFixed", descending: true },
|
|
311
|
+
{ calculation: "successRate" },
|
|
312
|
+
{ calculation: "failureRate", descending: true },
|
|
313
|
+
],
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
records: [
|
|
317
|
+
{
|
|
318
|
+
selected: ["woolworths"],
|
|
319
|
+
aggregated: [3, 1, 33.33, 66.67],
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
totals: {
|
|
323
|
+
selected: [""],
|
|
324
|
+
aggregated: [4, 2, 50, 50],
|
|
325
|
+
},
|
|
326
|
+
}
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
expect(result).toStrictEqual({
|
|
330
|
+
records: [{ customer: "woolworths", bugCount: 3, bugsFixed: 1, successRate: 33.33, failureRate: 66.67 }],
|
|
331
|
+
totals: { bugCount: 4, bugsFixed: 2, successRate: 50, failureRate: 50 },
|
|
332
|
+
});
|
|
333
|
+
});
|