From 0ac9b7a27a76bdfc45a9914bb436fcd3dbd69340 Mon Sep 17 00:00:00 2001 From: "Alex.V" Date: Wed, 24 Jun 2020 21:08:10 +0200 Subject: [PATCH] fix(cubejs-client-core): tableColumns empty data fix (#750) * fix(cubejs-client-core): tableColumns empty data fix * tableColumns fix --- .../dist/cubejs-client-core.esm.js | 280 +++++++++++------- .../dist/cubejs-client-core.js | 280 +++++++++++------- .../dist/cubejs-client-core.umd.js | 280 +++++++++++------- packages/cubejs-client-core/src/ResultSet.js | 51 +++- .../src/tests/table.test.js | 102 +++++++ 5 files changed, 646 insertions(+), 347 deletions(-) diff --git a/packages/cubejs-client-core/dist/cubejs-client-core.esm.js b/packages/cubejs-client-core/dist/cubejs-client-core.esm.js index a8c2734f456a8..1bbbb2100d1d1 100644 --- a/packages/cubejs-client-core/dist/cubejs-client-core.esm.js +++ b/packages/cubejs-client-core/dist/cubejs-client-core.esm.js @@ -48,6 +48,79 @@ import 'core-js/modules/web.url'; import fetch from 'cross-fetch'; import 'url-search-params-polyfill'; +/** + * Configuration object that contains information about pivot axes and other options. + * + * Let's apply `pivotConfig` and see how it affects the axes + * ```js + * // Example query + * { + * measures: ['Orders.count'], + * dimensions: ['Users.country', 'Users.gender'] + * } + * ``` + * If we put the `Users.gender` dimension on **y** axis + * ```js + * resultSet.tablePivot({ + * x: ['Users.country'], + * y: ['Users.gender', 'measures'] + * }) + * ``` + * + * The resulting table will look the following way + * + * | Users Country | male, Orders.count | female, Orders.count | + * | ------------- | ------------------ | -------------------- | + * | Australia | 3 | 27 | + * | Germany | 10 | 12 | + * | US | 5 | 7 | + * + * Now let's put the `Users.country` dimension on **y** axis instead + * ```js + * resultSet.tablePivot({ + * x: ['Users.gender'], + * y: ['Users.country', 'measures'], + * }); + * ``` + * + * in this case the `Users.country` values will be laid out on **y** or **columns** axis + * + * | Users Gender | Australia, Orders.count | Germany, Orders.count | US, Orders.count | + * | ------------ | ----------------------- | --------------------- | ---------------- | + * | male | 3 | 10 | 5 | + * | female | 27 | 12 | 7 | + * + * It's also possible to put the `measures` on **x** axis. + * But in either case it should always be the last item of the array. + * ```js + * resultSet.tablePivot({ + * x: ['Users.gender', 'measures'], + * y: ['Users.country'], + * }); + * ``` + * + * | Users Gender | measures | Australia | Germany | US | + * | ------------ | ------------ | --------- | ------- | --- | + * | male | Orders.count | 3 | 10 | 5 | + * | female | Orders.count | 27 | 12 | 7 | + * + * @memberof ResultSet + * @typedef {Object} PivotConfig Configuration object that contains the information about pivot axes and other options + * @property {Array} x Dimensions to put on **x** or **rows** axis. + * Put `measures` at the end of array here + * @property {Array} y Dimensions to put on **y** or **columns** axis. + * @property {Boolean} [fillMissingDates=true] If `true` missing dates on the time dimensions + * will be filled with `0` for all measures. + * Note: the `fillMissingDates` option set to `true` will override any **order** applied to the query + */ + +/** + * @memberof ResultSet + * @typedef {Object} DrillDownLocator + * @property {Array} xValues + * @property {Array} yValues + */ + var moment = momentRange.extendMoment(Moment); var TIME_SERIES = { day: function day(range) { @@ -106,7 +179,6 @@ function () { * Returns a measure drill down query. * * Provided you have a measure with the defined `drillMemebers` on the `Orders` cube - * * ```js * measures: { * count: { @@ -118,7 +190,6 @@ function () { * ``` * * Then you can use the `drillDown` method to see the rows that contribute to that metric - * * ```js * resultSet.drillDown( * { @@ -132,7 +203,6 @@ function () { * * the result will be a query with the required filters applied and the dimensions/measures filled out * ```js - * * { * measures: ['Orders.count'], * dimensions: ['Orders.status', 'Users.city'], @@ -144,8 +214,8 @@ function () { * ] * } * ``` - * @param {Object} drillDownLocator - expects `{ xValues: [], yValues: [] }` object. - * @param {Object} pivotConfig - See {@link ResultSet#pivot}. + * @param {DrillDownLocator} drillDownLocator + * @param {PivotConfig} [pivotConfig] * @returns {Object|null} Drill down query */ @@ -232,9 +302,8 @@ function () { } /** * Returns an array of series with key, title and series data. - * * ```js - * // For query + * // For the query * { * measures: ['Stories.count'], * timeDimensions: [{ @@ -247,18 +316,18 @@ function () { * // ResultSet.series() will return * [ * { - * "key":"Stories.count", - * "title": "Stories Count", - * "series": [ - * { "x":"2015-01-01T00:00:00", "value": 27120 }, - * { "x":"2015-02-01T00:00:00", "value": 25861 }, - * { "x": "2015-03-01T00:00:00", "value": 29661 }, + * key: 'Stories.count', + * title: 'Stories Count', + * series: [ + * { x: '2015-01-01T00:00:00', value: 27120 }, + * { x: '2015-02-01T00:00:00', value: 25861 }, + * { x: '2015-03-01T00:00:00', value: 29661 }, * //... - * ] - * } + * ], + * }, * ] * ``` - * @param pivotConfig - See {@link ResultSet#pivot}. + * @param {PivotConfig} [pivotConfig] * @returns {Array} */ @@ -430,9 +499,10 @@ function () { } /** * Base method for pivoting {@link ResultSet} data. - * Most of the times shouldn't be used directly and {@link ResultSet#chartPivot} or {@link ResultSet#tablePivot} - * should be used instead. + * Most of the times shouldn't be used directly and {@link ResultSet#chartPivot} + * or {@link ResultSet#tablePivot} should be used instead. * + * You can find the examples of using the `pivotConfig` [here](#pivot-config) * ```js * // For query * { @@ -466,14 +536,7 @@ function () { * } * ] * ``` - * @typedef {Object} PivotConfig - * @property {Array} x Dimensions to put on **x** or **rows** axis. - * Put `measures` at the end of array here - * @property {Array} y Dimensions to put on **y** or **columns** axis. - * @property {Boolean} [fillMissingDates=true] If `true` missing dates on time dimensions - * will be filled with `0` for all measures. - * @param {PivotConfig} [pivotConfig] Configuration object that contains information - * about pivot axes and other options + * @param {PivotConfig} [pivotConfig] * @returns {Array} of pivoted rows. */ @@ -533,7 +596,6 @@ function () { var allYValues = pipe(map( // eslint-disable-next-line no-unused-vars function (_ref14) { var _ref15 = _slicedToArray(_ref14, 2), - xValuesString = _ref15[0], rows = _ref15[1]; return unnest( // collect Y values only from filled rows @@ -589,8 +651,9 @@ function () { /** * Returns normalized query result data in the following format. * + * You can find the examples of using the `pivotConfig` [here](#pivot-config) * ```js - * // For query + * // For the query * { * measures: ['Stories.count'], * timeDimensions: [{ @@ -602,13 +665,13 @@ function () { * * // ResultSet.chartPivot() will return * [ - * { "x":"2015-01-01T00:00:00", "Stories.count": 27120 }, - * { "x":"2015-02-01T00:00:00", "Stories.count": 25861 }, - * { "x": "2015-03-01T00:00:00", "Stories.count": 29661 }, + * { "x":"2015-01-01T00:00:00", "Stories.count": 27120, "xValues": ["2015-01-01T00:00:00"] }, + * { "x":"2015-02-01T00:00:00", "Stories.count": 25861, "xValues": ["2015-02-01T00:00:00"] }, + * { "x":"2015-03-01T00:00:00", "Stories.count": 29661, "xValues": ["2015-03-01T00:00:00"] }, * //... * ] * ``` - * @param {PivotConfig} [pivotConfig] - See {@link ResultSet#pivot}. + * @param {PivotConfig} [pivotConfig] */ }, { @@ -648,10 +711,11 @@ function () { /** * Returns normalized query result data prepared for visualization in the table format. * - * For example + * You can find the examples of using the `pivotConfig` [here](#pivot-config) * + * For example: * ```js - * // For query + * // For the query * { * measures: ['Stories.count'], * timeDimensions: [{ @@ -669,71 +733,7 @@ function () { * //... * ] * ``` - * - * Now let's make use of `pivotConfig` and put the `Users.gender` dimension on * **y** axis. - * - * ```js - * // For example the query is - * { - * measures: ['Orders.count'], - * dimensions: ['Users.country', 'Users.gender'] - * } - * - * resultSet.tablePivot({ - * x: ['Users.country'], - * y: ['Users.gender', 'measures'] - * }) - * - * // then `tablePivot` will return the rows in the following format - * [ - * { - * 'Users.country': 'Australia', - * 'female.Orders.count': 3 - * 'male.Orders.count': 27 - * }, - * // ... - * ] - * ``` - * - * The resulting table will look like this - * - * | Users Country | male | female | - * | ------------- | ---- | ------ | - * | Australia | 3 | 27 | - * | Germany | 10 | 12 | - * | US | 5 | 7 | - * - * If we put the `Users.country` dimension on **y** axis instead - * - * ```js - * resultSet.tablePivot({ - * x: ['Users.gender'], - * y: ['Users.country', 'measures'], - * }); - * ``` - * - * the table will look like - * - * | Users Gender | Australia | Germany | US | - * | ------------ | --------- | ------- | --- | - * | male | 3 | 10 | 5 | - * | female | 27 | 12 | 7 | - * - * It's also possible to put the `measures` on **x** axis - * - * ```js - * resultSet.tablePivot({ - * x: ['Users.gender', 'measures'], - * y: ['Users.country'], - * }); - * ``` - * - * | Users Gender | measures | Australia | Germany | US | - * | ------------ | ------------ | --------- | ------- | --- | - * | male | Orders.count | 3 | 10 | 5 | - * | female | Orders.count | 27 | 12 | 7 | - * - * @param {PivotConfig} [pivotConfig] - See {@link ResultSet#pivot} + * @param {PivotConfig} [pivotConfig] * @returns {Array} of pivoted rows */ @@ -758,8 +758,7 @@ function () { /** * Returns array of column definitions for `tablePivot`. * - * For example - * + * For example: * ```js * // For the query * { @@ -793,8 +792,7 @@ function () { * ] * ``` * - * In case we want to pivot the table - * + * In case we want to pivot the table axes * ```js * // Let's take this query as an example * { @@ -810,7 +808,6 @@ function () { * ``` * * then `tableColumns` will group the table head and return - * * ```js * { * key: 'Germany', @@ -851,7 +848,7 @@ function () { * // ... * ``` * - * @param pivotConfig - See {@link ResultSet#pivot}. + * @param {PivotConfig} [pivotConfig] * @returns {Array} of columns */ @@ -883,7 +880,8 @@ function () { }; }; - this.pivot(normalizedPivotConfig)[0].yValuesArray.forEach(function (_ref30) { + var pivot = this.pivot(normalizedPivotConfig); + (pivot[0] && pivot[0].yValuesArray || []).forEach(function (_ref30) { var _ref31 = _slicedToArray(_ref30, 1), yValues = _ref31[0]; @@ -939,6 +937,18 @@ function () { }); }; + var measureColumns = []; + + if (!pivot.length && normalizedPivotConfig.y.find(function (key) { + return key === 'measures'; + })) { + measureColumns = (this.query().measures || []).map(function (key) { + return _objectSpread2({}, extractFields(key), { + dataIndex: key + }); + }); + } + return normalizedPivotConfig.x.map(function (key) { if (key === 'measures') { return { @@ -953,7 +963,44 @@ function () { return _objectSpread2({}, extractFields(key), { dataIndex: key }); - }).concat(toColumns(schema)); + }).concat(toColumns(schema)).concat(measureColumns); + } + }, { + key: "tableColumns2", + value: function tableColumns2(pivotConfig) { + var _this5 = this; + + var normalizedPivotConfig = this.normalizePivotConfig(pivotConfig); + + var column = function column(field) { + var exractFields = function exractFields() { + var annotation = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var title = annotation.title, + shortTitle = annotation.shortTitle, + format = annotation.format, + type = annotation.type, + meta = annotation.meta; + return { + title: title, + shortTitle: shortTitle, + format: format, + type: type, + meta: meta + }; + }; + + return field === 'measures' ? (_this5.query().measures || []).map(function (key) { + return _objectSpread2({ + key: key + }, exractFields(_this5.loadResponse.annotation.measures[key])); + }) : [_objectSpread2({ + key: field + }, exractFields(_this5.loadResponse.annotation.dimensions[field] || _this5.loadResponse.annotation.timeDimensions[field]))]; + }; + + return normalizedPivotConfig.x.map(column).concat(normalizedPivotConfig.y.map(column)).reduce(function (a, b) { + return a.concat(b); + }); } }, { key: "totalRow", @@ -968,7 +1015,6 @@ function () { } /** * Returns the array of series objects, containing `key` and `title` parameters. - * * ```js * // For query * { @@ -982,25 +1028,29 @@ function () { * * // ResultSet.seriesNames() will return * [ - * { "key":"Stories.count", "title": "Stories Count" } + * { + * key: 'Stories.count', + * title: 'Stories Count', + * yValues: ['Stories.count'], + * }, * ] * ``` - * @param {PivotConfig} [pivotConfig] - See {@link ResultSet#pivot}. + * @param {PivotConfig} [pivotConfig] * @returns {Array} of series names */ }, { key: "seriesNames", value: function seriesNames(pivotConfig) { - var _this5 = this; + var _this6 = this; pivotConfig = this.normalizePivotConfig(pivotConfig); return pipe(map(this.axisValues(pivotConfig.y)), unnest, uniq)(this.timeDimensionBackwardCompatibleData()).map(function (axisValues) { return { - title: _this5.axisValuesString(pivotConfig.y.find(function (d) { + title: _this6.axisValuesString(pivotConfig.y.find(function (d) { return d === 'measures'; - }) ? dropLast(1, axisValues).concat(_this5.loadResponse.annotation.measures[ResultSet.measureFromAxis(axisValues)].title) : axisValues, ', '), - key: _this5.axisValuesString(axisValues), + }) ? dropLast(1, axisValues).concat(_this6.loadResponse.annotation.measures[ResultSet.measureFromAxis(axisValues)].title) : axisValues, ', '), + key: _this6.axisValuesString(axisValues), yValues: axisValues }; }); @@ -1279,7 +1329,7 @@ var HttpTransport = /*#__PURE__*/ function () { /** - * @param options - mandatory options object + * @param {Object} options - mandatory options object * @param options.authorization - [jwt auth token](security) * @param options.apiUrl - path to `/cubejs-api/v1` * @param [options.headers] - object of custom headers diff --git a/packages/cubejs-client-core/dist/cubejs-client-core.js b/packages/cubejs-client-core/dist/cubejs-client-core.js index 14f0b882e75ee..53bd92d7f7fb8 100644 --- a/packages/cubejs-client-core/dist/cubejs-client-core.js +++ b/packages/cubejs-client-core/dist/cubejs-client-core.js @@ -54,6 +54,79 @@ require('core-js/modules/web.url'); var fetch = _interopDefault(require('cross-fetch')); require('url-search-params-polyfill'); +/** + * Configuration object that contains information about pivot axes and other options. + * + * Let's apply `pivotConfig` and see how it affects the axes + * ```js + * // Example query + * { + * measures: ['Orders.count'], + * dimensions: ['Users.country', 'Users.gender'] + * } + * ``` + * If we put the `Users.gender` dimension on **y** axis + * ```js + * resultSet.tablePivot({ + * x: ['Users.country'], + * y: ['Users.gender', 'measures'] + * }) + * ``` + * + * The resulting table will look the following way + * + * | Users Country | male, Orders.count | female, Orders.count | + * | ------------- | ------------------ | -------------------- | + * | Australia | 3 | 27 | + * | Germany | 10 | 12 | + * | US | 5 | 7 | + * + * Now let's put the `Users.country` dimension on **y** axis instead + * ```js + * resultSet.tablePivot({ + * x: ['Users.gender'], + * y: ['Users.country', 'measures'], + * }); + * ``` + * + * in this case the `Users.country` values will be laid out on **y** or **columns** axis + * + * | Users Gender | Australia, Orders.count | Germany, Orders.count | US, Orders.count | + * | ------------ | ----------------------- | --------------------- | ---------------- | + * | male | 3 | 10 | 5 | + * | female | 27 | 12 | 7 | + * + * It's also possible to put the `measures` on **x** axis. + * But in either case it should always be the last item of the array. + * ```js + * resultSet.tablePivot({ + * x: ['Users.gender', 'measures'], + * y: ['Users.country'], + * }); + * ``` + * + * | Users Gender | measures | Australia | Germany | US | + * | ------------ | ------------ | --------- | ------- | --- | + * | male | Orders.count | 3 | 10 | 5 | + * | female | Orders.count | 27 | 12 | 7 | + * + * @memberof ResultSet + * @typedef {Object} PivotConfig Configuration object that contains the information about pivot axes and other options + * @property {Array} x Dimensions to put on **x** or **rows** axis. + * Put `measures` at the end of array here + * @property {Array} y Dimensions to put on **y** or **columns** axis. + * @property {Boolean} [fillMissingDates=true] If `true` missing dates on the time dimensions + * will be filled with `0` for all measures. + * Note: the `fillMissingDates` option set to `true` will override any **order** applied to the query + */ + +/** + * @memberof ResultSet + * @typedef {Object} DrillDownLocator + * @property {Array} xValues + * @property {Array} yValues + */ + var moment = momentRange.extendMoment(Moment); var TIME_SERIES = { day: function day(range) { @@ -112,7 +185,6 @@ function () { * Returns a measure drill down query. * * Provided you have a measure with the defined `drillMemebers` on the `Orders` cube - * * ```js * measures: { * count: { @@ -124,7 +196,6 @@ function () { * ``` * * Then you can use the `drillDown` method to see the rows that contribute to that metric - * * ```js * resultSet.drillDown( * { @@ -138,7 +209,6 @@ function () { * * the result will be a query with the required filters applied and the dimensions/measures filled out * ```js - * * { * measures: ['Orders.count'], * dimensions: ['Orders.status', 'Users.city'], @@ -150,8 +220,8 @@ function () { * ] * } * ``` - * @param {Object} drillDownLocator - expects `{ xValues: [], yValues: [] }` object. - * @param {Object} pivotConfig - See {@link ResultSet#pivot}. + * @param {DrillDownLocator} drillDownLocator + * @param {PivotConfig} [pivotConfig] * @returns {Object|null} Drill down query */ @@ -238,9 +308,8 @@ function () { } /** * Returns an array of series with key, title and series data. - * * ```js - * // For query + * // For the query * { * measures: ['Stories.count'], * timeDimensions: [{ @@ -253,18 +322,18 @@ function () { * // ResultSet.series() will return * [ * { - * "key":"Stories.count", - * "title": "Stories Count", - * "series": [ - * { "x":"2015-01-01T00:00:00", "value": 27120 }, - * { "x":"2015-02-01T00:00:00", "value": 25861 }, - * { "x": "2015-03-01T00:00:00", "value": 29661 }, + * key: 'Stories.count', + * title: 'Stories Count', + * series: [ + * { x: '2015-01-01T00:00:00', value: 27120 }, + * { x: '2015-02-01T00:00:00', value: 25861 }, + * { x: '2015-03-01T00:00:00', value: 29661 }, * //... - * ] - * } + * ], + * }, * ] * ``` - * @param pivotConfig - See {@link ResultSet#pivot}. + * @param {PivotConfig} [pivotConfig] * @returns {Array} */ @@ -436,9 +505,10 @@ function () { } /** * Base method for pivoting {@link ResultSet} data. - * Most of the times shouldn't be used directly and {@link ResultSet#chartPivot} or {@link ResultSet#tablePivot} - * should be used instead. + * Most of the times shouldn't be used directly and {@link ResultSet#chartPivot} + * or {@link ResultSet#tablePivot} should be used instead. * + * You can find the examples of using the `pivotConfig` [here](#pivot-config) * ```js * // For query * { @@ -472,14 +542,7 @@ function () { * } * ] * ``` - * @typedef {Object} PivotConfig - * @property {Array} x Dimensions to put on **x** or **rows** axis. - * Put `measures` at the end of array here - * @property {Array} y Dimensions to put on **y** or **columns** axis. - * @property {Boolean} [fillMissingDates=true] If `true` missing dates on time dimensions - * will be filled with `0` for all measures. - * @param {PivotConfig} [pivotConfig] Configuration object that contains information - * about pivot axes and other options + * @param {PivotConfig} [pivotConfig] * @returns {Array} of pivoted rows. */ @@ -539,7 +602,6 @@ function () { var allYValues = ramda.pipe(ramda.map( // eslint-disable-next-line no-unused-vars function (_ref14) { var _ref15 = _slicedToArray(_ref14, 2), - xValuesString = _ref15[0], rows = _ref15[1]; return ramda.unnest( // collect Y values only from filled rows @@ -595,8 +657,9 @@ function () { /** * Returns normalized query result data in the following format. * + * You can find the examples of using the `pivotConfig` [here](#pivot-config) * ```js - * // For query + * // For the query * { * measures: ['Stories.count'], * timeDimensions: [{ @@ -608,13 +671,13 @@ function () { * * // ResultSet.chartPivot() will return * [ - * { "x":"2015-01-01T00:00:00", "Stories.count": 27120 }, - * { "x":"2015-02-01T00:00:00", "Stories.count": 25861 }, - * { "x": "2015-03-01T00:00:00", "Stories.count": 29661 }, + * { "x":"2015-01-01T00:00:00", "Stories.count": 27120, "xValues": ["2015-01-01T00:00:00"] }, + * { "x":"2015-02-01T00:00:00", "Stories.count": 25861, "xValues": ["2015-02-01T00:00:00"] }, + * { "x":"2015-03-01T00:00:00", "Stories.count": 29661, "xValues": ["2015-03-01T00:00:00"] }, * //... * ] * ``` - * @param {PivotConfig} [pivotConfig] - See {@link ResultSet#pivot}. + * @param {PivotConfig} [pivotConfig] */ }, { @@ -654,10 +717,11 @@ function () { /** * Returns normalized query result data prepared for visualization in the table format. * - * For example + * You can find the examples of using the `pivotConfig` [here](#pivot-config) * + * For example: * ```js - * // For query + * // For the query * { * measures: ['Stories.count'], * timeDimensions: [{ @@ -675,71 +739,7 @@ function () { * //... * ] * ``` - * - * Now let's make use of `pivotConfig` and put the `Users.gender` dimension on * **y** axis. - * - * ```js - * // For example the query is - * { - * measures: ['Orders.count'], - * dimensions: ['Users.country', 'Users.gender'] - * } - * - * resultSet.tablePivot({ - * x: ['Users.country'], - * y: ['Users.gender', 'measures'] - * }) - * - * // then `tablePivot` will return the rows in the following format - * [ - * { - * 'Users.country': 'Australia', - * 'female.Orders.count': 3 - * 'male.Orders.count': 27 - * }, - * // ... - * ] - * ``` - * - * The resulting table will look like this - * - * | Users Country | male | female | - * | ------------- | ---- | ------ | - * | Australia | 3 | 27 | - * | Germany | 10 | 12 | - * | US | 5 | 7 | - * - * If we put the `Users.country` dimension on **y** axis instead - * - * ```js - * resultSet.tablePivot({ - * x: ['Users.gender'], - * y: ['Users.country', 'measures'], - * }); - * ``` - * - * the table will look like - * - * | Users Gender | Australia | Germany | US | - * | ------------ | --------- | ------- | --- | - * | male | 3 | 10 | 5 | - * | female | 27 | 12 | 7 | - * - * It's also possible to put the `measures` on **x** axis - * - * ```js - * resultSet.tablePivot({ - * x: ['Users.gender', 'measures'], - * y: ['Users.country'], - * }); - * ``` - * - * | Users Gender | measures | Australia | Germany | US | - * | ------------ | ------------ | --------- | ------- | --- | - * | male | Orders.count | 3 | 10 | 5 | - * | female | Orders.count | 27 | 12 | 7 | - * - * @param {PivotConfig} [pivotConfig] - See {@link ResultSet#pivot} + * @param {PivotConfig} [pivotConfig] * @returns {Array} of pivoted rows */ @@ -764,8 +764,7 @@ function () { /** * Returns array of column definitions for `tablePivot`. * - * For example - * + * For example: * ```js * // For the query * { @@ -799,8 +798,7 @@ function () { * ] * ``` * - * In case we want to pivot the table - * + * In case we want to pivot the table axes * ```js * // Let's take this query as an example * { @@ -816,7 +814,6 @@ function () { * ``` * * then `tableColumns` will group the table head and return - * * ```js * { * key: 'Germany', @@ -857,7 +854,7 @@ function () { * // ... * ``` * - * @param pivotConfig - See {@link ResultSet#pivot}. + * @param {PivotConfig} [pivotConfig] * @returns {Array} of columns */ @@ -889,7 +886,8 @@ function () { }; }; - this.pivot(normalizedPivotConfig)[0].yValuesArray.forEach(function (_ref30) { + var pivot = this.pivot(normalizedPivotConfig); + (pivot[0] && pivot[0].yValuesArray || []).forEach(function (_ref30) { var _ref31 = _slicedToArray(_ref30, 1), yValues = _ref31[0]; @@ -945,6 +943,18 @@ function () { }); }; + var measureColumns = []; + + if (!pivot.length && normalizedPivotConfig.y.find(function (key) { + return key === 'measures'; + })) { + measureColumns = (this.query().measures || []).map(function (key) { + return _objectSpread2({}, extractFields(key), { + dataIndex: key + }); + }); + } + return normalizedPivotConfig.x.map(function (key) { if (key === 'measures') { return { @@ -959,7 +969,44 @@ function () { return _objectSpread2({}, extractFields(key), { dataIndex: key }); - }).concat(toColumns(schema)); + }).concat(toColumns(schema)).concat(measureColumns); + } + }, { + key: "tableColumns2", + value: function tableColumns2(pivotConfig) { + var _this5 = this; + + var normalizedPivotConfig = this.normalizePivotConfig(pivotConfig); + + var column = function column(field) { + var exractFields = function exractFields() { + var annotation = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var title = annotation.title, + shortTitle = annotation.shortTitle, + format = annotation.format, + type = annotation.type, + meta = annotation.meta; + return { + title: title, + shortTitle: shortTitle, + format: format, + type: type, + meta: meta + }; + }; + + return field === 'measures' ? (_this5.query().measures || []).map(function (key) { + return _objectSpread2({ + key: key + }, exractFields(_this5.loadResponse.annotation.measures[key])); + }) : [_objectSpread2({ + key: field + }, exractFields(_this5.loadResponse.annotation.dimensions[field] || _this5.loadResponse.annotation.timeDimensions[field]))]; + }; + + return normalizedPivotConfig.x.map(column).concat(normalizedPivotConfig.y.map(column)).reduce(function (a, b) { + return a.concat(b); + }); } }, { key: "totalRow", @@ -974,7 +1021,6 @@ function () { } /** * Returns the array of series objects, containing `key` and `title` parameters. - * * ```js * // For query * { @@ -988,25 +1034,29 @@ function () { * * // ResultSet.seriesNames() will return * [ - * { "key":"Stories.count", "title": "Stories Count" } + * { + * key: 'Stories.count', + * title: 'Stories Count', + * yValues: ['Stories.count'], + * }, * ] * ``` - * @param {PivotConfig} [pivotConfig] - See {@link ResultSet#pivot}. + * @param {PivotConfig} [pivotConfig] * @returns {Array} of series names */ }, { key: "seriesNames", value: function seriesNames(pivotConfig) { - var _this5 = this; + var _this6 = this; pivotConfig = this.normalizePivotConfig(pivotConfig); return ramda.pipe(ramda.map(this.axisValues(pivotConfig.y)), ramda.unnest, ramda.uniq)(this.timeDimensionBackwardCompatibleData()).map(function (axisValues) { return { - title: _this5.axisValuesString(pivotConfig.y.find(function (d) { + title: _this6.axisValuesString(pivotConfig.y.find(function (d) { return d === 'measures'; - }) ? ramda.dropLast(1, axisValues).concat(_this5.loadResponse.annotation.measures[ResultSet.measureFromAxis(axisValues)].title) : axisValues, ', '), - key: _this5.axisValuesString(axisValues), + }) ? ramda.dropLast(1, axisValues).concat(_this6.loadResponse.annotation.measures[ResultSet.measureFromAxis(axisValues)].title) : axisValues, ', '), + key: _this6.axisValuesString(axisValues), yValues: axisValues }; }); @@ -1285,7 +1335,7 @@ var HttpTransport = /*#__PURE__*/ function () { /** - * @param options - mandatory options object + * @param {Object} options - mandatory options object * @param options.authorization - [jwt auth token](security) * @param options.apiUrl - path to `/cubejs-api/v1` * @param [options.headers] - object of custom headers diff --git a/packages/cubejs-client-core/dist/cubejs-client-core.umd.js b/packages/cubejs-client-core/dist/cubejs-client-core.umd.js index 5478f0ac8d8fa..98f87f6ac30fa 100644 --- a/packages/cubejs-client-core/dist/cubejs-client-core.umd.js +++ b/packages/cubejs-client-core/dist/cubejs-client-core.umd.js @@ -15795,6 +15795,79 @@ }); var momentRange$1 = unwrapExports(momentRange); + /** + * Configuration object that contains information about pivot axes and other options. + * + * Let's apply `pivotConfig` and see how it affects the axes + * ```js + * // Example query + * { + * measures: ['Orders.count'], + * dimensions: ['Users.country', 'Users.gender'] + * } + * ``` + * If we put the `Users.gender` dimension on **y** axis + * ```js + * resultSet.tablePivot({ + * x: ['Users.country'], + * y: ['Users.gender', 'measures'] + * }) + * ``` + * + * The resulting table will look the following way + * + * | Users Country | male, Orders.count | female, Orders.count | + * | ------------- | ------------------ | -------------------- | + * | Australia | 3 | 27 | + * | Germany | 10 | 12 | + * | US | 5 | 7 | + * + * Now let's put the `Users.country` dimension on **y** axis instead + * ```js + * resultSet.tablePivot({ + * x: ['Users.gender'], + * y: ['Users.country', 'measures'], + * }); + * ``` + * + * in this case the `Users.country` values will be laid out on **y** or **columns** axis + * + * | Users Gender | Australia, Orders.count | Germany, Orders.count | US, Orders.count | + * | ------------ | ----------------------- | --------------------- | ---------------- | + * | male | 3 | 10 | 5 | + * | female | 27 | 12 | 7 | + * + * It's also possible to put the `measures` on **x** axis. + * But in either case it should always be the last item of the array. + * ```js + * resultSet.tablePivot({ + * x: ['Users.gender', 'measures'], + * y: ['Users.country'], + * }); + * ``` + * + * | Users Gender | measures | Australia | Germany | US | + * | ------------ | ------------ | --------- | ------- | --- | + * | male | Orders.count | 3 | 10 | 5 | + * | female | Orders.count | 27 | 12 | 7 | + * + * @memberof ResultSet + * @typedef {Object} PivotConfig Configuration object that contains the information about pivot axes and other options + * @property {Array} x Dimensions to put on **x** or **rows** axis. + * Put `measures` at the end of array here + * @property {Array} y Dimensions to put on **y** or **columns** axis. + * @property {Boolean} [fillMissingDates=true] If `true` missing dates on the time dimensions + * will be filled with `0` for all measures. + * Note: the `fillMissingDates` option set to `true` will override any **order** applied to the query + */ + + /** + * @memberof ResultSet + * @typedef {Object} DrillDownLocator + * @property {Array} xValues + * @property {Array} yValues + */ + var moment$1 = momentRange$1.extendMoment(moment); var TIME_SERIES = { day: function day(range$$1) { @@ -15853,7 +15926,6 @@ * Returns a measure drill down query. * * Provided you have a measure with the defined `drillMemebers` on the `Orders` cube - * * ```js * measures: { * count: { @@ -15865,7 +15937,6 @@ * ``` * * Then you can use the `drillDown` method to see the rows that contribute to that metric - * * ```js * resultSet.drillDown( * { @@ -15879,7 +15950,6 @@ * * the result will be a query with the required filters applied and the dimensions/measures filled out * ```js - * * { * measures: ['Orders.count'], * dimensions: ['Orders.status', 'Users.city'], @@ -15891,8 +15961,8 @@ * ] * } * ``` - * @param {Object} drillDownLocator - expects `{ xValues: [], yValues: [] }` object. - * @param {Object} pivotConfig - See {@link ResultSet#pivot}. + * @param {DrillDownLocator} drillDownLocator + * @param {PivotConfig} [pivotConfig] * @returns {Object|null} Drill down query */ @@ -15979,9 +16049,8 @@ } /** * Returns an array of series with key, title and series data. - * * ```js - * // For query + * // For the query * { * measures: ['Stories.count'], * timeDimensions: [{ @@ -15994,18 +16063,18 @@ * // ResultSet.series() will return * [ * { - * "key":"Stories.count", - * "title": "Stories Count", - * "series": [ - * { "x":"2015-01-01T00:00:00", "value": 27120 }, - * { "x":"2015-02-01T00:00:00", "value": 25861 }, - * { "x": "2015-03-01T00:00:00", "value": 29661 }, + * key: 'Stories.count', + * title: 'Stories Count', + * series: [ + * { x: '2015-01-01T00:00:00', value: 27120 }, + * { x: '2015-02-01T00:00:00', value: 25861 }, + * { x: '2015-03-01T00:00:00', value: 29661 }, * //... - * ] - * } + * ], + * }, * ] * ``` - * @param pivotConfig - See {@link ResultSet#pivot}. + * @param {PivotConfig} [pivotConfig] * @returns {Array} */ @@ -16177,9 +16246,10 @@ } /** * Base method for pivoting {@link ResultSet} data. - * Most of the times shouldn't be used directly and {@link ResultSet#chartPivot} or {@link ResultSet#tablePivot} - * should be used instead. + * Most of the times shouldn't be used directly and {@link ResultSet#chartPivot} + * or {@link ResultSet#tablePivot} should be used instead. * + * You can find the examples of using the `pivotConfig` [here](#pivot-config) * ```js * // For query * { @@ -16213,14 +16283,7 @@ * } * ] * ``` - * @typedef {Object} PivotConfig - * @property {Array} x Dimensions to put on **x** or **rows** axis. - * Put `measures` at the end of array here - * @property {Array} y Dimensions to put on **y** or **columns** axis. - * @property {Boolean} [fillMissingDates=true] If `true` missing dates on time dimensions - * will be filled with `0` for all measures. - * @param {PivotConfig} [pivotConfig] Configuration object that contains information - * about pivot axes and other options + * @param {PivotConfig} [pivotConfig] * @returns {Array} of pivoted rows. */ @@ -16280,7 +16343,6 @@ var allYValues = pipe(map( // eslint-disable-next-line no-unused-vars function (_ref14) { var _ref15 = _slicedToArray(_ref14, 2), - xValuesString = _ref15[0], rows = _ref15[1]; return unnest( // collect Y values only from filled rows @@ -16336,8 +16398,9 @@ /** * Returns normalized query result data in the following format. * + * You can find the examples of using the `pivotConfig` [here](#pivot-config) * ```js - * // For query + * // For the query * { * measures: ['Stories.count'], * timeDimensions: [{ @@ -16349,13 +16412,13 @@ * * // ResultSet.chartPivot() will return * [ - * { "x":"2015-01-01T00:00:00", "Stories.count": 27120 }, - * { "x":"2015-02-01T00:00:00", "Stories.count": 25861 }, - * { "x": "2015-03-01T00:00:00", "Stories.count": 29661 }, + * { "x":"2015-01-01T00:00:00", "Stories.count": 27120, "xValues": ["2015-01-01T00:00:00"] }, + * { "x":"2015-02-01T00:00:00", "Stories.count": 25861, "xValues": ["2015-02-01T00:00:00"] }, + * { "x":"2015-03-01T00:00:00", "Stories.count": 29661, "xValues": ["2015-03-01T00:00:00"] }, * //... * ] * ``` - * @param {PivotConfig} [pivotConfig] - See {@link ResultSet#pivot}. + * @param {PivotConfig} [pivotConfig] */ }, { @@ -16395,10 +16458,11 @@ /** * Returns normalized query result data prepared for visualization in the table format. * - * For example + * You can find the examples of using the `pivotConfig` [here](#pivot-config) * + * For example: * ```js - * // For query + * // For the query * { * measures: ['Stories.count'], * timeDimensions: [{ @@ -16416,71 +16480,7 @@ * //... * ] * ``` - * - * Now let's make use of `pivotConfig` and put the `Users.gender` dimension on * **y** axis. - * - * ```js - * // For example the query is - * { - * measures: ['Orders.count'], - * dimensions: ['Users.country', 'Users.gender'] - * } - * - * resultSet.tablePivot({ - * x: ['Users.country'], - * y: ['Users.gender', 'measures'] - * }) - * - * // then `tablePivot` will return the rows in the following format - * [ - * { - * 'Users.country': 'Australia', - * 'female.Orders.count': 3 - * 'male.Orders.count': 27 - * }, - * // ... - * ] - * ``` - * - * The resulting table will look like this - * - * | Users Country | male | female | - * | ------------- | ---- | ------ | - * | Australia | 3 | 27 | - * | Germany | 10 | 12 | - * | US | 5 | 7 | - * - * If we put the `Users.country` dimension on **y** axis instead - * - * ```js - * resultSet.tablePivot({ - * x: ['Users.gender'], - * y: ['Users.country', 'measures'], - * }); - * ``` - * - * the table will look like - * - * | Users Gender | Australia | Germany | US | - * | ------------ | --------- | ------- | --- | - * | male | 3 | 10 | 5 | - * | female | 27 | 12 | 7 | - * - * It's also possible to put the `measures` on **x** axis - * - * ```js - * resultSet.tablePivot({ - * x: ['Users.gender', 'measures'], - * y: ['Users.country'], - * }); - * ``` - * - * | Users Gender | measures | Australia | Germany | US | - * | ------------ | ------------ | --------- | ------- | --- | - * | male | Orders.count | 3 | 10 | 5 | - * | female | Orders.count | 27 | 12 | 7 | - * - * @param {PivotConfig} [pivotConfig] - See {@link ResultSet#pivot} + * @param {PivotConfig} [pivotConfig] * @returns {Array} of pivoted rows */ @@ -16505,8 +16505,7 @@ /** * Returns array of column definitions for `tablePivot`. * - * For example - * + * For example: * ```js * // For the query * { @@ -16540,8 +16539,7 @@ * ] * ``` * - * In case we want to pivot the table - * + * In case we want to pivot the table axes * ```js * // Let's take this query as an example * { @@ -16557,7 +16555,6 @@ * ``` * * then `tableColumns` will group the table head and return - * * ```js * { * key: 'Germany', @@ -16598,7 +16595,7 @@ * // ... * ``` * - * @param pivotConfig - See {@link ResultSet#pivot}. + * @param {PivotConfig} [pivotConfig] * @returns {Array} of columns */ @@ -16630,7 +16627,8 @@ }; }; - this.pivot(normalizedPivotConfig)[0].yValuesArray.forEach(function (_ref30) { + var pivot = this.pivot(normalizedPivotConfig); + (pivot[0] && pivot[0].yValuesArray || []).forEach(function (_ref30) { var _ref31 = _slicedToArray(_ref30, 1), yValues = _ref31[0]; @@ -16686,6 +16684,18 @@ }); }; + var measureColumns = []; + + if (!pivot.length && normalizedPivotConfig.y.find(function (key) { + return key === 'measures'; + })) { + measureColumns = (this.query().measures || []).map(function (key) { + return _objectSpread({}, extractFields(key), { + dataIndex: key + }); + }); + } + return normalizedPivotConfig.x.map(function (key) { if (key === 'measures') { return { @@ -16700,7 +16710,44 @@ return _objectSpread({}, extractFields(key), { dataIndex: key }); - }).concat(toColumns(schema)); + }).concat(toColumns(schema)).concat(measureColumns); + } + }, { + key: "tableColumns2", + value: function tableColumns2(pivotConfig) { + var _this5 = this; + + var normalizedPivotConfig = this.normalizePivotConfig(pivotConfig); + + var column = function column(field) { + var exractFields = function exractFields() { + var annotation = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var title = annotation.title, + shortTitle = annotation.shortTitle, + format = annotation.format, + type$$1 = annotation.type, + meta = annotation.meta; + return { + title: title, + shortTitle: shortTitle, + format: format, + type: type$$1, + meta: meta + }; + }; + + return field === 'measures' ? (_this5.query().measures || []).map(function (key) { + return _objectSpread({ + key: key + }, exractFields(_this5.loadResponse.annotation.measures[key])); + }) : [_objectSpread({ + key: field + }, exractFields(_this5.loadResponse.annotation.dimensions[field] || _this5.loadResponse.annotation.timeDimensions[field]))]; + }; + + return normalizedPivotConfig.x.map(column).concat(normalizedPivotConfig.y.map(column)).reduce(function (a, b) { + return a.concat(b); + }); } }, { key: "totalRow", @@ -16715,7 +16762,6 @@ } /** * Returns the array of series objects, containing `key` and `title` parameters. - * * ```js * // For query * { @@ -16729,25 +16775,29 @@ * * // ResultSet.seriesNames() will return * [ - * { "key":"Stories.count", "title": "Stories Count" } + * { + * key: 'Stories.count', + * title: 'Stories Count', + * yValues: ['Stories.count'], + * }, * ] * ``` - * @param {PivotConfig} [pivotConfig] - See {@link ResultSet#pivot}. + * @param {PivotConfig} [pivotConfig] * @returns {Array} of series names */ }, { key: "seriesNames", value: function seriesNames(pivotConfig) { - var _this5 = this; + var _this6 = this; pivotConfig = this.normalizePivotConfig(pivotConfig); return pipe(map(this.axisValues(pivotConfig.y)), unnest, uniq)(this.timeDimensionBackwardCompatibleData()).map(function (axisValues) { return { - title: _this5.axisValuesString(pivotConfig.y.find(function (d) { + title: _this6.axisValuesString(pivotConfig.y.find(function (d) { return d === 'measures'; - }) ? dropLast$1(1, axisValues).concat(_this5.loadResponse.annotation.measures[ResultSet.measureFromAxis(axisValues)].title) : axisValues, ', '), - key: _this5.axisValuesString(axisValues), + }) ? dropLast$1(1, axisValues).concat(_this6.loadResponse.annotation.measures[ResultSet.measureFromAxis(axisValues)].title) : axisValues, ', '), + key: _this6.axisValuesString(axisValues), yValues: axisValues }; }); @@ -19537,7 +19587,7 @@ /*#__PURE__*/ function () { /** - * @param options - mandatory options object + * @param {Object} options - mandatory options object * @param options.authorization - [jwt auth token](security) * @param options.apiUrl - path to `/cubejs-api/v1` * @param [options.headers] - object of custom headers diff --git a/packages/cubejs-client-core/src/ResultSet.js b/packages/cubejs-client-core/src/ResultSet.js index 47a71977d0a0b..ee39d652efd5d 100644 --- a/packages/cubejs-client-core/src/ResultSet.js +++ b/packages/cubejs-client-core/src/ResultSet.js @@ -694,7 +694,9 @@ class ResultSet { }; }; - this.pivot(normalizedPivotConfig)[0].yValuesArray.forEach(([yValues]) => { + const pivot = this.pivot(normalizedPivotConfig); + + (pivot[0] && pivot[0].yValuesArray || []).forEach(([yValues]) => { if (yValues.length > 0) { let currentItem = schema; @@ -745,6 +747,11 @@ class ResultSet { }); }; + let measureColumns = []; + if (!pivot.length && normalizedPivotConfig.y.find((key) => key === 'measures')) { + measureColumns = (this.query().measures || []).map((key) => ({ ...extractFields(key), dataIndex: key })); + } + return normalizedPivotConfig.x .map((key) => { if (key === 'measures') { @@ -759,9 +766,49 @@ class ResultSet { return ({ ...extractFields(key), dataIndex: key }); }) - .concat(toColumns(schema)); + .concat(toColumns(schema)) + .concat(measureColumns); } + tableColumns2(pivotConfig) { + const normalizedPivotConfig = this.normalizePivotConfig(pivotConfig); + + const column = (field) => { + const exractFields = (annotation = {}) => { + const { + title, + shortTitle, + format, + type, + meta + } = annotation; + + return { + title, + shortTitle, + format, + type, + meta + }; + }; + + return field === 'measures' ? (this.query().measures || []).map((key) => ({ + key, + ...exractFields(this.loadResponse.annotation.measures[key]) + })) : [ + { + key: field, + ...exractFields(this.loadResponse.annotation.dimensions[field] || + this.loadResponse.annotation.timeDimensions[field]) + }, + ]; + }; + + return normalizedPivotConfig.x.map(column) + .concat(normalizedPivotConfig.y.map(column)) + .reduce((a, b) => a.concat(b)); + } + totalRow() { return this.chartPivot()[0]; } diff --git a/packages/cubejs-client-core/src/tests/table.test.js b/packages/cubejs-client-core/src/tests/table.test.js index 4ed1dc71e19ac..a48bcab3dc89e 100644 --- a/packages/cubejs-client-core/src/tests/table.test.js +++ b/packages/cubejs-client-core/src/tests/table.test.js @@ -527,4 +527,106 @@ describe('resultSet tablePivot and tableColumns', () => { ]); }); }); + + describe('it works with no data', () => { + const resultSet = new ResultSet({ + query: { + measures: ['Orders.count'], + dimensions: ['Users.country', 'Users.gender'], + }, + data: [], + annotation: { + measures: { + 'Orders.count': { + title: 'Orders Count', + shortTitle: 'Count', + type: 'number', + }, + }, + dimensions: { + 'Users.country': { + title: 'Users Country', + shortTitle: 'Country', + type: 'string', + }, + 'Users.gender': { + title: 'Users Gender', + shortTitle: 'Gender', + type: 'string', + }, + }, + segments: {}, + timeDimensions: {}, + }, + }); + + test('all dimensions on `x` axis', () => { + const pivotConfig = { + x: ['Users.country', 'Users.gender'], + y: ['measures'], + }; + + expect(resultSet.tablePivot(pivotConfig)).toEqual([]); + + expect(resultSet.tableColumns(pivotConfig)).toEqual([ + { + dataIndex: 'Users.country', + format: undefined, + key: 'Users.country', + meta: undefined, + shortTitle: 'Country', + title: 'Users Country', + type: 'string', + }, + { + dataIndex: 'Users.gender', + format: undefined, + key: 'Users.gender', + meta: undefined, + shortTitle: 'Gender', + title: 'Users Gender', + type: 'string', + }, + { + dataIndex: 'Orders.count', + format: undefined, + key: 'Orders.count', + meta: undefined, + shortTitle: 'Count', + title: 'Orders Count', + type: 'number', + }, + ]); + }); + + test('one dimension on `y` axis', () => { + const pivotConfig = { + x: ['Users.gender'], + y: ['Users.country', 'measures'], + }; + + expect(resultSet.tablePivot(pivotConfig)).toEqual([]); + + expect(resultSet.tableColumns(pivotConfig)).toEqual([ + { + dataIndex: 'Users.gender', + format: undefined, + key: 'Users.gender', + meta: undefined, + shortTitle: 'Gender', + title: 'Users Gender', + type: 'string', + }, + { + dataIndex: 'Orders.count', + format: undefined, + key: 'Orders.count', + meta: undefined, + shortTitle: 'Count', + title: 'Orders Count', + type: 'number', + }, + ]); + }); + }); });