Browse Source

AMBARI-16195 Added pagination, and search fields for View URLs, made the UX better and added tests (Ashwin Rajeev via dipayanb)

Dipayan Bhowmick 9 years ago
parent
commit
f57185fbfe

+ 45 - 65
ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlCtrl.js

@@ -25,8 +25,11 @@ angular.module('ambariAdminConsole')
     props: $t('views.properties')
     props: $t('views.properties')
   };
   };
   var targetUrl = '/viewUrls';
   var targetUrl = '/viewUrls';
+
   $scope.url={};
   $scope.url={};
   $scope.formHolder = {};
   $scope.formHolder = {};
+  $scope.stepOneNotCompleted = true;
+  $scope.stepTwoNotCompleted = true;
 
 
   View.getAllVisibleInstance().then(function(views) {
   View.getAllVisibleInstance().then(function(views) {
     var names = [];
     var names = [];
@@ -57,6 +60,8 @@ angular.module('ambariAdminConsole')
       $scope.url.selectedInstance = instances.find(function(inst){
       $scope.url.selectedInstance = instances.find(function(inst){
          return inst.nameV === selectedView && inst.instance === $routeParams.viewInstanceName && inst.version === $routeParams.viewVersion && inst.cname === $routeParams.viewName;
          return inst.nameV === selectedView && inst.instance === $routeParams.viewInstanceName && inst.version === $routeParams.viewVersion && inst.cname === $routeParams.viewName;
       });
       });
+      $scope.stepOneNotCompleted = false;
+      $scope.stepTwoNotCompleted = false;
     }
     }
 
 
   }).catch(function(data) {
   }).catch(function(data) {
@@ -78,76 +83,51 @@ angular.module('ambariAdminConsole')
   };
   };
 
 
 
 
-  $scope.wizardController = function () {
-    var wizard = this;
-
-    //Model
-    wizard.currentStep = 1;
-    wizard.steps = [
-      {
-        step: 1,
-        name: $t('urls.step1'),
-        template: "views/urls/create_step_1.html"
-      },
-      {
-        step: 2,
-        name: $t('urls.step2'),
-        template: "views/urls/create_step_2.html"
-      },
-      {
-        step: 3,
-        name: $t('urls.step3'),
-        template: "views/urls/create_step_3.html"
-      }
-    ];
-    wizard.user = {};
-
-    //Functions
-    wizard.gotoStep = function(newStep) {
-      $scope.formHolder.form.submitted = true;
-      if (newStep < wizard.currentStep || $scope.formHolder.form.$valid) {
-        wizard.currentStep = newStep;
-      }
-    };
+  $scope.doStepOne = function () {
+    $scope.stepOneNotCompleted = false;
+  };
 
 
-    wizard.getStepTemplate = function(){
-      for (var i = 0; i < wizard.steps.length; i++) {
-        if (wizard.currentStep == wizard.steps[i].step) {
-          return wizard.steps[i].template;
-        }
-      }
-    };
 
 
-    wizard.save = function() {
-      $scope.formHolder.form.submitted = true;
-
-      if($scope.formHolder.form.$valid){
-
-        var payload = {ViewUrlInfo:{
-              url_name:$scope.url.urlName,
-              url_suffix:$scope.url.suffix,
-              view_instance_version:$scope.url.selectedInstance.version,
-              view_instance_name:$scope.url.selectedInstance.instance,
-              view_instance_common_name:$scope.url.selectedInstance.cname
-        }};
-
-        View.updateShortUrl(payload).then(function(urlStatus) {
-          Alert.success($t('urls.urlCreated', {
-            viewName:$scope.url.selectedInstance.cname ,
-            shortUrl:$scope.url.suffix,
-            urlName:$scope.url.urlName
-          }));
-          $scope.formHolder.form.$setPristine();
-          $location.path(targetUrl);
-        }).catch(function(data) {
-          Alert.error($t('views.alerts.cannotLoadViewUrls'), data.message);
-        });
+  $scope.doStepTwo = function () {
+    $scope.stepTwoNotCompleted = false;
 
 
-      }
-    };
-  }
+  };
 
 
+  $scope.cancelForm = function () {
+    $scope.stepOneNotCompleted = true;
+    $scope.stepTwoNotCompleted = true;
+  };
 
 
+  $scope.saveUrl = function() {
+    $scope.formHolder.form.submitted = true;
+
+    if($scope.formHolder.form.$valid){
+
+      var payload = {ViewUrlInfo:{
+        url_name:$scope.url.urlName,
+        url_suffix:$scope.url.suffix,
+        view_instance_version:$scope.url.selectedInstance.version,
+        view_instance_name:$scope.url.selectedInstance.instance,
+        view_instance_common_name:$scope.url.selectedInstance.cname
+      }};
+
+      View.updateShortUrl(payload).then(function(urlStatus) {
+        Alert.success($t('urls.urlCreated', {
+          viewName:$scope.url.selectedInstance.cname ,
+          shortUrl:$scope.url.suffix,
+          urlName:$scope.url.urlName
+        }));
+        $scope.formHolder.form.$setPristine();
+        $scope.url={};
+        $scope.formHolder = {};
+        $scope.stepOneNotCompleted = true;
+        $scope.stepTwoNotCompleted = true;
+        $location.path(targetUrl);
+      }).catch(function(data) {
+        Alert.error($t('views.alerts.cannotLoadViewUrls'), data.message);
+      });
 
 
+    }
+  };
 
 
 }]);
 }]);

+ 118 - 3
ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js

@@ -31,6 +31,8 @@ angular.module('ambariAdminConsole')
     })
     })
   });
   });
 
 
+  $scope.createUrlDisabled = false;
+
   function checkViewVersionStatus(view, versionObj, versionNumber){
   function checkViewVersionStatus(view, versionObj, versionNumber){
     var deferred = View.checkViewVersionStatus(view.view_name, versionNumber);
     var deferred = View.checkViewVersionStatus(view.view_name, versionNumber);
     deferredList.push(deferred);
     deferredList.push(deferred);
@@ -130,16 +132,129 @@ angular.module('ambariAdminConsole')
     loadViews();
     loadViews();
   };
   };
 
 
+  /**
+   * Url listing
+   */
+
+  $scope.loadedUrls = [];
+  $scope.urlsPerPage = 10;
+  $scope.currentPage = 1;
+  $scope.totalUrls = 1;
+  $scope.urlNameFilter = '';
+  $scope.maxVisiblePages=20;
+  $scope.tableInfo = {
+    total: 0,
+    showed: 0
+  };
+
+  $scope.isNotEmptyFilter = true;
+
+
+  $scope.pageChanged = function() {
+    $scope.listViewUrls();
+  };
+
+  $scope.urlsPerPageChanged = function() {
+    $scope.resetPagination();
+  };
+
+
+  $scope.resetPagination = function() {
+    $scope.currentPage = 1;
+    $scope.listViewUrls();
+  };
+
+
+  $scope.getVersions = function(instances) {
+    var names = [];
+
+    instances.map(function(view){
+      var name = view.view_name;
+      names.push(name);
+    });
+
+    var output = [],
+        keys = [];
+
+    angular.forEach(names, function(item) {
+      var key = item;
+      if(keys.indexOf(key) === -1) {
+        keys.push(key);
+        output.push(item);
+      }
+    })
+    return output;
+    };
+
+
+
+  $scope.clearFilters = function () {
+    $scope.urlNameFilter = '';
+    $scope.instanceTypeFilter = $scope.typeFilterOptions[0];
+    $scope.resetPagination();
+  };
+
+
+
+  $scope.$watch(
+      function (scope) {
+        return Boolean(scope.urlNameFilter || (scope.instanceTypeFilter && scope.instanceTypeFilter.value !== '*'));
+      },
+      function (newValue, oldValue, scope) {
+        scope.isNotEmptyFilter = newValue;
+      }
+  );
+
+
+
 
 
   $scope.listViewUrls = function(){
   $scope.listViewUrls = function(){
-    View.allUrls().then(function(urls) {
+    View.allUrls({
+      currentPage: $scope.currentPage,
+      urlsPerPage: $scope.urlsPerPage,
+      searchString: $scope.urlNameFilter,
+      instanceType: $scope.instanceTypeFilter?$scope.instanceTypeFilter.value:'*'
+    }).then(function(urls) {
       $scope.urls = urls;
       $scope.urls = urls;
       $scope.ViewNameFilterOptions = urls.items.map(function(url){
       $scope.ViewNameFilterOptions = urls.items.map(function(url){
         return url.ViewUrlInfo.view_instance_common_name;
         return url.ViewUrlInfo.view_instance_common_name;
       });
       });
+
+      $scope.totalUrls = urls.itemTotal;
+      $scope.tableInfo.showed = urls.items.length;
+      $scope.tableInfo.total = urls.itemTotal;
+
+      // get all view instances to enable/disable creation if empty
+
     }).catch(function(data) {
     }).catch(function(data) {
-      Alert.error($t('views.alerts.cannotLoadViewUrls'), data.data.message);
+      Alert.error($t('views.alerts.cannotLoadViewUrls'), data.message);
     });
     });
-  }
+  };
+
+
+  $scope.initViewUrls = function(){
+    $scope.listViewUrls();
+    View.getAllVisibleInstance().then(function(instances){
+      // if no instances then disable the create button
+      if(!instances.length){
+        $scope.createUrlDisabled = true;
+      } else {
+        $scope.typeFilterOptions = [{ label: $t('common.all'), value: '*'}]
+            .concat($scope.getVersions(instances).map(function(key) {
+              return {
+                label: key,
+                value: key
+              };
+            }));
+
+        $scope.instanceTypeFilter = $scope.typeFilterOptions[0];
+      }
+
+    }).catch(function(data) {
+      // Make the create button enabled, and swallow the error
+      $scope.createUrlDisabled = false;
+    });
+
+  };
 
 
 }]);
 }]);

+ 8 - 6
ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js

@@ -32,7 +32,7 @@ angular.module('ambariAdminConsole')
       'signOut': 'Sign out',
       'signOut': 'Sign out',
       'clusters': 'Clusters',
       'clusters': 'Clusters',
       'views': 'Views',
       'views': 'Views',
-      'viewUrls': 'View URL\'s',
+      'viewUrls': 'View URLs',
       'roles': 'Roles',
       'roles': 'Roles',
       'users': 'Users',
       'users': 'Users',
       'groups': 'Groups',
       'groups': 'Groups',
@@ -133,9 +133,10 @@ angular.module('ambariAdminConsole')
         'cannotLoadClusterStatus': 'Cannot load cluster status',
         'cannotLoadClusterStatus': 'Cannot load cluster status',
         'clusterRenamed': 'The cluster has been renamed to {{clusterName}}.',
         'clusterRenamed': 'The cluster has been renamed to {{clusterName}}.',
         'cannotRenameCluster': 'Cannot rename cluster to {{clusterName}}',
         'cannotRenameCluster': 'Cannot rename cluster to {{clusterName}}',
-        'tooShort': 'URL is too short',
-        'tooLong': 'URL is too long',
-        'onlyText': 'You can add only text urls in the lower case'
+        'tooShort': 'Too short',
+        'tooLong': 'Too long',
+        'onlyText': 'Only lower cased text allowed',
+        'onlyAnScore': 'Invalid input, only alphanumerics allowed eg: My_default_view'
       }
       }
     },
     },
 
 
@@ -228,7 +229,7 @@ angular.module('ambariAdminConsole')
         'cannotSaveProperties': 'Cannot save properties',
         'cannotSaveProperties': 'Cannot save properties',
         'cannotDeleteInstance': 'Cannot delete instance',
         'cannotDeleteInstance': 'Cannot delete instance',
         'cannotLoadViews': 'Cannot load views',
         'cannotLoadViews': 'Cannot load views',
-        'cannotLoadViewUrls': 'Cannot load view URL\'s',
+        'cannotLoadViewUrls': 'Cannot load view URLs',
         'cannotLoadViewUrl': 'Cannot load view URL'
         'cannotLoadViewUrl': 'Cannot load view URL'
       }
       }
     },
     },
@@ -243,7 +244,8 @@ angular.module('ambariAdminConsole')
       'step1':'Create URL',
       'step1':'Create URL',
       'step2':'Select instance',
       'step2':'Select instance',
       'step3':'Assign URL',
       'step3':'Assign URL',
-      'noUrlsToDisplay':'No short URL\'s configured',
+      'noUrlsToDisplay':'No URLs to display',
+      'noViewInstances':'No view instances',
       'urlCreated':'Created short URL <a href="/#/main/view/{{viewName}}/{{shortUrl}}">{{urlName}}</a>',
       'urlCreated':'Created short URL <a href="/#/main/view/{{viewName}}/{{shortUrl}}">{{urlName}}</a>',
       'urlUpdated':'Updated short URL <a href="/#/main/view/{{viewName}}/{{shortUrl}}">{{urlName}}</a>'
       'urlUpdated':'Updated short URL <a href="/#/main/view/{{viewName}}/{{shortUrl}}">{{urlName}}</a>'
     },
     },

+ 9 - 4
ambari-admin/src/main/resources/ui/admin-web/app/scripts/services/View.js

@@ -61,13 +61,18 @@ angular.module('ambariAdminConsole')
     angular.element(this,item);
     angular.element(this,item);
   }
   }
 
 
-  ViewUrl.all = function() {
+  ViewUrl.all = function(params) {
     var deferred = $q.defer();
     var deferred = $q.defer();
 
 
     $http({
     $http({
       method: 'GET',
       method: 'GET',
       dataType: "json",
       dataType: "json",
-      url: Settings.baseUrl + '/view/urls',
+      url: Settings.baseUrl + '/view/urls?'
+      + 'ViewUrlInfo/url_name.matches(.*'+params.searchString+'.*)'
+      + '&fields=*'
+      + '&from=' + (params.currentPage-1)*params.urlsPerPage
+      + '&page_size=' + params.urlsPerPage
+      + (params.instanceType === '*' ? '' : '&ViewUrlInfo/view_instance_common_name=' + params.instanceType)
 
 
     })
     })
         .success(function(data) {
         .success(function(data) {
@@ -189,8 +194,8 @@ angular.module('ambariAdminConsole')
     return ViewInstance.find(viewName, version, instanceName);
     return ViewInstance.find(viewName, version, instanceName);
   };
   };
 
 
-  View.allUrls =  function(){
-    return ViewUrl.all()
+  View.allUrls =  function(req){
+    return ViewUrl.all(req)
   };
   };
 
 
   View.getUrlInfo = function(urlName){
   View.getUrlInfo = function(urlName){

+ 25 - 0
ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css

@@ -219,6 +219,20 @@
    max-width: 300px;
    max-width: 300px;
  }
  }
 
 
+.v-small-input{
+  max-width: 100px;
+}
+
+.m-small-input{
+  max-width: 100px;
+}
+
+
+.white-bg{
+  background-color: #ffffff;
+
+}
+
 .paginator{
 .paginator{
   margin: 0;
   margin: 0;
 }
 }
@@ -1120,6 +1134,17 @@ button.btn.btn-xs{
   -moz-box-sizing: border-box;
   -moz-box-sizing: border-box;
   box-sizing: border-box;
   box-sizing: border-box;
 }
 }
+
+.fix-bottom{
+  border-bottom: none !important;;
+  border-top: none !important;
+  border-width: 0;
+}
+
+.fix-top{
+  border-top: none !important;
+  border-width: 0;
+}
 .ambariAlert .content {
 .ambariAlert .content {
   word-wrap: break-word;
   word-wrap: break-word;
   padding-right: 10px;
   padding-right: 10px;

+ 49 - 11
ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listUrls.html

@@ -16,7 +16,7 @@
 * limitations under the License.
 * limitations under the License.
 -->
 -->
 
 
-<div class="views-list-table" data-ng-init="listViewUrls()">
+<div class="views-list-table" data-ng-init="initViewUrls()">
 
 
 
 
     <div class="clearfix">
     <div class="clearfix">
@@ -24,27 +24,53 @@
             <li class="active">{{'common.viewUrls' | translate}}</li>
             <li class="active">{{'common.viewUrls' | translate}}</li>
         </ol>
         </ol>
         <div class="pull-right top-margin-4">
         <div class="pull-right top-margin-4">
-            <link-to route="views.createViewUrl" class="btn btn-primary createuser-btn"><span class="glyphicon glyphicon-plus"></span> {{'views.urlCreate' | translate}}</link-to>
+            <div class="tooltip-wrapper"  tooltip="{{(createUrlDisabled)? ('urls.noViewInstances' | translate) : ''}}">
+            <link-to ng-disabled="createUrlDisabled" route="views.createViewUrl" class="btn btn-primary createuser-btn"><span class="glyphicon glyphicon-plus"></span> {{'views.urlCreate' | translate}}</link-to>
+        </div>
         </div>
         </div>
     </div>
     </div>
     <hr>
     <hr>
     <table class="table table-striped table-hover">
     <table class="table table-striped table-hover">
         <thead>
         <thead>
-        <tr>
+        <tr class="fix-bottom">
 
 
-            <th>
-               <span class="padding-bottom-30">{{'urls.name' | translate}}</span>
+            <th class="fix-bottom">
+               <span>{{'urls.name' | translate}}</span>
+            </th>
+            <th class="fix-bottom">
+                <span>{{'urls.url' | translate}}</span>
+            </th>
+            <th class="fix-bottom">
+                <span >{{'urls.view' | translate}}</span>
             </th>
             </th>
-            <th>
-                <span class="padding-bottom-30">{{'urls.url' | translate}}</span>
+            <th class="fix-bottom">
+                <span>{{'urls.viewInstance' | translate}}</span>
+            </th>
+        </tr>
+
+        <tr>
+
+            <th class="fix-top">
+                <div class="input-group m-small-input">
+                    <input type="text" class="form-control namefilter" placeholder="{{'common.any' | translate}}" ng-model="urlNameFilter" ng-change="resetPagination()">
+                    <span class="input-group-addon white-bg">
+                        <button type="button" ng-show="urlNameFilter" ng-click="urlNameFilter=''; resetPagination()">
+                            <span aria-hidden="true">&times;</span><span class="sr-only">{{'common.controls.close' | translate}}</span>
+                        </button></span>
+                </div>
             </th>
             </th>
-            <th>
-                <span class="padding-bottom-30">{{'urls.view' | translate}}</span>
+            <th class="fix-top"></th>
+            <th class="fix-top">
+                <select class="form-control typefilter v-small-input"
+                        ng-model="instanceTypeFilter"
+                        ng-options="item.label for item in typeFilterOptions"
+                        ng-change="resetPagination()">
+                </select>
             </th>
             </th>
-            <th>
-                <span class="padding-bottom-30">{{'urls.viewInstance' | translate}}</span>
+            <th class="fix-top">
             </th>
             </th>
         </tr>
         </tr>
+
         </thead>
         </thead>
         <tbody>
         <tbody>
         <tr ng-repeat="url in urls.items">
         <tr ng-repeat="url in urls.items">
@@ -68,5 +94,17 @@
     <div class="alert alert-info col-sm-12" ng-show="!urls.items.length">
     <div class="alert alert-info col-sm-12" ng-show="!urls.items.length">
         {{'urls.noUrlsToDisplay'| translate}}
         {{'urls.noUrlsToDisplay'| translate}}
     </div>
     </div>
+    <div class="col-sm-12 table-bar">
+        <div class="pull-left filtered-info">
+            <span>{{'common.filterInfo' | translate: '{showed: tableInfo.showed, total: tableInfo.total, term: urs.urls}'}}</span>
+            <span ng-show="isNotEmptyFilter">- <a href ng-click="clearFilters()">{{'common.controls.clearFilters' | translate}}</a></span>
+        </div>
+        <div class="pull-right left-margin">
+            <pagination class="paginator" total-items="totalUrls" max-size="maxVisiblePages" items-per-page="urlsPerPage" ng-model="currentPage" ng-change="pageChanged()"></pagination>
+        </div>
+        <div class="pull-right">
+            <select class="form-control" ng-model="urlsPerPage" ng-change="urlsPerPageChanged()" ng-options="currOption for currOption in [10, 25, 50, 100]"></select>
+        </div>
+    </div>
 
 
 </div>
 </div>

+ 42 - 15
ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create.html

@@ -23,29 +23,56 @@
     <div class="row">
     <div class="row">
         <div class="col-sm-10">
         <div class="col-sm-10">
 
 
-            <div id="wizard-container" ng-controller="wizardController as wizard">
+            <form class="form-horizontal create-user-form" role="form" novalidate name="formHolder.form" autocomplete="off">
+                <div class="form-group" ng-class="{'has-error' : formHolder.form.url_name.$error.required  && formHolder.form.submitted}">
+                    <label for="urlname" class="col-sm-2 control-label">{{'urls.name' | translate}}</label>
+                    <div class="col-sm-10">
+                        <input ng-minlength="3" ng-maxlength="20" type="text" id="urlname" class="form-control urlname-input" name="url_name" placeholder="{{'urls.name' | translate}}" ng-model="url.urlName" required autocomplete="off">
+                        <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+                        <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_name.$error.minlength   && formHolder.form.submitted">{{'common.alerts.tooShort' | translate}}</div>
+                        <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_name.$error.maxlength   && formHolder.form.submitted">{{'common.alerts.tooLong' | translate}}</div>
+                    </div>
+                </div>
+
 
 
-                <div id="wizard-step-container" class="bottom-margin">
-                    <ul class="nav nav-pills nav-justified">
-                        <li ng-repeat="step in wizard.steps" ng-class="{'active':step.step == wizard.currentStep}"><a ng-click="wizard.gotoStep(step.step)" href="">{{step.name}}</a></li>
-                    </ul>
+                <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_name.$error.required  && formHolder.form.submitted}">
+                    <label for="urlselect" class="col-sm-2 control-label">{{'urls.view' | translate}}</label>
+                    <div class="col-sm-10">
+                        <select ng-change="doStepOne()" class="form-control" id="urlselect" name="url_view_name" ng-options="version for version in viewsVersions" ng-model="url.selectedView" required></select>
+                        <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+                    </div>
                 </div>
                 </div>
 
 
-                <div id="wizard-content-container">
-                    <ng-include src="wizard.getStepTemplate()"></ng-include>
+                <div ng-hide="stepOneNotCompleted" class="form-group" ng-class="{'has-error' : formHolder.form.url_view_instance_name.$error.required  && formHolder.form.submitted}">
+                    <label for="urlinstanceselect" class="col-sm-2 control-label">{{'urls.viewInstance' | translate}}</label>
+                    <div class="col-sm-10">
+                        <select ng-change="doStepTwo()" class="form-control" id="urlinstanceselect" name="url_view_instance_name" ng-options="instance.instance for instance in viewInstances | filter:filterByName(url.selectedView)" ng-model="url.selectedInstance" required></select>
+                        <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_instance_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+                    </div>
+                </div>
+
+                <div ng-hide="stepTwoNotCompleted"  class="form-group" ng-class="{'has-error' : formHolder.form.url_view_suffix.$error.required  && formHolder.form.submitted}">
+                    <label for="urlsuffixin" class="col-sm-2 control-label">{{'views.shortUrl' | translate}}</label>
+                    <div class="col-sm-10">
+                        <div class="input-group">
+                            <span id="basic-addon1" class="input-group-addon">/main/view/{{chomp(url.selectedView)}}/</span><input aria-describedby="basic-addon1" type="text" class="form-control" id="urlsuffixin" name="url_view_suffix" placeholder="{{'views.shortUrl' | translate}}" ng-model="url.suffix" ng-pattern="/[a-z]+/" ng-minlength="3" ng-maxlength="10" required autocomplete="off">
+                        </div>
+                        <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_suffix.$error.required   && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
+                        <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_suffix.$error.minlength   && formHolder.form.submitted">{{'common.alerts.tooShort' | translate}}</div>
+                        <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_suffix.$error.maxlength   && formHolder.form.submitted">{{'common.alerts.tooLong' | translate}}</div>
+                        <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_suffix.$error.pattern   && formHolder.form.submitted">{{'common.alerts.onlyText' | translate}}</div>
+
+                    </div>
                 </div>
                 </div>
 
 
-                <div id="wizard-navigation-container">
-                    <div class="pull-right">
-                <span class="btn-group">
-                  <button ng-disabled="wizard.currentStep <= 1" class="btn btn-default" name="previous" type="button" ng-click="wizard.gotoStep(wizard.currentStep - 1)"><i class="fa fa-arrow-left"></i> Previous step</button>
-                  <button ng-disabled="wizard.currentStep >= wizard.steps.length" class="btn btn-primary" name="next" type="button" ng-click="wizard.gotoStep(wizard.currentStep + 1)">Next step <i class="fa fa-arrow-right"></i></button>
-                </span>
-                        <button ng-disabled="wizard.currentStep != wizard.steps.length" class="btn btn-success" name="next" type="button" ng-click="wizard.save()"> <i class="fa fa-floppy-o"></i> Save</button>
+                <div class="form-group">
+                    <div class="col-sm-offset-2 col-sm-10">
+                        <button ng-disabled="stepTwoNotCompleted" class="btn btn-primary pull-right left-margin saveuser" ng-click="saveUrl()">{{'common.controls.save' | translate}}</button>
+                        <a ng-disabled="stepOneNotCompleted" class="btn btn-default pull-right cancel" href ng-click="cancelForm()">{{'common.controls.cancel' | translate}}</a>
                     </div>
                     </div>
                 </div>
                 </div>
 
 
-            </div>
+            </form>
 
 
         </div>
         </div>
 </div>
 </div>

+ 0 - 36
ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_1.html

@@ -1,36 +0,0 @@
-<!--
-* 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.
--->
-
-<form ng-controller="wizardController as wizard" class="form-horizontal create-user-form" role="form" novalidate name="formHolder.form" autocomplete="off">
-  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_name.$error.required  && formHolder.form.submitted}">
-    <label for="urlname" class="col-sm-2 control-label">{{'urls.name' | translate}}</label>
-    <div class="col-sm-10">
-      <input type="text" id="urlname" class="form-control urlname-input" name="url_name" placeholder="{{'urls.name' | translate}}" ng-model="url.urlName" required autocomplete="off">
-      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
-    </div>
-  </div>
-
-
-  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_name.$error.required  && formHolder.form.submitted}">
-    <label for="urlselect" class="col-sm-2 control-label">{{'urls.view' | translate}}</label>
-    <div class="col-sm-10">
-      <select class="form-control" id="urlselect" name="url_view_name" ng-options="version for version in viewsVersions" ng-model="url.selectedView" required></select>
-      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
-    </div>
-  </div>
-</form>

+ 0 - 44
ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_2.html

@@ -1,44 +0,0 @@
-<!--
-* 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.
--->
-<form  class="form-horizontal create-user-form" role="form" novalidate name="formHolder.form" autocomplete="off">
-  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_name.$error.required  && formHolder.form.submitted}">
-    <label for="urlname" class="col-sm-2 control-label">{{'urls.name' | translate}}</label>
-    <div class="col-sm-10">
-      <input disabled type="text" id="urlname" class="form-control urlname-input" name="url_name" placeholder="{{'urls.name' | translate}}" ng-model="url.urlName" required autocomplete="off">
-      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
-    </div>
-  </div>
-
-
-  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_name.$error.required  && formHolder.form.submitted}">
-    <label for="urlselect" class="col-sm-2 control-label">{{'urls.view' | translate}}</label>
-    <div class="col-sm-10">
-      <select class="form-control" disabled id="urlselect" name="url_view_name" ng-options="version for version in viewsVersions" ng-model="url.selectedView" required></select>
-      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
-    </div>
-  </div>
-
-  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_instance_name.$error.required  && formHolder.form.submitted}">
-    <label for="urlinstanceselect" class="col-sm-2 control-label">{{'urls.viewInstance' | translate}}</label>
-    <div class="col-sm-10">
-      <select class="form-control" id="urlinstanceselect" name="url_view_instance_name" ng-options="instance.instance for instance in viewInstances | filter:filterByName(url.selectedView)" ng-model="url.selectedInstance" required></select>
-      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_instance_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
-    </div>
-  </div>
-
-</form>

+ 0 - 60
ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_3.html

@@ -1,60 +0,0 @@
-<!--
-* 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.
--->
-<form  class="form-horizontal create-user-form" role="form" novalidate name="formHolder.form" autocomplete="off">
-  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_name.$error.required  && formHolder.form.submitted}">
-    <label for="urlname" class="col-sm-2 control-label">{{'urls.name' | translate}}</label>
-    <div class="col-sm-10">
-      <input disabled type="text" id="urlname" class="form-control urlname-input" name="url_name" placeholder="{{'urls.name' | translate}}" ng-model="url.urlName" required autocomplete="off">
-      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
-    </div>
-  </div>
-
-
-  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_name.$error.required  && formHolder.form.submitted}">
-    <label for="urlselect" class="col-sm-2 control-label">{{'urls.view' | translate}}</label>
-    <div class="col-sm-10">
-      <select class="form-control" disabled id="urlselect" name="url_view_name" ng-options="version for version in viewsVersions" ng-model="url.selectedView" required></select>
-      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
-    </div>
-  </div>
-
-  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_instance_name.$error.required  && formHolder.form.submitted}">
-    <label for="urlinstanceselect" class="col-sm-2 control-label">{{'urls.viewInstance' | translate}}</label>
-    <div class="col-sm-10">
-      <select class="form-control" disabled id="urlinstanceselect" name="url_view_instance_name" ng-options="instance.instance for instance in viewInstances | filter:filterByName(url.selectedView)" ng-model="url.selectedInstance" required></select>
-      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_instance_name.$error.required  && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
-    </div>
-  </div>
-
-
-
-  <div class="form-group" ng-class="{'has-error' : formHolder.form.url_view_suffix.$error.required  && formHolder.form.submitted}">
-    <label for="urlsuffixin" class="col-sm-2 control-label">{{'views.shortUrl' | translate}}</label>
-    <div class="col-sm-10">
-      <div class="input-group">
-      <span id="basic-addon1" class="input-group-addon">/main/view/{{chomp(url.selectedView)}}/</span><input aria-describedby="basic-addon1" type="text" class="form-control" id="urlsuffixin" name="url_view_suffix" placeholder="{{'views.shortUrl' | translate}}" ng-model="url.suffix" ng-pattern="/[a-z]+/" ng-minlength="3" ng-maxlength="10" required autocomplete="off">
-        </div>
-      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_suffix.$error.required   && formHolder.form.submitted">{{'common.alerts.fieldIsRequired' | translate}}</div>
-      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_suffix.$error.minlength   && formHolder.form.submitted">{{'common.alerts.tooShort' | translate}}</div>
-      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_suffix.$error.maxlength   && formHolder.form.submitted">{{'common.alerts.tooLong' | translate}}</div>
-      <div class="alert alert-danger top-margin" ng-show="formHolder.form.url_view_suffix.$error.pattern   && formHolder.form.submitted">{{'common.alerts.onlyText' | translate}}</div>
-
-    </div>
-  </div>
-
-</form>

+ 18 - 11
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewURLResourceProvider.java

@@ -19,9 +19,9 @@
 package org.apache.ambari.server.controller.internal;
 package org.apache.ambari.server.controller.internal;
 
 
 import com.google.common.base.Optional;
 import com.google.common.base.Optional;
+import com.google.common.base.Strings;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Sets;
 import com.google.inject.Inject;
 import com.google.inject.Inject;
-import com.google.inject.persist.Transactional;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.StaticallyInject;
 import org.apache.ambari.server.StaticallyInject;
 import org.apache.ambari.server.controller.predicate.EqualsPredicate;
 import org.apache.ambari.server.controller.predicate.EqualsPredicate;
@@ -34,7 +34,6 @@ import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
 import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
 import org.apache.ambari.server.controller.spi.SystemException;
 import org.apache.ambari.server.controller.spi.SystemException;
 import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
 import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
-import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.apache.ambari.server.orm.dao.ViewURLDAO;
 import org.apache.ambari.server.orm.dao.ViewURLDAO;
 import org.apache.ambari.server.orm.entities.ViewEntity;
 import org.apache.ambari.server.orm.entities.ViewEntity;
 import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
 import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
@@ -72,9 +71,6 @@ public class ViewURLResourceProvider extends AbstractAuthorizedResourceProvider
    */
    */
   private static Map<Resource.Type, String> keyPropertyIds = new HashMap<Resource.Type, String>();
   private static Map<Resource.Type, String> keyPropertyIds = new HashMap<Resource.Type, String>();
   static {
   static {
-    keyPropertyIds.put(Resource.Type.View, VIEW_INSTANCE_NAME_PROPERTY_ID);
-    keyPropertyIds.put(Resource.Type.ViewVersion, VIEW_INSTANCE_VERSION_PROPERTY_ID);
-    keyPropertyIds.put(Resource.Type.ViewInstance, VIEW_INSTANCE_COMMON_NAME_PROPERTY_ID);
     keyPropertyIds.put(Resource.Type.ViewURL, URL_NAME_PROPERTY_ID);
     keyPropertyIds.put(Resource.Type.ViewURL, URL_NAME_PROPERTY_ID);
   }
   }
 
 
@@ -93,12 +89,12 @@ public class ViewURLResourceProvider extends AbstractAuthorizedResourceProvider
   @Inject
   @Inject
   private static ViewURLDAO viewURLDAO;
   private static ViewURLDAO viewURLDAO;
 
 
-
   // ----- Constructors ------------------------------------------------------
   // ----- Constructors ------------------------------------------------------
 
 
   /**
   /**
    * Construct a view URL resource provider.
    * Construct a view URL resource provider.
    */
    */
+
   public ViewURLResourceProvider() {
   public ViewURLResourceProvider() {
     super(propertyIds, keyPropertyIds);
     super(propertyIds, keyPropertyIds);
 
 
@@ -127,10 +123,22 @@ public class ViewURLResourceProvider extends AbstractAuthorizedResourceProvider
   public Set<Resource> getResources(Request request, Predicate predicate)
   public Set<Resource> getResources(Request request, Predicate predicate)
       throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
       throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
 
 
-    Set<Resource> resources    = Sets.newHashSet();
-    List<ViewURLEntity> urlEntities = viewURLDAO.findAll();
-    for (ViewURLEntity urlEntity : urlEntities) {
-      resources.add(toResource(urlEntity));
+    Set<Resource> resources = Sets.newHashSet();
+    Set<Map<String, Object>> propertyMaps = getPropertyMaps(predicate);
+
+    for (Map<String, Object> propertyMap : propertyMaps) {
+      String urlNameProperty = (String) propertyMap.get(URL_NAME_PROPERTY_ID);
+      if (!Strings.isNullOrEmpty(urlNameProperty)) {
+        Optional<ViewURLEntity> urlEntity = viewURLDAO.findByName(urlNameProperty);
+        if(urlEntity.isPresent()){
+          resources.add(toResource(urlEntity.get()));
+        }
+      } else {
+        List<ViewURLEntity> urlEntities = viewURLDAO.findAll();
+        for (ViewURLEntity urlEntity : urlEntities) {
+          resources.add(toResource(urlEntity));
+        }
+      }
     }
     }
 
 
     return resources;
     return resources;
@@ -235,7 +243,6 @@ public class ViewURLResourceProvider extends AbstractAuthorizedResourceProvider
      */
      */
   private Command<Void> getCreateCommand(final Map<String, Object> properties) {
   private Command<Void> getCreateCommand(final Map<String, Object> properties) {
     return new Command<Void>() {
     return new Command<Void>() {
-      @Transactional
       @Override
       @Override
       public Void invoke() throws AmbariException {
       public Void invoke() throws AmbariException {
         ViewRegistry       viewRegistry   = ViewRegistry.getInstance();
         ViewRegistry       viewRegistry   = ViewRegistry.getInstance();

+ 319 - 0
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ViewURLResourceProviderTest.java

@@ -0,0 +1,319 @@
+/**
+ * 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.
+ */
+
+package org.apache.ambari.server.controller.internal;
+
+import com.google.common.base.Optional;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.actionmanager.ActionDBAccessor;
+import org.apache.ambari.server.actionmanager.ActionManager;
+import org.apache.ambari.server.actionmanager.StageFactory;
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.controller.AbstractRootServiceResponseFactory;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.AmbariManagementControllerImpl;
+import org.apache.ambari.server.controller.KerberosHelper;
+import org.apache.ambari.server.controller.predicate.EqualsPredicate;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
+import org.apache.ambari.server.controller.utilities.PredicateBuilder;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.orm.DBAccessor;
+import org.apache.ambari.server.orm.dao.UserDAO;
+import org.apache.ambari.server.orm.dao.ViewURLDAO;
+import org.apache.ambari.server.orm.dao.WidgetLayoutDAO;
+import org.apache.ambari.server.orm.entities.ViewEntity;
+import org.apache.ambari.server.orm.entities.ViewInstanceDataEntity;
+import org.apache.ambari.server.orm.entities.ViewInstanceEntity;
+import org.apache.ambari.server.orm.entities.ViewURLEntity;
+import org.apache.ambari.server.scheduler.ExecutionScheduler;
+import org.apache.ambari.server.security.TestAuthenticationFactory;
+import org.apache.ambari.server.security.authorization.AuthorizationException;
+import org.apache.ambari.server.security.authorization.Users;
+import org.apache.ambari.server.security.encryption.CredentialStoreService;
+import org.apache.ambari.server.security.encryption.CredentialStoreServiceImpl;
+import org.apache.ambari.server.stack.StackManagerFactory;
+import org.apache.ambari.server.stageplanner.RoleGraphFactory;
+import org.apache.ambari.server.stageplanner.RoleGraphFactoryImpl;
+import org.apache.ambari.server.state.Clusters;
+import org.apache.ambari.server.state.ConfigFactory;
+import org.apache.ambari.server.state.ServiceComponentFactory;
+import org.apache.ambari.server.state.ServiceComponentHostFactory;
+import org.apache.ambari.server.state.ServiceFactory;
+import org.apache.ambari.server.state.configgroup.ConfigGroupFactory;
+import org.apache.ambari.server.state.scheduler.RequestExecutionFactory;
+import org.apache.ambari.server.state.stack.OsFamily;
+import org.apache.ambari.server.view.ViewRegistry;
+import org.apache.ambari.server.view.configuration.ViewConfig;
+import org.apache.ambari.view.ViewDefinition;
+import org.easymock.Capture;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import javax.persistence.EntityManager;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+public class ViewURLResourceProviderTest {
+
+  private static final ViewRegistry viewregistry = createMock(ViewRegistry.class);
+
+  static {
+    ViewRegistry.initInstance(viewregistry);
+  }
+
+  @Before
+  public void before() throws Exception {
+    reset(viewregistry);
+  }
+
+  @After
+  public void clearAuthentication() {
+    SecurityContextHolder.getContext().setAuthentication(null);
+  }
+
+  @Test
+  public void testToResource() throws Exception {
+    ViewURLResourceProvider provider = new ViewURLResourceProvider();
+    Set<String> propertyIds = new HashSet<String>();
+    propertyIds.add(ViewURLResourceProvider.URL_NAME_PROPERTY_ID);
+    propertyIds.add(ViewURLResourceProvider.URL_SUFFIX_PROPERTY_ID);
+    ViewURLEntity viewURLEntity = createNiceMock(ViewURLEntity.class);
+
+    ViewEntity viewEntity = createNiceMock(ViewEntity.class);
+    ViewInstanceEntity viewInstanceEntity = createNiceMock(ViewInstanceEntity.class);
+    expect(viewURLEntity.getUrlName()).andReturn("test").once();
+    expect(viewURLEntity.getUrlSuffix()).andReturn("url").once();
+    expect(viewURLEntity.getViewInstanceEntity()).andReturn(viewInstanceEntity).once();
+    expect(viewInstanceEntity.getViewEntity()).andReturn(viewEntity).once();
+    expect(viewEntity.getCommonName()).andReturn("FILES").once();
+    expect(viewEntity.getVersion()).andReturn("1.0.0").once();
+    expect(viewInstanceEntity.getName()).andReturn("test").once();
+
+    replay(viewURLEntity, viewEntity, viewInstanceEntity);
+
+    Resource resource = provider.toResource(viewURLEntity);
+    assertEquals(resource.getPropertyValue(ViewURLResourceProvider.URL_NAME_PROPERTY_ID),"test");
+    assertEquals(resource.getPropertyValue(ViewURLResourceProvider.URL_SUFFIX_PROPERTY_ID),"url");
+    assertEquals(resource.getPropertyValue(ViewURLResourceProvider.VIEW_INSTANCE_NAME_PROPERTY_ID),"test");
+    assertEquals(resource.getPropertyValue(ViewURLResourceProvider.VIEW_INSTANCE_VERSION_PROPERTY_ID),"1.0.0");
+    assertEquals(resource.getPropertyValue(ViewURLResourceProvider.VIEW_INSTANCE_COMMON_NAME_PROPERTY_ID),"FILES");
+    verify(viewURLEntity,viewInstanceEntity,viewEntity);
+  }
+
+
+  @Test
+  public void testCreateResourcesAsAdministrator() throws Exception {
+    testCreateResources(TestAuthenticationFactory.createAdministrator());
+  }
+
+
+
+  static void setDao(Field field, Object newValue) throws Exception {
+    field.setAccessible(true);
+
+    Field modifiersField = Field.class.getDeclaredField("modifiers");
+    modifiersField.setAccessible(true);
+    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+    field.set(null, newValue);
+  }
+
+  private void testCreateResources(Authentication authentication) throws Exception {
+    ViewInstanceEntity viewInstanceEntity = createNiceMock(ViewInstanceEntity.class);
+    ViewEntity viewEntity = createNiceMock(ViewEntity.class);
+    ViewURLResourceProvider provider = new ViewURLResourceProvider();
+
+    ViewURLDAO viewURLDAO = createNiceMock(ViewURLDAO.class);
+    setDao(ViewURLResourceProvider.class.getDeclaredField("viewURLDAO"), viewURLDAO);
+    Set<Map<String, Object>> properties = new HashSet<>();
+    Map<String, Object> propertyMap = new HashMap<>();
+    propertyMap.put(ViewURLResourceProvider.URL_NAME_PROPERTY_ID,"test");
+    propertyMap.put(ViewURLResourceProvider.URL_SUFFIX_PROPERTY_ID,"suffix");
+    propertyMap.put(ViewURLResourceProvider.VIEW_INSTANCE_COMMON_NAME_PROPERTY_ID,"FILES");
+    propertyMap.put(ViewURLResourceProvider.VIEW_INSTANCE_NAME_PROPERTY_ID,"test");
+    propertyMap.put(ViewURLResourceProvider.VIEW_INSTANCE_VERSION_PROPERTY_ID,"1.0.0");
+
+    expect(viewregistry.getInstanceDefinition("FILES","1.0.0","test")).andReturn(viewInstanceEntity);
+    expect(viewregistry.getDefinition("FILES","1.0.0")).andReturn(viewEntity);
+    expect(viewInstanceEntity.getViewEntity()).andReturn(viewEntity).once();
+    expect(viewEntity.getCommonName()).andReturn("FILES").once();
+    expect(viewEntity.isDeployed()).andReturn(true).once();
+    expect(viewEntity.getVersion()).andReturn("1.0.0").once();
+    expect(viewInstanceEntity.getName()).andReturn("test").once();
+    expect(viewInstanceEntity.getViewUrl()).andReturn(null).once();
+    expect(viewURLDAO.findByName("test")).andReturn(Optional.<ViewURLEntity>absent());
+    Capture<ViewURLEntity> urlEntityCapture = newCapture();
+    viewURLDAO.save(capture(urlEntityCapture));
+    viewregistry.updateViewInstance(viewInstanceEntity);
+    viewregistry.updateView(viewInstanceEntity);
+    replay(viewregistry,viewEntity,viewInstanceEntity,viewURLDAO);
+
+    properties.add(propertyMap);
+
+    SecurityContextHolder.getContext().setAuthentication(authentication);
+
+    provider.createResources(PropertyHelper.getCreateRequest(properties, null));
+
+    ViewURLEntity urlEntity = urlEntityCapture.getValue();
+    assertEquals(urlEntity.getUrlName(),"test");
+    assertEquals(urlEntity.getUrlSuffix(),"suffix");
+    assertEquals(urlEntity.getViewInstanceEntity(),viewInstanceEntity);
+
+  }
+
+  @Test(expected = org.apache.ambari.server.controller.spi.SystemException.class)
+  public void testCreateResources_existingUrl() throws Exception {
+    ViewInstanceEntity viewInstanceEntity = createNiceMock(ViewInstanceEntity.class);
+    ViewEntity viewEntity = createNiceMock(ViewEntity.class);
+    ViewURLResourceProvider provider = new ViewURLResourceProvider();
+
+    ViewURLDAO viewURLDAO = createNiceMock(ViewURLDAO.class);
+    setDao(ViewURLResourceProvider.class.getDeclaredField("viewURLDAO"), viewURLDAO);
+    Set<Map<String, Object>> properties = new HashSet<>();
+    Map<String, Object> propertyMap = new HashMap<>();
+    propertyMap.put(ViewURLResourceProvider.URL_NAME_PROPERTY_ID,"test");
+    propertyMap.put(ViewURLResourceProvider.URL_SUFFIX_PROPERTY_ID,"suffix");
+    propertyMap.put(ViewURLResourceProvider.VIEW_INSTANCE_COMMON_NAME_PROPERTY_ID,"FILES");
+    propertyMap.put(ViewURLResourceProvider.VIEW_INSTANCE_NAME_PROPERTY_ID,"test");
+    propertyMap.put(ViewURLResourceProvider.VIEW_INSTANCE_VERSION_PROPERTY_ID,"1.0.0");
+
+    expect(viewregistry.getInstanceDefinition("FILES","1.0.0","test")).andReturn(viewInstanceEntity);
+    expect(viewregistry.getDefinition("FILES","1.0.0")).andReturn(viewEntity);
+    expect(viewInstanceEntity.getViewEntity()).andReturn(viewEntity).once();
+    expect(viewEntity.getCommonName()).andReturn("FILES").once();
+    expect(viewEntity.isDeployed()).andReturn(true).once();
+    expect(viewEntity.getVersion()).andReturn("1.0.0").once();
+    expect(viewInstanceEntity.getName()).andReturn("test").once();
+    expect(viewInstanceEntity.getViewUrl()).andReturn(null).once();
+    expect(viewURLDAO.findByName("test")).andReturn(Optional.of(new ViewURLEntity()));
+    replay(viewregistry,viewEntity,viewInstanceEntity,viewURLDAO);
+    properties.add(propertyMap);
+    SecurityContextHolder.getContext().setAuthentication(TestAuthenticationFactory.createAdministrator());
+    provider.createResources(PropertyHelper.getCreateRequest(properties, null));
+
+  }
+
+
+  @Test
+  public void testUpdateResources() throws Exception {
+    ViewInstanceEntity viewInstanceEntity = createNiceMock(ViewInstanceEntity.class);
+    ViewEntity viewEntity = createNiceMock(ViewEntity.class);
+    ViewURLResourceProvider provider = new ViewURLResourceProvider();
+    ViewURLEntity viewURLEntity = createNiceMock(ViewURLEntity.class);
+
+    ViewURLDAO viewURLDAO = createNiceMock(ViewURLDAO.class);
+    setDao(ViewURLResourceProvider.class.getDeclaredField("viewURLDAO"), viewURLDAO);
+    Set<Map<String, Object>> properties = new HashSet<>();
+    Map<String, Object> propertyMap = new HashMap<>();
+    propertyMap.put(ViewURLResourceProvider.URL_NAME_PROPERTY_ID,"test");
+    propertyMap.put(ViewURLResourceProvider.URL_SUFFIX_PROPERTY_ID,"suffix2");
+
+    expect(viewURLDAO.findByName("test")).andReturn(Optional.of(viewURLEntity));
+    expect(viewURLEntity.getViewInstanceEntity()).andReturn(viewInstanceEntity).once();
+    expect(viewURLEntity.getUrlName()).andReturn("test").once();
+    expect(viewURLEntity.getUrlSuffix()).andReturn("suffix2").once();
+    expect(viewURLEntity.getViewInstanceEntity()).andReturn(viewInstanceEntity).once();
+    viewURLEntity.setUrlSuffix("suffix2");
+    Capture<ViewURLEntity> urlEntityCapture = newCapture();
+    viewURLDAO.update(capture(urlEntityCapture));
+    viewregistry.updateViewInstance(viewInstanceEntity);
+    viewregistry.updateView(viewInstanceEntity);
+    replay(viewregistry,viewEntity,viewInstanceEntity,viewURLDAO,viewURLEntity);
+
+    properties.add(propertyMap);
+
+    SecurityContextHolder.getContext().setAuthentication(TestAuthenticationFactory.createAdministrator());
+
+    PredicateBuilder predicateBuilder = new PredicateBuilder();
+    Predicate predicate =
+            predicateBuilder.property(ViewURLResourceProvider.URL_NAME_PROPERTY_ID).equals("test").toPredicate();
+
+    provider.updateResources(PropertyHelper.getCreateRequest(properties, null),predicate);
+
+    ViewURLEntity urlEntity = urlEntityCapture.getValue();
+    assertEquals(urlEntity.getUrlName(),"test");
+    assertEquals(urlEntity.getUrlSuffix(),"suffix2");
+    assertEquals(urlEntity.getViewInstanceEntity(),viewInstanceEntity);
+
+  }
+
+  @Test
+  public void testDeleteResources() throws Exception {
+    ViewInstanceEntity viewInstanceEntity = createNiceMock(ViewInstanceEntity.class);
+    ViewEntity viewEntity = createNiceMock(ViewEntity.class);
+    ViewURLResourceProvider provider = new ViewURLResourceProvider();
+    ViewURLEntity viewURLEntity = createNiceMock(ViewURLEntity.class);
+    ViewURLDAO viewURLDAO = createNiceMock(ViewURLDAO.class);
+    EqualsPredicate equalsPredicate = new EqualsPredicate(ViewURLResourceProvider.URL_NAME_PROPERTY_ID,"test");
+
+
+    setDao(ViewURLResourceProvider.class.getDeclaredField("viewURLDAO"), viewURLDAO);
+    Set<Map<String, Object>> properties = new HashSet<>();
+    Map<String, Object> propertyMap = new HashMap<>();
+    propertyMap.put(ViewURLResourceProvider.URL_NAME_PROPERTY_ID,"test");
+    propertyMap.put(ViewURLResourceProvider.URL_SUFFIX_PROPERTY_ID,"suffix");
+
+    expect(viewURLDAO.findByName("test")).andReturn(Optional.of(viewURLEntity));
+    expect(viewURLEntity.getViewInstanceEntity()).andReturn(viewInstanceEntity).once();
+    expect(viewURLEntity.getUrlName()).andReturn("test").once();
+    expect(viewURLEntity.getUrlSuffix()).andReturn("suffix").once();
+    expect(viewURLEntity.getViewInstanceEntity()).andReturn(viewInstanceEntity).once();
+    viewURLEntity.setUrlSuffix("suffix");
+    Capture<ViewURLEntity> urlEntityCapture = newCapture();
+    viewInstanceEntity.clearUrl();
+    viewURLEntity.clearEntity();
+    viewURLDAO.delete(capture(urlEntityCapture));
+    viewregistry.updateViewInstance(viewInstanceEntity);
+    viewregistry.updateView(viewInstanceEntity);
+    replay(viewregistry,viewEntity,viewInstanceEntity,viewURLDAO,viewURLEntity);
+
+    properties.add(propertyMap);
+
+    SecurityContextHolder.getContext().setAuthentication(TestAuthenticationFactory.createAdministrator());
+
+    provider.deleteResources(PropertyHelper.getCreateRequest(properties, null),equalsPredicate);
+
+    ViewURLEntity urlEntity = urlEntityCapture.getValue();
+    assertEquals(urlEntity.getUrlName(),"test");
+    assertEquals(urlEntity.getUrlSuffix(),"suffix");
+    assertEquals(urlEntity.getViewInstanceEntity(),viewInstanceEntity);
+
+  }
+
+
+
+
+
+}