return this;
}
+ public QProfileTester activateRule(QualityProfile profile, String ruleKey, String severity) {
+ return activateRule(profile.getKey(), ruleKey, severity);
+ }
+
+ public QProfileTester activateRule(String profileKey, String ruleKey, String severity) {
+ ActivateRuleRequest request = new ActivateRuleRequest()
+ .setKey(profileKey)
+ .setRule(ruleKey)
+ .setSeverity(severity);
+ service().activateRule(request);
+ return this;
+ }
+
public QProfileTester deactivateRule(QualityProfile profile, String ruleKey) {
service().deactivateRule(new DeactivateRuleRequest().setKey(profile.getKey()).setRule(ruleKey));
return this;
public RulesPage showMissingSonarWayRules() {
Selenide.$(".quality-profile-rules-sonarway-missing")
.shouldBe(Condition.visible).$("a").click();
- Selenide.$(".coding-rules").shouldBe(Condition.visible);
return Selenide.page(RulesPage.class);
}
*/
package org.sonarqube.qa.util.pageobjects;
-import com.codeborne.selenide.Selenide;
+import com.codeborne.selenide.Condition;
+import com.codeborne.selenide.SelenideElement;
+import java.util.Locale;
import static com.codeborne.selenide.Condition.text;
+import static com.codeborne.selenide.Condition.visible;
+import static com.codeborne.selenide.Selenide.$;
public class RuleDetails {
+ RuleDetails() {
+ $(".coding-rule-details").shouldBe(visible);
+ }
+
+ public RuleDetails shouldHaveType(String type) {
+ $(".coding-rules-detail-property[data-meta=\"type\"]").shouldHave(text(type));
+ return this;
+ }
+
+ public RuleDetails shouldHaveSeverity(String severity) {
+ $(".coding-rules-detail-property[data-meta=\"severity\"]").shouldHave(text(severity));
+ return this;
+ }
+
+ public RuleDetails shouldHaveDescription(String description) {
+ $(".js-rule-description").shouldHave(text(description));
+ return this;
+ }
+
+ public RuleDetails shouldBeActivatedOn(String profileKey) {
+ $("#coding-rules-detail-quality-profiles [data-profile=\"" + profileKey + "\"]").shouldBe(visible);
+ return this;
+ }
+
+ public RuleDetails shouldNotBeActivatedOn(String profileName) {
+ $("#coding-rules-detail-quality-profiles").shouldNotHave(text(profileName));
+ return this;
+ }
+
+ public RuleDetails shouldHaveTotalIssues(int issues) {
+ $(".js-rule-issues h3").shouldHave(text(String.valueOf(issues)));
+ return this;
+ }
+
+ public RuleDetails shouldHaveIssuesOnProject(String projectName, int issues) {
+ $(".coding-rules-most-violated-projects").shouldHave(
+ Condition.and("", text(projectName), text(String.valueOf(issues))));
+ return this;
+ }
+
+ public RuleDetails shouldHaveCustomRule(String ruleKey) {
+ takeCustomRule(ruleKey).shouldBe(visible);
+ return this;
+ }
+
+ public RuleDetails shouldNotHaveCustomRule(String ruleKey) {
+ takeCustomRule(ruleKey).shouldNotBe(visible);
+ return this;
+ }
+
+ public RuleDetails createCustomRule(String ruleName) {
+ $(".js-create-custom-rule").click();
+ modal().shouldBe(visible);
+
+ $("#coding-rules-custom-rule-creation-name").val(ruleName);
+ $("#coding-rules-custom-rule-creation-html-description").val("description");
+ $("#coding-rules-custom-rule-creation-create").click();
+
+ modal().shouldNotBe(visible);
+ return this;
+ }
+
+ public RuleDetails reactivateCustomRule(String ruleName) {
+ $(".js-create-custom-rule").click();
+ modal().shouldBe(visible);
+
+ $("#coding-rules-custom-rule-creation-name").val(ruleName);
+ $("#coding-rules-custom-rule-creation-html-description").val("description");
+ $("#coding-rules-custom-rule-creation-create").click();
+
+ modal().find(".alert-warning").shouldBe(visible);
+ $("#coding-rules-custom-rule-creation-reactivate").click();
+
+ modal().shouldNotBe(visible);
+ return this;
+ }
+
+ public RuleDetails deleteCustomRule(String ruleKey) {
+ takeCustomRule(ruleKey).$(".js-delete-custom-rule").click();
+ modal().shouldBe(visible);
+ modal().find("button").click();
+ modal().shouldNotBe(visible);
+ return this;
+ }
+
+ public RuleActivation activate() {
+ $("#coding-rules-quality-profile-activate").click();
+ modal().shouldBe(visible);
+ return new RuleActivation();
+ }
+
+ private static SelenideElement modal() {
+ return $(".modal");
+ }
+
+ private static SelenideElement takeCustomRule(String ruleKey) {
+ return $("#coding-rules-detail-custom-rules tr[data-rule=\"" + ruleKey + "\"]");
+ }
+
+ private static SelenideElement getActiveProfileElement(String profileKey) {
+ return $("#coding-rules-detail-quality-profiles [data-profile=\"" + profileKey + "\"]");
+ }
+
+ public ExtendedDescription extendDescription() {
+ return new ExtendedDescription().start();
+ }
+
+ public Tags tags() {
+ return new Tags();
+ }
+
+ public RuleActivation changeActivationOn(String profileKey) {
+ getActiveProfileElement(profileKey).$(".coding-rules-detail-quality-profile-change").click();
+ modal().shouldBe(visible);
+ return new RuleActivation();
+ }
+
+ public RuleDetails activationShouldHaveParameter(String profileKey, String parameter, String value) {
+ getActiveProfileElement(profileKey).$$(".coding-rules-detail-quality-profile-parameter")
+ .findBy(Condition.and("", text(parameter), text(value)))
+ .shouldBe(visible);
+ return this;
+ }
+
+ public RuleDetails activationShouldHaveSeverity(String profileKey, String severity) {
+ getActiveProfileElement(profileKey).$(".coding-rules-detail-quality-profile-severity .icon-severity-" + severity.toLowerCase(Locale.ENGLISH)).shouldBe(visible);
+ return this;
+ }
- public RuleDetails shouldBeActivatedOn(String profileName) {
- Selenide.$("#coding-rules-detail-quality-profiles").shouldHave(text(profileName));
+ public RuleDetails revertActivationToParentDefinition(String profileKey) {
+ getActiveProfileElement(profileKey).$(".coding-rules-detail-quality-profile-revert").click();
+ modal().shouldBe(visible);
+ $(".modal button").click();
+ modal().shouldNotBe(visible);
return this;
}
+ public static class ExtendedDescription {
+ public ExtendedDescription start() {
+ $("#coding-rules-detail-extend-description").click();
+ textArea().shouldBe(visible);
+ return this;
+ }
+
+ public ExtendedDescription cancel() {
+ $("#coding-rules-detail-extend-description-cancel").click();
+ textArea().shouldNotBe(visible);
+ return this;
+ }
+
+ public ExtendedDescription type(String text) {
+ textArea().val(text);
+ return this;
+ }
+
+ public ExtendedDescription submit() {
+ $("#coding-rules-detail-extend-description-submit").click();
+ textArea().shouldNotBe(visible);
+ return this;
+ }
+
+ public ExtendedDescription remove() {
+ $("#coding-rules-detail-extend-description-remove").click();
+ modal().shouldBe(visible);
+ $("#coding-rules-detail-extend-description-remove-submit").click();
+ modal().shouldNotBe(visible);
+ textArea().shouldNotBe(visible);
+ return this;
+ }
+
+ private static SelenideElement textArea() {
+ return $("#coding-rules-detail-extend-description-text");
+ }
+ }
+
+ public static class Tags {
+ public Tags shouldHaveNoTags() {
+ element().shouldHave(text("No tags"));
+ return this;
+ }
+
+ public Tags shouldHaveTags(String... tags) {
+ for (String tag : tags) {
+ element().shouldHave(text(tag));
+ }
+ return this;
+ }
+
+ public Tags edit() {
+ element().$("button").click();
+ return this;
+ }
+
+ public Tags select(String tag) {
+ element().$$(".menu a").findBy(text(tag)).click();
+ return this;
+ }
+
+ public Tags search(String query) {
+ element().$(".search-box-input").val(query);
+ return this;
+ }
+
+ public Tags done() {
+ element().$(".search-box-input").pressEscape();
+ return this;
+ }
+
+ private static SelenideElement element() {
+ return $(".coding-rules-detail-property[data-meta=\"tags\"]");
+ }
+ }
+
+ public static class RuleActivation {
+ public RuleActivation select(String profileKey) {
+ $(".modal .js-profile .Select-input input").val(profileKey).pressEnter();
+ return this;
+ }
+
+ public RuleActivation fill(String parameter, String value) {
+ $(".modal-field input[name=\"" + parameter + "\"]").val(value);
+ return this;
+ }
+
+ public RuleActivation save() {
+ $(".modal button").click();
+ modal().shouldNotBe(visible);
+ return this;
+ }
+ }
}
import com.codeborne.selenide.SelenideElement;
+import static com.codeborne.selenide.Condition.visible;
+
public class RuleItem {
private final SelenideElement elt;
- public RuleItem(SelenideElement elt) {
+ RuleItem(SelenideElement elt) {
this.elt = elt;
}
- public SelenideElement getTitle() {
- return elt.$(".coding-rule-title");
+ public RuleItem filterSimilarRules(String field) {
+ elt.$(".js-rule-filter").click();
+ elt.$(".dropdown-menu a[data-field=\"" + field + "\"]").click();
+ return this;
}
- public SelenideElement getMetadata() {
- return elt.$(".coding-rule-meta");
+ public RuleDetails open() {
+ elt.$(".coding-rule-title a").click();
+ return new RuleDetails();
}
+ public RuleItem shouldDisplayDeactivate() {
+ elt.$(".coding-rules-detail-quality-profile-deactivate").shouldBe(visible);
+ return this;
+ }
}
import com.codeborne.selenide.Condition;
import com.codeborne.selenide.ElementsCollection;
-import com.codeborne.selenide.Selenide;
import com.codeborne.selenide.SelenideElement;
-import org.openqa.selenium.By;
+
+import static com.codeborne.selenide.Condition.exist;
+import static com.codeborne.selenide.Condition.visible;
+import static com.codeborne.selenide.Selenide.$;
+import static com.codeborne.selenide.Selenide.$$;
public class RulesPage extends Navigation {
public RulesPage() {
- Selenide.$(By.cssSelector(".coding-rules")).should(Condition.exist);
+ $("#coding-rules-page").should(exist);
}
public int getTotal() {
// warning - number is localized
- return Integer.parseInt(Selenide.$("#coding-rules-total").text());
+ return Integer.parseInt($("#coding-rules-total").text());
}
public ElementsCollection getSelectedFacetItems(String facetName) {
- SelenideElement facet = Selenide.$(".search-navigator-facet-box[data-property='"+ facetName+"']").shouldBe(Condition.visible);
- return facet.$$(".js-facet.active");
+ return getFacetElement(facetName).$$(".facet.active");
}
public RulesPage shouldHaveTotalRules(Integer total) {
- Selenide.$("#coding-rules-total").shouldHave(Condition.text(total.toString()));
+ $(".js-page-counter-total").shouldHave(Condition.text(total.toString()));
+ return this;
+ }
+
+ public RulesPage shouldDisplayRules(String... ruleKeys) {
+ for (String key : ruleKeys) {
+ getRuleElement(key).shouldBe(visible);
+ }
+ return this;
+ }
+
+ public RulesPage shouldNotDisplayRules(String... ruleKeys) {
+ for (String key : ruleKeys) {
+ getRuleElement(key).shouldNotBe(visible);
+ }
return this;
}
public RulesPage openFacet(String facet) {
- Selenide.$(".search-navigator-facet-box[data-property=\"" + facet + "\"] .js-facet-toggle").click();
+ getFacetElement(facet).$(".search-navigator-facet-header a").click();
+ return this;
+ }
+
+ public RulesPage selectFacetItem(String facet, String value) {
+ getFacetElement(facet).$(".facet[data-facet=\"" + value + "\"]").click();
return this;
}
- public RulesPage selectFacetItemByText(String facet, String itemText) {
- Selenide.$$(".search-navigator-facet-box[data-property=\"" + facet + "\"] .js-facet")
- .findBy(Condition.text(itemText)).click();
+ public RulesPage selectInactive() {
+ getFacetElement("profile").$(".active .js-inactive").click();
+ return this;
+ }
+
+ public RulesPage shouldHaveDisabledFacet(String facet) {
+ $(".search-navigator-facet-box-forbidden[data-property=\"" + facet + "\"]").shouldBe(visible);
+ return this;
+ }
+
+ public RulesPage shouldNotHaveDisabledFacet(String facet) {
+ $(".search-navigator-facet-box-forbidden[data-property=\"" + facet + "\"]").shouldNotBe(visible);
return this;
}
public RuleDetails openFirstRule() {
- Selenide.$$(".js-rule").first().click();
- Selenide.$(".coding-rules-details").shouldBe(Condition.visible);
+ $$(".coding-rule-title a").first().click();
return new RuleDetails();
}
+ public RuleItem takeRule(String ruleKey) {
+ return new RuleItem(getRuleElement(ruleKey));
+ }
+
+ public RulesPage search(String query) {
+ $("#coding-rules-search .search-box-input").val(query);
+ return this;
+ }
+
+ public RulesPage clearAllFilters() {
+ $("#coding-rules-clear-all-filters").click();
+ return this;
+ }
+
+ public RulesPage closeDetails() {
+ $(".js-back").click();
+ $(".coding-rule-details").shouldNotBe(visible);
+ return this;
+ }
+
+ public RulesPage activateRule(String ruleKey) {
+ getRuleElement(ruleKey).$(".coding-rules-detail-quality-profile-activate").click();
+ $(".modal").shouldBe(visible);
+ $(".modal button").click();
+ $(".modal").shouldNotBe(visible);
+ getRuleElement(ruleKey).$(".coding-rules-detail-quality-profile-activate").shouldNotBe(visible);
+ return this;
+ }
+
+ public RulesPage deactivateRule(String ruleKey) {
+ getRuleElement(ruleKey).$(".coding-rules-detail-quality-profile-deactivate").click();
+ $(".modal button").click();
+ getRuleElement(ruleKey).$(".coding-rules-detail-quality-profile-deactivate").shouldNotBe(visible);
+ return this;
+ }
+
+ private static SelenideElement getRuleElement(String key) {
+ return $(".coding-rule[data-rule=\"" + key + "\"]");
+ }
+
+ private static SelenideElement getFacetElement(String facet) {
+ return $(".search-navigator-facet-box[data-property=\"" + facet + "\"]");
+ }
+
}
"@types/escape-html": "0.0.20",
"@types/jest": "22.0.1",
"@types/jquery": "3.2.11",
+ "@types/keymaster": "1.6.28",
"@types/lodash": "4.14.80",
"@types/prop-types": "15.5.2",
"@types/react": "16.0.29",
}
interface IssuesResponse {
- components?: Array<{}>;
+ components?: { key: string; name: string; uuid: string }[];
debtTotal?: number;
facets: Array<{}>;
issues: RawIssue[];
total: number;
};
rules?: Array<{}>;
- users?: Array<{ login: string }>;
+ users?: { login: string }[];
}
export function searchIssues(query: RequestData): Promise<IssuesResponse> {
});
}
-export function getFacet(query: RequestData, facet: string): Promise<any> {
+export function getFacet(
+ query: RequestData,
+ facet: string
+): Promise<{ facet: { count: number; val: string }[]; response: IssuesResponse }> {
return getFacets(query, [facet]).then(r => {
return { facet: r.facets[0].values, response: r.response };
});
return getFacet(query, 'assignees').then(r => extractAssignees(r.facet, r.response));
}
+export function extractProjects(facet: { val: string }[], response: IssuesResponse) {
+ return facet.map(item => {
+ const project =
+ response.components && response.components.find(component => component.uuid === item.val);
+ return { ...item, project };
+ });
+}
+
+export function getProjects(query: RequestData) {
+ return getFacet(query, 'projectUuids').then(r => extractProjects(r.facet, r.response));
+}
+
export function getIssuesCount(query: RequestData): Promise<any> {
const data = { ...query, ps: 1, facetMode: 'effort' };
return searchIssues(data).then(r => {
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { map } from 'lodash';
+import { csvEscape } from '../helpers/csv';
import {
request,
checkStatus,
export function removeGroup(parameters: AddRemoveGroupParameters): Promise<void | Response> {
return post('/api/qualityprofiles/remove_group', parameters).catch(throwGlobalError);
}
+
+export interface BulkActivateParameters {
+ /* eslint-disable camelcase */
+ activation?: boolean;
+ active_severities?: string;
+ asc?: boolean;
+ available_since?: string;
+ compareToProfile?: string;
+ inheritance?: string;
+ is_template?: string;
+ languages?: string;
+ organization: string | undefined;
+ q?: string;
+ qprofile?: string;
+ repositories?: string;
+ rule_key?: string;
+ s?: string;
+ severities?: string;
+ statuses?: string;
+ tags?: string;
+ targetKey: string;
+ targetSeverity?: string;
+ template_key?: string;
+ types?: string;
+ /* eslint-enable camelcase */
+}
+
+export function bulkActivateRules(data: BulkActivateParameters) {
+ return postJSON('api/qualityprofiles/activate_rules', data);
+}
+
+export function bulkDeactivateRules(data: BulkActivateParameters) {
+ return postJSON('api/qualityprofiles/deactivate_rules', data);
+}
+
+export function activateRule(data: {
+ key: string;
+ organization: string | undefined;
+ params?: { [key: string]: string };
+ reset?: boolean;
+ rule: string;
+ severity?: string;
+}) {
+ const params =
+ data.params && map(data.params, (value, key) => `${key}=${csvEscape(value)}`).join(';');
+ return post('/api/qualityprofiles/activate_rule', { ...data, params }).catch(throwGlobalError);
+}
+
+export function deactivateRule(data: {
+ key: string;
+ organization: string | undefined;
+ rule: string;
+}) {
+ return post('/api/qualityprofiles/deactivate_rule', data).catch(throwGlobalError);
+}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { post, getJSON, RequestData } from '../helpers/request';
+import { post, getJSON, postJSON } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
+import { Rule, RuleDetails, RuleActivation } from '../app/types';
export interface GetRulesAppResponse {
- respositories: Array<{ key: string; language: string; name: string }>;
+ canWrite?: boolean;
+ repositories: { key: string; language: string; name: string }[];
}
-export function getRulesApp(): Promise<GetRulesAppResponse> {
- return getJSON('/api/rules/app').catch(throwGlobalError);
+export function getRulesApp(data: {
+ organization: string | undefined;
+}): Promise<GetRulesAppResponse> {
+ return getJSON('/api/rules/app', data).catch(throwGlobalError);
}
-export function searchRules(data: RequestData) {
+export interface SearchRulesResponse {
+ actives?: { [rule: string]: RuleActivation[] };
+ facets?: { property: string; values: { count: number; val: string }[] }[];
+ p: number;
+ ps: number;
+ rules: Rule[];
+ total: number;
+}
+
+export function searchRules(data: {
+ organization: string | undefined;
+ [x: string]: any;
+}): Promise<SearchRulesResponse> {
return getJSON('/api/rules/search', data).catch(throwGlobalError);
}
return facet ? facet.values : [];
}
-export interface GetRuleDetailsParameters {
+export function getRuleDetails(parameters: {
actives?: boolean;
key: string;
- organization?: string;
-}
-
-export function getRuleDetails(parameters: GetRuleDetailsParameters): Promise<any> {
+ organization: string | undefined;
+}): Promise<{ actives?: RuleActivation[]; rule: RuleDetails }> {
return getJSON('/api/rules/show', parameters).catch(throwGlobalError);
}
-export function getRuleTags(parameters: { organization?: string }): Promise<string[]> {
+export function getRuleTags(parameters: {
+ organization: string | undefined;
+ ps?: number;
+ q: string;
+}): Promise<string[]> {
return getJSON('/api/rules/tags', parameters).then(r => r.tags, throwGlobalError);
}
-export function deleteRule(parameters: { key: string }) {
+export function createRule(data: {
+ custom_key: string;
+ markdown_description: string;
+ name: string;
+ organization: string | undefined;
+ params?: string;
+ prevent_reactivation?: boolean;
+ severity?: string;
+ status?: string;
+ template_key: string;
+ type?: string;
+}): Promise<RuleDetails> {
+ return postJSON('/api/rules/create', data).then(
+ r => r.rule,
+ error => {
+ // do not show global error if the status code is 409
+ // this case should be handled inside a component
+ if (error && error.response && error.response.status === 409) {
+ return Promise.reject(error.response);
+ } else {
+ return throwGlobalError(error);
+ }
+ }
+ );
+}
+
+export function deleteRule(parameters: { key: string; organization: string | undefined }) {
return post('/api/rules/delete', parameters).catch(throwGlobalError);
}
+
+export function updateRule(data: {
+ key: string;
+ markdown_description?: string;
+ markdown_note?: string;
+ name?: string;
+ organization: string | undefined;
+ params?: string;
+ remediation_fn_base_effort?: string;
+ remediation_fn_type?: string;
+ remediation_fy_gap_multiplier?: string;
+ severity?: string;
+ status?: string;
+ tags?: string;
+}): Promise<RuleDetails> {
+ return postJSON('/api/rules/update', data).then(r => r.rule, throwGlobalError);
+}
.search-navigator-facet-box-forbidden .search-navigator-facet-header {
color: var(--secondFontColor);
- font-weight: 400;
}
.search-navigator-facet-box-forbidden .search-navigator-facet-header:hover {
}
.search-navigator-facet-list {
- padding-bottom: 10px;
+ padding-bottom: var(--gridSize);
font-size: 0;
}
.search-navigator-facet-footer {
display: block;
- padding: 6px 10px;
+ padding-bottom: var(--gridSize);
border-bottom: none;
}
}
.search-navigator-filters-header {
- float: left;
- line-height: 22px;
+ margin-bottom: 12px;
+ padding-bottom: 11px;
+ border-bottom: 1px solid var(--barBorderColor);
}
.search-navigator-filters-name {
.button:disabled:focus,
input[type='submit']:disabled:focus,
input[type='button']:disabled:focus {
- color: #bbb;
- border-color: #ddd;
- background: #ebebeb;
- cursor: not-allowed;
- box-shadow: none;
+ color: #bbb !important;
+ border-color: #ddd !important;
+ background: #ebebeb !important;
+ cursor: not-allowed !important;
+ box-shadow: none !important;
}
.button svg {
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+// Diff / Omit taken from https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-311923766
+export type Diff<T extends string, U extends string> = ({ [P in T]: P } &
+ { [P in U]: never } & { [x: string]: never })[T];
+
+export type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>;
+
export enum BranchType {
LONG = 'LONG',
SHORT = 'SHORT'
organizationsEnabled?: boolean;
qualifiers: string[];
}
+
+export interface Rule {
+ isTemplate?: boolean;
+ key: string;
+ lang: string;
+ langName: string;
+ name: string;
+ params?: RuleParameter[];
+ severity: string;
+ status: string;
+ sysTags?: string[];
+ tags?: string[];
+ type: string;
+}
+
+export interface RuleDetails extends Rule {
+ createdAt: string;
+ debtOverloaded?: boolean;
+ debtRemFnCoeff?: string;
+ debtRemFnOffset?: string;
+ debtRemFnType?: string;
+ defaultDebtRemFnOffset?: string;
+ defaultDebtRemFnType?: string;
+ defaultRemFnBaseEffort?: string;
+ defaultRemFnType?: string;
+ effortToFixDescription?: string;
+ htmlDesc?: string;
+ htmlNote?: string;
+ internalKey?: string;
+ mdDesc?: string;
+ mdNote?: string;
+ remFnBaseEffort?: string;
+ remFnOverloaded?: boolean;
+ remFnType?: string;
+ repo: string;
+ templateKey?: string;
+}
+
+export interface RuleActivation {
+ createdAt: string;
+ inherit: RuleInheritance;
+ params: { key: string; value: string }[];
+ qProfile: string;
+ severity: string;
+}
+
+export interface RuleParameter {
+ // TODO is this extra really returned?
+ extra?: string;
+ defaultValue?: string;
+ htmlDesc?: string;
+ key: string;
+ type: string;
+}
+
+export enum RuleInheritance {
+ NotInherited = 'NONE',
+ Inherited = 'INHERITED',
+ Overridden = 'OVERRIDES'
+}
export default function AboutStandards(props /*: Props */) {
const organization = props.appState.organizationsEnabled
? props.appState.defaultOrganization
- : null;
+ : undefined;
return (
<div className="boxed-group">
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import ModalFormView from '../../components/common/modal-form';
-import Template from './templates/coding-rules-bulk-change-modal.hbs';
-import { translateWithParameters } from '../../helpers/l10n';
-import { postJSON } from '../../helpers/request';
-
-export default ModalFormView.extend({
- template: Template,
-
- ui() {
- return {
- ...ModalFormView.prototype.ui.apply(this, arguments),
- codingRulesSubmitBulkChange: '#coding-rules-submit-bulk-change'
- };
- },
-
- showSuccessMessage(profile, succeeded) {
- const profileBase = this.options.app.qualityProfiles.find(p => p.key === profile);
- const message = translateWithParameters(
- 'coding_rules.bulk_change.success',
- profileBase.name,
- profileBase.language,
- succeeded
- );
- this.ui.messagesContainer.append(`<div class="alert alert-success">${message}</div>`);
- },
-
- showWarnMessage(profile, succeeded, failed) {
- const profileBase = this.options.app.qualityProfiles.find(p => p.key === profile);
- const message = translateWithParameters(
- 'coding_rules.bulk_change.warning',
- profileBase.name,
- profileBase.language,
- succeeded,
- failed
- );
- this.ui.messagesContainer.append(`<div class="alert alert-warning">${message}</div>`);
- },
-
- onRender() {
- ModalFormView.prototype.onRender.apply(this, arguments);
- this.$('#coding-rules-bulk-change-profile').select2({
- width: '250px',
- minimumResultsForSearch: 1,
- openOnEnter: false
- });
- },
-
- onFormSubmit() {
- ModalFormView.prototype.onFormSubmit.apply(this, arguments);
- const url = `/api/qualityprofiles/${this.options.action}_rules`;
- const options = { ...this.options.app.state.get('query'), wsAction: this.options.action };
- const profiles = this.$('#coding-rules-bulk-change-profile').val() || [this.options.param];
- this.ui.messagesContainer.empty();
- this.sendRequests(url, options, profiles);
- },
-
- sendRequests(url, options, profiles) {
- const that = this;
- let looper = Promise.resolve();
- this.disableForm();
- profiles.forEach(profile => {
- const opts = { ...options, profile_key: profile };
- looper = looper.then(() =>
- postJSON(url, opts).then(r => {
- if (!that.isDestroyed) {
- if (r.failed) {
- that.showWarnMessage(profile, r.succeeded, r.failed);
- } else {
- that.showSuccessMessage(profile, r.succeeded);
- }
- }
- })
- );
- });
- looper.then(
- () => {
- that.options.app.controller.fetchList();
- if (!that.isDestroyed) {
- that.$(that.ui.codingRulesSubmitBulkChange.selector).hide();
- that.enableForm();
- that.$('.modal-field').hide();
- that.$('.js-modal-close').focus();
- }
- },
- () => {}
- );
- },
-
- getAvailableQualityProfiles() {
- const queryLanguages = this.options.app.state.get('query').languages;
- const languages = queryLanguages && queryLanguages.length > 0 ? queryLanguages.split(',') : [];
- let profiles = this.options.app.qualityProfiles;
- if (languages.length > 0) {
- profiles = profiles.filter(profile => languages.indexOf(profile.language) !== -1);
- }
- return profiles
- .filter(profile => profile.actions && profile.actions.edit)
- .filter(profile => !profile.isBuiltIn);
- },
-
- serializeData() {
- const profile = this.options.app.qualityProfiles.find(p => p.key === this.options.param);
- return {
- ...ModalFormView.prototype.serializeData.apply(this, arguments),
- action: this.options.action,
- state: this.options.app.state.toJSON(),
- qualityProfile: this.options.param,
- qualityProfileName: profile != null ? profile.name : null,
- qualityProfiles: this.options.app.qualityProfiles,
- availableQualityProfiles: this.getAvailableQualityProfiles()
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import $ from 'jquery';
-import PopupView from '../../components/common/popup';
-import BulkChangeModalView from './bulk-change-modal-view';
-import Template from './templates/coding-rules-bulk-change-popup.hbs';
-
-export default PopupView.extend({
- template: Template,
-
- events: {
- 'click .js-bulk-change': 'doAction'
- },
-
- doAction(e) {
- const action = $(e.currentTarget).data('action');
- const param = $(e.currentTarget).data('param');
- new BulkChangeModalView({
- app: this.options.app,
- action,
- param
- }).render();
- },
-
- serializeData() {
- const query = this.options.app.state.get('query');
- const profileKey = query.qprofile;
- const profile = this.options.app.qualityProfiles.find(p => p.key === profileKey);
- const activation = '' + query.activation;
- const canChangeProfile =
- profile != null && !profile.isBuiltIn && profile.actions && profile.actions.edit;
-
- return {
- qualityProfile: profileKey,
- qualityProfileName: profile != null ? profile.name : null,
- allowActivateOnProfile: canChangeProfile && activation === 'false',
- allowDeactivateOnProfile: canChangeProfile && activation === 'true'
- };
- }
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import ActivationFormModal from './ActivationFormModal';
+import { Profile as BaseProfile } from '../../../api/quality-profiles';
+import { Rule, RuleDetails, RuleActivation } from '../../../app/types';
+
+interface Props {
+ activation?: RuleActivation;
+ buttonText: string;
+ className?: string;
+ modalHeader: string;
+ onDone: (severity: string) => Promise<void>;
+ organization: string | undefined;
+ profiles: BaseProfile[];
+ rule: Rule | RuleDetails;
+ updateMode?: boolean;
+}
+
+interface State {
+ modal: boolean;
+}
+
+export default class ActivationButton extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { modal: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleButtonClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.setState({ modal: true });
+ };
+
+ handleCloseModal = () => this.setState({ modal: false });
+
+ render() {
+ return (
+ <>
+ <button
+ className={this.props.className}
+ id="coding-rules-quality-profile-activate"
+ onClick={this.handleButtonClick}>
+ {this.props.buttonText}
+ </button>
+
+ {this.state.modal && (
+ <ActivationFormModal
+ activation={this.props.activation}
+ modalHeader={this.props.modalHeader}
+ onClose={this.handleCloseModal}
+ onDone={this.props.onDone}
+ organization={this.props.organization}
+ profiles={this.props.profiles}
+ rule={this.props.rule}
+ updateMode={this.props.updateMode}
+ />
+ )}
+ </>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import Modal from '../../../components/controls/Modal';
+import Select from '../../../components/controls/Select';
+import SeverityHelper from '../../../components/shared/SeverityHelper';
+import Tooltip from '../../../components/controls/Tooltip';
+import { activateRule, Profile as BaseProfile } from '../../../api/quality-profiles';
+import { Rule, RuleDetails, RuleActivation } from '../../../app/types';
+import { SEVERITIES } from '../../../helpers/constants';
+import { translate } from '../../../helpers/l10n';
+import { sortProfiles } from '../../quality-profiles/utils';
+
+interface Props {
+ activation?: RuleActivation;
+ modalHeader: string;
+ onClose: () => void;
+ onDone: (severity: string) => Promise<void>;
+ organization: string | undefined;
+ profiles: BaseProfile[];
+ rule: Rule | RuleDetails;
+ updateMode?: boolean;
+}
+
+interface State {
+ params: { [p: string]: string };
+ profile: string;
+ severity: string;
+ submitting: boolean;
+}
+
+export default class ActivationFormModal extends React.PureComponent<Props, State> {
+ mounted: boolean;
+
+ constructor(props: Props) {
+ super(props);
+ const profilesWithDepth = this.getQualityProfilesWithDepth(props);
+ this.state = {
+ params: this.getParams(props),
+ profile: profilesWithDepth.length > 0 ? profilesWithDepth[0].key : '',
+ severity: props.activation ? props.activation.severity : props.rule.severity,
+ submitting: false
+ };
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ getParams = ({ activation, rule } = this.props) => {
+ const params: { [p: string]: string } = {};
+ if (rule && rule.params) {
+ for (const param of rule.params) {
+ params[param.key] = param.defaultValue || '';
+ }
+ if (activation && activation.params) {
+ for (const param of activation.params) {
+ params[param.key] = param.value;
+ }
+ }
+ }
+ return params;
+ };
+
+ // Choose QP which a user can administrate, which are the same language and which are not built-in
+ getQualityProfilesWithDepth = ({ profiles } = this.props) =>
+ sortProfiles(
+ profiles.filter(
+ profile =>
+ !profile.isBuiltIn &&
+ profile.actions &&
+ profile.actions.edit &&
+ profile.language === this.props.rule.lang
+ )
+ ).map(profile => ({
+ ...profile,
+ // Decrease depth by 1, so the top level starts at 0
+ depth: profile.depth - 1
+ }));
+
+ handleCancelClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.props.onClose();
+ };
+
+ handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+ event.preventDefault();
+ this.setState({ submitting: true });
+ const data = {
+ key: this.state.profile,
+ organization: this.props.organization,
+ params: this.state.params,
+ rule: this.props.rule.key,
+ severity: this.state.severity
+ };
+ activateRule(data)
+ .then(() => this.props.onDone(data.severity))
+ .then(
+ () => {
+ if (this.mounted) {
+ this.setState({ submitting: false });
+ this.props.onClose();
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ submitting: false });
+ }
+ }
+ );
+ };
+
+ handleParameterChange = (event: React.SyntheticEvent<HTMLInputElement | HTMLTextAreaElement>) => {
+ const { name, value } = event.currentTarget;
+ this.setState((state: State) => ({ params: { ...state.params, [name]: value } }));
+ };
+
+ handleProfileChange = ({ value }: { value: string }) => this.setState({ profile: value });
+
+ handleSeverityChange = ({ value }: { value: string }) => this.setState({ severity: value });
+
+ renderSeverityOption = ({ value }: { value: string }) => <SeverityHelper severity={value} />;
+
+ render() {
+ const { activation, rule } = this.props;
+ const { profile, severity, submitting } = this.state;
+ const { params = [] } = rule;
+ const profilesWithDepth = this.getQualityProfilesWithDepth();
+ const isCustomRule = !!(rule as RuleDetails).templateKey;
+ const activeInAllProfiles = profilesWithDepth.length <= 0;
+ const isUpdateMode = !!activation;
+
+ return (
+ <Modal contentLabel={this.props.modalHeader} onRequestClose={this.props.onClose}>
+ <form onSubmit={this.handleFormSubmit}>
+ <div className="modal-head">
+ <h2>{this.props.modalHeader}</h2>
+ </div>
+
+ <div className="modal-body">
+ {!isUpdateMode &&
+ activeInAllProfiles && (
+ <div className="alert alert-info">
+ {translate('coding_rules.active_in_all_profiles')}
+ </div>
+ )}
+
+ <div className="modal-field">
+ <label>{translate('coding_rules.quality_profile')}</label>
+ <Select
+ className="js-profile"
+ clearable={false}
+ disabled={submitting || profilesWithDepth.length === 1}
+ onChange={this.handleProfileChange}
+ options={profilesWithDepth.map(profile => ({
+ label: ' '.repeat(profile.depth) + profile.name,
+ value: profile.key
+ }))}
+ value={profile}
+ />
+ </div>
+ <div className="modal-field">
+ <label>{translate('severity')}</label>
+ <Select
+ className="js-severity"
+ clearable={false}
+ disabled={submitting}
+ onChange={this.handleSeverityChange}
+ options={SEVERITIES.map(severity => ({
+ label: translate('severity', severity),
+ value: severity
+ }))}
+ optionRenderer={this.renderSeverityOption}
+ searchable={false}
+ value={severity}
+ valueRenderer={this.renderSeverityOption}
+ />
+ </div>
+ {isCustomRule ? (
+ <div className="modal-field">
+ <p className="note">{translate('coding_rules.custom_rule.activation_notice')}</p>
+ </div>
+ ) : (
+ params.map(param => (
+ <div className="modal-field" key={param.key}>
+ <Tooltip overlay={param.key} placement="left">
+ <label>{param.key}</label>
+ </Tooltip>
+ {param.type === 'TEXT' ? (
+ <textarea
+ className="width100"
+ disabled={submitting}
+ name={param.key}
+ onChange={this.handleParameterChange}
+ placeholder={param.defaultValue}
+ rows={3}
+ value={this.state.params[param.key] || ''}
+ />
+ ) : (
+ <input
+ className="input-super-large"
+ disabled={submitting}
+ name={param.key}
+ onChange={this.handleParameterChange}
+ placeholder={param.defaultValue}
+ type="text"
+ value={this.state.params[param.key] || ''}
+ />
+ )}
+ <div
+ className="note"
+ dangerouslySetInnerHTML={{ __html: param.htmlDesc || '' }}
+ />
+ {param.extra && <div className="note">{param.extra}</div>}
+ </div>
+ ))
+ )}
+ </div>
+
+ <footer className="modal-foot">
+ {submitting && <i className="spinner spacer-right" />}
+ <button disabled={submitting || activeInAllProfiles} type="submit">
+ {isUpdateMode ? translate('save') : translate('coding_rules.activate')}
+ </button>
+ <button
+ className="button-link"
+ disabled={submitting}
+ onClick={this.handleCancelClick}
+ type="reset">
+ {translate('cancel')}
+ </button>
+ </footer>
+ </form>
+ </Modal>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import Facet, { BasicProps } from './Facet';
+import SeverityHelper from '../../../components/shared/SeverityHelper';
+import { SEVERITIES } from '../../../helpers/constants';
+import { translate } from '../../../helpers/l10n';
+
+interface Props extends BasicProps {
+ disabled: boolean;
+}
+
+export default class ActivationSeverityFacet extends React.PureComponent<Props> {
+ renderName = (severity: string) => <SeverityHelper severity={severity} />;
+
+ renderTextName = (severity: string) => translate('severity', severity);
+
+ render() {
+ return (
+ <Facet
+ {...this.props}
+ disabled={this.props.disabled}
+ disabledHelper={translate('coding_rules.filters.active_severity.inactive')}
+ options={SEVERITIES}
+ property="activationSeverities"
+ renderName={this.renderName}
+ renderTextName={this.renderTextName}
+ />
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { Helmet } from 'react-helmet';
+import * as PropTypes from 'prop-types';
+import { keyBy } from 'lodash';
+import * as key from 'keymaster';
+import {
+ Facets,
+ Query,
+ parseQuery,
+ serializeQuery,
+ areQueriesEqual,
+ shouldRequestFacet,
+ FacetKey,
+ OpenFacets,
+ getServerFacet,
+ getAppFacet,
+ Actives,
+ Activation,
+ getOpen
+} from '../query';
+import { searchRules, getRulesApp } from '../../../api/rules';
+import { Paging, Rule, RuleActivation } from '../../../app/types';
+import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
+import { translate } from '../../../helpers/l10n';
+import { RawQuery } from '../../../helpers/query';
+import ListFooter from '../../../components/controls/ListFooter';
+import RuleListItem from './RuleListItem';
+import PageActions from './PageActions';
+import FiltersHeader from '../../../components/common/FiltersHeader';
+import SearchBox from '../../../components/controls/SearchBox';
+import FacetsList from './FacetsList';
+import { searchQualityProfiles, Profile } from '../../../api/quality-profiles';
+import { scrollToElement } from '../../../helpers/scrolling';
+import BulkChange from './BulkChange';
+import RuleDetails from './RuleDetails';
+
+import '../styles.css';
+
+const PAGE_SIZE = 100;
+
+interface Props {
+ location: { pathname: string; query: RawQuery };
+ organization?: { key: string };
+}
+
+interface State {
+ actives?: Actives;
+ canWrite?: boolean;
+ facets?: Facets;
+ loading: boolean;
+ openFacets: OpenFacets;
+ openRule?: Rule;
+ paging?: Paging;
+ query: Query;
+ referencedProfiles: { [profile: string]: Profile };
+ referencedRepositories: { [repository: string]: { key: string; language: string; name: string } };
+ rules: Rule[];
+ selected?: string;
+}
+
+// TODO redirect to default organization's rules page
+
+export default class App extends React.PureComponent<Props, State> {
+ mounted: boolean;
+
+ static contextTypes = {
+ organizationsEnabled: PropTypes.bool,
+ router: PropTypes.object.isRequired
+ };
+
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ loading: true,
+ openFacets: { languages: true, types: true },
+ query: parseQuery(props.location.query),
+ referencedProfiles: {},
+ referencedRepositories: {},
+ rules: []
+ };
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ document.body.classList.add('white-page');
+ const footer = document.getElementById('footer');
+ if (footer) {
+ footer.classList.add('page-footer-with-sidebar');
+ }
+ this.attachShortcuts();
+ this.fetchInitialData();
+ }
+
+ componentWillReceiveProps(nextProps: Props) {
+ const openRule = this.getOpenRule(nextProps, this.state.rules);
+ if (openRule && openRule.key !== this.state.selected) {
+ this.setState({ selected: openRule.key });
+ }
+ this.setState({ openRule, query: parseQuery(nextProps.location.query) });
+ }
+
+ componentDidUpdate(prevProps: Props, prevState: State) {
+ if (!areQueriesEqual(prevProps.location.query, this.props.location.query)) {
+ this.fetchFirstRules();
+ }
+ if (
+ !this.state.openRule &&
+ (prevState.selected !== this.state.selected || prevState.openRule)
+ ) {
+ // if user simply selected another issue
+ // or if he went from the source code back to the list of issues
+ this.scrollToSelectedRule();
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ document.body.classList.remove('white-page');
+ const footer = document.getElementById('footer');
+ if (footer) {
+ footer.classList.remove('page-footer-with-sidebar');
+ }
+ this.detachShortcuts();
+ }
+
+ attachShortcuts = () => {
+ key.setScope('coding-rules');
+ key('up', 'coding-rules', () => {
+ this.selectPreviousRule();
+ return false;
+ });
+ key('down', 'coding-rules', () => {
+ this.selectNextRule();
+ return false;
+ });
+ key('right', 'coding-rules', () => {
+ this.openSelectedRule();
+ return false;
+ });
+ key('left', 'coding-rules', () => {
+ this.closeRule();
+ return false;
+ });
+ };
+
+ detachShortcuts = () => key.deleteScope('coding-rules');
+
+ getOpenRule = (props: Props, rules: Rule[]) => {
+ const open = getOpen(props.location.query);
+ return open && rules.find(rule => rule.key === open);
+ };
+
+ getFacetsToFetch = () =>
+ Object.keys(this.state.openFacets)
+ .filter((facet: FacetKey) => this.state.openFacets[facet])
+ .filter((facet: FacetKey) => shouldRequestFacet(facet))
+ .map((facet: FacetKey) => getServerFacet(facet));
+
+ getFieldsToFetch = () => {
+ const fields = [
+ 'isTemplate',
+ 'name',
+ 'lang',
+ 'langName',
+ 'severity',
+ 'status',
+ 'sysTags',
+ 'tags',
+ 'templateKey'
+ ];
+ if (this.state.query.profile) {
+ fields.push('actives', 'params');
+ }
+ return fields;
+ };
+
+ getSearchParameters = () => ({
+ f: this.getFieldsToFetch().join(),
+ facets: this.getFacetsToFetch().join(),
+ organization: this.props.organization && this.props.organization.key,
+ ps: PAGE_SIZE,
+ s: 'name',
+ ...serializeQuery(this.state.query)
+ });
+
+ stopLoading = () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ };
+
+ fetchInitialData = () => {
+ this.setState({ loading: true });
+ const organization = this.props.organization && this.props.organization.key;
+ Promise.all([getRulesApp({ organization }), searchQualityProfiles({ organization })]).then(
+ ([{ canWrite, repositories }, { profiles }]) => {
+ this.setState({
+ canWrite,
+ referencedProfiles: keyBy(profiles, 'key'),
+ referencedRepositories: keyBy(repositories, 'key')
+ });
+ this.fetchFirstRules();
+ },
+ this.stopLoading
+ );
+ };
+
+ makeFetchRequest = (query?: RawQuery) =>
+ searchRules({ ...this.getSearchParameters(), ...query }).then(
+ ({ actives: rawActives, facets: rawFacets, p, ps, rules, total }) => {
+ const actives = rawActives && parseActives(rawActives);
+ const facets = rawFacets && parseFacets(rawFacets);
+ const paging = { pageIndex: p, pageSize: ps, total };
+ return { actives, facets, paging, rules };
+ }
+ );
+
+ fetchFirstRules = (query?: RawQuery) => {
+ this.setState({ loading: true });
+ this.makeFetchRequest(query).then(({ actives, facets, paging, rules }) => {
+ if (this.mounted) {
+ const openRule = this.getOpenRule(this.props, rules);
+ const selected = rules.length > 0 ? (openRule && openRule.key) || rules[0].key : undefined;
+ this.setState({ actives, facets, loading: false, openRule, paging, rules, selected });
+ }
+ }, this.stopLoading);
+ };
+
+ fetchMoreRules = () => {
+ const { paging } = this.state;
+ if (paging) {
+ this.setState({ loading: true });
+ const nextPage = paging.pageIndex + 1;
+ this.makeFetchRequest({ p: nextPage, facets: undefined }).then(
+ ({ actives, paging, rules }) => {
+ if (this.mounted) {
+ this.setState(state => ({
+ actives: { ...state.actives, actives },
+ loading: false,
+ paging,
+ rules: [...state.rules, ...rules]
+ }));
+ }
+ },
+ this.stopLoading
+ );
+ }
+ };
+
+ fetchFacet = (facet: FacetKey) => {
+ this.setState({ loading: true });
+ this.makeFetchRequest({ ps: 1, facets: getServerFacet(facet) }).then(({ facets }) => {
+ if (this.mounted) {
+ this.setState(state => ({ facets: { ...state.facets, ...facets }, loading: false }));
+ }
+ }, this.stopLoading);
+ };
+
+ getSelectedIndex = ({ selected, rules } = this.state) => {
+ const index = rules.findIndex(rule => rule.key === selected);
+ return index !== -1 ? index : undefined;
+ };
+
+ selectNextRule = () => {
+ const { rules } = this.state;
+ const selectedIndex = this.getSelectedIndex();
+ if (rules && selectedIndex !== undefined && selectedIndex < rules.length - 1) {
+ if (this.state.openRule) {
+ this.openRule(rules[selectedIndex + 1].key);
+ } else {
+ this.setState({ selected: rules[selectedIndex + 1].key });
+ }
+ }
+ };
+
+ selectPreviousRule = () => {
+ const { rules } = this.state;
+ const selectedIndex = this.getSelectedIndex();
+ if (rules && selectedIndex !== undefined && selectedIndex > 0) {
+ if (this.state.openRule) {
+ this.openRule(rules[selectedIndex - 1].key);
+ } else {
+ this.setState({ selected: rules[selectedIndex - 1].key });
+ }
+ }
+ };
+
+ getRulePath = (rule: string) => ({
+ pathname: this.props.location.pathname,
+ query: { ...serializeQuery(this.state.query), open: rule }
+ });
+
+ openRule = (rule: string) => {
+ const path = this.getRulePath(rule);
+ if (this.state.openRule) {
+ this.context.router.replace(path);
+ } else {
+ this.context.router.push(path);
+ }
+ };
+
+ openSelectedRule = () => {
+ const { selected } = this.state;
+ if (selected) {
+ this.openRule(selected);
+ }
+ };
+
+ closeRule = () => {
+ this.context.router.push({
+ pathname: this.props.location.pathname,
+ query: {
+ ...serializeQuery(this.state.query),
+ open: undefined
+ }
+ });
+ this.scrollToSelectedRule(false);
+ };
+
+ scrollToSelectedRule = (smooth = true) => {
+ const { selected } = this.state;
+ if (selected) {
+ const element = document.querySelector(`[data-rule="${selected}"]`);
+ if (element) {
+ scrollToElement(element, { topOffset: 150, bottomOffset: 100, smooth });
+ }
+ }
+ };
+
+ getRuleActivation = (rule: string) => {
+ const { actives, query } = this.state;
+ if (actives && actives[rule] && query.profile) {
+ return actives[rule][query.profile];
+ } else {
+ return undefined;
+ }
+ };
+
+ getSelectedProfile = () => {
+ const { query, referencedProfiles } = this.state;
+ if (query.profile) {
+ return referencedProfiles[query.profile];
+ } else {
+ return undefined;
+ }
+ };
+
+ closeFacet = (facet: string) =>
+ this.setState(state => ({
+ openFacets: { ...state.openFacets, [facet]: false }
+ }));
+
+ handleBack = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.closeRule();
+ };
+
+ handleFilterChange = (changes: Partial<Query>) =>
+ this.context.router.push({
+ pathname: this.props.location.pathname,
+ query: serializeQuery({ ...this.state.query, ...changes })
+ });
+
+ handleFacetToggle = (facet: keyof Query) => {
+ this.setState(state => ({
+ openFacets: { ...state.openFacets, [facet]: !state.openFacets[facet] }
+ }));
+ if (shouldRequestFacet(facet) && (!this.state.facets || !this.state.facets[facet])) {
+ this.fetchFacet(facet);
+ }
+ };
+
+ handleReload = () => this.fetchFirstRules();
+
+ handleReset = () => this.context.router.push({ pathname: this.props.location.pathname });
+
+ /** Tries to take rule by index, or takes the last one */
+ pickRuleAround = (rules: Rule[], selectedIndex: number | undefined) => {
+ if (selectedIndex === undefined || rules.length === 0) {
+ return undefined;
+ }
+ if (selectedIndex >= 0 && selectedIndex < rules.length) {
+ return rules[selectedIndex].key;
+ }
+ return rules[rules.length - 1].key;
+ };
+
+ handleRuleDelete = (ruleKey: string) => {
+ if (this.state.query.ruleKey === ruleKey) {
+ this.handleReset();
+ } else {
+ this.setState(state => {
+ const rules = state.rules.filter(rule => rule.key !== ruleKey);
+ const selectedIndex = this.getSelectedIndex(state);
+ const selected = this.pickRuleAround(rules, selectedIndex);
+ return { rules, selected };
+ });
+ this.closeRule();
+ }
+ };
+
+ handleRuleActivate = (profile: string, rule: string, activation: Activation) =>
+ this.setState((state: State) => {
+ const { actives = {} } = state;
+ if (!actives[rule]) {
+ return { actives: { ...actives, [rule]: { [profile]: activation } } };
+ }
+
+ return { actives: { ...actives, [rule]: { ...actives[rule], [profile]: activation } } };
+ });
+
+ handleRuleDeactivate = (profile: string, rule: string) =>
+ this.setState((state: State) => {
+ const { actives } = state;
+ if (actives && actives[rule]) {
+ return { actives: { ...actives, [rule]: { ...actives[rule], [profile]: undefined } } };
+ }
+ return {};
+ });
+
+ handleSearch = (searchQuery: string) => this.handleFilterChange({ searchQuery });
+
+ isFiltered = () => Object.keys(serializeQuery(this.state.query)).length > 0;
+
+ render() {
+ const { paging, rules } = this.state;
+ const selectedIndex = this.getSelectedIndex();
+ const organization = this.props.organization && this.props.organization.key;
+
+ return (
+ <>
+ <Helmet title={translate('coding_rules.page')} />
+ <div className="layout-page" id="coding-rules-page">
+ <ScreenPositionHelper className="layout-page-side-outer">
+ {({ top }) => (
+ <div className="layout-page-side" style={{ top }}>
+ <div className="layout-page-side-inner">
+ <div className="layout-page-filters">
+ <FiltersHeader displayReset={this.isFiltered()} onReset={this.handleReset} />
+ <SearchBox
+ className="spacer-bottom"
+ id="coding-rules-search"
+ onChange={this.handleSearch}
+ placeholder={translate('search.search_for_rules')}
+ value={this.state.query.searchQuery || ''}
+ />
+ <FacetsList
+ facets={this.state.facets}
+ onFacetToggle={this.handleFacetToggle}
+ onFilterChange={this.handleFilterChange}
+ organization={organization}
+ organizationsEnabled={this.context.organizationsEnabled}
+ openFacets={this.state.openFacets}
+ query={this.state.query}
+ referencedProfiles={this.state.referencedProfiles}
+ referencedRepositories={this.state.referencedRepositories}
+ selectedProfile={this.getSelectedProfile()}
+ />
+ </div>
+ </div>
+ </div>
+ )}
+ </ScreenPositionHelper>
+
+ <div className="layout-page-main">
+ <div className="layout-page-header-panel layout-page-main-header">
+ <div className="layout-page-header-panel-inner layout-page-main-header-inner">
+ <div className="layout-page-main-inner">
+ {this.state.openRule ? (
+ <a href="#" className="js-back" onClick={this.handleBack}>
+ {translate('coding_rules.return_to_list')}
+ </a>
+ ) : (
+ this.state.paging && (
+ <BulkChange
+ organization={organization}
+ query={this.state.query}
+ referencedProfiles={this.state.referencedProfiles}
+ total={this.state.paging.total}
+ />
+ )
+ )}
+ <PageActions
+ loading={this.state.loading}
+ onReload={this.handleReload}
+ paging={paging}
+ selectedIndex={selectedIndex}
+ />
+ </div>
+ </div>
+ </div>
+
+ <div className="layout-page-main-inner">
+ {this.state.openRule ? (
+ <RuleDetails
+ allowCustomRules={!this.context.organizationsEnabled}
+ canWrite={this.state.canWrite}
+ onActivate={this.handleRuleActivate}
+ onDeactivate={this.handleRuleDeactivate}
+ onDelete={this.handleRuleDelete}
+ onFilterChange={this.handleFilterChange}
+ organization={organization}
+ referencedProfiles={this.state.referencedProfiles}
+ referencedRepositories={this.state.referencedRepositories}
+ ruleKey={this.state.openRule.key}
+ selectedProfile={this.getSelectedProfile()}
+ />
+ ) : (
+ <>
+ {rules.map(rule => (
+ <RuleListItem
+ activation={this.getRuleActivation(rule.key)}
+ key={rule.key}
+ onActivate={this.handleRuleActivate}
+ onDeactivate={this.handleRuleDeactivate}
+ onFilterChange={this.handleFilterChange}
+ organization={organization}
+ path={this.getRulePath(rule.key)}
+ rule={rule}
+ selected={rule.key === this.state.selected}
+ selectedProfile={this.getSelectedProfile()}
+ />
+ ))}
+ {paging !== undefined && (
+ <ListFooter
+ count={rules.length}
+ loadMore={this.fetchMoreRules}
+ ready={!this.state.loading}
+ total={paging.total}
+ />
+ )}
+ </>
+ )}
+ </div>
+ </div>
+ </div>
+ </>
+ );
+ }
+}
+
+function parseActives(rawActives: { [rule: string]: RuleActivation[] }) {
+ const actives: Actives = {};
+ for (const [rule, activations] of Object.entries(rawActives)) {
+ actives[rule] = {};
+ for (const { inherit, qProfile, severity } of activations) {
+ actives[rule][qProfile] = { inherit, severity };
+ }
+ }
+ return actives;
+}
+
+function parseFacets(rawFacets: { property: string; values: { count: number; val: string }[] }[]) {
+ const facets: Facets = {};
+ for (const rawFacet of rawFacets) {
+ const values: { [value: string]: number } = {};
+ for (const rawValue of rawFacet.values) {
+ values[rawValue.val] = rawValue.count;
+ }
+ facets[getAppFacet(rawFacet.property)] = values;
+ }
+ return facets;
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { intlShape } from 'react-intl';
+import { Query } from '../query';
+import DateInput from '../../../components/controls/DateInput';
+import FacetBox from '../../../components/facet/FacetBox';
+import FacetHeader from '../../../components/facet/FacetHeader';
+import { longFormatterOption } from '../../../components/intl/DateFormatter';
+import { parseDate } from '../../../helpers/dates';
+import { translate } from '../../../helpers/l10n';
+import { serializeDateShort } from '../../../helpers/query';
+
+interface Props {
+ onChange: (changes: Partial<Query>) => void;
+ onToggle: (property: keyof Query) => void;
+ open: boolean;
+ value?: Date;
+}
+
+export default class AvailableSinceFacet extends React.PureComponent<Props> {
+ static contextTypes = {
+ intl: intlShape
+ };
+
+ handleHeaderClick = () => this.props.onToggle('availableSince');
+
+ handleClear = () => this.props.onChange({ availableSince: undefined });
+
+ handlePeriodChange = (value?: string) =>
+ this.props.onChange({ availableSince: value ? parseDate(value) : undefined });
+
+ getValues = () =>
+ this.props.value
+ ? [this.context.intl.formatDate(this.props.value, longFormatterOption)]
+ : undefined;
+
+ renderDateInput = () => (
+ <DateInput
+ name="available-since"
+ onChange={this.handlePeriodChange}
+ placeholder={translate('date')}
+ value={serializeDateShort(this.props.value)}
+ />
+ );
+
+ render() {
+ return (
+ <FacetBox property="availableSince">
+ <FacetHeader
+ name={translate('coding_rules.facet.available_since')}
+ onClear={this.handleClear}
+ onClick={this.handleHeaderClick}
+ open={this.props.open}
+ values={this.getValues()}
+ />
+
+ {this.props.open && this.renderDateInput()}
+ </FacetBox>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import BulkChangeModal from './BulkChangeModal';
+import { Query } from '../query';
+import { Profile } from '../../../api/quality-profiles';
+import Dropdown from '../../../components/controls/Dropdown';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ organization: string | undefined;
+ query: Query;
+ referencedProfiles: { [profile: string]: Profile };
+ total: number;
+}
+
+interface State {
+ action?: string;
+ modal: boolean;
+ profile?: Profile;
+}
+
+export default class BulkChange extends React.PureComponent<Props, State> {
+ closeDropdown: () => void;
+ state: State = { modal: false };
+
+ getSelectedProfile = () => {
+ const { profile } = this.props.query;
+ return (profile && this.props.referencedProfiles[profile]) || undefined;
+ };
+
+ closeModal = () => this.setState({ action: undefined, modal: false, profile: undefined });
+
+ handleActivateClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.closeDropdown();
+ this.setState({ action: 'activate', modal: true, profile: undefined });
+ };
+
+ handleActivateInProfileClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.closeDropdown();
+ this.setState({ action: 'activate', modal: true, profile: this.getSelectedProfile() });
+ };
+
+ handleDeactivateClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.closeDropdown();
+ this.setState({ action: 'deactivate', modal: true, profile: undefined });
+ };
+
+ handleDeactivateInProfileClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.closeDropdown();
+ this.setState({ action: 'deactivate', modal: true, profile: this.getSelectedProfile() });
+ };
+
+ render() {
+ // show "Bulk Change" button only if user has at least one QP which he administrates
+ const canBulkChange = Object.values(this.props.referencedProfiles).some(profile =>
+ Boolean(profile.actions && profile.actions.edit)
+ );
+ if (!canBulkChange) {
+ return null;
+ }
+
+ const { activation } = this.props.query;
+ const profile = this.getSelectedProfile();
+ const canChangeProfile = Boolean(
+ profile && !profile.isBuiltIn && profile.actions && profile.actions.edit
+ );
+ const allowActivateOnProfile = canChangeProfile && activation === false;
+ const allowDeactivateOnProfile = canChangeProfile && activation === true;
+
+ return (
+ <>
+ <Dropdown>
+ {({ closeDropdown, onToggleClick, open }) => {
+ this.closeDropdown = closeDropdown;
+ return (
+ <div className={classNames('pull-left dropdown', { open })}>
+ <button className="js-bulk-change" onClick={onToggleClick}>
+ {translate('bulk_change')}
+ </button>
+ <ul className="dropdown-menu">
+ <li>
+ <a href="#" onClick={this.handleActivateClick}>
+ {translate('coding_rules.activate_in')}…
+ </a>
+ </li>
+ {allowActivateOnProfile &&
+ profile && (
+ <li>
+ <a href="#" onClick={this.handleActivateInProfileClick}>
+ {translate('coding_rules.activate_in')} <strong>{profile.name}</strong>
+ </a>
+ </li>
+ )}
+ <li>
+ <a href="#" onClick={this.handleDeactivateClick}>
+ {translate('coding_rules.deactivate_in')}…
+ </a>
+ </li>
+ {allowDeactivateOnProfile &&
+ profile && (
+ <li>
+ <a href="#" onClick={this.handleDeactivateInProfileClick}>
+ {translate('coding_rules.deactivate_in')} <strong>{profile.name}</strong>
+ </a>
+ </li>
+ )}
+ </ul>
+ </div>
+ );
+ }}
+ </Dropdown>
+ {this.state.modal &&
+ this.state.action && (
+ <BulkChangeModal
+ action={this.state.action}
+ onClose={this.closeModal}
+ organization={this.props.organization}
+ profile={this.state.profile}
+ query={this.props.query}
+ referencedProfiles={this.props.referencedProfiles}
+ total={this.props.total}
+ />
+ )}
+ </>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { Query, serializeQuery } from '../query';
+import { Profile, bulkActivateRules, bulkDeactivateRules } from '../../../api/quality-profiles';
+import Modal from '../../../components/controls/Modal';
+import Select from '../../../components/controls/Select';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { formatMeasure } from '../../../helpers/measures';
+
+interface Props {
+ action: string;
+ onClose: () => void;
+ organization: string | undefined;
+ referencedProfiles: { [profile: string]: Profile };
+ profile?: Profile;
+ query: Query;
+ total: number;
+}
+
+interface ActivationResult {
+ failed: number;
+ profile: string;
+ succeeded: number;
+}
+
+interface State {
+ finished: boolean;
+ results: ActivationResult[];
+ selectedProfiles: any[];
+ submitting: boolean;
+}
+
+export default class BulkChangeModal extends React.PureComponent<Props, State> {
+ mounted: boolean;
+
+ constructor(props: Props) {
+ super(props);
+
+ // if there is only one possible option for profile, select it immediately
+ const selectedProfiles = [];
+ const availableProfiles = this.getAvailableQualityProfiles(props);
+ if (availableProfiles.length === 1) {
+ selectedProfiles.push(availableProfiles[0].key);
+ }
+
+ this.state = { finished: false, results: [], selectedProfiles, submitting: false };
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleCloseClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.props.onClose();
+ };
+
+ handleProfileSelect = (options: { value: string }[]) => {
+ const selectedProfiles = options.map(option => option.value);
+ this.setState({ selectedProfiles });
+ };
+
+ getAvailableQualityProfiles = ({ query, referencedProfiles } = this.props) => {
+ let profiles = Object.values(referencedProfiles);
+ if (query.languages.length > 0) {
+ profiles = profiles.filter(profile => query.languages.includes(profile.language));
+ }
+ return profiles
+ .filter(profile => profile.actions && profile.actions.edit)
+ .filter(profile => !profile.isBuiltIn);
+ };
+
+ processResponse = (profile: string, response: any) => {
+ if (this.mounted) {
+ const result: ActivationResult = {
+ failed: response.failed || 0,
+ profile,
+ succeeded: response.succeeded || 0
+ };
+ this.setState(state => ({ results: [...state.results, result] }));
+ }
+ };
+
+ sendRequests = () => {
+ let looper = Promise.resolve();
+
+ // serialize the query, but delete the `profile`
+ const data = serializeQuery(this.props.query);
+ delete data.profile;
+
+ const method = this.props.action === 'activate' ? bulkActivateRules : bulkDeactivateRules;
+
+ // if a profile is selected in the facet, pick it
+ // otherwise take all profiles selected in the dropdown
+ const profiles: string[] = this.props.profile
+ ? [this.props.profile.key]
+ : this.state.selectedProfiles;
+
+ for (const profile of profiles) {
+ looper = looper.then(() =>
+ method({ ...data, organization: this.props.organization, targetKey: profile }).then(
+ response => this.processResponse(profile, response)
+ )
+ );
+ }
+ return looper;
+ };
+
+ handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+ event.preventDefault();
+ this.setState({ submitting: true });
+ this.sendRequests().then(
+ () => {
+ if (this.mounted) {
+ this.setState({ finished: true, submitting: false });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ submitting: false });
+ }
+ }
+ );
+ };
+
+ renderResult = (result: ActivationResult) => {
+ const { profile: profileKey } = result;
+ const profile = this.props.referencedProfiles[profileKey];
+ if (!profile) {
+ return null;
+ }
+ return (
+ <div
+ className={classNames('alert', {
+ 'alert-warning': result.failed > 0,
+ 'alert-success': result.failed === 0
+ })}
+ key={result.profile}>
+ {result.failed
+ ? translateWithParameters(
+ 'coding_rules.bulk_change.warning',
+ profile.name,
+ profile.language,
+ result.succeeded,
+ result.failed
+ )
+ : translateWithParameters(
+ 'coding_rules.bulk_change.success',
+ profile.name,
+ profile.language,
+ result.succeeded
+ )}
+ </div>
+ );
+ };
+
+ renderProfileSelect = () => {
+ const profiles = this.getAvailableQualityProfiles();
+ const options = profiles.map(profile => ({
+ label: `${profile.name} - ${profile.languageName}`,
+ value: profile.key
+ }));
+ return (
+ <Select
+ multi={true}
+ onChange={this.handleProfileSelect}
+ options={options}
+ value={this.state.selectedProfiles}
+ />
+ );
+ };
+
+ render() {
+ const { action, profile, total } = this.props;
+ const header =
+ // prettier-ignore
+ action === 'activate'
+ ? `${translate('coding_rules.activate_in_quality_profile')} (${formatMeasure(total, 'INT')} ${translate('coding_rules._rules')})`
+ : `${translate('coding_rules.deactivate_in_quality_profile')} (${formatMeasure(total, 'INT')} ${translate('coding_rules._rules')})`;
+
+ return (
+ <Modal contentLabel={header} onRequestClose={this.props.onClose}>
+ <form onSubmit={this.handleFormSubmit}>
+ <header className="modal-head">
+ <h2>{header}</h2>
+ </header>
+
+ <div className="modal-body">
+ {this.state.results.map(this.renderResult)}
+
+ {!this.state.finished &&
+ !this.state.submitting && (
+ <div className="modal-field">
+ <h3>
+ <label htmlFor="coding-rules-bulk-change-profile">
+ {action === 'activate'
+ ? translate('coding_rules.activate_in')
+ : translate('coding_rules.deactivate_in')}
+ </label>
+ </h3>
+ {profile ? (
+ <h3 className="readonly-field">
+ {profile.name}
+ {' — '}
+ {translate('are_you_sure')}
+ </h3>
+ ) : (
+ this.renderProfileSelect()
+ )}
+ </div>
+ )}
+ </div>
+
+ <footer className="modal-foot">
+ {this.state.submitting && <i className="spinner spacer-right" />}
+ {!this.state.finished && (
+ <button
+ disabled={this.state.submitting}
+ id="coding-rules-submit-bulk-change"
+ type="submit">
+ {translate('apply')}
+ </button>
+ )}
+ <button className="button-link" onClick={this.handleCloseClick} type="reset">
+ {this.state.finished ? translate('close') : translate('cancel')}
+ </button>
+ </footer>
+ </form>
+ </Modal>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-// @flow
-import React from 'react';
-import Helmet from 'react-helmet';
-import { connect } from 'react-redux';
-import { withRouter } from 'react-router';
-import { getAppState } from '../../../store/rootReducer';
-import { translate } from '../../../helpers/l10n';
-import init from '../init';
-import '../styles.css';
-
-class CodingRulesAppContainer extends React.PureComponent {
- /*:: stop: ?() => void; */
- /*:: props: {
- appState: {
- defaultOrganization: string,
- organizationsEnabled: boolean
- },
- params: {
- organizationKey?: string
- },
- router: {
- replace: string => void
- }
- };
-*/
-
- componentDidMount() {
- // $FlowFixMe
- document.body.classList.add('white-page');
-
- if (this.props.appState.organizationsEnabled && !this.props.params.organizationKey) {
- // redirect to organization-level rules page
- this.props.router.replace(
- '/organizations/' +
- this.props.appState.defaultOrganization +
- '/rules' +
- window.location.hash
- );
- } else {
- this.stop = init(
- this.refs.container,
- this.props.params.organizationKey,
- this.props.params.organizationKey === this.props.appState.defaultOrganization
- );
- }
- }
-
- componentWillUnmount() {
- // $FlowFixMe
- document.body.classList.remove('white-page');
-
- if (this.stop) {
- this.stop();
- }
- }
-
- render() {
- // placing container inside div is required,
- // because when backbone.marionette's layout is destroyed,
- // it also destroys the root element,
- // but react wants it to be there to unmount it
- return (
- <div>
- <Helmet title={translate('coding_rules.page')} />
- <div ref="container" />
- </div>
- );
- }
-}
-
-const mapStateToProps = state => ({
- appState: getAppState(state)
-});
-
-export default connect(mapStateToProps)(withRouter(CodingRulesAppContainer));
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import SimpleModal from '../../../components/controls/SimpleModal';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ children: (
+ props: { onClick: (event: React.SyntheticEvent<HTMLButtonElement>) => void }
+ ) => React.ReactNode;
+ confirmButtonText: string;
+ confirmData?: string;
+ isDestructive?: boolean;
+ modalBody: React.ReactNode;
+ modalHeader: string;
+ onConfirm: (data?: string) => void | Promise<void>;
+}
+
+interface State {
+ modal: boolean;
+}
+
+// TODO move this component to components/ and use everywhere!
+export default class ConfirmButton extends React.PureComponent<Props, State> {
+ state: State = { modal: false };
+
+ handleButtonClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.setState({ modal: true });
+ };
+
+ handleSubmit = () => {
+ const result = this.props.onConfirm(this.props.confirmData);
+ if (result) {
+ result.then(this.handleCloseModal, () => {});
+ } else {
+ this.handleCloseModal();
+ }
+ };
+
+ handleCloseModal = () => this.setState({ modal: false });
+
+ render() {
+ const { confirmButtonText, isDestructive, modalBody, modalHeader } = this.props;
+
+ return (
+ <>
+ {this.props.children({ onClick: this.handleButtonClick })}
+ {this.state.modal && (
+ <SimpleModal
+ header={modalHeader}
+ onClose={this.handleCloseModal}
+ onSubmit={this.handleSubmit}>
+ {({ onCloseClick, onSubmitClick, submitting }) => (
+ <>
+ <header className="modal-head">
+ <h2>{modalHeader}</h2>
+ </header>
+
+ <div className="modal-body">{modalBody}</div>
+
+ <footer className="modal-foot">
+ {submitting && <i className="spinner spacer-right" />}
+ <button
+ className={isDestructive ? 'button-red' : undefined}
+ disabled={submitting}
+ onClick={onSubmitClick}>
+ {confirmButtonText}
+ </button>
+ <a href="#" onClick={onCloseClick}>
+ {translate('cancel')}
+ </a>
+ </footer>
+ </>
+ )}
+ </SimpleModal>
+ )}
+ </>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import CustomRuleFormModal from './CustomRuleFormModal';
+import { RuleDetails } from '../../../app/types';
+
+interface Props {
+ children: (
+ props: { onClick: (event: React.SyntheticEvent<HTMLButtonElement>) => void }
+ ) => React.ReactNode;
+ customRule?: RuleDetails;
+ onDone: (newRuleDetails: RuleDetails) => void;
+ organization: string | undefined;
+ templateRule: RuleDetails;
+}
+
+interface State {
+ modal: boolean;
+}
+
+export default class CustomRuleButton extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { modal: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.setState({ modal: true });
+ };
+
+ handleModalClose = () => {
+ if (this.mounted) {
+ this.setState({ modal: false });
+ }
+ };
+
+ handleDone = (newRuleDetails: RuleDetails) => {
+ this.handleModalClose();
+ this.props.onDone(newRuleDetails);
+ };
+
+ render() {
+ return (
+ <>
+ {this.props.children({ onClick: this.handleClick })}
+ {this.state.modal && (
+ <CustomRuleFormModal
+ customRule={this.props.customRule}
+ onClose={this.handleModalClose}
+ onDone={this.handleDone}
+ organization={this.props.organization}
+ templateRule={this.props.templateRule}
+ />
+ )}
+ </>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { RuleDetails, RuleParameter } from '../../../app/types';
+import Modal from '../../../components/controls/Modal';
+import { translate } from '../../../helpers/l10n';
+import MarkdownTips from '../../../components/common/MarkdownTips';
+import { SEVERITIES, TYPES, RULE_STATUSES } from '../../../helpers/constants';
+import latinize from '../../../helpers/latinize';
+import Select from '../../../components/controls/Select';
+import TypeHelper from '../../../components/shared/TypeHelper';
+import SeverityHelper from '../../../components/shared/SeverityHelper';
+import { createRule, updateRule } from '../../../api/rules';
+import { csvEscape } from '../../../helpers/csv';
+
+interface Props {
+ customRule?: RuleDetails;
+ onClose: () => void;
+ onDone: (newRuleDetails: RuleDetails) => void;
+ organization: string | undefined;
+ templateRule: RuleDetails;
+}
+
+interface State {
+ description: string;
+ key: string;
+ keyModifiedByUser: boolean;
+ name: string;
+ params: { [p: string]: string };
+ reactivating: boolean;
+ severity: string;
+ status: string;
+ submitting: boolean;
+ type: string;
+}
+
+export default class CustomRuleFormModal extends React.PureComponent<Props, State> {
+ mounted: boolean;
+
+ constructor(props: Props) {
+ super(props);
+ const params: { [p: string]: string } = {};
+ if (props.customRule && props.customRule.params) {
+ for (const param of props.customRule.params) {
+ params[param.key] = param.defaultValue || '';
+ }
+ }
+ this.state = {
+ description: (props.customRule && props.customRule.mdDesc) || '',
+ key: '',
+ keyModifiedByUser: false,
+ name: (props.customRule && props.customRule.name) || '',
+ params,
+ reactivating: false,
+ severity: (props.customRule && props.customRule.severity) || props.templateRule.severity,
+ status: (props.customRule && props.customRule.status) || props.templateRule.status,
+ submitting: false,
+ type: (props.customRule && props.customRule.type) || props.templateRule.type
+ };
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleCancelClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.props.onClose();
+ };
+
+ prepareRequest = () => {
+ /* eslint-disable camelcase */
+ const { customRule, organization, templateRule } = this.props;
+ const params = Object.keys(this.state.params)
+ .map(key => `${key}=${csvEscape(this.state.params[key])}`)
+ .join(';');
+ const ruleData = {
+ markdown_description: this.state.description,
+ name: this.state.name,
+ organization,
+ params,
+ severity: this.state.severity,
+ status: this.state.status
+ };
+ return customRule
+ ? updateRule({ ...ruleData, key: customRule.key })
+ : createRule({
+ ...ruleData,
+ custom_key: this.state.key,
+ prevent_reactivation: !this.state.reactivating,
+ template_key: templateRule.key,
+ type: this.state.type
+ });
+ /* eslint-enable camelcase */
+ };
+
+ handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+ event.preventDefault();
+ this.setState({ submitting: true });
+ this.prepareRequest().then(
+ newRuleDetails => {
+ if (this.mounted) {
+ this.setState({ submitting: false });
+ this.props.onDone(newRuleDetails);
+ }
+ },
+ (response: Response) => {
+ if (this.mounted) {
+ this.setState({ reactivating: response.status === 409, submitting: false });
+ }
+ }
+ );
+ };
+
+ handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
+ const { value: name } = event.currentTarget;
+ this.setState((state: State) => {
+ const change: Partial<State> = { name };
+ if (!state.keyModifiedByUser) {
+ change.key = latinize(name).replace(/[^A-Za-z0-9]/g, '_');
+ }
+ return change;
+ });
+ };
+
+ handleKeyChange = (event: React.SyntheticEvent<HTMLInputElement>) =>
+ this.setState({ key: event.currentTarget.value, keyModifiedByUser: true });
+
+ handleDescriptionChange = (event: React.SyntheticEvent<HTMLTextAreaElement>) =>
+ this.setState({ description: event.currentTarget.value });
+
+ handleTypeChange = ({ value }: { value: string }) => this.setState({ type: value });
+
+ handleSeverityChange = ({ value }: { value: string }) => this.setState({ severity: value });
+
+ handleStatusChange = ({ value }: { value: string }) => this.setState({ status: value });
+
+ handleParameterChange = (event: React.SyntheticEvent<HTMLInputElement | HTMLTextAreaElement>) => {
+ const { name, value } = event.currentTarget;
+ this.setState((state: State) => ({ params: { ...state.params, [name]: value } }));
+ };
+
+ renderNameField = () => (
+ <tr className="property">
+ <th className="nowrap">
+ <h3>
+ {translate('name')} <em className="mandatory">*</em>
+ </h3>
+ </th>
+ <td>
+ <input
+ autoFocus={true}
+ className="coding-rules-name-key"
+ disabled={this.state.submitting}
+ id="coding-rules-custom-rule-creation-name"
+ onChange={this.handleNameChange}
+ required={true}
+ type="text"
+ value={this.state.name}
+ />
+ </td>
+ </tr>
+ );
+
+ renderKeyField = () => (
+ <tr className="property">
+ <th className="nowrap">
+ <h3>
+ {translate('key')} {!this.props.customRule && <em className="mandatory">*</em>}
+ </h3>
+ </th>
+ <td>
+ {this.props.customRule ? (
+ <span className="coding-rules-detail-custom-rule-key" title={this.props.customRule.key}>
+ {this.props.customRule.key}
+ </span>
+ ) : (
+ <input
+ className="coding-rules-name-key"
+ disabled={this.state.submitting}
+ id="coding-rules-custom-rule-creation-key"
+ onChange={this.handleKeyChange}
+ required={true}
+ type="text"
+ value={this.state.key}
+ />
+ )}
+ </td>
+ </tr>
+ );
+
+ renderDescriptionField = () => (
+ <tr className="property">
+ <th className="nowrap">
+ <h3>
+ {translate('description')} <em className="mandatory">*</em>
+ </h3>
+ </th>
+ <td>
+ <textarea
+ className="coding-rules-markdown-description"
+ disabled={this.state.submitting}
+ id="coding-rules-custom-rule-creation-html-description"
+ onChange={this.handleDescriptionChange}
+ required={true}
+ rows={5}
+ value={this.state.description}
+ />
+ <span className="text-right">
+ <MarkdownTips />
+ </span>
+ </td>
+ </tr>
+ );
+
+ renderTypeOption = ({ value }: { value: string }) => <TypeHelper type={value} />;
+
+ renderTypeField = () => (
+ <tr className="property">
+ <th className="nowrap">
+ <h3>{translate('type')}</h3>
+ </th>
+ <td>
+ <Select
+ className="input-medium"
+ clearable={false}
+ disabled={this.state.submitting}
+ onChange={this.handleTypeChange}
+ options={TYPES.map(type => ({
+ label: translate('issue.type', type),
+ value: type
+ }))}
+ optionRenderer={this.renderTypeOption}
+ searchable={false}
+ value={this.state.type}
+ valueRenderer={this.renderTypeOption}
+ />
+ </td>
+ </tr>
+ );
+
+ renderSeverityOption = ({ value }: { value: string }) => <SeverityHelper severity={value} />;
+
+ renderSeverityField = () => (
+ <tr className="property">
+ <th className="nowrap">
+ <h3>{translate('severity')}</h3>
+ </th>
+ <td>
+ <Select
+ className="input-medium"
+ clearable={false}
+ disabled={this.state.submitting}
+ onChange={this.handleSeverityChange}
+ options={SEVERITIES.map(severity => ({
+ label: translate('severity', severity),
+ value: severity
+ }))}
+ optionRenderer={this.renderSeverityOption}
+ searchable={false}
+ value={this.state.severity}
+ valueRenderer={this.renderSeverityOption}
+ />
+ </td>
+ </tr>
+ );
+
+ renderStatusField = () => (
+ <tr className="property">
+ <th className="nowrap">
+ <h3>{translate('coding_rules.filters.status')}</h3>
+ </th>
+ <td>
+ <Select
+ className="input-medium"
+ clearable={false}
+ disabled={this.state.submitting}
+ onChange={this.handleStatusChange}
+ options={RULE_STATUSES.map(status => ({
+ label: translate('rules.status', status),
+ value: status
+ }))}
+ searchable={false}
+ value={this.state.status}
+ />
+ </td>
+ </tr>
+ );
+
+ renderParameterField = (param: RuleParameter) => (
+ <tr className="property" key={param.key}>
+ <th className="nowrap">
+ <h3>{param.key}</h3>
+ </th>
+ <td>
+ {param.type === 'TEXT' ? (
+ <textarea
+ className="width100"
+ disabled={this.state.submitting}
+ name={param.key}
+ onChange={this.handleParameterChange}
+ placeholder={param.defaultValue}
+ rows={3}
+ value={this.state.params[param.key] || ''}
+ />
+ ) : (
+ <input
+ className="input-super-large"
+ disabled={this.state.submitting}
+ name={param.key}
+ onChange={this.handleParameterChange}
+ placeholder={param.defaultValue}
+ type="text"
+ value={this.state.params[param.key] || ''}
+ />
+ )}
+ <div className="note" dangerouslySetInnerHTML={{ __html: param.htmlDesc || '' }} />
+ {param.extra && <div className="note">{param.extra}</div>}
+ </td>
+ </tr>
+ );
+
+ renderSubmitButton = () => {
+ if (this.state.reactivating) {
+ return (
+ <button
+ disabled={this.state.submitting}
+ id="coding-rules-custom-rule-creation-reactivate"
+ type="submit">
+ {translate('coding_rules.reactivate')}
+ </button>
+ );
+ } else {
+ return (
+ <button
+ disabled={this.state.submitting}
+ id="coding-rules-custom-rule-creation-create"
+ type="submit">
+ {translate(this.props.customRule ? 'save' : 'create')}
+ </button>
+ );
+ }
+ };
+
+ render() {
+ const { customRule, templateRule } = this.props;
+ const { reactivating, submitting } = this.state;
+ const { params = [] } = templateRule;
+ const header = translate(
+ customRule ? 'coding_rules.update_custom_rule' : 'coding_rules.create_custom_rule'
+ );
+ return (
+ <Modal contentLabel={header} onRequestClose={this.props.onClose}>
+ <form onSubmit={this.handleFormSubmit}>
+ <div className="modal-head">
+ <h2>{header}</h2>
+ </div>
+
+ <div className="modal-body modal-container">
+ {reactivating && (
+ <div className="alert alert-warning">{translate('coding_rules.reactivate.help')}</div>
+ )}
+ <table>
+ <tbody>
+ {this.renderNameField()}
+ {this.renderKeyField()}
+ {this.renderDescriptionField()}
+ {/* do not allow to change the type of existing rule */}
+ {!customRule && this.renderTypeField()}
+ {this.renderSeverityField()}
+ {this.renderStatusField()}
+ {params.map(this.renderParameterField)}
+ </tbody>
+ </table>
+ </div>
+
+ <div className="modal-foot">
+ {submitting && <i className="spinner spacer-right" />}
+ {this.renderSubmitButton()}
+ <button
+ className="button-link"
+ disabled={submitting}
+ id="coding-rules-custom-rule-creation-cancel"
+ onClick={this.handleCancelClick}
+ type="reset">
+ {translate('cancel')}
+ </button>
+ </div>
+ </form>
+ </Modal>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import Facet, { BasicProps } from './Facet';
+import SeverityHelper from '../../../components/shared/SeverityHelper';
+import { SEVERITIES } from '../../../helpers/constants';
+import { translate } from '../../../helpers/l10n';
+
+export default class DefaultSeverityFacet extends React.PureComponent<BasicProps> {
+ renderName = (severity: string) => <SeverityHelper severity={severity} />;
+
+ renderTextName = (severity: string) => translate('severity', severity);
+
+ render() {
+ return (
+ <Facet
+ {...this.props}
+ halfWidth={true}
+ options={SEVERITIES}
+ property="severities"
+ renderName={this.renderName}
+ renderTextName={this.renderTextName}
+ />
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { orderBy, without, sortBy } from 'lodash';
+import * as classNames from 'classnames';
+import { FacetKey } from '../query';
+import FacetBox from '../../../components/facet/FacetBox';
+import FacetHeader from '../../../components/facet/FacetHeader';
+import FacetItem from '../../../components/facet/FacetItem';
+import FacetItemsList from '../../../components/facet/FacetItemsList';
+import { translate } from '../../../helpers/l10n';
+import { formatMeasure } from '../../../helpers/measures';
+
+export interface BasicProps {
+ onChange: (changes: { [x: string]: string | string[] | undefined }) => void;
+ onToggle: (facet: FacetKey) => void;
+ open: boolean;
+ stats?: { [x: string]: number };
+ values: string[];
+}
+
+interface Props extends BasicProps {
+ disabled?: boolean;
+ disabledHelper?: string;
+ halfWidth?: boolean;
+ options?: string[];
+ property: FacetKey;
+ renderFooter?: () => React.ReactNode;
+ renderName?: (value: string) => React.ReactNode;
+ renderTextName?: (value: string) => string;
+ singleSelection?: boolean;
+}
+
+export default class Facet extends React.PureComponent<Props> {
+ handleItemClick = (itemValue: string) => {
+ const { values } = this.props;
+ let newValue;
+ if (this.props.singleSelection) {
+ const value = values.length ? values[0] : undefined;
+ newValue = itemValue === value ? undefined : itemValue;
+ } else {
+ newValue = orderBy(
+ values.includes(itemValue) ? without(values, itemValue) : [...values, itemValue]
+ );
+ }
+ this.props.onChange({ [this.props.property]: newValue });
+ };
+
+ handleHeaderClick = () => this.props.onToggle(this.props.property);
+
+ handleClear = () => this.props.onChange({ [this.props.property]: [] });
+
+ getStat = (value: string) => this.props.stats && this.props.stats[value];
+
+ renderItem = (value: string) => {
+ const active = this.props.values.includes(value);
+ const stat = this.getStat(value);
+ const { renderName = defaultRenderName } = this.props;
+
+ return (
+ <FacetItem
+ active={active}
+ disabled={stat === 0 && !active}
+ halfWidth={this.props.halfWidth}
+ key={value}
+ name={renderName(value)}
+ onClick={this.handleItemClick}
+ stat={stat && formatMeasure(stat, 'SHORT_INT')}
+ value={value}
+ />
+ );
+ };
+
+ render() {
+ const { renderTextName = defaultRenderName, stats } = this.props;
+ const values = this.props.values.map(renderTextName);
+ const items =
+ this.props.options ||
+ (stats &&
+ sortBy(Object.keys(stats), key => -stats[key], key => renderTextName(key).toLowerCase()));
+
+ return (
+ <FacetBox
+ className={classNames({ 'search-navigator-facet-box-forbidden': this.props.disabled })}
+ property={this.props.property}>
+ <FacetHeader
+ helper={this.props.disabled ? this.props.disabledHelper : undefined}
+ name={translate('coding_rules.facet', this.props.property)}
+ onClear={this.handleClear}
+ onClick={this.props.disabled ? undefined : this.handleHeaderClick}
+ open={this.props.open && !this.props.disabled}
+ values={values}
+ />
+
+ {this.props.open &&
+ items !== undefined && <FacetItemsList>{items.map(this.renderItem)}</FacetItemsList>}
+
+ {this.props.open && this.props.renderFooter !== undefined && this.props.renderFooter()}
+ </FacetBox>
+ );
+ }
+}
+
+function defaultRenderName(value: string) {
+ return value;
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import ActivationSeverityFacet from './ActivationSeverityFacet';
+import AvailableSinceFacet from './AvailableSinceFacet';
+import DefaultSeverityFacet from './DefaultSeverityFacet';
+import InheritanceFacet from './InheritanceFacet';
+import LanguageFacet from './LanguageFacet';
+import ProfileFacet from './ProfileFacet';
+import RepositoryFacet from './RepositoryFacet';
+import StatusFacet from './StatusFacet';
+import TagFacet from './TagFacet';
+import TemplateFacet from './TemplateFacet';
+import TypeFacet from './TypeFacet';
+import { Facets, Query, FacetKey, OpenFacets } from '../query';
+import { Profile } from '../../../api/quality-profiles';
+
+interface Props {
+ facets?: Facets;
+ onFacetToggle: (facet: FacetKey) => void;
+ onFilterChange: (changes: Partial<Query>) => void;
+ openFacets: OpenFacets;
+ organization: string | undefined;
+ organizationsEnabled?: boolean;
+ query: Query;
+ referencedProfiles: { [profile: string]: Profile };
+ referencedRepositories: { [repository: string]: { key: string; language: string; name: string } };
+ selectedProfile?: Profile;
+}
+
+export default function FacetsList(props: Props) {
+ const inheritanceDisabled =
+ props.query.compareToProfile !== undefined ||
+ props.selectedProfile === undefined ||
+ !props.selectedProfile.isInherited;
+
+ const activationSeverityDisabled =
+ props.query.compareToProfile !== undefined ||
+ props.selectedProfile === undefined ||
+ !props.query.activation;
+
+ return (
+ <div className="search-navigator-facets-list">
+ <LanguageFacet
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.languages}
+ stats={props.facets && props.facets.languages}
+ values={props.query.languages}
+ />
+ <TypeFacet
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.types}
+ stats={props.facets && props.facets.types}
+ values={props.query.types}
+ />
+ <TagFacet
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ organization={props.organization}
+ open={!!props.openFacets.tags}
+ stats={props.facets && props.facets.tags}
+ values={props.query.tags}
+ />
+ <RepositoryFacet
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.repositories}
+ stats={props.facets && props.facets.repositories}
+ referencedRepositories={props.referencedRepositories}
+ values={props.query.repositories}
+ />
+ <DefaultSeverityFacet
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.severities}
+ stats={props.facets && props.facets.severities}
+ values={props.query.severities}
+ />
+ <StatusFacet
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.statuses}
+ stats={props.facets && props.facets.statuses}
+ values={props.query.statuses}
+ />
+ <AvailableSinceFacet
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.availableSince}
+ value={props.query.availableSince}
+ />
+ {!props.organizationsEnabled && (
+ <TemplateFacet
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.template}
+ value={props.query.template}
+ />
+ )}
+ <ProfileFacet
+ activation={props.query.activation}
+ compareToProfile={props.query.compareToProfile}
+ languages={props.query.languages}
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.profile}
+ referencedProfiles={props.referencedProfiles}
+ value={props.query.profile}
+ />
+ <InheritanceFacet
+ disabled={inheritanceDisabled}
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.inheritance}
+ value={props.query.inheritance}
+ />
+ <ActivationSeverityFacet
+ disabled={activationSeverityDisabled}
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.activationSeverities}
+ stats={props.facets && props.facets.activationSeverities}
+ values={props.query.activationSeverities}
+ />
+ </div>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import Facet, { BasicProps } from './Facet';
+import { RuleInheritance, Omit } from '../../../app/types';
+import { translate } from '../../../helpers/l10n';
+
+interface Props extends Omit<BasicProps, 'values'> {
+ disabled: boolean;
+ value: RuleInheritance | undefined;
+}
+
+export default class InheritanceFacet extends React.PureComponent<Props> {
+ renderName = (value: RuleInheritance) =>
+ translate('coding_rules.filters.inheritance', value.toLowerCase());
+
+ render() {
+ const { value, ...props } = this.props;
+
+ return (
+ <Facet
+ {...props}
+ disabled={this.props.disabled}
+ disabledHelper={translate('coding_rules.filters.inheritance.inactive')}
+ options={Object.values(RuleInheritance)}
+ property="inheritance"
+ renderName={this.renderName}
+ renderTextName={this.renderName}
+ singleSelection={true}
+ values={value ? [value] : []}
+ />
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { uniq } from 'lodash';
+import Facet, { BasicProps } from './Facet';
+import LanguageFacetFooter from './LanguageFacetFooter';
+import { getLanguages } from '../../../store/rootReducer';
+
+interface StateProps {
+ referencedLanguages: { [language: string]: { key: string; name: string } };
+}
+
+interface Props extends BasicProps, StateProps {}
+
+class LanguageFacet extends React.PureComponent<Props> {
+ getLanguageName = (language: string) => {
+ const { referencedLanguages } = this.props;
+ return referencedLanguages[language] ? referencedLanguages[language].name : language;
+ };
+
+ handleSelect = (language: string) => {
+ const { values } = this.props;
+ this.props.onChange({ languages: uniq([...values, language]) });
+ };
+
+ renderFooter = () => {
+ if (!this.props.stats) {
+ return null;
+ }
+
+ return (
+ <LanguageFacetFooter
+ onSelect={this.handleSelect}
+ referencedLanguages={this.props.referencedLanguages}
+ />
+ );
+ };
+
+ render() {
+ const { referencedLanguages, ...facetProps } = this.props;
+ return (
+ <Facet
+ {...facetProps}
+ property="languages"
+ renderFooter={this.renderFooter}
+ renderName={this.getLanguageName}
+ renderTextName={this.getLanguageName}
+ />
+ );
+ }
+}
+
+const mapStateToProps = (state: any): StateProps => ({
+ referencedLanguages: getLanguages(state)
+});
+
+export default connect(mapStateToProps)(LanguageFacet);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import Select from '../../../components/controls/Select';
+import { translate } from '../../../helpers/l10n';
+
+type Option = { label: string; value: string };
+
+interface Props {
+ referencedLanguages: { [language: string]: { key: string; name: string } };
+ onSelect: (value: string) => void;
+}
+
+export default class LanguageFacetFooter extends React.PureComponent<Props> {
+ handleChange = (option: Option) => this.props.onSelect(option.value);
+
+ render() {
+ const options = Object.values(this.props.referencedLanguages).map(language => ({
+ label: language.name,
+ value: language.key
+ }));
+
+ return (
+ <div className="search-navigator-facet-footer">
+ <Select
+ className="input-super-large"
+ clearable={false}
+ noResultsText={translate('select2.noMatches')}
+ onChange={this.handleChange}
+ options={options}
+ placeholder={translate('search.search_for_languages')}
+ searchable={true}
+ />
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { Paging } from '../../../app/types';
+import { translate } from '../../../helpers/l10n';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import PageCounter from '../../../components/common/PageCounter';
+import ReloadButton from '../../../components/controls/ReloadButton';
+
+interface Props {
+ loading: boolean;
+ onReload: () => void;
+ paging?: Paging;
+ selectedIndex?: number;
+}
+
+export default function PageActions(props: Props) {
+ return (
+ <div className="pull-right">
+ <Shortcuts />
+
+ <DeferredSpinner loading={props.loading}>
+ <ReloadButton onClick={props.onReload} />
+ </DeferredSpinner>
+
+ {props.paging && (
+ <PageCounter
+ className="spacer-left flash flash-heavy"
+ current={props.selectedIndex}
+ label={translate('coding_rules._rules')}
+ total={props.paging.total}
+ />
+ )}
+ </div>
+ );
+}
+
+function Shortcuts() {
+ return (
+ <span className="note big-spacer-right">
+ <span className="big-spacer-right">
+ <span className="shortcut-button little-spacer-right">↑</span>
+ <span className="shortcut-button little-spacer-right">↓</span>
+ {translate('coding_rules.to_select_rules')}
+ </span>
+
+ <span>
+ <span className="shortcut-button little-spacer-right">←</span>
+ <span className="shortcut-button little-spacer-right">→</span>
+ {translate('issues.to_navigate')}
+ </span>
+ </span>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { sortBy } from 'lodash';
+import * as classNames from 'classnames';
+import { Query, FacetKey } from '../query';
+import { Profile } from '../../../api/quality-profiles';
+import FacetBox from '../../../components/facet/FacetBox';
+import FacetHeader from '../../../components/facet/FacetHeader';
+import FacetItem from '../../../components/facet/FacetItem';
+import FacetItemsList from '../../../components/facet/FacetItemsList';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ activation: boolean | undefined;
+ compareToProfile: string | undefined;
+ languages: string[];
+ onChange: (changes: Partial<Query>) => void;
+ onToggle: (facet: FacetKey) => void;
+ open: boolean;
+ referencedProfiles: { [profile: string]: Profile };
+ value: string | undefined;
+}
+
+export default class ProfileFacet extends React.PureComponent<Props> {
+ handleItemClick = (selected: string) => {
+ const newValue = this.props.value === selected ? '' : selected;
+ this.props.onChange({
+ activation: this.props.activation === undefined ? true : this.props.activation,
+ compareToProfile: undefined,
+ profile: newValue
+ });
+ };
+
+ handleHeaderClick = () => this.props.onToggle('profile');
+
+ handleClear = () =>
+ this.props.onChange({
+ activation: undefined,
+ activationSeverities: [],
+ compareToProfile: undefined,
+ inheritance: undefined,
+ profile: undefined
+ });
+
+ handleActiveClick = (event: React.SyntheticEvent<HTMLElement>) => {
+ this.stopPropagation(event);
+ this.props.onChange({ activation: true, compareToProfile: undefined });
+ };
+
+ handleInactiveClick = (event: React.SyntheticEvent<HTMLElement>) => {
+ this.stopPropagation(event);
+ this.props.onChange({ activation: false, compareToProfile: undefined });
+ };
+
+ stopPropagation = (event: React.SyntheticEvent<HTMLElement>) => {
+ event.preventDefault();
+ event.stopPropagation();
+ event.currentTarget.blur();
+ };
+
+ getTextValue = () => {
+ const { referencedProfiles, value } = this.props;
+ if (value) {
+ const profile = referencedProfiles[value];
+ const name = (profile && `${profile.name} ${profile.languageName}`) || value;
+ return [name];
+ } else {
+ return [];
+ }
+ };
+
+ renderName = (profile: Profile) => (
+ <>
+ {profile.name}
+ <span className="note little-spacer-left">
+ {profile.languageName}
+ {profile.isBuiltIn && ` (${translate('quality_profiles.built_in')})`}
+ </span>
+ </>
+ );
+
+ renderActivation = (profile: Profile) => {
+ const isCompare = profile.key === this.props.compareToProfile;
+ const activation = isCompare ? true : this.props.activation;
+ return (
+ <>
+ <span
+ aria-checked={activation}
+ className={classNames('js-active', 'facet-toggle', 'facet-toggle-green', {
+ 'facet-toggle-active': activation
+ })}
+ onClick={isCompare ? this.stopPropagation : this.handleActiveClick}
+ role="radio"
+ tabIndex={-1}>
+ active
+ </span>
+ <span
+ aria-checked={!activation}
+ className={classNames('js-inactive', 'facet-toggle', 'facet-toggle-red', {
+ 'facet-toggle-active': !activation
+ })}
+ onClick={isCompare ? this.stopPropagation : this.handleInactiveClick}
+ role="radio"
+ tabIndex={-1}>
+ inactive
+ </span>
+ </>
+ );
+ };
+
+ renderItem = (profile: Profile) => {
+ const active = [this.props.value, this.props.compareToProfile].includes(profile.key);
+
+ return (
+ <FacetItem
+ active={active}
+ className={this.props.compareToProfile === profile.key ? 'compare' : undefined}
+ key={profile.key}
+ name={this.renderName(profile)}
+ onClick={this.handleItemClick}
+ stat={this.renderActivation(profile)}
+ value={profile.key}
+ />
+ );
+ };
+
+ render() {
+ const { languages, referencedProfiles } = this.props;
+ let profiles = Object.values(referencedProfiles);
+ if (languages.length > 0) {
+ profiles = profiles.filter(profile => languages.includes(profile.language));
+ }
+ profiles = sortBy(
+ profiles,
+ profile => profile.name.toLowerCase(),
+ profile => profile.languageName
+ );
+
+ return (
+ <FacetBox property="profile">
+ <FacetHeader
+ name={translate('coding_rules.facet.qprofile')}
+ onClear={this.handleClear}
+ onClick={this.handleHeaderClick}
+ open={this.props.open}
+ values={this.getTextValue()}
+ />
+
+ {this.props.open && <FacetItemsList>{profiles.map(this.renderItem)}</FacetItemsList>}
+ </FacetBox>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import SimpleModal from '../../../components/controls/SimpleModal';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ onCancel: () => void;
+ onSubmit: () => void;
+}
+
+export default function RemoveExtendedDescriptionModal({ onCancel, onSubmit }: Props) {
+ const header = translate('coding_rules.remove_extended_description');
+ return (
+ <SimpleModal header={header} onClose={onCancel} onSubmit={onSubmit}>
+ {({ onCloseClick, onSubmitClick, submitting }) => (
+ <>
+ <header className="modal-head">
+ <h2>{header}</h2>
+ </header>
+
+ <div className="modal-body">
+ {translate('coding_rules.remove_extended_description.confirm')}
+ </div>
+
+ <footer className="modal-foot">
+ {submitting && <i className="spinner spacer-right" />}
+ <button
+ className="button-red"
+ disabled={submitting}
+ id="coding-rules-detail-extend-description-remove-submit"
+ onClick={onSubmitClick}>
+ {translate('remove')}
+ </button>
+ <a href="#" onClick={onCloseClick}>
+ {translate('cancel')}
+ </a>
+ </footer>
+ </>
+ )}
+ </SimpleModal>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { connect } from 'react-redux';
+import Facet, { BasicProps } from './Facet';
+import { getLanguages } from '../../../store/rootReducer';
+
+interface StateProps {
+ referencedLanguages: { [language: string]: { key: string; name: string } };
+}
+
+interface Props extends BasicProps, StateProps {
+ referencedRepositories: { [repository: string]: { key: string; language: string; name: string } };
+}
+
+class RepositoryFacet extends React.PureComponent<Props> {
+ getLanguageName = (languageKey: string) => {
+ const { referencedLanguages } = this.props;
+ const language = referencedLanguages[languageKey];
+ return (language && language.name) || languageKey;
+ };
+
+ renderName = (repositoryKey: string) => {
+ const { referencedRepositories } = this.props;
+ const repository = referencedRepositories[repositoryKey];
+ return repository ? (
+ <>
+ {repository.name}
+ <span className="note little-spacer-left">{this.getLanguageName(repository.language)}</span>
+ </>
+ ) : (
+ repositoryKey
+ );
+ };
+
+ renderTextName = (repositoryKey: string) => {
+ const { referencedRepositories } = this.props;
+ const repository = referencedRepositories[repositoryKey];
+ return (repository && repository.name) || repositoryKey;
+ };
+
+ render() {
+ const { referencedLanguages, referencedRepositories, ...facetProps } = this.props;
+ return (
+ <Facet
+ {...facetProps}
+ property="repositories"
+ renderName={this.renderName}
+ renderTextName={this.renderTextName}
+ />
+ );
+ }
+}
+
+const mapStateToProps = (state: any): StateProps => ({
+ referencedLanguages: getLanguages(state)
+});
+
+export default connect(mapStateToProps)(RepositoryFacet);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import ConfirmButton from './ConfirmButton';
+import CustomRuleButton from './CustomRuleButton';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import RuleDetailsCustomRules from './RuleDetailsCustomRules';
+import RuleDetailsDescription from './RuleDetailsDescription';
+import RuleDetailsIssues from './RuleDetailsIssues';
+import RuleDetailsMeta from './RuleDetailsMeta';
+import RuleDetailsParameters from './RuleDetailsParameters';
+import RuleDetailsProfiles from './RuleDetailsProfiles';
+import { Query, Activation } from '../query';
+import { Profile } from '../../../api/quality-profiles';
+import { getRuleDetails, deleteRule, updateRule } from '../../../api/rules';
+import { RuleActivation, RuleDetails as IRuleDetails } from '../../../app/types';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+
+interface Props {
+ allowCustomRules?: boolean;
+ canWrite?: boolean;
+ onActivate: (profile: string, rule: string, activation: Activation) => void;
+ onDeactivate: (profile: string, rule: string) => void;
+ onDelete: (rule: string) => void;
+ onFilterChange: (changes: Partial<Query>) => void;
+ organization: string | undefined;
+ referencedProfiles: { [profile: string]: Profile };
+ referencedRepositories: { [repository: string]: { key: string; language: string; name: string } };
+ ruleKey: string;
+ selectedProfile?: Profile;
+}
+
+interface State {
+ actives?: RuleActivation[];
+ loading: boolean;
+ ruleDetails?: IRuleDetails;
+}
+
+export default class RuleDetails extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { loading: true };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.setState({ loading: true });
+ this.fetchRuleDetails();
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ if (prevProps.ruleKey !== this.props.ruleKey) {
+ this.setState({ loading: true });
+ this.fetchRuleDetails();
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchRuleDetails = () =>
+ getRuleDetails({
+ actives: true,
+ key: this.props.ruleKey,
+ organization: this.props.organization
+ }).then(
+ ({ actives, rule }) => {
+ if (this.mounted) {
+ this.setState({ actives, loading: false, ruleDetails: rule });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+
+ handleRuleChange = (ruleDetails: IRuleDetails) => {
+ if (this.mounted) {
+ this.setState({ ruleDetails });
+ }
+ };
+
+ handleTagsChange = (tags: string[]) => {
+ // optimistic update
+ const oldTags = this.state.ruleDetails && this.state.ruleDetails.tags;
+ this.setState(state => ({ ruleDetails: { ...state.ruleDetails, tags } }));
+ updateRule({
+ key: this.props.ruleKey,
+ organization: this.props.organization,
+ tags: tags.join()
+ }).catch(() => {
+ if (this.mounted) {
+ this.setState(state => ({ ruleDetails: { ...state.ruleDetails, tags: oldTags } }));
+ }
+ });
+ };
+
+ handleActivate = () =>
+ this.fetchRuleDetails().then(() => {
+ const { ruleKey, selectedProfile } = this.props;
+ if (selectedProfile && this.state.actives) {
+ const active = this.state.actives.find(active => active.qProfile === selectedProfile.key);
+ if (active) {
+ this.props.onActivate(selectedProfile.key, ruleKey, active);
+ }
+ }
+ });
+
+ handleDeactivate = () =>
+ this.fetchRuleDetails().then(() => {
+ const { ruleKey, selectedProfile } = this.props;
+ if (selectedProfile && this.state.actives) {
+ if (!this.state.actives.find(active => active.qProfile === selectedProfile.key)) {
+ this.props.onDeactivate(selectedProfile.key, ruleKey);
+ }
+ }
+ });
+
+ handleDelete = () =>
+ deleteRule({ key: this.props.ruleKey, organization: this.props.organization }).then(() =>
+ this.props.onDelete(this.props.ruleKey)
+ );
+
+ render() {
+ const { ruleDetails } = this.state;
+
+ if (!ruleDetails) {
+ return <div className="coding-rule-details" />;
+ }
+
+ const { allowCustomRules, canWrite, organization, referencedProfiles } = this.props;
+ const { params = [] } = ruleDetails;
+
+ const isCustom = !!ruleDetails.templateKey;
+ const isEditable = canWrite && !!this.props.allowCustomRules && isCustom;
+
+ return (
+ <div className="coding-rule-details">
+ <DeferredSpinner loading={this.state.loading}>
+ <RuleDetailsMeta
+ canWrite={canWrite}
+ onFilterChange={this.props.onFilterChange}
+ onTagsChange={this.handleTagsChange}
+ organization={organization}
+ referencedRepositories={this.props.referencedRepositories}
+ ruleDetails={ruleDetails}
+ />
+
+ <RuleDetailsDescription
+ canWrite={canWrite}
+ onChange={this.handleRuleChange}
+ organization={organization}
+ ruleDetails={ruleDetails}
+ />
+
+ {params.length > 0 && <RuleDetailsParameters params={params} />}
+
+ {isEditable && (
+ <div className="coding-rules-detail-description">
+ {/* `templateRule` is used to get rule meta data, `customRule` is used to get parameter values */}
+ {/* it's expected to pass the same rule to both parameters */}
+ <CustomRuleButton
+ customRule={ruleDetails}
+ onDone={this.handleRuleChange}
+ organization={organization}
+ templateRule={ruleDetails}>
+ {({ onClick }) => (
+ <button
+ className="js-edit-custom"
+ id="coding-rules-detail-custom-rule-change"
+ onClick={onClick}>
+ {translate('edit')}
+ </button>
+ )}
+ </CustomRuleButton>
+ <ConfirmButton
+ confirmButtonText={translate('delete')}
+ isDestructive={true}
+ modalBody={translateWithParameters(
+ 'coding_rules.delete.custom.confirm',
+ ruleDetails.name
+ )}
+ modalHeader={translate('coding_rules.delete_rule')}
+ onConfirm={this.handleDelete}>
+ {({ onClick }) => (
+ <button
+ className="button-red spacer-left js-delete"
+ id="coding-rules-detail-rule-delete"
+ onClick={onClick}>
+ {translate('delete')}
+ </button>
+ )}
+ </ConfirmButton>
+ </div>
+ )}
+
+ {ruleDetails.isTemplate && (
+ <RuleDetailsCustomRules
+ canChange={allowCustomRules && canWrite}
+ organization={organization}
+ ruleDetails={ruleDetails}
+ />
+ )}
+
+ {!ruleDetails.isTemplate && (
+ <RuleDetailsProfiles
+ activations={this.state.actives}
+ canWrite={canWrite}
+ onActivate={this.handleActivate}
+ onDeactivate={this.handleDeactivate}
+ organization={organization}
+ referencedProfiles={referencedProfiles}
+ ruleDetails={ruleDetails}
+ />
+ )}
+
+ {!ruleDetails.isTemplate && (
+ <RuleDetailsIssues organization={organization} ruleKey={ruleDetails.key} />
+ )}
+ </DeferredSpinner>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { Link } from 'react-router';
+import { sortBy } from 'lodash';
+import ConfirmButton from './ConfirmButton';
+import CustomRuleButton from './CustomRuleButton';
+import { searchRules, deleteRule } from '../../../api/rules';
+import { Rule, RuleDetails } from '../../../app/types';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import SeverityHelper from '../../../components/shared/SeverityHelper';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { getRuleUrl } from '../../../helpers/urls';
+
+interface Props {
+ canChange?: boolean;
+ organization: string | undefined;
+ ruleDetails: RuleDetails;
+}
+
+interface State {
+ loading: boolean;
+ rules?: Rule[];
+}
+
+export default class RuleDetailsCustomRules extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { loading: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchRules();
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ if (prevProps.ruleDetails.key !== this.props.ruleDetails.key) {
+ this.fetchRules();
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchRules = () => {
+ this.setState({ loading: true });
+ searchRules({
+ f: 'name,severity,params',
+ organization: this.props.organization,
+ /* eslint-disable camelcase */
+ template_key: this.props.ruleDetails.key
+ /* eslint-enable camelcase */
+ }).then(
+ ({ rules }) => {
+ if (this.mounted) {
+ this.setState({ rules, loading: false });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ handleRuleCreate = (newRuleDetails: RuleDetails) => {
+ if (this.mounted) {
+ this.setState(({ rules = [] }: State) => ({
+ rules: [...rules, newRuleDetails]
+ }));
+ }
+ };
+
+ handleRuleDelete = (ruleKey: string) => {
+ return deleteRule({ key: ruleKey, organization: this.props.organization }).then(() => {
+ if (this.mounted) {
+ this.setState(({ rules = [] }) => ({
+ rules: rules.filter(rule => rule.key !== ruleKey)
+ }));
+ }
+ });
+ };
+
+ renderRule = (rule: Rule) => (
+ <tr key={rule.key} data-rule={rule.key}>
+ <td className="coding-rules-detail-list-name">
+ <Link to={getRuleUrl(rule.key, this.props.organization)}>{rule.name}</Link>
+ </td>
+
+ <td className="coding-rules-detail-list-severity">
+ <SeverityHelper severity={rule.severity} />
+ </td>
+
+ <td className="coding-rules-detail-list-parameters">
+ {rule.params &&
+ rule.params.filter(param => param.defaultValue).map(param => (
+ <div className="coding-rules-detail-list-parameter" key={param.key}>
+ <span className="key">{param.key}</span>
+ <span className="sep">: </span>
+ <span className="value" title={param.defaultValue}>
+ {param.defaultValue}
+ </span>
+ </div>
+ ))}
+ </td>
+
+ {this.props.canChange && (
+ <td className="coding-rules-detail-list-actions">
+ <ConfirmButton
+ confirmButtonText={translate('delete')}
+ confirmData={rule.key}
+ isDestructive={true}
+ modalBody={translateWithParameters('coding_rules.delete.custom.confirm', rule.name)}
+ modalHeader={translate('coding_rules.delete_rule')}
+ onConfirm={this.handleRuleDelete}>
+ {({ onClick }) => (
+ <button className="button-red js-delete-custom-rule" onClick={onClick}>
+ {translate('delete')}
+ </button>
+ )}
+ </ConfirmButton>
+ </td>
+ )}
+ </tr>
+ );
+
+ render() {
+ const { loading, rules = [] } = this.state;
+
+ return (
+ <div className="js-rule-custom-rules coding-rule-section">
+ <div className="coding-rules-detail-custom-rules-section">
+ <div className="coding-rule-section-separator" />
+
+ <h3 className="coding-rules-detail-title">{translate('coding_rules.custom_rules')}</h3>
+
+ {this.props.canChange && (
+ <CustomRuleButton
+ onDone={this.handleRuleCreate}
+ organization={this.props.organization}
+ templateRule={this.props.ruleDetails}>
+ {({ onClick }) => (
+ <button className="js-create-custom-rule spacer-left" onClick={onClick}>
+ {translate('coding_rules.create')}
+ </button>
+ )}
+ </CustomRuleButton>
+ )}
+
+ <DeferredSpinner loading={loading}>
+ {rules.length > 0 && (
+ <table id="coding-rules-detail-custom-rules" className="coding-rules-detail-list">
+ <tbody>{sortBy(rules, rule => rule.name).map(this.renderRule)}</tbody>
+ </table>
+ )}
+ </DeferredSpinner>
+ </div>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import RemoveExtendedDescriptionModal from './RemoveExtendedDescriptionModal';
+import { updateRule } from '../../../api/rules';
+import { RuleDetails } from '../../../app/types';
+import MarkdownTips from '../../../components/common/MarkdownTips';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ canWrite: boolean | undefined;
+ onChange: (newRuleDetails: RuleDetails) => void;
+ organization: string | undefined;
+ ruleDetails: RuleDetails;
+}
+
+interface State {
+ description: string;
+ descriptionForm: boolean;
+ removeDescriptionModal: boolean;
+ submitting: boolean;
+}
+
+export default class RuleDetailsDescription extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = {
+ description: '',
+ descriptionForm: false,
+ submitting: false,
+ removeDescriptionModal: false
+ };
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleDescriptionChange = (event: React.SyntheticEvent<HTMLTextAreaElement>) =>
+ this.setState({ description: event.currentTarget.value });
+
+ handleCancelClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.setState({ descriptionForm: false });
+ };
+
+ handleSaveClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.updateDescription(this.state.description);
+ };
+
+ handleRemoveDescriptionClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.setState({ removeDescriptionModal: true });
+ };
+
+ handleCancelRemoving = () => this.setState({ removeDescriptionModal: false });
+
+ handleConfirmRemoving = () => {
+ this.setState({ removeDescriptionModal: false });
+ this.updateDescription('');
+ };
+
+ updateDescription = (text: string) => {
+ this.setState({ submitting: true });
+
+ updateRule({
+ key: this.props.ruleDetails.key,
+ /* eslint-disable camelcase */
+ markdown_note: text,
+ /* eslint-enable camelcase*/
+ organization: this.props.organization
+ }).then(
+ ruleDetails => {
+ this.props.onChange(ruleDetails);
+ if (this.mounted) {
+ this.setState({ submitting: false, descriptionForm: false });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ submitting: false });
+ }
+ }
+ );
+ };
+
+ handleExtendDescriptionClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.setState({
+ // set description` to the current `mdNote` each time the form is open
+ description: this.props.ruleDetails.mdNote || '',
+ descriptionForm: true
+ });
+ };
+
+ renderDescription = () => (
+ <div id="coding-rules-detail-description-extra">
+ {this.props.ruleDetails.htmlNote !== undefined && (
+ <div
+ className="rule-desc spacer-bottom markdown"
+ dangerouslySetInnerHTML={{ __html: this.props.ruleDetails.htmlNote }}
+ />
+ )}
+ {this.props.canWrite && (
+ <button
+ id="coding-rules-detail-extend-description"
+ onClick={this.handleExtendDescriptionClick}>
+ {translate('coding_rules.extend_description')}
+ </button>
+ )}
+ </div>
+ );
+
+ renderForm = () => (
+ <div className="coding-rules-detail-extend-description-form">
+ <table className="width100">
+ <tbody>
+ <tr>
+ <td className="width100" colSpan={2}>
+ <textarea
+ autoFocus={true}
+ id="coding-rules-detail-extend-description-text"
+ onChange={this.handleDescriptionChange}
+ rows={4}
+ style={{ width: '100%', marginBottom: 4 }}
+ value={this.state.description}
+ />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <button
+ disabled={this.state.submitting}
+ id="coding-rules-detail-extend-description-submit"
+ onClick={this.handleSaveClick}>
+ {translate('save')}
+ </button>
+ {this.props.ruleDetails.mdNote !== undefined && (
+ <>
+ <button
+ className="button-red spacer-left"
+ disabled={this.state.submitting}
+ id="coding-rules-detail-extend-description-remove"
+ onClick={this.handleRemoveDescriptionClick}>
+ {translate('remove')}
+ </button>
+ {this.state.removeDescriptionModal && (
+ <RemoveExtendedDescriptionModal
+ onCancel={this.handleCancelRemoving}
+ onSubmit={this.handleConfirmRemoving}
+ />
+ )}
+ </>
+ )}
+ <button
+ className="spacer-left button-link"
+ disabled={this.state.submitting}
+ id="coding-rules-detail-extend-description-cancel"
+ onClick={this.handleCancelClick}>
+ {translate('cancel')}
+ </button>
+ {this.state.submitting && <i className="spinner spacer-left" />}
+ </td>
+ <td className="text-right">
+ <MarkdownTips />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ );
+
+ render() {
+ const { ruleDetails } = this.props;
+
+ return (
+ <div className="js-rule-description">
+ <div
+ className="coding-rules-detail-description rule-desc markdown"
+ dangerouslySetInnerHTML={{ __html: ruleDetails.htmlDesc || '' }}
+ />
+
+ {!ruleDetails.templateKey && (
+ <div className="coding-rules-detail-description coding-rules-detail-description-extra">
+ {!this.state.descriptionForm && this.renderDescription()}
+ {this.state.descriptionForm && this.props.canWrite && this.renderForm()}
+ </div>
+ )}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { Link } from 'react-router';
+import { getFacet } from '../../../api/issues';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import { translate } from '../../../helpers/l10n';
+import { formatMeasure } from '../../../helpers/measures';
+import { getIssuesUrl } from '../../../helpers/urls';
+
+interface Props {
+ organization: string | undefined;
+ ruleKey: string;
+}
+
+interface Project {
+ count: number;
+ id: string;
+ key: string;
+ name: string;
+}
+
+interface State {
+ loading: boolean;
+ projects?: Project[];
+ total?: number;
+}
+
+export default class RuleDetailsIssues extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { loading: true };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchIssues();
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ if (prevProps.ruleKey !== this.props.ruleKey) {
+ this.fetchIssues();
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchIssues = () => {
+ this.setState({ loading: true });
+ getFacet(
+ { organization: this.props.organization, rules: this.props.ruleKey, resolved: false },
+ 'projectUuids'
+ ).then(
+ ({ facet, response }) => {
+ if (this.mounted) {
+ const { components = [], paging } = response;
+ const projects = [];
+ for (const item of facet) {
+ const project = components.find(component => component.uuid === item.val);
+ if (project) {
+ projects.push({
+ count: item.count,
+ id: item.val,
+ key: project.key,
+ name: project.name
+ });
+ }
+ }
+ this.setState({ projects, loading: false, total: paging.total });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ renderTotal = () => {
+ const { total } = this.state;
+ if (total === undefined) {
+ return null;
+ }
+ const path = getIssuesUrl(
+ { resolved: 'false', rules: this.props.ruleKey },
+ this.props.organization
+ );
+ return (
+ <>
+ {' ('}
+ <Link to={path}>{total}</Link>
+ {')'}
+ </>
+ );
+ };
+
+ renderProject = (project: Project) => {
+ const path = getIssuesUrl(
+ { projectUuids: project.id, resolved: 'false', rules: this.props.ruleKey },
+ this.props.organization
+ );
+ return (
+ <tr key={project.key}>
+ <td className="coding-rules-detail-list-name">{project.name}</td>
+ <td className="coding-rules-detail-list-parameters">
+ <Link to={path}>{formatMeasure(project.count, 'INT')}</Link>
+ </td>
+ </tr>
+ );
+ };
+
+ render() {
+ const { loading, projects = [] } = this.state;
+
+ return (
+ <div className="js-rule-issues coding-rule-section">
+ <div className="coding-rule-section-separator" />
+
+ <DeferredSpinner loading={loading}>
+ <h3 className="coding-rules-detail-title">
+ {translate('coding_rules.issues')}
+ {this.renderTotal()}
+ </h3>
+
+ {projects.length > 0 && (
+ <table className="coding-rules-detail-list coding-rules-most-violated-projects">
+ <tbody>
+ <tr>
+ <td className="coding-rules-detail-list-name" colSpan={2}>
+ {translate('coding_rules.most_violating_projects')}
+ </td>
+ </tr>
+ {projects.map(this.renderProject)}
+ </tbody>
+ </table>
+ )}
+ </DeferredSpinner>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { Link } from 'react-router';
+import { Query } from '../query';
+import { RuleDetails } from '../../../app/types';
+import { getRuleUrl } from '../../../helpers/urls';
+import LinkIcon from '../../../components/icons-components/LinkIcon';
+import SimilarRulesFilter from './SimilarRulesFilter';
+import Tooltip from '../../../components/controls/Tooltip';
+import { translate } from '../../../helpers/l10n';
+import IssueTypeIcon from '../../../components/ui/IssueTypeIcon';
+import SeverityHelper from '../../../components/shared/SeverityHelper';
+import BubblePopupHelper from '../../../components/common/BubblePopupHelper';
+import RuleDetailsTagsPopup from './RuleDetailsTagsPopup';
+import TagsList from '../../../components/tags/TagsList';
+import DateFormatter from '../../../components/intl/DateFormatter';
+
+interface Props {
+ canWrite: boolean | undefined;
+ onFilterChange: (changes: Partial<Query>) => void;
+ onTagsChange: (tags: string[]) => void;
+ organization: string | undefined;
+ referencedRepositories: { [repository: string]: { key: string; language: string; name: string } };
+ ruleDetails: RuleDetails;
+}
+
+interface State {
+ tagsPopup: boolean;
+}
+
+export default class RuleDetailsMeta extends React.PureComponent<Props, State> {
+ state: State = { tagsPopup: false };
+
+ handleTagsClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.setState(state => ({ tagsPopup: !state.tagsPopup }));
+ };
+
+ handleTagsPopupToggle = (show: boolean) => this.setState({ tagsPopup: show });
+
+ renderType = () => {
+ const { ruleDetails } = this.props;
+ return (
+ <Tooltip overlay={translate('coding_rules.type.tooltip', ruleDetails.type)}>
+ <li className="coding-rules-detail-property" data-meta="type">
+ <IssueTypeIcon className="little-spacer-right" query={ruleDetails.type} />
+ {translate('issue.type', ruleDetails.type)}
+ </li>
+ </Tooltip>
+ );
+ };
+
+ renderSeverity = () => (
+ <Tooltip overlay={translate('default_severity')}>
+ <li className="coding-rules-detail-property" data-meta="severity">
+ <SeverityHelper severity={this.props.ruleDetails.severity} />
+ </li>
+ </Tooltip>
+ );
+
+ renderStatus = () => {
+ const { ruleDetails } = this.props;
+ if (ruleDetails.status === 'READY') {
+ return null;
+ }
+ return (
+ <Tooltip overlay={translate('status')}>
+ <li className="coding-rules-detail-property" data-meta="status">
+ <span className="badge badge-normal-size badge-danger-light">
+ {translate('rules.status', ruleDetails.status)}
+ </span>
+ </li>
+ </Tooltip>
+ );
+ };
+
+ renderTags = () => {
+ const { canWrite, ruleDetails } = this.props;
+ const { sysTags = [], tags = [] } = ruleDetails;
+ const allTags = [...sysTags, ...tags];
+ return (
+ <li className="coding-rules-detail-property" data-meta="tags">
+ {this.props.canWrite ? (
+ <BubblePopupHelper
+ isOpen={this.state.tagsPopup}
+ position="bottomleft"
+ popup={
+ <RuleDetailsTagsPopup
+ organization={this.props.organization}
+ setTags={this.props.onTagsChange}
+ sysTags={sysTags}
+ tags={tags}
+ />
+ }
+ togglePopup={this.handleTagsPopupToggle}>
+ <button className="button-link" onClick={this.handleTagsClick}>
+ <TagsList
+ allowUpdate={canWrite}
+ tags={allTags.length > 0 ? allTags : [translate('coding_rules.no_tags')]}
+ />
+ </button>
+ </BubblePopupHelper>
+ ) : (
+ <TagsList
+ allowUpdate={canWrite}
+ tags={allTags.length > 0 ? allTags : [translate('coding_rules.no_tags')]}
+ />
+ )}
+ </li>
+ );
+ };
+
+ renderCreationDate = () => (
+ <li className="coding-rules-detail-property" data-meta="available-since">
+ {translate('coding_rules.available_since')}{' '}
+ <DateFormatter date={this.props.ruleDetails.createdAt} />
+ </li>
+ );
+
+ renderRepository = () => {
+ const { referencedRepositories, ruleDetails } = this.props;
+ const repository = referencedRepositories[ruleDetails.repo];
+ if (!repository) {
+ return null;
+ }
+ return (
+ <Tooltip overlay={translate('coding_rules.repository_language')}>
+ <li className="coding-rules-detail-property" data-meta="repository">
+ {repository.name} ({ruleDetails.langName})
+ </li>
+ </Tooltip>
+ );
+ };
+
+ renderTemplate = () => {
+ if (!this.props.ruleDetails.isTemplate) {
+ return null;
+ }
+ return (
+ <Tooltip overlay={translate('coding_rules.rule_template.title')}>
+ <li className="coding-rules-detail-property">{translate('coding_rules.rule_template')}</li>
+ </Tooltip>
+ );
+ };
+
+ renderParentTemplate = () => {
+ const { ruleDetails } = this.props;
+ if (!ruleDetails.templateKey) {
+ return null;
+ }
+ return (
+ <Tooltip overlay={translate('coding_rules.custom_rule.title')}>
+ <li className="coding-rules-detail-property">
+ {translate('coding_rules.custom_rule')}
+ {' ('}
+ <Link to={getRuleUrl(ruleDetails.templateKey, this.props.organization)}>
+ {translate('coding_rules.show_template')}
+ </Link>
+ {')'}
+ </li>
+ </Tooltip>
+ );
+ };
+
+ renderRemediation = () => {
+ const { ruleDetails } = this.props;
+ if (!ruleDetails.debtRemFnType) {
+ return null;
+ }
+ return (
+ <Tooltip overlay={translate('coding_rules.remediation_function')}>
+ <li className="coding-rules-detail-property" data-meta="remediation-function">
+ {translate('coding_rules.remediation_function', ruleDetails.debtRemFnType)}
+ {':'}
+ {ruleDetails.debtRemFnOffset !== undefined && ` ${ruleDetails.debtRemFnOffset}`}
+ {ruleDetails.debtRemFnCoeff !== undefined && ` +${ruleDetails.debtRemFnCoeff}`}
+ {ruleDetails.effortToFixDescription !== undefined &&
+ ` ${ruleDetails.effortToFixDescription}`}
+ </li>
+ </Tooltip>
+ );
+ };
+
+ render() {
+ const { ruleDetails } = this.props;
+ return (
+ <div className="js-rule-meta">
+ <header className="page-header">
+ <div className="pull-right">
+ <span className="note text-middle">{ruleDetails.key}</span>
+ <Link
+ className="coding-rules-detail-permalink link-no-underline spacer-left text-middle"
+ to={getRuleUrl(ruleDetails.key, this.props.organization)}>
+ <LinkIcon />
+ </Link>
+ <SimilarRulesFilter onFilterChange={this.props.onFilterChange} rule={ruleDetails} />
+ </div>
+ <h3 className="page-title coding-rules-detail-header">
+ <big>{ruleDetails.name}</big>
+ </h3>
+ </header>
+
+ <ul className="coding-rules-detail-properties">
+ {this.renderType()}
+ {this.renderSeverity()}
+ {this.renderStatus()}
+ {this.renderTags()}
+ {this.renderCreationDate()}
+ {this.renderRepository()}
+ {this.renderTemplate()}
+ {this.renderParentTemplate()}
+ {this.renderRemediation()}
+ </ul>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { RuleParameter } from '../../../app/types';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ params: RuleParameter[];
+}
+
+export default class RuleDetailsParameters extends React.PureComponent<Props> {
+ renderParameter = (param: RuleParameter) => (
+ <tr className="coding-rules-detail-parameter" key={param.key}>
+ <td className="coding-rules-detail-parameter-name">{param.key}</td>
+ <td className="coding-rules-detail-parameter-description">
+ <p dangerouslySetInnerHTML={{ __html: param.htmlDesc || '' }} />
+ {param.defaultValue !== undefined && (
+ <div className="note spacer-top">
+ {translate('coding_rules.parameters.default_value')}
+ <br />
+ <span className="coding-rules-detail-parameter-value">{param.defaultValue}</span>
+ </div>
+ )}
+ </td>
+ </tr>
+ );
+
+ render() {
+ return (
+ <div className="js-rule-parameters">
+ <h3 className="coding-rules-detail-title">{translate('coding_rules.parameters')}</h3>
+ <table className="coding-rules-detail-parameters">
+ <tbody>{this.props.params.map(this.renderParameter)}</tbody>
+ </table>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { filter } from 'lodash';
+import { Link } from 'react-router';
+import ActivationButton from './ActivationButton';
+import ConfirmButton from './ConfirmButton';
+import RuleInheritanceIcon from './RuleInheritanceIcon';
+import { Profile, deactivateRule, activateRule } from '../../../api/quality-profiles';
+import { RuleActivation, RuleDetails, RuleInheritance } from '../../../app/types';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { getQualityProfileUrl } from '../../../helpers/urls';
+import BuiltInQualityProfileBadge from '../../quality-profiles/components/BuiltInQualityProfileBadge';
+import Tooltip from '../../../components/controls/Tooltip';
+import SeverityHelper from '../../../components/shared/SeverityHelper';
+
+interface Props {
+ activations: RuleActivation[] | undefined;
+ canWrite: boolean | undefined;
+ onActivate: () => Promise<void>;
+ onDeactivate: () => Promise<void>;
+ organization: string | undefined;
+ referencedProfiles: { [profile: string]: Profile };
+ ruleDetails: RuleDetails;
+}
+
+interface State {
+ loading: boolean;
+}
+
+export default class RuleDetailsProfiles extends React.PureComponent<Props, State> {
+ mounted: boolean;
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchProfiles();
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ if (prevProps.ruleDetails.key !== this.props.ruleDetails.key) {
+ this.fetchProfiles();
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchProfiles = () => this.setState({ loading: true });
+
+ handleActivate = () => this.props.onActivate();
+
+ handleDeactivate = (key?: string) => {
+ if (key) {
+ deactivateRule({
+ key,
+ organization: this.props.organization,
+ rule: this.props.ruleDetails.key
+ }).then(this.props.onDeactivate, () => {});
+ }
+ };
+
+ handleRevert = (key?: string) => {
+ if (key) {
+ activateRule({
+ key,
+ organization: this.props.organization,
+ rule: this.props.ruleDetails.key,
+ reset: true
+ }).then(this.props.onActivate, () => {});
+ }
+ };
+
+ renderInheritedProfile = (activation: RuleActivation, profile: Profile) => {
+ if (!profile.parentName) {
+ return null;
+ }
+ const profilePath = getQualityProfileUrl(
+ profile.parentName,
+ profile.language,
+ this.props.organization
+ );
+ return (
+ <div className="coding-rules-detail-quality-profile-inheritance">
+ {(activation.inherit === RuleInheritance.Overridden ||
+ activation.inherit === RuleInheritance.Inherited) && (
+ <>
+ <RuleInheritanceIcon
+ inheritance={activation.inherit}
+ parentProfileName={profile.parentName}
+ profileName={profile.name}
+ />
+ <Link className="link-base-color spacer-left" to={profilePath}>
+ {profile.parentName}
+ </Link>
+ </>
+ )}
+ </div>
+ );
+ };
+
+ renderSeverity = (activation: RuleActivation, parentActivation?: RuleActivation) => (
+ <td className="coding-rules-detail-quality-profile-severity">
+ <Tooltip overlay={translate('coding_rules.activation_severity')}>
+ <span>
+ <SeverityHelper severity={activation.severity} />
+ </span>
+ </Tooltip>
+ {parentActivation !== undefined &&
+ activation.severity !== parentActivation.severity && (
+ <div className="coding-rules-detail-quality-profile-inheritance">
+ {translate('coding_rules.original')} {translate('severity', parentActivation.severity)}
+ </div>
+ )}
+ </td>
+ );
+
+ renderParameter = (param: { key: string; value: string }, parentActivation?: RuleActivation) => {
+ const originalParam =
+ parentActivation && parentActivation.params.find(p => p.key === param.key);
+ const originalValue = originalParam && originalParam.value;
+
+ return (
+ <div className="coding-rules-detail-quality-profile-parameter" key={param.key}>
+ <span className="key">{param.key}</span>
+ <span className="sep">{': '}</span>
+ <span className="value" title={param.value}>
+ {param.value}
+ </span>
+ {parentActivation &&
+ param.value !== originalValue && (
+ <div className="coding-rules-detail-quality-profile-inheritance">
+ {translate('coding_rules.original')} <span className="value">{originalValue}</span>
+ </div>
+ )}
+ </div>
+ );
+ };
+
+ renderParameters = (activation: RuleActivation, parentActivation?: RuleActivation) => (
+ <td className="coding-rules-detail-quality-profile-parameters">
+ {activation.params.map(param => this.renderParameter(param, parentActivation))}
+ </td>
+ );
+
+ renderActions = (activation: RuleActivation, profile: Profile) => {
+ const canEdit = profile.actions && profile.actions.edit && !profile.isBuiltIn;
+ const { ruleDetails } = this.props;
+ const hasParent = activation.inherit !== RuleInheritance.NotInherited && profile.parentKey;
+ return (
+ <td className="coding-rules-detail-quality-profile-actions">
+ {canEdit && (
+ <>
+ {!ruleDetails.isTemplate && (
+ <ActivationButton
+ activation={activation}
+ buttonText={translate('change_verb')}
+ className="coding-rules-detail-quality-profile-change"
+ modalHeader={translate('coding_rules.change_details')}
+ onDone={this.handleActivate}
+ organization={this.props.organization}
+ profiles={[profile]}
+ rule={ruleDetails}
+ />
+ )}
+ {hasParent ? (
+ activation.inherit === RuleInheritance.Overridden &&
+ profile.parentName && (
+ <ConfirmButton
+ confirmButtonText={translate('yes')}
+ confirmData={profile.key}
+ modalBody={translateWithParameters(
+ 'coding_rules.revert_to_parent_definition.confirm',
+ profile.parentName
+ )}
+ modalHeader={translate('coding_rules.revert_to_parent_definition')}
+ onConfirm={this.handleRevert}>
+ {({ onClick }) => (
+ <button
+ className="coding-rules-detail-quality-profile-revert button-red spacer-left"
+ onClick={onClick}>
+ {translate('coding_rules.revert_to_parent_definition')}
+ </button>
+ )}
+ </ConfirmButton>
+ )
+ ) : (
+ <ConfirmButton
+ confirmButtonText={translate('yes')}
+ confirmData={profile.key}
+ modalBody={translate('coding_rules.deactivate.confirm')}
+ modalHeader={translate('coding_rules.deactivate')}
+ onConfirm={this.handleDeactivate}>
+ {({ onClick }) => (
+ <button
+ className="coding-rules-detail-quality-profile-deactivate button-red spacer-left"
+ onClick={onClick}>
+ {translate('coding_rules.deactivate')}
+ </button>
+ )}
+ </ConfirmButton>
+ )}
+ </>
+ )}
+ </td>
+ );
+ };
+
+ renderActivation = (activation: RuleActivation) => {
+ const { activations = [], ruleDetails } = this.props;
+ const profile = this.props.referencedProfiles[activation.qProfile];
+ if (!profile) {
+ return null;
+ }
+
+ const parentActivation = activations.find(x => x.qProfile === profile.parentKey);
+
+ return (
+ <tr key={profile.key} data-profile={profile.key}>
+ <td className="coding-rules-detail-quality-profile-name">
+ <Link to={getQualityProfileUrl(profile.name, profile.language, this.props.organization)}>
+ {profile.name}
+ </Link>
+ {profile.isBuiltIn && <BuiltInQualityProfileBadge className="spacer-left" />}
+ {this.renderInheritedProfile(activation, profile)}
+ </td>
+
+ {this.renderSeverity(activation, parentActivation)}
+ {!ruleDetails.templateKey && this.renderParameters(activation, parentActivation)}
+ {this.renderActions(activation, profile)}
+ </tr>
+ );
+ };
+
+ render() {
+ const { activations = [], referencedProfiles, ruleDetails } = this.props;
+ const canActivate = Object.values(referencedProfiles).some(profile =>
+ Boolean(profile.actions && profile.actions.edit && profile.language === ruleDetails.lang)
+ );
+
+ return (
+ <div className="js-rule-profiles coding-rule-section">
+ <div className="coding-rules-detail-quality-profiles-section">
+ <div className="coding-rule-section-separator" />
+
+ <h3 className="coding-rules-detail-title">
+ {translate('coding_rules.quality_profiles')}
+ </h3>
+
+ {canActivate && (
+ <ActivationButton
+ buttonText={translate('coding_rules.activate')}
+ className="coding-rules-quality-profile-activate spacer-left"
+ modalHeader={translate('coding_rules.activate_in_quality_profile')}
+ onDone={this.handleActivate}
+ organization={this.props.organization}
+ profiles={filter(
+ this.props.referencedProfiles,
+ profile => !activations.find(activation => activation.qProfile === profile.key)
+ )}
+ rule={ruleDetails}
+ />
+ )}
+
+ {activations.length > 0 && (
+ <table
+ id="coding-rules-detail-quality-profiles"
+ className="coding-rules-detail-quality-profiles width100">
+ <tbody>{activations.map(this.renderActivation)}</tbody>
+ </table>
+ )}
+ </div>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { without, uniq } from 'lodash';
+import TagsSelector from '../../../components/tags/TagsSelector';
+import { getRuleTags } from '../../../api/rules';
+import { BubblePopupPosition } from '../../../components/common/BubblePopup';
+
+interface Props {
+ organization: string | undefined;
+ popupPosition?: BubblePopupPosition;
+ setTags: (tags: string[]) => void;
+ sysTags: string[];
+ tags: string[];
+}
+
+interface State {
+ searchResult: any[];
+}
+
+const LIST_SIZE = 10;
+
+export default class RuleDetailsTagsPopup extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { searchResult: [] };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.onSearch('');
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ onSearch = (query: string) => {
+ getRuleTags({
+ q: query,
+ ps: Math.min(this.props.tags.length + LIST_SIZE, 100),
+ organization: this.props.organization
+ }).then(
+ tags => {
+ if (this.mounted) {
+ // systems tags can not be unset, don't display them in the results
+ this.setState({ searchResult: without(tags, ...this.props.sysTags) });
+ }
+ },
+ () => {}
+ );
+ };
+
+ onSelect = (tag: string) => {
+ this.props.setTags(uniq([...this.props.tags, tag]));
+ };
+
+ onUnselect = (tag: string) => {
+ this.props.setTags(without(this.props.tags, tag));
+ };
+
+ render() {
+ return (
+ <TagsSelector
+ position={this.props.popupPosition || {}}
+ tags={this.state.searchResult}
+ selectedTags={this.props.tags}
+ listSize={LIST_SIZE}
+ onSearch={this.onSearch}
+ onSelect={this.onSelect}
+ onUnselect={this.onUnselect}
+ />
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { RuleInheritance } from '../../../app/types';
+import { translateWithParameters } from '../../../helpers/l10n';
+
+interface Props {
+ inheritance: RuleInheritance.Inherited | RuleInheritance.Overridden;
+ parentProfileName: string;
+ profileName: string;
+}
+
+export default function RuleInheritanceIcon(props: Props) {
+ return (
+ <i
+ className={classNames('icon-inheritance', {
+ 'icon-inheritance-overridden': props.inheritance === RuleInheritance.Overridden
+ })}
+ title={translateWithParameters(
+ 'coding_rules.overrides',
+ props.profileName,
+ props.parentProfileName
+ )}
+ />
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { Link } from 'react-router';
+import { Activation, Query } from '../query';
+import ActivationButton from './ActivationButton';
+import ConfirmButton from './ConfirmButton';
+import SimilarRulesFilter from './SimilarRulesFilter';
+import { Profile, deactivateRule } from '../../../api/quality-profiles';
+import { Rule, RuleInheritance } from '../../../app/types';
+import Tooltip from '../../../components/controls/Tooltip';
+import SeverityIcon from '../../../components/shared/SeverityIcon';
+import IssueTypeIcon from '../../../components/ui/IssueTypeIcon';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+
+interface Props {
+ activation?: Activation;
+ onActivate: (profile: string, rule: string, activation: Activation) => void;
+ onDeactivate: (profile: string, rule: string) => void;
+ onFilterChange: (changes: Partial<Query>) => void;
+ organization: string | undefined;
+ path: { pathname: string; query: { [x: string]: any } };
+ rule: Rule;
+ selected: boolean;
+ selectedProfile?: Profile;
+}
+
+export default class RuleListItem extends React.PureComponent<Props> {
+ handleDeactivate = () => {
+ if (this.props.selectedProfile) {
+ const data = {
+ key: this.props.selectedProfile.key,
+ organization: this.props.organization,
+ rule: this.props.rule.key
+ };
+ deactivateRule(data).then(() => this.props.onDeactivate(data.key, data.rule), () => {});
+ }
+ };
+
+ handleActivate = (severity: string) => {
+ if (this.props.selectedProfile) {
+ this.props.onActivate(this.props.selectedProfile.key, this.props.rule.key, {
+ severity,
+ inherit: RuleInheritance.NotInherited
+ });
+ }
+ return Promise.resolve();
+ };
+
+ renderActivation = () => {
+ const { activation, selectedProfile } = this.props;
+ if (!activation) {
+ return null;
+ }
+
+ return (
+ <td className="coding-rule-table-meta-cell coding-rule-activation">
+ <SeverityIcon severity={activation.severity} />
+ {selectedProfile &&
+ selectedProfile.parentName && (
+ <>
+ {activation.inherit === RuleInheritance.Overridden && (
+ <Tooltip
+ overlay={translateWithParameters(
+ 'coding_rules.overrides',
+ selectedProfile.name,
+ selectedProfile.parentName
+ )}>
+ <i className="little-spacer-left icon-inheritance icon-inheritance-overridden" />
+ </Tooltip>
+ )}
+ {activation.inherit === RuleInheritance.Inherited && (
+ <Tooltip
+ overlay={translateWithParameters(
+ 'coding_rules.inherits',
+ selectedProfile.name,
+ selectedProfile.parentName
+ )}>
+ <i className="little-spacer-left icon-inheritance" />
+ </Tooltip>
+ )}
+ </>
+ )}
+ </td>
+ );
+ };
+
+ renderActions = () => {
+ const { activation, rule, selectedProfile } = this.props;
+ if (!selectedProfile) {
+ return null;
+ }
+
+ const canEdit = selectedProfile.actions && selectedProfile.actions.edit;
+ if (!canEdit || selectedProfile.isBuiltIn) {
+ return null;
+ }
+
+ return (
+ <td className="coding-rule-table-meta-cell coding-rule-activation-actions">
+ {activation
+ ? this.renderDeactivateButton(activation.inherit)
+ : !rule.isTemplate && (
+ <ActivationButton
+ buttonText={translate('coding_rules.activate')}
+ className="coding-rules-detail-quality-profile-activate"
+ modalHeader={translate('coding_rules.activate_in_quality_profile')}
+ onDone={this.handleActivate}
+ organization={this.props.organization}
+ profiles={[selectedProfile]}
+ rule={rule}
+ />
+ )}
+ </td>
+ );
+ };
+
+ renderDeactivateButton = (inherit: string) => {
+ return inherit === 'NONE' ? (
+ <ConfirmButton
+ confirmButtonText={translate('yes')}
+ modalBody={translate('coding_rules.deactivate.confirm')}
+ modalHeader={translate('coding_rules.deactivate')}
+ onConfirm={this.handleDeactivate}>
+ {({ onClick }) => (
+ <button
+ className="coding-rules-detail-quality-profile-deactivate button-red"
+ onClick={onClick}>
+ {translate('coding_rules.deactivate')}
+ </button>
+ )}
+ </ConfirmButton>
+ ) : (
+ <Tooltip overlay={translate('coding_rules.can_not_deactivate')} placement="left">
+ <button className="coding-rules-detail-quality-profile-deactivate button-red disabled">
+ {translate('coding_rules.deactivate')}
+ </button>
+ </Tooltip>
+ );
+ };
+
+ render() {
+ const { rule, selected } = this.props;
+ const allTags = [...(rule.tags || []), ...(rule.sysTags || [])];
+ return (
+ <div className={classNames('coding-rule', { selected })} data-rule={rule.key}>
+ <table className="coding-rule-table">
+ <tbody>
+ <tr>
+ {this.renderActivation()}
+
+ <td>
+ <div className="coding-rule-title">
+ <Link className="link-no-underline" to={this.props.path}>
+ {rule.name}
+ </Link>
+ {rule.isTemplate && (
+ <Tooltip overlay={translate('coding_rules.rule_template.title')}>
+ <span className="outline-badge spacer-left">
+ {translate('coding_rules.rule_template')}
+ </span>
+ </Tooltip>
+ )}
+ </div>
+ </td>
+
+ <td className="coding-rule-table-meta-cell">
+ <div className="coding-rule-meta">
+ {rule.status !== 'READY' && (
+ <span className="spacer-left badge badge-normal-size badge-danger-light">
+ {translate('rules.status', rule.status)}
+ </span>
+ )}
+ <span className="spacer-left note">{rule.langName}</span>
+ <Tooltip overlay={translate('coding_rules.type.tooltip', rule.type)}>
+ <span className="spacer-left note">
+ <IssueTypeIcon className="little-spacer-right" query={rule.type} />
+ {translate('issue.type', rule.type)}
+ </span>
+ </Tooltip>
+ {allTags.length > 0 && (
+ <span className="spacer-left">
+ <i className="icon-tags little-spacer-right" />
+ <span className="note">{allTags.join(', ')}</span>
+ </span>
+ )}
+ <SimilarRulesFilter onFilterChange={this.props.onFilterChange} rule={rule} />
+ </div>
+ </td>
+
+ {this.renderActions()}
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { Query } from '../query';
+import { Rule } from '../../../app/types';
+import Dropdown from '../../../components/controls/Dropdown';
+import { translate } from '../../../helpers/l10n';
+import SeverityHelper from '../../../components/shared/SeverityHelper';
+
+interface Props {
+ onFilterChange: (changes: Partial<Query>) => void;
+ rule: Rule;
+}
+
+export default class SimilarRulesFilter extends React.PureComponent<Props> {
+ closeDropdown: () => void;
+
+ handleLanguageClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.closeDropdown();
+ this.props.onFilterChange({ languages: [this.props.rule.lang] });
+ };
+
+ handleTypeClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.closeDropdown();
+ this.props.onFilterChange({ types: [this.props.rule.type] });
+ };
+
+ handleSeverityClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.closeDropdown();
+ if (this.props.rule.severity) {
+ this.props.onFilterChange({ severities: [this.props.rule.severity] });
+ }
+ };
+
+ handleTagClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.closeDropdown();
+ const { tag } = event.currentTarget.dataset;
+ if (tag) {
+ this.props.onFilterChange({ tags: [tag] });
+ }
+ };
+
+ render() {
+ const { rule } = this.props;
+ const { tags = [], sysTags = [], severity } = rule;
+ const allTags = [...tags, ...sysTags];
+
+ return (
+ <Dropdown>
+ {({ closeDropdown, onToggleClick, open }) => {
+ this.closeDropdown = closeDropdown;
+ return (
+ <div className={classNames('dropdown display-inline-block', { open })}>
+ <a
+ className="js-rule-filter link-no-underline spacer-left dropdown-toggle"
+ href="#"
+ onClick={onToggleClick}>
+ <i className="icon-filter icon-half-transparent" />
+ <i className="icon-dropdown little-spacer-left" />
+ </a>
+ <div className="dropdown-menu dropdown-menu-right">
+ <header className="dropdown-header">
+ {translate('coding_rules.filter_similar_rules')}
+ </header>
+ <ul className="menu">
+ <li>
+ <a data-field="language" href="#" onClick={this.handleLanguageClick}>
+ {rule.langName}
+ </a>
+ </li>
+
+ <li>
+ <a data-field="type" href="#" onClick={this.handleTypeClick}>
+ {translate('issue.type', rule.type)}
+ </a>
+ </li>
+
+ {severity && (
+ <li>
+ <a data-field="severity" href="#" onClick={this.handleSeverityClick}>
+ <SeverityHelper severity={rule.severity} />
+ </a>
+ </li>
+ )}
+
+ {allTags.length > 0 && (
+ <>
+ <li className="divider" />
+ {allTags.map(tag => (
+ <li key={tag}>
+ <a data-field="tag" data-tag={tag} href="#" onClick={this.handleTagClick}>
+ <i className="icon-tags icon-half-transparent little-spacer-right" />
+ {tag}
+ </a>
+ </li>
+ ))}
+ </>
+ )}
+ </ul>
+ </div>
+ </div>
+ );
+ }}
+ </Dropdown>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import Facet, { BasicProps } from './Facet';
+import { RULE_STATUSES } from '../../../helpers/constants';
+import { translate } from '../../../helpers/l10n';
+
+export default class StatusFacet extends React.PureComponent<BasicProps> {
+ renderName = (status: string) => translate('rules.status', status.toLowerCase());
+
+ render() {
+ return (
+ <Facet
+ {...this.props}
+ options={RULE_STATUSES}
+ property="statuses"
+ renderName={this.renderName}
+ renderTextName={this.renderName}
+ />
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { uniq } from 'lodash';
+import Facet, { BasicProps } from './Facet';
+import { getRuleTags } from '../../../api/rules';
+import FacetFooter from '../../../components/facet/FacetFooter';
+
+interface Props extends BasicProps {
+ organization: string | undefined;
+}
+
+export default class TagFacet extends React.PureComponent<Props> {
+ handleSearch = (query: string) =>
+ getRuleTags({ organization: this.props.organization, ps: 50, q: query }).then(tags =>
+ tags.map(tag => ({ label: tag, value: tag }))
+ );
+
+ handleSelect = (tag: string) => this.props.onChange({ tags: uniq([...this.props.values, tag]) });
+
+ renderName = (tag: string) => (
+ <>
+ <i className="icon-tags icon-gray little-spacer-right" />
+ {tag}
+ </>
+ );
+
+ renderFooter = () => {
+ if (!this.props.stats) {
+ return null;
+ }
+
+ return <FacetFooter onSearch={this.handleSearch} onSelect={this.handleSelect} />;
+ };
+
+ render() {
+ const { organization, ...facetProps } = this.props;
+ return (
+ <Facet
+ {...facetProps}
+ property="tags"
+ renderFooter={this.renderFooter}
+ renderName={this.renderName}
+ />
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import Facet, { BasicProps } from './Facet';
+import { Omit } from '../../../app/types';
+import { translate } from '../../../helpers/l10n';
+
+interface Props extends Omit<BasicProps, 'onChange' | 'values'> {
+ onChange: (changes: { template: boolean | undefined }) => void;
+ value: boolean | undefined;
+}
+
+export default class TemplateFacet extends React.PureComponent<Props> {
+ handleChange = (changes: { template: string | any[] }) => {
+ const template =
+ // empty array is returned when a user cleared the facet
+ // otherwise `"true"`, `"false"` or `undefined` can be returned
+ Array.isArray(changes.template) || changes.template === undefined
+ ? undefined
+ : changes.template === 'true';
+ this.props.onChange({ ...changes, template });
+ };
+
+ renderName = (template: string) =>
+ template === 'true'
+ ? translate('coding_rules.filters.template.is_template')
+ : translate('coding_rules.filters.template.is_not_template');
+
+ render() {
+ const { onChange, value, ...props } = this.props;
+
+ return (
+ <Facet
+ {...props}
+ onChange={this.handleChange}
+ options={['true', 'false']}
+ property="template"
+ renderName={this.renderName}
+ renderTextName={this.renderName}
+ singleSelection={true}
+ values={value !== undefined ? [String(value)] : []}
+ />
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import Facet, { BasicProps } from './Facet';
+import IssueTypeIcon from '../../../components/ui/IssueTypeIcon';
+import { translate } from '../../../helpers/l10n';
+
+export default class TypeFacet extends React.PureComponent<BasicProps> {
+ renderName = (type: string) => (
+ <>
+ <IssueTypeIcon className="little-spacer-right" query={type} />
+ {translate('issue.type', type)}
+ </>
+ );
+
+ renderTextName = (type: string) => translate('issue.type', type);
+
+ render() {
+ const options = ['BUG', 'VULNERABILITY', 'CODE_SMELL'];
+
+ return (
+ <Facet
+ {...this.props}
+ options={options}
+ property="types"
+ renderName={this.renderName}
+ renderTextName={this.renderTextName}
+ />
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import $ from 'jquery';
-
-const DEFAULTS = {
- title: 'Confirmation',
- html: '',
- yesLabel: 'Yes',
- noLabel: 'Cancel',
- yesHandler() {
- // no op
- },
- noHandler() {
- // no op
- },
- always() {
- // no op
- }
-};
-
-export default function(options) {
- const settings = { ...DEFAULTS, ...options };
- const dialog = $(
- '<div><div class="modal-head"><h2>' +
- settings.title +
- '</h2></div><div class="modal-body">' +
- settings.html +
- '</div><div class="modal-foot"><button data-confirm="yes">' +
- settings.yesLabel +
- '</button> <a data-confirm="no" class="action">' +
- settings.noLabel +
- '</a></div></div>'
- );
-
- $('[data-confirm=yes]', dialog).on('click', () => {
- dialog.dialog('close');
- settings.yesHandler();
- return settings.always();
- });
-
- $('[data-confirm=no]', dialog).on('click', () => {
- dialog.dialog('close');
- settings.noHandler();
- return settings.always();
- });
-
- return dialog.dialog({
- modal: true,
- minHeight: null,
- width: 540
- });
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import key from 'keymaster';
-import Controller from '../../components/navigator/controller';
-import Rule from './models/rule';
-import RuleDetailsView from './rule-details-view';
-import { searchRules, getRuleDetails } from '../../api/rules';
-
-export default Controller.extend({
- pageSize: 200,
- ruleFields: [
- 'name',
- 'lang',
- 'langName',
- 'sysTags',
- 'tags',
- 'status',
- 'severity',
- 'isTemplate',
- 'templateKey'
- ],
-
- _searchParameters() {
- const fields = this.ruleFields.slice();
- const profile = this.app.state.get('query').qprofile;
- if (profile != null) {
- fields.push('actives');
- fields.push('params');
- fields.push('isTemplate');
- fields.push('severity');
- }
- const params = {
- p: this.app.state.get('page'),
- ps: this.pageSize,
- facets: this._facetsFromServer().join(),
- f: fields.join()
- };
- if (this.app.state.get('query').q == null) {
- Object.assign(params, { s: 'name', asc: true });
- }
- return params;
- },
-
- fetchList(firstPage) {
- firstPage = firstPage == null ? true : firstPage;
- if (firstPage) {
- this.app.state.set({ selectedIndex: 0, page: 1 }, { silent: true });
- }
-
- this.hideDetails(firstPage);
-
- const options = { ...this._searchParameters(), ...this.app.state.get('query') };
- return searchRules(options).then(
- r => {
- const rules = this.app.list.parseRules(r);
- if (firstPage) {
- this.app.list.reset(rules);
- } else {
- this.app.list.add(rules);
- }
- this.app.list.setIndex();
- this.app.list.addExtraAttributes(this.app.repositories);
- this.app.facets.reset(this._allFacets());
- this.app.facets.add(r.facets, { merge: true });
- this.enableFacets(this._enabledFacets());
- this.app.state.set({
- page: r.p,
- pageSize: r.ps,
- total: r.total,
- maxResultsReached: r.p * r.ps >= r.total
- });
- if (firstPage && this.isRulePermalink()) {
- this.showDetails(this.app.list.first());
- }
- },
- () => {
- this.app.state.set({ maxResultsReached: true });
- }
- );
- },
-
- isRulePermalink() {
- const query = this.app.state.get('query');
- return query.rule_key != null && this.app.list.length === 1;
- },
-
- requestFacet(id) {
- const facet = this.app.facets.get(id);
- const options = { facets: id, ps: 1, ...this.app.state.get('query') };
- return searchRules(options).then(r => {
- const facetData = r.facets.find(facet => facet.property === id);
- if (facetData) {
- facet.set(facetData);
- }
- });
- },
-
- parseQuery() {
- const q = Controller.prototype.parseQuery.apply(this, arguments);
- delete q.asc;
- delete q.s;
- return q;
- },
-
- getRuleDetails(rule) {
- const parameters = { key: rule.id, actives: true, organization: this.app.organization };
- return getRuleDetails(parameters).then(r => {
- rule.set(r.rule);
- rule.addExtraAttributes(this.app.repositories);
- return r;
- });
- },
-
- showDetails(rule) {
- const that = this;
- const ruleModel = typeof rule === 'string' ? new Rule({ key: rule }) : rule;
- this.app.layout.workspaceDetailsRegion.reset();
- this.getRuleDetails(ruleModel).then(
- r => {
- key.setScope('details');
- that.app.workspaceListView.unbindScrollEvents();
- that.app.state.set({ rule: ruleModel });
- that.app.workspaceDetailsView = new RuleDetailsView({
- app: that.app,
- model: ruleModel,
- actives: r.actives
- });
- that.app.layout.showDetails();
- that.app.layout.workspaceDetailsRegion.show(that.app.workspaceDetailsView);
- },
- () => {}
- );
- },
-
- showDetailsForSelected() {
- const rule = this.app.list.at(this.app.state.get('selectedIndex'));
- this.showDetails(rule);
- },
-
- hideDetails(firstPage) {
- key.setScope('list');
- this.app.state.unset('rule');
- this.app.layout.workspaceDetailsRegion.reset();
- this.app.layout.hideDetails();
- this.app.workspaceListView.bindScrollEvents();
- if (firstPage) {
- this.app.workspaceListView.scrollTo();
- }
- },
-
- activateCurrent() {
- if (this.app.layout.detailsShow()) {
- this.app.workspaceDetailsView.$('#coding-rules-quality-profile-activate').click();
- } else {
- const rule = this.app.list.at(this.app.state.get('selectedIndex'));
- const ruleView = this.app.workspaceListView.children.findByModel(rule);
- ruleView.$('.coding-rules-detail-quality-profile-activate').click();
- }
- },
-
- deactivateCurrent() {
- if (!this.app.layout.detailsShow()) {
- const rule = this.app.list.at(this.app.state.get('selectedIndex'));
- const ruleView = this.app.workspaceListView.children.findByModel(rule);
- ruleView.$('.coding-rules-detail-quality-profile-deactivate').click();
- }
- },
-
- updateActivation(rule, actives) {
- const selectedProfile = this.options.app.state.get('query').qprofile;
- if (selectedProfile) {
- const profile = (actives || []).find(activation => activation.qProfile === selectedProfile);
- const listRule = this.app.list.get(rule.id);
- if (profile && listRule) {
- listRule.set('activation', {
- ...listRule.get('activation'),
- inherit: profile.inherit,
- severity: profile.severity
- });
- }
- }
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import FacetsView from '../../components/navigator/facets-view';
-import BaseFacet from './facets/base-facet';
-import QueryFacet from './facets/query-facet';
-import KeyFacet from './facets/key-facet';
-import LanguageFacet from './facets/language-facet';
-import RepositoryFacet from './facets/repository-facet';
-import TagFacet from './facets/tag-facet';
-import QualityProfileFacet from './facets/quality-profile-facet';
-import SeverityFacet from './facets/severity-facet';
-import StatusFacet from './facets/status-facet';
-import AvailableSinceFacet from './facets/available-since-facet';
-import InheritanceFacet from './facets/inheritance-facet';
-import ActiveSeverityFacet from './facets/active-severity-facet';
-import TemplateFacet from './facets/template-facet';
-import TypeFacet from './facets/type-facet';
-
-const viewsMapping = {
- q: QueryFacet,
- rule_key: KeyFacet,
- languages: LanguageFacet,
- repositories: RepositoryFacet,
- tags: TagFacet,
- qprofile: QualityProfileFacet,
- severities: SeverityFacet,
- statuses: StatusFacet,
- available_since: AvailableSinceFacet,
- inheritance: InheritanceFacet,
- active_severities: ActiveSeverityFacet,
- is_template: TemplateFacet,
- types: TypeFacet
-};
-
-export default FacetsView.extend({
- getChildView(model) {
- const view = viewsMapping[model.get('property')];
- return view ? view : BaseFacet;
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import { sortBy } from 'lodash';
-import BaseFacet from './base-facet';
-import Template from '../templates/facets/coding-rules-severity-facet.hbs';
-import { translate } from '../../../helpers/l10n';
-
-export default BaseFacet.extend({
- template: Template,
- severities: ['BLOCKER', 'MINOR', 'CRITICAL', 'INFO', 'MAJOR'],
-
- initialize(options) {
- this.listenTo(options.app.state, 'change:query', this.onQueryChange);
- },
-
- onQueryChange() {
- const query = this.options.app.state.get('query');
- const isProfileSelected = query.qprofile != null;
- const isActiveShown = '' + query.activation === 'true';
- if (!isProfileSelected || !isActiveShown) {
- this.forbid();
- }
- },
-
- onRender() {
- BaseFacet.prototype.onRender.apply(this, arguments);
- this.onQueryChange();
- },
-
- forbid() {
- BaseFacet.prototype.forbid.apply(this, arguments);
- this.$el.prop('title', translate('coding_rules.filters.active_severity.inactive'));
- },
-
- allow() {
- BaseFacet.prototype.allow.apply(this, arguments);
- this.$el.prop('title', null);
- },
-
- sortValues(values) {
- const order = this.severities;
- return sortBy(values, v => order.indexOf(v.val));
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import BaseFacet from './base-facet';
-import Template from '../templates/facets/coding-rules-available-since-facet.hbs';
-
-export default BaseFacet.extend({
- template: Template,
-
- events() {
- return {
- ...BaseFacet.prototype.events.apply(this, arguments),
- 'change input': 'applyFacet'
- };
- },
-
- onRender() {
- this.$el.toggleClass('search-navigator-facet-box-collapsed', !this.model.get('enabled'));
- this.$el.attr('data-property', this.model.get('property'));
- this.$('input').datepicker({
- dateFormat: 'yy-mm-dd',
- changeMonth: true,
- changeYear: true
- });
- const value = this.options.app.state.get('query').available_since;
- if (value) {
- this.$('input').val(value);
- }
- },
-
- applyFacet() {
- const obj = {};
- const property = this.model.get('property');
- obj[property] = this.$('input').val();
- this.options.app.state.updateFilter(obj);
- },
-
- getLabelsSource() {
- return this.options.app.languages;
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import BaseFacet from '../../../components/navigator/facets/base-facet';
-import Template from '../templates/facets/coding-rules-base-facet.hbs';
-
-export default BaseFacet.extend({
- className: 'search-navigator-facet-box',
- template: Template
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import BaseFacet from './base-facet';
-
-export default BaseFacet.extend({
- getLabelsSource() {
- return [];
- },
-
- getValues() {
- const that = this;
- const labels = that.getLabelsSource();
- return this.model.getValues().map(item => {
- return { ...item, label: labels[item.val] };
- });
- },
-
- serializeData() {
- return {
- ...BaseFacet.prototype.serializeData.apply(this, arguments),
- values: this.getValues()
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import BaseFacet from './base-facet';
-import Template from '../templates/facets/coding-rules-custom-values-facet.hbs';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-
-export default BaseFacet.extend({
- template: Template,
-
- events() {
- return {
- ...BaseFacet.prototype.events.apply(this, arguments),
- 'change .js-custom-value': 'addCustomValue'
- };
- },
-
- getUrl() {
- return window.baseUrl;
- },
-
- onRender() {
- BaseFacet.prototype.onRender.apply(this, arguments);
- this.prepareSearch();
- },
-
- prepareSearch() {
- this.$('.js-custom-value').select2({
- placeholder: translate('search_verb'),
- minimumInputLength: 1,
- allowClear: false,
- formatNoMatches() {
- return translate('select2.noMatches');
- },
- formatSearching() {
- return translate('select2.searching');
- },
- formatInputTooShort() {
- return translateWithParameters('select2.tooShort', 1);
- },
- width: '100%',
- ajax: this.prepareAjaxSearch()
- });
- },
-
- prepareAjaxSearch() {
- return {
- quietMillis: 300,
- url: this.getUrl(),
- data(term, page) {
- return { s: term, p: page };
- },
- results(data) {
- return { more: data.more, results: data.results };
- }
- };
- },
-
- addCustomValue() {
- const property = this.model.get('property');
- const customValue = this.$('.js-custom-value').select2('val');
- let value = this.getValue();
- if (value.length > 0) {
- value += ',';
- }
- value += customValue;
- const obj = {};
- obj[property] = value;
- this.options.app.state.updateFilter(obj);
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import $ from 'jquery';
-import BaseFacet from './base-facet';
-import Template from '../templates/facets/coding-rules-inheritance-facet.hbs';
-import { translate } from '../../../helpers/l10n';
-
-export default BaseFacet.extend({
- template: Template,
-
- initialize(options) {
- this.listenTo(options.app.state, 'change:query', this.onQueryChange);
- },
-
- onQueryChange() {
- const query = this.options.app.state.get('query');
- const isProfileSelected = query.qprofile != null;
- if (isProfileSelected) {
- const profile = this.options.app.qualityProfiles.find(p => p.key === query.qprofile);
- if (profile != null && profile.parentKey == null) {
- this.forbid();
- }
- } else {
- this.forbid();
- }
- },
-
- onRender() {
- BaseFacet.prototype.onRender.apply(this, arguments);
- this.onQueryChange();
- },
-
- forbid() {
- BaseFacet.prototype.forbid.apply(this, arguments);
- this.$el.prop('title', translate('coding_rules.filters.inheritance.inactive'));
- },
-
- allow() {
- BaseFacet.prototype.allow.apply(this, arguments);
- this.$el.prop('title', null);
- },
-
- getValues() {
- const values = ['NONE', 'INHERITED', 'OVERRIDES'];
- return values.map(key => {
- return {
- label: translate('coding_rules.filters.inheritance', key.toLowerCase()),
- val: key
- };
- });
- },
-
- toggleFacet(e) {
- const obj = {};
- const property = this.model.get('property');
- if ($(e.currentTarget).is('.active')) {
- obj[property] = null;
- } else {
- obj[property] = $(e.currentTarget).data('value');
- }
- this.options.app.state.updateFilter(obj);
- },
-
- serializeData() {
- return {
- ...BaseFacet.prototype.serializeData.apply(this, arguments),
- values: this.getValues()
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import BaseFacet from './base-facet';
-import Template from '../templates/facets/coding-rules-key-facet.hbs';
-
-export default BaseFacet.extend({
- template: Template,
-
- onRender() {
- this.$el.toggleClass('hidden', !this.options.app.state.get('query').rule_key);
- },
-
- disable() {
- this.options.app.state.updateFilter({ rule_key: null });
- },
-
- serializeData() {
- return {
- ...BaseFacet.prototype.serializeData.apply(this, arguments),
- key: this.options.app.state.get('query').rule_key
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import CustomValuesFacet from './custom-values-facet';
-
-export default CustomValuesFacet.extend({
- getUrl() {
- return window.baseUrl + '/api/languages/list';
- },
-
- prepareAjaxSearch() {
- return {
- quietMillis: 300,
- url: this.getUrl(),
- data(term) {
- return { q: term, ps: 10000 };
- },
- results(data) {
- return {
- more: false,
- results: data.languages.map(lang => {
- return { id: lang.key, text: lang.name };
- })
- };
- }
- };
- },
-
- getLabelsSource() {
- return this.options.app.languages;
- },
-
- getValues() {
- const that = this;
- const labels = that.getLabelsSource();
- return this.model.getValues().map(item => {
- return { ...item, label: labels[item.val] };
- });
- },
-
- serializeData() {
- return {
- ...CustomValuesFacet.prototype.serializeData.apply(this, arguments),
- values: this.sortValues(this.getValues())
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import $ from 'jquery';
-import { sortBy } from 'lodash';
-import BaseFacet from './base-facet';
-import Template from '../templates/facets/coding-rules-quality-profile-facet.hbs';
-
-export default BaseFacet.extend({
- template: Template,
-
- events() {
- return {
- ...BaseFacet.prototype.events.apply(this, arguments),
- 'click .js-active': 'setActivation',
- 'click .js-inactive': 'unsetActivation'
- };
- },
-
- onRender() {
- BaseFacet.prototype.onRender.apply(this, arguments);
- const compareToProfile = this.options.app.state.get('query').compareToProfile;
- if (typeof compareToProfile === 'string') {
- const facet = this.$('.js-facet').filter(`[data-value="${compareToProfile}"]`);
- if (facet.length > 0) {
- facet.addClass('active compare');
- }
- }
- },
-
- getValues() {
- const that = this;
- const languagesQuery = this.options.app.state.get('query').languages;
- const languages = languagesQuery != null ? languagesQuery.split(',') : [];
- const lang = languages.length === 1 ? languages[0] : null;
- const values = this.options.app.qualityProfiles
- .filter(profile => (lang != null ? profile.language === lang : true))
- .map(profile => ({
- extra: that.options.app.languages[profile.language],
- isBuiltIn: profile.isBuiltIn,
- label: profile.name,
- val: profile.key
- }));
- const compareProfile = this.options.app.state.get('query').compareToProfile;
- if (compareProfile != null) {
- const property = this.model.get('property');
- const selectedProfile = this.options.app.state.get('query')[property];
- return sortBy(values, [
- profile => (profile.val === compareProfile || profile.val === selectedProfile ? 0 : 1),
- 'label'
- ]);
- }
- return sortBy(values, 'label');
- },
-
- toggleFacet(e) {
- const obj = {};
- const property = this.model.get('property');
- if ($(e.currentTarget).is('.active')) {
- obj.activation = null;
- obj[property] = null;
- } else {
- obj.activation = true;
- obj[property] = $(e.currentTarget).data('value');
- }
- obj.compareToProfile = null;
- this.options.app.state.updateFilter(obj);
- },
-
- setActivation(e) {
- e.stopPropagation();
- const compareProfile = this.options.app.state.get('query').compareToProfile;
- const profile = $(e.currentTarget)
- .parents('.js-facet')
- .data('value');
- if (compareProfile == null || compareProfile !== profile) {
- this.options.app.state.updateFilter({ activation: 'true', compareToProfile: null });
- }
- },
-
- unsetActivation(e) {
- e.stopPropagation();
- const compareProfile = this.options.app.state.get('query').compareToProfile;
- const profile = $(e.currentTarget)
- .parents('.js-facet')
- .data('value');
- if (compareProfile == null || compareProfile !== profile) {
- this.options.app.state.updateFilter({
- activation: 'false',
- active_severities: null,
- compareToProfile: null
- });
- }
- },
-
- getToggled() {
- const activation = this.options.app.state.get('query').activation;
- return activation === 'true' || activation === true;
- },
-
- disable() {
- const obj = { activation: null };
- const property = this.model.get('property');
- obj[property] = null;
- obj.compareToProfile = null;
- this.options.app.state.updateFilter(obj);
- },
-
- serializeData() {
- return {
- ...BaseFacet.prototype.serializeData.apply(this, arguments),
- values: this.getValues(),
- toggled: this.getToggled()
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import { debounce } from 'lodash';
-import BaseFacet from './base-facet';
-import Template from '../templates/facets/coding-rules-query-facet.hbs';
-
-export default BaseFacet.extend({
- template: Template,
-
- events(...args) {
- return {
- ...BaseFacet.prototype.events.apply(this, args),
- 'submit form': 'onFormSubmit',
- 'keyup input': 'onKeyUp',
- 'search input': 'onSearch',
- 'click .js-reset': 'onResetClick'
- };
- },
-
- onRender() {
- this.$el.attr('data-property', this.model.get('property'));
- const query = this.options.app.state.get('query');
- const value = query.q;
- if (value != null) {
- this.$('input').val(value);
- this.$('.js-hint').toggleClass('hidden', value.length !== 1);
- this.$('.js-reset').toggleClass('hidden', value.length === 0);
- }
- },
-
- onFormSubmit(e) {
- e.preventDefault();
- this.applyFacet();
- },
-
- onKeyUp() {
- const q = this.$('input').val();
- this.$('.js-hint').toggleClass('hidden', q.length !== 1);
- this.$('.js-reset').toggleClass('hidden', q.length === 0);
- },
-
- onSearch() {
- const q = this.$('input').val();
- if (q.length !== 1) {
- this.applyFacet();
- }
- },
-
- onResetClick(e) {
- e.preventDefault();
- this.$('input')
- .val('')
- .focus();
- },
-
- applyFacet() {
- const obj = {};
- const property = this.model.get('property');
- const value = this.$('input').val();
- if (this.buffer !== value) {
- this.buffer = value;
- obj[property] = value;
- this.options.app.state.updateFilter(obj, { force: true });
- }
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import CustomValuesFacet from './custom-values-facet';
-
-export default CustomValuesFacet.extend({
- getUrl() {
- return window.baseUrl + '/api/rules/repositories';
- },
-
- prepareAjaxSearch() {
- return {
- quietMillis: 300,
- url: this.getUrl(),
- data(term) {
- return { q: term, ps: 10000 };
- },
- results(data) {
- return {
- more: false,
- results: data.repositories.map(repo => {
- return { id: repo.key, text: repo.name + ' (' + repo.language + ')' };
- })
- };
- }
- };
- },
-
- getLabelsSource() {
- const source = {};
- this.options.app.repositories.forEach(repo => (source[repo.key] = repo.name));
- return source;
- },
-
- getValues() {
- const that = this;
- const labels = that.getLabelsSource();
- return this.model.getValues().map(value => {
- const repo = that.options.app.repositories.find(repo => repo.key === value.val);
- if (repo != null) {
- const langName = that.options.app.languages[repo.language];
- Object.assign(value, { extra: langName });
- }
- return { ...value, label: labels[value.val] };
- });
- },
-
- serializeData() {
- return {
- ...CustomValuesFacet.prototype.serializeData.apply(this, arguments),
- values: this.getValues()
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import { sortBy } from 'lodash';
-import BaseFacet from './base-facet';
-import Template from '../templates/facets/coding-rules-severity-facet.hbs';
-
-export default BaseFacet.extend({
- template: Template,
- severities: ['BLOCKER', 'MINOR', 'CRITICAL', 'INFO', 'MAJOR'],
-
- sortValues(values) {
- const order = this.severities;
- return sortBy(values, v => order.indexOf(v.val));
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import { sortBy } from 'lodash';
-import BaseFacet from './base-facet';
-import { translate } from '../../../helpers/l10n';
-
-export default BaseFacet.extend({
- statuses: ['READY', 'DEPRECATED', 'BETA'],
-
- getValues() {
- const values = this.model.getValues();
- return values.map(value => ({
- ...value,
- label: translate('rules.status', value.val.toLowerCase())
- }));
- },
-
- sortValues(values) {
- const order = this.statuses;
- return sortBy(values, v => order.indexOf(v.val));
- },
-
- serializeData() {
- return {
- ...BaseFacet.prototype.serializeData.apply(this, arguments),
- values: this.sortValues(this.getValues())
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import CustomValuesFacet from './custom-values-facet';
-
-export default CustomValuesFacet.extend({
- getUrl() {
- return window.baseUrl + '/api/rules/tags';
- },
-
- prepareAjaxSearch() {
- return {
- quietMillis: 300,
- url: this.getUrl(),
- data: term => ({
- organization: this.options.app.organization,
- q: term,
- ps: 100
- }),
- results(data) {
- return {
- more: false,
- results: data.tags.map(tag => {
- return { id: tag, text: tag };
- })
- };
- }
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import $ from 'jquery';
-import BaseFacet from './base-facet';
-import Template from '../templates/facets/coding-rules-template-facet.hbs';
-
-export default BaseFacet.extend({
- template: Template,
-
- onRender() {
- BaseFacet.prototype.onRender.apply(this, arguments);
- const value = this.options.app.state.get('query').is_template;
- if (value != null) {
- this.$('.js-facet')
- .filter(`[data-value="${value}"]`)
- .addClass('active');
- }
- },
-
- toggleFacet(e) {
- $(e.currentTarget).toggleClass('active');
- const property = this.model.get('property');
- const obj = {};
- if ($(e.currentTarget).hasClass('active')) {
- obj[property] = '' + $(e.currentTarget).data('value');
- } else {
- obj[property] = null;
- }
- this.options.app.state.updateFilter(obj);
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import { sortBy } from 'lodash';
-import BaseFacet from './base-facet';
-import Template from '../templates/facets/coding-rules-type-facet.hbs';
-
-export default BaseFacet.extend({
- template: Template,
-
- sortValues(values) {
- const order = ['BUG', 'VULNERABILITY', 'CODE_SMELL'];
- return sortBy(values, v => order.indexOf(v.val));
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-// @flow
-import $ from 'jquery';
-import { sortBy } from 'lodash';
-import Backbone from 'backbone';
-import Marionette from 'backbone.marionette';
-import key from 'keymaster';
-import State from './models/state';
-import Layout from './layout';
-import Rules from './models/rules';
-import Facets from '../../components/navigator/models/facets';
-import Controller from './controller';
-import Router from '../../components/navigator/router';
-import WorkspaceListView from './workspace-list-view';
-import WorkspaceHeaderView from './workspace-header-view';
-import FacetsView from './facets-view';
-import { searchQualityProfiles } from '../../api/quality-profiles';
-import { getRulesApp } from '../../api/rules';
-import { areThereCustomOrganizations } from '../../store/organizations/utils';
-
-const App = new Marionette.Application();
-
-App.on('start', function(
- options /*: {
- el: HTMLElement,
- organization: ?string,
- isDefaultOrganization: boolean
-} */
-) {
- App.organization = options.organization;
- const data = options.organization ? { organization: options.organization } : {};
- Promise.all([getRulesApp(data), searchQualityProfiles(data)])
- .then(([appResponse, profilesResponse]) => {
- App.customRules = !areThereCustomOrganizations();
- App.canWrite = appResponse.canWrite;
- App.organization = options.organization;
- App.qualityProfiles = sortBy(profilesResponse.profiles, ['name', 'lang']);
- App.languages = { ...appResponse.languages, none: 'None' };
- App.repositories = appResponse.repositories;
- App.statuses = appResponse.statuses;
-
- this.layout = new Layout({ el: options.el });
- this.layout.render();
- $('#footer').addClass('page-footer-with-sidebar');
-
- const allFacets = [
- 'q',
- 'rule_key',
- 'languages',
- 'types',
- 'tags',
- 'repositories',
- 'severities',
- 'statuses',
- 'available_since',
- App.customRules ? 'is_template' : null,
- 'qprofile',
- 'inheritance',
- 'active_severities'
- ].filter(f => f != null);
-
- this.state = new State({ allFacets });
- this.list = new Rules();
- this.facets = new Facets();
-
- this.controller = new Controller({ app: this });
-
- this.workspaceListView = new WorkspaceListView({
- app: this,
- collection: this.list
- });
- this.layout.workspaceListRegion.show(this.workspaceListView);
- this.workspaceListView.bindScrollEvents();
-
- this.workspaceHeaderView = new WorkspaceHeaderView({
- app: this,
- collection: this.list
- });
- this.layout.workspaceHeaderRegion.show(this.workspaceHeaderView);
-
- this.facetsView = new FacetsView({
- app: this,
- collection: this.facets
- });
- this.layout.facetsRegion.show(this.facetsView);
-
- key.setScope('list');
- this.router = new Router({
- app: this
- });
- Backbone.history.start();
- })
- .catch(() => {
- // do nothing in case of WS error
- });
-});
-
-export default function(
- el /*: HTMLElement */,
- organization /*: ?string */,
- isDefaultOrganization /*: boolean */
-) {
- App.start({ el, organization, isDefaultOrganization });
-
- return () => {
- // $FlowFixMe
- Backbone.history.stop();
- App.layout.destroy();
- $('#footer').removeClass('page-footer-with-sidebar');
- };
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import $ from 'jquery';
-import Marionette from 'backbone.marionette';
-import Template from './templates/coding-rules-layout.hbs';
-
-export default Marionette.LayoutView.extend({
- template: Template,
-
- regions: {
- facetsRegion: '.layout-page-filters',
- workspaceHeaderRegion: '.coding-rules-header',
- workspaceListRegion: '.coding-rules-list',
- workspaceDetailsRegion: '.coding-rules-details'
- },
-
- onRender() {
- const navigator = this.$('.layout-page');
- const top = navigator.offset().top;
- this.$('.layout-page-side').css({ top });
- },
-
- showDetails() {
- this.scroll = $(window).scrollTop();
- this.$('.coding-rules').addClass('coding-rules-extended-view');
- },
-
- hideDetails() {
- this.$('.coding-rules').removeClass('coding-rules-extended-view');
- if (this.scroll != null) {
- $(window).scrollTop(this.scroll);
- }
- },
-
- detailsShow() {
- return this.$('.coding-rules').is('.coding-rules-extended-view');
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import Backbone from 'backbone';
-
-export default Backbone.Model.extend({
- idAttribute: 'key',
-
- addExtraAttributes(repositories) {
- const repo = repositories.find(repo => repo.key === this.get('repo')) || this.get('repo');
- const repoName = repo != null ? repo.name : repo;
- const isManual = this.get('repo') === 'manual';
- const isCustom = this.has('templateKey');
- this.set(
- {
- repoName,
- isManual,
- isCustom
- },
- { silent: true }
- );
- },
-
- getInactiveProfiles(actives, profiles) {
- return actives.map(profile => {
- const profileBase = profiles.find(p => p.key === profile.qProfile);
- if (profileBase != null) {
- Object.assign(profile, profileBase);
- }
- return profile;
- });
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import Backbone from 'backbone';
-import Rule from './rule';
-
-export default Backbone.Collection.extend({
- model: Rule,
-
- parseRules(r) {
- let rules = r.rules;
- const profiles = r.qProfiles || [];
-
- if (r.actives != null) {
- rules = rules.map(rule => {
- const activations = (r.actives[rule.key] || []).map(activation => {
- const profile = profiles[activation.qProfile];
- if (profile != null) {
- Object.assign(activation, { profile });
- if (profile.parent != null) {
- Object.assign(activation, { parentProfile: profiles[profile.parent] });
- }
- }
- return activation;
- });
- return { ...rule, activation: activations.length > 0 ? activations[0] : null };
- });
- }
- return rules;
- },
-
- setIndex() {
- this.forEach((rule, index) => {
- rule.set({ index });
- });
- },
-
- addExtraAttributes(repositories) {
- this.models.forEach(model => {
- model.addExtraAttributes(repositories);
- });
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import State from '../../../components/navigator/models/state';
-
-export default State.extend({
- defaults: {
- page: 1,
- maxResultsReached: false,
- query: {},
- facets: ['types', 'languages'],
- facetsFromServer: [
- 'languages',
- 'repositories',
- 'tags',
- 'severities',
- 'statuses',
- 'active_severities',
- 'types'
- ],
- transform: {}
- }
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import { RuleInheritance } from '../../app/types';
+import {
+ RawQuery,
+ parseAsString,
+ parseAsArray,
+ serializeString,
+ serializeStringArray,
+ cleanQuery,
+ queriesEqual,
+ parseAsDate,
+ serializeDateShort,
+ parseAsOptionalBoolean,
+ serializeOptionalBoolean,
+ parseAsOptionalString
+} from '../../helpers/query';
+
+export interface Query {
+ activation: boolean | undefined;
+ activationSeverities: string[];
+ availableSince: Date | undefined;
+ compareToProfile: string | undefined;
+ inheritance: RuleInheritance | undefined;
+ languages: string[];
+ profile: string | undefined;
+ repositories: string[];
+ ruleKey: string | undefined;
+ searchQuery: string | undefined;
+ severities: string[];
+ statuses: string[];
+ tags: string[];
+ template: boolean | undefined;
+ types: string[];
+}
+
+export type FacetKey = keyof Query;
+
+export interface Facet {
+ [value: string]: number;
+}
+
+export type Facets = { [F in FacetKey]?: Facet };
+
+export type OpenFacets = { [F in FacetKey]?: boolean };
+
+export interface Activation {
+ inherit: string;
+ severity: string;
+}
+
+export interface Actives {
+ [rule: string]: {
+ [profile: string]: Activation;
+ };
+}
+
+export function parseQuery(query: RawQuery): Query {
+ return {
+ activation: parseAsOptionalBoolean(query.activation),
+ activationSeverities: parseAsArray(query.active_severities, parseAsString),
+ availableSince: parseAsDate(query.available_since),
+ compareToProfile: parseAsOptionalString(query.compareToProfile),
+ inheritance: parseAsInheritance(query.inheritance),
+ languages: parseAsArray(query.languages, parseAsString),
+ profile: parseAsOptionalString(query.qprofile),
+ repositories: parseAsArray(query.repositories, parseAsString),
+ ruleKey: parseAsOptionalString(query.rule_key),
+ searchQuery: parseAsOptionalString(query.q),
+ severities: parseAsArray(query.severities, parseAsString),
+ statuses: parseAsArray(query.statuses, parseAsString),
+ tags: parseAsArray(query.tags, parseAsString),
+ template: parseAsOptionalBoolean(query.is_template),
+ types: parseAsArray(query.types, parseAsString)
+ };
+}
+
+export function serializeQuery(query: Query): RawQuery {
+ /* eslint-disable camelcase */
+ return cleanQuery({
+ activation: serializeOptionalBoolean(query.activation),
+ active_severities: serializeStringArray(query.activationSeverities),
+ available_since: serializeDateShort(query.availableSince),
+ compareToProfile: serializeString(query.compareToProfile),
+ inheritance: serializeInheritance(query.inheritance),
+ is_template: serializeOptionalBoolean(query.template),
+ languages: serializeStringArray(query.languages),
+ q: serializeString(query.searchQuery),
+ qprofile: serializeString(query.profile),
+ repositories: serializeStringArray(query.repositories),
+ rule_key: serializeString(query.ruleKey),
+ severities: serializeStringArray(query.severities),
+ statuses: serializeStringArray(query.statuses),
+ tags: serializeStringArray(query.tags),
+ types: serializeStringArray(query.types)
+ });
+ /* eslint-enable camelcase */
+}
+
+export function areQueriesEqual(a: RawQuery, b: RawQuery) {
+ return queriesEqual(parseQuery(a), parseQuery(b));
+}
+
+export function shouldRequestFacet(facet: FacetKey) {
+ const facetsToRequest = [
+ 'activationSeverities',
+ 'languages',
+ 'repositories',
+ 'severities',
+ 'statuses',
+ 'tags',
+ 'types'
+ ];
+ return facetsToRequest.includes(facet);
+}
+
+export function getServerFacet(facet: FacetKey) {
+ return facet === 'activationSeverities' ? 'active_severities' : facet;
+}
+
+export function getAppFacet(serverFacet: string): FacetKey {
+ return serverFacet === 'active_severities' ? 'activationSeverities' : (serverFacet as FacetKey);
+}
+
+export function getOpen(query: RawQuery) {
+ return query.open;
+}
+
+function parseAsInheritance(value?: string): RuleInheritance | undefined {
+ if (value === RuleInheritance.Inherited) {
+ return RuleInheritance.Inherited;
+ } else if (value === RuleInheritance.NotInherited) {
+ return RuleInheritance.NotInherited;
+ } else if (value === RuleInheritance.Overridden) {
+ return RuleInheritance.Overridden;
+ } else {
+ return undefined;
+ }
+}
+
+function serializeInheritance(value: RuleInheritance | undefined): string | undefined {
+ return value;
+}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { RouterState, RouteComponent } from 'react-router';
+import { RouterState, RouteComponent, RedirectFunction } from 'react-router';
+import { parseQuery, serializeQuery } from './query';
+import { RawQuery } from '../../helpers/query';
+
+function parseHash(hash: string): RawQuery {
+ const query: RawQuery = {};
+ const parts = hash.split('|');
+ parts.forEach(part => {
+ const tokens = part.split('=');
+ if (tokens.length === 2) {
+ query[decodeURIComponent(tokens[0])] = decodeURIComponent(tokens[1]);
+ }
+ });
+ return query;
+}
const routes = [
{
indexRoute: {
+ onEnter: (nextState: RouterState, replace: RedirectFunction) => {
+ const { hash } = window.location;
+ if (hash.length > 1) {
+ const query = parseHash(hash.substr(1));
+ const normalizedQuery = {
+ ...serializeQuery(parseQuery(query)),
+ open: query.open
+ };
+ replace({ pathname: nextState.location.pathname, query: normalizedQuery });
+ }
+ },
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
- import('./components/CodingRulesAppContainer').then(i => callback(null, i.default));
+ import('./components/App').then(i => callback(null, i.default));
}
}
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import { union } from 'lodash';
-import Backbone from 'backbone';
-import Marionette from 'backbone.marionette';
-import key from 'keymaster';
-import Rules from './models/rules';
-import MetaView from './rule/rule-meta-view';
-import DescView from './rule/rule-description-view';
-import ParamView from './rule/rule-parameters-view';
-import ProfilesView from './rule/rule-profiles-view';
-import CustomRulesView from './rule/custom-rules-view';
-import CustomRuleCreationView from './rule/custom-rule-creation-view';
-import DeleteRuleView from './rule/delete-rule-view';
-import IssuesView from './rule/rule-issues-view';
-import Template from './templates/coding-rules-rule-details.hbs';
-import { searchRules } from '../../api/rules';
-
-export default Marionette.LayoutView.extend({
- className: 'coding-rule-details',
- template: Template,
-
- regions: {
- metaRegion: '.js-rule-meta',
- descRegion: '.js-rule-description',
- paramRegion: '.js-rule-parameters',
- profilesRegion: '.js-rule-profiles',
- customRulesRegion: '.js-rule-custom-rules',
- issuesRegion: '.js-rule-issues'
- },
-
- events: {
- 'click .js-edit-custom': 'editCustomRule',
- 'click .js-delete': 'deleteRule'
- },
-
- initialize() {
- this.bindShortcuts();
- this.customRules = new Rules();
- if (this.model.get('isTemplate')) {
- this.fetchCustomRules();
- }
- this.listenTo(this.options.app.state, 'change:selectedIndex', this.select);
- },
-
- onRender() {
- this.metaRegion.show(
- new MetaView({
- app: this.options.app,
- model: this.model
- })
- );
- this.descRegion.show(
- new DescView({
- app: this.options.app,
- model: this.model
- })
- );
- this.paramRegion.show(
- new ParamView({
- app: this.options.app,
- model: this.model
- })
- );
- this.profilesRegion.show(
- new ProfilesView({
- app: this.options.app,
- model: this.model,
- collection: new Backbone.Collection(this.getQualityProfiles())
- })
- );
- this.customRulesRegion.show(
- new CustomRulesView({
- app: this.options.app,
- model: this.model,
- collection: this.customRules
- })
- );
- this.issuesRegion.show(
- new IssuesView({
- app: this.options.app,
- model: this.model
- })
- );
- this.$el.scrollParent().scrollTop(0);
- },
-
- onDestroy() {
- this.unbindShortcuts();
- },
-
- fetchCustomRules() {
- const options = {
- template_key: this.model.get('key'),
- f: 'name,severity,params'
- };
- searchRules(options).then(r => this.customRules.reset(r.rules), () => {});
- },
-
- getQualityProfiles() {
- return this.model.getInactiveProfiles(this.options.actives, this.options.app.qualityProfiles);
- },
-
- bindShortcuts() {
- const that = this;
- key('up', 'details', () => {
- that.options.app.controller.selectPrev();
- return false;
- });
- key('down', 'details', () => {
- that.options.app.controller.selectNext();
- return false;
- });
- key('left, backspace', 'details', () => {
- that.options.app.controller.hideDetails();
- return false;
- });
- },
-
- unbindShortcuts() {
- key.deleteScope('details');
- },
-
- editCustomRule() {
- new CustomRuleCreationView({
- app: this.options.app,
- model: this.model
- }).render();
- },
-
- deleteRule() {
- const deleteRuleView = new DeleteRuleView({
- model: this.model
- }).render();
-
- deleteRuleView.on('delete', () => {
- const { controller } = this.options.app;
- if (controller.isRulePermalink()) {
- controller.newSearch();
- } else {
- controller.fetchList();
- }
- });
- },
-
- select() {
- const selected = this.options.app.state.get('selectedIndex');
- const selectedRule = this.options.app.list.at(selected);
- this.options.app.controller.showDetails(selectedRule);
- },
-
- serializeData() {
- const isCustom = this.model.has('templateKey');
- const isEditable = this.options.app.canWrite && this.options.app.customRules && isCustom;
- let qualityProfilesVisible = true;
-
- if (this.model.get('isTemplate')) {
- qualityProfilesVisible = Object.keys(this.options.actives).length > 0;
- }
-
- return {
- ...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
- isEditable,
- qualityProfilesVisible,
- allTags: union(this.model.get('sysTags'), this.model.get('tags'))
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import $ from 'jquery';
-import { union } from 'lodash';
-import ActionOptionsView from '../../components/common/action-options-view';
-import Template from './templates/coding-rules-rule-filter-form.hbs';
-
-export default ActionOptionsView.extend({
- template: Template,
-
- selectOption(e) {
- const property = $(e.currentTarget).data('property');
- const value = $(e.currentTarget).data('value');
- this.trigger('select', property, value);
- return ActionOptionsView.prototype.selectOption.apply(this, arguments);
- },
-
- serializeData() {
- return {
- ...ActionOptionsView.prototype.serializeData.apply(this, arguments),
- tags: union(this.model.get('sysTags'), this.model.get('tags'))
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import $ from 'jquery';
-import ModalFormView from '../../../components/common/modal-form';
-import Template from '../templates/rule/coding-rules-custom-rule-creation.hbs';
-import { csvEscape } from '../../../helpers/csv';
-import latinize from '../../../helpers/latinize';
-import { translate } from '../../../helpers/l10n';
-
-export default ModalFormView.extend({
- template: Template,
-
- ui() {
- return {
- ...ModalFormView.prototype.ui.apply(this, arguments),
- customRuleCreationKey: '#coding-rules-custom-rule-creation-key',
- customRuleCreationName: '#coding-rules-custom-rule-creation-name',
- customRuleCreationHtmlDescription: '#coding-rules-custom-rule-creation-html-description',
- customRuleCreationType: '#coding-rules-custom-rule-creation-type',
- customRuleCreationSeverity: '#coding-rules-custom-rule-creation-severity',
- customRuleCreationStatus: '#coding-rules-custom-rule-creation-status',
- customRuleCreationParameters: '[name]',
- customRuleCreationCreate: '#coding-rules-custom-rule-creation-create',
- customRuleCreationReactivate: '#coding-rules-custom-rule-creation-reactivate',
- modalFoot: '.modal-foot'
- };
- },
-
- events() {
- return {
- ...ModalFormView.prototype.events.apply(this, arguments),
- 'input @ui.customRuleCreationName': 'generateKey',
- 'keydown @ui.customRuleCreationName': 'generateKey',
- 'keyup @ui.customRuleCreationName': 'generateKey',
-
- 'input @ui.customRuleCreationKey': 'flagKey',
- 'keydown @ui.customRuleCreationKey': 'flagKey',
- 'keyup @ui.customRuleCreationKey': 'flagKey',
-
- 'click #coding-rules-custom-rule-creation-cancel': 'destroy',
- 'click @ui.customRuleCreationCreate': 'create',
- 'click @ui.customRuleCreationReactivate': 'reactivate'
- };
- },
-
- generateKey() {
- if (!this.keyModifiedByUser && this.ui.customRuleCreationKey) {
- const generatedKey = latinize(this.ui.customRuleCreationName.val()).replace(
- /[^A-Za-z0-9]/g,
- '_'
- );
- this.ui.customRuleCreationKey.val(generatedKey);
- }
- },
-
- flagKey() {
- this.keyModifiedByUser = true;
- },
-
- onRender() {
- ModalFormView.prototype.onRender.apply(this, arguments);
-
- this.keyModifiedByUser = false;
-
- const format = function(state) {
- if (!state.id) {
- return state.text;
- } else {
- return `<i class="icon-severity-${state.id.toLowerCase()}"></i> ${state.text}`;
- }
- };
- const type = (this.model && this.model.get('type')) || this.options.templateRule.get('type');
- const severity =
- (this.model && this.model.get('severity')) || this.options.templateRule.get('severity');
- const status =
- (this.model && this.model.get('status')) || this.options.templateRule.get('status');
-
- this.ui.customRuleCreationType.val(type);
- this.ui.customRuleCreationType.select2({
- width: '250px',
- minimumResultsForSearch: 999
- });
-
- this.ui.customRuleCreationSeverity.val(severity);
- this.ui.customRuleCreationSeverity.select2({
- width: '250px',
- minimumResultsForSearch: 999,
- formatResult: format,
- formatSelection: format
- });
-
- this.ui.customRuleCreationStatus.val(status);
- this.ui.customRuleCreationStatus.select2({
- width: '250px',
- minimumResultsForSearch: 999
- });
- },
-
- create(e) {
- e.preventDefault();
- const action = this.model && this.model.has('key') ? 'update' : 'create';
- const options = {
- name: this.ui.customRuleCreationName.val(),
- markdown_description: this.ui.customRuleCreationHtmlDescription.val(),
- type: this.ui.customRuleCreationType.val(),
- severity: this.ui.customRuleCreationSeverity.val(),
- status: this.ui.customRuleCreationStatus.val()
- };
- if (this.model && this.model.has('key')) {
- options.key = this.model.get('key');
- } else {
- Object.assign(options, {
- template_key: this.options.templateRule.get('key'),
- custom_key: this.ui.customRuleCreationKey.val(),
- prevent_reactivation: true
- });
- }
- const params = this.ui.customRuleCreationParameters
- .map(function() {
- const node = $(this);
- let value = node.val();
- if (!value && action === 'create') {
- value = node.prop('placeholder') || '';
- }
- return {
- key: node.prop('name'),
- value
- };
- })
- .get();
- options.params = params.map(param => param.key + '=' + csvEscape(param.value)).join(';');
- this.sendRequest(action, options);
- },
-
- reactivate() {
- const options = {
- name: this.existingRule.name,
- markdown_description: this.existingRule.mdDesc,
- severity: this.existingRule.severity,
- status: this.existingRule.status,
- template_key: this.existingRule.templateKey,
- custom_key: this.ui.customRuleCreationKey.val(),
- prevent_reactivation: false
- };
- const params = this.existingRule.params;
- options.params = params.map(param => param.key + '=' + param.defaultValue).join(';');
- this.sendRequest('create', options);
- },
-
- sendRequest(action, options) {
- this.$('.alert').addClass('hidden');
- const that = this;
- const url = window.baseUrl + '/api/rules/' + action;
- return $.ajax({
- url,
- type: 'POST',
- data: options,
- statusCode: {
- // do not show global error
- 400: null
- }
- })
- .done(() => {
- if (that.options.templateRule) {
- that.options.app.controller.showDetails(that.options.templateRule);
- } else {
- that.options.app.controller.showDetails(that.model);
- }
- that.destroy();
- })
- .fail(jqXHR => {
- if (jqXHR.status === 409) {
- that.existingRule = jqXHR.responseJSON.rule;
- that.showErrors([], [{ msg: translate('coding_rules.reactivate.help') }]);
- that.ui.customRuleCreationCreate.addClass('hidden');
- that.ui.customRuleCreationReactivate.removeClass('hidden');
- } else {
- that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
- }
- });
- },
-
- serializeData() {
- let params = {};
- if (this.options.templateRule) {
- params = this.options.templateRule.get('params');
- } else if (this.model && this.model.has('params')) {
- params = this.model.get('params').map(p => ({ ...p, value: p.defaultValue }));
- }
-
- const statuses = ['READY', 'BETA', 'DEPRECATED'].map(status => {
- return {
- id: status,
- text: translate('rules.status', status.toLowerCase())
- };
- });
-
- return {
- ...ModalFormView.prototype.serializeData.apply(this, arguments),
- params,
- statuses,
- change: this.model && this.model.has('key'),
- severities: ['BLOCKER', 'CRITICAL', 'MAJOR', 'MINOR', 'INFO'],
- types: ['BUG', 'VULNERABILITY', 'CODE_SMELL']
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import Marionette from 'backbone.marionette';
-import DeleteRuleView from './delete-rule-view';
-import Template from '../templates/rule/coding-rules-custom-rule.hbs';
-
-export default Marionette.ItemView.extend({
- tagName: 'tr',
- template: Template,
-
- modelEvents: {
- change: 'render'
- },
-
- events: {
- 'click .js-delete-custom-rule': 'deleteRule'
- },
-
- deleteRule() {
- const deleteRuleView = new DeleteRuleView({
- model: this.model
- }).render();
-
- deleteRuleView.on('delete', () => {
- this.model.collection.remove(this.model);
- this.destroy();
- });
- },
-
- serializeData() {
- return {
- ...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
- canDeleteCustomRule: this.options.app.customRules && this.options.app.canWrite,
- templateRule: this.options.templateRule,
- permalink: window.baseUrl + '/coding_rules/#rule_key=' + encodeURIComponent(this.model.id)
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import Marionette from 'backbone.marionette';
-import CustomRuleView from './custom-rule-view';
-import CustomRuleCreationView from './custom-rule-creation-view';
-import Template from '../templates/rule/coding-rules-custom-rules.hbs';
-
-export default Marionette.CompositeView.extend({
- template: Template,
- childView: CustomRuleView,
- childViewContainer: '#coding-rules-detail-custom-rules',
-
- childViewOptions() {
- return {
- app: this.options.app,
- templateRule: this.model
- };
- },
-
- modelEvents: {
- change: 'render'
- },
-
- events: {
- 'click .js-create-custom-rule': 'createCustomRule'
- },
-
- onRender() {
- this.$el.toggleClass('hidden', !this.model.get('isTemplate'));
- },
-
- createCustomRule() {
- new CustomRuleCreationView({
- app: this.options.app,
- templateRule: this.model
- }).render();
- },
-
- serializeData() {
- return {
- ...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
- canCreateCustomRule: this.options.app.customRules && this.options.app.canWrite
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import ModalFormView from '../../../components/common/modal-form';
-import Template from '../templates/rule/coding-rules-delete-rule.hbs';
-import { deleteRule } from '../../../api/rules';
-
-export default ModalFormView.extend({
- template: Template,
-
- onFormSubmit() {
- ModalFormView.prototype.onFormSubmit.apply(this, arguments);
- deleteRule({ key: this.model.id }).then(
- () => {
- this.destroy();
- this.trigger('delete');
- },
- () => {}
- );
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import $ from 'jquery';
-import Backbone from 'backbone';
-import ModalForm from '../../../components/common/modal-form';
-import Template from '../templates/rule/coding-rules-profile-activation.hbs';
-import { csvEscape } from '../../../helpers/csv';
-import { sortProfiles } from '../../quality-profiles/utils';
-
-export default ModalForm.extend({
- template: Template,
-
- ui() {
- return {
- ...ModalForm.prototype.ui.apply(this, arguments),
- qualityProfileSelect: '#coding-rules-quality-profile-activation-select',
- qualityProfileSeverity: '#coding-rules-quality-profile-activation-severity',
- qualityProfileActivate: '#coding-rules-quality-profile-activation-activate',
- qualityProfileParameters: '[name]'
- };
- },
-
- events() {
- return {
- ...ModalForm.prototype.events.apply(this, arguments),
- 'click @ui.qualityProfileActivate': 'activate'
- };
- },
-
- onRender() {
- ModalForm.prototype.onRender.apply(this, arguments);
-
- this.ui.qualityProfileSelect.select2({
- width: '250px',
- minimumResultsForSearch: 5
- });
-
- const that = this;
- const format = function(state) {
- if (!state.id) {
- return state.text;
- } else {
- return `<i class="icon-severity-${state.id.toLowerCase()}"></i> ${state.text}`;
- }
- };
- const severity =
- (this.model && this.model.get('severity')) || this.options.rule.get('severity');
- this.ui.qualityProfileSeverity.val(severity);
- this.ui.qualityProfileSeverity.select2({
- width: '250px',
- minimumResultsForSearch: 999,
- formatResult: format,
- formatSelection: format
- });
- setTimeout(() => {
- that
- .$('a')
- .first()
- .focus();
- }, 0);
- },
-
- activate(e) {
- e.preventDefault();
- const that = this;
- let profileKey = this.ui.qualityProfileSelect.val();
- const params = this.ui.qualityProfileParameters
- .map(function() {
- return {
- key: $(this).prop('name'),
- value: $(this).val() || $(this).prop('placeholder') || ''
- };
- })
- .get();
- const paramsHash = params.map(param => param.key + '=' + csvEscape(param.value)).join(';');
-
- if (this.model) {
- profileKey = this.model.get('qProfile');
- if (!profileKey) {
- profileKey = this.model.get('key');
- }
- }
-
- const severity = this.ui.qualityProfileSeverity.val();
- const ruleKey = this.options.rule.get('key');
-
- this.disableForm();
-
- return $.ajax({
- type: 'POST',
- url: window.baseUrl + '/api/qualityprofiles/activate_rule',
- data: {
- severity,
- profile_key: profileKey,
- rule_key: ruleKey,
- params: paramsHash
- },
- statusCode: {
- // do not show global error
- 400: null
- }
- })
- .done(() => {
- that.destroy();
- that.trigger('profileActivated', severity, params, profileKey);
- })
- .fail(jqXHR => {
- that.enableForm();
- that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
- });
- },
-
- getAvailableQualityProfiles(lang) {
- const activeQualityProfiles = this.collection || new Backbone.Collection();
- const inactiveProfiles = this.options.app.qualityProfiles.filter(
- profile => !activeQualityProfiles.findWhere({ key: profile.key })
- );
- // choose QP which a user can administrate, which are the same language and which are not built-in
- return inactiveProfiles
- .filter(profile => profile.actions && profile.actions.edit)
- .filter(profile => profile.language === lang)
- .filter(profile => !profile.isBuiltIn);
- },
-
- serializeData() {
- let params = this.options.rule.get('params');
- if (this.model != null) {
- const modelParams = this.model.get('params');
- if (Array.isArray(modelParams)) {
- params = params.map(p => {
- const parentParam = modelParams.find(param => param.key === p.key);
- if (parentParam != null) {
- Object.assign(p, { value: parentParam.value });
- }
- return p;
- });
- }
- }
-
- const availableProfiles = this.getAvailableQualityProfiles(this.options.rule.get('lang'));
- const contextProfile = this.options.app.state.get('query').qprofile;
-
- // decrease depth by 1, so the top level starts at 0
- const profilesWithDepth = sortProfiles(availableProfiles).map(profile => ({
- ...profile,
- depth: profile.depth - 1
- }));
-
- return {
- ...ModalForm.prototype.serializeData.apply(this, arguments),
- params,
- contextProfile,
- change: this.model && this.model.has('severity'),
- qualityProfiles: profilesWithDepth,
- severities: ['BLOCKER', 'CRITICAL', 'MAJOR', 'MINOR', 'INFO'],
- saveEnabled: availableProfiles.length > 0 || (this.model && this.model.get('qProfile')),
- isCustomRule:
- (this.model && this.model.has('templateKey')) || this.options.rule.has('templateKey')
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import $ from 'jquery';
-import Marionette from 'backbone.marionette';
-import Template from '../templates/rule/coding-rules-rule-description.hbs';
-import confirmDialog from '../confirm-dialog';
-import { translate } from '../../../helpers/l10n';
-
-export default Marionette.ItemView.extend({
- template: Template,
-
- modelEvents: {
- change: 'render'
- },
-
- ui: {
- descriptionExtra: '#coding-rules-detail-description-extra',
- extendDescriptionLink: '#coding-rules-detail-extend-description',
- extendDescriptionForm: '.coding-rules-detail-extend-description-form',
- extendDescriptionSubmit: '#coding-rules-detail-extend-description-submit',
- extendDescriptionRemove: '#coding-rules-detail-extend-description-remove',
- extendDescriptionText: '#coding-rules-detail-extend-description-text',
- cancelExtendDescription: '#coding-rules-detail-extend-description-cancel'
- },
-
- events: {
- 'click @ui.extendDescriptionLink': 'showExtendDescriptionForm',
- 'click @ui.cancelExtendDescription': 'hideExtendDescriptionForm',
- 'click @ui.extendDescriptionSubmit': 'submitExtendDescription',
- 'click @ui.extendDescriptionRemove': 'removeExtendedDescription'
- },
-
- showExtendDescriptionForm() {
- this.ui.descriptionExtra.addClass('hidden');
- this.ui.extendDescriptionForm.removeClass('hidden');
- this.ui.extendDescriptionText.focus();
- },
-
- hideExtendDescriptionForm() {
- this.ui.descriptionExtra.removeClass('hidden');
- this.ui.extendDescriptionForm.addClass('hidden');
- },
-
- submitExtendDescription() {
- const that = this;
- this.ui.extendDescriptionForm.addClass('hidden');
- const data = {
- key: this.model.get('key'),
- markdown_note: this.ui.extendDescriptionText.val()
- };
- if (this.options.app.organization) {
- data.organization = this.options.app.organization;
- }
- return $.ajax({
- type: 'POST',
- url: window.baseUrl + '/api/rules/update',
- dataType: 'json',
- data
- })
- .done(r => {
- that.model.set({
- htmlNote: r.rule.htmlNote,
- mdNote: r.rule.mdNote
- });
- that.render();
- })
- .fail(() => {
- that.render();
- });
- },
-
- removeExtendedDescription() {
- const that = this;
- confirmDialog({
- html: translate('coding_rules.remove_extended_description.confirm'),
- yesHandler() {
- that.ui.extendDescriptionText.val('');
- that.submitExtendDescription();
- }
- });
- },
-
- serializeData() {
- return {
- ...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
- isCustom: this.model.get('isCustom'),
- canCustomizeRule: this.options.app.canWrite
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import $ from 'jquery';
-import RuleFilterView from '../rule-filter-view';
-
-export default {
- onRuleFilterClick(e) {
- e.preventDefault();
- e.stopPropagation();
- $('body').click();
- const that = this;
- const popup = new RuleFilterView({
- triggerEl: $(e.currentTarget),
- bottomRight: true,
- model: this.model
- });
- popup.on('select', (property, value) => {
- const obj = {};
- obj[property] = '' + value;
- that.options.app.state.updateFilter(obj);
- popup.destroy();
- });
- popup.render();
- }
-};
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import Marionette from 'backbone.marionette';
-import Template from '../templates/rule/coding-rules-rule-issues.hbs';
-import { searchIssues } from '../../../api/issues';
-import { getPathUrlAsString, getComponentIssuesUrl, getBaseUrl } from '../../../helpers/urls';
-
-export default Marionette.ItemView.extend({
- template: Template,
-
- initialize() {
- this.total = null;
- this.projects = [];
- this.loading = true;
- this.mounted = true;
- this.requestIssues().then(
- () => {
- if (this.mounted) {
- this.loading = false;
- this.render();
- }
- },
- () => {
- this.loading = false;
- }
- );
- },
-
- onDestroy() {
- this.mounted = false;
- },
-
- requestIssues() {
- const parameters = {
- rules: this.model.id,
- resolved: false,
- ps: 1,
- facets: 'projectUuids'
- };
- const { organization } = this.options.app;
- if (organization) {
- Object.assign(parameters, { organization });
- }
- return searchIssues(parameters).then(r => {
- const projectsFacet = r.facets.find(facet => facet.property === 'projectUuids');
- let projects = projectsFacet != null ? projectsFacet.values : [];
- projects = projects.map(project => {
- const projectBase = r.components.find(component => component.uuid === project.val);
- return {
- ...project,
- name: projectBase != null ? projectBase.longName : '',
- issuesUrl:
- projectBase != null &&
- getPathUrlAsString(
- getComponentIssuesUrl(projectBase.key, {
- resolved: 'false',
- rules: this.model.id
- })
- )
- };
- });
- this.projects = projects;
- this.total = r.total;
- });
- },
-
- serializeData() {
- const { organization } = this.options.app;
- const pathname = organization ? `/organizations/${organization}/issues` : '/issues';
- const query = `?resolved=false&rules=${encodeURIComponent(this.model.id)}`;
- const totalIssuesUrl = getBaseUrl() + pathname + query;
- return {
- ...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
- loading: this.loading,
- total: this.total,
- totalIssuesUrl,
- projects: this.projects
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import $ from 'jquery';
-import { difference, union } from 'lodash';
-import Marionette from 'backbone.marionette';
-import RuleFilterMixin from './rule-filter-mixin';
-import Template from '../templates/rule/coding-rules-rule-meta.hbs';
-import { getRuleTags } from '../../../api/rules';
-
-export default Marionette.ItemView.extend(RuleFilterMixin).extend({
- template: Template,
-
- modelEvents: {
- change: 'render'
- },
-
- ui: {
- tagsChange: '.coding-rules-detail-tags-change',
- tagInput: '.coding-rules-detail-tag-input',
- tagsEdit: '.coding-rules-detail-tag-edit',
- tagsEditDone: '.coding-rules-detail-tag-edit-done',
- tagsEditCancel: '.coding-rules-details-tag-edit-cancel',
- tagsList: '.coding-rules-detail-tag-list'
- },
-
- events: {
- 'click @ui.tagsChange': 'changeTags',
- 'click @ui.tagsEditDone': 'editDone',
- 'click @ui.tagsEditCancel': 'cancelEdit',
- 'click .js-rule-filter': 'onRuleFilterClick'
- },
-
- onRender() {
- this.$('[data-toggle="tooltip"]').tooltip({
- container: 'body'
- });
- },
-
- onDestroy() {
- this.$('[data-toggle="tooltip"]').tooltip('destroy');
- },
-
- changeTags() {
- getRuleTags({ organization: this.options.app.organization }).then(
- tags => {
- this.ui.tagInput.select2({
- tags: difference(difference(tags, this.model.get('tags')), this.model.get('sysTags')),
- width: '300px'
- });
-
- this.ui.tagsEdit.removeClass('hidden');
- this.ui.tagsList.addClass('hidden');
- this.tagsBuffer = this.ui.tagInput.select2('val');
- this.ui.tagInput.select2('open');
- },
- () => {}
- );
- },
-
- cancelEdit() {
- this.ui.tagsList.removeClass('hidden');
- this.ui.tagsEdit.addClass('hidden');
- if (this.ui.tagInput.select2) {
- this.ui.tagInput.select2('val', this.tagsBuffer);
- this.ui.tagInput.select2('close');
- }
- },
-
- editDone() {
- const that = this;
- const tags = this.ui.tagInput.val();
- const data = { key: this.model.get('key'), tags };
- if (this.options.app.organization) {
- data.organization = this.options.app.organization;
- }
- return $.ajax({
- type: 'POST',
- url: window.baseUrl + '/api/rules/update',
- data
- })
- .done(r => {
- that.model.set('tags', r.rule.tags);
- that.cancelEdit();
- })
- .always(() => {
- that.cancelEdit();
- });
- },
-
- serializeData() {
- const permalinkPath = this.options.app.organization
- ? `/organizations/${this.options.app.organization}/rules`
- : '/coding_rules';
-
- return {
- ...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
- canCustomizeRule: this.options.app.canWrite,
- allTags: union(this.model.get('sysTags'), this.model.get('tags')),
- permalink: window.baseUrl + permalinkPath + '#rule_key=' + encodeURIComponent(this.model.id)
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import Marionette from 'backbone.marionette';
-import Template from '../templates/rule/coding-rules-rule-parameters.hbs';
-
-export default Marionette.ItemView.extend({
- template: Template,
-
- modelEvents: {
- change: 'render'
- },
-
- onRender() {
- const params = this.model.get('params');
- this.$el.toggleClass('hidden', params == null || params.length === 0);
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import { stringify } from 'querystring';
-import $ from 'jquery';
-import { sortBy } from 'lodash';
-import Backbone from 'backbone';
-import Marionette from 'backbone.marionette';
-import ProfileActivationView from './profile-activation-view';
-import Template from '../templates/rule/coding-rules-rule-profile.hbs';
-import confirmDialog from '../confirm-dialog';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-
-export default Marionette.ItemView.extend({
- tagName: 'tr',
- template: Template,
-
- modelEvents: {
- change: 'render'
- },
-
- ui: {
- change: '.coding-rules-detail-quality-profile-change',
- revert: '.coding-rules-detail-quality-profile-revert',
- deactivate: '.coding-rules-detail-quality-profile-deactivate'
- },
-
- events: {
- 'click @ui.change': 'change',
- 'click @ui.revert': 'revert',
- 'click @ui.deactivate': 'deactivate'
- },
-
- onRender() {
- this.$('[data-toggle="tooltip"]').tooltip({
- container: 'body'
- });
- },
-
- change() {
- const that = this;
- const activationView = new ProfileActivationView({
- model: this.model,
- collection: this.model.collection,
- rule: this.options.rule,
- app: this.options.app
- });
- activationView.on('profileActivated', () => {
- that.options.refreshActives();
- });
- activationView.render();
- },
-
- revert() {
- const that = this;
- const ruleKey = this.options.rule.get('key');
- confirmDialog({
- title: translate('coding_rules.revert_to_parent_definition'),
- html: translateWithParameters(
- 'coding_rules.revert_to_parent_definition.confirm',
- this.getParent().name
- ),
- yesLabel: translate('yes'),
- noLabel: translate('cancel'),
- yesHandler() {
- return $.ajax({
- type: 'POST',
- url: window.baseUrl + '/api/qualityprofiles/activate_rule',
- data: {
- profile_key: that.model.get('qProfile'),
- rule_key: ruleKey,
- reset: true
- }
- }).done(() => {
- that.options.refreshActives();
- });
- }
- });
- },
-
- deactivate() {
- const that = this;
- const ruleKey = this.options.rule.get('key');
- confirmDialog({
- title: translate('coding_rules.deactivate'),
- html: translateWithParameters('coding_rules.deactivate.confirm'),
- yesLabel: translate('yes'),
- noLabel: translate('cancel'),
- yesHandler() {
- return $.ajax({
- type: 'POST',
- url: window.baseUrl + '/api/qualityprofiles/deactivate_rule',
- data: {
- profile_key: that.model.get('qProfile'),
- rule_key: ruleKey
- }
- }).done(() => {
- that.options.refreshActives();
- });
- }
- });
- },
-
- enableUpdate() {
- return this.ui.update.prop('disabled', false);
- },
-
- getParent() {
- if (!(this.model.get('inherit') && this.model.get('inherit') !== 'NONE')) {
- return null;
- }
- const myProfile = this.options.app.qualityProfiles.find(
- p => p.key === this.model.get('qProfile')
- );
- if (!myProfile) {
- return null;
- }
- const parentKey = myProfile.parentKey;
- const parent = { ...this.options.app.qualityProfiles.find(p => p.key === parentKey) };
- const parentActiveInfo =
- this.model.collection.findWhere({ qProfile: parentKey }) || new Backbone.Model();
- Object.assign(parent, parentActiveInfo.toJSON());
- return parent;
- },
-
- enhanceParameters(parent) {
- const params = sortBy(this.model.get('params'), 'key');
- if (!parent) {
- return params;
- }
- return params.map(p => {
- const parentParam = parent.params.find(param => param.key === p.key);
- if (parentParam != null) {
- return { ...p, original: parentParam.value };
- } else {
- return p;
- }
- });
- },
-
- getProfilePath(language, name) {
- const { organization } = this.options.app;
- const query = stringify({ language, name });
- return organization
- ? `${window.baseUrl}/organizations/${organization}/quality_profiles/show?${query}`
- : `${window.baseUrl}/profiles/show?${query}`;
- },
-
- serializeData() {
- const parent = this.getParent();
-
- return {
- ...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
- parent,
- actions: this.model.get('actions') || {},
- canWrite: this.options.app.canWrite,
- parameters: this.enhanceParameters(parent),
- templateKey: this.options.rule.get('templateKey'),
- isTemplate: this.options.rule.get('isTemplate'),
- profilePath: this.getProfilePath(this.model.get('language'), this.model.get('name')),
- parentProfilePath: parent && this.getProfilePath(parent.language, parent.name)
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import Marionette from 'backbone.marionette';
-import ProfileView from './rule-profile-view';
-import ProfileActivationView from './profile-activation-view';
-import Template from '../templates/rule/coding-rules-rule-profiles.hbs';
-
-export default Marionette.CompositeView.extend({
- template: Template,
- childView: ProfileView,
- childViewContainer: '#coding-rules-detail-quality-profiles',
-
- childViewOptions() {
- return {
- app: this.options.app,
- rule: this.model,
- refreshActives: this.refreshActives.bind(this)
- };
- },
-
- modelEvents: {
- change: 'render'
- },
-
- events: {
- 'click #coding-rules-quality-profile-activate': 'activate'
- },
-
- onRender() {
- let qualityProfilesVisible = true;
-
- if (this.model.get('isTemplate')) {
- qualityProfilesVisible = this.collection.length > 0;
- }
-
- this.$el.toggleClass('hidden', !qualityProfilesVisible);
- },
-
- activate() {
- const activationView = new ProfileActivationView({
- rule: this.model,
- collection: this.collection,
- app: this.options.app
- });
- activationView.on('profileActivated', (severity, params, profile) => {
- if (this.options.app.state.get('query').qprofile === profile) {
- const activation = {
- severity,
- params,
- inherit: 'NONE',
- qProfile: profile
- };
- this.model.set({ activation });
- }
- this.refreshActives();
- });
- activationView.render();
- },
-
- refreshActives() {
- this.options.app.controller.getRuleDetails(this.model).then(
- data => {
- this.collection.reset(
- this.model.getInactiveProfiles(data.actives, this.options.app.qualityProfiles)
- );
- this.options.app.controller.updateActivation(this.model, data.actives);
- },
- () => {}
- );
- },
-
- serializeData() {
- // show "Activate" button only if user has at least one QP of the same language which he administates
- const ruleLang = this.model.get('lang');
- const canActivate = this.options.app.qualityProfiles.some(
- profile => profile.actions && profile.actions.edit && profile.language === ruleLang
- );
-
- return {
- ...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
- canActivate
- };
- }
-});
.coding-rules-detail-property {
display: inline-block;
- vertical-align: middle;
margin-right: 20px;
font-size: var(--smallFontSize);
- height: var(--controlHeight);
- line-height: var(--controlHeight);
}
.coding-rules-detail-property .select2-search-field {
+++ /dev/null
-<form>
- <div class="modal-head">
- {{#eq action 'activate'}}
- <h2>{{t 'coding_rules.activate_in_quality_profile'}} ({{state.total}} {{t 'coding_rules._rules'}})</h2>
- {{/eq}}
- {{#eq action 'deactivate'}}
- <h2>{{t 'coding_rules.deactivate_in_quality_profile'}} ({{state.total}} {{t 'coding_rules._rules'}})</h2>
- {{/eq}}
- </div>
-
- <div class="modal-body modal-body-select2">
- <div class="js-modal-messages"></div>
-
- <div class="modal-field">
- <h3>
- <label for="coding-rules-bulk-change-profile">
- {{#eq action 'activate'}}{{t 'coding_rules.activate_in'}}{{/eq}}
- {{#eq action 'deactivate'}}{{t 'coding_rules.deactivate_in'}}{{/eq}}
- </label>
- </h3>
- {{#if qualityProfile}}
- <h3 class="readonly-field">
- {{qualityProfileName}}{{#notEq action 'change-severity'}} — {{t 'are_you_sure'}}{{/notEq}}
- </h3>
- {{else}}
- <select id="coding-rules-bulk-change-profile" multiple>
- {{#each availableQualityProfiles}}
- <option value="{{key}}" {{#ifLength ../availableQualityProfiles 1}}selected{{/ifLength}}>
- {{name}} - {{language}}
- </option>
- {{/each}}
- </select>
- {{/if}}
- </div>
- </div>
-
- <div class="modal-foot">
- <button id="coding-rules-submit-bulk-change">{{t 'apply'}}</button>
- <a class="js-modal-close" href="#">{{t 'close'}}</a>
- </div>
-</form>
+++ /dev/null
-<div class="bubble-popup-title">{{t 'bulk_change'}}</div>
-
-<ul class="bubble-popup-list">
-
- {{! activation }}
-
- <li>
- <a class="js-bulk-change" data-action="activate">
- {{t 'coding_rules.activate_in'}}…
- </a>
- </li>
-
- {{#if allowActivateOnProfile}}
- <li>
- <a class="js-bulk-change" data-action="activate" data-param="{{qualityProfile}}">
- {{t 'coding_rules.activate_in'}} <strong>{{qualityProfileName}}</strong>
- </a>
- </li>
- {{/if}}
-
-
-
- {{! deactivation }}
-
- <li>
- <a class="js-bulk-change" data-action="deactivate">
- {{t 'coding_rules.deactivate_in'}}…
- </a>
- </li>
-
- {{#if allowDeactivateOnProfile}}
- <li>
- <a class="js-bulk-change" data-action="deactivate" data-param="{{qualityProfile}}">
- {{tp 'coding_rules.deactivate_in'}} <strong>{{qualityProfileName}}</strong>
- </a>
- </li>
- {{/if}}
-</ul>
-
-
-<div class="bubble-popup-arrow"></div>
+++ /dev/null
-<div class="layout-page coding-rules">
- <div class="layout-page-side-outer">
- <div class="layout-page-side">
- <div class="layout-page-side-inner">
- <div class="layout-page-filters">
- </div>
- </div>
- </div>
- </div>
-
- <div class="layout-page-main">
- <div class="layout-page-header-panel layout-page-main-header">
- <div class="layout-page-header-panel-inner layout-page-main-header-inner">
- <div class="layout-page-main-inner coding-rules-header"></div>
- </div>
- </div>
- <div class="layout-page-main-inner coding-rules-list"></div>
- <div class="layout-page-main-inner coding-rules-details"></div>
- </div>
-</div>
+++ /dev/null
-<div class="js-rule-meta"></div>
-<div class="js-rule-description"></div>
-<div class="js-rule-parameters"></div>
-
-{{#if isEditable}}
- <div class="coding-rules-detail-description">
- <button class="js-edit-custom" id="coding-rules-detail-custom-rule-change">{{t 'edit'}}</button>
- <button class="button-red js-delete" id="coding-rules-detail-rule-delete" class="button-red">{{t 'delete'}}</button>
- </div>
-{{/if}}
-
-<div class="js-rule-custom-rules coding-rule-section"></div>
-<div class="js-rule-profiles coding-rule-section"></div>
-<div class="js-rule-issues coding-rule-section"></div>
+++ /dev/null
-<header class="menu-search">
- <h6>{{t 'coding_rules.filter_similar_rules'}}</h6>
-</header>
-
-<ul class="menu">
- <li>
- <a href="#" class="issue-action-option" data-property="languages" data-value="{{lang}}">
- {{langName}}
- </a>
- </li>
-
- <li>
- <a href="#" class="issue-action-option" data-property="types" data-value="{{this.type}}">
- {{issueType this.type}}
- </a>
- </li>
-
- {{#if severity}}
- <li>
- <a href="#" class="issue-action-option" data-property="severities" data-value="{{severity}}">
- {{severityHelper severity}}
- </a>
- </li>
- {{/if}}
-
- {{#notEmpty tags}}
- <li class="divider"></li>
- {{#each tags}}
- <li>
- <a href="#" class="issue-action-option" data-property="tags" data-value="{{this}}">
- <i class="icon-tags icon-half-transparent"></i> {{this}}
- </a>
- </li>
- {{/each}}
- {{/notEmpty}}
-</ul>
-
-<div class="bubble-popup-arrow"></div>
+++ /dev/null
-<div class="pull-left">
- {{#if state.rule}}
- <a class="js-back">{{t 'coding_rules.return_to_list'}}</a>
- {{else}}
- {{#if canBulkChange}}
- <button class="js-bulk-change">{{t 'bulk_change'}}</button>
- {{/if}}
- <button class="js-new-search" id="coding-rules-new-search">{{t 'clear_all_filters'}}</button>
- {{/if}}
-</div>
-
-
-<div class="pull-right">
- <span class="note big-spacer-right">
- <span class="shortcut-button little-spacer-right">↑</span><span class="shortcut-button little-spacer-right">↓</span>{{t 'coding_rules.to_select_rules'}}
- <span class="shortcut-button little-spacer-right big-spacer-left">←</span><span class="shortcut-button little-spacer-right">→</span>{{t 'issues.to_navigate'}}
- </span>
-
- {{#notNull state.total}}
- <a class="js-reload link-no-underline" href="#">
- <svg width="18" height="24" viewBox="0 0 18 24">
- <path fill="#777" d="M16.6454 8.1084c-.3-.5-.9-.7-1.4-.4-.5.3-.7.9-.4 1.4.9 1.6 1.1 3.4.6 5.1-.5 1.7-1.7 3.2-3.2 4-3.3 1.8-7.4.6-9.1-2.7-1.8-3.1-.8-6.9 2.1-8.8v3.3h2v-7h-7v2h3.9c-3.7 2.5-5 7.5-2.8 11.4 1.6 3 4.6 4.6 7.7 4.6 1.4 0 2.8-.3 4.2-1.1 2-1.1 3.5-3 4.2-5.2.6-2.2.3-4.6-.8-6.6z" />
- </svg>
- </a>
-
- <div class="search-navigator-header-pagination spacer-left flash flash-heavy">
- <strong>
- {{#gt state.total 0}}
- <span class="current">
- {{sum state.selectedIndex 1}}
- /
- <span id="coding-rules-total">{{formatMeasure state.total 'INT'}}</span>
- </span>
- {{else}}
- <span class="current">0 / <span id="coding-rules-total">0</span></span>
- {{/gt}}
- </strong>
- {{t 'coding_rules._rules'}}
- </div>
- {{/notNull}}
-</div>
+++ /dev/null
-<table class="coding-rule-table">
- <tr>
- {{#if activation}}
- <td class="coding-rule-table-meta-cell coding-rule-activation">
- {{severityIcon activation.severity}}
- {{#eq activation.inherit 'OVERRIDES'}}
- <i class="icon-inheritance icon-inheritance-overridden"
- title="{{tp 'coding_rules.overrides' activation.profile.name activation.parentProfile.name}}"></i>
- {{/eq}}
- {{#eq activation.inherit 'INHERITED'}}
- <i class="icon-inheritance"
- title="{{tp 'coding_rules.inherits' activation.profile.name activation.parentProfile.name}}"></i>
- {{/eq}}
- </td>
- {{/if}}
-
- <td>
- <div class="coding-rule-title">
- <a class="js-rule link-no-underline" href="{{permalink}}">{{name}}</a>
- {{#if isTemplate}}
- <span class="outline-badge spacer-left" title="{{t 'coding_rules.rule_template.title'}}"
- data-toggle="tooltip" data-placement="bottom">{{t 'coding_rules.rule_template'}}</span>
- {{/if}}
- </div>
- </td>
-
- <td class="coding-rule-table-meta-cell">
- <div class="coding-rule-meta">
- {{#notEq status 'READY'}}
- <span class="badge badge-normal-size badge-danger-light">
- {{t 'rules.status' status}}
- </span>
-
- {{/notEq}}
- <span class="note">{{langName}}</span>
-
- <span class="note" data-toggle="tooltip" data-placement="bottom"
- title="{{t 'coding_rules.type.tooltip' this.type}}">
- {{issueTypeIcon this.type}} {{issueType this.type}}
- </span>
- {{#notEmpty tags}}
-
- <i class="icon-tags"></i>
- <span class="note">{{join tags ', '}}</span>
- {{/notEmpty}}
- <a class="js-rule-filter link-no-underline spacer-left" href="#">
- <i class="icon-filter icon-half-transparent"></i> <i class="icon-dropdown"></i>
- </a>
- </div>
- </td>
-
- {{#any activation selectedProfile}}
- {{#if canEditQualityProfile}}
- {{#unless isSelectedProfileBuiltIn}}
- <td class="coding-rule-table-meta-cell coding-rule-activation-actions">
- {{#if activation}}
- <button class="coding-rules-detail-quality-profile-deactivate button-red"
- {{#notEq activation.inherit 'NONE'}}disabled title="{{t 'coding_rules.can_not_deactivate'}}"{{/notEq}}>
- {{t 'coding_rules.deactivate'}}
- </button>
- {{else}}
- {{#unless isTemplate}}
- <button class="coding-rules-detail-quality-profile-activate">{{t 'coding_rules.activate'}}</button>
- {{/unless}}
- {{/if}}
- </td>
- {{/unless}}
- {{/if}}
- {{/any}}
- </tr>
-</table>
+++ /dev/null
-<div class="js-list"></div>
-
-<div class="search-navigator-workspace-list-more">
- <span class="js-more"><i class="spinner"></i></span>
-</div>
+++ /dev/null
-<span class="search-navigator-facet-header">
- <a class="js-facet-toggle">
- <i class="icon-checkbox {{#if enabled}}icon-checkbox-checked{{/if}}"></i>
- {{t 'coding_rules.facet' property}}
- </a>
-</span>
+++ /dev/null
-{{> '_coding-rules-facet-header'}}
-
-<div class="search-navigator-facet-container">
- <input type="text" class="search-navigator-facet-input" name="availableSince" placeholder="{{t 'date'}}">
-</div>
+++ /dev/null
-{{> '_coding-rules-facet-header'}}
-
-<div class="search-navigator-facet-list">
- {{#each values}}
- <a class="facet search-navigator-facet js-facet" data-value="{{val}}" title="{{default label val}}">
- <span class="facet-name">{{default label val}}{{#if extra}} <span class="note">{{extra}}</span>{{/if}}</span>
- <span class="facet-stat">{{formatMeasure count 'SHORT_INT'}}</span>
- </a>
- {{/each}}
-</div>
+++ /dev/null
-{{> '_coding-rules-facet-header'}}
-
-<div class="search-navigator-facet-list">
- {{#each values}}
- <a class="facet search-navigator-facet js-facet" data-value="{{val}}" title="{{default label val}}">
- <span class="facet-name">{{default label val}}{{#if extra}} <span class="note">{{extra}}</span>{{/if}}</span>
- <span class="facet-stat">{{formatMeasure count 'SHORT_INT'}}</span>
- </a>
- {{/each}}
-
- <div class="search-navigator-facet-custom-value">
- <input type="hidden" class="js-custom-value">
- </div>
-</div>
+++ /dev/null
-{{> '_coding-rules-facet-header'}}
-
-<div class="search-navigator-facet-list">
- {{#each values}}
- <a class="facet search-navigator-facet js-facet" data-value="{{val}}" title="{{default label val}}">
- <span class="facet-name">{{default label val}}{{#if extra}} <span class="note">{{extra}}</span>{{/if}}</span>
- </a>
- {{/each}}
-</div>
+++ /dev/null
-{{> '_coding-rules-facet-header'}}
-
-<div class="search-navigator-facet-container">
- {{key}}
-</div>
+++ /dev/null
-{{> '_coding-rules-facet-header'}}
-
-<div class="search-navigator-facet-list">
- {{#each values}}
- <a class="facet search-navigator-facet js-facet" data-value="{{val}}" title="{{default label val}}">
- <span class="facet-name">{{default label val}}{{#if extra}} <span class="note">{{extra}}</span>{{/if}}{{#if isBuiltIn}} <span class="note">({{t 'quality_profiles.built_in'}})</span>{{/if}}</span>
- <span class="facet-stat">
- <span class="js-active facet-toggle facet-toggle-green {{#if ../toggled}}facet-toggle-active{{/if}}">active</span>
- <span class="js-inactive facet-toggle facet-toggle-red {{#unless ../toggled}}facet-toggle-active{{/unless}}">inactive</span>
- </span>
- </a>
- {{/each}}
-</div>
+++ /dev/null
-<div class="search-navigator-facet-query">
- <form class="search-box">
- <input class="search-box-input" type="text" name="q" placeholder="{{t 'search.search_for_rules'}}" maxlength="100">
- <svg class="search-box-magnifier" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
- <g transform="matrix(0.0288462,0,0,0.0288462,2,1.07692)">
- <path d="M288,208C288,177.167 277.042,150.792 255.125,128.875C233.208,106.958 206.833,96 176,96C145.167,96 118.792,106.958 96.875,128.875C74.958,150.792 64,177.167 64,208C64,238.833 74.958,265.208 96.875,287.125C118.792,309.042 145.167,320 176,320C206.833,320 233.208,309.042 255.125,287.125C277.042,265.208 288,238.833 288,208ZM416,416C416,424.667 412.833,432.167 406.5,438.5C400.167,444.833 392.667,448 384,448C375,448 367.5,444.833 361.5,438.5L275.75,353C245.917,373.667 212.667,384 176,384C152.167,384 129.375,379.375 107.625,370.125C85.875,360.875 67.125,348.375 51.375,332.625C35.625,316.875 23.125,298.125 13.875,276.375C4.625,254.625 0,231.833 0,208C0,184.167 4.625,161.375 13.875,139.625C23.125,117.875 35.625,99.125 51.375,83.375C67.125,67.625 85.875,55.125 107.625,45.875C129.375,36.625 152.167,32 176,32C199.833,32 222.625,36.625 244.375,45.875C266.125,55.125 284.875,67.625 300.625,83.375C316.375,99.125 328.875,117.875 338.125,139.625C347.375,161.375 352,184.167 352,208C352,244.667 341.667,277.917 321,307.75L406.75,393.5C412.917,399.667 416,407.167 416,416Z" style="fill:currentColor;fill-rule:nonzero;"/>
- </g>
- </svg>
- <button class="js-reset hidden button-tiny search-box-clear button-icon" style="color: rgb(153, 153, 153);" type="reset">
- <svg width="12" height="12" viewBox="0 0 16 16" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve">
- <path d="M14 4.242L11.758 2l-3.76 3.76L4.242 2 2 4.242l3.756 3.756L2 11.758 4.242 14l3.756-3.76 3.76 3.76L14 11.758l-3.76-3.76L14 4.242z" style="fill: currentcolor;"/>
- </svg>
- </button>
- <span class="js-hint search-box-note hidden" title="{{tp 'select2.tooShort' 2}}">
- {{tp 'select2.tooShort' 2}}
- </span>
- </form>
-</div>
+++ /dev/null
-{{> '_coding-rules-facet-header'}}
-
-<div class="search-navigator-facet-list">
- {{#each values}}
- <a class="facet search-navigator-facet search-navigator-facet-half js-facet" data-value="{{val}}" title="{{t 'severity' val}}">
- <span class="facet-name">{{severityIcon val}} {{t 'severity' val}}</span>
- <span class="facet-stat">{{formatMeasure count 'SHORT_INT'}}</span>
- </a>
- {{/each}}
-</div>
+++ /dev/null
-{{> '_coding-rules-facet-header'}}
-
-<div class="search-navigator-facet-list">
- <a class="facet search-navigator-facet js-facet" data-value="true">
- <span class="facet-name">{{t 'coding_rules.filters.template.is_template'}}</span>
- <span class="facet-stat"></span>
- </a>
- <a class="facet search-navigator-facet js-facet" data-value="false">
- <span class="facet-name">{{t 'coding_rules.filters.template.is_not_template'}}</span>
- <span class="facet-stat"></span>
- </a>
-</div>
+++ /dev/null
-{{> '_coding-rules-facet-header'}}
-
-<div class="search-navigator-facet-list">
- {{#each values}}
- <a class="facet search-navigator-facet js-facet"
- data-value="{{val}}">
- <span class="facet-name">{{issueTypeIcon val}} {{t 'issue.type' val}}</span>
- <span class="facet-stat">{{formatMeasure count 'SHORT_INT'}}</span>
- </a>
- {{/each}}
-</div>
+++ /dev/null
-<form>
- <div class="modal-head">
- {{#if change}}
- <h2>{{t 'coding_rules.update_custom_rule'}}</h2>
- {{else}}
- <h2>{{t 'coding_rules.create_custom_rule'}}</h2>
- {{/if}}
- </div>
-
- <div class="modal-body modal-container">
- <div class="js-modal-messages"></div>
-
- <table>
- <tr class="property">
- <th class="nowrap"><h3>{{t 'name'}} <em class="mandatory">*</em></h3></th>
- <td>
- <input type="text" name="name" id="coding-rules-custom-rule-creation-name"
- class="coding-rules-name-key" value="{{name}}"/>
- </td>
- </tr>
- <tr class="property">
- <th class="nowrap"><h3>{{t 'key'}}{{#unless change}} <em class="mandatory">*</em>{{/unless}}</h3></th>
- <td>
- {{#if change}}
- <span class="coding-rules-detail-custom-rule-key" title="{{key}}">{{key}}</span>
- {{else}}
- <input type="text" name="key" id="coding-rules-custom-rule-creation-key"
- class="coding-rules-name-key" value="{{internalKey}}"/>
- {{/if}}
- </td>
- </tr>
- <tr class="property">
- <th class="nowrap"><h3>{{t 'description'}} <em class="mandatory">*</em></h3></th>
- <td>
- <textarea name="markdown_description" id="coding-rules-custom-rule-creation-html-description"
- class="coding-rules-markdown-description" rows="15">{{{mdDesc}}}</textarea>
- <span class="text-right">{{> '../../../../components/common/templates/_markdown-tips' }}</span>
- </td>
- </tr>
- <tr class="property">
- <th class="nowrap"><h3>{{t 'type'}}</h3></th>
- <td>
- <select id="coding-rules-custom-rule-creation-type">
- {{#each types}}
- <option value="{{this}}">{{t 'issue.type' this}}</option>
- {{/each}}
- </select>
- </td>
- </tr>
- <tr class="property">
- <th class="nowrap"><h3>{{t 'severity'}}</h3></th>
- <td>
- <select id="coding-rules-custom-rule-creation-severity">
- {{#each severities}}
- <option value="{{this}}">{{t 'severity' this}}</option>
- {{/each}}
- </select>
- </td>
- </tr>
- <tr class="property">
- <th class="nowrap"><h3>{{t 'coding_rules.filters.status'}}</h3></th>
- <td>
- <select id="coding-rules-custom-rule-creation-status">
- {{#each statuses}}
- <option value="{{id}}">{{text}}</option>
- {{/each}}
- </select>
- </td>
- </tr>
- {{#each params}}
- <tr class="property">
- <th class="nowrap"><h3>{{key}}</h3></th>
- <td>
- {{#eq type 'TEXT'}}
- <textarea class="width100" rows="3" name="{{key}}" placeholder="{{defaultValue}}">{{value}}</textarea>
- {{else}}
- <input type="text" name="{{key}}" value="{{value}}" placeholder="{{defaultValue}}"/>
- {{/eq}}
- <div class="note">{{{htmlDesc}}}</div>
- {{#if extra}}
- <div class="note">{{extra}}</div>
- {{/if}}
- </td>
- </tr>
- {{/each}}
- </table>
- </div>
-
- <div class="modal-foot">
- <button id="coding-rules-custom-rule-creation-create">
- {{#if change}}{{t 'save'}}{{else}}{{t 'create'}}{{/if}}
- </button>
- <button id="coding-rules-custom-rule-creation-reactivate" class="hidden">{{t 'coding_rules.reactivate'}}</button>
- <a id="coding-rules-custom-rule-creation-cancel">{{t 'cancel'}}</a>
- </div>
-</form>
+++ /dev/null
-<td class="coding-rules-detail-list-name">
- <a href="{{permalink}}">{{name}}</a>
-</td>
-
-<td class="coding-rules-detail-list-severity">
- {{severityIcon severity}} {{t "severity" severity}}
-</td>
-
-<td class="coding-rules-detail-list-parameters">
- {{#each params}}
- {{#if defaultValue}}
- <div class="coding-rules-detail-list-parameter">
- <span class="key">{{key}}</span><span class="sep">: </span><span class="value" title="{{value}}">{{defaultValue}}</span>
- </div>
- {{/if}}
- {{/each}}
-
-</td>
-
-{{#if canDeleteCustomRule}}
-<td class="coding-rules-detail-list-actions">
- <button class="js-delete-custom-rule button-red">
- {{t 'delete'}}
- </button>
-</td>
-{{/if}}
+++ /dev/null
-<div class="coding-rules-detail-custom-rules-section">
- <div class="coding-rule-section-separator"></div>
-
- <h3 class="coding-rules-detail-title">{{t 'coding_rules.custom_rules'}}</h3>
-
- {{#if canCreateCustomRule}}
- <button class="js-create-custom-rule spacer-left">{{t 'coding_rules.create'}}</button>
- {{/if}}
-
- <table id="coding-rules-detail-custom-rules" class="coding-rules-detail-list"></table>
-</div>
+++ /dev/null
-<form>
- <div class="modal-head">
- <h2>{{t 'coding_rules.delete_rule'}}</h2>
- </div>
-
- <div class="modal-body">
- {{tp 'coding_rules.delete.custom.confirm' name}}
- </div>
-
- <div class="modal-foot">
- <button className="button-red">{{t 'delete'}}</button>
- <a class="js-modal-close">{{t 'cancel'}}</a>
- </div>
-</form>
+++ /dev/null
-<form>
- <div class="modal-head">
- {{#if change}}
- <h2>{{t 'coding_rules.change_details'}}</h2>
- {{else}}
- <h2>{{t 'coding_rules.activate_in_quality_profile'}}</h2>
- {{/if}}
- </div>
-
- <div class="modal-body modal-container">
- <div class="js-modal-messages"></div>
-
- {{#empty qualityProfiles}}
- {{#unless change}}
- <div class="alert alert-info">{{t 'coding_rules.active_in_all_profiles'}}</div>
- {{/unless}}
- {{/empty}}
-
- <div class="modal-field">
- <label>{{t 'coding_rules.quality_profile'}}</label>
- {{#any key qProfile}}
- {{name}}
- {{else}}
- <select id="coding-rules-quality-profile-activation-select">
- {{#each qualityProfiles}}
- <option value="{{key}}" {{#eq key ../contextProfile}}selected{{/eq}}>
- {{#repeat depth}} {{/repeat}}{{name}}
- </option>
- {{/each}}
- </select>
- {{/any}}
- </div>
- <div class="modal-field">
- <label>{{t 'severity'}}</label>
- <select id="coding-rules-quality-profile-activation-severity">
- {{#each severities}}
- <option value="{{this}}">{{t 'severity' this}}</option>
- {{/each}}
- </select>
- </div>
- {{#if isCustomRule}}
- <div class="modal-field">
- <p class="note">{{t 'coding_rules.custom_rule.activation_notice'}}</p>
- </div>
- {{else}}
- {{#each params}}
- <div class="modal-field">
- <label title="{{key}}">{{key}}</label>
- {{#eq type 'TEXT'}}
- <textarea class="width100" rows="3" name="{{key}}" placeholder="{{defaultValue}}">{{value}}</textarea>
- {{else}}
- {{#eq type 'BOOLEAN'}}
- <select name="{{key}}" value="{{value}}">
- <option value="{{defaultValue}}">{{t 'default'}} ({{t defaultValue}})</option>
- <option value="true"{{#eq value 'true'}} selected="selected"{{/eq}}>{{t 'true'}}</option>
- <option value="false"{{#eq value 'false'}} selected="selected"{{/eq}}>{{t 'false'}}</option>
- </select>
- {{else}}
- <input type="text" name="{{key}}" value="{{value}}" placeholder="{{defaultValue}}">
- {{/eq}}
- {{/eq}}
- <div class="modal-field-description">{{{htmlDesc}}}</div>
- {{#if extra}}
- <div class="modal-field-description">{{extra}}</div>
- {{/if}}
- </div>
- {{/each}}
- {{/if}}
- </div>
-
- <div class="modal-foot">
- <button id="coding-rules-quality-profile-activation-activate"
- {{#unless saveEnabled}}disabled="disabled"{{/unless}}>
- {{#if change}}{{t 'save'}}{{else}}{{t 'coding_rules.activate'}}{{/if}}
- </button>
- <a id="coding-rules-quality-profile-activation-cancel" class="js-modal-close">{{t 'cancel'}}</a>
- </div>
-</form>
+++ /dev/null
-<div class="coding-rules-detail-description rule-desc markdown">{{{htmlDesc}}}</div>
-
-{{#unless isCustom}}
- <div class="coding-rules-detail-description coding-rules-detail-description-extra">
- <div id="coding-rules-detail-description-extra">
- {{#if htmlNote}}
- <div class="rule-desc spacer-bottom markdown">{{{htmlNote}}}</div>
- {{/if}}
- {{#if canCustomizeRule}}
- <button id="coding-rules-detail-extend-description">{{t 'coding_rules.extend_description'}}</button>
- {{/if}}
- </div>
-
- {{#if canCustomizeRule}}
- <div class="coding-rules-detail-extend-description-form hidden">
- <table class="width100">
- <tbody>
- <tr>
- <td class="width100" colspan="2">
- <textarea id="coding-rules-detail-extend-description-text" rows="4"
- style="width: 100%; margin-bottom: 4px;">{{mdNote}}</textarea>
- </td>
- </tr>
- <tr>
- <td>
- <button id="coding-rules-detail-extend-description-submit">{{t 'save'}}</button>
- {{#if mdNote}}
- <button id="coding-rules-detail-extend-description-remove" class="button-red">{{t 'remove'}}</button>
- {{/if}}
- <a id="coding-rules-detail-extend-description-cancel" class="spacer-left">{{t 'cancel'}}</a>
- </td>
- <td class="text-right">
- {{> '../../../../components/common/templates/_markdown-tips' }}
- </td>
- </tr>
- </tbody>
- </table>
- </div>
- {{/if}}
- </div>
-{{/unless}}
+++ /dev/null
-<div class="coding-rule-section-separator"></div>
-
-{{#if loading}}
- <h3 class="coding-rules-detail-title">
- {{t 'coding_rules.issues'}} <i class="spinner spacer-left"/>
- </h3>
-{{else}}
- <h3 class="coding-rules-detail-title">
- {{t 'coding_rules.issues'}} (<a href="{{totalIssuesUrl}}">{{total}}</a>)
- </h3>
-
- {{#notEmpty projects}}
- <table class="coding-rules-detail-list coding-rules-most-violated-projects">
- <tr>
- <td class="coding-rules-detail-list-name" colspan="2">{{t 'coding_rules.most_violating_projects'}}</td>
- </tr>
- {{#each projects}}
- <tr>
- <td class="coding-rules-detail-list-name">{{name}}</td>
- <td class="coding-rules-detail-list-parameters">
- <a href="{{issuesUrl}}" target="_blank">{{count}}</a>
- </td>
- </tr>
- {{/each}}
- </table>
- {{/notEmpty}}
-{{/if}}
+++ /dev/null
-<header class="page-header">
- <div class="page-actions">
- <span class="note">{{key}}</span>
-
- <a class="coding-rules-detail-permalink link-no-underline spacer-left" target="_blank" href="{{permalink}}">
- <svg
- class="text-text-top"
- xmlns="http://www.w3.org/2000/svg"
- height=14
- width=14
- viewBox="0 0 16 16">
- <path
- fill="currentColor"
- d="M13.501 11.429q0-0.357-0.25-0.607l-1.857-1.857q-0.25-0.25-0.607-0.25-0.375 0-0.643 0.286 0.027 0.027 0.17 0.165t0.192 0.192 0.134 0.17 0.116 0.228 0.031 0.246q0 0.357-0.25 0.607t-0.607 0.25q-0.134 0-0.246-0.031t-0.228-0.116-0.17-0.134-0.192-0.192-0.165-0.17q-0.295 0.277-0.295 0.652 0 0.357 0.25 0.607l1.839 1.848q0.241 0.241 0.607 0.241 0.357 0 0.607-0.232l1.313-1.304q0.25-0.25 0.25-0.598zM7.224 5.134q0-0.357-0.25-0.607l-1.839-1.848q-0.25-0.25-0.607-0.25-0.348 0-0.607 0.241l-1.313 1.304q-0.25 0.25-0.25 0.598 0 0.357 0.25 0.607l1.857 1.857q0.241 0.241 0.607 0.241 0.375 0 0.643-0.277-0.027-0.027-0.17-0.165t-0.192-0.192-0.134-0.17-0.116-0.228-0.031-0.246q0-0.357 0.25-0.607t0.607-0.25q0.134 0 0.246 0.031t0.228 0.116 0.17 0.134 0.192 0.192 0.165 0.17q0.295-0.277 0.295-0.652zM15.215 11.429q0 1.071-0.759 1.813l-1.313 1.304q-0.741 0.741-1.813 0.741-1.080 0-1.821-0.759l-1.839-1.848q-0.741-0.741-0.741-1.813 0-1.098 0.786-1.866l-0.786-0.786q-0.768 0.786-1.857 0.786-1.071 0-1.821-0.75l-1.857-1.857q-0.75-0.75-0.75-1.821t0.759-1.813l1.313-1.304q0.741-0.741 1.813-0.741 1.080 0 1.821 0.759l1.839 1.848q0.741 0.741 0.741 1.813 0 1.098-0.786 1.866l0.786 0.786q0.768-0.786 1.857-0.786 1.071 0 1.821 0.75l1.857 1.857q0.75 0.75 0.75 1.821z"
- />
- </svg>
- </a>
-
- <a class="js-rule-filter link-no-underline spacer-left" href="#">
- <i class="icon-filter icon-half-transparent"></i> <i class="icon-dropdown"></i>
- </a>
- </div>
- <h3 class="page-title coding-rules-detail-header">
- <big>{{name}}</big>
- </h3>
-</header>
-
-<ul class="coding-rules-detail-properties">
- <li class="coding-rules-detail-property"
- data-toggle="tooltip" data-placement="bottom" title="{{t 'coding_rules.type.tooltip' this.type}}">
- {{issueTypeIcon this.type}} {{issueType this.type}}
- </li>
-
- <li class="coding-rules-detail-property"
- data-toggle="tooltip" data-placement="bottom" title="{{t 'default_severity'}}">
- {{severityIcon severity}} {{t "severity" severity}}
- </li>
-
- {{#notEq status 'READY'}}
- <li class="coding-rules-detail-property"
- data-toggle="tooltip" data-placement="bottom" title="{{t 'status'}}">
- <span class="badge badge-normal-size badge-danger-light">
- {{t 'rules.status' status}}
- </span>
- </li>
- {{/notEq}}
-
- <li class="coding-rules-detail-property coding-rules-detail-tag-list {{#if canCustomizeRule}}coding-rules-detail-tags-change{{/if}}"
- data-toggle="tooltip" data-placement="bottom" title="{{t 'tags'}}">
- <i class="icon-tags"></i>
- <span>{{#if allTags}}{{join allTags ', '}}{{else}}{{t 'coding_rules.no_tags'}}{{/if}}</span>
- {{#if canCustomizeRule}}<i class="icon-dropdown"></i>{{/if}}
- </li>
-
- {{#if canCustomizeRule}}
- <li class="coding-rules-detail-property coding-rules-detail-tag-edit hidden">
- {{#if sysTags}}<i class="icon-tags"></i>
- <span>{{join sysTags ', '}}</span>{{/if}}
- <input class="coding-rules-detail-tag-input" type="text" value="{{#if tags}}{{join tags ','}}{{/if}}">
-
- <button class="coding-rules-detail-tag-edit-done text-middle">{{t 'Done'}}</button>
- <a class="coding-rules-details-tag-edit-cancel spacer-left">{{t 'cancel'}}</a>
- </li>
- {{/if}}
-
- <li class="coding-rules-detail-property">{{t 'coding_rules.available_since'}} {{d createdAt}}</li>
-
- <li class="coding-rules-detail-property"
- data-toggle="tooltip" data-placement="bottom" title="Rule repository (language)">
- {{repoName}} ({{langName}})
- </li>
-
- {{#if isTemplate}}
- <li class="coding-rules-detail-property"
- title="{{t 'coding_rules.rule_template.title'}}">{{t 'coding_rules.rule_template'}}</li>
- {{/if}}
-
- {{#if templateKey}}
- <li class="coding-rules-detail-property"
- title="{{t 'coding_rules.custom_rule.title'}}">{{t 'coding_rules.custom_rule'}}
- (<a href="#rule_key={{templateKey}}">{{t 'coding_rules.show_template'}}</a>)
- </li>
- {{/if}}
-
- {{#if debtRemFnType}}
- <li class="coding-rules-detail-property"
- data-toggle="tooltip" data-placement="bottom" title="{{t 'coding_rules.remediation_function'}}">
- {{t 'coding_rules.remediation_function' debtRemFnType}}:
-
- {{#if debtRemFnOffset}}{{debtRemFnOffset}}{{/if}}
- {{#if debtRemFnCoeff}}{{#if debtRemFnOffset}}+{{/if}}{{debtRemFnCoeff}}{{/if}}
- {{#if effortToFixDescription}}{{effortToFixDescription}}{{/if}}
- </li>
- {{/if}}
-</ul>
+++ /dev/null
-<h3 class="coding-rules-detail-title">{{t 'coding_rules.parameters'}}</h3>
-<table class="coding-rules-detail-parameters">
- {{#each params}}
- <tr class="coding-rules-detail-parameter">
- <td class="coding-rules-detail-parameter-name">{{key}}</td>
- <td class="coding-rules-detail-parameter-description" data-key="{{key}}">
- <p>{{{htmlDesc}}}</p>
- {{#if ../../templateKey}}
- <div class="note spacer-top">
- {{#if defaultValue }}
- <span class="coding-rules-detail-parameter-value">{{defaultValue}}</span>
- {{else}}
- {{t 'coding_rules.parameter.empty'}}
- {{/if}}
- </div>
- {{else}}
- {{#if defaultValue}}
- <div class="note spacer-top">
- {{t 'coding_rules.parameters.default_value'}}<br>
- <span class="coding-rules-detail-parameter-value">{{defaultValue}}</span>
- </div>
- {{/if}}
- {{/if}}
- </td>
- </tr>
- {{/each}}
-</table>
+++ /dev/null
-<td class="coding-rules-detail-quality-profile-name">
- <a href="{{profilePath}}">
- {{name}}
- </a>
- {{#if isBuiltIn}}
- <span class="outline-badge spacer-left" data-toggle="tooltip" data-placement="bottom"
- title="{{t 'quality_profiles.built_in.description.1'}} {{t 'quality_profiles.built_in.description.2'}}">
- {{t 'quality_profiles.built_in'}}
- </span>
- {{/if}}
- {{#if parent}}
- <div class="coding-rules-detail-quality-profile-inheritance">
- {{#eq inherit 'OVERRIDES'}}
- <i class="icon-inheritance icon-inheritance-overridden" title="{{tp 'coding_rules.overrides' name parent.name}}"></i>
- {{/eq}}
- {{#eq inherit 'INHERITED'}}
- <i class="icon-inheritance" title="{{tp 'coding_rules.inherits' name parent.name}}"></i>
- {{/eq}}
- <a class="link-base-color" href="{{parentProfilePath}}">
- {{parent.name}}
- </a>
- </div>
- {{/if}}
-</td>
-
-{{#if severity}}
- <td class="coding-rules-detail-quality-profile-severity">
- <span data-toggle="tooltip" data-placement="bottom" title="Activation severity">
- {{severityIcon severity}} {{t "severity" severity}}
- </span>
- {{#if parent}}{{#notEq severity parent.severity}}
- <div class="coding-rules-detail-quality-profile-inheritance">
- {{t 'coding_rules.original'}} {{t 'severity' parent.severity}}
- </div>
- {{/notEq}}{{/if}}
- </td>
-
- {{#unless templateKey}}
- <td class="coding-rules-detail-quality-profile-parameters">
- {{#each parameters}}
- <div class="coding-rules-detail-quality-profile-parameter">
- <span class="key">{{key}}</span><span class="sep">: </span><span class="value"
- title="{{value}}">{{value}}</span>
- {{#if ../parent}}{{#notEq value original}}
- <div class="coding-rules-detail-quality-profile-inheritance">
- {{t 'coding_rules.original'}} <span class="value">{{original}}</span>
- </div>
- {{/notEq}}{{/if}}
- </div>
- {{/each}}
-
- </td>
- {{/unless}}
-
- <td class="coding-rules-detail-quality-profile-actions">
- {{#if actions.edit}}
- {{#unless isBuiltIn}}
- {{#unless isTemplate}}
- <button class="coding-rules-detail-quality-profile-change">{{t 'change_verb'}}</button>
- {{/unless}}
- {{#if parent}}
- {{#eq inherit 'OVERRIDES'}}
- <button class="coding-rules-detail-quality-profile-revert button-red">
- {{t 'coding_rules.revert_to_parent_definition'}}
- </button>
- {{/eq}}
- {{else}}
- <button class="coding-rules-detail-quality-profile-deactivate button-red">
- {{t 'coding_rules.deactivate'}}
- </button>
- {{/if}}
- {{/unless}}
- {{/if}}
- </td>
-
-{{else}}
- {{#if canWrite}}{{#unless isTemplate}}
- <td class="coding-rules-detail-quality-profile-actions">
- <button class="coding-rules-detail-quality-profile-activate">{{t 'coding_rules.activate'}}</button>
- </td>
- {{/unless}}{{/if}}
-{{/if}}
+++ /dev/null
-<div class="coding-rules-detail-quality-profiles-section">
- <div class="coding-rule-section-separator"></div>
-
- <h3 class="coding-rules-detail-title">{{t 'coding_rules.quality_profiles'}}</h3>
-
- {{#if canActivate}}
- {{#unless isTemplate}}
- <button id="coding-rules-quality-profile-activate" class="spacer-left">{{t 'coding_rules.activate'}}</button>
- {{/unless}}
- {{/if}}
-
- {{#if isTemplate}}
- <div class="alert alert-warning">
- {{t 'coding_rules.quality_profiles.template_caption'}}
- </div>
- {{/if}}
-
- <table id="coding-rules-detail-quality-profiles" class="coding-rules-detail-quality-profiles width100"></table>
-</div>
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import $ from 'jquery';
-import WorkspaceHeaderView from '../../components/navigator/workspace-header-view';
-import BulkChangePopup from './bulk-change-popup-view';
-import Template from './templates/coding-rules-workspace-header.hbs';
-
-export default WorkspaceHeaderView.extend({
- template: Template,
-
- events() {
- return {
- ...WorkspaceHeaderView.prototype.events.apply(this, arguments),
- 'click .js-back': 'onBackClick',
- 'click .js-bulk-change': 'onBulkChangeClick',
- 'click .js-reload': 'reload',
- 'click .js-new-search': 'newSearch'
- };
- },
-
- onBackClick() {
- this.options.app.controller.hideDetails();
- },
-
- onBulkChangeClick(e) {
- e.stopPropagation();
- $('body').click();
- new BulkChangePopup({
- app: this.options.app,
- triggerEl: $(e.currentTarget),
- bottomRight: true
- }).render();
- },
-
- reload(event) {
- event.preventDefault();
- this.options.app.controller.fetchList(true);
- },
-
- newSearch() {
- this.options.app.controller.newSearch();
- },
-
- serializeData() {
- // show "Bulk Change" button only if user has at least one QP which he administates
- const canBulkChange = this.options.app.qualityProfiles.some(
- profile => profile.actions && profile.actions.edit
- );
-
- return {
- ...WorkspaceHeaderView.prototype.serializeData.apply(this, arguments),
- canBulkChange
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import Marionette from 'backbone.marionette';
-import { translate } from '../../helpers/l10n';
-
-export default Marionette.ItemView.extend({
- className: 'search-navigator-no-results',
-
- template() {
- return translate('coding_rules.no_results');
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import $ from 'jquery';
-import { union } from 'lodash';
-import Backbone from 'backbone';
-import WorkspaceListItemView from '../../components/navigator/workspace-list-item-view';
-import ProfileActivationView from './rule/profile-activation-view';
-import RuleFilterMixin from './rule/rule-filter-mixin';
-import Template from './templates/coding-rules-workspace-list-item.hbs';
-import confirmDialog from './confirm-dialog';
-import { translate, translateWithParameters } from '../../helpers/l10n';
-import { getBaseUrl } from '../../helpers/urls';
-
-export default WorkspaceListItemView.extend(RuleFilterMixin).extend({
- className: 'coding-rule',
- template: Template,
-
- modelEvents: {
- change: 'render'
- },
-
- events: {
- click: 'selectCurrent',
- dblclick: 'openRule',
- 'click .js-rule': 'openRule',
- 'click .js-rule-filter': 'onRuleFilterClick',
- 'click .coding-rules-detail-quality-profile-activate': 'activate',
- 'click .coding-rules-detail-quality-profile-change': 'change',
- 'click .coding-rules-detail-quality-profile-revert': 'revert',
- 'click .coding-rules-detail-quality-profile-deactivate': 'deactivate'
- },
-
- onRender() {
- WorkspaceListItemView.prototype.onRender.apply(this, arguments);
- this.$('[data-toggle="tooltip"]').tooltip({
- container: 'body'
- });
- },
-
- onDestroy() {
- this.$('[data-toggle="tooltip"]').tooltip('destroy');
- },
-
- selectCurrent() {
- this.options.app.state.set({ selectedIndex: this.model.get('index') });
- },
-
- openRule(event) {
- const leftClick =
- event.button === 0 && !(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
- if (leftClick) {
- event.preventDefault();
- this.$('[data-toggle="tooltip"]').tooltip('destroy');
- this.options.app.controller.showDetails(this.model);
- }
- },
-
- activate() {
- const that = this;
- const selectedProfile = this.options.app.state.get('query').qprofile;
- const othersQualityProfiles = this.options.app.qualityProfiles.filter(
- profile => profile.key !== selectedProfile
- );
- const activationView = new ProfileActivationView({
- rule: this.model,
- collection: new Backbone.Collection(othersQualityProfiles),
- app: this.options.app
- });
- activationView.on('profileActivated', (severity, params, profile) => {
- const activation = {
- severity,
- params,
- inherit: 'NONE',
- qProfile: profile
- };
- that.model.set({ activation });
- });
- activationView.render();
- },
-
- deactivate() {
- const that = this;
- const ruleKey = this.model.get('key');
- const activation = this.model.get('activation');
- confirmDialog({
- title: translate('coding_rules.deactivate'),
- html: translateWithParameters('coding_rules.deactivate.confirm'),
- yesHandler() {
- return $.ajax({
- type: 'POST',
- url: window.baseUrl + '/api/qualityprofiles/deactivate_rule',
- data: {
- profile_key: activation.qProfile,
- rule_key: ruleKey
- }
- }).done(() => {
- that.model.unset('activation');
- });
- }
- });
- },
-
- serializeData() {
- const selectedProfileKey = this.options.app.state.get('query').qprofile;
- const selectedProfile =
- selectedProfileKey &&
- this.options.app.qualityProfiles.find(profile => profile.key === selectedProfileKey);
- const isSelectedProfileBuiltIn = selectedProfile != null && selectedProfile.isBuiltIn;
-
- const canEditQualityProfile =
- selectedProfile && selectedProfile.actions && selectedProfile.actions.edit;
-
- const permalinkPath = this.options.app.organization
- ? `/organizations/${this.options.app.organization}/rules`
- : '/coding_rules';
- const permalink =
- getBaseUrl() + permalinkPath + '#rule_key=' + encodeURIComponent(this.model.id);
-
- return {
- ...WorkspaceListItemView.prototype.serializeData.apply(this, arguments),
- canEditQualityProfile,
- tags: union(this.model.get('sysTags'), this.model.get('tags')),
- selectedProfile: selectedProfileKey,
- isSelectedProfileBuiltIn,
- permalink
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import key from 'keymaster';
-import WorkspaceListView from '../../components/navigator/workspace-list-view';
-import WorkspaceListItemView from './workspace-list-item-view';
-import WorkspaceListEmptyView from './workspace-list-empty-view';
-import Template from './templates/coding-rules-workspace-list.hbs';
-
-export default WorkspaceListView.extend({
- template: Template,
- childView: WorkspaceListItemView,
- childViewContainer: '.js-list',
- emptyView: WorkspaceListEmptyView,
-
- bindShortcuts() {
- WorkspaceListView.prototype.bindShortcuts.apply(this, arguments);
- const that = this;
- key('right', 'list', () => {
- that.options.app.controller.showDetailsForSelected();
- return false;
- });
- key('a', () => {
- that.options.app.controller.activateCurrent();
- return false;
- });
- key('d', () => {
- that.options.app.controller.deactivateCurrent();
- return false;
- });
- }
-});
const helper = `component_measures.domain_facets.${domain.name}.help`;
const translatedHelper = translate(helper);
return (
- <FacetBox>
+ <FacetBox property={domain.name}>
<FacetHeader
helper={helper !== translatedHelper ? translatedHelper : undefined}
name={getLocalizedMetricDomain(domain.name)}
export default function ProjectOverviewFacet({ value, selected, onChange } /*: Props */) {
const facetName = translate('component_measures.overview', value, 'facet');
return (
- <FacetBox>
+ <FacetBox property={value}>
<FacetItemsList>
<FacetItem
active={value === selected}
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should display facet item list 1`] = `
-<FacetBox>
+<FacetBox
+ property="Reliability"
+>
<FacetHeader
name="Reliability"
onClick={[Function]}
`;
exports[`should display facet item list with bugs selected 1`] = `
-<FacetBox>
+<FacetBox
+ property="Reliability"
+>
<FacetHeader
name="Reliability"
onClick={[Function]}
import { keyBy, without } from 'lodash';
import PropTypes from 'prop-types';
import PageActions from './PageActions';
-import FiltersHeader from './FiltersHeader';
import MyIssuesFilter from './MyIssuesFilter';
import Sidebar from '../sidebar/Sidebar';
import IssuesList from './IssuesList';
import { isLoggedIn } from '../../../app/types';
import ListFooter from '../../../components/controls/ListFooter';
import EmptySearch from '../../../components/common/EmptySearch';
+import FiltersHeader from '../../../components/common/FiltersHeader';
import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
import { getBranchName, isShortLivingBranch } from '../../../helpers/branches';
import { translate, translateWithParameters } from '../../../helpers/l10n';
<SearchSelect
onSearch={this.handleAssigneeSearch}
onSelect={this.handleAssigneeSelect}
- minimumQueryLength={2}
renderOption={this.renderAssigneeOption}
resetOnBlur={false}
value={this.state.assignee}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-// @flow
-import React from 'react';
-import { translate } from '../../../helpers/l10n';
-
-/*::
-type Props = {
- displayReset: boolean,
- onReset: () => void
-};
-*/
-
-export default class FiltersHeader extends React.PureComponent {
- /*:: props: Props; */
-
- handleResetClick = (e /*: Event & { currentTarget: HTMLElement } */) => {
- e.preventDefault();
- e.currentTarget.blur();
- this.props.onReset();
- };
-
- render() {
- return (
- <div className="issues-filters-header">
- {this.props.displayReset && (
- <div className="pull-right">
- <button className="button-red" onClick={this.handleResetClick}>
- {translate('clear_all_filters')}
- </button>
- </div>
- )}
-
- <h3>{translate('filters')}</h3>
- </div>
- );
- }
-}
*/
// @flow
import React from 'react';
+import PageCounter from '../../../components/common/PageCounter';
import { translate } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
};
*/
-const IssuesCounter = (props /*: Props */) => (
- <span className={props.className}>
- <strong>
- {props.current != null && (
- <span>
- {formatMeasure(props.current + 1, 'INT')}
- {' / '}
- </span>
- )}
- {formatMeasure(props.total, 'INT')}
- </strong>{' '}
- {translate('issues.issues')}
- </span>
-);
-
-export default IssuesCounter;
+export default function IssuesCounter(props /*:Props*/) {
+ return (
+ <PageCounter
+ className="spacer-left flash flash-heavy"
+ current={props.current}
+ label={translate('issues.issues')}
+ total={props.total}
+ />
+ );
+}
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`does not show current 1`] = `
-<span>
- <strong>
- 987,654,321
- </strong>
-
- issues.issues
-</span>
+<PageCounter
+ className="spacer-left flash flash-heavy"
+ current={null}
+ label="issues.issues"
+ total={987654321}
+/>
`;
exports[`formats numbers 1`] = `
-<span>
- <strong>
- <span>
- 1,235
- /
- </span>
- 987,654,321
- </strong>
-
- issues.issues
-</span>
+<PageCounter
+ className="spacer-left flash flash-heavy"
+ current={1234}
+ label="issues.issues"
+ total={987654321}
+/>
`;
render() {
return (
- <FacetBox>
+ <FacetBox property={this.property}>
<FacetHeader
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
render() {
return (
- <FacetBox>
+ <FacetBox property={this.property}>
<FacetHeader
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
render() {
return (
- <FacetBox>
+ <FacetBox property={this.property}>
<FacetHeader
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
render() {
const values = this.props.directories.map(dir => collapsePath(dir));
return (
- <FacetBox>
+ <FacetBox property={this.property}>
<FacetHeader
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
const modes = ['count', 'effort'];
return (
- <FacetBox>
+ <FacetBox property={this.property}>
<FacetHeader name={translate('issues.facet.mode')} />
<FacetItemsList>
render() {
const values = this.props.files.map(file => this.getFileName(file));
return (
- <FacetBox>
+ <FacetBox property={this.property}>
<FacetHeader
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
render() {
const values = this.props.languages.map(language => this.getLanguageName(language));
return (
- <FacetBox>
+ <FacetBox property={this.property}>
<FacetHeader
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
render() {
const values = this.props.modules.map(module => this.getModuleName(module));
return (
- <FacetBox>
+ <FacetBox property={this.property}>
<FacetHeader
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
render() {
const values = this.props.projects.map(project => this.getProjectName(project));
return (
- <FacetBox>
+ <FacetBox property={this.property}>
<FacetHeader
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
const values = this.props.resolutions.map(resolution => this.getFacetItemName(resolution));
return (
- <FacetBox>
+ <FacetBox property={this.property}>
<FacetHeader
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
languages: Array<string>,
onChange: (changes: { [string]: Array<string> }) => void,
onToggle: (property: string) => void,
+ organization: string | void;
open: boolean,
stats?: { [string]: number },
referencedRules: { [string]: { name: string } },
};
handleSearch = (query /*: string */) => {
- const { languages } = this.props;
+ const { languages, organization } = this.props;
return searchRules({
f: 'name,langName',
languages: languages.length ? languages.join() : undefined,
+ organization,
q: query
}).then(response =>
response.rules.map(rule => ({ label: `(${rule.langName}) ${rule.name}`, value: rule.key }))
render() {
const values = this.props.rules.map(rule => this.getRuleName(rule));
return (
- <FacetBox>
+ <FacetBox property={this.property}>
<FacetHeader
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
const values = this.props.severities.map(severity => translate('severity', severity));
return (
- <FacetBox>
+ <FacetBox property={this.property}>
<FacetHeader
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
languages={query.languages}
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
+ organization={this.props.organization && this.props.organization.key}
open={!!openFacets.rules}
stats={facets.rules}
referencedRules={this.props.referencedRules}
const values = this.props.statuses.map(status => translate('issue.status', status));
return (
- <FacetBox>
+ <FacetBox property={this.property}>
<FacetHeader
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
const values = this.props.types.map(type => translate('issue.type', type));
return (
- <FacetBox>
+ <FacetBox property={this.property}>
<FacetHeader
name={translate('issues.facet', this.property)}
onClear={this.handleClear}
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should render 1`] = `
-<FacetBox>
+<FacetBox
+ property="assignees"
+>
<FacetHeader
name="issues.facet.assignees"
onClear={[Function]}
`;
exports[`should render without stats 1`] = `
-<FacetBox>
+<FacetBox
+ property="assignees"
+>
<FacetHeader
name="issues.facet.assignees"
onClear={[Function]}
`;
exports[`should select unassigned 1`] = `
-<FacetBox>
+<FacetBox
+ property="assignees"
+>
<FacetHeader
name="issues.facet.assignees"
onClear={[Function]}
`;
exports[`should select user 1`] = `
-<FacetBox>
+<FacetBox
+ property="assignees"
+>
<FacetHeader
name="issues.facet.assignees"
onClear={[Function]}
border: none;
}
-.issues-filters-header {
- margin-bottom: 12px;
- padding-bottom: 11px;
- border-bottom: 1px solid var(--barBorderColor);
-}
-
.issues-my-issues-filter {
margin-bottom: 24px;
text-align: center;
text-align: right;
}
-.issues .search-navigator-facet-footer {
- padding: 0 0 10px 0;
-}
-
.issues .issue-list {
/* no math, just a good guess */
min-width: 640px;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-// @flow
-import React from 'react';
-import CodingRulesAppContainer from '../../coding-rules/components/CodingRulesAppContainer';
-
-export default class OrganizationRules extends React.PureComponent {
- render() {
- return <CodingRulesAppContainer {...this.props} />;
- }
-}
import OrganizationPageExtension from '../../app/components/extensions/OrganizationPageExtension';
import OrganizationContainer from './components/OrganizationContainer';
import OrganizationProjects from './components/OrganizationProjects';
-import OrganizationRules from './components/OrganizationRules';
import OrganizationAdminContainer from './components/OrganizationAdminContainer';
import OrganizationEdit from './components/OrganizationEdit';
import OrganizationGroups from './components/OrganizationGroups';
import OrganizationDelete from './components/OrganizationDelete';
import PermissionTemplateApp from '../permission-templates/components/AppContainer';
import ProjectManagementApp from '../projectsManagement/AppContainer';
+import codingRulesRoutes from '../coding-rules/routes';
import qualityGatesRoutes from '../quality-gates/routes';
import qualityProfilesRoutes from '../quality-profiles/routes';
import Issues from '../issues/components/AppContainer';
},
{
path: 'rules',
- component: OrganizationRules
+ component: OrganizationContainer,
+ childRoutes: codingRulesRoutes
},
{
path: 'quality_profiles',
<MetaQualityProfiles
component={component}
customOrganizations={organizationsEnabled}
+ organization={component.organization}
profiles={qualityProfiles}
/>
)}
component: { organization: string },
customOrganizations: boolean,
languages: { [string]: { name: string } },
+ organization: string | void;
profiles: Array<{ key: string, language: string, name: string }>
};
*/
loadDeprecatedRulesForProfile(profileKey) {
const data = {
- qprofile: profileKey,
activation: 'true',
- statuses: 'DEPRECATED',
- ps: 1
+ organization: this.props.organization,
+ ps: 1,
+ qprofile: profileKey,
+ statuses: 'DEPRECATED'
};
return searchRules(data).then(r => r.total);
}
className="button-link"
onClick={this.handleClick}
ref={tagsList => (this.tagsList = tagsList)}>
- <TagsList tags={tags.length ? tags : [translate('no_tags')]} allowUpdate={true} />
+ <TagsList allowUpdate={true} tags={tags.length ? tags : [translate('no_tags')]} />
</button>
{popupOpen && (
<div ref={tagsSelector => (this.tagsSelector = tagsSelector)}>
} else {
return (
<div className="overview-meta-card overview-meta-tags">
- <TagsList tags={tags.length ? tags : [translate('no_tags')]} allowUpdate={false} />
+ <TagsList
+ allowUpdate={false}
+ className="note"
+ tags={tags.length ? tags : [translate('no_tags')]}
+ />
</div>
);
}
>
<TagsList
allowUpdate={false}
+ className="note"
tags={
Array [
"no_tags",
{isPrivate && (
<PrivateBadge className="spacer-left" qualifier="TRK" tooltipPlacement="left" />
)}
- {hasTags && <TagsList tags={project.tags} customClass="spacer-left" />}
+ {hasTags && <TagsList className="spacer-left note" tags={project.tags} />}
</div>
{project.analysisDate &&
project.leakPeriodDate && (
{isPrivate && (
<PrivateBadge className="spacer-left" qualifier="TRK" tooltipPlacement="left" />
)}
- {hasTags && <TagsList tags={project.tags} customClass="spacer-left" />}
+ {hasTags && <TagsList className="spacer-left note" tags={project.tags} />}
</div>
{project.analysisDate && (
<div className="project-card-dates note text-right">
it('should render rule', () => {
const events = [createEvent()];
const changelog = shallow(<Changelog events={events} organization={null} />);
- expect(changelog.find('Link').prop('to')).toContain('rule_key=squid1234');
+ expect(changelog.find('Link').prop('to')).toHaveProperty('query', { rule_key: 'squid1234' });
});
it('should render ChangesList', () => {
const leftDiffs = output.find('.js-comparison-in-left');
expect(leftDiffs.length).toBe(1);
expect(leftDiffs.find(Link).length).toBe(1);
- expect(leftDiffs.find(Link).prop('to')).toContain('rule_key=rule1');
+ expect(leftDiffs.find(Link).prop('to')).toHaveProperty('query', { rule_key: 'rule1' });
expect(leftDiffs.find(Link).prop('children')).toContain('rule1');
expect(leftDiffs.find(SeverityIcon).length).toBe(1);
expect(leftDiffs.find(SeverityIcon).prop('severity')).toBe('BLOCKER');
.at(0)
.find(Link)
.prop('to')
- ).toContain('rule_key=rule2');
+ ).toHaveProperty('query', { rule_key: 'rule2' });
expect(
rightDiffs
.at(0)
.find(Link)
.at(0)
.prop('to')
- ).toContain('rule_key=rule4');
+ ).toHaveProperty('query', { rule_key: 'rule4' });
expect(
modifiedDiffs
.find(Link)
<ActionsDropdown>
<ActionsDropdownItem
id="quality-profile-activate-more-rules"
- to="/organizations/org/rules#qprofile=foo|activation=false"
+ to={
+ Object {
+ "pathname": "/organizations/org/rules",
+ "query": Object {
+ "activation": "false",
+ "qprofile": "foo",
+ },
+ }
+ }
>
quality_profiles.activate_more_rules
</ActionsDropdownItem>
<ActionsDropdown>
<ActionsDropdownItem
id="quality-profile-activate-more-rules"
- to="/organizations/org/rules#qprofile=foo|activation=false"
+ to={
+ Object {
+ "pathname": "/organizations/org/rules",
+ "query": Object {
+ "activation": "false",
+ "qprofile": "foo",
+ },
+ }
+ }
>
quality_profiles.activate_more_rules
</ActionsDropdownItem>
loadAllRules() {
return searchRules({
languages: this.props.profile.language,
- ps: 1,
- facets: 'types'
+ facets: 'types',
+ organization: this.props.organization || undefined,
+ ps: 1
});
}
loadActivatedRules() {
return searchRules({
- qprofile: this.props.profile.key,
activation: 'true',
+ facets: 'types',
+ organization: this.props.organization || undefined,
ps: 1,
- facets: 'types'
+ qprofile: this.props.profile.key
});
}
}
export default function ProfileRulesDeprecatedWarning(props: Props) {
- const url = getDeprecatedActiveRulesUrl({ qprofile: props.profile }, props.organization);
return (
<div className="quality-profile-rules-deprecated clearfix">
<span className="pull-left">
<i className="icon-help spacer-left" />
</Tooltip>
</span>
- <Link className="pull-right" to={url}>
+ <Link
+ className="pull-right"
+ to={getDeprecatedActiveRulesUrl({ qprofile: props.profile }, props.organization)}>
{props.activeDeprecatedRules}
</Link>
</div>
className="button js-activate-rules"
onlyActiveOnIndex={false}
style={Object {}}
- to="/organizations/foo/rules#qprofile=foo|activation=false"
+ to={
+ Object {
+ "pathname": "/organizations/foo/rules",
+ "query": Object {
+ "activation": "false",
+ "qprofile": "foo",
+ },
+ }
+ }
>
quality_profiles.activate_more
</Link>
className="pull-right"
onlyActiveOnIndex={false}
style={Object {}}
- to="/organizations/foo/rules#qprofile=bar|activation=true|statuses=DEPRECATED"
+ to={
+ Object {
+ "pathname": "/organizations/foo/rules",
+ "query": Object {
+ "activation": "true",
+ "qprofile": "bar",
+ "statuses": "DEPRECATED",
+ },
+ }
+ }
>
18
</Link>
<Link
onlyActiveOnIndex={false}
style={Object {}}
- to="/organizations/foo/rules#qprofile=bar|activation=true|types=BUG"
+ to={
+ Object {
+ "pathname": "/organizations/foo/rules",
+ "query": Object {
+ "activation": "true",
+ "qprofile": "bar",
+ "types": "BUG",
+ },
+ }
+ }
>
3
</Link>
className="small text-muted"
onlyActiveOnIndex={false}
style={Object {}}
- to="/organizations/foo/rules#qprofile=bar|activation=false|types=BUG"
+ to={
+ Object {
+ "pathname": "/organizations/foo/rules",
+ "query": Object {
+ "activation": "false",
+ "qprofile": "bar",
+ "types": "BUG",
+ },
+ }
+ }
>
7
</Link>
<Link
onlyActiveOnIndex={false}
style={Object {}}
- to="/coding_rules#qprofile=bar|activation=true|types=VULNERABILITY"
+ to={
+ Object {
+ "pathname": "/coding_rules",
+ "query": Object {
+ "activation": "true",
+ "qprofile": "bar",
+ "types": "VULNERABILITY",
+ },
+ }
+ }
>
0
</Link>
<Link
onlyActiveOnIndex={false}
style={Object {}}
- to="/coding_rules#qprofile=bar|activation=true|types=VULNERABILITY"
+ to={
+ Object {
+ "pathname": "/coding_rules",
+ "query": Object {
+ "activation": "true",
+ "qprofile": "bar",
+ "types": "VULNERABILITY",
+ },
+ }
+ }
>
5
</Link>
<Link
onlyActiveOnIndex={false}
style={Object {}}
- to="/organizations/foo/rules#qprofile=bar|activation=true"
+ to={
+ Object {
+ "pathname": "/organizations/foo/rules",
+ "query": Object {
+ "activation": "true",
+ "qprofile": "bar",
+ },
+ }
+ }
>
<strong>
3
className="small text-muted"
onlyActiveOnIndex={false}
style={Object {}}
- to="/organizations/foo/rules#qprofile=bar|activation=false"
+ to={
+ Object {
+ "pathname": "/organizations/foo/rules",
+ "query": Object {
+ "activation": "false",
+ "qprofile": "bar",
+ },
+ }
+ }
>
<strong>
7
<Link
onlyActiveOnIndex={false}
style={Object {}}
- to="/coding_rules#qprofile=bar|activation=true"
+ to={
+ Object {
+ "pathname": "/coding_rules",
+ "query": Object {
+ "activation": "true",
+ "qprofile": "bar",
+ },
+ }
+ }
>
<strong>
0
<Link
onlyActiveOnIndex={false}
style={Object {}}
- to="/coding_rules#qprofile=bar|activation=true"
+ to={
+ Object {
+ "pathname": "/coding_rules",
+ "query": Object {
+ "activation": "true",
+ "qprofile": "bar",
+ },
+ }
+ }
>
<strong>
5
className="pull-right"
onlyActiveOnIndex={false}
style={Object {}}
- to="/organizations/foo/rules#qprofile=bar|activation=false|compareToProfile=baz|languages=Java"
+ to={
+ Object {
+ "pathname": "/organizations/foo/rules",
+ "query": Object {
+ "activation": "false",
+ "compareToProfile": "baz",
+ "languages": "Java",
+ "qprofile": "bar",
+ },
+ }
+ }
>
158
</Link>
loadLatestRules() {
const data = {
- available_since: this.periodStartDate,
- s: 'createdAt',
asc: false,
+ available_since: this.periodStartDate,
+ f: 'name,langName,actives',
+ organization: this.props.organization || undefined,
ps: RULES_LIMIT,
- f: 'name,langName,actives'
+ s: 'createdAt'
};
searchRules(data).then(
import * as classNames from 'classnames';
export interface BubblePopupPosition {
- top: number;
- right: number;
+ top?: number;
+ left?: number;
+ right?: number;
}
interface Props {
import * as classNames from 'classnames';
interface Props {
- children?: JSX.Element | JSX.Element[];
+ children?: React.ReactNode;
className?: string;
loading?: boolean;
customSpinner?: JSX.Element;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { translate } from '../../helpers/l10n';
+
+interface Props {
+ displayReset: boolean;
+ onReset: () => void;
+}
+
+export default class FiltersHeader extends React.PureComponent<Props> {
+ handleResetClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.props.onReset();
+ };
+
+ render() {
+ return (
+ <div className="search-navigator-filters-header">
+ {this.props.displayReset && (
+ <div className="pull-right">
+ <button
+ className="button-red"
+ id="coding-rules-clear-all-filters"
+ onClick={this.handleResetClick}>
+ {translate('clear_all_filters')}
+ </button>
+ </div>
+ )}
+
+ <h3>{translate('filters')}</h3>
+ </div>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-// @flow
-import React from 'react';
-import { getMarkdownHelpUrl } from '../../helpers/urls';
-import { translate } from '../../helpers/l10n';
-
-export default class MarkdownTips extends React.PureComponent {
- handleClick(evt /*: MouseEvent */) {
- evt.preventDefault();
- window.open(getMarkdownHelpUrl(), 'Markdown', 'height=300,width=600,scrollbars=1,resizable=1');
- }
-
- render() {
- return (
- <div className="markdown-tips">
- <a className="little-spacer-right" href="#" onClick={this.handleClick}>
- {translate('markdown.helplink')}
- </a>
- {':'}
- <span className="spacer-left">*{translate('bold')}*</span>
- <span className="spacer-left">``{translate('code')}``</span>
- <span className="spacer-left">* {translate('bulleted_point')}</span>
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { getMarkdownHelpUrl } from '../../helpers/urls';
+import { translate } from '../../helpers/l10n';
+
+export default class MarkdownTips extends React.PureComponent {
+ handleClick(evt: React.SyntheticEvent<HTMLAnchorElement>) {
+ evt.preventDefault();
+ window.open(getMarkdownHelpUrl(), 'Markdown', 'height=300,width=600,scrollbars=1,resizable=1');
+ }
+
+ render() {
+ return (
+ <div className="markdown-tips">
+ <a className="little-spacer-right" href="#" onClick={this.handleClick}>
+ {translate('markdown.helplink')}
+ </a>
+ {':'}
+ <span className="spacer-left">*{translate('bold')}*</span>
+ <span className="spacer-left">``{translate('code')}``</span>
+ <span className="spacer-left">* {translate('bulleted_point')}</span>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { formatMeasure } from '../../helpers/measures';
+
+interface Props {
+ className?: string;
+ current?: number;
+ label: string;
+ total: number;
+}
+
+export default function PageCounter({ className, current, label, total }: Props) {
+ return (
+ <div className={classNames('display-inline-block', className)}>
+ <strong className="little-spacer-right">
+ {current !== undefined && formatMeasure(current + 1, 'INT') + ' / '}
+ <span className="js-page-counter-total">{formatMeasure(total, 'INT')}</span>
+ </strong>
+ {label}
+ </div>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import $ from 'jquery';
-import key from 'keymaster';
-import PopupView from './popup';
-
-export default PopupView.extend({
- className: 'bubble-popup bubble-popup-menu',
- keyScope: 'action-options',
-
- ui: {
- options: '.menu > li > a'
- },
-
- events() {
- return {
- 'click @ui.options': 'selectOption',
- 'mouseenter @ui.options': 'activateOptionByPointer'
- };
- },
-
- initialize() {
- this.bindShortcuts();
- },
-
- onRender() {
- PopupView.prototype.onRender.apply(this, arguments);
- this.selectInitialOption();
- },
-
- getOptions() {
- return this.$('.menu > li > a');
- },
-
- getActiveOption() {
- return this.getOptions().filter('.active');
- },
-
- makeActive(option) {
- if (option.length > 0) {
- this.getOptions()
- .removeClass('active')
- .tooltip('hide');
- option.addClass('active').tooltip('show');
- }
- },
-
- selectInitialOption() {
- this.makeActive(this.getOptions().first());
- },
-
- selectNextOption() {
- this.makeActive(
- this.getActiveOption()
- .parent()
- .nextAll('li:not(.divider)')
- .first()
- .children('a')
- );
- return false;
- },
-
- selectPreviousOption() {
- this.makeActive(
- this.getActiveOption()
- .parent()
- .prevAll('li:not(.divider)')
- .first()
- .children('a')
- );
- return false;
- },
-
- activateOptionByPointer(e) {
- this.makeActive($(e.currentTarget));
- },
-
- bindShortcuts() {
- const that = this;
- this.currentKeyScope = key.getScope();
- key.setScope(this.keyScope);
- key('down', this.keyScope, () => that.selectNextOption());
- key('up', this.keyScope, () => that.selectPreviousOption());
- key('return', this.keyScope, () => that.selectActiveOption());
- key('escape', this.keyScope, () => that.destroy());
- key('backspace', this.keyScope, () => false);
- key('shift+tab', this.keyScope, () => false);
- },
-
- unbindShortcuts() {
- key.unbind('down', this.keyScope);
- key.unbind('up', this.keyScope);
- key.unbind('return', this.keyScope);
- key.unbind('escape', this.keyScope);
- key.unbind('backspace', this.keyScope);
- key.unbind('tab', this.keyScope);
- key.unbind('shift+tab', this.keyScope);
- key.setScope(this.currentKeyScope);
- },
-
- onDestroy() {
- PopupView.prototype.onDestroy.apply(this, arguments);
- this.unbindShortcuts();
- this.$('[data-toggle="tooltip"]').tooltip('destroy');
- $('.tooltip').remove();
- },
-
- selectOption(e) {
- e.preventDefault();
- this.destroy();
- },
-
- selectActiveOption() {
- this.getActiveOption().click();
- }
-});
+++ /dev/null
-<div class="markdown-tips">
- <a href="#" onclick="window.open(window.baseUrl + '/markdown/help','markdown','height=300,width=600,scrollbars=1,resizable=1');return false;">{{t 'markdown.helplink'}}</a> :
- *{{t 'bold'}}* ``{{t 'code'}}`` * {{t 'bulleted_point'}}
-</div>
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import Tooltip from './Tooltip';
+import * as theme from '../../app/theme';
+import { translate } from '../../helpers/l10n';
+
+interface Props {
+ className?: string;
+ onClick: () => void;
+}
+
+export default class ReloadButton extends React.PureComponent<Props> {
+ handleClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.props.onClick();
+ };
+
+ renderIcon = () => (
+ <svg width="18" height="24" viewBox="0 0 18 24">
+ <path
+ fill={theme.secondFontColor}
+ d="M16.6454 8.1084c-.3-.5-.9-.7-1.4-.4-.5.3-.7.9-.4 1.4.9 1.6 1.1 3.4.6 5.1-.5 1.7-1.7 3.2-3.2 4-3.3 1.8-7.4.6-9.1-2.7-1.8-3.1-.8-6.9 2.1-8.8v3.3h2v-7h-7v2h3.9c-3.7 2.5-5 7.5-2.8 11.4 1.6 3 4.6 4.6 7.7 4.6 1.4 0 2.8-.3 4.2-1.1 2-1.1 3.5-3 4.2-5.2.6-2.2.3-4.6-.8-6.6z"
+ />
+ </svg>
+ );
+
+ render() {
+ return (
+ <Tooltip overlay={translate('reload')}>
+ <a
+ className={classNames('link-no-underline', this.props.className)}
+ href="#"
+ onClick={this.handleClick}>
+ {this.renderIcon()}
+ </a>
+ </Tooltip>
+ );
+ }
+}
interface Props {
autoFocus?: boolean;
+ className?: string;
innerRef?: (node: HTMLInputElement | null) => void;
+ id?: string;
minLength?: number;
onChange: (value: string) => void;
onClick?: React.MouseEventHandler<HTMLInputElement>;
const tooShort = minLength !== undefined && value.length > 0 && value.length < minLength;
return (
- <div className="search-box">
+ <div className={classNames('search-box', this.props.className)} id={this.props.id}>
<input
autoComplete="off"
autoFocus={this.props.autoFocus}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-// @flow
-import React from 'react';
-import { debounce } from 'lodash';
-import Select from '../../components/controls/Select';
-import { translate, translateWithParameters } from '../../helpers/l10n';
-
-/*::
-type Option = { label: string, value: string };
-*/
-
-/*::
-type Props = {|
- autofocus: boolean,
- minimumQueryLength: number,
- onSearch: (query: string) => Promise<Array<Option>>,
- onSelect: (value: string) => void,
- renderOption?: (option: Object) => React.Element<*>,
- resetOnBlur: boolean,
- value?: string
-|};
-*/
-
-/*::
-type State = {
- loading: boolean,
- options: Array<Option>,
- query: string
-};
-*/
-
-export default class SearchSelect extends React.PureComponent {
- /*:: mounted: boolean; */
- /*:: props: Props; */
- /*:: state: State; */
-
- static defaultProps = {
- autofocus: true,
- minimumQueryLength: 2,
- resetOnBlur: true
- };
-
- constructor(props /*: Props */) {
- super(props);
- this.state = { loading: false, options: [], query: '' };
- this.search = debounce(this.search, 250);
- }
-
- componentDidMount() {
- this.mounted = true;
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- search = (query /*: string */) => {
- this.props.onSearch(query).then(
- options => {
- if (this.mounted) {
- this.setState({ loading: false, options });
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- }
- );
- };
-
- handleChange = (option /*: Option */) => {
- this.props.onSelect(option.value);
- };
-
- handleInputChange = (query /*: string */) => {
- // `onInputChange` is called with an empty string after a user selects a value
- // in this case we shouldn't reset `options`, because it also resets select value :(
- if (query.length >= this.props.minimumQueryLength) {
- this.setState({ loading: true, query });
- this.search(query);
- } else if (query.length > 0) {
- this.setState({ options: [], query });
- }
- };
-
- // disable internal filtering
- handleFilterOption = () => true;
-
- render() {
- return (
- <Select
- autofocus={this.props.autofocus}
- cache={false}
- className="input-super-large"
- clearable={false}
- filterOption={this.handleFilterOption}
- isLoading={this.state.loading}
- noResultsText={
- this.state.query.length < this.props.minimumQueryLength
- ? translateWithParameters('select2.tooShort', this.props.minimumQueryLength)
- : translate('select2.noMatches')
- }
- onBlurResetsInput={this.props.resetOnBlur}
- onChange={this.handleChange}
- onInputChange={this.handleInputChange}
- onOpen={this.props.minimumQueryLength === 0 ? this.handleInputChange : undefined}
- optionRenderer={this.props.renderOption}
- options={this.state.options}
- placeholder={translate('search_verb')}
- searchable={true}
- value={this.props.value}
- valueRenderer={this.props.renderOption}
- />
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { debounce } from 'lodash';
+import Select from '../../components/controls/Select';
+import { translate, translateWithParameters } from '../../helpers/l10n';
+
+type Option = { label: string; value: string };
+
+interface Props {
+ autofocus?: boolean;
+ minimumQueryLength?: number;
+ onSearch: (query: string) => Promise<Option[]>;
+ onSelect: (value: string) => void;
+ renderOption?: (option: Object) => JSX.Element;
+ resetOnBlur?: boolean;
+ value?: string;
+}
+
+interface State {
+ loading: boolean;
+ options: Option[];
+ query: string;
+}
+
+export default class SearchSelect extends React.PureComponent<Props, State> {
+ mounted: boolean;
+
+ static defaultProps = {
+ autofocus: true,
+ resetOnBlur: true
+ };
+
+ constructor(props: Props) {
+ super(props);
+ this.state = { loading: false, options: [], query: '' };
+ this.search = debounce(this.search, 250);
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ get minimumQueryLength() {
+ return this.props.minimumQueryLength || 2;
+ }
+
+ search = (query: string) => {
+ this.props.onSearch(query).then(
+ options => {
+ if (this.mounted) {
+ this.setState({ loading: false, options });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ handleChange = (option: Option) => {
+ this.props.onSelect(option.value);
+ };
+
+ handleInputChange = (query: string) => {
+ // `onInputChange` is called with an empty string after a user selects a value
+ // in this case we shouldn't reset `options`, because it also resets select value :(
+ if (query.length >= this.minimumQueryLength) {
+ this.setState({ loading: true, query });
+ this.search(query);
+ } else if (query.length > 0) {
+ this.setState({ options: [], query });
+ }
+ };
+
+ // disable internal filtering
+ handleFilterOption = () => true;
+
+ render() {
+ return (
+ <Select
+ autofocus={this.props.autofocus}
+ className="input-super-large"
+ clearable={false}
+ filterOption={this.handleFilterOption}
+ isLoading={this.state.loading}
+ noResultsText={
+ this.state.query.length < this.minimumQueryLength
+ ? translateWithParameters('select2.tooShort', this.minimumQueryLength)
+ : translate('select2.noMatches')
+ }
+ onBlurResetsInput={this.props.resetOnBlur}
+ onChange={this.handleChange}
+ onInputChange={this.handleInputChange}
+ optionRenderer={this.props.renderOption}
+ options={this.state.options}
+ placeholder={translate('search_verb')}
+ searchable={true}
+ value={this.props.value}
+ valueRenderer={this.props.renderOption}
+ />
+ );
+ }
+}
exports[`should render Select 1`] = `
<Select
autofocus={true}
- cache={false}
className="input-super-large"
clearable={false}
filterOption={[Function]}
white-space: nowrap;
}
-.Select-value svg,
.Select-value [class^='icon-'] {
padding-top: 5px;
}
+.Select-value svg,
.Select-value img {
padding-top: 3px;
}
}
.Select--multi .Select-value-label {
+ display: inline-block;
+ max-width: 200px;
border-bottom-right-radius: 2px;
border-top-right-radius: 2px;
cursor: default;
padding: 2px 5px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
.Select--multi a.Select-value-label {
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-// @flow
-import React from 'react';
-
-/*::
-type Props = {|
- children?: React.Element<*>
-|};
-*/
-
-export default function FacetBox(props /*: Props */) {
- return <div className="search-navigator-facet-box">{props.children}</div>;
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+
+interface Props {
+ className?: string;
+ children: React.ReactNode;
+ property: string;
+}
+
+export default function FacetBox(props: Props) {
+ return (
+ <div
+ className={classNames('search-navigator-facet-box', props.className)}
+ data-property={props.property}>
+ {props.children}
+ </div>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-// @flow
-import React from 'react';
-import SearchSelect from '../controls/SearchSelect';
-
-/*::
-type Option = { label: string, value: string };
-*/
-
-/*::
-type Props = {|
- minimumQueryLength?: number,
- onSearch: (query: string) => Promise<Array<Option>>,
- onSelect: (value: string) => void,
- renderOption?: (option: Object) => React.Element<*>
-|};
-*/
-
-export default class FacetFooter extends React.PureComponent {
- /*:: props: Props; */
-
- render() {
- return (
- <div className="search-navigator-facet-footer">
- <SearchSelect autofocus={false} {...this.props} />
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import SearchSelect from '../controls/SearchSelect';
+
+type Option = { label: string; value: string };
+
+interface Props {
+ minimumQueryLength?: number;
+ onSearch: (query: string) => Promise<Option[]>;
+ onSelect: (value: string) => void;
+ renderOption?: (option: Object) => JSX.Element;
+}
+
+export default function FacetFooter(props: Props) {
+ return (
+ <div className="search-navigator-facet-footer">
+ <SearchSelect autofocus={false} {...props} />
+ </div>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-// @flow
-import React from 'react';
-import OpenCloseIcon from '../icons-components/OpenCloseIcon';
-import HelpIcon from '../icons-components/HelpIcon';
-import Tooltip from '../controls/Tooltip';
-import { translate, translateWithParameters } from '../../helpers/l10n';
-
-/*::
-type Props = {|
- helper?: string,
- name: string,
- onClear?: () => void,
- onClick?: () => void,
- open: boolean,
- values?: Array<string>
-|};
-*/
-
-export default class FacetHeader extends React.PureComponent {
- /*:: props: Props; */
-
- static defaultProps = {
- open: true
- };
-
- handleClearClick = (event /*: Event & { currentTarget: HTMLElement } */) => {
- event.preventDefault();
- event.currentTarget.blur();
- if (this.props.onClear) {
- this.props.onClear();
- }
- };
-
- handleClick = (event /*: Event & { currentTarget: HTMLElement } */) => {
- event.preventDefault();
- event.currentTarget.blur();
- if (this.props.onClick) {
- this.props.onClick();
- }
- };
-
- renderHelper() {
- if (!this.props.helper) {
- return null;
- }
- return (
- <Tooltip overlay={this.props.helper} placement="right">
- <span>
- <HelpIcon className="spacer-left text-info" />
- </span>
- </Tooltip>
- );
- }
-
- renderValueIndicator() {
- const { values } = this.props;
- if (this.props.open || !values || !values.length) {
- return null;
- }
- const value =
- values.length === 1 ? values[0] : translateWithParameters('x_selected', values.length);
- return (
- <span className="badge badge-secondary is-rounded text-ellipsis" title={value}>
- {value}
- </span>
- );
- }
-
- render() {
- const showClearButton =
- this.props.values != null && this.props.values.length > 0 && this.props.onClear != null;
-
- return (
- <div className="search-navigator-facet-header-wrapper">
- {this.props.onClick ? (
- <span className="search-navigator-facet-header">
- <a href="#" onClick={this.handleClick}>
- <OpenCloseIcon className="little-spacer-right" open={this.props.open} />
- {this.props.name}
- </a>
- {this.renderHelper()}
- </span>
- ) : (
- <span className="search-navigator-facet-header">
- {this.props.name}
- {this.renderHelper()}
- </span>
- )}
-
- <span className="search-navigator-facet-header-value spacer-left spacer-right ">
- {this.renderValueIndicator()}
- </span>
-
- {showClearButton && (
- <button
- className="search-navigator-facet-header-button button-small button-red"
- onClick={this.handleClearClick}>
- {translate('clear')}
- </button>
- )}
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import OpenCloseIcon from '../icons-components/OpenCloseIcon';
+import HelpIcon from '../icons-components/HelpIcon';
+import Tooltip from '../controls/Tooltip';
+import { translate, translateWithParameters } from '../../helpers/l10n';
+
+interface Props {
+ helper?: string;
+ name: string;
+ onClear?: () => void;
+ onClick?: () => void;
+ open: boolean;
+ values?: string[];
+}
+
+export default class FacetHeader extends React.PureComponent<Props> {
+ handleClearClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ if (this.props.onClear) {
+ this.props.onClear();
+ }
+ };
+
+ handleClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ if (this.props.onClick) {
+ this.props.onClick();
+ }
+ };
+
+ renderHelper() {
+ if (!this.props.helper) {
+ return null;
+ }
+ return (
+ <Tooltip overlay={this.props.helper} placement="right">
+ <span>
+ <HelpIcon className="spacer-left text-info" />
+ </span>
+ </Tooltip>
+ );
+ }
+
+ renderValueIndicator() {
+ const { values } = this.props;
+ if (this.props.open || !values || !values.length) {
+ return null;
+ }
+ const value =
+ values.length === 1 ? values[0] : translateWithParameters('x_selected', values.length);
+ return (
+ <span className="badge badge-secondary is-rounded text-ellipsis" title={value}>
+ {value}
+ </span>
+ );
+ }
+
+ render() {
+ const showClearButton =
+ this.props.values != null && this.props.values.length > 0 && this.props.onClear != null;
+
+ return (
+ <div className="search-navigator-facet-header-wrapper">
+ {this.props.onClick ? (
+ <span className="search-navigator-facet-header">
+ <a href="#" onClick={this.handleClick}>
+ <OpenCloseIcon className="little-spacer-right" open={this.props.open} />
+ {this.props.name}
+ </a>
+ {this.renderHelper()}
+ </span>
+ ) : (
+ <span className="search-navigator-facet-header">
+ {this.props.name}
+ {this.renderHelper()}
+ </span>
+ )}
+
+ <span className="search-navigator-facet-header-value spacer-left spacer-right ">
+ {this.renderValueIndicator()}
+ </span>
+
+ {showClearButton && (
+ <button
+ className="search-navigator-facet-header-button button-small button-red"
+ onClick={this.handleClearClick}>
+ {translate('clear')}
+ </button>
+ )}
+ </div>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-
-/*::
-type Props = {|
- active: boolean,
- disabled: boolean,
- halfWidth: boolean,
- name: string | React.Element<*>,
- onClick: string => void,
- stat?: ?(string | React.Element<*>),
- value: string
-|};
-*/
-
-export default class FacetItem extends React.PureComponent {
- /*:: props: Props; */
-
- static defaultProps = {
- disabled: false,
- halfWidth: false
- };
-
- handleClick = (event /*: Event & { currentTarget: HTMLElement } */) => {
- event.preventDefault();
- this.props.onClick(this.props.value);
- };
-
- render() {
- const className = classNames('facet', 'search-navigator-facet', {
- active: this.props.active,
- 'search-navigator-facet-half': this.props.halfWidth
- });
-
- return this.props.disabled ? (
- <span className={className}>
- <span className="facet-name">{this.props.name}</span>
- {this.props.stat != null && <span className="facet-stat">{this.props.stat}</span>}
- </span>
- ) : (
- <a className={className} href="#" onClick={this.handleClick}>
- <span className="facet-name">{this.props.name}</span>
- {this.props.stat != null && <span className="facet-stat">{this.props.stat}</span>}
- </a>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+
+export interface Props {
+ active?: boolean;
+ className?: string;
+ disabled?: boolean;
+ halfWidth?: boolean;
+ name: React.ReactNode;
+ onClick: (x: string) => void;
+ stat?: React.ReactNode;
+ value: string;
+}
+
+export default class FacetItem extends React.PureComponent<Props> {
+ static defaultProps = {
+ disabled: false,
+ halfWidth: false
+ };
+
+ handleClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.props.onClick(this.props.value);
+ };
+
+ render() {
+ const className = classNames('facet', 'search-navigator-facet', this.props.className, {
+ active: this.props.active,
+ 'search-navigator-facet-half': this.props.halfWidth
+ });
+
+ return this.props.disabled ? (
+ <span className={className} data-facet={this.props.value}>
+ <span className="facet-name">{this.props.name}</span>
+ {this.props.stat != null && <span className="facet-stat">{this.props.stat}</span>}
+ </span>
+ ) : (
+ <a className={className} data-facet={this.props.value} href="#" onClick={this.handleClick}>
+ <span className="facet-name">{this.props.name}</span>
+ {this.props.stat != null && <span className="facet-stat">{this.props.stat}</span>}
+ </a>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-// @flow
-import React from 'react';
-
-/*::
-type Props = {|
- children?: Array<React.Element<*>>
-|};
-*/
-
-export default function FacetItemsList(props /*: Props */) {
- return <div className="search-navigator-facet-list">{props.children}</div>;
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+
+interface Props {
+ children?: React.ReactNode;
+}
+
+export default function FacetItemsList(props: Props) {
+ return <div className="search-navigator-facet-list">{props.children}</div>;
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import FacetBox from '../FacetBox';
-
-it('should render', () => {
- expect(
- shallow(
- <FacetBox>
- <div />
- </FacetBox>
- )
- ).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import FacetBox from '../FacetBox';
+
+it('should render', () => {
+ expect(
+ shallow(
+ <FacetBox property="foo">
+ <div />
+ </FacetBox>
+ )
+ ).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import FacetFooter from '../FacetFooter';
-
-it('should render', () => {
- expect(shallow(<FacetFooter onSearch={jest.fn()} onSelect={jest.fn()} />)).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import FacetFooter from '../FacetFooter';
+
+it('should render', () => {
+ expect(shallow(<FacetFooter onSearch={jest.fn()} onSelect={jest.fn()} />)).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import { click } from '../../../helpers/testUtils';
-import FacetHeader from '../FacetHeader';
-
-it('should render open facet with value', () => {
- expect(
- shallow(<FacetHeader name="foo" onClick={jest.fn()} open={true} values={['foo']} />)
- ).toMatchSnapshot();
-});
-
-it('should render open facet without value', () => {
- expect(shallow(<FacetHeader name="foo" onClick={jest.fn()} open={true} />)).toMatchSnapshot();
-});
-
-it('should render closed facet with value', () => {
- expect(
- shallow(<FacetHeader name="foo" onClick={jest.fn()} open={false} values={['foo']} />)
- ).toMatchSnapshot();
-});
-
-it('should render closed facet without value', () => {
- expect(shallow(<FacetHeader name="foo" onClick={jest.fn()} open={false} />)).toMatchSnapshot();
-});
-
-it('should render without link', () => {
- expect(shallow(<FacetHeader name="foo" open={false} />)).toMatchSnapshot();
-});
-
-it('should call onClick', () => {
- const onClick = jest.fn();
- const wrapper = shallow(<FacetHeader name="foo" onClick={onClick} open={false} />);
- click(wrapper.find('a'));
- expect(onClick).toHaveBeenCalled();
-});
-
-it('should clear', () => {
- const onClear = jest.fn();
- const wrapper = shallow(
- <FacetHeader
- name="foo"
- onClear={onClear}
- onClick={jest.fn()}
- open={false}
- values={['foo', 'bar', 'baz']}
- />
- );
- expect(wrapper).toMatchSnapshot();
- click(wrapper.find('.button-red'));
- expect(onClear).toHaveBeenCalled();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import FacetHeader from '../FacetHeader';
+import { click } from '../../../helpers/testUtils';
+
+it('should render open facet with value', () => {
+ expect(
+ shallow(<FacetHeader name="foo" onClick={jest.fn()} open={true} values={['foo']} />)
+ ).toMatchSnapshot();
+});
+
+it('should render open facet without value', () => {
+ expect(shallow(<FacetHeader name="foo" onClick={jest.fn()} open={true} />)).toMatchSnapshot();
+});
+
+it('should render closed facet with value', () => {
+ expect(
+ shallow(<FacetHeader name="foo" onClick={jest.fn()} open={false} values={['foo']} />)
+ ).toMatchSnapshot();
+});
+
+it('should render closed facet without value', () => {
+ expect(shallow(<FacetHeader name="foo" onClick={jest.fn()} open={false} />)).toMatchSnapshot();
+});
+
+it('should render without link', () => {
+ expect(shallow(<FacetHeader name="foo" open={false} />)).toMatchSnapshot();
+});
+
+it('should call onClick', () => {
+ const onClick = jest.fn();
+ const wrapper = shallow(<FacetHeader name="foo" onClick={onClick} open={false} />);
+ click(wrapper.find('a'));
+ expect(onClick).toHaveBeenCalled();
+});
+
+it('should clear', () => {
+ const onClear = jest.fn();
+ const wrapper = shallow(
+ <FacetHeader
+ name="foo"
+ onClear={onClear}
+ onClick={jest.fn()}
+ open={false}
+ values={['foo', 'bar', 'baz']}
+ />
+ );
+ expect(wrapper).toMatchSnapshot();
+ click(wrapper.find('.button-red'));
+ expect(onClear).toHaveBeenCalled();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import { click } from '../../../helpers/testUtils';
-import FacetItem from '../FacetItem';
-
-const renderFacetItem = (props /*: {} */) =>
- shallow(
- <FacetItem active={false} name="foo" onClick={jest.fn()} stat={null} value="bar" {...props} />
- );
-
-it('should render active', () => {
- expect(renderFacetItem({ active: true })).toMatchSnapshot();
-});
-
-it('should render inactive', () => {
- expect(renderFacetItem({ active: false })).toMatchSnapshot();
-});
-
-it('should render stat', () => {
- expect(renderFacetItem({ stat: '13' })).toMatchSnapshot();
-});
-
-it('should render disabled', () => {
- expect(renderFacetItem({ disabled: true })).toMatchSnapshot();
-});
-
-it('should render half width', () => {
- expect(renderFacetItem({ halfWidth: true })).toMatchSnapshot();
-});
-
-it('should render effort stat', () => {
- expect(renderFacetItem({ facetMode: 'effort', stat: '1234' })).toMatchSnapshot();
-});
-
-it('should call onClick', () => {
- const onClick = jest.fn();
- const wrapper = renderFacetItem({ onClick });
- click(wrapper, { currentTarget: { dataset: { value: 'bar' } } });
- expect(onClick).toHaveBeenCalled();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import { click } from '../../../helpers/testUtils';
+import FacetItem, { Props } from '../FacetItem';
+
+it('should render active', () => {
+ expect(renderFacetItem({ active: true })).toMatchSnapshot();
+});
+
+it('should render inactive', () => {
+ expect(renderFacetItem({ active: false })).toMatchSnapshot();
+});
+
+it('should render stat', () => {
+ expect(renderFacetItem({ stat: '13' })).toMatchSnapshot();
+});
+
+it('should render disabled', () => {
+ expect(renderFacetItem({ disabled: true })).toMatchSnapshot();
+});
+
+it('should render half width', () => {
+ expect(renderFacetItem({ halfWidth: true })).toMatchSnapshot();
+});
+
+it('should call onClick', () => {
+ const onClick = jest.fn();
+ const wrapper = renderFacetItem({ onClick });
+ click(wrapper, { currentTarget: { blur() {}, dataset: { value: 'bar' } } });
+ expect(onClick).toHaveBeenCalled();
+});
+
+function renderFacetItem(props?: Partial<Props>) {
+ return shallow(
+ <FacetItem active={false} name="foo" onClick={jest.fn()} stat={null} value="bar" {...props} />
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import FacetItemsList from '../FacetItemsList';
-
-it('should render', () => {
- expect(
- shallow(
- <FacetItemsList>
- <div />
- </FacetItemsList>
- )
- ).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import FacetItemsList from '../FacetItemsList';
+
+it('should render', () => {
+ expect(
+ shallow(
+ <FacetItemsList>
+ <div />
+ </FacetItemsList>
+ )
+ ).toMatchSnapshot();
+});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render 1`] = `
-<div
- className="search-navigator-facet-box"
->
- <div />
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<div
+ className="search-navigator-facet-box"
+ data-property="foo"
+>
+ <div />
+</div>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render 1`] = `
-<div
- className="search-navigator-facet-footer"
->
- <SearchSelect
- autofocus={false}
- minimumQueryLength={2}
- onSearch={[MockFunction]}
- onSelect={[MockFunction]}
- resetOnBlur={true}
- />
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<div
+ className="search-navigator-facet-footer"
+>
+ <SearchSelect
+ autofocus={false}
+ onSearch={[MockFunction]}
+ onSelect={[MockFunction]}
+ resetOnBlur={true}
+ />
+</div>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should clear 1`] = `
-<div
- className="search-navigator-facet-header-wrapper"
->
- <span
- className="search-navigator-facet-header"
- >
- <a
- href="#"
- onClick={[Function]}
- >
- <OpenCloseIcon
- className="little-spacer-right"
- open={false}
- />
- foo
- </a>
- </span>
- <span
- className="search-navigator-facet-header-value spacer-left spacer-right "
- >
- <span
- className="badge badge-secondary is-rounded text-ellipsis"
- title="x_selected.3"
- >
- x_selected.3
- </span>
- </span>
- <button
- className="search-navigator-facet-header-button button-small button-red"
- onClick={[Function]}
- >
- clear
- </button>
-</div>
-`;
-
-exports[`should render closed facet with value 1`] = `
-<div
- className="search-navigator-facet-header-wrapper"
->
- <span
- className="search-navigator-facet-header"
- >
- <a
- href="#"
- onClick={[Function]}
- >
- <OpenCloseIcon
- className="little-spacer-right"
- open={false}
- />
- foo
- </a>
- </span>
- <span
- className="search-navigator-facet-header-value spacer-left spacer-right "
- >
- <span
- className="badge badge-secondary is-rounded text-ellipsis"
- title="foo"
- >
- foo
- </span>
- </span>
-</div>
-`;
-
-exports[`should render closed facet without value 1`] = `
-<div
- className="search-navigator-facet-header-wrapper"
->
- <span
- className="search-navigator-facet-header"
- >
- <a
- href="#"
- onClick={[Function]}
- >
- <OpenCloseIcon
- className="little-spacer-right"
- open={false}
- />
- foo
- </a>
- </span>
- <span
- className="search-navigator-facet-header-value spacer-left spacer-right "
- />
-</div>
-`;
-
-exports[`should render open facet with value 1`] = `
-<div
- className="search-navigator-facet-header-wrapper"
->
- <span
- className="search-navigator-facet-header"
- >
- <a
- href="#"
- onClick={[Function]}
- >
- <OpenCloseIcon
- className="little-spacer-right"
- open={true}
- />
- foo
- </a>
- </span>
- <span
- className="search-navigator-facet-header-value spacer-left spacer-right "
- />
-</div>
-`;
-
-exports[`should render open facet without value 1`] = `
-<div
- className="search-navigator-facet-header-wrapper"
->
- <span
- className="search-navigator-facet-header"
- >
- <a
- href="#"
- onClick={[Function]}
- >
- <OpenCloseIcon
- className="little-spacer-right"
- open={true}
- />
- foo
- </a>
- </span>
- <span
- className="search-navigator-facet-header-value spacer-left spacer-right "
- />
-</div>
-`;
-
-exports[`should render without link 1`] = `
-<div
- className="search-navigator-facet-header-wrapper"
->
- <span
- className="search-navigator-facet-header"
- >
- foo
- </span>
- <span
- className="search-navigator-facet-header-value spacer-left spacer-right "
- />
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should clear 1`] = `
+<div
+ className="search-navigator-facet-header-wrapper"
+>
+ <span
+ className="search-navigator-facet-header"
+ >
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ <OpenCloseIcon
+ className="little-spacer-right"
+ open={false}
+ />
+ foo
+ </a>
+ </span>
+ <span
+ className="search-navigator-facet-header-value spacer-left spacer-right "
+ >
+ <span
+ className="badge badge-secondary is-rounded text-ellipsis"
+ title="x_selected.3"
+ >
+ x_selected.3
+ </span>
+ </span>
+ <button
+ className="search-navigator-facet-header-button button-small button-red"
+ onClick={[Function]}
+ >
+ clear
+ </button>
+</div>
+`;
+
+exports[`should render closed facet with value 1`] = `
+<div
+ className="search-navigator-facet-header-wrapper"
+>
+ <span
+ className="search-navigator-facet-header"
+ >
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ <OpenCloseIcon
+ className="little-spacer-right"
+ open={false}
+ />
+ foo
+ </a>
+ </span>
+ <span
+ className="search-navigator-facet-header-value spacer-left spacer-right "
+ >
+ <span
+ className="badge badge-secondary is-rounded text-ellipsis"
+ title="foo"
+ >
+ foo
+ </span>
+ </span>
+</div>
+`;
+
+exports[`should render closed facet without value 1`] = `
+<div
+ className="search-navigator-facet-header-wrapper"
+>
+ <span
+ className="search-navigator-facet-header"
+ >
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ <OpenCloseIcon
+ className="little-spacer-right"
+ open={false}
+ />
+ foo
+ </a>
+ </span>
+ <span
+ className="search-navigator-facet-header-value spacer-left spacer-right "
+ />
+</div>
+`;
+
+exports[`should render open facet with value 1`] = `
+<div
+ className="search-navigator-facet-header-wrapper"
+>
+ <span
+ className="search-navigator-facet-header"
+ >
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ <OpenCloseIcon
+ className="little-spacer-right"
+ open={true}
+ />
+ foo
+ </a>
+ </span>
+ <span
+ className="search-navigator-facet-header-value spacer-left spacer-right "
+ />
+</div>
+`;
+
+exports[`should render open facet without value 1`] = `
+<div
+ className="search-navigator-facet-header-wrapper"
+>
+ <span
+ className="search-navigator-facet-header"
+ >
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ <OpenCloseIcon
+ className="little-spacer-right"
+ open={true}
+ />
+ foo
+ </a>
+ </span>
+ <span
+ className="search-navigator-facet-header-value spacer-left spacer-right "
+ />
+</div>
+`;
+
+exports[`should render without link 1`] = `
+<div
+ className="search-navigator-facet-header-wrapper"
+>
+ <span
+ className="search-navigator-facet-header"
+ >
+ foo
+ </span>
+ <span
+ className="search-navigator-facet-header-value spacer-left spacer-right "
+ />
+</div>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render active 1`] = `
-<a
- className="facet search-navigator-facet active"
- href="#"
- onClick={[Function]}
->
- <span
- className="facet-name"
- >
- foo
- </span>
-</a>
-`;
-
-exports[`should render disabled 1`] = `
-<span
- className="facet search-navigator-facet"
->
- <span
- className="facet-name"
- >
- foo
- </span>
-</span>
-`;
-
-exports[`should render effort stat 1`] = `
-<a
- className="facet search-navigator-facet"
- href="#"
- onClick={[Function]}
->
- <span
- className="facet-name"
- >
- foo
- </span>
- <span
- className="facet-stat"
- >
- 1234
- </span>
-</a>
-`;
-
-exports[`should render half width 1`] = `
-<a
- className="facet search-navigator-facet search-navigator-facet-half"
- href="#"
- onClick={[Function]}
->
- <span
- className="facet-name"
- >
- foo
- </span>
-</a>
-`;
-
-exports[`should render inactive 1`] = `
-<a
- className="facet search-navigator-facet"
- href="#"
- onClick={[Function]}
->
- <span
- className="facet-name"
- >
- foo
- </span>
-</a>
-`;
-
-exports[`should render stat 1`] = `
-<a
- className="facet search-navigator-facet"
- href="#"
- onClick={[Function]}
->
- <span
- className="facet-name"
- >
- foo
- </span>
- <span
- className="facet-stat"
- >
- 13
- </span>
-</a>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render active 1`] = `
+<a
+ className="facet search-navigator-facet active"
+ data-facet="bar"
+ href="#"
+ onClick={[Function]}
+>
+ <span
+ className="facet-name"
+ >
+ foo
+ </span>
+</a>
+`;
+
+exports[`should render disabled 1`] = `
+<span
+ className="facet search-navigator-facet"
+ data-facet="bar"
+>
+ <span
+ className="facet-name"
+ >
+ foo
+ </span>
+</span>
+`;
+
+exports[`should render half width 1`] = `
+<a
+ className="facet search-navigator-facet search-navigator-facet-half"
+ data-facet="bar"
+ href="#"
+ onClick={[Function]}
+>
+ <span
+ className="facet-name"
+ >
+ foo
+ </span>
+</a>
+`;
+
+exports[`should render inactive 1`] = `
+<a
+ className="facet search-navigator-facet"
+ data-facet="bar"
+ href="#"
+ onClick={[Function]}
+>
+ <span
+ className="facet-name"
+ >
+ foo
+ </span>
+</a>
+`;
+
+exports[`should render stat 1`] = `
+<a
+ className="facet search-navigator-facet"
+ data-facet="bar"
+ href="#"
+ onClick={[Function]}
+>
+ <span
+ className="facet-name"
+ >
+ foo
+ </span>
+ <span
+ className="facet-stat"
+ >
+ 13
+ </span>
+</a>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render 1`] = `
-<div
- className="search-navigator-facet-list"
->
- <div />
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<div
+ className="search-navigator-facet-list"
+>
+ <div />
+</div>
+`;
className={'js-issue-edit-tags button-link issue-action issue-action-with-options'}
onClick={this.toggleSetTags}>
<TagsList
- tags={issue.tags && issue.tags.length > 0 ? issue.tags : [translate('issue.no_tag')]}
allowUpdate={this.props.canSetTags}
+ tags={issue.tags && issue.tags.length > 0 ? issue.tags : [translate('issue.no_tag')]}
/>
</button>
</BubblePopupHelper>
} else {
return (
<TagsList
- tags={issue.tags && issue.tags.length > 0 ? issue.tags : [translate('issue.no_tag')]}
allowUpdate={this.props.canSetTags}
+ className="note"
+ tags={issue.tags && issue.tags.length > 0 ? issue.tags : [translate('issue.no_tag')]}
/>
);
}
exports[`should render without the action when the correct rights are missing 1`] = `
<TagsList
allowUpdate={false}
+ className="note"
tags={
Array [
"issue.no_tag",
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import { uniq } from 'lodash';
-import Marionette from 'backbone.marionette';
-
-export default Marionette.Controller.extend({
- pageSize: 50,
-
- initialize(options) {
- this.app = options.app;
- this.listenTo(options.app.state, 'change:query', this.fetchList);
- },
-
- _allFacets() {
- return this.options.app.state.get('allFacets').map(facet => {
- return { property: facet };
- });
- },
-
- _enabledFacets() {
- const that = this;
- let facets = this.options.app.state.get('facets');
- const criteria = Object.keys(this.options.app.state.get('query'));
- facets = facets.concat(criteria);
- facets = facets.map(facet => {
- return that.options.app.state.get('transform')[facet] != null
- ? that.options.app.state.get('transform')[facet]
- : facet;
- });
- facets = uniq(facets);
- return facets.filter(facet => that.options.app.state.get('allFacets').indexOf(facet) !== -1);
- },
-
- _facetsFromServer() {
- const that = this;
- const facets = this._enabledFacets();
- return facets.filter(
- facet => that.options.app.state.get('facetsFromServer').indexOf(facet) !== -1
- );
- },
-
- fetchList() {},
-
- fetchNextPage() {
- this.options.app.state.nextPage();
- return this.fetchList(false);
- },
-
- enableFacet(id) {
- const facet = this.options.app.facets.get(id);
- if (facet.has('values') || this.options.app.state.get('facetsFromServer').indexOf(id) === -1) {
- facet.set({ enabled: true });
- } else {
- this.requestFacet(id).then(() => {
- facet.set({ enabled: true });
- });
- }
- },
-
- disableFacet(id) {
- const facet = this.options.app.facets.get(id);
- facet.set({ enabled: false });
- this.options.app.facetsView.children.findByModel(facet).disable();
- },
-
- toggleFacet(id) {
- const facet = this.options.app.facets.get(id);
- if (facet.get('enabled')) {
- this.disableFacet(id);
- } else {
- this.enableFacet(id);
- }
- },
-
- enableFacets(facets) {
- facets.forEach(this.enableFacet, this);
- },
-
- newSearch() {
- this.options.app.state.setQuery({});
- },
-
- parseQuery(query, separator) {
- separator = separator || '|';
- const q = {};
- (query || '').split(separator).forEach(t => {
- const tokens = t.split('=');
- if (tokens[0] && tokens[1] != null) {
- q[tokens[0]] = decodeURIComponent(tokens[1]);
- }
- });
- return q;
- },
-
- getQuery(separator) {
- separator = separator || '|';
- const filter = this.options.app.state.get('query');
- const route = [];
- Object.keys(filter).forEach(property => {
- route.push(`${property}=${encodeURIComponent(filter[property])}`);
- });
- return route.join(separator);
- },
-
- getRoute(separator) {
- separator = separator || '|';
- return this.getQuery(separator);
- },
-
- selectNext() {
- const index = this.options.app.state.get('selectedIndex') + 1;
- if (index < this.options.app.list.length) {
- this.options.app.state.set({ selectedIndex: index });
- } else if (!this.options.app.state.get('maxResultsReached')) {
- const that = this;
- this.fetchNextPage().then(() => {
- that.options.app.state.set({ selectedIndex: index });
- });
- } else {
- this.options.app.list.trigger('limitReached');
- }
- },
-
- selectPrev() {
- const index = this.options.app.state.get('selectedIndex') - 1;
- if (index >= 0) {
- this.options.app.state.set({ selectedIndex: index });
- } else {
- this.options.app.list.trigger('limitReached');
- }
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import Marionette from 'backbone.marionette';
-import BaseFacet from './facets/base-facet';
-
-export default Marionette.CollectionView.extend({
- className: 'search-navigator-facets-list',
-
- childViewOptions() {
- return {
- app: this.options.app
- };
- },
-
- getChildView() {
- return BaseFacet;
- },
-
- collectionEvents() {
- return {
- 'change:enabled': 'updateState'
- };
- },
-
- updateState() {
- const enabledFacets = this.collection.filter(model => model.get('enabled'));
- const enabledFacetIds = enabledFacets.map(model => model.id);
- this.options.app.state.set({ facets: enabledFacetIds });
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import $ from 'jquery';
-import Marionette from 'backbone.marionette';
-
-export default Marionette.ItemView.extend({
- className: 'search-navigator-facet-box',
- forbiddenClassName: 'search-navigator-facet-box-forbidden',
-
- modelEvents() {
- return {
- change: 'render'
- };
- },
-
- events() {
- return {
- 'click .js-facet-toggle': 'toggle',
- 'click .js-facet': 'toggleFacet'
- };
- },
-
- onRender() {
- this.$el.toggleClass('search-navigator-facet-box-collapsed', !this.model.get('enabled'));
- this.$el.attr('data-property', this.model.get('property'));
- const that = this;
- const property = this.model.get('property');
- const value = this.options.app.state.get('query')[property];
- if (typeof value === 'string') {
- value.split(',').forEach(s => {
- const facet = that.$('.js-facet').filter(`[data-value="${s}"]`);
- if (facet.length > 0) {
- facet.addClass('active');
- }
- });
- }
- },
-
- toggle() {
- if (!this.isForbidden()) {
- this.options.app.controller.toggleFacet(this.model.id);
- }
- },
-
- getValue() {
- return this.$('.js-facet.active')
- .map(function() {
- return $(this).data('value');
- })
- .get()
- .join();
- },
-
- toggleFacet(e) {
- $(e.currentTarget).toggleClass('active');
- const property = this.model.get('property');
- const obj = {};
- obj[property] = this.getValue();
- this.options.app.state.updateFilter(obj);
- },
-
- disable() {
- const property = this.model.get('property');
- const obj = {};
- obj[property] = null;
- this.options.app.state.updateFilter(obj);
- },
-
- forbid() {
- this.options.app.controller.disableFacet(this.model.id);
- this.$el.addClass(this.forbiddenClassName);
- },
-
- allow() {
- this.$el.removeClass(this.forbiddenClassName);
- },
-
- isForbidden() {
- return this.$el.hasClass(this.forbiddenClassName);
- },
-
- sortValues(values) {
- return values.slice().sort((left, right) => {
- if (left.count !== right.count) {
- return right.count - left.count;
- }
- if (left.val !== right.val) {
- if (left.val > right.val) {
- return 1;
- }
- if (left.val < right.val) {
- return -1;
- }
- }
- return 0;
- });
- },
-
- serializeData() {
- return {
- ...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
- values: this.sortValues(this.model.getValues())
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import Backbone from 'backbone';
-
-export default Backbone.Model.extend({
- idAttribute: 'property',
-
- defaults: {
- enabled: false
- },
-
- getValues() {
- return this.get('values') || [];
- },
-
- toggle() {
- const enabled = this.get('enabled');
- this.set({ enabled: !enabled });
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import Backbone from 'backbone';
-import Facet from './facet';
-
-export default Backbone.Collection.extend({
- model: Facet
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import Backbone from 'backbone';
-
-export default Backbone.Model.extend({
- defaults() {
- return {
- page: 1,
- maxResultsReached: false,
- query: {},
- facets: []
- };
- },
-
- nextPage() {
- const page = this.get('page');
- this.set({ page: page + 1 });
- },
-
- clearQuery(query) {
- const q = {};
- Object.keys(query).forEach(key => {
- if (query[key]) {
- q[key] = query[key];
- }
- });
- return q;
- },
-
- _areQueriesEqual(a, b) {
- let equal = Object.keys(a).length === Object.keys(b).length;
- Object.keys(a).forEach(key => {
- equal = equal && a[key] === b[key];
- });
- return equal;
- },
-
- updateFilter(obj, options) {
- const oldQuery = this.get('query');
- let query = { ...oldQuery, ...obj };
- const opts = { force: false, ...options };
- query = this.clearQuery(query);
- if (opts.force || !this._areQueriesEqual(oldQuery, query)) {
- this.setQuery(query);
- }
- },
-
- setQuery(query) {
- this.set({ query }, { silent: true });
- this.set({ changed: true });
- this.trigger('change:query');
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import Backbone from 'backbone';
-
-export default Backbone.Router.extend({
- routeSeparator: '|',
-
- routes: {
- '': 'index',
- ':query': 'index'
- },
-
- initialize(options) {
- this.options = options;
- this.listenTo(this.options.app.state, 'change:query', this.updateRoute);
- },
-
- index(query) {
- query = this.options.app.controller.parseQuery(query);
- this.options.app.state.setQuery(query);
- },
-
- updateRoute() {
- const route = this.options.app.controller.getRoute();
- this.navigate(route);
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import Marionette from 'backbone.marionette';
-
-export default Marionette.ItemView.extend({
- collectionEvents() {
- return {
- all: 'shouldRender',
- limitReached: 'flashPagination'
- };
- },
-
- events() {
- return {
- 'click .js-bulk-change': 'onBulkChangeClick',
- 'click .js-reload': 'reload',
- 'click .js-next': 'selectNext',
- 'click .js-prev': 'selectPrev'
- };
- },
-
- initialize(options) {
- this.listenTo(options.app.state, 'change', this.render);
- },
-
- onRender() {
- this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
- },
-
- onBeforeRender() {
- this.$('[data-toggle="tooltip"]').tooltip('destroy');
- },
-
- onDestroy() {
- this.$('[data-toggle="tooltip"]').tooltip('destroy');
- },
-
- onBulkChangeClick(e) {
- e.preventDefault();
- this.bulkChange();
- },
-
- bulkChange() {},
-
- shouldRender(event) {
- if (event !== 'limitReached') {
- this.render();
- }
- },
-
- reload() {
- this.options.app.controller.fetchList();
- },
-
- selectNext() {
- this.options.app.controller.selectNext();
- },
-
- selectPrev() {
- this.options.app.controller.selectPrev();
- },
-
- flashPagination() {
- const flashElement = this.$('.search-navigator-header-pagination');
- flashElement.addClass('in');
- setTimeout(() => {
- flashElement.removeClass('in');
- }, 2000);
- },
-
- serializeData() {
- return {
- ...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
- state: this.options.app.state.toJSON()
- };
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import Marionette from 'backbone.marionette';
-
-export default Marionette.ItemView.extend({
- initialize(options) {
- this.listenTo(options.app.state, 'change:selectedIndex', this.select);
- },
-
- onRender() {
- this.select();
- },
-
- select() {
- const selected = this.model.get('index') === this.options.app.state.get('selectedIndex');
- this.$el.toggleClass('selected', selected);
- },
-
- selectCurrent() {
- this.options.app.state.set({ selectedIndex: this.model.get('index') });
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-import $ from 'jquery';
-import { throttle } from 'lodash';
-import Marionette from 'backbone.marionette';
-import key from 'keymaster';
-
-const BOTTOM_OFFSET = 60;
-
-export default Marionette.CompositeView.extend({
- ui: {
- loadMore: '.js-more',
- lastElementReached: '.js-last-element-reached'
- },
-
- childViewOptions() {
- return {
- app: this.options.app
- };
- },
-
- collectionEvents: {
- reset: 'scrollToTop'
- },
-
- initialize(options) {
- this.loadMoreThrottled = throttle(this.loadMore, 1000, { trailing: false });
- this.listenTo(options.app.state, 'change:maxResultsReached', this.toggleLoadMore);
- this.listenTo(options.app.state, 'change:selectedIndex', this.scrollTo);
- this.bindShortcuts();
- },
-
- onDestroy() {
- this.unbindScrollEvents();
- this.unbindShortcuts();
- },
-
- onRender() {
- this.toggleLoadMore();
- },
-
- toggleLoadMore() {
- const maxResultsReached = this.options.app.state.get('maxResultsReached');
- this.ui.loadMore.toggle(!maxResultsReached);
- this.ui.lastElementReached.toggle(maxResultsReached);
- },
-
- bindScrollEvents() {
- const that = this;
- $(window).on('scroll.workspace-list-view', () => {
- that.onScroll();
- });
- },
-
- unbindScrollEvents() {
- $(window).off('scroll.workspace-list-view');
- },
-
- bindShortcuts() {
- const that = this;
- key('up', 'list', () => {
- that.options.app.controller.selectPrev();
- return false;
- });
-
- key('down', 'list', () => {
- that.options.app.controller.selectNext();
- return false;
- });
- },
-
- unbindShortcuts() {
- key.unbind('up', 'list');
- key.unbind('down', 'list');
- },
-
- loadMore() {
- if (!this.options.app.state.get('maxResultsReached')) {
- const that = this;
- this.unbindScrollEvents();
- this.options.app.controller.fetchNextPage().then(() => {
- that.bindScrollEvents();
- });
- }
- },
-
- onScroll() {
- if ($(window).scrollTop() + $(window).height() >= this.ui.loadMore.offset().top) {
- this.loadMoreThrottled();
- }
- },
-
- scrollToTop() {
- this.$el.scrollParent().scrollTop(0);
- },
-
- scrollTo() {
- const selected = this.collection.at(this.options.app.state.get('selectedIndex'));
- if (selected == null) {
- return;
- }
- const selectedView = this.children.findByModel(selected);
- const parentTopOffset = this.$el.offset().top;
- const viewTop = selectedView.$el.offset().top - parentTopOffset;
- const viewBottom =
- selectedView.$el.offset().top + selectedView.$el.outerHeight() + BOTTOM_OFFSET;
- const windowTop = $(window).scrollTop();
- const windowBottom = windowTop + $(window).height();
- if (viewTop < windowTop) {
- $(window).scrollTop(viewTop);
- }
- if (viewBottom > windowBottom) {
- $(window).scrollTop($(window).scrollTop() - windowBottom + viewBottom);
- }
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-//@flow
-import React from 'react';
-import SeverityIcon from './SeverityIcon';
-import { translate } from '../../helpers/l10n';
-
-export default function SeverityHelper(props /*: { severity: ?string, className?: string } */) {
- const { severity } = props;
- if (!severity) {
- return null;
- }
- return (
- <span className={props.className}>
- <SeverityIcon className="little-spacer-right" severity={severity} />
- {translate('severity', severity)}
- </span>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import SeverityIcon from './SeverityIcon';
+import { translate } from '../../helpers/l10n';
+
+interface Props {
+ className?: string;
+ // TODO avoid passing nil values
+ severity: string | undefined | null;
+}
+
+export default function SeverityHelper({ className, severity }: Props) {
+ if (!severity) {
+ return null;
+ }
+ return (
+ <span className={className}>
+ <SeverityIcon className="little-spacer-right" severity={severity} />
+ {translate('severity', severity)}
+ </span>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-//@flow
-import React from 'react';
-import IssueTypeIcon from '../ui/IssueTypeIcon';
-import { translate } from '../../helpers/l10n';
-
-/*::
-type Props = {
- type: string
-};
-*/
-
-const TypeHelper = (props /*: Props */) => (
- <span>
- <IssueTypeIcon className="little-spacer-right" query={props.type} />
- {translate('issue.type', props.type)}
- </span>
-);
-
-export default TypeHelper;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+import * as React from 'react';
+import IssueTypeIcon from '../ui/IssueTypeIcon';
+import { translate } from '../../helpers/l10n';
+
+interface Props {
+ className?: string;
+ type: string;
+}
+
+export default function TypeHelper(props: Props) {
+ return (
+ <span className={props.className}>
+ <IssueTypeIcon className="little-spacer-right" query={props.type} />
+ {translate('issue.type', props.type)}
+ </span>
+ );
+}
interface Props {
allowUpdate?: boolean;
- customClass?: string;
+ className?: string;
tags: string[];
}
-export default function TagsList({ allowUpdate = false, customClass, tags }: Props) {
- const spanClass = classNames('text-ellipsis', { note: !allowUpdate });
- const tagListClass = classNames('tags-list', customClass);
-
+export default function TagsList({ allowUpdate = false, className, tags }: Props) {
return (
- <span className={tagListClass} title={tags.join(', ')}>
- <i className="icon-tags icon-half-transparent" />
- <span className={spanClass}>{tags.join(', ')}</span>
+ <span className={classNames('tags-list', className)} title={tags.join(', ')}>
+ <i className="icon-tags" />
+ <span className="text-ellipsis">{tags.join(', ')}</span>
{allowUpdate && <i className="icon-dropdown" />}
</span>
);
const tags = ['foo', 'bar'];
it('should render with a list of tag', () => {
- const taglist = shallow(<TagsList tags={tags} />);
- expect(taglist.text()).toBe(tags.join(', '));
- expect(taglist.find('i').length).toBe(1);
- expect(taglist.find('span.note').hasClass('text-ellipsis')).toBe(true);
-});
-
-it('should correctly handle a lot of tags', () => {
- const lotOfTags = [];
- for (let i = 0; i < 20; i++) {
- lotOfTags.push(String(tags));
- }
- const taglist = shallow(<TagsList tags={lotOfTags} />);
- expect(taglist.text()).toBe(lotOfTags.join(', '));
- expect(taglist.find('span.note').hasClass('text-ellipsis')).toBe(true);
+ expect(shallow(<TagsList tags={tags} />)).toMatchSnapshot();
});
it('should render with a caret on the right if update is allowed', () => {
- const taglist = shallow(<TagsList tags={tags} allowUpdate={true} />);
- expect(taglist.find('i').length).toBe(2);
+ expect(shallow(<TagsList allowUpdate={true} tags={tags} />)).toMatchSnapshot();
});
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render with a caret on the right if update is allowed 1`] = `
+<span
+ className="tags-list"
+ title="foo, bar"
+>
+ <i
+ className="icon-tags"
+ />
+ <span
+ className="text-ellipsis"
+ >
+ foo, bar
+ </span>
+ <i
+ className="icon-dropdown"
+ />
+</span>
+`;
+
+exports[`should render with a list of tag 1`] = `
+<span
+ className="tags-list"
+ title="foo, bar"
+>
+ <i
+ className="icon-tags"
+ />
+ <span
+ className="text-ellipsis"
+ >
+ foo, bar
+ </span>
+</span>
+`;
import Marionette from 'backbone.marionette';
import BaseView from './base-viewer-view';
import Template from '../templates/workspace-rule.hbs';
-import { getRulesUrl } from '../../../helpers/urls';
+import { getPathUrlAsString, getRulesUrl } from '../../../helpers/urls';
import { areThereCustomOrganizations } from '../../../store/organizations/utils';
export default BaseView.extend({
serializeData() {
const query = { rule_key: this.model.get('key') };
- const permalink = areThereCustomOrganizations()
- ? getRulesUrl(query, this.model.get('organization'))
- : getRulesUrl(query);
+ const permalink = getPathUrlAsString(
+ areThereCustomOrganizations()
+ ? getRulesUrl(query, this.model.get('organization'))
+ : getRulesUrl(query, undefined)
+ );
return {
...Marionette.LayoutView.prototype.serializeData.apply(this, arguments),
export const SEVERITIES = ['BLOCKER', 'CRITICAL', 'MAJOR', 'MINOR', 'INFO'];
export const STATUSES = ['OPEN', 'REOPENED', 'CONFIRMED', 'RESOLVED', 'CLOSED'];
+export const TYPES = ['BUG', 'VULNERABILITY', 'CODE_SMELL'];
+export const RULE_STATUSES = ['READY', 'BETA', 'DEPRECATED'];
export const CHART_COLORS_RANGE_PERCENT = [
theme.green,
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-const React = require('react');
-const { renderToString } = require('react-dom/server');
-const Handlebars = require('handlebars/runtime');
-const WithStore = require('../../components/shared/WithStore').default;
-const Avatar = require('../../components/ui/Avatar').default;
-
-module.exports = function(hash, name, size) {
- return new Handlebars.default.SafeString(
- renderToString(
- <WithStore>
- <Avatar hash={hash} name={name} size={size} />
- </WithStore>
- )
- );
-};
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-module.exports = function(array, options) {
- const cond = Array.isArray(array) && array.length > 0;
- return cond ? options.inverse(this) : options.fn(this);
-};
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-module.exports = function(v1, v2, options) {
- return v1 > v2 ? options.fn(this) : options.inverse(this);
-};
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-module.exports = function(array, len, options) {
- const cond = Array.isArray(array) && array.length === +len;
- return cond ? options.fn(this) : options.inverse(this);
-};
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-module.exports = function(number, options) {
- let ret = '';
- for (let i = 0; i < number; i++) {
- ret += options.fn(this);
- }
- return ret;
-};
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { isNil, omitBy } from 'lodash';
-import { isValidDate, parseDate, toNotSoISOString } from './dates';
+import { isValidDate, parseDate, toNotSoISOString, toShortNotSoISOString } from './dates';
export interface RawQuery {
[x: string]: any;
return value === 'false' ? false : value === 'true' ? true : defaultValue;
}
+export function parseAsOptionalBoolean(value: string | undefined): boolean | undefined {
+ if (value === 'true') {
+ return true;
+ } else if (value === 'false') {
+ return false;
+ } else {
+ return undefined;
+ }
+}
+
export function parseAsDate(value?: string): Date | undefined {
if (value) {
const date = parseDate(value);
return value || '';
}
+export function parseAsOptionalString(value: string | undefined): string | undefined {
+ return value || undefined;
+}
+
export function parseAsArray(
value: string | undefined,
itemParser: (x: string) => string
return value ? value.split(',').map(itemParser) : [];
}
-export function serializeDate(value?: Date): string | undefined {
+export function serializeDate(value?: Date, serializer = toNotSoISOString): string | undefined {
if (value != null && value.toISOString) {
- return toNotSoISOString(value);
+ return serializer(value);
}
return undefined;
}
-export function serializeString(value: string): string | undefined {
+export function serializeDateShort(value: Date | undefined): string | undefined {
+ return serializeDate(value, toShortNotSoISOString);
+}
+
+export function serializeString(value: string | undefined): string | undefined {
return value || undefined;
}
export function serializeStringArray(value: string[] | undefined[]): string | undefined {
return value && value.length ? value.join() : undefined;
}
+
+export function serializeOptionalBoolean(value: boolean | undefined): string | undefined {
+ if (value === true) {
+ return 'true';
+ } else if (value === false) {
+ return 'false';
+ } else {
+ return undefined;
+ }
+}
smoothScrollTop = debounce(smoothScrollTop, SCROLLING_DURATION, { leading: true });
export function scrollToElement(
- element: HTMLElement,
+ element: Element,
options: {
topOffset?: number;
bottomOffset?: number;
/**
* Generate URL for a global issues page
*/
-export function getIssuesUrl(query: Query): Location {
- return { pathname: '/issues', query };
+export function getIssuesUrl(query: Query, organization?: string): Location {
+ const pathname = organization ? `/organizations/${organization}/issues` : '/issues';
+ return { pathname, query };
}
/**
/**
* Generate URL for the rules page
*/
-export function getRulesUrl(query: { [x: string]: string }, organization?: string | null): string {
- const path = organization ? `/organizations/${organization}/rules` : '/coding_rules';
-
- if (query) {
- const serializedQuery = Object.keys(query)
- .map(criterion => `${encodeURIComponent(criterion)}=${encodeURIComponent(query[criterion])}`)
- .join('|');
-
- // return a string (not { pathname }) to help react-router's Link handle this properly
- return path + '#' + serializedQuery;
- }
-
- return path;
+export function getRulesUrl(query: Query, organization: string | null | undefined): Location {
+ const pathname = organization ? `/organizations/${organization}/rules` : '/coding_rules';
+ return { pathname, query };
}
/**
* Generate URL for the rules page filtering only active deprecated rules
*/
-export function getDeprecatedActiveRulesUrl(query = {}, organization?: string | null): string {
+export function getDeprecatedActiveRulesUrl(
+ query: Query = {},
+ organization: string | null | undefined
+): Location {
const baseQuery = { activation: 'true', statuses: 'DEPRECATED' };
return getRulesUrl({ ...query, ...baseQuery }, organization);
}
+export function getRuleUrl(rule: string, organization: string | undefined) {
+ /* eslint-disable camelcase */
+ return getRulesUrl({ open: rule, rule_key: rule }, organization);
+ /* eslint-enable camelcase */
+}
+
export function getMarkdownHelpUrl(): string {
return getBaseUrl() + '/markdown/help';
}
version "3.2.11"
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.2.11.tgz#9119f91bb103b16ae8c4375b019a9b341b409f50"
+"@types/keymaster@1.6.28":
+ version "1.6.28"
+ resolved "https://registry.yarnpkg.com/@types/keymaster/-/keymaster-1.6.28.tgz#093fc6fe49deff4ee17d36935a49230edb1c935f"
+
"@types/lodash@4.14.80":
version "4.14.80"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.80.tgz#a6b8b7900e6a7dcbc2e90d9b6dfbe3f6a7f69951"
coding_rules.activate=Activate
coding_rules.activate_in=Activate In
coding_rules.activate_in_quality_profile=Activate In Quality Profile
+coding_rules.activation_severity=Activation Severity
coding_rules.available_since=Available Since
coding_rules.bulk_change=Bulk Change
coding_rules.bulk_change.success={2} rule(s) changed in profile {0} - {1}
coding_rules.reactivate=Reactivate
coding_rules.reactivate.help=A rule with the same key has been previously deleted. Please reactivate the existing rule or modify the key to create a new rule.
coding_rules.return_to_list=Return to List
+coding_rules.remove_extended_description=Remove Extended Description
coding_rules.remove_extended_description.confirm=Are you sure you want to remove the extended description?
+coding_rules.repository_language=Rule repository (language)
coding_rules.revert_to_parent_definition=Revert to Parent Definition
coding_rules.revert_to_parent_definition.confirm=This rule will be reverted to the parameters defined in profile {0}. Are you sure?
coding_rules.rule_template=Rule Template
-coding_rules.rule_template.title=This rule can be used as a template to create custom rules,\nit cannot be activated on a profile
+coding_rules.rule_template.title=This rule can be used as a template to create custom rules, it cannot be activated on a profile
coding_rules._rules=rules
coding_rules.show_template=Show Template
coding_rules.to_select_rules=to select rules
coding_rules.facet.statuses=Status
coding_rules.facet.available_since=Available Since
coding_rules.facet.inheritance=Inheritance
-coding_rules.facet.active_severities=Activation Severity
-coding_rules.facet.is_template=Template
+coding_rules.facet.activationSeverities=Activation Severity
+coding_rules.facet.template=Template
coding_rules.facet.rule_key=Rule
coding_rules.facet.types=Type
import org.sonarqube.tests.issue.OrganizationIssuesPageTest;
import org.sonarqube.tests.qualityProfile.BuiltInQualityProfilesTest;
import org.sonarqube.tests.qualityProfile.CustomQualityProfilesTest;
+import org.sonarqube.tests.qualityProfile.OrganizationQualityProfilesUiTest;
import org.sonarqube.tests.qualityProfile.QualityProfilesEditTest;
import org.sonarqube.tests.qualityProfile.QualityProfilesWsTest;
import org.sonarqube.tests.rule.RulesMarkdownTest;
OrganizationIdentityProviderTest.class,
OrganizationIssueAssignTest.class,
OrganizationIssuesPageTest.class,
+ OrganizationQualityProfilesUiTest.class,
BuiltInQualityProfilesTest.class,
QualityProfilesEditTest.class,
QualityProfilesWsTest.class,
qpPage.shouldHaveMissingSonarWayRules(2);
RulesPage rPage = qpPage.showMissingSonarWayRules();
rPage.shouldHaveTotalRules(2);
- rPage.getSelectedFacetItems("qprofile")
+ rPage.openFacet("profile").getSelectedFacetItems("profile")
.shouldHaveSize(2)
.findBy(Condition.cssClass("compare")).has(Condition.text("Sonar way"));
}
import org.junit.Rule;
import org.junit.Test;
import org.sonarqube.qa.util.Tester;
+import org.sonarqube.qa.util.pageobjects.RuleDetails;
import org.sonarqube.qa.util.pageobjects.RulesPage;
import org.sonarqube.tests.Category4Suite;
+import org.sonarqube.ws.Qualityprofiles.CreateWsResponse.QualityProfile;
+import org.sonarqube.ws.client.qualityprofiles.ChangeParentRequest;
+import org.sonarqube.ws.client.qualityprofiles.CreateRequest;
+import util.ProjectAnalysisRule;
+
+import static com.codeborne.selenide.WebDriverRunner.url;
+import static org.assertj.core.api.Assertions.assertThat;
public class RulesPageTest {
+ private static final String SAMPLE_RULE = "xoo:HasTag";
+ private static final String SAMPLE_RULE_XOO2 = "xoo2:HasTag";
+ private static final String SAMPLE_RULE2 = "common-xoo:InsufficientBranchCoverage";
+ private static final String SAMPLE_SECURITY_RULE = "xoo:OneVulnerabilityIssuePerModule";
+ private static final String SAMPLE_TEMPLATE_RULE = "xoo:xoo-template";
+ private static final String XOO_LANG = "xoo";
+ private static int profileIndex = 0;
+
@ClassRule
public static Orchestrator ORCHESTRATOR = Category4Suite.ORCHESTRATOR;
@Rule
public Tester tester = new Tester(ORCHESTRATOR).disableOrganizations();
+ @Rule
+ public ProjectAnalysisRule projectAnalysisRule = ProjectAnalysisRule.from(ORCHESTRATOR);
+
+ @Test
+ public void should_search_rules() {
+ RulesPage page = tester.openBrowser().openRules();
+ page.shouldDisplayRules(SAMPLE_RULE, SAMPLE_RULE2);
+ page.search("branches");
+ page.shouldNotDisplayRules(SAMPLE_RULE).shouldDisplayRules(SAMPLE_RULE2);
+ assertThat(url()).contains("q=branches");
+ }
+
+ @Test
+ public void should_filter_rules_by_language() {
+ RulesPage page = tester.openBrowser().openRules();
+ page.shouldDisplayRules(SAMPLE_RULE, SAMPLE_RULE_XOO2);
+ page.selectFacetItem("languages", XOO_LANG);
+ page.shouldNotDisplayRules(SAMPLE_RULE_XOO2).shouldDisplayRules(SAMPLE_RULE);
+ assertThat(url()).contains("languages=" + XOO_LANG);
+ }
+
+ @Test
+ public void should_filter_rules_by_type() {
+ RulesPage page = tester.openBrowser().openRules();
+ page.shouldDisplayRules(SAMPLE_RULE, SAMPLE_SECURITY_RULE);
+ page.selectFacetItem("types", "VULNERABILITY");
+ page.shouldNotDisplayRules(SAMPLE_RULE).shouldDisplayRules(SAMPLE_SECURITY_RULE);
+ assertThat(url()).contains("types=VULNERABILITY");
+ }
+
+ @Test
+ public void should_filter_rules_by_default_severity() {
+ String ruleKey = "custom_blocker_rule";
+ String blockerRule = "xoo:" + ruleKey;
+ createBlockerCustomRule(ruleKey);
+
+ RulesPage page = tester.openBrowser().openRules();
+ page.shouldDisplayRules(SAMPLE_RULE, blockerRule);
+ page.openFacet("severities").selectFacetItem("severities", "BLOCKER");
+ page.shouldNotDisplayRules(SAMPLE_RULE).shouldDisplayRules(blockerRule);
+ assertThat(url()).contains("severities=BLOCKER");
+
+ deleteCustomRule(blockerRule);
+ }
+
+ @Test
+ public void should_filter_rules_by_availability_date() {
+ // TODO
+ // all rules have the same "available since" date, because this is the date when server started
+ }
+
+ @Test
+ public void should_filter_rules_by_template() {
+ RulesPage page = tester.openBrowser().openRules();
+ page.shouldDisplayRules(SAMPLE_RULE, SAMPLE_TEMPLATE_RULE);
+
+ page.openFacet("template").selectFacetItem("template", "true");
+ page.shouldNotDisplayRules(SAMPLE_RULE).shouldDisplayRules(SAMPLE_TEMPLATE_RULE);
+ assertThat(url()).contains("template=true");
+
+ page.selectFacetItem("template", "false");
+ page.shouldNotDisplayRules(SAMPLE_TEMPLATE_RULE).shouldDisplayRules(SAMPLE_RULE);
+ assertThat(url()).contains("template=false");
+ }
+
+ @Test
+ public void should_filter_rules_by_quality_profile() {
+ QualityProfile empty = createQualityProfile();
+ QualityProfile profile = createQualityProfile();
+ tester.qProfiles().activateRule(profile, SAMPLE_RULE);
+
+ RulesPage page = tester.openBrowser().openRules();
+ page.shouldDisplayRules(SAMPLE_RULE);
+
+ page.openFacet("profile").selectFacetItem("profile", empty.getKey());
+ page.shouldNotDisplayRules(SAMPLE_RULE);
+ assertThat(url()).contains("qprofile="); // TODO we don't know profile key
+
+ page.selectFacetItem("profile", profile.getKey());
+ page.shouldDisplayRules(SAMPLE_RULE);
+ assertThat(url()).contains("qprofile=" + profile.getKey());
+ }
+
+ @Test
+ public void should_filter_rules_by_inheritance() {
+ QualityProfile profile = createInheritedQualityProfile();
+
+ RulesPage page = tester.openBrowser().openRules();
+ page.shouldHaveDisabledFacet("inheritance").shouldDisplayRules(SAMPLE_RULE);
+
+ page.openFacet("profile").selectFacetItem("profile", profile.getKey());
+ page.shouldNotHaveDisabledFacet("inheritance").openFacet("inheritance");
+
+ page.selectFacetItem("inheritance", "NONE");
+ page.shouldDisplayRules(SAMPLE_RULE2).shouldNotDisplayRules(SAMPLE_RULE);
+ assertThat(url()).contains("inheritance=NONE");
+
+ page.selectFacetItem("inheritance", "INHERITED");
+ page.shouldDisplayRules("xoo:OneIssuePerLine").shouldNotDisplayRules(SAMPLE_RULE);
+ assertThat(url()).contains("inheritance=INHERITED");
+
+ page.selectFacetItem("inheritance", "OVERRIDES");
+ page.shouldDisplayRules(SAMPLE_RULE).shouldNotDisplayRules(SAMPLE_RULE2);
+ assertThat(url()).contains("inheritance=OVERRIDES");
+ }
+
+ @Test
+ public void should_filter_rules_by_activation_severity() {
+ QualityProfile profile = createQualityProfile();
+ tester.qProfiles().activateRule(profile, SAMPLE_RULE, "BLOCKER");
+
+ RulesPage page = tester.openBrowser().openRules();
+ page.shouldHaveDisabledFacet("activationSeverities").shouldDisplayRules(SAMPLE_RULE, SAMPLE_RULE2);
+
+ page.openFacet("profile").selectFacetItem("profile", profile.getKey());
+ page.shouldNotHaveDisabledFacet("activationSeverities").openFacet("activationSeverities");
+
+ page.selectFacetItem("activationSeverities", "BLOCKER");
+ page.shouldDisplayRules(SAMPLE_RULE).shouldNotDisplayRules(SAMPLE_RULE2);
+ assertThat(url()).contains("active_severities=BLOCKER");
+ }
+
+ @Test
+ public void should_clear_all_filters() {
+ RulesPage page = tester.openBrowser().openRules()
+ .shouldDisplayRules(SAMPLE_RULE, SAMPLE_RULE_XOO2, SAMPLE_RULE2);
+
+ page.search("branches")
+ .shouldNotDisplayRules(SAMPLE_RULE);
+
+ page.selectFacetItem("languages", XOO_LANG)
+ .shouldNotDisplayRules(SAMPLE_RULE_XOO2);
+
+ page.clearAllFilters()
+ .shouldDisplayRules(SAMPLE_RULE, SAMPLE_RULE_XOO2, SAMPLE_RULE2);
+ }
+
+ @Test
+ public void should_load_more_rules() {
+ // TODO
+ }
+
+ @Test
+ public void should_filter_similar_rules() {
+ RulesPage page = tester.openBrowser().openRules();
+ page.shouldDisplayRules(SAMPLE_RULE, SAMPLE_SECURITY_RULE);
+ page.takeRule(SAMPLE_SECURITY_RULE).filterSimilarRules("type");
+ page.shouldNotDisplayRules(SAMPLE_RULE).shouldDisplayRules(SAMPLE_SECURITY_RULE);
+ assertThat(url()).contains("types=VULNERABILITY");
+ }
+
+ @Test
+ public void should_display_rule_details() {
+ RulesPage page = tester.openBrowser().openRules();
+ RuleDetails ruleDetails = page.takeRule(SAMPLE_RULE).open();
+ ruleDetails
+ .shouldHaveType("Code Smell")
+ .shouldHaveSeverity("Major")
+ .shouldHaveDescription("Search for a given tag in Xoo files")
+ .tags().shouldHaveNoTags();
+ }
+
@Test
- public void should_display_rule_profiles() {
+ public void should_display_rule_issues() {
+ analyzeProjectWithIssues();
+
RulesPage page = tester.openBrowser().openRules();
- page.openFacet("qprofile").selectFacetItemByText("qprofile", "Basic").shouldHaveTotalRules(1);
- page.openFirstRule().shouldBeActivatedOn("Basic");
+ page.selectFacetItem("languages", XOO_LANG);
+ page.takeRule("xoo:OneIssuePerLine").open()
+ .shouldHaveTotalIssues(17).shouldHaveIssuesOnProject("Sample", 17);
+ }
+
+ @Test
+ public void should_extend_rule_description() {
+ RuleDetails ruleDetails = openRulesAsAdmin().openFirstRule();
+ ruleDetails.extendDescription().cancel();
+ ruleDetails.extendDescription().type("my extended description").submit();
+ ruleDetails.extendDescription().type("another description").submit();
+ ruleDetails.extendDescription().remove();
+ }
+
+ @Test
+ public void should_change_rule_tags() {
+ RuleDetails ruleDetails = openRulesAsAdmin().takeRule(SAMPLE_RULE2).open();
+ ruleDetails.tags()
+ .shouldHaveTags("bad-practice")
+ .edit().search("foo").select("+ foo").done()
+ .edit().select("convention").done()
+ .shouldHaveTags("bad-practice", "convention", "foo");
+ }
+
+ @Test
+ public void should_create_edit_delete_reactivate_custom_rule() {
+ String customRuleName = "custom_rule_name";
+ String customRuleKey = "xoo:" + customRuleName;
+ RuleDetails ruleDetails = openRulesAsAdmin().takeRule(SAMPLE_TEMPLATE_RULE).open();
+
+ ruleDetails.createCustomRule(customRuleName).shouldHaveCustomRule(customRuleKey);
+ ruleDetails.deleteCustomRule(customRuleKey).shouldNotHaveCustomRule(customRuleKey);
+ ruleDetails.reactivateCustomRule(customRuleName).shouldHaveCustomRule(customRuleKey);
+ }
+
+ @Test
+ public void should_activate_deactivate_rule_from_list() {
+ QualityProfile profile = createQualityProfile();
+
+ RulesPage page = openRulesAsAdmin();
+ page.openFacet("profile").selectFacetItem("profile", profile.getKey()).selectInactive();
+ page.shouldDisplayRules(SAMPLE_SECURITY_RULE);
+
+ page.activateRule(SAMPLE_SECURITY_RULE).deactivateRule(SAMPLE_SECURITY_RULE);
+ }
+
+ @Test
+ public void should_activate_rule_from_details() {
+ // make sure we have at least two quality profiles, so we can choose one of them in the select
+ createQualityProfile();
+ QualityProfile profile = createQualityProfile();
+
+ RulesPage page = openRulesAsAdmin();
+ RuleDetails ruleDetails = page.takeRule(SAMPLE_SECURITY_RULE).open();
+ ruleDetails.shouldNotBeActivatedOn(profile.getName()).activate().select(profile.getName()).save();
+ ruleDetails.shouldBeActivatedOn(profile.getKey());
+ }
+
+ @Test
+ public void should_synchronize_activation_between_list_and_details() {
+ // make sure we have at least two quality profiles, so we can choose one of them in the select
+ createQualityProfile();
+ QualityProfile profile = createQualityProfile();
+ RulesPage page = openRulesAsAdmin();
+
+ page.openFacet("profile").selectFacetItem("profile", profile.getKey()).selectInactive();
+ page.shouldDisplayRules(SAMPLE_SECURITY_RULE);
+
+ RuleDetails ruleDetails = page.takeRule(SAMPLE_SECURITY_RULE).open();
+
+ ruleDetails.shouldNotBeActivatedOn(profile.getKey())
+ .activate().select(profile.getKey()).save();
+
+ ruleDetails.shouldBeActivatedOn(profile.getKey());
+
+ page.closeDetails();
+ page.takeRule(SAMPLE_SECURITY_RULE).shouldDisplayDeactivate();
+ }
+
+ @Test
+ public void should_change_rule_activation() {
+ String ruleWithParameters = "xoo:RuleWithParameters";
+ // make sure we have at least two quality profiles, so we can choose one of them in the select
+ createQualityProfile();
+ QualityProfile profile = createQualityProfile();
+ RuleDetails ruleDetails = openRulesAsAdmin().takeRule(ruleWithParameters).open();
+
+ ruleDetails.activate().select(profile.getKey()).save();
+ ruleDetails.shouldBeActivatedOn(profile.getKey());
+
+ ruleDetails.changeActivationOn(profile.getKey())
+ .fill("string", "foo").fill("integer", "123").save();
+
+ ruleDetails
+ .activationShouldHaveParameter(profile.getKey(),"string", "foo")
+ .activationShouldHaveParameter(profile.getKey(),"integer", "123");
+ }
+
+ @Test
+ public void should_revert_rule_activation_to_parent_definition() {
+ QualityProfile profile = createInheritedQualityProfile();
+
+ RuleDetails ruleDetails = openRulesAsAdmin().takeRule(SAMPLE_RULE).open();
+ ruleDetails.activationShouldHaveSeverity(profile.getKey(), "BLOCKER");
+ ruleDetails.revertActivationToParentDefinition(profile.getKey());
+ ruleDetails.activationShouldHaveSeverity(profile.getKey(), "MAJOR");
}
+
+ private RulesPage openRulesAsAdmin() {
+ String admin = tester.users().generateAdministrator().getLogin();
+ return tester.openBrowser().logIn().submitCredentials(admin).openRules();
+ }
+
+ private String randomQualityProfileName() {
+ return "random_profile_" + String.valueOf(profileIndex++);
+ }
+
+ private QualityProfile createQualityProfile() {
+ return tester.qProfiles().service().create(new CreateRequest().setName(randomQualityProfileName()).setLanguage("xoo")).getProfile();
+ }
+
+ private QualityProfile createInheritedQualityProfile() {
+ QualityProfile profile = createQualityProfile();
+ tester.qProfiles().service().changeParent(new ChangeParentRequest().setQualityProfile(profile.getName()).setLanguage("xoo").setParentQualityProfile("Sonar way"));
+ // activate a rule
+ tester.qProfiles().activateRule(profile, SAMPLE_RULE2);
+ // override a rule
+ tester.qProfiles().activateRule(profile, SAMPLE_RULE, "BLOCKER");
+ return profile;
+ }
+
+ private void analyzeProjectWithIssues() {
+ String qualityProfileKey = projectAnalysisRule.registerProfile("/issue/IssueActionTest/xoo-one-issue-per-line-profile.xml");
+ String projectKey = projectAnalysisRule.registerProject("shared/xoo-sample");
+ projectAnalysisRule.newProjectAnalysis(projectKey).withQualityProfile(qualityProfileKey).run();
+ }
+
+ private void createBlockerCustomRule(String ruleKey) {
+ tester.wsClient().rules().create(new org.sonarqube.ws.client.rules.CreateRequest()
+ .setTemplateKey(SAMPLE_TEMPLATE_RULE)
+ .setSeverity("BLOCKER")
+ .setMarkdownDescription(ruleKey).setName(ruleKey).setCustomKey(ruleKey));
+ }
+
+ private void deleteCustomRule(String ruleKey) {
+ tester.wsClient().rules().delete(new org.sonarqube.ws.client.rules.DeleteRequest().setKey(ruleKey));
+ }
+
}
</tr>
<tr>
<td>assertElementPresent</td>
- <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-rules a[href^="/organizations/test-org/rules#qprofile"]</td>
+ <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-rules a[href^="/organizations/test-org/rules?activation=true&qprofile"]</td>
<td></td>
</tr>
<tr>
</tr>
<tr>
<td>assertElementPresent</td>
- <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-rules a[href^="/coding_rules#qprofile"]</td>
+ <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-rules a[href^="/coding_rules?"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertElementPresent</td>
+ <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-rules a[href*="qprofile="]</td>
<td></td>
</tr>
<tr>