http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/6d19e129/odf/odf-web/src/main/webapp/scripts/odf-metadata-browser.js
----------------------------------------------------------------------
diff --git a/odf/odf-web/src/main/webapp/scripts/odf-metadata-browser.js 
b/odf/odf-web/src/main/webapp/scripts/odf-metadata-browser.js
new file mode 100755
index 0000000..d7072dd
--- /dev/null
+++ b/odf/odf-web/src/main/webapp/scripts/odf-metadata-browser.js
@@ -0,0 +1,661 @@
+/**
+ *
+ * Licensed 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.
+ */
+var $ = require("jquery");
+var React = require("react");
+var ReactBootstrap = require("react-bootstrap");
+
+var Panel = ReactBootstrap.Panel;
+var Table = ReactBootstrap.Table;
+var Label = ReactBootstrap.Label;
+var Image = ReactBootstrap.Image;
+var Modal = ReactBootstrap.Modal;
+var Button = ReactBootstrap.Button;
+var FormControls = ReactBootstrap.FormControls;
+var ListGroup = ReactBootstrap.ListGroup;
+var ListGroupItem = ReactBootstrap.ListGroupItem;
+
+var ODFGlobals = require("./odf-globals.js");
+var UISpec = require("./odf-ui-spec.js");
+var AJAXCleanupMixin = require("./odf-mixins.js");
+var Utils = require("./odf-utils.js")
+var AtlasHelper = Utils.AtlasHelper;
+var URLHelper = Utils.URLHelper;
+
+var ODFBrowser = {
+
+       //set rowReferences property and pass an array of atlas references {id 
: ..., repositoryId: ...}, these rows will then be fetched
+       //or set rowAssets property and pass an array of data that is supposed 
to be displayed as is
+       ODFPagingTable : React.createClass({
+
+               mixins : [AJAXCleanupMixin],
+
+               getInitialState : function(){
+                       var pageSize = (this.props.pageSize ? 
this.props.pageSize : 5);
+                       var rowReferences = this.props.rowReferences;
+                       var rowAssets = this.props.rowAssets;
+                       var max = (rowReferences ? rowReferences.length : 
(rowAssets ? rowAssets.length : 0));
+                       var pageRows = (rowAssets ? rowAssets.slice(0, 
pageSize) : null);
+
+                       return {
+                               pageRows : pageRows,
+                               max : 0, tablePage : 0,
+                               pageSize : pageSize,
+                               max: max,
+                               tablePage: 0,
+                               rowReferenceLoadingAborts : []
+                       };
+               },
+
+               componentDidMount : function() {
+                       if(this.props.rowReferences){
+                               var pagerowReferences = 
this.props.rowReferences.slice(0, this.state.pageSize);
+                               this.loadRows(pagerowReferences);
+                       }
+               },
+
+               componentWillReceiveProps : function(nextProps){
+                       if(!this.isMounted()){
+                               return;
+                       }
+                       this.setStateFromProps(nextProps);
+               },
+
+               setStateFromProps : function(nextProps){
+                       if(nextProps.rowReferences && 
!Utils.arraysEqual(this.props.rowReferences, nextProps.rowReferences)){
+                               this.setState({max: 
nextProps.rowReferences.length, tablePage: 0});
+                               var pagerowReferences = 
nextProps.rowReferences.slice(0, this.state.pageSize);
+                               this.loadRows(pagerowReferences);
+                       }else if(nextProps.rowAssets && 
!Utils.arraysEqual(this.props.rowAssets, nextProps.rowAssets)){
+                               var rows = nextProps.rowAssets.slice(0, 
this.state.pageSize);
+                               this.setState({pageRows : rows, max: 
nextProps.rowAssets.length, tablePage: 0});
+                       }
+               },
+
+               getType : function(){
+                       if(this.props.assetType){
+                               return this.props.assetType;
+                       }else if(this.state.pageRows && 
this.state.pageRows.length > 0 && this.state.pageRows[0].type){
+                               return this.state.pageRows[0].type;
+                       }
+               },
+
+               getUISpec : function(){
+                       if(this.props.spec){
+                               return this.props.spec;
+                       }
+                       return UISpec[this.getType()];
+               },
+
+               loadRows : function(rowReferences){
+                       $.each(this.state.rowReferenceLoadingAborts, 
function(key, abort){
+                               if(abort && abort.call){
+                                       abort.call();
+                               }
+                       });
+
+                       this.setState({pageRows: [], rowReferenceLoadingAborts: 
[]});
+
+                       var reqs = AtlasHelper.loadAtlasAssets(rowReferences,
+                               function(rowAsset){
+                                       var rowData = this.state.pageRows;
+                                       rowData.push(rowAsset);
+                                       if(this.isMounted()){
+                                               this.setState({pageRows : 
rowData});
+                                       }
+                                       if(rowReferences && rowData && 
rowData.length == rowReferences.length && this.props.onLoad){
+                                               this.props.onLoad(rowData);
+                                       }
+                               }.bind(this),
+                               function(err){
+
+                               }
+                       );
+                       var aborts = [];
+                       $.each(reqs, function(key, val){
+                               var aborts = 
this.state.rowReferenceLoadingAborts;
+                               aborts.push(val.abort);
+                               this.setState({rowReferenceLoadingAborts: 
aborts});
+                       }.bind(this));
+
+                       this.storeAbort(aborts);
+               },
+
+               previousPage : function(){
+                       if(this.state.tablePage > -1){
+                               var tablePage = this.state.tablePage - 1;
+                               this.setState({tablePage : tablePage});
+                               if(this.props.rowAssets){
+                                       var rows = 
this.props.rowAssets.slice(tablePage * this.state.pageSize, (tablePage + 1) * 
this.state.pageSize)
+                                       this.setState({pageRows : rows});
+                               }else if(this.props.rowReferences){
+                                       var rowRefs = 
this.props.rowReferences.slice(tablePage * this.state.pageSize, (tablePage + 1) 
* this.state.pageSize)
+                                       this.loadRows(rowRefs);
+                               }
+                       }
+               },
+
+               nextPage : function(){
+                       var max = this.state.max;
+                       if((this.state.tablePage * this.state.pageSize) < max){
+                               var tablePage = this.state.tablePage + 1;
+                               this.setState({tablePage : tablePage});
+                               if(this.props.rowAssets){
+                                       var rows = 
this.props.rowAssets.slice(tablePage * this.state.pageSize, (tablePage + 1) * 
this.state.pageSize)
+                                       this.setState({pageRows : rows})
+                               }else if(this.props.rowReferences){
+                                       var rows = 
this.props.rowReferences.slice(tablePage * this.state.pageSize, (tablePage + 1) 
* this.state.pageSize)
+                                       this.loadRows(rows);
+                               }
+                       }
+               },
+
+               sortAlphabetical : function(a, b){
+                       if(this.getUISpec() && this.getUISpec().attributes){
+                               var attrs = this.getUISpec().attributes;
+                               var sortProp = null;
+                               for(var no = 0; no < attrs.length; no++){
+                                       if(attrs[no].sort == true){
+                                               sortProp = attrs[no].key;
+                                               var aProp = 
a[sortProp].toLowerCase();
+                                               var bProp = 
b[sortProp].toLowerCase();
+                                               return ((aProp < bProp) ? -1 : 
((aProp > bProp) ? 1 : 0));
+                                       }
+                               }
+                       }
+                       return 0;
+               },
+
+               onRowClick : function(rowData){
+                       if(this.props.onRowClick){
+                               var type = this.getType();
+                               if(type){
+                                       //new type is singular of list type ...
+                                       type = type.substring(0, type.length - 
1);
+                               }
+                               this.props.onRowClick(rowData, type);
+                       }
+               },
+
+               parseValue : function(value){
+                   if (value) {
+                       if(Array.isArray(value)){
+                           return value.length;
+                       }else if(typeof value === "object" && value.id && 
value.url && value.repositoryId){
+                           return <a href={value.url}>{value.id}</a>;
+                       }else if(typeof value === "object"){
+                           return JSON.stringify(value);
+                       }
+                   }
+                       return value;
+               },
+
+               render : function(){
+                       var loadingImg = <Image src="img/lg_proc.gif" rounded 
/>;
+                       var contentRows = [];
+                       var pageIndicator = "(0-0)";
+                       if(this.state.pageRows){
+                               loadingImg = null;
+                               this.state.pageRows.sort(this.sortAlphabetical);
+                               $.each(this.state.pageRows, function(key, 
rowData){
+                                       var displayProperties = [];
+                                       var icon = null;
+                                       if(this.getUISpec()){
+                                               displayProperties = 
this.getUISpec().attributes;
+                                               if(this.getUISpec().icon){
+                                                       icon = 
this.getUISpec().icon;
+                                               }
+                                       }else{
+                                               $.each(rowData, 
function(propName, val){
+                                                       var label = propName;
+                                                       if(label && label[0]){
+                                                               label = 
label[0].toUpperCase() + label.slice(1);
+                                                       }
+                                                       
displayProperties.push({key: propName, label: label});
+                                               });
+                                       }
+
+                                       var colCss = {};
+                                       if(this.props.actions){
+                                               colCss.paddingTop = "26px";
+                                       }
+                                       var columns = [<td style={colCss} 
key={"iconCol" + key}>{icon}</td>];
+                                       $.each(displayProperties,
+                                               function(key, propObj){
+                                                       //properties can be a 
path such as prop1.prop2
+                                               var value = 
ODFGlobals.getPathValue(rowData, propObj.key);
+
+                                                       if(propObj.func){
+                                                               value = 
propObj.func(value, rowData);
+                                                       }else{
+                                                               value = 
this.parseValue(value);
+                                                       }
+
+                                                       var col = <td 
style={colCss} key={propObj.key}>{value}</td>;
+                                                       columns.push(col);
+                                               }.bind(this)
+                                       );
+
+                                       if(this.props.actions){
+                                               var btns = [];
+                                               $.each(this.props.actions, 
function(key, obj){
+                                                       
if(obj.assetType.indexOf(this.getType()) > -1){
+                                                                       
$.each(obj.actions, function(actKey, action){
+                                                                               
if((action.filter && action.filter(rowData)) || !action.filter){
+                                                                               
        var btn = <div key={actKey}><Button 
onClick={function(e){e.stopPropagation(); 
action.func(rowData);}}>{action.label}</Button><br/></div>;
+                                                                               
        btns.push(btn);
+                                                                               
}
+                                                                       });
+                                                       }
+                                               }.bind(this));
+                                               columns.push(<td 
key={"actionBtns"}>{btns}</td>);
+                                       }
+
+                                       var rowCss = {};
+                                       if(this.props.onRowClick){
+                                               rowCss.cursor = "pointer";
+                                       }
+
+                                       var row = <tr style={rowCss} 
onClick={function(){this.onRowClick(rowData);}.bind(this)} key={key}>
+                                                               {columns}
+                                                         </tr>;
+                                       contentRows.push(row);
+                               }.bind(this));
+
+                               var max = this.state.max;
+                               var min = (max > 0 ? (this.state.tablePage * 
this.state.pageSize + 1)  : 0);
+                               pageIndicator = "(" + min + "-";
+                               if((this.state.tablePage + 1) * 
this.state.pageSize >= max){
+                                       pageIndicator += max + ")";
+                               }else{
+                                       pageIndicator += (this.state.tablePage 
+ 1) * this.state.pageSize + ")";
+                               }
+                       }
+
+                       var header = [];
+                       var lbls = [""];
+
+                       if(this.getUISpec()){
+                               $.each(this.getUISpec().attributes, 
function(key, propObj){
+                                       lbls.push(propObj.label);
+                               });
+                       }else if(this.state.pageRows && 
this.state.pageRows.length > 0){
+                               $.each(this.state.pageRows[0], function(key, 
val){
+                                       lbls.push(key[0].toUpperCase() + 
key.slice(1));
+                               });
+                       }
+                       if(this.props.actions){
+                               lbls.push("Actions");
+                       }
+
+                       $.each(lbls, function(key, val){
+                                       var headerCss = null;
+                                       if(val == "Actions"){
+                                               headerCss = {paddingLeft: 
"38px"};
+                                       }
+                                       var th = <th style={headerCss} 
key={key}>{val}</th>;
+                                       header.push(th);
+                               });
+
+                       return <div style={this.props.style}>
+                                       <div style={{minHeight:250}}>
+                                       <Table responsive>
+                                               <thead>
+                                                       <tr>
+                                                               {header}
+                                                       </tr>
+                                               </thead>
+                                               <tbody>
+                                                       {contentRows}
+                                               </tbody>
+                                       </Table>
+                                       </div>
+                                       <Button 
disabled={(this.state.pageRows==null || this.state.tablePage <= 0 )} 
onClick={this.previousPage}>previous</Button>
+                                       <span>
+                                               {pageIndicator}
+                                       </span>
+                                       <Button 
disabled={(this.state.pageRows==null || (this.state.tablePage + 1) * 
this.state.pageSize >= this.state.max)} onClick={this.nextPage}>next</Button>
+                               </div>;
+               }
+       }),
+
+       ODFAssetDetails : React.createClass({
+
+               mixins : [AJAXCleanupMixin],
+
+               onHide : function(){
+                       if(this.props.onHide){
+                               this.props.onHide();
+                       }
+                       if(this.isMounted()){
+                               this.setState({show: true});
+                       }
+               },
+
+               getInitialState : function(){
+                       return {
+                                       show : true
+                                       };
+               },
+
+               getType : function(){
+                       if(this.props.assetType){
+                               return this.props.assetType;
+                       }else if(this.props.asset && this.props.asset.type){
+                               return this.props.asset.type;
+                       }
+                       return null;
+               },
+
+               getUISpec : function(asset){
+                       return UISpec[this.getType()];
+               },
+
+               getPropertiesByType : function(srcObject, uiSpecAttributes){
+                       var properties = [];
+                       var references = [];
+                       var lists = [];
+                       var objects = [];
+                       if (uiSpecAttributes) {
+                   var label = null;
+                   var func = null;
+                   var key = null;
+                   $.each(uiSpecAttributes, function(index, property){
+                       var value = ODFGlobals.getPathValue(srcObject, 
property.key);
+                       if (value) {
+                           if(property.func){
+                               value = property.func(value, srcObject);
+                           }
+                           var obj = property;
+                           obj.value = value;
+                           if(value && Array.isArray(value)){
+                               lists.push(obj);
+                           }else if(value && value.id && value.repositoryId){
+                               references.push(obj);
+                           } /*else if(typeof value === "object"){
+                               objects.push(obj);
+                           } */else{
+                               properties.push(obj);
+                           }
+                       }
+                   }.bind(this) );
+                       }
+               return {lists: lists, properties: properties, references: 
references, objects: objects};
+               },
+
+               sortPropsByLabelPosition : function(properties, 
uiSpecAttributes){
+                       if(uiSpecAttributes){
+                               properties.sort(function(val1, val2){
+                                       var index1 = -1;
+                                       var index2 = -1;
+                                       for(var no = 0; no < 
uiSpecAttributes.length; no++){
+                                               if(uiSpecAttributes[no].label 
== val1.label){
+                                                       index1 = no;
+                                               }else 
if(uiSpecAttributes[no].label == val2.label){
+                                                       index2 = no
+                                               }
+                                               if(index1 != -1 && index2 != 
-1){
+                                                       break;
+                                               }
+                                       }
+                                       if(index1 > index2){
+                                               return 1;
+                                       }else if(index1 < index2){
+                                               return -1;
+                                       }
+                                       return 0;
+                               });
+                       }
+               },
+
+               createPropertiesJSX : function(properties){
+                       var props = [];
+                       $.each(properties, function(key, val){
+                               var value = val.value;
+                               if(value){
+                                       var prop = <FormControls.Static 
key={key} label={val.label} standalone>{val.value}</FormControls.Static>
+                                       props.push(prop);
+                               }
+                       }.bind(this));
+                       return props;
+               },
+
+               createReferenceJSX : function(references){
+                       var refs = [];
+                       $.each(references, function(key, val){
+                               var prop = <a key={key} 
href={val.value.url}>{val.label}</a>
+                               refs.push(prop);
+                       }.bind(this));
+                       return refs;
+               },
+
+               createObjectJSX : function(objects){
+                       var objs = [];
+                       $.each(objects, function(key, val){
+                               var obj = <span 
key={key}>{JSON.stringify(val.value)}</span>;
+                               objs.push(obj);
+                       }.bind(this));
+
+                       return objs;
+               },
+
+               createTableJSX : function(lists){
+                       var tables = [];
+                       $.each(lists, function(key, val){
+                               var isRemote = false;
+                               var first = val.value[0];
+                               var rowReferences = null;
+                               var rowAssets = null;
+                               if(first && first.id && first.repositoryId){
+                                       rowReferences = val.value;
+                               }else{
+                                       rowAssets = val.value;
+                               }
+
+                               var spec = null;
+                               var label = val.label.toLowerCase();
+                               var type = label;
+                               if(val.uiSpec){
+                                       spec = UISpec[val.uiSpec];
+                               }else{
+                                       spec = UISpec[type];
+                               }
+
+                               var table = <div key={val.label + "_" + key}>
+                                                               
<h3>{val.label}</h3>
+                                                               
<ODFBrowser.ODFPagingTable rowAssets={rowAssets} assetType={type} 
rowReferences={rowReferences} onRowClick={this.props.onReferenceClick} 
spec={spec}/>
+                                                       </div>;
+                               tables.push(table);
+                       }.bind(this));
+
+                       return tables;
+               },
+
+               render : function(){
+                       var loadingOverlay = <div style={{position:"absolute", 
width:"100%", height:"100%", left:"50%", top: "30%"}}><Image 
src="img/lg_proc.gif" rounded /></div>;
+                       if(!this.props.loading){
+                               loadingOverlay = null;
+                       }
+
+                       var tablesPanel = <Panel collapsible 
defaultExpanded={false} header="References">
+                 </Panel>;
+                       var propertiesPanel = <Panel collapsible 
defaultExpanded={false} header="Properties">
+                 </Panel>;
+
+                       if(this.props.asset){
+                               var uiSpecAttrs = 
this.getUISpec(this.props.asset).attributes;
+                               if(!uiSpecAttrs){
+                                       uiSpecAttrs = [];
+                                       $.each(this.props.asset, 
function(propName, val){
+                                               var label = propName;
+                                               if(label && label[0]){
+                                                       label = 
label[0].toUpperCase() + label.slice(1);
+                                               }
+                                               uiSpecAttrs.push({key: 
propName, label: label});
+                                       });
+                               }
+                               var allProps = 
this.getPropertiesByType(this.props.asset, uiSpecAttrs);
+
+                               var properties = allProps.properties;
+                               var references = allProps.references;
+                               var objects = allProps.objects;
+                               var lists = allProps.lists;
+
+                               var props = [];
+                               var refs = [];
+                               var objs = [];
+                               var tables = [];
+
+                               this.sortPropsByLabelPosition(properties, 
uiSpecAttrs);
+                               props = this.createPropertiesJSX(properties);
+                               refs = this.createReferenceJSX(references);
+                               objs = this.createObjectJSX(objects);
+                               tables = this.createTableJSX(lists);
+
+                               if(props.length > 0 || refs.length > 0 || 
objs.length > 0){
+                                       propertiesPanel = <Panel collapsible 
defaultExpanded={true} header="Properties">
+                                                                       {props}
+                                                                       {refs}
+                                                                       {objs}
+                                                                 </Panel>;
+                               }
+
+                               if(tables.length > 0){
+                                       tablesPanel = <Panel collapsible 
defaultExpanded={true} header="References">
+                                                               {tables}
+                                                         </Panel>;
+                               }
+                       }
+
+                       var icon = null;
+                       if(this.getUISpec(this.props.asset) && 
this.getUISpec(this.props.asset).icon){
+                               icon = this.getUISpec(this.props.asset).icon;
+                       }
+
+                   var title = <span>{icon} Details</span>;
+                   if(this.props.asset && this.props.asset.reference){
+                         title = <div>{title} <a target="_blank" 
href={this.props.asset.reference.url}>( {this.props.asset.reference.id} 
)</a></div>;
+                   }
+                       return <Modal show={this.props.show} 
onHide={this.onHide}>
+                                       <Modal.Header closeButton>
+                                  <Modal.Title>{title}</Modal.Title>
+                                       </Modal.Header>
+                                       <Modal.Body>
+                                               {loadingOverlay}
+                                               {propertiesPanel}
+                                               {tablesPanel}
+                                               </Modal.Body>
+                                      <Modal.Footer>
+                                      <Button 
onClick={function(){this.onHide();}.bind(this)}>Close</Button>
+                                      </Modal.Footer>
+                                       </Modal>
+               }
+
+       }),
+
+       //Atlas Metadata browser: either pass an atlas query in the query 
property in order to execute the query and display the results
+       ODFMetadataBrowser : React.createClass({
+
+               mixins : [AJAXCleanupMixin],
+
+               getInitialState : function() {
+                       return ({
+                               assets: null,
+                               loadingAssetDetails: false
+                               });
+               },
+
+               componentWillMount : function() {
+                       if(this.props.selection){
+                               
this.loadSelectionFromAtlas(this.props.selection);
+                       }
+               },
+
+               referenceClick: function(val, type){
+                       if(!type || (type && val.type)){
+                               type = val.type;
+                       }
+                       var selectedAsset = {id: val.reference.id, 
repositoryId: val.reference.repositoryId, type: type};
+                       URLHelper.setUrlHash(selectedAsset);
+               },
+
+               loadSelectionFromAtlas : function(selection){
+                       if(selection){
+                               this.setState({showAssetDetails: true, 
loadingAssetDetails: true});
+                               var sel = selection;
+                               if(!sel.id){
+                                       sel = 
JSON.parse(decodeURIComponent(sel));
+                               }
+
+                               var loading = false;
+                               if(sel.id && sel.repositoryId){
+                                       if(!this.state.assetDetails || 
!this.state.assetDetails.reference || this.state.assetDetails.reference &&
+                                                       
(this.state.assetDetails.reference.id != sel.id ||
+                                                       
this.state.assetDetails.reference.repositoryId != sel.repositoryId)){
+                                               loading = true;
+                                               var req = 
AtlasHelper.loadAtlasAsset(sel,
+                                                               function(data){
+                                                                       
if(!data.type && sel.type){
+                                                                               
data.type = sel.type;
+                                                                       }
+                                                                       var 
state = {
+                                                                               
        assetDetails: data,
+                                                                               
        loadingAssetDetails: false};
+                                                                       
this.setState(state);
+                                                               }.bind(this),
+                                                               function(){
+
+                                                               }
+                                               );
+                                               this.storeAbort(req.abort);
+                                       }
+                               }
+                               if(!loading && this.state.loadingAssetDetails){
+                                       this.setState({loadingAssetDetails: 
false});
+                               }
+                       }
+               },
+
+               componentWillReceiveProps : function(nextProps){
+                       if(!this.isMounted()){
+                               return;
+                       }
+                       var newState = {};
+                       if(nextProps.selection && this.props.selection != 
nextProps.selection){
+                               
this.loadSelectionFromAtlas(nextProps.selection);
+                       }else if(nextProps.selection == null){
+                               newState.assetDetails = null;
+                               newState.showAssetDetails = false;
+                       }
+                       this.setState(newState);
+               },
+
+               render : function(){
+                       var loadingImg = null;
+                       var list = null;
+                       if(this.props.assets){
+                               list = <ODFBrowser.ODFPagingTable 
actions={this.props.actions} rowAssets={this.props.assets} 
onRowClick={this.referenceClick} assetType={this.props.type}/>;
+                       }else{
+                               loadingImg = <Image src="img/lg_proc.gif" 
rounded />;
+                       }
+
+                       return <div>{list}
+                                               {loadingImg}
+                                               <ODFBrowser.ODFAssetDetails 
show={this.state.assetDetails != null || this.state.loadingAssetDetails} 
loading={this.state.loadingAssetDetails} key={(this.state.assetDetails ? 
this.state.assetDetails.id : "0")} onReferenceClick={this.referenceClick} 
asset={this.state.assetDetails} onHide={function(){URLHelper.setUrlHash(); 
this.setState({assetDetails : null})}.bind(this)} />
+                                       </div>;
+               }
+       })
+}
+
+module.exports = ODFBrowser;

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/6d19e129/odf/odf-web/src/main/webapp/scripts/odf-mixins.js
----------------------------------------------------------------------
diff --git a/odf/odf-web/src/main/webapp/scripts/odf-mixins.js 
b/odf/odf-web/src/main/webapp/scripts/odf-mixins.js
new file mode 100755
index 0000000..40c0aa9
--- /dev/null
+++ b/odf/odf-web/src/main/webapp/scripts/odf-mixins.js
@@ -0,0 +1,51 @@
+/**
+ *
+ * Licensed 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.
+ */
+var $ = require("jquery");
+var React = require("react");
+
+var AJAXCleanupMixin = {
+
+       componentWillMount: function() {
+               this.requestAborts = [];
+       },
+
+       storeAborts : function(aborts) {
+               if(Array.isArray(aborts)){
+                       $.each(aborts, function(key, val){
+                               this.storeAbort(val);
+                       }.bind(this));
+               }
+       },
+
+       storeAbort : function(abort) {
+               if(Array.isArray(abort)){
+                       $.each(abort, function(key, val){
+                               this.requestAborts.push(val);
+                       }.bind(this));
+               }else{
+                       this.requestAborts.push(abort);
+               }
+       },
+
+       componentWillUnmount : function() {
+               $.each(this.requestAborts, function(key, val){
+                       if(val && val.call){
+                               val.call();
+                       }
+               });
+       }
+};
+
+module.exports = AJAXCleanupMixin;

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/6d19e129/odf/odf-web/src/main/webapp/scripts/odf-notifications.js
----------------------------------------------------------------------
diff --git a/odf/odf-web/src/main/webapp/scripts/odf-notifications.js 
b/odf/odf-web/src/main/webapp/scripts/odf-notifications.js
new file mode 100755
index 0000000..a3b99ce
--- /dev/null
+++ b/odf/odf-web/src/main/webapp/scripts/odf-notifications.js
@@ -0,0 +1,171 @@
+/**
+ *
+ * Licensed 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.
+ */
+var $ = require("jquery");
+var React = require("react");
+var ReactDOM = require("react-dom");
+var d3 = require("d3");
+var ReactBootstrap = require("react-bootstrap");
+var ReactD3 = require("react-d3-components");
+var ODFGlobals = require("./odf-globals.js");
+var AJAXCleanupMixin = require("./odf-mixins.js");
+var ReactD3 = require("react-d3-components");
+var LineChart = ReactD3.LineChart;
+var Input = ReactBootstrap.Input;
+var Image = ReactBootstrap.Image;
+
+var REFRESH_DELAY = 5000;
+
+var CurrentNotificationsGraph = React.createClass({
+
+       tooltipLine : function(label, data) {
+        return "Arrived notifications " + data.y;
+    },
+
+       render : function(){
+               var lineChart = null;
+
+               if(this.props.values){
+                       var data = [
+                               {
+                                       label: 'Asset notifications',
+                                   values: [ ]
+                                }
+                           ];
+
+                       for(var no = 0; no < this.props.values.length; no++){
+                               data[0].values.push({x : no + 1, y : 
this.props.values[no]});
+                       };
+
+                       lineChart = (<LineChart
+                                       data={data}
+                                                       width={400}
+                                       height={400}
+                                       margin={{top: 10, bottom: 50, left: 50, 
right: 10}}
+                                       tooltipContained
+                                   tooltipHtml={this.tooltipLine}
+                                       shapeColor={"red"}
+                                                       xAxis={{tickValues: []}}
+                                                       />);
+               }
+
+               return (
+                               <div>
+                                       <h4>Number of received 
notifications</h4>
+                                       <h5>(This only works for the node this 
web application is running on.  In a clustered environment, notifications could 
be processed on another node and therefore not be visible here)</h5>
+
+                                       {lineChart}
+                               </div>);
+       }
+
+
+});
+
+var ODFNotificationsGraph = React.createClass({
+       mixins : [AJAXCleanupMixin],
+
+       getInitialState : function(){
+               return {notifications : [], notificationCount : [0]};
+       },
+
+       getNotifications : function(){
+               const url = ODFGlobals.metadataUrl + 
"/notifications?numberOfNotifications=50";
+        var req = $.ajax({
+            url: url,
+            contentType: "application/json",
+            type: 'GET',
+            success: function(data) {
+               this.setState({notifications: data.notifications});
+            }.bind(this),
+            error: function(xhr, status, err) {
+              var msg = "ODF notification request failed, " + err.toString();
+              this.props.alertCallback({type: "danger", message: msg});
+            }.bind(this)
+        });
+
+        this.storeAbort(req.abort);
+       },
+
+       getNotificationCount : function() {
+               const url = ODFGlobals.metadataUrl + "/notifications/count";
+        var req = $.ajax({
+            url: url,
+            contentType: "application/json",
+            type: 'GET',
+            success: function(data) {
+               var current = this.state.notificationCount;
+               if(!current){
+                       current = [];
+               }else if(current.length > 1 && current[current.length - 1] != 
current[current.length - 2]){
+                       this.getNotifications();
+               }
+               if(current.length == 10){
+                       current.splice(0, 1);
+               }
+               current.push(data.notificationCount);
+               this.setState({notificationCount: current});
+            }.bind(this),
+            error: function(xhr, status, err) {
+              var msg = "ODF notification count request failed, " + 
err.toString();
+              this.props.alertCallback({type: "danger", message: msg});
+            }.bind(this)
+        });
+
+        this.storeAbort(req.abort);
+       },
+
+       componentWillMount : function() {
+               this.getNotifications();
+               this.getNotificationCount();
+       },
+
+       componentWillUnmount () {
+           this.refreshInterval && clearInterval(this.refreshInterval);
+           this.refreshInterval = false;
+       },
+
+       componentWillReceiveProps: function(nextProps){
+               if(!nextProps.visible){
+                        this.refreshInterval && 
clearInterval(this.refreshInterval);
+                        this.refreshInterval = false;
+               }else if(!this.refreshInterval){
+                       this.refreshInterval = 
window.setInterval(this.getNotificationCount, REFRESH_DELAY);
+               }
+       },
+       render : function(){
+               var progressIndicator = <Image src="img/lg_proc.gif" rounded />;
+
+               var notificationGraph = null;
+               if(this.state){
+                       progressIndicator = null;
+                       notificationGraph = <CurrentNotificationsGraph 
values={this.state.notificationCount} />;
+               }
+
+               var notificationsValue = "";
+               $.each(this.state.notifications, function(key, val){
+                       notificationsValue +="\n";
+                       notificationsValue += val.type + " , " + 
val.asset.repositoryId + " -- " + val.asset.id;
+               });
+
+               return (
+                               <div>
+                                       {progressIndicator}
+                                       {notificationGraph}
+                                       <textarea disabled style={{width: 
'100%', height: '300px'}} value={notificationsValue} />
+                               </div>);
+
+       }
+});
+
+module.exports = ODFNotificationsGraph;

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/6d19e129/odf/odf-web/src/main/webapp/scripts/odf-request-browser.js
----------------------------------------------------------------------
diff --git a/odf/odf-web/src/main/webapp/scripts/odf-request-browser.js 
b/odf/odf-web/src/main/webapp/scripts/odf-request-browser.js
new file mode 100755
index 0000000..55c053b
--- /dev/null
+++ b/odf/odf-web/src/main/webapp/scripts/odf-request-browser.js
@@ -0,0 +1,154 @@
+/**
+ *
+ * Licensed 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.
+ */
+var $ = require("jquery");
+var React = require("react");
+var ReactBootstrap = require("react-bootstrap");
+
+var ODFAssetDetails = require("./odf-metadata-browser.js").ODFAssetDetails;
+var ODFPagingTable = require("./odf-metadata-browser.js").ODFPagingTable;
+var ODFGlobals = require("./odf-globals.js");
+var AtlasHelper = require("./odf-utils.js").AtlasHelper;
+var URLHelper = require("./odf-utils.js").URLHelper;
+var AJAXCleanupMixin = require("./odf-mixins.js");
+
+var Image = ReactBootstrap.Image;
+
+var ODFRequestBrowser = React.createClass({
+
+       mixins : [AJAXCleanupMixin],
+
+       getInitialState : function(){
+               return {assetDetails : null, loadingAssetDetails: false};
+       },
+
+       getDiscoveryServiceNameFromId(id) {
+               if(!this.props.registeredServices){
+                       return id;
+               }
+               var servicesWithSameId = this.props.registeredServices.filter(
+                function(dsreg) {
+                    return dsreg.id == id;
+                }
+               );
+               if (servicesWithSameId.length > 0) {
+                       return servicesWithSameId[0].name;
+               }
+               return id;
+       },
+
+       loadSelectedRequestStatus: function(requestId){
+               if(requestId){
+                       this.setState({showAssetDetails: true, 
loadingAssetDetails: true});
+
+                       var req = $.ajax({
+                   url: ODFGlobals.analysisUrl + "/" + requestId,
+                   contentType: "application/json",
+                   dataType: 'json',
+                   type: 'GET',
+                   success: function(data) {
+                       $.each(data.serviceRequests, function(key, request){
+                               var serviceName = 
this.getDiscoveryServiceNameFromId(request.discoveryServiceId);
+                               request.discoveryServiceName = serviceName;
+                       }.bind(this));
+
+                      this.setState({assetType: "request", assetDetails: data, 
loadingAssetDetails: false});
+                   }.bind(this),
+                   error: function(data){
+                       this.setState({loadingAssetDetails: false});
+                   }.bind(this)
+               });
+                   this.storeAbort(req.abort);
+
+                       if(this.state.loadingAssetDetails){
+                               this.setState({loadingAssetDetails: false});
+                       }
+               }
+       },
+
+       loadSelectionFromAtlas : function(selection){
+               if(selection){
+                       this.setState({showAssetDetails: true, 
loadingAssetDetails: true});
+                       var sel = selection;
+                       if(!sel.id){
+                               sel = JSON.parse(decodeURIComponent(sel));
+                       }
+
+                       var loading = false;
+                       if(sel.id && sel.repositoryId){
+                               if(!this.state.assetDetails || 
!this.state.assetDetails.reference || this.state.assetDetails.reference &&
+                                               
(this.state.assetDetails.reference.id != sel.id ||
+                                               
this.state.assetDetails.reference.repositoryId != sel.repositoryId)){
+                                               loading = true;
+                                               var req = 
AtlasHelper.loadAtlasAsset(sel,
+                                                       function(data){
+                                                               if(!data.type 
&& sel.type){
+                                                                       
data.type = sel.type;
+                                                               }
+                                                               var state = {
+                                                                               
assetDetails: data, assetType: data.type, loadingAssetDetails: false};
+                                                               
this.setState(state);
+                                                       }.bind(this),
+                                                       function(){
+
+                                                       }
+                                               );
+                                           this.storeAbort(req.abort);
+                               }
+                       }
+
+                       if(!loading && this.state.loadingAssetDetails){
+                               this.setState({loadingAssetDetails: false});
+                       }
+               }
+       },
+
+       componentWillReceiveProps : function(nextProps){
+               if(!this.isMounted()){
+                       return;
+               }
+               var newState = {};
+               if((nextProps.selection && this.props.selection && 
this.props.selection.id != nextProps.selection.id) || (nextProps.selection && 
this.props.selection == null)){
+                       if(nextProps.selection.id && 
nextProps.selection.repositoryId){
+                               
this.loadSelectionFromAtlas(nextProps.selection);
+                       }else{
+                               
this.loadSelectedRequestStatus(nextProps.selection);
+                       }
+               }else if(nextProps.selection == null){
+                       newState.assetDetails = null;
+               }
+               this.setState(newState);
+       },
+
+       rowClick : function(val, type){
+               if(!type || (type && val.type)){
+                       type = val.type;
+               }
+               if(val && val.reference && val.reference.id){
+                       var selectedAsset = {id: val.reference.id, 
repositoryId: val.reference.repositoryId, type: type};
+                       URLHelper.setUrlHash(JSON.stringify(selectedAsset));
+               }else if(val && val.request && val.request.id){
+                       URLHelper.setUrlHash(JSON.stringify({requestId : 
val.request.id}));
+               }
+       },
+
+       render : function(){
+               return <div>
+                                       <ODFPagingTable 
actions={this.props.actions} rowAssets={this.props.assets} 
onRowClick={this.rowClick} assetType="requests"/>
+                                       <ODFAssetDetails 
show={this.state.assetDetails != null || this.state.loadingAssetDetails} 
loading={this.state.loadingAssetDetails} key={(this.state.assetDetails ? 
this.state.assetDetails.id : "0")} onReferenceClick={this.rowClick} 
asset={this.state.assetDetails} assetType={this.state.assetType} 
onHide={function(){URLHelper.setUrlHash(); this.setState({showAssetDetails : 
false})}.bind(this)} />
+                               </div>;
+       }
+});
+
+module.exports = ODFRequestBrowser;

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/6d19e129/odf/odf-web/src/main/webapp/scripts/odf-services.js
----------------------------------------------------------------------
diff --git a/odf/odf-web/src/main/webapp/scripts/odf-services.js 
b/odf/odf-web/src/main/webapp/scripts/odf-services.js
new file mode 100755
index 0000000..cf5314b
--- /dev/null
+++ b/odf/odf-web/src/main/webapp/scripts/odf-services.js
@@ -0,0 +1,251 @@
+/**
+ *
+ * Licensed 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.
+ */
+//js imports
+var $ = require("jquery");
+var bootstrap = require("bootstrap");
+
+var React = require("react");
+var ReactDOM = require("react-dom");
+var LinkedStateMixin = require("react-addons-linked-state-mixin");
+var ReactBootstrap = require("react-bootstrap");
+
+var ODFGlobals = require("./odf-globals.js");
+var AJAXCleanupMixin = require("./odf-mixins.js");
+var configurationStore = require("./odf-utils.js").ConfigurationStore;
+var servicesStore = require("./odf-utils.js").ServicesStore;
+
+var Button = ReactBootstrap.Button;
+var Jumbotron = ReactBootstrap.Jumbotron;
+var Grid = ReactBootstrap.Grid;
+var Row = ReactBootstrap.Row;
+var Col = ReactBootstrap.Col;
+var Table = ReactBootstrap.Table;
+var Modal = ReactBootstrap.Modal;
+var Input = ReactBootstrap.Input;
+var Alert = ReactBootstrap.Alert;
+var Panel = ReactBootstrap.Panel;
+var Label = ReactBootstrap.Label;
+var Input = ReactBootstrap.Input;
+var Image = ReactBootstrap.Image;
+
+var DiscoveryServiceInfo = React.createClass({
+       mixins : [AJAXCleanupMixin],
+
+    testService() {
+        const url = ODFGlobals.servicesUrl + "/" + this.props.dsreg.id;
+        var req = $.ajax({
+            url: url,
+            contentType: "application/json",
+            dataType: 'json',
+            type: 'GET',
+            success: function(data) {
+                var type = "success";
+                if (data.status != "OK") {
+                    type = "danger";
+                }
+                var msg = "Status of ODF service '" + this.props.dsreg.name + 
"' is "+ data.status +" (" + data.message + ")";
+                this.props.alertCallback({type: type, message: msg});
+            }.bind(this),
+            error: function(xhr, status, err) {
+               if(status != "abort" ){
+                       console.error(url, status, err.toString());
+               }
+                   if(this.isMounted()){
+                       var msg = "Service test failed: " + status + ", " + 
err.toString();
+                       this.props.alertCallback({type: "danger", message: 
msg});
+                   }
+            }.bind(this)
+        });
+
+        this.storeAbort(req.abort);
+    },
+
+    deleteService() {
+        const url = ODFGlobals.servicesUrl + "/" + this.props.dsreg.id;
+        $.ajax({
+            url: url,
+            type: 'DELETE',
+            success: function(data) {
+               if(this.isMounted()){
+                       this.props.refreshCallback();
+               }
+            }.bind(this),
+            error: function(xhr, status, err) {
+               if(status != "abort" ){
+                       console.error(url, status, err.toString());
+               }
+               if(this.isMounted()){
+                     var msg = "Service could not be deleted: " + status + ", 
" + err.toString();
+                     this.props.alertCallback({type: "danger", message: msg});
+                         }
+                       }.bind(this)
+        });
+    },
+
+       render() {
+       var icon = "";
+       var imgUrl = this.props.dsreg.iconUrl;
+       //urls will be used directly.
+       if(imgUrl != null && (imgUrl.trim().startsWith("http://";) || 
imgUrl.trim().startsWith("https://";))){
+               icon = imgUrl;
+       }else{
+               icon = ODFGlobals.servicesUrl + "/" + 
encodeURIComponent(this.props.dsreg.id) + "/image";
+       }
+
+       var endpointInfo = <span>No additional information</span>;
+       if (this.props.dsreg.endpoint.type == "Java") {
+            endpointInfo = <span><em>Java class name</em>: 
{this.props.dsreg.endpoint.className}</span>;
+       }
+               return (
+                               <Grid>
+                                 <Row className="show-grid">
+                                   <Col sm={1}>
+                            <div >
+                              <Image src={icon} rounded/>
+                            </div>
+                           </Col>
+                           <Col sm={4}>
+                         <b>{this.props.dsreg.name}</b>
+                         <br/>
+                         {this.props.dsreg.description}
+                         <br/>
+                         <a href={this.props.dsreg.link} 
target="_blank">More</a>
+                             </Col>
+                       <Col sm={5}>
+                         <em>Type</em>: {this.props.dsreg.endpoint.type}
+                         <br/>
+                         {endpointInfo}
+                         <br/>
+                         <em>ID</em>: {this.props.dsreg.id}
+                         <br/>
+                         <em>Protocol</em>: {this.props.dsreg.protocol}
+                       </Col>
+                       <Col sm={2}>
+                         <Button bsStyle="primary" 
onClick={this.testService}>Test</Button>
+                         <br/>
+                        <Button bsStyle="warning" 
onClick={this.deleteService}>Delete</Button>
+                       </Col>
+                        </Row>
+                     </Grid>
+               );
+       }
+});
+
+var AddDiscoveryServiceButton = React.createClass({
+  mixins: [LinkedStateMixin, AJAXCleanupMixin],
+
+  getInitialState() {
+         return({showModal: false, serviceEndpointType: "Spark", 
parallelismCount: 2, serviceInterfaceType: "DataFrame"});
+  },
+
+  open() {
+    this.setState({showModal: true, errorMessage: null});
+  },
+
+  close() {
+    this.setState({showModal: false});
+  },
+
+  addService() {
+
+       var newService = JSON.parse(JSON.stringify(this.state));
+       delete newService.showModal;
+       delete newService.errorMessage
+
+        var sparkEndpoint = {
+        jar: newService.serviceApplication,
+        className: newService.serviceClassName,
+        inputMethod: newService.serviceInterfaceType,
+        runtimeName: newService.serviceEndpointType
+        };
+
+       newService.endpoint = sparkEndpoint;
+
+    delete newService.serviceEndpointType;
+    delete newService.serviceType;
+    delete newService.serviceApplication;
+    delete newService.serviceClassName;
+    delete newService.serviceInterfaceType;
+
+    $.ajax({
+      url: ODFGlobals.servicesUrl,
+      contentType: "application/json",
+      type: 'POST',
+      data: JSON.stringify(newService),
+      success: function(data) {
+                 if(this.isMounted()){
+                         this.close();
+                         this.props.refreshCallback();
+                 }
+      }.bind(this),
+      error: function(xhr, status, err) {
+                 if(this.isMounted()){
+                       var errorMsg = status;
+                       if(xhr.responseJSON && xhr.responseJSON.error){
+                               errorMsg = xhr.responseJSON.error;
+                       }
+                       var msg = "Service could not be added: " + errorMsg + 
", " + err.toString();
+                       this.setState({errorMessage: msg});
+                 }
+      }.bind(this)
+    });
+  },
+
+  render() {
+    var alert = null;
+    if (this.state.errorMessage) {
+       alert = <Alert bsStyle="danger">{this.state.errorMessage}</Alert>;
+    }
+
+       var endpointInput = null;
+       endpointInput = <div>
+                                               <Input type="text" 
valueLink={this.linkState("serviceApplication")} label="Application jar (or 
zip) file"/>
+                                               <Input type="text" 
valueLink={this.linkState("serviceClassName")} label="Class name"/>
+                                   <Input type="select" 
valueLink={this.linkState("serviceInterfaceType")} label="Service interface 
type" placeholder="DataFrame">
+                                       <option 
value="DataFrame">DataFrame</option>
+                                       <option value="Generic">Generic</option>
+                               </Input>
+                       </div>;
+
+         return(
+                               <span>
+                               <Button bsStyle="primary" bsSize="large" 
onClick={this.open}>Add ODF Service</Button>
+                                 <Modal show={this.state.showModal} 
onHide={this.close}>
+                                               <Modal.Header closeButton>
+                                                       <Modal.Title>Add ODF 
Service</Modal.Title>
+                                               </Modal.Header>
+                                               <Modal.Body>
+                                               {alert}
+                                                 <Input type="text" 
ref="serviceName"  valueLink={this.linkState("name")} label="Name"/>
+                                                       <Input type="text" 
valueLink={this.linkState("description")} label="Description"/>
+                                                       <Input type="text" 
valueLink={this.linkState("id")} label="ID"/>
+                                                       <Input type="number" 
valueLink={this.linkState("parallelismCount")} label="Allowed parallel 
requests"/>
+                                                       <Input type="select" 
valueLink={this.linkState("serviceEndpointType")} label="Type" 
placeholder="Spark">
+                                               <option 
value="Spark">Spark</option>
+                                       </Input>
+                                                       {endpointInput}
+                                                       <Input type="text" 
valueLink={this.linkState("iconUrl")} label="Icon (Optional)"/>
+                                                       <Input type="text" 
valueLink={this.linkState("link")} label="Link (Optional)"/>
+                                         </Modal.Body>
+                                   <Modal.Footer>
+                                   <Button bsStyle="primary" 
onClick={this.addService}>Add</Button>
+                                   <Button onClick={this.close}>Cancel</Button>
+                                   </Modal.Footer>
+                            </Modal>
+                               </span>
+                       );
+               }
+});
+module.exports = {DiscoveryServiceInfo: DiscoveryServiceInfo, 
AddDiscoveryServiceButton: AddDiscoveryServiceButton};

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/6d19e129/odf/odf-web/src/main/webapp/scripts/odf-settings.js
----------------------------------------------------------------------
diff --git a/odf/odf-web/src/main/webapp/scripts/odf-settings.js 
b/odf/odf-web/src/main/webapp/scripts/odf-settings.js
new file mode 100755
index 0000000..32802c7
--- /dev/null
+++ b/odf/odf-web/src/main/webapp/scripts/odf-settings.js
@@ -0,0 +1,552 @@
+/**
+ *
+ * Licensed 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.
+ */
+
+//js imports
+var $ = require("jquery");
+var bootstrap = require("bootstrap");
+var React = require("react");
+var ReactDOM = require("react-dom");
+var LinkedStateMixin = require("react-addons-linked-state-mixin");
+var ReactBootstrap = require("react-bootstrap");
+
+var ODFGlobals = require("./odf-globals.js");
+var AJAXCleanupMixin = require("./odf-mixins.js");
+var configurationStore = require("./odf-utils.js").ConfigurationStore;
+var metadataStore = require("./odf-utils.js").MetadataStore;
+
+var Button = ReactBootstrap.Button;
+var Table = ReactBootstrap.Table;
+var Modal = ReactBootstrap.Modal;
+var Input = ReactBootstrap.Input;
+var Alert = ReactBootstrap.Alert;
+var Panel = ReactBootstrap.Panel;
+var Label = ReactBootstrap.Label;
+var Input = ReactBootstrap.Input;
+var Image = ReactBootstrap.Image;
+var Tabs = ReactBootstrap.Tabs;
+var Tab = ReactBootstrap.Tab;
+
+var ODFConfigPage = React.createClass({
+  mixins: [LinkedStateMixin, AJAXCleanupMixin],
+
+  getInitialState() {
+      return ({odfconfig: { odf: {} }, showDeleteConfirmationDialog: false});
+  },
+
+  componentWillMount() {
+    this.loadODFConfig();
+  },
+
+  componentWillUnmount() {
+         this.props.alertCallback({type: ""});
+  },
+
+  // all the properties we display under the "odf" path
+  relevantODFPropList: ["instanceId", "odfUrl", "odfUser", "odfPassword", 
"consumeMessageHubEvents", "atlasMessagehubVcap", "runAnalysisOnImport", 
"runNewServicesOnRegistration"],
+
+  loadODFConfig() {
+     var req = configurationStore.readConfig(
+              function(data) {
+                // only "fish out" the properties we display and add them as
+                // toplevel properties to the state.
+
+                // if we have to make more complex updates this will no longer 
work
+                var newStateObj = {};
+                for (var i=0; i<this.relevantODFPropList.length; i++) {
+                        var prop = this.relevantODFPropList[i];
+                        if (data[prop]) {
+                                newStateObj[prop] = data[prop];
+                        }
+                }
+                this.setState( newStateObj );
+              }.bind(this),
+              this.props.alertCallback
+            );
+        metadataStore.getProperties(
+                        function(data) {
+                            this.setState({repositoryId: 
data.STORE_PROPERTY_ID});
+                        }.bind(this)
+        );
+     this.storeAbort(req.abort);
+  },
+
+  saveODFConfig() {
+         var newConfigObj = {};
+         for (var i=0; i<this.relevantODFPropList.length; i++) {
+                var prop = this.relevantODFPropList[i];
+                if (this.state[prop] != null) {
+                        newConfigObj[prop] = this.state[prop];
+                }
+         }
+         var req = configurationStore.updateConfig(newConfigObj,
+                         () => {
+                                         if(this.isMounted()){
+                                                 
this.props.alertCallback({type: "success", message: "Settings saved 
successfully."})
+                                         }
+                                 },
+                         this.props.alertCallback );
+         this.storeAbort(req.abort);
+  },
+
+  createAtlasSampleData() {
+    this.refs.sampleDataButton.disabled = true;
+    $.ajax({
+       url: ODFGlobals.metadataUrl + "/sampledata",
+       type: 'GET',
+       success: function(data) {
+          if(this.isMounted()){
+                  this.refs.sampleDataButton.disabled = false;
+                  this.props.alertCallback({type: "success", message: "Sample 
data created successfully."});
+          }
+          }.bind(this),
+       error: function(xhr, status, err) {
+          if(this.isMounted()){
+                  var msg = "Sample data creation failed failed: " + 
err.toString();
+                  this.props.alertCallback({type: "danger", message: msg});
+                  this.refs.sampleDataButton.disabled = false;
+          }
+          }.bind(this)
+     });
+  },
+
+  deleteAllAtlasData() {
+        this.refs.deleteAllDataButton.disabled = true;
+        $.ajax({
+              url: ODFGlobals.metadataUrl + "/resetalldata",
+              type: 'POST',
+              success: function(data) {
+                  if(this.isMounted()){
+                          this.refs.deleteAllDataButton.disabled = false;
+                          this.props.alertCallback({type: "success", message: 
"All data removed!"});
+                          this.closeDeleteConfirmationDialog();
+                  }
+                  }.bind(this),
+              error: function(xhr, status, err) {
+                  if(this.isMounted()){
+                          var msg = "Data deletion failed: " + err.toString();
+                          this.props.alertCallback({type: "danger", message: 
msg});
+                          this.refs.deleteAllDataButton.disabled = false;
+                          this.closeDeleteConfirmationDialog();
+                  }
+                  }.bind(this)
+            });
+  },
+
+  openDeleteConfirmationDialog() {
+         this.setState( { showDeleteConfirmationDialog: true} );
+  },
+
+  closeDeleteConfirmationDialog() {
+         this.setState( { showDeleteConfirmationDialog: false} );
+  },
+
+
+  testAtlasConnection() {
+//       this.props.alertCallback({type: "warning", message: "Test connection 
not implemented yet."});
+    $.ajax({
+       url: ODFGlobals.metadataUrl + "/connectiontest",
+       type: 'GET',
+       success: function(data) {
+          if(this.isMounted()){
+                  this.props.alertCallback({type: "success", message: 
"Connection test successful."});
+          }
+          }.bind(this),
+       error: function(xhr, status, err) {
+          if(this.isMounted()){
+                  var msg = "Connection test failed: " + err.toString();
+                  this.props.alertCallback({type: "danger", message: msg});
+          }
+          }.bind(this)
+     });
+  },
+
+  notificationValue() {
+      if (this.state.runAnalysisOnImport) {
+          return "create";
+      }
+      return "none";
+  },
+
+  notificationsChanged() {
+      var newValue = this.refs.runAnalysisInput.getValue();
+      var val = (newValue != "none");
+      this.setState({runAnalysisOnImport: val});
+  },
+
+  render() {
+    var divStyle = {
+      marginLeft: "20px"
+    };
+    return (
+      <div>
+       <form>
+             <fieldset className="form-group  label-floating">
+                       <legend>General Settings</legend>
+                       <br/>
+                       <h4>Instance</h4>
+                         <div style={divStyle}>
+                           <Input type="text" label="ODF Instance ID" 
valueLink={this.linkState("instanceId")} disabled/>
+                           <Input type="text" label="ODF URL" 
valueLink={this.linkState("odfUrl")}/>
+                           <Input type="text" label="ODF User ID" 
valueLink={this.linkState("odfUser")}/>
+                           <Input type="password" label="ODF Password" 
valueLink={this.linkState("odfPassword")}/>
+                         </div>
+                       <hr/>
+                       <h4>Metadata store</h4>
+                       <div style={divStyle}>
+                         <Input type="text" label="Repository ID" 
valueLink={this.linkState("repositoryId")} disabled/>
+
+                         <div style={divStyle} className="checkbox">
+                          <label>
+                       <input ref="consumeMessageHubEvents" type="checkbox" 
checkedLink={this.linkState("consumeMessageHubEvents")} />
+                       <span className="checkbox-material">
+                               <span className="check"></span>
+                       </span>
+                       &nbsp;&nbsp;Consume events from Messagehub instead of a 
Kafka instance
+                     </label>
+                   </div>
+                         <Input type="text" label="Atlas Messagehub VCAP" 
disabled={(this.state && !this.state["consumeMessageHubEvents"])} 
valueLink={this.linkState("atlasMessagehubVcap")}/>
+                         <Button bsStyle="primary" 
onClick={this.testAtlasConnection}>Test connection</Button>
+                         <Button bsStyle="success" ref="sampleDataButton" 
onClick={this.createAtlasSampleData}>Create Atlas sample data</Button>
+                         <Button bsStyle="danger" ref="deleteAllDataButton" 
onClick={this.openDeleteConfirmationDialog}>Delete all Atlas data</Button>
+                         <Modal show={this.state.showDeleteConfirmationDialog} 
onHide={this.closeDeleteConfirmationDialog}>
+                           <Modal.Header closeButton>
+                             <Modal.Title>Confirm deletion</Modal.Title>
+                           </Modal.Header>
+                           <Modal.Body>
+                             <h4>Are you sure you want to delete all data from 
the metadata repository?</h4>
+                           </Modal.Body>
+                             <Modal.Footer>
+                             <Button onClick={this.deleteAllAtlasData}>Delete 
all Data</Button>
+                             <Button 
onClick={this.closeDeleteConfirmationDialog}>Close</Button>
+                           </Modal.Footer>
+                         </Modal>
+                       </div>
+                       <hr/>
+                       <h4>Notifications</h4>
+                       <div style={divStyle}>
+                         <Input type="select" label="Run analysis 
automatically" ref="runAnalysisInput" onChange={this.notificationsChanged} 
value={this.notificationValue()}>
+                            <option value="none">Never</option>
+                     <option value="create">On create</option>
+                         </Input>
+                       </div>
+                       <div style={divStyle} className="checkbox">
+                          <label>
+                       <input ref="runServicesOnRegInput" type="checkbox" 
checkedLink={this.linkState("runNewServicesOnRegistration")} />
+                       <span className="checkbox-material">
+                               <span className="check"></span>
+                       </span>
+                       &nbsp;&nbsp;Automatically run all newly registered 
services in order to keep asset metadata up-to-date
+                     </label>
+                   </div>
+                       <hr/>
+                       <Button className="btn-raised" bsStyle="primary" 
onClick={this.saveODFConfig}>Save Settings</Button>
+                       <Button onClick={this.loadODFConfig}>Reload</Button>
+               </fieldset>
+        </form>
+      </div>);
+  }
+
+});
+
+
+var SparkConfigPage = React.createClass({
+  mixins: [LinkedStateMixin, AJAXCleanupMixin],
+
+  getInitialState() {
+      return ({"clusterMasterUrl": ""});
+  },
+
+  componentWillMount() {
+    this.loadODFConfig();
+  },
+
+  componentWillUnmount() {
+         this.props.alertCallback({type: ""});
+  },
+
+   loadODFConfig() {
+     var req = configurationStore.readConfig(
+              function(data) {
+                var sparkConfig = {};
+                if(data.sparkConfig != null){
+                        sparkConfig = data.sparkConfig;
+                }
+                this.setState(sparkConfig);
+              }.bind(this),
+              this.props.alertCallback
+            );
+     this.storeAbort(req.abort);
+  },
+
+  saveODFConfig() {
+         var sparkConfig = {clusterMasterUrl: this.state.clusterMasterUrl};
+         var req = configurationStore.updateConfig({"sparkConfig" : 
sparkConfig},
+                         () => {
+                                         if(this.isMounted()){
+                                                 
this.props.alertCallback({type: "success", message: "Spark config saved 
successfully."})
+                                         }
+                                 },
+                         this.props.alertCallback );
+         this.storeAbort(req.abort);
+  },
+
+  render() {
+    var divStyle = {
+      marginLeft: "20px"
+    };
+
+       var sparkSettings =  <div>
+                                               <h4>Local spark cluster</h4>
+                                       <div style={divStyle}>
+                                                 <Input type="text" 
label="Cluster master url" valueLink={this.linkState("clusterMasterUrl")}/>
+                                               </div>
+                                        </div>;
+
+
+    return (
+      <div>
+       <form>
+             <fieldset className="form-group  label-floating">
+                       <legend>Spark configuration</legend>
+                               {sparkSettings}
+                       <Button className="btn-raised" bsStyle="primary" 
onClick={this.saveODFConfig}>Save Settings</Button>
+                       <Button onClick={this.loadODFConfig}>Reload</Button>
+               </fieldset>
+        </form>
+      </div>);
+  }
+
+});
+
+
+var PropertyAddButton = React.createClass({
+  getInitialState() {
+     return ({showModal: false});
+  },
+
+  close() {
+    this.setState({ showModal: false });
+  },
+
+  save() {
+    var newPropObj = {};
+    newPropObj[this.state.name] = this.state.value;
+    var updateConfig = { userDefined: newPropObj };
+    configurationStore.updateConfig(updateConfig,
+               () => { this.props.successCallback();
+                       this.props.alertCallback({type: "success", message: 
"User-defined property added successfully."})
+               },
+               this.props.alertCallback
+    );
+  },
+
+  saveAndClose() {
+    this.save();
+    this.close();
+  },
+
+  open() {
+    this.setState({ showModal: true });
+  },
+
+  handleTextChange() {
+    this.setState({
+          name: this.refs.inputName.getValue(),
+          value: this.refs.inputValue.getValue()
+        });
+  },
+
+  handleClick() {
+      this.open();
+  },
+
+  render: function() {
+    return (<span>
+    <Button bsStyle="primary" className="btn-raised" 
onClick={this.handleClick}>Add</Button>
+      <Modal show={this.state.showModal} onHide={this.close}>
+          <Modal.Header closeButton>
+             <Modal.Title>Add Property</Modal.Title>
+          </Modal.Header>
+          <Modal.Body>
+             <Input type="text" ref="inputName" label="Name" 
onChange={this.handleTextChange}></Input>
+             <Input type="text" ref="inputValue" label="Value" 
onChange={this.handleTextChange}></Input>
+         </Modal.Body>
+         <Modal.Footer>
+         <Button bsStyle="primary" onClick={this.saveAndClose}>Save</Button>
+         <Button onClick={this.close}>Cancel</Button>
+         </Modal.Footer>
+                       </Modal>
+      </span>);
+  }
+
+});
+
+
+var PropertyRemoveButton = React.createClass({
+
+   handleClick() {
+      var newPropObj = {};
+      newPropObj[this.props.name] = null;
+      var updateConfig = { userDefined: newPropObj };
+      configurationStore.updateConfig(updateConfig,
+                 () => { this.props.successCallback();
+                             this.props.alertCallback({type: "success", 
message: "User-defined property removed successfully."});
+                 },
+                 this.props.alertCallback
+      );
+   },
+
+   render() {
+     return (
+                <Button onClick={this.handleClick}>Remove</Button>
+    );
+   }
+
+});
+var PropertyEditButton = React.createClass({
+
+   getInitialState() {
+      return ({showModal: false});
+   },
+
+   close() {
+     this.setState({ showModal: false });
+   },
+
+   save() {
+     var newPropObj = {};
+     newPropObj[this.props.name] = this.state.value;
+     var updateConfig = { userDefined: newPropObj };
+     configurationStore.updateConfig(updateConfig,
+                () => { this.props.successCallback();
+                        this.props.alertCallback({type: "success", message: 
"User-defined property saved successfully."})
+                }, this.props.alertCallback
+     );
+   },
+
+   saveAndClose() {
+     this.save();
+     this.close();
+   },
+
+   open() {
+     this.setState({ showModal: true });
+   },
+
+   handleTextChange() {
+     this.setState({
+           value: this.refs.input.getValue()
+         });
+   },
+
+   handleClick() {
+       this.open();
+   },
+
+   render: function() {
+     return (
+       <span>
+        <Button bsStyle="primary" onClick={this.handleClick}>Edit</Button>
+        <Modal show={this.state.showModal} onHide={this.close}>
+            <Modal.Header closeButton>
+               <Modal.Title>Edit Property</Modal.Title>
+            </Modal.Header>
+            <Modal.Body>
+               <h4>Enter new value for property ''{this.props.name}''</h4>
+               <Input type="text" ref="input" onChange={this.handleTextChange} 
defaultValue={this.props.value}></Input>
+           </Modal.Body>
+           <Modal.Footer>
+           <Button bsStyle="primary" onClick={this.saveAndClose}>Save</Button>
+           <Button onClick={this.close}>Cancel</Button>
+           </Modal.Footer>
+        </Modal>
+       </span>);
+   }
+});
+
+var UserDefinedConfigPage = React.createClass({
+   mixins : [AJAXCleanupMixin],
+
+   getInitialState: function() {
+      return {odfconfig: { userDefined: {}}};
+   },
+
+   loadUserDefConfig: function() {
+     var req = configurationStore.readConfig(
+       function(data) {
+         this.setState( {odfconfig: data} );
+       }.bind(this),
+       this.props.alertCallback
+     );
+
+     this.storeAbort(req.abort);
+   },
+
+   componentDidMount: function() {
+     this.loadUserDefConfig();
+   },
+
+   componentWillUnmount : function() {
+         this.props.alertCallback({type: ""});
+   },
+
+   render: function() {
+     var tableContents = $.map(
+           this.state.odfconfig.userDefined,
+           function(value, name) {
+                  if (value) {
+                        var tdBtnFixStyle = { paddingTop : "26px"};
+
+                 return <tr key={name}>
+                          <td style={tdBtnFixStyle}>{name}</td>
+                          <td style={tdBtnFixStyle}>{value}</td>
+                          <td><PropertyEditButton name={name} value={value} 
successCallback={this.loadUserDefConfig} 
alertCallback={this.props.alertCallback}/>
+                              <PropertyRemoveButton name={name} 
successCallback={this.loadUserDefConfig} 
alertCallback={this.props.alertCallback}/>
+                          </td>
+                        </tr>;
+                  }
+                  // empty element
+                  return null;
+           }.bind(this));
+     return (
+       <div>
+              <form>
+                     <fieldset className="form-group  label-floating">
+                     <legend>
+                              User-defined properties
+                      </legend>
+                      <Table responsive>
+                         <thead>
+                           <tr>
+                             <th>Name</th>
+                             <th>Value</th>
+                             <th></th>
+                           </tr>
+                         </thead>
+                         <tbody>
+                            {tableContents}
+                         </tbody>
+                      </Table>
+                      <PropertyAddButton 
successCallback={this.loadUserDefConfig} 
alertCallback={this.props.alertCallback}/>
+              </fieldset>
+              </form>
+       </div>);
+   }
+
+});
+
+
+
+module.exports = {ODFConfigPage : ODFConfigPage, SparkConfigPage : 
SparkConfigPage, UserDefinedConfigPage: UserDefinedConfigPage} ;

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/6d19e129/odf/odf-web/src/main/webapp/scripts/odf-statistics.js
----------------------------------------------------------------------
diff --git a/odf/odf-web/src/main/webapp/scripts/odf-statistics.js 
b/odf/odf-web/src/main/webapp/scripts/odf-statistics.js
new file mode 100755
index 0000000..ea0e151
--- /dev/null
+++ b/odf/odf-web/src/main/webapp/scripts/odf-statistics.js
@@ -0,0 +1,413 @@
+/**
+ *
+ * Licensed 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.
+ */
+var $ = require("jquery");
+var React = require("react");
+var d3 = require("d3");
+var ReactBootstrap = require("react-bootstrap");
+var ReactD3 = require("react-d3-components");
+
+var AJAXCleanupMixin = require("./odf-mixins.js");
+
+var Image = ReactBootstrap.Image;
+var Panel = ReactBootstrap.Panel;
+var BarChart = ReactD3.BarChart;
+var PieChart = ReactD3.PieChart;
+var LineChart = ReactD3.LineChart;
+
+var ODFGlobals = require("./odf-globals.js");
+
+const GRAPH_REFRESH_DELAY_MS = 4000;
+
+var ODFStats = {
+       CurrentThreadGraph : React.createClass({
+
+               tooltipLine : function(label, data) {
+               return "Running threads " + data.y;
+           },
+
+           xScale : function() {
+               return "";
+           },
+
+               render : function(){
+                       var lineChart = null;
+
+                       if(this.props.threadValues){
+
+                               var data = [
+                                       {
+                                               label: 'Thread count',
+                                           values: [ ]
+                                        }
+                                   ];
+
+                               for(var no = 0; no < 
this.props.threadValues.length; no++){
+                                       data[0].values.push({x : no + 1, y : 
this.props.threadValues[no]});
+                               };
+
+                               lineChart = <LineChart
+                                               data={data}
+                                                               width={400}
+                                               height={400}
+                                               margin={{top: 10, bottom: 50, 
left: 50, right: 10}}
+                                               tooltipContained
+                                           tooltipHtml={this.tooltipLine}
+                                               shapeColor={"red"}
+                                                               
xAxis={{tickValues: []}}
+                                                               />;
+                       }
+
+                       return (
+                                       <div>
+                                               <h4>Currently running threads 
in ODF</h4>
+                                               {lineChart}
+                                       </div>);
+               }
+
+
+       }),
+
+       SystemDiagnostics : React.createClass({
+               mixins : [AJAXCleanupMixin],
+
+               getODFStatus : function(){
+                       var currentState = this.state;
+
+                       const url = ODFGlobals.engineUrl + "/status";
+               var req = $.ajax({
+                   url: url,
+                   contentType: "application/json",
+                   dataType: 'json',
+                   type: 'GET',
+                   success: function(data) {
+                       if(currentState == null){
+                               currentState = { threadValues : [0]};
+                       }
+                       
currentState.threadValues.push(data.threadManagerStatus.length);
+                       if(currentState.threadValues.length > 5){
+                               currentState.threadValues.splice(0, 1);
+                       }
+
+                       this.setState(currentState);
+                   }.bind(this),
+                   error: function(xhr, status, err) {
+                     var msg = "ODF status request failed, " + err.toString();
+                     this.props.alertCallback({type: "danger", message: msg});
+                   }.bind(this)
+               });
+
+               this.storeAbort(req.abort);
+               },
+
+               componentWillMount : function() {
+                       this.getODFStatus();
+               },
+
+               componentWillUnmount () {
+                   this.refreshInterval && clearInterval(this.refreshInterval);
+                   this.refreshInterval = false;
+               },
+
+               componentWillReceiveProps: function(nextProps){
+                       if(!nextProps.visible){
+                                this.refreshInterval && 
clearInterval(this.refreshInterval);
+                                this.refreshInterval = false;
+                       }else if(!this.refreshInterval){
+                               this.refreshInterval = 
window.setInterval(this.getODFStatus, GRAPH_REFRESH_DELAY_MS);
+                       }
+               },
+
+               tooltipLine : function(label, data) {
+               return "Running threads " + data.y;
+           },
+
+           xScale : function() {
+               return "";
+           },
+
+               render : function(){
+                       var progressIndicator = <Image src="img/lg_proc.gif" 
rounded />;
+
+                       var threadGraph = null;
+                       if(this.state){
+                               progressIndicator = null;
+                               threadGraph = <ODFStats.CurrentThreadGraph 
threadValues={this.state.threadValues} />;
+                       }
+
+                       return (
+                                       <div>
+                                               {progressIndicator}
+                                               {threadGraph}
+                                       </div> );
+               }
+       }),
+
+       TotalAnalysisGraph : React.createClass({
+               mixins : [AJAXCleanupMixin],
+
+               getAnalysisStats : function() {
+                       const url = ODFGlobals.analysisUrl + "/stats";
+               var req = $.ajax({
+                   url: url,
+                   contentType: "application/json",
+                   dataType: 'json',
+                   type: 'GET',
+                   success: function(data) {
+                      this.setState(data);
+                   }.bind(this),
+                   error: function(xhr, status, err) {
+                     var msg = "Analysis stats request failed, " + 
err.toString();
+                     this.props.alertCallback({type: "danger", message: msg});
+                   }.bind(this)
+               });
+
+               this.storeAbort(req.abort);
+               },
+
+               componentWillMount : function() {
+                       this.getAnalysisStats();
+               },
+
+               componentWillUnmount () {
+                   this.refreshInterval && clearInterval(this.refreshInterval);
+                   this.refreshInterval = false;
+               },
+
+               componentWillReceiveProps: function(nextProps){
+                       if(!nextProps.visible){
+                                this.refreshInterval && 
clearInterval(this.refreshInterval);
+                                this.refreshInterval = false;
+                       }else if(!this.refreshInterval){
+                               this.refreshInterval = 
window.setInterval(this.getAnalysisStats, GRAPH_REFRESH_DELAY_MS);
+                       }
+               },
+
+               tooltipPie : function(x, y) {
+                   return y.toString() + " absolute";
+               },
+
+               render : function() {
+                       var progressIndicator = <Image src="img/lg_proc.gif" 
rounded />;
+                       var pieChart = null;
+
+                       if(this.state){
+                               progressIndicator = null;
+                               var succ = (this.state.success ? 
this.state.success : (this.state.failure ? 0 : 100));
+                               var fail = (this.state.failure ? 
this.state.failure : 0);
+                               var onePercent = (succ + fail) / 100;
+
+                               var succVal = (onePercent == 0 ? 100 : (succ / 
onePercent)).toFixed(2);
+                               var failVal = (onePercent == 0 ? 0 : (fail / 
onePercent)).toFixed(2);
+
+                               var pieData = {label: "Total success and 
failure",
+                                                               values : [{x: 
"Finished requests (" + succVal + " %)", y: succ},
+                                                                         {x: 
"Failed requests (" + failVal + " %)", y: fail}
+                                                                       ]
+                                                               };
+
+                               var colorScale = 
d3.scale.ordinal().range(["lightgreen", "#F44336"]);
+
+                               var pieStyle = {opacity : "1 !important"};
+                               pieChart = (<PieChart
+                           data={pieData}
+                           width={800}
+                           height={400}
+                           margin={{top: 10, bottom: 10, left: 200, right: 
200}}
+                           tooltipHtml={this.tooltipPie}
+                           tooltipOffset={{top: 175, left: 200}}
+                                               tooltipMode={"fixed"}
+                                               style={pieStyle}
+                                       colorScale={colorScale}
+                           />);
+                       }
+                       return (
+                                       <div>
+                                               <h4>Total analysis requests and 
failures</h4>
+                                       {progressIndicator}
+                                               {pieChart}
+                                               <hr />
+                                       </div>);
+               }
+       }),
+
+       PerServiceStatusGraph : React.createClass({
+               mixins : [AJAXCleanupMixin],
+
+               getServiceStatus : function() {
+                       const url = ODFGlobals.servicesUrl + "/status";
+               var req = $.ajax({
+                   url: url,
+                   contentType: "application/json",
+                   dataType: 'json',
+                   type: 'GET',
+                   success: function(data) {
+                      this.setState(data);
+                   }.bind(this),
+                   error: function(xhr, status, err) {
+                     var msg = "Analysis stats request failed, " + 
err.toString();
+                     this.props.alertCallback({type: "danger", message: msg});
+                   }.bind(this)
+               });
+
+               this.storeAbort(req.abort);
+               },
+
+               componentWillMount : function() {
+                       this.getServiceStatus();
+               },
+
+               componentWillUnmount () {
+                   this.refreshInterval && clearInterval(this.refreshInterval);
+                   this.refreshInterval = false;
+               },
+
+               componentWillReceiveProps: function(nextProps){
+                       if(!nextProps.visible){
+                                this.refreshInterval && 
clearInterval(this.refreshInterval);
+                                this.refreshInterval = false;
+                       }else if(!this.refreshInterval){
+                               this.refreshInterval = 
window.setInterval(this.getServiceStatus, GRAPH_REFRESH_DELAY_MS);
+                       }
+               },
+
+               tooltip : function(x, y0, y, total) {
+                       var barData = this.getBarData();
+                       var text = y;
+                       var name = null;
+                       if(barData && barData.length > 0){
+                               $.map(barData, function(res){
+                                       var bData = barData;
+                                       $.map(res.values, function(val){
+                                               if(val.x == x && val.y == y){
+                                                       name = val.fullName;
+                                               }
+                                       });
+                               });
+                       }
+
+                       var tooltipStyle = {top : "-20px", position: 
"absolute", left: "-100px", "minWidth" : "350px"};
+
+                       if(name == null){
+                               tooltipStyle.left = 0;
+                       }
+
+                   return (
+                               <div style={tooltipStyle}>
+                                       <span>{name}, {text}</span>
+                               </div>
+                               );
+               },
+
+               getBarData : function(){
+                       if(this.state && !$.isEmptyObject(this.state)){
+                               var currentState = this.state;
+                               var statusMap = {};
+                               $.map(currentState, function(res){
+                                       var states = res.statusCountMap;
+                                       $.each(states, function(state, count){
+                                               var currentArr = 
statusMap[state];
+                                               if(currentArr === undefined){
+                                                       currentArr = [];
+                                               }
+
+                                               var lbl = (res.name ? res.name 
: res.id);
+                                               //only shorten names if more 
than 1 bar is displayed
+                                               if(currentState && 
Object.keys(currentState) && Object.keys(currentState).length > 1 && lbl && 
lbl.length > 17){
+                                                       lbl = lbl.substring(0, 
17) + "..";
+                                               }
+
+                                               currentArr.push({"x" : lbl, 
"y": count, "fullName" : res.name});
+                                               statusMap[state] = currentArr;
+                                       });
+                               });
+
+                               var barData = [];
+
+                               $.each(statusMap, function(key, val){
+                                       barData.push({"label" : key, "values" : 
val});
+                               });
+
+                               barData = barData.reverse();
+                               return barData;
+                       }else{
+                               return [ { "label" : "No data available", 
"values" : [{"x" : "No data availbale", "y" : 0}]}];
+                       }
+               },
+
+               getLegend : function(barData, colors){
+                       var lbls = [];
+                       for(var no = 0; no < barData.length; no++){
+                               lbls.push(<div key={no} ><span style={{color: 
colors[no]}}>{barData[no].label}</span><br/></div>);
+                       };
+
+                       return (
+                                       <div style={{float:"right"}}>
+                                               {lbls}
+                                       </div>
+                               );
+               },
+
+               render : function() {
+                       var progressIndicator = <Image src="img/lg_proc.gif" 
rounded />;
+                       var barChart = null;
+
+                       if(this.state){
+                               progressIndicator = null;
+                               var barData = this.getBarData();
+
+                               var barStyle = {marginTop: "50px"};
+
+                               var barChart = (<BarChart
+                                 width={400}
+                         height={400}
+                         margin={{top: 70, bottom: 50, left: 50, right: 10}}
+                                 tooltipHtml={this.tooltip}
+                             tooltipMode={"element"}/>);
+
+                               //cancelled, initialized, error, running, in 
queue, finished
+                               var colors = ["black", "#F44336", "lightgreen", 
"blue", "lightblue", "grey"];
+                               var colorScale = 
d3.scale.ordinal().range(colors);
+
+                               if(barData != null){
+                                       var barWidth = 
(Object.keys(this.state).length >= 2 ? Object.keys(this.state).length * 200 : 
400);
+
+                                       barChart = (
+                                                               <div 
style={barStyle}>
+                                                                       
{this.getLegend(barData, colors)}
+                                                                       
<BarChart
+                                                                         
data={barData}
+                                                                         
width={barWidth}
+                                                                 height={400}
+                                                                     
colorScale={colorScale}
+                                                                 margin={{top: 
30, bottom: 50, left: 50, right: 10}}
+                                                                         
tooltipHtml={this.tooltip}
+                                                                     
tooltipMode={"element"}
+                                                                       />
+                                                               </div>
+                                                       );
+                               }
+                       }
+
+                       return (
+                                       <div>
+                                               <h4>Analysis runs per 
service</h4>
+                                       {progressIndicator}
+                                               {barChart}
+                                       </div>);
+                       }
+               })
+}
+
+module.exports = ODFStats;

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/6d19e129/odf/odf-web/src/main/webapp/scripts/odf-ui-spec.js
----------------------------------------------------------------------
diff --git a/odf/odf-web/src/main/webapp/scripts/odf-ui-spec.js 
b/odf/odf-web/src/main/webapp/scripts/odf-ui-spec.js
new file mode 100755
index 0000000..a6bc381
--- /dev/null
+++ b/odf/odf-web/src/main/webapp/scripts/odf-ui-spec.js
@@ -0,0 +1,316 @@
+/**
+ *
+ * Licensed 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.
+ */
+var $ = require("jquery");
+var React = require("react");
+var ReactBootstrap = require("react-bootstrap");
+
+var Label = ReactBootstrap.Label;
+var ListGroup = ReactBootstrap.ListGroup;
+var ListGroupItem = ReactBootstrap.ListGroupItem;
+var Glyphicon = ReactBootstrap.Glyphicon;
+
+/*
+ * for every data type a UI specification can be created.
+ * A UI specification is an array of objects.
+ *
+ * Normally, the key of a property will be used to find a matching ui spec.
+ * This can be overwritten by defining the uiSpec attribute on a property 
object.
+ *
+ * Each object requires a key to identify the property and a label that will 
be displayed
+ * In order to manipulate the value or how it is displayed, a property object 
can pass a function to the func attribute.
+ * This function will be called with the property value and the object as 
parameters.
+ *
+ * Properties with an array as their value will automatically be displayed in 
a grid.
+ * A UI specification that is used for a grid can have a property object with 
the attribute sort:true, causing the tablet to be sorted alphabetically on this 
property
+*/
+
+var UISpec = {
+
+               DefaultDocument : {
+                       attributes: [
+                                   {key: "name", label: "Name"},
+                                   {key: "description", label: "Description"},
+                                   {key: "type", label: "Type"}],
+                   icon: <Glyphicon glyph="question-sign" />
+               },
+
+               DefaultDocuments : {
+                       attributes: [
+                                   {key: "name", label: "Name"},
+                                   {key: "description", label: "Description"},
+                                   {key: "type", label: "Type"}],
+                   icon: <Glyphicon glyph="question-sign" />
+               },
+
+               Document : {
+                       attributes: [{key: "reference.id", label: "ID"},
+                           {key: "name", label: "Name"},
+                           {key: "type", label: "Type"}],
+                   icon: <Glyphicon glyph="file" />
+               },
+
+               Documents : {
+                       attributes: [{key: "name", label: "Name"},
+                           {key: "description", label: "Description"},
+                           {key: "columns", label: "Columns"} ,
+                           {key: "annotations", label: "Annotations",
+                                func: function(val){
+                                        if(!val){
+                                                return 0;
+                                        }
+                                        return val.length;
+                                }
+                            }],
+                  icon: <Glyphicon glyph="file" />
+               },
+
+               DataFile : {
+                       attributes: [{key: "name", label: "Name"},
+                                 {key: "description", label: "Description"},
+                                 {key: "columns", label: "Columns"} ,
+                                 {key: "annotations", label: "Annotations"}],
+                       icon: <Glyphicon glyph="list-alt" />
+               },
+
+               DataFiles: {
+                       attributes: [{key: "name", label: "Name"},
+                            {key: "columns", label: "Columns"} ,
+                            {key: "annotations", label: "Annotations",
+                                func: function(val){
+                                        if(!val){
+                                                return 0;
+                                        }
+                                        return val.length;
+                                }
+                            }],
+                   icon: <Glyphicon glyph="list-alt" />
+               },
+
+               Table : {
+                       attributes: [{key: "schema", label: "Schema"},
+                        {key: "name", label: "Name"},
+                        {key:"description", label: "Description"},
+                        {key:"columns", label: "Columns"} ,
+                        {key: "annotations", label: "Annotations"}],
+           icon: <Glyphicon glyph="th" />
+               },
+
+               Tables : {
+                       attributes: [ // {key: "schema", label: "Schema"},
+                 {key: "name", label: "Name"},
+                         {key: "columns", label: "Columns"} ,
+                         {key: "annotations", label: "Annotations",
+                                 func: function(val){
+                                         if(!val){
+                                                 return 0;
+                                                 }
+                                         return val.length;
+                                       }
+                         }],
+           icon: <Glyphicon glyph="th" />
+               },
+
+               column : {
+                       attributes: [{key: "name", label: "Name"},
+                         {key: "dataType", label: "Datatype"},
+                         {key: "annotations", label: "Annotations"}],
+
+                   icon: <Glyphicon glyph="th-list" />
+               },
+
+               columns : {
+                       attributes: [{key: "name", label: "Name", sort: true},
+                          {key: "dataType", label: "Datatype"},
+                          {key: "annotations", label: "Annotations",
+                                         func: function(val){
+                                                 if(!val){
+                                                         return 0;
+                                                 }
+                                                 return val.length;
+                                               }
+                                 }],
+                   icon: <Glyphicon glyph="th-list" />
+               },
+
+               //InferredDataClass     AnalysisRun     AnnotationType  
JavaClass       Annotations     AnnotatedObject Reference       JsonProperties
+               annotation : {
+                       attributes: [{key: "annotationType", label: "Annotation 
type"},
+
+                             // see Infosphere DQ service: 
ColumnAnalysisTableAnnotation
+                      {key: "dataClassDistribution", label: "Data Class 
Distribution",
+                                 func: function(val) {
+                                     if (val) {
+                                         return 
<span>{JSON.stringify(val)}</span>;
+                                     }
+                                 }
+                      },
+                      // see Infosphere DQ service: 
ColumnAnalysisColumnAnnotation
+                             {key: "inferredDataClass", label: "Data Class",
+                               func: function(val) {
+                                    if (val) {
+                                if(val.className){
+                                    var confidence = "";
+                                    if (val.confidenceThreshold) {
+                                        confidence = " 
("+val.confidenceThreshold+")";
+                                    }
+                                    return 
<span>{val.className}{confidence}</span>;
+                                }
+                                return <span>{JSON.stringify(val)}</span>;
+                                    }
+                          }
+                             },
+                      {key: "qualityScore", label: "Data Quality Score"},
+
+                             // see alchemy taxonomy service: 
TaxonomyDiscoveryService.TaxonomyAnnotation
+                             {key: "label", label: "Category"},
+                      {key: "score", label: "Score"},
+
+                             {key: "analysisRun", label:"Analysis"},
+                             {key: "jsonProperties", label: "Properties"}
+                             ],
+                   icon: <Glyphicon glyph="tag" />
+               },
+
+               annotations : {
+                       attributes: [{key: "annotationType", label: "Annotation 
type"},
+                                    {key: "analysisRun", label:"Analysis"}],
+                       icon: <Glyphicon glyph="tag" />
+               },
+
+           request : {
+               attributes:[
+                      {key: "request.id", label: "Request ID"},
+                      {key: "state", label: "Status",
+                          func: function(val){
+                                  var btnCss = {};
+                              var statusLabel = <Label 
bsStyle="warning">Unknown</Label>;
+                              if (val == "ACTIVE") {
+                                 statusLabel = <Label 
bsStyle="info">Active</Label>;
+                              } else if (val== "QUEUED") {
+                                 statusLabel = <Label 
bsStyle="info">Queued</Label>;
+                              } else if (val== "CANCELLED") {
+                                 statusLabel = <Label 
bsStyle="warning">Cancelled</Label>;
+                              } else if (val== "FINISHED") {
+                                 statusLabel = <Label 
bsStyle="success">Finished</Label>;
+                              } else if (val== "ERROR") {
+                                 statusLabel = <Label 
bsStyle="danger">Error</Label>;
+                              }
+                              return statusLabel;
+                          }},
+                  {key: "request.dataSets", label: "Data sets", uiSpec: 
"DefaultDocuments"},
+                  {key: "totalTimeOnQueues", label: "Total time on queues", 
func: function(val){
+                          if(val){
+                                       var x = val / 1000;
+                                   var seconds = Math.floor(x % 60);
+                                   x /= 60;
+                                   var minutes = Math.floor(x % 60);
+                                   x /= 60;
+                                   var hours = Math.floor(x % 24);
+
+                                   return hours + "h " + minutes + "m " + 
seconds + " s";
+                          }
+                          return "";
+                  }},
+                  {key: "totalTimeProcessing", label: "Total time processing", 
func: function(val){
+                          if(val){
+                                       var x = val / 1000;
+                                   var seconds = Math.floor(x % 60);
+                                   x /= 60;
+                                   var minutes = Math.floor(x % 60);
+                                   x /= 60;
+                                   var hours = Math.floor(x % 24);
+
+                                   return hours + "h " + minutes + "m " + 
seconds + " s";
+                          }
+                          return "";
+                  }},
+                  {key: "totalTimeStoringAnnotations", label: "Total time 
storing results", func: function(val){
+                          if(val){
+                                       var x = val / 1000;
+                                   var seconds = Math.floor(x % 60);
+                                   x /= 60;
+                                   var minutes = Math.floor(x % 60);
+                                   x /= 60;
+                                   var hours = Math.floor(x % 24);
+
+                                   return hours + "h " + minutes + "m " + 
seconds + " s";
+                          }
+                          return "";
+                  }},
+                      {key: "serviceRequests", label: "Service Sequence", 
func: function(val, obj){
+                          var serviceNames = [];
+                          var services = [];
+                          for (var i=0; i<val.length; i++) {
+                              var dsreq = val[i];
+                              var dsName = dsreq.discoveryServiceName;
+                              if(serviceNames.indexOf(dsName) == -1){
+                                  serviceNames.push(dsName);
+                                  services.push(<span 
key={dsName}>{dsName}<br/></span>);
+                              }
+                          }
+
+                          return <em>{services}</em>;
+                       }
+                      },
+                      {key: "details", label: "Status Details"}
+                      ],
+                               icon: <Glyphicon glyph="play-circle" />
+           },
+
+           requests : {
+               attributes: [
+                              {key: "request.id", label: "Request ID"},
+                              {key: "status", label: "Status",
+                                  func: function(val){
+                                      var statusLabel = <Label 
bsStyle="warning">Unknown</Label>;
+                                      if (val == "INITIALIZED") {
+                                         statusLabel = <Label 
bsStyle="info">Initialized</Label>;
+                                      } else if (val== 
"IN_DISCOVERY_SERVICE_QUEUE") {
+                                         statusLabel = <Label 
bsStyle="info">Queued</Label>;
+                                      } else if (val== 
"DISCOVERY_SERVICE_RUNNING") {
+                                          statusLabel = <Label 
bsStyle="info">Running</Label>;
+                                      } else if (val== "CANCELLED") {
+                                         statusLabel = <Label 
bsStyle="warning">Cancelled</Label>;
+                                      } else if (val== "FINISHED") {
+                                         statusLabel = <Label 
bsStyle="success">Finished</Label>;
+                                      } else if (val== "ERROR") {
+                                         statusLabel = <Label 
bsStyle="danger">Error</Label>;
+                                      }
+                                      return statusLabel;
+                                  }},
+                              {key: "lastModified", label: "Last modified", 
func: function(val){
+                                  return new Date(val).toLocaleString();
+                              }},
+                              {key: "discoveryServiceRequests", label: 
"Service sequence", func: function(val, obj){
+                                  var serviceNames = [];
+                                  var services = [];
+                                  for (var i=0; i<val.length; i++) {
+                                      var dsreq = val[i];
+                                      var dsName = dsreq.discoveryServiceName;
+                                      if(serviceNames.indexOf(dsName) == -1){
+                                          serviceNames.push(dsName);
+                                          services.push(<span 
key={dsName}>{dsName}<br/></span>);
+                                      }
+                                  }
+
+                                  return <ListGroup>{services}</ListGroup>;
+                              }},
+                              {key: "statusDetails", label: "Status Details"}
+                              ],
+               icon: <Glyphicon glyph="play-circle" />
+           }
+};
+
+module.exports = UISpec;


Reply via email to