Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[SIP-5] Build metrics in query_object in the client
- Unify the metric interface (absorb the current plain string metric for built-in metric keys into the format used by adhoc metric) - Port the logic in adhocMetric on the client and process_metrics in the backend to the new typed Metrics class - Omit hasCustomLabel and formFromData properties from the new metric interface as their value can be inferred from label and optionName - Expose from the Metrics class both metrics and their labels as public methods to match the all_metrics and metric_labels fields in the backend code - Provide defaut values for filters, metrics and groupby in the backend
- Loading branch information
Showing
7 changed files
with
233 additions
and
17 deletions.
There are no files selected for viewing
92 changes: 92 additions & 0 deletions
92
superset/assets/spec/javascripts/superset-ui/Metric.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { AdhocMetric, Aggregate, ExpressionType, Metrics } from 'src/query/Metric'; | ||
|
||
describe('Metrics', () => { | ||
let metrics: Metrics; | ||
const formData = { | ||
datasource: '5__table', | ||
granularity_sqla: 'ds', | ||
}; | ||
|
||
it('should build metrics for built-in metric keys', () => { | ||
metrics = new Metrics({ | ||
...formData, | ||
metric: 'sum__num', | ||
}); | ||
expect(metrics.getMetrics()).toEqual([{label: 'sum__num'}]); | ||
expect(metrics.getLabels()).toEqual(['sum__num']); | ||
}); | ||
|
||
it('should build metrics for simple adhoc metrics', () => { | ||
const adhocMetric: AdhocMetric = { | ||
aggregate: Aggregate.AVG, | ||
column: { | ||
columnName: 'sum_girls', | ||
id: 5, | ||
type: 'BIGINT', | ||
}, | ||
expressionType: ExpressionType.SIMPLE, | ||
}; | ||
metrics = new Metrics({ | ||
...formData, | ||
metric: adhocMetric, | ||
}); | ||
expect(metrics.getMetrics()).toEqual([{ | ||
aggregate: 'AVG', | ||
column: { | ||
columnName: 'sum_girls', | ||
id: 5, | ||
type: 'BIGINT', | ||
}, | ||
expressionType: 'SIMPLE', | ||
label: 'AVG(sum_girls)', | ||
}]); | ||
expect(metrics.getLabels()).toEqual(['AVG(sum_girls)']); | ||
}); | ||
|
||
it('should build metrics for SQL adhoc metrics', () => { | ||
const adhocMetric: AdhocMetric = { | ||
expressionType: ExpressionType.SQL, | ||
sqlExpression: 'COUNT(sum_girls)', | ||
}; | ||
metrics = new Metrics({ | ||
...formData, | ||
metric: adhocMetric, | ||
}); | ||
expect(metrics.getMetrics()).toEqual([{ | ||
expressionType: 'SQL', | ||
label: 'COUNT(sum_girls)', | ||
sqlExpression: 'COUNT(sum_girls)', | ||
}]); | ||
expect(metrics.getLabels()).toEqual(['COUNT(sum_girls)']); | ||
}); | ||
|
||
it('should build metrics for adhoc metrics with custom labels', () => { | ||
const adhocMetric: AdhocMetric = { | ||
expressionType: ExpressionType.SQL, | ||
label: 'foo', | ||
sqlExpression: 'COUNT(sum_girls)', | ||
}; | ||
metrics = new Metrics({ | ||
...formData, | ||
metric: adhocMetric, | ||
}); | ||
expect(metrics.getMetrics()).toEqual([{ | ||
expressionType: 'SQL', | ||
label: 'foo', | ||
sqlExpression: 'COUNT(sum_girls)', | ||
}]); | ||
expect(metrics.getLabels()).toEqual(['foo']); | ||
}); | ||
|
||
it('should truncate labels if they are too long', () => { | ||
const adhocMetric: AdhocMetric = { | ||
expressionType: ExpressionType.SQL, | ||
sqlExpression: 'COUNT(verrrrrrrrry_loooooooooooooooooooooong_string)', | ||
}; | ||
metrics = new Metrics({ | ||
...formData, | ||
metric: adhocMetric, | ||
}); | ||
expect(metrics.getLabels()).toEqual(['COUNT(verrrrrrrrry_loooooooooooooooooooo...']); | ||
}); | ||
}); |
17 changes: 14 additions & 3 deletions
17
superset/assets/spec/javascripts/superset-ui/buildQueryObject.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,24 @@ | ||
import build from 'src/query/buildQueryObject'; | ||
import build, { QueryObject } from 'src/query/buildQueryObject'; | ||
|
||
describe('queryObjectBuilder', () => { | ||
let query: QueryObject; | ||
|
||
it('should build granularity for sql alchemy datasources', () => { | ||
const query = build({datasource: '5__table', granularity_sqla: 'ds'}); | ||
query = build({datasource: '5__table', granularity_sqla: 'ds'}); | ||
expect(query.granularity).toEqual('ds'); | ||
}); | ||
|
||
it('should build granularity for sql alchemy datasources', () => { | ||
const query = build({datasource: '5__druid', granularity: 'ds'}); | ||
query = build({datasource: '5__druid', granularity: 'ds'}); | ||
expect(query.granularity).toEqual('ds'); | ||
}); | ||
|
||
it('should build metrics', () => { | ||
query = build({ | ||
datasource: '5__table', | ||
granularity_sqla: 'ds', | ||
metric: 'sum__num', | ||
}); | ||
expect(query.metrics).toEqual([{label: 'sum__num'}]); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// TODO: fill out additional fields of the Column interface | ||
export default interface Column { | ||
id: number; | ||
type: string; | ||
columnName: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import Column from './Column'; | ||
import FormData from './FormData'; | ||
|
||
export enum MetricKey { | ||
METRIC = 'metric', | ||
METRICS = 'metrics', | ||
PERCENT_METRICS = 'percent_metrics', | ||
RIGHT_AXIS_METRIC = 'metric_2', | ||
SECONDARY_METRIC = 'secondary_metric', | ||
X = 'x', | ||
Y = 'y', | ||
SIZE = 'size', | ||
} | ||
|
||
export enum Aggregate { | ||
AVG = 'AVG', | ||
COUNT = 'COUNT ', | ||
COUNT_DISTINCT = 'COUNT_DISTINCT', | ||
MAX = 'MAX', | ||
MIN = 'MIN', | ||
SUM = 'SUM', | ||
} | ||
|
||
export enum ExpressionType { | ||
SIMPLE = 'SIMPLE', | ||
SQL = 'SQL', | ||
} | ||
|
||
interface AdhocMetricSimple { | ||
expressionType: ExpressionType.SIMPLE; | ||
column: Column; | ||
aggregate: Aggregate; | ||
} | ||
|
||
interface AdhocMetricSQL { | ||
expressionType: ExpressionType.SQL; | ||
sqlExpression: string; | ||
} | ||
|
||
export type AdhocMetric = { | ||
label?: string, | ||
optionName?: string, | ||
} & (AdhocMetricSimple | AdhocMetricSQL); | ||
|
||
type Metric = { | ||
label: string; | ||
} & Partial<AdhocMetric>; | ||
|
||
export default Metric; | ||
|
||
export class Metrics { | ||
// Use Array to maintain insertion order for metrics that are order sensitive | ||
private metrics: Metric[]; | ||
|
||
constructor(formData: FormData) { | ||
this.metrics = []; | ||
for (const key of Object.keys(MetricKey)) { | ||
const metric = formData[MetricKey[key] as MetricKey]; | ||
if (metric) { | ||
if (typeof metric === 'string') { | ||
this.metrics.push({ | ||
label: metric, | ||
}); | ||
} else { | ||
// Note we further sanitize the metric label for BigQuery datasources | ||
// TODO: move this logic to the client once client has more info on the | ||
// the datasource | ||
const label = metric.label || this.getDefaultLabel(metric); | ||
this.metrics.push({ | ||
...metric, | ||
label, | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
|
||
public getMetrics() { | ||
return this.metrics; | ||
} | ||
|
||
public getLabels() { | ||
return this.metrics.map((m) => m.label); | ||
} | ||
|
||
private getDefaultLabel(metric: AdhocMetric) { | ||
let label: string; | ||
if (metric.expressionType === ExpressionType.SIMPLE) { | ||
label = `${metric.aggregate}(${(metric.column.columnName)})`; | ||
} else { | ||
label = metric.sqlExpression; | ||
} | ||
return label.length < 43 ? label : `${label.substring(0, 40)}...`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters