Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

RuleDetailsProfiles.tsx 9.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import styled from '@emotion/styled';
  21. import {
  22. ActionCell,
  23. CellComponent,
  24. ContentCell,
  25. DangerButtonSecondary,
  26. DiscreetLink,
  27. InheritanceIcon,
  28. Link,
  29. Note,
  30. SubTitle,
  31. Table,
  32. TableRowInteractive,
  33. } from 'design-system';
  34. import { filter } from 'lodash';
  35. import * as React from 'react';
  36. import { FormattedMessage } from 'react-intl';
  37. import { Profile } from '../../../api/quality-profiles';
  38. import ConfirmButton from '../../../components/controls/ConfirmButton';
  39. import { translate, translateWithParameters } from '../../../helpers/l10n';
  40. import { getQualityProfileUrl } from '../../../helpers/urls';
  41. import {
  42. useActivateRuleMutation,
  43. useDeactivateRuleMutation,
  44. } from '../../../queries/quality-profiles';
  45. import { Dict, RuleActivation, RuleDetails } from '../../../types/types';
  46. import BuiltInQualityProfileBadge from '../../quality-profiles/components/BuiltInQualityProfileBadge';
  47. import ActivationButton from './ActivationButton';
  48. interface Props {
  49. activations: RuleActivation[] | undefined;
  50. canDeactivateInherited?: boolean;
  51. onActivate: () => void;
  52. onDeactivate: () => void;
  53. referencedProfiles: Dict<Profile>;
  54. ruleDetails: RuleDetails;
  55. }
  56. const COLUMN_COUNT_WITH_PARAMS = 3;
  57. const COLUMN_COUNT_WITHOUT_PARAMS = 2;
  58. const PROFILES_HEADING_ID = 'rule-details-profiles-heading';
  59. export default function RuleDetailsProfiles(props: Readonly<Props>) {
  60. const { activations = [], referencedProfiles, ruleDetails, canDeactivateInherited } = props;
  61. const { mutate: activateRule } = useActivateRuleMutation(props.onActivate);
  62. const { mutate: deactivateRule } = useDeactivateRuleMutation(props.onDeactivate);
  63. const canActivate = Object.values(referencedProfiles).some((profile) =>
  64. Boolean(profile.actions?.edit && profile.language === ruleDetails.lang),
  65. );
  66. const handleDeactivate = (key?: string) => {
  67. if (key !== undefined) {
  68. deactivateRule({
  69. key,
  70. rule: ruleDetails.key,
  71. });
  72. }
  73. };
  74. const handleRevert = (key?: string) => {
  75. if (key !== undefined) {
  76. activateRule({
  77. key,
  78. rule: ruleDetails.key,
  79. reset: true,
  80. });
  81. }
  82. };
  83. const renderRowActions = (activation: RuleActivation, profile: Profile) => {
  84. const canEdit = profile.actions?.edit && !profile.isBuiltIn;
  85. const hasParent = activation.inherit !== 'NONE' && profile.parentKey;
  86. return (
  87. <ActionCell>
  88. {canEdit && (
  89. <>
  90. {!ruleDetails.isTemplate && (
  91. <ActivationButton
  92. activation={activation}
  93. ariaLabel={translateWithParameters('coding_rules.change_details_x', profile.name)}
  94. buttonText={translate('change_verb')}
  95. modalHeader={translate('coding_rules.change_details')}
  96. onDone={props.onActivate}
  97. profiles={[profile]}
  98. rule={ruleDetails}
  99. />
  100. )}
  101. {hasParent && activation.inherit === 'OVERRIDES' && profile.parentName && (
  102. <ConfirmButton
  103. confirmButtonText={translate('yes')}
  104. confirmData={profile.key}
  105. isDestructive
  106. modalBody={translateWithParameters(
  107. 'coding_rules.revert_to_parent_definition.confirm',
  108. profile.parentName,
  109. )}
  110. modalHeader={translate('coding_rules.revert_to_parent_definition')}
  111. onConfirm={handleRevert}
  112. >
  113. {({ onClick }) => (
  114. <DangerButtonSecondary className="sw-ml-2 sw-whitespace-nowrap" onClick={onClick}>
  115. {translate('coding_rules.revert_to_parent_definition')}
  116. </DangerButtonSecondary>
  117. )}
  118. </ConfirmButton>
  119. )}
  120. {(!hasParent || canDeactivateInherited) && (
  121. <ConfirmButton
  122. confirmButtonText={translate('yes')}
  123. confirmData={profile.key}
  124. modalBody={translate('coding_rules.deactivate.confirm')}
  125. modalHeader={translate('coding_rules.deactivate')}
  126. onConfirm={handleDeactivate}
  127. >
  128. {({ onClick }) => (
  129. <DangerButtonSecondary
  130. className="sw-ml-2 sw-whitespace-nowrap"
  131. aria-label={translateWithParameters(
  132. 'coding_rules.deactivate_in_quality_profile_x',
  133. profile.name,
  134. )}
  135. onClick={onClick}
  136. >
  137. {translate('coding_rules.deactivate')}
  138. </DangerButtonSecondary>
  139. )}
  140. </ConfirmButton>
  141. )}
  142. </>
  143. )}
  144. </ActionCell>
  145. );
  146. };
  147. const renderActivationRow = (activation: RuleActivation) => {
  148. const profile = referencedProfiles[activation.qProfile];
  149. if (!profile) {
  150. return null;
  151. }
  152. const parentActivation = activations.find((x) => x.qProfile === profile.parentKey);
  153. const inheritedProfileSection = profile.parentName
  154. ? (activation.inherit === 'OVERRIDES' || activation.inherit === 'INHERITED') && (
  155. <Note as="div" className="sw-flex sw-items-center sw-w-full">
  156. <InheritanceIcon
  157. fill={activation.inherit === 'OVERRIDES' ? 'destructiveIconFocus' : 'currentColor'}
  158. />
  159. <DiscreetLink
  160. aria-label={`${translate('quality_profiles.parent')} ${profile.parentName}`}
  161. className="sw-ml-1 sw-truncate"
  162. title={profile.parentName}
  163. to={getQualityProfileUrl(profile.parentName, profile.language)}
  164. >
  165. {profile.parentName}
  166. </DiscreetLink>
  167. </Note>
  168. )
  169. : null;
  170. return (
  171. <TableRowInteractive key={profile.key}>
  172. <ContentCell className="sw-flex sw-flex-col sw-gap-2 sw-w-64">
  173. <div
  174. className="sw-truncate sw-w-full"
  175. title={`${profile.name}${
  176. profile.isBuiltIn ? ` (${translate('quality_profiles.built_in')})` : ''
  177. }`}
  178. >
  179. <Link
  180. aria-label={profile.name}
  181. to={getQualityProfileUrl(profile.name, profile.language)}
  182. >
  183. {profile.name}
  184. </Link>
  185. {profile.isBuiltIn && <BuiltInQualityProfileBadge className="sw-ml-2" />}
  186. </div>
  187. {inheritedProfileSection}
  188. </ContentCell>
  189. {!ruleDetails.templateKey && (
  190. <CellComponent>
  191. {activation.params.map((param: { key: string; value: string }) => {
  192. const originalParam = parentActivation?.params.find((p) => p.key === param.key);
  193. const originalValue = originalParam?.value;
  194. return (
  195. <StyledParameter className="sw-my-4" key={param.key}>
  196. <span className="key">{param.key}</span>
  197. <span className="sep sw-mr-1">: </span>
  198. <span className="value" title={param.value}>
  199. {param.value}
  200. </span>
  201. {parentActivation && param.value !== originalValue && (
  202. <div className="sw-flex sw-ml-4">
  203. {translate('coding_rules.original')}
  204. <span className="value sw-ml-1" title={originalValue}>
  205. {originalValue}
  206. </span>
  207. </div>
  208. )}
  209. </StyledParameter>
  210. );
  211. })}
  212. </CellComponent>
  213. )}
  214. {renderRowActions(activation, profile)}
  215. </TableRowInteractive>
  216. );
  217. };
  218. return (
  219. <div className="js-rule-profiles sw-mb-8">
  220. <SubTitle id={PROFILES_HEADING_ID}>
  221. <FormattedMessage id="coding_rules.quality_profiles" />
  222. </SubTitle>
  223. {canActivate && (
  224. <ActivationButton
  225. buttonText={translate('coding_rules.activate')}
  226. className="sw-mt-6"
  227. modalHeader={translate('coding_rules.activate_in_quality_profile')}
  228. onDone={props.onActivate}
  229. profiles={filter(
  230. referencedProfiles,
  231. (profile) => !activations.find((activation) => activation.qProfile === profile.key),
  232. )}
  233. rule={ruleDetails}
  234. />
  235. )}
  236. {activations.length > 0 && (
  237. <Table
  238. aria-labelledby={PROFILES_HEADING_ID}
  239. className="sw-my-6"
  240. columnCount={
  241. ruleDetails.templateKey ? COLUMN_COUNT_WITHOUT_PARAMS : COLUMN_COUNT_WITH_PARAMS
  242. }
  243. id="coding-rules-detail-quality-profiles"
  244. >
  245. {activations.map(renderActivationRow)}
  246. </Table>
  247. )}
  248. </div>
  249. );
  250. }
  251. const StyledParameter = styled.div`
  252. display: flex;
  253. align-items: center;
  254. flex-wrap: wrap;
  255. .value {
  256. display: inline-block;
  257. max-width: 300px;
  258. white-space: nowrap;
  259. overflow: hidden;
  260. text-overflow: ellipsis;
  261. }
  262. `;