]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23196 Activity page uses combined graphs for MQR metrics
authorViktor Vorona <viktor.vorona@sonarsource.com>
Tue, 29 Oct 2024 16:40:35 +0000 (17:40 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 5 Nov 2024 20:03:02 +0000 (20:03 +0000)
15 files changed:
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.tsx
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-it.tsx
server/sonar-web/src/main/js/apps/projectActivity/utils.ts
server/sonar-web/src/main/js/components/activity-graph/AddGraphMetric.tsx
server/sonar-web/src/main/js/components/activity-graph/AddGraphMetricPopup.tsx
server/sonar-web/src/main/js/components/activity-graph/EventInner.tsx
server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx
server/sonar-web/src/main/js/components/activity-graph/GraphsLegendItem.tsx
server/sonar-web/src/main/js/helpers/activity-graph.ts
server/sonar-web/src/main/js/helpers/constants.ts
server/sonar-web/src/main/js/queries/applications.ts
server/sonar-web/src/main/js/queries/branch.tsx
server/sonar-web/src/main/js/queries/project-analyses.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 9ab33dd9f2ab59e60b23c5b3f3e78f2d67d254d9..1cb21f296509d9cfc22b990f7b16ce3a301e7145 100644 (file)
@@ -32,11 +32,11 @@ import {
   getHistoryMetrics,
   isCustomGraph,
 } from '../../../components/activity-graph/utils';
-import { mergeRatingMeasureHistory } from '../../../helpers/activity-graph';
-import { SOFTWARE_QUALITY_RATING_METRICS } from '../../../helpers/constants';
+import { mergeMeasureHistory } from '../../../helpers/activity-graph';
 import { parseDate } from '../../../helpers/dates';
-import useApplicationLeakQuery from '../../../queries/applications';
+import { useApplicationLeakQuery } from '../../../queries/applications';
 import { useCurrentBranchQuery } from '../../../queries/branch';
+import { StaleTime } from '../../../queries/common';
 import { useAllMeasuresHistoryQuery } from '../../../queries/measures';
 import { useAllProjectAnalysesQuery } from '../../../queries/project-analyses';
 import { useStandardExperienceMode } from '../../../queries/settings';
@@ -59,22 +59,27 @@ export const PROJECT_ACTIVITY_GRAPH = 'sonar_project_activity.graph';
 
 export function ProjectActivityApp() {
   const { query, pathname } = useLocation();
-  const parsedQuery = parseQuery(query);
+  const { data: isStandardMode, isLoading: isLoadingStandardMode } = useStandardExperienceMode();
+  const parsedQuery = parseQuery(query, isStandardMode);
   const router = useRouter();
   const { component } = useComponent();
   const metrics = useMetrics();
-  const { data: branchLike, isFetching: isFetchingBranch } = useCurrentBranchQuery(component);
+  const { data: branchLike, isFetching: isFetchingBranch } = useCurrentBranchQuery(
+    component,
+    StaleTime.LONG,
+  );
   const enabled =
     component?.key !== undefined &&
     (isPortfolioLike(component?.qualifier) || (Boolean(branchLike) && !isFetchingBranch));
 
-  const { data: appLeaks } = useApplicationLeakQuery(
-    component?.key ?? '',
-    isApplication(component?.qualifier),
-  );
+  const { data: appLeaks } = useApplicationLeakQuery(component?.key ?? '', {
+    enabled: isApplication(component?.qualifier),
+  });
 
-  const { data: analysesData, isLoading: isLoadingAnalyses } = useAllProjectAnalysesQuery(enabled);
-  const { data: isStandardMode, isLoading: isLoadingStandardMode } = useStandardExperienceMode();
+  const { data: analysesData, isLoading: isLoadingAnalyses } = useAllProjectAnalysesQuery({
+    enabled,
+    staleTime: StaleTime.LONG,
+  });
 
   const { data: historyData, isLoading: isLoadingHistory } = useAllMeasuresHistoryQuery(
     {
@@ -82,16 +87,14 @@ export function ProjectActivityApp() {
       branchParams: getBranchLikeQuery(branchLike),
       metrics: getHistoryMetrics(query.graph || DEFAULT_GRAPH, parsedQuery.customMetrics).join(','),
     },
-    { enabled },
+    { enabled, staleTime: StaleTime.LONG },
   );
 
   const analyses = React.useMemo(() => analysesData ?? [], [analysesData]);
 
   const measuresHistory = React.useMemo(
     () =>
-      isLoadingStandardMode
-        ? []
-        : mergeRatingMeasureHistory(historyData, parseDate, isStandardMode),
+      isLoadingStandardMode ? [] : mergeMeasureHistory(historyData, parseDate, isStandardMode),
     [historyData, isStandardMode, isLoadingStandardMode],
   );
 
@@ -122,10 +125,13 @@ export function ProjectActivityApp() {
   }, [component?.qualifier, metrics]);
 
   const handleUpdateQuery = (newQuery: Query) => {
-    const q = serializeUrlQuery({
-      ...parsedQuery,
-      ...newQuery,
-    });
+    const q = serializeUrlQuery(
+      {
+        ...parsedQuery,
+        ...newQuery,
+      },
+      isStandardMode,
+    );
 
     router.push({
       pathname,
@@ -137,20 +143,12 @@ export function ProjectActivityApp() {
     });
   };
 
-  const firstSoftwareQualityRatingMetric = historyData?.measures.find((m) =>
-    SOFTWARE_QUALITY_RATING_METRICS.includes(m.metric),
-  );
-
   return (
     component && (
       <Spinner isLoading={isLoadingStandardMode}>
         <ProjectActivityAppRenderer
           analyses={analyses}
-          isStandardMode={
-            isStandardMode ||
-            !firstSoftwareQualityRatingMetric ||
-            firstSoftwareQualityRatingMetric.history.every((h) => h.value === undefined)
-          }
+          isStandardMode={isStandardMode}
           analysesLoading={isLoadingAnalyses}
           graphLoading={isLoadingHistory}
           leakPeriodDate={leakPeriodDate}
index ebf076627bbdf22400830341c0742433c8a7a20e..10523c0eb927f2c61d8e7d26427ad0c6bf715c25 100644 (file)
@@ -35,10 +35,6 @@ import {
   splitSeriesInGraphs,
 } from '../../../components/activity-graph/utils';
 import DocumentationLink from '../../../components/common/DocumentationLink';
-import {
-  CCT_SOFTWARE_QUALITY_METRICS,
-  SOFTWARE_QUALITY_RATING_METRICS_MAP,
-} from '../../../helpers/constants';
 import { DocLink } from '../../../helpers/doc-links';
 import { translate } from '../../../helpers/l10n';
 import { MetricKey } from '../../../sonar-aligned/types/metrics';
@@ -50,6 +46,7 @@ import {
   Serie,
 } from '../../../types/project-activity';
 import { Metric } from '../../../types/types';
+import { MQR_CONDITIONS_MAP } from '../../quality-gates/utils';
 import { Query, datesQueryChanged, historyQueryChanged } from '../utils';
 import { PROJECT_ACTIVITY_GRAPH } from './ProjectActivityApp';
 
@@ -221,17 +218,11 @@ export default class ProjectActivityGraphs extends React.PureComponent<Props, St
   renderQualitiesMetricInfoMessage = () => {
     const { measuresHistory, isStandardMode } = this.props;
 
-    const qualityMeasuresHistory = measuresHistory.find((history) =>
-      CCT_SOFTWARE_QUALITY_METRICS.includes(history.metric),
-    );
-    const ratingQualityMeasuresHistory = measuresHistory.find((history) =>
-      (Object.keys(SOFTWARE_QUALITY_RATING_METRICS_MAP) as MetricKey[]).includes(history.metric),
+    const mqrMeasuresHistory = measuresHistory.find(
+      (history) => MQR_CONDITIONS_MAP[history.metric as MetricKey] !== undefined,
     );
 
-    if (
-      this.hasGaps(qualityMeasuresHistory) ||
-      (!isStandardMode && this.hasGaps(ratingQualityMeasuresHistory))
-    ) {
+    if (!isStandardMode && this.hasGaps(mqrMeasuresHistory)) {
       return (
         <FlagMessage variant="info">
           <FormattedMessage
index 63deee154e38ae3805eecd322f81515040ce0477..b74a5f34e9627d5ff65038248d3c449aecb02493 100644 (file)
@@ -253,7 +253,17 @@ describe('rendering', () => {
   it('should render graph gap info message', async () => {
     timeMachineHandler.setMeasureHistory([
       mockMeasureHistory({
-        metric: MetricKey.maintainability_issues,
+        metric: MetricKey.code_smells,
+        history: projectActivityHandler.getAnalysesList().map(({ date }) =>
+          mockHistoryItem({
+            // eslint-disable-next-line jest/no-conditional-in-test
+            value: '2',
+            date: parseDate(date),
+          }),
+        ),
+      }),
+      mockMeasureHistory({
+        metric: MetricKey.software_quality_maintainability_issues,
         history: projectActivityHandler.getAnalysesList().map(({ date }, index) =>
           mockHistoryItem({
             // eslint-disable-next-line jest/no-conditional-in-test
@@ -274,7 +284,7 @@ describe('rendering', () => {
 
     await ui.changeGraphType(GraphType.custom);
     await ui.openMetricsDropdown();
-    await ui.toggleMetric(MetricKey.maintainability_issues);
+    await ui.toggleMetric(MetricKey.software_quality_maintainability_issues);
     expect(ui.gapInfoMessage.get()).toBeInTheDocument();
   });
 
@@ -290,7 +300,7 @@ describe('rendering', () => {
 
     await ui.changeGraphType(GraphType.custom);
     await ui.openMetricsDropdown();
-    await ui.toggleMetric(MetricKey.maintainability_issues);
+    await ui.toggleMetric(MetricKey.software_quality_maintainability_issues);
     expect(ui.gapInfoMessage.query()).not.toBeInTheDocument();
   });
 });
@@ -522,7 +532,7 @@ describe('graph interactions', () => {
   });
 
   it.each([
-    ['MQR', 'true', MetricKey.maintainability_issues],
+    ['MQR', 'true', MetricKey.software_quality_maintainability_issues],
     ['Standard', 'false', MetricKey.code_smells],
   ])('should correctly handle customizing the graph in %s mode', async (_, mode, metric) => {
     settingsHandler.set(SettingsKey.MQRMode, mode);
@@ -607,7 +617,7 @@ describe('ratings', () => {
 
     await ui.changeGraphType(GraphType.custom);
     await ui.openMetricsDropdown();
-    await ui.toggleMetric(MetricKey.reliability_rating);
+    await ui.toggleMetric(MetricKey.software_quality_reliability_rating);
     await ui.closeMetricsDropdown();
 
     expect(await ui.graphs.findAll()).toHaveLength(1);
@@ -649,7 +659,7 @@ describe('ratings', () => {
 
     await ui.changeGraphType(GraphType.custom);
     await ui.openMetricsDropdown();
-    await ui.toggleMetric(MetricKey.reliability_rating);
+    await ui.toggleMetric(MetricKey.software_quality_reliability_rating);
     await ui.closeMetricsDropdown();
 
     expect(await ui.graphs.findAll()).toHaveLength(1);
@@ -682,7 +692,7 @@ describe('ratings', () => {
 
     await ui.changeGraphType(GraphType.custom);
     await ui.openMetricsDropdown();
-    await ui.toggleMetric(MetricKey.reliability_rating);
+    await ui.toggleMetric(MetricKey.software_quality_reliability_rating);
     await ui.closeMetricsDropdown();
 
     expect(await ui.graphs.findAll()).toHaveLength(1);
@@ -956,12 +966,19 @@ function renderProjectActivityAppContainer(
     {
       metrics: keyBy(
         [
-          mockMetric({ key: MetricKey.maintainability_issues, type: MetricType.Data }),
+          mockMetric({
+            key: MetricKey.software_quality_maintainability_issues,
+            type: MetricType.Integer,
+          }),
           mockMetric({ key: MetricKey.bugs, type: MetricType.Integer }),
           mockMetric({ key: MetricKey.code_smells, type: MetricType.Integer }),
           mockMetric({ key: MetricKey.security_hotspots_reviewed }),
           mockMetric({ key: MetricKey.security_review_rating, type: MetricType.Rating }),
           mockMetric({ key: MetricKey.reliability_rating, type: MetricType.Rating }),
+          mockMetric({
+            key: MetricKey.software_quality_reliability_rating,
+            type: MetricType.Rating,
+          }),
         ],
         'key',
       ),
index a93df4ef78501c058fb68dff2b0e37f12dba2a54..9ec0145af44796311dc26943f14cb2343fb2c46d 100644 (file)
@@ -23,7 +23,6 @@ import { isEqual, uniq } from 'lodash';
 import { MetricKey } from '~sonar-aligned/types/metrics';
 import { RawQuery } from '~sonar-aligned/types/router';
 import { DEFAULT_GRAPH } from '../../components/activity-graph/utils';
-import { SOFTWARE_QUALITY_RATING_METRICS_MAP } from '../../helpers/constants';
 import { parseDate } from '../../helpers/dates';
 import { MEASURES_REDIRECTION } from '../../helpers/measures';
 import {
@@ -37,6 +36,7 @@ import {
 } from '../../helpers/query';
 import { GraphType, ParsedAnalysis } from '../../types/project-activity';
 import { Dict } from '../../types/types';
+import { MQR_CONDITIONS_MAP, STANDARD_CONDITIONS_MAP } from '../quality-gates/utils';
 
 export interface Query {
   category: string;
@@ -113,21 +113,17 @@ export function getAnalysesByVersionByDay(
   }, []);
 }
 
-export function parseQuery(urlQuery: RawQuery): Query {
+export function parseQuery(urlQuery: RawQuery, isStandardMode = false): Query {
   const parsedMetrics = parseAsArray(urlQuery['custom_metrics'], parseAsString<MetricKey>);
   let customMetrics = uniq(parsedMetrics.map((metric) => MEASURES_REDIRECTION[metric] ?? metric));
 
-  const reversedMetricMap = Object.fromEntries(
-    Object.entries(SOFTWARE_QUALITY_RATING_METRICS_MAP).map(
-      ([k, v]) => [v, k] as [MetricKey, MetricKey],
+  customMetrics = uniq(
+    customMetrics.map((metric) =>
+      !isStandardMode ? (STANDARD_CONDITIONS_MAP[metric] ?? metric) : metric,
     ),
-  );
-
-  customMetrics = uniq(customMetrics.map((metric) => reversedMetricMap[metric] ?? metric))
+  )
     .map((metric) =>
-      SOFTWARE_QUALITY_RATING_METRICS_MAP[metric]
-        ? [metric, SOFTWARE_QUALITY_RATING_METRICS_MAP[metric]]
-        : metric,
+      !isStandardMode && MQR_CONDITIONS_MAP[metric] ? [metric, MQR_CONDITIONS_MAP[metric]] : metric,
     )
     .flat();
 
@@ -149,13 +145,12 @@ export function serializeQuery(query: Query): RawQuery {
   });
 }
 
-export function serializeUrlQuery(query: Query): RawQuery {
+export function serializeUrlQuery(query: Query, isStandardMode = false): RawQuery {
   return cleanQuery({
     category: serializeString(query.category),
     custom_metrics: serializeStringArray(
       query.customMetrics.filter(
-        (metric) =>
-          !Object.values(SOFTWARE_QUALITY_RATING_METRICS_MAP).includes(metric as MetricKey),
+        (metric) => isStandardMode || !STANDARD_CONDITIONS_MAP[metric as MetricKey],
       ),
     ),
     from: serializeDate(query.from),
index 1b4ef643205864186b87f2a906339cba74966555..ad331f5cfcf208097b5359acacf4958f4ec42a30 100644 (file)
@@ -23,24 +23,15 @@ import { sortBy } from 'lodash';
 import * as React from 'react';
 import { Dropdown } from '~design-system';
 import { MetricKey, MetricType } from '~sonar-aligned/types/metrics';
-import {
-  CCT_SOFTWARE_QUALITY_METRICS,
-  DEPRECATED_ACTIVITY_METRICS,
-  HIDDEN_METRICS,
-  LEAK_CCT_SOFTWARE_QUALITY_METRICS,
-  LEAK_OLD_TAXONOMY_METRICS,
-  LEAK_OLD_TAXONOMY_RATINGS,
-  OLD_TAXONOMY_METRICS,
-  SOFTWARE_QUALITY_RATING_METRICS,
-  SOFTWARE_QUALITY_RATING_METRICS_MAP,
-} from '../../helpers/constants';
+import { MQR_CONDITIONS_MAP, STANDARD_CONDITIONS_MAP } from '../../apps/quality-gates/utils';
+import { HIDDEN_METRICS } from '../../helpers/constants';
 import { getLocalizedMetricName, translate } from '../../helpers/l10n';
 import { isDiffMetric } from '../../helpers/measures';
+import { useStandardExperienceMode } from '../../queries/settings';
 import { Metric } from '../../types/types';
 import AddGraphMetricPopup from './AddGraphMetricPopup';
 
 interface Props {
-  isStandardMode?: boolean;
   metrics: Metric[];
   metricsTypeFilter?: string[];
   onAddMetric: (metric: string) => void;
@@ -48,27 +39,22 @@ interface Props {
   selectedMetrics: string[];
 }
 
-interface State {
-  metrics: string[];
-  query: string;
-  selectedMetrics: string[];
-}
+export default function AddGraphMetric(props: Readonly<Props>) {
+  const [metrics, setMetrics] = React.useState<string[]>([]);
+  const [query, setQuery] = React.useState<string>('');
+  const [selectedMetrics, setSelectedMetrics] = React.useState<string[]>([]);
 
-export default class AddGraphMetric extends React.PureComponent<Props, State> {
-  state: State = {
-    metrics: [],
-    query: '',
-    selectedMetrics: [],
-  };
+  const { data: isStandardMode } = useStandardExperienceMode();
 
-  filterSelected = (query: string, selectedElements: string[]) => {
+  const filterSelected = (query: string, selectedElements: string[]) => {
     return selectedElements.filter((element) =>
-      this.getLocalizedMetricNameFromKey(element).toLowerCase().includes(query.toLowerCase()),
+      getLocalizedMetricNameFromKey(element).toLowerCase().includes(query.toLowerCase()),
     );
   };
 
-  filterMetricsElements = (query: string) => {
-    const { metrics, selectedMetrics, metricsTypeFilter, isStandardMode } = this.props;
+  const filterMetricsElements = () => {
+    const { metricsTypeFilter, metrics, selectedMetrics } = props;
+
     return metrics
       .filter((metric) => {
         if (metric.hidden) {
@@ -77,39 +63,19 @@ export default class AddGraphMetric extends React.PureComponent<Props, State> {
         if (isDiffMetric(metric.key)) {
           return false;
         }
-        if (
-          [MetricType.Data, MetricType.Distribution].includes(metric.type as MetricType) &&
-          !CCT_SOFTWARE_QUALITY_METRICS.includes(metric.key as MetricKey)
-        ) {
+        if ([MetricType.Data, MetricType.Distribution].includes(metric.type as MetricType)) {
           return false;
         }
         if (HIDDEN_METRICS.includes(metric.key as MetricKey)) {
           return false;
         }
         if (
-          isStandardMode &&
-          [
-            ...LEAK_CCT_SOFTWARE_QUALITY_METRICS,
-            ...CCT_SOFTWARE_QUALITY_METRICS,
-            ...SOFTWARE_QUALITY_RATING_METRICS,
-          ].includes(metric.key as MetricKey)
+          isStandardMode
+            ? MQR_CONDITIONS_MAP[metric.key as MetricKey]
+            : STANDARD_CONDITIONS_MAP[metric.key as MetricKey]
         ) {
           return false;
         }
-        if (
-          !isStandardMode &&
-          [
-            ...LEAK_OLD_TAXONOMY_METRICS,
-            ...OLD_TAXONOMY_METRICS,
-            ...LEAK_OLD_TAXONOMY_RATINGS,
-            ...DEPRECATED_ACTIVITY_METRICS,
-          ].includes(metric.key as MetricKey)
-        ) {
-          return false;
-        }
-        if (Object.values(SOFTWARE_QUALITY_RATING_METRICS_MAP).includes(metric.key as MetricKey)) {
-          return false;
-        }
         if (
           selectedMetrics.includes(metric.key) ||
           !getLocalizedMetricName(metric).toLowerCase().includes(query.toLowerCase())
@@ -124,78 +90,64 @@ export default class AddGraphMetric extends React.PureComponent<Props, State> {
       .map((metric) => metric.key);
   };
 
-  getSelectedMetricsElements = (metrics: Metric[], selectedMetrics: string[]) => {
+  const getSelectedMetricsElements = (metrics: Metric[], selectedMetrics: string[]) => {
     return metrics
       .filter(
         (metric) =>
           selectedMetrics.includes(metric.key) &&
-          !Object.values(SOFTWARE_QUALITY_RATING_METRICS_MAP).includes(metric.key as MetricKey),
+          (isStandardMode || !STANDARD_CONDITIONS_MAP[metric.key as MetricKey]),
       )
       .map((metric) => metric.key);
   };
 
-  getLocalizedMetricNameFromKey = (key: string) => {
-    const metric = this.props.metrics.find((m) => m.key === key);
+  const getLocalizedMetricNameFromKey = (key: string) => {
+    const metric = props.metrics.find((m) => m.key === key);
     return metric === undefined ? key : getLocalizedMetricName(metric);
   };
 
-  onSearch = (query: string) => {
-    this.setState({ query });
+  const onSearch = (query: string) => {
+    setQuery(query);
     return Promise.resolve();
   };
 
-  onSelect = (metric: string) => {
-    this.props.onAddMetric(metric);
-    this.setState((state) => {
-      return {
-        selectedMetrics: sortBy([...state.selectedMetrics, metric]),
-        metrics: this.filterMetricsElements(state.query),
-      };
-    });
+  const onSelect = (metric: string) => {
+    props.onAddMetric(metric);
+    setSelectedMetrics(sortBy([...selectedMetrics, metric]));
+    setMetrics(filterMetricsElements());
   };
 
-  onUnselect = (metric: string) => {
-    this.props.onRemoveMetric(metric);
-    this.setState((state) => {
-      return {
-        metrics: sortBy([...state.metrics, metric]),
-        selectedMetrics: state.selectedMetrics.filter((selected) => selected !== metric),
-      };
-    });
+  const onUnselect = (metric: string) => {
+    props.onRemoveMetric(metric);
+    setSelectedMetrics(selectedMetrics.filter((selected) => selected !== metric));
+    setMetrics(sortBy(metrics, metric));
   };
 
-  render() {
-    const { query } = this.state;
-    const filteredMetrics = this.filterMetricsElements(query);
-    const selectedMetrics = this.getSelectedMetricsElements(
-      this.props.metrics,
-      this.props.selectedMetrics,
-    );
+  const filteredMetrics = filterMetricsElements();
+  const selectedElements = getSelectedMetricsElements(props.metrics, props.selectedMetrics);
 
-    return (
-      <Dropdown
-        allowResizing
-        size="large"
-        closeOnClick={false}
-        id="activity-graph-custom-metric-selector"
-        overlay={
-          <AddGraphMetricPopup
-            elements={filteredMetrics}
-            filterSelected={this.filterSelected}
-            metricsTypeFilter={this.props.metricsTypeFilter}
-            onSearch={this.onSearch}
-            onSelect={this.onSelect}
-            onUnselect={this.onUnselect}
-            selectedElements={selectedMetrics}
-          />
-        }
-      >
-        <Button suffix={<IconChevronDown />}>
-          <span className="sw-typo-default sw-flex">
-            {translate('project_activity.graphs.custom.add')}
-          </span>
-        </Button>
-      </Dropdown>
-    );
-  }
+  return (
+    <Dropdown
+      allowResizing
+      size="large"
+      closeOnClick={false}
+      id="activity-graph-custom-metric-selector"
+      overlay={
+        <AddGraphMetricPopup
+          elements={filteredMetrics}
+          filterSelected={filterSelected}
+          metricsTypeFilter={props.metricsTypeFilter}
+          onSearch={onSearch}
+          onSelect={onSelect}
+          onUnselect={onUnselect}
+          selectedElements={selectedElements}
+        />
+      }
+    >
+      <Button suffix={<IconChevronDown />}>
+        <span className="sw-typo-default sw-flex">
+          {translate('project_activity.graphs.custom.add')}
+        </span>
+      </Button>
+    </Dropdown>
+  );
 }
index 07d000626546ff1d9fc32d16b188719338837447..5cd379f1e2e74b145b8051c94eb0254135871a7e 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { ReactNode, useCallback } from 'react';
-import { FlagMessage, MultiSelectMenu } from '~design-system';
+import { ReactNode } from 'react';
+import { FormattedMessage, useIntl } from 'react-intl';
+import { Badge, FlagMessage, MultiSelectMenu } from '~design-system';
 import { MetricKey } from '~sonar-aligned/types/metrics';
+import { DEPRECATED_ACTIVITY_METRICS } from '../../helpers/constants';
 import { getLocalizedMetricName, translate, translateWithParameters } from '../../helpers/l10n';
+import { getDeprecatedTranslationKeyForTooltip } from './utils';
 
 export interface AddGraphMetricPopupProps {
   elements: string[];
@@ -39,6 +42,7 @@ export default function AddGraphMetricPopup({
   metricsTypeFilter,
   ...props
 }: Readonly<AddGraphMetricPopupProps>) {
+  const intl = useIntl();
   let footerNode: ReactNode = '';
 
   if (props.selectedElements.length >= 6) {
@@ -61,10 +65,37 @@ export default function AddGraphMetricPopup({
     );
   }
 
-  const renderLabel = useCallback((key: MetricKey) => {
-    return getLocalizedMetricName({ key });
-  }, []);
+  const renderAriaLabel = (key: MetricKey) => {
+    const metricName = getLocalizedMetricName({ key });
+    const isDeprecated = DEPRECATED_ACTIVITY_METRICS.includes(key);
 
+    return isDeprecated
+      ? `${metricName} (${intl.formatMessage({ id: 'deprecated' })})`
+      : metricName;
+  };
+
+  const renderLabel = (key: MetricKey) => {
+    const metricName = getLocalizedMetricName({ key });
+    const isDeprecated = DEPRECATED_ACTIVITY_METRICS.includes(key);
+
+    return (
+      <>
+        {metricName}
+        {isDeprecated && (
+          <Badge className="sw-ml-1">{intl.formatMessage({ id: 'deprecated' })}</Badge>
+        )}
+      </>
+    );
+  };
+
+  const renderTooltip = (key: MetricKey) => {
+    const isDeprecated = DEPRECATED_ACTIVITY_METRICS.includes(key);
+    if (isDeprecated) {
+      return <FormattedMessage id={getDeprecatedTranslationKeyForTooltip(key)} tagName="div" />;
+    }
+
+    return null;
+  };
   return (
     <MultiSelectMenu
       createElementLabel=""
@@ -79,7 +110,8 @@ export default function AddGraphMetricPopup({
       onSelect={(item: string) => elements.includes(item) && props.onSelect(item)}
       onUnselect={props.onUnselect}
       placeholder={translate('search.search_for_metrics')}
-      renderAriaLabel={renderLabel}
+      renderAriaLabel={renderAriaLabel}
+      renderTooltip={renderTooltip}
       renderLabel={renderLabel}
       selectedElements={props.selectedElements}
       listSize={0}
index 2604c4f5725a44c36b74f310d9df3f37186d06fd..050fac489ad6fa464f8ac95437c51b712fc04804 100644 (file)
@@ -23,6 +23,7 @@ import { Note } from '~design-system';
 import { ComponentContext } from '../../app/components/componentContext/ComponentContext';
 import { translate } from '../../helpers/l10n';
 import { useCurrentBranchQuery } from '../../queries/branch';
+import { StaleTime } from '../../queries/common';
 import { AnalysisEvent, ProjectAnalysisEventCategory } from '../../types/project-activity';
 import Tooltip from '../controls/Tooltip';
 import { DefinitionChangeEventInner, isDefinitionChangeEvent } from './DefinitionChangeEventInner';
@@ -40,7 +41,7 @@ export interface EventInnerProps {
 
 export default function EventInner({ event, readonly }: EventInnerProps) {
   const { component } = React.useContext(ComponentContext);
-  const { data: branchLike } = useCurrentBranchQuery(component);
+  const { data: branchLike } = useCurrentBranchQuery(component, StaleTime.LONG);
   if (isRichQualityGateEvent(event)) {
     return <RichQualityGateEventInner event={event} readonly={readonly} />;
   } else if (isDefinitionChangeEvent(event)) {
index 88785de65ceff2c495f11f0e701c51e8b6b70da4..35d1150b9cddbf4af354bf626d51b730b67261e4 100644 (file)
@@ -21,7 +21,6 @@
 import { ButtonGroup, InputSize, Select } from '@sonarsource/echoes-react';
 import * as React from 'react';
 import { translate } from '../../helpers/l10n';
-import { useStandardExperienceMode } from '../../queries/settings';
 import { GraphType } from '../../types/project-activity';
 import { Metric } from '../../types/types';
 import AddGraphMetric from './AddGraphMetric';
@@ -48,8 +47,6 @@ export default function GraphsHeader(props: Readonly<Props>) {
     selectedMetrics = [],
   } = props;
 
-  const { data: isStandardMode } = useStandardExperienceMode();
-
   const handleGraphChange = React.useCallback(
     (value: GraphType) => {
       if (value !== graph) {
@@ -90,7 +87,6 @@ export default function GraphsHeader(props: Readonly<Props>) {
               metricsTypeFilter={metricsTypeFilter}
               onRemoveMetric={props.onRemoveCustomMetric}
               selectedMetrics={selectedMetrics}
-              isStandardMode={isStandardMode}
             />
           )}
       </ButtonGroup>
index dbd3729e9141154131a47ec83e5dd58276b4ca8a..d9ae9ae663b2fd23240b7c006ecdc0ca63b59624 100644 (file)
@@ -50,7 +50,7 @@ export function GraphsLegendItem({
       isActionable={isActionable}
     >
       {showWarning ? (
-        <IconWarning className="sw-mr-2" />
+        <IconWarning color="echoes-color-icon-warning" className="sw-mr-2" />
       ) : (
         <ChartLegend className="sw-mr-2" index={index} />
       )}
index d2273460103611d8c4e7eb8c29b53092bdb2635f..ea0ccc4dd03c47ddd75d83fb141fa6e69ec11074 100644 (file)
 
 import { ScaleTime } from 'd3-scale';
 import { TimeMachineResponse } from '../api/time-machine';
-import { SOFTWARE_QUALITY_RATING_METRICS_MAP } from './constants';
+import { MQR_CONDITIONS_MAP, STANDARD_CONDITIONS_MAP } from '../apps/quality-gates/utils';
 
-export const mergeRatingMeasureHistory = (
+export const mergeMeasureHistory = (
   historyData: TimeMachineResponse | undefined,
   parseDateFn: (date: string) => Date,
   isStandardMode = false,
 ) => {
-  const softwareQualityMeasures = Object.values(SOFTWARE_QUALITY_RATING_METRICS_MAP);
-  const softwareQualityMeasuresMap = new Map<
+  const standardMeasuresMap = new Map<
     string,
     { history: { date: string; value?: string }[]; index: number; splitDate?: Date }
   >();
   if (isStandardMode) {
     return (
-      historyData?.measures
-        ?.filter((m) => !softwareQualityMeasures.includes(m.metric))
-        .map((measure) => ({
-          metric: measure.metric,
-          history: measure.history.map((historyItem) => ({
-            date: parseDateFn(historyItem.date),
-            value: historyItem.value,
-          })),
-        })) ?? []
+      historyData?.measures.map((measure) => ({
+        metric: measure.metric,
+        history: measure.history.map((historyItem) => ({
+          date: parseDateFn(historyItem.date),
+          value: historyItem.value,
+        })),
+      })) ?? []
     );
   }
 
   const historyDataFiltered =
     historyData?.measures?.filter((measure) => {
-      if (softwareQualityMeasures.includes(measure.metric)) {
+      if (MQR_CONDITIONS_MAP[measure.metric]) {
         const splitPointIndex = measure.history.findIndex(
           (historyItem) => historyItem.value != null,
         );
-        softwareQualityMeasuresMap.set(measure.metric, {
+        standardMeasuresMap.set(measure.metric, {
           history: measure.history,
           index: measure.history.findIndex((historyItem) => historyItem.value != null),
           splitDate:
@@ -72,20 +69,24 @@ export const mergeRatingMeasureHistory = (
   });
 
   return historyDataFiltered.map((measure) => {
-    const softwareQualityMetric = softwareQualityMeasuresMap.get(
-      SOFTWARE_QUALITY_RATING_METRICS_MAP[measure.metric],
-    );
+    const metric = STANDARD_CONDITIONS_MAP[measure.metric];
+    const softwareQualityMetric = standardMeasuresMap.get(metric as string);
+    if (softwareQualityMetric !== undefined && metric) {
+      return {
+        metric,
+        splitPointDate: softwareQualityMetric.splitDate,
+        history: measure.history
+          .slice(0, softwareQualityMetric.index)
+          .map(historyMapper)
+          .concat(
+            softwareQualityMetric.history.slice(softwareQualityMetric.index).map(historyMapper),
+          ),
+      };
+    }
+
     return {
       metric: measure.metric,
-      splitPointDate: softwareQualityMetric ? softwareQualityMetric.splitDate : undefined,
-      history: softwareQualityMetric
-        ? measure.history
-            .slice(0, softwareQualityMetric.index)
-            .map(historyMapper)
-            .concat(
-              softwareQualityMetric.history.slice(softwareQualityMetric.index).map(historyMapper),
-            )
-        : measure.history.map(historyMapper),
+      history: measure.history.map(historyMapper),
     };
   });
 };
index cf169fbcf944f4b51e1088cfe889e28b9daf9a37..2afd6152403618aafd2b3114119a9f4f8cdc6ea6 100644 (file)
@@ -199,14 +199,7 @@ export const HIDDEN_METRICS = [
   MetricKey.high_impact_accepted_issues,
 ];
 
-export const DEPRECATED_ACTIVITY_METRICS = [
-  MetricKey.blocker_violations,
-  MetricKey.critical_violations,
-  MetricKey.major_violations,
-  MetricKey.minor_violations,
-  MetricKey.info_violations,
-  MetricKey.confirmed_issues,
-];
+export const DEPRECATED_ACTIVITY_METRICS = [MetricKey.confirmed_issues];
 
 export const SOFTWARE_QUALITY_RATING_METRICS_MAP: Record<string, MetricKey> = {
   [MetricKey.sqale_rating]: MetricKey.software_quality_maintainability_rating,
@@ -267,11 +260,6 @@ export const SOFTWARE_QUALITY_RATING_METRICS = [
   MetricKey.new_software_quality_reliability_remediation_effort,
   MetricKey.new_software_quality_security_remediation_effort,
   MetricKey.new_software_quality_maintainability_debt_ratio,
-  MetricKey.software_quality_blocker_issues,
-  MetricKey.software_quality_high_issues,
-  MetricKey.software_quality_medium_issues,
-  MetricKey.software_quality_low_issues,
-  MetricKey.software_quality_info_issues,
 ];
 
 export const PROJECT_KEY_MAX_LEN = 400;
index c08095b30e2d997aa42f522aadb6cf2aa7dbcb3b..13b20809900ba9d75dfd8542b39c97d12196fa7d 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import { queryOptions, useMutation, useQueryClient } from '@tanstack/react-query';
 import { deleteApplication, getApplicationLeak } from '../api/application';
+import { createQueryHook } from './common';
 import { invalidateMeasuresByComponentKey } from './measures';
 
-export default function useApplicationLeakQuery(application: string, enabled = true) {
-  return useQuery({
+export const useApplicationLeakQuery = createQueryHook((application: string) => {
+  return queryOptions({
     queryKey: ['application', 'leak', application],
     queryFn: () => getApplicationLeak(application),
-    enabled,
   });
-}
+});
 
 export function useDeleteApplicationMutation() {
   const queryClient = useQueryClient();
index ada7254d6132e0019d3f0c3c2907a61a3099f91f..8acc97b5e689358b0be4dbe22de76f0db7495e4e 100644 (file)
@@ -168,7 +168,10 @@ export function useBranchesQuery(component: LightComponent | undefined) {
   });
 }
 
-export function useCurrentBranchQuery(component: LightComponent | undefined) {
+export function useCurrentBranchQuery(
+  component: LightComponent | undefined,
+  staleTime = StaleTime.LIVE,
+) {
   const features = useContext(AvailableFeaturesContext);
   const { search } = useLocation();
 
@@ -196,7 +199,7 @@ export function useCurrentBranchQuery(component: LightComponent | undefined) {
   return useQuery({
     ...branchesQuery(component, features.includes(Feature.BranchSupport)),
     select,
-    staleTime: StaleTime.LIVE,
+    staleTime,
   });
 }
 
index ee6fe2a0e738a8cc4226862b00800a7bb206e4b5..c7edf3eb132787380ed249db8fd305d8d442f1cb 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import { queryOptions, useMutation, useQueryClient } from '@tanstack/react-query';
 import { getBranchLikeQuery } from '~sonar-aligned/helpers/branch-like';
 import { BranchParameters } from '~sonar-aligned/types/branch-like';
 import {
@@ -38,6 +38,7 @@ import { parseDate } from '../helpers/dates';
 import { serializeStringArray } from '../helpers/query';
 import { ParsedAnalysis } from '../types/project-activity';
 import { useCurrentBranchQuery } from './branch';
+import { createQueryHook } from './common';
 
 const ACTIVITY_PAGE_SIZE = 500;
 
@@ -55,9 +56,10 @@ function useProjectActivityQueryKey() {
   ];
 }
 
-export function useAllProjectAnalysesQuery(enabled = true) {
+export const useAllProjectAnalysesQuery = createQueryHook(() => {
   const queryKey = useProjectActivityQueryKey();
-  return useQuery({
+
+  return queryOptions({
     queryKey,
     queryFn: ({ queryKey: [_0, _1, project, branchParams] }) =>
       getAllTimeProjectActivity({
@@ -75,9 +77,8 @@ export function useAllProjectAnalysesQuery(enabled = true) {
           date: parseDate(analysis.date),
         })),
       ),
-    enabled,
   });
-}
+});
 
 export function useDeleteAnalysisMutation(successCb?: () => void) {
   const queryClient = useQueryClient();
index a352da7def547945b90d49f4ae4021d758429fea..6b03a41cfe137320cfbba7e1fbfd3b07f0927291 100644 (file)
@@ -2168,7 +2168,7 @@ project_activity.graphs.data_table.no_data_warning_check_dates_x_y=There is no d
 project_activity.graphs.data_table.data_gap=The chart history for issues related to software qualities may contain gaps while information is not available for one or more projects. {learn_more}
 
 project_activity.graphs.rating_split.title=Metrics calculation changed
-project_activity.graphs.rating_split.description=The way we calculate ratings has changed and it might have affected this metric's values.
+project_activity.graphs.rating_split.description=The way we calculate metrics has changed and it might have affected this metric's values.
 project_activity.graphs.rating_split.info_icon=Metrics calculation change information
 
 project_activity.custom_metric.covered_lines=Covered Lines
@@ -3720,26 +3720,26 @@ metric.new_reliability_issues.short_name=New Reliability
 metric.new_security_issues.name=New Security Issues
 metric.new_security_issues.description=New security issues
 metric.new_security_issues.short_name=New Security
-metric.software_quality_blocker_issues.description=Blocker issues
-metric.software_quality_blocker_issues.name=Blocker Issues
-metric.software_quality_high_issues.description=Critical issues
-metric.software_quality_high_issues.name=Critical Issues
-metric.software_quality_info_issues.description=Info issues
-metric.software_quality_info_issues.name=Info Issues
-metric.software_quality_medium_issues.description=Major issues
-metric.software_quality_medium_issues.name=Major Issues
-metric.software_quality_low_issues.description=Minor issues
-metric.software_quality_low_issues.name=Minor Issues
-metric.new_software_quality_blocker_issues.description=New Blocker issues
-metric.new_software_quality_blocker_issues.name=New Blocker Issues
-metric.new_software_quality_high_issues.description=New Critical issues
-metric.new_software_quality_high_issues.name=New Critical Issues
-metric.new_software_quality_info_issues.description=New Info issues
-metric.new_software_quality_info_issues.name=New Info Issues
-metric.new_software_quality_medium_issues.description=New Major issues
-metric.new_software_quality_medium_issues.name=New Major Issues
-metric.new_software_quality_low_issues.description=New Minor issues
-metric.new_software_quality_low_issues.name=New Minor Issues
+metric.software_quality_blocker_issues.description=Blocker Severity issues
+metric.software_quality_blocker_issues.name=Blocker Severity Issues
+metric.software_quality_high_issues.description=High Severity issues
+metric.software_quality_high_issues.name=High Severity Issues
+metric.software_quality_info_issues.description=Info Severity issues
+metric.software_quality_info_issues.name=Info Severity Issues
+metric.software_quality_medium_issues.description=Medium Severity issues
+metric.software_quality_medium_issues.name=Medium Severity Issues
+metric.software_quality_low_issues.description=Low Severity issues
+metric.software_quality_low_issues.name=Low Severity Issues
+metric.new_software_quality_blocker_issues.description=New Blocker Severity issues
+metric.new_software_quality_blocker_issues.name=New Blocker Severity Issues
+metric.new_software_quality_high_issues.description=New High Severity issues
+metric.new_software_quality_high_issues.name=New High Severity Issues
+metric.new_software_quality_info_issues.description=New Info Severity issues
+metric.new_software_quality_info_issues.name=New Info Severity Issues
+metric.new_software_quality_medium_issues.description=New Medium Severity issues
+metric.new_software_quality_medium_issues.name=New Medium Severity Issues
+metric.new_software_quality_low_issues.description=New Low Severity issues
+metric.new_software_quality_low_issues.name=New Low Severity Issues
 metric.software_quality_maintainability_issues.name=Maintainability Issues
 metric.software_quality_maintainability_issues.description=Maintainability issues
 metric.software_quality_reliability_issues.name=Reliability Issues