aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-web/design-system/src/components/MultiSelector.tsx6
-rw-r--r--server/sonar-web/design-system/src/components/input/MultiSelectMenu.tsx8
-rw-r--r--server/sonar-web/design-system/src/components/input/MultiSelectMenuOption.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsTagsPopup.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx22
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/AddGraphMetric.tsx19
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/AddGraphMetricPopup.tsx52
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/GraphsLegendItem.tsx31
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/ActivityGraph-it.tsx4
-rw-r--r--server/sonar-web/src/main/js/helpers/constants.ts18
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties2
11 files changed, 150 insertions, 35 deletions
diff --git a/server/sonar-web/design-system/src/components/MultiSelector.tsx b/server/sonar-web/design-system/src/components/MultiSelector.tsx
index 7a7c066c59c..ebbc3c06830 100644
--- a/server/sonar-web/design-system/src/components/MultiSelector.tsx
+++ b/server/sonar-web/design-system/src/components/MultiSelector.tsx
@@ -23,7 +23,6 @@ interface Props {
allowNewElements?: boolean;
allowSearch?: boolean;
createElementLabel: string;
- disableMessage?: string;
elements: string[];
headerLabel: string;
listSize?: number;
@@ -31,6 +30,7 @@ interface Props {
onSearch?: (query: string) => Promise<void>;
onSelect: (item: string) => void;
onUnselect: (item: string) => void;
+ renderTooltip?: (item: string, disabled: boolean) => React.ReactNode;
searchInputAriaLabel: string;
selectedElements: string[];
selectedElementsDisabled?: string[];
@@ -42,7 +42,6 @@ export function MultiSelector(props: Readonly<Props>) {
const {
allowNewElements,
createElementLabel,
- disableMessage,
selectedElementsDisabled,
headerLabel,
noResultsLabel,
@@ -50,6 +49,7 @@ export function MultiSelector(props: Readonly<Props>) {
selectedElements,
elements,
allowSearch = true,
+ renderTooltip,
listSize = LIST_SIZE,
} = props;
@@ -58,7 +58,6 @@ export function MultiSelector(props: Readonly<Props>) {
allowNewElements={allowNewElements}
allowSearch={allowSearch}
createElementLabel={createElementLabel}
- disableMessage={disableMessage}
elements={elements}
headerNode={<div className="sw-mt-4 sw-font-semibold">{headerLabel}</div>}
listSize={listSize}
@@ -67,6 +66,7 @@ export function MultiSelector(props: Readonly<Props>) {
onSelect={props.onSelect}
onUnselect={props.onUnselect}
placeholder={searchInputAriaLabel}
+ renderTooltip={renderTooltip}
searchInputAriaLabel={searchInputAriaLabel}
selectedElements={selectedElements}
selectedElementsDisabled={selectedElementsDisabled}
diff --git a/server/sonar-web/design-system/src/components/input/MultiSelectMenu.tsx b/server/sonar-web/design-system/src/components/input/MultiSelectMenu.tsx
index 97121244228..86da7e4b5bd 100644
--- a/server/sonar-web/design-system/src/components/input/MultiSelectMenu.tsx
+++ b/server/sonar-web/design-system/src/components/input/MultiSelectMenu.tsx
@@ -30,7 +30,6 @@ interface Props {
allowSearch?: boolean;
allowSelection?: boolean;
createElementLabel: string;
- disableMessage?: string;
elements: string[];
elementsDisabled?: string[];
footerNode?: React.ReactNode;
@@ -42,6 +41,7 @@ interface Props {
onSelect: (item: string) => void;
onUnselect: (item: string) => void;
placeholder: string;
+ renderTooltip?: (element: string, disabled: boolean) => React.ReactNode;
searchInputAriaLabel: string;
selectedElements: string[];
selectedElementsDisabled?: string[];
@@ -265,7 +265,6 @@ export class MultiSelectMenu extends PureComponent<Props, State> {
allowSelection = true,
allowNewElements = true,
createElementLabel,
- disableMessage,
selectedElementsDisabled = [],
headerNode = '',
footerNode = '',
@@ -273,6 +272,7 @@ export class MultiSelectMenu extends PureComponent<Props, State> {
noResultsLabel,
searchInputAriaLabel,
elementsDisabled,
+ renderTooltip,
} = this.props;
const { renderLabel } = this.props as PropsWithDefault;
@@ -313,13 +313,13 @@ export class MultiSelectMenu extends PureComponent<Props, State> {
<MultiSelectMenuOption
active={activeElement === element}
createElementLabel={createElementLabel}
- disableMessage={disableMessage}
disabled={selectedElementsDisabled.includes(element)}
element={element}
key={element}
onHover={this.handleElementHover}
onSelectChange={this.handleSelectChange}
renderLabel={renderLabel}
+ renderTooltip={renderTooltip}
selected
/>
))}
@@ -334,6 +334,7 @@ export class MultiSelectMenu extends PureComponent<Props, State> {
onHover={this.handleElementHover}
onSelectChange={this.handleSelectChange}
renderLabel={renderLabel}
+ renderTooltip={renderTooltip}
/>
))}
{elementsDisabled?.map((element) => (
@@ -346,6 +347,7 @@ export class MultiSelectMenu extends PureComponent<Props, State> {
onHover={this.handleElementHover}
onSelectChange={this.handleSelectChange}
renderLabel={renderLabel}
+ renderTooltip={renderTooltip}
/>
))}
{showNewElement && (
diff --git a/server/sonar-web/design-system/src/components/input/MultiSelectMenuOption.tsx b/server/sonar-web/design-system/src/components/input/MultiSelectMenuOption.tsx
index 7a5d2eec6a7..2df004a3c6a 100644
--- a/server/sonar-web/design-system/src/components/input/MultiSelectMenuOption.tsx
+++ b/server/sonar-web/design-system/src/components/input/MultiSelectMenuOption.tsx
@@ -27,12 +27,12 @@ export interface MultiSelectOptionProps {
active?: boolean;
createElementLabel: string;
custom?: boolean;
- disableMessage?: string;
disabled?: boolean;
element: string;
onHover: (element: string) => void;
onSelectChange: (selected: boolean, element: string) => void;
renderLabel?: (element: string) => React.ReactNode;
+ renderTooltip?: (element: string, disabled: boolean) => React.ReactNode;
selected?: boolean;
}
@@ -41,12 +41,12 @@ export function MultiSelectMenuOption(props: MultiSelectOptionProps) {
active,
createElementLabel,
custom,
- disabled,
- disableMessage,
+ disabled = false,
element,
onSelectChange,
selected,
renderLabel = identity,
+ renderTooltip,
} = props;
const onHover = () => {
@@ -56,7 +56,7 @@ export function MultiSelectMenuOption(props: MultiSelectOptionProps) {
const label = renderLabel(element);
return (
- <Tooltip overlay={disabled && disableMessage} placement={PopupPlacement.Right}>
+ <Tooltip overlay={renderTooltip?.(element, disabled)} placement={PopupPlacement.Right}>
<ItemCheckbox
checked={Boolean(selected)}
className={classNames('sw-flex sw-py-2 sw-px-4', { active })}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsTagsPopup.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsTagsPopup.tsx
index c0be592eabb..188cde32c99 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsTagsPopup.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsTagsPopup.tsx
@@ -70,12 +70,13 @@ export default class RuleDetailsTagsPopup extends React.PureComponent<Props, Sta
};
render() {
- const availableTags = difference(this.state.searchResult, this.props.tags);
- const selectedTags = [...this.props.sysTags, ...this.props.tags];
+ const { sysTags, tags } = this.props;
+ const { searchResult } = this.state;
+ const availableTags = difference(searchResult, tags);
+ const selectedTags = [...sysTags, ...tags];
return (
<MultiSelector
createElementLabel={translate('coding_rules.create_tag')}
- disableMessage={translate('coding_rules.system_tags_tooltip')}
headerLabel={translate('tags')}
searchInputAriaLabel={translate('search.search_for_tags')}
noResultsLabel={translate('no_results')}
@@ -83,8 +84,14 @@ export default class RuleDetailsTagsPopup extends React.PureComponent<Props, Sta
onSelect={this.onSelect}
onUnselect={this.onUnselect}
selectedElements={selectedTags}
- selectedElementsDisabled={this.props.sysTags}
+ selectedElementsDisabled={sysTags}
elements={availableTags}
+ renderTooltip={(element: string, disabled: boolean) => {
+ if (sysTags.includes(element) && disabled) {
+ return translate('coding_rules.system_tags_tooltip');
+ }
+ return null;
+ }}
/>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx
index b8f83ead011..b0460c1475f 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx
@@ -31,7 +31,6 @@ import {
} from '../../../components/activity-graph/utils';
import { useLocation, useRouter } from '../../../components/hoc/withRouter';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
-import { HIDDEN_METRICS } from '../../../helpers/constants';
import { parseDate } from '../../../helpers/dates';
import useApplicationLeakQuery from '../../../queries/applications';
import { useBranchesQuery } from '../../../queries/branch';
@@ -106,16 +105,19 @@ export function ProjectActivityApp() {
}, [appLeaks, component?.leakPeriodDate, component?.qualifier]);
const filteredMetrics = React.useMemo(() => {
- if (isPortfolioLike(component?.qualifier)) {
- return Object.values(metrics).filter(
- (metric) => metric.key !== MetricKey.security_hotspots_reviewed,
- );
- }
+ return Object.values(metrics).filter((metric) => {
+ if (
+ isPortfolioLike(component?.qualifier) &&
+ metric.key === MetricKey.security_hotspots_reviewed
+ ) {
+ return false;
+ }
+ if (isProject(component?.qualifier) && metric.key === MetricKey.security_review_rating) {
+ return false;
+ }
- return Object.values(metrics).filter(
- (metric) =>
- ![...HIDDEN_METRICS, MetricKey.security_review_rating].includes(metric.key as MetricKey),
- );
+ return true;
+ });
}, [component?.qualifier, metrics]);
const handleUpdateQuery = (newQuery: Query) => {
diff --git a/server/sonar-web/src/main/js/components/activity-graph/AddGraphMetric.tsx b/server/sonar-web/src/main/js/components/activity-graph/AddGraphMetric.tsx
index 2ee99609452..7a224c9d3f7 100644
--- a/server/sonar-web/src/main/js/components/activity-graph/AddGraphMetric.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/AddGraphMetric.tsx
@@ -20,9 +20,10 @@
import { ButtonSecondary, ChevronDownIcon, Dropdown, TextMuted } from 'design-system';
import { sortBy } from 'lodash';
import * as React from 'react';
+import { HIDDEN_METRICS } from '../../helpers/constants';
import { getLocalizedMetricName, translate } from '../../helpers/l10n';
import { isDiffMetric } from '../../helpers/measures';
-import { MetricType } from '../../types/metrics';
+import { MetricKey, MetricType } from '../../types/metrics';
import { Metric } from '../../types/types';
import AddGraphMetricPopup from './AddGraphMetricPopup';
@@ -59,10 +60,19 @@ export default class AddGraphMetric extends React.PureComponent<Props, State> {
) => {
return metrics
.filter((metric) => {
+ if (metric.hidden) {
+ return false;
+ }
+ if (isDiffMetric(metric.key)) {
+ return false;
+ }
+ if ([MetricType.Data, MetricType.Distribution].includes(metric.type as MetricType)) {
+ return false;
+ }
+ if (HIDDEN_METRICS.includes(metric.key as MetricKey)) {
+ return false;
+ }
if (
- metric.hidden ||
- isDiffMetric(metric.key) ||
- [MetricType.Data, MetricType.Distribution].includes(metric.type as MetricType) ||
selectedMetrics.includes(metric.key) ||
!getLocalizedMetricName(metric).toLowerCase().includes(query.toLowerCase())
) {
@@ -134,7 +144,6 @@ export default class AddGraphMetric extends React.PureComponent<Props, State> {
onSearch={this.onSearch}
onSelect={this.onSelect}
onUnselect={this.onUnselect}
- renderLabel={(element) => this.getLocalizedMetricNameFromKey(element)}
selectedElements={selectedMetrics}
/>
}
diff --git a/server/sonar-web/src/main/js/components/activity-graph/AddGraphMetricPopup.tsx b/server/sonar-web/src/main/js/components/activity-graph/AddGraphMetricPopup.tsx
index 5acd46b0190..1498b661f0b 100644
--- a/server/sonar-web/src/main/js/components/activity-graph/AddGraphMetricPopup.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/AddGraphMetricPopup.tsx
@@ -17,9 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { FlagMessage, MultiSelectMenu } from 'design-system';
+import { Badge, FlagMessage, MultiSelectMenu } from 'design-system';
import * as React from 'react';
-import { translate, translateWithParameters } from '../../helpers/l10n';
+import { FormattedMessage, useIntl } from 'react-intl';
+import { DEPRECATED_ACTIVITY_METRICS } from '../../helpers/constants';
+import { getLocalizedMetricName, translate, translateWithParameters } from '../../helpers/l10n';
+import { MetricKey } from '../../types/metrics';
+import DocumentationLink from '../common/DocumentationLink';
export interface AddGraphMetricPopupProps {
elements: string[];
@@ -29,7 +33,6 @@ export interface AddGraphMetricPopupProps {
onSelect: (item: string) => void;
onUnselect: (item: string) => void;
popupPosition?: any;
- renderLabel: (element: string) => React.ReactNode;
selectedElements: string[];
}
@@ -38,6 +41,7 @@ export default function AddGraphMetricPopup({
metricsTypeFilter,
...props
}: AddGraphMetricPopupProps) {
+ const intl = useIntl();
let footerNode: React.ReactNode = '';
if (props.selectedElements.length >= 6) {
@@ -60,6 +64,45 @@ export default function AddGraphMetricPopup({
);
}
+ const renderLabel = (key: string) => {
+ const metricName = getLocalizedMetricName({ key });
+ const isDeprecated = DEPRECATED_ACTIVITY_METRICS.includes(key as MetricKey);
+
+ return (
+ <>
+ {metricName}
+ {isDeprecated && (
+ <Badge className="sw-ml-1">{intl.formatMessage({ id: 'deprecated' })}</Badge>
+ )}
+ </>
+ );
+ };
+
+ const renderTooltip = (key: string) => {
+ const isDeprecated = DEPRECATED_ACTIVITY_METRICS.includes(key as MetricKey);
+
+ if (isDeprecated) {
+ return (
+ <FormattedMessage
+ id="project_activity.custom_metric.deprecated"
+ tagName="div"
+ values={{
+ learn_more: (
+ <DocumentationLink
+ className="sw-ml-2 sw-whitespace-nowrap"
+ to="/user-guide/clean-code/code-analysis/"
+ >
+ {intl.formatMessage({ id: 'learn_more' })}
+ </DocumentationLink>
+ ),
+ }}
+ />
+ );
+ }
+
+ return null;
+ };
+
return (
<MultiSelectMenu
createElementLabel=""
@@ -74,7 +117,8 @@ export default function AddGraphMetricPopup({
onSelect={(item: string) => elements.includes(item) && props.onSelect(item)}
onUnselect={props.onUnselect}
placeholder={translate('search.search_for_metrics')}
- renderLabel={props.renderLabel}
+ renderLabel={renderLabel}
+ renderTooltip={renderTooltip}
selectedElements={props.selectedElements}
listSize={0}
/>
diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendItem.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendItem.tsx
index 58767ea47ed..f8655c20a29 100644
--- a/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendItem.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendItem.tsx
@@ -21,6 +21,7 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import classNames from 'classnames';
import {
+ Badge,
CloseIcon,
FlagWarningIcon,
InteractiveIcon,
@@ -29,7 +30,12 @@ import {
themeColor,
} from 'design-system';
import * as React from 'react';
+import { FormattedMessage, useIntl } from 'react-intl';
+import { DEPRECATED_ACTIVITY_METRICS } from '../../helpers/constants';
import { translateWithParameters } from '../../helpers/l10n';
+import { MetricKey } from '../../types/metrics';
+import DocumentationLink from '../common/DocumentationLink';
+import Tooltip from '../controls/Tooltip';
import { ChartLegend } from './ChartLegend';
interface Props {
@@ -49,9 +55,11 @@ export function GraphsLegendItem({
removeMetric,
showWarning,
}: Props) {
+ const intl = useIntl();
const theme = useTheme() as Theme;
const isActionable = removeMetric !== undefined;
+ const isDeprecated = DEPRECATED_ACTIVITY_METRICS.includes(metric as MetricKey);
return (
<StyledLegendItem
@@ -66,6 +74,29 @@ export function GraphsLegendItem({
<span className="sw-body-sm" style={{ color: themeColor('graphCursorLineColor')({ theme }) }}>
{name}
</span>
+ {isDeprecated && (
+ <Tooltip
+ overlay={
+ <FormattedMessage
+ id="project_activity.custom_metric.deprecated"
+ values={{
+ learn_more: (
+ <DocumentationLink
+ className="sw-ml-2 sw-whitespace-nowrap"
+ to="/user-guide/clean-code/code-analysis/"
+ >
+ {intl.formatMessage({ id: 'learn_more' })}
+ </DocumentationLink>
+ ),
+ }}
+ />
+ }
+ >
+ <div>
+ <Badge className="sw-ml-1">{intl.formatMessage({ id: 'deprecated' })}</Badge>
+ </div>
+ </Tooltip>
+ )}
{isActionable && (
<InteractiveIcon
Icon={CloseIcon}
diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/ActivityGraph-it.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/ActivityGraph-it.tsx
index c2c0943c804..88131755a68 100644
--- a/server/sonar-web/src/main/js/components/activity-graph/__tests__/ActivityGraph-it.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/ActivityGraph-it.tsx
@@ -132,6 +132,9 @@ it('should correctly handle adding/removing custom metrics', async () => {
// We should see 2 graphs, correctly labelled.
expect(ui.graphs.getAll()).toHaveLength(2);
+ // old types and confirmed metrics should be deprecated and show a badge (both in dropdown and in legend)
+ expect(ui.deprecatedBadge.getAll()).toHaveLength(6);
+
// We cannot select anymore Int types. It should hide options, and show an alert.
expect(ui.vulnerabilityCheckbox.query()).not.toBeInTheDocument();
expect(ui.hiddenOptionsAlert.get()).toBeInTheDocument();
@@ -172,6 +175,7 @@ function getPageObject() {
// Add/remove metrics.
addMetricBtn: byRole('button', { name: 'project_activity.graphs.custom.add' }),
+ deprecatedBadge: byText('deprecated'),
bugsCheckbox: byRole('checkbox', { name: MetricKey.bugs }),
newBugsCheckbox: byRole('checkbox', { name: MetricKey.new_bugs }),
burnedBudgetCheckbox: byRole('checkbox', { name: MetricKey.burned_budget }),
diff --git a/server/sonar-web/src/main/js/helpers/constants.ts b/server/sonar-web/src/main/js/helpers/constants.ts
index 63de008d849..f5b42b3adce 100644
--- a/server/sonar-web/src/main/js/helpers/constants.ts
+++ b/server/sonar-web/src/main/js/helpers/constants.ts
@@ -137,7 +137,23 @@ export const RATING_COLORS = [
{ fill: colors.error400, fillTransparent: colors.error400a20, stroke: colors.error700 },
];
-export const HIDDEN_METRICS = [MetricKey.open_issues, MetricKey.reopened_issues];
+export const HIDDEN_METRICS = [
+ MetricKey.open_issues,
+ MetricKey.reopened_issues,
+ 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.code_smells,
+ MetricKey.bugs,
+ MetricKey.vulnerabilities,
+ MetricKey.confirmed_issues,
+];
export const PROJECT_KEY_MAX_LEN = 400;
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 520c4ec9605..7d302471139 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -1952,7 +1952,7 @@ project_activity.graphs.data_table.no_data_warning_check_dates_y=There is no dat
project_activity.graphs.data_table.no_data_warning_check_dates_x_y=There is no data for the selected date range ({start} to {end}). Try modifying the date filters on the main page.
project_activity.custom_metric.covered_lines=Covered Lines
-
+project_activity.custom_metric.deprecated=We are deprecating those filters to align the Activity page with our definition of Clean Code. They will be removed in a future release. {learn_more}
#------------------------------------------------------------------------------
#