aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2015-07-08 12:02:44 +0200
committerStas Vilchik <vilchiks@gmail.com>2015-07-08 14:08:00 +0200
commit7a4939f74f1bc53203eb99945182adeb7ab750da (patch)
treeefddc46d7afbdb9bd59eda48196a7aec571adf03
parentafa5a589582cfd710a8f83ff410204e082c14dfe (diff)
downloadsonarqube-7a4939f74f1bc53203eb99945182adeb7ab750da.tar.gz
sonarqube-7a4939f74f1bc53203eb99945182adeb7ab750da.zip
SONAR-6697 add new custom measures page
-rw-r--r--server/sonar-web/Gruntfile.coffee6
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/app.js53
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/create-view.js32
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/custom-measure.js37
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/custom-measures.js45
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/delete-view.js32
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/form-view.js41
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/header-view.js26
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/layout.js15
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/list-footer-view.js34
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/list-item-view.js48
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/list-view.js11
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-delete.hbs13
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-form.hbs30
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-header.hbs9
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-layout.hbs5
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list-footer.hbs6
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list-item.hbs34
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/update-view.js29
-rw-r--r--server/sonar-web/src/main/js/apps/nav/templates/nav-context-navbar.hbs3
-rw-r--r--server/sonar-web/src/main/js/libs/application.js1
-rw-r--r--server/sonar-web/src/main/less/init/type.less5
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/controllers/custom_measures_controller.rb29
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/custom_measures/index.html.erb7
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties14
25 files changed, 564 insertions, 1 deletions
diff --git a/server/sonar-web/Gruntfile.coffee b/server/sonar-web/Gruntfile.coffee
index 8e61d0b9553..1e74a5f7812 100644
--- a/server/sonar-web/Gruntfile.coffee
+++ b/server/sonar-web/Gruntfile.coffee
@@ -120,6 +120,7 @@ module.exports = (grunt) ->
'build-app:api-documentation'
'build-app:coding-rules'
'build-app:computation'
+ 'build-app:custom-measures'
'build-app:drilldown'
'build-app:groups'
'build-app:issues'
@@ -213,10 +214,13 @@ module.exports = (grunt) ->
]
'<%= BUILD_PATH %>/js/apps/account/templates.js': [
'<%= SOURCE_PATH %>/js/apps/account/templates/**/*.hbs'
- ],
+ ]
'<%= BUILD_PATH %>/js/apps/update-center/templates.js': [
'<%= SOURCE_PATH %>/js/apps/update-center/templates/**/*.hbs'
]
+ '<%= BUILD_PATH %>/js/apps/custom-measures/templates.js': [
+ '<%= SOURCE_PATH %>/js/apps/custom-measures/templates/**/*.hbs'
+ ]
clean:
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/app.js b/server/sonar-web/src/main/js/apps/custom-measures/app.js
new file mode 100644
index 00000000000..de6316fb658
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/app.js
@@ -0,0 +1,53 @@
+define([
+ './layout',
+ './custom-measures',
+ './header-view',
+ './list-view',
+ './list-footer-view'
+], function (Layout, CustomMeasures, HeaderView, ListView, ListFooterView) {
+
+ var App = new Marionette.Application(),
+ init = function (options) {
+ // Layout
+ this.layout = new Layout({
+ el: options.el
+ });
+ this.layout.render();
+
+ // Collection
+ this.customMeasures = new CustomMeasures({
+ projectId: options.projectId
+ });
+
+ // Header View
+ this.headerView = new HeaderView({
+ collection: this.customMeasures,
+ projectId: options.projectId
+ });
+ this.layout.headerRegion.show(this.headerView);
+
+ // List View
+ this.listView = new ListView({
+ collection: this.customMeasures
+ });
+ this.layout.listRegion.show(this.listView);
+
+ // List Footer View
+ this.listFooterView = new ListFooterView({
+ collection: this.customMeasures
+ });
+ this.layout.listFooterRegion.show(this.listFooterView);
+
+ // Go!
+ this.customMeasures.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/custom-measures/create-view.js b/server/sonar-web/src/main/js/apps/custom-measures/create-view.js
new file mode 100644
index 00000000000..d7461fe7158
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/create-view.js
@@ -0,0 +1,32 @@
+define([
+ './custom-measure',
+ './form-view'
+], function (CustomMeasure, FormView) {
+
+ return FormView.extend({
+
+ sendRequest: function () {
+ var that = this,
+ customMeasure = new CustomMeasure({
+ metricId: this.$('#create-custom-measure-metric').val(),
+ value: this.$('#create-custom-measure-value').val(),
+ description: this.$('#create-custom-measure-description').val(),
+ projectId: this.options.projectId
+ });
+ this.disableForm();
+ return customMeasure.save(null, {
+ statusCode: {
+ // do not show global error
+ 400: null
+ }
+ }).done(function () {
+ that.collection.refresh();
+ that.destroy();
+ }).fail(function (jqXHR) {
+ that.enableForm();
+ that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
+ });
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/custom-measure.js b/server/sonar-web/src/main/js/apps/custom-measures/custom-measure.js
new file mode 100644
index 00000000000..fce8bf4fbdf
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/custom-measure.js
@@ -0,0 +1,37 @@
+define(function () {
+
+ return Backbone.Model.extend({
+ idAttribute: 'id',
+
+ urlRoot: function () {
+ return baseUrl + '/api/custom_measures';
+ },
+
+ sync: function (method, model, options) {
+ var opts = options || {};
+ if (method === 'create') {
+ _.defaults(opts, {
+ url: this.urlRoot() + '/create',
+ type: 'POST',
+ data: _.pick(model.toJSON(), 'metricId', 'value', 'description', 'projectId')
+ });
+ }
+ if (method === 'update') {
+ _.defaults(opts, {
+ url: this.urlRoot() + '/update',
+ type: 'POST',
+ data: _.pick(model.toJSON(), 'id', 'value', 'description')
+ });
+ }
+ if (method === 'delete') {
+ _.defaults(opts, {
+ url: this.urlRoot() + '/delete',
+ type: 'POST',
+ data: { id: this.id }
+ });
+ }
+ return Backbone.ajax(opts);
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/custom-measures.js b/server/sonar-web/src/main/js/apps/custom-measures/custom-measures.js
new file mode 100644
index 00000000000..4d2c6d29797
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/custom-measures.js
@@ -0,0 +1,45 @@
+define([
+ './custom-measure'
+], function (CustomMeasure) {
+
+ return Backbone.Collection.extend({
+ model: CustomMeasure,
+
+ initialize: function (options) {
+ this.projectId = options.projectId;
+ },
+
+ url: function () {
+ return baseUrl + '/api/custom_measures/search';
+ },
+
+ parse: function (r) {
+ this.total = r.total;
+ this.p = r.p;
+ this.ps = r.ps;
+ return r.customMeasures;
+ },
+
+ fetch: function (options) {
+ var opts = _.defaults(options || {}, { data: {} });
+ this.q = opts.data.q;
+ opts.data.projectId = this.projectId;
+ return this._super(opts);
+ },
+
+ 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/custom-measures/delete-view.js b/server/sonar-web/src/main/js/apps/custom-measures/delete-view.js
new file mode 100644
index 00000000000..1838b83e044
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/delete-view.js
@@ -0,0 +1,32 @@
+define([
+ 'components/common/modal-form',
+ './templates'
+], function (ModalForm) {
+
+ return ModalForm.extend({
+ template: Templates['custom-measures-delete'],
+
+ onFormSubmit: function (e) {
+ this._super(e);
+ 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.destroy();
+ }).fail(function (jqXHR) {
+ that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
+ });
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/form-view.js b/server/sonar-web/src/main/js/apps/custom-measures/form-view.js
new file mode 100644
index 00000000000..43a62a7de83
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/form-view.js
@@ -0,0 +1,41 @@
+define([
+ 'components/common/modal-form',
+ 'apps/metrics/metrics',
+ './templates'
+], function (ModalForm, Metrics) {
+
+ return ModalForm.extend({
+ template: Templates['custom-measures-form'],
+
+ initialize: function () {
+ this.metrics = new Metrics();
+ this.listenTo(this.metrics, 'reset', this.render);
+ this.metrics.fetch({ reset: true });
+ },
+
+ onRender: function () {
+ this._super();
+ this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
+ this.$('#create-custom-measure-metric').select2({
+ width: '250px',
+ minimumResultsForSearch: 20
+ });
+ },
+
+ onDestroy: function () {
+ this._super();
+ this.$('[data-toggle="tooltip"]').tooltip('destroy');
+ },
+
+ onFormSubmit: function (e) {
+ this._super(e);
+ this.sendRequest();
+ },
+
+ serializeData: function () {
+ // TODO show only not taken metrics
+ return _.extend(this._super(), { metrics: this.metrics.toJSON() });
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/header-view.js b/server/sonar-web/src/main/js/apps/custom-measures/header-view.js
new file mode 100644
index 00000000000..8b22efcec5a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/header-view.js
@@ -0,0 +1,26 @@
+define([
+ './create-view',
+ './templates'
+], function (CreateView) {
+
+ return Marionette.ItemView.extend({
+ template: Templates['custom-measures-header'],
+
+ events: {
+ 'click #custom-measures-create': 'onCreateClick'
+ },
+
+ onCreateClick: function (e) {
+ e.preventDefault();
+ this.createCustomMeasure();
+ },
+
+ createCustomMeasure: function () {
+ new CreateView({
+ collection: this.collection,
+ projectId: this.options.projectId
+ }).render();
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/layout.js b/server/sonar-web/src/main/js/apps/custom-measures/layout.js
new file mode 100644
index 00000000000..b4aa4ece791
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/layout.js
@@ -0,0 +1,15 @@
+define([
+ './templates'
+], function () {
+
+ return Marionette.LayoutView.extend({
+ template: Templates['custom-measures-layout'],
+
+ regions: {
+ headerRegion: '#custom-measures-header',
+ listRegion: '#custom-measures-list',
+ listFooterRegion: '#custom-measures-list-footer'
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/list-footer-view.js b/server/sonar-web/src/main/js/apps/custom-measures/list-footer-view.js
new file mode 100644
index 00000000000..d66c27ad335
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/list-footer-view.js
@@ -0,0 +1,34 @@
+define([
+ './templates'
+], function () {
+
+ return Marionette.ItemView.extend({
+ template: Templates['custom-measures-list-footer'],
+
+ collectionEvents: {
+ 'all': 'render'
+ },
+
+ events: {
+ 'click #custom-measures-fetch-more': 'onMoreClick'
+ },
+
+ onMoreClick: function (e) {
+ e.preventDefault();
+ this.fetchMore();
+ },
+
+ fetchMore: function () {
+ this.collection.fetchMore();
+ },
+
+ serializeData: function () {
+ return _.extend(this._super(), {
+ total: this.collection.total,
+ count: this.collection.length,
+ more: this.collection.hasMore()
+ });
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/list-item-view.js b/server/sonar-web/src/main/js/apps/custom-measures/list-item-view.js
new file mode 100644
index 00000000000..97d9671eb17
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/list-item-view.js
@@ -0,0 +1,48 @@
+define([
+ './update-view',
+ './delete-view',
+ './templates'
+], function (UpdateView, DeleteView) {
+
+ return Marionette.ItemView.extend({
+ tagName: 'li',
+ className: 'panel panel-vertical',
+ template: Templates['custom-measures-list-item'],
+
+ events: {
+ 'click .js-custom-measure-update': 'onUpdateClick',
+ 'click .js-custom-measure-delete': 'onDeleteClick'
+ },
+
+ onRender: function () {
+ this.$el.attr('data-id', this.model.id);
+ this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
+ },
+
+ onDestroy: function () {
+ this.$('[data-toggle="tooltip"]').tooltip('destroy');
+ },
+
+ onUpdateClick: function (e) {
+ e.preventDefault();
+ this.updateCustomMeasure();
+ },
+
+ onDeleteClick: function (e) {
+ e.preventDefault();
+ this.deleteCustomMeasure();
+ },
+
+ updateCustomMeasure: function () {
+ new UpdateView({
+ model: this.model,
+ collection: this.model.collection
+ }).render();
+ },
+
+ deleteCustomMeasure: function () {
+ new DeleteView({ model: this.model }).render();
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/list-view.js b/server/sonar-web/src/main/js/apps/custom-measures/list-view.js
new file mode 100644
index 00000000000..24878864d30
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/list-view.js
@@ -0,0 +1,11 @@
+define([
+ './list-item-view',
+ './templates'
+], function (ListItemView) {
+
+ return Marionette.CollectionView.extend({
+ tagName: 'ul',
+ childView: ListItemView
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-delete.hbs b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-delete.hbs
new file mode 100644
index 00000000000..87a29038ba1
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-delete.hbs
@@ -0,0 +1,13 @@
+<form id="delete-custom-measure-form">
+ <div class="modal-head">
+ <h2>Delete Custom Measure</h2>
+ </div>
+ <div class="modal-body">
+ <div class="js-modal-messages"></div>
+ Are you sure you want to delete custom measure "{{metric.name}}"?
+ </div>
+ <div class="modal-foot">
+ <button id="delete-custom-measure-submit" class="button-red">Delete</button>
+ <a href="#" class="js-modal-close" id="delete-custom-measure-cancel">Cancel</a>
+ </div>
+</form>
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-form.hbs b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-form.hbs
new file mode 100644
index 00000000000..52e16a0e98b
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-form.hbs
@@ -0,0 +1,30 @@
+<form id="create-custom-measure-form" autocomplete="off">
+ <div class="modal-head">
+ <h2>{{#if id}}Update{{else}}Create{{/if}} Custom Measure</h2>
+ </div>
+ <div class="modal-body">
+ <div class="js-modal-messages"></div>
+ {{#unless id}}
+ <div class="modal-field">
+ <label for="create-custom-measure-metric">Metric<em class="mandatory">*</em></label>
+ <select id="create-custom-measure-metric" name="metric" required>
+ {{#each metrics}}
+ <option value="{{id}}" {{#eq id ../metric.id}}selected{{/eq}}>{{name}}</option>
+ {{/each}}
+ </select>
+ </div>
+ {{/unless}}
+ <div class="modal-field">
+ <label for="create-custom-measure-value">Value<em class="mandatory">*</em></label>
+ <input id="create-custom-measure-value" name="value" type="text" maxlength="200" required value="{{value}}">
+ </div>
+ <div class="modal-field">
+ <label for="create-custom-measure-description">Description</label>
+ <textarea id="create-custom-measure-description" name="description">{{description}}</textarea>
+ </div>
+ </div>
+ <div class="modal-foot">
+ <button id="create-custom-measure-submit">{{#if id}}Update{{else}}Create{{/if}}</button>
+ <a href="#" class="js-modal-close" id="create-custom-measure-cancel">Cancel</a>
+ </div>
+</form>
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-header.hbs b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-header.hbs
new file mode 100644
index 00000000000..a8183635b8c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-header.hbs
@@ -0,0 +1,9 @@
+<header class="page-header">
+ <h1 class="page-title">{{t 'custom_measures.page'}}</h1>
+ <div class="page-actions">
+ <div class="button-group">
+ <button id="custom-measures-create">{{t 'create'}}</button>
+ </div>
+ </div>
+ <p class="page-description">{{t 'custom_measures.page.description'}}</p>
+</header>
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-layout.hbs b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-layout.hbs
new file mode 100644
index 00000000000..a7bd51772ff
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-layout.hbs
@@ -0,0 +1,5 @@
+<div class="page">
+ <div id="custom-measures-header"></div>
+ <div id="custom-measures-list"></div>
+ <div id="custom-measures-list-footer"></div>
+</div>
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list-footer.hbs b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list-footer.hbs
new file mode 100644
index 00000000000..3a91e24482c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list-footer.hbs
@@ -0,0 +1,6 @@
+<footer class="spacer-top note text-center">
+ {{count}}/{{total}} shown
+ {{#if more}}
+ <a id="custom-measures-fetch-more" class="spacer-left" href="#">show more</a>
+ {{/if}}
+</footer>
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list-item.hbs b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list-item.hbs
new file mode 100644
index 00000000000..d8aeedf60d3
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list-item.hbs
@@ -0,0 +1,34 @@
+<div class="pull-right big-spacer-left nowrap">
+ <a class="js-custom-measure-update icon-edit" title="Update" data-toggle="tooltip" href="#"></a>
+ <a class="js-custom-measure-delete icon-delete" title="Delete" data-toggle="tooltip" href="#"></a>
+</div>
+
+<div class="display-inline-block text-middle text-right width-10 big-spacer-right text-ellipsis"
+ title="{{formatMeasure value metric.type}}">
+ <span class="js-custom-measure-value emphasised-measure">
+ {{formatMeasure value metric.type}}
+ </span>
+</div>
+
+<div class="display-inline-block text-middle width-20">
+ <div>
+ <strong class="js-custom-measure-metric-name">
+ {{metric.name}}
+ </strong>
+ {{#if pending}}
+ <span class="js-custom-measure-pending badge badge-warning spacer-left"
+ title="{{t 'custom_measures.pending_tooltip'}}"
+ data-toggle="tooltip" data-placement="bottom">{{t 'custom_measures.pending'}}</span>
+ {{/if}}
+ </div>
+ <span class="js-custom-measure-domain note">{{metric.domain}}</span>
+</div>
+
+<div class="display-inline-block text-top width-20">
+ <span class="js-custom-measure-description">{{description}}</span>
+</div>
+
+<div class="display-inline-block text-top width-30">
+ Created on <span class="js-custom-measure-created-at">{{d createdAt}}</span>
+ by <span class="js-custom-measure-user">{{user.name}}</span>
+</div>
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/update-view.js b/server/sonar-web/src/main/js/apps/custom-measures/update-view.js
new file mode 100644
index 00000000000..301c083e5e6
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/update-view.js
@@ -0,0 +1,29 @@
+define([
+ './form-view'
+], function (FormView) {
+
+ return FormView.extend({
+
+ sendRequest: function () {
+ var that = this;
+ this.model.set({
+ value: this.$('#create-custom-measure-value').val(),
+ description: this.$('#create-custom-measure-description').val()
+ });
+ this.disableForm();
+ return this.model.save(null, {
+ statusCode: {
+ // do not show global error
+ 400: null
+ }
+ }).done(function () {
+ that.collection.refresh();
+ that.destroy();
+ }).fail(function (jqXHR) {
+ that.enableForm();
+ that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
+ });
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/nav/templates/nav-context-navbar.hbs b/server/sonar-web/src/main/js/apps/nav/templates/nav-context-navbar.hbs
index 11ef4f53ad7..189faf5bd7c 100644
--- a/server/sonar-web/src/main/js/apps/nav/templates/nav-context-navbar.hbs
+++ b/server/sonar-web/src/main/js/apps/nav/templates/nav-context-navbar.hbs
@@ -52,6 +52,9 @@
<li>
<a href="{{link '/manual_measures/index?id=' contextKeyEncoded}}">{{t 'manual_measures.page'}}</a>
</li>
+ <li>
+ <a href="{{link '/custom_measures/index?id=' contextKeyEncoded}}">Custom Measures <span class="badge big-spacer-left">New</span></a>
+ </li>
{{/if}}
{{#if component.configuration.showActionPlans}}
<li>
diff --git a/server/sonar-web/src/main/js/libs/application.js b/server/sonar-web/src/main/js/libs/application.js
index fa006f7bacc..e26addbfd1b 100644
--- a/server/sonar-web/src/main/js/libs/application.js
+++ b/server/sonar-web/src/main/js/libs/application.js
@@ -495,6 +495,7 @@ function closeModalWindow () {
* @param {number} value
*/
var ratingFormatter = function (value) {
+ value = parseInt(value, 10)
return String.fromCharCode(97 + value - 1).toUpperCase();
};
diff --git a/server/sonar-web/src/main/less/init/type.less b/server/sonar-web/src/main/less/init/type.less
index 490dd34e895..dd16c4d3f83 100644
--- a/server/sonar-web/src/main/less/init/type.less
+++ b/server/sonar-web/src/main/less/init/type.less
@@ -65,6 +65,11 @@ sub { vertical-align: text-bottom; }
em { font-style: italic; }
strong { font-weight: 500; }
+.emphasised-measure {
+ font-size: 24px;
+ font-weight: 300;
+}
+
// Quotes
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/custom_measures_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/custom_measures_controller.rb
new file mode 100644
index 00000000000..d811f1ddc56
--- /dev/null
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/custom_measures_controller.rb
@@ -0,0 +1,29 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2014 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# SonarQube is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 3 of the License, or (at your option) any later version.
+#
+# SonarQube is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+class CustomMeasuresController < ApplicationController
+
+ SECTION=Navigation::SECTION_RESOURCE
+ before_filter :init_resource_for_admin_role
+
+ def index
+
+ end
+
+end
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/custom_measures/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/custom_measures/index.html.erb
new file mode 100644
index 00000000000..54ba4fcdac4
--- /dev/null
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/custom_measures/index.html.erb
@@ -0,0 +1,7 @@
+<% content_for :extra_script do %>
+ <script>
+ require(['apps/custom-measures/app'], function (App) {
+ App.start({ el: '#content', projectId: '<%= @resource.uuid -%>' });
+ });
+ </script>
+<% end %>
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 4a98a79471e..fd9b4b79784 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -466,6 +466,8 @@ manual_metrics.delete_manual_metric=Delete Manual Metric
manual_metrics.delete_manual_metric_message=Are you sure that you want to delete manual metric "{0}"? \n Warning: all the associated manual measures will be deleted.
manual_measures.page=Manual Measures
manual_measures.page.description=Update the values of manual metrics for this project. Changes will take effect at the project's next analysis. Manual Metrics must be created at the global level.
+custom_measures.page=Custom Measures
+custom_measures.page.description=Update the values of custom metrics for this project. Changes will take effect at the project's next analysis. Custom metrics must be created at the global level.
manual_rules.page=Manual Rules
manual_rules.page.description=These rules are available for all projects. Manual issues can be created at project level via the component code viewer.
manual_rules.delete_manual_rule=Delete Manual Rule
@@ -1580,6 +1582,18 @@ manual_measures.pending_message=Pending measures are marked with orange box. The
manual_measures.no_more_available_metric=All available manual metrics have a measure.
manual_measures.to_define_new_manual_metric_il_require=You can define new manual metrics if required.
+
+#------------------------------------------------------------------------------
+#
+# CUSTOM MEASURES
+#
+#------------------------------------------------------------------------------
+
+custom_measures.pending=Pending
+custom_measures.pending_tooltip=The value will be integrated to project during next analysis.
+
+
+
#------------------------------------------------------------------------------
#
# MANUAL MEASURES