diff --git a/src/client/components/pinboard-tile/pinboard-tile.tsx b/src/client/components/pinboard-tile/pinboard-tile.tsx index 898d218e6..bb564c1a8 100644 --- a/src/client/components/pinboard-tile/pinboard-tile.tsx +++ b/src/client/components/pinboard-tile/pinboard-tile.tsx @@ -18,14 +18,13 @@ import { Set } from "immutable"; import { Dataset, Datum } from "plywood"; import * as React from "react"; import { Clicker } from "../../../common/models/clicker/clicker"; +import { DatasetRequest, error, isError, isLoaded, isLoading, loaded, loading } from "../../../common/models/dataset-request/dataset-request"; import { Dimension } from "../../../common/models/dimension/dimension"; import { Essence } from "../../../common/models/essence/essence"; import { BooleanFilterClause, StringFilterAction, StringFilterClause } from "../../../common/models/filter-clause/filter-clause"; import { SortOn } from "../../../common/models/sort-on/sort-on"; import { Timekeeper } from "../../../common/models/timekeeper/timekeeper"; -import { DatasetLoad, error, isError, isLoaded, isLoading, loaded, loading } from "../../../common/models/visualization-props/visualization-props"; import { debounceWithPromise, Unary } from "../../../common/utils/functional/functional"; -import { Fn } from "../../../common/utils/general/general"; import { MAX_SEARCH_LENGTH } from "../../config/constants"; import { setDragData, setDragGhost } from "../../utils/dom/dom"; import { DragManager } from "../../utils/drag-manager/drag-manager"; @@ -57,11 +56,9 @@ export class PinboardTileProps { export interface PinboardTileState { searchText: string; showSearch: boolean; - datasetLoad: DatasetLoad; + datasetLoad: DatasetRequest; } -const noMeasureError = new Error("No measure selected"); - export class PinboardTile extends React.Component { state: PinboardTileState = { @@ -81,14 +78,14 @@ export class PinboardTile extends React.Component { + private fetchData(params: QueryParams): Promise { this.lastQueryParams = params; return this.debouncedCallExecutor(params); } private lastQueryParams: Partial = {}; - private callExecutor = (params: QueryParams): Promise => { + private callExecutor = (params: QueryParams): Promise => { const { essence: { timezone, dataCube } } = params; return dataCube.executor(makeQuery(params), { timezone }) .then((dataset: Dataset) => { diff --git a/src/client/components/pinboard-tile/utils/tile-styles.mocha.ts b/src/client/components/pinboard-tile/utils/tile-styles.mocha.ts index 4281f0ee9..c3a92cd8d 100644 --- a/src/client/components/pinboard-tile/utils/tile-styles.mocha.ts +++ b/src/client/components/pinboard-tile/utils/tile-styles.mocha.ts @@ -15,7 +15,7 @@ */ import { expect } from "chai"; import { Dataset } from "plywood"; -import { error, loaded, loading } from "../../../../common/models/visualization-props/visualization-props"; +import { error, loaded, loading } from "../../../../common/models/dataset-request/dataset-request"; import { range } from "../../../../common/utils/functional/functional"; import { tileStyles } from "./tile-styles"; diff --git a/src/client/components/pinboard-tile/utils/tile-styles.ts b/src/client/components/pinboard-tile/utils/tile-styles.ts index 95b06aee2..dcb2fc3f9 100644 --- a/src/client/components/pinboard-tile/utils/tile-styles.ts +++ b/src/client/components/pinboard-tile/utils/tile-styles.ts @@ -15,10 +15,10 @@ */ import * as React from "react"; -import { DatasetLoad, isLoaded } from "../../../../common/models/visualization-props/visualization-props"; +import { DatasetRequest, isLoaded } from "../../../../common/models/dataset-request/dataset-request"; import { PIN_ITEM_HEIGHT, PIN_PADDING_BOTTOM, PIN_TITLE_HEIGHT } from "../../../config/constants"; -export function tileStyles(datasetLoad: DatasetLoad): React.CSSProperties { +export function tileStyles(datasetLoad: DatasetRequest): React.CSSProperties { const topOffset = PIN_TITLE_HEIGHT + PIN_PADDING_BOTTOM; const rowsCount = isLoaded(datasetLoad) ? datasetLoad.dataset.count() : 0; const rowsHeight = Math.max(4, rowsCount) * PIN_ITEM_HEIGHT; diff --git a/src/client/components/preview-string-filter-menu/preview-string-filter-menu.tsx b/src/client/components/preview-string-filter-menu/preview-string-filter-menu.tsx index 7cd5c88f4..aba2bd16c 100644 --- a/src/client/components/preview-string-filter-menu/preview-string-filter-menu.tsx +++ b/src/client/components/preview-string-filter-menu/preview-string-filter-menu.tsx @@ -18,6 +18,15 @@ import { Set } from "immutable"; import { Dataset } from "plywood"; import * as React from "react"; +import { + DatasetRequest, + error, + isError, + isLoaded, + isLoading, + loaded, + loading +} from "../../../common/models/dataset-request/dataset-request"; import { Dimension } from "../../../common/models/dimension/dimension"; import { Essence } from "../../../common/models/essence/essence"; import { @@ -27,15 +36,6 @@ import { } from "../../../common/models/filter-clause/filter-clause"; import { FilterMode } from "../../../common/models/filter/filter"; import { Timekeeper } from "../../../common/models/timekeeper/timekeeper"; -import { - DatasetLoad, - error, - isError, - isLoaded, - isLoading, - loaded, - loading -} from "../../../common/models/visualization-props/visualization-props"; import { debounceWithPromise, Unary } from "../../../common/utils/functional/functional"; import { Fn } from "../../../common/utils/general/general"; import { previewStringFilterQuery } from "../../../common/utils/query/preview-string-filter-query"; @@ -74,7 +74,7 @@ export interface PreviewStringFilterMenuProps { export interface PreviewStringFilterMenuState { searchText: string; - dataset: DatasetLoad; + dataset: DatasetRequest; } interface QueryProps { @@ -112,7 +112,7 @@ export class PreviewStringFilterMenu extends React.Component { + private sendQueryFilter(): Promise { const { searchText } = this.state; this.lastSearchText = searchText; return this.debouncedQueryFilter({ ...this.props, searchText }); @@ -124,7 +124,7 @@ export class PreviewStringFilterMenu extends React.Component => { + private queryFilter = (props: QueryProps): Promise => { const { essence } = props; const { searchText } = this.state; const query = previewStringFilterQuery({ ...props, searchText, limit: TOP_N + 1 }); diff --git a/src/client/components/selectable-string-filter-menu/selectable-string-filter-menu.tsx b/src/client/components/selectable-string-filter-menu/selectable-string-filter-menu.tsx index 837260adc..bd382dc15 100644 --- a/src/client/components/selectable-string-filter-menu/selectable-string-filter-menu.tsx +++ b/src/client/components/selectable-string-filter-menu/selectable-string-filter-menu.tsx @@ -18,6 +18,15 @@ import { Set } from "immutable"; import { Dataset } from "plywood"; import * as React from "react"; +import { + DatasetRequest, + error, + isError, + isLoaded, + isLoading, + loaded, + loading +} from "../../../common/models/dataset-request/dataset-request"; import { Dimension } from "../../../common/models/dimension/dimension"; import { Essence } from "../../../common/models/essence/essence"; import { @@ -27,15 +36,6 @@ import { } from "../../../common/models/filter-clause/filter-clause"; import { FilterMode } from "../../../common/models/filter/filter"; import { Timekeeper } from "../../../common/models/timekeeper/timekeeper"; -import { - DatasetLoad, - error, - isError, - isLoaded, - isLoading, - loaded, - loading -} from "../../../common/models/visualization-props/visualization-props"; import { debounceWithPromise, Unary } from "../../../common/utils/functional/functional"; import { Fn } from "../../../common/utils/general/general"; import { stringFilterOptionsQuery } from "../../../common/utils/query/selectable-string-filter-query"; @@ -65,7 +65,7 @@ export interface SelectableStringFilterMenuProps { export interface SelectableStringFilterMenuState { searchText: string; - dataset: DatasetLoad; + dataset: DatasetRequest; selectedValues: Set; pasteModeEnabled: boolean; } @@ -106,13 +106,13 @@ export class SelectableStringFilterMenu extends React.Component { + private sendQueryFilter(): Promise { const { searchText } = this.state; this.lastSearchText = searchText; return this.debouncedQueryFilter({ ...this.props, searchText }); } - private queryFilter = (props: QueryProps): Promise => { + private queryFilter = (props: QueryProps): Promise => { const { essence, searchText } = props; const query = stringFilterOptionsQuery({ ...props, limit: TOP_N + 1 }); diff --git a/src/client/views/cube-view/center-panel/center-panel.tsx b/src/client/views/cube-view/center-panel/center-panel.tsx new file mode 100644 index 000000000..69627a51e --- /dev/null +++ b/src/client/views/cube-view/center-panel/center-panel.tsx @@ -0,0 +1,181 @@ +/* + * Copyright 2017-2021 Allegro.pl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from "react"; +import { ChartProps } from "../../../../common/models/chart-props/chart-props"; +import { Clicker } from "../../../../common/models/clicker/clicker"; +import { ClientCustomization } from "../../../../common/models/customization/customization"; +import { Dimension } from "../../../../common/models/dimension/dimension"; +import { DragPosition } from "../../../../common/models/drag-position/drag-position"; +import { Essence } from "../../../../common/models/essence/essence"; +import { Series } from "../../../../common/models/series/series"; +import { Stage } from "../../../../common/models/stage/stage"; +import { Timekeeper } from "../../../../common/models/timekeeper/timekeeper"; +import { Binary, Omit, Unary } from "../../../../common/utils/functional/functional"; +import { Fn } from "../../../../common/utils/general/general"; +import { DropIndicator } from "../../../components/drop-indicator/drop-indicator"; +import { FilterTilesRow } from "../../../components/filter-tile/filter-tiles-row"; +import { ManualFallback } from "../../../components/manual-fallback/manual-fallback"; +import { SeriesTilesRow } from "../../../components/series-tile/series-tiles-row"; +import { SplitTilesRow } from "../../../components/split-tile/split-tiles-row"; +import { VisSelector } from "../../../components/vis-selector/vis-selector"; +import { classNames } from "../../../utils/dom/dom"; +import { DataProvider } from "../../../visualizations/data-provider/data-provider"; +import { HighlightController } from "../../../visualizations/highlight-controller/highlight-controller"; +import { PartialFilter, PartialSeries } from "../partial-tiles-provider"; + +interface VisualizationControlsProps { + essence: Essence; + clicker: Clicker; + stage: Stage; + timekeeper: Timekeeper; + customization: ClientCustomization; + addSeries: Binary; + addFilter: Binary; + partialSeries: PartialSeries | null; + partialFilter: PartialFilter | null; + removeTile: Fn; +} + +export const VisualizationControls: React.SFC = props => { + const { + addSeries, + addFilter, + clicker, + essence, + customization, + timekeeper, + stage, + partialSeries, + partialFilter, + removeTile + } = props; + return
+
+ + + +
+ +
; +}; + +interface ChartPanelProps { + essence: Essence; + clicker: Clicker; + stage: Stage; + chartComponent: React.ComponentType; + timekeeper: Timekeeper; + lastRefreshRequestTimestamp: number; + dragEnter: Unary, void>; + dragOver: Unary, void>; + isDraggedOver: boolean; + dragLeave: Fn; + drop: Unary, void>; +} + +export const ChartPanel: React.SFC = props => { + const { + chartComponent, + essence, + clicker, + timekeeper, + lastRefreshRequestTimestamp, + stage, + isDraggedOver, + dragOver, + dragEnter, + dragLeave, + drop + } = props; + return
+
+ +
+ {isDraggedOver && + +
+ + } +
; +}; + +type ChartWrapperProps = Pick; + +function ChartWrapper(props: ChartWrapperProps) { + const { chartComponent: ChartComponent, essence, clicker, timekeeper, stage, lastRefreshRequestTimestamp } = props; + if (essence.visResolve.isManual()) { + return ; + } + + return + {highlightProps => + + {data =>
+ +
} +
} +
; +} + +type VisualizationPanelProps = ChartPanelProps & VisualizationControlsProps; +export type VisualizationProps = Omit; diff --git a/src/client/views/cube-view/cube-view.mocha.tsx b/src/client/views/cube-view/cube-view.mocha.tsx index ab16c4cac..7f839819b 100644 --- a/src/client/views/cube-view/cube-view.mocha.tsx +++ b/src/client/views/cube-view/cube-view.mocha.tsx @@ -22,7 +22,7 @@ import { clientAppSettings } from "../../../common/models/app-settings/app-setti import { wikiClientDataCube } from "../../../common/models/data-cube/data-cube.fixtures"; import { TimekeeperFixtures } from "../../../common/models/timekeeper/timekeeper.fixtures"; import { noop } from "../../../common/utils/functional/functional"; -import { Totals } from "../../visualizations/totals/totals"; +import { TotalsVisualization } from "../../visualizations/totals/totals"; import { CubeView } from "./cube-view"; // TODO: skip this test till we resolve issue with esModuleInterop in ts-register in mocha. We should consider migrating to mochapack and test code processed by webpack @@ -42,7 +42,7 @@ describe.skip("CubeView", () => { /> ); - expect(cubeView.find(".visualization").find(Totals)).to.have.lengthOf(1); + expect(cubeView.find(".visualization").find(TotalsVisualization)).to.have.lengthOf(1); cubeView.unmount(); }); diff --git a/src/client/views/cube-view/cube-view.scss b/src/client/views/cube-view/cube-view.scss index 40e53db19..d4749b290 100644 --- a/src/client/views/cube-view/cube-view.scss +++ b/src/client/views/cube-view/cube-view.scss @@ -47,11 +47,43 @@ $control-panel-height: 3 * $control-tile-height + 2px; left: $left-panel-width; right: $pinboard-width; + .dimension-panel-toggle, .pinboard-toggle { + @include css-variable(background-color, background-lightest); + position: absolute; + top: 0; + height: $control-panel-height; + width: $toggle-width; + display: flex; + align-items: center; + cursor: pointer; + + svg { + width: $toggle-icon-width; + + path { + @include css-variable(fill, background-dark); + } + } + } + + .dimension-panel-toggle { + left: 0; + } + + .pinboard-toggle { + right: 0; + } + .center-top-bar { - @include pin-top($control-panel-height); + position: absolute; + top: 0; + //redefined in CENTER_PANEL_HEIGHT in cube-view.tsx + height: $control-panel-height; + left: $toggle-width; + right: $toggle-width; .filter-split-section { - @include unpin-full(0, $vis-selector-width + 1px + $toggle-width, 0, $toggle-width); + @include unpin-full(0, $vis-selector-width + 1px, 0, 0); .filter-tile, .series-tile, @@ -77,35 +109,11 @@ $control-panel-height: 3 * $control-tile-height + 2px; position: absolute; top: 0; bottom: 0; - right: $toggle-width; + right: 0; width: $vis-selector-width; z-index: 8; border-radius: 0 $corner $corner 0; } - - .dimension-panel-toggle, .pinboard-toggle { - @include css-variable(background-color, background-lightest); - display: flex; - align-items: center; - height: 100%; - cursor: pointer; - - svg { - width: $toggle-icon-width; - - path { - @include css-variable(fill, background-dark); - } - } - } - - .dimension-panel-toggle { - @include pin-left($toggle-width); - } - - .pinboard-toggle { - @include pin-right($toggle-width); - } } .center-main { diff --git a/src/client/views/cube-view/cube-view.tsx b/src/client/views/cube-view/cube-view.tsx index 51d930d52..a38280467 100644 --- a/src/client/views/cube-view/cube-view.tsx +++ b/src/client/views/cube-view/cube-view.tsx @@ -43,27 +43,18 @@ import { Fn } from "../../../common/utils/general/general"; import { maxTimeQuery } from "../../../common/utils/query/max-time-query"; import { datesEqual } from "../../../common/utils/time/time"; import { DimensionMeasurePanel } from "../../components/dimension-measure-panel/dimension-measure-panel"; -import { DropIndicator } from "../../components/drop-indicator/drop-indicator"; -import { FilterTilesRow } from "../../components/filter-tile/filter-tiles-row"; import { GlobalEventListener } from "../../components/global-event-listener/global-event-listener"; -import { ManualFallback } from "../../components/manual-fallback/manual-fallback"; import { PinboardPanel } from "../../components/pinboard-panel/pinboard-panel"; import { Direction, DragHandle, ResizeHandle } from "../../components/resize-handle/resize-handle"; -import { SeriesTilesRow } from "../../components/series-tile/series-tiles-row"; import { SideDrawer } from "../../components/side-drawer/side-drawer"; -import { SplitTilesRow } from "../../components/split-tile/split-tiles-row"; import { SvgIcon } from "../../components/svg-icon/svg-icon"; -import { VisSelector } from "../../components/vis-selector/vis-selector"; import { DruidQueryModal } from "../../modals/druid-query-modal/druid-query-modal"; import { RawDataModal } from "../../modals/raw-data-modal/raw-data-modal"; import { UrlShortenerModal } from "../../modals/url-shortener-modal/url-shortener-modal"; import { ViewDefinitionModal } from "../../modals/view-definition-modal/view-definition-modal"; -import { classNames } from "../../utils/dom/dom"; import { DragManager } from "../../utils/drag-manager/drag-manager"; import * as localStorage from "../../utils/local-storage/local-storage"; import { getVisualizationComponent } from "../../visualizations"; -import { DataProvider } from "../../visualizations/data-provider/data-provider"; -import { HighlightController } from "../../visualizations/highlight-controller/highlight-controller"; import { CubeContext, CubeContextValue } from "./cube-context"; import { CubeHeaderBar } from "./cube-header-bar/cube-header-bar"; import "./cube-view.scss"; @@ -108,7 +99,6 @@ export interface CubeViewProps { export interface CubeViewState { essence?: Essence; timekeeper?: Timekeeper; - visualizationStage?: Stage; menuStage?: Stage; dragOver?: boolean; showSideBar?: boolean; @@ -125,6 +115,8 @@ export interface CubeViewState { const MIN_PANEL_WIDTH = 240; const MAX_PANEL_WIDTH = 400; +const CONTROL_PANEL_HEIGHT = 115; // redefined in .center-top-bar in cube-view.scss + export class CubeView extends React.Component { static defaultProps: Partial = { maxFilters: 20 }; @@ -134,8 +126,8 @@ export class CubeView extends React.Component { public mounted: boolean; private readonly clicker: Clicker; - private visualization = React.createRef(); private container = React.createRef(); + private centerPanel = React.createRef(); constructor(props: CubeViewProps) { super(props); @@ -313,14 +305,9 @@ export class CubeView extends React.Component { } globalResizeListener = () => { - const containerDOM = this.container.current; - const visualizationDOM = this.visualization.current; - if (!containerDOM || !visualizationDOM) return; - this.setState({ deviceSize: Device.getSize(), - menuStage: Stage.fromClientRect(containerDOM.getBoundingClientRect()), - visualizationStage: Stage.fromClientRect(visualizationDOM.getBoundingClientRect()) + menuStage: this.container.current && Stage.fromClientRect(this.container.current.getBoundingClientRect()) }); }; @@ -509,6 +496,17 @@ export class CubeView extends React.Component { this.globalResizeListener(); }; + // TODO: Refactor via https://github.com/allegro/turnilo/issues/799 + private chartStage(): Stage | null { + const { menuStage, layout: { factPanel, pinboard } } = this.state; + if (!menuStage) return null; + return menuStage.within({ + left: factPanel.hidden ? 0 : factPanel.width, + right: pinboard.hidden ? 0 : pinboard.width, + top: CONTROL_PANEL_HEIGHT + }); + } + private getCubeContext(): CubeContextValue { const { essence } = this.state; /* @@ -539,7 +537,6 @@ export class CubeView extends React.Component { essence, timekeeper, menuStage, - visualizationStage, dragOver, updatingMaxTime, lastRefreshRequestTimestamp @@ -565,6 +562,8 @@ export class CubeView extends React.Component { updatingMaxTime={updatingMaxTime} />; + const Visualization = getVisualizationComponent(essence.visualization); + return
@@ -593,54 +592,32 @@ export class CubeView extends React.Component { } -
-
-
- -
-
- - - -
- -
- -
+
+
+
-
-
- {this.visElement()} -
- {this.manualFallback()} - {dragOver ? : null} - {dragOver ?
: null} + +
+
@@ -731,38 +708,4 @@ export class CubeView extends React.Component { } }; } - - private manualFallback() { - const { essence } = this.state; - if (!essence.visResolve.isManual()) return null; - return ; - } - - private visElement() { - const { essence, timekeeper, visualizationStage: stage, lastRefreshRequestTimestamp } = this.state; - if (!(essence.visResolve.isReady() && stage)) return null; - const clicker = this.clicker; - const { visualization } = essence; - const Visualization = getVisualizationComponent(visualization); - - return - {highlightProps => - - {data =>
- -
} -
} -
; - } } diff --git a/src/client/visualizations/bar-chart/bar-chart.tsx b/src/client/visualizations/bar-chart/bar-chart.tsx index 3e42b2896..4c98a9031 100644 --- a/src/client/visualizations/bar-chart/bar-chart.tsx +++ b/src/client/visualizations/bar-chart/bar-chart.tsx @@ -19,6 +19,7 @@ import * as d3 from "d3"; import { List, Set } from "immutable"; import { Dataset, Datum, NumberRange, PlywoodRange, PseudoDatum, Range } from "plywood"; import * as React from "react"; +import { ChartProps } from "../../../common/models/chart-props/chart-props"; import { DateRange } from "../../../common/models/date-range/date-range"; import { canBucketByDefault, Dimension } from "../../../common/models/dimension/dimension"; import { findDimensionByName } from "../../../common/models/dimension/dimensions"; @@ -36,7 +37,6 @@ import { SortDirection } from "../../../common/models/sort/sort"; import { SplitType } from "../../../common/models/split/split"; import { Splits } from "../../../common/models/splits/splits"; import { Stage } from "../../../common/models/stage/stage"; -import { VisualizationProps } from "../../../common/models/visualization-props/visualization-props"; import { formatValue } from "../../../common/utils/formatter/formatter"; import { or } from "../../../common/utils/functional/functional"; import { Predicates } from "../../../common/utils/rules/predicates"; @@ -51,6 +51,7 @@ import { VerticalAxis } from "../../components/vertical-axis/vertical-axis"; import { VisMeasureLabel } from "../../components/vis-measure-label/vis-measure-label"; import { SPLIT, VIS_H_PADDING } from "../../config/constants"; import { classNames, roundToPx } from "../../utils/dom/dom"; +import { ChartPanel, VisualizationControls, VisualizationProps } from "../../views/cube-view/center-panel/center-panel"; import { hasHighlightOn } from "../highlight-controller/highlight-controller"; import "./bar-chart.scss"; import { BarCoordinates } from "./bar-coordinates"; @@ -155,7 +156,14 @@ function padDataset(originalDataset: Dataset, dimension: Dimension, measures: Me return new Dataset(value); } -export class BarChart extends React.Component { +export function BarChartVisualization(props: VisualizationProps) { + return + + + ; +} + +class BarChart extends React.Component { protected className = BAR_CHART_MANIFEST.name; private coordinatesCache: BarCoordinates[][] = []; diff --git a/src/client/visualizations/data-provider/data-provider.tsx b/src/client/visualizations/data-provider/data-provider.tsx index 67cf28ddf..cb38c7173 100644 --- a/src/client/visualizations/data-provider/data-provider.tsx +++ b/src/client/visualizations/data-provider/data-provider.tsx @@ -17,17 +17,18 @@ import { Dataset, Expression } from "plywood"; import * as React from "react"; import { ReactNode } from "react"; -import { Essence } from "../../../common/models/essence/essence"; -import { Stage } from "../../../common/models/stage/stage"; -import { Timekeeper } from "../../../common/models/timekeeper/timekeeper"; import { - DatasetLoad, DatasetLoadStatus, + DatasetRequest, + DatasetRequestStatus, error, isError, isLoaded, loaded, loading -} from "../../../common/models/visualization-props/visualization-props"; +} from "../../../common/models/dataset-request/dataset-request"; +import { Essence } from "../../../common/models/essence/essence"; +import { Stage } from "../../../common/models/stage/stage"; +import { Timekeeper } from "../../../common/models/timekeeper/timekeeper"; import { debounceWithPromise, Unary } from "../../../common/utils/functional/functional"; import visualizationQuery from "../../../common/utils/query/visualization-query"; import { Loader } from "../../components/loader/loader"; @@ -45,7 +46,7 @@ interface DataProviderProps { } interface DataProviderState { - dataset: DatasetLoad; + dataset: DatasetRequest; } export class DataProvider extends React.Component { @@ -91,7 +92,7 @@ export class DataProvider extends React.Component { + private fetchData(essence: Essence, timekeeper: Timekeeper): Promise { this.lastQueryEssence = essence; return this.debouncedCallExecutor(essence, timekeeper); } @@ -100,7 +101,7 @@ export class DataProvider extends React.Component => + private callExecutor = (essence: Essence, timekeeper: Timekeeper): Promise => essence.dataCube.executor(this.getQuery(essence, timekeeper), { timezone: essence.timezone }) .then((dataset: Dataset) => { // signal out of order requests with null @@ -120,7 +121,7 @@ export class DataProvider extends React.Component; - case DatasetLoadStatus.ERROR: + case DatasetRequestStatus.ERROR: return ; - case DatasetLoadStatus.LOADED: + case DatasetRequestStatus.LOADED: return children(dataset.dataset); } } diff --git a/src/client/visualizations/grid/grid.tsx b/src/client/visualizations/grid/grid.tsx index 4161334ef..9e65505ac 100644 --- a/src/client/visualizations/grid/grid.tsx +++ b/src/client/visualizations/grid/grid.tsx @@ -17,8 +17,8 @@ import * as d3 from "d3"; import { Datum, PseudoDatum } from "plywood"; import * as React from "react"; +import { ChartProps } from "../../../common/models/chart-props/chart-props"; import { Essence } from "../../../common/models/essence/essence"; -import { VisualizationProps } from "../../../common/models/visualization-props/visualization-props"; import { Direction, ResizeHandle } from "../../components/resize-handle/resize-handle"; import { Scroller, ScrollerLayout } from "../../components/scroller/scroller"; import { @@ -36,6 +36,7 @@ import { FlattenedSplits } from "../../components/tabular-scroller/splits/flatte import { measureColumnsCount } from "../../components/tabular-scroller/utils/measure-columns-count"; import { visibleIndexRange } from "../../components/tabular-scroller/visible-rows/visible-index-range"; import { selectFirstSplitDatums } from "../../utils/dataset/selectors/selectors"; +import { ChartPanel, VisualizationControls, VisualizationProps } from "../../views/cube-view/center-panel/center-panel"; import "./grid.scss"; import { MeasureRows } from "./measure-rows"; @@ -44,7 +45,14 @@ interface GridState { scrollTop: number; } -export class Grid extends React.Component { +export function GridVisualization(props: VisualizationProps) { + return + + + ; +} + +class Grid extends React.Component { private innerGridRef = React.createRef(); state: GridState = { diff --git a/src/client/visualizations/heat-map/heat-map.tsx b/src/client/visualizations/heat-map/heat-map.tsx index 40c9f480c..74b53d8cb 100644 --- a/src/client/visualizations/heat-map/heat-map.tsx +++ b/src/client/visualizations/heat-map/heat-map.tsx @@ -24,17 +24,25 @@ import { Timezone } from "chronoshift"; import memoizeOne from "memoize-one"; import { Dataset } from "plywood"; import * as React from "react"; +import { ChartProps } from "../../../common/models/chart-props/chart-props"; import { ConcreteSeries } from "../../../common/models/series/concrete-series"; import { Split } from "../../../common/models/split/split"; -import { VisualizationProps } from "../../../common/models/visualization-props/visualization-props"; import { HEAT_MAP_MANIFEST } from "../../../common/visualization-manifests/heat-map/heat-map"; import { SPLIT } from "../../config/constants"; import { fillDatasetWithMissingValues } from "../../utils/dataset/sparse-dataset/dataset"; +import { ChartPanel, VisualizationControls, VisualizationProps } from "../../views/cube-view/center-panel/center-panel"; import "./heat-map.scss"; import { LabelledHeatmap, TILE_SIZE } from "./labeled-heatmap"; import scales from "./utils/scales"; -export class HeatMap extends React.Component { +export function HeatMapVisualization(props: VisualizationProps) { + return + + + ; +} + +class HeatMap extends React.Component { protected className = HEAT_MAP_MANIFEST.name; getScales = memoizeOne(scales); diff --git a/src/client/visualizations/index.ts b/src/client/visualizations/index.ts index a7ece9817..ac1f1ae51 100644 --- a/src/client/visualizations/index.ts +++ b/src/client/visualizations/index.ts @@ -16,22 +16,22 @@ */ import { VisualizationManifest } from "../../common/models/visualization-manifest/visualization-manifest"; -import { BarChart } from "./bar-chart/bar-chart"; -import { Grid } from "./grid/grid"; -import { HeatMap } from "./heat-map/heat-map"; -import { LineChart } from "./line-chart/line-chart"; -import { Table } from "./table/table"; -import { Totals } from "./totals/totals"; +import { BarChartVisualization } from "./bar-chart/bar-chart"; +import { GridVisualization } from "./grid/grid"; +import { HeatMapVisualization } from "./heat-map/heat-map"; +import { LineChartVisualization } from "./line-chart/line-chart"; +import { TableVisualization } from "./table/table"; +import { TotalsVisualization } from "./totals/totals"; -const VIS_COMPONENTS = { - "totals": Totals, - "table": Table, - "line-chart": LineChart, - "bar-chart": BarChart, - "heatmap": HeatMap, - "grid": Grid +const VISUALIZATIONS = { + "totals": TotalsVisualization, + "table": TableVisualization, + "line-chart": LineChartVisualization, + "bar-chart": BarChartVisualization, + "heatmap": HeatMapVisualization, + "grid": GridVisualization }; export function getVisualizationComponent({ name }: VisualizationManifest) { - return VIS_COMPONENTS[name]; + return VISUALIZATIONS[name]; } diff --git a/src/client/visualizations/line-chart/line-chart.tsx b/src/client/visualizations/line-chart/line-chart.tsx index 41fe41755..985f6dab5 100644 --- a/src/client/visualizations/line-chart/line-chart.tsx +++ b/src/client/visualizations/line-chart/line-chart.tsx @@ -16,9 +16,10 @@ */ import * as React from "react"; -import { VisualizationProps } from "../../../common/models/visualization-props/visualization-props"; +import { ChartProps } from "../../../common/models/chart-props/chart-props"; import { LINE_CHART_MANIFEST } from "../../../common/visualization-manifests/line-chart/line-chart"; import { MessageCard } from "../../components/message-card/message-card"; +import { ChartPanel, VisualizationControls, VisualizationProps } from "../../views/cube-view/center-panel/center-panel"; import { Charts } from "./charts/charts"; import { InteractionController } from "./interactions/interaction-controller"; import "./line-chart.scss"; @@ -29,7 +30,14 @@ import { XAxis } from "./x-axis/x-axis"; const Y_AXIS_WIDTH = 60; const X_AXIS_HEIGHT = 30; -export class LineChart extends React.Component { +export function LineChartVisualization(props: VisualizationProps) { + return + + + ; +} + +class LineChart extends React.Component { protected className = LINE_CHART_MANIFEST.name; private chartsRef = React.createRef(); diff --git a/src/client/visualizations/table/table.tsx b/src/client/visualizations/table/table.tsx index 0fb2f1a0b..a69b94459 100644 --- a/src/client/visualizations/table/table.tsx +++ b/src/client/visualizations/table/table.tsx @@ -17,16 +17,24 @@ import { FlattenOptions, PseudoDatum } from "plywood"; import * as React from "react"; -import { VisualizationProps } from "../../../common/models/visualization-props/visualization-props"; +import { ChartProps } from "../../../common/models/chart-props/chart-props"; import { ImmutableRecord } from "../../../common/utils/immutable-utils/immutable-utils"; import { TableSettings } from "../../../common/visualization-manifests/table/settings"; +import { ChartPanel, VisualizationControls, VisualizationProps } from "../../views/cube-view/center-panel/center-panel"; import { InteractionController } from "./interactions/interaction-controller"; import { ScrolledTable } from "./scrolled-table/scrolled-table"; import "./table.scss"; +export function TableVisualization(props: VisualizationProps) { + return + + + ; +} + const MIN_DIMENSION_WIDTH = 100; -export class Table extends React.Component { +class Table extends React.Component { private innerTableRef = React.createRef(); availableWidth(): number | undefined { diff --git a/src/client/visualizations/totals/totals.tsx b/src/client/visualizations/totals/totals.tsx index 9e8baf407..32db6b717 100644 --- a/src/client/visualizations/totals/totals.tsx +++ b/src/client/visualizations/totals/totals.tsx @@ -15,31 +15,29 @@ * limitations under the License. */ -import { Dataset } from "plywood"; import * as React from "react"; -import { VisualizationProps } from "../../../common/models/visualization-props/visualization-props"; +import { ChartProps } from "../../../common/models/chart-props/chart-props"; +import { ChartPanel, VisualizationControls, VisualizationProps } from "../../views/cube-view/center-panel/center-panel"; import { Total } from "./total"; import "./totals.scss"; -export class Totals extends React.Component { - renderTotals(dataset: Dataset): JSX.Element[] { - const { essence } = this.props; - const series = essence.getConcreteSeries().toArray(); - const datum = dataset.data[0]; - return series.map(series => - ); +const BigNumbers: React.SFC = ({ essence, data }) => { + const series = essence.getConcreteSeries().toArray(); + const datum = data.data[0]; + return
+ {series.map(series => + )} +
; +}; - } - - render() { - const { data } = this.props; - return
- {this.renderTotals(data)} -
; - } +export function TotalsVisualization(props: VisualizationProps) { + return + + + ; } diff --git a/src/common/models/visualization-props/visualization-props.ts b/src/common/models/chart-props/chart-props.ts similarity index 54% rename from src/common/models/visualization-props/visualization-props.ts rename to src/common/models/chart-props/chart-props.ts index cff20803d..99d6b376c 100644 --- a/src/common/models/visualization-props/visualization-props.ts +++ b/src/common/models/chart-props/chart-props.ts @@ -25,7 +25,7 @@ import { FilterClause } from "../filter-clause/filter-clause"; import { Stage } from "../stage/stage"; import { Timekeeper } from "../timekeeper/timekeeper"; -export interface VisualizationProps { +export interface ChartProps { data: Dataset; clicker: Clicker; essence: Essence; @@ -36,33 +36,3 @@ export interface VisualizationProps { highlight: Highlight | null; saveHighlight: (clauses: List, key?: string) => void; } - -export enum DatasetLoadStatus { LOADED, LOADING, ERROR } - -interface DatasetLoadBase { - status: DatasetLoadStatus; -} - -interface DatasetLoading extends DatasetLoadBase { - status: DatasetLoadStatus.LOADING; -} - -interface DatasetLoaded extends DatasetLoadBase { - status: DatasetLoadStatus.LOADED; - dataset: Dataset; -} - -interface DatasetLoadError extends DatasetLoadBase { - status: DatasetLoadStatus.ERROR; - error: Error; -} - -export const loading: DatasetLoading = { status: DatasetLoadStatus.LOADING }; -export const error = (error: Error): DatasetLoadError => ({ error, status: DatasetLoadStatus.ERROR }); -export const loaded = (dataset: Dataset): DatasetLoaded => ({ status: DatasetLoadStatus.LOADED, dataset }); - -export const isLoading = (dl: DatasetLoad): dl is DatasetLoading => dl.status === DatasetLoadStatus.LOADING; -export const isLoaded = (dl: DatasetLoad): dl is DatasetLoaded => dl.status === DatasetLoadStatus.LOADED; -export const isError = (dl: DatasetLoad): dl is DatasetLoadError => dl.status === DatasetLoadStatus.ERROR; - -export type DatasetLoad = DatasetLoading | DatasetLoaded | DatasetLoadError; diff --git a/src/common/models/dataset-request/dataset-request.ts b/src/common/models/dataset-request/dataset-request.ts new file mode 100644 index 000000000..54b0c68ff --- /dev/null +++ b/src/common/models/dataset-request/dataset-request.ts @@ -0,0 +1,47 @@ +/* + * Copyright 2017-2021 Allegro.pl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Dataset } from "plywood"; + +export enum DatasetRequestStatus { LOADED, LOADING, ERROR } + +interface DatasetRequestBase { + status: DatasetRequestStatus; +} + +interface DatasetLoading extends DatasetRequestBase { + status: DatasetRequestStatus.LOADING; +} + +interface DatasetLoaded extends DatasetRequestBase { + status: DatasetRequestStatus.LOADED; + dataset: Dataset; +} + +interface DatasetLoadError extends DatasetRequestBase { + status: DatasetRequestStatus.ERROR; + error: Error; +} + +export const loading: DatasetLoading = { status: DatasetRequestStatus.LOADING }; +export const error = (error: Error): DatasetLoadError => ({ error, status: DatasetRequestStatus.ERROR }); +export const loaded = (dataset: Dataset): DatasetLoaded => ({ status: DatasetRequestStatus.LOADED, dataset }); + +export const isLoading = (dr: DatasetRequest): dr is DatasetLoading => dr.status === DatasetRequestStatus.LOADING; +export const isLoaded = (dr: DatasetRequest): dr is DatasetLoaded => dr.status === DatasetRequestStatus.LOADED; +export const isError = (dr: DatasetRequest): dr is DatasetLoadError => dr.status === DatasetRequestStatus.ERROR; + +export type DatasetRequest = DatasetLoading | DatasetLoaded | DatasetLoadError;