You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@falcon.apache.org by sr...@apache.org on 2015/04/01 13:10:32 UTC
[05/21] falcon git commit: FALCON-790 Falcon UI to enable
entity/process/feed edits and management. Contributed by Armando
Reyna/Kenneth Ho
http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/js/lib/xml2json.min.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/lib/xml2json.min.js b/falcon-ui/app/js/lib/xml2json.min.js
new file mode 100644
index 0000000..12c4a45
--- /dev/null
+++ b/falcon-ui/app/js/lib/xml2json.min.js
@@ -0,0 +1,578 @@
+/*
+ Copyright 2011-2013 Abdulla Abdurakhmanov
+ Original sources are available at https://code.google.com/p/x2js/
+
+ 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.
+ */
+
+function X2JS(config) {
+ 'use strict';
+
+ var VERSION = "1.1.7";
+
+ config = config || {};
+ initConfigDefaults();
+ initRequiredPolyfills();
+
+ function initConfigDefaults() {
+ if(config.escapeMode === undefined) {
+ config.escapeMode = true;
+ }
+ config.attributePrefix = config.attributePrefix || "_";
+ config.arrayAccessForm = config.arrayAccessForm || "none";
+ config.emptyNodeForm = config.emptyNodeForm || "text";
+ if(config.enableToStringFunc === undefined) {
+ config.enableToStringFunc = true;
+ }
+ config.arrayAccessFormPaths = config.arrayAccessFormPaths || [];
+ if(config.skipEmptyTextNodesForObj === undefined) {
+ config.skipEmptyTextNodesForObj = true;
+ }
+ if(config.stripWhitespaces === undefined) {
+ config.stripWhitespaces = true;
+ }
+ config.datetimeAccessFormPaths = config.datetimeAccessFormPaths || [];
+
+ if(config.useDoubleQuotes === undefined) {
+ config.useDoubleQuotes = false;
+ }
+ }
+
+ var DOMNodeTypes = {
+ ELEMENT_NODE : 1,
+ TEXT_NODE : 3,
+ CDATA_SECTION_NODE : 4,
+ COMMENT_NODE : 8,
+ DOCUMENT_NODE : 9
+ };
+
+ function initRequiredPolyfills() {
+ function pad(number) {
+ var r = String(number);
+ if ( r.length === 1 ) {
+ r = '0' + r;
+ }
+ return r;
+ }
+ // Hello IE8-
+ if(typeof String.prototype.trim !== 'function') {
+ String.prototype.trim = function() {
+ return this.replace(/^\s+|^\n+|(\s|\n)+$/g, '');
+ }
+ }
+ if(typeof Date.prototype.toISOString !== 'function') {
+ // Implementation from http://stackoverflow.com/questions/2573521/how-do-i-output-an-iso-8601-formatted-string-in-javascript
+ Date.prototype.toISOString = function() {
+ return this.getUTCFullYear()
+ + '-' + pad( this.getUTCMonth() + 1 )
+ + '-' + pad( this.getUTCDate() )
+ + 'T' + pad( this.getUTCHours() )
+ + ':' + pad( this.getUTCMinutes() )
+ + ':' + pad( this.getUTCSeconds() )
+ + '.' + String( (this.getUTCMilliseconds()/1000).toFixed(3) ).slice( 2, 5 )
+ + 'Z';
+ };
+ }
+ }
+
+ function getNodeLocalName( node ) {
+ var nodeLocalName = node.localName;
+ if(nodeLocalName == null) // Yeah, this is IE!!
+ nodeLocalName = node.baseName;
+ if(nodeLocalName == null || nodeLocalName=="") // =="" is IE too
+ nodeLocalName = node.nodeName;
+ return nodeLocalName;
+ }
+
+ function getNodePrefix(node) {
+ return node.prefix;
+ }
+
+ function escapeXmlChars(str) {
+ if(typeof(str) == "string")
+ return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
+ else
+ return str;
+ }
+
+ function unescapeXmlChars(str) {
+ return str.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, '&');
+ }
+
+ function toArrayAccessForm(obj, childName, path) {
+ switch(config.arrayAccessForm) {
+ case "property":
+ if(!(obj[childName] instanceof Array))
+ obj[childName+"_asArray"] = [obj[childName]];
+ else
+ obj[childName+"_asArray"] = obj[childName];
+ break;
+ /*case "none":
+ break;*/
+ }
+
+ if(!(obj[childName] instanceof Array) && config.arrayAccessFormPaths.length > 0) {
+ var idx = 0;
+ for(; idx < config.arrayAccessFormPaths.length; idx++) {
+ var arrayPath = config.arrayAccessFormPaths[idx];
+ if( typeof arrayPath === "string" ) {
+ if(arrayPath == path)
+ break;
+ }
+ else
+ if( arrayPath instanceof RegExp) {
+ if(arrayPath.test(path))
+ break;
+ }
+ else
+ if( typeof arrayPath === "function") {
+ if(arrayPath(obj, childName, path))
+ break;
+ }
+ }
+ if(idx!=config.arrayAccessFormPaths.length) {
+ obj[childName] = [obj[childName]];
+ }
+ }
+ }
+
+ function fromXmlDateTime(prop) {
+ // Implementation based up on http://stackoverflow.com/questions/8178598/xml-datetime-to-javascript-date-object
+ // Improved to support full spec and optional parts
+ var bits = prop.split(/[-T:+Z]/g);
+
+ var d = new Date(bits[0], bits[1]-1, bits[2]);
+ var secondBits = bits[5].split("\.");
+ d.setHours(bits[3], bits[4], secondBits[0]);
+ if(secondBits.length>1)
+ d.setMilliseconds(secondBits[1]);
+
+ // Get supplied time zone offset in minutes
+ if(bits[6] && bits[7]) {
+ var offsetMinutes = bits[6] * 60 + Number(bits[7]);
+ var sign = /\d\d-\d\d:\d\d$/.test(prop)? '-' : '+';
+
+ // Apply the sign
+ offsetMinutes = 0 + (sign == '-'? -1 * offsetMinutes : offsetMinutes);
+
+ // Apply offset and local timezone
+ d.setMinutes(d.getMinutes() - offsetMinutes - d.getTimezoneOffset())
+ }
+ else
+ if(prop.indexOf("Z", prop.length - 1) !== -1) {
+ d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds()));
+ }
+
+ // d is now a local time equivalent to the supplied time
+ return d;
+ }
+
+ function checkFromXmlDateTimePaths(value, childName, fullPath) {
+ if(config.datetimeAccessFormPaths.length > 0) {
+ var path = fullPath.split("\.#")[0];
+ var idx = 0;
+ for(; idx < config.datetimeAccessFormPaths.length; idx++) {
+ var dtPath = config.datetimeAccessFormPaths[idx];
+ if( typeof dtPath === "string" ) {
+ if(dtPath == path)
+ break;
+ }
+ else
+ if( dtPath instanceof RegExp) {
+ if(dtPath.test(path))
+ break;
+ }
+ else
+ if( typeof dtPath === "function") {
+ if(dtPath(obj, childName, path))
+ break;
+ }
+ }
+ if(idx!=config.datetimeAccessFormPaths.length) {
+ return fromXmlDateTime(value);
+ }
+ else
+ return value;
+ }
+ else
+ return value;
+ }
+
+ function parseDOMChildren( node, path ) {
+ if(node.nodeType == DOMNodeTypes.DOCUMENT_NODE) {
+ var result = new Object;
+ var nodeChildren = node.childNodes;
+ // Alternative for firstElementChild which is not supported in some environments
+ for(var cidx=0; cidx <nodeChildren.length; cidx++) {
+ var child = nodeChildren.item(cidx);
+ if(child.nodeType == DOMNodeTypes.ELEMENT_NODE) {
+ var childName = getNodeLocalName(child);
+ result[childName] = parseDOMChildren(child, childName);
+ }
+ }
+ return result;
+ }
+ else
+ if(node.nodeType == DOMNodeTypes.ELEMENT_NODE) {
+ var result = new Object;
+ result.__cnt=0;
+
+ var nodeChildren = node.childNodes;
+
+ // Children nodes
+ for(var cidx=0; cidx <nodeChildren.length; cidx++) {
+ var child = nodeChildren.item(cidx); // nodeChildren[cidx];
+ var childName = getNodeLocalName(child);
+
+ if(child.nodeType!= DOMNodeTypes.COMMENT_NODE) {
+ result.__cnt++;
+ if(result[childName] == null) {
+ result[childName] = parseDOMChildren(child, path+"."+childName);
+ toArrayAccessForm(result, childName, path+"."+childName);
+ }
+ else {
+ if(result[childName] != null) {
+ if( !(result[childName] instanceof Array)) {
+ result[childName] = [result[childName]];
+ toArrayAccessForm(result, childName, path+"."+childName);
+ }
+ }
+ (result[childName])[result[childName].length] = parseDOMChildren(child, path+"."+childName);
+ }
+ }
+ }
+
+ // Attributes
+ for(var aidx=0; aidx <node.attributes.length; aidx++) {
+ var attr = node.attributes.item(aidx); // [aidx];
+ result.__cnt++;
+ result[config.attributePrefix+attr.name]=attr.value;
+ }
+
+ // Node namespace prefix
+ var nodePrefix = getNodePrefix(node);
+ if(nodePrefix!=null && nodePrefix!="") {
+ result.__cnt++;
+ result.__prefix=nodePrefix;
+ }
+
+ if(result["#text"]!=null) {
+ result.__text = result["#text"];
+ if(result.__text instanceof Array) {
+ result.__text = result.__text.join("\n");
+ }
+ //if(config.escapeMode)
+ // result.__text = unescapeXmlChars(result.__text);
+ if(config.stripWhitespaces)
+ result.__text = result.__text.trim();
+ delete result["#text"];
+ if(config.arrayAccessForm=="property")
+ delete result["#text_asArray"];
+ result.__text = checkFromXmlDateTimePaths(result.__text, childName, path+"."+childName);
+ }
+ if(result["#cdata-section"]!=null) {
+ result.__cdata = result["#cdata-section"];
+ delete result["#cdata-section"];
+ if(config.arrayAccessForm=="property")
+ delete result["#cdata-section_asArray"];
+ }
+
+ if( result.__cnt == 1 && result.__text!=null ) {
+ result = result.__text;
+ }
+ else
+ if( result.__cnt == 0 && config.emptyNodeForm=="text" ) {
+ result = '';
+ }
+ else
+ if ( result.__cnt > 1 && result.__text!=null && config.skipEmptyTextNodesForObj) {
+ if( (config.stripWhitespaces && result.__text=="") || (result.__text.trim()=="")) {
+ delete result.__text;
+ }
+ }
+ delete result.__cnt;
+
+ if( config.enableToStringFunc && (result.__text!=null || result.__cdata!=null )) {
+ result.toString = function() {
+ return (this.__text!=null? this.__text:'')+( this.__cdata!=null ? this.__cdata:'');
+ };
+ }
+
+ return result;
+ }
+ else
+ if(node.nodeType == DOMNodeTypes.TEXT_NODE || node.nodeType == DOMNodeTypes.CDATA_SECTION_NODE) {
+ return node.nodeValue;
+ }
+ }
+
+ function startTag(jsonObj, element, attrList, closed) {
+ var resultStr = "<"+ ( (jsonObj!=null && jsonObj.__prefix!=null)? (jsonObj.__prefix+":"):"") + element;
+ if(attrList!=null) {
+ for(var aidx = 0; aidx < attrList.length; aidx++) {
+ var attrName = attrList[aidx];
+ var attrVal = jsonObj[attrName];
+ if(config.escapeMode)
+ attrVal=escapeXmlChars(attrVal);
+ resultStr+=" "+attrName.substr(config.attributePrefix.length)+"=";
+ if(config.useDoubleQuotes)
+ resultStr+='"'+attrVal+'"';
+ else
+ resultStr+="'"+attrVal+"'";
+ }
+ }
+ if(!closed)
+ resultStr+=">";
+ else
+ resultStr+="/>";
+ return resultStr;
+ }
+
+ function endTag(jsonObj,elementName) {
+ return "</"+ (jsonObj.__prefix!=null? (jsonObj.__prefix+":"):"")+elementName+">";
+ }
+
+ function endsWith(str, suffix) {
+ return str.indexOf(suffix, str.length - suffix.length) !== -1;
+ }
+
+ function jsonXmlSpecialElem ( jsonObj, jsonObjField ) {
+ if((config.arrayAccessForm=="property" && endsWith(jsonObjField.toString(),("_asArray")))
+ || jsonObjField.toString().indexOf(config.attributePrefix)==0
+ || jsonObjField.toString().indexOf("__")==0
+ || (jsonObj[jsonObjField] instanceof Function) )
+ return true;
+ else
+ return false;
+ }
+
+ function jsonXmlElemCount ( jsonObj ) {
+ var elementsCnt = 0;
+ if(jsonObj instanceof Object ) {
+ for( var it in jsonObj ) {
+ if(jsonXmlSpecialElem ( jsonObj, it) )
+ continue;
+ elementsCnt++;
+ }
+ }
+ return elementsCnt;
+ }
+
+ function parseJSONAttributes ( jsonObj ) {
+ var attrList = [];
+ if(jsonObj instanceof Object ) {
+ for( var ait in jsonObj ) {
+ if(ait.toString().indexOf("__")== -1 && ait.toString().indexOf(config.attributePrefix)==0) {
+ attrList.push(ait);
+ }
+ }
+ }
+ return attrList;
+ }
+
+ function parseJSONTextAttrs ( jsonTxtObj ) {
+ var result ="";
+
+ if(jsonTxtObj.__cdata!=null) {
+ result+="<![CDATA["+jsonTxtObj.__cdata+"]]>";
+ }
+
+ if(jsonTxtObj.__text!=null) {
+ if(config.escapeMode)
+ result+=escapeXmlChars(jsonTxtObj.__text);
+ else
+ result+=jsonTxtObj.__text;
+ }
+ return result;
+ }
+
+ function parseJSONTextObject ( jsonTxtObj ) {
+ var result ="";
+
+ if( jsonTxtObj instanceof Object ) {
+ result+=parseJSONTextAttrs ( jsonTxtObj );
+ }
+ else
+ if(jsonTxtObj!=null) {
+ if(config.escapeMode)
+ result+=escapeXmlChars(jsonTxtObj);
+ else
+ result+=jsonTxtObj;
+ }
+
+ return result;
+ }
+
+ function parseJSONArray ( jsonArrRoot, jsonArrObj, attrList ) {
+ var result = "";
+ if(jsonArrRoot.length == 0) {
+ result+=startTag(jsonArrRoot, jsonArrObj, attrList, true);
+ }
+ else {
+ for(var arIdx = 0; arIdx < jsonArrRoot.length; arIdx++) {
+ result+=startTag(jsonArrRoot[arIdx], jsonArrObj, parseJSONAttributes(jsonArrRoot[arIdx]), false);
+ result+=parseJSONObject(jsonArrRoot[arIdx]);
+ result+=endTag(jsonArrRoot[arIdx],jsonArrObj);
+ }
+ }
+ return result;
+ }
+
+ function parseJSONObject ( jsonObj ) {
+ var result = "";
+
+ var elementsCnt = jsonXmlElemCount ( jsonObj );
+
+ if(elementsCnt > 0) {
+ for( var it in jsonObj ) {
+
+ if(jsonXmlSpecialElem ( jsonObj, it) )
+ continue;
+
+ var subObj = jsonObj[it];
+
+ var attrList = parseJSONAttributes( subObj )
+
+ if(subObj == null || subObj == undefined) {
+ result+=startTag(subObj, it, attrList, true);
+ }
+ else
+ if(subObj instanceof Object) {
+
+ if(subObj instanceof Array) {
+ result+=parseJSONArray( subObj, it, attrList );
+ }
+ else if(subObj instanceof Date) {
+ result+=startTag(subObj, it, attrList, false);
+ result+=subObj.toISOString();
+ result+=endTag(subObj,it);
+ }
+ else {
+ var subObjElementsCnt = jsonXmlElemCount ( subObj );
+ if(subObjElementsCnt > 0 || subObj.__text!=null || subObj.__cdata!=null) {
+ result+=startTag(subObj, it, attrList, false);
+ result+=parseJSONObject(subObj);
+ result+=endTag(subObj,it);
+ }
+ else {
+ result+=startTag(subObj, it, attrList, true);
+ }
+ }
+ }
+ else {
+ result+=startTag(subObj, it, attrList, false);
+ result+=parseJSONTextObject(subObj);
+ result+=endTag(subObj,it);
+ }
+ }
+ }
+ result+=parseJSONTextObject(jsonObj);
+
+ return result;
+ }
+
+ this.parseXmlString = function(xmlDocStr) {
+ var isIEParser = window.ActiveXObject || "ActiveXObject" in window;
+ if (xmlDocStr === undefined) {
+ return null;
+ }
+ var xmlDoc;
+ if (window.DOMParser) {
+ var parser=new window.DOMParser();
+ var parsererrorNS = null;
+ // IE9+ now is here
+ if(!isIEParser) {
+ try {
+ parsererrorNS = parser.parseFromString("INVALID", "text/xml").childNodes[0].namespaceURI;
+ }
+ catch(err) {
+ parsererrorNS = null;
+ }
+ }
+ try {
+ xmlDoc = parser.parseFromString( xmlDocStr, "text/xml" );
+ if( parsererrorNS!= null && xmlDoc.getElementsByTagNameNS(parsererrorNS, "parsererror").length > 0) {
+ //throw new Error('Error parsing XML: '+xmlDocStr);
+ xmlDoc = null;
+ }
+ }
+ catch(err) {
+ xmlDoc = null;
+ }
+ }
+ else {
+ // IE :(
+ if(xmlDocStr.indexOf("<?")==0) {
+ xmlDocStr = xmlDocStr.substr( xmlDocStr.indexOf("?>") + 2 );
+ }
+ xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
+ xmlDoc.async="false";
+ xmlDoc.loadXML(xmlDocStr);
+ }
+ return xmlDoc;
+ };
+
+ this.asArray = function(prop) {
+ if (prop === undefined || prop == null)
+ return [];
+ else
+ if(prop instanceof Array)
+ return prop;
+ else
+ return [prop];
+ };
+
+ this.toXmlDateTime = function(dt) {
+ if(dt instanceof Date)
+ return dt.toISOString();
+ else
+ if(typeof(dt) === 'number' )
+ return new Date(dt).toISOString();
+ else
+ return null;
+ };
+
+ this.asDateTime = function(prop) {
+ if(typeof(prop) == "string") {
+ return fromXmlDateTime(prop);
+ }
+ else
+ return prop;
+ };
+
+ this.xml2json = function (xmlDoc) {
+ return parseDOMChildren ( xmlDoc );
+ };
+
+ this.xml_str2json = function (xmlDocStr) {
+ var xmlDoc = this.parseXmlString(xmlDocStr);
+ if(xmlDoc!=null)
+ return this.xml2json(xmlDoc);
+ else
+ return null;
+ };
+
+ this.json2xml_str = function (jsonObj) {
+ return parseJSONObject ( jsonObj );
+ };
+
+ this.json2xml = function (jsonObj) {
+ var xmlDocStr = this.json2xml_str (jsonObj);
+ return this.parseXmlString(xmlDocStr);
+ };
+
+ this.getVersion = function () {
+ return VERSION;
+ };
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/js/services/common/falcon-api.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/services/common/falcon-api.js b/falcon-ui/app/js/services/common/falcon-api.js
new file mode 100644
index 0000000..cfb1ddc
--- /dev/null
+++ b/falcon-ui/app/js/services/common/falcon-api.js
@@ -0,0 +1,128 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function () {
+ 'use strict';
+
+ var falconModule = angular.module('app.services.falcon', ['app.services.x2js', 'ngCookies']);
+
+ falconModule.factory('Falcon', ["$http", "X2jsService", function ($http, X2jsService) {
+
+ var Falcon = {},
+ NUMBER_OF_RESULTS = 50;
+
+ function buildURI(uri) {
+ var paramSeparator = (uri.indexOf('?') !== -1) ? '&' : '?';
+ uri = uri + paramSeparator + 'user.name=ambari-qa';
+ return uri;
+ }
+
+ //-------------Server RESPONSE----------------------//
+ Falcon.responses = {
+ display:true,
+ queue:[],
+ count: {pending: 0, success:0, error:0},
+ multiRequest: {cluster:0, feed:0, process:0},
+ listLoaded: {cluster:false, feed:false, process:false}
+ };
+
+ Falcon.logRequest = function () {
+ Falcon.responses.count.pending = Falcon.responses.count.pending + 1;
+
+ };
+ Falcon.logResponse = function (type, messageObject, entityType, hide) {
+ if(type === 'success') {
+ if(!hide) {
+ var message = { success: true, status: messageObject.status, message: messageObject.message, requestId: messageObject.requestId};
+ Falcon.responses.queue.push(message);
+ Falcon.responses.count.success = Falcon.responses.count.success +1;
+ }
+ Falcon.responses.count.pending = Falcon.responses.count.pending -1;
+ }
+ if(type === 'error') {
+
+ if(messageObject.slice(0,6) !== "Cannot") {
+ var errorMessage = X2jsService.xml_str2json(messageObject),
+ message = { success: false, status: errorMessage.result.status, message: errorMessage.result.message, requestId: errorMessage.result.requestId};
+ }
+ else {
+ var message = { success: false, status: "No connection", message: messageObject, requestId: "no ID"};
+ }
+ Falcon.responses.queue.push(message);
+ Falcon.responses.count.error = Falcon.responses.count.error +1;
+ Falcon.responses.count.pending = Falcon.responses.count.pending -1;
+ }
+ if(entityType !== false) {
+ entityType = entityType.toLowerCase();
+ Falcon.responses.multiRequest[entityType] = Falcon.responses.multiRequest[entityType] - 1;
+ }
+
+ };
+ Falcon.removeMessage = function (index) {
+ if(Falcon.responses.queue[index].success) { Falcon.responses.count.success = Falcon.responses.count.success -1; }
+ else { Falcon.responses.count.error = Falcon.responses.count.error -1; }
+ Falcon.responses.queue.splice(index, 1);
+ };
+ // serverResponse: null,
+ // success: null
+
+ //-------------METHODS-----------------------------//
+ Falcon.getServerVersion = function () {
+ return $http.get(buildURI('../api/admin/version'));
+ };
+ Falcon.getServerStack = function () {
+ return $http.get(buildURI('../api/admin/stack'));
+ };
+ Falcon.postValidateEntity = function (xml, type) {
+ return $http.post(buildURI('../api/entities/validate/' + type), xml, { headers: {'Content-Type': 'text/plain'} });
+ };
+ Falcon.postSubmitEntity = function (xml, type) {
+ return $http.post(buildURI('../api/entities/submit/' + type), xml, { headers: {'Content-Type': 'text/plain'} });
+ };
+ Falcon.postUpdateEntity = function (xml, type, name) {
+ return $http.post(buildURI('../api/entities/update/' + type + '/' + name), xml, { headers: {'Content-Type': 'text/plain'} });
+ };
+
+ Falcon.postScheduleEntity = function (type, name) {
+ return $http.post(buildURI('../api/entities/schedule/' + type + '/' + name));
+ };
+ Falcon.postSuspendEntity = function (type, name) {
+ return $http.post(buildURI('../api/entities/suspend/' + type + '/' + name));
+ };
+ Falcon.postResumeEntity = function (type, name) {
+ return $http.post(buildURI('../api/entities/resume/' + type + '/' + name));
+ };
+
+ Falcon.deleteEntity = function (type, name) {
+ return $http.delete(buildURI('../api/entities/delete/' + type + '/' + name));
+ };
+
+ Falcon.getEntities = function (type) {
+ return $http.get(buildURI('../api/entities/list/' + type + '?fields=status,tags&numResults=' + NUMBER_OF_RESULTS));
+ };
+
+ Falcon.getEntityDefinition = function (type, name) {
+ return $http.get(buildURI('../api/entities/definition/' + type + '/' + name), { headers: {'Accept': 'text/plain'} });
+ };
+
+
+ //----------------------------------------------//
+ return Falcon;
+
+ }]);
+
+})();
http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/js/services/common/file-api.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/services/common/file-api.js b/falcon-ui/app/js/services/common/file-api.js
new file mode 100644
index 0000000..e3f6745
--- /dev/null
+++ b/falcon-ui/app/js/services/common/file-api.js
@@ -0,0 +1,62 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function () {
+ 'use strict';
+
+ var fileModule = angular.module('app.services.fileapi', ['app.services.entity.model']);
+
+ fileModule.factory('FileApi', ["$http", "$q", "EntityModel", function ($http, $q, EntityModel) {
+
+ var FileApi = {};
+
+ FileApi.supported = (window.File && window.FileReader && window.FileList && window.Blob);
+ FileApi.errorMessage = 'The File APIs are not fully supported in this browser.';
+
+ FileApi.fileDetails = "No file loaded";
+ FileApi.fileRaw = "No file loaded";
+
+ FileApi.loadFile = function (evt) {
+
+ if (FileApi.supported) {
+ var deferred = $q.defer(),
+ reader = new FileReader(),
+ file = evt.target.files[0];
+
+ reader.onload = (function (theFile) {
+
+ reader.readAsText(theFile, "UTF-8");
+
+ return function (e) {
+ FileApi.fileRaw = e.target.result;
+ FileApi.fileDetails = theFile;
+ EntityModel.getJson(FileApi.fileRaw);
+ deferred.resolve();
+ };
+
+ })(file);
+
+ return deferred.promise;
+ }
+ else {
+ alert(FileApi.errorMessage);
+ }
+ };
+ return FileApi;
+ }]);
+
+})();
http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/js/services/common/json-transformer.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/services/common/json-transformer.js b/falcon-ui/app/js/services/common/json-transformer.js
new file mode 100644
index 0000000..e08a477
--- /dev/null
+++ b/falcon-ui/app/js/services/common/json-transformer.js
@@ -0,0 +1,109 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function () {
+ 'use strict';
+ var module = angular.module('app.services.json.transformer', []);
+
+ module.factory('JsonTransformerFactory', function() {
+ return {
+ transform: newFieldTransformation
+ };
+ });
+
+ function newFieldTransformation(sourceField, targetField, mappingCallback) {
+ return new FieldTransformation(sourceField, targetField || sourceField, mappingCallback);
+ }
+
+ function FieldTransformation(sourceField, targetField, mappingCallback) {
+ var self = this;
+
+ self.sourceFieldPath = sourceField.split(".");
+ self.targetFieldPath = targetField.split(".");
+ self.mappingCallback = mappingCallback;
+
+ self.transform = function(sourceField, targetField, mappingCallback) {
+ return new ComposedFieldTransformation(self, newFieldTransformation(sourceField, targetField, mappingCallback));
+ };
+
+ self.apply = function(source, target) {
+
+ var sourceHolder = find(source, self.sourceFieldPath);
+
+ var sourceValue = sourceHolder.get();
+ sourceValue = sourceValue && self.mappingCallback ? self.mappingCallback(sourceValue) : sourceValue;
+
+ if (sourceValue) {
+ var targetHolder = find(target, self.targetFieldPath);
+ targetHolder.set(sourceValue);
+ }
+
+ return target;
+ };
+
+ function find(target, path) {
+ var current = target;
+ var child;
+ for(var i = 0, n = path.length - 1; i < n; i++) {
+ child = path[i];
+ createIfNotExists(current, child);
+ current = current[child];
+ }
+ return new Holder(current, path[path.length - 1]);
+ }
+
+ function createIfNotExists(current, child) {
+ if (!current[child]) {
+ current[child] = {};
+ }
+ }
+
+ function Holder(target, child) {
+ this.target = target;
+ this.child = child;
+
+ this.get = function() {
+ return target[child];
+ };
+
+ this.set = function(value) {
+ target[child] = value;
+ };
+
+ }
+
+ function ComposedFieldTransformation (firstTransformation, secondTransformation) {
+ var self = this;
+
+ self.firstTransformation = firstTransformation;
+ self.secondTransformation = secondTransformation;
+
+ self.apply = function(source, target) {
+ self.firstTransformation.apply(source, target);
+ self.secondTransformation.apply(source, target);
+ return target;
+ };
+
+ self.transform = function(sourceField, targetField, mappingCallback) {
+ return new ComposedFieldTransformation(self, newFieldTransformation(sourceField, targetField, mappingCallback));
+ };
+
+ }
+
+ }
+
+})();
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/js/services/common/validation-service.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/services/common/validation-service.js b/falcon-ui/app/js/services/common/validation-service.js
new file mode 100644
index 0000000..fe45d7a
--- /dev/null
+++ b/falcon-ui/app/js/services/common/validation-service.js
@@ -0,0 +1,128 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function () {
+ 'use strict';
+
+ var module = angular.module('app.services.validation', []);
+
+ module.factory('ValidationService', ["$window", function ($window) {
+ var checkMessages = {
+ name: {
+ patternInvalid: "The name has an invalid format.",
+ unavailable: "The name you choosed is not available",
+ empty: "You need to specify a name"
+ },
+ colo: {
+ empty: "You need to provide a colo",
+ patternInvalid: "The Colo has an invalid format. "
+ },
+ description: {
+ empty: "You need to provide a description",
+ patternInvalid: "The Description has an invalid format. "
+ },
+ path: {
+ empty: "You need to provide a path",
+ patternInvalid: "The Path has an invalid format. "
+ },
+ key: {
+ empty: "You need to provide a key",
+ patternInvalid: "The Key has an invalid format. "
+ },
+ value: {
+ empty: "You need to provide a value",
+ patternInvalid: "The Value has an invalid format. "
+ },
+ location: {
+ empty: "You need to provide a location",
+ patternInvalid: "The Location has an invalid format. "
+ },
+ provider: {
+ empty: "You need to provide a provider",
+ patternInvalid: "The provider has an invalid format. "
+ },
+ engine: { empty: "You need to select an engine" },
+ cluster: { empty: "You need to select a cluster" },
+ feed: { empty: "You need to select a feed" },
+ date: { empty: "You need to select a date" },
+ number: { empty: "You need to provide a number" },
+ option: { empty: "You need to select an option" },
+ user: { empty: "Please enter your user name." },
+ password: { empty: "Please enter your password." }
+ },
+ checkPatterns = {
+ name: new RegExp("^[a-zA-Z0-9]{1,39}$"),
+ id: new RegExp("^(([a-zA-Z]([\\-a-zA-Z0-9])*){1,39})$"),
+ password: new RegExp("^(([a-zA-Z]([\\-a-zA-Z0-9])*){1,39})$"),
+ freeText: new RegExp("^([\\sa-zA-Z0-9]){1,40}$"),
+ alpha: new RegExp("^([a-zA-Z0-9]){1,100}$"),
+ commaSeparated: new RegExp("/^[a-zA-Z0-9,]{1,80}$"),
+ unixId: new RegExp("^([a-z_][a-z0-9-_\\.\\-]{0,30})$"),
+ unixPermissions: new RegExp("^((([0-7]){1,4})|(\\*))$"),
+ osPath: new RegExp("^[^\\0 ]+$"),
+ twoDigits: new RegExp("^([0-9]){1,2}$"),
+ tableUri: new RegExp("^[^\\0]+$"),
+ versionNumbers: new RegExp("^[0-9]{1,2}\\.[0-9]{1,2}\\.[0-9]{1,2}$")
+ };
+
+ function acceptOnlyNumber(evt) {
+ var theEvent = evt || $window.event,
+ key = theEvent.keyCode || theEvent.which,
+ BACKSPACE = 8,
+ DEL = 46,
+ ARROW_KEYS = {left: 37, right: 39},
+ regex = /[0-9]|\./;
+
+ if (key === BACKSPACE || key === DEL || key === ARROW_KEYS.left || key === ARROW_KEYS.right) {
+ return true;
+ }
+
+ key = String.fromCharCode(key);
+
+ if (!regex.test(key)) {
+ theEvent.returnValue = false;
+ if (theEvent.preventDefault) { theEvent.preventDefault(); }
+ }
+ }
+ function acceptNoSpaces(evt) {
+ var theEvent = evt || $window.event,
+ key = theEvent.keyCode || theEvent.which,
+ SPACE = 32;
+
+ if (key === SPACE) {
+ theEvent.returnValue = false;
+ theEvent.preventDefault();
+ return false;
+ }
+ }
+
+ return {
+ messages: checkMessages,
+ patterns: checkPatterns,
+ nameAvailable: true,
+ displayValidations: {show: false, nameShow: false},
+ acceptOnlyNumber: acceptOnlyNumber,
+ acceptNoSpaces: acceptNoSpaces
+ };
+
+ }]);
+}());
+
+
+
+
+
http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/js/services/common/xml-to-json-service.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/services/common/xml-to-json-service.js b/falcon-ui/app/js/services/common/xml-to-json-service.js
new file mode 100644
index 0000000..d054e89
--- /dev/null
+++ b/falcon-ui/app/js/services/common/xml-to-json-service.js
@@ -0,0 +1,80 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function () {
+ 'use strict';
+
+ var servicesModule = angular.module('app.services.x2js', []);
+
+ servicesModule.factory('X2jsService', function() {
+ var x2js = new X2JS(
+ {arrayAccessFormPaths: [
+ 'feed.properties.property',
+ 'feed.locations.location',
+ 'feed.clusters.cluster',
+ 'feed.clusters.cluster.locations.location',
+ 'cluster.properties.property',
+ 'cluster.locations.location',
+ 'process.clusters.cluster',
+ 'process.inputs.input',
+ 'process.outputs.output'
+ ]});
+
+ return {
+ xml_str2json: function(string) {
+ return x2js.xml_str2json(string);
+ },
+
+ json2xml_str: function(jsonObj) {
+ return x2js.json2xml_str(jsonObj);
+ },
+
+ prettifyXml: function (xml) {
+ var formatted = '';
+ var reg = /(>)(<)(\/*)/g;
+ xml = xml.replace(reg, '$1\r\n$2$3');
+ var pad = 0;
+ jQuery.each(xml.split('\r\n'), function(index, node) {
+ var indent = 0;
+ if (node.match( /.+<\/\w[^>]*>$/ )) {
+ indent = 0;
+ } else if (node.match( /^<\/\w/ )) {
+ if (pad !== 0) {
+ pad -= 1;
+ }
+ } else if (node.match( /^<\w[^>]*[^\/]>.*$/ )) {
+ indent = 1;
+ } else {
+ indent = 0;
+ }
+
+ var padding = '';
+ for (var i = 0; i < pad; i++) {
+ padding += ' ';
+ }
+
+ formatted += padding + node + '\r\n';
+ pad += indent;
+ });
+
+ return formatted;
+ }
+ };
+
+ });
+
+})();
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/js/services/entity/entity-factory.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/services/entity/entity-factory.js b/falcon-ui/app/js/services/entity/entity-factory.js
new file mode 100644
index 0000000..c64ad98
--- /dev/null
+++ b/falcon-ui/app/js/services/entity/entity-factory.js
@@ -0,0 +1,248 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function () {
+ 'use strict';
+ var module = angular.module('app.services.entity.factory', []);
+
+ module.factory('EntityFactory', [function () {
+ return {
+ newFeed: function () {
+ return new Feed();
+ },
+
+ newFeedProperties: function () {
+ return feedProperties();
+ },
+
+ newFeedCustomProperties: function () {
+ return feedCustomProperties();
+ },
+
+ newFrequency: function (quantity, unit) {
+ return new Frequency(quantity, unit);
+ },
+
+ newLocation: function (type, path) {
+ return new Location(type, path);
+ },
+
+ newCluster: function (type, selected) {
+ return new Cluster(type, selected);
+ },
+
+ newEntry: function (key, value) {
+ return new Entry(key, value);
+ },
+
+ newProcess: function () {
+ return new Process();
+ },
+
+ newInput: function () {
+ return new Input();
+ },
+
+ newOutput: function () {
+ return new Output();
+ },
+
+ newEntity: function (type) {
+ if (type === 'feed') {
+ return this.newFeed();
+ }
+ if (type === 'process') {
+ return this.newProcess();
+ }
+ }
+
+ };
+ }]);
+
+ function Feed() {
+// this.name = null;
+ this.name = "";
+ this.description = null;
+ this.groups = null;
+ this.tags = [new Entry(null, null)];
+ this.ACL = new ACL();
+ this.schema = new Schema();
+ this.frequency = new Frequency(null, 'hours');
+ this.lateArrival = new LateArrival();
+ this.availabilityFlag = null;
+ this.properties = feedProperties();
+ this.customProperties = [new Entry(null, null)];
+ this.storage = new Storage();
+ this.clusters = [new Cluster('source', true)];
+ this.timezone = null;
+ }
+
+
+ function ACL() {
+ this.owner = null;
+ this.group = null;
+ this.permission = '*';
+ }
+
+ function Schema() {
+ this.location = null;
+ this.provider = null;
+ }
+
+ function feedProperties() {
+ return [
+ new Entry('queueName', 'default'),
+ new Entry('jobPriority', ''),
+ new Entry('timeout', new Frequency(1, 'hours')),
+ new Entry('parallel', 3),
+ new Entry('maxMaps', 8),
+ new Entry('mapBandwidthKB', 1024)
+ ];
+ }
+
+ function feedCustomProperties() {
+ return [
+ new Entry(null, null)
+ ];
+ }
+
+ function LateArrival() {
+ this.active = false;
+ this.cutOff = new Frequency(null, 'hours');
+ }
+
+ function Frequency(quantity, unit) {
+ this.quantity = quantity;
+ this.unit = unit;
+ }
+
+ function Entry(key, value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ function Storage() {
+ this.fileSystem = new FileSystem();
+ this.catalog = new Catalog();
+ }
+
+ function Catalog() {
+ this.active = false;
+ this.catalogTable = new CatalogTable();
+ }
+
+ function CatalogTable() {
+ this.uri = null;
+ this.focused = false;
+ }
+
+ function FileSystem() {
+ this.active = true;
+ this.locations = [new Location('data','/'), new Location('stats','/'), new Location('meta','/')];
+ }
+
+ function Location(type, path) {
+ this.type = type;
+ this.path= path;
+ this.focused = false;
+ }
+
+ function Cluster(type, selected) {
+// this.name = null;
+ this.name = "";
+ this.type = type;
+ this.selected = selected;
+ this.retention = new Frequency(null, 'hours');
+ this.retention.action = 'delete';
+ this.validity = new Validity();
+ this.storage = new Storage();
+ }
+
+ function Validity() {
+ this.start = new DateAndTime();
+ this.end = new DateAndTime();
+ this.timezone = "";
+ }
+
+ function DateAndTime() {
+ this.date = "";
+ this.time = currentTime();
+ this.opened = false;
+ }
+
+ /*function currentDate() {
+ var now = new Date();
+ return now;
+ }*/
+
+ function currentTime() {
+ return new Date(1900, 1, 1, 0, 0, 0);
+ }
+
+ function Process() {
+ this.name = null;
+ this.tags = [new Entry(null, null)];
+ this.workflow = new Workflow();
+ this.timezone = null;
+ this.frequency = new Frequency(null, 'hours');
+ this.parallel = 1;
+ this.order = "";
+ this.retry = new Retry();
+ this.clusters = [new Cluster('source', true)];
+ this.inputs = [];
+ this.outputs = [];
+
+ /*
+ this.name = 'P';
+ this.workflow.name = 'W';
+ this.workflow.engine = 'oozie';
+ this.workflow.version = '3.3.1';
+ this.frequency.quantity = '2';
+ this.retry.attempts = '4';
+ this.retry.delay.quantity = '4';
+ this.clusters[0].name = 'backupCluster';
+ this.tags = [{key: 'tag1', value: 'value1'},{key: 'tag2', value: 'value2'}];
+ */
+ }
+
+ function Workflow() {
+ this.name = null;
+ this.engine = null;
+ this.version = '';
+ this.path = '/';
+ }
+
+ function Retry() {
+ this.policy = '';
+ this.attempts = null;
+ this.delay = new Frequency(null, '');
+ }
+
+ function Input() {
+ this.name = null;
+ this.feed = "";
+ this.start = null;
+ this.end = null;
+ }
+
+ function Output() {
+ this.name = null;
+ this.feed = null;
+ this.outputInstance = null;
+ }
+
+})();
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/js/services/entity/entity-messages.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/services/entity/entity-messages.js b/falcon-ui/app/js/services/entity/entity-messages.js
new file mode 100644
index 0000000..76f4cd2
--- /dev/null
+++ b/falcon-ui/app/js/services/entity/entity-messages.js
@@ -0,0 +1,57 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function () {
+ 'use strict';
+ var module = angular.module('app.services.messages', []);
+
+ var service = new MessagesService();
+
+ module.factory('MessagesService', [function() {
+ return service;
+ }]);
+
+ function MessagesService() {
+ this.messages = {
+ error: [],
+ info: []
+ };
+ }
+
+ MessagesService.prototype.validateCategory = function(category) {
+ if(!this.messages[category]) {
+ throw new Error('Category not registered');
+ }
+ };
+
+ MessagesService.prototype.push = function(category, title, detail) {
+ this.validateCategory(category);
+ this.messages[category].push(new Message(title, detail));
+ };
+
+ MessagesService.prototype.pop = function(category) {
+ this.validateCategory(category);
+ return this.messages[category].pop();
+ };
+
+
+ function Message(title, detail) {
+ this.title = title;
+ this.detail = detail;
+ }
+
+})();
http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/js/services/entity/entity-model.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/services/entity/entity-model.js b/falcon-ui/app/js/services/entity/entity-model.js
new file mode 100644
index 0000000..3398af2
--- /dev/null
+++ b/falcon-ui/app/js/services/entity/entity-model.js
@@ -0,0 +1,155 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function () {
+ 'use strict';
+ var module = angular.module('app.services.entity.model', []);
+
+ module.factory('EntityModel', ["X2jsService", function(X2jsService) {
+
+ var EntityModel = {};
+
+ EntityModel.json = null;
+ EntityModel.detailsPageModel = null;
+
+ EntityModel.identifyType = function(json) {
+ if(json.feed) { EntityModel.type = "feed"; }
+ else if(json.cluster) { EntityModel.type = "cluster"; }
+ else if(json.process) { EntityModel.type = "process"; }
+ else { EntityModel.type = 'Type not recognized'; }
+ };
+
+ EntityModel.getJson = function(xmlString) {
+ EntityModel.json = X2jsService.xml_str2json( xmlString );
+ return EntityModel.identifyType(EntityModel.json);
+ };
+
+ EntityModel.clusterModel = {
+ cluster:{
+ tags: "",
+ interfaces:{
+ interface:[
+ {
+ _type:"readonly",
+ _endpoint:"hftp://sandbox.hortonworks.com:50070",
+ _version:"2.2.0"
+ },
+ {
+ _type:"write",
+ _endpoint:"hdfs://sandbox.hortonworks.com:8020",
+ _version:"2.2.0"
+
+ },
+ {
+ _type:"execute",
+ _endpoint:"sandbox.hortonworks.com:8050",
+ _version:"2.2.0"
+
+ },
+ {
+ _type:"workflow",
+ _endpoint:"http://sandbox.hortonworks.com:11000/oozie/",
+ _version:"4.0.0"
+
+ },
+ {
+ _type:"messaging",
+ _endpoint:"tcp://sandbox.hortonworks.com:61616?daemon=true",
+ _version:"5.1.6"
+
+ }
+ ]
+ },
+ locations:{
+ location:[
+ {_name: "staging", _path: ""},
+ {_name: "temp", _path: ""},
+ {_name: "working", _path: ""}
+ ]
+ },
+ ACL: {
+ _owner: "",
+ _group: "",
+ _permission: ""
+ },
+ properties: {
+ property: [
+ { _name: "", _value: ""}
+ ]
+ },
+ _xmlns:"uri:falcon:cluster:0.1",
+ _name:"",
+ _description:"",
+ _colo:""
+ }
+ };
+
+ EntityModel.feedModel = {
+ feed: {
+ tags: "",
+ groups: "",
+ frequency: "",
+ timezone: "",
+ "late-arrival": {
+ "_cut-off": ""
+ },
+ clusters: [{
+ "cluster": {
+ validity: {
+ _start: "",
+ _end: ""
+ },
+ retention: {
+ _limit: "",
+ _action: ""
+ },
+ _name: "",
+ _type: "source"
+ }
+ }],
+ locations: {
+ location: [{
+ _type: "data",
+ _path: "/none"
+ }, {
+ _type: "stats",
+ _path: "/none"
+ }, {
+ _type: "meta",
+ _path: "/none"
+ }]
+ },
+ ACL: {
+ _owner: "",
+ _group: "",
+ _permission: ""
+ },
+ schema: {
+ _location: "/none",
+ _provider: "none"
+ },
+ _xmlns: "uri:falcon:feed:0.1",
+ _name: "",
+ _description: ""
+ }
+ };
+
+ return EntityModel;
+
+ }]);
+
+})();
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/js/services/entity/entity-serializer.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/services/entity/entity-serializer.js b/falcon-ui/app/js/services/entity/entity-serializer.js
new file mode 100644
index 0000000..b7d723e
--- /dev/null
+++ b/falcon-ui/app/js/services/entity/entity-serializer.js
@@ -0,0 +1,510 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function () {
+ 'use strict';
+ var module = angular.module('app.services.entity.serializer',
+ ['app.services.json.transformer',
+ 'app.services',
+ 'app.services.entity.factory']);
+
+ module.factory('EntitySerializer',
+ ['EntityFactory', 'JsonTransformerFactory', 'X2jsService',
+ function(EntityFactory, JsonTransformerFactory, X2jsService) {
+
+ return {
+ preSerialize: function(feed, type) {
+ if(type === 'feed') {
+ if(feed.properties) {
+ feed.allproperties = feed.properties.concat(feed.customProperties);
+ }
+ return preSerializeFeed(feed, JsonTransformerFactory);
+ } else if(type === 'process') {
+ return preSerializeProcess(feed, JsonTransformerFactory);
+ }
+ },
+
+ serialize: function(feed, type) {
+ return X2jsService.json2xml_str(this.preSerialize(feed, type));
+ },
+
+ preDeserialize: function(entityModel, type) {
+ if(type === 'feed') {
+ return preDeserializeFeed(entityModel, JsonTransformerFactory);
+ } else if(type === 'process') {
+ return preDeserializeProcess(entityModel, JsonTransformerFactory);
+ }
+ },
+
+ deserialize: function(xml, type) {
+ var entityModel = X2jsService.xml_str2json(xml);
+ return this.preDeserialize(entityModel, type);
+ }
+
+ };
+
+ function keyValuePairs(input) {
+ return input.filter(emptyKey).map(entryToString).join(',');
+ }
+
+ function emptyKey (input) {
+ return input.key;
+ }
+
+ function emptyValue (input) {
+ return input && input.value;
+ }
+
+ function emptyFrequency (input) {
+ return input.value.unit ? input.value.quantity : input.value;
+ }
+
+ function entryToString(input) {
+ return input.key + '=' + input.value;
+ }
+
+ function frequencyToString(input) {
+ return input.quantity ? input.unit + '(' + input.quantity + ')' : null;
+ }
+
+ function pad(n) {
+ return String("00" + n).slice(-2);
+ }
+
+ function timeAndDateToString(input) {
+ if(input.date !== "") {
+ var dateComponent =
+ input.date.getFullYear() + '-' +
+ pad(input.date.getMonth()+1) + '-' +
+ pad(input.date.getDate());
+ }
+ else {
+ var dateComponent = "";
+ }
+
+ if(input.time !== "") {
+ var timeComponent =
+ pad(input.time.getHours()) + ':' +
+ pad(input.time.getMinutes());
+ }
+ else {
+ var timeComponent = "00:00";
+ }
+
+ return dateComponent + 'T' + timeComponent + 'Z';
+ }
+
+ function emptyElement() {return {};}
+
+ function EntityModel(type) {
+ this[type] = {_xmlns: 'uri:falcon:' + type + ':0.1'};
+ }
+
+ function preSerializeFeed (feed, transformerFactory) {
+ var propertyTransform = transformerFactory
+ .transform('key', '_name')
+ .transform('value', '_value', function(value) {
+ return value.quantity ? frequencyToString(value) : value;
+ });
+
+ var locationTransform = transformerFactory
+ .transform('type', '_type')
+ .transform('path', '_path');
+
+ var clusterTransform = transformerFactory
+ .transform('name', '_name')
+ .transform('type', '_type')
+ .transform('validity.start', 'validity._start', timeAndDateToString)
+ .transform('validity.end', 'validity._end', timeAndDateToString)
+ .transform('retention', 'retention._limit', frequencyToString)
+ .transform('retention.action', 'retention._action')
+ .transform('storage.fileSystem', 'locations.location', function(fileSystem) {
+ return feed.storage.fileSystem.active ? transformfileSystem(fileSystem) : null;
+ })
+ .transform('storage.catalog', 'table', function(catalog) {
+ return feed.storage.catalog.active ? transformCatalog(catalog) : null;
+ });
+
+ var transform = transformerFactory
+ .transform('name', 'feed._name')
+ .transform('description', 'feed._description')
+ .transform('tags', 'feed.tags', keyValuePairs)
+ .transform('groups', 'feed.groups')
+ .transform('availabilityFlag', 'feed.availabilityFlag')
+ .transform('frequency', 'feed.frequency', frequencyToString)
+ .transform('timezone', 'feed.timezone')
+ .transform('lateArrival.cutOff', 'feed.late-arrival._cut-off', frequencyToString)
+ .transform('clusters', 'feed.clusters.cluster', function(clusters) {
+ return clusters.map(function(cluster) {
+ return clusterTransform.apply(cluster, {});
+ });
+ })
+ .transform('storage.fileSystem', 'feed.locations.location', function(fileSystem) {
+ return fileSystem.active ? transformfileSystem(fileSystem) : null;
+ })
+ .transform('storage.catalog', 'feed.table', function(catalog) {
+ return catalog.active ? transformCatalog(catalog) : null;
+ })
+ .transform('ACL', 'feed.ACL', emptyElement)
+ .transform('ACL.owner', 'feed.ACL._owner')
+ .transform('ACL.group', 'feed.ACL._group')
+ .transform('ACL.permission', 'feed.ACL._permission')
+ .transform('schema', 'feed.schema', emptyElement)
+ .transform('schema.location', 'feed.schema._location')
+ .transform('schema.provider', 'feed.schema._provider')
+ .transform('allproperties', 'feed.properties.property', function(properties) {
+ return properties.filter(emptyValue).filter(emptyFrequency).map(function(property) {
+ return propertyTransform.apply(property, {});
+ });
+ });
+
+ function transformfileSystem (fileSystem) {
+ return fileSystem.locations.map(function(location) {
+ return locationTransform.apply(location, {});
+ });
+ }
+
+ function transformCatalog(catalog) {
+ return {_uri : catalog.catalogTable.uri};
+ }
+
+ return transform.apply(feed, new EntityModel('feed'));
+
+ }
+
+ function preSerializeProcess (process, transformerFactory) {
+
+ var clusterTransform = transformerFactory
+ .transform('name', '_name')
+ .transform('validity.start', 'validity._start', timeAndDateToString)
+ .transform('validity.end', 'validity._end', timeAndDateToString);
+
+ var inputTransform = transformerFactory
+ .transform('name', '_name')
+ .transform('feed', '_feed')
+ .transform('start', '_start')
+ .transform('end', '_end');
+
+ var outputTransform = transformerFactory
+ .transform('name', '_name')
+ .transform('feed', '_feed')
+ .transform('outputInstance', '_instance');
+
+ var transform = transformerFactory
+ .transform('name', 'process._name')
+ .transform('tags', 'process.tags', keyValuePairs)
+ .transform('clusters', 'process.clusters.cluster', function(clusters) {
+ return clusters.map(function(cluster) {
+ return clusterTransform.apply(cluster, {});
+ });
+ })
+ .transform('parallel', 'process.parallel')
+ .transform('order', 'process.order')
+ .transform('frequency', 'process.frequency', frequencyToString)
+ .transform('timezone', 'process.timezone')
+ .transform('inputs', 'process.inputs.input', function(inputs) {
+ if(inputs.length === 0) {
+ return null;
+ }
+ return inputs.map(function(input) {
+ return inputTransform.apply(input, {});
+ });
+ })
+ .transform('outputs', 'process.outputs.output', function(outputs) {
+ if(outputs.length === 0) {
+ return null;
+ }
+ return outputs.map(function(output) {
+ return outputTransform.apply(output, {});
+ });
+ })
+ .transform('workflow.name', 'process.workflow._name')
+ .transform('workflow.version', 'process.workflow._version')
+ .transform('workflow.engine', 'process.workflow._engine')
+ .transform('workflow.path', 'process.workflow._path')
+ .transform('retry.policy', 'process.retry._policy')
+ .transform('retry.delay', 'process.retry._delay', frequencyToString)
+ .transform('retry.attempts', 'process.retry._attempts');
+
+
+ return transform.apply(process, new EntityModel('process'));
+
+ }
+
+ function preDeserializeFeed(feedModel, transformerFactory) {
+ var feed = EntityFactory.newFeed();
+ feed.storage.fileSystem.active = false;
+
+ var clusterTransform = transformerFactory
+ .transform('_name', 'name')
+ .transform('_type', 'type')
+ .transform('validity._start', 'validity.start.date', parseDate)
+ .transform('validity._start', 'validity.start.time', parseTime)
+ .transform('validity._end', 'validity.end.date', parseDate)
+ .transform('validity._end', 'validity.end.time', parseTime)
+ .transform('retention._limit', 'retention', parseFrequency)
+ .transform('retention._action', 'retention.action')
+ .transform('locations', 'storage.fileSystem.active', parseBoolean)
+ .transform('locations.location', 'storage.fileSystem.locations', parseLocations)
+ .transform('table', 'storage.catalog.active', parseBoolean)
+ .transform('table._uri', 'storage.catalog.catalogTable.uri')
+ ;
+
+ var transform = transformerFactory
+ .transform('_name', 'name')
+ .transform('_description', 'description')
+ .transform('tags', 'tags', parseKeyValuePairs)
+ .transform('groups','groups')
+ .transform('ACL._owner','ACL.owner')
+ .transform('ACL._group','ACL.group')
+ .transform('ACL._permission','ACL.permission')
+ .transform('schema._location','schema.location')
+ .transform('schema._provider','schema.provider')
+ .transform('frequency','frequency', parseFrequency)
+ .transform('late-arrival','lateArrival.active', parseBoolean)
+ .transform('late-arrival._cut-off','lateArrival.cutOff', parseFrequency)
+ .transform('availabilityFlag', 'availabilityFlag')
+ .transform('properties.property', 'customProperties', parseProperties(isCustomProperty, EntityFactory.newFeedCustomProperties()))
+ .transform('properties.property', 'properties', parseProperties(isFalconProperty, EntityFactory.newFeedProperties()))
+ .transform('locations', 'storage.fileSystem.active', parseBoolean)
+ .transform('locations.location', 'storage.fileSystem.locations', parseLocations)
+ .transform('table', 'storage.catalog.active', parseBoolean)
+ .transform('table._uri', 'storage.catalog.catalogTable.uri')
+ .transform('clusters.cluster', 'clusters', parseClusters(clusterTransform))
+ .transform('timezone', 'timezone')
+ ;
+
+ return transform.apply(angular.copy(feedModel.feed), feed);
+ }
+
+ function preDeserializeProcess(processModel, transformerFactory) {
+ var process = EntityFactory.newProcess();
+
+ var clusterTransform = transformerFactory
+ .transform('_name', 'name')
+ .transform('validity._start', 'validity.start.date', parseDate)
+ .transform('validity._start', 'validity.start.time', parseTime)
+ .transform('validity._end', 'validity.end.date', parseDate)
+ .transform('validity._end', 'validity.end.time', parseTime);
+
+ var inputTransform = transformerFactory
+ .transform('_name', 'name')
+ .transform('_feed', 'feed')
+ .transform('_start', 'start')
+ .transform('_end', 'end');
+
+ var outputTransform = transformerFactory
+ .transform('_name', 'name')
+ .transform('_feed', 'feed')
+ .transform('_instance', 'outputInstance');
+
+ var transform = transformerFactory
+ .transform('_name', 'name')
+ .transform('tags', 'tags', parseKeyValuePairs)
+ .transform('workflow._name', 'workflow.name')
+ .transform('workflow._version', 'workflow.version')
+ .transform('workflow._engine', 'workflow.engine')
+ .transform('workflow._path', 'workflow.path')
+ .transform('timezone', 'timezone')
+ .transform('frequency','frequency', parseFrequency)
+ .transform('parallel','parallel')
+ .transform('order','order')
+ .transform('retry._policy','retry.policy')
+ .transform('retry._attempts','retry.attempts')
+ .transform('retry._delay','retry.delay', parseFrequency)
+ .transform('clusters.cluster', 'clusters', parseClusters(clusterTransform))
+ .transform('inputs.input', 'inputs', parseInputs(inputTransform))
+ .transform('outputs.output', 'outputs', parseOutputs(outputTransform));
+
+
+ function parseClusters(transform) {
+ return function(clusters) {
+ var result = clusters.map(parseCluster(transform));
+ return result;
+ };
+ }
+
+ return transform.apply(angular.copy(processModel.process), process);
+ }
+
+ function parseDate(input) {
+ var dateComponent = (input.split('T')[0]).split('-');
+ return newUtcDate(dateComponent[0], dateComponent[1], dateComponent[2]);
+ }
+
+ function parseTime(input) {
+ var timeComponent = (input.split('T')[1].split('Z')[0]).split(':');
+ return newUtcTime(timeComponent[0], timeComponent[1]);
+ }
+
+ function parseClusters(transform) {
+ return function(clusters) {
+ var result = clusters.map(parseCluster(transform));
+ selectFirstSourceCluster(result);
+ return result;
+ };
+ }
+
+ function parseInputs(transform) {
+ return function(inputs) {
+ return inputs.map(parseInput(transform));
+ };
+ }
+
+ function parseInput(transform) {
+ return function(input) {
+ return transform.apply(input, EntityFactory.newInput());
+ };
+ }
+
+ function parseOutputs(transform) {
+ return function(outputs) {
+ return outputs.map(parseOutput(transform));
+ };
+ }
+
+ function parseOutput(transform) {
+ return function(output) {
+ return transform.apply(output, EntityFactory.newOutput());
+ };
+ }
+
+ function parseCluster(transform) {
+ return function(input) {
+ var cluster = EntityFactory.newCluster('target', false);
+ cluster.storage.fileSystem.active = false;
+ return transform.apply(input, cluster);
+ };
+ }
+
+ function selectFirstSourceCluster(clusters) {
+ for(var i = 0, n = clusters.length; i < n; i++) {
+ if(clusters[i].type === 'source') {
+ clusters[i].selected = true;
+ return;
+ }
+ }
+ }
+
+ function parseKeyValue(keyValue) {
+ var parsedPair = keyValue.split('=');
+ return EntityFactory.newEntry(parsedPair[0], parsedPair[1]);
+ }
+
+ function parseKeyValuePairs(tagsString) {
+ return tagsString.split(',').map(parseKeyValue);
+ }
+
+ function parseFrequency(frequencyString) {
+ var parsedFrequency = frequencyString.split('(');
+ return EntityFactory.newFrequency(parsedFrequency[1].split(')')[0], parsedFrequency[0]);
+ }
+
+ function parseBoolean(input) {
+ return !!input;
+ }
+
+ function parseProperties(filterCallback, defaults) {
+ return function(properties) {
+ var result = filter(properties, filterCallback).map(parseProperty);
+ return merge(defaults, result);
+ };
+ }
+
+
+ function merge(defaults, newValues) {
+ var result = angular.copy(defaults);
+ var defaultMap = indexBy(result, 'key');
+
+ newValues.forEach(function(newValue) {
+ if(defaultMap[newValue.key]) {
+ defaultMap[newValue.key].value = newValue.value;
+ } else {
+ result.push(newValue);
+ }
+ });
+
+
+ return result;
+ }
+
+
+ function filter(array, callback) {
+ var out = [];
+ for(var i = 0, n = array.length; i < n; i++) {
+ if(callback(array[i])) {
+ out.push(array[i]);
+ }
+ }
+ return out;
+ }
+
+
+ function parseProperty(property) {
+ var value = property._name !== 'timeout' ? property._value : parseFrequency(property._value);
+ return EntityFactory.newEntry(property._name, value);
+ }
+
+ function parseLocations(locations) {
+ return locations.map(parseLocation);
+ }
+
+ function parseLocation(location) {
+ return EntityFactory.newLocation(location._type, location._path);
+ }
+
+ function indexBy(array, property) {
+ var map = {};
+
+ array.forEach(function(element) {
+ map[element[property]] = element;
+ });
+
+ return map;
+ }
+
+ function newUtcDate(year, month, day) {
+ return new Date(year, (month-1), day);
+ }
+
+ function newUtcTime(hours, minutes) {
+ return new Date(1900, 1, 1, hours, minutes, 0);
+ }
+
+ }]);
+
+
+ var falconProperties = {
+ queueName: true,
+ jobPriority: true,
+ timeout: true,
+ parallel: true,
+ maxMaps: true,
+ mapBandwidthKB: true
+ };
+
+
+ function isCustomProperty(property) {
+ return !falconProperties[property._name];
+ }
+
+ function isFalconProperty(property) {
+ return falconProperties[property._name];
+ }
+
+
+})();
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/js/services/services.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/js/services/services.js b/falcon-ui/app/js/services/services.js
new file mode 100644
index 0000000..98235d3
--- /dev/null
+++ b/falcon-ui/app/js/services/services.js
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function () {
+ 'use strict';
+
+ angular.module('app.services', [
+ 'app.services.falcon',
+ 'app.services.fileapi',
+ 'app.services.json.transformer',
+ 'app.services.x2js',
+ 'app.services.validation',
+ 'app.services.entity.serializer',
+ 'app.services.entity.factory',
+ 'app.services.entity.model'
+ ]);
+
+})();
http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/test/controllers/HeaderControllerSpec.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/test/controllers/HeaderControllerSpec.js b/falcon-ui/app/test/controllers/HeaderControllerSpec.js
new file mode 100644
index 0000000..13a73be
--- /dev/null
+++ b/falcon-ui/app/test/controllers/HeaderControllerSpec.js
@@ -0,0 +1,66 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function () {
+ 'use strict';
+
+ describe('HeaderController', function () {
+ var controller,
+ entityModel = {},
+ scope;
+
+ beforeEach(module('app.controllers.navHeader'));
+
+ beforeEach(inject(function($rootScope, $controller) {
+
+ scope = $rootScope.$new();
+
+ controller = $controller('HeaderController', {
+ $scope: scope,
+ EntityModel: entityModel,
+ $state: {
+ $current: {
+ name: 'forms.feed.general'
+ },
+ go: angular.noop
+ }
+ });
+
+ }));
+
+ it('should reset EntityModel.clusterModel', function() {
+ expect(entityModel).toEqual({});
+ expect(entityModel.clusterModel).toBeUndefined();
+ scope.resetCluster();
+ expect(entityModel.clusterModel).not.toBeUndefined();
+ expect(entityModel.clusterModel).toEqual(
+ {cluster:{tags: "",interfaces:{interface:[
+ {_type:"readonly",_endpoint:"hftp://sandbox.hortonworks.com:50070",_version:"2.2.0"},
+ {_type:"write",_endpoint:"hdfs://sandbox.hortonworks.com:8020",_version:"2.2.0"},
+ {_type:"execute",_endpoint:"sandbox.hortonworks.com:8050",_version:"2.2.0"},
+ {_type:"workflow",_endpoint:"http://sandbox.hortonworks.com:11000/oozie/",_version:"4.0.0"},
+ {_type:"messaging",_endpoint:"tcp://sandbox.hortonworks.com:61616?daemon=true",_version:"5.1.6"}
+ ]},locations:{location:[{_name: "staging", _path: ""},{_name: "temp", _path: ""},{_name: "working", _path: ""}]},
+ ACL: {_owner: "",_group: "",_permission: ""},properties: {property: [{ _name: "", _value: ""}]},
+ _xmlns:"uri:falcon:cluster:0.1",_name:"",_description:"",_colo:""}
+ }
+ );
+ });
+
+ });
+
+})();
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/test/controllers/MainControllerSpec.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/test/controllers/MainControllerSpec.js b/falcon-ui/app/test/controllers/MainControllerSpec.js
new file mode 100644
index 0000000..26e92fc
--- /dev/null
+++ b/falcon-ui/app/test/controllers/MainControllerSpec.js
@@ -0,0 +1,203 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function () {
+ 'use strict';
+ var q;
+ var scope;
+ var controller;
+ var falconServiceMock = jasmine.createSpyObj('Falcon', ['getEntities', 'getEntityDefinition', 'logResponse', 'logRequest']);
+ var x2jsServiceMock = jasmine.createSpyObj('X2jsService', ['xml_str2json']);
+ var stateMock = jasmine.createSpyObj('state', ['go']);
+ var entityModel = {};
+
+
+ describe('MainController', function () {
+
+ beforeEach(module('app.controllers'));
+
+ beforeEach(inject(function($q, $rootScope, $controller) {
+ q = $q;
+
+ var promise = {};
+ promise.success = function() {return {error: function() {}}};
+
+
+ scope = $rootScope.$new();
+ scope.$parent.refreshLists = angular.noop;
+ scope.models = {};
+ falconServiceMock.getEntities.andReturn(successResponse({}));
+
+ controller = $controller('DashboardCtrl', {
+ $scope: scope,
+ $timeout: {},
+ Falcon: falconServiceMock,
+ FileApi: {},
+ EntityModel: entityModel,
+ $state: stateMock,
+ X2jsService: x2jsServiceMock
+ });
+ }));
+
+
+
+ describe('editEntity', function() {
+
+ it('Should invoke the Falcon.getEntityDefinition', function() {
+ falconServiceMock.getEntityDefinition.andReturn(successResponse({}));
+
+ scope.editEntity('feed', 'myFeed');
+
+ expect(falconServiceMock.getEntityDefinition).toHaveBeenCalled();
+ });
+
+ describe('call to the api was successful', function() {
+ it('Should set the retrieved entity from the server into EntityModel', function () {
+ var myFeed = {};
+ falconServiceMock.getEntityDefinition.andReturn(successResponse({}));
+ x2jsServiceMock.xml_str2json.andReturn(myFeed);
+
+ scope.editEntity('feed', 'myFeed');
+
+ expect(entityModel.feedModel).toBe(myFeed);
+ });
+
+ it('Should set editing mode to true', function () {
+ falconServiceMock.getEntityDefinition.andReturn(successResponse({}));
+ scope.editingMode = false;
+
+ scope.editEntity('feed', 'myFeed');
+
+ expect(scope.editingMode).toBe(true);
+ });
+
+ it('Should navigate to the appropriate landing page for the entity type', function () {
+ falconServiceMock.getEntityDefinition.andReturn(successResponse());
+ scope.editingMode = false;
+
+ scope.editEntity('feed', 'myFeed');
+
+ expect(stateMock.go).toHaveBeenCalledWith('forms.feed.general');
+ });
+
+ it('Should set a copy of the model into the scope', function () {
+ var feedModel = {name: 'MyFeed'};
+ falconServiceMock.getEntityDefinition.andReturn(successResponse());
+ x2jsServiceMock.xml_str2json.andReturn(feedModel);
+
+ scope.editEntity('feed', 'myFeed');
+
+ expect(scope.models.feedModel).toNotBe(feedModel);
+ expect(scope.models.feedModel).toEqual(feedModel);
+ });
+ });
+
+ xdescribe('call to the api errored out', function() {
+ it('Should set the retrieved entity from the server into EntityModel', function () {
+ var error = {result: 'error message'};
+ falconServiceMock.success = true;
+ falconServiceMock.getEntityDefinition.andReturn(errorResponse());
+ x2jsServiceMock.xml_str2json.andReturn(error);
+
+ scope.editEntity('feed', 'myFeed');
+
+ expect(falconServiceMock.success).toBe(false);
+ expect(falconServiceMock.serverResponse).toBe('error message');
+ });
+
+ });
+ });
+
+ describe('clone entity', function() {
+ it('Should invoke the Falcon.getEntityDefinition', function() {
+ var myFeed = {feed: {}};
+ falconServiceMock.getEntityDefinition.andReturn(successResponse({}));
+ x2jsServiceMock.xml_str2json.andReturn(myFeed);
+
+ scope.cloneEntity('feed', 'myFeed');
+
+ expect(falconServiceMock.getEntityDefinition).toHaveBeenCalled();
+ });
+
+ describe('call to the api was successful', function() {
+ it('Should set the retrieved entity from the server into EntityModel', function () {
+ var myFeed = {feed: {}};
+ falconServiceMock.getEntityDefinition.andReturn(successResponse({}));
+ x2jsServiceMock.xml_str2json.andReturn(myFeed);
+
+ scope.cloneEntity('feed', 'myFeed');
+
+ expect(entityModel.feedModel).toBe(myFeed);
+ });
+
+ it('Should set clone mode to true', function () {
+ falconServiceMock.getEntityDefinition.andReturn(successResponse({}));
+ scope.cloningMode = false;
+
+ scope.cloneEntity('feed', 'myFeed');
+
+ expect(scope.cloningMode).toBe(true);
+ });
+
+ it('Should navigate to the appropriate landing page for the entity type', function () {
+ falconServiceMock.getEntityDefinition.andReturn(successResponse());
+ scope.cloningMode = false;
+
+ scope.cloneEntity('feed', 'myFeed');
+
+ expect(stateMock.go).toHaveBeenCalledWith('forms.feed.general');
+ });
+
+ it('Should set a copy of the model into the scope', function () {
+ var feedModel = {feed: {name: 'MyFeed'}};
+ falconServiceMock.getEntityDefinition.andReturn(successResponse());
+ x2jsServiceMock.xml_str2json.andReturn(feedModel);
+
+ scope.cloneEntity('feed', 'myFeed');
+
+ expect(scope.models.feedModel).toNotBe(feedModel);
+ expect(scope.models.feedModel).toEqual(feedModel);
+ });
+ });
+ });
+
+
+ });
+
+ function successResponse(value) {
+ var fakePromise = {};
+ fakePromise.success = function(callback) {
+ callback(value);
+ return fakePromise;
+ };
+ fakePromise.error = angular.noop;
+ return fakePromise;
+ }
+
+ function errorResponse(value) {
+ var fakePromise = {};
+ fakePromise.success = function() {
+ return fakePromise;
+ };
+ fakePromise.error = function(callback) {
+ callback(value);
+ return fakePromise;
+ };
+ return fakePromise;
+ }
+
+})();
\ No newline at end of file