YARN-5153. Add a toggle button to switch between timeline view / table view for containers and application-attempts in new YARN UI. Contributed by Akhil PB.

This commit is contained in:
Sunil G 2017-04-10 13:35:08 +05:30
parent 5d38504071
commit 443aa51bc1
7 changed files with 331 additions and 51 deletions

View File

@ -19,11 +19,4 @@
import Ember from 'ember';
export default Ember.Component.extend({
nodeHttpAddressFormatted: Ember.computed('attempt.nodeHttpAddress', function() {
var nodeHttpAddress = this.get('attempt.nodeHttpAddress');
if (nodeHttpAddress && nodeHttpAddress.indexOf('://') < 0) {
nodeHttpAddress = 'http://' + nodeHttpAddress;
}
return nodeHttpAddress;
})
});

View File

@ -18,6 +18,7 @@
import Ember from 'ember';
import Converter from 'yarn-ui/utils/converter';
import ColumnDef from 'em-table/utils/column-definition';
export default Ember.Component.extend({
canvas: {
@ -31,6 +32,8 @@ export default Ember.Component.extend({
modelArr: [],
colors: d3.scale.category10().range(),
_selected: undefined,
gridColumns: [],
gridRows: [],
selected: function() {
return this._selected;
@ -276,5 +279,199 @@ export default Ember.Component.extend({
if (this.modelArr.length > 0) {
this.setSelected(this.modelArr[0]);
}
if (this.get('attemptModel')) {
this.setAttemptsGridColumnsAndRows();
} else {
this.setContainersGridColumnsAndRows();
}
},
});
setAttemptsGridColumnsAndRows: function() {
var self = this;
var columns = [];
columns.push({
id: 'id',
headerTitle: 'Attempt ID',
contentPath: 'id',
cellComponentName: 'em-table-linked-cell',
minWidth: '300px',
getCellContent: function(row) {
return {
displayText: row.get('id'),
routeName: 'yarn-app-attempt',
id: row.get('id')
};
}
}, {
id: 'attemptStartedTime',
headerTitle: 'Started Time',
contentPath: 'attemptStartedTime'
}, {
id: 'finishedTime',
headerTitle: 'Finished Time',
contentPath: 'finishedTime',
getCellContent: function(row) {
if (row.get('finishedTs')) {
return row.get('finishedTime');
}
return 'N/A';
}
}, {
id: 'elapsedTime',
headerTitle: 'Elapsed Time',
contentPath: 'elapsedTime'
}, {
id: 'appMasterContainerId',
headerTitle: 'AM Container ID',
contentPath: 'appMasterContainerId',
minWidth: '300px'
}, {
id: 'amNodeId',
headerTitle: 'AM Node ID',
contentPath: 'amNodeId'
}, {
id: 'attemptState',
headerTitle: 'State',
contentPath: 'attemptState',
getCellContent: function(row) {
var state = row.get('attemptState');
if (state) {
return state;
} else {
return 'N/A';
}
}
}, {
id: 'nodeHttpAddress',
headerTitle: 'NodeManager Web UI',
contentPath: 'nodeHttpAddress',
cellComponentName: 'em-table-html-cell',
getCellContent: function(row) {
var address = self.checkHttpProtocol(row.get('nodeHttpAddress'));
if (address) {
return `<a href="${address}" target="_blank">${address}</a>`;
} else {
return 'N/A';
}
}
}, {
id: 'logsLink',
headerTitle: 'Logs',
contentPath: 'logsLink',
cellComponentName: 'em-table-html-cell',
getCellContent: function(row) {
var logUrl = self.checkHttpProtocol(row.get('logsLink'));
if (logUrl) {
return `<a href="${logUrl}" target="_blank">Link</a>`;
} else {
return 'N/A';
}
}
});
var gridCols = ColumnDef.make(columns);
this.set('gridColumns', gridCols);
this.set('gridRows', this.modelArr);
},
setContainersGridColumnsAndRows: function() {
var self = this;
var columns = [];
columns.push({
id: 'id',
headerTitle: 'Container ID',
contentPath: 'id',
minWidth: '300px'
}, {
id: 'startedTime',
headerTitle: 'Started Time',
contentPath: 'startedTime'
}, {
id: 'finishedTime',
headerTitle: 'Finished Time',
contentPath: 'finishedTime',
getCellContent: function(row) {
if (row.get('finishedTs')) {
return row.get('finishedTime');
}
return 'N/A';
}
}, {
id: 'elapsedTime',
headerTitle: 'Elapsed Time',
contentPath: 'elapsedTime'
}, {
id: 'priority',
headerTitle: 'Priority',
contentPath: 'priority'
}, {
id: 'containerExitStatus',
headerTitle: 'Exit Status',
contentPath: 'containerExitStatus',
getCellContent: function(row) {
var status = row.get('containerExitStatus');
if (status) {
return status;
} else {
return 'N/A';
}
}
}, {
id: 'containerState',
headerTitle: 'State',
contentPath: 'containerState',
getCellContent: function(row) {
var state = row.get('containerState');
if (state) {
return state;
} else {
return 'N/A';
}
}
}, {
id: 'logUrl',
headerTitle: 'Logs',
contentPath: 'logUrl',
cellComponentName: 'em-table-html-cell',
getCellContent: function(row) {
var url = self.checkHttpProtocol(row.get('logUrl'));
if (url) {
return `<a href="${url}" target="_blank">${url}</a>`;
} else {
return 'N/A';
}
}
}, {
id: 'nodeHttpAddress',
headerTitle: 'Node Manager UI',
contentPath: 'nodeHttpAddress',
cellComponentName: 'em-table-html-cell',
getCellContent: function(row) {
var address = self.checkHttpProtocol(row.get('nodeHttpAddress'));
if (address) {
return `<a href="${address}" target="_blank">${address}</a>`;
} else {
return 'N/A';
}
}
});
var gridCols = ColumnDef.make(columns);
this.set('gridColumns', gridCols);
this.set('gridRows', this.modelArr);
},
checkHttpProtocol: function(prop) {
if (prop && prop.indexOf('://') < 0) {
prop = 'http://' + prop;
}
return prop;
},
isDataEmpty: Ember.computed(function() {
return this.modelArr.length === 0;
})
});

View File

@ -0,0 +1,29 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Ember from 'ember';
export function prependProtocol(params/*, hash*/) {
let address = params[0];
if (address && address.indexOf('://') < 0) {
address = 'http://' + address;
}
return address;
}
export default Ember.Helper.helper(prependProtocol);

View File

@ -23,39 +23,43 @@
<td>{{attempt.id}}</td>
</tr>
<tr>
<td>Start Time</td>
<td>Started Time</td>
<td>{{attempt.attemptStartedTime}}</td>
</tr>
{{#if attempt.validatedFinishedTs}}
<tr>
<td>Finished Time</td>
<td>{{attempt.validatedFinishedTs}}</td>
</tr>
{{/if}}
<tr>
<td>Elapsed Time</td>
<td>{{attempt.elapsedTime}}</td>
</tr>
<tr>
<td>AM Container Id</td>
<td>{{attempt.appMasterContainerId}}</td>
</tr>
{{#if attempt.IsAmNodeUrl}}
<tr>
<td>AM Node Web UI</td>
<td><a href="{{nodeHttpAddressFormatted}}" target="_blank">{{nodeHttpAddressFormatted}}</a></td>
</tr>
{{/if}}
<tr>
<td>AM Node Id</td>
<td>{{attempt.amNodeId}}</td>
</tr>
{{#if attempt.IsLinkAvailable}}
<tr>
<td>Log</td>
<td><a href="{{attempt.logsLink}}" target="_blank">Link</a></td>
</tr>
{{/if}}
{{#if attempt.attemptState}}
<tr>
<td>Attempt State</td>
<td>{{attempt.attemptState}}</td>
</tr>
{{/if}}
{{#if attempt.elapsedTime}}
{{#if attempt.nodeHttpAddress}}
<tr>
<td>Elapsed Time</td>
<td>{{attempt.elapsedTime}}</td>
<td>AM Node Web UI</td>
<td><a href="{{prepend-protocol attempt.nodeHttpAddress}}" target="_blank">{{attempt.nodeHttpAddress}}</a></td>
</tr>
{{/if}}
{{#if attempt.logsLink}}
<tr>
<td>Log</td>
<td><a href="{{prepend-protocol attempt.logsLink}}" target="_blank">Link</a></td>
</tr>
{{/if}}
</tbody>

View File

@ -19,13 +19,15 @@
<table id="container-table" class="table table-striped table-bordered" cellspacing="0" width="100%" height="100%">
<tbody>
<tr>
<td>Start Time</td>
<td>Started Time</td>
<td>{{container.startedTime}}</td>
</tr>
{{#if container.validatedFinishedTs}}
<tr>
<td>Finished Time</td>
<td>{{container.validatedFinishedTs}}</td>
</tr>
{{/if}}
<tr>
<td>Elapsed Time</td>
<td>{{container.elapsedTime}}</td>
@ -34,21 +36,29 @@
<td>Priority</td>
<td>{{container.priority}}</td>
</tr>
<tr>
<td>Log</td>
<td><a href="{{container.logUrl}}" target="_blank">Link</a></td>
</tr>
{{#if container.containerExitStatus}}
<tr>
<td>Exit Status</td>
<td>{{container.containerExitStatus}}</td>
</tr>
{{/if}}
{{#if container.containerState}}
<tr>
<td>State</td>
<td>{{container.containerState}}</td>
</tr>
{{/if}}
{{#if container.nodeHttpAddress}}
<tr>
<td>NodeManager UI</td>
<td><a href="{{container.nodeHttpAddress}}" target="_blank">{{container.nodeHttpAddress}}</a></td>
<td><a href="{{prepend-protocol container.nodeHttpAddress}}" target="_blank">{{container.nodeHttpAddress}}</a></td>
</tr>
{{/if}}
{{#if container.logUrl}}
<tr>
<td>Log</td>
<td><a href="{{prepend-protocol container.logUrl}}" target="_blank">Link</a></td>
</tr>
{{/if}}
</tbody>
</table>

View File

@ -25,30 +25,49 @@
Containers
{{/if}}
</div>
<div class="panel-body">
<br/><br/>
<div class="col-md-8 container-fluid" id={{parent-id}}>
</div>
<!-- diag info -->
<div class="col-md-4 container-fluid">
<div class="panel panel-default add-ellipsis">
<div class="panel-heading">
{{#if selected.link}}
{{#link-to selected.linkname selected.id}}{{selected.id}}{{/link-to}}
{{else}}
{{selected.id}}
{{/if}}
{{#if isDataEmpty}}
<ul class="nav nav-tabs" role="tablist">
<li class="active">
<a href="#graphViewTab" role="tab" data-toggle="tab">Graph View</a>
</li>
<li class="">
<a href="#gridViewTab" role="tab" data-toggle="tab">Grid View</a>
</li>
</ul>
<div class="panel-body">
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="graphViewTab">
<br/><br/>
<div class="col-md-8 container-fluid" id={{parent-id}}></div>
<!-- diag info -->
<div class="col-md-4 container-fluid">
<div class="panel panel-default add-ellipsis">
<div class="panel-heading">
{{#if selected.link}}
{{#link-to selected.linkname selected.id}}{{selected.id}}{{/link-to}}
{{else}}
{{selected.id}}
{{/if}}
</div>
{{#if attemptModel}}
{{app-attempt-table attempt=selected}}
{{else}}
{{container-table container=selected}}
{{/if}}
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="gridViewTab">
{{em-table columns=gridColumns rows=gridRows}}
</div>
{{#if attemptModel}}
{{app-attempt-table attempt=selected}}
{{else}}
{{container-table container=selected}}
{{/if}}
</div>
</div>
</div>
{{else}}
<div class="panel-body">
<h4 class="text-center">No data available!</h4>
</div>
{{/if}}
</div>
</div>
{{outlet}}
{{outlet}}

View File

@ -0,0 +1,28 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { prependProtocol } from '../../../helpers/prepend-protocol';
import { module, test } from 'qunit';
module('Unit | Helper | prepend protocol');
// Replace this with your real tests.
test('it works', function(assert) {
let result = prependProtocol(42);
assert.ok(result);
});