@@ -21,7 +21,7 @@ | |||
"react-dom": "16.13.0", | |||
"react-helmet": "5.2.1", | |||
"react-typography": "0.16.19", | |||
"sonar-ui-common": "1.0.4", | |||
"sonar-ui-common": "1.0.6", | |||
"typography": "0.16.19" | |||
}, | |||
"devDependencies": { |
@@ -12736,10 +12736,10 @@ sockjs@0.3.19: | |||
faye-websocket "^0.10.0" | |||
uuid "^3.0.1" | |||
sonar-ui-common@1.0.4: | |||
version "1.0.4" | |||
resolved "https://repox.jfrog.io/repox/api/npm/npm/sonar-ui-common/-/sonar-ui-common-1.0.4.tgz#342cb674a560a79cbae47ecbf60ab47fe1052fa4" | |||
integrity sha1-NCy2dKVgp5y65H7L9gq0f+EFL6Q= | |||
sonar-ui-common@1.0.6: | |||
version "1.0.6" | |||
resolved "https://repox.jfrog.io/repox/api/npm/npm/sonar-ui-common/-/sonar-ui-common-1.0.6.tgz#0b22b9b35e4e210b34304780328b9831bf1f7a58" | |||
integrity sha1-CyK5s15OIQs0MEeAMouYMb8felg= | |||
dependencies: | |||
"@types/react-select" "1.2.6" | |||
classnames "2.2.6" |
@@ -38,7 +38,7 @@ | |||
"rehype-slug": "3.0.0", | |||
"remark-custom-blocks": "2.5.0", | |||
"remark-rehype": "6.0.0", | |||
"sonar-ui-common": "1.0.4", | |||
"sonar-ui-common": "1.0.6", | |||
"unist-util-visit": "2.0.2", | |||
"valid-url": "1.0.9", | |||
"whatwg-fetch": "3.0.0" |
@@ -70,41 +70,39 @@ export function BranchOverviewRenderer(props: BranchOverviewRendererProps) { | |||
{projectIsEmpty ? ( | |||
<NoCodeWarning branchLike={branchLike} component={component} measures={measures} /> | |||
) : ( | |||
<> | |||
<div className="display-flex-row"> | |||
<div className="width-25 big-spacer-right"> | |||
<QualityGatePanel | |||
<div className="display-flex-row"> | |||
<div className="width-25 big-spacer-right"> | |||
<QualityGatePanel | |||
component={component} | |||
loading={loadingStatus} | |||
qgStatuses={qgStatuses} | |||
/> | |||
</div> | |||
<div className="flex-1"> | |||
<div className="display-flex-column"> | |||
<MeasuresPanel | |||
branchLike={branchLike} | |||
component={component} | |||
leakPeriod={leakPeriod} | |||
loading={loadingStatus} | |||
qgStatuses={qgStatuses} | |||
measures={measures} | |||
/> | |||
</div> | |||
<div className="flex-1"> | |||
<div className="display-flex-column"> | |||
<MeasuresPanel | |||
branchLike={branchLike} | |||
component={component} | |||
leakPeriod={leakPeriod} | |||
loading={loadingStatus} | |||
measures={measures} | |||
/> | |||
<ActivityPanel | |||
analyses={analyses} | |||
branchLike={branchLike} | |||
component={component} | |||
graph={graph} | |||
leakPeriodDate={leakPeriod && parseDate(leakPeriod.date)} | |||
loading={loadingHistory} | |||
measuresHistory={measuresHistory} | |||
metrics={metrics} | |||
onGraphChange={onGraphChange} | |||
/> | |||
</div> | |||
<ActivityPanel | |||
analyses={analyses} | |||
branchLike={branchLike} | |||
component={component} | |||
graph={graph} | |||
leakPeriodDate={leakPeriod && parseDate(leakPeriod.date)} | |||
loading={loadingHistory} | |||
measuresHistory={measuresHistory} | |||
metrics={metrics} | |||
onGraphChange={onGraphChange} | |||
/> | |||
</div> | |||
</div> | |||
</> | |||
</div> | |||
)} | |||
</div> | |||
</div> |
@@ -20,16 +20,16 @@ | |||
import * as classNames from 'classnames'; | |||
import { debounce } from 'lodash'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { Link } from 'react-router'; | |||
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'; | |||
import { getNewCodePeriod, resetNewCodePeriod, setNewCodePeriod } from '../../../api/newCodePeriod'; | |||
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; | |||
import { BranchLike } from '../../../types/branch-like'; | |||
import { isBranch, sortBranches } from '../../../helpers/branch-like'; | |||
import { Branch, BranchLike } from '../../../types/branch-like'; | |||
import '../styles.css'; | |||
import { getSettingValue } from '../utils'; | |||
import AppHeader from './AppHeader'; | |||
import BranchList from './BranchList'; | |||
import ProjectBaselineSelector from './ProjectBaselineSelector'; | |||
@@ -42,17 +42,21 @@ interface Props { | |||
interface State { | |||
analysis?: string; | |||
branchList: Branch[]; | |||
currentSetting?: T.NewCodePeriodSettingType; | |||
currentSettingValue?: string; | |||
days: string; | |||
generalSetting?: T.NewCodePeriod; | |||
loading: boolean; | |||
overrideGeneralSetting?: boolean; | |||
referenceBranch?: string; | |||
saving: boolean; | |||
selected?: T.NewCodePeriodSettingType; | |||
success?: boolean; | |||
} | |||
const DEFAULT_NUMBER_OF_DAYS = '30'; | |||
const DEFAULT_GENERAL_SETTING: { type: T.NewCodePeriodSettingType } = { | |||
type: 'PREVIOUS_VERSION' | |||
}; | |||
@@ -60,7 +64,8 @@ const DEFAULT_GENERAL_SETTING: { type: T.NewCodePeriodSettingType } = { | |||
export default class App extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = { | |||
days: '30', | |||
branchList: [], | |||
days: DEFAULT_NUMBER_OF_DAYS, | |||
loading: true, | |||
saving: false | |||
}; | |||
@@ -71,6 +76,13 @@ export default class App extends React.PureComponent<Props, State> { | |||
componentDidMount() { | |||
this.mounted = true; | |||
this.fetchLeakPeriodSetting(); | |||
this.sortAndFilterBranches(this.props.branchLikes); | |||
} | |||
componentDidUpdate(prevProps: Props) { | |||
if (prevProps.branchLikes !== this.props.branchLikes) { | |||
this.sortAndFilterBranches(this.props.branchLikes); | |||
} | |||
} | |||
componentWillUnmount() { | |||
@@ -83,9 +95,10 @@ export default class App extends React.PureComponent<Props, State> { | |||
generalSetting: T.NewCodePeriod; | |||
}) { | |||
const { currentSetting, currentSettingValue, generalSetting } = params; | |||
const { referenceBranch } = this.state; | |||
const defaultDays = | |||
(!currentSetting && generalSetting.type === 'NUMBER_OF_DAYS' && generalSetting.value) || '30'; | |||
(generalSetting.type === 'NUMBER_OF_DAYS' && generalSetting.value) || DEFAULT_NUMBER_OF_DAYS; | |||
return { | |||
loading: false, | |||
@@ -95,10 +108,17 @@ export default class App extends React.PureComponent<Props, State> { | |||
selected: currentSetting || generalSetting.type, | |||
overrideGeneralSetting: Boolean(currentSetting), | |||
days: (currentSetting === 'NUMBER_OF_DAYS' && currentSettingValue) || defaultDays, | |||
analysis: (currentSetting === 'SPECIFIC_ANALYSIS' && currentSettingValue) || '' | |||
analysis: (currentSetting === 'SPECIFIC_ANALYSIS' && currentSettingValue) || '', | |||
referenceBranch: | |||
(currentSetting === 'REFERENCE_BRANCH' && currentSettingValue) || referenceBranch | |||
}; | |||
} | |||
sortAndFilterBranches(branchLikes: BranchLike[] = []) { | |||
const branchList = sortBranches(branchLikes.filter(isBranch)); | |||
this.setState({ branchList, referenceBranch: branchList[0].name }); | |||
} | |||
fetchLeakPeriodSetting() { | |||
this.setState({ loading: true }); | |||
@@ -118,7 +138,11 @@ export default class App extends React.PureComponent<Props, State> { | |||
const currentSetting = setting.inherited ? undefined : setting.type || 'PREVIOUS_VERSION'; | |||
this.setState( | |||
this.getUpdatedState({ generalSetting, currentSetting, currentSettingValue }) | |||
this.getUpdatedState({ | |||
generalSetting, | |||
currentSetting, | |||
currentSettingValue | |||
}) | |||
); | |||
} | |||
}, | |||
@@ -150,6 +174,10 @@ export default class App extends React.PureComponent<Props, State> { | |||
handleSelectDays = (days: string) => this.setState({ days }); | |||
handleSelectReferenceBranch = (referenceBranch: string) => { | |||
this.setState({ referenceBranch }); | |||
}; | |||
handleCancel = () => | |||
this.setState( | |||
({ generalSetting = DEFAULT_GENERAL_SETTING, currentSetting, currentSettingValue }) => | |||
@@ -165,14 +193,14 @@ export default class App extends React.PureComponent<Props, State> { | |||
e.preventDefault(); | |||
const { component } = this.props; | |||
const { analysis, days, selected: type, overrideGeneralSetting } = this.state; | |||
const { analysis, days, selected: type, referenceBranch, overrideGeneralSetting } = this.state; | |||
if (!overrideGeneralSetting) { | |||
this.resetSetting(); | |||
return; | |||
} | |||
const value = getSettingValue({ type, analysis, days }); | |||
const value = getSettingValue({ type, analysis, days, referenceBranch }); | |||
if (type) { | |||
this.setState({ saving: true }); | |||
@@ -197,51 +225,18 @@ export default class App extends React.PureComponent<Props, State> { | |||
} | |||
}; | |||
renderHeader() { | |||
return ( | |||
<header className="page-header"> | |||
<h1 className="page-title">{translate('project_baseline.page')}</h1> | |||
<p className="page-description"> | |||
<FormattedMessage | |||
defaultMessage={translate('project_baseline.page.description')} | |||
id="project_baseline.page.description" | |||
values={{ | |||
link: ( | |||
<Link to="/documentation/project-administration/new-code-period/"> | |||
{translate('project_baseline.page.description.link')} | |||
</Link> | |||
) | |||
}} | |||
/> | |||
<br /> | |||
{this.props.canAdmin && ( | |||
<FormattedMessage | |||
defaultMessage={translate('project_baseline.page.description2')} | |||
id="project_baseline.page.description2" | |||
values={{ | |||
link: ( | |||
<Link to="/admin/settings?category=new_code_period"> | |||
{translate('project_baseline.page.description2.link')} | |||
</Link> | |||
) | |||
}} | |||
/> | |||
)} | |||
</p> | |||
</header> | |||
); | |||
} | |||
render() { | |||
const { branchLikes, branchesEnabled, component } = this.props; | |||
const { branchesEnabled, canAdmin, component } = this.props; | |||
const { | |||
analysis, | |||
branchList, | |||
currentSetting, | |||
days, | |||
generalSetting, | |||
loading, | |||
currentSettingValue, | |||
overrideGeneralSetting, | |||
referenceBranch, | |||
saving, | |||
selected, | |||
success | |||
@@ -251,7 +246,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
<> | |||
<Suggestions suggestions="project_baseline" /> | |||
<div className="page page-limited"> | |||
{this.renderHeader()} | |||
<AppHeader canAdmin={!!canAdmin} /> | |||
{loading ? ( | |||
<DeferredSpinner /> | |||
) : ( | |||
@@ -261,6 +256,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
{generalSetting && overrideGeneralSetting !== undefined && ( | |||
<ProjectBaselineSelector | |||
analysis={analysis} | |||
branchList={branchList} | |||
branchesEnabled={branchesEnabled} | |||
component={component.key} | |||
currentSetting={currentSetting} | |||
@@ -270,10 +266,12 @@ export default class App extends React.PureComponent<Props, State> { | |||
onCancel={this.handleCancel} | |||
onSelectAnalysis={this.handleSelectAnalysis} | |||
onSelectDays={this.handleSelectDays} | |||
onSelectReferenceBranch={this.handleSelectReferenceBranch} | |||
onSelectSetting={this.handleSelectSetting} | |||
onSubmit={this.handleSubmit} | |||
onToggleSpecificSetting={this.handleToggleSpecificSetting} | |||
overrideGeneralSetting={overrideGeneralSetting} | |||
referenceBranch={referenceBranch} | |||
saving={saving} | |||
selected={selected} | |||
/> | |||
@@ -290,7 +288,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
<hr /> | |||
<h2>{translate('project_baseline.configure_branches')}</h2> | |||
<BranchList | |||
branchLikes={branchLikes} | |||
branchList={branchList} | |||
component={component} | |||
inheritedSetting={ | |||
currentSetting |
@@ -0,0 +1,64 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 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 * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { Link } from 'react-router'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
export interface AppHeaderProps { | |||
canAdmin: boolean; | |||
} | |||
export default function AppHeader(props: AppHeaderProps) { | |||
const { canAdmin } = props; | |||
return ( | |||
<header className="page-header"> | |||
<h1 className="page-title">{translate('project_baseline.page')}</h1> | |||
<p className="page-description"> | |||
<FormattedMessage | |||
defaultMessage={translate('project_baseline.page.description')} | |||
id="project_baseline.page.description" | |||
values={{ | |||
link: ( | |||
<Link to="/documentation/project-administration/new-code-period/"> | |||
{translate('project_baseline.page.description.link')} | |||
</Link> | |||
) | |||
}} | |||
/> | |||
<br /> | |||
{canAdmin && ( | |||
<FormattedMessage | |||
defaultMessage={translate('project_baseline.page.description2')} | |||
id="project_baseline.page.description2" | |||
values={{ | |||
link: ( | |||
<Link to="/admin/settings?category=new_code_period"> | |||
{translate('project_baseline.page.description2.link')} | |||
</Link> | |||
) | |||
}} | |||
/> | |||
)} | |||
</p> | |||
</header> | |||
); | |||
} |
@@ -0,0 +1,113 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 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 * as React from 'react'; | |||
import RadioCard from 'sonar-ui-common/components/controls/RadioCard'; | |||
import SearchSelect from 'sonar-ui-common/components/controls/SearchSelect'; | |||
import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; | |||
import AlertErrorIcon from 'sonar-ui-common/components/icons/AlertErrorIcon'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
export interface BaselineSettingReferenceBranchProps { | |||
branchList: BranchOption[]; | |||
className?: string; | |||
configuredBranchName?: string; | |||
disabled?: boolean; | |||
onChangeReferenceBranch: (value: string) => void; | |||
onSelect: (selection: T.NewCodePeriodSettingType) => void; | |||
referenceBranch: string; | |||
selected: boolean; | |||
settingLevel: 'project' | 'branch'; | |||
} | |||
export interface BranchOption { | |||
disabled?: boolean; | |||
isInvalid?: boolean; | |||
isMain: boolean; | |||
value: string; | |||
} | |||
function renderBranchOption(option: BranchOption) { | |||
return option.isInvalid ? ( | |||
<Tooltip | |||
overlay={translateWithParameters('baseline.reference_branch.does_not_exist', option.value)}> | |||
<span> | |||
{option.value} <AlertErrorIcon /> | |||
</span> | |||
</Tooltip> | |||
) : ( | |||
<> | |||
<span | |||
title={ | |||
option.disabled ? translate('baseline.reference_branch.cannot_be_itself') : undefined | |||
}> | |||
{option.value} | |||
</span> | |||
{option.isMain && ( | |||
<div className="badge spacer-left">{translate('branches.main_branch')}</div> | |||
)} | |||
</> | |||
); | |||
} | |||
export default function BaselineSettingReferenceBranch(props: BaselineSettingReferenceBranchProps) { | |||
const { branchList, className, disabled, referenceBranch, selected, settingLevel } = props; | |||
const currentBranch = branchList.find(b => b.value === referenceBranch) || { | |||
value: referenceBranch, | |||
isMain: false, | |||
isInvalid: true | |||
}; | |||
return ( | |||
<RadioCard | |||
className={className} | |||
disabled={disabled} | |||
onClick={() => props.onSelect('REFERENCE_BRANCH')} | |||
selected={selected} | |||
title={translate('baseline.reference_branch')}> | |||
<> | |||
<p>{translate('baseline.reference_branch.description')}</p> | |||
{selected && ( | |||
<> | |||
{settingLevel === 'project' && ( | |||
<p className="spacer-top">{translate('baseline.reference_branch.description2')}</p> | |||
)} | |||
<div className="big-spacer-top display-flex-column"> | |||
<label className="text-middle" htmlFor="reference_branch"> | |||
<strong>{translate('baseline.reference_branch.choose')}</strong> | |||
<em className="mandatory">*</em> | |||
</label> | |||
<SearchSelect<BranchOption> | |||
autofocus={false} | |||
className="little-spacer-top spacer-bottom" | |||
defaultOptions={branchList} | |||
minimumQueryLength={1} | |||
onSearch={q => Promise.resolve(branchList.filter(b => b.value.includes(q)))} | |||
onSelect={option => props.onChangeReferenceBranch(option.value)} | |||
renderOption={renderBranchOption} | |||
value={currentBranch} | |||
/> | |||
</div> | |||
</> | |||
)} | |||
</> | |||
</RadioCard> | |||
); | |||
} |
@@ -17,22 +17,13 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as classNames from 'classnames'; | |||
import { subDays } from 'date-fns'; | |||
import { throttle } from 'lodash'; | |||
import * as React from 'react'; | |||
import Radio from 'sonar-ui-common/components/controls/Radio'; | |||
import Select from 'sonar-ui-common/components/controls/Select'; | |||
import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; | |||
import DateFormatter from 'sonar-ui-common/components/intl/DateFormatter'; | |||
import TimeFormatter from 'sonar-ui-common/components/intl/TimeFormatter'; | |||
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 Events from '../../projectActivity/components/Events'; | |||
import { getAnalysesByVersionByDay } from '../../projectActivity/utils'; | |||
import BranchAnalysisListRenderer from './BranchAnalysisListRenderer'; | |||
interface Props { | |||
analysis: string; | |||
@@ -48,10 +39,12 @@ interface State { | |||
scroll: number; | |||
} | |||
const STICKY_BADGE_SCROLL_OFFSET = 10; | |||
export default class BranchAnalysisList extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
badges: T.Dict<HTMLDivElement> = {}; | |||
rootNodeRef: React.RefObject<HTMLDivElement>; | |||
scrollableNode?: HTMLDivElement; | |||
state: State = { | |||
analyses: [], | |||
loading: true, | |||
@@ -61,7 +54,6 @@ 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); | |||
} | |||
@@ -76,8 +68,8 @@ export default class BranchAnalysisList extends React.PureComponent<Props, State | |||
scrollToSelected() { | |||
const selectedNode = document.querySelector('.branch-analysis.selected'); | |||
if (this.rootNodeRef.current && selectedNode) { | |||
scrollToElement(selectedNode, { parent: this.rootNodeRef.current, bottomOffset: 40 }); | |||
if (this.scrollableNode && selectedNode) { | |||
scrollToElement(selectedNode, { parent: this.scrollableNode, bottomOffset: 40 }); | |||
} | |||
} | |||
@@ -133,145 +125,35 @@ export default class BranchAnalysisList extends React.PureComponent<Props, State | |||
shouldStick = (version: string) => { | |||
const badge = this.badges[version]; | |||
return badge && Number(badge.getAttribute('originOffsetTop')) < this.state.scroll + 10; | |||
return ( | |||
!!badge && | |||
Number(badge.getAttribute('originOffsetTop')) < this.state.scroll + STICKY_BADGE_SCROLL_OFFSET | |||
); | |||
}; | |||
getRangeOptions() { | |||
return [ | |||
{ | |||
label: translate('baseline.branch_analyses.ranges.30days'), | |||
value: 30 | |||
}, | |||
{ | |||
label: translate('baseline.branch_analyses.ranges.allTime'), | |||
value: 0 | |||
} | |||
]; | |||
} | |||
handleRangeChange = ({ value }: { value: number }) => { | |||
this.setState({ range: value }, () => this.fetchAnalyses()); | |||
}; | |||
render() { | |||
const { analysis, onSelectAnalysis } = this.props; | |||
const { analyses, loading, range } = this.state; | |||
const byVersionByDay = getAnalysesByVersionByDay(analyses, { | |||
category: '' | |||
}); | |||
const hasFilteredData = | |||
byVersionByDay.length > 1 || | |||
(byVersionByDay.length === 1 && Object.keys(byVersionByDay[0].byDay).length > 0); | |||
return ( | |||
<> | |||
<div className="spacer-bottom"> | |||
{translate('baseline.analysis_from')} | |||
<Select | |||
autoBlur={true} | |||
className="input-medium spacer-left" | |||
clearable={false} | |||
onChange={this.handleRangeChange} | |||
options={this.getRangeOptions()} | |||
searchable={false} | |||
value={range} | |||
/> | |||
</div> | |||
<div className="branch-analysis-list-wrapper"> | |||
<div | |||
className="bordered branch-analysis-list" | |||
onScroll={this.handleScroll} | |||
ref={this.rootNodeRef}> | |||
{loading && <DeferredSpinner className="big-spacer-top" />} | |||
{!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: this.shouldStick(version.version) | |||
})} | |||
ref={this.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={toShortNotSoISOString(Number(day))} | |||
key={day}> | |||
<div className="branch-analysis-date"> | |||
<DateFormatter date={Number(day)} long={true} /> | |||
</div> | |||
<ul className="branch-analysis-analyses-list"> | |||
{version.byDay[day] != null && | |||
version.byDay[day].map(analysis => ( | |||
<li | |||
className={classNames('branch-analysis', { | |||
selected: analysis.key === this.props.analysis | |||
})} | |||
data-date={parseDate(analysis.date).valueOf()} | |||
key={analysis.key} | |||
onClick={() => this.props.onSelectAnalysis(analysis)}> | |||
<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={analyses[0].key === analysis.key} | |||
/> | |||
)} | |||
<div className="analysis-selection-button"> | |||
<Radio | |||
checked={analysis.key === this.props.analysis} | |||
onCheck={() => {}} | |||
value="" | |||
/> | |||
</div> | |||
</li> | |||
))} | |||
</ul> | |||
</li> | |||
))} | |||
</ul> | |||
</li> | |||
); | |||
})} | |||
</ul> | |||
)} | |||
</div> | |||
</div> | |||
</> | |||
<BranchAnalysisListRenderer | |||
analyses={analyses} | |||
handleRangeChange={this.handleRangeChange} | |||
handleScroll={this.handleScroll} | |||
loading={loading} | |||
onSelectAnalysis={onSelectAnalysis} | |||
range={range} | |||
registerBadgeNode={this.registerBadgeNode} | |||
registerScrollableNode={el => { | |||
this.scrollableNode = el; | |||
}} | |||
selectedAnalysisKey={analysis} | |||
shouldStick={this.shouldStick} | |||
/> | |||
); | |||
} | |||
} |
@@ -0,0 +1,186 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 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 * as classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import Radio from 'sonar-ui-common/components/controls/Radio'; | |||
import Select from 'sonar-ui-common/components/controls/Select'; | |||
import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; | |||
import DateFormatter from 'sonar-ui-common/components/intl/DateFormatter'; | |||
import TimeFormatter from 'sonar-ui-common/components/intl/TimeFormatter'; | |||
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 Events from '../../projectActivity/components/Events'; | |||
import { getAnalysesByVersionByDay } from '../../projectActivity/utils'; | |||
export interface BranchAnalysisListRendererProps { | |||
analyses: T.ParsedAnalysis[]; | |||
handleRangeChange: ({ value }: { value: number }) => void; | |||
handleScroll: (e: React.SyntheticEvent<HTMLDivElement>) => void; | |||
loading: boolean; | |||
onSelectAnalysis: (analysis: T.ParsedAnalysis) => void; | |||
range: number; | |||
registerBadgeNode: (version: string) => (el: HTMLDivElement) => void; | |||
registerScrollableNode: (el: HTMLDivElement) => void; | |||
selectedAnalysisKey: string; | |||
shouldStick: (version: string) => boolean; | |||
} | |||
function renderAnalysis(args: { | |||
analysis: T.ParsedAnalysis; | |||
isFirst: boolean; | |||
onSelectAnalysis: (analysis: T.ParsedAnalysis) => void; | |||
selectedAnalysisKey: string; | |||
}) { | |||
const { analysis, isFirst, onSelectAnalysis, selectedAnalysisKey } = args; | |||
return ( | |||
<li | |||
className={classNames('branch-analysis', { | |||
selected: analysis.key === selectedAnalysisKey | |||
})} | |||
data-date={parseDate(analysis.date).valueOf()} | |||
key={analysis.key} | |||
onClick={() => onSelectAnalysis(analysis)}> | |||
<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} onCheck={() => {}} value="" /> | |||
</div> | |||
</li> | |||
); | |||
} | |||
export default function BranchAnalysisListRenderer(props: BranchAnalysisListRendererProps) { | |||
const { analyses, loading, range, selectedAnalysisKey } = props; | |||
const byVersionByDay = React.useMemo( | |||
() => | |||
getAnalysesByVersionByDay(analyses, { | |||
category: '' | |||
}), | |||
[analyses] | |||
); | |||
const hasFilteredData = | |||
byVersionByDay.length > 1 || | |||
(byVersionByDay.length === 1 && Object.keys(byVersionByDay[0].byDay).length > 0); | |||
return ( | |||
<> | |||
<div className="spacer-bottom"> | |||
{translate('baseline.analysis_from')} | |||
<Select | |||
autoBlur={true} | |||
className="input-medium spacer-left" | |||
clearable={false} | |||
onChange={props.handleRangeChange} | |||
options={[ | |||
{ | |||
label: translate('baseline.branch_analyses.ranges.30days'), | |||
value: 30 | |||
}, | |||
{ | |||
label: translate('baseline.branch_analyses.ranges.allTime'), | |||
value: 0 | |||
} | |||
]} | |||
searchable={false} | |||
value={range} | |||
/> | |||
</div> | |||
<div className="branch-analysis-list-wrapper"> | |||
<div | |||
className="bordered branch-analysis-list" | |||
onScroll={props.handleScroll} | |||
ref={props.registerScrollableNode}> | |||
{loading && <DeferredSpinner className="big-spacer-top" />} | |||
{!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={toShortNotSoISOString(Number(day))} | |||
key={day}> | |||
<div className="branch-analysis-date"> | |||
<DateFormatter date={Number(day)} long={true} /> | |||
</div> | |||
<ul className="branch-analysis-analyses-list"> | |||
{version.byDay[day] != null && | |||
version.byDay[day].map(analysis => | |||
renderAnalysis({ | |||
analysis, | |||
selectedAnalysisKey, | |||
isFirst: analyses[0].key === analysis.key, | |||
onSelectAnalysis: props.onSelectAnalysis | |||
}) | |||
)} | |||
</ul> | |||
</li> | |||
))} | |||
</ul> | |||
</li> | |||
); | |||
})} | |||
</ul> | |||
)} | |||
</div> | |||
</div> | |||
</> | |||
); | |||
} |
@@ -24,15 +24,17 @@ import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; | |||
import { toNotSoISOString } from 'sonar-ui-common/helpers/dates'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { setNewCodePeriod } from '../../../api/newCodePeriod'; | |||
import { BranchWithNewCodePeriod } from '../../../types/branch-like'; | |||
import { Branch, BranchWithNewCodePeriod } from '../../../types/branch-like'; | |||
import { getSettingValue, validateSetting } from '../utils'; | |||
import BaselineSettingAnalysis from './BaselineSettingAnalysis'; | |||
import BaselineSettingDays from './BaselineSettingDays'; | |||
import BaselineSettingPreviousVersion from './BaselineSettingPreviousVersion'; | |||
import BaselineSettingReferenceBranch from './BaselineSettingReferenceBranch'; | |||
import BranchAnalysisList from './BranchAnalysisList'; | |||
interface Props { | |||
branch: BranchWithNewCodePeriod; | |||
branchList: Branch[]; | |||
component: string; | |||
onClose: (branch?: string, newSetting?: T.NewCodePeriod) => void; | |||
} | |||
@@ -41,6 +43,7 @@ interface State { | |||
analysis: string; | |||
analysisDate?: Date; | |||
days: string; | |||
referenceBranch: string; | |||
saving: boolean; | |||
selected?: T.NewCodePeriodSettingType; | |||
} | |||
@@ -51,9 +54,13 @@ export default class BranchBaselineSettingModal extends React.PureComponent<Prop | |||
constructor(props: Props) { | |||
super(props); | |||
const otherBranches = props.branchList.filter(b => b.name !== props.branch.name); | |||
const defaultBranch = otherBranches.length > 0 ? otherBranches[0].name : ''; | |||
this.state = { | |||
analysis: this.getValueFromProps('SPECIFIC_ANALYSIS') || '', | |||
days: this.getValueFromProps('NUMBER_OF_DAYS') || '30', | |||
referenceBranch: this.getValueFromProps('REFERENCE_BRANCH') || defaultBranch, | |||
saving: false, | |||
selected: this.props.branch.newCodePeriod && this.props.branch.newCodePeriod.type | |||
}; | |||
@@ -73,13 +80,19 @@ export default class BranchBaselineSettingModal extends React.PureComponent<Prop | |||
: null; | |||
} | |||
branchToOption = (b: Branch) => ({ | |||
value: b.name, | |||
isMain: b.isMain, | |||
disabled: b.name === this.props.branch.name // cannot itself be used as a reference branch | |||
}); | |||
handleSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => { | |||
e.preventDefault(); | |||
const { branch, component } = this.props; | |||
const { analysis, analysisDate, days, selected: type } = this.state; | |||
const { analysis, analysisDate, days, referenceBranch, selected: type } = this.state; | |||
const value = getSettingValue({ type, analysis, days }); | |||
const value = getSettingValue({ type, analysis, days, referenceBranch }); | |||
if (type) { | |||
this.setState({ saving: true }); | |||
@@ -115,11 +128,13 @@ export default class BranchBaselineSettingModal extends React.PureComponent<Prop | |||
handleSelectDays = (days: string) => this.setState({ days }); | |||
handleSelectReferenceBranch = (referenceBranch: string) => this.setState({ referenceBranch }); | |||
handleSelectSetting = (selected: T.NewCodePeriodSettingType) => this.setState({ selected }); | |||
render() { | |||
const { branch } = this.props; | |||
const { analysis, days, saving, selected } = this.state; | |||
const { branch, branchList } = this.props; | |||
const { analysis, days, referenceBranch, saving, selected } = this.state; | |||
const header = translateWithParameters('baseline.new_code_period_for_branch_x', branch.name); | |||
@@ -131,6 +146,7 @@ export default class BranchBaselineSettingModal extends React.PureComponent<Prop | |||
currentSetting, | |||
currentSettingValue, | |||
days, | |||
referenceBranch, | |||
selected | |||
}); | |||
@@ -159,6 +175,14 @@ export default class BranchBaselineSettingModal extends React.PureComponent<Prop | |||
onSelect={this.handleSelectSetting} | |||
selected={selected === 'SPECIFIC_ANALYSIS'} | |||
/> | |||
<BaselineSettingReferenceBranch | |||
branchList={branchList.map(this.branchToOption)} | |||
onChangeReferenceBranch={this.handleSelectReferenceBranch} | |||
onSelect={this.handleSelectSetting} | |||
referenceBranch={referenceBranch} | |||
selected={selected === 'REFERENCE_BRANCH'} | |||
settingLevel="branch" | |||
/> | |||
</div> | |||
{selected === 'SPECIFIC_ANALYSIS' && ( | |||
<BranchAnalysisList |
@@ -18,20 +18,16 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import ActionsDropdown, { | |||
ActionsDropdownItem | |||
} from 'sonar-ui-common/components/controls/ActionsDropdown'; | |||
import DateTimeFormatter from 'sonar-ui-common/components/intl/DateTimeFormatter'; | |||
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { listBranchesNewCodePeriod, resetNewCodePeriod } from '../../../api/newCodePeriod'; | |||
import BranchLikeIcon from '../../../components/icons/BranchLikeIcon'; | |||
import { isBranch, sortBranches } from '../../../helpers/branch-like'; | |||
import { BranchLike, BranchWithNewCodePeriod } from '../../../types/branch-like'; | |||
import { Branch, BranchLike, BranchWithNewCodePeriod } from '../../../types/branch-like'; | |||
import BranchBaselineSettingModal from './BranchBaselineSettingModal'; | |||
import BranchListRow from './BranchListRow'; | |||
interface Props { | |||
branchLikes: BranchLike[]; | |||
branchList: Branch[]; | |||
component: T.Component; | |||
inheritedSetting: T.NewCodePeriod; | |||
} | |||
@@ -66,15 +62,13 @@ export default class BranchList extends React.PureComponent<Props, State> { | |||
const project = this.props.component.key; | |||
this.setState({ loading: true }); | |||
const sortedBranches = this.sortAndFilterBranches(this.props.branchLikes); | |||
listBranchesNewCodePeriod({ project }).then( | |||
branchSettings => { | |||
const newCodePeriods = branchSettings.newCodePeriods | |||
? branchSettings.newCodePeriods.filter(ncp => !ncp.inherited) | |||
: []; | |||
const branchesWithBaseline = sortedBranches.map(b => { | |||
const branchesWithBaseline = this.props.branchList.map(b => { | |||
const newCodePeriod = newCodePeriods.find(ncp => ncp.branchKey === b.name); | |||
if (!newCodePeriod) { | |||
return b; | |||
@@ -119,38 +113,17 @@ export default class BranchList extends React.PureComponent<Props, State> { | |||
} | |||
}; | |||
resetToDefault(branch: string) { | |||
resetToDefault = (branch: string) => { | |||
return resetNewCodePeriod({ | |||
project: this.props.component.key, | |||
branch | |||
}).then(() => { | |||
this.setState({ branches: this.updateBranchNewCodePeriod(branch, undefined) }); | |||
}); | |||
} | |||
renderNewCodePeriodSetting(newCodePeriod: T.NewCodePeriod) { | |||
switch (newCodePeriod.type) { | |||
case 'SPECIFIC_ANALYSIS': | |||
return ( | |||
<> | |||
{`${translate('baseline.specific_analysis')}: `} | |||
{newCodePeriod.effectiveValue ? ( | |||
<DateTimeFormatter date={newCodePeriod.effectiveValue} /> | |||
) : ( | |||
'?' | |||
)} | |||
</> | |||
); | |||
case 'NUMBER_OF_DAYS': | |||
return `${translate('baseline.number_days')}: ${newCodePeriod.value}`; | |||
case 'PREVIOUS_VERSION': | |||
return translate('baseline.previous_version'); | |||
default: | |||
return newCodePeriod.type; | |||
} | |||
} | |||
}; | |||
render() { | |||
const { branchList, inheritedSetting } = this.props; | |||
const { branches, editedBranch, loading } = this.state; | |||
if (branches.length < 1) { | |||
@@ -175,38 +148,21 @@ export default class BranchList extends React.PureComponent<Props, State> { | |||
</thead> | |||
<tbody> | |||
{branches.map(branch => ( | |||
<tr key={branch.name}> | |||
<td className="nowrap"> | |||
<BranchLikeIcon branchLike={branch} className="little-spacer-right" /> | |||
{branch.name} | |||
{branch.isMain && ( | |||
<div className="badge spacer-left">{translate('branches.main_branch')}</div> | |||
)} | |||
</td> | |||
<td className="huge-spacer-right nowrap"> | |||
{branch.newCodePeriod | |||
? this.renderNewCodePeriodSetting(branch.newCodePeriod) | |||
: translate('branch_list.default_setting')} | |||
</td> | |||
<td className="text-right"> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem onClick={() => this.openEditModal(branch)}> | |||
{translate('edit')} | |||
</ActionsDropdownItem> | |||
{branch.newCodePeriod && ( | |||
<ActionsDropdownItem onClick={() => this.resetToDefault(branch.name)}> | |||
{translate('reset_to_default')} | |||
</ActionsDropdownItem> | |||
)} | |||
</ActionsDropdown> | |||
</td> | |||
</tr> | |||
<BranchListRow | |||
branch={branch} | |||
existingBranches={branchList.map(b => b.name)} | |||
inheritedSetting={inheritedSetting} | |||
key={branch.name} | |||
onOpenEditModal={this.openEditModal} | |||
onResetToDefault={this.resetToDefault} | |||
/> | |||
))} | |||
</tbody> | |||
</table> | |||
{editedBranch && ( | |||
<BranchBaselineSettingModal | |||
branch={editedBranch} | |||
branchList={branchList} | |||
component={this.props.component.key} | |||
onClose={this.closeEditModal} | |||
/> |
@@ -0,0 +1,135 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 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 * as React from 'react'; | |||
import ActionsDropdown, { | |||
ActionsDropdownItem | |||
} from 'sonar-ui-common/components/controls/ActionsDropdown'; | |||
import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; | |||
import WarningIcon from 'sonar-ui-common/components/icons/WarningIcon'; | |||
import DateTimeFormatter from 'sonar-ui-common/components/intl/DateTimeFormatter'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import BranchLikeIcon from '../../../components/icons/BranchLikeIcon'; | |||
import { BranchWithNewCodePeriod } from '../../../types/branch-like'; | |||
export interface BranchListRowProps { | |||
branch: BranchWithNewCodePeriod; | |||
existingBranches: Array<string>; | |||
inheritedSetting: T.NewCodePeriod; | |||
onOpenEditModal: (branch: BranchWithNewCodePeriod) => void; | |||
onResetToDefault: (branchName: string) => void; | |||
} | |||
function renderNewCodePeriodSetting(newCodePeriod: T.NewCodePeriod) { | |||
switch (newCodePeriod.type) { | |||
case 'SPECIFIC_ANALYSIS': | |||
return ( | |||
<> | |||
{`${translate('baseline.specific_analysis')}: `} | |||
{newCodePeriod.effectiveValue ? ( | |||
<DateTimeFormatter date={newCodePeriod.effectiveValue} /> | |||
) : ( | |||
'?' | |||
)} | |||
</> | |||
); | |||
case 'NUMBER_OF_DAYS': | |||
return `${translate('baseline.number_days')}: ${newCodePeriod.value}`; | |||
case 'PREVIOUS_VERSION': | |||
return translate('baseline.previous_version'); | |||
case 'REFERENCE_BRANCH': | |||
return `${translate('baseline.reference_branch')}: ${newCodePeriod.value}`; | |||
default: | |||
return newCodePeriod.type; | |||
} | |||
} | |||
function branchInheritsItselfAsReference( | |||
branch: BranchWithNewCodePeriod, | |||
inheritedSetting: T.NewCodePeriod | |||
) { | |||
return ( | |||
!branch.newCodePeriod && | |||
inheritedSetting.type === 'REFERENCE_BRANCH' && | |||
branch.name === inheritedSetting.value | |||
); | |||
} | |||
function referenceBranchDoesNotExist( | |||
branch: BranchWithNewCodePeriod, | |||
existingBranches: Array<string> | |||
) { | |||
return ( | |||
branch.newCodePeriod && | |||
branch.newCodePeriod.value && | |||
branch.newCodePeriod.type === 'REFERENCE_BRANCH' && | |||
!existingBranches.includes(branch.newCodePeriod.value) | |||
); | |||
} | |||
export default function BranchListRow(props: BranchListRowProps) { | |||
const { branch, existingBranches, inheritedSetting } = props; | |||
let settingWarning: string | undefined; | |||
if (branchInheritsItselfAsReference(branch, inheritedSetting)) { | |||
settingWarning = translateWithParameters( | |||
'baseline.reference_branch.invalid_branch_setting', | |||
branch.name | |||
); | |||
} else if (referenceBranchDoesNotExist(branch, existingBranches)) { | |||
settingWarning = translateWithParameters( | |||
'baseline.reference_branch.does_not_exist', | |||
branch.newCodePeriod?.value || '' | |||
); | |||
} | |||
return ( | |||
<tr className={settingWarning ? 'branch-setting-warning' : ''}> | |||
<td className="nowrap"> | |||
<BranchLikeIcon branchLike={branch} className="little-spacer-right" /> | |||
{branch.name} | |||
{branch.isMain && ( | |||
<div className="badge spacer-left">{translate('branches.main_branch')}</div> | |||
)} | |||
</td> | |||
<td className="huge-spacer-right nowrap"> | |||
<Tooltip overlay={settingWarning}> | |||
<span> | |||
{settingWarning && <WarningIcon className="little-spacer-right" />} | |||
{branch.newCodePeriod | |||
? renderNewCodePeriodSetting(branch.newCodePeriod) | |||
: translate('branch_list.default_setting')} | |||
</span> | |||
</Tooltip> | |||
</td> | |||
<td className="text-right"> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem onClick={() => props.onOpenEditModal(branch)}> | |||
{translate('edit')} | |||
</ActionsDropdownItem> | |||
{branch.newCodePeriod && ( | |||
<ActionsDropdownItem onClick={() => props.onResetToDefault(branch.name)}> | |||
{translate('reset_to_default')} | |||
</ActionsDropdownItem> | |||
)} | |||
</ActionsDropdown> | |||
</td> | |||
</tr> | |||
); | |||
} |
@@ -21,16 +21,20 @@ import * as classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons'; | |||
import Radio from 'sonar-ui-common/components/controls/Radio'; | |||
import { Alert } from 'sonar-ui-common/components/ui/Alert'; | |||
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { Branch } from '../../../types/branch-like'; | |||
import { validateSetting } from '../utils'; | |||
import BaselineSettingAnalysis from './BaselineSettingAnalysis'; | |||
import BaselineSettingDays from './BaselineSettingDays'; | |||
import BaselineSettingPreviousVersion from './BaselineSettingPreviousVersion'; | |||
import BaselineSettingReferenceBranch from './BaselineSettingReferenceBranch'; | |||
import BranchAnalysisList from './BranchAnalysisList'; | |||
export interface ProjectBaselineSelectorProps { | |||
analysis?: string; | |||
branchList: Branch[]; | |||
branchesEnabled?: boolean; | |||
component: string; | |||
currentSetting?: T.NewCodePeriodSettingType; | |||
@@ -40,9 +44,11 @@ export interface ProjectBaselineSelectorProps { | |||
onCancel: () => void; | |||
onSelectAnalysis: (analysis: T.ParsedAnalysis) => void; | |||
onSelectDays: (value: string) => void; | |||
onSelectReferenceBranch: (value: string) => void; | |||
onSelectSetting: (value?: T.NewCodePeriodSettingType) => void; | |||
onSubmit: (e: React.SyntheticEvent<HTMLFormElement>) => void; | |||
onToggleSpecificSetting: (selection: boolean) => void; | |||
referenceBranch?: string; | |||
saving: boolean; | |||
selected?: T.NewCodePeriodSettingType; | |||
overrideGeneralSetting: boolean; | |||
@@ -69,18 +75,24 @@ function renderGeneralSetting(generalSetting: T.NewCodePeriod) { | |||
); | |||
} | |||
function branchToOption(b: Branch) { | |||
return { value: b.name, isMain: b.isMain }; | |||
} | |||
export default function ProjectBaselineSelector(props: ProjectBaselineSelectorProps) { | |||
const { | |||
analysis, | |||
branchList, | |||
branchesEnabled, | |||
component, | |||
currentSetting, | |||
currentSettingValue, | |||
days, | |||
generalSetting, | |||
overrideGeneralSetting, | |||
referenceBranch, | |||
saving, | |||
selected, | |||
overrideGeneralSetting | |||
selected | |||
} = props; | |||
const { isChanged, isValid } = validateSetting({ | |||
@@ -88,8 +100,9 @@ export default function ProjectBaselineSelector(props: ProjectBaselineSelectorPr | |||
currentSetting, | |||
currentSettingValue, | |||
days, | |||
selected, | |||
overrideGeneralSetting | |||
overrideGeneralSetting, | |||
referenceBranch, | |||
selected | |||
}); | |||
return ( | |||
@@ -113,7 +126,7 @@ export default function ProjectBaselineSelector(props: ProjectBaselineSelectorPr | |||
</Radio> | |||
</div> | |||
<div className="big-spacer-left big-spacer-right branch-baseline-setting-modal"> | |||
<div className="big-spacer-left big-spacer-right project-baseline-setting"> | |||
<div className="display-flex-row big-spacer-bottom" role="radiogroup"> | |||
<BaselineSettingPreviousVersion | |||
disabled={!overrideGeneralSetting} | |||
@@ -129,7 +142,17 @@ export default function ProjectBaselineSelector(props: ProjectBaselineSelectorPr | |||
onSelect={props.onSelectSetting} | |||
selected={overrideGeneralSetting && selected === 'NUMBER_OF_DAYS'} | |||
/> | |||
{!branchesEnabled && ( | |||
{branchesEnabled ? ( | |||
<BaselineSettingReferenceBranch | |||
branchList={branchList.map(branchToOption)} | |||
disabled={!overrideGeneralSetting} | |||
onChangeReferenceBranch={props.onSelectReferenceBranch} | |||
onSelect={props.onSelectSetting} | |||
referenceBranch={referenceBranch || ''} | |||
selected={overrideGeneralSetting && selected === 'REFERENCE_BRANCH'} | |||
settingLevel="project" | |||
/> | |||
) : ( | |||
<BaselineSettingAnalysis | |||
disabled={!overrideGeneralSetting} | |||
onSelect={props.onSelectSetting} | |||
@@ -147,7 +170,9 @@ export default function ProjectBaselineSelector(props: ProjectBaselineSelectorPr | |||
)} | |||
</div> | |||
<div className={classNames('big-spacer-top', { invisible: !isChanged })}> | |||
<p className="spacer-bottom">{translate('baseline.next_analysis_notice')}</p> | |||
<Alert variant="info" className="spacer-bottom"> | |||
{translate('baseline.next_analysis_notice')} | |||
</Alert> | |||
<DeferredSpinner className="spacer-right" loading={saving} /> | |||
<SubmitButton disabled={saving || !isValid || !isChanged}>{translate('save')}</SubmitButton> | |||
<ResetButtonLink className="spacer-left" onClick={props.onCancel}> |
@@ -25,6 +25,7 @@ import { | |||
resetNewCodePeriod, | |||
setNewCodePeriod | |||
} from '../../../../api/newCodePeriod'; | |||
import { mockBranch, mockMainBranch, mockPullRequest } from '../../../../helpers/mocks/branch-like'; | |||
import { mockComponent, mockEvent } from '../../../../helpers/testMocks'; | |||
import App from '../App'; | |||
@@ -38,6 +39,16 @@ it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
it('should initialize correctly', async () => { | |||
const wrapper = shallowRender({ | |||
branchLikes: [mockBranch(), mockPullRequest(), mockMainBranch()] | |||
}); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().branchList).toHaveLength(2); | |||
expect(wrapper.state().referenceBranch).toBe('master'); | |||
}); | |||
it('should not display reset button if project setting is not set', () => { | |||
const wrapper = shallowRender(); | |||
@@ -89,7 +100,7 @@ it('should handle errors gracefully', async () => { | |||
function shallowRender(props: Partial<App['props']> = {}) { | |||
return shallow<App>( | |||
<App | |||
branchLikes={[]} | |||
branchLikes={[mockMainBranch()]} | |||
branchesEnabled={true} | |||
canAdmin={true} | |||
component={mockComponent()} |
@@ -0,0 +1,31 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import AppHeader, { AppHeaderProps } from '../AppHeader'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('can admin'); | |||
expect(shallowRender({ canAdmin: false })).toMatchSnapshot('cannot admin'); | |||
}); | |||
function shallowRender(props: Partial<AppHeaderProps> = {}) { | |||
return shallow(<AppHeader canAdmin={true} {...props} />); | |||
} |
@@ -0,0 +1,117 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import RadioCard from 'sonar-ui-common/components/controls/RadioCard'; | |||
import SearchSelect from 'sonar-ui-common/components/controls/SearchSelect'; | |||
import BaselineSettingReferenceBranch, { | |||
BaselineSettingReferenceBranchProps, | |||
BranchOption | |||
} from '../BaselineSettingReferenceBranch'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('Project level'); | |||
expect(shallowRender({ settingLevel: 'branch', configuredBranchName: 'master' })).toMatchSnapshot( | |||
'Branch level' | |||
); | |||
expect( | |||
shallowRender({ | |||
branchList: [{ value: 'master', isMain: true }], | |||
settingLevel: 'branch', | |||
configuredBranchName: 'master' | |||
}) | |||
).toMatchSnapshot('Branch level - no other branches'); | |||
}); | |||
it('should not display input when not selected', () => { | |||
const wrapper = shallowRender({ selected: false }); | |||
expect(wrapper.find('SearchSelect')).toHaveLength(0); | |||
}); | |||
it('should callback when clicked', () => { | |||
const onSelect = jest.fn(); | |||
const wrapper = shallowRender({ onSelect, selected: false }); | |||
wrapper | |||
.find(RadioCard) | |||
.first() | |||
.simulate('click'); | |||
expect(onSelect).toHaveBeenCalledWith('REFERENCE_BRANCH'); | |||
}); | |||
it('should callback when changing selection', () => { | |||
const onChangeReferenceBranch = jest.fn(); | |||
const wrapper = shallowRender({ onChangeReferenceBranch }); | |||
wrapper | |||
.find(SearchSelect) | |||
.first() | |||
.simulate('select', { value: 'branch-6.9' }); | |||
expect(onChangeReferenceBranch).toHaveBeenCalledWith('branch-6.9'); | |||
}); | |||
it('should handle an invalid branch', () => { | |||
const unknownBranchName = 'branch-unknown'; | |||
const wrapper = shallowRender({ referenceBranch: unknownBranchName }); | |||
expect( | |||
wrapper | |||
.find(SearchSelect) | |||
.first() | |||
.props().value | |||
).toEqual({ value: unknownBranchName, isMain: false, isInvalid: true }); | |||
}); | |||
describe('renderOption', () => { | |||
const select = shallowRender() | |||
.find(SearchSelect) | |||
.first(); | |||
const renderFunction = select.props().renderOption as (option: BranchOption) => JSX.Element; | |||
it('should render correctly', () => { | |||
expect(renderFunction({ value: 'master', isMain: true })).toMatchSnapshot('main'); | |||
expect(renderFunction({ value: 'branch-7.4', isMain: false })).toMatchSnapshot('branch'); | |||
expect(renderFunction({ value: 'disabled', isMain: false, disabled: true })).toMatchSnapshot( | |||
'disabled' | |||
); | |||
expect( | |||
renderFunction({ value: 'branch-nope', isMain: false, isInvalid: true }) | |||
).toMatchSnapshot("branch doesn't exist"); | |||
}); | |||
}); | |||
function shallowRender(props: Partial<BaselineSettingReferenceBranchProps> = {}) { | |||
const branchOptions = [ | |||
{ value: 'master', isMain: true }, | |||
{ value: 'branch-7.9', isMain: false } | |||
]; | |||
return shallow( | |||
<BaselineSettingReferenceBranch | |||
branchList={branchOptions} | |||
settingLevel="project" | |||
onChangeReferenceBranch={jest.fn()} | |||
onSelect={jest.fn()} | |||
referenceBranch="master" | |||
selected={true} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -76,7 +76,8 @@ it('should render correctly', async () => { | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot(); | |||
expect(getProjectActivity).toBeCalled(); | |||
expect(wrapper.state().analyses).toHaveLength(4); | |||
}); | |||
it('should reload analyses after range change', () => { | |||
@@ -109,6 +110,22 @@ it('should handle scroll', () => { | |||
expect(wrapper.state('scroll')).toBe(12); | |||
}); | |||
describe('shouldStick', () => { | |||
const wrapper = shallowRender(); | |||
wrapper.instance().badges['10.5'] = mockBadge('43'); | |||
wrapper.instance().badges['12.2'] = mockBadge('85'); | |||
it('should handle no badge', () => { | |||
expect(wrapper.instance().shouldStick('unknown version')).toBe(false); | |||
}); | |||
it('should return the correct result', () => { | |||
wrapper.setState({ scroll: 36 }); // => 46 with STICKY_BADGE_SCROLL_OFFSET = 10 | |||
expect(wrapper.instance().shouldStick('10.5')).toBe(true); | |||
expect(wrapper.instance().shouldStick('12.2')).toBe(false); | |||
}); | |||
}); | |||
function shallowRender(props: Partial<BranchAnalysisList['props']> = {}) { | |||
return shallow<BranchAnalysisList>( | |||
<BranchAnalysisList | |||
@@ -120,3 +137,11 @@ function shallowRender(props: Partial<BranchAnalysisList['props']> = {}) { | |||
/> | |||
); | |||
} | |||
function mockBadge(offsetTop: string) { | |||
const element = document.createElement('div'); | |||
element.setAttribute('originOffsetTop', offsetTop); | |||
return element; | |||
} |
@@ -0,0 +1,84 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { mockAnalysisEvent, mockParsedAnalysis } from '../../../../helpers/testMocks'; | |||
import BranchAnalysisListRenderer, { | |||
BranchAnalysisListRendererProps | |||
} from '../BranchAnalysisListRenderer'; | |||
jest.mock('date-fns/start_of_day', () => (date: Date) => { | |||
const startDay = new Date(date); | |||
startDay.setUTCHours(0, 0, 0, 0); | |||
return startDay; | |||
}); | |||
jest.mock('sonar-ui-common/helpers/dates', () => { | |||
const actual = require.requireActual('sonar-ui-common/helpers/dates'); | |||
return { ...actual, toShortNotSoISOString: (date: string) => `ISO.${date}` }; | |||
}); | |||
const analyses = [ | |||
mockParsedAnalysis({ | |||
key: '4', | |||
date: new Date('2017-03-02T10:36:01Z'), | |||
projectVersion: '4.2' | |||
}), | |||
mockParsedAnalysis({ | |||
key: '3', | |||
date: new Date('2017-03-02T09:36:01Z'), | |||
events: [mockAnalysisEvent()], | |||
projectVersion: '4.2' | |||
}), | |||
mockParsedAnalysis({ | |||
key: '2', | |||
date: new Date('2017-03-02T08:36:01Z'), | |||
events: [ | |||
mockAnalysisEvent(), | |||
mockAnalysisEvent({ category: 'VERSION', qualityGate: undefined }) | |||
], | |||
projectVersion: '4.1' | |||
}), | |||
mockParsedAnalysis({ key: '1', projectVersion: '4.1' }) | |||
]; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('empty'); | |||
expect(shallowRender({ loading: true })).toMatchSnapshot('loading'); | |||
expect(shallowRender({ analyses, selectedAnalysisKey: '2' })).toMatchSnapshot('Analyses'); | |||
}); | |||
function shallowRender(props: Partial<BranchAnalysisListRendererProps> = {}) { | |||
return shallow( | |||
<BranchAnalysisListRenderer | |||
analyses={[]} | |||
handleRangeChange={jest.fn()} | |||
handleScroll={jest.fn()} | |||
loading={false} | |||
onSelectAnalysis={jest.fn()} | |||
range={30} | |||
registerBadgeNode={jest.fn()} | |||
registerScrollableNode={jest.fn()} | |||
selectedAnalysisKey="" | |||
shouldStick={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -21,7 +21,7 @@ import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { mockEvent, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||
import { setNewCodePeriod } from '../../../../api/newCodePeriod'; | |||
import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; | |||
import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like'; | |||
import BranchBaselineSettingModal from '../BranchBaselineSettingModal'; | |||
jest.mock('../../../../api/newCodePeriod', () => ({ | |||
@@ -29,7 +29,10 @@ jest.mock('../../../../api/newCodePeriod', () => ({ | |||
})); | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
expect(shallowRender()).toMatchSnapshot('only one branch'); | |||
expect( | |||
shallowRender({ branchList: [mockMainBranch(), mockBranch()], branch: mockMainBranch() }) | |||
).toMatchSnapshot('multiple branches'); | |||
}); | |||
it('should display the branch analysis list when necessary', () => { | |||
@@ -92,6 +95,7 @@ function shallowRender(props: Partial<BranchBaselineSettingModal['props']> = {}) | |||
return shallow<BranchBaselineSettingModal>( | |||
<BranchBaselineSettingModal | |||
branch={mockMainBranch()} | |||
branchList={[mockMainBranch()]} | |||
component="compKey" | |||
onClose={jest.fn()} | |||
{...props} |
@@ -21,7 +21,7 @@ import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||
import { listBranchesNewCodePeriod, resetNewCodePeriod } from '../../../../api/newCodePeriod'; | |||
import { mockBranch, mockMainBranch, mockPullRequest } from '../../../../helpers/mocks/branch-like'; | |||
import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like'; | |||
import { mockComponent } from '../../../../helpers/testMocks'; | |||
import BranchBaselineSettingModal from '../BranchBaselineSettingModal'; | |||
import BranchList from '../BranchList'; | |||
@@ -31,24 +31,19 @@ jest.mock('../../../../api/newCodePeriod', () => ({ | |||
resetNewCodePeriod: jest.fn().mockResolvedValue(null) | |||
})); | |||
const newCodePeriods = [ | |||
{ | |||
projectKey: '', | |||
branchKey: 'master', | |||
type: 'NUMBER_OF_DAYS', | |||
value: '27' | |||
} | |||
]; | |||
it('should render correctly', async () => { | |||
(listBranchesNewCodePeriod as jest.Mock).mockResolvedValueOnce({ | |||
newCodePeriods: [ | |||
{ | |||
projectKey: '', | |||
branchKey: 'master', | |||
type: 'NUMBER_OF_DAYS', | |||
value: '27' | |||
} | |||
] | |||
}); | |||
(listBranchesNewCodePeriod as jest.Mock).mockResolvedValueOnce({ newCodePeriods }); | |||
const wrapper = shallowRender({ | |||
branchLikes: [ | |||
mockMainBranch(), | |||
mockBranch(), | |||
mockBranch({ name: 'branch-7.0' }), | |||
mockPullRequest() | |||
] | |||
branchList: [mockMainBranch(), mockBranch(), mockBranch({ name: 'branch-7.0' })] | |||
}); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().branches).toHaveLength(3); | |||
@@ -68,7 +63,7 @@ it('should handle reset', () => { | |||
}); | |||
it('should toggle popup', async () => { | |||
const wrapper = shallowRender({ branchLikes: [mockMainBranch(), mockBranch()] }); | |||
const wrapper = shallowRender({ branchList: [mockMainBranch(), mockBranch()] }); | |||
wrapper.setState({ editedBranch: mockMainBranch() }); | |||
@@ -93,35 +88,10 @@ it('should toggle popup', async () => { | |||
}); | |||
}); | |||
it('should render the right setting label', () => { | |||
const wrapper = shallowRender(); | |||
expect( | |||
wrapper.instance().renderNewCodePeriodSetting({ type: 'NUMBER_OF_DAYS', value: '21' }) | |||
).toBe('baseline.number_days: 21'); | |||
expect(wrapper.instance().renderNewCodePeriodSetting({ type: 'PREVIOUS_VERSION' })).toBe( | |||
'baseline.previous_version' | |||
); | |||
expect( | |||
wrapper.instance().renderNewCodePeriodSetting({ | |||
type: 'SPECIFIC_ANALYSIS', | |||
value: 'A85835', | |||
effectiveValue: '2018-12-02T13:01:12' | |||
}) | |||
).toMatchInlineSnapshot(` | |||
<React.Fragment> | |||
baseline.specific_analysis: | |||
<DateTimeFormatter | |||
date="2018-12-02T13:01:12" | |||
/> | |||
</React.Fragment> | |||
`); | |||
}); | |||
function shallowRender(props: Partial<BranchList['props']> = {}) { | |||
return shallow<BranchList>( | |||
<BranchList | |||
branchLikes={[]} | |||
branchList={[]} | |||
component={mockComponent()} | |||
inheritedSetting={{ type: 'PREVIOUS_VERSION' }} | |||
{...props} |
@@ -0,0 +1,103 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { ActionsDropdownItem } from 'sonar-ui-common/components/controls/ActionsDropdown'; | |||
import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like'; | |||
import BranchListRow, { BranchListRowProps } from '../BranchListRow'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('main branch with default'); | |||
expect( | |||
shallowRender({ | |||
branch: mockBranch({ name: 'branch-7.3' }), | |||
inheritedSetting: { type: 'REFERENCE_BRANCH', value: 'branch-7.3' } | |||
}) | |||
).toMatchSnapshot('faulty branch'); | |||
expect( | |||
shallowRender({ | |||
branch: { ...mockBranch(), newCodePeriod: { type: 'NUMBER_OF_DAYS', value: '21' } } | |||
}) | |||
).toMatchSnapshot('branch with number of days'); | |||
expect( | |||
shallowRender({ | |||
branch: { ...mockBranch(), newCodePeriod: { type: 'PREVIOUS_VERSION' } } | |||
}) | |||
).toMatchSnapshot('branch with previous version'); | |||
expect( | |||
shallowRender({ | |||
branch: { | |||
...mockBranch(), | |||
newCodePeriod: { | |||
type: 'SPECIFIC_ANALYSIS', | |||
value: 'A85835', | |||
effectiveValue: '2018-12-02T13:01:12' | |||
} | |||
} | |||
}) | |||
).toMatchSnapshot('branch with specific analysis'); | |||
expect( | |||
shallowRender({ | |||
branch: { ...mockBranch(), newCodePeriod: { type: 'REFERENCE_BRANCH', value: 'master' } } | |||
}) | |||
).toMatchSnapshot('branch with reference branch'); | |||
}); | |||
it('should callback to open modal when clicked', () => { | |||
const openEditModal = jest.fn(); | |||
const branch = mockBranch(); | |||
const wrapper = shallowRender({ branch, onOpenEditModal: openEditModal }); | |||
wrapper | |||
.find(ActionsDropdownItem) | |||
.first() | |||
.simulate('click'); | |||
expect(openEditModal).toBeCalledWith(branch); | |||
}); | |||
it('should callback to reset when clicked', () => { | |||
const resetToDefault = jest.fn(); | |||
const branchName = 'branch-6.5'; | |||
const wrapper = shallowRender({ | |||
branch: { ...mockBranch({ name: branchName }), newCodePeriod: { type: 'REFERENCE_BRANCH' } }, | |||
onResetToDefault: resetToDefault | |||
}); | |||
wrapper | |||
.find(ActionsDropdownItem) | |||
.at(1) | |||
.simulate('click'); | |||
expect(resetToDefault).toBeCalledWith(branchName); | |||
}); | |||
function shallowRender(props: Partial<BranchListRowProps> = {}) { | |||
return shallow( | |||
<BranchListRow | |||
branch={mockMainBranch()} | |||
existingBranches={['master']} | |||
inheritedSetting={{}} | |||
onOpenEditModal={jest.fn()} | |||
onResetToDefault={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -19,6 +19,7 @@ | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; | |||
import ProjectBaselineSelector, { ProjectBaselineSelectorProps } from '../ProjectBaselineSelector'; | |||
it('should render correctly', () => { | |||
@@ -104,6 +105,7 @@ it('should disable the save button when date is invalid', () => { | |||
function shallowRender(props: Partial<ProjectBaselineSelectorProps> = {}) { | |||
return shallow( | |||
<ProjectBaselineSelector | |||
branchList={[mockMainBranch()]} | |||
branchesEnabled={true} | |||
component="" | |||
days="12" | |||
@@ -111,10 +113,12 @@ function shallowRender(props: Partial<ProjectBaselineSelectorProps> = {}) { | |||
onCancel={jest.fn()} | |||
onSelectAnalysis={jest.fn()} | |||
onSelectDays={jest.fn()} | |||
onSelectReferenceBranch={jest.fn()} | |||
onSelectSetting={jest.fn()} | |||
onSubmit={jest.fn()} | |||
onToggleSpecificSetting={jest.fn()} | |||
overrideGeneralSetting={false} | |||
referenceBranch="master" | |||
saving={false} | |||
{...props} | |||
/> |
@@ -8,50 +8,9 @@ exports[`should render correctly 1`] = ` | |||
<div | |||
className="page page-limited" | |||
> | |||
<header | |||
className="page-header" | |||
> | |||
<h1 | |||
className="page-title" | |||
> | |||
project_baseline.page | |||
</h1> | |||
<p | |||
className="page-description" | |||
> | |||
<FormattedMessage | |||
defaultMessage="project_baseline.page.description" | |||
id="project_baseline.page.description" | |||
values={ | |||
Object { | |||
"link": <Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/documentation/project-administration/new-code-period/" | |||
> | |||
project_baseline.page.description.link | |||
</Link>, | |||
} | |||
} | |||
/> | |||
<br /> | |||
<FormattedMessage | |||
defaultMessage="project_baseline.page.description2" | |||
id="project_baseline.page.description2" | |||
values={ | |||
Object { | |||
"link": <Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/admin/settings?category=new_code_period" | |||
> | |||
project_baseline.page.description2.link | |||
</Link>, | |||
} | |||
} | |||
/> | |||
</p> | |||
</header> | |||
<AppHeader | |||
canAdmin={true} | |||
/> | |||
<DeferredSpinner | |||
timeout={100} | |||
/> |
@@ -0,0 +1,80 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: can admin 1`] = ` | |||
<header | |||
className="page-header" | |||
> | |||
<h1 | |||
className="page-title" | |||
> | |||
project_baseline.page | |||
</h1> | |||
<p | |||
className="page-description" | |||
> | |||
<FormattedMessage | |||
defaultMessage="project_baseline.page.description" | |||
id="project_baseline.page.description" | |||
values={ | |||
Object { | |||
"link": <Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/documentation/project-administration/new-code-period/" | |||
> | |||
project_baseline.page.description.link | |||
</Link>, | |||
} | |||
} | |||
/> | |||
<br /> | |||
<FormattedMessage | |||
defaultMessage="project_baseline.page.description2" | |||
id="project_baseline.page.description2" | |||
values={ | |||
Object { | |||
"link": <Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/admin/settings?category=new_code_period" | |||
> | |||
project_baseline.page.description2.link | |||
</Link>, | |||
} | |||
} | |||
/> | |||
</p> | |||
</header> | |||
`; | |||
exports[`should render correctly: cannot admin 1`] = ` | |||
<header | |||
className="page-header" | |||
> | |||
<h1 | |||
className="page-title" | |||
> | |||
project_baseline.page | |||
</h1> | |||
<p | |||
className="page-description" | |||
> | |||
<FormattedMessage | |||
defaultMessage="project_baseline.page.description" | |||
id="project_baseline.page.description" | |||
values={ | |||
Object { | |||
"link": <Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/documentation/project-administration/new-code-period/" | |||
> | |||
project_baseline.page.description.link | |||
</Link>, | |||
} | |||
} | |||
/> | |||
<br /> | |||
</p> | |||
</header> | |||
`; |
@@ -0,0 +1,210 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renderOption should render correctly: branch 1`] = ` | |||
<React.Fragment> | |||
<span> | |||
branch-7.4 | |||
</span> | |||
</React.Fragment> | |||
`; | |||
exports[`renderOption should render correctly: branch doesn't exist 1`] = ` | |||
<Tooltip | |||
overlay="baseline.reference_branch.does_not_exist.branch-nope" | |||
> | |||
<span> | |||
branch-nope | |||
<AlertErrorIcon /> | |||
</span> | |||
</Tooltip> | |||
`; | |||
exports[`renderOption should render correctly: disabled 1`] = ` | |||
<React.Fragment> | |||
<span | |||
title="baseline.reference_branch.cannot_be_itself" | |||
> | |||
disabled | |||
</span> | |||
</React.Fragment> | |||
`; | |||
exports[`renderOption should render correctly: main 1`] = ` | |||
<React.Fragment> | |||
<span> | |||
master | |||
</span> | |||
<div | |||
className="badge spacer-left" | |||
> | |||
branches.main_branch | |||
</div> | |||
</React.Fragment> | |||
`; | |||
exports[`should render correctly: Branch level - no other branches 1`] = ` | |||
<RadioCard | |||
onClick={[Function]} | |||
selected={true} | |||
title="baseline.reference_branch" | |||
> | |||
<p> | |||
baseline.reference_branch.description | |||
</p> | |||
<div | |||
className="big-spacer-top display-flex-column" | |||
> | |||
<label | |||
className="text-middle" | |||
htmlFor="reference_branch" | |||
> | |||
<strong> | |||
baseline.reference_branch.choose | |||
</strong> | |||
<em | |||
className="mandatory" | |||
> | |||
* | |||
</em> | |||
</label> | |||
<SearchSelect | |||
autofocus={false} | |||
className="little-spacer-top spacer-bottom" | |||
defaultOptions={ | |||
Array [ | |||
Object { | |||
"isMain": true, | |||
"value": "master", | |||
}, | |||
] | |||
} | |||
minimumQueryLength={1} | |||
onSearch={[Function]} | |||
onSelect={[Function]} | |||
renderOption={[Function]} | |||
value={ | |||
Object { | |||
"isMain": true, | |||
"value": "master", | |||
} | |||
} | |||
/> | |||
</div> | |||
</RadioCard> | |||
`; | |||
exports[`should render correctly: Branch level 1`] = ` | |||
<RadioCard | |||
onClick={[Function]} | |||
selected={true} | |||
title="baseline.reference_branch" | |||
> | |||
<p> | |||
baseline.reference_branch.description | |||
</p> | |||
<div | |||
className="big-spacer-top display-flex-column" | |||
> | |||
<label | |||
className="text-middle" | |||
htmlFor="reference_branch" | |||
> | |||
<strong> | |||
baseline.reference_branch.choose | |||
</strong> | |||
<em | |||
className="mandatory" | |||
> | |||
* | |||
</em> | |||
</label> | |||
<SearchSelect | |||
autofocus={false} | |||
className="little-spacer-top spacer-bottom" | |||
defaultOptions={ | |||
Array [ | |||
Object { | |||
"isMain": true, | |||
"value": "master", | |||
}, | |||
Object { | |||
"isMain": false, | |||
"value": "branch-7.9", | |||
}, | |||
] | |||
} | |||
minimumQueryLength={1} | |||
onSearch={[Function]} | |||
onSelect={[Function]} | |||
renderOption={[Function]} | |||
value={ | |||
Object { | |||
"isMain": true, | |||
"value": "master", | |||
} | |||
} | |||
/> | |||
</div> | |||
</RadioCard> | |||
`; | |||
exports[`should render correctly: Project level 1`] = ` | |||
<RadioCard | |||
onClick={[Function]} | |||
selected={true} | |||
title="baseline.reference_branch" | |||
> | |||
<p> | |||
baseline.reference_branch.description | |||
</p> | |||
<p | |||
className="spacer-top" | |||
> | |||
baseline.reference_branch.description2 | |||
</p> | |||
<div | |||
className="big-spacer-top display-flex-column" | |||
> | |||
<label | |||
className="text-middle" | |||
htmlFor="reference_branch" | |||
> | |||
<strong> | |||
baseline.reference_branch.choose | |||
</strong> | |||
<em | |||
className="mandatory" | |||
> | |||
* | |||
</em> | |||
</label> | |||
<SearchSelect | |||
autofocus={false} | |||
className="little-spacer-top spacer-bottom" | |||
defaultOptions={ | |||
Array [ | |||
Object { | |||
"isMain": true, | |||
"value": "master", | |||
}, | |||
Object { | |||
"isMain": false, | |||
"value": "branch-7.9", | |||
}, | |||
] | |||
} | |||
minimumQueryLength={1} | |||
onSearch={[Function]} | |||
onSelect={[Function]} | |||
renderOption={[Function]} | |||
value={ | |||
Object { | |||
"isMain": true, | |||
"value": "master", | |||
} | |||
} | |||
/> | |||
</div> | |||
</RadioCard> | |||
`; |
@@ -1,6 +1,6 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
exports[`should render correctly: Analyses 1`] = ` | |||
<Fragment> | |||
<div | |||
className="spacer-bottom" | |||
@@ -10,7 +10,7 @@ exports[`should render correctly 1`] = ` | |||
autoBlur={true} | |||
className="input-medium spacer-left" | |||
clearable={false} | |||
onChange={[Function]} | |||
onChange={[MockFunction]} | |||
options={ | |||
Array [ | |||
Object { | |||
@@ -24,7 +24,7 @@ exports[`should render correctly 1`] = ` | |||
] | |||
} | |||
searchable={false} | |||
value={0} | |||
value={30} | |||
/> | |||
</div> | |||
<div | |||
@@ -32,7 +32,7 @@ exports[`should render correctly 1`] = ` | |||
> | |||
<div | |||
className="bordered branch-analysis-list" | |||
onScroll={[Function]} | |||
onScroll={[MockFunction]} | |||
> | |||
<ul> | |||
<li | |||
@@ -43,14 +43,14 @@ exports[`should render correctly 1`] = ` | |||
> | |||
<li | |||
className="branch-analysis-day" | |||
data-day="2017-03-02" | |||
key="1488322800000" | |||
data-day="ISO.1488412800000" | |||
key="1488412800000" | |||
> | |||
<div | |||
className="branch-analysis-date" | |||
> | |||
<DateFormatter | |||
date={1488322800000} | |||
date={1488412800000} | |||
long={true} | |||
/> | |||
</div> | |||
@@ -59,7 +59,7 @@ exports[`should render correctly 1`] = ` | |||
> | |||
<li | |||
className="branch-analysis" | |||
data-date="2017-03-02" | |||
data-date={1488450961000} | |||
key="4" | |||
onClick={[Function]} | |||
> | |||
@@ -67,7 +67,7 @@ exports[`should render correctly 1`] = ` | |||
className="branch-analysis-time spacer-right" | |||
> | |||
<TimeFormatter | |||
date="2017-03-02" | |||
date={2017-03-02T10:36:01.000Z} | |||
long={false} | |||
> | |||
<Component /> | |||
@@ -85,7 +85,7 @@ exports[`should render correctly 1`] = ` | |||
</li> | |||
<li | |||
className="branch-analysis" | |||
data-date="2017-03-02" | |||
data-date={1488447361000} | |||
key="3" | |||
onClick={[Function]} | |||
> | |||
@@ -93,7 +93,7 @@ exports[`should render correctly 1`] = ` | |||
className="branch-analysis-time spacer-right" | |||
> | |||
<TimeFormatter | |||
date="2017-03-02" | |||
date={2017-03-02T09:36:01.000Z} | |||
long={false} | |||
> | |||
<Component /> | |||
@@ -165,14 +165,14 @@ exports[`should render correctly 1`] = ` | |||
> | |||
<li | |||
className="branch-analysis-day" | |||
data-day="2017-03-02" | |||
key="1488322800000" | |||
data-day="ISO.1488412800000" | |||
key="1488412800000" | |||
> | |||
<div | |||
className="branch-analysis-date" | |||
> | |||
<DateFormatter | |||
date={1488322800000} | |||
date={1488412800000} | |||
long={true} | |||
/> | |||
</div> | |||
@@ -180,8 +180,8 @@ exports[`should render correctly 1`] = ` | |||
className="branch-analysis-analyses-list" | |||
> | |||
<li | |||
className="branch-analysis" | |||
data-date="2017-03-02" | |||
className="branch-analysis selected" | |||
data-date={1488443761000} | |||
key="2" | |||
onClick={[Function]} | |||
> | |||
@@ -189,7 +189,7 @@ exports[`should render correctly 1`] = ` | |||
className="branch-analysis-time spacer-right" | |||
> | |||
<TimeFormatter | |||
date="2017-03-02" | |||
date={2017-03-02T08:36:01.000Z} | |||
long={false} | |||
> | |||
<Component /> | |||
@@ -236,15 +236,33 @@ exports[`should render correctly 1`] = ` | |||
className="analysis-selection-button" | |||
> | |||
<Radio | |||
checked={false} | |||
checked={true} | |||
onCheck={[Function]} | |||
value="" | |||
/> | |||
</div> | |||
</li> | |||
</ul> | |||
</li> | |||
<li | |||
className="branch-analysis-day" | |||
data-day="ISO.1488326400000" | |||
key="1488326400000" | |||
> | |||
<div | |||
className="branch-analysis-date" | |||
> | |||
<DateFormatter | |||
date={1488326400000} | |||
long={true} | |||
/> | |||
</div> | |||
<ul | |||
className="branch-analysis-analyses-list" | |||
> | |||
<li | |||
className="branch-analysis" | |||
data-date="2017-03-02" | |||
data-date={1488357421000} | |||
key="1" | |||
onClick={[Function]} | |||
> | |||
@@ -252,7 +270,7 @@ exports[`should render correctly 1`] = ` | |||
className="branch-analysis-time spacer-right" | |||
> | |||
<TimeFormatter | |||
date="2017-03-02" | |||
date={2017-03-01T08:37:01.000Z} | |||
long={false} | |||
> | |||
<Component /> | |||
@@ -277,3 +295,91 @@ exports[`should render correctly 1`] = ` | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: empty 1`] = ` | |||
<Fragment> | |||
<div | |||
className="spacer-bottom" | |||
> | |||
baseline.analysis_from | |||
<Select | |||
autoBlur={true} | |||
className="input-medium spacer-left" | |||
clearable={false} | |||
onChange={[MockFunction]} | |||
options={ | |||
Array [ | |||
Object { | |||
"label": "baseline.branch_analyses.ranges.30days", | |||
"value": 30, | |||
}, | |||
Object { | |||
"label": "baseline.branch_analyses.ranges.allTime", | |||
"value": 0, | |||
}, | |||
] | |||
} | |||
searchable={false} | |||
value={30} | |||
/> | |||
</div> | |||
<div | |||
className="branch-analysis-list-wrapper" | |||
> | |||
<div | |||
className="bordered branch-analysis-list" | |||
onScroll={[MockFunction]} | |||
> | |||
<div | |||
className="big-spacer-top big-spacer-bottom strong" | |||
> | |||
baseline.no_analyses | |||
</div> | |||
</div> | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: loading 1`] = ` | |||
<Fragment> | |||
<div | |||
className="spacer-bottom" | |||
> | |||
baseline.analysis_from | |||
<Select | |||
autoBlur={true} | |||
className="input-medium spacer-left" | |||
clearable={false} | |||
onChange={[MockFunction]} | |||
options={ | |||
Array [ | |||
Object { | |||
"label": "baseline.branch_analyses.ranges.30days", | |||
"value": 30, | |||
}, | |||
Object { | |||
"label": "baseline.branch_analyses.ranges.allTime", | |||
"value": 0, | |||
}, | |||
] | |||
} | |||
searchable={false} | |||
value={30} | |||
/> | |||
</div> | |||
<div | |||
className="branch-analysis-list-wrapper" | |||
> | |||
<div | |||
className="bordered branch-analysis-list" | |||
onScroll={[MockFunction]} | |||
> | |||
<DeferredSpinner | |||
className="big-spacer-top" | |||
timeout={100} | |||
/> | |||
<ul /> | |||
</div> | |||
</div> | |||
</Fragment> | |||
`; |
@@ -1,6 +1,6 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
exports[`should render correctly: multiple branches 1`] = ` | |||
<Modal | |||
contentLabel="baseline.new_code_period_for_branch_x.master" | |||
onRequestClose={[Function]} | |||
@@ -40,6 +40,108 @@ exports[`should render correctly 1`] = ` | |||
onSelect={[Function]} | |||
selected={false} | |||
/> | |||
<BaselineSettingReferenceBranch | |||
branchList={ | |||
Array [ | |||
Object { | |||
"disabled": true, | |||
"isMain": true, | |||
"value": "master", | |||
}, | |||
Object { | |||
"disabled": false, | |||
"isMain": false, | |||
"value": "branch-6.7", | |||
}, | |||
] | |||
} | |||
onChangeReferenceBranch={[Function]} | |||
onSelect={[Function]} | |||
referenceBranch="branch-6.7" | |||
selected={false} | |||
settingLevel="branch" | |||
/> | |||
</div> | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<DeferredSpinner | |||
className="spacer-right" | |||
loading={false} | |||
timeout={100} | |||
/> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<ResetButtonLink | |||
onClick={[MockFunction]} | |||
> | |||
cancel | |||
</ResetButtonLink> | |||
</footer> | |||
</form> | |||
</Modal> | |||
`; | |||
exports[`should render correctly: only one branch 1`] = ` | |||
<Modal | |||
contentLabel="baseline.new_code_period_for_branch_x.master" | |||
onRequestClose={[Function]} | |||
size="large" | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
baseline.new_code_period_for_branch_x.master | |||
</h2> | |||
</header> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<div | |||
className="modal-body modal-container branch-baseline-setting-modal" | |||
> | |||
<div | |||
className="display-flex-row huge-spacer-bottom" | |||
role="radiogroup" | |||
> | |||
<BaselineSettingPreviousVersion | |||
isDefault={false} | |||
onSelect={[Function]} | |||
selected={false} | |||
/> | |||
<BaselineSettingDays | |||
days="30" | |||
isChanged={false} | |||
isValid={false} | |||
onChangeDays={[Function]} | |||
onSelect={[Function]} | |||
selected={false} | |||
/> | |||
<BaselineSettingAnalysis | |||
onSelect={[Function]} | |||
selected={false} | |||
/> | |||
<BaselineSettingReferenceBranch | |||
branchList={ | |||
Array [ | |||
Object { | |||
"disabled": true, | |||
"isMain": true, | |||
"value": "master", | |||
}, | |||
] | |||
} | |||
onChangeReferenceBranch={[Function]} | |||
onSelect={[Function]} | |||
referenceBranch="" | |||
selected={false} | |||
settingLevel="branch" | |||
/> | |||
</div> | |||
</div> | |||
<footer |
@@ -23,129 +23,86 @@ exports[`should render correctly 1`] = ` | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<tr | |||
<BranchListRow | |||
branch={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": true, | |||
"name": "master", | |||
"newCodePeriod": Object { | |||
"effectiveValue": undefined, | |||
"type": "NUMBER_OF_DAYS", | |||
"value": "27", | |||
}, | |||
} | |||
} | |||
existingBranches={ | |||
Array [ | |||
"master", | |||
"branch-6.7", | |||
"branch-7.0", | |||
] | |||
} | |||
inheritedSetting={ | |||
Object { | |||
"type": "PREVIOUS_VERSION", | |||
} | |||
} | |||
key="master" | |||
> | |||
<td | |||
className="nowrap" | |||
> | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": true, | |||
"name": "master", | |||
"newCodePeriod": Object { | |||
"effectiveValue": undefined, | |||
"type": "NUMBER_OF_DAYS", | |||
"value": "27", | |||
}, | |||
} | |||
} | |||
className="little-spacer-right" | |||
/> | |||
master | |||
<div | |||
className="badge spacer-left" | |||
> | |||
branches.main_branch | |||
</div> | |||
</td> | |||
<td | |||
className="huge-spacer-right nowrap" | |||
> | |||
baseline.number_days: 27 | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem | |||
onClick={[Function]} | |||
> | |||
edit | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
onClick={[Function]} | |||
> | |||
reset_to_default | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
</td> | |||
</tr> | |||
<tr | |||
onOpenEditModal={[Function]} | |||
onResetToDefault={[Function]} | |||
/> | |||
<BranchListRow | |||
branch={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": false, | |||
"name": "branch-6.7", | |||
} | |||
} | |||
existingBranches={ | |||
Array [ | |||
"master", | |||
"branch-6.7", | |||
"branch-7.0", | |||
] | |||
} | |||
inheritedSetting={ | |||
Object { | |||
"type": "PREVIOUS_VERSION", | |||
} | |||
} | |||
key="branch-6.7" | |||
> | |||
<td | |||
className="nowrap" | |||
> | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": false, | |||
"name": "branch-6.7", | |||
} | |||
} | |||
className="little-spacer-right" | |||
/> | |||
branch-6.7 | |||
</td> | |||
<td | |||
className="huge-spacer-right nowrap" | |||
> | |||
branch_list.default_setting | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem | |||
onClick={[Function]} | |||
> | |||
edit | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
</td> | |||
</tr> | |||
<tr | |||
onOpenEditModal={[Function]} | |||
onResetToDefault={[Function]} | |||
/> | |||
<BranchListRow | |||
branch={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": false, | |||
"name": "branch-7.0", | |||
} | |||
} | |||
existingBranches={ | |||
Array [ | |||
"master", | |||
"branch-6.7", | |||
"branch-7.0", | |||
] | |||
} | |||
inheritedSetting={ | |||
Object { | |||
"type": "PREVIOUS_VERSION", | |||
} | |||
} | |||
key="branch-7.0" | |||
> | |||
<td | |||
className="nowrap" | |||
> | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": false, | |||
"name": "branch-7.0", | |||
} | |||
} | |||
className="little-spacer-right" | |||
/> | |||
branch-7.0 | |||
</td> | |||
<td | |||
className="huge-spacer-right nowrap" | |||
> | |||
branch_list.default_setting | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem | |||
onClick={[Function]} | |||
> | |||
edit | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
</td> | |||
</tr> | |||
onOpenEditModal={[Function]} | |||
onResetToDefault={[Function]} | |||
/> | |||
</tbody> | |||
</table> | |||
</Fragment> |
@@ -0,0 +1,308 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: branch with number of days 1`] = ` | |||
<tr | |||
className="" | |||
> | |||
<td | |||
className="nowrap" | |||
> | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": false, | |||
"name": "branch-6.7", | |||
"newCodePeriod": Object { | |||
"type": "NUMBER_OF_DAYS", | |||
"value": "21", | |||
}, | |||
} | |||
} | |||
className="little-spacer-right" | |||
/> | |||
branch-6.7 | |||
</td> | |||
<td | |||
className="huge-spacer-right nowrap" | |||
> | |||
<Tooltip> | |||
<span> | |||
baseline.number_days: 21 | |||
</span> | |||
</Tooltip> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem | |||
onClick={[Function]} | |||
> | |||
edit | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
onClick={[Function]} | |||
> | |||
reset_to_default | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
</td> | |||
</tr> | |||
`; | |||
exports[`should render correctly: branch with previous version 1`] = ` | |||
<tr | |||
className="" | |||
> | |||
<td | |||
className="nowrap" | |||
> | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": false, | |||
"name": "branch-6.7", | |||
"newCodePeriod": Object { | |||
"type": "PREVIOUS_VERSION", | |||
}, | |||
} | |||
} | |||
className="little-spacer-right" | |||
/> | |||
branch-6.7 | |||
</td> | |||
<td | |||
className="huge-spacer-right nowrap" | |||
> | |||
<Tooltip> | |||
<span> | |||
baseline.previous_version | |||
</span> | |||
</Tooltip> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem | |||
onClick={[Function]} | |||
> | |||
edit | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
onClick={[Function]} | |||
> | |||
reset_to_default | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
</td> | |||
</tr> | |||
`; | |||
exports[`should render correctly: branch with reference branch 1`] = ` | |||
<tr | |||
className="" | |||
> | |||
<td | |||
className="nowrap" | |||
> | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": false, | |||
"name": "branch-6.7", | |||
"newCodePeriod": Object { | |||
"type": "REFERENCE_BRANCH", | |||
"value": "master", | |||
}, | |||
} | |||
} | |||
className="little-spacer-right" | |||
/> | |||
branch-6.7 | |||
</td> | |||
<td | |||
className="huge-spacer-right nowrap" | |||
> | |||
<Tooltip> | |||
<span> | |||
baseline.reference_branch: master | |||
</span> | |||
</Tooltip> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem | |||
onClick={[Function]} | |||
> | |||
edit | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
onClick={[Function]} | |||
> | |||
reset_to_default | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
</td> | |||
</tr> | |||
`; | |||
exports[`should render correctly: branch with specific analysis 1`] = ` | |||
<tr | |||
className="" | |||
> | |||
<td | |||
className="nowrap" | |||
> | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": false, | |||
"name": "branch-6.7", | |||
"newCodePeriod": Object { | |||
"effectiveValue": "2018-12-02T13:01:12", | |||
"type": "SPECIFIC_ANALYSIS", | |||
"value": "A85835", | |||
}, | |||
} | |||
} | |||
className="little-spacer-right" | |||
/> | |||
branch-6.7 | |||
</td> | |||
<td | |||
className="huge-spacer-right nowrap" | |||
> | |||
<Tooltip> | |||
<span> | |||
baseline.specific_analysis: | |||
<DateTimeFormatter | |||
date="2018-12-02T13:01:12" | |||
/> | |||
</span> | |||
</Tooltip> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem | |||
onClick={[Function]} | |||
> | |||
edit | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
onClick={[Function]} | |||
> | |||
reset_to_default | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
</td> | |||
</tr> | |||
`; | |||
exports[`should render correctly: faulty branch 1`] = ` | |||
<tr | |||
className="branch-setting-warning" | |||
> | |||
<td | |||
className="nowrap" | |||
> | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": false, | |||
"name": "branch-7.3", | |||
} | |||
} | |||
className="little-spacer-right" | |||
/> | |||
branch-7.3 | |||
</td> | |||
<td | |||
className="huge-spacer-right nowrap" | |||
> | |||
<Tooltip | |||
overlay="baseline.reference_branch.invalid_branch_setting.branch-7.3" | |||
> | |||
<span> | |||
<WarningIcon | |||
className="little-spacer-right" | |||
/> | |||
branch_list.default_setting | |||
</span> | |||
</Tooltip> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem | |||
onClick={[Function]} | |||
> | |||
edit | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
</td> | |||
</tr> | |||
`; | |||
exports[`should render correctly: main branch with default 1`] = ` | |||
<tr | |||
className="" | |||
> | |||
<td | |||
className="nowrap" | |||
> | |||
<BranchLikeIcon | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
className="little-spacer-right" | |||
/> | |||
master | |||
<div | |||
className="badge spacer-left" | |||
> | |||
branches.main_branch | |||
</div> | |||
</td> | |||
<td | |||
className="huge-spacer-right nowrap" | |||
> | |||
<Tooltip> | |||
<span> | |||
branch_list.default_setting | |||
</span> | |||
</Tooltip> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem | |||
onClick={[Function]} | |||
> | |||
edit | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
</td> | |||
</tr> | |||
`; |
@@ -40,7 +40,7 @@ exports[`should render correctly 1`] = ` | |||
</Radio> | |||
</div> | |||
<div | |||
className="big-spacer-left big-spacer-right branch-baseline-setting-modal" | |||
className="big-spacer-left big-spacer-right project-baseline-setting" | |||
> | |||
<div | |||
className="display-flex-row big-spacer-bottom" | |||
@@ -60,16 +60,33 @@ exports[`should render correctly 1`] = ` | |||
onSelect={[MockFunction]} | |||
selected={false} | |||
/> | |||
<BaselineSettingReferenceBranch | |||
branchList={ | |||
Array [ | |||
Object { | |||
"isMain": true, | |||
"value": "master", | |||
}, | |||
] | |||
} | |||
disabled={true} | |||
onChangeReferenceBranch={[MockFunction]} | |||
onSelect={[MockFunction]} | |||
referenceBranch="master" | |||
selected={false} | |||
settingLevel="project" | |||
/> | |||
</div> | |||
</div> | |||
<div | |||
className="big-spacer-top invisible" | |||
> | |||
<p | |||
<Alert | |||
className="spacer-bottom" | |||
variant="info" | |||
> | |||
baseline.next_analysis_notice | |||
</p> | |||
</Alert> | |||
<DeferredSpinner | |||
className="spacer-right" | |||
loading={false} | |||
@@ -130,7 +147,7 @@ exports[`should render correctly 2`] = ` | |||
</Radio> | |||
</div> | |||
<div | |||
className="big-spacer-left big-spacer-right branch-baseline-setting-modal" | |||
className="big-spacer-left big-spacer-right project-baseline-setting" | |||
> | |||
<div | |||
className="display-flex-row big-spacer-bottom" | |||
@@ -160,11 +177,12 @@ exports[`should render correctly 2`] = ` | |||
<div | |||
className="big-spacer-top invisible" | |||
> | |||
<p | |||
<Alert | |||
className="spacer-bottom" | |||
variant="info" | |||
> | |||
baseline.next_analysis_notice | |||
</p> | |||
</Alert> | |||
<DeferredSpinner | |||
className="spacer-right" | |||
loading={false} | |||
@@ -225,7 +243,7 @@ exports[`should render correctly 3`] = ` | |||
</Radio> | |||
</div> | |||
<div | |||
className="big-spacer-left big-spacer-right branch-baseline-setting-modal" | |||
className="big-spacer-left big-spacer-right project-baseline-setting" | |||
> | |||
<div | |||
className="display-flex-row big-spacer-bottom" | |||
@@ -255,11 +273,12 @@ exports[`should render correctly 3`] = ` | |||
<div | |||
className="big-spacer-top invisible" | |||
> | |||
<p | |||
<Alert | |||
className="spacer-bottom" | |||
variant="info" | |||
> | |||
baseline.next_analysis_notice | |||
</p> | |||
</Alert> | |||
<DeferredSpinner | |||
className="spacer-right" | |||
loading={false} |
@@ -20,22 +20,26 @@ | |||
import { getSettingValue, validateSetting } from '../../utils'; | |||
describe('getSettingValue', () => { | |||
const state = { | |||
analysis: 'analysis', | |||
days: '35', | |||
referenceBranch: 'branch-4.2' | |||
}; | |||
it('should work for Days', () => { | |||
expect(getSettingValue({ analysis: 'analysis', days: '35', type: 'NUMBER_OF_DAYS' })).toBe( | |||
'35' | |||
); | |||
expect(getSettingValue({ ...state, type: 'NUMBER_OF_DAYS' })).toBe(state.days); | |||
}); | |||
it('should work for Analysis', () => { | |||
expect(getSettingValue({ analysis: 'analysis1', days: '35', type: 'SPECIFIC_ANALYSIS' })).toBe( | |||
'analysis1' | |||
); | |||
expect(getSettingValue({ ...state, type: 'SPECIFIC_ANALYSIS' })).toBe(state.analysis); | |||
}); | |||
it('should work for Previous version', () => { | |||
expect( | |||
getSettingValue({ analysis: 'analysis1', days: '35', type: 'PREVIOUS_VERSION' }) | |||
).toBeUndefined(); | |||
expect(getSettingValue({ ...state, type: 'PREVIOUS_VERSION' })).toBeUndefined(); | |||
}); | |||
it('should work for Reference branch', () => { | |||
expect(getSettingValue({ ...state, type: 'REFERENCE_BRANCH' })).toBe(state.referenceBranch); | |||
}); | |||
}); | |||
@@ -90,6 +94,24 @@ describe('validateSettings', () => { | |||
selected: 'SPECIFIC_ANALYSIS' | |||
}) | |||
).toEqual({ isChanged: true, isValid: true }); | |||
expect( | |||
validateSetting({ | |||
currentSetting: 'REFERENCE_BRANCH', | |||
currentSettingValue: 'master', | |||
days: '', | |||
referenceBranch: 'master', | |||
selected: 'REFERENCE_BRANCH' | |||
}) | |||
).toEqual({ isChanged: false, isValid: true }); | |||
expect( | |||
validateSetting({ | |||
currentSetting: 'REFERENCE_BRANCH', | |||
currentSettingValue: 'master', | |||
days: '', | |||
referenceBranch: '', | |||
selected: 'REFERENCE_BRANCH' | |||
}) | |||
).toEqual({ isChanged: true, isValid: false }); | |||
}); | |||
it('should validate at project level', () => { |
@@ -21,7 +21,9 @@ | |||
padding: calc(4 * var(--gridSize)); | |||
} | |||
.project-baseline-selector > .branch-baseline-setting-modal { | |||
.project-baseline-setting { | |||
display: flex; | |||
flex-direction: column; | |||
max-height: 60vh; | |||
padding-top: 2px; | |||
} | |||
@@ -35,6 +37,7 @@ | |||
} | |||
.branch-baseline-setting-modal { | |||
min-height: 450px; | |||
display: flex; | |||
flex-direction: column; | |||
} | |||
@@ -125,6 +128,10 @@ | |||
text-overflow: ellipsis; | |||
} | |||
.branch-setting-warning { | |||
background-color: var(--alertBackgroundWarning) !important; | |||
} | |||
.project-activity-event-icon.VERSION { | |||
color: var(--blue); | |||
} |
@@ -26,15 +26,19 @@ export function validateDays(days: string) { | |||
export function getSettingValue({ | |||
analysis, | |||
days, | |||
referenceBranch, | |||
type | |||
}: { | |||
analysis?: string; | |||
days?: string; | |||
referenceBranch?: string; | |||
type?: T.NewCodePeriodSettingType; | |||
}) { | |||
switch (type) { | |||
case 'NUMBER_OF_DAYS': | |||
return days; | |||
case 'REFERENCE_BRANCH': | |||
return referenceBranch; | |||
case 'SPECIFIC_ANALYSIS': | |||
return analysis; | |||
default: | |||
@@ -47,16 +51,18 @@ export function validateSetting(state: { | |||
currentSetting?: T.NewCodePeriodSettingType; | |||
currentSettingValue?: string; | |||
days: string; | |||
selected?: T.NewCodePeriodSettingType; | |||
overrideGeneralSetting?: boolean; | |||
referenceBranch?: string; | |||
selected?: T.NewCodePeriodSettingType; | |||
}) { | |||
const { | |||
analysis = '', | |||
currentSetting, | |||
currentSettingValue, | |||
days, | |||
selected, | |||
overrideGeneralSetting | |||
overrideGeneralSetting, | |||
referenceBranch = '', | |||
selected | |||
} = state; | |||
let isChanged; | |||
@@ -67,14 +73,16 @@ export function validateSetting(state: { | |||
overrideGeneralSetting === false || | |||
selected !== currentSetting || | |||
(selected === 'NUMBER_OF_DAYS' && days !== currentSettingValue) || | |||
(selected === 'SPECIFIC_ANALYSIS' && analysis !== currentSettingValue); | |||
(selected === 'SPECIFIC_ANALYSIS' && analysis !== currentSettingValue) || | |||
(selected === 'REFERENCE_BRANCH' && referenceBranch !== currentSettingValue); | |||
} | |||
const isValid = | |||
overrideGeneralSetting === false || | |||
selected === 'PREVIOUS_VERSION' || | |||
(selected === 'SPECIFIC_ANALYSIS' && analysis.length > 0) || | |||
(selected === 'NUMBER_OF_DAYS' && validateDays(days)); | |||
(selected === 'NUMBER_OF_DAYS' && validateDays(days)) || | |||
(selected === 'REFERENCE_BRANCH' && referenceBranch.length > 0); | |||
return { isChanged, isValid }; | |||
} |
@@ -503,7 +503,8 @@ declare namespace T { | |||
export type NewCodePeriodSettingType = | |||
| 'PREVIOUS_VERSION' | |||
| 'NUMBER_OF_DAYS' | |||
| 'SPECIFIC_ANALYSIS'; | |||
| 'SPECIFIC_ANALYSIS' | |||
| 'REFERENCE_BRANCH'; | |||
export interface Notification { | |||
channel: string; |
@@ -10522,10 +10522,10 @@ sockjs@0.3.19: | |||
faye-websocket "^0.10.0" | |||
uuid "^3.0.1" | |||
sonar-ui-common@1.0.4: | |||
version "1.0.4" | |||
resolved "https://repox.jfrog.io/repox/api/npm/npm/sonar-ui-common/-/sonar-ui-common-1.0.4.tgz#342cb674a560a79cbae47ecbf60ab47fe1052fa4" | |||
integrity sha1-NCy2dKVgp5y65H7L9gq0f+EFL6Q= | |||
sonar-ui-common@1.0.6: | |||
version "1.0.6" | |||
resolved "https://repox.jfrog.io/repox/api/npm/npm/sonar-ui-common/-/sonar-ui-common-1.0.6.tgz#0b22b9b35e4e210b34304780328b9831bf1f7a58" | |||
integrity sha1-CyK5s15OIQs0MEeAMouYMb8felg= | |||
dependencies: | |||
"@types/react-select" "1.2.6" | |||
classnames "2.2.6" |
@@ -563,8 +563,8 @@ project_branch_pull_request.table.branch=Branch | |||
project_branch_pull_request.table.pull_request=Pull Request | |||
project_branch_pull_request.last_analysis_date=Last Analysis Date | |||
project_baseline.page=New Code Period | |||
project_baseline.page.description=Use this page to manage the New Code Period of your project. {link} | |||
project_baseline.page=New Code | |||
project_baseline.page.description=Use this page to define the New Code of your project. {link} | |||
project_baseline.page.description.link=Learn More | |||
project_baseline.page.description2=You can adjust this setting globally in {link} | |||
project_baseline.page.description2.link=General Settings | |||
@@ -574,18 +574,26 @@ project_baseline.specific_setting=Define a specific setting for this project | |||
project_baseline.configure_branches=Set a specific setting for a branch | |||
baseline.previous_version=Previous version | |||
baseline.previous_version.description=The New Code Period will begin with the analysis following the previous version. | |||
baseline.previous_version.description=The New Code will be based on the analysis following the previous version. | |||
baseline.number_days=Number of days | |||
baseline.number_days.description=A floating New Code Period window set to a specific number of days. | |||
baseline.specific_date=Specific date | |||
baseline.specific_date.description=Set a specific date as the start of the New Code Period. (First analysis on this date will be used) | |||
baseline.number_days.description=A floating window set to a specific number of days used to define New Code. | |||
baseline.specific_analysis=Specific analysis | |||
baseline.specific_analysis.description=Choose an analysis as the baseline for the New Code Period. | |||
baseline.specific_analysis.description=Choose an analysis as the baseline for the New Code. | |||
baseline.reference_branch=Reference branch | |||
baseline.reference_branch.description=Choose a branch as the reference for the New Code. | |||
baseline.reference_branch.description2=The branch you select as the reference branch will need its own New Code definition to prevent it from using itself as a reference. | |||
baseline.specify_days=Specify a number of days | |||
baseline.last_analysis_before=Last analysis before | |||
baseline.next_analysis_notice=Changes will take effect after the next analysis | |||
baseline.reference_branch.choose=Choose a branch | |||
baseline.reference_branch.does_not_exist=Branch {0} could not be found in SonarQube. | |||
baseline.reference_branch.cannot_be_itself=A branch cannot be used as its own reference branch | |||
baseline.reference_branch.invalid_branch_setting=Branch {0} cannot use itself as a reference. Define a specific setting instead of using the project-level setting. | |||
baseline.edit_branch_setting=Edit the branch's setting | |||
branch_list.branch=Branch | |||
branch_list.current_setting=Setting | |||
branch_list.current_baseline=Current Baseline | |||
@@ -1000,10 +1008,10 @@ settings.analysis_scope.wildcards.zero_more_char=Match zero or more characters | |||
settings.analysis_scope.wildcards.zero_more_dir=Match zero or more directories | |||
settings.analysis_scope.wildcards.single_char=Match a single character | |||
settings.new_code_period.category=New Code Period | |||
settings.new_code_period.title=Default New Code Period behavior | |||
settings.new_code_period.description=The New Code Period is the period used to compare measures and track new issues. {link} | |||
settings.new_code_period.description2=This setting is the default for all projects. A specific New Code Period setting can be configured at project level. | |||
settings.new_code_period.category=New Code | |||
settings.new_code_period.title=Default New Code behavior | |||
settings.new_code_period.description=The New Code definition is used to compare measures and track new issues. {link} | |||
settings.new_code_period.description2=This setting is the default for all projects. A specific New Code definition can be configured at project level. | |||
settings.languages.select_a_language_placeholder=Select a language | |||