diff options
author | stanislavh <stanislav.honcharov@sonarsource.com> | 2023-12-06 11:31:21 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-12-08 20:03:05 +0000 |
commit | 5ed270ff91f07132ac004af022b0d4b5b441fcf7 (patch) | |
tree | a6225e4c5f9e28f09f91d6a7b24406975c69369e /server | |
parent | 3c3ee9dbc3e54a3b1920e1db3006174b205dee91 (diff) | |
download | sonarqube-5ed270ff91f07132ac004af022b0d4b5b441fcf7.tar.gz sonarqube-5ed270ff91f07132ac004af022b0d4b5b441fcf7.zip |
SONAR-21131 Use api/v2 for custom rule creation
Diffstat (limited to 'server')
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; |