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
