diff --git a/frontend/src/metabase/dashboard/dashboard.js b/frontend/src/metabase/dashboard/dashboard.js index c9a6eeb9fe2caf7171ecc7407658326f9a64bcbb..9ead04cd94b1261103381693f31e2ab35d9c5a5d 100644 --- a/frontend/src/metabase/dashboard/dashboard.js +++ b/frontend/src/metabase/dashboard/dashboard.js @@ -8,7 +8,7 @@ import { normalize, Schema, arrayOf } from "normalizr"; import MetabaseAnalytics from "metabase/lib/analytics"; import { getPositionForNewDashCard } from "metabase/lib/dashboard_grid"; - +import { applyParameters } from "metabase/meta/Card"; import { fetchDatabaseMetadata } from "metabase/redux/metadata"; const DATASET_SLOW_TIMEOUT = 15 * 1000; @@ -136,24 +136,7 @@ export const fetchCardData = createThunkAction(FETCH_CARD_DATA, function(card, d let dashboard = dashboards[dashboardId]; - let parameters = []; - if (dashboard && dashboard.parameters) { - for (const parameter of dashboard.parameters) { - let mapping = _.findWhere(dashcard && dashcard.parameter_mappings, { card_id: card.id, parameter_id: parameter.id }); - if (parameterValues[parameter.id] != null) { - parameters.push({ - type: parameter.type, - target: mapping && mapping.target, - value: parameterValues[parameter.id] - }); - } - } - } - - let datasetQuery = { - ...card.dataset_query, - parameters - }; + const datasetQuery = applyParameters(card, dashboard.parameters, parameterValues, dashcard && dashcard.parameter_mappings); let slowCardTimer = setTimeout(() => { if (result === null) { diff --git a/frontend/src/metabase/meta/Card.js b/frontend/src/metabase/meta/Card.js index 62d4ac56146e03e247873a91eb6b8902d067608b..36df109e35c1cc70f41d250fe3cd9f56ccec7f8e 100644 --- a/frontend/src/metabase/meta/Card.js +++ b/frontend/src/metabase/meta/Card.js @@ -1,13 +1,16 @@ /* @flow */ import type { StructuredQueryObject, NativeQueryObject, TemplateTag } from "./types/Query"; -import type { CardObject, StructuredDatasetQueryObject, NativeDatasetQueryObject } from "./types/Card"; +import type { CardObject, DatasetQueryObject, StructuredDatasetQueryObject, NativeDatasetQueryObject } from "./types/Card"; +import type { ParameterObject, ParameterId, ParameterMappingObject, ParameterMappingTarget } from "metabase/meta/types/Dashboard"; declare class Object { static values<T>(object: { [key:string]: T }): Array<T>; } import * as Query from "./Query"; +import QueryLib from "metabase/lib/query"; +import _ from "underscore"; export const STRUCTURED_QUERY_TEMPLATE: StructuredDatasetQueryObject = { type: "query", @@ -62,3 +65,46 @@ export function getTemplateTags(card: ?CardObject): Array<TemplateTag> { Object.values(card.dataset_query.native.template_tags) : []; } + +export function applyParameters( + card: CardObject, + parameters: Array<ParameterObject>, + parameterValues: { [key: ParameterId]: string } = {}, + parameterMappings: Array<ParameterMappingObject> = [] +): DatasetQueryObject { + const datasetQuery = JSON.parse(JSON.stringify(card.dataset_query)); + // clean the query + if (datasetQuery.type === "query") { + datasetQuery.query = QueryLib.cleanQuery(datasetQuery.query); + } + datasetQuery.parameters = []; + for (const parameter of parameters || []) { + let value = parameterValues[parameter.id]; + + // dashboards + const mapping = _.findWhere(parameterMappings, { card_id: card.id, parameter_id: parameter.id }); + if (value != null && mapping) { + 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 + }); + } + } + } + + return datasetQuery; +} diff --git a/frontend/src/metabase/meta/types/Card.js b/frontend/src/metabase/meta/types/Card.js index 94859cc4a6ec1b22429dea7a6a6670f41978a35e..c890081a825c355813a13abd75d572abc10eb30a 100644 --- a/frontend/src/metabase/meta/types/Card.js +++ b/frontend/src/metabase/meta/types/Card.js @@ -2,6 +2,7 @@ import type { DatabaseId } from "./base"; import type { StructuredQueryObject, NativeQueryObject } from "./Query"; +import type { ParameterInstance } from "./Dashboard"; export type CardId = number; @@ -17,13 +18,15 @@ export type CardObject = { export type StructuredDatasetQueryObject = { type: "query", database: ?DatabaseId, - query: StructuredQueryObject + query: StructuredQueryObject, + parameters?: Array<ParameterInstance> }; export type NativeDatasetQueryObject = { type: "native", database: ?DatabaseId, native: NativeQueryObject, + parameters?: Array<ParameterInstance> }; export type DatasetQueryObject = StructuredDatasetQueryObject | NativeDatasetQueryObject; diff --git a/frontend/src/metabase/meta/types/Dashboard.js b/frontend/src/metabase/meta/types/Dashboard.js index e1c3b5bdc2c41f97f4e7b383376219ff801c370b..187f14098910d6e332b933ac59330bf98c9447d1 100644 --- a/frontend/src/metabase/meta/types/Dashboard.js +++ b/frontend/src/metabase/meta/types/Dashboard.js @@ -52,3 +52,9 @@ export type ParameterOption = { description?: string, type: ParameterType }; + +export type ParameterInstance = { + type: ParameterType, + target: ParameterMappingTarget, + value: string +}; diff --git a/frontend/src/metabase/query_builder/actions.js b/frontend/src/metabase/query_builder/actions.js index 5a1e5c8fa253be94ff0639ae03be9f836b1794ea..abdd2ca351f2d921fc0cbf80678404eeafb433c5 100644 --- a/frontend/src/metabase/query_builder/actions.js +++ b/frontend/src/metabase/query_builder/actions.js @@ -14,6 +14,7 @@ import Query from "metabase/lib/query"; import { createQuery } from "metabase/lib/query"; import { loadTableAndForeignKeys } from "metabase/lib/table"; import Utils from "metabase/lib/utils"; +import { applyParameters } from "metabase/meta/Card"; import { getParameters } from "./selectors"; @@ -310,19 +311,10 @@ export const updateTemplateTag = createThunkAction(UPDATE_TEMPLATE_TAG, (templat }); export const SET_PARAMETER_VALUE = "SET_PARAMETER_VALUE"; -export const setParameterValue = createThunkAction(SET_PARAMETER_VALUE, (parameterId, value) => { - return (dispatch, getState) => { - let { qb: { parameterValues } } = getState(); - - // apply this specific value - parameterValues = { ...parameterValues, [parameterId]: value}; - - // the return value from our action is still just the id/value of the parameter set - return {id: parameterId, value}; - }; +export const setParameterValue = createAction(SET_PARAMETER_VALUE, (parameterId, value) => { + return { id: parameterId, value }; }); - export const NOTIFY_CARD_CREATED = "NOTIFY_CARD_CREATED"; export const notifyCardCreatedFn = createThunkAction(NOTIFY_CARD_CREATED, (card) => { return (dispatch, getState) => { @@ -685,40 +677,24 @@ export const setQuerySort = createThunkAction(SET_QUERY_SORT, (column) => { }; }); + // runQuery export const RUN_QUERY = "RUN_QUERY"; -export const runQuery = createThunkAction(RUN_QUERY, (card, updateUrl=true, paramValues) => { +export const runQuery = createThunkAction(RUN_QUERY, (card, updateUrl=true, parameterValues) => { return async (dispatch, getState) => { const state = getState(); const parameters = getParameters(state); // if we got a query directly on the action call then use it, otherwise take whatever is in our current state card = card || state.qb.card; - card = JSON.parse(JSON.stringify(card)); - let dataset_query = card.dataset_query, - cardIsDirty = isCardDirty(card, state.qb.originalCard); + parameterValues = parameterValues || state.qb.parameterValues || {}; - if (dataset_query.query) { - // TODO: this needs to be immutable - dataset_query.query = Query.cleanQuery(dataset_query.query); - } + const cardIsDirty = isCardDirty(card, state.qb.originalCard); - // apply any pseudo-parameters, if specified - if (parameters && parameters.length > 0) { - let templateTags = card.dataset_query.native.template_tags || {}; - let parameterValues = paramValues || state.qb.parameterValues || {}; - dataset_query.parameters = parameters.map(parameter => { - let tag = _.findWhere(templateTags, { id: parameter.id }); - let value = parameterValues[parameter.id]; - if (value != null && tag) { - return { - type: parameter.type, - target: [tag.type === "dimension" ? "dimension" : "variable", ["template-tag", tag.name]], - value: value - }; - } - }).filter(p => p); - } + card = { + ...card, + dataset_query: applyParameters(card, parameters, parameterValues) + }; if (updateUrl) { state.qb.updateUrl(card, cardIsDirty); @@ -728,14 +704,14 @@ export const runQuery = createThunkAction(RUN_QUERY, (card, updateUrl=true, para let startTime = new Date(); // make our api call - Metabase.dataset({ timeout: cancelQueryDeferred.promise }, dataset_query, function (queryResult) { + Metabase.dataset({ timeout: cancelQueryDeferred.promise }, card.dataset_query, function (queryResult) { dispatch(queryCompleted(card, queryResult)); }, function (error) { dispatch(queryErrored(startTime, error)); }); - MetabaseAnalytics.trackEvent("QueryBuilder", "Run Query", dataset_query.type); + MetabaseAnalytics.trackEvent("QueryBuilder", "Run Query", card.dataset_query.type); // HACK: prevent SQL editor from losing focus try { ace.edit("id_sql").focus() } catch (e) {}