From bf46e7b5556d4affefdc3a3b1051e0dffcd32648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Mr=C3=B3=C5=BA?= <78143552+adrianmroz-allegro@users.noreply.github.com> Date: Wed, 8 Feb 2023 13:35:50 +0100 Subject: [PATCH] Query endpoint: Raw data modal (#1025) * query method for raw data modal --- .../modals/raw-data-modal/raw-data-modal.tsx | 20 +++++++------- src/client/views/cube-view/api-context.tsx | 21 ++++++++++++--- .../models/raw-data-modal/raw-data-modal.ts | 16 +++++++++++ src/server/routes/query/query.ts | 27 +++++++++++++++++++ 4 files changed, 71 insertions(+), 13 deletions(-) create mode 100644 src/common/models/raw-data-modal/raw-data-modal.ts diff --git a/src/client/modals/raw-data-modal/raw-data-modal.tsx b/src/client/modals/raw-data-modal/raw-data-modal.tsx index 583962f1b..ffb4db697 100644 --- a/src/client/modals/raw-data-modal/raw-data-modal.tsx +++ b/src/client/modals/raw-data-modal/raw-data-modal.tsx @@ -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"; @@ -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; @@ -83,7 +84,10 @@ function classFromAttribute(attribute: AttributeInfo): string { } export class RawDataModal extends React.Component { + static contextType = ApiContext; + public mounted: boolean; + context: ApiContextValue; constructor(props: RawDataModalProps) { super(props); @@ -100,20 +104,18 @@ export class RawDataModal extends React.Component { if (!this.mounted) return; diff --git a/src/client/views/cube-view/api-context.tsx b/src/client/views/cube-view/api-context.tsx index 006b1bd17..7b379708d 100644 --- a/src/client/views/cube-view/api-context.tsx +++ b/src/client/views/cube-view/api-context.tsx @@ -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>; +export type RawDataQuery = Unary>; + export type BooleanFilterQuery = Binary>; export interface ApiContextValue { visualizationQuery: VisualizationQuery; booleanFilterQuery: BooleanFilterQuery; + rawDataQuery: RawDataQuery; } export const ApiContext = React.createContext({ @@ -38,6 +41,9 @@ export const ApiContext = React.createContext({ }, 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"); } }); @@ -49,13 +55,15 @@ interface QueryResponse { result: DatasetJS; } -type QueryEndpoints = "visualization" | "boolean-filter"; +type QueryEndpoints = "visualization" | "boolean-filter" | "raw-data"; type ExtraParams = Record; type SerializeExtraBase = (...args: any[]) => ExtraParams; type QueryFunction = (essence: Essence, ...args: Parameters) => Promise; +const emptyParams: Nullary = () => ({}); + function createApiCall(settings: ClientAppSettings, query: QueryEndpoints, serializeExtraParams: T): QueryFunction { const { oauth, clientTimeout: timeout } = settings; const viewDefinitionVersion = DEFAULT_VIEW_DEFINITION_VERSION; @@ -81,17 +89,22 @@ function createApiCall(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) }; } diff --git a/src/common/models/raw-data-modal/raw-data-modal.ts b/src/common/models/raw-data-modal/raw-data-modal.ts new file mode 100644 index 000000000..6e00016da --- /dev/null +++ b/src/common/models/raw-data-modal/raw-data-modal.ts @@ -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; diff --git a/src/server/routes/query/query.ts b/src/server/routes/query/query.ts index 07e22f50c..fc28f9337 100644 --- a/src/server/routes/query/query.ts +++ b/src/server/routes/query/query.ts @@ -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"; @@ -58,6 +59,32 @@ export function queryRouter(settings: Pick { + 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;