aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorstanislavh <stanislav.honcharov@sonarsource.com>2023-12-06 11:31:21 +0100
committersonartech <sonartech@sonarsource.com>2023-12-08 20:03:05 +0000
commit5ed270ff91f07132ac004af022b0d4b5b441fcf7 (patch)
treea6225e4c5f9e28f09f91d6a7b24406975c69369e /server
parent3c3ee9dbc3e54a3b1920e1db3006174b205dee91 (diff)
downloadsonarqube-5ed270ff91f07132ac004af022b0d4b5b441fcf7.tar.gz
sonarqube-5ed270ff91f07132ac004af022b0d4b5b441fcf7.zip
SONAR-21131 Use api/v2 for custom rule creation
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts51
-rw-r--r--server/sonar-web/src/main/js/api/rules.ts39
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/utils.ts51
-rw-r--r--server/sonar-web/src/main/js/helpers/testMocks.ts18
-rw-r--r--server/sonar-web/src/main/js/queries/rules.ts6
-rw-r--r--server/sonar-web/src/main/js/types/types.ts45
7 files changed, 167 insertions, 52 deletions
diff --git a/server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts
index d0c9612a95a..aa3c90bc886 100644
--- a/server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts
+++ b/server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts
@@ -20,12 +20,13 @@
import { HttpStatusCode } from 'axios';
import { cloneDeep, countBy, pick, trim } from 'lodash';
import { RuleDescriptionSections } from '../../apps/coding-rules/rule';
+import { mapRestRuleToRule } from '../../apps/coding-rules/utils';
import { getStandards } from '../../helpers/security-standard';
import {
mockCurrentUser,
mockPaging,
+ mockRestRuleDetails,
mockRuleActivation,
- mockRuleDetails,
mockRuleRepository,
} from '../../helpers/testMocks';
import { RuleRepository, SearchRulesResponse } from '../../types/coding-rules';
@@ -33,7 +34,14 @@ import { ComponentQualifier, Visibility } from '../../types/component';
import { RawIssuesResponse } from '../../types/issues';
import { RuleStatus, SearchRulesQuery } from '../../types/rules';
import { SecurityStandard } from '../../types/security';
-import { Dict, Rule, RuleActivation, RuleDetails, RulesUpdateRequest } from '../../types/types';
+import {
+ Dict,
+ Rule,
+ RuleActivation,
+ RuleDetails,
+ RuleParameter,
+ RulesUpdateRequest,
+} from '../../types/types';
import { NoticeType } from '../../types/users';
import { getComponentData } from '../components';
import { getFacet } from '../issues';
@@ -338,9 +346,9 @@ export default class CodingRulesServiceMock {
const template = this.rules.find((r) => r.key === rule.templateKey);
// Lets not convert the md to html in test.
- rule.mdDesc = data.markdown_description !== undefined ? data.markdown_description : rule.mdDesc;
+ rule.mdDesc = data.markdownDescription !== undefined ? data.markdownDescription : rule.mdDesc;
rule.htmlDesc =
- data.markdown_description !== undefined ? data.markdown_description : rule.htmlDesc;
+ data.markdownDescription !== undefined ? data.markdownDescription : rule.htmlDesc;
rule.mdNote = data.markdown_note !== undefined ? data.markdown_note : rule.mdNote;
rule.htmlNote = data.markdown_note !== undefined ? data.markdown_note : rule.htmlNote;
rule.name = data.name !== undefined ? data.name : rule.name;
@@ -372,41 +380,30 @@ export default class CodingRulesServiceMock {
};
handleCreateRule = (data: CreateRuleData) => {
- const newRule = mockRuleDetails({
+ const newRule = mockRestRuleDetails({
descriptionSections: [
{ key: RuleDescriptionSections.DEFAULT, content: data.markdownDescription },
],
- ...pick(data, ['templateKey', 'name', 'status', 'cleanCodeAttribute', 'impacts']),
- key: data.customKey,
- params:
- data.params?.split(';').map((param: string) => {
- const [key, value] = param.split('=');
- return { key, defaultValue: value, type: 'TEXT' };
- }) ?? [],
+ ...pick(data, ['templateKey', 'name', 'status', 'cleanCodeAttribute', 'impacts', 'key']),
+ parameters: data.parameters as RuleParameter[],
});
- const rulesFromTemplateWithSameKeys = this.rules.filter(
- (rule) => rule.templateKey === newRule.templateKey && rule.key === newRule.key,
+ const ruleFromTemplateWithSameKey = this.rules.find(
+ (rule) => rule.templateKey === newRule.templateKey && newRule.key.split(':')[1] === rule.key,
);
- if (rulesFromTemplateWithSameKeys.length > 0) {
- if (
- rulesFromTemplateWithSameKeys.find(
- (rule) => rule.status === RuleStatus.Removed && rule.key === newRule.key,
- )
- ) {
- return Promise.reject({
- status: HttpStatusCode.Conflict,
- errors: [{ msg: `Rule with the same was removed before` }],
- });
- }
-
+ if (ruleFromTemplateWithSameKey?.status === RuleStatus.Removed) {
+ return Promise.reject({
+ status: HttpStatusCode.Conflict,
+ errors: [{ msg: `Rule with the same was removed before` }],
+ });
+ } else if (ruleFromTemplateWithSameKey) {
return Promise.reject({
errors: [{ msg: `A rule with key ${newRule.key} already exists` }],
});
}
- this.rules.push(newRule);
+ this.rules.push(mapRestRuleToRule(newRule));
return this.reply(newRule);
};
diff --git a/server/sonar-web/src/main/js/api/rules.ts b/server/sonar-web/src/main/js/api/rules.ts
index f8cbf1c301e..ec8c67c1563 100644
--- a/server/sonar-web/src/main/js/api/rules.ts
+++ b/server/sonar-web/src/main/js/api/rules.ts
@@ -18,18 +18,25 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { throwGlobalError } from '../helpers/error';
-import { getJSON, post, postJSON } from '../helpers/request';
+import { axiosToCatch, getJSON, post, postJSON } from '../helpers/request';
import { CleanCodeAttribute, SoftwareImpact } from '../types/clean-code-taxonomy';
import { GetRulesAppResponse, SearchRulesResponse } from '../types/coding-rules';
import { SearchRulesQuery } from '../types/rules';
-import { RuleActivation, RuleDetails, RulesUpdateRequest } from '../types/types';
+import {
+ RestRuleDetails,
+ RestRuleParameter,
+ RuleActivation,
+ RuleDetails,
+ RulesUpdateRequest,
+} from '../types/types';
+
+const RULES_ENDPOINT = '/api/v2/clean-code-policy/rules';
export interface CreateRuleData {
- customKey: string;
+ key: string;
markdownDescription: string;
name: string;
- params?: string;
- preventReactivation?: boolean;
+ parameters?: Partial<RestRuleParameter>[];
status?: string;
templateKey: string;
cleanCodeAttribute: CleanCodeAttribute;
@@ -68,19 +75,15 @@ export function getRuleTags(parameters: { ps?: number; q: string }): Promise<str
return getJSON('/api/rules/tags', parameters).then((r) => r.tags, throwGlobalError);
}
-export function createRule(data: CreateRuleData): Promise<RuleDetails> {
- return postJSON('/api/rules/create', data).then(
- (r) => r.rule,
- (response) => {
- // do not show global error if the status code is 409
- // this case should be handled inside a component
- if (response && response.status === 409) {
- return Promise.reject(response);
- } else {
- return throwGlobalError(response);
- }
- },
- );
+export function createRule(data: CreateRuleData): Promise<RestRuleDetails> {
+ return axiosToCatch.post<RuleDetails>(RULES_ENDPOINT, data).catch(({ response }) => {
+ // do not show global error if the status code is 409
+ // this case should be handled inside a component
+ if (response && response.status === 409) {
+ return Promise.reject(response);
+ }
+ return throwGlobalError(response);
+ });
}
export function deleteRule(parameters: { key: string }) {
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx
index 87d3c2dd768..11741ba8897 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx
@@ -99,30 +99,31 @@ export default function CustomRuleFormModal(props: Readonly<Props>) {
.map((key) => `${key}=${csvEscape(params[key])}`)
.join(';');
const ruleData = {
- markdownDescription: description,
name,
- params: stringifiedParams,
status,
+ markdownDescription: description,
};
if (customRule) {
updateRule({
...ruleData,
+ params: stringifiedParams,
key: customRule.key,
});
} else if (reactivating) {
updateRule({
...ruleData,
+ params: stringifiedParams,
key: `${templateRule.repo}:${key}`,
});
} else {
createRule({
...ruleData,
- customKey: key,
+ key: `${templateRule.repo}:${key}`,
templateKey: templateRule.key,
- preventReactivation: true,
cleanCodeAttribute: ccAttribute,
impacts,
+ parameters: Object.entries(params).map(([key, value]) => ({ key, defaultValue: value })),
});
}
};
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/utils.ts b/server/sonar-web/src/main/js/apps/coding-rules/utils.ts
new file mode 100644
index 00000000000..e86ff4cc370
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/coding-rules/utils.ts
@@ -0,0 +1,51 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { omit } from 'lodash';
+import { RestRuleDetails, RuleDetails } from '../../types/types';
+
+export const REST_RULE_KEYS_TO_OLD_KEYS = {
+ repositoryKey: 'repo',
+ external: 'isExternal',
+ markdownDescription: 'mdDesc',
+ markdownNote: 'mdNote',
+ template: 'isTemplate',
+ systemTags: 'sysTags',
+ language: 'lang',
+ languageName: 'langName',
+};
+
+// Mapping new resource to old api. We should get rid of it with migration of rules/search & rule/details
+export function mapRestRuleToRule(rule: RestRuleDetails): RuleDetails {
+ return {
+ ...omit(rule, Object.keys(REST_RULE_KEYS_TO_OLD_KEYS)),
+ ...Object.entries(REST_RULE_KEYS_TO_OLD_KEYS).reduce(
+ (obj, [key, value]: [keyof RestRuleDetails, keyof RuleDetails]) => {
+ obj[value] = rule[key] as never;
+ return obj;
+ },
+ {} as RuleDetails,
+ ),
+ params: rule.parameters?.map((param) => ({
+ ...omit(param, 'htmlDescription'),
+ htmlDesc: param.htmlDescription,
+ })),
+ };
+}
diff --git a/server/sonar-web/src/main/js/helpers/testMocks.ts b/server/sonar-web/src/main/js/helpers/testMocks.ts
index acd56af28be..54485a4e12e 100644
--- a/server/sonar-web/src/main/js/helpers/testMocks.ts
+++ b/server/sonar-web/src/main/js/helpers/testMocks.ts
@@ -17,9 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { omit } from 'lodash';
import { To } from 'react-router-dom';
import { CompareResponse } from '../api/quality-profiles';
import { RuleDescriptionSections } from '../apps/coding-rules/rule';
+import { REST_RULE_KEYS_TO_OLD_KEYS } from '../apps/coding-rules/utils';
import { Exporter, Profile, ProfileChangelogEvent } from '../apps/quality-profiles/types';
import { LogsLevels } from '../apps/system/utils';
import { Location, Router } from '../components/hoc/withRouter';
@@ -59,6 +61,7 @@ import {
Metric,
Paging,
Period,
+ RestRuleDetails,
Rule,
RuleActivation,
RuleDetails,
@@ -660,6 +663,21 @@ export function mockRuleDetails(overrides: Partial<RuleDetails> = {}): RuleDetai
};
}
+export function mockRestRuleDetails(overrides: Partial<RestRuleDetails> = {}): RestRuleDetails {
+ const ruleDetails = mockRuleDetails(overrides);
+ return {
+ ...omit(ruleDetails, Object.values(REST_RULE_KEYS_TO_OLD_KEYS)),
+ ...Object.entries(REST_RULE_KEYS_TO_OLD_KEYS).reduce(
+ (obj, [key, value]: [keyof RestRuleDetails, keyof RuleDetails]) => {
+ obj[key] = ruleDetails[value] as never;
+ return obj;
+ },
+ {} as RestRuleDetails,
+ ),
+ ...overrides,
+ };
+}
+
export function mockRuleDetailsParameter(overrides: Partial<RuleParameter> = {}): RuleParameter {
return {
defaultValue: '1',
diff --git a/server/sonar-web/src/main/js/queries/rules.ts b/server/sonar-web/src/main/js/queries/rules.ts
index 22e03e2f257..105cddd816e 100644
--- a/server/sonar-web/src/main/js/queries/rules.ts
+++ b/server/sonar-web/src/main/js/queries/rules.ts
@@ -20,6 +20,7 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { createRule, deleteRule, getRuleDetails, searchRules, updateRule } from '../api/rules';
+import { mapRestRuleToRule } from '../apps/coding-rules/utils';
import { SearchRulesResponse } from '../types/coding-rules';
import { SearchRulesQuery } from '../types/rules';
import { RuleActivation, RuleDetails } from '../types/types';
@@ -63,11 +64,12 @@ export function useCreateRuleMutation(
mutationFn: createRule,
onError,
onSuccess: (rule) => {
- onSuccess?.(rule);
+ const mappedRule = mapRestRuleToRule(rule);
+ onSuccess?.(mappedRule);
queryClient.setQueryData<SearchRulesResponse>(
getRulesQueryKey('search', searchQuery),
(oldData) => {
- return oldData ? { ...oldData, rules: [rule, ...oldData.rules] } : undefined;
+ return oldData ? { ...oldData, rules: [mappedRule, ...oldData.rules] } : undefined;
},
);
},
diff --git a/server/sonar-web/src/main/js/types/types.ts b/server/sonar-web/src/main/js/types/types.ts
index 9a11700a27a..bac8bcf99ee 100644
--- a/server/sonar-web/src/main/js/types/types.ts
+++ b/server/sonar-web/src/main/js/types/types.ts
@@ -554,6 +554,23 @@ export interface Rule {
type: RuleType;
}
+export interface RestRule {
+ cleanCodeAttributeCategory?: CleanCodeAttributeCategory;
+ cleanCodeAttribute?: CleanCodeAttribute;
+ impacts: SoftwareImpact[];
+ template?: boolean;
+ key: string;
+ language?: string;
+ languageName?: string;
+ name: string;
+ parameters?: RestRuleParameter[];
+ severity: string;
+ status: string;
+ systemTags?: string[];
+ tags?: string[];
+ type: RuleType;
+}
+
export interface RuleActivation {
createdAt: string;
inherit: RuleInheritance;
@@ -564,7 +581,7 @@ export interface RuleActivation {
export interface RulesUpdateRequest {
key: string;
- markdown_description?: string;
+ markdownDescription?: string;
markdown_note?: string;
name?: string;
params?: string;
@@ -597,8 +614,34 @@ export interface RuleDetails extends Rule {
templateKey?: string;
}
+export interface RestRuleDetails extends RestRule {
+ createdAt: string;
+ descriptionSections?: RuleDescriptionSection[];
+ educationPrinciples?: string[];
+ gapDescription?: string;
+ htmlDesc?: string;
+ htmlNote?: string;
+ internalKey?: string;
+ external?: boolean;
+ markdownDescription?: string;
+ markdownNote?: string;
+ remFnBaseEffort?: string;
+ remFnGapMultiplier?: string;
+ remFnType?: string;
+ repositoryKey: string;
+ scope?: RuleScope;
+ templateKey?: string;
+}
+
export type RuleInheritance = 'NONE' | 'INHERITED' | 'OVERRIDES';
+export interface RestRuleParameter {
+ defaultValue?: string;
+ htmlDescription?: string;
+ key: string;
+ type: string;
+}
+
export interface RuleParameter {
defaultValue?: string;
htmlDesc?: string;