From 3fa0e6eaa1b5908097cbe625c1a23e606e332630 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Mon, 25 May 2015 16:47:13 +0200 Subject: [PATCH] SONAR-6581 refactor provisioning page --- server/sonar-web/Gruntfile.coffee | 11 + .../src/main/js/apps/provisioning/app.js | 47 ++++ .../main/js/apps/provisioning/create-view.js | 32 +++ .../main/js/apps/provisioning/delete-view.js | 32 +++ .../main/js/apps/provisioning/form-view.js | 26 ++ .../main/js/apps/provisioning/header-view.js | 25 ++ .../src/main/js/apps/provisioning/layout.js | 16 ++ .../js/apps/provisioning/list-footer-view.js | 34 +++ .../js/apps/provisioning/list-item-view.js | 34 +++ .../main/js/apps/provisioning/list-view.js | 11 + .../src/main/js/apps/provisioning/project.js | 30 ++ .../src/main/js/apps/provisioning/projects.js | 40 +++ .../main/js/apps/provisioning/search-view.js | 49 ++++ .../templates/provisioning-delete.hbs | 13 + .../templates/provisioning-form.hbs | 30 ++ .../templates/provisioning-header.hbs | 9 + .../templates/provisioning-layout.hbs | 6 + .../templates/provisioning-list-footer.hbs | 6 + .../templates/provisioning-list-item.hbs | 12 + .../templates/provisioning-search.hbs | 6 + .../controllers/provisioning_controller.rb | 50 ---- .../views/provisioning/_create_form.html.erb | 33 --- .../views/provisioning/_delete_form.html.erb | 25 -- .../app/views/provisioning/index.html.erb | 52 +--- .../src/test/js/provisioning-spec.js | 256 ++++++++++++++++++ .../json/provisioning-spec/delete-error.json | 7 + .../test/json/provisioning-spec/error.json | 4 + .../json/provisioning-spec/search-big-1.json | 19 ++ .../json/provisioning-spec/search-big-2.json | 13 + .../provisioning-spec/search-created.json | 31 +++ .../provisioning-spec/search-filtered.json | 13 + .../test/json/provisioning-spec/search.json | 25 ++ .../src/test/views/provisioning.jade | 5 + 33 files changed, 848 insertions(+), 154 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/provisioning/app.js create mode 100644 server/sonar-web/src/main/js/apps/provisioning/create-view.js create mode 100644 server/sonar-web/src/main/js/apps/provisioning/delete-view.js create mode 100644 server/sonar-web/src/main/js/apps/provisioning/form-view.js create mode 100644 server/sonar-web/src/main/js/apps/provisioning/header-view.js create mode 100644 server/sonar-web/src/main/js/apps/provisioning/layout.js create mode 100644 server/sonar-web/src/main/js/apps/provisioning/list-footer-view.js create mode 100644 server/sonar-web/src/main/js/apps/provisioning/list-item-view.js create mode 100644 server/sonar-web/src/main/js/apps/provisioning/list-view.js create mode 100644 server/sonar-web/src/main/js/apps/provisioning/project.js create mode 100644 server/sonar-web/src/main/js/apps/provisioning/projects.js create mode 100644 server/sonar-web/src/main/js/apps/provisioning/search-view.js create mode 100644 server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-delete.hbs create mode 100644 server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-form.hbs create mode 100644 server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-header.hbs create mode 100644 server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-layout.hbs create mode 100644 server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-list-footer.hbs create mode 100644 server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-list-item.hbs create mode 100644 server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-search.hbs delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/provisioning/_create_form.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/provisioning/_delete_form.html.erb create mode 100644 server/sonar-web/src/test/js/provisioning-spec.js create mode 100644 server/sonar-web/src/test/json/provisioning-spec/delete-error.json create mode 100644 server/sonar-web/src/test/json/provisioning-spec/error.json create mode 100644 server/sonar-web/src/test/json/provisioning-spec/search-big-1.json create mode 100644 server/sonar-web/src/test/json/provisioning-spec/search-big-2.json create mode 100644 server/sonar-web/src/test/json/provisioning-spec/search-created.json create mode 100644 server/sonar-web/src/test/json/provisioning-spec/search-filtered.json create mode 100644 server/sonar-web/src/test/json/provisioning-spec/search.json create mode 100644 server/sonar-web/src/test/views/provisioning.jade diff --git a/server/sonar-web/Gruntfile.coffee b/server/sonar-web/Gruntfile.coffee index 3f33fd9dc95..460c62b4802 100644 --- a/server/sonar-web/Gruntfile.coffee +++ b/server/sonar-web/Gruntfile.coffee @@ -163,6 +163,10 @@ module.exports = (grunt) -> name: 'apps/users/app' out: '<%= ASSETS_PATH %>/js/apps/users/app.js' + provisioning: options: + name: 'apps/provisioning/app' + out: '<%= ASSETS_PATH %>/js/apps/provisioning/app.js' + parallel: build: @@ -184,6 +188,7 @@ module.exports = (grunt) -> 'requirejs:issueFilterWidget' 'requirejs:markdown' 'requirejs:users' + 'requirejs:provisioning' ] casper: options: grunt: true @@ -204,6 +209,7 @@ module.exports = (grunt) -> 'casper:ui' 'casper:workspace' 'casper:users' + 'casper:provisioning' ] @@ -264,6 +270,9 @@ module.exports = (grunt) -> '<%= BUILD_PATH %>/js/apps/users/templates.js': [ '<%= SOURCE_PATH %>/js/apps/users/templates/**/*.hbs' ] + '<%= BUILD_PATH %>/js/apps/provisioning/templates.js': [ + '<%= SOURCE_PATH %>/js/apps/provisioning/templates/**/*.hbs' + ] clean: @@ -358,6 +367,8 @@ module.exports = (grunt) -> src: ['src/test/js/workspace*.js'] users: src: ['src/test/js/users*.js'] + provisioning: + src: ['src/test/js/provisioning*.js'] uglify: build: diff --git a/server/sonar-web/src/main/js/apps/provisioning/app.js b/server/sonar-web/src/main/js/apps/provisioning/app.js new file mode 100644 index 00000000000..aa754e5ba77 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/app.js @@ -0,0 +1,47 @@ +define([ + './layout', + './projects', + './header-view', + './search-view', + './list-view', + './list-footer-view' +], function (Layout, Projects, HeaderView, SearchView, ListView, ListFooterView) { + + var App = new Marionette.Application(), + init = function (options) { + // Layout + this.layout = new Layout({ el: options.el }); + this.layout.render(); + + // Collection + this.projects = new Projects(); + + // Header View + this.headerView = new HeaderView({ collection: this.projects }); + this.layout.headerRegion.show(this.headerView); + + // Search View + this.searchView = new SearchView({ collection: this.projects }); + this.layout.searchRegion.show(this.searchView); + + // List View + this.listView = new ListView({ collection: this.projects }); + this.layout.listRegion.show(this.listView); + + // List Footer View + this.listFooterView = new ListFooterView({ collection: this.projects }); + this.layout.listFooterRegion.show(this.listFooterView); + + // Go! + this.projects.fetch(); + }; + + App.on('start', function (options) { + window.requestMessages().done(function () { + init.call(App, options); + }); + }); + + return App; + +}); diff --git a/server/sonar-web/src/main/js/apps/provisioning/create-view.js b/server/sonar-web/src/main/js/apps/provisioning/create-view.js new file mode 100644 index 00000000000..100da85fff1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/create-view.js @@ -0,0 +1,32 @@ +define([ + './project', + './form-view' +], function (Project, FormView) { + + return FormView.extend({ + + sendRequest: function () { + var that = this, + project = new Project({ + name: this.$('#create-project-name').val(), + branch: this.$('#create-project-branch').val(), + key: this.$('#create-project-key').val() + }); + this.disableForm(); + return project.save(null, { + statusCode: { + // do not show global error + 400: null + } + }).done(function () { + that.collection.refresh(); + that.close(); + }).fail(function (jqXHR) { + that.enableForm(); + console.log(jqXHR.responseJSON); + that.showErrors([{ msg: jqXHR.responseJSON.err_msg }]); + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/provisioning/delete-view.js b/server/sonar-web/src/main/js/apps/provisioning/delete-view.js new file mode 100644 index 00000000000..2ff38de169f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/delete-view.js @@ -0,0 +1,32 @@ +define([ + 'components/common/modal-form', + './templates' +], function (ModalForm) { + + return ModalForm.extend({ + template: Templates['provisioning-delete'], + + onFormSubmit: function () { + ModalForm.prototype.onFormSubmit.apply(this, arguments); + this.sendRequest(); + }, + + sendRequest: function () { + var that = this, + collection = this.model.collection; + return this.model.destroy({ + wait: true, + statusCode: { + // do not show global error + 400: null + } + }).done(function () { + collection.refresh(); + that.close(); + }).fail(function (jqXHR) { + that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings); + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/provisioning/form-view.js b/server/sonar-web/src/main/js/apps/provisioning/form-view.js new file mode 100644 index 00000000000..ed7fe702f2c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/form-view.js @@ -0,0 +1,26 @@ +define([ + 'components/common/modal-form', + './templates' +], function (ModalForm) { + + return ModalForm.extend({ + template: Templates['provisioning-form'], + + onRender: function () { + ModalForm.prototype.onRender.apply(this, arguments); + this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' }); + }, + + onClose: function () { + ModalForm.prototype.onClose.apply(this, arguments); + this.$('[data-toggle="tooltip"]').tooltip('destroy'); + }, + + onFormSubmit: function () { + ModalForm.prototype.onFormSubmit.apply(this, arguments); + this.sendRequest(); + } + + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/provisioning/header-view.js b/server/sonar-web/src/main/js/apps/provisioning/header-view.js new file mode 100644 index 00000000000..4be7c21196d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/header-view.js @@ -0,0 +1,25 @@ +define([ + './create-view', + './templates' +], function (CreateView) { + + return Marionette.ItemView.extend({ + template: Templates['provisioning-header'], + + events: { + 'click #provisioning-create': 'onCreateClick' + }, + + onCreateClick: function (e) { + e.preventDefault(); + this.createProject(); + }, + + createProject: function () { + new CreateView({ + collection: this.collection + }).render(); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/provisioning/layout.js b/server/sonar-web/src/main/js/apps/provisioning/layout.js new file mode 100644 index 00000000000..d0627a1e5b8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/layout.js @@ -0,0 +1,16 @@ +define([ + './templates' +], function () { + + return Marionette.Layout.extend({ + template: Templates['provisioning-layout'], + + regions: { + headerRegion: '#provisioning-header', + searchRegion: '#provisioning-search', + listRegion: '#provisioning-list', + listFooterRegion: '#provisioning-list-footer' + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/provisioning/list-footer-view.js b/server/sonar-web/src/main/js/apps/provisioning/list-footer-view.js new file mode 100644 index 00000000000..6dc243e7b67 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/list-footer-view.js @@ -0,0 +1,34 @@ +define([ + './templates' +], function () { + + return Marionette.ItemView.extend({ + template: Templates['provisioning-list-footer'], + + collectionEvents: { + 'all': 'render' + }, + + events: { + 'click #provisioning-fetch-more': 'onMoreClick' + }, + + onMoreClick: function (e) { + e.preventDefault(); + this.fetchMore(); + }, + + fetchMore: function () { + this.collection.fetchMore(); + }, + + serializeData: function () { + return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), { + total: this.collection.total, + count: this.collection.length, + more: this.collection.hasMore() + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/provisioning/list-item-view.js b/server/sonar-web/src/main/js/apps/provisioning/list-item-view.js new file mode 100644 index 00000000000..8e565d54981 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/list-item-view.js @@ -0,0 +1,34 @@ +define([ + './delete-view', + './templates' +], function (DeleteView) { + + return Marionette.ItemView.extend({ + tagName: 'li', + className: 'panel panel-vertical', + template: Templates['provisioning-list-item'], + + events: { + 'click .js-project-delete': 'onDeleteClick' + }, + + onRender: function () { + this.$el.attr('data-id', this.model.id); + this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' }); + }, + + onClose: function () { + this.$('[data-toggle="tooltip"]').tooltip('destroy'); + }, + + onDeleteClick: function (e) { + e.preventDefault(); + this.deleteProject(); + }, + + deleteProject: function () { + new DeleteView({ model: this.model }).render(); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/provisioning/list-view.js b/server/sonar-web/src/main/js/apps/provisioning/list-view.js new file mode 100644 index 00000000000..138c36b7619 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/list-view.js @@ -0,0 +1,11 @@ +define([ + './list-item-view', + './templates' +], function (ListItemView) { + + return Marionette.CollectionView.extend({ + tagName: 'ul', + itemView: ListItemView + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/provisioning/project.js b/server/sonar-web/src/main/js/apps/provisioning/project.js new file mode 100644 index 00000000000..90963b7a1b5 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/project.js @@ -0,0 +1,30 @@ +define(function () { + + return Backbone.Model.extend({ + idAttribute: 'uuid', + + urlRoot: function () { + return baseUrl + '/api/projects'; + }, + + sync: function (method, model, options) { + var opts = options || {}; + if (method === 'create') { + _.defaults(opts, { + url: this.urlRoot() + '/create', + type: 'POST', + data: _.pick(model.toJSON(), 'key', 'name', 'branch') + }); + } + if (method === 'delete') { + _.defaults(opts, { + url: this.urlRoot() + '/delete', + type: 'POST', + data: { uuids: this.id } + }); + } + return Backbone.ajax(opts); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/provisioning/projects.js b/server/sonar-web/src/main/js/apps/provisioning/projects.js new file mode 100644 index 00000000000..44f1c5b4b5a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/projects.js @@ -0,0 +1,40 @@ +define([ + './project' +], function (Project) { + + return Backbone.Collection.extend({ + model: Project, + + url: function () { + return baseUrl + '/api/projects/provisioned'; + }, + + parse: function (r) { + this.total = r.total; + this.p = r.p; + this.ps = r.ps; + return r.projects; + }, + + fetch: function (options) { + var d = (options && options.data) || {}; + this.q = d.q; + return Backbone.Collection.prototype.fetch.apply(this, arguments); + }, + + fetchMore: function () { + var p = this.p + 1; + return this.fetch({ add: true, remove: false, data: { p: p, ps: this.ps, q: this.q } }); + }, + + refresh: function () { + return this.fetch({ reset: true, data: { q: this.q } }); + }, + + hasMore: function () { + return this.total > this.p * this.ps; + } + + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/provisioning/search-view.js b/server/sonar-web/src/main/js/apps/provisioning/search-view.js new file mode 100644 index 00000000000..519e3c44b2a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/search-view.js @@ -0,0 +1,49 @@ +define([ + './templates' +], function () { + + return Marionette.ItemView.extend({ + template: Templates['provisioning-search'], + + events: { + 'submit #provisioning-search-form': 'onFormSubmit', + 'search #provisioning-search-query': 'debouncedOnKeyUp', + 'keyup #provisioning-search-query': 'debouncedOnKeyUp' + }, + + initialize: function () { + this._bufferedValue = null; + this.debouncedOnKeyUp = _.debounce(this.onKeyUp, 400); + }, + + onRender: function () { + this.delegateEvents(); + }, + + onFormSubmit: function (e) { + e.preventDefault(); + this.debouncedOnKeyUp(); + }, + + onKeyUp: function () { + var q = this.getQuery(); + if (q === this._bufferedValue) { + return; + } + this._bufferedValue = this.getQuery(); + if (this.searchRequest != null) { + this.searchRequest.abort(); + } + this.searchRequest = this.search(q); + }, + + getQuery: function () { + return this.$('#provisioning-search-query').val(); + }, + + search: function (q) { + return this.collection.fetch({ reset: true, data: { q: q } }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-delete.hbs b/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-delete.hbs new file mode 100644 index 00000000000..c1d2a469848 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-delete.hbs @@ -0,0 +1,13 @@ +
+ + + +
diff --git a/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-form.hbs b/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-form.hbs new file mode 100644 index 00000000000..e931b66efd7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-form.hbs @@ -0,0 +1,30 @@ +
+ + + +
diff --git a/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-header.hbs b/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-header.hbs new file mode 100644 index 00000000000..3daa8f061e3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-header.hbs @@ -0,0 +1,9 @@ + diff --git a/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-layout.hbs b/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-layout.hbs new file mode 100644 index 00000000000..0f07a62acc6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-layout.hbs @@ -0,0 +1,6 @@ +
+
+ +
+ +
diff --git a/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-list-footer.hbs b/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-list-footer.hbs new file mode 100644 index 00000000000..8d00837153f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-list-footer.hbs @@ -0,0 +1,6 @@ + diff --git a/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-list-item.hbs b/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-list-item.hbs new file mode 100644 index 00000000000..b751d622f8d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-list-item.hbs @@ -0,0 +1,12 @@ +
+ +
+ +
+ {{name}} + {{key}} +
+ +
+ Created at {{dt creationDate}} +
diff --git a/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-search.hbs b/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-search.hbs new file mode 100644 index 00000000000..3911130a30e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/provisioning/templates/provisioning-search.hbs @@ -0,0 +1,6 @@ +
+ +
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/provisioning_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/provisioning_controller.rb index 1b91cc25eca..e0ea0fdaa1d 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/provisioning_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/provisioning_controller.rb @@ -20,61 +20,11 @@ class ProvisioningController < ApplicationController before_filter :admin_required - verify :method => :delete, :only => [:delete], :redirect_to => {:action => :index} SECTION=Navigation::SECTION_CONFIGURATION def index access_denied unless has_role?("provisioning") - params['qualifiers'] = 'TRK' - - @query_result = Api::Utils.insensitive_sort( - Internal.component_api.findProvisionedProjects(params) - ) { |p| p.key } - end - - def create - verify_post_request - @id = params[:id] - @key = params[:key] - @name = params[:name] - @branch = params[:branch] - - begin - bad_request('provisioning.missing.key') if @key.blank? - bad_request('provisioning.missing.name') if @name.blank? - - Internal.component_api.createComponent(@key, @branch, @name, nil) - - redirect_to :action => 'index' - rescue Exception => e - flash.now[:error]= Api::Utils.message(e.message) - render :partial => 'create_form', :key => @key, :branch => @branch, :name => @name, :status => 400 - end - end - - def create_form - @id = params[:id] - @key = params[:key] - @name = params[:name] - render :partial => 'create_form' end - def delete_form - @id = params[:id] - render :partial => 'delete_form' - end - - def delete - access_denied unless has_role?("provisioning") - - @id = params[:id].to_i - project = Project.first(:conditions => {:id => @id}) - Java::OrgSonarServerUi::JRubyFacade.getInstance().deleteResourceTree(project.key) - flash.now[:notice]= Api::Utils.message('resource_viewer.resource_deleted') - redirect_to :action => 'index' - end - - private - end diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/provisioning/_create_form.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/provisioning/_create_form.html.erb deleted file mode 100644 index 5b2bd449e40..00000000000 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/provisioning/_create_form.html.erb +++ /dev/null @@ -1,33 +0,0 @@ -
-
- - - -
-
- - - diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/provisioning/_delete_form.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/provisioning/_delete_form.html.erb deleted file mode 100644 index 477affddcae..00000000000 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/provisioning/_delete_form.html.erb +++ /dev/null @@ -1,25 +0,0 @@ -
- - -
- - - -
-
- - diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/provisioning/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/provisioning/index.html.erb index 204e2774790..96e3b8965f8 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/provisioning/index.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/provisioning/index.html.erb @@ -1,46 +1,6 @@ -
- - - <% if @query_result.empty? %> -
- <%= message('provisioning.no_results') -%> - <% else %> - - - - - - - - - - - - <% @query_result.each_with_index do |resource, index| %> - - - - - - - - <% end %> - -
<%= message('key') -%><%= message('name') -%><%= message('created') -%><%= message('operations') -%>
- <%= link_to h(resource.key), {:controller => 'dashboard', :action => 'index', :id => resource.id}, - :id => "view-#{resource.key.parameterize}" %> - <%= h resource.name -%><%= format_datetime(resource.created_at) -%> - <%= link_to message('delete'), {:action => :delete_form, :id => resource.id}, - {:id => "delete-#{resource.key.parameterize}", :class => 'open-modal link-action link-red'} -%> -
- - <% end %> - -
- +
+ diff --git a/server/sonar-web/src/test/js/provisioning-spec.js b/server/sonar-web/src/test/js/provisioning-spec.js new file mode 100644 index 00000000000..24ae5ac8d39 --- /dev/null +++ b/server/sonar-web/src/test/js/provisioning-spec.js @@ -0,0 +1,256 @@ +/* globals casper: false */ +var lib = require('../lib'), + testName = lib.testName('Provisioning'); + +lib.initMessages(); +lib.changeWorkingDirectory('provisioning-spec'); +lib.configureCasper(); + +casper.test.begin(testName('List'), 5, function (test) { + casper + .start(lib.buildUrl('provisioning'), function () { + lib.setDefaultViewport(); + lib.mockRequestFromFile('/api/projects/provisioned', 'search.json'); + }) + + .then(function () { + casper.evaluate(function () { + require(['apps/provisioning/app'], function (App) { + App.start({ el: '#provisioning' }); + }); + }); + }) + + .then(function () { + casper.waitForSelector('#provisioning-list ul'); + }) + + .then(function () { + test.assertElementCount('#provisioning-list li[data-id]', 3); + test.assertSelectorContains('#provisioning-list .js-project-name', 'SonarQube'); + test.assertSelectorContains('#provisioning-list .js-project-key', 'sonarqube'); + test.assertElementCount('#provisioning-list .js-project-delete', 3); + test.assertSelectorContains('#provisioning-list-footer', '3/3'); + }) + + .then(function () { + lib.sendCoverage(); + }) + .run(function () { + test.done(); + }); +}); + + +casper.test.begin(testName('Search'), 4, function (test) { + casper + .start(lib.buildUrl('provisioning'), function () { + lib.setDefaultViewport(); + this.searchMock = lib.mockRequestFromFile('/api/projects/provisioned', 'search.json'); + }) + + .then(function () { + casper.evaluate(function () { + require(['apps/provisioning/app'], function (App) { + App.start({ el: '#provisioning' }); + }); + }); + }) + + .then(function () { + casper.waitForSelector('#provisioning-list ul'); + }) + + .then(function () { + test.assertElementCount('#provisioning-list li[data-id]', 3); + lib.clearRequestMock(this.searchMock); + this.searchMock = lib.mockRequestFromFile('/api/projects/provisioned', 'search-filtered.json', + { data: { q: 'script' } }); + casper.evaluate(function () { + jQuery('#provisioning-search-query').val('script'); + }); + casper.click('#provisioning-search-submit'); + casper.waitForSelectorTextChange('#provisioning-list-footer'); + }) + + .then(function () { + test.assertElementCount('#provisioning-list li[data-id]', 1); + lib.clearRequestMock(this.searchMock); + this.searchMock = lib.mockRequestFromFile('/api/projects/provisioned', 'search.json'); + casper.evaluate(function () { + jQuery('#provisioning-search-query').val(''); + }); + casper.click('#provisioning-search-submit'); + casper.waitForSelectorTextChange('#provisioning-list-footer'); + }) + + .then(function () { + test.assertElementCount('#provisioning-list li[data-id]', 3); + test.assert(casper.evaluate(function () { + return jQuery('#provisioning-search-query').val() === ''; + })); + }) + + .then(function () { + lib.sendCoverage(); + }) + .run(function () { + test.done(); + }); +}); + + +casper.test.begin(testName('Show More'), 4, function (test) { + casper + .start(lib.buildUrl('provisioning'), function () { + lib.setDefaultViewport(); + this.searchMock = lib.mockRequestFromFile('/api/projects/provisioned', 'search-big-1.json'); + }) + + .then(function () { + casper.evaluate(function () { + require(['apps/provisioning/app'], function (App) { + App.start({ el: '#provisioning' }); + }); + }); + }) + + .then(function () { + casper.waitForSelector('#provisioning-list ul'); + }) + + .then(function () { + test.assertElementCount('#provisioning-list li[data-id]', 2); + test.assertSelectorContains('#provisioning-list-footer', '2/3'); + lib.clearRequestMock(this.searchMock); + this.searchMock = lib.mockRequestFromFile('/api/projects/provisioned', 'search-big-2.json', { data: { p: '2' } }); + casper.click('#provisioning-fetch-more'); + casper.waitForSelectorTextChange('#provisioning-list-footer'); + }) + + .then(function () { + test.assertElementCount('#provisioning-list li[data-id]', 3); + test.assertSelectorContains('#provisioning-list-footer', '3/3'); + }) + + .then(function () { + lib.sendCoverage(); + }) + .run(function () { + test.done(); + }); +}); + + +casper.test.begin(testName('Create'), 4, function (test) { + casper + .start(lib.buildUrl('provisioning'), function () { + lib.setDefaultViewport(); + this.searchMock = lib.mockRequestFromFile('/api/projects/provisioned', 'search.json'); + this.createMock = lib.mockRequestFromFile('/api/projects/create', 'error.json', { status: 400 }); + }) + + .then(function () { + casper.evaluate(function () { + require(['apps/provisioning/app'], function (App) { + App.start({ el: '#provisioning' }); + }); + jQuery.ajaxSetup({ dataType: 'json' }); + }); + }) + + .then(function () { + casper.waitForSelector('#provisioning-list ul'); + }) + + .then(function () { + test.assertElementCount('#provisioning-list li[data-id]', 3); + casper.click('#provisioning-create'); + casper.waitForSelector('#create-project-form'); + }) + + .then(function () { + casper.click('#create-project-submit'); + casper.waitForSelector('.alert.alert-danger'); + }) + + .then(function () { + lib.clearRequestMock(this.searchMock); + lib.mockRequestFromFile('/api/projects/provisioned', 'search-created.json'); + lib.clearRequestMock(this.createMock); + lib.mockRequest('/api/projects/create', '{}', + { data: { name: 'name', branch: 'branch', key: 'key' } }); + casper.evaluate(function () { + jQuery('#create-project-name').val('name'); + jQuery('#create-project-branch').val('branch'); + jQuery('#create-project-key').val('key'); + }); + casper.click('#create-project-submit'); + casper.waitForSelectorTextChange('#provisioning-list-footer'); + }) + + .then(function () { + test.assertElementCount('#provisioning-list li[data-id]', 4); + test.assertSelectorContains('#provisioning-list .js-project-name', 'name'); + test.assertSelectorContains('#provisioning-list .js-project-key', 'key:branch'); + }) + + .then(function () { + lib.sendCoverage(); + }) + .run(function () { + test.done(); + }); +}); + + +casper.test.begin(testName('Delete'), 1, function (test) { + casper + .start(lib.buildUrl('provisioning'), function () { + lib.setDefaultViewport(); + this.searchMock = lib.mockRequestFromFile('/api/projects/provisioned', 'search.json'); + this.updateMock = lib.mockRequestFromFile('/api/projects/delete', 'delete-error.json', { status: 400 }); + }) + + .then(function () { + casper.evaluate(function () { + require(['apps/provisioning/app'], function (App) { + App.start({ el: '#provisioning' }); + }); + jQuery.ajaxSetup({ dataType: 'json' }); + }); + }) + + .then(function () { + casper.waitForSelector('#provisioning-list ul'); + }) + + .then(function () { + casper.click('[data-id="id-javascript"] .js-project-delete'); + casper.waitForSelector('#delete-project-form'); + }) + + .then(function () { + casper.click('#delete-project-submit'); + casper.waitForSelector('.alert.alert-danger'); + }) + + .then(function () { + lib.clearRequestMock(this.updateMock); + lib.mockRequest('/api/projects/delete', '{}', { data: { uuids: 'id-javascript'} }); + casper.click('#delete-project-submit'); + casper.waitWhileSelector('[data-id="id-javascript"]'); + }) + + .then(function () { + test.assert(true); + }) + + .then(function () { + lib.sendCoverage(); + }) + .run(function () { + test.done(); + }); +}); + diff --git a/server/sonar-web/src/test/json/provisioning-spec/delete-error.json b/server/sonar-web/src/test/json/provisioning-spec/delete-error.json new file mode 100644 index 00000000000..dc1b261128c --- /dev/null +++ b/server/sonar-web/src/test/json/provisioning-spec/delete-error.json @@ -0,0 +1,7 @@ +{ + "errors": [ + { + "msg": "Some error message" + } + ] +} diff --git a/server/sonar-web/src/test/json/provisioning-spec/error.json b/server/sonar-web/src/test/json/provisioning-spec/error.json new file mode 100644 index 00000000000..9f5a49f4904 --- /dev/null +++ b/server/sonar-web/src/test/json/provisioning-spec/error.json @@ -0,0 +1,4 @@ +{ + "err_code": 400, + "err_msg": "error message" +} diff --git a/server/sonar-web/src/test/json/provisioning-spec/search-big-1.json b/server/sonar-web/src/test/json/provisioning-spec/search-big-1.json new file mode 100644 index 00000000000..bbe3a9bd79e --- /dev/null +++ b/server/sonar-web/src/test/json/provisioning-spec/search-big-1.json @@ -0,0 +1,19 @@ +{ + "projects": [ + { + "uuid": "id-sonarqube", + "key": "sonarqube", + "name": "SonarQube", + "creationDate": "2015-05-25T16:49:16+0200" + }, + { + "uuid": "id-javascript", + "key": "javascript", + "name": "JavaScript", + "creationDate": "2015-05-25T16:49:41+0200" + } + ], + "total": 3, + "p": 1, + "ps": 2 +} diff --git a/server/sonar-web/src/test/json/provisioning-spec/search-big-2.json b/server/sonar-web/src/test/json/provisioning-spec/search-big-2.json new file mode 100644 index 00000000000..ac0bb15c13f --- /dev/null +++ b/server/sonar-web/src/test/json/provisioning-spec/search-big-2.json @@ -0,0 +1,13 @@ +{ + "projects": [ + { + "uuid": "id-sonarqube-release", + "key": "sonarqube:release", + "name": "SonarQube", + "creationDate": "2015-05-25T16:49:30+0200" + } + ], + "total": 3, + "p": 2, + "ps": 100 +} diff --git a/server/sonar-web/src/test/json/provisioning-spec/search-created.json b/server/sonar-web/src/test/json/provisioning-spec/search-created.json new file mode 100644 index 00000000000..c750b72665b --- /dev/null +++ b/server/sonar-web/src/test/json/provisioning-spec/search-created.json @@ -0,0 +1,31 @@ +{ + "projects": [ + { + "uuid": "id-sonarqube", + "key": "sonarqube", + "name": "SonarQube", + "creationDate": "2015-05-25T16:49:16+0200" + }, + { + "uuid": "id-javascript", + "key": "javascript", + "name": "JavaScript", + "creationDate": "2015-05-25T16:49:41+0200" + }, + { + "uuid": "id-sonarqube-release", + "key": "sonarqube:release", + "name": "SonarQube", + "creationDate": "2015-05-25T16:49:30+0200" + }, + { + "uuid": "id-key-branch", + "key": "key:branch", + "name": "name", + "creationDate": "2015-05-25T16:49:30+0200" + } + ], + "total": 4, + "p": 1, + "ps": 100 +} diff --git a/server/sonar-web/src/test/json/provisioning-spec/search-filtered.json b/server/sonar-web/src/test/json/provisioning-spec/search-filtered.json new file mode 100644 index 00000000000..85e5bca139e --- /dev/null +++ b/server/sonar-web/src/test/json/provisioning-spec/search-filtered.json @@ -0,0 +1,13 @@ +{ + "projects": [ + { + "uuid": "id-javascript", + "key": "javascript", + "name": "JavaScript", + "creationDate": "2015-05-25T16:49:41+0200" + } + ], + "total": 1, + "p": 1, + "ps": 100 +} diff --git a/server/sonar-web/src/test/json/provisioning-spec/search.json b/server/sonar-web/src/test/json/provisioning-spec/search.json new file mode 100644 index 00000000000..9697960ec61 --- /dev/null +++ b/server/sonar-web/src/test/json/provisioning-spec/search.json @@ -0,0 +1,25 @@ +{ + "projects": [ + { + "uuid": "id-sonarqube", + "key": "sonarqube", + "name": "SonarQube", + "creationDate": "2015-05-25T16:49:16+0200" + }, + { + "uuid": "id-javascript", + "key": "javascript", + "name": "JavaScript", + "creationDate": "2015-05-25T16:49:41+0200" + }, + { + "uuid": "id-sonarqube-release", + "key": "sonarqube:release", + "name": "SonarQube", + "creationDate": "2015-05-25T16:49:30+0200" + } + ], + "total": 3, + "p": 1, + "ps": 100 +} diff --git a/server/sonar-web/src/test/views/provisioning.jade b/server/sonar-web/src/test/views/provisioning.jade new file mode 100644 index 00000000000..41016938b55 --- /dev/null +++ b/server/sonar-web/src/test/views/provisioning.jade @@ -0,0 +1,5 @@ +extends layouts/main + +block body + #content + #provisioning -- 2.39.5