Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

Conditions.tsx 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 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 {
  21. ButtonSecondary,
  22. FlagMessage,
  23. HeadingDark,
  24. HelperHintIcon,
  25. LightPrimary,
  26. Link,
  27. Note,
  28. SubHeading,
  29. } from 'design-system';
  30. import { differenceWith, map, uniqBy } from 'lodash';
  31. import * as React from 'react';
  32. import { FormattedMessage } from 'react-intl';
  33. import withAvailableFeatures, {
  34. WithAvailableFeaturesProps,
  35. } from '../../../app/components/available-features/withAvailableFeatures';
  36. import withMetricsContext from '../../../app/components/metrics/withMetricsContext';
  37. import DocumentationTooltip from '../../../components/common/DocumentationTooltip';
  38. import ModalButton, { ModalProps } from '../../../components/controls/ModalButton';
  39. import { useDocUrl } from '../../../helpers/docs';
  40. import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
  41. import { Feature } from '../../../types/features';
  42. import { MetricKey } from '../../../types/metrics';
  43. import {
  44. CaycStatus,
  45. Condition as ConditionType,
  46. Dict,
  47. Metric,
  48. QualityGate,
  49. } from '../../../types/types';
  50. import { groupAndSortByPriorityConditions, isQualityGateOptimized } from '../utils';
  51. import CaYCConditionsSimplificationGuide from './CaYCConditionsSimplificationGuide';
  52. import CaycCompliantBanner from './CaycCompliantBanner';
  53. import CaycConditionsTable from './CaycConditionsTable';
  54. import CaycFixOptimizeBanner from './CaycFixOptimizeBanner';
  55. import ConditionModal from './ConditionModal';
  56. import CaycReviewUpdateConditionsModal from './ConditionReviewAndUpdateModal';
  57. import ConditionsTable from './ConditionsTable';
  58. interface Props extends WithAvailableFeaturesProps {
  59. metrics: Dict<Metric>;
  60. onAddCondition: (condition: ConditionType) => void;
  61. onRemoveCondition: (Condition: ConditionType) => void;
  62. onSaveCondition: (newCondition: ConditionType, oldCondition: ConditionType) => void;
  63. qualityGate: QualityGate;
  64. updatedConditionId?: string;
  65. }
  66. const FORBIDDEN_METRIC_TYPES = ['DATA', 'DISTRIB', 'STRING', 'BOOL'];
  67. const FORBIDDEN_METRICS: string[] = [
  68. MetricKey.alert_status,
  69. MetricKey.releasability_rating,
  70. MetricKey.security_hotspots,
  71. MetricKey.new_security_hotspots,
  72. ];
  73. function Conditions({
  74. qualityGate,
  75. metrics,
  76. onRemoveCondition,
  77. onSaveCondition,
  78. onAddCondition,
  79. hasFeature,
  80. updatedConditionId,
  81. }: Readonly<Props>) {
  82. const [editing, setEditing] = React.useState<boolean>(
  83. qualityGate.caycStatus === CaycStatus.NonCompliant,
  84. );
  85. const { name } = qualityGate;
  86. const canEdit = Boolean(qualityGate.actions?.manageConditions);
  87. const { conditions = [] } = qualityGate;
  88. const existingConditions = conditions.filter((condition) => metrics[condition.metric]);
  89. const { overallCodeConditions, newCodeConditions, caycConditions } =
  90. groupAndSortByPriorityConditions(existingConditions, metrics, qualityGate.isBuiltIn);
  91. const duplicates: ConditionType[] = [];
  92. const savedConditions = existingConditions.filter((condition) => condition.id != null);
  93. savedConditions.forEach((condition) => {
  94. const sameCount = savedConditions.filter((sample) => sample.metric === condition.metric).length;
  95. if (sameCount > 1) {
  96. duplicates.push(condition);
  97. }
  98. });
  99. const uniqDuplicates = uniqBy(duplicates, (d) => d.metric).map((condition) => ({
  100. ...condition,
  101. metric: metrics[condition.metric],
  102. }));
  103. // set edit only when the name is change
  104. // i.e when user changes the quality gate
  105. React.useEffect(() => {
  106. setEditing(qualityGate.caycStatus === CaycStatus.NonCompliant);
  107. }, [name]); // eslint-disable-line react-hooks/exhaustive-deps
  108. const renderConditionModal = React.useCallback(
  109. ({ onClose }: ModalProps) => {
  110. const { conditions = [] } = qualityGate;
  111. const availableMetrics = differenceWith(
  112. map(metrics, (metric) => metric).filter(
  113. (metric) =>
  114. !metric.hidden &&
  115. !FORBIDDEN_METRIC_TYPES.includes(metric.type) &&
  116. !FORBIDDEN_METRICS.includes(metric.key),
  117. ),
  118. conditions,
  119. (metric, condition) => metric.key === condition.metric,
  120. );
  121. return (
  122. <ConditionModal
  123. header={translate('quality_gates.add_condition')}
  124. metrics={availableMetrics}
  125. onAddCondition={onAddCondition}
  126. onClose={onClose}
  127. qualityGate={qualityGate}
  128. />
  129. );
  130. },
  131. [metrics, qualityGate, onAddCondition],
  132. );
  133. const getDocUrl = useDocUrl();
  134. const isCompliantCustomQualityGate =
  135. qualityGate.caycStatus !== CaycStatus.NonCompliant && !qualityGate.isBuiltIn;
  136. const isOptimizing = isCompliantCustomQualityGate && !isQualityGateOptimized(qualityGate);
  137. const renderCaycModal = React.useCallback(
  138. ({ onClose }: ModalProps) => {
  139. const { conditions = [] } = qualityGate;
  140. const canEdit = Boolean(qualityGate.actions?.manageConditions);
  141. return (
  142. <CaycReviewUpdateConditionsModal
  143. qualityGate={qualityGate}
  144. metrics={metrics}
  145. canEdit={canEdit}
  146. onRemoveCondition={onRemoveCondition}
  147. onSaveCondition={onSaveCondition}
  148. onAddCondition={onAddCondition}
  149. lockEditing={() => setEditing(false)}
  150. updatedConditionId={updatedConditionId}
  151. conditions={conditions}
  152. scope="new-cayc"
  153. onClose={onClose}
  154. isOptimizing={isOptimizing}
  155. />
  156. );
  157. },
  158. [
  159. qualityGate,
  160. metrics,
  161. updatedConditionId,
  162. onAddCondition,
  163. onRemoveCondition,
  164. onSaveCondition,
  165. isOptimizing,
  166. ],
  167. );
  168. return (
  169. <div>
  170. <CaYCConditionsSimplificationGuide />
  171. {isCompliantCustomQualityGate && !isOptimizing && <CaycCompliantBanner />}
  172. {isCompliantCustomQualityGate && isOptimizing && canEdit && (
  173. <CaycFixOptimizeBanner renderCaycModal={renderCaycModal} isOptimizing />
  174. )}
  175. {qualityGate.caycStatus === CaycStatus.NonCompliant && canEdit && (
  176. <CaycFixOptimizeBanner renderCaycModal={renderCaycModal} />
  177. )}
  178. <header className="sw-flex sw-items-center sw-mb-4 sw-justify-between">
  179. <div className="sw-flex">
  180. <HeadingDark className="sw-body-md-highlight sw-m-0">
  181. {translate('quality_gates.conditions')}
  182. </HeadingDark>
  183. {!qualityGate.isBuiltIn && (
  184. <DocumentationTooltip
  185. className="sw-ml-2"
  186. content={translate('quality_gates.conditions.help')}
  187. links={[
  188. {
  189. href: '/user-guide/clean-as-you-code/',
  190. label: translate('quality_gates.conditions.help.link'),
  191. },
  192. ]}
  193. >
  194. <HelperHintIcon />
  195. </DocumentationTooltip>
  196. )}
  197. </div>
  198. <div>
  199. {(qualityGate.caycStatus === CaycStatus.NonCompliant || editing) && canEdit && (
  200. <ModalButton modal={renderConditionModal}>
  201. {({ onClick }) => (
  202. <ButtonSecondary data-test="quality-gates__add-condition" onClick={onClick}>
  203. {translate('quality_gates.add_condition')}
  204. </ButtonSecondary>
  205. )}
  206. </ModalButton>
  207. )}
  208. </div>
  209. </header>
  210. {uniqDuplicates.length > 0 && (
  211. <FlagMessage variant="warning" className="sw-flex sw-mb-4">
  212. <p>
  213. <p>{translate('quality_gates.duplicated_conditions')}</p>
  214. <ul className="sw-my-2 sw-list-disc sw-pl-10">
  215. {uniqDuplicates.map((d) => (
  216. <li key={d.metric.key}>{getLocalizedMetricName(d.metric)}</li>
  217. ))}
  218. </ul>
  219. </p>
  220. </FlagMessage>
  221. )}
  222. <div className="sw-flex sw-flex-col sw-gap-8">
  223. {caycConditions.length > 0 && (
  224. <div>
  225. <div className="sw-flex sw-items-center sw-gap-2 sw-mb-2">
  226. <HeadingDark as="h3">{translate('quality_gates.conditions.cayc')}</HeadingDark>
  227. <DocumentationTooltip
  228. content={translate('quality_gates.conditions.cayc.hint')}
  229. placement="right"
  230. >
  231. <HelperHintIcon />
  232. </DocumentationTooltip>
  233. </div>
  234. <CaycConditionsTable metrics={metrics} conditions={caycConditions} />
  235. {hasFeature(Feature.BranchSupport) && (
  236. <Note className="sw-mb-2 sw-body-sm">
  237. {translate('quality_gates.conditions.cayc', 'description')}
  238. </Note>
  239. )}
  240. </div>
  241. )}
  242. {newCodeConditions.length > 0 && (
  243. <div>
  244. <HeadingDark as="h3" className="sw-mb-2">
  245. {translate('quality_gates.conditions.new_code', 'long')}
  246. </HeadingDark>
  247. <ConditionsTable
  248. qualityGate={qualityGate}
  249. metrics={metrics}
  250. canEdit={canEdit}
  251. onRemoveCondition={onRemoveCondition}
  252. onSaveCondition={onSaveCondition}
  253. updatedConditionId={updatedConditionId}
  254. conditions={newCodeConditions}
  255. showEdit={editing}
  256. scope="new"
  257. />
  258. {hasFeature(Feature.BranchSupport) && (
  259. <Note className="sw-mb-2 sw-body-sm">
  260. {translate('quality_gates.conditions.new_code', 'description')}
  261. </Note>
  262. )}
  263. </div>
  264. )}
  265. {overallCodeConditions.length > 0 && (
  266. <div className="sw-mt-5">
  267. <HeadingDark as="h3" className="sw-mb-2">
  268. {translate('quality_gates.conditions.overall_code', 'long')}
  269. </HeadingDark>
  270. <ConditionsTable
  271. qualityGate={qualityGate}
  272. metrics={metrics}
  273. canEdit={canEdit}
  274. onRemoveCondition={onRemoveCondition}
  275. onSaveCondition={onSaveCondition}
  276. updatedConditionId={updatedConditionId}
  277. conditions={overallCodeConditions}
  278. scope="overall"
  279. />
  280. {hasFeature(Feature.BranchSupport) && (
  281. <Note className="sw-mb-2 sw-body-sm">
  282. {translate('quality_gates.conditions.overall_code', 'description')}
  283. </Note>
  284. )}
  285. </div>
  286. )}
  287. </div>
  288. {qualityGate.caycStatus !== CaycStatus.NonCompliant && !editing && canEdit && (
  289. <div className="sw-mt-4 it__qg-unfollow-cayc">
  290. <SubHeading as="p" className="sw-mb-2 sw-body-sm">
  291. <FormattedMessage
  292. id="quality_gates.cayc_unfollow.description"
  293. defaultMessage={translate('quality_gates.cayc_unfollow.description')}
  294. values={{
  295. cayc_link: (
  296. <Link to={getDocUrl('/user-guide/clean-as-you-code/')}>
  297. {translate('quality_gates.cayc')}
  298. </Link>
  299. ),
  300. }}
  301. />
  302. </SubHeading>
  303. <ButtonSecondary className="sw-mt-2" onClick={() => setEditing(true)}>
  304. {translate('quality_gates.cayc.unlock_edit')}
  305. </ButtonSecondary>
  306. </div>
  307. )}
  308. {existingConditions.length === 0 && (
  309. <div className="sw-mt-4 sw-body-sm">
  310. <LightPrimary as="p">{translate('quality_gates.no_conditions')}</LightPrimary>
  311. </div>
  312. )}
  313. </div>
  314. );
  315. }
  316. export default withMetricsContext(withAvailableFeatures(Conditions));