allowNewElements?: boolean;
allowSearch?: boolean;
createElementLabel: string;
- disableMessage?: string;
elements: string[];
headerLabel: string;
listSize?: number;
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[];
const {
allowNewElements,
createElementLabel,
- disableMessage,
selectedElementsDisabled,
headerLabel,
noResultsLabel,
selectedElements,
elements,
allowSearch = true,
+ renderTooltip,
listSize = LIST_SIZE,
} = props;
allowNewElements={allowNewElements}
allowSearch={allowSearch}
createElementLabel={createElementLabel}
- disableMessage={disableMessage}
elements={elements}
headerNode={<div className="sw-mt-4 sw-font-semibold">{headerLabel}</div>}
listSize={listSize}
onSelect={props.onSelect}
onUnselect={props.onUnselect}
placeholder={searchInputAriaLabel}
+ renderTooltip={renderTooltip}
searchInputAriaLabel={searchInputAriaLabel}
selectedElements={selectedElements}
selectedElementsDisabled={selectedElementsDisabled}
allowSearch?: boolean;
allowSelection?: boolean;
createElementLabel: string;
- disableMessage?: string;
elements: string[];
elementsDisabled?: string[];
footerNode?: React.ReactNode;
onSelect: (item: string) => void;
onUnselect: (item: string) => void;
placeholder: string;
+ renderTooltip?: (element: string, disabled: boolean) => React.ReactNode;
searchInputAriaLabel: string;
selectedElements: string[];
selectedElementsDisabled?: string[];
allowSelection = true,
allowNewElements = true,
createElementLabel,
- disableMessage,
selectedElementsDisabled = [],
headerNode = '',
footerNode = '',
noResultsLabel,
searchInputAriaLabel,
elementsDisabled,
+ renderTooltip,
} = this.props;
const { renderLabel } = this.props as PropsWithDefault;
<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
/>
))}
onHover={this.handleElementHover}
onSelectChange={this.handleSelectChange}
renderLabel={renderLabel}
+ renderTooltip={renderTooltip}
/>
))}
{elementsDisabled?.map((element) => (
onHover={this.handleElementHover}
onSelectChange={this.handleSelectChange}
renderLabel={renderLabel}
+ renderTooltip={renderTooltip}
/>
))}
{showNewElement && (
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;
}
active,
createElementLabel,
custom,
- disabled,
- disableMessage,
+ disabled = false,
element,
onSelectChange,
selected,
renderLabel = identity,
+ renderTooltip,
} = props;
const onHover = () => {
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 })}
};
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')}
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;
+ }}
/>
);
}
} 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';
}, [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) => {
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';
) => {
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())
) {
onSearch={this.onSearch}
onSelect={this.onSelect}
onUnselect={this.onUnselect}
- renderLabel={(element) => this.getLocalizedMetricNameFromKey(element)}
selectedElements={selectedMetrics}
/>
}
* 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[];
onSelect: (item: string) => void;
onUnselect: (item: string) => void;
popupPosition?: any;
- renderLabel: (element: string) => React.ReactNode;
selectedElements: string[];
}
metricsTypeFilter,
...props
}: AddGraphMetricPopupProps) {
+ const intl = useIntl();
let footerNode: React.ReactNode = '';
if (props.selectedElements.length >= 6) {
);
}
+ 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=""
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}
/>
import styled from '@emotion/styled';
import classNames from 'classnames';
import {
+ Badge,
CloseIcon,
FlagWarningIcon,
InteractiveIcon,
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 {
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
<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}
// 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();
// 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 }),
{ 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;
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}
#------------------------------------------------------------------------------
#