aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin Silva <kevin.silva@sonarsource.com>2023-10-20 13:29:42 +0200
committersonartech <sonartech@sonarsource.com>2023-10-25 20:02:59 +0000
commit5a5718ef3e3867d06091ca8eca59fde9f1d673ec (patch)
tree6f2727ae94f7d642982027360cc6c9237e1a7305
parente875fbe32a1cf60e6d6598f80c2b89da9fa11117 (diff)
downloadsonarqube-5a5718ef3e3867d06091ca8eca59fde9f1d673ec.tar.gz
sonarqube-5a5718ef3e3867d06091ca8eca59fde9f1d673ec.zip
SONAR-20814 - Update layout when SPECIFIC_ANALYSIS is selected
-rw-r--r--server/sonar-web/src/main/js/apps/projectNewCode/components/BranchAnalysisList.tsx160
-rw-r--r--server/sonar-web/src/main/js/apps/projectNewCode/components/BranchAnalysisListRenderer.tsx207
-rw-r--r--server/sonar-web/src/main/js/apps/projectNewCode/components/BranchNewCodeDefinitionSettingModal.tsx16
-rw-r--r--server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingAnalysis.tsx63
-rw-r--r--server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingReferenceBranch.tsx51
-rw-r--r--server/sonar-web/src/main/js/apps/projectNewCode/components/ProjectNewCodeDefinitionSelector.tsx148
-rw-r--r--server/sonar-web/src/main/js/apps/projectNewCode/components/__tests__/ProjectNewCodeDefinitionApp-it.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/projectNewCode/styles.css93
-rw-r--r--server/sonar-web/src/main/js/components/new-code-definition/NewCodeDefinitionAnalysisWarning.tsx36
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties5
10 files changed, 182 insertions, 615 deletions
diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchAnalysisList.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchAnalysisList.tsx
deleted file mode 100644
index 03f39eae6e9..00000000000
--- a/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchAnalysisList.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * 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 { subDays } from 'date-fns';
-import { throttle } from 'lodash';
-import * as React from 'react';
-import { getProjectActivity } from '../../../api/projectActivity';
-import { parseDate, toShortISO8601String } from '../../../helpers/dates';
-import { Analysis, ParsedAnalysis } from '../../../types/project-activity';
-import { Dict } from '../../../types/types';
-import BranchAnalysisListRenderer from './BranchAnalysisListRenderer';
-
-interface Props {
- analysis: string;
- branch: string;
- component: string;
- onSelectAnalysis: (analysis: ParsedAnalysis) => void;
-}
-
-interface State {
- analyses: ParsedAnalysis[];
- loading: boolean;
- range: number;
- scroll: number;
-}
-
-const STICKY_BADGE_SCROLL_OFFSET = 10;
-
-export default class BranchAnalysisList extends React.PureComponent<Props, State> {
- mounted = false;
- badges: Dict<HTMLDivElement> = {};
- state: State = {
- analyses: [],
- loading: true,
- range: 30,
- scroll: 0,
- };
-
- constructor(props: Props) {
- super(props);
- this.updateScroll = throttle(this.updateScroll, 20);
- }
-
- componentDidMount() {
- this.mounted = true;
- this.fetchAnalyses(true);
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- scrollToSelected() {
- document.querySelector('.branch-analysis.selected')?.scrollIntoView({
- block: 'center',
- behavior: 'smooth',
- });
- }
-
- fetchAnalyses(initial = false) {
- const { analysis, branch, component } = this.props;
- const { range } = this.state;
- this.setState({ loading: true });
-
- return getProjectActivity({
- branch,
- project: component,
- from: range ? toShortISO8601String(subDays(new Date(), range)) : undefined,
- }).then((result: { analyses: Analysis[] }) => {
- // If the selected analysis wasn't found in the default 30 days range, redo the search
- if (initial && analysis && !result.analyses.find((a) => a.key === analysis)) {
- this.handleRangeChange({ value: 0 });
- return;
- }
-
- this.setState(
- {
- analyses: result.analyses.map((analysis) => ({
- ...analysis,
- date: parseDate(analysis.date),
- })) as ParsedAnalysis[],
- loading: false,
- },
- () => {
- this.scrollToSelected();
- },
- );
- });
- }
-
- handleScroll = (e: React.SyntheticEvent<HTMLDivElement>) => {
- if (e.currentTarget) {
- this.updateScroll(e.currentTarget.scrollTop);
- }
- };
-
- updateScroll = (scroll: number) => {
- this.setState({ scroll });
- };
-
- registerBadgeNode = (version: string) => (el: HTMLDivElement) => {
- if (el) {
- if (!el.getAttribute('originOffsetTop')) {
- el.setAttribute('originOffsetTop', String(el.offsetTop));
- }
- this.badges[version] = el;
- }
- };
-
- shouldStick = (version: string) => {
- const badge = this.badges[version];
- return (
- !!badge &&
- Number(badge.getAttribute('originOffsetTop')) < this.state.scroll + STICKY_BADGE_SCROLL_OFFSET
- );
- };
-
- handleRangeChange = ({ value }: { value: number }) => {
- this.setState({ range: value }, () => {
- this.fetchAnalyses().catch(() => {
- /* noop */
- });
- });
- };
-
- render() {
- const { analysis, onSelectAnalysis } = this.props;
- const { analyses, loading, range } = this.state;
-
- return (
- <BranchAnalysisListRenderer
- analyses={analyses}
- handleRangeChange={this.handleRangeChange}
- handleScroll={this.handleScroll}
- loading={loading}
- onSelectAnalysis={onSelectAnalysis}
- range={range}
- registerBadgeNode={this.registerBadgeNode}
- selectedAnalysisKey={analysis}
- shouldStick={this.shouldStick}
- />
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchAnalysisListRenderer.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchAnalysisListRenderer.tsx
deleted file mode 100644
index d4d0c66530d..00000000000
--- a/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchAnalysisListRenderer.tsx
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * 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 classNames from 'classnames';
-import * as React from 'react';
-import { injectIntl, IntlShape, WrappedComponentProps } from 'react-intl';
-import Radio from '../../../components/controls/Radio';
-import Select from '../../../components/controls/Select';
-import Tooltip from '../../../components/controls/Tooltip';
-import DateFormatter, { longFormatterOption } from '../../../components/intl/DateFormatter';
-import TimeFormatter from '../../../components/intl/TimeFormatter';
-import Spinner from '../../../components/ui/Spinner';
-import { parseDate, toShortISO8601String } from '../../../helpers/dates';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { ParsedAnalysis } from '../../../types/project-activity';
-import Events from '../../projectActivity/components/Events';
-import { getAnalysesByVersionByDay } from '../../projectActivity/utils';
-
-export interface BranchAnalysisListRendererProps {
- analyses: ParsedAnalysis[];
- handleRangeChange: ({ value }: { value: number }) => void;
- handleScroll: (e: React.SyntheticEvent<HTMLDivElement>) => void;
- loading: boolean;
- onSelectAnalysis: (analysis: ParsedAnalysis) => void;
- range: number;
- registerBadgeNode: (version: string) => (el: HTMLDivElement) => void;
- selectedAnalysisKey: string;
- shouldStick: (version: string) => boolean;
-}
-
-function renderAnalysis(args: {
- analysis: ParsedAnalysis;
- isFirst: boolean;
- onSelectAnalysis: (analysis: ParsedAnalysis) => void;
- selectedAnalysisKey: string;
- intl: IntlShape;
-}) {
- const { analysis, isFirst, selectedAnalysisKey, intl } = args;
- return (
- <li
- className={classNames('branch-analysis', {
- selected: analysis.key === selectedAnalysisKey,
- })}
- data-date={parseDate(analysis.date).valueOf()}
- key={analysis.key}
- >
- <div className="branch-analysis-time spacer-right">
- <TimeFormatter date={parseDate(analysis.date)} long={false}>
- {(formattedTime) => (
- <time className="text-middle" dateTime={parseDate(analysis.date).toISOString()}>
- {formattedTime}
- </time>
- )}
- </TimeFormatter>
- </div>
-
- {analysis.events.length > 0 && (
- <Events analysisKey={analysis.key} events={analysis.events} isFirst={isFirst} />
- )}
-
- <div className="analysis-selection-button">
- <Radio
- checked={analysis.key === selectedAnalysisKey}
- ariaLabel={translateWithParameters(
- 'baseline.branch_analyses.analysis_for_x',
- `${intl.formatDate(analysis.date, longFormatterOption)}, ${intl.formatTime(
- analysis.date,
- )}`,
- )}
- onCheck={() => {}}
- value=""
- disabled
- />
- </div>
- </li>
- );
-}
-
-function BranchAnalysisListRenderer(
- props: BranchAnalysisListRendererProps & WrappedComponentProps,
-) {
- const { analyses, loading, range, selectedAnalysisKey, intl } = props;
-
- const byVersionByDay = React.useMemo(
- () =>
- getAnalysesByVersionByDay(analyses, {
- category: '',
- }),
- [analyses],
- );
-
- const hasFilteredData =
- byVersionByDay.length > 1 ||
- (byVersionByDay.length === 1 && Object.keys(byVersionByDay[0].byDay).length > 0);
-
- const options = [
- {
- label: translate('baseline.branch_analyses.ranges.30days'),
- value: 30,
- },
- {
- label: translate('baseline.branch_analyses.ranges.allTime'),
- value: 0,
- },
- ];
-
- return (
- <>
- <div className="spacer-bottom">
- <label htmlFor="branch-analysis-from-input" className="spacer-right">
- {translate('baseline.analysis_from')}
- </label>
- <Select
- blurInputOnSelect
- inputId="branch-analysis-from-input"
- className="input-medium spacer-left"
- onChange={props.handleRangeChange}
- options={options}
- isSearchable={false}
- value={options.filter((o) => o.value === range)}
- />
- </div>
- <div className="branch-analysis-list-wrapper">
- <div className="bordered branch-analysis-list" onScroll={props.handleScroll}>
- <Spinner className="big-spacer-top" loading={loading} />
-
- {!loading && !hasFilteredData ? (
- <div className="big-spacer-top big-spacer-bottom strong">
- {translate('baseline.no_analyses')}
- </div>
- ) : (
- <ul>
- {byVersionByDay.map((version, idx) => {
- const days = Object.keys(version.byDay);
- if (days.length <= 0) {
- return null;
- }
- return (
- <li key={version.key || 'noversion'}>
- {version.version && (
- <div
- className={classNames('branch-analysis-version-badge', {
- first: idx === 0,
- sticky: props.shouldStick(version.version),
- })}
- ref={props.registerBadgeNode(version.version)}
- >
- <Tooltip
- mouseEnterDelay={0.5}
- overlay={`${translate('version')} ${version.version}`}
- >
- <span className="badge">{version.version}</span>
- </Tooltip>
- </div>
- )}
- <ul className="branch-analysis-days-list">
- {days.map((day) => (
- <li
- className="branch-analysis-day"
- data-day={toShortISO8601String(Number(day))}
- key={day}
- >
- <div className="branch-analysis-date">
- <DateFormatter date={Number(day)} long />
- </div>
- <ul className="branch-analysis-analyses-list">
- {version.byDay[day]?.map((analysis) =>
- renderAnalysis({
- analysis,
- selectedAnalysisKey,
- isFirst: analyses[0].key === analysis.key,
- onSelectAnalysis: props.onSelectAnalysis,
- intl,
- }),
- )}
- </ul>
- </li>
- ))}
- </ul>
- </li>
- );
- })}
- </ul>
- )}
- </div>
- </div>
- </>
- );
-}
-
-export default injectIntl(BranchAnalysisListRenderer);
diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchNewCodeDefinitionSettingModal.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchNewCodeDefinitionSettingModal.tsx
index 771303c7a2e..c1c7d727f11 100644
--- a/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchNewCodeDefinitionSettingModal.tsx
+++ b/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchNewCodeDefinitionSettingModal.tsx
@@ -22,7 +22,6 @@ import * as React from 'react';
import { setNewCodeDefinition } from '../../../api/newCodeDefinition';
import Modal from '../../../components/controls/Modal';
import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
-import NewCodeDefinitionAnalysisWarning from '../../../components/new-code-definition/NewCodeDefinitionAnalysisWarning';
import NewCodeDefinitionDaysOption from '../../../components/new-code-definition/NewCodeDefinitionDaysOption';
import NewCodeDefinitionPreviousVersionOption from '../../../components/new-code-definition/NewCodeDefinitionPreviousVersionOption';
import { NewCodeDefinitionLevels } from '../../../components/new-code-definition/utils';
@@ -33,7 +32,6 @@ import { getNumberOfDaysDefaultValue } from '../../../helpers/new-code-definitio
import { Branch, BranchWithNewCodePeriod } from '../../../types/branch-like';
import { NewCodeDefinition, NewCodeDefinitionType } from '../../../types/new-code-definition';
import { getSettingValue, validateSetting } from '../utils';
-import BranchAnalysisList from './BranchAnalysisList';
import NewCodeDefinitionSettingAnalysis from './NewCodeDefinitionSettingAnalysis';
import NewCodeDefinitionSettingReferenceBranch from './NewCodeDefinitionSettingReferenceBranch';
@@ -183,9 +181,6 @@ export default class BranchNewCodeDefinitionSettingModal extends React.PureCompo
<form onSubmit={this.handleSubmit}>
<div className="modal-body modal-container branch-baseline-setting-modal">
<p className="sw-mb-3">{translate('baseline.new_code_period_for_branch_x.question')}</p>
- {currentSetting === NewCodeDefinitionType.SpecificAnalysis && (
- <NewCodeDefinitionAnalysisWarning />
- )}
<div className="display-flex-column huge-spacer-bottom sw-gap-4" role="radiogroup">
<NewCodeDefinitionPreviousVersionOption
isDefault={false}
@@ -212,20 +207,15 @@ export default class BranchNewCodeDefinitionSettingModal extends React.PureCompo
{currentSetting === NewCodeDefinitionType.SpecificAnalysis && (
<NewCodeDefinitionSettingAnalysis
onSelect={noop}
+ analysis={analysis}
+ branch={branch.name}
+ component={this.props.component}
selected={
selectedNewCodeDefinitionType === NewCodeDefinitionType.SpecificAnalysis
}
/>
)}
</div>
- {selectedNewCodeDefinitionType === NewCodeDefinitionType.SpecificAnalysis && (
- <BranchAnalysisList
- analysis={analysis}
- branch={branch.name}
- component={this.props.component}
- onSelectAnalysis={noop}
- />
- )}
</div>
<footer className="modal-foot">
<Spinner className="spacer-right" loading={saving} />
diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingAnalysis.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingAnalysis.tsx
index 027db43c2f3..777e91f7691 100644
--- a/server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingAnalysis.tsx
+++ b/server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingAnalysis.tsx
@@ -17,26 +17,77 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { subDays } from 'date-fns';
+import { SelectionCard } from 'design-system';
import * as React from 'react';
-import RadioCard from '../../../components/controls/RadioCard';
+import { useEffect, useState } from 'react';
+import { getProjectActivity } from '../../../api/projectActivity';
+import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
+import NewCodeDefinitionAnalysisWarning from '../../../components/new-code-definition/NewCodeDefinitionAnalysisWarning';
+import { parseDate, toShortISO8601String } from '../../../helpers/dates';
import { translate } from '../../../helpers/l10n';
import { NewCodeDefinitionType } from '../../../types/new-code-definition';
+import { Analysis } from '../../../types/project-activity';
export interface Props {
onSelect: (selection: NewCodeDefinitionType) => void;
selected: boolean;
+ analysis: string;
+ branch: string;
+ component: string;
}
-export default function NewCodeDefinitionSettingAnalysis({ onSelect, selected }: Props) {
+export default function NewCodeDefinitionSettingAnalysis({
+ onSelect,
+ selected,
+ analysis,
+ branch,
+ component,
+}: Readonly<Props>) {
+ const [parsedAnalysis, setParsedAnalysis] = useState<Analysis>();
+
+ const BASE_DAY_SEARCH = 30;
+
+ useEffect(() => {
+ async function fetchAnalyses(range = BASE_DAY_SEARCH, initial = true) {
+ const result = await getProjectActivity({
+ branch,
+ project: component,
+ from: range ? toShortISO8601String(subDays(new Date(), range)) : undefined,
+ });
+ // If the selected analysis wasn't found in the default 30 days range, redo the search
+ if (initial && analysis && !result.analyses.find((a) => a.key === analysis)) {
+ fetchAnalyses(0, false);
+ return;
+ }
+
+ const filteredResult = result.analyses.find((a) => a.key === analysis);
+ setParsedAnalysis(filteredResult);
+ }
+
+ fetchAnalyses();
+ }, [analysis, branch, component]);
+
+ const parsedDate = parsedAnalysis?.date ? parseDate(parsedAnalysis?.date) : '';
+
return (
- <RadioCard
- noRadio
+ <SelectionCard
disabled
onClick={() => onSelect(NewCodeDefinitionType.SpecificAnalysis)}
selected={selected}
title={translate('baseline.specific_analysis')}
>
- <p className="big-spacer-bottom">{translate('baseline.specific_analysis.description')}</p>
- </RadioCard>
+ <p className="sw-mb-4">{translate('baseline.specific_analysis.description')}</p>
+ {parsedAnalysis && (
+ <p className="sw-mb-4">
+ <span>
+ {`${translate('baseline.specific_analysis')}: `}
+ {parsedDate ? <DateTimeFormatter date={parsedDate} /> : '?'}
+ </span>
+ </p>
+ )}
+
+ <NewCodeDefinitionAnalysisWarning />
+ </SelectionCard>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingReferenceBranch.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingReferenceBranch.tsx
index 967f9c03bb6..b5b6972069c 100644
--- a/server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingReferenceBranch.tsx
+++ b/server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingReferenceBranch.tsx
@@ -17,14 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { SelectionCard } from 'design-system';
+import { Badge, FlagErrorIcon, FormField, InputSelect, SelectionCard } from 'design-system';
import * as React from 'react';
-import { components, OptionProps } from 'react-select';
-import Select from '../../../components/controls/Select';
+import { OptionProps, components } from 'react-select';
import Tooltip from '../../../components/controls/Tooltip';
-import AlertErrorIcon from '../../../components/icons/AlertErrorIcon';
import { NewCodeDefinitionLevels } from '../../../components/new-code-definition/utils';
-import MandatoryFieldMarker from '../../../components/ui/MandatoryFieldMarker';
import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { NewCodeDefinitionType } from '../../../types/new-code-definition';
@@ -64,7 +61,7 @@ function renderBranchOption(props: OptionProps<BranchOption, false>) {
)}
>
<span>
- {option.value} <AlertErrorIcon />
+ {option.value} <FlagErrorIcon className="sw-ml-2" />
</span>
</Tooltip>
) : (
@@ -78,9 +75,7 @@ function renderBranchOption(props: OptionProps<BranchOption, false>) {
>
{option.value}
</span>
- {option.isMain && (
- <div className="badge spacer-left">{translate('branches.main_branch')}</div>
- )}
+ {option.isMain && <Badge className="sw-ml-2">{translate('branches.main_branch')}</Badge>}
</>
)}
</components.Option>
@@ -115,24 +110,28 @@ export default function NewCodeDefinitionSettingReferenceBranch(
{selected && (
<>
{settingLevel === NewCodeDefinitionLevels.Project && (
- <p className="spacer-top">{translate('baseline.reference_branch.description2')}</p>
+ <p>{translate('baseline.reference_branch.description2')}</p>
)}
- <div className="big-spacer-top display-flex-column">
- <MandatoryFieldsExplanation className="spacer-bottom" />
- <label className="text-middle" htmlFor="reference_branch">
- <strong>{translate('baseline.reference_branch.choose')}</strong>
- <MandatoryFieldMarker />
- </label>
- <Select
- className="little-spacer-top spacer-bottom"
- options={branchList}
- aria-label={translate('baseline.reference_branch.choose')}
- onChange={(option: BranchOption) => props.onChangeReferenceBranch(option.value)}
- value={currentBranch}
- components={{
- Option: renderBranchOption,
- }}
- />
+ <div className="sw-flex sw-flex-col">
+ <MandatoryFieldsExplanation className="sw-mb-2" />
+
+ <FormField
+ ariaLabel={translate('baseline.reference_branch.choose')}
+ label={translate('baseline.reference_branch.choose')}
+ htmlFor="new-code-definition-reference-branch"
+ required
+ >
+ <InputSelect
+ inputId="new-code-definition-reference-branch"
+ size="large"
+ options={branchList}
+ onChange={(option: BranchOption) => props.onChangeReferenceBranch(option.value)}
+ value={currentBranch}
+ components={{
+ Option: renderBranchOption,
+ }}
+ />
+ </FormField>
</div>
</>
)}
diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/ProjectNewCodeDefinitionSelector.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/ProjectNewCodeDefinitionSelector.tsx
index a9cbced11c4..8ef34130d1b 100644
--- a/server/sonar-web/src/main/js/apps/projectNewCode/components/ProjectNewCodeDefinitionSelector.tsx
+++ b/server/sonar-web/src/main/js/apps/projectNewCode/components/ProjectNewCodeDefinitionSelector.tsx
@@ -17,23 +17,17 @@
* 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 { RadioButton } from 'design-system';
+import { ButtonPrimary, ButtonSecondary, FlagMessage, RadioButton, Spinner } from 'design-system';
import { noop } from 'lodash';
import * as React from 'react';
-import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
import GlobalNewCodeDefinitionDescription from '../../../components/new-code-definition/GlobalNewCodeDefinitionDescription';
-import NewCodeDefinitionAnalysisWarning from '../../../components/new-code-definition/NewCodeDefinitionAnalysisWarning';
import NewCodeDefinitionDaysOption from '../../../components/new-code-definition/NewCodeDefinitionDaysOption';
import NewCodeDefinitionPreviousVersionOption from '../../../components/new-code-definition/NewCodeDefinitionPreviousVersionOption';
import { NewCodeDefinitionLevels } from '../../../components/new-code-definition/utils';
-import { Alert } from '../../../components/ui/Alert';
-import Spinner from '../../../components/ui/Spinner';
import { translate } from '../../../helpers/l10n';
import { Branch } from '../../../types/branch-like';
import { NewCodeDefinition, NewCodeDefinitionType } from '../../../types/new-code-definition';
import { validateSetting } from '../utils';
-import BranchAnalysisList from './BranchAnalysisList';
import NewCodeDefinitionSettingAnalysis from './NewCodeDefinitionSettingAnalysis';
import NewCodeDefinitionSettingReferenceBranch from './NewCodeDefinitionSettingReferenceBranch';
@@ -100,11 +94,11 @@ export default function ProjectNewCodeDefinitionSelector(
}
return (
- <form className="project-baseline-selector" onSubmit={props.onSubmit}>
- <div className="big-spacer-top spacer-bottom" role="radiogroup">
+ <form className="it__project-baseline-selector" onSubmit={props.onSubmit}>
+ <div className="sw-mb-4 sw-mt-8" role="radiogroup">
<RadioButton
checked={!overrideGlobalNewCodeDefinition}
- className="big-spacer-bottom"
+ className="sw-mb-4"
onCheck={() => props.onToggleSpecificSetting(false)}
value="general"
>
@@ -117,7 +111,7 @@ export default function ProjectNewCodeDefinitionSelector(
<RadioButton
checked={overrideGlobalNewCodeDefinition}
- className="huge-spacer-top"
+ className="sw-mt-8"
onCheck={() => props.onToggleSpecificSetting(true)}
value="specific"
>
@@ -125,87 +119,81 @@ export default function ProjectNewCodeDefinitionSelector(
</RadioButton>
</div>
- <div className="big-spacer-left big-spacer-right project-baseline-setting">
- {newCodeDefinitionType === NewCodeDefinitionType.SpecificAnalysis && (
- <NewCodeDefinitionAnalysisWarning />
- )}
- <div className="display-flex-column big-spacer-bottom sw-gap-4" role="radiogroup">
- <NewCodeDefinitionPreviousVersionOption
+ <div className="sw-flex sw-flex-col sw-gap-4" role="radiogroup">
+ <NewCodeDefinitionPreviousVersionOption
+ disabled={!overrideGlobalNewCodeDefinition}
+ onSelect={props.onSelectSetting}
+ selected={
+ overrideGlobalNewCodeDefinition &&
+ selectedNewCodeDefinitionType === NewCodeDefinitionType.PreviousVersion
+ }
+ />
+ <NewCodeDefinitionDaysOption
+ days={days}
+ currentDaysValue={
+ newCodeDefinitionType === NewCodeDefinitionType.NumberOfDays
+ ? newCodeDefinitionValue
+ : undefined
+ }
+ previousNonCompliantValue={previousNonCompliantValue}
+ updatedAt={projectNcdUpdatedAt}
+ disabled={!overrideGlobalNewCodeDefinition}
+ isChanged={isChanged}
+ isValid={isValid}
+ onChangeDays={props.onSelectDays}
+ onSelect={props.onSelectSetting}
+ selected={
+ overrideGlobalNewCodeDefinition &&
+ selectedNewCodeDefinitionType === NewCodeDefinitionType.NumberOfDays
+ }
+ settingLevel={NewCodeDefinitionLevels.Project}
+ />
+ {branchesEnabled && (
+ <NewCodeDefinitionSettingReferenceBranch
+ branchList={branchList.map(branchToOption)}
disabled={!overrideGlobalNewCodeDefinition}
+ onChangeReferenceBranch={props.onSelectReferenceBranch}
onSelect={props.onSelectSetting}
+ referenceBranch={referenceBranch ?? ''}
selected={
overrideGlobalNewCodeDefinition &&
- selectedNewCodeDefinitionType === NewCodeDefinitionType.PreviousVersion
+ selectedNewCodeDefinitionType === NewCodeDefinitionType.ReferenceBranch
}
+ settingLevel={NewCodeDefinitionLevels.Project}
/>
- <NewCodeDefinitionDaysOption
- days={days}
- currentDaysValue={
- newCodeDefinitionType === NewCodeDefinitionType.NumberOfDays
- ? newCodeDefinitionValue
- : undefined
- }
- previousNonCompliantValue={previousNonCompliantValue}
- updatedAt={projectNcdUpdatedAt}
- disabled={!overrideGlobalNewCodeDefinition}
- isChanged={isChanged}
- isValid={isValid}
- onChangeDays={props.onSelectDays}
- onSelect={props.onSelectSetting}
+ )}
+ {!branchesEnabled && newCodeDefinitionType === NewCodeDefinitionType.SpecificAnalysis && (
+ <NewCodeDefinitionSettingAnalysis
+ onSelect={noop}
+ analysis={analysis ?? ''}
+ branch={branch.name}
+ component={component}
selected={
overrideGlobalNewCodeDefinition &&
- selectedNewCodeDefinitionType === NewCodeDefinitionType.NumberOfDays
+ selectedNewCodeDefinitionType === NewCodeDefinitionType.SpecificAnalysis
}
- settingLevel={NewCodeDefinitionLevels.Project}
/>
- {branchesEnabled && (
- <NewCodeDefinitionSettingReferenceBranch
- branchList={branchList.map(branchToOption)}
- disabled={!overrideGlobalNewCodeDefinition}
- onChangeReferenceBranch={props.onSelectReferenceBranch}
- onSelect={props.onSelectSetting}
- referenceBranch={referenceBranch ?? ''}
- selected={
- overrideGlobalNewCodeDefinition &&
- selectedNewCodeDefinitionType === NewCodeDefinitionType.ReferenceBranch
- }
- settingLevel={NewCodeDefinitionLevels.Project}
- />
- )}
- {!branchesEnabled && newCodeDefinitionType === NewCodeDefinitionType.SpecificAnalysis && (
- <NewCodeDefinitionSettingAnalysis
- onSelect={noop}
- selected={
- overrideGlobalNewCodeDefinition &&
- selectedNewCodeDefinitionType === NewCodeDefinitionType.SpecificAnalysis
- }
- />
- )}
- </div>
- {!branchesEnabled &&
- overrideGlobalNewCodeDefinition &&
- selectedNewCodeDefinitionType === NewCodeDefinitionType.SpecificAnalysis && (
- <BranchAnalysisList
- analysis={analysis ?? ''}
- branch={branch.name}
- component={component}
- onSelectAnalysis={noop}
- />
- )}
+ )}
</div>
- <div className="big-spacer-top">
- <Alert variant="info" className={classNames('spacer-bottom', { invisible: !isChanged })}>
- {translate('baseline.next_analysis_notice')}
- </Alert>
- <Spinner className="spacer-right" loading={saving} />
- {!saving && (
- <>
- <SubmitButton disabled={!isValid || !isChanged}>{translate('save')}</SubmitButton>
- <ResetButtonLink className="spacer-left" disabled={!isChanged} onClick={props.onCancel}>
- {translate('cancel')}
- </ResetButtonLink>
- </>
+ <div className="sw-mt-4">
+ {isChanged && (
+ <FlagMessage variant="info" className="sw-mb-4">
+ {translate('baseline.next_analysis_notice')}
+ </FlagMessage>
)}
+ <div className="sw-flex sw-items-center">
+ <ButtonPrimary type="submit" disabled={!isValid || !isChanged || saving}>
+ {translate('save')}
+ </ButtonPrimary>
+ <ButtonSecondary
+ className="sw-ml-2"
+ disabled={saving || !isChanged}
+ onClick={props.onCancel}
+ >
+ {translate('cancel')}
+ </ButtonSecondary>
+ <Spinner className="sw-ml-2" loading={saving} />
+ </div>
</div>
</form>
);
diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/__tests__/ProjectNewCodeDefinitionApp-it.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/__tests__/ProjectNewCodeDefinitionApp-it.tsx
index 7025d1bb8d5..1a633c223e2 100644
--- a/server/sonar-web/src/main/js/apps/projectNewCode/components/__tests__/ProjectNewCodeDefinitionApp-it.tsx
+++ b/server/sonar-web/src/main/js/apps/projectNewCode/components/__tests__/ProjectNewCodeDefinitionApp-it.tsx
@@ -28,6 +28,7 @@ import NewCodeDefinitionServiceMock from '../../../../api/mocks/NewCodeDefinitio
import { ProjectActivityServiceMock } from '../../../../api/mocks/ProjectActivityServiceMock';
import { mockComponent } from '../../../../helpers/mocks/component';
import { mockNewCodePeriodBranch } from '../../../../helpers/mocks/new-code-definition';
+import { mockAnalysis } from '../../../../helpers/mocks/project-activity';
import { mockAppState } from '../../../../helpers/testMocks';
import {
RenderContext,
@@ -157,16 +158,21 @@ it('cannot set specific analysis setting', async () => {
value: 'analysis_id',
}),
]);
+ projectActivityMock.setAnalysesList([
+ mockAnalysis({
+ key: `analysis_id`,
+ date: '2018-01-11T00:00:00+0200',
+ }),
+ ]);
renderProjectNewCodeDefinitionApp();
await ui.appIsLoaded();
expect(await ui.specificAnalysisRadio.find()).toBeChecked();
+ expect(ui.baselineSpecificAnalysisDate.get()).toBeInTheDocument();
+
expect(ui.specificAnalysisRadio.get()).toHaveClass('disabled');
expect(ui.specificAnalysisWarning.get()).toBeInTheDocument();
- await selectEvent.select(ui.analysisFromSelect.get(), 'baseline.branch_analyses.ranges.allTime');
-
- expect(first(ui.analysisListItem.getAll())).toHaveClass('disabled');
expect(ui.saveButton.get()).toBeDisabled();
});
@@ -245,9 +251,6 @@ it('cannot set a specific analysis setting for branch', async () => {
expect(ui.specificAnalysisRadio.get()).toHaveClass('disabled');
expect(ui.specificAnalysisWarning.get()).toBeInTheDocument();
- await selectEvent.select(ui.analysisFromSelect.get(), 'baseline.branch_analyses.ranges.allTime');
-
- expect(first(ui.analysisListItem.getAll())).toHaveClass('disabled');
expect(last(ui.saveButton.getAll())).toBeDisabled();
});
@@ -402,8 +405,6 @@ function getPageObjects() {
chooseBranchSelect: byRole('combobox', { name: 'baseline.reference_branch.choose' }),
specificAnalysisRadio: byRole('radio', { name: /baseline.specific_analysis.description/ }),
specificAnalysisWarning: byText('baseline.specific_analysis.compliance_warning.title'),
- analysisFromSelect: byRole('combobox', { name: 'baseline.analysis_from' }),
- analysisListItem: byRole('radio', { name: /baseline.branch_analyses.analysis_for_x/ }),
saveButton: byRole('button', { name: 'save' }),
cancelButton: byRole('button', { name: 'cancel' }),
branchActionsButton: () => byRole('button', { name: `menu` }),
@@ -411,6 +412,7 @@ function getPageObjects() {
resetToDefaultButton: byRole('menuitem', { name: 'reset_to_default' }),
branchNCDsBanner: byText(/new_code_definition.auto_update.branch.message/),
dismissButton: byLabelText('dismiss'),
+ baselineSpecificAnalysisDate: byText(/January 10, 2018/),
};
async function appIsLoaded() {
diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/styles.css b/server/sonar-web/src/main/js/apps/projectNewCode/styles.css
index 5e09ae45cbc..665f3fdf466 100644
--- a/server/sonar-web/src/main/js/apps/projectNewCode/styles.css
+++ b/server/sonar-web/src/main/js/apps/projectNewCode/styles.css
@@ -21,13 +21,6 @@
padding: calc(4 * var(--gridSize));
}
-.project-baseline-setting {
- display: flex;
- flex-direction: column;
- max-height: 60vh;
- padding-top: 2px;
-}
-
.branch-baseline-selector > hr {
margin: 0 calc(-4 * var(--gridSize)) calc(4 * var(--gridSize));
}
@@ -38,92 +31,6 @@
flex-direction: column;
}
-.branch-analysis-list-wrapper {
- display: flex;
- flex-direction: column;
- overflow: hidden;
- position: relative;
- min-height: 200px;
-}
-
-.branch-analysis-list {
- overflow-y: auto;
- padding-left: 12px;
- padding-right: 15px;
- min-height: 50px;
-}
-
-.branch-analysis-list > ul {
- padding-top: 18px;
-}
-
-.branch-analysis-date {
- margin-bottom: 16px;
- font-size: 15px;
- font-weight: bold;
-}
-
-.branch-analysis-day {
- margin-top: var(--gridSize);
- margin-bottom: calc(3 * var(--gridSize));
-}
-
-.branch-analysis {
- display: flex;
- justify-content: space-between;
- padding: var(--gridSize);
- border-top: 1px solid var(--barBorderColor);
- border-bottom: 1px solid var(--barBorderColor);
- cursor: not-allowed;
-}
-
-.branch-analysis + .branch-analysis {
- border-top: none;
-}
-
-.branch-analysis:hover {
- background-color: var(--disableGrayBg);
-}
-
-.branch-analysis > .project-activity-events {
- flex: 1 0 50%;
-}
-
-.branch-analysis-time {
- width: 150px;
-}
-
-.branch-analysis-version-badge {
- margin-left: -12px;
- padding-top: var(--gridSize);
- padding-bottom: var(--gridSize);
- background-color: white;
-}
-
-.branch-analysis-version-badge.sticky + .branch-analysis-days-list {
- padding-top: 36px;
-}
-
-.branch-analysis-version-badge.sticky,
-.branch-analysis-version-badge.first {
- position: absolute;
- top: 1px;
- left: 13px;
- right: 16px;
- padding-top: calc(3 * var(--gridSize));
- z-index: var(--belowNormalZIndex);
-}
-
-.branch-analysis-version-badge .badge {
- max-width: 385px;
- border-radius: 0 2px 2px 0;
- font-weight: bold;
- font-size: var(--smallFontSize);
- letter-spacing: 0;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
.branch-setting-warning {
background-color: var(--alertBackgroundWarning) !important;
}
diff --git a/server/sonar-web/src/main/js/components/new-code-definition/NewCodeDefinitionAnalysisWarning.tsx b/server/sonar-web/src/main/js/components/new-code-definition/NewCodeDefinitionAnalysisWarning.tsx
index 44b830290dd..375121bdf9c 100644
--- a/server/sonar-web/src/main/js/components/new-code-definition/NewCodeDefinitionAnalysisWarning.tsx
+++ b/server/sonar-web/src/main/js/components/new-code-definition/NewCodeDefinitionAnalysisWarning.tsx
@@ -17,27 +17,29 @@
* 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, Link } from 'design-system';
import * as React from 'react';
+import { useDocUrl } from '../../helpers/docs';
import { translate } from '../../helpers/l10n';
-import DocLink from '../common/DocLink';
-import { Alert } from '../ui/Alert';
export default function NewCodeDefinitionAnalysisWarning() {
+ const toStatic = useDocUrl('/project-administration/defining-new-code/');
return (
- <Alert variant="warning" className="sw-mb-4 sw-max-w-[800px]">
- <p className="sw-mb-2 sw-font-bold">
- {translate('baseline.specific_analysis.compliance_warning.title')}
- </p>
- <p className="sw-mb-2">
- {translate('baseline.specific_analysis.compliance_warning.explanation')}
- </p>
- <p>
- {translate('learn_more')}:&nbsp;
- <DocLink to="/project-administration/defining-new-code/">
- {translate('baseline.specific_analysis.compliance_warning.link')}
- </DocLink>
- </p>
- </Alert>
+ <FlagMessage variant="warning" className="sw-mb-4 sw-max-w-[800px]">
+ <div>
+ <p className="sw-mb-2 sw-font-bold">
+ {translate('baseline.specific_analysis.compliance_warning.title')}
+ </p>
+ <p className="sw-mb-2">
+ {translate('baseline.specific_analysis.compliance_warning.explanation')}
+ </p>
+ <p>
+ {translate('learn_more')}:
+ <Link className="sw-ml-2" to={toStatic}>
+ {translate('learn_more')}
+ </Link>
+ </p>
+ </div>
+ </FlagMessage>
);
}
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 e57f0450734..f5e0132f773 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -695,11 +695,6 @@ branch_list.default_setting=Project setting
baseline.new_code_period_for_branch_x=New Code for {0}
baseline.new_code_period_for_branch_x.question=Choose the baseline for new code for this branch
-baseline.analysis_from=Analysis from:
-baseline.branch_analyses.ranges.30days=Last 30 days
-baseline.branch_analyses.ranges.allTime=All time
-baseline.branch_analyses.analysis_for_x=Analysis for {0}
-baseline.no_analyses=No analyses
regulatory_report.page=Regulatory Report
regulatory_report.description1=The regulatory report is a zip file containing a snapshot of the selected branch. It contains: