Repository: ambari Updated Branches: refs/heads/trunk 08763a77f -> 5dbb43e7f
AMBARI-18247 Capacity scheduler's dependent config suggestion is incomprehensible (zhewang) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/5dbb43e7 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/5dbb43e7 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/5dbb43e7 Branch: refs/heads/trunk Commit: 5dbb43e7ff9fedf9b35718a1980ec70fbf7b0cd2 Parents: 08763a7 Author: Zhe (Joe) Wang <[email protected]> Authored: Thu Aug 25 15:00:21 2016 -0700 Committer: Zhe (Joe) Wang <[email protected]> Committed: Thu Aug 25 15:00:21 2016 -0700 ---------------------------------------------------------------------- .../common/configs/config_recommendations.js | 20 +- .../modal_popups/dependent_configs_list.hbs | 29 +- ambari-web/app/utils/config.js | 4 +- ambari-web/config.coffee | 3 + ambari-web/karma.conf.js | 2 + .../configs/config_recommendations_test.js | 13 +- ambari-web/vendor/scripts/difflib.js | 413 +++++++++++++++++++ ambari-web/vendor/scripts/diffview.js | 172 ++++++++ ambari-web/vendor/styles/diffview.css | 84 ++++ 9 files changed, 718 insertions(+), 22 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/5dbb43e7/ambari-web/app/mixins/common/configs/config_recommendations.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mixins/common/configs/config_recommendations.js b/ambari-web/app/mixins/common/configs/config_recommendations.js index 398cf55..c86aebb 100644 --- a/ambari-web/app/mixins/common/configs/config_recommendations.js +++ b/ambari-web/app/mixins/common/configs/config_recommendations.js @@ -96,6 +96,18 @@ App.ConfigRecommendations = Em.Mixin.create({ Em.assert('name and fileName should be defined', name && fileName); var site = App.config.getConfigTagFromFileName(fileName); var service = App.config.get('serviceByConfigTypeMap')[site]; + var trimAndSort = function (value) { + if (value == null) { + return []; + } + var values = value.split("\n").filter(function (item) { + return item != ""; + }).sort().join("\n"); + return difflib.stringAsLines(values); + }; + var initialValues = trimAndSort(initialValue); + var recommendedValues = trimAndSort(recommendedValue); + var recommendation = { saveRecommended: true, saveRecommendedDefault: true, @@ -112,7 +124,13 @@ App.ConfigRecommendations = Em.Mixin.create({ allowChangeGroup: false,//TODO groupName!= "Default" && (service.get('serviceName') != this.get('selectedService.serviceName')) //TODO&& (App.ServiceConfigGroup.find().filterProperty('serviceName', service.get('serviceName')).length > 1), //TODO serviceDisplayName: service.get('displayName'), - recommendedValue: recommendedValue + recommendedValue: recommendedValue, + + diff: new Handlebars.SafeString(diffview.buildView({ + baseTextLines: initialValues, + newTextLines: recommendedValues, + opcodes: new difflib.SequenceMatcher(initialValues, recommendedValues).get_opcodes() + }).outerHTML) }; this.get('recommendations').pushObject(recommendation); return recommendation; http://git-wip-us.apache.org/repos/asf/ambari/blob/5dbb43e7/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs b/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs index 0fe073c..18dbe99 100644 --- a/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs +++ b/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs @@ -19,7 +19,7 @@ <div class="alert alert-warning"> {{t popup.dependent.configs.title}} </div> -<div id="config-dependencies" class="limited-height-2"> +<span id="config-dependencies" class="limited-height-2"> <table class="table table-striped"> <thead> <tr> @@ -28,8 +28,14 @@ <th>{{t common.service}}</th> <th>{{t common.configGroup}}</th> <th>{{t common.fileName}}</th> - <th>{{t popup.dependent.configs.table.currentValue}}</th> - <th>{{t popup.dependent.configs.table.recommendedValue}}</th> + <th class="row-fliud"> + <div class="span6"> + {{t popup.dependent.configs.table.currentValue}} + </div> + <div class="span6"> + {{t popup.dependent.configs.table.recommendedValue}} + </div> + </th> </tr> </thead> <tbody> @@ -45,19 +51,10 @@ </a></span> </td> <td class="config-dependency-filename">{{recommendation.propertyFileName}}</td> - <td class="config-dependency-value"> - {{#if recommendation.notDefined}} - <i>{{t popup.dependent.configs.table.not.defined}}</i> - {{else}} - {{recommendation.initialValue}} - {{/if}} - </td> - <td class="config-dependency-recommended-value"> - {{#if recommendation.isDeleted}} - <i>{{t common.removed}}</i> - {{else}} - {{recommendation.recommendedValue}} - {{/if}} + <td> + <div> + {{recommendation.diff}} + </div> </td> </tr> {{/each}} http://git-wip-us.apache.org/repos/asf/ambari/blob/5dbb43e7/ambari-web/app/utils/config.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/config.js b/ambari-web/app/utils/config.js index 7e1f5b8..ae09b82 100644 --- a/ambari-web/app/utils/config.js +++ b/ambari-web/app/utils/config.js @@ -773,8 +773,8 @@ App.config = Em.Object.create({ return configs.sort(function(a, b) { if (Em.get(a, 'index') > Em.get(b, 'index')) return 1; if (Em.get(a, 'index') < Em.get(b, 'index')) return -1; - if (Em.get(a, 'name') > Em.get(b, 'index')) return 1; - if (Em.get(a, 'name') < Em.get(b, 'index')) return -1; + if (Em.get(a, 'name') > Em.get(b, 'name')) return 1; + if (Em.get(a, 'name') < Em.get(b, 'name')) return -1; return 0; }); }, http://git-wip-us.apache.org/repos/asf/ambari/blob/5dbb43e7/ambari-web/config.coffee ---------------------------------------------------------------------- diff --git a/ambari-web/config.coffee b/ambari-web/config.coffee index 4a29e73..6a3f5ef 100644 --- a/ambari-web/config.coffee +++ b/ambari-web/config.coffee @@ -60,6 +60,8 @@ exports.config = 'vendor/scripts/jquery.sticky-kit.js', 'vendor/scripts/underscore.js', 'vendor/scripts/backbone.js', + 'vendor/scripts/difflib.js', + 'vendor/scripts/diffview.js', 'vendor/scripts/visualsearch.js', 'vendor/scripts/moment.min.js', 'vendor/scripts/moment-timezone-with-data-2010-2020.js', @@ -88,6 +90,7 @@ exports.config = 'vendor/styles/bootstrap-checkbox.css', 'vendor/styles/bootstrap-slider.min.css', 'vendor/styles/bootstrap-switch.min.css', + 'vendor/styles/diffview.css', 'vendor/styles/visualsearch-datauri.css' ], after: ['app/styles/custom-ui.css'] http://git-wip-us.apache.org/repos/asf/ambari/blob/5dbb43e7/ambari-web/karma.conf.js ---------------------------------------------------------------------- diff --git a/ambari-web/karma.conf.js b/ambari-web/karma.conf.js index 013cc2e..3dd6be1 100644 --- a/ambari-web/karma.conf.js +++ b/ambari-web/karma.conf.js @@ -69,6 +69,8 @@ module.exports = function(config) { 'vendor/scripts/jquery.ui.custom-effects.js', 'vendor/scripts/jquery.timeago.js', 'vendor/scripts/jquery.ajax-retry.js', + 'vendor/scripts/difflib.js', + 'vendor/scripts/diffview.js', 'vendor/scripts/underscore.js', 'vendor/scripts/backbone.js', 'vendor/scripts/visualsearch.js', http://git-wip-us.apache.org/repos/asf/ambari/blob/5dbb43e7/ambari-web/test/mixins/common/configs/config_recommendations_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/mixins/common/configs/config_recommendations_test.js b/ambari-web/test/mixins/common/configs/config_recommendations_test.js index 3f7d6b1..5de60e0 100644 --- a/ambari-web/test/mixins/common/configs/config_recommendations_test.js +++ b/ambari-web/test/mixins/common/configs/config_recommendations_test.js @@ -32,10 +32,12 @@ describe('App.ConfigRecommendations', function() { sinon.stub(App.config, 'get').withArgs('serviceByConfigTypeMap').returns({ 'pFile': Em.Object.create({serviceName: 'sName', displayName: 'sDisplayName'}) }); + sinon.stub(Handlebars, 'SafeString'); }); afterEach(function() { instanceObject.formatParentProperties.restore(); App.config.get.restore(); + Handlebars.SafeString.restore(); }); it('adds new recommendation', function() { @@ -53,7 +55,8 @@ describe('App.ConfigRecommendations', function() { serviceName: 'sName', allowChangeGroup: false, serviceDisplayName: 'sDisplayName', - recommendedValue: 'pRecommended' + recommendedValue: 'pRecommended', + diff: {} }); expect(instanceObject.getRecommendation('pName', 'pFile', 'pGroup')).to.eql(res); }); @@ -128,7 +131,8 @@ describe('App.ConfigRecommendations', function() { serviceName: 'sName', allowChangeGroup: false, serviceDisplayName: 'sDisplayName', - recommendedValue: 'pRecommended' + recommendedValue: 'pRecommended', + diff: {} } }, { @@ -148,7 +152,8 @@ describe('App.ConfigRecommendations', function() { serviceName: 'sName', allowChangeGroup: false, serviceDisplayName: 'sDisplayName', - recommendedValue: undefined + recommendedValue: undefined, + diff: {} } } ]; @@ -160,11 +165,13 @@ describe('App.ConfigRecommendations', function() { sinon.stub(App.config, 'get').withArgs('serviceByConfigTypeMap').returns({ 'pFile': c.service }); + sinon.stub(Handlebars, 'SafeString'); recommendation = instanceObject.addRecommendation(c.name, c.file, c.group, c.recommended, c.initial, c.parent); }); afterEach(function() { App.config.get.restore(); + Handlebars.SafeString.restore(); }); it(c.title, function() { http://git-wip-us.apache.org/repos/asf/ambari/blob/5dbb43e7/ambari-web/vendor/scripts/difflib.js ---------------------------------------------------------------------- diff --git a/ambari-web/vendor/scripts/difflib.js b/ambari-web/vendor/scripts/difflib.js new file mode 100755 index 0000000..191fe45 --- /dev/null +++ b/ambari-web/vendor/scripts/difflib.js @@ -0,0 +1,413 @@ +/*** +This is part of jsdifflib v1.0. <http://snowtide.com/jsdifflib> + +Copyright (c) 2007, Snowtide Informatics Systems, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the Snowtide Informatics Systems nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. +***/ +/* Author: Chas Emerick <[email protected]> */ +var __whitespace = {" ":true, "\t":true, "\n":true, "\f":true, "\r":true}; + +var difflib = { + defaultJunkFunction: function (c) { + return __whitespace.hasOwnProperty(c); + }, + + stripLinebreaks: function (str) { return str.replace(/^[\n\r]*|[\n\r]*$/g, ""); }, + + stringAsLines: function (str) { + var lfpos = str.indexOf("\n"); + var crpos = str.indexOf("\r"); + var linebreak = ((lfpos > -1 && crpos > -1) || crpos < 0) ? "\n" : "\r"; + + var lines = str.split(linebreak); + for (var i = 0; i < lines.length; i++) { + lines[i] = difflib.stripLinebreaks(lines[i]); + } + + return lines; + }, + + // iteration-based reduce implementation + __reduce: function (func, list, initial) { + if (initial != null) { + var value = initial; + var idx = 0; + } else if (list) { + var value = list[0]; + var idx = 1; + } else { + return null; + } + + for (; idx < list.length; idx++) { + value = func(value, list[idx]); + } + + return value; + }, + + // comparison function for sorting lists of numeric tuples + __ntuplecomp: function (a, b) { + var mlen = Math.max(a.length, b.length); + for (var i = 0; i < mlen; i++) { + if (a[i] < b[i]) return -1; + if (a[i] > b[i]) return 1; + } + + return a.length == b.length ? 0 : (a.length < b.length ? -1 : 1); + }, + + __calculate_ratio: function (matches, length) { + return length ? 2.0 * matches / length : 1.0; + }, + + // returns a function that returns true if a key passed to the returned function + // is in the dict (js object) provided to this function; replaces being able to + // carry around dict.has_key in python... + __isindict: function (dict) { + return function (key) { return dict.hasOwnProperty(key); }; + }, + + // replacement for python's dict.get function -- need easy default values + __dictget: function (dict, key, defaultValue) { + return dict.hasOwnProperty(key) ? dict[key] : defaultValue; + }, + + SequenceMatcher: function (a, b, isjunk) { + this.set_seqs = function (a, b) { + this.set_seq1(a); + this.set_seq2(b); + } + + this.set_seq1 = function (a) { + if (a == this.a) return; + this.a = a; + this.matching_blocks = this.opcodes = null; + } + + this.set_seq2 = function (b) { + if (b == this.b) return; + this.b = b; + this.matching_blocks = this.opcodes = this.fullbcount = null; + this.__chain_b(); + } + + this.__chain_b = function () { + var b = this.b; + var n = b.length; + var b2j = this.b2j = {}; + var populardict = {}; + for (var i = 0; i < b.length; i++) { + var elt = b[i]; + if (b2j.hasOwnProperty(elt)) { + var indices = b2j[elt]; + if (n >= 200 && indices.length * 100 > n) { + populardict[elt] = 1; + delete b2j[elt]; + } else { + indices.push(i); + } + } else { + b2j[elt] = [i]; + } + } + + for (var elt in populardict) { + if (populardict.hasOwnProperty(elt)) { + delete b2j[elt]; + } + } + + var isjunk = this.isjunk; + var junkdict = {}; + if (isjunk) { + for (var elt in populardict) { + if (populardict.hasOwnProperty(elt) && isjunk(elt)) { + junkdict[elt] = 1; + delete populardict[elt]; + } + } + for (var elt in b2j) { + if (b2j.hasOwnProperty(elt) && isjunk(elt)) { + junkdict[elt] = 1; + delete b2j[elt]; + } + } + } + + this.isbjunk = difflib.__isindict(junkdict); + this.isbpopular = difflib.__isindict(populardict); + } + + this.find_longest_match = function (alo, ahi, blo, bhi) { + var a = this.a; + var b = this.b; + var b2j = this.b2j; + var isbjunk = this.isbjunk; + var besti = alo; + var bestj = blo; + var bestsize = 0; + var j = null; + var k; + + var j2len = {}; + var nothing = []; + for (var i = alo; i < ahi; i++) { + var newj2len = {}; + var jdict = difflib.__dictget(b2j, a[i], nothing); + for (var jkey in jdict) { + if (jdict.hasOwnProperty(jkey)) { + j = jdict[jkey]; + if (j < blo) continue; + if (j >= bhi) break; + newj2len[j] = k = difflib.__dictget(j2len, j - 1, 0) + 1; + if (k > bestsize) { + besti = i - k + 1; + bestj = j - k + 1; + bestsize = k; + } + } + } + j2len = newj2len; + } + + while (besti > alo && bestj > blo && !isbjunk(b[bestj - 1]) && a[besti - 1] == b[bestj - 1]) { + besti--; + bestj--; + bestsize++; + } + + while (besti + bestsize < ahi && bestj + bestsize < bhi && + !isbjunk(b[bestj + bestsize]) && + a[besti + bestsize] == b[bestj + bestsize]) { + bestsize++; + } + + while (besti > alo && bestj > blo && isbjunk(b[bestj - 1]) && a[besti - 1] == b[bestj - 1]) { + besti--; + bestj--; + bestsize++; + } + + while (besti + bestsize < ahi && bestj + bestsize < bhi && isbjunk(b[bestj + bestsize]) && + a[besti + bestsize] == b[bestj + bestsize]) { + bestsize++; + } + + return [besti, bestj, bestsize]; + } + + this.get_matching_blocks = function () { + if (this.matching_blocks != null) return this.matching_blocks; + var la = this.a.length; + var lb = this.b.length; + + var queue = [[0, la, 0, lb]]; + var matching_blocks = []; + var alo, ahi, blo, bhi, qi, i, j, k, x; + while (queue.length) { + qi = queue.pop(); + alo = qi[0]; + ahi = qi[1]; + blo = qi[2]; + bhi = qi[3]; + x = this.find_longest_match(alo, ahi, blo, bhi); + i = x[0]; + j = x[1]; + k = x[2]; + + if (k) { + matching_blocks.push(x); + if (alo < i && blo < j) + queue.push([alo, i, blo, j]); + if (i+k < ahi && j+k < bhi) + queue.push([i + k, ahi, j + k, bhi]); + } + } + + matching_blocks.sort(difflib.__ntuplecomp); + + var i1 = 0, j1 = 0, k1 = 0, block = 0; + var i2, j2, k2; + var non_adjacent = []; + for (var idx in matching_blocks) { + if (matching_blocks.hasOwnProperty(idx)) { + block = matching_blocks[idx]; + i2 = block[0]; + j2 = block[1]; + k2 = block[2]; + if (i1 + k1 == i2 && j1 + k1 == j2) { + k1 += k2; + } else { + if (k1) non_adjacent.push([i1, j1, k1]); + i1 = i2; + j1 = j2; + k1 = k2; + } + } + } + + if (k1) non_adjacent.push([i1, j1, k1]); + + non_adjacent.push([la, lb, 0]); + this.matching_blocks = non_adjacent; + return this.matching_blocks; + } + + this.get_opcodes = function () { + if (this.opcodes != null) return this.opcodes; + var i = 0; + var j = 0; + var answer = []; + this.opcodes = answer; + var block, ai, bj, size, tag; + var blocks = this.get_matching_blocks(); + for (var idx in blocks) { + if (blocks.hasOwnProperty(idx)) { + block = blocks[idx]; + ai = block[0]; + bj = block[1]; + size = block[2]; + tag = ''; + if (i < ai && j < bj) { + tag = 'replace'; + } else if (i < ai) { + tag = 'delete'; + } else if (j < bj) { + tag = 'insert'; + } + if (tag) answer.push([tag, i, ai, j, bj]); + i = ai + size; + j = bj + size; + + if (size) answer.push(['equal', ai, i, bj, j]); + } + } + + return answer; + } + + // this is a generator function in the python lib, which of course is not supported in javascript + // the reimplementation builds up the grouped opcodes into a list in their entirety and returns that. + this.get_grouped_opcodes = function (n) { + if (!n) n = 3; + var codes = this.get_opcodes(); + if (!codes) codes = [["equal", 0, 1, 0, 1]]; + var code, tag, i1, i2, j1, j2; + if (codes[0][0] == 'equal') { + code = codes[0]; + tag = code[0]; + i1 = code[1]; + i2 = code[2]; + j1 = code[3]; + j2 = code[4]; + codes[0] = [tag, Math.max(i1, i2 - n), i2, Math.max(j1, j2 - n), j2]; + } + if (codes[codes.length - 1][0] == 'equal') { + code = codes[codes.length - 1]; + tag = code[0]; + i1 = code[1]; + i2 = code[2]; + j1 = code[3]; + j2 = code[4]; + codes[codes.length - 1] = [tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)]; + } + + var nn = n + n; + var group = []; + var groups = []; + for (var idx in codes) { + if (codes.hasOwnProperty(idx)) { + code = codes[idx]; + tag = code[0]; + i1 = code[1]; + i2 = code[2]; + j1 = code[3]; + j2 = code[4]; + if (tag == 'equal' && i2 - i1 > nn) { + group.push([tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)]); + groups.push(group); + group = []; + i1 = Math.max(i1, i2-n); + j1 = Math.max(j1, j2-n); + } + + group.push([tag, i1, i2, j1, j2]); + } + } + + if (group && !(group.length == 1 && group[0][0] == 'equal')) groups.push(group) + + return groups; + } + + this.ratio = function () { + matches = difflib.__reduce( + function (sum, triple) { return sum + triple[triple.length - 1]; }, + this.get_matching_blocks(), 0); + return difflib.__calculate_ratio(matches, this.a.length + this.b.length); + } + + this.quick_ratio = function () { + var fullbcount, elt; + if (this.fullbcount == null) { + this.fullbcount = fullbcount = {}; + for (var i = 0; i < this.b.length; i++) { + elt = this.b[i]; + fullbcount[elt] = difflib.__dictget(fullbcount, elt, 0) + 1; + } + } + fullbcount = this.fullbcount; + + var avail = {}; + var availhas = difflib.__isindict(avail); + var matches = numb = 0; + for (var i = 0; i < this.a.length; i++) { + elt = this.a[i]; + if (availhas(elt)) { + numb = avail[elt]; + } else { + numb = difflib.__dictget(fullbcount, elt, 0); + } + avail[elt] = numb - 1; + if (numb > 0) matches++; + } + + return difflib.__calculate_ratio(matches, this.a.length + this.b.length); + } + + this.real_quick_ratio = function () { + var la = this.a.length; + var lb = this.b.length; + return _calculate_ratio(Math.min(la, lb), la + lb); + } + + this.isjunk = isjunk ? isjunk : difflib.defaultJunkFunction; + this.a = this.b = null; + this.set_seqs(a, b); + } +}; + http://git-wip-us.apache.org/repos/asf/ambari/blob/5dbb43e7/ambari-web/vendor/scripts/diffview.js ---------------------------------------------------------------------- diff --git a/ambari-web/vendor/scripts/diffview.js b/ambari-web/vendor/scripts/diffview.js new file mode 100755 index 0000000..9345a32 --- /dev/null +++ b/ambari-web/vendor/scripts/diffview.js @@ -0,0 +1,172 @@ +/* +This is part of jsdifflib v1.0. <http://github.com/cemerick/jsdifflib> + +Copyright 2007 - 2011 Chas Emerick <[email protected]>. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY Chas Emerick ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Chas Emerick OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the +authors and should not be interpreted as representing official policies, either expressed +or implied, of Chas Emerick. +*/ +diffview = { + /** + * Builds and returns a visual diff view. The single parameter, `params', should contain + * the following values: + * + * - baseTextLines: the array of strings that was used as the base text input to SequenceMatcher + * - newTextLines: the array of strings that was used as the new text input to SequenceMatcher + * - opcodes: the array of arrays returned by SequenceMatcher.get_opcodes() + * - baseTextName: the title to be displayed above the base text listing in the diff view; defaults + * to "Base Text" + * - newTextName: the title to be displayed above the new text listing in the diff view; defaults + * to "New Text" + * - contextSize: the number of lines of context to show around differences; by default, all lines + * are shown + * - viewType: if 0, a side-by-side diff view is generated (default); if 1, an inline diff view is + * generated + */ + buildView: function (params) { + var baseTextLines = params.baseTextLines; + var newTextLines = params.newTextLines; + var opcodes = params.opcodes; + var contextSize = params.contextSize; + var inline = (params.viewType == 0 || params.viewType == 1) ? params.viewType : 0; + + if (baseTextLines == null) + throw "Cannot build diff view; baseTextLines is not defined."; + if (newTextLines == null) + throw "Cannot build diff view; newTextLines is not defined."; + if (!opcodes) + throw "Canno build diff view; opcodes is not defined."; + + function celt (name, clazz) { + var e = document.createElement(name); + e.className = clazz; + return e; + } + + function telt (name, text) { + var e = document.createElement(name); + e.appendChild(document.createTextNode(text)); + return e; + } + + function ctelt (name, clazz, text) { + var e = document.createElement(name); + e.className = clazz; + e.appendChild(document.createTextNode(text)); + return e; + } + + var tdata = []; + + var rows = []; + var node2; + + /** + * Adds two cells to the given row; if the given row corresponds to a real + * line number (based on the line index tidx and the endpoint of the + * range in question tend), then the cells will contain the line number + * and the line of text from textLines at position tidx (with the class of + * the second cell set to the name of the change represented), and tidx + 1 will + * be returned. Otherwise, tidx is returned, and two empty cells are added + * to the given row. + */ + function addCells (row, tidx, tend, textLines, change) { + if (tidx < tend) { + row.appendChild(ctelt("td", change, textLines[tidx].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0"))); + return tidx + 1; + } else { + row.appendChild(celt("td", "empty")); + return tidx; + } + } + + function addCellsInline (row, tidx, tidx2, textLines, change) { + row.appendChild(ctelt("td", change, textLines[tidx != null ? tidx : tidx2].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0"))); + } + + for (var idx = 0; idx < opcodes.length; idx++) { + code = opcodes[idx]; + change = code[0]; + var b = code[1]; + var be = code[2]; + var n = code[3]; + var ne = code[4]; + var rowcnt = Math.max(be - b, ne - n); + var toprows = []; + var botrows = []; + for (var i = 0; i < rowcnt; i++) { + // jump ahead if we've alredy provided leading context or if this is the first range + if (contextSize && opcodes.length > 1 && ((idx > 0 && i == contextSize) || (idx == 0 && i == 0)) && change=="equal") { + var jump = rowcnt - ((idx == 0 ? 1 : 2) * contextSize); + if (jump > 1) { + toprows.push(node = celt("tr", "row")); + + b += jump; + n += jump; + i += jump - 1; + if (!inline) node.appendChild(ctelt("td", "skip span6", "")); + node.appendChild(ctelt("td", "skip span6", "")); + + // skip last lines if they're all equal + if (idx + 1 == opcodes.length) { + break; + } else { + continue; + } + } + } + + toprows.push(node = celt("tr", "row")); + if (inline) { + if (change == "insert") { + addCellsInline(node, null, n++, newTextLines, change + " span6"); + } else if (change == "replace") { + botrows.push(node2 = celt("tr", "row")); + if (b < be) addCellsInline(node, b++, null, baseTextLines, "delete span6"); + if (n < ne) addCellsInline(node2, null, n++, newTextLines, "insert span6"); + } else if (change == "delete") { + addCellsInline(node, b++, null, baseTextLines, change + " span6"); + } else { + // equal + addCellsInline(node, b++, n++, baseTextLines, change + " span6"); + } + } else { + b = addCells(node, b, be, baseTextLines, change + " span6"); + n = addCells(node, n, ne, newTextLines, change + " span6"); + } + } + + for (var i = 0; i < toprows.length; i++) rows.push(toprows[i]); + for (var i = 0; i < botrows.length; i++) rows.push(botrows[i]); + } + + tdata.push(node = document.createElement("tbody")); + for (var idx in rows) rows.hasOwnProperty(idx) && node.appendChild(rows[idx]); + + node = celt("table", "diff" + (inline ? " inlinediff" : "")); + for (var idx in tdata) tdata.hasOwnProperty(idx) && node.appendChild(tdata[idx]); + return node; + } +}; + http://git-wip-us.apache.org/repos/asf/ambari/blob/5dbb43e7/ambari-web/vendor/styles/diffview.css ---------------------------------------------------------------------- diff --git a/ambari-web/vendor/styles/diffview.css b/ambari-web/vendor/styles/diffview.css new file mode 100755 index 0000000..959f8b3 --- /dev/null +++ b/ambari-web/vendor/styles/diffview.css @@ -0,0 +1,84 @@ +/* +This is part of jsdifflib v1.0. <http://github.com/cemerick/jsdifflib> + +Copyright 2007 - 2011 Chas Emerick <[email protected]>. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY Chas Emerick ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Chas Emerick OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the +authors and should not be interpreted as representing official policies, either expressed +or implied, of Chas Emerick. +*/ +table.diff { + border-collapse:collapse; + border:1px solid darkgray; + white-space:pre-wrap +} +table.diff tbody { + font-family:Courier, monospace +} +table.diff tbody th { + font-family:verdana,arial,'Bitstream Vera Sans',helvetica,sans-serif; + background:#EED; + font-size:11px; + font-weight:normal; + border:1px solid #BBC; + color:#886; + padding:.3em .5em .1em 2em; + text-align:right; + vertical-align:top +} +table.diff thead { + border-bottom:1px solid #BBC; + background:#EFEFEF; + font-family:Verdana +} +table.diff thead th.texttitle { + text-align:left +} +table.diff tbody td { + padding:0px .4em; + padding-top:.4em; + vertical-align:top; + border:1px solid #BBC; +} +table.diff .empty { + background-color:#DDD !important; +} +table.diff .replace { + background-color:#FD8 !important; +} +table.diff .delete { + background-color:#E99 !important; +} +table.diff .skip { + background-color:#EFEFEF !important; + border:1px solid #AAA; + border-right:1px solid #BBC; +} +table.diff .insert { + background-color:#9E9 !important; +} +table.diff th.author { + text-align:right; + border-top:1px solid #BBC; + background:#EFEFEF +} \ No newline at end of file
