Repository: tez Updated Branches: refs/heads/master 1264d5d01 -> 2c22e23a7
TEZ-3288. Tez UI: Display more details in the error bar (sree) Project: http://git-wip-us.apache.org/repos/asf/tez/repo Commit: http://git-wip-us.apache.org/repos/asf/tez/commit/2c22e23a Tree: http://git-wip-us.apache.org/repos/asf/tez/tree/2c22e23a Diff: http://git-wip-us.apache.org/repos/asf/tez/diff/2c22e23a Branch: refs/heads/master Commit: 2c22e23a738a7764aa8d53ca7e2bcba7d7053337 Parents: 1264d5d Author: Sreenath Somarajapuram <s...@apache.org> Authored: Sat Jun 25 17:49:55 2016 +0530 Committer: Sreenath Somarajapuram <s...@apache.org> Committed: Sat Jun 25 17:49:55 2016 +0530 ---------------------------------------------------------------------- CHANGES.txt | 1 + tez-ui/src/main/webapp/app/adapters/abstract.js | 67 ++++++-- tez-ui/src/main/webapp/app/adapters/loader.js | 3 +- .../src/main/webapp/app/components/error-bar.js | 51 +------ .../src/main/webapp/app/initializers/jquery.js | 4 - .../src/main/webapp/app/styles/error-bar.less | 17 ++- .../app/templates/components/error-bar.hbs | 18 ++- tez-ui/src/main/webapp/package.json | 2 +- .../integration/components/error-bar-test.js | 86 ++++++++++- .../webapp/tests/unit/adapters/abstract-test.js | 151 +++++++++++++++++++ .../webapp/tests/unit/adapters/loader-test.js | 2 + 11 files changed, 328 insertions(+), 74 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tez/blob/2c22e23a/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index bb130ef..640d957 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -58,6 +58,7 @@ ALL CHANGES: TEZ-3281. Tez UI: Swimlane improvements TEZ-3264. Tez UI: UI discrepancies TEZ-3292. Tez UI: UTs breaking with timezone change + TEZ-3288. Tez UI: Display more details in the error bar Release 0.8.4: Unreleased http://git-wip-us.apache.org/repos/asf/tez/blob/2c22e23a/tez-ui/src/main/webapp/app/adapters/abstract.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/adapters/abstract.js b/tez-ui/src/main/webapp/app/adapters/abstract.js index 121d4ee..9a7dcae 100644 --- a/tez-ui/src/main/webapp/app/adapters/abstract.js +++ b/tez-ui/src/main/webapp/app/adapters/abstract.js @@ -53,18 +53,63 @@ export default LoaderAdapter.extend({ return path; }, - normalizeErrorResponse: function(status, headers, payload) { - var response; - - if(payload && payload.exception && !payload.errors) { - payload = `${payload.exception}\n${payload.message}\n${payload.javaClassName}`; - response = this._super(status, headers, payload); - } - else { - response = this._super(status, headers, payload); - Ember.set(response, '0.title', this.get("outOfReachMessage")); + normalizeErrorResponse: function (status, headers, payload) { + var title; + switch(typeof payload) { + case "object": + title = payload.message; + break; + case "string": + let html = Ember.$(payload.bold()); + html.find('script').remove(); + html.find('style').remove(); + payload = html.text().trim(); + break; } - return response; + return [{ + title: title, + status: status, + headers: headers, + detail: payload + }]; + }, + + _loaderAjax: function (url, queryParams, namespace) { + var requestInfo = { + adapterName: this.get("name"), + url: url + }, + that = this; + + return this._super(url, queryParams, namespace).catch(function (error) { + var message = `${error.message} »`, + status = Ember.get(error, "errors.0.status"); + + if(status === 0) { + let outOfReachMessage = that.get("outOfReachMessage"); + message = `${message} ${outOfReachMessage}`; + } + else { + let title = Ember.get(error, "errors.0.title") || `Error accessing ${url}`; + message = `${message} ${status}: ${title}`; + } + + requestInfo.responseHeaders = Ember.get(error, "errors.0.headers"); + if(queryParams) { + requestInfo.queryParams = queryParams; + } + if(namespace) { + requestInfo.namespace = namespace; + } + + Ember.setProperties(error, { + message: message, + details: Ember.get(error, "errors.0.detail"), + requestInfo: requestInfo + }); + + throw(error); + }); } }); http://git-wip-us.apache.org/repos/asf/tez/blob/2c22e23a/tez-ui/src/main/webapp/app/adapters/loader.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/adapters/loader.js b/tez-ui/src/main/webapp/app/adapters/loader.js index f63bd07..5000bea 100644 --- a/tez-ui/src/main/webapp/app/adapters/loader.js +++ b/tez-ui/src/main/webapp/app/adapters/loader.js @@ -18,10 +18,11 @@ */ import DS from 'ember-data'; +import NameMixin from '../mixins/name'; var MoreString = more.String; -export default DS.RESTAdapter.extend({ +export default DS.RESTAdapter.extend(NameMixin, { _isLoader: true, buildURL: function(modelName, id, snapshot, requestType, query, params) { http://git-wip-us.apache.org/repos/asf/tez/blob/2c22e23a/tez-ui/src/main/webapp/app/components/error-bar.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/components/error-bar.js b/tez-ui/src/main/webapp/app/components/error-bar.js index 1086010..d562614 100644 --- a/tez-ui/src/main/webapp/app/components/error-bar.js +++ b/tez-ui/src/main/webapp/app/components/error-bar.js @@ -26,57 +26,21 @@ export default Ember.Component.extend({ visible: false, detailsAvailable: false, + showDetails: false, + displayTimerId: 0, classNames: ['error-bar'], classNameBindings: ['visible', 'detailsAvailable'], - code: null, message: null, - details: null, - stack: null, - showDetails: false, - - displayTimerId: 0, - - _errorObserver: Ember.observer("error", function () { - var error = this.get("error"), - - code = Ember.get(error, "errors.0.status"), - title = Ember.get(error, "errors.0.title"), - message = error.message || "Error", - details = Ember.get(error, "errors.0.detail") || "", - stack = error.stack, - lineEndIndex = Math.min(message.indexOf('\n'), message.indexOf('<br')); - - if(code === "0") { - code = ""; - } - - if(title) { - message += ". " + title; - } - - if(lineEndIndex > 0) { - if(details) { - details = "\n" + details; - } - details = message.substr(lineEndIndex) + details; - message = message.substr(0, lineEndIndex); - } - - if(details) { - details += "\n"; - } + _errorObserver: Ember.on("init", Ember.observer("error", function () { + var error = this.get("error"); if(error) { this.setProperties({ - code: code, - message: message, - details: details, - stack: stack, - - detailsAvailable: !!(details || stack), + message: error.message || "Error", + detailsAvailable: !!(error.details || error.requestInfo || error.stack), visible: true }); @@ -86,7 +50,7 @@ export default Ember.Component.extend({ else { this.close(); } - }), + })), clearTimer: function () { clearTimeout(this.get("displayTimerId")); @@ -105,5 +69,4 @@ export default Ember.Component.extend({ this.close(); } } - }); http://git-wip-us.apache.org/repos/asf/tez/blob/2c22e23a/tez-ui/src/main/webapp/app/initializers/jquery.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/initializers/jquery.js b/tez-ui/src/main/webapp/app/initializers/jquery.js index 2c34b1d..37ebc4d 100644 --- a/tez-ui/src/main/webapp/app/initializers/jquery.js +++ b/tez-ui/src/main/webapp/app/initializers/jquery.js @@ -24,10 +24,6 @@ export function initialize(/* application */) { tooltipClass: 'generic-tooltip' }); - Ember.$.ajaxPrefilter(function(options, originalOptions, jqXHR) { - jqXHR.requestOptions = originalOptions; - }); - Ember.$.ajaxSetup({ cache: false }); http://git-wip-us.apache.org/repos/asf/tez/blob/2c22e23a/tez-ui/src/main/webapp/app/styles/error-bar.less ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/styles/error-bar.less b/tez-ui/src/main/webapp/app/styles/error-bar.less index dec2100..56d2b88 100644 --- a/tez-ui/src/main/webapp/app/styles/error-bar.less +++ b/tez-ui/src/main/webapp/app/styles/error-bar.less @@ -44,22 +44,31 @@ color: @text-red; .message, .details { - overflow: scroll; - max-height: 100px; - + max-height: 150px; text-align: left; } + .message { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin-right: 30px; + } + .details { display: none; visibility: hidden; + overflow: scroll; margin-bottom: 10px; border-top: 1px @border-lite solid; .force-scrollbar; - white-space: pre-line; + + p { + white-space: pre-wrap; + } &.visible { display: block; http://git-wip-us.apache.org/repos/asf/tez/blob/2c22e23a/tez-ui/src/main/webapp/app/templates/components/error-bar.hbs ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/templates/components/error-bar.hbs b/tez-ui/src/main/webapp/app/templates/components/error-bar.hbs index a21bba0..fe5d44d 100644 --- a/tez-ui/src/main/webapp/app/templates/components/error-bar.hbs +++ b/tez-ui/src/main/webapp/app/templates/components/error-bar.hbs @@ -18,14 +18,22 @@ <div class="message" {{action "toggleDetailsDisplay"}}> <i class="fa fa-exclamation-circle"></i> - {{#if code}} - <b>{{code}}</b> : - {{/if}} {{message}} <i class="show-details fa {{if showDetails 'fa-minus-circle' 'fa-plus-circle'}}"></i> </div> -<div class="details {{if showDetails "visible"}}">{{{details}}}{{#if stack}}<b>Stack:</b> - {{stack}} + +<div class="details {{if showDetails "visible"}}"> + {{#if error.details}} + <b>Details:</b> + <p>{{txt error.details type="json"}}</p> + {{/if}} + {{#if error.requestInfo}} + <b>Request:</b> + <p>{{txt error.requestInfo type="json"}}</p> + {{/if}} + {{#if error.stack}} + <b>Stack:</b> + <p>{{error.stack}}</p> {{/if}} </div> <i class="close-button fa fa-arrow-circle-down" {{action "close"}}></i> http://git-wip-us.apache.org/repos/asf/tez/blob/2c22e23a/tez-ui/src/main/webapp/package.json ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/package.json b/tez-ui/src/main/webapp/package.json index 15a77e4..e5c3279 100644 --- a/tez-ui/src/main/webapp/package.json +++ b/tez-ui/src/main/webapp/package.json @@ -57,7 +57,7 @@ "phantomjs": "1.9.19" }, "dependencies": { - "em-helpers": "0.5.10", + "em-helpers": "0.5.13", "em-table": "0.3.14", "em-tgraph": "0.0.5" } http://git-wip-us.apache.org/repos/asf/tez/blob/2c22e23a/tez-ui/src/main/webapp/tests/integration/components/error-bar-test.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/tests/integration/components/error-bar-test.js b/tez-ui/src/main/webapp/tests/integration/components/error-bar-test.js index 10c6c7d..f736b45 100644 --- a/tez-ui/src/main/webapp/tests/integration/components/error-bar-test.js +++ b/tez-ui/src/main/webapp/tests/integration/components/error-bar-test.js @@ -19,15 +19,13 @@ import { moduleForComponent, test } from 'ember-qunit'; import hbs from 'htmlbars-inline-precompile'; +import wait from 'ember-test-helpers/wait'; + moduleForComponent('error-bar', 'Integration | Component | error bar', { integration: true }); test('Basic creation test', function(assert) { - - // Set any properties with this.set('myProperty', 'value'); - // Handle any actions with this.on('myAction', function(val) { ... });" + EOL + EOL + - this.render(hbs`{{error-bar}}`); assert.equal(this.$().text().trim(), ''); @@ -41,3 +39,83 @@ test('Basic creation test', function(assert) { assert.equal(this.$().text().trim(), ''); }); + +test('Plain Object test', function(assert) { + Function.prototype.bind = function () {}; + + this.set("error", {}); + this.render(hbs`{{error-bar error=error}}`); + + return wait().then(() => { + assert.equal(this.$().text().trim(), 'Error'); + }); +}); + +test('Message test', function(assert) { + var testMessage = "Test Message"; + + Function.prototype.bind = function () {}; + + this.set("error", { + message: testMessage + }); + this.render(hbs`{{error-bar error=error}}`); + + return wait().then(() => { + assert.equal(this.$().text().trim(), testMessage); + }); +}); + +test('details test', function(assert) { + var testMessage = "Test Message", + testDetails = "details"; + + Function.prototype.bind = function () {}; + + this.set("error", { + message: testMessage, + details: testDetails + }); + this.render(hbs`{{error-bar error=error}}`); + + return wait().then(() => { + assert.equal(this.$(".message").text().trim(), testMessage); + assert.equal(this.$(".details p").text().trim(), testDetails); + }); +}); + +test('requestInfo test', function(assert) { + var testMessage = "Test Message", + testInfo = "info"; + + Function.prototype.bind = function () {}; + + this.set("error", { + message: testMessage, + requestInfo: testInfo + }); + this.render(hbs`{{error-bar error=error}}`); + + return wait().then(() => { + assert.equal(this.$(".message").text().trim(), testMessage); + assert.equal(this.$(".details p").text().trim(), testInfo); + }); +}); + +test('stack test', function(assert) { + var testMessage = "Test Message", + testStack = "stack"; + + Function.prototype.bind = function () {}; + + this.set("error", { + message: testMessage, + stack: testStack + }); + this.render(hbs`{{error-bar error=error}}`); + + return wait().then(() => { + assert.equal(this.$(".message").text().trim(), testMessage); + assert.equal(this.$(".details p").text().trim(), testStack); + }); +}); http://git-wip-us.apache.org/repos/asf/tez/blob/2c22e23a/tez-ui/src/main/webapp/tests/unit/adapters/abstract-test.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/tests/unit/adapters/abstract-test.js b/tez-ui/src/main/webapp/tests/unit/adapters/abstract-test.js index 5ff39e7..24ba624 100644 --- a/tez-ui/src/main/webapp/tests/unit/adapters/abstract-test.js +++ b/tez-ui/src/main/webapp/tests/unit/adapters/abstract-test.js @@ -16,6 +16,8 @@ * limitations under the License. */ +import Ember from 'ember'; + import { moduleFor, test } from 'ember-qunit'; moduleFor('adapter:abstract', 'Unit | Adapter | abstract', { @@ -35,6 +37,9 @@ test('Basic creation test', function(assert) { assert.ok(adapter.ajaxOptions); assert.ok(adapter.pathForType); + + assert.ok(adapter.normalizeErrorResponse); + assert.ok(adapter._loaderAjax); }); test('host, namespace & pathTypeHash test', function(assert) { @@ -108,3 +113,149 @@ test('pathForType test', function(assert) { adapter.pathForType("noType"); }); }); + +test('normalizeErrorResponse test', function(assert) { + let adapter = this.subject(), + status = "200", + testTitle = "title", + strPayload = "StringPayload", + objPayload = {x: 1, message: testTitle}, + testHeaders = {}, + response; + + response = adapter.normalizeErrorResponse(status, testHeaders, strPayload); + assert.equal(response[0].title, undefined); + assert.equal(response[0].status, status); + assert.equal(response[0].detail, strPayload); + assert.equal(response[0].headers, testHeaders); + + response = adapter.normalizeErrorResponse(status, testHeaders, objPayload); + assert.equal(response[0].title, testTitle); + assert.equal(response[0].status, status); + assert.deepEqual(response[0].detail, objPayload); + assert.equal(response[0].headers, testHeaders); +}); + +test('normalizeErrorResponse html payload test', function(assert) { + let adapter = this.subject(), + status = "200", + htmlPayload = "StringPayload <b>boldText</b> <script>scriptText</script> <style>styleText</style>", + testHeaders = {}, + response; + + response = adapter.normalizeErrorResponse(status, testHeaders, htmlPayload); + assert.equal(response[0].detail, "StringPayload boldText"); +}); + +test('_loaderAjax resolve test', function(assert) { + let result = {}, + adapter = this.subject({ + ajax: function () { + assert.ok(1); + return Ember.RSVP.resolve(result); + } + }); + + assert.expect(1 + 1); + + adapter._loaderAjax().then(function (val) { + assert.equal(val.data, result); + }); +}); + +test('_loaderAjax reject, without title test', function(assert) { + let errorInfo = { + status: "500", + detail: "testDetails" + }, + msg = "Msg", + testUrl = "http://foo.bar", + testQuery = {}, + testNS = "namespace", + adapter = this.subject({ + outOfReachMessage: "OutOfReach", + ajax: function () { + assert.ok(1); + return Ember.RSVP.reject({ + message: msg, + errors:[errorInfo] + }); + } + }); + + assert.expect(1 + 7); + + adapter._loaderAjax(testUrl, testQuery, testNS).catch(function (val) { + assert.equal(val.message, `${msg} » ${errorInfo.status}: Error accessing ${testUrl}`); + assert.equal(val.details, errorInfo.detail); + assert.equal(val.requestInfo.adapterName, "abstract"); + assert.equal(val.requestInfo.url, testUrl); + + assert.equal(val.requestInfo.queryParams, testQuery); + assert.equal(val.requestInfo.namespace, testNS); + + assert.ok(val.requestInfo.hasOwnProperty("responseHeaders")); + }); +}); + +test('_loaderAjax reject, with title test', function(assert) { + let errorInfo = { + status: "500", + title: "Server Error", + detail: "testDetails" + }, + msg = "Msg", + testUrl = "url", + adapter = this.subject({ + outOfReachMessage: "OutOfReach", + ajax: function () { + assert.ok(1); + return Ember.RSVP.reject({ + message: msg, + errors:[errorInfo] + }); + } + }); + + assert.expect(1 + 5); + + adapter._loaderAjax(testUrl).catch(function (val) { + assert.equal(val.message, `${msg} » ${errorInfo.status}: ${errorInfo.title}`); + assert.equal(val.details, errorInfo.detail); + assert.equal(val.requestInfo.adapterName, "abstract"); + assert.equal(val.requestInfo.url, testUrl); + + assert.ok(val.requestInfo.hasOwnProperty("responseHeaders")); + }); +}); + +test('_loaderAjax reject, status 0 test', function(assert) { + let errorInfo = { + status: 0, + title: "Server Error", + detail: "testDetails" + }, + msg = "Msg", + testUrl = "url", + adapter = this.subject({ + outOfReachMessage: "OutOfReach", + ajax: function () { + assert.ok(1); + return Ember.RSVP.reject({ + message: msg, + errors:[errorInfo] + }); + } + }); + + assert.expect(1 + 5); + + adapter._loaderAjax(testUrl).catch(function (val) { + assert.equal(val.message, `${msg} » ${adapter.outOfReachMessage}`); + assert.equal(val.details, errorInfo.detail); + assert.equal(val.requestInfo.adapterName, "abstract"); + assert.equal(val.requestInfo.url, testUrl); + + assert.ok(val.requestInfo.hasOwnProperty("responseHeaders")); + }); +}); http://git-wip-us.apache.org/repos/asf/tez/blob/2c22e23a/tez-ui/src/main/webapp/tests/unit/adapters/loader-test.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/tests/unit/adapters/loader-test.js b/tez-ui/src/main/webapp/tests/unit/adapters/loader-test.js index 7b4a2df..e8e0428 100644 --- a/tez-ui/src/main/webapp/tests/unit/adapters/loader-test.js +++ b/tez-ui/src/main/webapp/tests/unit/adapters/loader-test.js @@ -34,6 +34,8 @@ test('Basic creation', function(assert) { assert.ok(adapter._loaderAjax); assert.ok(adapter.queryRecord); assert.ok(adapter.query); + + assert.equal(adapter.get("name"), "loader"); }); test('buildURL test', function(assert) {