Skip to content

Commit

Permalink
Query Builder 2.0 [WIP] (metabase#4496)
Browse files Browse the repository at this point in the history
* QB modes, actions, and drill throughs.

* Refactor drill throughs + misc bug fixes

* Port sort action to drills

* Smarter 'view this/these' name

* Scalar hover state

* QB actions: don't skip action selection screen unless the action is marked as 'default'

* Fix flow errors

* Fix drills

* Show "This X's Ys" for FKs

* Prevent qb from uneccesary push states

* Don't show (fk) breakout fields that have already been broken out

* Fix 'drill into this' unit

* Cleanup timeseries footer

* Cleanup qb selectors

* Namespace all qb actions

* Make modes dependent on previously run card

* More timeseries footer cleanup

* Fix table crash

* Make breakout legend clickable

* Fix datagrid pivot test

* Flow + lint
  • Loading branch information
tlrobinson authored Apr 4, 2017
1 parent 19fb5aa commit ff12c03
Show file tree
Hide file tree
Showing 127 changed files with 3,436 additions and 908 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"rules": {
"strict": [2, "never"],
"no-undef": 2,
"no-unused-vars": [1, {"vars": "all", "args": "none", "varsIgnorePattern": "React|PropTypes|Component"}],
"no-unused-vars": [1, {"vars": "all", "args": "none", "varsIgnorePattern": "^(React|PropTypes|Component)$"}],
"import/no-commonjs": 1,
"quotes": 0,
"camelcase": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,11 @@ import GuiQueryEditor from "metabase/query_builder/components/GuiQueryEditor.jsx

import { serializeCardForUrl } from "metabase/lib/card";

import _ from "underscore";
import cx from "classnames";

export default class PartialQueryBuilder extends Component {
constructor(props, context) {
super(props, context);
this.state = {};

_.bindAll(this, "setQuery");
}
import * as Query from "metabase/lib/query/query";

export default class PartialQueryBuilder extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
tableMetadata: PropTypes.object.isRequired,
Expand All @@ -34,15 +28,15 @@ export default class PartialQueryBuilder extends Component {
});
}

setQuery(query) {
this.props.onChange(query.query);
this.props.updatePreviewSummary(query);
setDatasetQuery = (datasetQuery) => {
this.props.onChange(datasetQuery.query);
this.props.updatePreviewSummary(datasetQuery);
}

render() {
let { features, value, tableMetadata, previewSummary } = this.props;

let dataset_query = {
let datasetQuery = {
type: "query",
database: tableMetadata.db_id,
query: {
Expand All @@ -53,28 +47,39 @@ export default class PartialQueryBuilder extends Component {

let previewCard = {
dataset_query: {
...dataset_query,
...datasetQuery,
query: {
aggregation: ["rows"],
breakout: [],
filter: [],
...dataset_query.query
...datasetQuery.query
}
}
};
let previewUrl = "/q#" + serializeCardForUrl(previewCard);

const onChange = (query) => {
this.props.onChange(query);
this.props.updatePreviewSummary({ ...datasetQuery, query });
}

return (
<div className="py1">
<GuiQueryEditor
query={dataset_query}
features={features}
datasetQuery={datasetQuery}
tableMetadata={tableMetadata}
databases={tableMetadata && [tableMetadata.db]}
setQueryFn={this.setQuery}
setDatasetQuery={this.setDatasetQuery}
isShowingDataReference={false}
setDatabaseFn={null}
setSourceTableFn={null}
addQueryFilter={(filter) => onChange(Query.addFilter(datasetQuery.query, filter))}
updateQueryFilter={(index, filter) => onChange(Query.updateFilter(datasetQuery.query, index, filter))}
removeQueryFilter={(index) => onChange(Query.removeFilter(datasetQuery.query, index))}
addQueryAggregation={(aggregation) => onChange(Query.addAggregation(datasetQuery.query, aggregation))}
updateQueryAggregation={(index, aggregation) => onChange(Query.updateAggregation(datasetQuery.query, index, aggregation))}
removeQueryAggregation={(index) => onChange(Query.removeAggregation(datasetQuery.query, index))}
>
<div className="flex align-center mx2 my2">
<span className="text-bold px3">{previewSummary}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ import cx from "classnames";
},
metricFormSelectors)
export default class MetricForm extends Component {
updatePreviewSummary(query) {
updatePreviewSummary(datasetQuery) {
this.props.updatePreviewSummary({
...query,
...datasetQuery,
query: {
aggregation: ["count"],
...query.query,
...datasetQuery.query,
}
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ import cx from "classnames";
},
segmentFormSelectors)
export default class SegmentForm extends Component {
updatePreviewSummary(query) {
updatePreviewSummary(datasetQuery) {
this.props.updatePreviewSummary({
...query,
...datasetQuery,
query: {
...query.query,
...datasetQuery.query,
aggregation: ["count"]
}
})
Expand Down
1 change: 0 additions & 1 deletion frontend/src/metabase/components/DownloadButton.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { Component, PropTypes } from "react";
import ReactDOM from "react-dom";

import Button from "metabase/components/Button.jsx";

Expand Down
20 changes: 13 additions & 7 deletions frontend/src/metabase/components/Select.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,8 @@ class BrowserSelect extends Component {
<PopoverWithTrigger
ref="popover"
className={className}
triggerElement={
<div className={"flex align-center " + (!value ? " text-grey-3" : "")}>
<span className="AdminSelect-content mr1">{selectedName}</span>
<Icon className="AdminSelect-chevron flex-align-right" name="chevrondown" size={12} />
</div>
}
triggerClasses={cx("AdminSelect", className)}
triggerElement={<SelectButton hasValue={!!value}>{selectedName}</SelectButton>}
triggerClasses={className}
verticalAttachments={["top"]}
isInitiallyOpen={isInitiallyOpen}
>
Expand Down Expand Up @@ -117,6 +112,17 @@ class BrowserSelect extends Component {
}
}

export const SelectButton = ({ hasValue, children }) =>
<div className={"AdminSelect flex align-center " + (!hasValue ? " text-grey-3" : "")}>
<span className="AdminSelect-content mr1">{children}</span>
<Icon className="AdminSelect-chevron flex-align-right" name="chevrondown" size={12} />
</div>

SelectButton.propTypes = {
hasValue: PropTypes.bool,
children: PropTypes.any,
};

export class Option extends Component {
static propTypes = {
children: PropTypes.any,
Expand Down
1 change: 0 additions & 1 deletion frontend/src/metabase/dashboard/components/Dashboard.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { Component, PropTypes } from "react";
import ReactDOM from "react-dom";

import DashboardHeader from "../components/DashboardHeader.jsx";
import DashboardGrid from "../components/DashboardGrid.jsx";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { Component, PropTypes } from "react";
import ReactDOM from "react-dom";

import GridLayout from "./grid/GridLayout.jsx";
import DashCard from "./DashCard.jsx";
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/metabase/icon_paths.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions frontend/src/metabase/lib/card.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ export function isCardRunnable(card, tableMetadata) {
if (!card) {
return false;
}
const query = card.dataset_query;
if (query.query) {
return Query.canRun(query.query, tableMetadata);
const datasetQuery = card.dataset_query;
if (datasetQuery.query) {
return Query.canRun(datasetQuery.query, tableMetadata);
} else {
return (query.database != undefined && query.native.query !== "");
return (datasetQuery.database != undefined && datasetQuery.native.query !== "");
}
}

Expand Down
25 changes: 17 additions & 8 deletions frontend/src/metabase/lib/data_grid.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,19 @@ export function pivot(data) {
normalColValues.sort();
}


// make sure that the first element in the pivoted column list is null which makes room for the label of the other column
pivotColValues.unshift(data.cols[normalCol].display_name);

// start with an empty grid that we'll fill with the appropriate values
var pivotedRows = [];
var emptyRow = Array.apply(null, Array(pivotColValues.length)).map(function() { return null; });
for (var i=0; i < normalColValues.length; i++) {
pivotedRows.push(_.clone(emptyRow));
}
const pivotedRows = normalColValues.map((normalColValues, index) => {
const row = pivotColValues.map(() => null);
// for onVisualizationClick:
row._dimension = {
value: normalColValues,
column: data.cols[normalCol]
};
return row;
})

// fill it up with the data
for (var j=0; j < data.rows.length; j++) {
Expand All @@ -59,14 +62,20 @@ export function pivot(data) {
}

// provide some column metadata to maintain consistency
var cols = pivotColValues.map(function(val, idx) {
const cols = pivotColValues.map(function(value, idx) {
if (idx === 0) {
// first column is always the coldef of the normal column
return data.cols[normalCol];
}

var colDef = _.clone(data.cols[cellCol]);
colDef['name'] = colDef['display_name'] = formatValue(val, { column: data.cols[pivotCol] }) || "";
colDef.name = colDef.display_name = formatValue(value, { column: data.cols[pivotCol] }) || "";
// for onVisualizationClick:
colDef._dimension = {
value: value,
column: data.cols[pivotCol]
};
// delete colDef.id
return colDef;
});

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/metabase/lib/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ var STYLE_SHEET = (function() {
return style.sheet;
})();

export function addCSSRule(selector, rules, index) {
export function addCSSRule(selector, rules, index = 0) {
if("insertRule" in STYLE_SHEET) {
STYLE_SHEET.insertRule(selector + "{" + rules + "}", index);
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/metabase/lib/formatting.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ function formatMajorMinor(major, minor, options = {}) {
}
}

function formatTimeWithUnit(value, unit, options = {}) {
export function formatTimeWithUnit(value, unit, options = {}) {
let m = parseTimestamp(value, unit);
if (!m.isValid()) {
return String(value);
Expand Down
15 changes: 2 additions & 13 deletions frontend/src/metabase/lib/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { isFK, TYPE } from "metabase/lib/types";
import { stripId } from "metabase/lib/formatting";
import { format as formatExpression } from "metabase/lib/expressions/formatter";

import * as Table from "./query/table";

import * as Q from "./query/query";
import { mbql, mbqlEq } from "./query/util";
Expand Down Expand Up @@ -270,7 +271,7 @@ var Query = {
},

getExpressions(query) {
return query.expressions;
return query.expressions || {};
},

setExpression(query, name, expression) {
Expand Down Expand Up @@ -744,18 +745,6 @@ export const BreakoutClause = {
}
}

const Table = {
getField(table, fieldId) {
if (table) {
// sometimes we populate fields_lookup, sometimes we don't :(
if (table.fields_lookup) {
return table.fields_lookup[fieldId];
} else {
return _.findWhere(table.fields, { id: fieldId });
}
}
}
}

function joinList(list, joiner) {
return _.flatten(list.map((l, i) => i === list.length - 1 ? [l] : [l, joiner]), true);
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/metabase/lib/query/breakout.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
/* @flow */

import type { Breakout, BreakoutClause } from "metabase/meta/types/Query";
import type { TableMetadata } from "metabase/meta/types/Metadata";
import type { Field } from "metabase/meta/types/Field";

import Q from "metabase/lib/query";

import { add, update, remove, clear } from "./util";

// returns canonical list of Breakouts, with nulls removed
export function getBreakouts(breakout: ?BreakoutClause): Breakout[] {
return (breakout || []).filter(b => b != null);
export function getBreakouts(breakouts: ?BreakoutClause): Breakout[] {
return (breakouts || []).filter(b => b != null);
}

export function getBreakoutFields(breakouts: ?BreakoutClause, tableMetadata: TableMetadata): Field[] {
return getBreakouts(breakouts).map(breakout => (Q.getFieldTarget(breakout, tableMetadata) || {}).field);
}

// turns a list of Breakouts into the canonical BreakoutClause
Expand Down
27 changes: 27 additions & 0 deletions frontend/src/metabase/lib/query/expression.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import _ from "underscore";

import type { ExpressionName, ExpressionClause, Expression } from "metabase/meta/types/Query";

export function getExpressions(expressions: ?ExpressionClause = {}): ExpressionClause {
return expressions;
}

export function getExpressionsList(expressions: ?ExpressionClause = {}): Array<{ name: ExpressionName, expression: Expression }> {
return Object.entries(expressions).map(([name, expression]) => ({ name, expression }));
}

export function addExpression(expressions: ?ExpressionClause = {}, name: ExpressionName, expression: Expression): ?ExpressionClause {
return { ...expressions, [name]: expression };
}
export function updateExpression(expressions: ?ExpressionClause = {}, name: ExpressionName, expression: Expression, oldName?: ExpressionName): ?ExpressionClause {
if (oldName != null) {
expressions = removeExpression(expressions, oldName);
}
return addExpression(expressions, name, expression);
}
export function removeExpression(expressions: ?ExpressionClause = {}, name: ExpressionName): ?ExpressionClause {
return _.omit(expressions, name)
}
export function clearExpressions(expressions: ?ExpressionClause): ?ExpressionClause {
return {};
}
12 changes: 12 additions & 0 deletions frontend/src/metabase/lib/query/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,15 @@ export function canAddFilter(filter: ?FilterClause): boolean {
}
return true;
}

export function isSegmentFilter(filter: FilterClause): boolean {
return Array.isArray(filter) && mbqlEq(filter[0], "segment");
}

export function isCompoundFilter(filter: FilterClause): boolean {
return Array.isArray(filter) && (mbqlEq(filter[0], "and") || mbqlEq(filter[0], "or"));
}

export function isFieldFilter(filter: FilterClause): boolean {
return !isSegmentFilter(filter) && !isCompoundFilter(filter);
}
Loading

0 comments on commit ff12c03

Please sign in to comment.