@sisense/sdk-query-client
Advanced tools
@@ -8,3 +8,3 @@ /** | ||
| */ | ||
| export { QueryApiDispatcher } from './query-api-dispatcher/query-api-dispatcher.js'; | ||
| export { QueryApiDispatcher, shouldUseSearchByDisplayName, } from './query-api-dispatcher/query-api-dispatcher.js'; | ||
| export type { DisplayNameConfig, GetDataSourceFieldsOptions, } from './query-api-dispatcher/types.js'; |
@@ -10,4 +10,5 @@ "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.QueryApiDispatcher = void 0; | ||
| exports.shouldUseSearchByDisplayName = exports.QueryApiDispatcher = void 0; | ||
| var query_api_dispatcher_js_1 = require("./query-api-dispatcher/query-api-dispatcher.js"); | ||
| Object.defineProperty(exports, "QueryApiDispatcher", { enumerable: true, get: function () { return query_api_dispatcher_js_1.QueryApiDispatcher; } }); | ||
| Object.defineProperty(exports, "shouldUseSearchByDisplayName", { enumerable: true, get: function () { return query_api_dispatcher_js_1.shouldUseSearchByDisplayName; } }); |
@@ -23,7 +23,13 @@ import { DataSource } from '@sisense/sdk-data'; | ||
| * | ||
| * Calculated dimensions used purely as dimensions (no `filter`) and filters that already carry a | ||
| * `datatype` (for example, those created in Fusion) are left untouched. The payload metadata is | ||
| * stamped in place — it is freshly built per query (by `getJaqlQueryPayload` and `filter.jaql()`), | ||
| * so this is not observable by callers. | ||
| * This covers both forms a calculated-dimension filter takes in the payload: | ||
| * - a FILTER, whose condition sits at the top level of its own metadata item (`jaql.filter`); | ||
| * - a HIGHLIGHT, which `getJaqlQueryPayload` embeds into its dimension as `jaql.in.selected`. Here | ||
| * the engine reads the datatype off the DIMENSION element itself, so the dimension element is | ||
| * stamped as well as the embedded selection. | ||
| * | ||
| * Calculated dimensions used purely as dimensions (no `filter`, no highlight) and filters that | ||
| * already carry a `datatype` (for example, those created in Fusion) are left untouched. The payload | ||
| * metadata is stamped in place — it is freshly built per query (by `getJaqlQueryPayload` and | ||
| * `filter.jaql()`), so this is not observable by callers. | ||
| * | ||
| * @param jaqlPayload - The JAQL payload whose filter metadata is enriched (only `metadata` is read). | ||
@@ -30,0 +36,0 @@ * @param dataSource - The data source the formulas are evaluated against. |
@@ -32,7 +32,13 @@ "use strict"; | ||
| * | ||
| * Calculated dimensions used purely as dimensions (no `filter`) and filters that already carry a | ||
| * `datatype` (for example, those created in Fusion) are left untouched. The payload metadata is | ||
| * stamped in place — it is freshly built per query (by `getJaqlQueryPayload` and `filter.jaql()`), | ||
| * so this is not observable by callers. | ||
| * This covers both forms a calculated-dimension filter takes in the payload: | ||
| * - a FILTER, whose condition sits at the top level of its own metadata item (`jaql.filter`); | ||
| * - a HIGHLIGHT, which `getJaqlQueryPayload` embeds into its dimension as `jaql.in.selected`. Here | ||
| * the engine reads the datatype off the DIMENSION element itself, so the dimension element is | ||
| * stamped as well as the embedded selection. | ||
| * | ||
| * Calculated dimensions used purely as dimensions (no `filter`, no highlight) and filters that | ||
| * already carry a `datatype` (for example, those created in Fusion) are left untouched. The payload | ||
| * metadata is stamped in place — it is freshly built per query (by `getJaqlQueryPayload` and | ||
| * `filter.jaql()`), so this is not observable by callers. | ||
| * | ||
| * @param jaqlPayload - The JAQL payload whose filter metadata is enriched (only `metadata` is read). | ||
@@ -46,15 +52,28 @@ * @param dataSource - The data source the formulas are evaluated against. | ||
| return __awaiter(this, void 0, void 0, function* () { | ||
| const filtersToResolve = jaqlPayload.metadata.filter(isUnresolvedCalculatedDimensionFilter); | ||
| yield Promise.all(filtersToResolve.map((filterMetadata) => __awaiter(this, void 0, void 0, function* () { | ||
| var _a; | ||
| const { formula, context } = filterMetadata.jaql; | ||
| // formula/filter presence is guaranteed by isUnresolvedCalculatedDimensionFilter | ||
| const parseResponse = yield parseCalculatedDimension(dataSource, formula !== null && formula !== void 0 ? formula : '', context !== null && context !== void 0 ? context : {}); | ||
| if (parseResponse === null || parseResponse === void 0 ? void 0 : parseResponse.error) { | ||
| throw new translatable_error_js_1.TranslatableError('errors.calculatedDimensionFormulaInvalid', { | ||
| message: (_a = parseResponse.message) !== null && _a !== void 0 ? _a : '', | ||
| const jaqlsToResolve = jaqlPayload.metadata.flatMap(collectUnresolvedCalculatedDimensionJaqls); | ||
| // A highlight produces both an unresolved dimension element and an unresolved embedded selection | ||
| // with the same formula/context, so resolve each distinct formula/context once per run and share | ||
| // the result across every matching node. | ||
| const resolutionByKey = new Map(); | ||
| const resolveDatatype = (formula, context) => { | ||
| const key = JSON.stringify({ formula, context }); | ||
| let pending = resolutionByKey.get(key); | ||
| if (!pending) { | ||
| pending = parseCalculatedDimension(dataSource, formula, context).then((parseResponse) => { | ||
| var _a; | ||
| if (parseResponse === null || parseResponse === void 0 ? void 0 : parseResponse.error) { | ||
| throw new translatable_error_js_1.TranslatableError('errors.calculatedDimensionFormulaInvalid', { | ||
| message: (_a = parseResponse.message) !== null && _a !== void 0 ? _a : '', | ||
| }); | ||
| } | ||
| return (parseResponse === null || parseResponse === void 0 ? void 0 : parseResponse.dataType) || DEFAULT_CALCULATED_DIMENSION_DATATYPE; | ||
| }); | ||
| resolutionByKey.set(key, pending); | ||
| } | ||
| filterMetadata.jaql.datatype = | ||
| (parseResponse === null || parseResponse === void 0 ? void 0 : parseResponse.dataType) || DEFAULT_CALCULATED_DIMENSION_DATATYPE; | ||
| return pending; | ||
| }; | ||
| yield Promise.all(jaqlsToResolve.map((cdJaql) => __awaiter(this, void 0, void 0, function* () { | ||
| var _a, _b; | ||
| // formula presence is guaranteed by needsCalculatedDimensionDatatype | ||
| cdJaql.datatype = yield resolveDatatype((_a = cdJaql.formula) !== null && _a !== void 0 ? _a : '', (_b = cdJaql.context) !== null && _b !== void 0 ? _b : {}); | ||
| }))); | ||
@@ -64,15 +83,42 @@ }); | ||
| /** | ||
| * Returns whether a metadata item is a calculated-dimension filter that still needs its result | ||
| * data type resolved (a `filter` present, but no top-level `datatype`). | ||
| * Collects the calculated-dimension JAQL nodes within a metadata item that still need their result | ||
| * data type resolved: | ||
| * - the item's own element, when it drives a top-level `filter` OR carries an embedded highlight | ||
| * (`jaql.in.selected`) — the engine reads the datatype off this element in both cases; | ||
| * - the embedded highlight node itself (`jaql.in.selected.jaql`). | ||
| * | ||
| * @param metadataItem - The metadata item to test. | ||
| * @param metadataItem - The metadata item to inspect. | ||
| * @returns The calculated-dimension JAQL nodes (0-2) within the item that still lack a `datatype`. | ||
| * @internal | ||
| */ | ||
| function isUnresolvedCalculatedDimensionFilter(metadataItem) { | ||
| var _a; | ||
| return (((_a = metadataItem.jaql) === null || _a === void 0 ? void 0 : _a.type) === sdk_data_1.CALCULATED_DIMENSION_JAQL_TYPE && | ||
| function collectUnresolvedCalculatedDimensionJaqls(metadataItem) { | ||
| var _a, _b; | ||
| const jaql = metadataItem.jaql; | ||
| const highlightJaql = (_b = (_a = jaql === null || jaql === void 0 ? void 0 : jaql.in) === null || _a === void 0 ? void 0 : _a.selected) === null || _b === void 0 ? void 0 : _b.jaql; | ||
| const cdJaqls = []; | ||
| // The element drives a filter/highlight when it either owns a top-level filter or carries an | ||
| // embedded highlight. A calculated dimension has no `dim`, so it must carry its own `datatype`. | ||
| const drivesFilterOrHighlight = Boolean(jaql === null || jaql === void 0 ? void 0 : jaql.filter) || Boolean(highlightJaql === null || highlightJaql === void 0 ? void 0 : highlightJaql.filter); | ||
| if (drivesFilterOrHighlight && needsCalculatedDimensionDatatype(jaql)) { | ||
| cdJaqls.push(jaql); | ||
| } | ||
| if (Boolean(highlightJaql === null || highlightJaql === void 0 ? void 0 : highlightJaql.filter) && needsCalculatedDimensionDatatype(highlightJaql)) { | ||
| cdJaqls.push(highlightJaql); | ||
| } | ||
| return cdJaqls; | ||
| } | ||
| /** | ||
| * Returns whether a JAQL node is a calculated dimension that still needs its result data type | ||
| * resolved (a calculated dimension with a `formula` but no `datatype`). | ||
| * | ||
| * @param jaql - The JAQL node to test. | ||
| * @returns `true` when the node is a calculated dimension with a `formula` and no `datatype`. | ||
| * @internal | ||
| */ | ||
| function needsCalculatedDimensionDatatype(jaql) { | ||
| return ((jaql === null || jaql === void 0 ? void 0 : jaql.type) === sdk_data_1.CALCULATED_DIMENSION_JAQL_TYPE && | ||
| // presence, not truthiness: an empty formula must still reach the parser to surface the | ||
| // server's invalid-formula error rather than being silently skipped | ||
| metadataItem.jaql.formula !== undefined && | ||
| Boolean(metadataItem.jaql.filter) && | ||
| !metadataItem.jaql.datatype); | ||
| jaql.formula !== undefined && | ||
| !jaql.datatype); | ||
| } |
| import { Filter, MetadataItem } from '@sisense/sdk-data'; | ||
| export declare function applyHighlightFilters(metadataItem: MetadataItem, highlights: Filter[]): MetadataItem; | ||
| export declare function matchHighlightsWithAttributes(attributesMetadata: MetadataItem[], highlights: Filter[]): Filter[][]; | ||
| /** | ||
| * Builds the identity key used to match a highlight filter to a query dimension. | ||
| * | ||
| * Keyed on `dim` for a regular attribute. A calculated dimension has no `dim`, so its `formula` is | ||
| * used instead (matching `DimensionalCalculatedAttribute.id`, which returns the formula). Without | ||
| * this fallback a calculated-dimension highlight never matches its query dimension and would be | ||
| * applied as a standalone slice filter instead of being embedded as a highlight on the dimension. | ||
| * | ||
| * @param metadataItem - The dimension or filter metadata item to derive an identity from | ||
| * @returns The identity key: `dim` (or `formula` for a calculated dimension), suffixed with the | ||
| * level and bucket when present. | ||
| * @internal | ||
| */ | ||
| export declare function getMetadataItemId(metadataItem: MetadataItem): string; |
@@ -37,5 +37,18 @@ "use strict"; | ||
| } | ||
| /** | ||
| * Builds the identity key used to match a highlight filter to a query dimension. | ||
| * | ||
| * Keyed on `dim` for a regular attribute. A calculated dimension has no `dim`, so its `formula` is | ||
| * used instead (matching `DimensionalCalculatedAttribute.id`, which returns the formula). Without | ||
| * this fallback a calculated-dimension highlight never matches its query dimension and would be | ||
| * applied as a standalone slice filter instead of being embedded as a highlight on the dimension. | ||
| * | ||
| * @param metadataItem - The dimension or filter metadata item to derive an identity from | ||
| * @returns The identity key: `dim` (or `formula` for a calculated dimension), suffixed with the | ||
| * level and bucket when present. | ||
| * @internal | ||
| */ | ||
| function getMetadataItemId(metadataItem) { | ||
| const { dim, level, dateTimeLevel, bucket } = metadataItem.jaql; | ||
| let id = `${dim}`; | ||
| const { dim, formula, level, dateTimeLevel, bucket } = metadataItem.jaql; | ||
| let id = `${dim !== null && dim !== void 0 ? dim : formula}`; | ||
| if (level || dateTimeLevel) { | ||
@@ -42,0 +55,0 @@ id += `_${level || dateTimeLevel}`; |
| import { DataSource, DataSourceField, DataSourceMetadata, DataSourceSchema } from '@sisense/sdk-data'; | ||
| import { HttpClient } from '@sisense/sdk-rest-client'; | ||
| import { CalculatedDimensionParseResponse, CountRowsResponse, JaqlQueryPayload, JaqlResponse, QueryGuid } from '../types.js'; | ||
| import { GetDataSourceFieldsOptions } from './types.js'; | ||
| import { DisplayNameConfig, GetDataSourceFieldsOptions } from './types.js'; | ||
| /** | ||
@@ -16,7 +16,9 @@ * Thin HttpClient-backed datasource/query REST dispatcher. | ||
| * | ||
| * Endpoint choice depends on system display-name settings (see options.displayNameConfig): | ||
| * Path and endpoint selection: | ||
| * - When display names are enabled *and* `useNewSearchByDisplayNameApi` is true, | ||
| * uses `POST …/fields/searchByDisplayName?isLive=` so search matches display titles | ||
| * and the BE routes live vs ElastiCube correctly via `isLive`. | ||
| * - Otherwise uses `POST …/fields/search` (viewer-safe field listing used by data browser). | ||
| * uses `POST …/fields/searchByDisplayName?isLive=` with the datasource **title** | ||
| * (not fullname) so search matches display titles and the BE routes live vs EC via `isLive`. | ||
| * - Otherwise uses `POST …/fields/search` with the datasource **fullname** when available | ||
| * (from `DataSourceInfo` or the viewer-safe list), encoding path segments while | ||
| * preserving `/` — required for EC, EC perspectives, live, and live perspectives. | ||
| * | ||
@@ -101,1 +103,12 @@ * @param dataSource - Datasource title or info object | ||
| } | ||
| /** | ||
| * Determines whether to use the display-name field search endpoint. | ||
| * Both flags must be true — matching system settings defaults | ||
| * (`displayNameConfig.enabled` can be true while `useNewSearchByDisplayNameApi` is still false). | ||
| * | ||
| * @param displayNameConfig - Optional display-name configuration from system settings. | ||
| * @returns `true` when both `enabled` and `useNewSearchByDisplayNameApi` are true, | ||
| * selecting the new search-by-display-name API; otherwise `false`. | ||
| * @internal | ||
| */ | ||
| export declare function shouldUseSearchByDisplayName(displayNameConfig?: DisplayNameConfig): boolean; |
@@ -13,2 +13,3 @@ "use strict"; | ||
| exports.QueryApiDispatcher = void 0; | ||
| exports.shouldUseSearchByDisplayName = shouldUseSearchByDisplayName; | ||
| const sdk_data_1 = require("@sisense/sdk-data"); | ||
@@ -30,7 +31,9 @@ const translatable_error_js_1 = require("../translation/translatable-error.js"); | ||
| * | ||
| * Endpoint choice depends on system display-name settings (see options.displayNameConfig): | ||
| * Path and endpoint selection: | ||
| * - When display names are enabled *and* `useNewSearchByDisplayNameApi` is true, | ||
| * uses `POST …/fields/searchByDisplayName?isLive=` so search matches display titles | ||
| * and the BE routes live vs ElastiCube correctly via `isLive`. | ||
| * - Otherwise uses `POST …/fields/search` (viewer-safe field listing used by data browser). | ||
| * uses `POST …/fields/searchByDisplayName?isLive=` with the datasource **title** | ||
| * (not fullname) so search matches display titles and the BE routes live vs EC via `isLive`. | ||
| * - Otherwise uses `POST …/fields/search` with the datasource **fullname** when available | ||
| * (from `DataSourceInfo` or the viewer-safe list), encoding path segments while | ||
| * preserving `/` — required for EC, EC perspectives, live, and live perspectives. | ||
| * | ||
@@ -42,16 +45,25 @@ * @param dataSource - Datasource title or info object | ||
| return __awaiter(this, arguments, void 0, function* (dataSource, options = {}) { | ||
| var _a, _b; | ||
| var _a, _b, _c; | ||
| const { count = 9999, offset = 0, term, displayNameConfig } = options; | ||
| const dataSourceName = (0, sdk_data_1.getDataSourceName)(dataSource); | ||
| const title = (0, sdk_data_1.getDataSourceName)(dataSource); | ||
| const providedFullName = (0, sdk_data_1.getDataSourceFullName)(dataSource); | ||
| const useDisplayNameSearch = shouldUseSearchByDisplayName(displayNameConfig); | ||
| // Display-name path needs `live`; legacy search needs `fullname`. Skip the list when we already have it. | ||
| const needsListLookup = useDisplayNameSearch | ||
| ? options.live === undefined | ||
| : providedFullName === undefined; | ||
| const metadata = needsListLookup ? yield this.getDataSourceByTitle(title) : null; | ||
| // Fusion: searchByDisplayName → title; fields/search → fullname. | ||
| const pathId = useDisplayNameSearch ? title : (_a = providedFullName !== null && providedFullName !== void 0 ? providedFullName : metadata === null || metadata === void 0 ? void 0 : metadata.fullname) !== null && _a !== void 0 ? _a : title; | ||
| const encodedPath = (0, sdk_data_1.encodeDataSourcePath)(pathId); | ||
| let url; | ||
| if (useDisplayNameSearch) { | ||
| // `isLive` is required by the BE on this path to pick live vs EC field handlers. | ||
| // Prefer an explicit option; otherwise resolve from the viewer-safe list endpoint | ||
| // Prefer an explicit option; otherwise list metadata; then caller DataSourceInfo.type | ||
| // (do not GET api/datasources/{title} — that requires manage/viewschema and fails for viewers). | ||
| const live = (_a = options.live) !== null && _a !== void 0 ? _a : ((_b = (yield this.getDataSourceByTitle(dataSourceName))) === null || _b === void 0 ? void 0 : _b.live) === true; | ||
| url = `${API_DATASOURCES_BASE_PATH}/${encodeURIComponent(dataSourceName)}/fields/searchByDisplayName?isLive=${live}`; | ||
| const live = (_c = (_b = options.live) !== null && _b !== void 0 ? _b : metadata === null || metadata === void 0 ? void 0 : metadata.live) !== null && _c !== void 0 ? _c : ((0, sdk_data_1.isDataSourceInfo)(dataSource) && dataSource.type === 'live'); | ||
| url = `${API_DATASOURCES_BASE_PATH}/${encodedPath}/fields/searchByDisplayName?isLive=${live}`; | ||
| } | ||
| else { | ||
| url = `${API_DATASOURCES_BASE_PATH}/${encodeURIComponent(dataSourceName)}/fields/search`; | ||
| url = `${API_DATASOURCES_BASE_PATH}/${encodedPath}/fields/search`; | ||
| } | ||
@@ -120,5 +132,6 @@ const body = { offset, count }; | ||
| return __awaiter(this, void 0, void 0, function* () { | ||
| var _a, _b; | ||
| const list = (_a = (yield this.getDataSourceList())) !== null && _a !== void 0 ? _a : []; | ||
| return (_b = list.find((ds) => ds.title === title)) !== null && _b !== void 0 ? _b : null; | ||
| var _a; | ||
| const list = yield this.getDataSourceList(); | ||
| const items = Array.isArray(list) ? list : []; | ||
| return (_a = items.find((ds) => ds.title === title)) !== null && _a !== void 0 ? _a : null; | ||
| }); | ||
@@ -222,2 +235,5 @@ } | ||
| * | ||
| * @param displayNameConfig - Optional display-name configuration from system settings. | ||
| * @returns `true` when both `enabled` and `useNewSearchByDisplayNameApi` are true, | ||
| * selecting the new search-by-display-name API; otherwise `false`. | ||
| * @internal | ||
@@ -224,0 +240,0 @@ */ |
| import { Cell, DataCell, Element, QueryResultData } from '@sisense/sdk-data'; | ||
| import { JaqlResponse } from '../types.js'; | ||
| export declare const getDataFromQueryResult: (result: JaqlResponse, metadata: Element[]) => QueryResultData; | ||
| export declare function prepareResultAsColsAndRows(data: DataCell[][], metadata: Element[]): QueryResultData; | ||
| /** | ||
| * The subset of an {@link Element} needed to describe a result column. | ||
| * | ||
| * Query metadata is normally made of full model elements, but columns appended by advanced | ||
| * analytics have no element behind them, so only these three fields are ever available. | ||
| */ | ||
| export type ResultColumnMetadata = Pick<Element, 'name' | 'title' | 'type'>; | ||
| export declare const getDataFromQueryResult: (result: JaqlResponse, metadata: readonly ResultColumnMetadata[]) => QueryResultData; | ||
| export declare function prepareResultAsColsAndRows(data: DataCell[][], metadata: readonly ResultColumnMetadata[]): QueryResultData; | ||
| /** | ||
| * Sets the `blur` property for each cell in a 2D array of data cells based on the `selected` property. | ||
@@ -7,0 +14,0 @@ * |
@@ -21,2 +21,6 @@ "use strict"; | ||
| name: d.name, | ||
| // `name` carries the element identity (the physical column), so the display label has to | ||
| // be surfaced separately for consumers rendering result headers. Model elements always | ||
| // resolve `title`, defaulting to `name`. | ||
| title: d.title, | ||
| type: (0, sdk_data_1.simpleColumnType)(d.type), | ||
@@ -23,0 +27,0 @@ })), |
@@ -20,3 +20,3 @@ "use strict"; | ||
| const query_client_js_1 = require("../query-client.js"); | ||
| const index_js_1 = require("../query-result/index.js"); | ||
| const get_data_from_query_result_js_1 = require("../query-result/get-data-from-query-result.js"); | ||
| const translatable_error_js_1 = require("../translation/translatable-error.js"); | ||
@@ -91,7 +91,9 @@ class QueryTaskManager extends task_manager_1.AbstractTaskManager { | ||
| const metadata = [...queryDescription.attributes, ...queryDescription.measures]; | ||
| // extra columns are assumed to have been added by advanced analytics functions | ||
| // extra columns are assumed to have been added by advanced analytics functions. | ||
| // They have no model element behind them, so the response header doubles as both | ||
| // identity and display label. | ||
| const extraColumns = (jaqlResponse.headers || []) | ||
| .slice(metadata.length) | ||
| .map((c) => ({ name: c, type: 'number' })); | ||
| return (0, index_js_1.getDataFromQueryResult)(jaqlResponse, [...metadata, ...extraColumns]); | ||
| .map((c) => ({ name: c, title: c, type: 'number' })); | ||
| return (0, get_data_from_query_result_js_1.getDataFromQueryResult)(jaqlResponse, [...metadata, ...extraColumns]); | ||
| }); | ||
@@ -98,0 +100,0 @@ } |
@@ -8,3 +8,3 @@ /** | ||
| */ | ||
| export { QueryApiDispatcher } from './query-api-dispatcher/query-api-dispatcher.js'; | ||
| export { QueryApiDispatcher, shouldUseSearchByDisplayName, } from './query-api-dispatcher/query-api-dispatcher.js'; | ||
| export type { DisplayNameConfig, GetDataSourceFieldsOptions, } from './query-api-dispatcher/types.js'; |
@@ -8,2 +8,2 @@ /** | ||
| */ | ||
| export { QueryApiDispatcher } from './query-api-dispatcher/query-api-dispatcher.js'; | ||
| export { QueryApiDispatcher, shouldUseSearchByDisplayName, } from './query-api-dispatcher/query-api-dispatcher.js'; |
@@ -23,7 +23,13 @@ import { DataSource } from '@sisense/sdk-data'; | ||
| * | ||
| * Calculated dimensions used purely as dimensions (no `filter`) and filters that already carry a | ||
| * `datatype` (for example, those created in Fusion) are left untouched. The payload metadata is | ||
| * stamped in place — it is freshly built per query (by `getJaqlQueryPayload` and `filter.jaql()`), | ||
| * so this is not observable by callers. | ||
| * This covers both forms a calculated-dimension filter takes in the payload: | ||
| * - a FILTER, whose condition sits at the top level of its own metadata item (`jaql.filter`); | ||
| * - a HIGHLIGHT, which `getJaqlQueryPayload` embeds into its dimension as `jaql.in.selected`. Here | ||
| * the engine reads the datatype off the DIMENSION element itself, so the dimension element is | ||
| * stamped as well as the embedded selection. | ||
| * | ||
| * Calculated dimensions used purely as dimensions (no `filter`, no highlight) and filters that | ||
| * already carry a `datatype` (for example, those created in Fusion) are left untouched. The payload | ||
| * metadata is stamped in place — it is freshly built per query (by `getJaqlQueryPayload` and | ||
| * `filter.jaql()`), so this is not observable by callers. | ||
| * | ||
| * @param jaqlPayload - The JAQL payload whose filter metadata is enriched (only `metadata` is read). | ||
@@ -30,0 +36,0 @@ * @param dataSource - The data source the formulas are evaluated against. |
@@ -10,3 +10,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
| }; | ||
| import { CALCULATED_DIMENSION_JAQL_TYPE } from '@sisense/sdk-data'; | ||
| import { CALCULATED_DIMENSION_JAQL_TYPE, } from '@sisense/sdk-data'; | ||
| import { TranslatableError } from '../translation/translatable-error.js'; | ||
@@ -30,7 +30,13 @@ /** | ||
| * | ||
| * Calculated dimensions used purely as dimensions (no `filter`) and filters that already carry a | ||
| * `datatype` (for example, those created in Fusion) are left untouched. The payload metadata is | ||
| * stamped in place — it is freshly built per query (by `getJaqlQueryPayload` and `filter.jaql()`), | ||
| * so this is not observable by callers. | ||
| * This covers both forms a calculated-dimension filter takes in the payload: | ||
| * - a FILTER, whose condition sits at the top level of its own metadata item (`jaql.filter`); | ||
| * - a HIGHLIGHT, which `getJaqlQueryPayload` embeds into its dimension as `jaql.in.selected`. Here | ||
| * the engine reads the datatype off the DIMENSION element itself, so the dimension element is | ||
| * stamped as well as the embedded selection. | ||
| * | ||
| * Calculated dimensions used purely as dimensions (no `filter`, no highlight) and filters that | ||
| * already carry a `datatype` (for example, those created in Fusion) are left untouched. The payload | ||
| * metadata is stamped in place — it is freshly built per query (by `getJaqlQueryPayload` and | ||
| * `filter.jaql()`), so this is not observable by callers. | ||
| * | ||
| * @param jaqlPayload - The JAQL payload whose filter metadata is enriched (only `metadata` is read). | ||
@@ -44,15 +50,28 @@ * @param dataSource - The data source the formulas are evaluated against. | ||
| return __awaiter(this, void 0, void 0, function* () { | ||
| const filtersToResolve = jaqlPayload.metadata.filter(isUnresolvedCalculatedDimensionFilter); | ||
| yield Promise.all(filtersToResolve.map((filterMetadata) => __awaiter(this, void 0, void 0, function* () { | ||
| var _a; | ||
| const { formula, context } = filterMetadata.jaql; | ||
| // formula/filter presence is guaranteed by isUnresolvedCalculatedDimensionFilter | ||
| const parseResponse = yield parseCalculatedDimension(dataSource, formula !== null && formula !== void 0 ? formula : '', context !== null && context !== void 0 ? context : {}); | ||
| if (parseResponse === null || parseResponse === void 0 ? void 0 : parseResponse.error) { | ||
| throw new TranslatableError('errors.calculatedDimensionFormulaInvalid', { | ||
| message: (_a = parseResponse.message) !== null && _a !== void 0 ? _a : '', | ||
| const jaqlsToResolve = jaqlPayload.metadata.flatMap(collectUnresolvedCalculatedDimensionJaqls); | ||
| // A highlight produces both an unresolved dimension element and an unresolved embedded selection | ||
| // with the same formula/context, so resolve each distinct formula/context once per run and share | ||
| // the result across every matching node. | ||
| const resolutionByKey = new Map(); | ||
| const resolveDatatype = (formula, context) => { | ||
| const key = JSON.stringify({ formula, context }); | ||
| let pending = resolutionByKey.get(key); | ||
| if (!pending) { | ||
| pending = parseCalculatedDimension(dataSource, formula, context).then((parseResponse) => { | ||
| var _a; | ||
| if (parseResponse === null || parseResponse === void 0 ? void 0 : parseResponse.error) { | ||
| throw new TranslatableError('errors.calculatedDimensionFormulaInvalid', { | ||
| message: (_a = parseResponse.message) !== null && _a !== void 0 ? _a : '', | ||
| }); | ||
| } | ||
| return (parseResponse === null || parseResponse === void 0 ? void 0 : parseResponse.dataType) || DEFAULT_CALCULATED_DIMENSION_DATATYPE; | ||
| }); | ||
| resolutionByKey.set(key, pending); | ||
| } | ||
| filterMetadata.jaql.datatype = | ||
| (parseResponse === null || parseResponse === void 0 ? void 0 : parseResponse.dataType) || DEFAULT_CALCULATED_DIMENSION_DATATYPE; | ||
| return pending; | ||
| }; | ||
| yield Promise.all(jaqlsToResolve.map((cdJaql) => __awaiter(this, void 0, void 0, function* () { | ||
| var _a, _b; | ||
| // formula presence is guaranteed by needsCalculatedDimensionDatatype | ||
| cdJaql.datatype = yield resolveDatatype((_a = cdJaql.formula) !== null && _a !== void 0 ? _a : '', (_b = cdJaql.context) !== null && _b !== void 0 ? _b : {}); | ||
| }))); | ||
@@ -62,15 +81,42 @@ }); | ||
| /** | ||
| * Returns whether a metadata item is a calculated-dimension filter that still needs its result | ||
| * data type resolved (a `filter` present, but no top-level `datatype`). | ||
| * Collects the calculated-dimension JAQL nodes within a metadata item that still need their result | ||
| * data type resolved: | ||
| * - the item's own element, when it drives a top-level `filter` OR carries an embedded highlight | ||
| * (`jaql.in.selected`) — the engine reads the datatype off this element in both cases; | ||
| * - the embedded highlight node itself (`jaql.in.selected.jaql`). | ||
| * | ||
| * @param metadataItem - The metadata item to test. | ||
| * @param metadataItem - The metadata item to inspect. | ||
| * @returns The calculated-dimension JAQL nodes (0-2) within the item that still lack a `datatype`. | ||
| * @internal | ||
| */ | ||
| function isUnresolvedCalculatedDimensionFilter(metadataItem) { | ||
| var _a; | ||
| return (((_a = metadataItem.jaql) === null || _a === void 0 ? void 0 : _a.type) === CALCULATED_DIMENSION_JAQL_TYPE && | ||
| function collectUnresolvedCalculatedDimensionJaqls(metadataItem) { | ||
| var _a, _b; | ||
| const jaql = metadataItem.jaql; | ||
| const highlightJaql = (_b = (_a = jaql === null || jaql === void 0 ? void 0 : jaql.in) === null || _a === void 0 ? void 0 : _a.selected) === null || _b === void 0 ? void 0 : _b.jaql; | ||
| const cdJaqls = []; | ||
| // The element drives a filter/highlight when it either owns a top-level filter or carries an | ||
| // embedded highlight. A calculated dimension has no `dim`, so it must carry its own `datatype`. | ||
| const drivesFilterOrHighlight = Boolean(jaql === null || jaql === void 0 ? void 0 : jaql.filter) || Boolean(highlightJaql === null || highlightJaql === void 0 ? void 0 : highlightJaql.filter); | ||
| if (drivesFilterOrHighlight && needsCalculatedDimensionDatatype(jaql)) { | ||
| cdJaqls.push(jaql); | ||
| } | ||
| if (Boolean(highlightJaql === null || highlightJaql === void 0 ? void 0 : highlightJaql.filter) && needsCalculatedDimensionDatatype(highlightJaql)) { | ||
| cdJaqls.push(highlightJaql); | ||
| } | ||
| return cdJaqls; | ||
| } | ||
| /** | ||
| * Returns whether a JAQL node is a calculated dimension that still needs its result data type | ||
| * resolved (a calculated dimension with a `formula` but no `datatype`). | ||
| * | ||
| * @param jaql - The JAQL node to test. | ||
| * @returns `true` when the node is a calculated dimension with a `formula` and no `datatype`. | ||
| * @internal | ||
| */ | ||
| function needsCalculatedDimensionDatatype(jaql) { | ||
| return ((jaql === null || jaql === void 0 ? void 0 : jaql.type) === CALCULATED_DIMENSION_JAQL_TYPE && | ||
| // presence, not truthiness: an empty formula must still reach the parser to surface the | ||
| // server's invalid-formula error rather than being silently skipped | ||
| metadataItem.jaql.formula !== undefined && | ||
| Boolean(metadataItem.jaql.filter) && | ||
| !metadataItem.jaql.datatype); | ||
| jaql.formula !== undefined && | ||
| !jaql.datatype); | ||
| } |
| import { Filter, MetadataItem } from '@sisense/sdk-data'; | ||
| export declare function applyHighlightFilters(metadataItem: MetadataItem, highlights: Filter[]): MetadataItem; | ||
| export declare function matchHighlightsWithAttributes(attributesMetadata: MetadataItem[], highlights: Filter[]): Filter[][]; | ||
| /** | ||
| * Builds the identity key used to match a highlight filter to a query dimension. | ||
| * | ||
| * Keyed on `dim` for a regular attribute. A calculated dimension has no `dim`, so its `formula` is | ||
| * used instead (matching `DimensionalCalculatedAttribute.id`, which returns the formula). Without | ||
| * this fallback a calculated-dimension highlight never matches its query dimension and would be | ||
| * applied as a standalone slice filter instead of being embedded as a highlight on the dimension. | ||
| * | ||
| * @param metadataItem - The dimension or filter metadata item to derive an identity from | ||
| * @returns The identity key: `dim` (or `formula` for a calculated dimension), suffixed with the | ||
| * level and bucket when present. | ||
| * @internal | ||
| */ | ||
| export declare function getMetadataItemId(metadataItem: MetadataItem): string; |
@@ -32,5 +32,18 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ | ||
| } | ||
| /** | ||
| * Builds the identity key used to match a highlight filter to a query dimension. | ||
| * | ||
| * Keyed on `dim` for a regular attribute. A calculated dimension has no `dim`, so its `formula` is | ||
| * used instead (matching `DimensionalCalculatedAttribute.id`, which returns the formula). Without | ||
| * this fallback a calculated-dimension highlight never matches its query dimension and would be | ||
| * applied as a standalone slice filter instead of being embedded as a highlight on the dimension. | ||
| * | ||
| * @param metadataItem - The dimension or filter metadata item to derive an identity from | ||
| * @returns The identity key: `dim` (or `formula` for a calculated dimension), suffixed with the | ||
| * level and bucket when present. | ||
| * @internal | ||
| */ | ||
| export function getMetadataItemId(metadataItem) { | ||
| const { dim, level, dateTimeLevel, bucket } = metadataItem.jaql; | ||
| let id = `${dim}`; | ||
| const { dim, formula, level, dateTimeLevel, bucket } = metadataItem.jaql; | ||
| let id = `${dim !== null && dim !== void 0 ? dim : formula}`; | ||
| if (level || dateTimeLevel) { | ||
@@ -37,0 +50,0 @@ id += `_${level || dateTimeLevel}`; |
| import { DataSource, DataSourceField, DataSourceMetadata, DataSourceSchema } from '@sisense/sdk-data'; | ||
| import { HttpClient } from '@sisense/sdk-rest-client'; | ||
| import { CalculatedDimensionParseResponse, CountRowsResponse, JaqlQueryPayload, JaqlResponse, QueryGuid } from '../types.js'; | ||
| import { GetDataSourceFieldsOptions } from './types.js'; | ||
| import { DisplayNameConfig, GetDataSourceFieldsOptions } from './types.js'; | ||
| /** | ||
@@ -16,7 +16,9 @@ * Thin HttpClient-backed datasource/query REST dispatcher. | ||
| * | ||
| * Endpoint choice depends on system display-name settings (see options.displayNameConfig): | ||
| * Path and endpoint selection: | ||
| * - When display names are enabled *and* `useNewSearchByDisplayNameApi` is true, | ||
| * uses `POST …/fields/searchByDisplayName?isLive=` so search matches display titles | ||
| * and the BE routes live vs ElastiCube correctly via `isLive`. | ||
| * - Otherwise uses `POST …/fields/search` (viewer-safe field listing used by data browser). | ||
| * uses `POST …/fields/searchByDisplayName?isLive=` with the datasource **title** | ||
| * (not fullname) so search matches display titles and the BE routes live vs EC via `isLive`. | ||
| * - Otherwise uses `POST …/fields/search` with the datasource **fullname** when available | ||
| * (from `DataSourceInfo` or the viewer-safe list), encoding path segments while | ||
| * preserving `/` — required for EC, EC perspectives, live, and live perspectives. | ||
| * | ||
@@ -101,1 +103,12 @@ * @param dataSource - Datasource title or info object | ||
| } | ||
| /** | ||
| * Determines whether to use the display-name field search endpoint. | ||
| * Both flags must be true — matching system settings defaults | ||
| * (`displayNameConfig.enabled` can be true while `useNewSearchByDisplayNameApi` is still false). | ||
| * | ||
| * @param displayNameConfig - Optional display-name configuration from system settings. | ||
| * @returns `true` when both `enabled` and `useNewSearchByDisplayNameApi` are true, | ||
| * selecting the new search-by-display-name API; otherwise `false`. | ||
| * @internal | ||
| */ | ||
| export declare function shouldUseSearchByDisplayName(displayNameConfig?: DisplayNameConfig): boolean; |
@@ -10,3 +10,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
| }; | ||
| import { getDataSourceName, } from '@sisense/sdk-data'; | ||
| import { encodeDataSourcePath, getDataSourceFullName, getDataSourceName, isDataSourceInfo, } from '@sisense/sdk-data'; | ||
| import { TranslatableError } from '../translation/translatable-error.js'; | ||
@@ -27,7 +27,9 @@ const API_DATASOURCES_BASE_PATH = 'api/datasources'; | ||
| * | ||
| * Endpoint choice depends on system display-name settings (see options.displayNameConfig): | ||
| * Path and endpoint selection: | ||
| * - When display names are enabled *and* `useNewSearchByDisplayNameApi` is true, | ||
| * uses `POST …/fields/searchByDisplayName?isLive=` so search matches display titles | ||
| * and the BE routes live vs ElastiCube correctly via `isLive`. | ||
| * - Otherwise uses `POST …/fields/search` (viewer-safe field listing used by data browser). | ||
| * uses `POST …/fields/searchByDisplayName?isLive=` with the datasource **title** | ||
| * (not fullname) so search matches display titles and the BE routes live vs EC via `isLive`. | ||
| * - Otherwise uses `POST …/fields/search` with the datasource **fullname** when available | ||
| * (from `DataSourceInfo` or the viewer-safe list), encoding path segments while | ||
| * preserving `/` — required for EC, EC perspectives, live, and live perspectives. | ||
| * | ||
@@ -39,16 +41,25 @@ * @param dataSource - Datasource title or info object | ||
| return __awaiter(this, arguments, void 0, function* (dataSource, options = {}) { | ||
| var _a, _b; | ||
| var _a, _b, _c; | ||
| const { count = 9999, offset = 0, term, displayNameConfig } = options; | ||
| const dataSourceName = getDataSourceName(dataSource); | ||
| const title = getDataSourceName(dataSource); | ||
| const providedFullName = getDataSourceFullName(dataSource); | ||
| const useDisplayNameSearch = shouldUseSearchByDisplayName(displayNameConfig); | ||
| // Display-name path needs `live`; legacy search needs `fullname`. Skip the list when we already have it. | ||
| const needsListLookup = useDisplayNameSearch | ||
| ? options.live === undefined | ||
| : providedFullName === undefined; | ||
| const metadata = needsListLookup ? yield this.getDataSourceByTitle(title) : null; | ||
| // Fusion: searchByDisplayName → title; fields/search → fullname. | ||
| const pathId = useDisplayNameSearch ? title : (_a = providedFullName !== null && providedFullName !== void 0 ? providedFullName : metadata === null || metadata === void 0 ? void 0 : metadata.fullname) !== null && _a !== void 0 ? _a : title; | ||
| const encodedPath = encodeDataSourcePath(pathId); | ||
| let url; | ||
| if (useDisplayNameSearch) { | ||
| // `isLive` is required by the BE on this path to pick live vs EC field handlers. | ||
| // Prefer an explicit option; otherwise resolve from the viewer-safe list endpoint | ||
| // Prefer an explicit option; otherwise list metadata; then caller DataSourceInfo.type | ||
| // (do not GET api/datasources/{title} — that requires manage/viewschema and fails for viewers). | ||
| const live = (_a = options.live) !== null && _a !== void 0 ? _a : ((_b = (yield this.getDataSourceByTitle(dataSourceName))) === null || _b === void 0 ? void 0 : _b.live) === true; | ||
| url = `${API_DATASOURCES_BASE_PATH}/${encodeURIComponent(dataSourceName)}/fields/searchByDisplayName?isLive=${live}`; | ||
| const live = (_c = (_b = options.live) !== null && _b !== void 0 ? _b : metadata === null || metadata === void 0 ? void 0 : metadata.live) !== null && _c !== void 0 ? _c : (isDataSourceInfo(dataSource) && dataSource.type === 'live'); | ||
| url = `${API_DATASOURCES_BASE_PATH}/${encodedPath}/fields/searchByDisplayName?isLive=${live}`; | ||
| } | ||
| else { | ||
| url = `${API_DATASOURCES_BASE_PATH}/${encodeURIComponent(dataSourceName)}/fields/search`; | ||
| url = `${API_DATASOURCES_BASE_PATH}/${encodedPath}/fields/search`; | ||
| } | ||
@@ -117,5 +128,6 @@ const body = { offset, count }; | ||
| return __awaiter(this, void 0, void 0, function* () { | ||
| var _a, _b; | ||
| const list = (_a = (yield this.getDataSourceList())) !== null && _a !== void 0 ? _a : []; | ||
| return (_b = list.find((ds) => ds.title === title)) !== null && _b !== void 0 ? _b : null; | ||
| var _a; | ||
| const list = yield this.getDataSourceList(); | ||
| const items = Array.isArray(list) ? list : []; | ||
| return (_a = items.find((ds) => ds.title === title)) !== null && _a !== void 0 ? _a : null; | ||
| }); | ||
@@ -218,5 +230,8 @@ } | ||
| * | ||
| * @param displayNameConfig - Optional display-name configuration from system settings. | ||
| * @returns `true` when both `enabled` and `useNewSearchByDisplayNameApi` are true, | ||
| * selecting the new search-by-display-name API; otherwise `false`. | ||
| * @internal | ||
| */ | ||
| function shouldUseSearchByDisplayName(displayNameConfig) { | ||
| export function shouldUseSearchByDisplayName(displayNameConfig) { | ||
| return Boolean((displayNameConfig === null || displayNameConfig === void 0 ? void 0 : displayNameConfig.enabled) && (displayNameConfig === null || displayNameConfig === void 0 ? void 0 : displayNameConfig.useNewSearchByDisplayNameApi)); | ||
@@ -223,0 +238,0 @@ } |
| import { Cell, DataCell, Element, QueryResultData } from '@sisense/sdk-data'; | ||
| import { JaqlResponse } from '../types.js'; | ||
| export declare const getDataFromQueryResult: (result: JaqlResponse, metadata: Element[]) => QueryResultData; | ||
| export declare function prepareResultAsColsAndRows(data: DataCell[][], metadata: Element[]): QueryResultData; | ||
| /** | ||
| * The subset of an {@link Element} needed to describe a result column. | ||
| * | ||
| * Query metadata is normally made of full model elements, but columns appended by advanced | ||
| * analytics have no element behind them, so only these three fields are ever available. | ||
| */ | ||
| export type ResultColumnMetadata = Pick<Element, 'name' | 'title' | 'type'>; | ||
| export declare const getDataFromQueryResult: (result: JaqlResponse, metadata: readonly ResultColumnMetadata[]) => QueryResultData; | ||
| export declare function prepareResultAsColsAndRows(data: DataCell[][], metadata: readonly ResultColumnMetadata[]): QueryResultData; | ||
| /** | ||
| * Sets the `blur` property for each cell in a 2D array of data cells based on the `selected` property. | ||
@@ -7,0 +14,0 @@ * |
@@ -11,2 +11,6 @@ import { simpleColumnType } from '@sisense/sdk-data'; | ||
| name: d.name, | ||
| // `name` carries the element identity (the physical column), so the display label has to | ||
| // be surfaced separately for consumers rendering result headers. Model elements always | ||
| // resolve `title`, defaulting to `name`. | ||
| title: d.title, | ||
| type: simpleColumnType(d.type), | ||
@@ -13,0 +17,0 @@ })), |
@@ -15,3 +15,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
| import { QUERY_DEFAULT_LIMIT } from '../query-client.js'; | ||
| import { getDataFromQueryResult } from '../query-result/index.js'; | ||
| import { getDataFromQueryResult, } from '../query-result/get-data-from-query-result.js'; | ||
| import { TranslatableError } from '../translation/translatable-error.js'; | ||
@@ -86,6 +86,8 @@ export class QueryTaskManager extends AbstractTaskManager { | ||
| const metadata = [...queryDescription.attributes, ...queryDescription.measures]; | ||
| // extra columns are assumed to have been added by advanced analytics functions | ||
| // extra columns are assumed to have been added by advanced analytics functions. | ||
| // They have no model element behind them, so the response header doubles as both | ||
| // identity and display label. | ||
| const extraColumns = (jaqlResponse.headers || []) | ||
| .slice(metadata.length) | ||
| .map((c) => ({ name: c, type: 'number' })); | ||
| .map((c) => ({ name: c, title: c, type: 'number' })); | ||
| return getDataFromQueryResult(jaqlResponse, [...metadata, ...extraColumns]); | ||
@@ -92,0 +94,0 @@ }); |
+5
-5
@@ -14,3 +14,3 @@ { | ||
| ], | ||
| "version": "2.32.0", | ||
| "version": "2.33.0", | ||
| "type": "module", | ||
@@ -42,6 +42,6 @@ "exports": { | ||
| "dependencies": { | ||
| "@sisense/sdk-common": "2.32.0", | ||
| "@sisense/sdk-data": "2.32.0", | ||
| "@sisense/sdk-pivot-query-client": "2.32.0", | ||
| "@sisense/sdk-rest-client": "2.32.0", | ||
| "@sisense/sdk-common": "2.33.0", | ||
| "@sisense/sdk-data": "2.33.0", | ||
| "@sisense/sdk-pivot-query-client": "2.33.0", | ||
| "@sisense/sdk-rest-client": "2.33.0", | ||
| "@sisense/task-manager": "^0.1.0", | ||
@@ -48,0 +48,0 @@ "numeral": "^2.0.6", |
Sorry, the diff of this file is not supported yet
313681
5.19%4957
5.09%+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
Updated
Updated