-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7048 from metabase/automagic-dashboards-stage1
Automagic dashboards
- Loading branch information
Showing
75 changed files
with
4,513 additions
and
516 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
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,68 @@ | ||
/* @flow */ | ||
|
||
import React from "react"; | ||
import { Link } from "react-router"; | ||
|
||
import Icon from "metabase/components/Icon"; | ||
import MetabotLogo from "metabase/components/MetabotLogo"; | ||
|
||
import { t } from "c-3po"; | ||
|
||
import type { Candidate } from "metabase/meta/types/Auto"; | ||
|
||
const DEFAULT_TITLE = t`Hi, Metabot here.`; | ||
const DEFAULT_DESCRIPTION = ""; | ||
|
||
export const ExplorePane = ({ | ||
options, | ||
// $FlowFixMe | ||
title = DEFAULT_TITLE, | ||
// $FlowFixMe | ||
description = DEFAULT_DESCRIPTION, | ||
}: { | ||
options?: ?(Candidate[]), | ||
title?: ?string, | ||
description?: ?string, | ||
}) => ( | ||
<div> | ||
{title && ( | ||
<div className="flex align-center mb2"> | ||
<MetabotLogo className="mr2" /> | ||
<h3> | ||
<span>{title}</span> | ||
</h3> | ||
</div> | ||
)} | ||
{description && ( | ||
<div className="mb4"> | ||
<span>{description}</span> | ||
</div> | ||
)} | ||
{options && <ExploreList options={options} />} | ||
</div> | ||
); | ||
|
||
export const ExploreList = ({ options }: { options: Candidate[] }) => ( | ||
<ol className="Grid Grid--1of2 Grid--gutters"> | ||
{options && | ||
options.map((option, index) => ( | ||
<li className="Grid-cell" key={index}> | ||
<ExploreOption option={option} /> | ||
</li> | ||
))} | ||
</ol> | ||
); | ||
|
||
export const ExploreOption = ({ option }: { option: Candidate }) => ( | ||
<Link to={option.url} className="link flex align-center text-bold"> | ||
<div | ||
className="bg-slate-almost-extra-light p2 flex align-center rounded mr1 justify-center text-gold" | ||
style={{ width: 48, height: 48 }} | ||
> | ||
<Icon name="bolt" size={32} /> | ||
</div> | ||
<span>{option.title}</span> | ||
</Link> | ||
); | ||
|
||
export default ExplorePane; |
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,11 @@ | ||
import React from "react"; | ||
import cx from "classnames"; | ||
|
||
const MetabotLogo = ({ className }) => ( | ||
<div | ||
style={{ width: 58, height: 40 }} | ||
className={cx("bg-brand rounded", className)} | ||
/> | ||
); | ||
|
||
export default MetabotLogo; |
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,42 @@ | ||
/* @flow */ | ||
|
||
import React, { Component } from "react"; | ||
|
||
type Props = { | ||
period: number, | ||
quotes: string[], | ||
}; | ||
type State = { | ||
count: number, | ||
}; | ||
|
||
export default class Quotes extends Component { | ||
props: Props; | ||
state: State = { | ||
count: 0, | ||
}; | ||
|
||
_timer: ?number = null; | ||
|
||
static defaultProps = { | ||
quotes: [], | ||
period: 1000, | ||
}; | ||
|
||
componentWillMount() { | ||
this._timer = setInterval( | ||
() => this.setState({ count: this.state.count + 1 }), | ||
this.props.period, | ||
); | ||
} | ||
componentWillUnmount() { | ||
if (this._timer != null) { | ||
clearInterval(this._timer); | ||
} | ||
} | ||
render() { | ||
const { quotes } = this.props; | ||
const { count } = this.state; | ||
return <span>{quotes[count % quotes.length]}</span>; | ||
} | ||
} |
135 changes: 135 additions & 0 deletions
135
frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx
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,135 @@ | ||
import React from "react"; | ||
|
||
import { connect } from "react-redux"; | ||
import { push } from "react-router-redux"; | ||
import { Link } from "react-router"; | ||
import { withBackground } from "metabase/hoc/Background"; | ||
import ActionButton from "metabase/components/ActionButton"; | ||
import Icon from "metabase/components/Icon"; | ||
import cxs from "cxs"; | ||
|
||
import { Dashboard } from "./Dashboard"; | ||
import DashboardData from "metabase/dashboard/hoc/DashboardData"; | ||
import Parameters from "metabase/parameters/components/Parameters"; | ||
|
||
import { DashboardApi } from "metabase/services"; | ||
import * as Urls from "metabase/lib/urls"; | ||
|
||
import { dissoc } from "icepick"; | ||
|
||
const suggestionClasses = cxs({ | ||
":hover h3": { | ||
color: "#509ee3", | ||
}, | ||
":hover .Icon": { | ||
color: "#F9D45C", | ||
}, | ||
}); | ||
|
||
const SuggestionsList = ({ suggestions }) => ( | ||
<ol className="px2"> | ||
{suggestions.map((s, i) => ( | ||
<li key={i} className={suggestionClasses}> | ||
<Link | ||
to={s.url} | ||
className="bordered rounded bg-white shadowed mb2 p2 flex no-decoration" | ||
> | ||
<div | ||
className="bg-slate-extra-light rounded flex align-center justify-center text-slate mr1 flex-no-shrink" | ||
style={{ width: 48, height: 48 }} | ||
> | ||
<Icon name="bolt" className="Icon text-grey-1" size={22} /> | ||
</div> | ||
<div> | ||
<h3 className="m0 mb1 ml1">{s.title}</h3> | ||
<p className="text-grey-4 ml1 mt0 mb0">{s.description}</p> | ||
</div> | ||
</Link> | ||
</li> | ||
))} | ||
</ol> | ||
); | ||
|
||
const SuggestionsSidebar = ({ related }) => ( | ||
<div className="flex flex-column"> | ||
<div className="py2 text-centered my3"> | ||
<h3>More explorations</h3> | ||
</div> | ||
{Object.values(related).map(suggestions => ( | ||
<SuggestionsList suggestions={suggestions} /> | ||
))} | ||
</div> | ||
); | ||
|
||
const getDashboardId = (state, { params: { splat } }) => | ||
`/auto/dashboard/${splat}`; | ||
|
||
const mapStateToProps = (state, props) => ({ | ||
dashboardId: getDashboardId(state, props), | ||
}); | ||
|
||
@connect(mapStateToProps, { push }) | ||
@DashboardData | ||
class AutomaticDashboardApp extends React.Component { | ||
save = async () => { | ||
const { dashboard, push } = this.props; | ||
// remove the transient id before trying to save | ||
const newDashboard = await DashboardApi.save(dissoc(dashboard, "id")); | ||
push(Urls.dashboard(newDashboard.id)); | ||
}; | ||
|
||
render() { | ||
const { | ||
dashboard, | ||
parameters, | ||
parameterValues, | ||
setParameterValue, | ||
location, | ||
} = this.props; | ||
return ( | ||
<div className="flex"> | ||
<div className="flex-full"> | ||
<div className="bg-white border-bottom py2"> | ||
<div className="wrapper flex align-center"> | ||
<Icon name="bolt" className="text-gold mr1" size={24} /> | ||
<h2>{dashboard && dashboard.name}</h2> | ||
<ActionButton | ||
className="ml-auto bg-green text-white" | ||
borderless | ||
actionFn={this.save} | ||
> | ||
Save this | ||
</ActionButton> | ||
</div> | ||
</div> | ||
<div className="px3 pb4"> | ||
{parameters && | ||
parameters.length > 0 && ( | ||
<div className="px1 pt1"> | ||
<Parameters | ||
parameters={parameters.map(p => ({ | ||
...p, | ||
value: parameterValues && parameterValues[p.id], | ||
}))} | ||
query={location.query} | ||
setParameterValue={setParameterValue} | ||
syncQueryString | ||
isQB | ||
/> | ||
</div> | ||
)} | ||
<Dashboard {...this.props} /> | ||
</div> | ||
</div> | ||
{dashboard && | ||
dashboard.related && ( | ||
<div className="Layout-sidebar flex-no-shrink"> | ||
<SuggestionsSidebar related={dashboard.related} /> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export default withBackground("bg-slate-extra-light")(AutomaticDashboardApp); |
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,60 @@ | ||
/* @flow */ | ||
|
||
import React, { Component } from "react"; | ||
import cx from "classnames"; | ||
|
||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper"; | ||
import DashboardGrid from "metabase/dashboard/components/DashboardGrid"; | ||
import DashboardData from "metabase/dashboard/hoc/DashboardData"; | ||
|
||
import type { Dashboard as _Dashboard } from "metabase/meta/types/Dashboard"; | ||
import type { Parameter } from "metabase/meta/types/Parameter"; | ||
|
||
type Props = { | ||
location?: { query: { [key: string]: string } }, | ||
dashboardId: string, | ||
|
||
dashboard?: _Dashboard, | ||
parameters: Parameter[], | ||
parameterValues: { [key: string]: string }, | ||
|
||
initialize: () => void, | ||
isFullscreen: boolean, | ||
isNightMode: boolean, | ||
fetchDashboard: ( | ||
dashId: string, | ||
query?: { [key: string]: string }, | ||
) => Promise<void>, | ||
fetchDashboardCardData: (options: { | ||
reload: boolean, | ||
clear: boolean, | ||
}) => Promise<void>, | ||
setParameterValue: (id: string, value: string) => void, | ||
setErrorPage: (error: { status: number }) => void, | ||
}; | ||
|
||
export class Dashboard extends Component { | ||
props: Props; | ||
|
||
render() { | ||
const { dashboard } = this.props; | ||
|
||
return ( | ||
<LoadingAndErrorWrapper | ||
className={cx("Dashboard p1 flex-full")} | ||
loading={!dashboard} | ||
> | ||
{() => ( | ||
<DashboardGrid | ||
{...this.props} | ||
className={"spread"} | ||
// Don't allow clicking titles on public dashboards | ||
navigateToNewCardFromDashboard={null} | ||
/> | ||
)} | ||
</LoadingAndErrorWrapper> | ||
); | ||
} | ||
} | ||
|
||
export default DashboardData(Dashboard); |
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
Oops, something went wrong.