diff --git a/frontend/src/metabase/query_builder/components/view/View.jsx b/frontend/src/metabase/query_builder/components/view/View.jsx index 8ba1f2eb8a98d0ef640ec4ecc8af6762410279e5..16fa5b0d413ea93c2e76d470341557784471ab9d 100644 --- a/frontend/src/metabase/query_builder/components/view/View.jsx +++ b/frontend/src/metabase/query_builder/components/view/View.jsx @@ -1,31 +1,24 @@ /* eslint-disable react/prop-types */ import React from "react"; -import { t } from "ttag"; - -import cx from "classnames"; +import { Motion, spring } from "react-motion"; import ExplicitSize from "metabase/components/ExplicitSize"; import Popover from "metabase/components/Popover"; -import DebouncedFrame from "metabase/components/DebouncedFrame"; -import Subhead from "metabase/components/type/Subhead"; import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper"; +import NativeQuery from "metabase-lib/lib/queries/NativeQuery"; +import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery"; + +import AggregationPopover from "../AggregationPopover"; +import BreakoutPopover from "../BreakoutPopover"; +import DatasetEditor from "../DatasetEditor"; import NativeQueryEditor from "../NativeQueryEditor"; import QueryVisualization from "../QueryVisualization"; import DataReference from "../dataref/DataReference"; import TagEditorSidebar from "../template_tags/TagEditorSidebar"; import SnippetSidebar from "../template_tags/SnippetSidebar"; import SavedQuestionIntroModal from "../SavedQuestionIntroModal"; - -import AggregationPopover from "../AggregationPopover"; -import BreakoutPopover from "../BreakoutPopover"; - import QueryModals from "../QueryModals"; -import { ViewTitleHeader, ViewSubHeader } from "./ViewHeader"; -import NewQuestionHeader from "./NewQuestionHeader"; -import ViewFooter from "./ViewFooter"; -import ViewSidebar from "./ViewSidebar"; -import QuestionDataSelector from "./QuestionDataSelector"; import ChartSettingsSidebar from "./sidebars/ChartSettingsSidebar"; import ChartTypeSidebar from "./sidebars/ChartTypeSidebar"; @@ -33,16 +26,24 @@ import SummarizeSidebar from "./sidebars/SummarizeSidebar/SummarizeSidebar"; import FilterSidebar from "./sidebars/FilterSidebar"; import QuestionDetailsSidebar from "./sidebars/QuestionDetailsSidebar"; -import { Motion, spring } from "react-motion"; - -import NativeQuery from "metabase-lib/lib/queries/NativeQuery"; -import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery"; -import SyncedParametersList from "metabase/parameters/components/SyncedParametersList/SyncedParametersList"; - -import DatasetEditor from "../DatasetEditor"; - +import { ViewSubHeader } from "./ViewHeader"; +import NewQuestionHeader from "./NewQuestionHeader"; +import ViewFooter from "./ViewFooter"; +import ViewSidebar from "./ViewSidebar"; +import NewQuestionView from "./View/NewQuestionView"; import QueryViewNotebook from "./View/QueryViewNotebook"; +import { + QueryBuilderViewRoot, + QueryBuilderContentContainer, + QueryBuilderMain, + QueryBuilderViewHeaderContainer, + BorderedViewTitleHeader, + NativeQueryEditorContainer, + StyledDebouncedFrame, + StyledSyncedParametersList, +} from "./View.styled"; + const DEFAULT_POPOVER_STATE = { aggregationIndex: null, aggregationPopoverTarget: null, @@ -62,6 +63,7 @@ export default class View extends React.Component { aggregationPopoverTarget: e.target, }); }; + handleEditSeries = (e, index) => { this.setState({ ...DEFAULT_POPOVER_STATE, @@ -69,10 +71,12 @@ export default class View extends React.Component { aggregationIndex: index, }); }; + handleRemoveSeries = (e, index) => { const { query } = this.props; query.removeAggregation(index).update(null, { run: true }); }; + handleEditBreakout = (e, index) => { this.setState({ ...DEFAULT_POPOVER_STATE, @@ -80,77 +84,178 @@ export default class View extends React.Component { breakoutIndex: index, }); }; + handleClosePopover = () => { this.setState({ ...DEFAULT_POPOVER_STATE, }); }; - render() { + onChangeAggregation = aggregation => { + const { query } = this.props; + const { aggregationIndex } = this.state; + if (aggregationIndex != null) { + query + .updateAggregation(aggregationIndex, aggregation) + .update(null, { run: true }); + } else { + query.aggregate(aggregation).update(null, { run: true }); + } + this.handleClosePopover(); + }; + + onChangeBreakout = breakout => { + const { query } = this.props; + const { breakoutIndex } = this.state; + if (breakoutIndex != null) { + query.updateBreakout(breakoutIndex, breakout).update(null, { run: true }); + } else { + query.breakout(breakout).update(null, { run: true }); + } + this.handleClosePopover(); + }; + + getLeftSidebar = () => { const { question, - query, - card, - isDirty, - isResultDirty, - isLiveResizable, - runQuestionQuery, - databases, - isShowingTemplateTagsEditor, - isShowingDataReference, - isShowingNewbModal, - isShowingChartTypeSidebar, isShowingChartSettingsSidebar, - isShowingSummarySidebar, - isShowingFilterSidebar, - isShowingSnippetSidebar, + isShowingChartTypeSidebar, isShowingQuestionDetailsSidebar, - queryBuilderMode, - mode, - fitClassNames, - height, onOpenModal, + onCloseChartSettings, + onCloseChartType, } = this.props; + + if (isShowingChartSettingsSidebar) { + return ( + <ChartSettingsSidebar {...this.props} onClose={onCloseChartSettings} /> + ); + } + + if (isShowingChartTypeSidebar) { + return <ChartTypeSidebar {...this.props} onClose={onCloseChartType} />; + } + + if (isShowingQuestionDetailsSidebar) { + return ( + <QuestionDetailsSidebar question={question} onOpenModal={onOpenModal} /> + ); + } + + return null; + }; + + getRightSidebarForStructuredQuery = () => { const { - aggregationIndex, - aggregationPopoverTarget, - breakoutIndex, - breakoutPopoverTarget, - } = this.state; + question, + isResultDirty, + isShowingSummarySidebar, + isShowingFilterSidebar, + runQuestionQuery, + onCloseSummary, + onCloseFilter, + } = 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) { - return <LoadingAndErrorWrapper className={fitClassNames} loading />; + if (isShowingSummarySidebar) { + return ( + <SummarizeSidebar + question={question} + onClose={onCloseSummary} + isResultDirty={isResultDirty} + runQuestionQuery={runQuestionQuery} + /> + ); } - const queryMode = mode && mode.queryMode(); - const ModeFooter = queryMode && queryMode.ModeFooter; - const isStructured = query instanceof StructuredQuery; - const isNative = query instanceof NativeQuery; - const isNewQuestion = - query instanceof StructuredQuery && - !query.sourceTableId() && - !query.sourceQuery(); + if (isShowingFilterSidebar) { + return <FilterSidebar question={question} onClose={onCloseFilter} />; + } - if (isNewQuestion && queryBuilderMode === "view") { + return null; + }; + + getRightSidebarForNativeQuery = () => { + const { + isShowingTemplateTagsEditor, + isShowingDataReference, + isShowingSnippetSidebar, + toggleTemplateTagsEditor, + toggleDataReference, + toggleSnippetSidebar, + } = this.props; + + if (isShowingTemplateTagsEditor) { return ( - <div className={fitClassNames}> - <div className="p4 mx2"> - <QuestionDataSelector - query={query} - triggerElement={ - <Subhead className="mb2">{t`Pick your data`}</Subhead> - } - /> - </div> - </div> + <TagEditorSidebar {...this.props} onClose={toggleTemplateTagsEditor} /> ); } - if (card.dataset && queryBuilderMode === "dataset") { - return <DatasetEditor {...this.props} />; + if (isShowingDataReference) { + return <DataReference {...this.props} onClose={toggleDataReference} />; + } + + if (isShowingSnippetSidebar) { + return <SnippetSidebar {...this.props} onClose={toggleSnippetSidebar} />; } + return null; + }; + + getRightSidebar = () => { + const { question } = this.props; + const isStructured = question.isStructured(); + return isStructured + ? this.getRightSidebarForStructuredQuery() + : this.getRightSidebarForNativeQuery(); + }; + + renderHeader = () => { + const { query } = this.props; + const isStructured = query instanceof StructuredQuery; + + const isNewQuestion = + isStructured && !query.sourceTableId() && !query.sourceQuery(); + + return ( + <Motion + defaultStyle={isNewQuestion ? { opacity: 0 } : { opacity: 1 }} + style={isNewQuestion ? { opacity: spring(0) } : { opacity: spring(1) }} + > + {({ opacity }) => ( + <QueryBuilderViewHeaderContainer> + <BorderedViewTitleHeader {...this.props} style={{ opacity }} /> + {opacity < 1 && ( + <NewQuestionHeader + className="spread" + style={{ opacity: 1 - opacity }} + /> + )} + </QueryBuilderViewHeaderContainer> + )} + </Motion> + ); + }; + + renderMain = ({ leftSidebar, rightSidebar }) => { + const { + query, + card, + mode, + parameters, + isDirty, + isLiveResizable, + isPreviewable, + isPreviewing, + height, + setParameterValue, + setIsPreviewing, + } = this.props; + + const queryMode = mode && mode.queryMode(); + const ModeFooter = queryMode && queryMode.ModeFooter; + const isStructured = query instanceof StructuredQuery; + const isNative = query instanceof NativeQuery; + const topQuery = isStructured && query.topLevelQuery(); // only allow editing of series for structured queries @@ -161,203 +266,162 @@ export default class View extends React.Component { const onEditBreakout = topQuery && topQuery.hasBreakouts() ? this.handleEditBreakout : null; - const leftSideBar = isShowingChartSettingsSidebar ? ( - <ChartSettingsSidebar - {...this.props} - onClose={this.props.onCloseChartSettings} - /> - ) : isShowingChartTypeSidebar ? ( - <ChartTypeSidebar {...this.props} onClose={this.props.onCloseChartType} /> - ) : isShowingQuestionDetailsSidebar ? ( - <QuestionDetailsSidebar question={question} onOpenModal={onOpenModal} /> - ) : null; - - const rightSideBar = - isStructured && isShowingSummarySidebar ? ( - <SummarizeSidebar - question={question} - onClose={this.props.onCloseSummary} - isResultDirty={isResultDirty} - runQuestionQuery={runQuestionQuery} - /> - ) : isStructured && isShowingFilterSidebar ? ( - <FilterSidebar question={question} onClose={this.props.onCloseFilter} /> - ) : isNative && isShowingTemplateTagsEditor ? ( - <TagEditorSidebar - {...this.props} - onClose={this.props.toggleTemplateTagsEditor} + const isSidebarOpen = leftSidebar || rightSidebar; + + return ( + <QueryBuilderMain isSidebarOpen={isSidebarOpen}> + {isNative ? ( + <NativeQueryEditorContainer className="hide sm-show"> + <NativeQueryEditor + {...this.props} + viewHeight={height} + isOpen={!card.dataset_query.native.query || isDirty} + datasetQuery={card && card.dataset_query} + /> + </NativeQueryEditorContainer> + ) : ( + <StyledSyncedParametersList + parameters={parameters} + setParameterValue={setParameterValue} + commitImmediately + /> + )} + + <ViewSubHeader + isPreviewable={isPreviewable} + isPreviewing={isPreviewing} + setIsPreviewing={setIsPreviewing} /> - ) : isNative && isShowingDataReference ? ( - <DataReference - {...this.props} - onClose={this.props.toggleDataReference} + + <StyledDebouncedFrame enabled={!isLiveResizable}> + <QueryVisualization + {...this.props} + noHeader + className="spread" + onAddSeries={onAddSeries} + onEditSeries={onEditSeries} + onRemoveSeries={onRemoveSeries} + onEditBreakout={onEditBreakout} + /> + </StyledDebouncedFrame> + + {ModeFooter && ( + <ModeFooter {...this.props} className="flex-no-shrink" /> + )} + + <ViewFooter {...this.props} className="flex-no-shrink" /> + </QueryBuilderMain> + ); + }; + + renderAggregationPopover = () => { + const { query } = this.props; + const { aggregationPopoverTarget, aggregationIndex } = this.state; + return ( + <Popover + isOpen={!!aggregationPopoverTarget} + target={aggregationPopoverTarget} + onClose={this.handleClosePopover} + > + <AggregationPopover + query={query} + aggregation={ + aggregationIndex >= 0 ? query.aggregations()[aggregationIndex] : 0 + } + onChangeAggregation={this.onChangeAggregation} + onClose={this.handleClosePopover} /> - ) : isNative && isShowingSnippetSidebar ? ( - <SnippetSidebar - {...this.props} - onClose={this.props.toggleSnippetSidebar} + </Popover> + ); + }; + + renderBreakoutPopover = () => { + const { query } = this.props; + const { breakoutPopoverTarget, breakoutIndex } = this.state; + return ( + <Popover + isOpen={!!breakoutPopoverTarget} + onClose={this.handleClosePopover} + target={breakoutPopoverTarget} + > + <BreakoutPopover + query={query} + breakout={breakoutIndex >= 0 ? query.breakouts()[breakoutIndex] : 0} + onChangeBreakout={this.onChangeBreakout} + onClose={this.handleClosePopover} /> - ) : null; + </Popover> + ); + }; + + render() { + const { + question, + query, + card, + databases, + isShowingNewbModal, + queryBuilderMode, + fitClassNames, + closeQbNewbModal, + } = this.props; - const isSidebarOpen = leftSideBar || rightSideBar; + // if we don't have a card at all or no databases then we are initializing, so keep it simple + if (!card || !databases) { + return <LoadingAndErrorWrapper className={fitClassNames} loading />; + } + + const isStructured = query instanceof StructuredQuery; + + const isNewQuestion = + isStructured && !query.sourceTableId() && !query.sourceQuery(); + + if (isNewQuestion && queryBuilderMode === "view") { + return <NewQuestionView query={query} fitClassNames={fitClassNames} />; + } + + if (card.dataset && queryBuilderMode === "dataset") { + return <DatasetEditor {...this.props} />; + } const isNotebookContainerOpen = isNewQuestion || queryBuilderMode === "notebook"; + const leftSidebar = this.getLeftSidebar(); + const rightSidebar = this.getRightSidebar(); + return ( <div className={fitClassNames}> - <div className={cx("QueryBuilder flex flex-column bg-white spread")}> - <Motion - defaultStyle={isNewQuestion ? { opacity: 0 } : { opacity: 1 }} - style={ - isNewQuestion ? { opacity: spring(0) } : { opacity: spring(1) } - } - > - {({ opacity }) => ( - <div className="flex-no-shrink z3 bg-white relative"> - <ViewTitleHeader - {...this.props} - style={{ opacity }} - py={1} - className="border-bottom" - /> - {opacity < 1 && ( - <NewQuestionHeader - className="spread" - style={{ opacity: 1 - opacity }} - /> - )} - </div> - )} - </Motion> - - <div className="flex flex-full relative"> - {query instanceof StructuredQuery && ( + <QueryBuilderViewRoot className="QueryBuilder"> + {this.renderHeader()} + <QueryBuilderContentContainer> + {isStructured && ( <QueryViewNotebook isNotebookContainerOpen={isNotebookContainerOpen} {...this.props} /> )} - - <ViewSidebar side="left" isOpen={!!leftSideBar}> - {leftSideBar} + <ViewSidebar side="left" isOpen={!!leftSidebar}> + {leftSidebar} </ViewSidebar> - - <div - className={cx("flex-full flex flex-column flex-basis-none", { - "hide sm-show": isSidebarOpen, - })} - > - {isNative ? ( - <div className="z2 hide sm-show border-bottom mb2"> - <NativeQueryEditor - {...this.props} - viewHeight={height} - isOpen={!card.dataset_query.native.query || isDirty} - datasetQuery={card && card.dataset_query} - /> - </div> - ) : ( - <SyncedParametersList - className="mt2 ml3" - parameters={this.props.parameters} - setParameterValue={this.props.setParameterValue} - commitImmediately - /> - )} - - <ViewSubHeader {...this.props} /> - - <DebouncedFrame - className="flex-full" - style={{ flexGrow: 1 }} - enabled={!isLiveResizable} - > - <QueryVisualization - {...this.props} - onAddSeries={onAddSeries} - onEditSeries={onEditSeries} - onRemoveSeries={onRemoveSeries} - onEditBreakout={onEditBreakout} - noHeader - className="spread" - /> - </DebouncedFrame> - - {ModeFooter && ( - <ModeFooter {...this.props} className="flex-no-shrink" /> - )} - - <ViewFooter {...this.props} className="flex-no-shrink" /> - </div> - - <ViewSidebar side="right" isOpen={!!rightSideBar}> - {rightSideBar} + {this.renderMain({ leftSidebar, rightSidebar })} + <ViewSidebar side="right" isOpen={!!rightSidebar}> + {rightSidebar} </ViewSidebar> - </div> - </div> + </QueryBuilderContentContainer> + </QueryBuilderViewRoot> {isShowingNewbModal && ( <SavedQuestionIntroModal - question={this.props.question} - onClose={() => this.props.closeQbNewbModal()} + question={question} + onClose={() => closeQbNewbModal()} /> )} <QueryModals {...this.props} /> - {isStructured && ( - <Popover - isOpen={!!aggregationPopoverTarget} - target={aggregationPopoverTarget} - onClose={this.handleClosePopover} - > - <AggregationPopover - query={query} - aggregation={ - aggregationIndex >= 0 - ? query.aggregations()[aggregationIndex] - : 0 - } - onChangeAggregation={aggregation => { - if (aggregationIndex != null) { - query - .updateAggregation(aggregationIndex, aggregation) - .update(null, { run: true }); - } else { - query.aggregate(aggregation).update(null, { run: true }); - } - this.handleClosePopover(); - }} - onClose={this.handleClosePopover} - /> - </Popover> - )} - {isStructured && ( - <Popover - isOpen={!!breakoutPopoverTarget} - onClose={this.handleClosePopover} - target={breakoutPopoverTarget} - > - <BreakoutPopover - query={query} - breakout={ - breakoutIndex >= 0 ? query.breakouts()[breakoutIndex] : 0 - } - onChangeBreakout={breakout => { - if (breakoutIndex != null) { - query - .updateBreakout(breakoutIndex, breakout) - .update(null, { run: true }); - } else { - query.breakout(breakout).update(null, { run: true }); - } - this.handleClosePopover(); - }} - onClose={this.handleClosePopover} - /> - </Popover> - )} + {isStructured && this.renderAggregationPopover()} + {isStructured && this.renderBreakoutPopover()} </div> ); } diff --git a/frontend/src/metabase/query_builder/components/view/View.styled.tsx b/frontend/src/metabase/query_builder/components/view/View.styled.tsx new file mode 100644 index 0000000000000000000000000000000000000000..179b7bc39ff48926b0523a506db76934ef5e363f --- /dev/null +++ b/frontend/src/metabase/query_builder/components/view/View.styled.tsx @@ -0,0 +1,71 @@ +import styled from "@emotion/styled"; +import { css } from "@emotion/react"; + +import DebouncedFrame from "metabase/components/DebouncedFrame"; +import SyncedParametersList from "metabase/parameters/components/SyncedParametersList/SyncedParametersList"; + +import { color } from "metabase/lib/colors"; +import { breakpointMaxSmall } from "metabase/styled-components/theme/media-queries"; + +import { ViewTitleHeader } from "./ViewHeader"; + +export const QueryBuilderViewRoot = styled.div` + display: flex; + flex-direction: column; + background-color: ${color("bg-white")}; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +`; + +export const QueryBuilderContentContainer = styled.div` + display: flex; + flex: 1 0 auto; + position: relative; +`; + +export const QueryBuilderMain = styled.main<{ isSidebarOpen: boolean }>` + display: flex; + flex-direction: column; + flex: 1 0 auto; + flex-basis: 0; + + ${breakpointMaxSmall} { + ${props => + props.isSidebarOpen && + css` + display: none !important; + `}; + } +`; + +export const BorderedViewTitleHeader = styled(ViewTitleHeader)` + border-bottom: 1px solid ${color("border")}; + padding-top: 8px; + padding-bottom: 8px; +`; + +export const QueryBuilderViewHeaderContainer = styled.div` + flex-shrink: 0; + background-color: ${color("bg-white")}; + position: relative; + z-index: 3; +`; + +export const NativeQueryEditorContainer = styled.div` + margin-bottom: 1rem; + border-bottom: 1px solid ${color("border")}; + z-index: 2; +`; + +export const StyledDebouncedFrame = styled(DebouncedFrame)` + flex: 1 0 auto; + flex-grow: 1; +`; + +export const StyledSyncedParametersList = styled(SyncedParametersList)` + margin-top: 1rem; + margin-left: 1.5rem; +`; diff --git a/frontend/src/metabase/query_builder/components/view/View/NewQuestionView/NewQuestionView.tsx b/frontend/src/metabase/query_builder/components/view/View/NewQuestionView/NewQuestionView.tsx new file mode 100644 index 0000000000000000000000000000000000000000..62bb064504ac89a8bb38c9dc77fded1da4aaf90c --- /dev/null +++ b/frontend/src/metabase/query_builder/components/view/View/NewQuestionView/NewQuestionView.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import { t } from "ttag"; + +import Subhead from "metabase/components/type/Subhead"; +import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery"; + +import QuestionDataSelector from "../../QuestionDataSelector"; + +type Props = { + query: StructuredQuery; + fitClassNames: string; +}; + +function NewQuestionView({ query, fitClassNames }: Props) { + return ( + <div className={fitClassNames}> + <div className="p4 mx2"> + <QuestionDataSelector + query={query} + triggerElement={ + <Subhead className="mb2">{t`Pick your data`}</Subhead> + } + /> + </div> + </div> + ); +} + +export default NewQuestionView; diff --git a/frontend/src/metabase/query_builder/components/view/View/NewQuestionView/index.ts b/frontend/src/metabase/query_builder/components/view/View/NewQuestionView/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..c8acd815501ee9948534c20001239ea9dded92d8 --- /dev/null +++ b/frontend/src/metabase/query_builder/components/view/View/NewQuestionView/index.ts @@ -0,0 +1 @@ +export { default } from "./NewQuestionView"; diff --git a/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx b/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx index a2024aeb865eab9200fa22ca8b472f989958922d..837680b754e25e692b50248677d5d0f563086d37 100644 --- a/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx +++ b/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx @@ -1,17 +1,21 @@ /* eslint-disable react/prop-types */ import React, { Component } from "react"; import { connect } from "react-redux"; +import { push } from "react-router-redux"; import { t } from "ttag"; import _ from "underscore"; -import fitViewport from "metabase/hoc/FitViewPort"; - -import View from "../components/view/View"; -// import Notebook from "../components/notebook/Notebook"; +import Collections from "metabase/entities/collections"; +import { MetabaseApi } from "metabase/services"; +import { getMetadata } from "metabase/selectors/metadata"; +import { getUser, getUserIsAdmin } from "metabase/selectors/user"; +import fitViewport from "metabase/hoc/FitViewPort"; import title from "metabase/hoc/Title"; import titleWithLoadingTime from "metabase/hoc/TitleWithLoadingTime"; +import View from "../components/view/View"; + import { getCard, getDatabasesList, @@ -53,15 +57,7 @@ import { getNativeEditorCursorOffset, getNativeEditorSelectedText, } from "../selectors"; - -import { getMetadata } from "metabase/selectors/metadata"; -import { getUser, getUserIsAdmin } from "metabase/selectors/user"; - import * as actions from "../actions"; -import { push } from "react-router-redux"; - -import Collections from "metabase/entities/collections"; -import { MetabaseApi } from "metabase/services"; function autocompleteResults(card, prefix) { const databaseId = card && card.dataset_query && card.dataset_query.database; @@ -90,11 +86,9 @@ const mapStateToProps = (state, props) => { parameterValues: getParameterValues(state), - // TODO: data ref tableForeignKeys: getTableForeignKeys(state), tableForeignKeyReferences: getTableForeignKeyReferences(state), - // TODO: legacy card: getCard(state), originalCard: getOriginalCard(state), databases: getDatabasesList(state), @@ -102,7 +96,6 @@ const mapStateToProps = (state, props) => { tables: getTables(state), tableMetadata: getTableMetadata(state), - // TODO: redundant, accessible through question query: getQuery(state), metadata: getMetadata(state), @@ -161,8 +154,6 @@ const mapDispatchToProps = { export default class QueryBuilder extends Component { constructor(props, context) { super(props, context); - - // TODO: React tells us that forceUpdate() is not the best thing to use, so ideally we can find a different way to trigger this this.forceUpdateDebounced = _.debounce(this.forceUpdate.bind(this), 400); } @@ -204,14 +195,10 @@ export default class QueryBuilder extends Component { } componentWillUnmount() { - // cancel the query if one is running this.props.cancelQuery(); - window.removeEventListener("resize", this.handleResize); - clearTimeout(this.timeout); - - this.closeModal(); // close any modal that might be open + this.closeModal(); } // When the window is resized we need to re-render, mainly so that our visualization pane updates @@ -220,10 +207,10 @@ export default class QueryBuilder extends Component { this.forceUpdateDebounced(); }; - // NOTE: these were lifted from QueryHeader. Move to Redux? openModal = modal => { this.props.setUIControls({ modal }); }; + closeModal = () => { this.props.setUIControls({ modal: null }); }; @@ -259,15 +246,6 @@ export default class QueryBuilder extends Component { } }; - resetStateOnTimeout = () => { - // clear any previously set timeouts then start a new one - clearTimeout(this.timeout); - this.timeout = setTimeout(() => { - this.props.onSetRecentlySaved(null); - this.timeout = null; - }, 5000); - }; - render() { const { uiControls: { modal, recentlySaved }, @@ -276,14 +254,11 @@ export default class QueryBuilder extends Component { return ( <View {...this.props} - // NOTE: these were lifted from QueryHeader. Move to Redux? modal={modal} onOpenModal={this.openModal} onCloseModal={this.closeModal} - // recently saved indication recentlySaved={recentlySaved} onSetRecentlySaved={this.setRecentlySaved} - // save/create actions onSave={this.handleSave} onCreate={this.handleCreate} handleResize={this.handleResize}