From de46551dbd35185c7878021485156fd2e11439bd Mon Sep 17 00:00:00 2001 From: ZitRos Date: Wed, 3 Dec 2014 22:32:50 +0200 Subject: [PATCH] better MDX parser, code optimizations, refactoring --- gulpfile.js | 13 +++- package.json | 4 +- source/js/DataController.js | 2 - source/js/DataSource.js | 2 +- source/js/LightPivotTable.js | 43 +++++++----- source/js/MDXParser.js | 126 +++++++++++++++-------------------- source/js/PivotView.js | 11 +-- 7 files changed, 101 insertions(+), 100 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 06e744b..0ea2141 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -6,7 +6,17 @@ var gulp = require("gulp"), wrap = require("gulp-wrap"), minifyCSS = require("gulp-minify-css"), htmlReplace = require("gulp-html-replace"), - replace = require('gulp-replace'); + header = require("gulp-header"), + replace = require("gulp-replace"), + pkg = require("./package.json"); + +var banner = [ + "/** <%= pkg.name %>: <%= pkg.description %>", + " ** @author <%= pkg.author %>", + " ** @version <%= pkg.version %>", + " **/", + "" +].join("\n"); gulp.task("clean", function () { return gulp.src("build", {read: false}) @@ -22,6 +32,7 @@ gulp.task("gatherScripts", ["clean"], function () { ascii_only: true } })) + .pipe(header(banner, { pkg: pkg })) .pipe(gulp.dest("build/js/")); }); diff --git a/package.json b/package.json index 9c27c45..b1545a1 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "LightPivotTable", "author": "ZitRo", - "version": "0.5.2", - "description": "Light pivot table for MDX2JSON source for InterSystems Cache", + "version": "0.6.0", + "description": "A lightweight pivot table for MDX2JSON source for InterSystems Cache", "main": "test/testServer.js", "directories": { "test": "test" diff --git a/source/js/DataController.js b/source/js/DataController.js index aae7fca..e799ac6 100644 --- a/source/js/DataController.js +++ b/source/js/DataController.js @@ -282,8 +282,6 @@ DataController.prototype.sortByColumn = function (columnIndex) { .concat(newRawData) .concat(this.SUMMARY_SHOWN ? [data._rawDataOrigin[data._rawDataOrigin.length - 1]] : []); - console.log(data.rawData); - this._trigger(); }; \ No newline at end of file diff --git a/source/js/DataSource.js b/source/js/DataSource.js index f9b0e65..faddb65 100644 --- a/source/js/DataSource.js +++ b/source/js/DataSource.js @@ -109,7 +109,7 @@ DataSource.prototype.getCurrentData = function (callback) { mdx = mdxParser.applyFilter(mdx, this.FILTERS[i]); } - console.log("MDX: " + mdx); + console.log("Request MDX: " + mdx); this._post(this.SOURCE_URL + "/" + this.ACTION, { MDX: mdx diff --git a/source/js/LightPivotTable.js b/source/js/LightPivotTable.js index a2fa40c..afec82a 100644 --- a/source/js/LightPivotTable.js +++ b/source/js/LightPivotTable.js @@ -1,3 +1,9 @@ +/** + * Light pivot table global object. + * + * @param {object} configuration + * @constructor + */ var LightPivotTable = function (configuration) { var _ = this; @@ -16,7 +22,7 @@ var LightPivotTable = function (configuration) { * @type {DataController} */ this.dataController = new DataController(this, function () { - _.dataChangeTrigger.call(_); + _.dataIsChanged.call(_); }); this.init(); @@ -70,6 +76,10 @@ LightPivotTable.prototype.clearFilters = function () { }; +/** + * @param {object} config - part of dataSource configuration. Usually a part of config given to LPT. + * @returns {DataSource} + */ LightPivotTable.prototype.pushDataSource = function (config) { var newDataSource; @@ -92,12 +102,20 @@ LightPivotTable.prototype.popDataSource = function () { }; -LightPivotTable.prototype.dataChangeTrigger = function () { +/** + * Data change handler. + */ +LightPivotTable.prototype.dataIsChanged = function () { this.pivotView.renderRawData(this.dataController.getData().rawData); }; +/** + * Try to DrillDown with given filter. + * + * @param {string} filter + */ LightPivotTable.prototype.tryDrillDown = function (filter) { var _ = this, @@ -108,8 +126,8 @@ LightPivotTable.prototype.tryDrillDown = function (filter) { for (var i in _.CONFIG.dataSource) { ds[i] = _.CONFIG.dataSource[i]; } if (this.CONFIG.DrillDownExpression && this._dataSourcesStack.length < 2) { - ds.basicMDX = this.mdxParser.customDrillDown( - this.dataSource.BASIC_MDX, this.CONFIG.DrillDownExpression, filter + ds.basicMDX = this.mdxParser.drillDown( + this.dataSource.BASIC_MDX, filter, this.CONFIG.DrillDownExpression ) || this.dataSource.BASIC_MDX; } else { ds.basicMDX = this.mdxParser.drillDown(this.dataSource.BASIC_MDX, filter) || this.dataSource.BASIC_MDX; @@ -134,9 +152,11 @@ LightPivotTable.prototype.tryDrillDown = function (filter) { }; /** + * Try to DrillThrough with given filters. + * * @param {string[]} [filters] */ -LightPivotTable.prototype.showDrillThrough = function (filters) { +LightPivotTable.prototype.tryDrillThrough = function (filters) { var _ = this, oldDataSource, @@ -145,19 +165,12 @@ LightPivotTable.prototype.showDrillThrough = function (filters) { // clone dataSource config object for (var i in _.CONFIG.dataSource) { ds[i] = _.CONFIG.dataSource[i]; } ds.action = "MDXDrillthrough"; - if (filters instanceof Array) { - console.log("BASIC MDX: " + this.dataSource.BASIC_MDX, "\n\nFILTERS: " + filters); - ds.basicMDX = this.mdxParser.customDrillThrough(this.dataSource.BASIC_MDX, filters) - || this.dataSource.basicMDX; - } else { - ds.basicMDX = this.dataSource.BASIC_MDX; - ds.basicMDX = this.mdxParser.drillThrough(ds.basicMDX) || ds.basicMDX; - } - oldDataSource = this.dataSource; + ds.basicMDX = this.mdxParser.drillThrough(this.dataSource.BASIC_MDX, filters) + || this.dataSource.basicMDX; + oldDataSource = this.dataSource; this.pushDataSource(ds); - this.dataSource.FILTERS = oldDataSource.FILTERS; this.dataSource.getCurrentData(function (data) { diff --git a/source/js/MDXParser.js b/source/js/MDXParser.js index 77f74e0..d0661eb 100644 --- a/source/js/MDXParser.js +++ b/source/js/MDXParser.js @@ -5,114 +5,92 @@ var MDXParser = function () { }; /** - * Performs DrillDown on MDX query. + * Debug method. * - * @param {string} basicMDX - * @param {string} filter - * @returns {string} - new query. + * @param {string} mdx + * @param {string} [message] + * @private */ -MDXParser.prototype.drillDown = function (basicMDX, filter) { - - try { - - var filterParts = filter.split(/(\(?)([^\)]*)(\)?)/), - clearFilter = filterParts[2], - parts = basicMDX.split(/(\s+ON\s+0,\s*)(.*)(\s+ON\s+1\s*)/i), - oldPath = parts[2].split(/(\(?)(\[[^\(^\)]*)(\)?)/); - - oldPath[2] = clearFilter + ".children"; - parts[2] = oldPath.join(""); - - //console.log("\n\nIN: "+basicMDX+"\n\nFILTER: " + filter + "\n\nCUSTOM: "+ parts.join("") - // + " %FILTER " + filterParts.join("")); - - return parts.join("") + " %FILTER " + filterParts.join(""); - - } catch (e) { - - console.error("Unable to get DrillDown statement from", basicMDX, "with filter", filter); - return ""; - - } - +MDXParser.prototype._warnMDX = function (mdx, message) { + console.warn("MDX is not parsed:\n\n%s\n\n" + (message ? "(" + message + ")" : ""), mdx); }; /** - * Replace dimension [1] with expression. + * Converts filter to setExpression that can be inserted to MDX. * - * @param {string} basicMDX - * @param {string} expression - * @param {string} [filter] - * @returns {string} + * @param filterSpec */ -MDXParser.prototype.customDrillDown = function (basicMDX, expression, filter) { - - try { - - var parts = basicMDX.split(/(\s+ON\s+0,\s*)(.*)(\s+ON\s+1\s*)/i); - - parts[2] = expression; - - if (filter) parts.push(" %FILTER " + filter); - - //console.log("\n\nIN: "+basicMDX+"\n\nEXPR: " + expression + "\n\nFILTER: " - // + filter + "\n\nCUSTOM: " + parts.join("")); - - return parts.join(""); - - } catch (e) { - - console.error("Unable to get DrillDown statement from", basicMDX, "by", expression, - "with filter", filter); - return ""; - +MDXParser.prototype.makeSetExpressionFromFilter = function (filterSpec) { + if (filterSpec.match(/^\([^\),]*,[^\)]*\)$/)) { + return "NONEMPTYCROSSJOIN" + filterSpec.slice(0, filterSpec.length - 1) + ".children)"; + } else { + return filterSpec + ".children"; } - }; /** - * Returns DrillThrough query for given MDX query. + * Performs DrillDown on MDX query. * - * @param {string} basicMDX - * @returns {string} + * @param {string} mdx + * @param {string} filter + * @param {string} [expression] - if is set, "* ON 1" will be replaced with "{value} ON 1" + * @returns {string} - new query. */ -MDXParser.prototype.drillThrough = function (basicMDX) { +MDXParser.prototype.drillDown = function (mdx, filter, expression) { - try { + if (!filter) { + this._warnMDX(mdx, "no filter specified"); + return ""; + } - var statement = ["DRILLTHROUGH SELECT "] - .concat(basicMDX.split(/(\s+ON\s+0,\s*)(.*)(\s+ON\s+1\s*)/i).slice(2)).join(""); + var parts = mdx.split(/(select)(.*?)(from)/ig); // split by SELECT queries - console.log("DRILLTHROUGH STATEMENT:", statement); + if (parts.length < 4) { + this._warnMDX(mdx); + return ""; // no select query matched + } - return statement === "DRILLTHROUGH SELECT " ? "" : statement; + var selectBody = parts[parts.length - 3], + dimensions = selectBody.split(/(\s*ON\s*[01]\s*,?\s*)/); - } catch (e) { + if (dimensions.length < 2) { + this._warnMDX(mdx); + return ""; // no dimensions matched + } - console.error("Unable to get DrillThrough statement from", basicMDX); - return ""; + var index = -1; + dimensions.map(function(e,i){if(e.match(/\s*ON\s*[01]\s*,?\s*/)) index=i-1; return e;}); + if (index === -1) { + this._warnMDX(mdx, "DrillDown is impossible"); + return ""; // DrillDown is impossible (no "1" dimension) } + dimensions[index] = expression || this.makeSetExpressionFromFilter(filter); + for (var i in dimensions) { + if (dimensions[i].length === 1) { // "0" || "1" + dimensions[i](parseInt(i), 1); + } + } + parts[parts.length - 3] = dimensions.join(""); + + return this.applyFilter(parts.join(""), filter); + }; /** * @param {string} basicMDX - * @param {string[]} filters + * @param {string[]} [filters] */ -MDXParser.prototype.customDrillThrough = function (basicMDX, filters) { +MDXParser.prototype.drillThrough = function (basicMDX, filters) { var cubeAndFilters = basicMDX.split(/(FROM\s*\[[^\]]*].*)/i)[1], query = "DRILLTHROUGH SELECT " + cubeAndFilters; - if (!(filters instanceof Array)) filters = [filters]; - for (var i in filters) { - query += " %FILTER " + filters[i]; + query = this.applyFilter(query, filters[i]); } - console.log("CUSTOM DRILLTHROUGH STATEMENT: " + query); - return query; }; diff --git a/source/js/PivotView.js b/source/js/PivotView.js index 46036fa..c54bba3 100644 --- a/source/js/PivotView.js +++ b/source/js/PivotView.js @@ -154,7 +154,7 @@ PivotView.prototype._backClickHandler = function (event) { PivotView.prototype._drillThroughClickHandler = function (event) { - this.controller.showDrillThrough(); + this.controller.tryDrillThrough(); event.cancelBubble = true; event.stopPropagation(); @@ -164,7 +164,7 @@ PivotView.prototype._drillThroughClickHandler = function (event) { PivotView.prototype._cellClickHandler = function (x, y) { var data = this.controller.dataController.getData(), - f1, f2; + f = [], f1, f2; try { f1 = data.rawData[y][data.info.leftHeaderColumnsNumber - 1].source.path; @@ -173,14 +173,15 @@ PivotView.prototype._cellClickHandler = function (x, y) { console.warn("Unable to get filters for cell (%d, %d)", x, y); } - if (!f1) return; + if (f1) f.push(f1); + if (f2) f.push(f2); if (this.controller.CONFIG["drillDownTarget"]) { window.location = location.origin + location.pathname + "?DASHBOARD=" + encodeURIComponent(this.controller.CONFIG["drillDownTarget"]) + "&SETTINGS=FILTER:" - + encodeURIComponent(f1 + "~" + f2) + ";"; + + encodeURIComponent(f.join("~")) + ";"; } else { - this.controller.showDrillThrough(f2 ? [f1, f2] : [f1]); + this.controller.tryDrillThrough(f); } };