-
Notifications
You must be signed in to change notification settings - Fork 13.9k
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 (#6423)
* [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 * addressing PR comments * Adding a comment for metrictype values
- Loading branch information
Showing
8 changed files
with
262 additions
and
20 deletions.
There are no files selected for viewing
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
95 changes: 95 additions & 0 deletions
95
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,95 @@ | ||
import { ColumnType } from 'src/query/Column'; | ||
import { | ||
AdhocMetric, Aggregate, ExpressionType, LABEL_MAX_LENGTH, 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: ColumnType.BIGINT, | ||
}, | ||
expressionType: ExpressionType.SIMPLE, | ||
}; | ||
metrics = new Metrics({ | ||
...formData, | ||
metric: adhocMetric, | ||
}); | ||
expect(metrics.getMetrics()).toEqual([{ | ||
aggregate: 'AVG', | ||
column: { | ||
columnName: 'sum_girls', | ||
id: 5, | ||
type: ColumnType.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()[0].length).toBeLessThanOrEqual(LABEL_MAX_LENGTH); | ||
}); | ||
}); |
19 changes: 15 additions & 4 deletions
19
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'}); | ||
it('should build granularity for sql druid datasources', () => { | ||
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,24 @@ | ||
export enum ColumnType { | ||
DOUBLE = 'DOUBLE', | ||
FLOAT = 'FLOAT', | ||
INT = 'INT', | ||
BIGINT = 'BIGINT', | ||
LONG = 'LONG', | ||
REAL = 'REAL', | ||
NUMERIC = 'NUMERIC', | ||
DECIMAL = 'DECIMAL', | ||
MONEY = 'MONEY', | ||
DATE = 'DATE', | ||
TIME = 'TIME', | ||
DATETIME = 'DATETIME', | ||
VARCHAR = 'VARCHAR', | ||
STRING = 'STRING', | ||
CHAR = 'CHAR', | ||
} | ||
|
||
// TODO: fill out additional fields of the Column interface | ||
export default interface Column { | ||
id: number; | ||
type: ColumnType; | ||
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,100 @@ | ||
import Column from './Column'; | ||
import FormData from './FormData'; | ||
|
||
export const LABEL_MAX_LENGTH = 43; | ||
|
||
// Note that the values of MetricKeys are lower_snake_case because they're | ||
// used as keys of form data jsons. | ||
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 <= LABEL_MAX_LENGTH ? label : | ||
`${label.substring(0, LABEL_MAX_LENGTH - 3)}...`; | ||
} | ||
} |
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