Skip to content
Snippets Groups Projects
Commit af90d88f authored by Atte Keinänen's avatar Atte Keinänen
Browse files

New query start page styling

parent fe7f052f
No related branches found
No related tags found
No related merge requests found
Showing
with 353 additions and 12 deletions
import React, { Component } from "react";
import cx from "classnames";
class NewQueryOption extends Component {
props: {
image: string,
title: string,
description: string,
onClick: () => void
};
state = {
hover: false
};
render() {
const { width, image, title, description, onClick } = this.props;
const { hover } = this.state;
return (
<div
className="bg-white p1 align-center bordered rounded cursor-pointer transition-all text-centered text-brand-light"
style={{
boxShadow: hover ? "0 3px 8px 0 rgba(220,220,220,0.50)" : "0 1px 3px 0 rgba(220,220,220,0.50)",
height: "310px"
}}
onMouseOver={() => this.setState({hover: true})}
onMouseLeave={() => this.setState({hover: false})}
onClick={onClick}
>
<div className="flex align-center layout-centered" style={{ height: "160px" }}>
<img
src={`${image}.png`}
style={{ width: width ? `${width}px` : "210px" }}
srcSet={`${image}@2x.png 2x`}
/>
</div>
<div className="text-grey-2 text-normal mt2 mb2 text-paragraph" style={{lineHeight: "1.5em"}}>
<h2 className={cx("transition-all", {"text-grey-5": !hover}, {"text-brand": hover})}>{title}</h2>
<p className={"text-grey-4"}>{description}</p>
</div>
</div>
);
}
}
export default class NewQuery extends Component {
props: {
onClose: () => void
};
state = {
addingSavedMetric: false
}
render() {
return (
<div className="flex-full full ml-auto mr-auto pl1 pr1 mt2 mb2 align-center"
style={{maxWidth: "800px"}}>
<ol className="flex-full Grid Grid--guttersXl Grid--full small-Grid--1of2">
<li className="Grid-cell">
{/*TODO: Move illustrations to the new location in file hierarchy. At the same time put an end to the equal-size-@2x ridicule. */}
<NewQueryOption
image="/app/img/questions_illustration"
title="Metrics"
description="See data over time, as a map, or pivoted to help you understand trends or changes."
onClick={() => this.setState({addingSavedMetric: true})}
/>
</li>
<li className="Grid-cell">
<NewQueryOption
image="/app/img/list_illustration"
title="Segments"
description="Explore tables and see what’s going on underneath your charts."
width={180}
onClick={() => alert("Not implemented yet.")}
/>
</li>
<li className="Grid-cell">
{/*TODO: Move illustrations to the new location in file hierarchy. At the same time put an end to the equal-size-@2x ridicule. */}
<NewQueryOption
image="/app/img/custom_question"
title="Custom"
description="Use the simple query builder to create your own new custom question."
onClick={() => this.setState({addingSavedMetric: true})}
/>
</li>
<li className="Grid-cell">
<NewQueryOption
image="/app/img/sql_illustration"
title="SQL"
description="Use SQL or other native languages for data prep or manipulation."
onClick={() => alert("Not implemented yet.")}
/>
</li>
</ol>
</div>
);
}
}
/* @flow */
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchDatabases, fetchTableMetadata } from 'metabase/redux/metadata'
import { resetQuery, updateQuery } from '../new_query'
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
import { getQuery } from "../selectors";
import Question from "metabase-lib/lib/Question";
import Table from "metabase-lib/lib/metadata/Table";
import Database from "metabase-lib/lib/metadata/Database";
import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery"
import AggregationOption from "metabase-lib/lib/metadata/AggregationOption";
import type { Field } from "metabase/meta/types/Field";
import type { TableId } from "metabase/meta/types/Table";
import Metadata from "metabase-lib/lib/metadata/Metadata";
import { getMetadata, getTables } from "metabase/selectors/metadata";
class OptionListItem extends Component {
props: {
item: any,
action: (any) => void,
children?: React$Element<any>
}
onClick = () => { this.props.action(this.props.item); }
render() {
return (
<li onClick={this.onClick}>
{ this.props.children }
</li>
);
}
}
const mapStateToProps = state => ({
query: getQuery(state),
metadata: getMetadata(state),
tables: getTables(state)
})
const mapDispatchToProps = {
fetchDatabases,
fetchTableMetadata,
resetQuery,
updateQuery
}
type Props = {
// Component parameters
question: Question,
onComplete: (StructuredQuery) => void,
// Properties injected with redux connect
query: StructuredQuery,
resetQuery: () => void,
updateQuery: (StructuredQuery) => void,
fetchDatabases: () => void,
fetchTableMetadata: (TableId) => void,
metadata: Metadata
}
/**
* NewQueryOptions forms together with NewQueryBar the elements of new query flow.
* It renders the current options to choose from (for instance databases, tables or metrics).
*/
export class NewQueryOptions extends Component {
props: Props
componentWillMount() {
this.props.fetchDatabases();
this.props.resetQuery();
}
setDatabase = (database: Database) => {
this.props.updateQuery(this.props.query.setDatabase(database))
}
setTable = (table: Table) => {
this.props.fetchTableMetadata(table.id);
this.props.updateQuery(this.props.query.setTable(table))
}
setAggregation = (option: AggregationOption) => {
const updatedQuery = this.props.query.addAggregation(option.toAggregation().clause);
if (option.hasFields()) {
this.props.updateQuery(updatedQuery)
} else {
this.props.onComplete(updatedQuery);
}
}
setAggregationField = (field: Field) => {
const { query } = this.props;
const aggregation = query.aggregationsWrapped()[0];
if (!aggregation) throw new Error("Trying to set the field of a non-existing aggregation");
const updatedQuery = this.props.query.updateAggregation(0, aggregation.setField(field.id).clause);
this.props.onComplete(updatedQuery);
}
render() {
const { query, metadata } = this.props
if (!query) {
return <LoadingAndErrorWrapper loading={true}/>
}
const database = query.database()
const table = query.table()
const aggregation = query.aggregationsWrapped()[0]
const aggregationOption = aggregation && aggregation.getOption()
return (
<LoadingAndErrorWrapper loading={!query}>
<div className="wrapper wrapper--trim">
{ !database && (
<div>
<h2>Pick a database</h2>
<ol>
{ metadata.databasesList().map(database =>
<OptionListItem key={database.id} item={database} action={this.setDatabase}>
{ database.name }
</OptionListItem>
)}
</ol>
</div>
)}
{ database && !table && (
<div>
<h2>Pick a table</h2>
<ol>
{ database.tables.map(table =>
<OptionListItem key={table.id} item={table} action={this.setTable}>
{ table.display_name }
</OptionListItem>
)}
</ol>
</div>
)}
{ table && !aggregationOption && (
<ol>
{ query.aggregationOptionsWithoutRows().map(option =>
<OptionListItem key={option.short} item={option} action={this.setAggregation}>
{option.name}
</OptionListItem>
)}
</ol>
)}
{ aggregationOption && (
<ol>
{ aggregationOption.fields[0].map(field =>
<OptionListItem key={field.id} item={field} action={this.setAggregationField}>
{field.name}
</OptionListItem>
)}
</ol>
)}
</div>
</LoadingAndErrorWrapper>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(NewQueryOptions)
......@@ -28,7 +28,7 @@ class Start extends Component {
Start
<div className="Grid Grid--1of2 Grid--gutters">
{metrics &&
<div className="Grid-cell">
<div className= "Grid-cell">
<Link to="/question/new/metrics">
<div className="bordered rounded shadowed p4">
<h2>Metrics</h2>
......
/**
* Redux actions and reducers for the new query flow
* (used both for new questions and for adding "ad-hoc metrics" to multi-query questions)
*/
import { handleActions, combineReducers } from "metabase/lib/redux";
import StructuredQuery, { STRUCTURED_QUERY_TEMPLATE } from "metabase-lib/lib/queries/StructuredQuery";
import type { DatasetQuery } from "metabase/meta/types/Card";
/**
* Initializes the new query flow for a given question
*/
export const RESET_QUERY = "metabase/new_query/RESET_QUERY";
export function resetQuery() {
return function(dispatch, getState) {
dispatch.action(RESET_QUERY, STRUCTURED_QUERY_TEMPLATE)
}
}
export const UPDATE_QUERY = "metabase/new_query/UPDATE_QUERY";
export function updateQuery(updatedQuery: StructuredQuery) {
return function(dispatch, getState) {
dispatch.action(UPDATE_QUERY, updatedQuery.datasetQuery())
}
}
/**
* The current query that we are creating
*/
// TODO Atte Keinänen 6/12/17: Test later how Flow typing with redux-actions could work best for our reducers
// something like const query = handleActions<DatasetQuery>({
const datasetQuery = handleActions({
[RESET_QUERY]: (state, { payload }): DatasetQuery => payload,
[UPDATE_QUERY]: (state, { payload }): DatasetQuery => payload
}, STRUCTURED_QUERY_TEMPLATE);
export default combineReducers({
datasetQuery
});
/**
* Would possibly be nice to test the whole action-reducer-selector loop in here
*/
describe("New query flow", () => {
it("temporary placeholder test", () => {
expect(true).toEqual(true);
})
});
/**
* Redux selectors for the new query flow
* (used both for new questions and for adding "ad-hoc metrics" to multi-query questions)
*/
import { getQuestion } from "metabase/query_builder/selectors";
import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery";
export const getQuery = state => new StructuredQuery(getQuestion(state), state.new_query.datasetQuery);
......@@ -59,6 +59,7 @@ import { MetabaseApi } from "metabase/services";
import NativeQuery from "metabase-lib/lib/queries/NativeQuery";
import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery";
import NewQuery from "metabase/new_query/containers/NewQuery";
function autocompleteResults(card, prefix) {
let databaseId = card && card.dataset_query && card.dataset_query.database;
......@@ -201,8 +202,16 @@ export default class QueryBuilder extends Component {
}
class LegacyQueryBuilder extends Component {
onNewQueryFlowCompleted = (newQuery: StructuredQuery) => {
const {question, updateQuestion, runQuestionQuery} = this.props;
const updatedQuestion = question.setQuery(newQuery);
updateQuestion(updatedQuestion);
runQuestionQuery();
}
render() {
const { query, card, isDirty, databases, uiControls, mode } = this.props;
const { question, query, card, isDirty, databases, uiControls, mode } = this.props;
// if we don't have a card at all or no databases then we are initializing, so keep it simple
if (!card || !databases) {
......@@ -214,6 +223,12 @@ class LegacyQueryBuilder extends Component {
const showDrawer = uiControls.isShowingDataReference || uiControls.isShowingTemplateTagsEditor;
const ModeFooter = mode && mode.ModeFooter;
const showNewQueryFlow = question && question.isEmpty();
if (showNewQueryFlow) {
return <NewQuery question={question} onComplete={this.onNewQueryFlowCompleted}/>
}
return (
<div className="flex-full relative">
<div className={cx("QueryBuilder flex flex-column bg-white spread", {"QueryBuilder--showSideDrawer": showDrawer})}>
......@@ -230,10 +245,10 @@ class LegacyQueryBuilder extends Component {
/>
: (query instanceof StructuredQuery) ?
<div className="wrapper">
<GuiQueryEditor
{...this.props}
datasetQuery={card && card.dataset_query}
/>
<GuiQueryEditor
{...this.props}
datasetQuery={card && card.dataset_query}
/>
</div>
: null }
</div>
......@@ -243,9 +258,7 @@ class LegacyQueryBuilder extends Component {
<QueryVisualization {...this.props} className="full wrapper mb2 z1"/>
</div>
{ ModeFooter &&
<ModeFooter {...this.props} className="flex-no-shrink" />
}
<ModeFooter {...this.props} className="flex-no-shrink"/>
</div>
<div className={cx("SideDrawer hide sm-show", { "SideDrawer--show": showDrawer })}>
......
......@@ -42,9 +42,9 @@ import SetupApp from "metabase/setup/containers/SetupApp.jsx";
import UserSettingsApp from "metabase/user/containers/UserSettingsApp.jsx";
// new question
import NewQuestionStart from "metabase/new_question/containers/Start";
import NewQuestionMetrics from "metabase/new_question/containers/Metrics";
import NewQuestionSegments from "metabase/new_question/containers/Segments";
import NewQuestionStart from "metabase/new_query/containers/Start";
import NewQuestionMetrics from "metabase/new_query/containers/Metrics";
import NewQuestionSegments from "metabase/new_query/containers/Segments";
// admin containers
import DatabaseListApp from "metabase/admin/databases/containers/DatabaseListApp.jsx";
......
resources/frontend_client/app/img/custom_question.png

39.7 KiB

resources/frontend_client/app/img/custom_question@2x.png

39.7 KiB

resources/frontend_client/app/img/list_illustration.png

8.17 KiB

resources/frontend_client/app/img/list_illustration@2x.png

8.17 KiB

resources/frontend_client/app/img/sql_illustration@2x.png

20.7 KiB

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