From 9493515af700ac8d5ff4527fd461a6d3fc3f33df Mon Sep 17 00:00:00 2001 From: Adrian Mroz Date: Wed, 8 Feb 2023 15:41:57 +0100 Subject: [PATCH] split query router --- src/server/routes/query/query.ts | 239 +++--------------- .../routes/query/routes/boolean-filter.ts | 42 +++ .../routes/query/routes/number-filter.ts | 43 ++++ src/server/routes/query/routes/pinboard.ts | 60 +++++ src/server/routes/query/routes/raw-data.ts | 38 +++ .../routes/query/routes/string-filter.ts | 57 +++++ .../routes/query/routes/visualization.ts | 35 +++ src/server/utils/express/async-handler.ts | 21 ++ 8 files changed, 338 insertions(+), 197 deletions(-) create mode 100644 src/server/routes/query/routes/boolean-filter.ts create mode 100644 src/server/routes/query/routes/number-filter.ts create mode 100644 src/server/routes/query/routes/pinboard.ts create mode 100644 src/server/routes/query/routes/raw-data.ts create mode 100644 src/server/routes/query/routes/string-filter.ts create mode 100644 src/server/routes/query/routes/visualization.ts create mode 100644 src/server/utils/express/async-handler.ts diff --git a/src/server/routes/query/query.ts b/src/server/routes/query/query.ts index 5d4b719bc..e88f06453 100644 --- a/src/server/routes/query/query.ts +++ b/src/server/routes/query/query.ts @@ -14,227 +14,72 @@ * limitations under the License. */ -import { Request, Response, Router } from "express"; -import { $, Expression, ply, SortExpression } from "plywood"; -import makeGridQuery from "../../../client/visualizations/grid/make-query"; -import { Dimension } from "../../../common/models/dimension/dimension"; -import { findDimensionByName } from "../../../common/models/dimension/dimensions"; +import { NextFunction, Request, Response, Router } from "express"; +import { QueryableDataCube } from "../../../common/models/data-cube/queryable-data-cube"; import { Essence } from "../../../common/models/essence/essence"; -import { StringFilterClause } from "../../../common/models/filter-clause/filter-clause"; -import { LIMIT } from "../../../common/models/raw-data-modal/raw-data-modal"; -import { Split } from "../../../common/models/split/split"; -import { TimeShiftEnvType } from "../../../common/models/time-shift/time-shift-env"; import { Timekeeper } from "../../../common/models/timekeeper/timekeeper"; -import { CANONICAL_LENGTH_ID } from "../../../common/utils/canonical-length/query"; -import timeFilterCanonicalLength from "../../../common/utils/canonical-length/time-filter-canonical-length"; -import { isNil } from "../../../common/utils/general/general"; -import makeQuery from "../../../common/utils/query/visualization-query"; import { DEFAULT_VIEW_DEFINITION_VERSION, definitionConverters } from "../../../common/view-definitions"; import { createEssence } from "../../utils/essence/create-essence"; -import { getQueryDecorator } from "../../utils/query-decorator-loader/get-query-decorator"; -import { executeQuery } from "../../utils/query/execute-query"; +import { asyncHandler } from "../../utils/express/async-handler"; +import { AppliedQueryDecorator, getQueryDecorator } from "../../utils/query-decorator-loader/get-query-decorator"; import { handleRequestErrors } from "../../utils/request-errors/handle-request-errors"; import { parseDataCube } from "../../utils/request-params/parse-data-cube"; -import { parseDimension } from "../../utils/request-params/parse-dimension"; -import { - parseOptionalStringFilterClause, - parseStringFilterClause -} from "../../utils/request-params/parse-filter-clause"; -import { parseSplit } from "../../utils/request-params/parse-split"; import { parseViewDefinition } from "../../utils/request-params/parse-view-definition"; import { SettingsManager } from "../../utils/settings-manager/settings-manager"; +import booleanFilterRoute from "./routes/boolean-filter"; +import numberFilterRoute from "./routes/number-filter"; +import pinboardRoute from "./routes/pinboard"; +import rawDataRoute from "./routes/raw-data"; +import stringFilterRoute from "./routes/string-filter"; +import visualizationRoute from "./routes/visualization"; const converter = definitionConverters[DEFAULT_VIEW_DEFINITION_VERSION]; +interface QueryRouterContext { + dataCube: QueryableDataCube; + essence: Essence; + decorator: AppliedQueryDecorator; + timekeeper: Timekeeper; +} + +export type QueryRouterRequest = Request & { + context?: QueryRouterContext; +}; + export function queryRouter(settings: Pick) { const router = Router(); - router.post("/visualization", async (req: Request, res: Response) => { + router.use(asyncHandler(async (req: Request, res: Response, next: NextFunction) => { + const dataCube = await parseDataCube(req, settings); + const viewDefinition = parseViewDefinition(req); + const essence = createEssence(viewDefinition, converter, dataCube, settings.appSettings); + const decorator = getQueryDecorator(req, dataCube, settings); - function getQuery(essence: Essence, timekeeper: Timekeeper): Expression { - return essence.visualization.name === "grid" ? makeGridQuery(essence, timekeeper) : makeQuery(essence, timekeeper); - } + (req as QueryRouterRequest).context = { + dataCube, + essence, + decorator, + timekeeper: settings.getTimekeeper() + }; - try { - const dataCube = await parseDataCube(req, settings); - const viewDefinition = parseViewDefinition(req); + next(); + })); - const essence = createEssence(viewDefinition, converter, dataCube, settings.appSettings); + router.post("/visualization", asyncHandler(visualizationRoute)); - 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("/raw-data", asyncHandler(rawDataRoute)); - 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 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", asyncHandler(booleanFilterRoute)); - router.post("/boolean-filter", async (req: Request, res: Response) => { - function getQuery(essence: Essence, dimension: Dimension, timekeeper: Timekeeper): Expression { - const { dataCube } = essence; - const filterExpression = essence - .getEffectiveFilter(timekeeper, { unfilterDimension: dimension }) - .toExpression(dataCube); - - return $("main") - .filter(filterExpression) - .split(dimension.expression, dimension.name); - } - - try { - const dataCube = await parseDataCube(req, settings); - const viewDefinition = parseViewDefinition(req); - const dimension = parseDimension(req, dataCube); - - const essence = createEssence(viewDefinition, converter, dataCube, settings.appSettings); - - const query = getQuery(essence, dimension, 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("/string-filter", asyncHandler(stringFilterRoute)); - router.post("/string-filter", async (req: Request, res: Response) => { - - // TODO: expose for UI - const TOP_N = 100; - - function getQuery(essence: Essence, clause: StringFilterClause, timekeeper: Timekeeper): Expression { - const { dataCube } = essence; - const { reference: dimensionName } = clause; - - const $main = $("main"); - const dimension = findDimensionByName(dataCube.dimensions, dimensionName); - const nativeCount = findDimensionByName(dataCube.dimensions, "count"); - const measureExpression = nativeCount ? nativeCount.expression : $main.count(); - - const filter = essence - .changeFilter(essence.filter.setClause(clause)) - .getEffectiveFilter(timekeeper).toExpression(dataCube); - - return $main - .filter(filter) - .split(dimension.expression, dimension.name) - .apply("MEASURE", measureExpression) - .sort($("MEASURE"), SortExpression.DESCENDING) - .limit(TOP_N); - } - - try { - const dataCube = await parseDataCube(req, settings); - const viewDefinition = parseViewDefinition(req); - const clause = parseStringFilterClause(req, dataCube); - - const essence = createEssence(viewDefinition, converter, dataCube, settings.appSettings); - - const query = getQuery(essence, clause, 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("/number-filter", asyncHandler(numberFilterRoute)); - router.post("/number-filter", async (req: Request, res: Response) => { - - function getQuery(essence: Essence, dimension: Dimension, timekeeper: Timekeeper): Expression { - const { dataCube } = essence; - const filterExpression = essence - .getEffectiveFilter(timekeeper, { unfilterDimension: dimension }) - .toExpression(dataCube); - const $main = $("main"); - return ply() - .apply("main", $main.filter(filterExpression)) - .apply("Min", $main.min($(dimension.name))) - .apply("Max", $main.max($(dimension.name))); - } - - try { - const dataCube = await parseDataCube(req, settings); - const viewDefinition = parseViewDefinition(req); - const dimension = parseDimension(req, dataCube); - - const essence = createEssence(viewDefinition, converter, dataCube, settings.appSettings); - - const query = getQuery(essence, dimension, 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("/pinboard", asyncHandler(pinboardRoute)); - router.post("/pinboard", async (req: Request, res: Response) => { - - function getQuery(essence: Essence, clause: StringFilterClause | null, split: Split, timekeeper: Timekeeper): Expression { - const { dataCube } = essence; - - const dimension = findDimensionByName(dataCube.dimensions, split.reference); - const sortSeries = essence.findConcreteSeries(split.sort.reference); - const canonicalLength = timeFilterCanonicalLength(essence, timekeeper); - - const filter = essence - .changeFilter(isNil(clause) ? essence.filter.removeClause(split.reference) : essence.filter.setClause(clause)) - .getEffectiveFilter(timekeeper) - .toExpression(dataCube); - - return $("main") - .filter(filter) - .split(dimension.expression, dimension.name) - .apply(CANONICAL_LENGTH_ID, canonicalLength) - .performAction(sortSeries.plywoodExpression(0, { type: TimeShiftEnvType.CURRENT })) - .performAction(split.sort.toExpression()) - .limit(split.limit); - } - - try { - const dataCube = await parseDataCube(req, settings); - const viewDefinition = parseViewDefinition(req); - const clause = parseOptionalStringFilterClause(req, dataCube); - const split = parseSplit(req, dataCube); - - const essence = createEssence(viewDefinition, converter, dataCube, settings.appSettings); - - const query = getQuery(essence, clause, split, 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.use((error: Error, req: Request, res: Response, next: NextFunction) => { + handleRequestErrors(error, res, settings.logger); }); return router; diff --git a/src/server/routes/query/routes/boolean-filter.ts b/src/server/routes/query/routes/boolean-filter.ts new file mode 100644 index 000000000..1cda8dde6 --- /dev/null +++ b/src/server/routes/query/routes/boolean-filter.ts @@ -0,0 +1,42 @@ +/* + * 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. + */ +import { Response } from "express"; +import { $, Expression } from "plywood"; +import { Dimension } from "../../../../common/models/dimension/dimension"; +import { Essence } from "../../../../common/models/essence/essence"; +import { Timekeeper } from "../../../../common/models/timekeeper/timekeeper"; +import { executeQuery } from "../../../utils/query/execute-query"; +import { parseDimension } from "../../../utils/request-params/parse-dimension"; +import { QueryRouterRequest } from "../query"; + +function getQuery(essence: Essence, dimension: Dimension, timekeeper: Timekeeper): Expression { + const { dataCube } = essence; + const filterExpression = essence + .getEffectiveFilter(timekeeper, { unfilterDimension: dimension }) + .toExpression(dataCube); + + return $("main") + .filter(filterExpression) + .split(dimension.expression, dimension.name); +} + +export default async function booleanFilterRoute(req: QueryRouterRequest, res: Response) { + const { dataCube, essence, decorator, timekeeper } = req.context; + const dimension = parseDimension(req, dataCube); + const query = getQuery(essence, dimension, timekeeper); + const result = await executeQuery(dataCube, query, essence.timezone, decorator); + res.json({ result }); +} diff --git a/src/server/routes/query/routes/number-filter.ts b/src/server/routes/query/routes/number-filter.ts new file mode 100644 index 000000000..4b7132349 --- /dev/null +++ b/src/server/routes/query/routes/number-filter.ts @@ -0,0 +1,43 @@ +/* + * 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. + */ +import { Response } from "express"; +import { $, Expression, ply } from "plywood"; +import { Dimension } from "../../../../common/models/dimension/dimension"; +import { Essence } from "../../../../common/models/essence/essence"; +import { Timekeeper } from "../../../../common/models/timekeeper/timekeeper"; +import { executeQuery } from "../../../utils/query/execute-query"; +import { parseDimension } from "../../../utils/request-params/parse-dimension"; +import { QueryRouterRequest } from "../query"; + +function getQuery(essence: Essence, dimension: Dimension, timekeeper: Timekeeper): Expression { + const { dataCube } = essence; + const filterExpression = essence + .getEffectiveFilter(timekeeper, { unfilterDimension: dimension }) + .toExpression(dataCube); + const $main = $("main"); + return ply() + .apply("main", $main.filter(filterExpression)) + .apply("Min", $main.min($(dimension.name))) + .apply("Max", $main.max($(dimension.name))); +} + +export default async function numberFilterRoute(req: QueryRouterRequest, res: Response) { + const { dataCube, essence, decorator, timekeeper } = req.context; + const dimension = parseDimension(req, dataCube); + const query = getQuery(essence, dimension, timekeeper); + const result = await executeQuery(dataCube, query, essence.timezone, decorator); + res.json({ result }); +} diff --git a/src/server/routes/query/routes/pinboard.ts b/src/server/routes/query/routes/pinboard.ts new file mode 100644 index 000000000..a9b19627d --- /dev/null +++ b/src/server/routes/query/routes/pinboard.ts @@ -0,0 +1,60 @@ +/* + * 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. + */ +import { Response } from "express"; +import { $, Expression } from "plywood"; +import { findDimensionByName } from "../../../../common/models/dimension/dimensions"; +import { Essence } from "../../../../common/models/essence/essence"; +import { StringFilterClause } from "../../../../common/models/filter-clause/filter-clause"; +import { Split } from "../../../../common/models/split/split"; +import { TimeShiftEnvType } from "../../../../common/models/time-shift/time-shift-env"; +import { Timekeeper } from "../../../../common/models/timekeeper/timekeeper"; +import { CANONICAL_LENGTH_ID } from "../../../../common/utils/canonical-length/query"; +import timeFilterCanonicalLength from "../../../../common/utils/canonical-length/time-filter-canonical-length"; +import { isNil } from "../../../../common/utils/general/general"; +import { executeQuery } from "../../../utils/query/execute-query"; +import { parseOptionalStringFilterClause } from "../../../utils/request-params/parse-filter-clause"; +import { parseSplit } from "../../../utils/request-params/parse-split"; +import { QueryRouterRequest } from "../query"; + +function getQuery(essence: Essence, clause: StringFilterClause | null, split: Split, timekeeper: Timekeeper): Expression { + const { dataCube } = essence; + + const dimension = findDimensionByName(dataCube.dimensions, split.reference); + const sortSeries = essence.findConcreteSeries(split.sort.reference); + const canonicalLength = timeFilterCanonicalLength(essence, timekeeper); + + const filter = essence + .changeFilter(isNil(clause) ? essence.filter.removeClause(split.reference) : essence.filter.setClause(clause)) + .getEffectiveFilter(timekeeper) + .toExpression(dataCube); + + return $("main") + .filter(filter) + .split(dimension.expression, dimension.name) + .apply(CANONICAL_LENGTH_ID, canonicalLength) + .performAction(sortSeries.plywoodExpression(0, { type: TimeShiftEnvType.CURRENT })) + .performAction(split.sort.toExpression()) + .limit(split.limit); +} + +export default async function pinboardRoute(req: QueryRouterRequest, res: Response) { + const { dataCube, essence, decorator, timekeeper } = req.context; + const clause = parseOptionalStringFilterClause(req, dataCube); + const split = parseSplit(req, dataCube); + const query = getQuery(essence, clause, split, timekeeper); + const result = await executeQuery(dataCube, query, essence.timezone, decorator); + res.json({ result }); +} diff --git a/src/server/routes/query/routes/raw-data.ts b/src/server/routes/query/routes/raw-data.ts new file mode 100644 index 000000000..c34aa8b6c --- /dev/null +++ b/src/server/routes/query/routes/raw-data.ts @@ -0,0 +1,38 @@ +/* + * 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. + */ +import { Response } from "express"; +import { $, Expression } from "plywood"; +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 { executeQuery } from "../../../utils/query/execute-query"; +import { QueryRouterRequest } from "../query"; + +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); +} + +export default async function rawDataRoute({ context }: QueryRouterRequest, res: Response) { + const { dataCube, essence, decorator, timekeeper } = context; + const query = getQuery(essence, timekeeper); + const result = await executeQuery(dataCube, query, essence.timezone, decorator); + res.json({ result }); +} diff --git a/src/server/routes/query/routes/string-filter.ts b/src/server/routes/query/routes/string-filter.ts new file mode 100644 index 000000000..0732ee194 --- /dev/null +++ b/src/server/routes/query/routes/string-filter.ts @@ -0,0 +1,57 @@ +/* + * 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. + */ + +import { Response } from "express"; +import { $, Expression, SortExpression } from "plywood"; +import { findDimensionByName } from "../../../../common/models/dimension/dimensions"; +import { Essence } from "../../../../common/models/essence/essence"; +import { StringFilterClause } from "../../../../common/models/filter-clause/filter-clause"; +import { Timekeeper } from "../../../../common/models/timekeeper/timekeeper"; +import { executeQuery } from "../../../utils/query/execute-query"; +import { parseStringFilterClause } from "../../../utils/request-params/parse-filter-clause"; +import { QueryRouterRequest } from "../query"; + +// TODO: expose for UI +const TOP_N = 100; + +function getQuery(essence: Essence, clause: StringFilterClause, timekeeper: Timekeeper): Expression { + const { dataCube } = essence; + const { reference: dimensionName } = clause; + + const $main = $("main"); + const dimension = findDimensionByName(dataCube.dimensions, dimensionName); + const nativeCount = findDimensionByName(dataCube.dimensions, "count"); + const measureExpression = nativeCount ? nativeCount.expression : $main.count(); + + const filter = essence + .changeFilter(essence.filter.setClause(clause)) + .getEffectiveFilter(timekeeper).toExpression(dataCube); + + return $main + .filter(filter) + .split(dimension.expression, dimension.name) + .apply("MEASURE", measureExpression) + .sort($("MEASURE"), SortExpression.DESCENDING) + .limit(TOP_N); +} + +export default async function stringFilterRoute(req: QueryRouterRequest, res: Response) { + const { dataCube, essence, decorator, timekeeper } = req.context; + const clause = parseStringFilterClause(req, dataCube); + const query = getQuery(essence, clause, timekeeper); + const result = await executeQuery(dataCube, query, essence.timezone, decorator); + res.json({ result }); +} diff --git a/src/server/routes/query/routes/visualization.ts b/src/server/routes/query/routes/visualization.ts new file mode 100644 index 000000000..1887ae935 --- /dev/null +++ b/src/server/routes/query/routes/visualization.ts @@ -0,0 +1,35 @@ +/* + * 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. + */ + +import { Response } from "express"; +import { Expression } from "plywood"; +import makeGridQuery from "../../../../client/visualizations/grid/make-query"; +import { Essence } from "../../../../common/models/essence/essence"; +import { Timekeeper } from "../../../../common/models/timekeeper/timekeeper"; +import makeQuery from "../../../../common/utils/query/visualization-query"; +import { executeQuery } from "../../../utils/query/execute-query"; +import { QueryRouterRequest } from "../query"; + +function getQuery(essence: Essence, timekeeper: Timekeeper): Expression { + return essence.visualization.name === "grid" ? makeGridQuery(essence, timekeeper) : makeQuery(essence, timekeeper); +} + +export default async function visualizationRoute({ context }: QueryRouterRequest, res: Response) { + const { dataCube, essence, decorator, timekeeper } = context; + const query = getQuery(essence, timekeeper); + const result = await executeQuery(dataCube, query, essence.timezone, decorator); + res.json({ result }); +} diff --git a/src/server/utils/express/async-handler.ts b/src/server/utils/express/async-handler.ts new file mode 100644 index 000000000..1a297fec4 --- /dev/null +++ b/src/server/utils/express/async-handler.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ +import { NextFunction, Request, RequestHandler, Response } from "express"; + +// NOTE: Express 4.x can't handle rejected promises in handlers, so we need to wrap it in this function +export const asyncHandler = (fn: RequestHandler) => + (req: Request, res: Response, next: NextFunction) => + Promise.resolve(fn(req, res, next)).catch(next);