module.exports = {
...jest.requireActual('react-intl'),
+ useIntl: () => ({
+ formatMessage: ({ id }, values = {}) => [id, ...Object.values(values)].join('.'),
+ }),
FormattedMessage: ({ id, values }: { id: string; values?: { [x: string]: React.ReactNode } }) => {
return (
<>
import { FCProps } from '../types/misc';
export interface TableProps extends ComponentProps<'table'> {
+ caption?: ReactNode;
columnCount: number;
columnWidths?: Array<number | string>;
header?: ReactNode;
columnCount,
columnWidths = [],
header,
+ caption,
children,
noHeaderTopBorder,
noSidePadding,
<StyledTable
className={classNames(
{ 'no-header-top-border': noHeaderTopBorder, 'no-side-padding': noSidePadding },
- className
+ className,
)}
{...rest}
>
<col key={i} width={columnWidths[i] ?? 'auto'} />
))}
</colgroup>
+
+ {caption && (
+ <caption>
+ <div className="sw-py-4 sw-text-middle sw-flex sw-justify-center sw-body-sm-highlight">
+ {caption}
+ </div>
+ </caption>
+ )}
+
{header && (
<thead>
<CellTypeContext.Provider value="th">{header}</CellTypeContext.Provider>
</thead>
)}
+
<tbody>{children}</tbody>
</StyledTable>
);
V,
Option extends LabelValueSelectOption<V>,
IsMulti extends boolean = false,
- Group extends GroupBase<Option> = GroupBase<Option>
+ Group extends GroupBase<Option> = GroupBase<Option>,
> extends SelectProps<V, Option, IsMulti, Group>,
AsyncProps<Option, IsMulti, Group> {
className?: string;
controlAriaLabel?: string;
controlLabel?: React.ReactNode | string;
+ controlPlaceholder?: string;
controlSize?: InputSizeKeys;
isDiscreet?: boolean;
zLevel?: PopupZLevel;
V,
Option extends LabelValueSelectOption<V>,
IsMulti extends boolean = false,
- Group extends GroupBase<Option> = GroupBase<Option>
+ Group extends GroupBase<Option> = GroupBase<Option>,
>(props: SearchSelectDropdownProps<V, Option, IsMulti, Group>) {
const {
className,
value,
loadOptions,
controlLabel,
+ controlPlaceholder,
controlSize,
isDisabled,
minLength,
(value?: boolean) => {
setOpen(value === undefined ? !open : value);
},
- [open]
+ [open],
);
const handleChange = React.useCallback(
toggleDropdown(false);
onChange?.(newValue, actionMeta);
},
- [toggleDropdown, onChange]
+ [toggleDropdown, onChange],
);
const handleLoadOptions = React.useCallback(
(query: string, callback: (options: OptionsOrGroups<Option, Group>) => void) => {
return query.length >= (minLength ?? 0) ? loadOptions?.(query, callback) : undefined;
},
- [minLength, loadOptions]
+ [minLength, loadOptions],
);
const debouncedLoadOptions = React.useRef(debounce(handleLoadOptions, DEBOUNCE_DELAY));
onInputChange?.(newValue, actionMeta);
return newValue;
},
- [onInputChange]
+ [onInputChange],
);
React.useEffect(() => {
onClick={() => {
toggleDropdown(true);
}}
+ placeholder={controlPlaceholder}
size={controlSize}
/>
</DropdownToggler>
isDiscreet?: boolean;
label?: React.ReactNode | string;
onClick: VoidFunction;
+ placeholder?: string;
size?: InputSizeKeys;
}
export function SearchSelectDropdownControl(props: SearchSelectDropdownControlProps) {
- const { className, disabled, label, isDiscreet, onClick, size = 'full', ariaLabel = '' } = props;
+ const {
+ className,
+ disabled,
+ placeholder,
+ label,
+ isDiscreet,
+ onClick,
+ size = 'full',
+ ariaLabel = '',
+ } = props;
return (
<StyledControl
aria-label={ariaLabel}
{
'is-disabled': disabled,
'is-placeholder': !label,
- }
+ },
)}
>
- <span className="sw-truncate">{label}</span>
+ <span className="sw-truncate">{label ?? placeholder}</span>
<ChevronDownIcon className="sw-ml-1" />
</InputValue>
</StyledControl>
'/project/activity',
'/code',
'/profiles/show',
+ '/profiles/compare',
'/project/extension/securityreport/securityreport',
'/projects',
'/project/information',
searchItemListWrapper: byRole('menu'),
searchItem: byRole('menuitem'),
showMoreButton: byRole('menuitem', { name: 'show_more' }),
- tooShortWarning: byText('select2.tooShort'),
+ tooShortWarning: byText('select2.tooShort.2'),
noResultTextABCD: byText('no_results_for_x.abcd'),
};
nameCreatePopupInput: byRole('textbox', { name: 'name field_required' }),
comparisonDiffTableHeading: (rulesQuantity: number, profileName: string) =>
byRole('columnheader', {
- name: `quality_profiles.x_rules_only_in.${rulesQuantity} ${profileName}`,
+ name: `quality_profiles.x_rules_only_in.${rulesQuantity}.${profileName}`,
}),
comparisonModifiedTableHeading: (rulesQuantity: number) =>
byRole('table', {
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Spinner } from 'design-system';
import * as React from 'react';
import { compareProfiles, CompareResponse } from '../../../api/quality-profiles';
import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
const { withKey } = location.query;
return (
- <div className="boxed-group boxed-group-inner js-profile-comparison">
- <ComparisonForm
- onCompare={this.handleCompare}
- profile={profile}
- profiles={profiles}
- withKey={withKey}
- />
+ <div className="sw-body-sm">
+ <div className="sw-flex sw-items-center">
+ <ComparisonForm
+ onCompare={this.handleCompare}
+ profile={profile}
+ profiles={profiles}
+ withKey={withKey}
+ />
- {this.state.loading && <i className="spinner spacer-left" />}
+ <Spinner className="sw-ml-2" loading={this.state.loading} />
+ </div>
{this.hasResults(this.state) && (
- <div className="spacer-top">
- <ComparisonResults
- inLeft={this.state.inLeft}
- inRight={this.state.inRight}
- left={this.state.left}
- leftProfile={profile}
- modified={this.state.modified}
- refresh={this.loadResults}
- right={this.state.right}
- rightProfile={profiles.find((p) => p.key === withKey)}
- />
- </div>
+ <ComparisonResults
+ inLeft={this.state.inLeft}
+ inRight={this.state.inRight}
+ left={this.state.left}
+ leftProfile={profile}
+ modified={this.state.modified}
+ refresh={this.loadResults}
+ right={this.state.right}
+ rightProfile={profiles.find((p) => p.key === withKey)}
+ />
)}
</div>
);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Badge, SearchSelectDropdown } from 'design-system';
import * as React from 'react';
-import { components, OptionProps, SingleValueProps } from 'react-select';
-import Select from '../../../components/controls/Select';
+import { useIntl } from 'react-intl';
+import { OptionProps, Options, components } from 'react-select';
import Tooltip from '../../../components/controls/Tooltip';
-import { translate } from '../../../helpers/l10n';
import { Profile } from '../types';
interface Props {
label: string;
isDefault: boolean | undefined;
}
-export default class ComparisonForm extends React.PureComponent<Props> {
- handleChange = (option: { value: string }) => {
- this.props.onCompare(option.value);
- };
- optionRenderer(
- options: Option[],
- props: OptionProps<Omit<Option, 'label' | 'isDefault'>, false>,
- ) {
- const { data } = props;
- return <components.Option {...props}>{renderValue(data, options)}</components.Option>;
- }
+export default function ComparisonForm(props: Readonly<Props>) {
+ const { profile, profiles, withKey } = props;
+ const intl = useIntl();
- singleValueRenderer = (
- options: Option[],
- props: SingleValueProps<Omit<Option, 'label' | 'isDefault'>, false>,
- ) => (
- <components.SingleValue {...props}>{renderValue(props.data, options)}</components.SingleValue>
- );
+ const options = profiles
+ .filter((p) => p.language === profile.language && p !== profile)
+ .map((p) => ({ value: p.key, label: p.name, isDefault: p.isDefault }));
+
+ const value = options.find((o) => o.value === withKey);
- render() {
- const { profile, profiles, withKey } = this.props;
- const options = profiles
- .filter((p) => p.language === profile.language && p !== profile)
- .map((p) => ({ value: p.key, label: p.name, isDefault: p.isDefault }));
+ const handleProfilesSearch = React.useCallback(
+ (query: string, cb: (options: Options<Option>) => void) => {
+ cb(options.filter((option) => option.label.toLowerCase().includes(query.toLowerCase())));
+ },
+ [options],
+ );
- return (
- <div>
- <label htmlFor="quality-profiles-comparison-input" className="spacer-right">
- {translate('quality_profiles.compare_with')}
- </label>
- <Select
- className="input-super-large"
- autoFocus
- isClearable={false}
- id="quality-profiles-comparision"
- inputId="quality-profiles-comparison-input"
- onChange={this.handleChange}
- options={options}
- isSearchable
- components={{
- Option: this.optionRenderer.bind(this, options),
- SingleValue: this.singleValueRenderer.bind(null, options),
- }}
- value={options.filter((o) => o.value === withKey)}
- />
- </div>
- );
- }
+ return (
+ <>
+ <span className="sw-mr-2">{intl.formatMessage({ id: 'quality_profiles.compare_with' })}</span>
+ <SearchSelectDropdown
+ placeholder=""
+ controlPlaceholder={intl.formatMessage({ id: 'select_verb' })}
+ controlLabel={value?.label}
+ controlAriaLabel={intl.formatMessage({ id: 'quality_profiles.compare_with' })}
+ options={options}
+ onChange={(option: Option) => props.onCompare(option.value)}
+ defaultOptions={options}
+ loadOptions={handleProfilesSearch}
+ components={{
+ Option: OptionRenderer,
+ }}
+ autoFocus
+ controlSize="medium"
+ value={options.find((o) => o.value === withKey)}
+ />
+ </>
+ );
}
-function renderValue(p: Omit<Option, 'label' | 'isDefault'>, options: Option[]) {
- const selectedOption = options.find((o) => o.value === p.value);
- if (selectedOption !== undefined) {
- return (
- <>
- <span>{selectedOption.label}</span>
- {selectedOption.isDefault && (
- <Tooltip overlay={translate('quality_profiles.list.default.help')}>
- <span className="spacer-left badge">{translate('default')}</span>
- </Tooltip>
- )}
- </>
- );
- }
+function OptionRenderer(props: Readonly<OptionProps<Option, false>>) {
+ const { isDefault, label } = props.data;
+ const intl = useIntl();
+
+ return (
+ <components.Option {...props}>
+ <span>{label}</span>
+ {isDefault && (
+ <Tooltip overlay={intl.formatMessage({ id: 'quality_profiles.list.default.help' })}>
+ <span>
+ <Badge className="sw-ml-1">{intl.formatMessage({ id: 'default' })}</Badge>
+ </span>
+ </Tooltip>
+ )}
+ </components.Option>
+ );
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ButtonSecondary, Spinner } from 'design-system';
import * as React from 'react';
+import { useIntl } from 'react-intl';
import { Profile } from '../../../api/quality-profiles';
import { getRuleDetails } from '../../../api/rules';
import Tooltip from '../../../components/controls/Tooltip';
-import { Button } from '../../../components/controls/buttons';
-import Spinner from '../../../components/ui/Spinner';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
import { RuleDetails } from '../../../types/types';
import ActivationFormModal from '../../coding-rules/components/ActivationFormModal';
ruleKey: string;
}
-interface State {
- rule?: RuleDetails;
- state: 'closed' | 'opening' | 'open';
-}
-
-export default class ComparisonResultActivation extends React.PureComponent<Props, State> {
- mounted = false;
- state: State = { state: 'closed' };
+export default function ComparisonResultActivation(props: React.PropsWithChildren<Props>) {
+ const { profile, ruleKey } = props;
+ const [state, setState] = React.useState<'closed' | 'opening' | 'open'>('closed');
+ const [rule, setRule] = React.useState<RuleDetails>();
+ const intl = useIntl();
- componentDidMount() {
- this.mounted = true;
- }
+ const isOpen = state === 'open' && rule;
- componentWillUnmount() {
- this.mounted = false;
- }
+ const activateRuleMsg = intl.formatMessage(
+ { id: 'quality_profiles.comparison.activate_rule' },
+ { profile: profile.name },
+ );
- handleButtonClick = () => {
- this.setState({ state: 'opening' });
- getRuleDetails({ key: this.props.ruleKey }).then(
+ const handleButtonClick = () => {
+ setState('opening');
+ getRuleDetails({ key: ruleKey }).then(
({ rule }) => {
- if (this.mounted) {
- this.setState({ rule, state: 'open' });
- }
+ setState('open');
+ setRule(rule);
},
() => {
- if (this.mounted) {
- this.setState({ state: 'closed' });
- }
+ setState('closed');
},
);
};
- handleCloseModal = () => {
- this.setState({ state: 'closed' });
- };
-
- isOpen(state: State): state is { state: 'open'; rule: RuleDetails } {
- return state.state === 'open';
- }
-
- render() {
- const { profile } = this.props;
-
- return (
- <Spinner loading={this.state.state === 'opening'}>
- <Tooltip
- placement="bottom"
- overlay={translateWithParameters(
- 'quality_profiles.comparison.activate_rule',
- profile.name,
- )}
+ return (
+ <Spinner loading={state === 'opening'}>
+ <Tooltip placement="bottom" overlay={activateRuleMsg}>
+ <ButtonSecondary
+ disabled={state !== 'closed'}
+ aria-label={activateRuleMsg}
+ onClick={handleButtonClick}
>
- <Button
- disabled={this.state.state !== 'closed'}
- aria-label={translateWithParameters(
- 'quality_profiles.comparison.activate_rule',
- profile.name,
- )}
- onClick={this.handleButtonClick}
- >
- {this.props.children}
- </Button>
- </Tooltip>
+ {intl.formatMessage({ id: 'activate' })}
+ </ButtonSecondary>
+ </Tooltip>
- {this.isOpen(this.state) && (
- <ActivationFormModal
- modalHeader={translate('coding_rules.activate_in_quality_profile')}
- onClose={this.handleCloseModal}
- onDone={this.props.onDone}
- profiles={[profile]}
- rule={this.state.rule}
- />
- )}
- </Spinner>
- );
- }
+ {isOpen && (
+ <ActivationFormModal
+ modalHeader={intl.formatMessage({ id: 'coding_rules.activate_in_quality_profile' })}
+ onClose={() => {
+ setState('closed');
+ }}
+ onDone={props.onDone}
+ profiles={[profile]}
+ rule={rule}
+ />
+ )}
+ </Spinner>
+ );
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import classNames from 'classnames';
+import { ActionCell, ContentCell, Link, Table, TableRowInteractive } from 'design-system';
import * as React from 'react';
+import { useIntl } from 'react-intl';
import { CompareResponse, Profile } from '../../../api/quality-profiles';
-import Link from '../../../components/common/Link';
-import ChevronLeftIcon from '../../../components/icons/ChevronLeftIcon';
-import ChevronRightIcon from '../../../components/icons/ChevronRightIcon';
-import SeverityIcon from '../../../components/icons/SeverityIcon';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getRulesUrl } from '../../../helpers/urls';
import { Dict } from '../../../types/types';
import ComparisonResultActivation from './ComparisonResultActivation';
rightProfile?: Profile;
}
-export default class ComparisonResults extends React.PureComponent<Props> {
- canActivate(profile: Profile) {
- return !profile.isBuiltIn && profile.actions && profile.actions.edit;
- }
+export default function ComparisonResults(props: Readonly<Props>) {
+ const { leftProfile, rightProfile, inLeft, left, right, inRight, modified } = props;
- renderRule(rule: { key: string; name: string }, severity: string) {
+ const intl = useIntl();
+
+ const emptyComparison = !inLeft.length && !inRight.length && !modified.length;
+
+ const canActivate = (profile: Profile) =>
+ !profile.isBuiltIn && profile.actions && profile.actions.edit;
+
+ const renderRule = React.useCallback((rule: { key: string; name: string }) => {
return (
<div>
- <SeverityIcon severity={severity} />{' '}
- <Link to={getRulesUrl({ rule_key: rule.key, open: rule.key })}>{rule.name}</Link>
+ <Link className="sw-ml-1" to={getRulesUrl({ rule_key: rule.key, open: rule.key })}>
+ {rule.name}
+ </Link>
</div>
);
- }
+ }, []);
- renderParameters(params: Params) {
+ const renderParameters = React.useCallback((params: Params) => {
if (!params) {
return null;
}
return (
<ul>
{Object.keys(params).map((key) => (
- <li className="spacer-top break-word" key={key}>
- <code>
+ <li className="sw-mt-2 sw-break-all" key={key}>
+ <code className="sw-code">
{key}
{': '}
{params[key]}
))}
</ul>
);
- }
+ }, []);
- renderLeft() {
- if (this.props.inLeft.length === 0) {
+ const renderLeft = () => {
+ if (inLeft.length === 0) {
return null;
}
- const renderSecondColumn = this.props.rightProfile && this.canActivate(this.props.rightProfile);
+ const renderSecondColumn = rightProfile && canActivate(rightProfile);
return (
- <table className="data fixed zebra">
- <thead>
- <tr>
- <th>
- {translateWithParameters(
- 'quality_profiles.x_rules_only_in',
- this.props.inLeft.length,
- )}{' '}
- {this.props.left.name}
- </th>
- {renderSecondColumn && <th aria-label={translate('actions')}> </th>}
- </tr>
- </thead>
- <tbody>
- {this.props.inLeft.map((rule) => (
- <tr key={`left-${rule.key}`}>
- <td>{this.renderRule(rule, rule.severity)}</td>
- {renderSecondColumn && (
- <td>
- <ComparisonResultActivation
- key={rule.key}
- onDone={this.props.refresh}
- profile={this.props.rightProfile as Profile}
- ruleKey={rule.key}
- >
- <ChevronRightIcon />
- </ComparisonResultActivation>
- </td>
+ <Table
+ columnCount={2}
+ columnWidths={['50%', 'auto']}
+ noSidePadding
+ header={
+ <TableRowInteractive>
+ <ContentCell>
+ {intl.formatMessage(
+ {
+ id: 'quality_profiles.x_rules_only_in',
+ },
+ { count: inLeft.length, profile: left.name },
)}
- </tr>
- ))}
- </tbody>
- </table>
+ </ContentCell>
+ {renderSecondColumn && (
+ <ContentCell aria-label={intl.formatMessage({ id: 'actions' })}> </ContentCell>
+ )}
+ </TableRowInteractive>
+ }
+ >
+ {inLeft.map((rule) => (
+ <TableRowInteractive key={`left-${rule.key}`}>
+ <ContentCell>{renderRule(rule)}</ContentCell>
+ {renderSecondColumn && (
+ <ContentCell className="sw-px-0">
+ <ComparisonResultActivation
+ key={rule.key}
+ onDone={props.refresh}
+ profile={rightProfile}
+ ruleKey={rule.key}
+ />
+ </ContentCell>
+ )}
+ </TableRowInteractive>
+ ))}
+ </Table>
);
- }
+ };
- renderRight() {
- if (this.props.inRight.length === 0) {
+ const renderRight = () => {
+ if (inRight.length === 0) {
return null;
}
- const renderFirstColumn = this.props.leftProfile && this.canActivate(this.props.leftProfile);
+ const renderFirstColumn = leftProfile && canActivate(leftProfile);
return (
- <table
- className={classNames('data fixed zebra quality-profile-compare-right-table', {
- 'has-first-column': renderFirstColumn,
- })}
- >
- <thead>
- <tr>
- {renderFirstColumn && <th aria-label={translate('actions')}> </th>}
- <th>
- {translateWithParameters(
- 'quality_profiles.x_rules_only_in',
- this.props.inRight.length,
- )}{' '}
- {this.props.right.name}
- </th>
- </tr>
- </thead>
- <tbody>
- {this.props.inRight.map((rule) => (
- <tr key={`right-${rule.key}`}>
- {renderFirstColumn && (
- <td className="text-right">
- <ComparisonResultActivation
- key={rule.key}
- onDone={this.props.refresh}
- profile={this.props.leftProfile}
- ruleKey={rule.key}
- >
- <ChevronLeftIcon />
- </ComparisonResultActivation>
- </td>
+ <Table
+ columnCount={2}
+ columnWidths={['50%', 'auto']}
+ noSidePadding
+ header={
+ <TableRowInteractive>
+ {renderFirstColumn && (
+ <ContentCell aria-label={intl.formatMessage({ id: 'actions' })}> </ContentCell>
+ )}
+ <ContentCell className="sw-pl-4">
+ {intl.formatMessage(
+ {
+ id: 'quality_profiles.x_rules_only_in',
+ },
+ { count: inRight.length, profile: right.name },
)}
- <td>{this.renderRule(rule, rule.severity)}</td>
- </tr>
- ))}
- </tbody>
- </table>
+ </ContentCell>
+ </TableRowInteractive>
+ }
+ >
+ {inRight.map((rule) => (
+ <TableRowInteractive key={`right-${rule.key}`}>
+ {renderFirstColumn && (
+ <ActionCell className="sw-px-0">
+ <ComparisonResultActivation
+ key={rule.key}
+ onDone={props.refresh}
+ profile={leftProfile}
+ ruleKey={rule.key}
+ />
+ </ActionCell>
+ )}
+ <ContentCell className="sw-pl-4">{renderRule(rule)}</ContentCell>
+ </TableRowInteractive>
+ ))}
+ </Table>
);
- }
+ };
- renderModified() {
- if (this.props.modified.length === 0) {
+ const renderModified = () => {
+ if (modified.length === 0) {
return null;
}
- return (
- <table className="data fixed zebra zebra-inversed">
- <caption>
- {translateWithParameters(
- 'quality_profiles.x_rules_have_different_configuration',
- this.props.modified.length,
- )}
- </caption>
- <thead>
- <tr>
- <th>{this.props.left.name}</th>
- <th>{this.props.right.name}</th>
- </tr>
- </thead>
- <tbody>
- {this.props.modified.map((rule) => (
- <tr key={`modified-${rule.key}`}>
- <td>
- {this.renderRule(rule, rule.left.severity)}
- {this.renderParameters(rule.left.params)}
- </td>
- <td>
- {this.renderRule(rule, rule.right.severity)}
- {this.renderParameters(rule.right.params)}
- </td>
- </tr>
- ))}
- </tbody>
- </table>
- );
- }
-
- render() {
- if (!this.props.inLeft.length && !this.props.inRight.length && !this.props.modified.length) {
- return <div className="big-spacer-top">{translate('quality_profile.empty_comparison')}</div>;
- }
return (
- <>
- {this.renderLeft()}
- {this.renderRight()}
- {this.renderModified()}
- </>
+ <Table
+ columnCount={2}
+ columnWidths={['50%', 'auto']}
+ noSidePadding
+ header={
+ <TableRowInteractive>
+ <ContentCell>{left.name}</ContentCell>
+ <ContentCell className="sw-pl-4">{right.name}</ContentCell>
+ </TableRowInteractive>
+ }
+ caption={
+ <>
+ {intl.formatMessage(
+ { id: 'quality_profiles.x_rules_have_different_configuration' },
+ { count: modified.length },
+ )}
+ </>
+ }
+ >
+ {modified.map((rule) => (
+ <TableRowInteractive key={`modified-${rule.key}`}>
+ <ContentCell>
+ <div>
+ {renderRule(rule)}
+ {renderParameters(rule.left.params)}
+ </div>
+ </ContentCell>
+ <ContentCell className="sw-pl-4">
+ <div>
+ {renderRule(rule)}
+ {renderParameters(rule.right.params)}
+ </div>
+ </ContentCell>
+ </TableRowInteractive>
+ ))}
+ </Table>
);
- }
+ };
+
+ return (
+ <div className="sw-mt-4">
+ {emptyComparison ? (
+ intl.formatMessage({ id: 'quality_profile.empty_comparison' })
+ ) : (
+ <>
+ {renderLeft()}
+ {renderRight()}
+ {renderModified()}
+ </>
+ )}
+ </div>
+ );
}
<div className={classNames('sw-flex sw-justify-between', className)}>
<header className="sw-flex-1">
<AdminPageTitle className="sw-heading-lg sw-pb-4">{title}</AdminPageTitle>
- <AdminPageDescription className="sw-body-sm sw-pb-12 sw-max-w-9/12">
- {description}
- </AdminPageDescription>
+ {description && (
+ <AdminPageDescription className="sw-body-sm sw-pb-12 sw-max-w-9/12">
+ {description}
+ </AdminPageDescription>
+ )}
</header>
{children && <div className="sw-flex sw-gap-2">{children}</div>}
</div>
quality_profiles.x_sonarway_missing_rules={linkCount} Sonar way {count, plural, one {rule} other {rules}} not included
quality_profiles.parent=Parent:
quality_profiles.parameter_set_to=Parameter {0} set to {1}
-quality_profiles.x_rules_only_in={0} rules only in
-quality_profiles.x_rules_have_different_configuration={0} rules have a different configuration
+quality_profiles.x_rules_only_in={count} rules only in {profile}
+quality_profiles.x_rules_have_different_configuration={count} rules have a different configuration
quality_profiles.copy_x_title=Copy Profile "{0}" - {1}
quality_profiles.extend_x_title=Extend Profile "{0}" - {1}
quality_profiles.rename_x_title=Rename Profile {0} - {1}
quality_profiles.activate_more=Activate More
quality_profiles.activate_more.help.built_in=This quality profile is built in, and cannot be updated manually. If you want to activate more rules, create a new profile that inherits from this one and add rules there.
quality_profiles.activate_more_rules=Activate More Rules
-quality_profiles.comparison.activate_rule=Activate rule for profile "{0}"
+quality_profiles.comparison.activate_rule=Activate rule for profile "{profile}"
quality_profiles.intro1=Quality profiles are collections of rules to apply during an analysis.
quality_profiles.intro2=For each language there is a default profile. All projects not explicitly assigned to some other profile will be analyzed with the default. Ideally, all projects will use the same profile for a language.
quality_profiles.list.projects=Projects