Repository: ambari Updated Branches: refs/heads/trunk d32f61c9e -> f57185fbf
AMBARI-16195 Added pagination, and search fields for View URLs, made the UX better and added tests (Ashwin Rajeev via dipayanb) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/f57185fb Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/f57185fb Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/f57185fb Branch: refs/heads/trunk Commit: f57185fbfe24e1b0b7f1d72e8495c12e2fa0923d Parents: d32f61c Author: Dipayan Bhowmick <[email protected]> Authored: Tue May 3 10:56:41 2016 +0530 Committer: Dipayan Bhowmick <[email protected]> Committed: Tue May 3 11:00:26 2016 +0530 ---------------------------------------------------------------------- .../controllers/ambariViews/ViewUrlCtrl.js | 110 +++---- .../controllers/ambariViews/ViewsListCtrl.js | 121 ++++++- .../ui/admin-web/app/scripts/i18n.config.js | 14 +- .../ui/admin-web/app/scripts/services/View.js | 13 +- .../resources/ui/admin-web/app/styles/main.css | 25 ++ .../app/views/ambariViews/listUrls.html | 60 +++- .../ui/admin-web/app/views/urls/create.html | 57 +++- .../admin-web/app/views/urls/create_step_1.html | 36 --- .../admin-web/app/views/urls/create_step_2.html | 44 --- .../admin-web/app/views/urls/create_step_3.html | 60 ---- .../internal/ViewURLResourceProvider.java | 29 +- .../internal/ViewURLResourceProviderTest.java | 319 +++++++++++++++++++ 12 files changed, 633 insertions(+), 255 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/f57185fb/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlCtrl.js ---------------------------------------------------------------------- diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlCtrl.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlCtrl.js index 0cf8d03..ef9cfa4 100644 --- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewUrlCtrl.js +++ b/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') }; var targetUrl = '/viewUrls'; + $scope.url={}; $scope.formHolder = {}; + $scope.stepOneNotCompleted = true; + $scope.stepTwoNotCompleted = true; View.getAllVisibleInstance().then(function(views) { var names = []; @@ -57,6 +60,8 @@ angular.module('ambariAdminConsole') $scope.url.selectedInstance = instances.find(function(inst){ 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) { @@ -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); + }); + } + }; }]); http://git-wip-us.apache.org/repos/asf/ambari/blob/f57185fb/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js ---------------------------------------------------------------------- diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js index ed389e1..594ac87 100644 --- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js +++ b/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){ var deferred = View.checkViewVersionStatus(view.view_name, versionNumber); deferredList.push(deferred); @@ -130,16 +132,129 @@ angular.module('ambariAdminConsole') 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(){ - 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.ViewNameFilterOptions = urls.items.map(function(url){ 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) { - 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; + }); + + }; }]); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/f57185fb/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js ---------------------------------------------------------------------- diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js index 6a927b6..6af7d4c 100644 --- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js +++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js @@ -32,7 +32,7 @@ angular.module('ambariAdminConsole') 'signOut': 'Sign out', 'clusters': 'Clusters', 'views': 'Views', - 'viewUrls': 'View URL\'s', + 'viewUrls': 'View URLs', 'roles': 'Roles', 'users': 'Users', 'groups': 'Groups', @@ -133,9 +133,10 @@ angular.module('ambariAdminConsole') 'cannotLoadClusterStatus': 'Cannot load cluster status', 'clusterRenamed': 'The cluster has been renamed 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', 'cannotDeleteInstance': 'Cannot delete instance', 'cannotLoadViews': 'Cannot load views', - 'cannotLoadViewUrls': 'Cannot load view URL\'s', + 'cannotLoadViewUrls': 'Cannot load view URLs', 'cannotLoadViewUrl': 'Cannot load view URL' } }, @@ -243,7 +244,8 @@ angular.module('ambariAdminConsole') 'step1':'Create URL', 'step2':'Select instance', '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>', 'urlUpdated':'Updated short URL <a href="/#/main/view/{{viewName}}/{{shortUrl}}">{{urlName}}</a>' }, http://git-wip-us.apache.org/repos/asf/ambari/blob/f57185fb/ambari-admin/src/main/resources/ui/admin-web/app/scripts/services/View.js ---------------------------------------------------------------------- diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/services/View.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/services/View.js index 36bd32d..edc47e4 100644 --- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/services/View.js +++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/services/View.js @@ -61,13 +61,18 @@ angular.module('ambariAdminConsole') angular.element(this,item); } - ViewUrl.all = function() { + ViewUrl.all = function(params) { var deferred = $q.defer(); $http({ method: 'GET', 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) { @@ -189,8 +194,8 @@ angular.module('ambariAdminConsole') return ViewInstance.find(viewName, version, instanceName); }; - View.allUrls = function(){ - return ViewUrl.all() + View.allUrls = function(req){ + return ViewUrl.all(req) }; View.getUrlInfo = function(urlName){ http://git-wip-us.apache.org/repos/asf/ambari/blob/f57185fb/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css ---------------------------------------------------------------------- diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css b/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css index 167e661..7309cfb 100644 --- a/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css +++ b/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css @@ -219,6 +219,20 @@ max-width: 300px; } +.v-small-input{ + max-width: 100px; +} + +.m-small-input{ + max-width: 100px; +} + + +.white-bg{ + background-color: #ffffff; + +} + .paginator{ margin: 0; } @@ -1120,6 +1134,17 @@ button.btn.btn-xs{ -moz-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 { word-wrap: break-word; padding-right: 10px; http://git-wip-us.apache.org/repos/asf/ambari/blob/f57185fb/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listUrls.html ---------------------------------------------------------------------- diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listUrls.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listUrls.html index a2949f9..0613183 100644 --- a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listUrls.html +++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/listUrls.html @@ -16,7 +16,7 @@ * 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"> @@ -24,27 +24,53 @@ <li class="active">{{'common.viewUrls' | translate}}</li> </ol> <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> <hr> <table class="table table-striped table-hover"> <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> - <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">×</span><span class="sr-only">{{'common.controls.close' | translate}}</span> + </button></span> + </div> </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> - <span class="padding-bottom-30">{{'urls.viewInstance' | translate}}</span> + <th class="fix-top"> </th> </tr> + </thead> <tbody> <tr ng-repeat="url in urls.items"> @@ -68,5 +94,17 @@ <div class="alert alert-info col-sm-12" ng-show="!urls.items.length"> {{'urls.noUrlsToDisplay'| translate}} </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> http://git-wip-us.apache.org/repos/asf/ambari/blob/f57185fb/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create.html ---------------------------------------------------------------------- diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create.html index eb31b42..3397299 100644 --- a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create.html +++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create.html @@ -23,29 +23,56 @@ <div class="row"> <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 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 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> + </form> </div> </div> http://git-wip-us.apache.org/repos/asf/ambari/blob/f57185fb/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_1.html ---------------------------------------------------------------------- diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_1.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_1.html deleted file mode 100644 index e19313e..0000000 --- a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_1.html +++ /dev/null @@ -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> http://git-wip-us.apache.org/repos/asf/ambari/blob/f57185fb/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_2.html ---------------------------------------------------------------------- diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_2.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_2.html deleted file mode 100644 index 36bce88..0000000 --- a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_2.html +++ /dev/null @@ -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> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/f57185fb/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_3.html ---------------------------------------------------------------------- diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_3.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_3.html deleted file mode 100644 index e6b3c1c..0000000 --- a/ambari-admin/src/main/resources/ui/admin-web/app/views/urls/create_step_3.html +++ /dev/null @@ -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> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/f57185fb/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewURLResourceProvider.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewURLResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewURLResourceProvider.java index f3d6b11..b1b062c 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewURLResourceProvider.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewURLResourceProvider.java @@ -19,9 +19,9 @@ package org.apache.ambari.server.controller.internal; import com.google.common.base.Optional; +import com.google.common.base.Strings; import com.google.common.collect.Sets; import com.google.inject.Inject; -import com.google.inject.persist.Transactional; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.StaticallyInject; 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.SystemException; 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.entities.ViewEntity; 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>(); 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); } @@ -93,12 +89,12 @@ public class ViewURLResourceProvider extends AbstractAuthorizedResourceProvider @Inject private static ViewURLDAO viewURLDAO; - // ----- Constructors ------------------------------------------------------ /** * Construct a view URL resource provider. */ + public ViewURLResourceProvider() { super(propertyIds, keyPropertyIds); @@ -127,10 +123,22 @@ public class ViewURLResourceProvider extends AbstractAuthorizedResourceProvider public Set<Resource> getResources(Request request, Predicate predicate) 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; @@ -235,7 +243,6 @@ public class ViewURLResourceProvider extends AbstractAuthorizedResourceProvider */ private Command<Void> getCreateCommand(final Map<String, Object> properties) { return new Command<Void>() { - @Transactional @Override public Void invoke() throws AmbariException { ViewRegistry viewRegistry = ViewRegistry.getInstance(); http://git-wip-us.apache.org/repos/asf/ambari/blob/f57185fb/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ViewURLResourceProviderTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ViewURLResourceProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ViewURLResourceProviderTest.java new file mode 100644 index 0000000..e223d0f --- /dev/null +++ b/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); + + } + + + + + +} \ No newline at end of file
