diff options
author | Kevin Silva <kevin.silva@sonarsource.com> | 2023-10-20 13:29:42 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-10-25 20:02:59 +0000 |
commit | 5a5718ef3e3867d06091ca8eca59fde9f1d673ec (patch) | |
tree | 6f2727ae94f7d642982027360cc6c9237e1a7305 | |
parent | e875fbe32a1cf60e6d6598f80c2b89da9fa11117 (diff) | |
download | sonarqube-5a5718ef3e3867d06091ca8eca59fde9f1d673ec.tar.gz sonarqube-5a5718ef3e3867d06091ca8eca59fde9f1d673ec.zip |
SONAR-20814 - Update layout when SPECIFIC_ANALYSIS is selected
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')}: - <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: |