Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query endpoint: Raw data modal #1025

Merged
merged 25 commits into from
Feb 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
43bdf5d
endpoint validation and helpers
adrianmroz-allegro Jan 31, 2023
2f33cc0
timezone is optional and we need to support old parameter dataCubeName
adrianmroz-allegro Jan 31, 2023
e657df3
pass whole settings and avoid functional-object pattern mismatch
adrianmroz-allegro Feb 1, 2023
6052d96
tests for query endpoint
adrianmroz-allegro Feb 1, 2023
98cc42f
organize utils
adrianmroz-allegro Feb 1, 2023
7417818
split running query and obtaining query decorator (with some partial …
adrianmroz-allegro Feb 1, 2023
19ff58d
tests for mkurl for invalid inputs
adrianmroz-allegro Feb 1, 2023
5cd7fdd
viewDefinition2 fixtures for mkurl tests
adrianmroz-allegro Feb 1, 2023
f0c95d4
timekeeper fixture for wiki data cube
adrianmroz-allegro Feb 1, 2023
f2e6f6c
normalize view definition mocks across wiki data cube and syntetic cube
adrianmroz-allegro Feb 1, 2023
d7b51c8
use fixtures in query tests
adrianmroz-allegro Feb 1, 2023
273530e
better wording
adrianmroz-allegro Feb 1, 2023
ebf1c51
tests for handle-request-errors
adrianmroz-allegro Feb 2, 2023
c61c753
endpoints test only endpoint logic, controllers are tested e2e
adrianmroz-allegro Feb 2, 2023
b6fc886
ApiContext
adrianmroz-allegro Feb 1, 2023
c404e50
expose ApiContext inside turnilo application
adrianmroz-allegro Feb 1, 2023
0854ead
use new ApiContext in data visualization
adrianmroz-allegro Feb 1, 2023
ebb6cad
nest query endpoints and prepare one for boolean
adrianmroz-allegro Feb 3, 2023
317aaea
boolean filter query endpoint and usage
adrianmroz-allegro Feb 3, 2023
31dd984
fix tests
adrianmroz-allegro Feb 3, 2023
31759bc
extract api call creation
adrianmroz-allegro Feb 3, 2023
4c6bc85
reuse type
adrianmroz-allegro Feb 3, 2023
3e539a3
fix spread of arguments
adrianmroz-allegro Feb 3, 2023
5511f71
query method for raw data modal
adrianmroz-allegro Feb 3, 2023
897e6da
Merge branch 'master' into feature/raw-data-modal-query-endpoint
adrianmroz-allegro Feb 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions src/client/modals/raw-data-modal/raw-data-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@

import { isDate } from "chronoshift";
import { List } from "immutable";
import { $, AttributeInfo, Dataset, Datum, Expression } from "plywood";
import { AttributeInfo, Dataset, Datum, Expression } from "plywood";
import React from "react";
import { ClientDataCube } from "../../../common/models/data-cube/data-cube";
import { findDimensionByName } from "../../../common/models/dimension/dimensions";
import { Essence } from "../../../common/models/essence/essence";
import { Locale } from "../../../common/models/locale/locale";
import { LIMIT } from "../../../common/models/raw-data-modal/raw-data-modal";
import { Stage } from "../../../common/models/stage/stage";
import { Timekeeper } from "../../../common/models/timekeeper/timekeeper";
import { formatFilterClause } from "../../../common/utils/formatter/formatter";
Expand All @@ -36,11 +37,11 @@ import { exportOptions, STRINGS } from "../../config/constants";
import { classNames } from "../../utils/dom/dom";
import { download, FileFormat, fileNameBase } from "../../utils/download/download";
import { getVisibleSegments } from "../../utils/sizing/sizing";
import { ApiContext, ApiContextValue } from "../../views/cube-view/api-context";
import "./raw-data-modal.scss";

const HEADER_HEIGHT = 30;
const ROW_HEIGHT = 30;
const LIMIT = 100;
const TIME_COL_WIDTH = 180;
const BOOLEAN_COL_WIDTH = 100;
const NUMBER_COL_WIDTH = 100;
Expand Down Expand Up @@ -83,7 +84,10 @@ function classFromAttribute(attribute: AttributeInfo): string {
}

export class RawDataModal extends React.Component<RawDataModalProps, RawDataModalState> {
static contextType = ApiContext;

public mounted: boolean;
context: ApiContextValue;

constructor(props: RawDataModalProps) {
super(props);
Expand All @@ -100,20 +104,18 @@ export class RawDataModal extends React.Component<RawDataModalProps, RawDataModa

componentDidMount() {
this.mounted = true;
const { essence, timekeeper } = this.props;
this.fetchData(essence, timekeeper);
const { essence } = this.props;
this.fetchData(essence);
}

componentWillUnmount() {
this.mounted = false;
}

fetchData(essence: Essence, timekeeper: Timekeeper): void {
const { dataCube } = essence;
const $main = $("main");
const query = $main.filter(essence.getEffectiveFilter(timekeeper).toExpression(dataCube)).limit(LIMIT);
fetchData(essence: Essence): void {
const { rawDataQuery } = this.context;
this.setState({ loading: true });
dataCube.executor(query, { timezone: essence.timezone })
rawDataQuery(essence)
.then(
(dataset: Dataset) => {
if (!this.mounted) return;
Expand Down
21 changes: 17 additions & 4 deletions src/client/views/cube-view/api-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@ import React, { useContext } from "react";
import { ClientAppSettings } from "../../../common/models/app-settings/app-settings";
import { Dimension } from "../../../common/models/dimension/dimension";
import { Essence } from "../../../common/models/essence/essence";
import { Binary, Unary } from "../../../common/utils/functional/functional";
import { Binary, Nullary, Unary } from "../../../common/utils/functional/functional";
import { DEFAULT_VIEW_DEFINITION_VERSION, definitionConverters } from "../../../common/view-definitions";
import { Ajax } from "../../utils/ajax/ajax";

export type VisualizationQuery = Unary<Essence, Promise<Dataset>>;

export type RawDataQuery = Unary<Essence, Promise<Dataset>>;

export type BooleanFilterQuery = Binary<Essence, Dimension, Promise<Dataset>>;

export interface ApiContextValue {
visualizationQuery: VisualizationQuery;
booleanFilterQuery: BooleanFilterQuery;
rawDataQuery: RawDataQuery;
}

export const ApiContext = React.createContext<ApiContextValue>({
Expand All @@ -38,6 +41,9 @@ export const ApiContext = React.createContext<ApiContextValue>({
},
get visualizationQuery(): VisualizationQuery {
throw new Error("Attempted to consume ApiContext when there was no Provider");
},
get rawDataQuery(): RawDataQuery {
throw new Error("Attempted to consume ApiContext when there was no Provider");
}
});

Expand All @@ -49,13 +55,15 @@ interface QueryResponse {
result: DatasetJS;
}

type QueryEndpoints = "visualization" | "boolean-filter";
type QueryEndpoints = "visualization" | "boolean-filter" | "raw-data";

type ExtraParams = Record<string, unknown>;

type SerializeExtraBase = (...args: any[]) => ExtraParams;
type QueryFunction<T extends SerializeExtraBase> = (essence: Essence, ...args: Parameters<T>) => Promise<Dataset>;

const emptyParams: Nullary<ExtraParams> = () => ({});

function createApiCall<T extends SerializeExtraBase>(settings: ClientAppSettings, query: QueryEndpoints, serializeExtraParams: T): QueryFunction<T> {
const { oauth, clientTimeout: timeout } = settings;
const viewDefinitionVersion = DEFAULT_VIEW_DEFINITION_VERSION;
Expand All @@ -81,17 +89,22 @@ function createApiCall<T extends SerializeExtraBase>(settings: ClientAppSettings
const constructDataset = (res: QueryResponse) => Dataset.fromJS(res.result);

function createVizQueryApi(settings: ClientAppSettings): VisualizationQuery {
return createApiCall(settings, "visualization", () => ({}));
return createApiCall(settings, "visualization", emptyParams);
}

function createBooleanFilterQuery(settings: ClientAppSettings): BooleanFilterQuery {
return createApiCall(settings, "boolean-filter", (dimension: Dimension) => ({ dimension: dimension.name }));
}

function createRawDataQueryApi(settings: ClientAppSettings): RawDataQuery {
return createApiCall(settings, "raw-data", emptyParams);
}

function createApi(settings: ClientAppSettings): ApiContextValue {
return {
booleanFilterQuery: createBooleanFilterQuery(settings),
visualizationQuery: createVizQueryApi(settings)
visualizationQuery: createVizQueryApi(settings),
rawDataQuery: createRawDataQueryApi(settings)
};
}

Expand Down
16 changes: 16 additions & 0 deletions src/common/models/raw-data-modal/raw-data-modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright 2017-2022 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.
*/
export const LIMIT = 100;
27 changes: 27 additions & 0 deletions src/server/routes/query/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { $, Expression } from "plywood";
import makeGridQuery from "../../../client/visualizations/grid/make-query";
import { Dimension } from "../../../common/models/dimension/dimension";
import { Essence } from "../../../common/models/essence/essence";
import { LIMIT } from "../../../common/models/raw-data-modal/raw-data-modal";
import { Timekeeper } from "../../../common/models/timekeeper/timekeeper";
import makeQuery from "../../../common/utils/query/visualization-query";
import { createEssence } from "../../utils/essence/create-essence";
Expand Down Expand Up @@ -58,6 +59,32 @@ export function queryRouter(settings: Pick<SettingsManager, "logger" | "getSourc
}
});

router.post("/raw-data", async (req: Request, res: Response) => {
function getQuery(essence: Essence, timekeeper: Timekeeper): Expression {
const { dataCube } = essence;
const $main = $("main");
const filterExpression = essence
.getEffectiveFilter(timekeeper)
.toExpression(dataCube);
return $main.filter(filterExpression).limit(LIMIT);
}

try {
const dataCube = await parseDataCube(req, settings);
const viewDefinition = parseViewDefinition(req);
const converter = parseViewDefinitionConverter(req);

const essence = createEssence(viewDefinition, converter, dataCube, settings.appSettings);

const query = getQuery(essence, settings.getTimekeeper());
const queryDecorator = getQueryDecorator(req, dataCube, settings);
const result = await executeQuery(dataCube, query, essence.timezone, queryDecorator);
res.json({ result });
} catch (error) {
handleRequestErrors(error, res, settings.logger);
}
});

router.post("/boolean-filter", async (req: Request, res: Response) => {
function getQuery(essence: Essence, dimension: Dimension, timekeeper: Timekeeper): Expression {
const { dataCube } = essence;
Expand Down