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;
