diff options
author | Jacek <jacek.poreda@sonarsource.com> | 2019-09-06 10:19:26 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2019-09-24 20:21:17 +0200 |
commit | e8f1a84869bcb72af1ca30fa78b9c936a3e028a3 (patch) | |
tree | c525ec29534c2266bd7cc76cfab344cfa6e1c3ae /server/sonar-web | |
parent | 3079907d1f13a509709c72a5dade87d129a4eb5c (diff) | |
download | sonarqube-e8f1a84869bcb72af1ca30fa78b9c936a3e028a3.tar.gz sonarqube-e8f1a84869bcb72af1ca30fa78b9c936a3e028a3.zip |
Fix conflicts after rebase
Diffstat (limited to 'server/sonar-web')
18 files changed, 478 insertions, 153 deletions
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.tsx index 25171679c5c..ff168b99032 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.tsx @@ -157,10 +157,7 @@ export default class ProjectActivityAnalysesList extends React.PureComponent<Pro }; shouldRenderBaselineMarker(analysis: T.ParsedAnalysis): boolean { - return Boolean( - analysis.manualNewCodePeriodBaseline || - (this.props.leakPeriodDate && isEqual(this.props.leakPeriodDate, analysis.date)) - ); + return Boolean(this.props.leakPeriodDate && isEqual(this.props.leakPeriodDate, analysis.date)); } renderAnalysis(analysis: T.ParsedAnalysis) { diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/App-test.tsx index 84c2a008476..1e1b5faf34f 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/App-test.tsx @@ -40,15 +40,6 @@ it('should not display reset button if project setting is not set', () => { expect(wrapper.find('Button')).toHaveLength(0); }); -it('should display reset button if project setting is set', async () => { - (getNewCodePeriod as jest.Mock).mockResolvedValue({ type: 'NUMBER_OF_DAYS', value: '27' }); - - const wrapper = shallowRender(); - - await waitAndUpdate(wrapper); - expect(wrapper.find('Button')).toHaveLength(1); -}); - it('should reset the setting correctly', async () => { const wrapper = shallowRender(); await waitAndUpdate(wrapper); diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/ProjectBaselineSelector-test.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/ProjectBaselineSelector-test.tsx index 74384231bf2..68b96d50a0e 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/ProjectBaselineSelector-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/ProjectBaselineSelector-test.tsx @@ -25,13 +25,22 @@ import ProjectBaselineSelector, { it('should render correctly', () => { expect(shallowRender()).toMatchSnapshot(); - expect(shallowRender({ branchesEnabled: false })).toMatchSnapshot(); + expect( + shallowRender({ + branchesEnabled: false, + generalSetting: { type: 'NUMBER_OF_DAYS', value: '23' } + }) + ).toMatchSnapshot(); + expect( + shallowRender({ branchesEnabled: false, generalSetting: { type: 'NUMBER_OF_DAYS', value: '' } }) + ).toMatchSnapshot(); }); it('should not show save button when unchanged', () => { const wrapper = shallowRender({ currentSetting: 'PREVIOUS_VERSION', - selected: 'PREVIOUS_VERSION' + selected: 'PREVIOUS_VERSION', + overrideGeneralSetting: true }); expect( wrapper @@ -42,7 +51,11 @@ it('should not show save button when unchanged', () => { }); it('should show save button when changed', () => { - const wrapper = shallowRender({ currentSetting: 'PREVIOUS_VERSION', selected: 'NUMBER_OF_DAYS' }); + const wrapper = shallowRender({ + currentSetting: 'PREVIOUS_VERSION', + selected: 'NUMBER_OF_DAYS', + overrideGeneralSetting: true + }); expect(wrapper.find('SubmitButton')).toHaveLength(1); }); @@ -51,7 +64,8 @@ it('should show save button when value changed', () => { currentSetting: 'NUMBER_OF_DAYS', currentSettingValue: '23', days: '25', - selected: 'NUMBER_OF_DAYS' + selected: 'NUMBER_OF_DAYS', + overrideGeneralSetting: true }); expect(wrapper.find('SubmitButton')).toHaveLength(1); }); @@ -61,7 +75,8 @@ it('should disable the save button when saving', () => { currentSetting: 'NUMBER_OF_DAYS', currentSettingValue: '25', saving: true, - selected: 'PREVIOUS_VERSION' + selected: 'PREVIOUS_VERSION', + overrideGeneralSetting: true }); expect( @@ -76,7 +91,8 @@ it('should disable the save button when date is invalid', () => { const wrapper = shallowRender({ currentSetting: 'PREVIOUS_VERSION', days: 'hello', - selected: 'NUMBER_OF_DAYS' + selected: 'NUMBER_OF_DAYS', + overrideGeneralSetting: true }); expect( @@ -93,10 +109,13 @@ function shallowRender(props: Partial<ProjectBaselineSelectorProps> = {}) { branchesEnabled={true} component="" days="12" + generalSetting={{}} onSelectAnalysis={jest.fn()} onSelectDays={jest.fn()} onSelectSetting={jest.fn()} onSubmit={jest.fn()} + onToggleSpecificSetting={jest.fn()} + overrideGeneralSetting={false} saving={false} {...props} /> diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/BranchList-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/BranchList-test.tsx.snap index 571628b3fda..8d070004181 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/BranchList-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/BranchList-test.tsx.snap @@ -11,11 +11,6 @@ exports[`should render correctly 1`] = ` branch_list.branch </th> <th - className="thin" - > - - </th> - <th className="thin nowrap huge-spacer-right" > branch_list.current_setting @@ -56,7 +51,6 @@ exports[`should render correctly 1`] = ` branches.main_branch </div> </td> - <td /> <td className="huge-spacer-right nowrap" > @@ -98,17 +92,10 @@ exports[`should render correctly 1`] = ` /> branch-6.7 </td> - <td> - <span - className="badge badge-info" - > - default - </span> - </td> <td className="huge-spacer-right nowrap" > - baseline.previous_version + branch_list.default_setting </td> <td className="text-right" diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/ProjectBaselineSelector-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/ProjectBaselineSelector-test.tsx.snap index d938d19be84..215bbdd3965 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/ProjectBaselineSelector-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/ProjectBaselineSelector-test.tsx.snap @@ -6,20 +6,56 @@ exports[`should render correctly 1`] = ` onSubmit={[MockFunction]} > <div - className="branch-baseline-setting-modal" + className="big-spacer-top spacer-bottom" + role="radiogroup" + > + <Radio + checked={true} + className="big-spacer-bottom" + onCheck={[Function]} + value="general" + > + project_baseline.general_setting + </Radio> + <div + className="big-spacer-left" + > + <div + className="general-setting" + > + <strong> + baseline.previous_version + </strong> + : + baseline.previous_version.description + </div> + </div> + <Radio + checked={false} + className="huge-spacer-top" + onCheck={[Function]} + value="specific" + > + project_baseline.specific_setting + </Radio> + </div> + <div + className="big-spacer-left big-spacer-right branch-baseline-setting-modal" > <div className="display-flex-row big-spacer-bottom" role="radiogroup" > <BaselineSettingPreviousVersion + disabled={true} onSelect={[MockFunction]} selected={false} /> <BaselineSettingDays days="12" + disabled={true} isChanged={false} - isValid={false} + isValid={true} onChangeDays={[MockFunction]} onSelect={[MockFunction]} selected={false} @@ -54,25 +90,151 @@ exports[`should render correctly 2`] = ` onSubmit={[MockFunction]} > <div - className="branch-baseline-setting-modal" + className="big-spacer-top spacer-bottom" + role="radiogroup" + > + <Radio + checked={true} + className="big-spacer-bottom" + onCheck={[Function]} + value="general" + > + project_baseline.general_setting + </Radio> + <div + className="big-spacer-left" + > + <div + className="general-setting" + > + <strong> + baseline.number_days (duration.days.23) + </strong> + : + baseline.number_days.description + </div> + </div> + <Radio + checked={false} + className="huge-spacer-top" + onCheck={[Function]} + value="specific" + > + project_baseline.specific_setting + </Radio> + </div> + <div + className="big-spacer-left big-spacer-right branch-baseline-setting-modal" + > + <div + className="display-flex-row big-spacer-bottom" + role="radiogroup" + > + <BaselineSettingPreviousVersion + disabled={true} + onSelect={[MockFunction]} + selected={false} + /> + <BaselineSettingDays + days="12" + disabled={true} + isChanged={false} + isValid={true} + onChangeDays={[MockFunction]} + onSelect={[MockFunction]} + selected={false} + /> + <BaselineSettingAnalysis + disabled={true} + onSelect={[MockFunction]} + selected={false} + /> + </div> + </div> + <div + className="big-spacer-top invisible" + > + <p + className="spacer-bottom" + > + baseline.next_analysis_notice + </p> + <DeferredSpinner + className="spacer-right" + loading={false} + timeout={100} + /> + <SubmitButton + disabled={true} + > + save + </SubmitButton> + </div> +</form> +`; + +exports[`should render correctly 3`] = ` +<form + className="project-baseline-selector" + onSubmit={[MockFunction]} +> + <div + className="big-spacer-top spacer-bottom" + role="radiogroup" + > + <Radio + checked={true} + className="big-spacer-bottom" + onCheck={[Function]} + value="general" + > + project_baseline.general_setting + </Radio> + <div + className="big-spacer-left" + > + <div + className="general-setting" + > + <strong> + baseline.number_days (duration.days.?) + </strong> + : + baseline.number_days.description + </div> + </div> + <Radio + checked={false} + className="huge-spacer-top" + onCheck={[Function]} + value="specific" + > + project_baseline.specific_setting + </Radio> + </div> + <div + className="big-spacer-left big-spacer-right branch-baseline-setting-modal" > <div className="display-flex-row big-spacer-bottom" role="radiogroup" > <BaselineSettingPreviousVersion + disabled={true} onSelect={[MockFunction]} selected={false} /> <BaselineSettingDays days="12" + disabled={true} isChanged={false} - isValid={false} + isValid={true} onChangeDays={[MockFunction]} onSelect={[MockFunction]} selected={false} /> <BaselineSettingAnalysis + disabled={true} onSelect={[MockFunction]} selected={false} /> diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/utils-test.ts index c77f816b373..a06699d2261 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/projectBaseline/__tests__/utils-test.ts @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { getSettingValue } from '../utils'; +import { getSettingValue, validateSetting } from '../utils'; describe('getSettingValue', () => { it('should work for Days', () => { @@ -38,3 +38,78 @@ describe('getSettingValue', () => { ).toBeUndefined(); }); }); + +describe('validateSettings', () => { + it('should validate at branch level', () => { + expect(validateSetting({ days: '' })).toEqual({ isChanged: false, isValid: false }); + expect( + validateSetting({ + currentSetting: 'PREVIOUS_VERSION', + days: '12', + selected: 'NUMBER_OF_DAYS' + }) + ).toEqual({ isChanged: true, isValid: true }); + expect( + validateSetting({ + currentSetting: 'PREVIOUS_VERSION', + days: 'nope', + selected: 'NUMBER_OF_DAYS' + }) + ).toEqual({ isChanged: true, isValid: false }); + expect( + validateSetting({ + currentSetting: 'NUMBER_OF_DAYS', + currentSettingValue: '15', + days: '15', + selected: 'NUMBER_OF_DAYS' + }) + ).toEqual({ isChanged: false, isValid: true }); + expect( + validateSetting({ + currentSetting: 'NUMBER_OF_DAYS', + currentSettingValue: '15', + days: '13', + selected: 'NUMBER_OF_DAYS' + }) + ).toEqual({ isChanged: true, isValid: true }); + expect( + validateSetting({ + analysis: 'analysis1', + currentSetting: 'SPECIFIC_ANALYSIS', + currentSettingValue: 'analysis1', + days: '', + selected: 'SPECIFIC_ANALYSIS' + }) + ).toEqual({ isChanged: false, isValid: true }); + expect( + validateSetting({ + analysis: 'analysis2', + currentSetting: 'SPECIFIC_ANALYSIS', + currentSettingValue: 'analysis1', + days: '', + selected: 'SPECIFIC_ANALYSIS' + }) + ).toEqual({ isChanged: true, isValid: true }); + }); + + it('should validate at project level', () => { + expect(validateSetting({ days: '', overrideGeneralSetting: false })).toEqual({ + isChanged: false, + isValid: true + }); + expect(validateSetting({ days: '', overrideGeneralSetting: true })).toEqual({ + isChanged: true, + isValid: false + }); + expect( + validateSetting({ + currentSetting: 'PREVIOUS_VERSION', + days: '', + overrideGeneralSetting: false + }) + ).toEqual({ + isChanged: true, + isValid: true + }); + }); +}); diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx index 816c0805985..1c6ab579617 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx @@ -20,9 +20,8 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { Link } from 'react-router'; -import { Button } from 'sonar-ui-common/components/controls/buttons'; import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; -import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; +import { translate } from 'sonar-ui-common/helpers/l10n'; import { getNewCodePeriod, resetNewCodePeriod, setNewCodePeriod } from '../../../api/newCodePeriod'; import '../styles.css'; import { getSettingValue } from '../utils'; @@ -43,6 +42,7 @@ interface State { days: string; generalSetting?: T.NewCodePeriod; loading: boolean; + overrideGeneralSetting?: boolean; saving: boolean; selected?: T.NewCodePeriodSettingType; } @@ -75,13 +75,17 @@ export default class App extends React.PureComponent<Props, State> { }) { const { currentSetting, currentSettingValue, generalSetting } = params; + const defaultDays = + (!currentSetting && generalSetting.type === 'NUMBER_OF_DAYS' && generalSetting.value) || '30'; + return { loading: false, currentSetting, currentSettingValue, generalSetting, - selected: currentSetting, - days: currentSetting === 'NUMBER_OF_DAYS' ? currentSettingValue || '30' : '', + selected: currentSetting || generalSetting.type, + overrideGeneralSetting: Boolean(currentSetting), + days: (currentSetting === 'NUMBER_OF_DAYS' && currentSettingValue) || defaultDays, analysis: (currentSetting === 'SPECIFIC_ANALYSIS' && currentSettingValue) || '' }; } @@ -92,7 +96,7 @@ export default class App extends React.PureComponent<Props, State> { Promise.all([ getNewCodePeriod(), getNewCodePeriod({ - branch: this.props.branchesEnabled ? 'master' : undefined, + branch: !this.props.branchesEnabled ? 'master' : undefined, project: this.props.component.key }) ]).then( @@ -137,11 +141,19 @@ export default class App extends React.PureComponent<Props, State> { handleSelectSetting = (selected?: T.NewCodePeriodSettingType) => this.setState({ selected }); + handleToggleSpecificSetting = (overrideGeneralSetting: boolean) => + this.setState({ overrideGeneralSetting }); + handleSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => { e.preventDefault(); const { component } = this.props; - const { analysis, days, selected: type } = this.state; + const { analysis, days, selected: type, overrideGeneralSetting } = this.state; + + if (!overrideGeneralSetting) { + this.resetSetting(); + return; + } const value = getSettingValue({ type, analysis, days }); @@ -201,17 +213,6 @@ export default class App extends React.PureComponent<Props, State> { ); } - renderGeneralSetting(generalSetting: T.NewCodePeriod) { - if (generalSetting.type === 'NUMBER_OF_DAYS') { - return `${translate('baseline.number_days')} (${translateWithParameters( - 'duration.days', - generalSetting.value || '?' - )})`; - } else { - return translate('baseline.previous_version'); - } - } - render() { const { branchLikes, branchesEnabled, component } = this.props; const { @@ -221,6 +222,7 @@ export default class App extends React.PureComponent<Props, State> { generalSetting, loading, currentSettingValue, + overrideGeneralSetting, saving, selected } = this.state; @@ -231,57 +233,45 @@ export default class App extends React.PureComponent<Props, State> { {loading ? ( <DeferredSpinner /> ) : ( - <div className="panel panel-white"> - {branchesEnabled && ( - <> - <h2>{translate('project_baseline.default_setting')}</h2> - <p>{translate('project_baseline.default_setting.description')}</p> - </> - )} + <div className="panel-white project-baseline"> + {branchesEnabled && <h2>{translate('project_baseline.default_setting')}</h2>} - {generalSetting && ( - <div className="text-right spacer-bottom"> - {currentSetting && ( - <> - <Button className="little-spacer-bottom" onClick={this.resetSetting}> - {translate('project_baseline.reset_to_general')} - </Button> - </> - )} - <div className="spacer-top medium"> - <strong>{translate('project_baseline.general_setting')}: </strong> - {this.renderGeneralSetting(generalSetting)} - </div> - </div> + {generalSetting && overrideGeneralSetting !== undefined && ( + <ProjectBaselineSelector + analysis={analysis} + branchesEnabled={branchesEnabled} + component={component.key} + currentSetting={currentSetting} + currentSettingValue={currentSettingValue} + days={days} + generalSetting={generalSetting} + onSelectAnalysis={this.handleSelectAnalysis} + onSelectDays={this.handleSelectDays} + onSelectSetting={this.handleSelectSetting} + onSubmit={this.handleSubmit} + onToggleSpecificSetting={this.handleToggleSpecificSetting} + overrideGeneralSetting={overrideGeneralSetting} + saving={saving} + selected={selected} + /> )} - - <ProjectBaselineSelector - analysis={analysis} - branchesEnabled={branchesEnabled} - component={component.key} - currentSetting={currentSetting} - currentSettingValue={currentSettingValue} - days={days} - onSelectAnalysis={this.handleSelectAnalysis} - onSelectDays={this.handleSelectDays} - onSelectSetting={this.handleSelectSetting} - onSubmit={this.handleSubmit} - saving={saving} - selected={selected} - /> {generalSetting && branchesEnabled && ( - <BranchList - branchLikes={branchLikes} - component={component} - inheritedSetting={ - currentSetting - ? { - type: currentSetting, - value: currentSettingValue - } - : generalSetting - } - /> + <div className="huge-spacer-top branch-baseline-selector"> + <hr /> + <h2>{translate('project_baseline.configure_branches')}</h2> + <BranchList + branchLikes={branchLikes} + component={component} + inheritedSetting={ + currentSetting + ? { + type: currentSetting, + value: currentSettingValue + } + : generalSetting + } + /> + </div> )} </div> )} diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingAnalysis.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingAnalysis.tsx index 9cb63f366af..f56491c551c 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingAnalysis.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingAnalysis.tsx @@ -22,13 +22,15 @@ import RadioCard from 'sonar-ui-common/components/controls/RadioCard'; import { translate } from 'sonar-ui-common/helpers/l10n'; export interface Props { + disabled?: boolean; onSelect: (selection: T.NewCodePeriodSettingType) => void; selected: boolean; } -export default function BaselineSettingAnalysis({ onSelect, selected }: Props) { +export default function BaselineSettingAnalysis({ disabled, onSelect, selected }: Props) { return ( <RadioCard + disabled={disabled} onClick={() => onSelect('SPECIFIC_ANALYSIS')} selected={selected} title={translate('baseline.specific_analysis')}> diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingDays.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingDays.tsx index 3e8b505283d..a2d859bafe0 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingDays.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingDays.tsx @@ -25,6 +25,7 @@ import { translate } from 'sonar-ui-common/helpers/l10n'; export interface Props { className?: string; days: string; + disabled?: boolean; isChanged: boolean; isValid: boolean; onChangeDays: (value: string) => void; @@ -33,10 +34,11 @@ export interface Props { } export default function BaselineSettingDays(props: Props) { - const { className, days, isChanged, isValid, onChangeDays, onSelect, selected } = props; + const { className, days, disabled, isChanged, isValid, onChangeDays, onSelect, selected } = props; return ( <RadioCard className={className} + disabled={disabled} onClick={() => onSelect('NUMBER_OF_DAYS')} selected={selected} title={translate('baseline.number_days')}> diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingPreviousVersion.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingPreviousVersion.tsx index aae1d40a52b..a75051c4d0d 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingPreviousVersion.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingPreviousVersion.tsx @@ -22,14 +22,17 @@ import RadioCard from 'sonar-ui-common/components/controls/RadioCard'; import { translate } from 'sonar-ui-common/helpers/l10n'; export interface Props { + disabled?: boolean; isDefault?: boolean; onSelect: (selection: T.NewCodePeriodSettingType) => void; selected: boolean; } -export default function BaselineSettingPreviousVersion({ isDefault, onSelect, selected }: Props) { +export default function BaselineSettingPreviousVersion(props: Props) { + const { disabled, isDefault, onSelect, selected } = props; return ( <RadioCard + disabled={disabled} onClick={() => onSelect('PREVIOUS_VERSION')} selected={selected} title={ diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx index b2ed30fd434..04bb1688782 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx @@ -26,6 +26,7 @@ import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; import { parseDate, toShortNotSoISOString } from 'sonar-ui-common/helpers/dates'; import { translate } from 'sonar-ui-common/helpers/l10n'; +import { scrollToElement } from 'sonar-ui-common/helpers/scrolling'; import { getProjectActivity } from '../../../api/projectActivity'; import DateFormatter from '../../../components/intl/DateFormatter'; import TimeFormatter from '../../../components/intl/TimeFormatter'; @@ -49,6 +50,7 @@ interface State { export default class BranchAnalysisList extends React.PureComponent<Props, State> { mounted = false; badges: T.Dict<HTMLDivElement> = {}; + rootNodeRef: React.RefObject<HTMLDivElement>; state: State = { analyses: [], loading: true, @@ -58,6 +60,7 @@ export default class BranchAnalysisList extends React.PureComponent<Props, State constructor(props: Props) { super(props); + this.rootNodeRef = React.createRef<HTMLDivElement>(); this.updateScroll = throttle(this.updateScroll, 20); } @@ -70,6 +73,13 @@ export default class BranchAnalysisList extends React.PureComponent<Props, State this.mounted = false; } + scrollToSelected() { + const selectedNode = document.querySelector('.branch-analysis.selected'); + if (this.rootNodeRef.current && selectedNode) { + scrollToElement(selectedNode, { parent: this.rootNodeRef.current, bottomOffset: 40 }); + } + } + fetchAnalyses(initial = false) { const { analysis, branch, component } = this.props; const { range } = this.state; @@ -86,13 +96,18 @@ export default class BranchAnalysisList extends React.PureComponent<Props, State return; } - this.setState({ - analyses: result.analyses.map(analysis => ({ - ...analysis, - date: parseDate(analysis.date) - })) as T.ParsedAnalysis[], - loading: false - }); + this.setState( + { + analyses: result.analyses.map(analysis => ({ + ...analysis, + date: parseDate(analysis.date) + })) as T.ParsedAnalysis[], + loading: false + }, + () => { + this.scrollToSelected(); + } + ); }); } @@ -115,11 +130,9 @@ export default class BranchAnalysisList extends React.PureComponent<Props, State } }; - shouldStick = (version: string, index: number) => { + shouldStick = (version: string) => { const badge = this.badges[version]; - return ( - badge && Number(badge.getAttribute('originOffsetTop')) < this.state.scroll + 18 + index * 2 - ); + return badge && Number(badge.getAttribute('originOffsetTop')) < this.state.scroll + 10; }; getRangeOptions() { @@ -168,7 +181,10 @@ export default class BranchAnalysisList extends React.PureComponent<Props, State /> </div> <div className="branch-analysis-list-wrapper"> - <div className="bordered branch-analysis-list" onScroll={this.handleScroll}> + <div + className="bordered branch-analysis-list" + onScroll={this.handleScroll} + ref={this.rootNodeRef}> {loading && <DeferredSpinner className="big-spacer-top" />} {!loading && !hasFilteredData ? ( @@ -188,7 +204,7 @@ export default class BranchAnalysisList extends React.PureComponent<Props, State <div className={classNames('branch-analysis-version-badge', { first: idx === 0, - sticky: this.shouldStick(version.version, idx) + sticky: this.shouldStick(version.version) })} ref={this.registerBadgeNode(version.version)}> <Tooltip @@ -212,7 +228,7 @@ export default class BranchAnalysisList extends React.PureComponent<Props, State version.byDay[day].map(analysis => ( <li className={classNames('branch-analysis', { - selected: false + selected: analysis.key === this.props.analysis })} data-date={parseDate(analysis.date).valueOf()} key={analysis.key} diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchList.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchList.tsx index 234cac81b58..95ad1db5475 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchList.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchList.tsx @@ -168,7 +168,6 @@ export default class BranchList extends React.PureComponent<Props, State> { <thead> <tr> <th>{translate('branch_list.branch')}</th> - <th className="thin"> </th> <th className="thin nowrap huge-spacer-right"> {translate('branch_list.current_setting')} </th> @@ -185,15 +184,10 @@ export default class BranchList extends React.PureComponent<Props, State> { <div className="badge spacer-left">{translate('branches.main_branch')}</div> )} </td> - <td> - {!branch.newCodePeriod && ( - <span className="badge badge-info">{translate('default')}</span> - )} - </td> <td className="huge-spacer-right nowrap"> {branch.newCodePeriod ? this.renderNewCodePeriodSetting(branch.newCodePeriod) - : this.renderNewCodePeriodSetting(this.props.inheritedSetting)} + : translate('branch_list.default_setting')} </td> <td className="text-right"> <ActionsDropdown> diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineSelector.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineSelector.tsx index 5aabd1ad5ac..e587ae19b25 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineSelector.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineSelector.tsx @@ -20,8 +20,9 @@ import * as classNames from 'classnames'; import * as React from 'react'; import { SubmitButton } from 'sonar-ui-common/components/controls/buttons'; +import Radio from 'sonar-ui-common/components/controls/Radio'; import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; -import { translate } from 'sonar-ui-common/helpers/l10n'; +import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; import { validateSetting } from '../utils'; import BaselineSettingAnalysis from './BaselineSettingAnalysis'; import BaselineSettingDays from './BaselineSettingDays'; @@ -35,12 +36,36 @@ export interface ProjectBaselineSelectorProps { currentSetting?: T.NewCodePeriodSettingType; currentSettingValue?: string; days: string; + generalSetting: T.NewCodePeriod; onSelectAnalysis: (analysis: T.ParsedAnalysis) => void; onSelectDays: (value: string) => void; - onSelectSetting: (value: T.NewCodePeriodSettingType) => void; + onSelectSetting: (value?: T.NewCodePeriodSettingType) => void; onSubmit: (e: React.SyntheticEvent<HTMLFormElement>) => void; + onToggleSpecificSetting: (selection: boolean) => void; saving: boolean; selected?: T.NewCodePeriodSettingType; + overrideGeneralSetting: boolean; +} + +function renderGeneralSetting(generalSetting: T.NewCodePeriod) { + let setting: string; + let description: string; + if (generalSetting.type === 'NUMBER_OF_DAYS') { + setting = `${translate('baseline.number_days')} (${translateWithParameters( + 'duration.days', + generalSetting.value || '?' + )})`; + description = translate('baseline.number_days.description'); + } else { + setting = translate('baseline.previous_version'); + description = translate('baseline.previous_version.description'); + } + + return ( + <div className="general-setting"> + <strong>{setting}</strong>: {description} + </div> + ); } export default function ProjectBaselineSelector(props: ProjectBaselineSelectorProps) { @@ -49,10 +74,12 @@ export default function ProjectBaselineSelector(props: ProjectBaselineSelectorPr branchesEnabled, component, currentSetting, - days, currentSettingValue, + days, + generalSetting, saving, - selected + selected, + overrideGeneralSetting } = props; const { isChanged, isValid } = validateSetting({ @@ -60,29 +87,52 @@ export default function ProjectBaselineSelector(props: ProjectBaselineSelectorPr currentSetting, currentSettingValue, days, - selected + selected, + overrideGeneralSetting }); return ( <form className="project-baseline-selector" onSubmit={props.onSubmit}> - <div className="branch-baseline-setting-modal"> + <div className="big-spacer-top spacer-bottom" role="radiogroup"> + <Radio + checked={!overrideGeneralSetting} + className="big-spacer-bottom" + onCheck={() => props.onToggleSpecificSetting(false)} + value="general"> + {translate('project_baseline.general_setting')} + </Radio> + <div className="big-spacer-left">{renderGeneralSetting(generalSetting)}</div> + + <Radio + checked={overrideGeneralSetting} + className="huge-spacer-top" + onCheck={() => props.onToggleSpecificSetting(true)} + value="specific"> + {translate('project_baseline.specific_setting')} + </Radio> + </div> + + <div className="big-spacer-left big-spacer-right branch-baseline-setting-modal"> <div className="display-flex-row big-spacer-bottom" role="radiogroup"> <BaselineSettingPreviousVersion + disabled={!overrideGeneralSetting} onSelect={props.onSelectSetting} - selected={selected === 'PREVIOUS_VERSION'} + selected={overrideGeneralSetting && selected === 'PREVIOUS_VERSION'} /> <BaselineSettingDays days={days} + disabled={!overrideGeneralSetting} isChanged={isChanged} isValid={isValid} onChangeDays={props.onSelectDays} onSelect={props.onSelectSetting} - selected={selected === 'NUMBER_OF_DAYS'} + selected={overrideGeneralSetting && selected === 'NUMBER_OF_DAYS'} /> {!branchesEnabled && ( <BaselineSettingAnalysis + disabled={!overrideGeneralSetting} onSelect={props.onSelectSetting} - selected={selected === 'SPECIFIC_ANALYSIS'} + selected={overrideGeneralSetting && selected === 'SPECIFIC_ANALYSIS'} /> )} </div> diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/styles.css b/server/sonar-web/src/main/js/apps/projectBaseline/styles.css index 9bffdfa35cc..3a23b727efc 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/styles.css +++ b/server/sonar-web/src/main/js/apps/projectBaseline/styles.css @@ -17,12 +17,23 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +.project-baseline { + padding: calc(4 * var(--gridSize)); +} .project-baseline-selector > .branch-baseline-setting-modal { max-height: 60vh; padding-top: 2px; } +.project-baseline-selector .general-setting { + margin-left: 7px; +} + +.branch-baseline-selector > hr { + margin: 0 calc(-4 * var(--gridSize)) calc(4 * var(--gridSize)); +} + .branch-baseline-setting-modal { display: flex; flex-direction: column; @@ -44,7 +55,7 @@ } .branch-analysis-list > ul { - padding-top: 52px; + padding-top: 18px; } .branch-analysis-date { @@ -90,6 +101,10 @@ 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; diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/utils.ts b/server/sonar-web/src/main/js/apps/projectBaseline/utils.ts index 69b7cfabda0..99ee11e50dd 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/utils.ts +++ b/server/sonar-web/src/main/js/apps/projectBaseline/utils.ts @@ -48,15 +48,30 @@ export function validateSetting(state: { currentSettingValue?: string; days: string; selected?: T.NewCodePeriodSettingType; + overrideGeneralSetting?: boolean; }) { - const { analysis = '', currentSetting, currentSettingValue, days, selected } = state; + const { + analysis = '', + currentSetting, + currentSettingValue, + days, + selected, + overrideGeneralSetting + } = state; - const isChanged = - selected !== currentSetting || - (selected === 'NUMBER_OF_DAYS' && days !== currentSettingValue) || - (selected === 'SPECIFIC_ANALYSIS' && analysis !== currentSettingValue); + let isChanged; + if (!currentSetting && overrideGeneralSetting !== undefined) { + isChanged = overrideGeneralSetting; + } else { + isChanged = + overrideGeneralSetting === false || + selected !== currentSetting || + (selected === 'NUMBER_OF_DAYS' && days !== currentSettingValue) || + (selected === 'SPECIFIC_ANALYSIS' && analysis !== currentSettingValue); + } const isValid = + overrideGeneralSetting === false || selected === 'PREVIOUS_VERSION' || (selected === 'SPECIFIC_ANALYSIS' && analysis.length > 0) || (selected === 'NUMBER_OF_DAYS' && validateDays(days)); diff --git a/server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx b/server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx index 82f282f8701..1b7d8e7230e 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx @@ -19,7 +19,7 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import { SubmitButton } from 'sonar-ui-common/components/controls/buttons'; +import { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons'; import AlertSuccessIcon from 'sonar-ui-common/components/icons/AlertSuccessIcon'; import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; import { translate } from 'sonar-ui-common/helpers/l10n'; @@ -84,6 +84,13 @@ export default class NewCodePeriod extends React.PureComponent<{}, State> { this.setState({ selected, success: false }); }; + onCancel = () => { + this.setState(({ currentSetting, currentSettingValue, days }) => ({ + selected: currentSetting, + days: currentSetting === 'NUMBER_OF_DAYS' ? String(currentSettingValue) : days + })); + }; + onSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => { e.preventDefault(); @@ -191,6 +198,9 @@ export default class NewCodePeriod extends React.PureComponent<{}, State> { <SubmitButton disabled={saving || !isValid}> {translate('save')} </SubmitButton> + <ResetButtonLink className="spacer-left" onClick={this.onCancel}> + {translate('cancel')} + </ResetButtonLink> </div> )} {!saving && !loading && success && ( diff --git a/server/sonar-web/src/main/js/helpers/__tests__/periods-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/periods-test.ts index 917b797d311..53689b77ce4 100644 --- a/server/sonar-web/src/main/js/helpers/__tests__/periods-test.ts +++ b/server/sonar-web/src/main/js/helpers/__tests__/periods-test.ts @@ -85,15 +85,12 @@ describe('getPeriodLabel', () => { it('should handle SPECIFIC_ANALYSIS', () => { expect( - getPeriodLabel( - mockPeriod({ - mode: 'SPECIFIC_ANALYSIS', - parameter: 'should be overriden' - }), - formatter - ) - ).toBe('overview.period.specific_analysis.2019-04-23T02:12:32+0100'); - expect(formatter).toBeCalled(); + getPeriodLabel(mockPeriod({ mode: 'SPECIFIC_ANALYSIS', modeParam: 'A658678DE' }), formatter) + ).toBe('overview.period.specific_analysis.A658678DE'); + expect(getPeriodLabel(mockPeriod({ mode: 'SPECIFIC_ANALYSIS' }), formatter)).toBe( + 'overview.period.specific_analysis.2019-04-23T02:12:32+0100' + ); + expect(formatter).toBeCalledTimes(1); }); it('should handle PREVIOUS_VERSION', () => { diff --git a/server/sonar-web/src/main/js/helpers/periods.ts b/server/sonar-web/src/main/js/helpers/periods.ts index 6a9702fec7b..b2442175b78 100644 --- a/server/sonar-web/src/main/js/helpers/periods.ts +++ b/server/sonar-web/src/main/js/helpers/periods.ts @@ -43,7 +43,7 @@ export function getPeriodLabel( switch (period.mode) { case 'SPECIFIC_ANALYSIS': - parameter = dateFormatter(period.date); + parameter = parameter || dateFormatter(period.date); break; case 'PREVIOUS_VERSION': parameter = parameter || dateFormatter(period.date); |