"eslint-plugin-import": "2.28.1",
"eslint-plugin-local-rules": "2.0.0",
"eslint-plugin-typescript-sort-keys": "2.3.0",
- "highlight.js": "11.8.0",
- "highlightjs-apex": "1.2.0",
- "highlightjs-sap-abap": "0.3.0",
"history": "5.3.0",
"jest": "29.6.4",
"postcss": "8.4.29",
"config": "../tailwind.config.js",
"preset": "emotion"
}
+ },
+ "dependencies": {
+ "highlight.js": "11.8.0",
+ "highlightjs-apex": "1.2.0",
+ "highlightjs-cobol": "0.3.3",
+ "highlightjs-sap-abap": "0.3.0"
}
}
import classNames from 'classnames';
import hljs, { HighlightResult } from 'highlight.js';
import apex from 'highlightjs-apex';
+import cobol from 'highlightjs-cobol';
import abap from 'highlightjs-sap-abap';
import tw from 'twin.macro';
import { themeColor, themeContrast } from '../helpers/theme';
hljs.registerLanguage('abap', abap);
hljs.registerLanguage('apex', apex);
+hljs.registerLanguage('cobol', cobol);
hljs.registerAliases('azureresourcemanager', { languageName: 'json' });
hljs.registerAliases('flex', { languageName: 'actionscript' });
let highlightedCode: HighlightResult;
try {
+ const actualLanguage =
+ language !== undefined && hljs.getLanguage(language) ? language : 'plaintext';
+
highlightedCode = hljs.highlight(unescapedCode, {
ignoreIllegals: true,
- language: language ?? 'plaintext',
+ language: actualLanguage,
});
} catch {
highlightedCode = hljs.highlight(unescapedCode, {
codeBlock,
// Use a function to avoid triggering special replacement patterns
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_the_replacement
- () => `<${tag}${attributes}>${highlightedCode.value}</${tag}>`
+ () => `<${tag}${attributes}>${highlightedCode.value}</${tag}>`,
);
});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { useTheme } from '@emotion/react';
+import { themeColor } from '../../helpers/theme';
+import { CustomIcon, IconProps } from './Icon';
+
+export function InheritanceIcon({ fill = 'currentColor', ...iconProps }: Readonly<IconProps>) {
+ const theme = useTheme();
+ const fillColor = themeColor(fill)({ theme });
+ return (
+ <CustomIcon {...iconProps}>
+ <mask fill="white" id="path-1-inside-1_3266_8058">
+ <rect height="6" rx="0.5" width="6" x="1" y="1" />
+ </mask>
+ <rect
+ height="6"
+ mask="url(#path-1-inside-1_3266_8058)"
+ rx="0.5"
+ stroke={fillColor}
+ strokeWidth="3"
+ width="6"
+ x="1"
+ y="1"
+ />
+ <mask fill="white" id="path-2-inside-2_3266_8058">
+ <rect height="6" rx="0.5" width="6" x="9" y="9" />
+ </mask>
+ <rect
+ height="6"
+ mask="url(#path-2-inside-2_3266_8058)"
+ rx="0.5"
+ stroke={fillColor}
+ strokeWidth="3"
+ width="6"
+ x="9"
+ y="9"
+ />
+ <path d="M4 7V11C4 11.5523 4.44772 12 5 12H9" stroke={fillColor} strokeWidth="1.5" />
+ </CustomIcon>
+ );
+}
export { HomeFillIcon } from './HomeFillIcon';
export { HomeIcon } from './HomeIcon';
export * from './Icon';
+export { InheritanceIcon } from './InheritanceIcon';
export { IssueLocationIcon } from './IssueLocationIcon';
export { LinkIcon } from './LinkIcon';
export { LockIcon } from './LockIcon';
* 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 } from 'design-system';
import * as React from 'react';
import { Profile as BaseProfile } from '../../../api/quality-profiles';
-import { Button } from '../../../components/controls/buttons';
import { Rule, RuleActivation, RuleDetails } from '../../../types/types';
import ActivationFormModal from './ActivationFormModal';
return (
<>
- <Button
+ <ButtonSecondary
aria-label={ariaLabel}
className={className}
id="coding-rules-quality-profile-activate"
onClick={() => setModalOpen(true)}
>
{buttonText}
- </Button>
+ </ButtonSecondary>
{modalOpen && (
<ActivationFormModal
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { DangerButtonPrimary, Modal } from 'design-system';
import * as React from 'react';
-import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
-import SimpleModal from '../../../components/controls/SimpleModal';
import { translate } from '../../../helpers/l10n';
interface Props {
}
export default function RemoveExtendedDescriptionModal({ onCancel, onSubmit }: Props) {
+ const [submitting, setSubmitting] = React.useState(false);
const header = translate('coding_rules.remove_extended_description');
- return (
- <SimpleModal header={header} onClose={onCancel} onSubmit={onSubmit}>
- {({ onCloseClick, onFormSubmit, submitting }) => (
- <form onSubmit={onFormSubmit}>
- <header className="modal-head">
- <h2>{header}</h2>
- </header>
- <div className="modal-body">
- {translate('coding_rules.remove_extended_description.confirm')}
- </div>
+ const handleClick = React.useCallback(() => {
+ setSubmitting(true);
+ onSubmit();
+ }, [onSubmit]);
- <footer className="modal-foot">
- {submitting && <i className="spinner spacer-right" />}
- <SubmitButton className="button-red" disabled={submitting}>
- {translate('remove')}
- </SubmitButton>
- <ResetButtonLink onClick={onCloseClick}>{translate('cancel')}</ResetButtonLink>
- </footer>
- </form>
- )}
- </SimpleModal>
+ return (
+ <Modal
+ headerTitle={header}
+ body={translate('coding_rules.remove_extended_description.confirm')}
+ onClose={onCancel}
+ primaryButton={
+ <DangerButtonPrimary disabled={submitting} onClick={handleClick}>
+ {translate('remove')}
+ </DangerButtonPrimary>
+ }
+ loading={submitting}
+ secondaryButtonLabel={translate('cancel')}
+ />
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { SubHeadingHighlight } from 'design-system/lib';
import * as React from 'react';
import { Profile } from '../../../api/quality-profiles';
import { deleteRule, getRuleDetails, updateRule } from '../../../api/rules';
import ConfirmButton from '../../../components/controls/ConfirmButton';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import { Button } from '../../../components/controls/buttons';
+import DateFormatter from '../../../components/intl/DateFormatter';
import Spinner from '../../../components/ui/Spinner';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Dict, RuleActivation, RuleDetails as TypeRuleDetails } from '../../../types/types';
{!ruleDetails.isTemplate && ruleDetails.type !== 'SECURITY_HOTSPOT' && (
<RuleDetailsIssues ruleDetails={ruleDetails} />
)}
+
+ <div className="sw-mb-8" data-meta="available-since">
+ <SubHeadingHighlight as="h3">
+ {translate('coding_rules.available_since')}
+ </SubHeadingHighlight>
+ <DateFormatter date={ruleDetails.createdAt} />
+ </div>
</Spinner>
</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 {
+ ButtonSecondary,
+ ContentCell,
+ DangerButtonSecondary,
+ HeadingDark,
+ Link,
+ Spinner,
+ Table,
+ TableRow,
+ UnorderedList,
+} from 'design-system';
import { sortBy } from 'lodash';
import * as React from 'react';
import { deleteRule, searchRules } from '../../../api/rules';
-import Link from '../../../components/common/Link';
import ConfirmButton from '../../../components/controls/ConfirmButton';
-import { Button } from '../../../components/controls/buttons';
-import SeverityHelper from '../../../components/shared/SeverityHelper';
-import Spinner from '../../../components/ui/Spinner';
+import IssueSeverityIcon from '../../../components/icon-mappers/IssueSeverityIcon';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getRuleUrl } from '../../../helpers/urls';
+import { IssueSeverity } from '../../../types/issues';
import { Rule, RuleDetails } from '../../../types/types';
import CustomRuleButton from './CustomRuleButton';
rules?: Rule[];
}
+const COLUMN_COUNT = 3;
+const COLUMN_COUNT_WITH_EDIT_PERMISSIONS = 4;
+
export default class RuleDetailsCustomRules extends React.PureComponent<Props, State> {
mounted = false;
state: State = { loading: false };
};
renderRule = (rule: Rule) => (
- <tr data-rule={rule.key} key={rule.key}>
- <td className="coding-rules-detail-list-name">
+ <TableRow data-rule={rule.key} key={rule.key}>
+ <ContentCell>
<Link to={getRuleUrl(rule.key)}>{rule.name}</Link>
- </td>
-
- <td className="coding-rules-detail-list-severity">
- <SeverityHelper className="display-flex-center" severity={rule.severity} />
- </td>
-
- <td className="coding-rules-detail-list-parameters">
- {rule.params &&
- rule.params
- .filter((param) => param.defaultValue)
+ </ContentCell>
+
+ <ContentCell>
+ <IssueSeverityIcon
+ className="sw-mr-1"
+ severity={rule.severity as IssueSeverity}
+ aria-hidden
+ />
+ {translate('severity', rule.severity)}
+ </ContentCell>
+
+ <ContentCell>
+ <UnorderedList className="sw-mt-0">
+ {rule.params
+ ?.filter((param) => param.defaultValue)
.map((param) => (
- <div className="coding-rules-detail-list-parameter" key={param.key}>
- <span className="key">{param.key}</span>
- <span className="sep">: </span>
- <span className="value" title={param.defaultValue}>
- {param.defaultValue}
- </span>
- </div>
+ <li key={param.key}>
+ <span className="sw-font-semibold">{param.key}</span>
+ <span>: </span>
+ <span title={param.defaultValue}>{param.defaultValue}</span>
+ </li>
))}
- </td>
+ </UnorderedList>
+ </ContentCell>
{this.props.canChange && (
- <td className="coding-rules-detail-list-actions">
+ <ContentCell>
<ConfirmButton
confirmButtonText={translate('delete')}
confirmData={rule.key}
onConfirm={this.handleRuleDelete}
>
{({ onClick }) => (
- <Button
- className="button-red js-delete-custom-rule"
+ <DangerButtonSecondary
+ className="js-delete-custom-rule"
aria-label={translateWithParameters('coding_rules.delete_rule_x', rule.name)}
onClick={onClick}
>
{translate('delete')}
- </Button>
+ </DangerButtonSecondary>
)}
</ConfirmButton>
- </td>
+ </ContentCell>
)}
- </tr>
+ </TableRow>
);
render() {
const { loading, rules = [] } = this.state;
return (
- <div className="js-rule-custom-rules coding-rule-section">
- <div className="coding-rules-detail-custom-rules-section">
- <h2 className="coding-rules-detail-title">{translate('coding_rules.custom_rules')}</h2>
+ <div className="js-rule-custom-rules">
+ <div>
+ <HeadingDark as="h2">{translate('coding_rules.custom_rules')}</HeadingDark>
{this.props.canChange && (
<CustomRuleButton onDone={this.handleRuleCreate} templateRule={this.props.ruleDetails}>
{({ onClick }) => (
- <Button className="js-create-custom-rule spacer-left" onClick={onClick}>
+ <ButtonSecondary className="js-create-custom-rule sw-mt-6" onClick={onClick}>
{translate('coding_rules.create')}
- </Button>
+ </ButtonSecondary>
)}
</CustomRuleButton>
)}
- <Spinner className="spacer-left" loading={loading}>
+ <Spinner className="sw-my-6" loading={loading}>
{rules.length > 0 && (
- <table className="coding-rules-detail-list" id="coding-rules-detail-custom-rules">
- <tbody>{sortBy(rules, (rule) => rule.name).map(this.renderRule)}</tbody>
- </table>
+ <Table
+ className="sw-my-6"
+ id="coding-rules-detail-custom-rules"
+ columnCount={
+ this.props.canChange ? COLUMN_COUNT_WITH_EDIT_PERMISSIONS : COLUMN_COUNT
+ }
+ >
+ {sortBy(rules, (rule) => rule.name).map(this.renderRule)}
+ </Table>
)}
</Spinner>
</div>
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { CodeSyntaxHighlighter } from 'design-system';
+import {
+ ButtonPrimary,
+ ButtonSecondary,
+ CodeSyntaxHighlighter,
+ DangerButtonSecondary,
+ InputTextArea,
+ Spinner,
+} from 'design-system';
import * as React from 'react';
import { updateRule } from '../../../api/rules';
import FormattingTips from '../../../components/common/FormattingTips';
-import { Button, ResetButtonLink } from '../../../components/controls/buttons';
import RuleTabViewer from '../../../components/rules/RuleTabViewer';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { sanitizeString, sanitizeUserInput } from '../../../helpers/sanitize';
this.setState({ descriptionForm: false });
};
- handleSaveClick = () => {
+ handleSaveClick = (event: React.SyntheticEvent<HTMLFormElement>) => {
+ event.preventDefault();
this.updateDescription(this.state.description);
};
<div id="coding-rules-detail-description-extra">
{this.props.ruleDetails.htmlNote !== undefined && (
<CodeSyntaxHighlighter
- className="rule-desc markdown sw-mb-2"
+ className="markdown sw-my-6"
htmlAsString={sanitizeUserInput(this.props.ruleDetails.htmlNote)}
language={this.props.ruleDetails.lang}
/>
)}
- {this.props.canWrite && (
- <Button
- id="coding-rules-detail-extend-description"
- onClick={this.handleExtendDescriptionClick}
- >
- {translate('coding_rules.extend_description')}
- </Button>
- )}
+ <div className="sw-my-6">
+ {this.props.canWrite && (
+ <ButtonSecondary onClick={this.handleExtendDescriptionClick}>
+ {translate('coding_rules.extend_description')}
+ </ButtonSecondary>
+ )}
+ </div>
</div>
);
renderForm = () => (
- <div className="coding-rules-detail-extend-description-form">
- <table className="width-100">
- <tbody>
- <tr>
- <td colSpan={2}>
- <textarea
- autoFocus
- aria-label={translate('coding_rules.extend_description')}
- className="width-100 little-spacer-bottom"
- id="coding-rules-detail-extend-description-text"
- onChange={this.handleDescriptionChange}
- rows={4}
- value={this.state.description}
- />
- </td>
- </tr>
-
- <tr>
- <td>
- <Button
+ <form
+ aria-label={translate('coding_rules.detail.extend_description.form')}
+ className="sw-my-6"
+ onSubmit={this.handleSaveClick}
+ >
+ <InputTextArea
+ aria-label={translate('coding_rules.extend_description')}
+ className="sw-mb-2 sw-resize-y"
+ id="coding-rules-detail-extend-description-text"
+ size="full"
+ onChange={this.handleDescriptionChange}
+ rows={4}
+ value={this.state.description}
+ />
+
+ <div className="sw-flex sw-items-center sw-justify-between">
+ <div className="sw-flex sw-items-center">
+ <ButtonPrimary
+ id="coding-rules-detail-extend-description-submit"
+ disabled={this.state.submitting}
+ type="submit"
+ >
+ {translate('save')}
+ </ButtonPrimary>
+
+ {this.props.ruleDetails.mdNote !== undefined && (
+ <>
+ <DangerButtonSecondary
+ className="sw-ml-2"
disabled={this.state.submitting}
- id="coding-rules-detail-extend-description-submit"
- onClick={this.handleSaveClick}
+ id="coding-rules-detail-extend-description-remove"
+ onClick={this.handleRemoveDescriptionClick}
>
- {translate('save')}
- </Button>
-
- {this.props.ruleDetails.mdNote !== undefined && (
- <>
- <Button
- className="button-red spacer-left"
- disabled={this.state.submitting}
- id="coding-rules-detail-extend-description-remove"
- onClick={this.handleRemoveDescriptionClick}
- >
- {translate('remove')}
- </Button>
- {this.state.removeDescriptionModal && (
- <RemoveExtendedDescriptionModal
- onCancel={this.handleCancelRemoving}
- onSubmit={this.handleConfirmRemoving}
- />
- )}
- </>
+ {translate('remove')}
+ </DangerButtonSecondary>
+ {this.state.removeDescriptionModal && (
+ <RemoveExtendedDescriptionModal
+ onCancel={this.handleCancelRemoving}
+ onSubmit={this.handleConfirmRemoving}
+ />
)}
-
- <ResetButtonLink
- className="spacer-left"
- disabled={this.state.submitting}
- id="coding-rules-detail-extend-description-cancel"
- onClick={this.handleCancelClick}
- >
- {translate('cancel')}
- </ResetButtonLink>
- {this.state.submitting && <i className="spinner spacer-left" />}
- </td>
-
- <td className="text-right">
- <FormattingTips />
- </td>
- </tr>
- </tbody>
- </table>
- </div>
+ </>
+ )}
+
+ <ButtonSecondary
+ className="sw-ml-2"
+ disabled={this.state.submitting}
+ id="coding-rules-detail-extend-description-cancel"
+ onClick={this.handleCancelClick}
+ >
+ {translate('cancel')}
+ </ButtonSecondary>
+
+ <Spinner className="sw-ml-2" loading={this.state.submitting} />
+ </div>
+
+ <FormattingTips />
+ </div>
+ </form>
);
render() {
)}
{!ruleDetails.templateKey && (
- <div className="coding-rules-detail-description coding-rules-detail-description-extra">
+ <div className="sw-mt-6">
{!this.state.descriptionForm && this.renderExtendedDescription()}
{this.state.descriptionForm && this.props.canWrite && this.renderForm()}
</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 { ContentCell, Link, Spinner, SubHeadingHighlight, Table, TableRow } from 'design-system';
import * as React from 'react';
import { getFacet } from '../../../api/issues';
import withAvailableFeatures, {
WithAvailableFeaturesProps,
} from '../../../app/components/available-features/withAvailableFeatures';
-import Link from '../../../components/common/Link';
import Tooltip from '../../../components/controls/Tooltip';
-import Spinner from '../../../components/ui/Spinner';
import { translate } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
import { getIssuesUrl } from '../../../helpers/urls';
const path = getIssuesUrl({ resolved: 'false', rules: key, projects: project.key });
return (
- <tr key={project.key}>
- <td className="coding-rules-detail-list-name">{project.name}</td>
- <td className="coding-rules-detail-list-parameters">
+ <TableRow key={project.key}>
+ <ContentCell>{project.name}</ContentCell>
+ <ContentCell>
<Link to={path}>{formatMeasure(project.count, MetricType.Integer)}</Link>
- </td>
- </tr>
+ </ContentCell>
+ </TableRow>
);
};
const { loading, projects = [] } = this.state;
return (
- <div className="js-rule-issues coding-rule-section">
+ <div className="sw-mb-8">
<Spinner loading={loading}>
- <h2 className="coding-rules-detail-title">
+ <SubHeadingHighlight as="h2">
{translate('coding_rules.issues')}
{this.renderTotal()}
- </h2>
+ </SubHeadingHighlight>
{projects.length > 0 ? (
- <table className="coding-rules-detail-list coding-rules-most-violated-projects">
- <tbody>
- <tr>
- <td className="coding-rules-detail-list-name" colSpan={2}>
+ <Table
+ className="sw-mt-6"
+ columnCount={2}
+ header={
+ <TableRow>
+ <ContentCell colSpan={2}>
{translate('coding_rules.most_violating_projects')}
- </td>
- </tr>
- {projects.map(this.renderProject)}
- </tbody>
- </table>
+ </ContentCell>
+ </TableRow>
+ }
+ >
+ {projects.map(this.renderProject)}
+ </Table>
) : (
- <div className="big-padded-bottom">
+ <div className="sw-mb-6">
{translate('coding_rules.no_issue_detected_for_projects')}
</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 { CellComponent, Note, SubHeadingHighlight, Table, TableRow } from 'design-system';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
import { sanitizeString } from '../../../helpers/sanitize';
export default function RuleDetailsParameters({ params }: Props) {
return (
<div className="js-rule-parameters">
- <h3 className="coding-rules-detail-title">{translate('coding_rules.parameters')}</h3>
- <table className="coding-rules-detail-parameters">
- <tbody>
- {params.map((param) => (
- <tr className="coding-rules-detail-parameter" key={param.key}>
- <td className="coding-rules-detail-parameter-name">{param.key}</td>
- <td className="coding-rules-detail-parameter-description">
+ <SubHeadingHighlight as="h3">{translate('coding_rules.parameters')}</SubHeadingHighlight>
+ <Table className="sw-my-4" columnCount={2} columnWidths={[0, 'auto']}>
+ {params.map((param) => (
+ <TableRow key={param.key}>
+ <CellComponent className="sw-align-top sw-font-semibold">{param.key}</CellComponent>
+ <CellComponent>
+ <div className="sw-flex sw-flex-col sw-gap-2">
{param.htmlDesc !== undefined && (
- <p
+ <div
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: sanitizeString(param.htmlDesc) }}
/>
)}
{param.defaultValue !== undefined && (
- <div className="note spacer-top">
+ <Note as="div">
{translate('coding_rules.parameters.default_value')}
<br />
<span className="coding-rules-detail-parameter-value">
{param.defaultValue}
</span>
- </div>
+ </Note>
)}
- </td>
- </tr>
- ))}
- </tbody>
- </table>
+ </div>
+ </CellComponent>
+ </TableRow>
+ ))}
+ </Table>
</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 styled from '@emotion/styled';
+import {
+ ActionCell,
+ CellComponent,
+ ContentCell,
+ DangerButtonSecondary,
+ DiscreetLink,
+ InheritanceIcon,
+ Link,
+ Note,
+ SubHeadingHighlight,
+ Table,
+ TableRowInteractive,
+} from 'design-system';
import { filter } from 'lodash';
import * as React from 'react';
-import { activateRule, deactivateRule, Profile } from '../../../api/quality-profiles';
-import InstanceMessage from '../../../components/common/InstanceMessage';
-import Link from '../../../components/common/Link';
-import { Button } from '../../../components/controls/buttons';
+import { FormattedMessage } from 'react-intl';
+import { Profile, activateRule, deactivateRule } from '../../../api/quality-profiles';
import ConfirmButton from '../../../components/controls/ConfirmButton';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getQualityProfileUrl } from '../../../helpers/urls';
import { Dict, RuleActivation, RuleDetails } from '../../../types/types';
import BuiltInQualityProfileBadge from '../../quality-profiles/components/BuiltInQualityProfileBadge';
import ActivationButton from './ActivationButton';
-import RuleInheritanceIcon from './RuleInheritanceIcon';
interface Props {
activations: RuleActivation[] | undefined;
ruleDetails: RuleDetails;
}
+const COLUMN_COUNT_WITH_PARAMS = 3;
+const COLUMN_COUNT_WITHOUT_PARAMS = 2;
+
+const PROFILES_HEADING_ID = 'rule-details-profiles-heading';
+
export default class RuleDetailsProfiles extends React.PureComponent<Props> {
handleActivate = () => this.props.onActivate();
}
const profilePath = getQualityProfileUrl(profile.parentName, profile.language);
return (
- <div className="coding-rules-detail-quality-profile-inheritance">
- {(activation.inherit === 'OVERRIDES' || activation.inherit === 'INHERITED') && (
- <>
- <RuleInheritanceIcon className="text-middle" inheritance={activation.inherit} />
- <Link className="little-spacer-left text-middle" to={profilePath}>
- {profile.parentName}
- </Link>
- </>
- )}
- </div>
+ (activation.inherit === 'OVERRIDES' || activation.inherit === 'INHERITED') && (
+ <Note as="div" className="sw-flex sw-items-center sw-mt-2">
+ <InheritanceIcon
+ fill={activation.inherit === 'OVERRIDES' ? 'destructiveIconFocus' : 'currentColor'}
+ />
+ <DiscreetLink className="sw-ml-1" to={profilePath}>
+ {profile.parentName}
+ </DiscreetLink>
+ </Note>
+ )
);
};
const originalValue = originalParam?.value;
return (
- <div className="coding-rules-detail-quality-profile-parameter" key={param.key}>
+ <StyledParameter className="sw-my-4" key={param.key}>
<span className="key">{param.key}</span>
- <span className="sep">: </span>
+ <span className="sep sw-mr-1">: </span>
<span className="value" title={param.value}>
{param.value}
</span>
{parentActivation && param.value !== originalValue && (
- <div className="coding-rules-detail-quality-profile-inheritance">
- {translate('coding_rules.original')} <span className="value">{originalValue}</span>
+ <div className="sw-flex sw-ml-4">
+ {translate('coding_rules.original')}
+ <span className="value sw-ml-1" title={originalValue}>
+ {originalValue}
+ </span>
</div>
)}
- </div>
+ </StyledParameter>
);
};
renderParameters = (activation: RuleActivation, parentActivation?: RuleActivation) => (
- <td className="coding-rules-detail-quality-profile-parameters">
+ <CellComponent>
{activation.params.map((param) => this.renderParameter(param, parentActivation))}
- </td>
+ </CellComponent>
);
renderActions = (activation: RuleActivation, profile: Profile) => {
const { ruleDetails } = this.props;
const hasParent = activation.inherit !== 'NONE' && profile.parentKey;
return (
- <td className="coding-rules-detail-quality-profile-actions">
+ <ActionCell>
{canEdit && (
<>
{!ruleDetails.isTemplate && !!ruleDetails.params?.length && (
activation={activation}
ariaLabel={translateWithParameters('coding_rules.change_details_x', profile.name)}
buttonText={translate('change_verb')}
- className="coding-rules-detail-quality-profile-change"
modalHeader={translate('coding_rules.change_details')}
onDone={this.handleActivate}
profiles={[profile]}
onConfirm={this.handleRevert}
>
{({ onClick }) => (
- <Button
- className="coding-rules-detail-quality-profile-revert button-red spacer-left"
- onClick={onClick}
- >
+ <DangerButtonSecondary className="sw-ml-2" onClick={onClick}>
{translate('coding_rules.revert_to_parent_definition')}
- </Button>
+ </DangerButtonSecondary>
)}
</ConfirmButton>
)}
onConfirm={this.handleDeactivate}
>
{({ onClick }) => (
- <Button
- className="coding-rules-detail-quality-profile-deactivate button-red spacer-left"
+ <DangerButtonSecondary
+ className="sw-ml-2"
aria-label={translateWithParameters(
'coding_rules.deactivate_in_quality_profile_x',
profile.name,
onClick={onClick}
>
{translate('coding_rules.deactivate')}
- </Button>
+ </DangerButtonSecondary>
)}
</ConfirmButton>
)}
</>
)}
- </td>
+ </ActionCell>
);
};
const parentActivation = activations.find((x) => x.qProfile === profile.parentKey);
return (
- <tr key={profile.key}>
- <td className="coding-rules-detail-quality-profile-name">
- <Link to={getQualityProfileUrl(profile.name, profile.language)}>{profile.name}</Link>
- {profile.isBuiltIn && <BuiltInQualityProfileBadge className="spacer-left" />}
- {this.renderInheritedProfile(activation, profile)}
- </td>
+ <TableRowInteractive key={profile.key}>
+ <ContentCell className="coding-rules-detail-quality-profile-name">
+ <div className="sw-flex sw-flex-col">
+ <div>
+ <Link to={getQualityProfileUrl(profile.name, profile.language)}>{profile.name}</Link>
+ {profile.isBuiltIn && <BuiltInQualityProfileBadge className="sw-ml-2" />}
+ </div>
+ {this.renderInheritedProfile(activation, profile)}
+ </div>
+ </ContentCell>
{!ruleDetails.templateKey && this.renderParameters(activation, parentActivation)}
{this.renderActions(activation, profile)}
- </tr>
+ </TableRowInteractive>
);
};
);
return (
- <div className="js-rule-profiles coding-rule-section">
- <div className="coding-rules-detail-quality-profiles-section">
- <h2 className="coding-rules-detail-title">
- <InstanceMessage message={translate('coding_rules.quality_profiles')} />
- </h2>
-
- {canActivate && (
- <ActivationButton
- buttonText={translate('coding_rules.activate')}
- className="coding-rules-quality-profile-activate"
- modalHeader={translate('coding_rules.activate_in_quality_profile')}
- onDone={this.handleActivate}
- profiles={filter(
- this.props.referencedProfiles,
- (profile) => !activations.find((activation) => activation.qProfile === profile.key),
- )}
- rule={ruleDetails}
- />
- )}
-
- {activations.length > 0 && (
- <table
- className="coding-rules-detail-quality-profiles width-100"
- id="coding-rules-detail-quality-profiles"
- >
- <tbody>{activations.map(this.renderActivation)}</tbody>
- </table>
- )}
- </div>
+ <div className="js-rule-profiles sw-mb-8">
+ <SubHeadingHighlight as="h2" id={PROFILES_HEADING_ID}>
+ <FormattedMessage id="coding_rules.quality_profiles" />
+ </SubHeadingHighlight>
+
+ {canActivate && (
+ <ActivationButton
+ buttonText={translate('coding_rules.activate')}
+ className="sw-mt-6"
+ modalHeader={translate('coding_rules.activate_in_quality_profile')}
+ onDone={this.handleActivate}
+ profiles={filter(
+ this.props.referencedProfiles,
+ (profile) => !activations.find((activation) => activation.qProfile === profile.key),
+ )}
+ rule={ruleDetails}
+ />
+ )}
+
+ {activations.length > 0 && (
+ <Table
+ aria-labelledby={PROFILES_HEADING_ID}
+ className="sw-my-6"
+ columnCount={
+ ruleDetails.templateKey ? COLUMN_COUNT_WITHOUT_PARAMS : COLUMN_COUNT_WITH_PARAMS
+ }
+ id="coding-rules-detail-quality-profiles"
+ >
+ {activations.map(this.renderActivation)}
+ </Table>
+ )}
</div>
);
}
}
+
+const StyledParameter = styled.div`
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+
+ .value {
+ display: inline-block;
+ max-width: 300px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+`;
name: 'coding_rules.description_section.title.assess_the_problem',
}),
moreInfoTab: byRole('tab', {
- name: 'coding_rules.description_section.title.more_info',
+ name: /coding_rules.description_section.title.more_info/,
}),
howToFixTab: byRole('tab', {
name: 'coding_rules.description_section.title.how_to_fix',
import { translate } from '../../helpers/l10n';
import { Issue, RuleDetails } from '../../types/types';
import { NoticeType } from '../../types/users';
-import ScreenPositionHelper from '../common/ScreenPositionHelper';
-import BoxedTabs, { getTabId, getTabPanelId } from '../controls/BoxedTabs';
+import { getTabId, getTabPanelId } from '../controls/BoxedTabs';
import withLocation from '../hoc/withLocation';
import MoreInfoRuleDescription from './MoreInfoRuleDescription';
import RuleDescription from './RuleDescription';
+import { ToggleButton } from 'design-system/lib';
import './style.css';
interface RuleTabViewerProps extends CurrentUserContextInterface {
ruleDetails: RuleDetails;
extendedDescription?: string;
ruleDescriptionContextKey?: string;
- codeTabContent?: React.ReactNode;
activityTabContent?: React.ReactNode;
- scrollInTab?: boolean;
location: Location;
selectedFlowIndex?: number;
selectedLocationIndex?: number;
}
export interface Tab {
- key: TabKeys;
- label: React.ReactNode;
+ value: TabKeys;
+ label: string;
content: React.ReactNode;
+ counter?: number;
}
export enum TabKeys {
if (query.has('why')) {
this.setState({
- selectedTab: tabs.find((tab) => tab.key === TabKeys.WhyIsThisAnIssue) ?? tabs[0],
+ selectedTab: tabs.find((tab) => tab.value === TabKeys.WhyIsThisAnIssue) ?? tabs[0],
});
}
}
);
}
- if (selectedTab?.key === TabKeys.MoreInfo) {
+ if (selectedTab?.value === TabKeys.MoreInfo) {
this.checkIfEducationPrinciplesAreVisible();
}
if (
- prevState.selectedTab?.key === TabKeys.MoreInfo &&
+ prevState.selectedTab?.value === TabKeys.MoreInfo &&
prevState.displayEducationalPrinciplesNotification &&
prevState.educationalPrinciplesNotificationHasBeenDismissed
) {
computeTabs = (displayEducationalPrinciplesNotification: boolean) => {
const {
- codeTabContent,
ruleDetails: { descriptionSections, educationPrinciples, lang: ruleLanguage, type: ruleType },
- ruleDescriptionContextKey,
extendedDescription,
activityTabContent,
} = this.props;
content: (descriptionSectionsByKey[RuleDescriptionSections.DEFAULT] ||
descriptionSectionsByKey[RuleDescriptionSections.ROOT_CAUSE]) && (
<RuleDescription
- className="padded"
- defaultContextKey={ruleDescriptionContextKey}
language={ruleLanguage}
sections={
descriptionSectionsByKey[RuleDescriptionSections.DEFAULT] ||
}
/>
),
- key: TabKeys.WhyIsThisAnIssue,
+ value: TabKeys.WhyIsThisAnIssue,
label:
ruleType === 'SECURITY_HOTSPOT'
? translate('coding_rules.description_section.title.root_cause.SECURITY_HOTSPOT')
{
content: descriptionSectionsByKey[RuleDescriptionSections.ASSESS_THE_PROBLEM] && (
<RuleDescription
- className="padded"
language={ruleLanguage}
sections={descriptionSectionsByKey[RuleDescriptionSections.ASSESS_THE_PROBLEM]}
/>
),
- key: TabKeys.AssessTheIssue,
+ value: TabKeys.AssessTheIssue,
label: translate('coding_rules.description_section.title', TabKeys.AssessTheIssue),
},
{
content: descriptionSectionsByKey[RuleDescriptionSections.HOW_TO_FIX] && (
<RuleDescription
- className="padded"
- defaultContextKey={ruleDescriptionContextKey}
language={ruleLanguage}
sections={descriptionSectionsByKey[RuleDescriptionSections.HOW_TO_FIX]}
/>
),
- key: TabKeys.HowToFixIt,
+ value: TabKeys.HowToFixIt,
label: translate('coding_rules.description_section.title', TabKeys.HowToFixIt),
},
{
content: activityTabContent,
- key: TabKeys.Activity,
+ value: TabKeys.Activity,
label: translate('coding_rules.description_section.title', TabKeys.Activity),
},
{
sections={descriptionSectionsByKey[RuleDescriptionSections.RESOURCES]}
/>
),
- key: TabKeys.MoreInfo,
- label: (
- <>
- {translate('coding_rules.description_section.title', TabKeys.MoreInfo)}
- {displayEducationalPrinciplesNotification && <div className="notice-dot" />}
- </>
- ),
+ value: TabKeys.MoreInfo,
+ label: translate('coding_rules.description_section.title', TabKeys.MoreInfo),
+ counter: displayEducationalPrinciplesNotification ? 1 : undefined,
},
];
- if (codeTabContent !== undefined) {
- tabs.unshift({
- content: codeTabContent,
- key: TabKeys.Code,
- label: translate('issue.tabs', TabKeys.Code),
- });
- }
-
return tabs.filter((tab) => tab.content);
};
handleSelectTabs = (currentTabKey: TabKeys) => {
this.setState(({ tabs }) => ({
- selectedTab: tabs.find((tab) => tab.key === currentTabKey) || tabs[0],
+ selectedTab: tabs.find((tab) => tab.value === currentTabKey) ?? tabs[0],
}));
};
render() {
- const { scrollInTab } = this.props;
const { tabs, selectedTab } = this.state;
if (!tabs || tabs.length === 0 || !selectedTab) {
return (
<>
- <div>
- <BoxedTabs
- className="big-spacer-top"
- onSelect={this.handleSelectTabs}
- selected={selectedTab.key}
- tabs={tabs}
+ <div className="sw-mt-4">
+ <ToggleButton
+ role="tablist"
+ onChange={this.handleSelectTabs}
+ options={tabs}
+ value={selectedTab.value}
/>
</div>
- <ScreenPositionHelper>
- {({ top }) => (
- <div
- aria-labelledby={getTabId(selectedTab.key)}
- className="bordered display-flex-column"
- id={getTabPanelId(selectedTab.key)}
- role="tabpanel"
- style={{
- // We substract the footer height with padding (80) and the main layout padding (20)
- maxHeight: scrollInTab ? `calc(100vh - ${top + 100}px)` : 'initial',
- }}
- >
- {
- // Preserve tabs state by always rendering all of them. Only hide them when not selected
- tabs.map((tab) => (
- <div
- className={classNames('overflow-y-auto spacer', {
- hidden: tab.key !== selectedTab.key,
- })}
- key={tab.key}
- >
- {tab.content}
- </div>
- ))
- }
- </div>
- )}
- </ScreenPositionHelper>
+
+ <div
+ aria-labelledby={getTabId(selectedTab.value)}
+ className="sw-flex sw-flex-col sw-py-6"
+ id={getTabPanelId(selectedTab.value)}
+ role="tabpanel"
+ >
+ {
+ // Preserve tabs state by always rendering all of them. Only hide them when not selected
+ tabs.map((tab) => (
+ <div
+ className={classNames({
+ 'sw-hidden': tab.value !== selectedTab.value,
+ })}
+ key={tab.value}
+ >
+ {tab.content}
+ </div>
+ ))
+ }
+ </div>
</>
);
}
eslint-plugin-typescript-sort-keys: 2.3.0
highlight.js: 11.8.0
highlightjs-apex: 1.2.0
+ highlightjs-cobol: 0.3.3
highlightjs-sap-abap: 0.3.0
history: 5.3.0
jest: 29.6.4
languageName: node
linkType: hard
+"highlightjs-cobol@npm:0.3.3":
+ version: 0.3.3
+ resolution: "highlightjs-cobol@npm:0.3.3"
+ dependencies:
+ minimist: ">=1.2.6"
+ mkdirp: ^1.0.4
+ checksum: f59a694703f883ead2fbdf262f36eab583c8bbf64649e59d5de1e13a43e43979ad9030eab69e1cb08f7558ce60ca5cfd1fe42c7a38e34fd8b0baebf06df9118d
+ languageName: node
+ linkType: hard
+
"highlightjs-sap-abap@npm:0.3.0":
version: 0.3.0
resolution: "highlightjs-sap-abap@npm:0.3.0"
languageName: node
linkType: hard
+"minimist@npm:>=1.2.6":
+ version: 1.2.8
+ resolution: "minimist@npm:1.2.8"
+ checksum: 75a6d645fb122dad29c06a7597bddea977258957ed88d7a6df59b5cd3fe4a527e253e9bbf2e783e4b73657f9098b96a5fe96ab8a113655d4109108577ecf85b0
+ languageName: node
+ linkType: hard
+
"minimist@npm:^1.2.0, minimist@npm:^1.2.5":
version: 1.2.5
resolution: "minimist@npm:1.2.5"
coding_rules.deactivate_in_quality_profile=Deactivate In Quality Profile
coding_rules.deactivate_in_quality_profile_x=Deactivate In Quality Profile {0}
coding_rules.delete_rule=Delete Rule
-condig_rules.delete_rule_x=Delete Rule {0}
+coding_rules.delete_rule_x=Delete Rule {0}
coding_rules.delete.custom.confirm=Are you sure you want to delete custom rule "{0}"?
coding_rules.extend_description=Extend Description
coding_rules.deactivate_in=Deactivate In
coding_rules.more_info.notification_message=We've added new information about Clean Code principles below to help you improve your code quality and security. Take a moment to read through them.
coding_rules.more_info.scroll_message=Scroll down to Code Quality principles
+coding_rules.detail.extend_description.form=Extend this rule's description
+
rule.impact.severity.tooltip=Issues found for this rule will have a {severity} impact on the {quality} of your software.
rule.clean_code_attribute_category.CONSISTENT=Consistency