falcon-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From srik...@apache.org
Subject [01/21] falcon git commit: FALCON-790 Falcon UI to enable entity/process/feed edits and management. Contributed by Armando Reyna/Kenneth Ho
Date Wed, 01 Apr 2015 11:10:28 GMT
Repository: falcon
Updated Branches:
  refs/heads/master adf53c86f -> c4df0a5e9


http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/test/services/EntitySerializerSpec.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/test/services/EntitySerializerSpec.js b/falcon-ui/app/test/services/EntitySerializerSpec.js
new file mode 100644
index 0000000..448fec9
--- /dev/null
+++ b/falcon-ui/app/test/services/EntitySerializerSpec.js
@@ -0,0 +1,1286 @@
+/**
+ * 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('EntitySerializer', function () {
+    var serializer;
+
+    beforeEach(module('app.services.entity.serializer'));
+
+
+    beforeEach(inject(function(EntitySerializer) {
+      serializer = EntitySerializer;
+    }));
+
+    describe('deserialize feed', function() {
+
+      it('Should copy the general information', function() {
+
+        var feedModel = {
+          feed: {
+            _xmlns: "uri:falcon:feed:0.1",
+            _name: 'FeedName',
+            _description: 'Feed Description'
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.name).toBe(feedModel.feed._name);
+        expect(feed.description).toBe(feedModel.feed._description);
+        expect(feed.xmlns).toBe(undefined);
+      });
+
+      it('Should copy tags', function() {
+
+        var feedModel = {
+          feed: {
+            tags: 'owner=USMarketing,classification=Secure'
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.tags[0].key).toBe('owner');
+        expect(feed.tags[0].value).toBe('USMarketing');
+        expect(feed.tags[1].key).toBe('classification');
+        expect(feed.tags[1].value).toBe('Secure');
+      });
+
+      it('Should copy groups', function() {
+
+        var feedModel = {
+          feed: {
+            groups: 'churnAnalysisDataPipeline,Group2,Group3'
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.groups).toBe(feedModel.feed.groups);
+      });
+
+      it('Should copy ACL', function() {
+        var feedModel = {
+          feed: {
+            ACL: {_owner: 'ambari-qa', _group: 'users', _permission: '0755' }
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.ACL.owner).toBe(feedModel.feed.ACL._owner);
+        expect(feed.ACL.group).toBe(feedModel.feed.ACL._group);
+        expect(feed.ACL.permission).toBe(feedModel.feed.ACL._permission);
+      });
+
+      it('Should copy Schema', function() {
+        var feedModel = {
+          feed: {
+            schema: {_location: '/location', _provider: 'provider'}
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.schema.location).toBe(feedModel.feed.schema._location);
+        expect(feed.schema.provider).toBe(feedModel.feed.schema._provider);
+      });
+
+      it('Should copy frequency', function() {
+        var feedModel = {
+          feed: {
+            frequency: 'hours(20)'
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.frequency.unit).toBe('hours');
+        expect(feed.frequency.quantity).toBe('20');
+      });
+
+      it('Should copy late arrival', function() {
+        var feedModel = {
+          feed: {
+            "late-arrival": {"_cut-off": 'days(10)'}
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.lateArrival.active).toBe(true);
+        expect(feed.lateArrival.cutOff.unit).toBe('days');
+        expect(feed.lateArrival.cutOff.quantity).toBe('10');
+      });
+
+      it('Should not copy late arrival when is not present', function() {
+        var feedModel = {
+          feed: {}
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.lateArrival.active).toBe(false);
+        expect(feed.lateArrival.cutOff.unit).toBe('hours');
+        expect(feed.lateArrival.cutOff.quantity).toBe(null);
+      });
+
+      it('Should copy availabilityFlag', function() {
+        var feedModel = {
+          feed: {
+            availabilityFlag: 'Available'
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.availabilityFlag).toBe(feedModel.feed.availabilityFlag);
+      });
+
+      it('Should not copy availabilityFlag if not present in the xml', function() {
+        var feedModel = {
+          feed: {}
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.availabilityFlag).toBe(null);
+      });
+
+      it('Should copy custom properties', function() {
+        var feedModel = {
+          feed: {
+            properties: {property: [
+              {_name: 'Prop1', _value: 'Value1'},
+              {_name: 'Prop2', _value: 'Value2'}
+            ]}
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.customProperties.length).toBe(3);
+        expect(feed.customProperties[1].key).toBe('Prop1');
+        expect(feed.customProperties[1].value).toBe('Value1');
+        expect(feed.customProperties[2].key).toBe('Prop2');
+        expect(feed.customProperties[2].value).toBe('Value2');
+      });
+
+      it('Should not copy falcon properties into the custom properties', function() {
+        var feedModel = {
+          feed: {
+            properties: {property: [
+              {_name: 'queueName', _value: 'QueueName'},
+              {_name: 'Prop1', _value: 'Value1'}
+            ]}
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.customProperties.length).toBe(2);
+        expect(feed.customProperties[0].key).toBe(null);
+        expect(feed.customProperties[0].value).toBe(null);
+        expect(feed.customProperties[1].key).toBe('Prop1');
+        expect(feed.customProperties[1].value).toBe('Value1');
+      });
+
+      it('Should copy queueName properties into properties', function() {
+        var feedModel = {
+          feed: {
+            properties: {property: [
+              {_name: 'queueName', _value: 'QueueName'},
+              {_name: 'Prop1', _value: 'Value1'}
+            ]}
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.properties.length).toBe(6);
+        expect(feed.properties[0].key).toBe('queueName');
+        expect(feed.properties[0].value).toBe('QueueName');
+      });
+
+      it('Should leave the default properties if no properties appear on the xml and copy the new ones', function() {
+        var feedModel = {
+          feed: {
+            properties: {
+              property: [
+                {_name: 'jobPriority', _value: 'MEDIUM'}
+              ]
+            }
+          }
+        };
+
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.properties.length).toBe(6);
+        expect(feed.properties[0].key).toBe('queueName');
+        expect(feed.properties[0].value).toBe('default');
+        expect(feed.properties[1].key).toBe('jobPriority');
+        expect(feed.properties[1].value).toBe('MEDIUM');
+      });
+
+      it('Should copy timeout as a Frequency Object', function() {
+        var feedModel = {
+          feed: {
+            properties: {property: [
+              {_name: 'queueName', _value: 'QueueName'},
+              {_name: 'timeout', _value: 'days(4)'}
+            ]}
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.properties.length).toBe(6);
+        expect(feed.properties[2].key).toBe('timeout');
+        expect(feed.properties[2].value.quantity).toBe('4');
+        expect(feed.properties[2].value.unit).toBe('days');
+      });
+
+      it('Should copy file system locations', function() {
+        var feedModel = {
+          feed: {
+            locations: {location: [
+              {_type: 'data', _path: '/none1'},
+              {_type: 'stats', _path: '/none2'}
+            ]}
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+        var locations = feed.storage.fileSystem.locations;
+
+        expect(feed.storage.fileSystem.active).toBe(true);
+        expect(locations.length).toBe(2);
+        expect(locations[0].type).toBe('data');
+        expect(locations[0].path).toBe('/none1');
+        expect(locations[1].type).toBe('stats');
+        expect(locations[1].path).toBe('/none2');
+      });
+
+      it('Should not copy file system locations if they are not defined and keep the defaults', function() {
+        var feedModel = {
+          feed: {
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+        var locations = feed.storage.fileSystem.locations;
+
+        expect(feed.storage.fileSystem.active).toBe(false);
+        expect(locations.length).toBe(3);
+        expect(locations[0].type).toBe('data');
+        expect(locations[0].path).toBe('/');
+        expect(locations[1].type).toBe('stats');
+        expect(locations[1].path).toBe('/');
+        expect(locations[2].type).toBe('meta');
+        expect(locations[2].path).toBe('/');
+      });
+
+      it('Should set file system active flag as false if there are no locations are', function() {
+        var feedModel = {
+          feed: {}
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.storage.fileSystem.active).toBe(false);
+      });
+
+      it('Should copy catalog uri', function() {
+        var feedModel = {
+          feed: {
+            "table": {
+              _uri : 'table:uri'
+            }
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.storage.catalog.active).toBe(true);
+        expect(feed.storage.catalog.catalogTable.uri).toBe('table:uri');
+      });
+
+      it('Should not copy catalog uri if not present', function() {
+        var feedModel = {
+          feed: {}
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.storage.catalog.active).toBe(false);
+        expect(feed.storage.catalog.catalogTable.uri).toBe(null);
+      });
+
+      it('Should copy cluster name and type', function() {
+        var feedModel = {
+          feed: {
+            clusters: {
+              cluster: [{
+                _name: 'ClusterOne',
+                _type: 'target'
+              }]
+            }
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.clusters.length).toBe(1);
+        expect(feed.clusters[0].name).toBe('ClusterOne');
+        expect(feed.clusters[0].type).toBe('target');
+      });
+
+      it('Should copy clusters and select the first source cluster', function() {
+        var feedModel = {
+          feed: {
+            clusters: {
+              cluster: [
+                {_name: 'ClusterOne', _type: 'target'},
+                {_name: 'ClusterTwo', _type: 'source'},
+                {_name: 'ClusterThree', _type: 'target'},
+                {_name: 'ClusterFour', _type: 'source'}
+              ]
+            }
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.clusters[0].selected).toBe(false);
+        expect(feed.clusters[1].selected).toBe(true);
+        expect(feed.clusters[2].selected).toBe(false);
+        expect(feed.clusters[3].selected).toBe(false);
+
+      });
+
+      xit('Should copy validity', function() {
+        var feedModel = {
+          feed: {
+            clusters: {cluster: [{_name: 'ClusterOne', _type: 'target',
+              validity: {
+                _start: '2014-02-28T01:20Z',
+                _end: '2016-03-31T04:30Z'
+              }
+            }]}
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.clusters[0].validity.start.date).toEqual(new Date(2014, 2, 28,0,0));
+        expect(feed.clusters[0].validity.start.time).toEqual(newUtcTime(1, 20));
+        expect(feed.clusters[0].validity.end.date).toEqual(newUtcDate(2016, 3, 31));
+        expect(feed.clusters[0].validity.end.time).toEqual(newUtcTime(4, 30));
+
+      });
+
+      it('Should copy retention', function() {
+        var feedModel = {
+          feed: {
+            clusters: {cluster: [{_name: 'ClusterOne', _type: 'target',
+              retention: {
+                _limit: 'weeks(4)',
+                _action: 'delete'
+              }
+            }]}
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.clusters[0].retention.quantity).toBe('4');
+        expect(feed.clusters[0].retention.unit).toBe('weeks');
+        expect(feed.clusters[0].retention.action).toBe('delete');
+      });
+
+      it('Should copy clusters locations', function() {
+        var feedModel = {
+          feed: {
+            clusters: {cluster: [{_name: 'ClusterOne', _type: 'target',
+              locations: {
+                location: [
+                  {_type: 'stats', _path: '/path1'},
+                  {_type: 'data', _path: '/path2'},
+                  {_type: 'tmp', _path: '/path3'}
+                ]}
+            }]}
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+        var locations = feed.clusters[0].storage.fileSystem.locations;
+
+        expect(feed.clusters[0].storage.fileSystem.active).toBe(true);
+        expect(locations.length).toBe(3);
+        expect(locations[0].type).toBe('stats');
+        expect(locations[0].path).toBe('/path1');
+        expect(locations[1].type).toBe('data');
+        expect(locations[1].path).toBe('/path2');
+        expect(locations[2].type).toBe('tmp');
+        expect(locations[2].path).toBe('/path3');
+      });
+
+      it('filesystem should be inactive if there are no locations', function() {
+        var feedModel = {
+          feed: {
+            clusters: {cluster: [{_name: 'ClusterOne', _type: 'target'}]}
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+        var locations = feed.clusters[0].storage.fileSystem.locations;
+
+        expect(feed.clusters[0].storage.fileSystem.active).toBe(false);
+        expect(locations.length).toBe(3);
+      });
+
+      it('Should copy catalog uri', function() {
+        var feedModel = {
+          feed: {
+            clusters: {cluster: [{_name: 'ClusterOne', _type: 'target',
+              "table": {
+                _uri : 'table:uri'
+              }
+            }]}
+          }
+        };
+
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+        var catalogStorage = feed.clusters[0].storage.catalog;
+
+        expect(catalogStorage.active).toBe(true);
+        expect(catalogStorage.catalogTable.uri).toBe('table:uri');
+      });
+
+      it('Should set catalog storage as inactive when not present in the xml', function() {
+        var feedModel = {
+          feed: {
+            clusters: {cluster: [{_name: 'ClusterOne', _type: 'target'}]}
+          }
+        };
+
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+        var catalogStorage = feed.clusters[0].storage.catalog;
+
+        expect(catalogStorage.active).toBe(false);
+        expect(catalogStorage.catalogTable.uri).toBe(null);
+      });
+
+      it('Should copy timezone', function() {
+        var feedModel = {
+          feed: {
+            timezone: 'GMT+03:50'
+          }
+        };
+
+        var feed = serializer.preDeserialize(feedModel, 'feed');
+
+        expect(feed.timezone).toBe('GMT+03:50');
+      });
+
+    });
+
+    describe('serialize feed into xml', function() {
+
+      it('Should transform the basic properties', function () {
+        var feed = {
+          name: 'FeedName',
+          description: 'Feed Description',
+          groups: 'a,b,c'
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+            "<feed xmlns='uri:falcon:feed:0.1' name='FeedName' description='Feed Description'>" +
+            "<groups>a,b,c</groups>" +
+            "</feed>"
+        );
+
+      });
+
+      it('Should transform tags properly', function () {
+        var feed = {name: 'FeedName',
+          tags: [{key: 'key1', value: 'value1'}, {key: 'key2', value: 'value2'}, {key: null, value: 'value3'}]
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+            "<feed xmlns='uri:falcon:feed:0.1' name='FeedName'>" +
+            "<tags>key1=value1,key2=value2</tags>" +
+            "</feed>"
+        );
+
+      });
+
+      it('Should transform ACL properly', function () {
+        var feed = {name: 'FeedName',
+          ACL: {owner: 'ambari-qa', group: 'users', permission: '0755'}
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+            "<feed xmlns='uri:falcon:feed:0.1' name='FeedName'>" +
+            "<ACL owner='ambari-qa' group='users' permission='0755'/>" +
+            "</feed>"
+        );
+
+      });
+
+      it('Should add an ACL element even though the properties are empty', function () {
+        var feed = {name: 'FeedName',
+          ACL: {owner: null, group: null, permission: null}
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+            "<feed xmlns='uri:falcon:feed:0.1' name='FeedName'>" +
+            "<ACL/>" +
+            "</feed>"
+        );
+
+      });
+
+      it('Should transform schema properly', function () {
+        var feed = {name: 'FeedName',
+          schema: {location: '/location', provider: 'none'}
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+            "<feed xmlns='uri:falcon:feed:0.1' name='FeedName'>" +
+            "<schema location='/location' provider='none'/>" +
+            "</feed>"
+        );
+
+      });
+
+      it('Should add the schema element even though the properties are empty', function () {
+        var feed = {name: 'FeedName',
+          schema: {location: null, provider: null}
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+            "<feed xmlns='uri:falcon:feed:0.1' name='FeedName'>" +
+            "<schema/>" +
+            "</feed>"
+        );
+
+      });
+
+      it('Should transform frequency properly', function () {
+        var feed = {name: 'FeedName',
+          frequency: {quantity: 4, unit: 'weeks'}
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+            "<feed xmlns='uri:falcon:feed:0.1' name='FeedName'>" +
+            "<frequency>weeks(4)</frequency>" +
+            "</feed>"
+        );
+
+      });
+
+      it('Should transform late arrival properly when defined', function () {
+        var feed = {name: 'FeedName',
+          lateArrival: {active: true, cutOff: {quantity: 22, unit: 'hours'}}
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+            "<feed xmlns='uri:falcon:feed:0.1' name='FeedName'>" +
+            "<late-arrival cut-off='hours(22)'/>" +
+            "</feed>"
+        );
+
+      });
+
+      it('Should not transform late arrival properly when quantity is not defined', function () {
+        var feed = {name: 'FeedName',
+          lateArrival: {active: false, cutOff: {quantity: null, unit: 'hours'}}
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+          "<feed xmlns='uri:falcon:feed:0.1' name='FeedName'/>"
+        );
+
+      });
+
+      it('Should transform availability flag', function () {
+        var feed = {name: 'FeedName',
+          availabilityFlag: 'Available'
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+            "<feed xmlns='uri:falcon:feed:0.1' name='FeedName'>" +
+            "<availabilityFlag>Available</availabilityFlag>" +
+            "</feed>"
+        );
+
+      });
+
+      it('Should transform timezone', function () {
+        var feed = {name: 'FeedName',
+          timezone: 'GMT+1:00'
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+            "<feed xmlns='uri:falcon:feed:0.1' name='FeedName'>" +
+            "<timezone>GMT+1:00</timezone>" +
+            "</feed>"
+        );
+
+      });
+
+      it('Should transform queueName, jobPriority and timeout and custom properties', function () {
+        var feed = {name: 'FeedName',
+          properties: [
+            {key: 'queueName', value: 'Queue'},
+            {key: 'jobPriority', value: 'HIGH'},
+            {key: 'timeout', value: {quantity: 7, unit: 'weeks'}}
+          ],
+          customProperties: [
+            {key: 'custom1', value: 'value1'},
+            {key: 'custom2', value: 'value2'}
+          ]
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+            "<feed xmlns='uri:falcon:feed:0.1' name='FeedName'>" +
+            "<properties>" +
+            "<property name='queueName' value='Queue'></property>" +
+            "<property name='jobPriority' value='HIGH'></property>" +
+            "<property name='timeout' value='weeks(7)'></property>" +
+            "<property name='custom1' value='value1'></property>" +
+            "<property name='custom2' value='value2'></property>" +
+            "</properties>" +
+            "</feed>"
+        );
+
+      });
+
+      it('Should transform not add queueName nor timeout if they were not defined', function () {
+        var feed = {name: 'FeedName',
+          properties: [
+            {key: 'queueName', value: null},
+            {key: 'jobPriority', value: 'HIGH'},
+            {key: 'timeout', value: {quantity: null, unit: 'weeks'}}
+          ]
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+            "<feed xmlns='uri:falcon:feed:0.1' name='FeedName'>" +
+            "<properties>" +
+            "<property name='jobPriority' value='HIGH'></property>" +
+            "</properties>" +
+            "</feed>"
+        );
+
+      });
+
+      it('Should transform locations properly if file system storage is active', function () {
+        var feed = {name: 'FeedName',
+          storage: {
+            fileSystem: {
+              active: true,
+              locations: [
+                {type: 'data', path: '/none1'},
+                {type: 'stats', path: '/none2'},
+                {type: 'meta', path: '/none3'}
+              ]
+            }
+          }
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+            "<feed xmlns='uri:falcon:feed:0.1' name='FeedName'>" +
+            "<locations>" +
+            "<location type='data' path='/none1'></location>" +
+            "<location type='stats' path='/none2'></location>" +
+            "<location type='meta' path='/none3'></location>" +
+            "</locations>" +
+            "</feed>"
+        );
+
+      });
+
+      it('Should not transform locations properly if file system storage is not active', function () {
+        var feed = {name: 'FeedName',
+          storage: {
+            fileSystem: {
+              active: false,
+              locations: [
+                {type: 'data', path: '/none1'},
+                {type: 'stats', path: '/none2'},
+                {type: 'meta', path: '/none3'}
+              ]
+            }
+          }
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+          "<feed xmlns='uri:falcon:feed:0.1' name='FeedName'/>"
+        );
+
+      });
+
+      it('Should transform catalog properly if catalog storage is active', function () {
+        var feed = {name: 'FeedName',
+          storage: {
+            catalog: {
+              active: true,
+              catalogTable: {uri: '/none'}
+            }
+          }
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+            "<feed xmlns='uri:falcon:feed:0.1' name='FeedName'>" +
+            "<table uri='/none'/>" +
+            "</feed>"
+        );
+
+      });
+
+      it('Should not transform catalog if catalog storage is not active', function () {
+        var feed = {name: 'FeedName',
+          storage: {
+            catalog: {
+              active: false,
+              catalogTable: {uri: '/none'}
+            }
+          }
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+          "<feed xmlns='uri:falcon:feed:0.1' name='FeedName'/>"
+        );
+
+      });
+
+      xit('Should transform clusters', function () {
+        var feed = {name: 'FeedName',
+          storage: {
+            fileSystem: {active: true, locations: [
+              {type: 'data', path: '/masterpath'}
+            ]},
+            catalog: {active: true, catalogTable: {uri: '/masteruri'}}
+          },
+          clusters: [
+            {
+              name: 'primaryCluster',
+              type: 'source',
+              validity: {start: {date: newUtcDate(2014, 2, 28), time: newUtcTime(0,0)}, end: {date: newUtcDate(2016, 4, 1), time: newUtcTime(0,0)}},
+              retention: {quantity: 2, unit: 'hours', action: 'delete'},
+              storage: {
+                fileSystem: {
+                  active: true,
+                  locations: [
+                    {type: 'data', path: '/none1'},
+                    {type: 'stats', path: '/none2'},
+                    {type: 'meta', path: '/none3'}
+                  ]
+                },
+                catalog: {
+                  active: false,
+                  catalogTable: {uri: '/primaryuri'}
+                }
+              }
+            },
+            {
+              name: 'secondaryCluster',
+              type: 'target',
+              validity: {start: {date: newUtcDate(2015, 2, 28), time: newUtcTime(0,0)}, end: {date: newUtcDate(2017, 4, 1), time: newUtcTime(0,0)}},
+              retention: {quantity: 5, unit: 'weeks', action: 'archive'},
+              storage: {
+                fileSystem: {
+                  active: true,
+                  locations: [
+                    {type: 'data', path: '/none4'},
+                    {type: 'stats', path: '/none5'},
+                    {type: 'meta', path: '/none6'}
+                  ]
+                },
+                catalog: {
+                  active: true,
+                  catalogTable: {uri: '/secondaryuri'}
+                }
+              }
+            }
+          ]
+        };
+
+        var xml = serializer.serialize(feed, 'feed');
+
+        expect(xml).toBe(
+            "<feed xmlns='uri:falcon:feed:0.1' name='FeedName'>" +
+            "<clusters>" +
+            "<cluster name='primaryCluster' type='source'>" +
+            "<validity start='2014-02-28T00:00Z' end='2016-04-01T00:00Z'/>" +
+            "<retention limit='hours(2)' action='delete'/>" +
+            "<locations>" +
+            "<location type='data' path='/none1'></location>" +
+            "<location type='stats' path='/none2'></location>" +
+            "<location type='meta' path='/none3'></location>" +
+            "</locations>" +
+            "<table uri='/primaryuri'/>" +
+            "</cluster>" +
+            "<cluster name='secondaryCluster' type='target'>" +
+            "<validity start='2015-02-28T00:00Z' end='2017-04-01T00:00Z'/>" +
+            "<retention limit='weeks(5)' action='archive'/>" +
+            "<locations>" +
+            "<location type='data' path='/none4'></location>" +
+            "<location type='stats' path='/none5'></location>" +
+            "<location type='meta' path='/none6'></location>" +
+            "</locations>" +
+            "<table uri='/secondaryuri'/>" +
+            "</cluster>" +
+            "</clusters>" +
+            "<locations>" +
+            "<location type='data' path='/masterpath'></location>" +
+            "</locations>" +
+            "<table uri='/masteruri'/>" +
+            "</feed>"
+        );
+
+      });
+
+    });
+
+    describe('deserialize process', function() {
+
+      it('Should copy the general information', function() {
+
+        var processModel = {
+          process: {
+            _xmlns: "uri:falcon:process:0.1",
+            _name: 'ProcessName'
+          }
+        };
+
+        var process = serializer.preDeserialize(processModel, 'process');
+
+        expect(process.name).toBe(processModel.process._name);
+        expect(process.xmlns).toBe(undefined);
+      });
+
+      it('Should copy tags', function() {
+
+        var processModel = {
+          process: {
+            tags: 'owner=USMarketing,classification=Secure'
+          }
+        };
+
+        var process = serializer.preDeserialize(processModel, 'process');
+
+        expect(process.tags[0].key).toBe('owner');
+        expect(process.tags[0].value).toBe('USMarketing');
+        expect(process.tags[1].key).toBe('classification');
+        expect(process.tags[1].value).toBe('Secure');
+      });
+
+      it('Should copy workflow', function() {
+
+        var processModel = {
+          process: {_xmlns: "uri:falcon:process:0.1", _name: 'ProcessName',
+            workflow: {
+              _name: 'emailCleanseWorkflow',
+              _version: '5.0',
+              _engine: 'pig',
+              _path: '/user/ambari-qa/falcon/demo/apps/pig/id.pig'
+            }
+          }
+        };
+
+        var process = serializer.preDeserialize(processModel, 'process');
+
+        expect(process.workflow.name).toBe(processModel.process.workflow._name);
+        expect(process.workflow.version).toBe(processModel.process.workflow._version);
+        expect(process.workflow.engine).toBe(processModel.process.workflow._engine);
+        expect(process.workflow.path).toBe(processModel.process.workflow._path);
+
+      });
+
+      it('Should copy timezone', function() {
+
+        var processModel = {
+          process: {_xmlns: "uri:falcon:process:0.1", _name: 'ProcessName',
+            timezone: 'GMT+1:00'
+          }
+        };
+
+        var process = serializer.preDeserialize(processModel, 'process');
+
+        expect(process.timezone).toBe(processModel.process.timezone);
+      });
+
+      it('Should copy frequency', function() {
+        var processModel = {process: {
+            frequency: 'hours(20)'
+        }};
+
+        var process = serializer.preDeserialize(processModel, 'process');
+
+        expect(process.frequency.unit).toBe('hours');
+        expect(process.frequency.quantity).toBe('20');
+
+      });
+
+      it('Should copy parallel', function() {
+        var processModel = { process: {
+          parallel: 3
+        }};
+
+        var process = serializer.preDeserialize(processModel, 'process');
+
+        expect(process.parallel).toBe(processModel.process.parallel);
+
+      });
+
+      it('Should copy order', function() {
+        var processModel = { process: {
+          order: 'LIFO'
+        }};
+
+        var process = serializer.preDeserialize(processModel, 'process');
+
+        expect(process.order).toBe(processModel.process.order);
+
+      });
+
+      it('Should copy retry', function() {
+        var processModel = { process: {
+          retry: {
+            _policy: 'periodic', _attempts: '3', _delay: 'minutes(15)'
+          }
+        }};
+
+        var process = serializer.preDeserialize(processModel, 'process');
+
+        expect(process.retry.policy).toBe(processModel.process.retry._policy);
+        expect(process.retry.attempts).toBe(processModel.process.retry._attempts);
+        expect(process.retry.delay.quantity).toBe('15');
+        expect(process.retry.delay.unit).toBe('minutes');
+
+      });
+
+      xit('Should copy clusters', function() {
+        var processModel = {
+          process: {
+            clusters: {cluster: [{_name: 'ClusterOne',
+              validity: {
+                _start: '2014-02-28T01:20Z',
+                _end: '2016-03-31T04:30Z'
+              }
+            }]}
+          }
+        };
+
+        var process = serializer.preDeserialize(processModel, 'process');
+
+        expect(process.clusters[0].validity.start.date).toEqual(newUtcDate(2014, 2, 28));
+        expect(process.clusters[0].validity.start.time).toEqual(newUtcTime(1, 20));
+        expect(process.clusters[0].validity.end.date).toEqual(newUtcDate(2016, 3, 31));
+        expect(process.clusters[0].validity.end.time).toEqual(newUtcTime(4, 30));
+
+      });
+
+      it('Should copy inputs', function() {
+        var processModel = {
+          process: {
+            inputs: {input: [
+              {_name: 'input', _feed: 'rawEmailFeed', _start: 'now(0,0)', _end: 'now(0,0)' }
+            ]}
+          }
+        };
+
+        var process = serializer.preDeserialize(processModel, 'process');
+
+        expect(process.inputs[0].name).toEqual('input');
+        expect(process.inputs[0].feed).toEqual('rawEmailFeed');
+        expect(process.inputs[0].start).toEqual('now(0,0)');
+        expect(process.inputs[0].end).toEqual('now(0,0)');
+      });
+
+      it('Should copy outputs', function() {
+        var processModel = {
+          process: {
+            outputs: {output: [
+              {_name: 'output', _feed: 'cleansedEmailFeed', _instance: 'now(0,0)' }
+            ]}
+          }
+        };
+
+        var process = serializer.preDeserialize(processModel, 'process');
+
+        expect(process.outputs[0].name).toEqual('output');
+        expect(process.outputs[0].feed).toEqual('cleansedEmailFeed');
+        expect(process.outputs[0].outputInstance).toEqual('now(0,0)');
+      });
+
+    });
+
+    describe('serialize process into xml', function() {
+
+      it('Should transform the basic properties', function () {
+        var process = {
+          name: 'ProcessName'
+        };
+
+        var xml = serializer.serialize(process, 'process');
+
+        expect(xml).toBe(
+            "<process xmlns='uri:falcon:process:0.1' name='ProcessName'/>"
+        );
+      });
+
+      it('Should transform tags properly', function () {
+        var process = {name: 'ProcessName',
+          tags: [{key: 'key1', value: 'value1'}, {key: 'key2', value: 'value2'}, {key: null, value: 'value3'}]
+        };
+
+        var xml = serializer.serialize(process, 'process');
+
+        expect(xml).toBe(
+            "<process xmlns='uri:falcon:process:0.1' name='ProcessName'>" +
+              "<tags>key1=value1,key2=value2</tags>" +
+            "</process>"
+        );
+      });
+
+      it('Should transform workflow properly', function () {
+        var process = {name: 'ProcessName',
+          workflow: {
+           name: 'emailCleanseWorkflow',
+           engine: 'pig',
+           version: '5.0',
+           path: '/user/ambari-qa/falcon/demo/apps/pig/id.pig'
+          }
+        };
+
+        var xml = serializer.serialize(process, 'process');
+
+        expect(xml).toBe(
+            "<process xmlns='uri:falcon:process:0.1' name='ProcessName'>" +
+              "<workflow name='emailCleanseWorkflow' version='5.0' engine='pig' path='/user/ambari-qa/falcon/demo/apps/pig/id.pig'/>" +
+            "</process>"
+        );
+      });
+
+      it('Should transform timezone', function () {
+        var process = {name: 'ProcessName',
+          timezone: 'GMT+1:00'
+        };
+
+        var xml = serializer.serialize(process, 'process');
+
+        expect(xml).toBe(
+            "<process xmlns='uri:falcon:process:0.1' name='ProcessName'>" +
+              "<timezone>GMT+1:00</timezone>" +
+            "</process>"
+        );
+
+      });
+
+      it('Should transform frequency properly', function () {
+        var process = {name: 'ProcessName',
+          frequency: {quantity: 4, unit: 'weeks'}
+        };
+
+        var xml = serializer.serialize(process, 'process');
+
+        expect(xml).toBe(
+            "<process xmlns='uri:falcon:process:0.1' name='ProcessName'>" +
+              "<frequency>weeks(4)</frequency>" +
+            "</process>"
+        );
+
+      });
+
+      it('Should transform parallel properly', function () {
+        var process = {name: 'ProcessName',
+          parallel: 11
+        };
+
+        var xml = serializer.serialize(process, 'process');
+
+        expect(xml).toBe(
+            "<process xmlns='uri:falcon:process:0.1' name='ProcessName'>" +
+              "<parallel>11</parallel>" +
+            "</process>"
+        );
+
+      });
+
+      it('Should transform order properly', function () {
+        var process = {name: 'ProcessName',
+          order: 'LIFO'
+        };
+
+        var xml = serializer.serialize(process, 'process');
+
+        expect(xml).toBe(
+            "<process xmlns='uri:falcon:process:0.1' name='ProcessName'>" +
+              "<order>LIFO</order>" +
+            "</process>"
+        );
+
+      });
+
+      it('Should transform retry properly', function () {
+        var process = {name: 'ProcessName',
+          retry: {
+            policy: 'periodic',
+            delay: {quantity: 15, unit: 'minutes'},
+            attempts: 3
+          }
+        };
+
+        var xml = serializer.serialize(process, 'process');
+
+        expect(xml).toBe(
+            "<process xmlns='uri:falcon:process:0.1' name='ProcessName'>" +
+              "<retry policy='periodic' delay='minutes(15)' attempts='3'/>" +
+            "</process>"
+        );
+      });
+
+      xit('Should transform clusters', function () {
+
+        var process = {name: 'ProcessName',
+          clusters: [
+            {
+              name: 'primaryCluster',
+              validity: {start: {date: newUtcDate(2014, 2, 28), time: newUtcTime(0,0)}, end: {date: newUtcDate(2016, 4, 1), time: newUtcTime(0,0)}}
+            },
+            {
+              name: 'secondaryCluster',
+              validity: {start: {date: newUtcDate(2015, 2, 28), time: newUtcTime(0,0)}, end: {date: newUtcDate(2017, 4, 1), time: newUtcTime(0,0)}}
+            }
+          ]
+        };
+
+        var xml = serializer.serialize(process, 'process');
+
+        expect(xml).toBe(
+            "<process xmlns='uri:falcon:process:0.1' name='ProcessName'>" +
+              "<clusters>" +
+                "<cluster name='primaryCluster'>" +
+                  "<validity start='2014-02-28T00:00Z' end='2016-04-01T00:00Z'/>" +
+                "</cluster>" +
+                "<cluster name='secondaryCluster'>" +
+                  "<validity start='2015-02-28T00:00Z' end='2017-04-01T00:00Z'/>" +
+                "</cluster>" +
+              "</clusters>" +
+            "</process>"
+        );
+
+      });
+
+      it('Should transform inputs', function () {
+
+        var process = {name: 'ProcessName',
+          inputs: [
+            { name: 'input', feed: 'rawEmailFeed', start: 'now(0,0)', end: 'now(0,0)' }
+          ]
+        };
+
+        var xml = serializer.serialize(process, 'process');
+
+        expect(xml).toBe(
+            "<process xmlns='uri:falcon:process:0.1' name='ProcessName'>" +
+              "<inputs>" +
+                "<input name='input' feed='rawEmailFeed' start='now(0,0)' end='now(0,0)'>" +
+                "</input>" +
+              "</inputs>" +
+            "</process>"
+        );
+
+      });
+
+      it('Should transform outputs', function () {
+
+        var process = {name: 'ProcessName',
+          outputs: [
+            { name: 'output', feed: 'cleansedEmailFeed', outputInstance: 'now(0,0)'}
+          ]
+        };
+
+        var xml = serializer.serialize(process, 'process');
+
+        expect(xml).toBe(
+            "<process xmlns='uri:falcon:process:0.1' name='ProcessName'>" +
+              "<outputs>" +
+                "<output name='output' feed='cleansedEmailFeed' instance='now(0,0)'>" +
+                "</output>" +
+              "</outputs>" +
+            "</process>"
+        );
+
+      });
+
+    });
+
+    function newUtcDate(year, month, day) {
+      return new Date(Date.UTC(year, month, day))
+    }
+
+    function newUtcTime(hours, minutes) {
+      return new Date(Date.UTC(1900, 1, 1, hours, minutes, 0));
+    }
+
+  });
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/test/services/FalconServiceSpec.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/test/services/FalconServiceSpec.js b/falcon-ui/app/test/services/FalconServiceSpec.js
new file mode 100644
index 0000000..04f6b5a
--- /dev/null
+++ b/falcon-ui/app/test/services/FalconServiceSpec.js
@@ -0,0 +1,77 @@
+/**
+ * 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('Falcon', function () {
+    var httpBackendMock;
+    var service;
+    var cookiesMock;
+
+    beforeEach(module('ngCookies', 'app.services.falcon'));
+
+    beforeEach(inject(function($httpBackend, Falcon) {
+      httpBackendMock = $httpBackend;
+      service = Falcon;      
+    }));
+
+    describe('initialize', function() {
+      it('Should set the object Falcon.responses', function() {
+        expect(service.responses).toEqual({
+          display : true,
+          queue:[], 
+          count: {pending: 0, success:0, error:0},
+          multiRequest: {cluster:0, feed:0, process:0},
+          listLoaded: {cluster:false, feed:false, process:false}   
+        });
+      });
+
+    });
+    describe('.logRequest()', function() {
+      it('Should log the pending request', function() {
+        service.logRequest();
+        expect(service.responses.count.pending).toEqual(1);
+        service.logRequest();
+        service.logRequest();
+        service.logRequest();
+        expect(service.responses.count.pending).toEqual(4);  
+      });
+
+      it('Should throw an error when the category does not exist', function() {
+        
+      });
+
+    });
+    describe('Falcon.logResponse(type, messageObject, hide)', function() {
+      it('Should log resolve pending request', function() {
+        var responseOne = {"status":"SUCCEEDED","message":"default/TEST2(FEED) suspended successfully\n","requestId":"default/b3a31c93-23e0-450d-bb46-b3e1be0525ff\n"};
+        service.logRequest();
+        service.logRequest();
+        service.logRequest();
+        expect(service.responses.count.pending).toEqual(3);
+        service.logResponse('success', responseOne, "cluster");
+        expect(service.responses.count.success).toEqual(1);
+        expect(service.responses.count.pending).toEqual(2);
+        
+        service.logResponse('success', responseOne, "cluster");
+        service.logResponse('success', responseOne, "cluster");
+        expect(service.responses.multiRequest.cluster).toEqual(-3);
+      });
+   });
+  });
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/test/services/JsonTransformerSpec.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/test/services/JsonTransformerSpec.js b/falcon-ui/app/test/services/JsonTransformerSpec.js
new file mode 100644
index 0000000..80864f4
--- /dev/null
+++ b/falcon-ui/app/test/services/JsonTransformerSpec.js
@@ -0,0 +1,144 @@
+/**
+ * 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('JsonTransformerFactory', function () {
+    var httpBackendMock;
+    var factory;
+
+    beforeEach(module('app.services.json.transformer'));
+
+    beforeEach(inject(function($httpBackend, JsonTransformerFactory) {
+      httpBackendMock = $httpBackend;
+      factory = JsonTransformerFactory;
+    }));
+
+    describe('Field transformation', function() {
+
+
+      it('Should create a new field transformation from source and target', function() {
+        var transformation = factory.transform('name', '_name');
+        var  source = {name: 'someName'}, target = {};
+
+        transformation.apply(source, target);
+
+        expect(target).toEqual({_name: 'someName'});
+      });
+
+      it('Should not add the element if it does not exist', function() {
+        var transformation = factory.transform('name', '_name');
+        var  source = {}, target = {};
+
+        transformation.apply(source, target);
+
+        expect(target).toEqual({});
+      });
+
+      it('Should not add the element if it is null', function() {
+        var transformation = factory.transform('name', '_name');
+        var  source = {name: null}, target = {};
+
+        transformation.apply(source, target);
+
+        expect(target).toEqual({});
+      });
+
+      it('Should create a new field transformation implying the target field', function() {
+        var transformation = factory.transform('name');
+        var  source = {name: 'someName'}, target = {};
+
+        transformation.apply(source, target);
+
+        expect(target).toEqual({name: 'someName'});
+      });
+
+
+      it('Should create a new field transformation for a nested property', function() {
+        var transformation = factory.transform('nested.key', 'nested1.nested2.nested3.key');
+        var  source = {nested: {key: 'key', key2: 'key2'}}, target = {};
+
+        transformation.apply(source, target);
+
+        expect(target).toEqual({nested1: {nested2: { nested3: {key: 'key'}}}});
+      });
+
+      it('Should be able to apply many transformations one after the other', function() {
+        var transformation1 = factory.transform('nested.key', 'nested1.nested2.nested3.key');
+        var transformation2 = factory.transform('nested.key2', 'nested1.nested2._key');
+        var  source = {nested: {key: 'key', key2: 'key2'}}, target = {};
+
+        transformation1.apply(source, target);
+        transformation2.apply(source, target);
+
+        expect(target).toEqual({nested1: {nested2: { _key: 'key2', nested3: {key: 'key'}}}});
+      });
+
+      it('Should work for arrays too', function() {
+        var transformation = factory.transform('nested.key', 'key');
+        var  source = {nested: {key: ['a', 'b']}}, target = {};
+
+        transformation.apply(source, target);
+
+        expect(target).toEqual({key: ['a','b']});
+      });
+
+      it('Should support custom mapping functions', function() {
+        var mapping = function (input) {
+          return input.key + '=' + input.value;
+        };
+        var transformation =  factory.transform('nested.value', 'nested1.nested2', mapping);
+        var  source = {nested: {value: {key: 'key', value: 'value'}}}, target = {};
+
+        transformation.apply(source, target);
+
+        expect(target).toEqual({nested1: {nested2: 'key=value'}});
+      });
+
+
+      it('Should be able to chain transformations', function() {
+        var  source = {nested: {key: 'key', key2: 'key2'}}, target = {};
+
+        var transformation = factory
+          .transform('nested.key', 'nested1.nested2.nested3.key')
+          .transform('nested.key2', 'nested1.nested2._key')
+          .transform('nested.key2', 'nested1.nested2._key')
+          .transform('nested.key2', 'nested1.nested2._key');
+
+        transformation.apply(source, target);
+
+        expect(target).toEqual({nested1: {nested2: { _key: 'key2', nested3: {key: 'key'}}}});
+      });
+
+      it('Should be able transform arrays', function() {
+        var  source = {nested: {list: [{key: 'key'}], key2: 'key2'}}, target = {};
+        var nestedTransformation = factory.transform('key', '_key');
+        var transformation = factory.transform('nested.list', 'nested1.nested2.list', function (list) {
+          return list.map(function(input) { return nestedTransformation.apply(input, {}); });
+        });
+
+
+        transformation.apply(source, target);
+
+        expect(target).toEqual({nested1: {nested2: {list: [{_key: 'key'}]}}});
+      });
+
+
+    });
+  });
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/test/services/ValdationServiceSpec.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/test/services/ValdationServiceSpec.js b/falcon-ui/app/test/services/ValdationServiceSpec.js
new file mode 100644
index 0000000..6699892
--- /dev/null
+++ b/falcon-ui/app/test/services/ValdationServiceSpec.js
@@ -0,0 +1,70 @@
+/**
+ * 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('ValidationService', function () {
+    var validationService;
+
+    beforeEach(module('app.services.validation'));
+
+    beforeEach(inject(function (ValidationService) {
+      validationService = ValidationService;
+    }));
+
+    describe('init', function () {
+      it('Should return a validation object service', function () {
+        expect(validationService).toBeDefined();
+      });
+    });
+    describe('messages', function () {
+      it('Should return a messages validation object', function () {
+        expect(validationService.messages).toBeDefined();
+      });
+    });
+    describe('patterns', function () {
+      it('Should return an object with the validation patterns', function () {
+        expect(validationService.patterns).toBeDefined();
+        expect(validationService.patterns.name).toEqual(/^[a-zA-Z0-9]{1,39}$/);
+
+      });
+    });
+    describe('nameAvailable', function () {
+      it('Should return a boolean with the name availability', function () {
+        expect(validationService.nameAvailable).toBe(true);
+      });
+    });
+    describe('displayValidations', function () {
+      it('Should return an object with the displays in false', function () {
+        expect(validationService.displayValidations).toEqual({show: false, nameShow: false});
+      });
+    });
+    describe('acceptOnlyNumber', function () {
+      it('Should return an method to prevent default if no number typed', function () {
+        var isFunction = validationService.acceptOnlyNumber instanceof Function;
+        expect(isFunction).toBe(true);
+      });
+    });
+    describe('acceptNoSpaces', function () {
+      it('Should return a method prevent default if space typed', function () {
+        var isFunction = validationService.acceptNoSpaces instanceof Function;
+        expect(isFunction).toBe(true);
+      });
+    });
+  });
+}());
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/app/test/services/X2jsServiceSpec.js
----------------------------------------------------------------------
diff --git a/falcon-ui/app/test/services/X2jsServiceSpec.js b/falcon-ui/app/test/services/X2jsServiceSpec.js
new file mode 100644
index 0000000..1d7621d
--- /dev/null
+++ b/falcon-ui/app/test/services/X2jsServiceSpec.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';
+
+  describe('X2jsService', function () {
+    var x2jsService;
+
+    beforeEach(module('app.services'));
+
+    beforeEach(inject(function(X2jsService) {
+      x2jsService = X2jsService;
+    }));
+
+    describe('prettifyXml', function() {
+      it('Should prettify the xml', function() {
+        var uglyXml = '<markup><child></child></markup>';
+
+        expect(x2jsService.prettifyXml(uglyXml)).toNotBe(null);
+      });
+    });
+
+    describe('prettifyXml', function() {
+      it('Should prettify the xml', function() {
+        var uglyXml = '<markup><child></child></markup>';
+
+        expect(x2jsService.prettifyXml(uglyXml)).toNotBe(null);
+      });
+    });
+
+    describe('Arrays configuration', function() {
+      it('Should convert feed.properties.property as an array when only one element', function() {
+
+        var xml =
+          '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
+          '<feed name="feedOne" description="feedOneDescription" xmlns="uri:falcon:feed:0.1">' +
+            '<properties>' +
+              '<property name="jobPriority" value="VERY_LOW"/>' +
+            '</properties>' +
+          '</feed>';
+
+        var wrapper = x2jsService.xml_str2json(xml);
+
+        expect(wrapper.feed.properties.property).toEqual(
+          [{_name: 'jobPriority', _value: 'VERY_LOW'}]
+        );
+
+      });
+
+
+
+      it('Should convert feed.locations.location as an array when only one element', function() {
+
+        var xml =
+          '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
+          '<feed name="feedOne" description="feedOneDescription" xmlns="uri:falcon:feed:0.1">' +
+            '<locations>' +
+              '<location type="data" path="/path"/>' +
+            '</locations>' +
+          '</feed>';
+
+        var wrapper = x2jsService.xml_str2json(xml);
+
+        expect(wrapper.feed.locations.location).toEqual(
+          [{_type: 'data', _path: '/path'}]
+        );
+
+      });
+
+      it('Should convert feed.clusters.cluster as an array when only one element', function() {
+
+        var xml =
+          '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
+          '<feed name="feedOne" description="feedOneDescription" xmlns="uri:falcon:feed:0.1">' +
+            '<clusters>' +
+              '<cluster name="ClusterName" type="source"/>' +
+            '</clusters>' +
+          '</feed>';
+
+        var wrapper = x2jsService.xml_str2json(xml);
+
+        expect(wrapper.feed.clusters.cluster).toEqual(
+          [{_name: 'ClusterName', _type: 'source'}]
+        );
+
+      });
+
+      it('Should convert feed.clusters.cluster.locations.location as an array when only one element', function() {
+
+        var xml =
+          '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
+          '<feed name="feedOne" description="feedOneDescription" xmlns="uri:falcon:feed:0.1">' +
+            '<clusters>' +
+              '<cluster name="ClusterName" type="source">' +
+                '<locations>' +
+                  '<location type="data" path="/path" />' +
+                '</locations>' +
+              '</cluster>' +
+            '</clusters>' +
+          '</feed>';
+
+        var wrapper = x2jsService.xml_str2json(xml);
+
+        expect(wrapper.feed.clusters.cluster[0].locations.location).toEqual(
+          [{_type: 'data', _path: '/path'}]
+        );
+
+      });
+
+    });
+
+  });
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/bower.json
----------------------------------------------------------------------
diff --git a/falcon-ui/bower.json b/falcon-ui/bower.json
new file mode 100644
index 0000000..d924150
--- /dev/null
+++ b/falcon-ui/bower.json
@@ -0,0 +1,18 @@
+{
+  "name": "falcon-ui",
+  "version": "1.0.0",
+  "homepage": "empty",
+  "authors": [
+    "Apache Foundation"
+  ],
+  "description": "falcon-ui",
+  "main": "app/index.html",
+  "license": "MIT",
+  "ignore": [
+    "**/.*",
+    "node_modules",
+    "bower_components",
+    "app/js/lib",
+    "app/test"
+  ]
+}

http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/express-data/mockData.js
----------------------------------------------------------------------
diff --git a/falcon-ui/express-data/mockData.js b/falcon-ui/express-data/mockData.js
new file mode 100644
index 0000000..cdd783c
--- /dev/null
+++ b/falcon-ui/express-data/mockData.js
@@ -0,0 +1,204 @@
+/**
+ * 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';  
+  
+  function findByNameInList(type, name) {
+    var i;
+    for (i = 0; i < entitiesList[type].entity.length; i++) {
+      if (entitiesList[type].entity[i]["name"] === name) {       
+        return i;     
+      }
+    } 
+  }
+
+  var entitiesList = {
+    cluster : {
+      "entity":[
+        {"type":"cluster","name":"completeCluster","status":"SUBMITTED","list":{"tag":["uruguay=mvd","argentina=bsas","mexico=mex", "usa=was"]}},
+        {"type":"cluster","name":"primaryCluster","status":"SUBMITTED"}
+      ]
+    },
+    feed: {
+      "entity":[
+        {"type":"FEED","name":"feedOne","status":"SUBMITTED","list":{"tag":["externalSystem=USWestEmailServers","classification=secure"]}},
+        {"type":"FEED","name":"feedTwo","status":"RUNNING","list":{"tag":["owner=USMarketing","classification=Secure","externalSource=USProdEmailServers","externalTarget=BITools"]}}
+      ]
+    },
+    process:{"entity":[
+      {"type":"process","name":"processOne","status":"SUBMITTED","list":{"tag":["pipeline=churnAnalysisDataPipeline","owner=ETLGroup","montevideo=mvd"]}},
+      {"type":"process","name":"processTwo","status":"SUBMITTED","list":{"tag":["pipeline=churnAnalysisDataPipeline","owner=ETLGroup","externalSystem=USWestEmailServers"]}}]}
+    },
+    definitions = {
+      CLUSTER: {
+        completeCluster : '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
+          '<cluster name="completeCluster" description="desc" colo="colo" xmlns="uri:falcon:cluster:0.1">' +
+            '<tags>uruguay=mvd,argentina=bsas</tags>' +
+            '<interfaces>' +
+              '<interface type="readonly" endpoint="hftp://sandbox.hortonworks.com:50070" version="2.2.0"/>' +
+              '<interface type="write" endpoint="hdfs://sandbox.hortonworks.com:8020" version="2.2.0"/>' +
+              '<interface type="execute" endpoint="sandbox.hortonworks.com:8050" version="2.2.0"/>' +
+              '<interface type="workflow" endpoint="http://sandbox.hortonworks.com:11000/oozie/" version="4.0.0"/>' +
+              '<interface type="messaging" endpoint="tcp://sandbox.hortonworks.com:61616?daemon=true" version="5.1.6"/>' +
+              '<interface type="registry" endpoint="thrift://localhost:9083" version="0.11.0"/>' +
+            '</interfaces>' +
+            '<locations>' +
+              '<location name="staging" path="/apps/falcon/backupCluster/staging"/>' +
+              '<location name="temp" path="/tmp"/>' +
+              '<location name="working" path="/apps/falcon/backupCluster/working"/>' +
+            '</locations>' +
+            '<ACL owner="ambari-qa" group="users" permission="0755"/>' +
+            '<properties>' +
+              '<property name="this" value="property"/>' +
+            '</properties>' +   
+          '</cluster>',
+        primaryCluster: '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
+          '<cluster name="primaryCluster" description="oregonHadoopCluster" colo="USWestOregon" xmlns="uri:falcon:cluster:0.1">' +
+            '<interfaces>' +
+              '<interface type="readonly" endpoint="hftp://sandbox.hortonworks.com:50070" version="2.2.0"/>' +
+              '<interface type="write" endpoint="hdfs://sandbox.hortonworks.com:8020" version="2.2.0"/>' +
+              '<interface type="execute" endpoint="sandbox.hortonworks.com:8050" version="2.2.0"/>' +
+              '<interface type="workflow" endpoint="http://sandbox.hortonworks.com:11000/oozie/" version="4.0.0"/>' +
+              '<interface type="messaging" endpoint="tcp://sandbox.hortonworks.com:61616?daemon=true" version="5.1.6"/>' +
+            '</interfaces>' +
+            '<locations>' +
+              '<location name="staging" path="/apps/falcon/primaryCluster/staging"/>' +
+              '<location name="temp" path="/tmp"/>' +
+              '<location name="working" path="/apps/falcon/primaryCluster/working"/>' +
+            '</locations>' +
+          '</cluster>'
+      },
+      FEED: {
+        feedOne: '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
+          '<feed name="feedOne" description="Raw customer email feed" xmlns="uri:falcon:feed:0.1">' +
+            '<tags>externalSystem=USWestEmailServers,classification=secure</tags>' +
+            '<frequency>hours(1)</frequency>' +
+            '<timezone>UTC</timezone>' +
+            '<late-arrival cut-off="hours(4)"/>' +
+            '<clusters>' +
+              '<cluster name="primaryCluster" type="source">' +
+                '<validity start="2014-02-28T00:00Z" end="2016-04-01T00:00Z"/>' +
+                '<retention limit="days(90)" action="delete"/>' +
+                '<locations>' +
+                  '<location type="data" path="/"/>' +
+                  '<location type="stats" path="/"/>' +
+                  '<location type="meta" path="/"/>' +
+                '</locations>' +
+              '</cluster>' +
+            '</clusters>' +
+            '<locations>' +
+              '<location type="data" path="hdfs://sandbox.hortonworks.com:8020/"/>' +
+              '<location type="stats" path="/none"/>' +
+              '<location type="meta" path="/none"/>' +
+            '</locations>' +
+            '<ACL owner="ambari-qa" group="users" permission="0755"/>' +
+            '<schema location="/none" provider="none"/>' +
+            '<properties>' +
+              '<property name="queueName" value="default"/>' +
+              '<property name="jobPriority" value="NORMAL"/>' +
+              '<property name="timeout" value="hours(1)"/>' +
+              '<property name="parallel" value="3"/>' +
+              '<property name="maxMaps" value="8"/>' +
+              '<property name="mapBandwidthKB" value="1024"/>' +
+            '</properties>' +
+          '</feed>',
+        feedTwo: '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
+          '<feed name="feedTwo" description="Cleansed customer emails" xmlns="uri:falcon:feed:0.1">' +
+            '<tags>owner=USMarketing,classification=Secure,externalSource=USProdEmailServers,externalTarget=BITools</tags>' +
+            '<groups>churnAnalysisDataPipeline</groups>' +
+            '<frequency>hours(1)</frequency>' +
+            '<timezone>UTC</timezone>' +
+            '<clusters>' +
+              '<cluster name="primaryCluster" type="source">' +
+                '<validity start="2014-02-28T00:00Z" end="2016-04-01T00:00Z"/>' +
+                '<retention limit="days(90)" action="delete"/>' +
+                '<locations>' +
+                  '<location type="data" path="/user/ambari-qa/falcon/demo/primary/processed/enron/${YEAR}-${MONTH}-${DAY}-${HOUR}"/>' +
+                '</locations>' +
+              '</cluster>' +
+              '<cluster name="backupCluster" type="target">' +
+                '<validity start="2014-02-28T00:00Z" end="2016-04-01T00:00Z"/>' +
+                '<retention limit="months(36)" action="delete"/>' +
+                '<locations>' +
+                  '<location type="data" path="/falcon/demo/bcp/processed/enron/${YEAR}-${MONTH}-${DAY}-${HOUR}"/>' +
+                '</locations>' +
+              '</cluster>' +
+            '</clusters>' +
+            '<locations>' +
+              '<location type="data" path="/user/ambari-qa/falcon/demo/processed/enron/${YEAR}-${MONTH}-${DAY}-${HOUR}"/>' +
+            '</locations>' +
+            '<ACL owner="ambari-qa" group="users" permission="0755"/>' +
+            '<schema location="/none" provider="none"/>' +
+            '<properties>' +
+              '<property name="queueName" value="default"/>' +
+              '<property name="jobPriority" value="NORMAL"/>' +
+              '<property name="timeout" value="hours(1)"/>' +
+              '<property name="parallel" value="3"/>' +
+              '<property name="maxMaps" value="8"/>' +
+              '<property name="mapBandwidthKB" value="1024"/>' +
+            '</properties>' +
+          '</feed>'
+      },
+      PROCESS: {
+        processOne: '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
+          '<process name="processOne" xmlns="uri:falcon:process:0.1">' +
+            '<tags>pipeline=churnAnalysisDataPipeline,owner=ETLGroup,montevideo=mvd</tags>' +
+            '<clusters>' +
+              '<cluster name="primaryCluster">' +
+                '<validity start="2014-02-28T00:00Z" end="2016-04-01T00:00Z"/>' +
+              '</cluster>' +
+            '</clusters>' +
+            '<parallel>1</parallel>' +
+            '<order>FIFO</order>' +
+            '<frequency>hours(1)</frequency>' +
+            '<timezone>GMT-12:00</timezone>' +
+            '<inputs>' +
+              '<input name="input" feed="rawEmailFeed" start="now(0,0)" end="now(0,0)"/>' +
+            '</inputs>' +
+            '<outputs>' +
+              '<output name="output" feed="cleansedEmailFeed" instance="now(0,0)"/>' +
+            '</outputs>' +
+            '<workflow name="emailCleanseWorkflow2" version="pig-0.10.0" engine="pig" path="/user/ambari-qa/falcon/demo/apps/pig/id.pig"/>' +
+            '<retry policy="periodic" delay="minutes(15)" attempts="3"/>' +
+          '</process>',
+        processTwo: '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
+          '<process name="processTwo" xmlns="uri:falcon:process:0.1">' +
+            '<tags>pipeline=churnAnalysisDataPipeline,owner=ETLGroup,externalSystem=USWestEmailServers</tags>' +
+            '<clusters>' +
+              '<cluster name="primaryCluster">' +
+                '<validity start="2014-02-28T00:00Z" end="2016-03-31T00:00Z"/>' +
+              '</cluster>' +
+            '</clusters>' +
+            '<parallel>1</parallel>' +
+            '<order>FIFO</order>' +
+            '<frequency>hours(1)</frequency>' +
+            '<timezone>UTC</timezone>' +
+            '<outputs>' +
+              '<output name="output" feed="rawEmailFeed" instance="now(0,0)"/>' +
+            '</outputs>' +
+            '<workflow name="emailIngestWorkflow" version="2.0.0" engine="oozie" path="/user/ambari-qa/falcon/demo/apps/ingest/fs"/>' +
+            '<retry policy="periodic" delay="minutes(15)" attempts="3"/>' +
+          '</process>'
+        }
+      };
+  
+  exports.findByNameInList = findByNameInList;
+  exports.entitiesList = entitiesList;
+  exports.definitions = definitions;
+  
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/karma.conf.js
----------------------------------------------------------------------
diff --git a/falcon-ui/karma.conf.js b/falcon-ui/karma.conf.js
new file mode 100644
index 0000000..11189e8
--- /dev/null
+++ b/falcon-ui/karma.conf.js
@@ -0,0 +1,81 @@
+// Karma configuration
+// Generated on Wed Sep 24 2014 21:15:41 GMT-0500 (CDT)
+
+module.exports = function(config) {
+  config.set({
+
+    // base path that will be used to resolve all patterns (eg. files, exclude)
+    basePath: '',
+
+
+    // frameworks to use
+    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+    frameworks: ['jasmine'],
+
+
+    // list of files / patterns to load in the browser
+    files: [
+      'app/js/lib/angular.min.js',
+      'app/js/lib/angular-cookies.min.js',
+      'app/js/lib/angular-messages.min.js',
+      'app/js/lib/angular-mocks.js',
+      'app/js/lib/jquery-1.11.1.min.js',
+      'app/js/lib/d3.min.js',
+      'app/js/lib/ui-bootstrap-tpls-0.11.0.min.js',
+      'app/js/lib/uirouter.min.js',
+      'app/js/lib/xml2json.min.js',
+      'app/js/controllers/**/*-module.js',
+      'app/js/controllers/**/*.js',
+      'app/js/services/**/*.js',
+      'app/js/directives/**/*.js',
+      'app/js/app.js',
+      'app/test/**/*Spec.js'
+    ],
+
+
+
+
+    // list of files to exclude
+    exclude: [
+    ],
+
+
+    // preprocess matching files before serving them to the browser
+    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+    preprocessors: {
+    },
+
+
+    // test results reporter to use
+    // possible values: 'dots', 'progress'
+    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+    reporters: ['progress'],
+
+
+    // web server port
+    port: 9876,
+
+
+    // enable / disable colors in the output (reporters and logs)
+    colors: true,
+
+
+    // level of logging
+    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+    logLevel: config.LOG_INFO,
+
+
+    // enable / disable watching file and executing tests whenever any file changes
+    autoWatch: false,
+
+
+    // start these browsers
+    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+    browsers: ['PhantomJS'],
+
+
+    // Continuous Integration mode
+    // if true, Karma captures browsers, runs the tests and exits
+    singleRun: false
+  });
+};

http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/package.json
----------------------------------------------------------------------
diff --git a/falcon-ui/package.json b/falcon-ui/package.json
new file mode 100644
index 0000000..dda4855
--- /dev/null
+++ b/falcon-ui/package.json
@@ -0,0 +1,38 @@
+{
+  "name": "FalconUI",
+  "version": "0.0.1",
+  "description": "UI to communicate with Apache Falcon",
+  "main": "server.js",
+  "scripts": {
+    "start": "node server.js"
+  },
+  "author": "Apache Foundation",
+  "license": "ISC",
+  "devDependencies": {
+    "angular-mocks": "^1.2.22",
+    "body-parser": "~1.0.2",
+    "bower": "^1.3.12",
+    "express": "~4.0.0",
+    "file": "~0.2.2",
+    "grunt": "~0.4.4",
+    "grunt-cli": "~0.1.9",
+    "grunt-contrib-clean": "^0.6.0",
+    "grunt-contrib-copy": "~0.5.0",
+    "grunt-contrib-concat": "^0.5.0",
+    "grunt-contrib-csslint": "~0.2.0",
+    "grunt-contrib-jshint": "~0.10.0",
+    "grunt-contrib-less": "~0.11.0",
+    "grunt-contrib-uglify": "~0.4.0",
+    "grunt-contrib-watch": "~0.6.1",
+    "grunt-datauri": "~0.4.0",
+    "grunt-express-server": "~0.4.13",
+    "grunt-karma": "^0.9.0",
+    "grunt-scp": "^0.1.7",
+    "jasmine": "^2.0.1",
+    "karma": "^0.12.23",
+    "karma-jasmine": "^0.1.5",
+    "karma-phantomjs-launcher": "^0.1.4",
+    "phantomjs": "^1.9.10",
+    "protractor": "^1.3.1"
+  }
+}

http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/pom.xml
----------------------------------------------------------------------
diff --git a/falcon-ui/pom.xml b/falcon-ui/pom.xml
new file mode 100644
index 0000000..8853937
--- /dev/null
+++ b/falcon-ui/pom.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.falcon</groupId>
+    <artifactId>falcon-main</artifactId>
+    <version>0.7-SNAPSHOT</version>
+  </parent>
+  <artifactId>falcon-ui</artifactId>
+  <packaging>pom</packaging>
+  <name>Apache Falcon UI</name>
+  <description>Apache Falcon UI Application</description>
+  <build>
+    <plugins>
+
+      <plugin>
+        <groupId>com.github.eirslett</groupId>
+        <artifactId>frontend-maven-plugin</artifactId>
+        <version>0.0.14</version>
+        <executions>
+
+          <execution>
+            <id>install node and npm</id>
+            <goals>
+              <goal>install-node-and-npm</goal>
+            </goals>
+            <configuration>
+              <nodeVersion>v0.10.30</nodeVersion>
+              <npmVersion>1.4.3</npmVersion>
+            </configuration>
+          </execution>
+
+          <execution>
+            <id>npm install</id>
+            <goals>
+              <goal>npm</goal>
+            </goals>
+            <configuration>
+              <arguments>install</arguments>
+            </configuration>
+          </execution>
+
+          <execution>
+            <id>grunt build</id>
+            <goals>
+              <goal>grunt</goal>
+            </goals>
+            <configuration>
+              <arguments>build</arguments>
+            </configuration>
+          </execution>
+
+          <execution>
+            <id>grunt test</id>
+            <goals>
+              <goal>grunt</goal>
+            </goals>
+            <configuration>
+              <arguments>test</arguments>
+            </configuration>
+          </execution>
+
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.rat</groupId>
+        <artifactId>apache-rat-plugin</artifactId>
+        <configuration>
+          <excludes>
+            <exclude>app/js/lib/**</exclude>
+            <exclude>app/css/bootstrap/**</exclude>
+            <exclude>app/test/lib/**</exclude>
+            <exclude>app/css/fonts/**</exclude>
+            <exclude>app/config/loginData.js</exclude>
+            <exclude>dist/**</exclude>
+            <exclude>node/**</exclude>
+	    <exclude>node_modules/**</exclude>
+            <exclude>package.json</exclude>
+            <exclude>bower.json</exclude>
+            <exclude>karma.conf.js</exclude>
+            <exclude>bower.json</exclude>
+            <exclude>app/css/main.css</exclude>
+            <exclude>app/test/e2e/protractor.js</exclude>
+          </excludes>
+        </configuration>
+      </plugin>
+
+    </plugins>
+  </build>
+</project>

http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/falcon-ui/server.js
----------------------------------------------------------------------
diff --git a/falcon-ui/server.js b/falcon-ui/server.js
new file mode 100644
index 0000000..422bcf9
--- /dev/null
+++ b/falcon-ui/server.js
@@ -0,0 +1,143 @@
+/**
+ * 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 bodyParser = require('body-parser'),
+    express = require('express'),
+    mockData = require('./express-data/mockData.js'),
+    server = express(),
+    PORT = 3000;
+
+  server.use('/', express.static(__dirname + '/dist'));
+  server.use(bodyParser());
+  server.use(function (req, res, next) {
+    if (req.is('text/*')) {
+      req.text = '';
+      req.setEncoding('utf8');
+      req.on('data', function (chunk) { req.text += chunk; });
+      req.on('end', next);
+    } else {
+      next();
+    }
+  });
+
+  server.get('/api/entities/list/:type', function (req, res) {
+    var type = req.params.type;
+    res.json(mockData.entitiesList[type]);
+  });
+
+  server.get('/api/entities/definition/:type/:name', function(req, res) {
+    var type = req.params.type.toUpperCase(),
+      name = req.params.name;
+    if (mockData.definitions[type][name]) {
+      res.send(200, mockData.definitions[type][name]);
+    } else {
+      res.send(404, "not found");
+    }
+  });
+
+  server.post('/api/entities/submit/:type', function (req, res) {
+    var type = req.params.type.toUpperCase(),
+      text = req.text,
+      name,
+      indexInArray,
+      responseSuccessMessage,
+      responseFailedMessage,
+      initialIndex = text.indexOf("name") + 6,
+      finalIndex = getFinalIndexOfName(),
+      i;
+    function getFinalIndexOfName () {
+      for (i = initialIndex; i < text.length; i++) {
+        if (text[i] === '"' || text[i] === "'") {
+          return i;
+        }
+      }
+    }
+    name = text.slice(initialIndex, finalIndex);
+    responseSuccessMessage = {"status": "SUCCEEDED", "message": "default/successful (" + type + ") " + name + "\n\n","requestId":"default/546cbe05-2cb3-4e5c-8e7a-b1559d866c99\n"};
+    responseFailedMessage = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><result><status>FAILED</status><message>(' + type + ') '+ name +' already registered with configuration store. Can\'t be submitted again. Try removing before submitting.</message><requestId>586fffcd-10c1-4975-8dda-4b34a712f2f4</requestId></result>';
+
+    if (!mockData.definitions[type][name]) {
+      mockData.definitions[type][name] = text;
+      mockData.entitiesList[type.toLowerCase()].entity.push(
+        {"type": type, "name": name, "status": "SUBMITTED"}
+      );
+      res.send(200, responseSuccessMessage);
+    } else {
+      res.send(404, responseFailedMessage);
+    }
+  });
+
+  server.post('/api/entities/schedule/:type/:name', function (req, res) {
+    var type = req.params.type.toLowerCase(),
+      name = req.params.name,
+      indexInArray = mockData.findByNameInList(type, name),
+      responseMessage = {
+        "status": "SUCCEEDED",
+        "message": "default/" + name + "(" + type + ") scheduled successfully\n",
+        "requestId": "default/546cbe05-2cb3-4e5c-8e7a-b1559d866c99\n"
+      };
+    mockData.entitiesList[type].entity[indexInArray].status = "RUNNING";
+    res.json(200, responseMessage);
+  });
+
+  server.post('/api/entities/suspend/:type/:name', function (req, res) {
+    var type = req.params.type.toLowerCase(),
+      name = req.params.name,
+      indexInArray = mockData.findByNameInList(type, name),
+      responseMessage = {
+        "status": "SUCCEEDED",
+        "message": "default/" + name + "(" + type + ") suspended successfully\n",
+        "requestId": "default/546cbe05-2cb3-4e5c-8e7a-b1559d866c99\n"
+      };
+    mockData.entitiesList[type].entity[indexInArray].status = "SUSPENDED";
+    res.json(200, responseMessage);
+  });
+
+  server.post('/api/entities/resume/:type/:name', function (req, res) {
+    var type = req.params.type.toLowerCase(),
+      name = req.params.name,
+      indexInArray = mockData.findByNameInList(type, name),
+      responseMessage = {
+        "status": "SUCCEEDED",
+        "message": "default/" + name + "(" + type + ") resumed successfully\n",
+        "requestId": "default/546cbe05-2cb3-4e5c-8e7a-b1559d866c99\n"
+      };
+    mockData.entitiesList[type].entity[indexInArray].status = "RUNNING";
+    res.json(200, responseMessage);
+  });
+
+  server.delete('/api/entities/delete/:type/:name', function (req, res) {
+    var type = req.params.type,
+      name = req.params.name,
+      responseMessage = {
+        "status": "SUCCEEDED",
+        "message": "falcon/default/" + name + "(" + type + ")removed successfully (KILLED in ENGINE)\n\n",
+        "requestId": "falcon/default/13015853-8e40-4923-9d32-6d01053c31c6\n\n"
+      },
+      indexInArray = mockData.findByNameInList(type, name);
+    mockData.entitiesList[type].entity.splice(indexInArray, 1);
+    res.json(200, responseMessage);
+  });
+
+  server.listen(PORT, function () {
+    console.log('Dev server listening on port ' + PORT);
+  });
+
+}());

http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index d290ebf..ee743c5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -371,6 +371,7 @@
     </profiles>
 
     <modules>
+	<module>falcon-ui</module>
         <module>checkstyle</module>
         <module>build-tools</module>
         <module>client</module>

http://git-wip-us.apache.org/repos/asf/falcon/blob/c4df0a5e/webapp/pom.xml
----------------------------------------------------------------------
diff --git a/webapp/pom.xml b/webapp/pom.xml
index 556b171..063d42c 100644
--- a/webapp/pom.xml
+++ b/webapp/pom.xml
@@ -44,6 +44,10 @@
                             <packagingExcludes>WEB-INF/classes/deploy.properties</packagingExcludes>
                             <webResources>
                                 <resource>
+                                    <directory>../falcon-ui/dist</directory>
+                                    <targetPath>public</targetPath>
+                                </resource>
+                                <resource>
                                     <directory>src/main/webapp/WEB-INF/distributed</directory>
                                     <targetPath>WEB-INF</targetPath>
                                 </resource>
@@ -241,6 +245,10 @@
                             <directory>../html5-ui</directory>
                         </resource>
                         <resource>
+                            <directory>../falcon-ui/dist</directory>
+                            <targetPath>public</targetPath>
+                        </resource>
+                        <resource>
                             <directory>src/main/webapp/WEB-INF/embedded</directory>
                             <targetPath>WEB-INF</targetPath>
                         </resource>
@@ -479,6 +487,34 @@
                 </executions>
             </plugin>
 
+	    <plugin>
+              <groupId>org.apache.rat</groupId>
+              <artifactId>apache-rat-plugin</artifactId>
+              <configuration>
+                <excludes>
+		  <exclude>*.txt</exclude>
+	          <exclude>**/*.txt</exclude>
+	          <exclude>.git/**</exclude>
+	          <exclude>**/.idea/**</exclude>
+	          <exclude>**/*.twiki</exclude>
+	          <exclude>**/*.iml</exclude>
+	          <exclude>**/target/**</exclude>
+	          <exclude>**/activemq-data/**</exclude>
+	          <exclude>**/build/**</exclude>
+	          <exclude>**/*.patch</exclude>
+	          <exclude>derby.log</exclude>
+	          <exclude>**/logs/**</exclude>
+	          <exclude>**/.classpath</exclude>
+	          <exclude>**/.project</exclude>
+	          <exclude>**/.settings/**</exclude>
+	          <exclude>**/test-output/**</exclude>
+	          <exclude>**/data.txt</exclude>
+	          <exclude>**/maven-eclipse.xml</exclude>
+	          <exclude>**/.externalToolBuilders/**</exclude>
+                </excludes>
+              </configuration>
+      	    </plugin>	
+
         </plugins>
     </build>
 


Mime
View raw message