Skip to content
Snippets Groups Projects
Commit f2342df5 authored by Allen Gilliland's avatar Allen Gilliland
Browse files

Merge branch 'dashboard-filters' of github.com:metabase/metabase into dashboard-filters

parents d1f99423 4c16fa31
No related branches found
No related tags found
No related merge requests found
Showing
with 368 additions and 226 deletions
import React, { Component, PropTypes } from "react";
import ReactDOM from "react-dom";
import Input from "metabase/components/Input.jsx";
import HeaderModal from "metabase/components/HeaderModal.jsx";
export default class Header extends Component {
static defaultProps = {
......@@ -11,6 +13,26 @@ export default class Header extends Component {
headerClassName: "py1 lg-py2 xl-py3 wrapper"
};
constructor(props, context) {
super(props, context);
this.state = {
headerHeight: 0
};
}
componentDidMount() {
this.componentDidUpdate();
}
componentDidUpdate() {
if (this.refs.header) {
const rect = ReactDOM.findDOMNode(this.refs.header).getBoundingClientRect();
if (this.state.headerHeight !== rect.top) {
this.setState({ headerHeight: rect.top });
}
}
}
setItemAttribute(attribute, event) {
this.props.setItemAttributeFn(attribute, event.target.value);
}
......@@ -18,7 +40,7 @@ export default class Header extends Component {
renderEditHeader() {
if (this.props.isEditing) {
return (
<div className="EditHeader wrapper py1 flex align-center">
<div className="EditHeader wrapper py1 flex align-center" ref="editHeader">
<span className="EditHeader-title">{this.props.editingTitle}</span>
<span className="EditHeader-subtitle mx1">{this.props.editingSubtitle}</span>
<span className="flex-align-right">
......@@ -29,6 +51,19 @@ export default class Header extends Component {
}
}
renderHeaderModal() {
if (this.props.headerModalMessage) {
return (
<HeaderModal
height={this.state.headerHeight}
title={this.props.headerModalMessage}
onDone={this.props.onHeaderModalDone}
onCancel={this.props.onHeaderModalCancel}
/>
);
}
}
render() {
var titleAndDescription;
if (this.props.isEditingInfo) {
......@@ -79,7 +114,8 @@ export default class Header extends Component {
return (
<div>
{this.renderEditHeader()}
<div className={"QueryBuilder-section flex align-center " + this.props.headerClassName}>
{this.renderHeaderModal()}
<div className={"QueryBuilder-section flex align-center " + this.props.headerClassName} ref="header">
<div className="Entity">
{titleAndDescription}
{attribution}
......
import React, { Component, PropTypes } from "react";
import BodyComponent from "metabase/components/BodyComponent";
import cx from "classnames";
@BodyComponent
export default class HeaderModal extends Component {
render() {
const { className, height, children, title, onDone, onCancel } = this.props;
return (
<div className={cx(className, "absolute top left right bg-brand flex flex-column layout-centered")} style={{ zIndex: 9999, height: height }}>
<h2 className="text-white pb2">{title}</h2>
<div className="flex layout-centered">
<button className="Button Button--borderless text-brand bg-white text-bold" onClick={onDone}>Done</button>
{ onCancel && <span className="text-white mx1">or</span> }
{ onCancel && <a className="cursor-pointer text-white text-bold" onClick={onCancel}>Cancel</a> }
</div>
</div>
);
}
}
......@@ -36,6 +36,10 @@
align-items: flex-start;
}
.align-end {
align-items: flex-end;
}
.align-self-end {
align-self: flex-end;
}
......
......@@ -10,6 +10,8 @@ import { augmentDatabase } from "metabase/lib/table";
import MetabaseAnalytics from "metabase/lib/analytics";
import { getPositionForNewDashCard } from "metabase/lib/dashboard_grid";
import { createParameter } from "metabase/meta/Dashboard";
const DATASET_SLOW_TIMEOUT = 15 * 1000;
const DATASET_USUALLY_FAST_THRESHOLD = 15 * 1000;
......@@ -48,6 +50,9 @@ export const MARK_NEW_CARD_SEEN = 'MARK_NEW_CARD_SEEN';
export const FETCH_DATABASE_METADATA = 'FETCH_DATABASE_METADATA';
export const SET_EDITING_PARAMETER = 'SET_EDITING_PARAMETER';
export const ADD_PARAMETER = 'ADD_PARAMETER';
// resource wrappers
const DashboardApi = new AngularResourceProxy("Dashboard", ["get", "update", "delete", "reposition_cards", "addcard", "removecard"]);
const MetabaseApi = new AngularResourceProxy("Metabase", ["dataset", "dataset_duration", "db_metadata"]);
......@@ -188,7 +193,7 @@ export const saveDashboard = createThunkAction(SAVE_DASHBOARD, function(dashId)
if (dashboard.isDirty) {
let { id, name, description, public_perms, parameters } = dashboard;
console.log("saving", parameters);
dashboard = await DashboardApi.update({ id, name, description, public_perms });
dashboard = await DashboardApi.update({ id, name, description, public_perms, parameters });
// HACK!
dashboard.parameters = parameters;
}
......@@ -241,3 +246,5 @@ export const fetchDatabaseMetadata = createThunkAction(FETCH_DATABASE_METADATA,
});
export const setDashCardVisualizationSetting = createAction(SET_DASHCARD_VISUALIZATION_SETTING);
export const setEditingParameter = createAction(SET_EDITING_PARAMETER);
......@@ -6,7 +6,9 @@ import DashboardGrid from "../components/DashboardGrid.jsx";
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper.jsx";
import MetabaseAnalytics from "metabase/lib/analytics";
import ParameterPicker from "metabase/query_builder/parameters/ParameterPicker.jsx";
import ParameterWidget from "./parameters/ParameterWidget.jsx";
import { createParameter, setParameterName } from "metabase/meta/Dashboard";
import screenfull from "screenfull";
......@@ -31,11 +33,18 @@ export default class Dashboard extends Component {
refreshElapsed: null
};
_.bindAll(this, "setRefreshPeriod", "tickRefreshClock", "setFullscreen", "setNightMode", "setEditing", "fullScreenChanged");
_.bindAll(this,
"setRefreshPeriod", "tickRefreshClock",
"setFullscreen", "setNightMode", "fullScreenChanged",
"setEditing", "setDashboardAttribute",
"addParameter"
);
}
static propTypes = {
isEditing: PropTypes.bool.isRequired,
isEditingParameter: PropTypes.bool.isRequired,
dashboard: PropTypes.object,
cards: PropTypes.array,
......@@ -169,6 +178,46 @@ export default class Dashboard extends Component {
this.props.setEditingDashboard(isEditing);
}
setDashboardAttribute(attribute, value) {
this.props.setDashboardAttributes({
id: this.props.dashboard.id,
attributes: { [attribute]: value }
});
}
addParameter(parameterOption) {
let parameters = this.props.dashboard && this.props.dashboard.parameters || [];
let parameter = createParameter(parameterOption);
this.setDashboardAttribute("parameters", [...parameters, parameter]);
this.props.setEditingParameter(parameter.id);
}
removeParameter(parameterId) {
let parameters = this.props.dashboard && this.props.dashboard.parameters || [];
parameters = _.reject(parameters, (p) => p.id === parameterId);
this.setDashboardAttribute("parameters", parameters);
}
setParameterName(parameter, name) {
let parameters = this.props.dashboard.parameters || [];
let index = _.findIndex(parameters, (p) => p.id === parameter.id);
if (index < 0) {
return;
}
this.props.setDashboardAttributes({
id: this.props.dashboard.id,
attributes: { "parameters": [
...parameters.slice(0, index),
setParameterName(parameter, name),
...parameters.slice(index + 1)
] }
});
}
setParameterValue(parameter, value) {
console.log("setting dash param", parameter, value);
......@@ -217,7 +266,7 @@ export default class Dashboard extends Component {
}
render() {
let { dashboard } = this.props;
let { dashboard, isEditing, editingParameter } = this.props;
let { error, isFullscreen, isNightMode } = this.state;
isNightMode = isNightMode && isFullscreen;
......@@ -236,15 +285,23 @@ export default class Dashboard extends Component {
onFullscreenChange={this.setFullscreen}
onNightModeChange={this.setNightMode}
onEditingChange={this.setEditing}
setDashboardAttribute={this.setDashboardAttribute}
addParameter={this.addParameter}
/>
</header>
{this.props.dashboard.parameters && this.props.dashboard.parameters.length > 0 &&
<div className="wrapper">
<div className="flex flex-row" ref="parameters">
<div className="wrapper flex flex-column align-end mt1">
<div className="flex flex-row align-end" ref="parameters">
{this.props.dashboard.parameters.map(parameter =>
<ParameterPicker
<ParameterWidget
className="ml1"
parameter={parameter}
onChange={(value) => this.setParameterValue(parameter, value)} />
isEditing={isEditing}
isSelected={editingParameter === parameter.id}
onNameChange={(name) => this.setParameterName(parameter, name)}
onChange={(value) => this.setParameterValue(parameter, value)}
setEditingParameter={this.props.setEditingParameter}
/>
)}
</div>
</div>
......
......@@ -47,7 +47,7 @@ export default class DashboardHeader extends Component {
fetchRevisions: PropTypes.func.isRequired,
revertToRevision: PropTypes.func.isRequired,
saveDashboard: PropTypes.func.isRequired,
setDashboardAttributes: PropTypes.func.isRequired,
setDashboardAttribute: PropTypes.func.isRequired,
onEditingChange: PropTypes.func.isRequired,
setRefreshPeriod: PropTypes.func.isRequired,
......@@ -67,13 +67,6 @@ export default class DashboardHeader extends Component {
this.props.fetchDashboard(this.props.dashboard.id);
}
onAttributeChange(attribute, value) {
this.props.setDashboardAttributes({
id: this.props.dashboard.id,
attributes: { [attribute]: value }
});
}
async onSave() {
await this.props.saveDashboard(this.props.dashboard.id);
this.onDoneEditing();
......@@ -90,37 +83,6 @@ export default class DashboardHeader extends Component {
this.props.onChangeLocation("/")
}
setParameter(parameter, previousName) {
console.log("setting parameter", parameter, previousName);
let parameters = this.props.dashboard && this.props.dashboard.parameters || [];
if (!_.isEmpty(previousName)) {
// remove old expression using original name. this accounts for case where parameter is renamed.
parameters = _.reject(parameters, (p) => p.name === previousName);
}
// now add the new parameter
parameters = [...parameters, parameter];
this.onAttributeChange("parameters", parameters);
console.log("parameters", parameters);
MetabaseAnalytics.trackEvent("Dashboard", "Set Parameter", !_.isEmpty(previousName));
}
removeParameter(parameter) {
console.log("remove parameter", parameter);
let parameters = this.props.dashboard && this.props.dashboard.parameters || [];
parameters = _.reject(parameters, (p) => p.name === parameter.name);
this.onAttributeChange("parameters", parameters);
console.log("parameters", parameters);
MetabaseAnalytics.trackEvent("Dashboard", "Remove Parameter");
}
// 1. fetch revisions
onFetchRevisions({ entity, id }) {
return this.props.fetchRevisions({ entity, id });
......@@ -210,10 +172,7 @@ export default class DashboardHeader extends Component {
{this.state.modal && this.state.modal === "parameters" &&
<Popover onClose={() => this.setState({modal: false})}>
<ParametersPopover
parameters={dashboard.parameters}
cards={this.props.cards}
onSetParameter={(newParameter) => this.setParameter(newParameter, name)}
onRemoveParameter={(parameter) => this.removeParameter(parameter)}
onAddParameter={this.props.addParameter}
onClose={() => this.setState({modal: false})}
/>
</Popover>
......@@ -300,7 +259,10 @@ export default class DashboardHeader extends Component {
headerButtons={this.getHeaderButtons()}
editingTitle="You are editing a dashboard"
editingButtons={this.getEditingButtons()}
setItemAttributeFn={this.onAttributeChange.bind(this)}
setItemAttributeFn={this.props.setDashboardAttribute}
headerModalMessage={this.props.isEditingParameter ?
"Select the field that should be filtered for each card" : null}
onHeaderModalDone={() => this.props.setEditingParameter(null)}
>
</Header>
);
......
:local(.parameter) {
float: right;
font-size: 16px;
font-weight: bold;
border: 2px solid var(--grey-1);
border-radius: 2px;
min-height: 36px;
min-width: 200px;
background-color: white;
display: flex;
align-items: center;
padding: 0.25em;
color: var(--grey-4);
}
:local(.parameter) input {
color: var(--grey-4);
flex: 1;
font-size: 16px;
border: none;
background-color: trasparent;
}
:local(.parameter) input:focus {
outline: none;
}
:local(.parameter.selected) {
border-color: var(--brand-color);
}
import React, { Component, PropTypes } from 'react';
import cx from "classnames";
import _ from 'underscore';
import Query from "metabase/lib/query";
import S from "./ParameterWidget.css";
import cx from "classnames";
export default class ParameterWidget extends Component {
static propTypes = {
parameter: PropTypes.object,
tableMetadata: PropTypes.object.isRequired,
onSetParameter: PropTypes.func.isRequired,
onRemoveParameter: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired
parameter: PropTypes.object
};
static defaultProps = {
parameter: null,
}
componentWillMount() {
this.componentWillReceiveProps(this.props);
}
componentWillReceiveProps(newProps) {
this.setState({
parameter: newProps.parameter
});
}
isValid() {
const { parameter, error } = this.state;
return (parameter && !_.isEmpty(parameter.name) && !error) ; //&& isExpression(expression));
}
setField(field) {
console.log("setting field", field);
if (_.isNumber(field)) {
field = ["field-id", field];
}
let parameter = this.state.parameter;
this.setState({
parameter: {...parameter, field: field, value: "Widget"}
});
}
render() {
const { parameter } = this.state;
const { tableMetadata } = this.props;
const { className, parameter, isEditing, isSelected, onNameChange, setEditingParameter } = this.props;
return (
<div style={{minWidth: "350px", maxWidth: "500px"}}>
<div className="p2">
<div className="mt3 h5 text-uppercase text-grey-3 text-bold">Give it a name</div>
<div>
<div className={className}>
{ isEditing && isSelected && <div className="mb1">Give your filter a label</div>}
{ isEditing && isSelected ?
<div className={cx(S.parameter, { [S.selected]: true })}>
<input
className="my1 p1 input block full h4 text-dark"
type="text"
value={parameter && parameter.name || ""}
placeholder="Something nice and descriptive"
onChange={(event) => this.setState({parameter: {...parameter, name: event.target.value}})}
value={parameter.name}
onChange={(e) => onNameChange(e.target.value)}
autoFocus
/>
</div>
</div>
<div className="mt2 p2 border-top flex flex-row align-center justify-between">
<div>
<button
className={cx("Button", {"Button--primary": this.isValid()})}
onClick={() => this.props.onSetParameter(this.state.parameter)}
disabled={!this.isValid()}>{this.props.parameter ? "Update" : "Done"}</button>
<span className="pl1">or</span> <a className="link" onClick={() => this.props.onCancel()}>Cancel</a>
: isEditing && !isSelected ?
<div className={S.parameter} onClick={() => setEditingParameter(parameter.id)}>
{parameter.name}
</div>
<div>
{this.props.parameter ?
<a className="pr2 text-warning link" onClick={() => this.props.onRemoveParameter(this.props.parameter)}>Remove</a>
: null }
:
<div className={S.parameter}>
{parameter.name}
</div>
</div>
}
</div>
);
}
......
/* @flow */
import React, { Component, PropTypes } from "react";
import _ from "underscore";
import Icon from "metabase/components/Icon.jsx";
import IconBorder from "metabase/components/IconBorder.jsx";
import Tooltip from "metabase/components/Tooltip.jsx";
import ParameterWidget from "./ParameterWidget.jsx";
import { formatExpression } from "metabase/lib/expressions";
import { PARAMETER_SECTIONS } from "metabase/meta/Dashboard";
import type { ParameterOption } from "metabase/meta/types/Dashboard";
import _ from "underscore";
export default class ParametersPopover extends Component {
constructor(props, context) {
super(props, context);
this.state = {
editParameter: null
};
}
static propTypes = {
parameters: PropTypes.array,
onSetParameter: PropTypes.func.isRequired,
onRemoveParameter: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired
props: {
onAddParameter: (option: ParameterOption) => {}
};
static defaultProps = {
parameters: []
state: {
section?: string
};
renderParametersWidget() {
// if we aren't editing any parameter then there is nothing to do
if (!this.state.editParameter) return null;
const { parameters } = this.props,
parameter = parameters && _.find(parameters, (p) => p.name === this.state.editParameter),
name = _.isString(this.state.editParameter) ? this.state.editParameter : "";
// TODO: at some point we need to prevent the add parameter button if there are none possible?
// TODO: pass in names that aren't allowed to be used (to prevent dupes)
return (
<ParameterWidget
parameter={parameter}
onSetParameter={(newParameter) => {
this.props.onSetParameter(newParameter, name);
this.props.onClose();
}}
onRemoveParameter={(parameter) => {
this.props.onRemoveParameter(parameter);
this.props.onClose();
}}
onCancel={() => this.setState({editParameter: null})}
/>
);
constructor(props: any, context: any) {
super(props, context);
this.state = {};
}
render() {
const { parameters } = this.props;
let sortedParameters = _.sortBy(parameters, "name");
return (
<div>
{!this.state.editParameter &&
<div style={{minWidth: "350px"}} className="p3">
<div className="pb1 h6 text-uppercase text-grey-3 text-bold">Parameters</div>
{ sortedParameters && sortedParameters.map(param =>
<div key={param.name} className="pb1 text-brand text-bold cursor-pointer flex flex-row align-center justify-between" onClick={() => this.setState({editParameter: param.name})}>
<span>{param.name}</span>
</div>
)}
<a data-metabase-event={"QueryBuilder;Show Add Parameter"} className="text-grey-2 text-bold flex align-center text-grey-4-hover cursor-pointer no-decoration transition-color" onClick={() => this.setState({editParameter: true})}>
<IconBorder borderRadius="3px">
<Icon name="add" width="14px" height="14px" />
</IconBorder>
<span className="ml1">Add a parameter</span>
</a>
</div>
}
{this.renderParametersWidget()}
</div>
);
if (this.state.section == null) {
return <ParameterOptionsSectionsPane sections={PARAMETER_SECTIONS} onSelectSection={(section) => this.setState({ section: section.id })} />
} else {
let options = _.findWhere(PARAMETER_SECTIONS, { id: this.state.section }).options;
return <ParameterOptionsPane options={options} onSelectOption={(option) => this.props.onAddParameter(option)}/>
}
}
}
const ParameterOptionsSection = ({ section, onClick }) =>
<li onClick={onClick} className="p1 px2">
<div>{section.name}</div>
<div>{section.description}</div>
</li>
const ParameterOptionsSectionsPane = ({ sections, onSelectSection }) =>
<div>
<h3 className="p2">What do you want to filter?</h3>
<ul>
{ sections.map(section =>
<ParameterOptionsSection section={section} onClick={() => onSelectSection(section) }/>
)}
</ul>
</div>
const ParameterOptionItem = ({ option, onClick }) =>
<li onClick={onClick} className="p1 px2">
<div>{option.name}</div>
<div>{option.description}</div>
</li>
const ParameterOptionsPane = ({ options, onSelectOption }) =>
<div>
<h3 className="p2">What kind of filter?</h3>
<ul>
{ options.map(option =>
<ParameterOptionItem option={option} onClick={() => onSelectOption(option)} />
)}
</ul>
</div>
/* @flow */
import React, { Component, PropTypes } from "react";
import { connect } from "react-redux";
import Dashboard from "../components/Dashboard.jsx";
import { dashboardSelectors } from "../selectors";
import * as dashboardActions from "../actions";
import { getIsEditing, getIsEditingParameter, getIsDirty, getSelectedDashboard, getDashboardComplete, getCardList, getRevisions, getCardData, getCardDurations, getDatabases, getEditingParameter } from "../selectors";
import * as mapDispatchToProps from "../actions";
const mapStateToProps = (state, props) => {
return {
isEditing: getIsEditing(state),
isEditingParameter: getIsEditingParameter(state),
isDirty: getIsDirty(state),
selectedDashboard: getSelectedDashboard(state),
dashboard: getDashboardComplete(state),
cards: getCardList(state),
revisions: getRevisions(state),
cardData: getCardData(state),
cardDurations: getCardDurations(state),
databases: getDatabases(state),
editingParameter: getEditingParameter(state)
}
}
@connect(dashboardSelectors, dashboardActions)
@connect(mapStateToProps, mapDispatchToProps)
export default class DashboardApp extends Component {
render() {
return <Dashboard {...this.props} />;
......
......@@ -17,7 +17,8 @@ import {
DELETE_CARD,
FETCH_REVISIONS,
MARK_NEW_CARD_SEEN,
FETCH_DATABASE_METADATA
FETCH_DATABASE_METADATA,
SET_EDITING_PARAMETER
} from './actions';
export const selectedDashboard = handleActions({
......@@ -79,6 +80,10 @@ export const dashcards = handleActions({
})
}, {});
export const editingParameter = handleActions({
[SET_EDITING_PARAMETER]: { next: (state, { payload }) => payload }
}, null);
export const revisions = handleActions({
[FETCH_REVISIONS]: { next: (state, { payload: { entity, id, revisions } }) => ({ ...state, [entity+'-'+id]: revisions })}
}, {});
......
/* @flow-weak */
import _ from "underscore";
import { createSelector } from 'reselect';
const selectedDashboardSelector = state => state.selectedDashboard
const isEditingSelector = state => state.isEditing;
const cardsSelector = state => state.cards;
const dashboardsSelector = state => state.dashboards;
const dashcardsSelector = state => state.dashcards;
const cardDataSelector = state => state.cardData;
const cardDurationsSelector = state => state.cardDurations;
const cardIdListSelector = state => state.cardList;
const revisionsSelector = state => state.revisions;
export const getSelectedDashboard = state => state.selectedDashboard
export const getIsEditing = state => state.isEditing;
export const getCards = state => state.cards;
export const getDashboards = state => state.dashboards;
export const getDashcards = state => state.dashcards;
export const getCardData = state => state.cardData;
export const getCardDurations = state => state.cardDurations;
export const getCardIdList = state => state.cardList;
export const getRevisions = state => state.revisions;
const databasesSelector = state => state.metadata.databases;
export const getDatabases = state => state.metadata.databases;
const dashboardSelector = createSelector(
[selectedDashboardSelector, dashboardsSelector],
export const getDashboard = createSelector(
[getSelectedDashboard, getDashboards],
(selectedDashboard, dashboards) => dashboards[selectedDashboard]
);
const dashboardCompleteSelector = createSelector(
[dashboardSelector, dashcardsSelector],
export const getDashboardComplete = createSelector(
[getDashboard, getDashcards],
(dashboard, dashcards) => (dashboard && {
...dashboard,
ordered_cards: dashboard.ordered_cards.map(id => dashcards[id]).filter(dc => !dc.isRemoved)
})
);
const isDirtySelector = createSelector(
[dashboardSelector, dashcardsSelector],
export const getIsDirty = createSelector(
[getDashboard, getDashcards],
(dashboard, dashcards) => !!(
dashboard && (
dashboard.isDirty ||
......@@ -40,12 +42,11 @@ const isDirtySelector = createSelector(
)
);
const cardListSelector = createSelector(
[cardIdListSelector, cardsSelector],
export const getCardList = createSelector(
[getCardIdList, getCards],
(cardIdList, cards) => cardIdList && cardIdList.map(id => cards[id])
);
export const dashboardSelectors = createSelector(
[isEditingSelector, isDirtySelector, selectedDashboardSelector, dashboardCompleteSelector, cardListSelector, revisionsSelector, cardDataSelector, cardDurationsSelector, databasesSelector],
(isEditing, isDirty, selectedDashboard, dashboard, cards, revisions, cardData, cardDurations, databases) => ({ isEditing, isDirty, selectedDashboard, dashboard, cards, revisions, cardData, cardDurations, databases })
);
export const getEditingParameter = (state) => state.editingParameter;
export const getIsEditingParameter = (state) => state.editingParameter != null;
/* @flow */
import type { ParameterOption, ParameterObject } from "./types/Dashboard";
import { slugify } from "metabase/lib/formatting";
import _ from "underscore";
const PARAMETER_OPTIONS: Array<ParameterOption> = [
{
id: "datetime/range",
name: "Date Picker",
description: "Lets you pick specific or relative dates",
type: "datetime"
},
{
id: "datetime/month-year",
name: "Month and Year",
description: "Like January, 2016",
type: "datetime"
},
{
id: "datetime/quarter-year",
name: "Month and Year",
description: "Like Q1, 2016",
type: "datetime"
},
];
export const PARAMETER_SECTIONS = [
{ id: "datetime", name: "Time", description: "Date range, relative date, time of day, etc." },
{ id: "location", name: "Location", description: "City, State, Country, ZIP code." },
{ id: "id", name: "ID", description: "User ID, product ID, event ID, etc." },
{ id: "category", name: "Other Categories", description: "Category, Type, Model, Rating, etc." },
];
for (const option of PARAMETER_OPTIONS) {
let sectionId = option.id.split("/")[0];
let section = _.findWhere(PARAMETER_SECTIONS, { id: sectionId });
if (!section) {
section = _.findWhere(PARAMETER_SECTIONS, { id: "category" });
}
section.options = section.options || [];
section.options.push(option);
}
export function createParameter(option: ParameterOption): ParameterObject {
return {
id: Math.floor(Math.random()*Math.pow(2,32)).toString(16),
name: "",
slug: "",
widget: option.id,
type: option.type,
}
}
export function setParameterName(parameter: ParameterObject, name: string): ParameterObject {
return {
...parameter,
name: name,
slug: slugify(name)
};
}
......@@ -4,40 +4,48 @@ import type { CardObject, CardId } from "./Card";
import type { ConcreteField } from "./Query";
type DashboardObject = {
export type DashboardObject = {
id: number,
ordered_cards: Array<DashCardObject>,
// incomplete
parameters: Array<ParameterObject>
};
type DashCardObject = {
export type DashCardObject = {
id: number,
series: Array<CardObject>,
// incomplete
parameter_mappings: Array<ParameterMappingObject>;
};
type ParameterId = string;
export type ParameterId = string;
type ParameterType =
"date-range" |
export type ParameterType =
"datetime" |
"category" |
"id";
type ParameterObject = {
export type ParameterObject = {
id: ParameterId,
name: string,
widget: string,
type: ParameterType,
default: any
default?: string
};
type ParameterMappingTarget =
export type ParameterMappingTarget =
["parameter", string] |
["dimension", ConcreteField];
type ParameterMappingObject = {
export type ParameterMappingObject = {
card_id: CardId,
parameter_id: ParameterId,
target: ParameterMappingTarget
};
export type ParameterOption = {
id: string,
name: string,
description: string,
type: ParameterType
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment