aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-web/src/main/hbs/quality-profiles/quality-profile-changelog.hbs48
-rw-r--r--server/sonar-web/src/main/hbs/quality-profiles/quality-profiles-profile-details.hbs7
-rw-r--r--server/sonar-web/src/main/js/common/handlebars-extensions.js9
-rw-r--r--server/sonar-web/src/main/js/quality-profiles/helpers.js14
-rw-r--r--server/sonar-web/src/main/js/quality-profiles/profile-changelog-view.js43
-rw-r--r--server/sonar-web/src/main/js/quality-profiles/profile-details-view.js70
-rw-r--r--server/sonar-web/src/main/js/quality-profiles/profile.js20
-rw-r--r--server/sonar-web/src/test/js/quality-profiles.js72
-rw-r--r--server/sonar-web/src/test/json/quality-profiles/changelog.json28
-rw-r--r--server/sonar-web/src/test/json/quality-profiles/changelog2.json13
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties12
11 files changed, 294 insertions, 42 deletions
diff --git a/server/sonar-web/src/main/hbs/quality-profiles/quality-profile-changelog.hbs b/server/sonar-web/src/main/hbs/quality-profiles/quality-profile-changelog.hbs
new file mode 100644
index 00000000000..d9b184cdb6d
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/quality-profiles/quality-profile-changelog.hbs
@@ -0,0 +1,48 @@
+<header class="page-header">
+ <div class="page-title">
+ <span class="h3">{{t 'changelog'}}</span>{{#unless totalEvents}}&nbsp;&nbsp;<a class="js-show-changelog small" href="#">{{t 'show_verb'}}</a>{{/unless}}
+ </div>
+</header>
+
+{{#notEmpty events}}
+ <table class="width-100 data zebra">
+ <thead>
+ <tr>
+ <th>{{t 'date'}}</th>
+ <th>{{t 'user'}}</th>
+ <th>{{t 'action'}}</th>
+ <th>{{t 'rule'}}</th>
+ <th>{{t 'parameters'}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#each events}}
+ <tr>
+ <td class="text-top nowrap thin">{{dt date}}</td>
+ <td class="text-top nowrap thin">{{default authorName 'System'}}</td>
+ <td class="text-top nowrap">{{t 'quality_profiles.changelog' action}}</td>
+ <td class="text-top"><a href="{{rulePermalink ruleKey}}">{{ruleName}}</a></td>
+ <td class="text-top thin">
+ <ul>
+ {{#each params}}
+ <li>
+ {{#eq @key 'severity'}}
+ <span class="nowrap">{{severityChangelog this}}</span>
+ {{else}}
+ {{parameterChangelog @key this}}
+ {{/eq}}
+ </li>
+ {{/each}}
+ </ul>
+ </td>
+ </tr>
+ {{/each}}
+ </tbody>
+ </table>
+
+ {{#unlessLength events totalEvents}}
+ <p class="spacer-top text-center">
+ <a class="js-show-more-changelog" href="#">{{t 'show_more'}}</a>
+ </p>
+ {{/unlessLength}}
+{{/notEmpty}}
diff --git a/server/sonar-web/src/main/hbs/quality-profiles/quality-profiles-profile-details.hbs b/server/sonar-web/src/main/hbs/quality-profiles/quality-profiles-profile-details.hbs
index 2dbd17a71ca..502448d9f7a 100644
--- a/server/sonar-web/src/main/hbs/quality-profiles/quality-profiles-profile-details.hbs
+++ b/server/sonar-web/src/main/hbs/quality-profiles/quality-profiles-profile-details.hbs
@@ -108,9 +108,4 @@
</div>
</div>
-<div class="panel panel-vertical" id="quality-profile-changelog">
- <header class="page-header">
- <h3 class="page-title">{{t 'changelog'}}</h3>
- </header>
- <p class="alert alert-warning">Coming soon...</p>
-</div>
+<div class="panel panel-vertical" id="quality-profile-changelog"></div>
diff --git a/server/sonar-web/src/main/js/common/handlebars-extensions.js b/server/sonar-web/src/main/js/common/handlebars-extensions.js
index c449d0278cc..42d3b0085d8 100644
--- a/server/sonar-web/src/main/js/common/handlebars-extensions.js
+++ b/server/sonar-web/src/main/js/common/handlebars-extensions.js
@@ -30,6 +30,10 @@
return baseUrl + url;
});
+ Handlebars.registerHelper('rulePermalink', function (ruleKey) {
+ return baseUrl + '/coding_rules#rule_key=' + encodeURIComponent(ruleKey);
+ });
+
Handlebars.registerHelper('isActiveLink', function () {
var args = Array.prototype.slice.call(arguments, 0, -1),
options = arguments[arguments.length - 1],
@@ -236,6 +240,11 @@
return cond ? options.fn(this) : options.inverse(this);
});
+ Handlebars.registerHelper('unlessLength', function (array, len, options) {
+ var cond = _.isArray(array) && array.length === +len;
+ return cond ? options.inverse(this) : options.fn(this);
+ });
+
Handlebars.registerHelper('eachReverse', function (array, options) {
var ret = '';
diff --git a/server/sonar-web/src/main/js/quality-profiles/helpers.js b/server/sonar-web/src/main/js/quality-profiles/helpers.js
index e1d3cb57e84..fa3858e126c 100644
--- a/server/sonar-web/src/main/js/quality-profiles/helpers.js
+++ b/server/sonar-web/src/main/js/quality-profiles/helpers.js
@@ -23,4 +23,18 @@
return baseUrl + '/quality_profiles/show?key=' + encodeURIComponent(key);
});
+ Handlebars.registerHelper('severityChangelog', function (severity) {
+ var label = '<i class="icon-severity-' + severity.toLowerCase() + '"></i>&nbsp;' + t('severity', severity),
+ message = tp('quality_profiles.severity_set_to_x', label);
+ return new Handlebars.SafeString(message);
+ });
+
+ Handlebars.registerHelper('parameterChangelog', function (value, parameter) {
+ if (parameter) {
+ return new Handlebars.SafeString(tp('quality_profiles.parameter_set_to_x', value, parameter));
+ } else {
+ return new Handlebars.SafeString(tp('quality_profiles.changelog.parameter_reset_to_default_value_x', parameter));
+ }
+ });
+
})();
diff --git a/server/sonar-web/src/main/js/quality-profiles/profile-changelog-view.js b/server/sonar-web/src/main/js/quality-profiles/profile-changelog-view.js
new file mode 100644
index 00000000000..b87406132cd
--- /dev/null
+++ b/server/sonar-web/src/main/js/quality-profiles/profile-changelog-view.js
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+define([
+ 'templates/quality-profiles'
+], function () {
+
+ return Marionette.ItemView.extend({
+ template: Templates['quality-profile-changelog'],
+
+ events: {
+ 'click .js-show-changelog': 'onShowChangelogClick',
+ 'click .js-show-more-changelog': 'onShowMoreChangelogClick'
+ },
+
+ onShowChangelogClick: function (e) {
+ e.preventDefault();
+ this.model.fetchChangelog();
+ },
+
+ onShowMoreChangelogClick: function (e) {
+ e.preventDefault();
+ this.model.fetchChangelog({ next: true });
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/quality-profiles/profile-details-view.js b/server/sonar-web/src/main/js/quality-profiles/profile-details-view.js
index 8a3ab3fcc51..8c26922ad85 100644
--- a/server/sonar-web/src/main/js/quality-profiles/profile-details-view.js
+++ b/server/sonar-web/src/main/js/quality-profiles/profile-details-view.js
@@ -19,16 +19,21 @@
*/
define([
'quality-profiles/change-profile-parent-view',
+ 'quality-profiles/profile-changelog-view',
'common/select-list',
'quality-profiles/helpers',
'templates/quality-profiles'
-], function (ChangeProfileParentView) {
+], function (ChangeProfileParentView, ProfileChangelogView) {
var $ = jQuery;
- return Marionette.ItemView.extend({
+ return Marionette.Layout.extend({
template: Templates['quality-profiles-profile-details'],
+ regions: {
+ changelogRegion: '#quality-profile-changelog'
+ },
+
modelEvents: {
'change': 'render'
},
@@ -39,36 +44,41 @@ define([
},
onRender: function () {
- var key = this.model.get('key');
if (!this.model.get('isDefault')) {
- new SelectList({
- el: this.$('#quality-profile-projects-list'),
- width: '100%',
- readOnly: false,
- focusSearch: false,
- format: function (item) {
- return item.name;
- },
- searchUrl: baseUrl + '/api/qualityprofiles/projects?key=' + encodeURIComponent(key),
- selectUrl: baseUrl + '/api/qualityprofiles/add_project',
- deselectUrl: baseUrl + '/api/qualityprofiles/remove_project',
- extra: {
- profileKey: key
- },
- selectParameter: 'projectUuid',
- selectParameterValue: 'uuid',
- labels: {
- selected: t('quality_gates.projects.with'),
- deselected: t('quality_gates.projects.without'),
- all: t('quality_gates.projects.all'),
- noResults: t('quality_gates.projects.noResults')
- },
- tooltips: {
- select: t('quality_gates.projects.select_hint'),
- deselect: t('quality_gates.projects.deselect_hint')
- }
- });
+ this.initProjectsSelect();
}
+ this.changelogRegion.show(new ProfileChangelogView({ model: this.model }));
+ },
+
+ initProjectsSelect: function () {
+ var key = this.model.get('key');
+ new window.SelectList({
+ el: this.$('#quality-profile-projects-list'),
+ width: '100%',
+ readOnly: false,
+ focusSearch: false,
+ format: function (item) {
+ return item.name;
+ },
+ searchUrl: baseUrl + '/api/qualityprofiles/projects?key=' + encodeURIComponent(key),
+ selectUrl: baseUrl + '/api/qualityprofiles/add_project',
+ deselectUrl: baseUrl + '/api/qualityprofiles/remove_project',
+ extra: {
+ profileKey: key
+ },
+ selectParameter: 'projectUuid',
+ selectParameterValue: 'uuid',
+ labels: {
+ selected: t('quality_gates.projects.with'),
+ deselected: t('quality_gates.projects.without'),
+ all: t('quality_gates.projects.all'),
+ noResults: t('quality_gates.projects.noResults')
+ },
+ tooltips: {
+ select: t('quality_gates.projects.select_hint'),
+ deselect: t('quality_gates.projects.deselect_hint')
+ }
+ });
},
onProfileClick: function (e) {
diff --git a/server/sonar-web/src/main/js/quality-profiles/profile.js b/server/sonar-web/src/main/js/quality-profiles/profile.js
index c88f747cf97..31339ec98db 100644
--- a/server/sonar-web/src/main/js/quality-profiles/profile.js
+++ b/server/sonar-web/src/main/js/quality-profiles/profile.js
@@ -72,6 +72,26 @@ define(function () {
children: r.children
});
});
+ },
+
+ fetchChangelog: function (options) {
+ var that = this,
+ url = baseUrl + '/api/qualityprofiles/changelog',
+ opts = { ps: 100, profileKey: this.id };
+ options = _.defaults(options || {}, { next: false });
+ if (options.next) {
+ var page = this.get('eventsPage') || 0;
+ _.extend(opts, { p: page + 1 });
+ }
+ return $.get(url, opts).done(function (r) {
+ var events = options.next ? that.get('events') : [];
+ events = events.concat(r.events);
+ that.set({
+ events: events,
+ eventsPage: r.p,
+ totalEvents: r.total
+ });
+ });
}
});
diff --git a/server/sonar-web/src/test/js/quality-profiles.js b/server/sonar-web/src/test/js/quality-profiles.js
index e8868fd340e..40d48e3a9ea 100644
--- a/server/sonar-web/src/test/js/quality-profiles.js
+++ b/server/sonar-web/src/test/js/quality-profiles.js
@@ -644,3 +644,75 @@ casper.test.begin(testName('Permalink'), 9, function (test) {
test.done();
});
});
+
+
+casper.test.begin(testName('Changelog'), 22, function (test) {
+ casper
+ .start(lib.buildUrl('profiles#show?key=java-sonar-way-67887'), function () {
+ lib.setDefaultViewport();
+
+ lib.mockRequestFromFile('/api/qualityprofiles/search', 'search.json');
+ lib.mockRequestFromFile('/api/rules/search', 'rules.json');
+ lib.mockRequestFromFile('/api/qualityprofiles/inheritance', 'inheritance.json');
+ lib.mockRequestFromFile('/api/qualityprofiles/changelog', 'changelog2.json', { data: { p: '2' } });
+ lib.mockRequestFromFile('/api/qualityprofiles/changelog', 'changelog.json');
+ })
+
+ .then(function () {
+ casper.evaluate(function () {
+ require(['/js/quality-profiles/app.js']);
+ });
+ })
+
+ .then(function () {
+ casper.waitForSelector('.js-show-changelog');
+ })
+
+ .then(function () {
+ test.assertDoesntExist('.js-show-more-changelog');
+
+ casper.click('.js-show-changelog');
+ casper.waitForSelector('#quality-profile-changelog table');
+ })
+
+ .then(function () {
+ test.assertDoesntExist('.js-show-changelog');
+ test.assertExists('.js-show-more-changelog');
+ test.assertElementCount('#quality-profile-changelog tbody tr', 2);
+
+ test.assertSelectorContains('#quality-profile-changelog tbody tr:nth-child(1)', 'April 13 2015 1:44 PM');
+ test.assertSelectorContains('#quality-profile-changelog tbody tr:nth-child(1)', 'System');
+ test.assertSelectorContains('#quality-profile-changelog tbody tr:nth-child(1)', 'ACTIVATED');
+ test.assertSelectorContains('#quality-profile-changelog tbody tr:nth-child(1)', 'Synchronisation should not');
+ test.assertSelectorContains('#quality-profile-changelog tbody tr:nth-child(1)', 'BLOCKER');
+
+ test.assertSelectorContains('#quality-profile-changelog tbody tr:nth-child(2)', 'April 13 2015 1:44 PM');
+ test.assertSelectorContains('#quality-profile-changelog tbody tr:nth-child(2)', 'Anakin Skywalker');
+ test.assertSelectorContains('#quality-profile-changelog tbody tr:nth-child(2)', 'ACTIVATED');
+ test.assertSelectorContains('#quality-profile-changelog tbody tr:nth-child(2)', 'Double.longBitsToDouble');
+ test.assertSelectorContains('#quality-profile-changelog tbody tr:nth-child(2)', 'threshold');
+ test.assertSelectorContains('#quality-profile-changelog tbody tr:nth-child(2)', '3');
+ test.assertSelectorContains('#quality-profile-changelog tbody tr:nth-child(2)', 'emptyParameter');
+
+ casper.click('.js-show-more-changelog');
+ lib.waitForElementCount('#quality-profile-changelog tbody tr', 3);
+ })
+
+ .then(function () {
+ test.assertDoesntExist('.js-show-changelog');
+ test.assertDoesntExist('.js-show-more-changelog');
+
+ test.assertSelectorContains('#quality-profile-changelog tbody tr:nth-child(3)', 'April 13 2015 1:44 PM');
+ test.assertSelectorContains('#quality-profile-changelog tbody tr:nth-child(3)', 'System');
+ test.assertSelectorContains('#quality-profile-changelog tbody tr:nth-child(3)', 'DEACTIVATED');
+ test.assertSelectorContains('#quality-profile-changelog tbody tr:nth-child(3)', 'runFinalizersOnExit');
+ })
+
+ .then(function () {
+ lib.sendCoverage();
+ })
+
+ .run(function () {
+ test.done();
+ });
+});
diff --git a/server/sonar-web/src/test/json/quality-profiles/changelog.json b/server/sonar-web/src/test/json/quality-profiles/changelog.json
new file mode 100644
index 00000000000..196f81cdb36
--- /dev/null
+++ b/server/sonar-web/src/test/json/quality-profiles/changelog.json
@@ -0,0 +1,28 @@
+{
+ "total": 3,
+ "p": 1,
+ "ps": 2,
+ "events": [
+ {
+ "date": "2015-04-13T13:44:23+0200",
+ "action": "ACTIVATED",
+ "ruleKey": "squid:S1860",
+ "ruleName": "Synchronisation should not be based on Strings or boxed primitives",
+ "params": {
+ "severity": "BLOCKER"
+ }
+ },
+ {
+ "date": "2015-04-13T13:44:23+0200",
+ "action": "ACTIVATED",
+ "authorLogin" : "anakin.skywalker",
+ "authorName" : "Anakin Skywalker",
+ "ruleKey": "squid:S2127",
+ "ruleName": "\"Double.longBitsToDouble\" should not be used for \"int\"",
+ "params": {
+ "threshold": "3",
+ "emptyParameter": ""
+ }
+ }
+ ]
+}
diff --git a/server/sonar-web/src/test/json/quality-profiles/changelog2.json b/server/sonar-web/src/test/json/quality-profiles/changelog2.json
new file mode 100644
index 00000000000..d64d6aeaf30
--- /dev/null
+++ b/server/sonar-web/src/test/json/quality-profiles/changelog2.json
@@ -0,0 +1,13 @@
+{
+ "total": 3,
+ "p": 2,
+ "ps": 2,
+ "events": [
+ {
+ "date": "2015-04-13T13:44:23+0200",
+ "action": "DEACTIVATED",
+ "ruleKey": "squid:S2151",
+ "ruleName": "\"runFinalizersOnExit\" should not be called"
+ }
+ ]
+}
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 bd28ae542db..b1eb6e9d794 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -1749,7 +1749,7 @@ quality_profiles.no_version=no version
quality_profiles.last_version_x_with_date=last version {0} ({1})
quality_profiles.version_x_with_date=version {0} ({1})
quality_profiles.version_x=version {0}
-quality_profiles.parameter_set_to_x=Parameter <b>{0}</b> set to <b>{1}</b>
+quality_profiles.parameter_set_to_x=Parameter <strong>{0}</strong> set to <strong>{1}</strong>
quality_profiles.only_in_profile_x=Only in {0}
quality_profiles.with_different_configuration=With different configuration
quality_profiles.with_same_configuration=With same configuration
@@ -1778,13 +1778,13 @@ quality_profiles.manage_rules_tooltip=Manage rules of this profile
quality_profiles.manage_rules_tooltip_x_profile=Manage rules of profile '{0}'
quality_profiles.see_rules_tooltip=See rules of this profile
quality_profiles.see_rules_tooltip_x_profile=See rules of profile '{0}'
-quality_profiles.severity_set_to_x=Severity set to {0}<b>{1}</b>
+quality_profiles.severity_set_to_x=Severity set to <strong>{0}</strong>
quality_profiles.changelog_from=Changelog from
quality_profiles.changelog.empty=No changes have been done.
-quality_profiles.changelog.activated=Activated
-quality_profiles.changelog.deactivated=Deactivated
-quality_profiles.changelog.updated=Updated
-quality_profiles.changelog.parameter_reset_to_default_value_x=Parameter <b>{0}</b> reset to default value
+quality_profiles.changelog.ACTIVATED=Activated
+quality_profiles.changelog.DEACTIVATED=Deactivated
+quality_profiles.changelog.UPDATED=Updated
+quality_profiles.changelog.parameter_reset_to_default_value_x=Parameter <strong>{0}</strong> reset to default value
quality_profiles.deleted_profile=The profile {0} doesn't exists anymore
quality_profiles.projects_for_default=Every project not specifically associated to a quality profile will be associated to this one by default.
quality_profiles.projects_for_default.edit=You must not select specific projects for the default quality profile.