Skip to content

Commit

Permalink
Merge pull request #4654 from metabase/add_filter_widgets_23.1
Browse files Browse the repository at this point in the history
Add filter widgets 23.1
  • Loading branch information
salsakran committed Mar 30, 2017
2 parents 8b1823c + 1476ce8 commit 3c09c54
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 73 deletions.
28 changes: 12 additions & 16 deletions frontend/src/metabase/meta/Card.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,29 +90,25 @@ export function applyParameters(
datasetQuery.parameters = [];
for (const parameter of parameters || []) {
let value = parameterValues[parameter.id];
if (value == null) {
continue;
}

// dashboards
const mapping = _.findWhere(parameterMappings, { card_id: card.id, parameter_id: parameter.id });
if (value != null && mapping) {
if (mapping) {
// mapped target, e.x. on a dashboard
datasetQuery.parameters.push({
type: parameter.type,
target: mapping.target,
value: value
});
}

// SQL parameters
if (datasetQuery.type === "native") {
let tag = _.findWhere(datasetQuery.native.template_tags, { id: parameter.id });
if (tag) {
datasetQuery.parameters.push({
type: parameter.type,
target: tag.type === "dimension" ?
["dimension", ["template-tag", tag.name]]:
["variable", ["template-tag", tag.name]],
value: value
});
}
} else if (parameter.target) {
// inline target, e.x. on a card
datasetQuery.parameters.push({
type: parameter.type,
target: parameter.target,
value: value
});
}
}

Expand Down
16 changes: 12 additions & 4 deletions frontend/src/metabase/meta/Dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type Field from "./metadata/Field";
import type { FieldId } from "./types/Field";
import type { TemplateTag } from "./types/Query";
import type { Card } from "./types/Card";
import type { ParameterOption, Parameter, ParameterMappingUIOption, ParameterMappingTarget, DimensionTarget, VariableTarget } from "./types/Dashboard";
import type { ParameterOption, Parameter, ParameterType, ParameterMappingUIOption, ParameterMappingTarget, DimensionTarget, VariableTarget } from "./types/Dashboard";

import { getTemplateTags } from "./Card";

Expand Down Expand Up @@ -217,14 +217,18 @@ export function getParameterMappingTargetField(metadata: Metadata, card: Card, t
return null;
}
function fieldFilterForParameter(parameter: Parameter): FieldFilter {
const [type] = parameter.type.split("/");
function fieldFilterForParameter(parameter: Parameter) {
return fieldFilterForParameterType(parameter.type);
}
export function fieldFilterForParameterType(parameterType: ParameterType): FieldFilter {
const [type] = parameterType.split("/");
switch (type) {
case "date": return (field: Field) => field.isDate();
case "id": return (field: Field) => field.isID();
case "category": return (field: Field) => field.isCategory();
}
switch (parameter.type) {
switch (parameterType) {
case "location/city": return (field: Field) => isa(field.special_type, TYPE.City);
case "location/state": return (field: Field) => isa(field.special_type, TYPE.State);
case "location/zip_code": return (field: Field) => isa(field.special_type, TYPE.ZipCode);
Expand All @@ -233,6 +237,10 @@ function fieldFilterForParameter(parameter: Parameter): FieldFilter {
return (field: Field) => false;
}
export function parameterOptionsForField(field: Field): ParameterOption[] {
return PARAMETER_OPTIONS.filter(option => fieldFilterForParameterType(option.type)(field));
}
function tagFilterForParameter(parameter: Parameter): TemplateTagFilter {
const [type, subtype] = parameter.type.split("/");
switch (type) {
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/metabase/meta/Parameter.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ export type ParameterValues = {
[id: ParameterId]: string
};

// NOTE: this should mirror `template-tag-parameters` in src/metabase/api/embed.clj
export function getTemplateTagParameters(tags: TemplateTag[]): Parameter[] {
return tags.filter(tag => tag.type != null && tag.type !== "dimension")
return tags.filter(tag => tag.type != null && (tag.widget_type || tag.type !== "dimension"))
.map(tag => ({
id: tag.id,
type: tag.type === "date" ? "date/single" : "category",
target: ["variable", ["template-tag", tag.name]],
type: tag.widget_type || (tag.type === "date" ? "date/single" : "category"),
target: tag.type === "dimension" ?
["dimension", ["template-tag", tag.name]]:
["variable", ["template-tag", tag.name]],
name: tag.display_name,
slug: tag.name,
default: tag.default
Expand Down
Empty file.
3 changes: 3 additions & 0 deletions frontend/src/metabase/meta/types/Query.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import type { TableId } from "./Table";
import type { FieldId } from "./Field";
import type { SegmentId } from "./Segment";
import type { ParameterType } from "./Dashboard";

export type MetricId = number;

Expand All @@ -27,6 +28,8 @@ export type TemplateTag = {
display_name: string,
type: string,
dimension?: ["field-id", number],
widget_type?: ParameterType,
required?: boolean,
default?: string,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import Toggle from "metabase/components/Toggle.jsx";
import Select, { Option } from "metabase/components/Select.jsx";
import ParameterValueWidget from "metabase/dashboard/components/parameters/ParameterValueWidget.jsx";

import { parameterOptionsForField } from "metabase/meta/Dashboard";
import Field from "metabase/meta/metadata/Field";

import _ from "underscore";

export default class TagEditorParam extends Component {
Expand Down Expand Up @@ -46,7 +49,28 @@ export default class TagEditorParam extends Component {
this.props.onUpdate({
...this.props.tag,
type: type,
dimension: undefined
dimension: undefined,
widget_type: undefined
});
}
}

setDimension(fieldId) {
const { tag, onUpdate, databaseFields } = this.props;
const dimension = ["field-id", fieldId];
if (!_.isEqual(tag.dimension !== dimension)) {
const field = _.findWhere(databaseFields, { id: fieldId });
const options = parameterOptionsForField(new Field(field));
let widget_type;
if (tag.widget_type && _.findWhere(options, { type: tag.widget_type })) {
widget_type = tag.widget_type;
} else if (options.length > 0) {
widget_type = options[0].type;
}
onUpdate({
...tag,
dimension,
widget_type
});
}
}
Expand All @@ -60,11 +84,19 @@ export default class TagEditorParam extends Component {
dabaseHasSchemas = schemas.length > 1;
}

let widgetOptions;
if (tag.type === "dimension" && tag.dimension) {
const field = _.findWhere(databaseFields, { id: tag.dimension[1] });
if (field) {
widgetOptions = parameterOptionsForField(new Field(field));
}
}

return (
<div className="pb2 mb2 border-bottom border-dark">
<h3 className="pb1">{tag.name}</h3>
<h3 className="pb2">{tag.name}</h3>

<div className="pb2">
<div className="pb1">
<h5 className="pb1 text-normal">Filter label</h5>
<input
type="text"
Expand All @@ -74,7 +106,7 @@ export default class TagEditorParam extends Component {
/>
</div>

<div className="pb2">
<div className="pb1">
<h5 className="pb1 text-normal">Variable type</h5>
<Select
className="border-med bg-white block"
Expand All @@ -90,36 +122,13 @@ export default class TagEditorParam extends Component {
</Select>
</div>

{ tag.type !== "dimension" &&
<div className="flex align-center pb2">
<h5 className="text-normal mr1">Required?</h5>
<Toggle value={tag.required} onChange={(value) => this.setRequired(value)} />
</div>
}

{ tag.type !== "dimension" && tag.required &&
<div className="pb2">
<h5 className="pb1 text-normal">Default value</h5>
<ParameterValueWidget
parameter={{
type: tag.type === "date" ? "date/single" : null
}}
value={tag.default}
setValue={(value) => this.setParameterAttribute("default", value)}
className="AdminSelect p1 text-bold text-grey-4 bordered border-med rounded bg-white"
isEditing
commitImmediately
/>
</div>
}

{ tag.type === "dimension" &&
<div className="pb2">
<div className="pb1">
<h5 className="pb1 text-normal">Field</h5>
<Select
className="border-med bg-white block"
value={Array.isArray(tag.dimension) ? tag.dimension[1] : null}
onChange={(e) => this.setParameterAttribute("dimension", ["field-id", e.target.value])}
onChange={(e) => this.setDimension(e.target.value)}
searchProp="name"
searchCaseInsensitive
isInitiallyOpen={!tag.dimension}
Expand All @@ -137,6 +146,48 @@ export default class TagEditorParam extends Component {

</div>
}

{ widgetOptions && widgetOptions.length > 0 &&
<div className="pb1">
<h5 className="pb1 text-normal">Widget</h5>
<Select
className="border-med bg-white block"
value={tag.widget_type}
onChange={(e) => this.setParameterAttribute("widget_type", e.target.value)}
isInitiallyOpen={!tag.widget_type}
placeholder="Select…"
>
{[{ name: "None", type: undefined }].concat(widgetOptions).map(widgetOption =>
<Option key={widgetOption.type} value={widgetOption.type}>
{widgetOption.name}
</Option>
)}
</Select>
</div>
}

{ tag.type !== "dimension" &&
<div className="flex align-center pb1">
<h5 className="text-normal mr1">Required?</h5>
<Toggle value={tag.required} onChange={(value) => this.setRequired(value)} />
</div>
}

{ ((tag.type !== "dimension" && tag.required) || (tag.type === "dimension" || tag.widget_type)) &&
<div className="pb1">
<h5 className="pb1 text-normal">Default value</h5>
<ParameterValueWidget
parameter={{
type: tag.widget_type || (tag.type === "date" ? "date/single" : null)
}}
value={tag.default}
setValue={(value) => this.setParameterAttribute("default", value)}
className="AdminSelect p1 text-bold text-grey-4 bordered border-med rounded bg-white"
isEditing
commitImmediately
/>
</div>
}
</div>
);
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/metabase/query_builder/reducers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Utils from "metabase/lib/utils";
import { handleActions } from "redux-actions";
import { assoc } from "icepick";
import { assoc, dissoc } from "icepick";

import {
RESET_QB,
Expand Down Expand Up @@ -158,7 +158,7 @@ export const queryExecutionPromise = handleActions({
}, null);

export const parameterValues = handleActions({
[SET_PARAMETER_VALUE]: { next: (state, { payload: { id, value }}) => assoc(state, id, value) }
[SET_PARAMETER_VALUE]: { next: (state, { payload: { id, value }}) => value == null ? dissoc(state, id) : assoc(state, id, value) }
}, {});

export const currentState = handleActions({
Expand Down
18 changes: 10 additions & 8 deletions src/metabase/api/database.clj
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,16 @@
"Get a list of all `Fields` in `Database`."
[id]
(read-check Database id)
(for [{:keys [id display_name table]} (filter mi/can-read? (-> (db/select [Field :id :display_name :table_id]
:table_id [:in (db/select-field :id Table, :db_id id)]
:visibility_type [:not-in ["sensitive" "retired"]])
(hydrate :table)))]
{:id id
:name display_name
:table_name (:display_name table)
:schema (:schema table)}))
(for [{:keys [id display_name table base_type special_type]} (filter mi/can-read? (-> (db/select [Field :id :display_name :table_id :base_type :special_type]
:table_id [:in (db/select-field :id Table, :db_id id)]
:visibility_type [:not-in ["sensitive" "retired"]])
(hydrate :table)))]
{:id id
:name display_name
:base_type base_type
:special_type special_type
:table_name (:display_name table)
:schema (:schema table)}))


;;; ------------------------------------------------------------ GET /api/database/:id/idfields ------------------------------------------------------------
Expand Down
10 changes: 6 additions & 4 deletions src/metabase/api/embed.clj
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,14 @@
"Transforms native query's `template_tags` into `parameters`."
[card]
;; NOTE: this should mirror `getTemplateTagParameters` in frontend/src/metabase/meta/Parameter.js
(for [[_ {tag-type :type, :as tag}] (get-in card [:dataset_query :native :template_tags])
(for [[_ {tag-type :type, widget-type :widget_type, :as tag}] (get-in card [:dataset_query :native :template_tags])
:when (and tag-type
(not= tag-type "dimension"))]
(or widget-type (not= tag-type "dimension")))]
{:id (:id tag)
:type (if (= tag-type "date") "date/single" "category")
:target ["variable" ["template-tag" (:name tag)]]
:type (or widget-type (if (= tag-type "date") "date/single" "category"))
:target (if (= tag-type "dimension")
["dimension" ["template-tag" (:name tag)]]
["variable" ["template-tag" (:name tag)]])
:name (:display_name tag)
:slug (:name tag)
:default (:default tag)}))
Expand Down
15 changes: 8 additions & 7 deletions src/metabase/query_processor/sql_parameters.clj
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,14 @@
;; TAGS in this case are simple params like {{x}} that get replaced with a single value ("ABC" or 1) as opposed to a "FieldFilter" clause like Dimensions
(def ^:private TagParam
"Schema for values passed in as part of the `:template_tags` list."
{(s/optional-key :id) su/NonBlankString ; this is used internally by the frontend
:name su/NonBlankString
:display_name su/NonBlankString
:type (s/enum "number" "dimension" "text" "date")
(s/optional-key :dimension) [s/Any]
(s/optional-key :required) s/Bool
(s/optional-key :default) s/Any})
{(s/optional-key :id) su/NonBlankString ; this is used internally by the frontend
:name su/NonBlankString
:display_name su/NonBlankString
:type (s/enum "number" "dimension" "text" "date")
(s/optional-key :dimension) [s/Any]
(s/optional-key :widget_type) su/NonBlankString
(s/optional-key :required) s/Bool
(s/optional-key :default) s/Any})

(def ^:private DimensionValue
{:type su/NonBlankString
Expand Down

0 comments on commit 3c09c54

Please sign in to comment.