You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@atlas.apache.org by ma...@apache.org on 2017/06/28 05:57:15 UTC
[02/25] incubator-atlas git commit: ATLAS-1898: initial commit of ODF
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>
+ 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>
+ 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;