Skip to content

Commit

Permalink
Central panel (#796)
Browse files Browse the repository at this point in the history
* Introduce CenterPanel component

* CenterPanel refactors

* Divide into components
* CenterPanel handles creating stage based on DOM rectangle
* Pass component for visualization
* Some type machinery

* Define CenterStage for all menus inside CenterPanel based on bounding rect of div rendered in CubeView.

* CenterPanel does not have any responsibilities, so all Visualizations just call CenterTopBar and CenterMain explicitly.

* Big bunch of renames

* Quick fix for chart stage. More work expected in #799
  • Loading branch information
adrianmroz-allegro authored Oct 4, 2021
1 parent f1e46c0 commit a3fd142
Show file tree
Hide file tree
Showing 19 changed files with 436 additions and 251 deletions.
11 changes: 4 additions & 7 deletions src/client/components/pinboard-tile/pinboard-tile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<PinboardTileProps, PinboardTileState> {

state: PinboardTileState = {
Expand All @@ -81,14 +78,14 @@ export class PinboardTile extends React.Component<PinboardTileProps, PinboardTil
});
}

private fetchData(params: QueryParams): Promise<DatasetLoad | null> {
private fetchData(params: QueryParams): Promise<DatasetRequest | null> {
this.lastQueryParams = params;
return this.debouncedCallExecutor(params);
}

private lastQueryParams: Partial<QueryParams> = {};

private callExecutor = (params: QueryParams): Promise<DatasetLoad | null> => {
private callExecutor = (params: QueryParams): Promise<DatasetRequest | null> => {
const { essence: { timezone, dataCube } } = params;
return dataCube.executor(makeQuery(params), { timezone })
.then((dataset: Dataset) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
4 changes: 2 additions & 2 deletions src/client/components/pinboard-tile/utils/tile-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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";
Expand Down Expand Up @@ -74,7 +74,7 @@ export interface PreviewStringFilterMenuProps {

export interface PreviewStringFilterMenuState {
searchText: string;
dataset: DatasetLoad;
dataset: DatasetRequest;
}

interface QueryProps {
Expand Down Expand Up @@ -112,7 +112,7 @@ export class PreviewStringFilterMenu extends React.Component<PreviewStringFilter
});
}

private sendQueryFilter(): Promise<DatasetLoad> {
private sendQueryFilter(): Promise<DatasetRequest> {
const { searchText } = this.state;
this.lastSearchText = searchText;
return this.debouncedQueryFilter({ ...this.props, searchText });
Expand All @@ -124,7 +124,7 @@ export class PreviewStringFilterMenu extends React.Component<PreviewStringFilter
return filterMode === FilterMode.REGEX && searchText && checkRegex(searchText);
}

private queryFilter = (props: QueryProps): Promise<DatasetLoad> => {
private queryFilter = (props: QueryProps): Promise<DatasetRequest> => {
const { essence } = props;
const { searchText } = this.state;
const query = previewStringFilterQuery({ ...props, searchText, limit: TOP_N + 1 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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";
Expand Down Expand Up @@ -65,7 +65,7 @@ export interface SelectableStringFilterMenuProps {

export interface SelectableStringFilterMenuState {
searchText: string;
dataset: DatasetLoad;
dataset: DatasetRequest;
selectedValues: Set<string>;
pasteModeEnabled: boolean;
}
Expand Down Expand Up @@ -106,13 +106,13 @@ export class SelectableStringFilterMenu extends React.Component<SelectableString
});
}

private sendQueryFilter(): Promise<DatasetLoad> {
private sendQueryFilter(): Promise<DatasetRequest> {
const { searchText } = this.state;
this.lastSearchText = searchText;
return this.debouncedQueryFilter({ ...this.props, searchText });
}

private queryFilter = (props: QueryProps): Promise<DatasetLoad> => {
private queryFilter = (props: QueryProps): Promise<DatasetRequest> => {
const { essence, searchText } = props;
const query = stringFilterOptionsQuery({ ...props, limit: TOP_N + 1 });

Expand Down
181 changes: 181 additions & 0 deletions src/client/views/cube-view/center-panel/center-panel.tsx
Original file line number Diff line number Diff line change
@@ -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<Series, DragPosition, void>;
addFilter: Binary<Dimension, DragPosition, void>;
partialSeries: PartialSeries | null;
partialFilter: PartialFilter | null;
removeTile: Fn;
}

export const VisualizationControls: React.SFC<VisualizationControlsProps> = props => {
const {
addSeries,
addFilter,
clicker,
essence,
customization,
timekeeper,
stage,
partialSeries,
partialFilter,
removeTile
} = props;
return <div className="center-top-bar">
<div className="filter-split-section">
<FilterTilesRow
locale={customization.locale}
timekeeper={timekeeper}
menuStage={stage}
partialFilter={partialFilter}
removePartialFilter={removeTile}
addPartialFilter={addFilter}
/>
<SplitTilesRow
clicker={clicker}
essence={essence}
menuStage={stage}
/>
<SeriesTilesRow
removePartialSeries={removeTile}
partialSeries={partialSeries}
menuStage={stage}
addPartialSeries={addSeries}/>
</div>
<VisSelector clicker={clicker} essence={essence}/>
</div>;
};

interface ChartPanelProps {
essence: Essence;
clicker: Clicker;
stage: Stage;
chartComponent: React.ComponentType<ChartProps>;
timekeeper: Timekeeper;
lastRefreshRequestTimestamp: number;
dragEnter: Unary<React.DragEvent<HTMLElement>, void>;
dragOver: Unary<React.DragEvent<HTMLElement>, void>;
isDraggedOver: boolean;
dragLeave: Fn;
drop: Unary<React.DragEvent<HTMLElement>, void>;
}

export const ChartPanel: React.SFC<ChartPanelProps> = props => {
const {
chartComponent,
essence,
clicker,
timekeeper,
lastRefreshRequestTimestamp,
stage,
isDraggedOver,
dragOver,
dragEnter,
dragLeave,
drop
} = props;
return <div
className="center-main"
onDragEnter={dragEnter}
>
<div className="visualization">
<ChartWrapper
chartComponent={chartComponent}
essence={essence}
clicker={clicker}
timekeeper={timekeeper}
lastRefreshRequestTimestamp={lastRefreshRequestTimestamp}
stage={stage}/>
</div>
{isDraggedOver && <React.Fragment>
<DropIndicator/>
<div
className="drag-mask"
onDragOver={dragOver}
onDragLeave={dragLeave}
onDragExit={dragLeave}
onDrop={drop}
/>
</React.Fragment>
}
</div>;
};

type ChartWrapperProps = Pick<ChartPanelProps,
"timekeeper" |
"essence" |
"clicker" |
"stage" |
"lastRefreshRequestTimestamp" |
"chartComponent">;

function ChartWrapper(props: ChartWrapperProps) {
const { chartComponent: ChartComponent, essence, clicker, timekeeper, stage, lastRefreshRequestTimestamp } = props;
if (essence.visResolve.isManual()) {
return <ManualFallback clicker={clicker} essence={essence}/>;
}

return <HighlightController essence={essence} clicker={clicker}>
{highlightProps =>
<DataProvider
refreshRequestTimestamp={lastRefreshRequestTimestamp}
essence={essence}
timekeeper={timekeeper}
stage={stage}>
{data => <div className={classNames("visualization-root", essence.visualization.name)}>
<ChartComponent
data={data}
clicker={clicker}
essence={essence}
timekeeper={timekeeper}
stage={stage}
{...highlightProps} />
</div>}
</DataProvider>}
</HighlightController>;
}

type VisualizationPanelProps = ChartPanelProps & VisualizationControlsProps;
export type VisualizationProps = Omit<VisualizationPanelProps, "chartComponent">;
4 changes: 2 additions & 2 deletions src/client/views/cube-view/cube-view.mocha.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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();
});
Expand Down
Loading

0 comments on commit a3fd142

Please sign in to comment.