@@ -31,7 +31,7 @@ export function setNewCodePeriod(data: { | |||
project?: string; | |||
branch?: string; | |||
type: T.NewCodePeriodSettingType; | |||
value: string | null; | |||
value?: string; | |||
}): Promise<void> { | |||
return post('/api/new_code_periods/set', data).catch(throwGlobalError); | |||
} |
@@ -115,10 +115,7 @@ declare namespace T { | |||
export type BranchType = 'LONG' | 'SHORT'; | |||
export interface BranchWithNewCodePeriod extends Branch { | |||
newCodePeriod?: { | |||
type: NewCodePeriodSettingType; | |||
value: string | null; | |||
}; | |||
newCodePeriod?: NewCodePeriod; | |||
} | |||
export interface Breadcrumb { | |||
@@ -506,12 +503,19 @@ declare namespace T { | |||
qualityGate?: string; | |||
} | |||
export interface NewCodePeriod { | |||
type?: NewCodePeriodSettingType; | |||
value?: string; | |||
effectiveValue?: string; | |||
} | |||
export interface NewCodePeriodBranch { | |||
projectKey: string; | |||
branchKey: string; | |||
inherited?: boolean; | |||
type?: NewCodePeriodSettingType; | |||
value?: string | null; | |||
value?: string; | |||
effectiveValue?: string; | |||
} | |||
export type NewCodePeriodSettingType = |
@@ -18,6 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as classNames from 'classnames'; | |||
import { isEqual } from 'date-fns'; | |||
import { throttle } from 'lodash'; | |||
import * as React from 'react'; | |||
import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; | |||
@@ -45,6 +46,7 @@ interface Props { | |||
deleteAnalysis: (analysis: string) => Promise<void>; | |||
deleteEvent: (analysis: string, event: string) => Promise<void>; | |||
initializing: boolean; | |||
leakPeriodDate?: Date; | |||
project: { qualifier: string }; | |||
query: Query; | |||
updateQuery: (changes: Partial<Query>) => void; | |||
@@ -155,6 +157,40 @@ export default class ProjectActivityAnalysesList extends React.PureComponent<Pro | |||
this.props.updateQuery({ selectedDate: date }); | |||
}; | |||
shouldRenderBaselineMarker(analysis: ParsedAnalysis): boolean { | |||
return Boolean( | |||
analysis.manualNewCodePeriodBaseline || | |||
(this.props.leakPeriodDate && isEqual(this.props.leakPeriodDate, analysis.date)) | |||
); | |||
} | |||
renderAnalysis(analysis: ParsedAnalysis) { | |||
const firstAnalysisKey = this.props.analyses[0].key; | |||
const selectedDate = this.props.query.selectedDate | |||
? this.props.query.selectedDate.valueOf() | |||
: null; | |||
return ( | |||
<ProjectActivityAnalysis | |||
addCustomEvent={this.props.addCustomEvent} | |||
addVersion={this.props.addVersion} | |||
analysis={analysis} | |||
canAdmin={this.props.canAdmin} | |||
canCreateVersion={this.props.project.qualifier === 'TRK'} | |||
canDeleteAnalyses={this.props.canDeleteAnalyses} | |||
changeEvent={this.props.changeEvent} | |||
deleteAnalysis={this.props.deleteAnalysis} | |||
deleteEvent={this.props.deleteEvent} | |||
isBaseline={this.shouldRenderBaselineMarker(analysis)} | |||
isFirst={analysis.key === firstAnalysisKey} | |||
key={analysis.key} | |||
selected={analysis.date.valueOf() === selectedDate} | |||
updateSelectedDate={this.updateSelectedDate} | |||
/> | |||
); | |||
} | |||
render() { | |||
const byVersionByDay = getAnalysesByVersionByDay(this.props.analyses, this.props.query); | |||
const hasFilteredData = | |||
@@ -174,11 +210,6 @@ export default class ProjectActivityAnalysesList extends React.PureComponent<Pro | |||
); | |||
} | |||
const firstAnalysisKey = this.props.analyses[0].key; | |||
const selectedDate = this.props.query.selectedDate | |||
? this.props.query.selectedDate.valueOf() | |||
: null; | |||
return ( | |||
<ul | |||
className={classNames('project-activity-versions-list', this.props.className)} | |||
@@ -212,23 +243,7 @@ export default class ProjectActivityAnalysesList extends React.PureComponent<Pro | |||
</div> | |||
<ul className="project-activity-analyses-list"> | |||
{version.byDay[day] != null && | |||
version.byDay[day].map(analysis => ( | |||
<ProjectActivityAnalysis | |||
addCustomEvent={this.props.addCustomEvent} | |||
addVersion={this.props.addVersion} | |||
analysis={analysis} | |||
canAdmin={this.props.canAdmin} | |||
canCreateVersion={this.props.project.qualifier === 'TRK'} | |||
canDeleteAnalyses={this.props.canDeleteAnalyses} | |||
changeEvent={this.props.changeEvent} | |||
deleteAnalysis={this.props.deleteAnalysis} | |||
deleteEvent={this.props.deleteEvent} | |||
isFirst={analysis.key === firstAnalysisKey} | |||
key={analysis.key} | |||
selected={analysis.date.valueOf() === selectedDate} | |||
updateSelectedDate={this.updateSelectedDate} | |||
/> | |||
))} | |||
version.byDay[day].map(analysis => this.renderAnalysis(analysis))} | |||
</ul> | |||
</li> | |||
))} |
@@ -23,6 +23,7 @@ import ActionsDropdown, { | |||
ActionsDropdownDivider, | |||
ActionsDropdownItem | |||
} from 'sonar-ui-common/components/controls/ActionsDropdown'; | |||
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; | |||
import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; | |||
import { parseDate } from 'sonar-ui-common/helpers/dates'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
@@ -42,6 +43,7 @@ interface Props { | |||
changeEvent: (event: string, name: string) => Promise<void>; | |||
deleteAnalysis: (analysis: string) => Promise<void>; | |||
deleteEvent: (analysis: string, event: string) => Promise<void>; | |||
isBaseline: boolean; | |||
isFirst: boolean; | |||
selected: boolean; | |||
updateSelectedDate: (date: Date) => void; | |||
@@ -51,7 +53,6 @@ interface State { | |||
addEventForm: boolean; | |||
addVersionForm: boolean; | |||
removeAnalysisForm: boolean; | |||
suppressVersionTooltip?: boolean; | |||
} | |||
export default class ProjectActivityAnalysis extends React.PureComponent<Props, State> { | |||
@@ -102,22 +103,31 @@ export default class ProjectActivityAnalysis extends React.PureComponent<Props, | |||
this.setState({ addVersionForm: true }); | |||
}; | |||
handleTimeTooltipHide = () => { | |||
this.setState({ suppressVersionTooltip: false }); | |||
}; | |||
handleTimeTooltipShow = () => { | |||
this.setState({ suppressVersionTooltip: true }); | |||
}; | |||
closeAddVersionForm = () => { | |||
if (this.mounted) { | |||
this.setState({ addVersionForm: false }); | |||
} | |||
}; | |||
renderBaselineMarker() { | |||
return ( | |||
<div className="baseline-marker"> | |||
<div className="wedge" /> | |||
<hr /> | |||
<div className="label display-flex-center"> | |||
{translate('project_activity.new_code_period_start')} | |||
<HelpTooltip | |||
className="little-spacer-left" | |||
overlay={translate('project_activity.new_code_period_start.help')} | |||
placement="top" | |||
/> | |||
</div> | |||
</div> | |||
); | |||
} | |||
render() { | |||
const { analysis, isFirst, canAdmin, canCreateVersion } = this.props; | |||
const { analysis, isBaseline, isFirst, canAdmin, canCreateVersion } = this.props; | |||
const { date, events } = analysis; | |||
const parsedDate = parseDate(date); | |||
const hasVersion = events.find(event => event.category === 'VERSION') != null; | |||
@@ -222,6 +232,8 @@ export default class ProjectActivityAnalysis extends React.PureComponent<Props, | |||
isFirst={this.props.isFirst} | |||
/> | |||
)} | |||
{isBaseline && this.renderBaselineMarker()} | |||
</li> | |||
</Tooltip> | |||
); |
@@ -82,6 +82,9 @@ export default function ProjectActivityApp(props: Props) { | |||
deleteAnalysis={props.deleteAnalysis} | |||
deleteEvent={props.deleteEvent} | |||
initializing={props.initializing} | |||
leakPeriodDate={ | |||
props.project.leakPeriodDate ? parseDate(props.project.leakPeriodDate) : undefined | |||
} | |||
project={props.project} | |||
query={props.query} | |||
updateQuery={props.updateQuery} |
@@ -34,10 +34,12 @@ jest.mock('sonar-ui-common/helpers/dates', () => { | |||
return { ...actual, toShortNotSoISOString: (date: string) => 'ISO.' + date }; | |||
}); | |||
const DATE = parseDate('2016-10-27T16:33:50+0000'); | |||
const ANALYSES = [ | |||
{ | |||
key: 'A1', | |||
date: parseDate('2016-10-27T16:33:50+0000'), | |||
date: DATE, | |||
events: [{ key: 'E1', category: 'VERSION', name: '6.5-SNAPSHOT' }] | |||
}, | |||
{ key: 'A2', date: parseDate('2016-10-27T12:21:15+0000'), events: [] }, | |||
@@ -66,6 +68,7 @@ const DEFAULT_PROPS: ProjectActivityAnalysesList['props'] = { | |||
deleteAnalysis: jest.fn().mockResolvedValue(undefined), | |||
deleteEvent: jest.fn().mockResolvedValue(undefined), | |||
initializing: false, | |||
leakPeriodDate: parseDate('2016-10-27T12:21:15+0000'), | |||
project: { qualifier: 'TRK' }, | |||
query: { | |||
category: '', | |||
@@ -91,8 +94,8 @@ it('should correctly filter analyses by date range', () => { | |||
wrapper.setProps({ | |||
query: { | |||
...DEFAULT_PROPS.query, | |||
from: parseDate('2016-10-27T16:33:50+0000'), | |||
to: parseDate('2016-10-27T16:33:50+0000') | |||
from: DATE, | |||
to: DATE | |||
} | |||
}); | |||
expect(wrapper).toMatchSnapshot(); |
@@ -38,6 +38,7 @@ it('should render correctly', () => { | |||
expect( | |||
shallowRender({ analysis: mockParsedAnalysis({ buildString: '1.0.234' }) }) | |||
).toMatchSnapshot(); | |||
expect(shallowRender({ isBaseline: true })).toMatchSnapshot(); | |||
}); | |||
it('should show the correct admin options', () => { | |||
@@ -87,6 +88,7 @@ function shallowRender(props: Partial<ProjectActivityAnalysis['props']> = {}) { | |||
changeEvent={jest.fn()} | |||
deleteAnalysis={jest.fn()} | |||
deleteEvent={jest.fn()} | |||
isBaseline={false} | |||
isFirst={false} | |||
selected={false} | |||
updateSelectedDate={jest.fn()} |
@@ -67,6 +67,7 @@ exports[`should correctly filter analyses by category 1`] = ` | |||
changeEvent={[MockFunction]} | |||
deleteAnalysis={[MockFunction]} | |||
deleteEvent={[MockFunction]} | |||
isBaseline={false} | |||
isFirst={false} | |||
key="A4" | |||
selected={false} | |||
@@ -146,6 +147,7 @@ exports[`should correctly filter analyses by date range 1`] = ` | |||
changeEvent={[MockFunction]} | |||
deleteAnalysis={[MockFunction]} | |||
deleteEvent={[MockFunction]} | |||
isBaseline={false} | |||
isFirst={true} | |||
key="A1" | |||
selected={false} | |||
@@ -225,6 +227,7 @@ exports[`should render correctly 1`] = ` | |||
changeEvent={[MockFunction]} | |||
deleteAnalysis={[MockFunction]} | |||
deleteEvent={[MockFunction]} | |||
isBaseline={false} | |||
isFirst={true} | |||
key="A1" | |||
selected={false} | |||
@@ -245,6 +248,7 @@ exports[`should render correctly 1`] = ` | |||
changeEvent={[MockFunction]} | |||
deleteAnalysis={[MockFunction]} | |||
deleteEvent={[MockFunction]} | |||
isBaseline={true} | |||
isFirst={false} | |||
key="A2" | |||
selected={false} | |||
@@ -316,6 +320,7 @@ exports[`should render correctly 1`] = ` | |||
changeEvent={[MockFunction]} | |||
deleteAnalysis={[MockFunction]} | |||
deleteEvent={[MockFunction]} | |||
isBaseline={false} | |||
isFirst={false} | |||
key="A3" | |||
selected={false} | |||
@@ -360,6 +365,7 @@ exports[`should render correctly 1`] = ` | |||
changeEvent={[MockFunction]} | |||
deleteAnalysis={[MockFunction]} | |||
deleteEvent={[MockFunction]} | |||
isBaseline={false} | |||
isFirst={false} | |||
key="A4" | |||
selected={false} |
@@ -221,6 +221,65 @@ exports[`should render correctly 3`] = ` | |||
</Tooltip> | |||
`; | |||
exports[`should render correctly 4`] = ` | |||
<Tooltip | |||
mouseEnterDelay={0.5} | |||
overlay={ | |||
<TimeFormatter | |||
date={ | |||
Object { | |||
"toISOString": [Function], | |||
"valueOf": [Function], | |||
} | |||
} | |||
long={true} | |||
/> | |||
} | |||
placement="left" | |||
> | |||
<li | |||
className="project-activity-analysis" | |||
data-date={1546333200000} | |||
onClick={[Function]} | |||
tabIndex={0} | |||
> | |||
<div | |||
className="project-activity-time spacer-right" | |||
> | |||
<TimeFormatter | |||
date={ | |||
Object { | |||
"toISOString": [Function], | |||
"valueOf": [Function], | |||
} | |||
} | |||
long={false} | |||
> | |||
<Component /> | |||
</TimeFormatter> | |||
</div> | |||
<div | |||
className="baseline-marker" | |||
> | |||
<div | |||
className="wedge" | |||
/> | |||
<hr /> | |||
<div | |||
className="label display-flex-center" | |||
> | |||
project_activity.new_code_period_start | |||
<HelpTooltip | |||
className="little-spacer-left" | |||
overlay="project_activity.new_code_period_start.help" | |||
placement="top" | |||
/> | |||
</div> | |||
</div> | |||
</li> | |||
</Tooltip> | |||
`; | |||
exports[`should show the correct admin options 1`] = ` | |||
<Tooltip | |||
mouseEnterDelay={0.5} |
@@ -80,6 +80,7 @@ exports[`should render correctly 1`] = ` | |||
deleteAnalysis={[MockFunction]} | |||
deleteEvent={[MockFunction]} | |||
initializing={false} | |||
leakPeriodDate={2017-05-16T11:50:02.000Z} | |||
project={ | |||
Object { | |||
"key": "foo", |
@@ -61,9 +61,6 @@ | |||
flex-shrink: 0; | |||
} | |||
.project-activity-days-list { | |||
} | |||
.project-activity-day { | |||
margin-top: 8px; | |||
margin-bottom: 24px; | |||
@@ -79,14 +76,11 @@ | |||
font-weight: bold; | |||
} | |||
.project-activity-analyses-list { | |||
} | |||
.project-activity-analysis { | |||
position: relative; | |||
display: flex; | |||
min-height: var(--smallControlHeight); | |||
padding: calc(0.5 * var(--gridSize)); | |||
padding: var(--gridSize) calc(0.5 * var(--gridSize)); | |||
border-top: 1px solid var(--barBorderColor); | |||
border-bottom: 1px solid var(--barBorderColor); | |||
cursor: pointer; | |||
@@ -294,3 +288,33 @@ | |||
.project-activity-graph-tooltip-value { | |||
font-weight: bold; | |||
} | |||
.baseline-marker { | |||
position: absolute; | |||
top: -10px; | |||
left: 0; | |||
right: 0; | |||
display: flex; | |||
flex-direction: row; | |||
align-items: center; | |||
} | |||
.baseline-marker > .wedge { | |||
border: 10px solid transparent; | |||
border-left-color: var(--leakBorderColor); | |||
} | |||
.baseline-marker > hr { | |||
border: none; | |||
margin: 0 0 0 -11px; | |||
background-color: var(--leakBorderColor); | |||
height: 2px; | |||
flex: 1 0 auto; | |||
} | |||
.baseline-marker > .label { | |||
background-color: var(--leakColor); | |||
border: 1px solid var(--leakBorderColor); | |||
padding: 2px 8px; | |||
font-size: var(--verySmallFontSize); | |||
} |
@@ -104,7 +104,7 @@ describe('getSettingValue', () => { | |||
it('should work for Previous version', () => { | |||
wrapper.setState({ selected: 'PREVIOUS_VERSION' }); | |||
expect(wrapper.instance().getSettingValue()).toBeNull(); | |||
expect(wrapper.instance().getSettingValue()).toBeUndefined(); | |||
}); | |||
}); | |||
@@ -102,16 +102,32 @@ it('should render the right setting label', () => { | |||
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: 'PREVIOUS_VERSION', value: null }) | |||
).toBe('baseline.previous_version'); | |||
expect( | |||
wrapper.instance().renderNewCodePeriodSetting({ type: 'SPECIFIC_ANALYSIS', value: 'A85835' }) | |||
).toBe('baseline.specific_analysis: A85835'); | |||
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={[]} component={mockComponent()} {...props} /> | |||
<BranchList | |||
branchLikes={[]} | |||
component={mockComponent()} | |||
inheritedSetting={{ type: 'PREVIOUS_VERSION' }} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -9,6 +9,11 @@ exports[`should render correctly 1`] = ` | |||
<tr> | |||
<th> | |||
branch_list.branch | |||
</th> | |||
<th | |||
className="thin" | |||
> | |||
</th> | |||
<th | |||
className="thin nowrap huge-spacer-right" | |||
@@ -36,6 +41,7 @@ exports[`should render correctly 1`] = ` | |||
"isMain": true, | |||
"name": "master", | |||
"newCodePeriod": Object { | |||
"effectiveValue": undefined, | |||
"type": "NUMBER_OF_DAYS", | |||
"value": "27", | |||
}, | |||
@@ -50,6 +56,7 @@ exports[`should render correctly 1`] = ` | |||
branches.main_branch | |||
</div> | |||
</td> | |||
<td /> | |||
<td | |||
className="huge-spacer-right nowrap" | |||
> | |||
@@ -91,15 +98,18 @@ exports[`should render correctly 1`] = ` | |||
/> | |||
branch-6.7 | |||
</td> | |||
<td | |||
className="huge-spacer-right nowrap" | |||
> | |||
<td> | |||
<span | |||
className="badge badge-info" | |||
> | |||
default | |||
</span> | |||
</td> | |||
<td | |||
className="huge-spacer-right nowrap" | |||
> | |||
baseline.previous_version | |||
</td> | |||
<td | |||
className="text-right" | |||
> |
@@ -36,9 +36,9 @@ interface Props { | |||
interface State { | |||
currentSetting?: T.NewCodePeriodSettingType; | |||
currentSettingValue?: string | number; | |||
currentSettingValue?: string; | |||
days: string; | |||
generalSetting?: { type: T.NewCodePeriodSettingType; value?: string }; | |||
generalSetting?: T.NewCodePeriod; | |||
loading: boolean; | |||
saving: boolean; | |||
selected?: T.NewCodePeriodSettingType; | |||
@@ -126,7 +126,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
const { days, selected } = this.state; | |||
const type = selected; | |||
const value = type === 'NUMBER_OF_DAYS' ? days : null; | |||
const value = type === 'NUMBER_OF_DAYS' ? days : undefined; | |||
if (type) { | |||
this.setState({ saving: true }); | |||
@@ -184,7 +184,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
); | |||
} | |||
renderGeneralSetting(generalSetting: { type: T.NewCodePeriodSettingType; value?: string }) { | |||
renderGeneralSetting(generalSetting: T.NewCodePeriod) { | |||
if (generalSetting.type === 'NUMBER_OF_DAYS') { | |||
return `${translate('baseline.number_days')} (${translateWithParameters( | |||
'duration.days', | |||
@@ -238,15 +238,26 @@ export default class App extends React.PureComponent<Props, State> { | |||
currentSetting={currentSetting} | |||
currentSettingValue={currentSettingValue} | |||
days={days} | |||
generalSetting={generalSetting} | |||
onSelectDays={this.handleSelectDays} | |||
onSelectSetting={this.handleSelectSetting} | |||
onSubmit={this.handleSubmit} | |||
saving={saving} | |||
selected={selected} | |||
/> | |||
<BranchList branchLikes={this.props.branchLikes} component={this.props.component} /> | |||
{generalSetting && ( | |||
<BranchList | |||
branchLikes={this.props.branchLikes} | |||
component={this.props.component} | |||
inheritedSetting={ | |||
currentSetting | |||
? { | |||
type: currentSetting, | |||
value: currentSettingValue | |||
} | |||
: generalSetting | |||
} | |||
/> | |||
)} | |||
</div> | |||
)} | |||
</div> |
@@ -36,7 +36,7 @@ interface Props { | |||
analysis: string; | |||
branch: string; | |||
component: string; | |||
onSelectAnalysis: (analysis: string) => void; | |||
onSelectAnalysis: (analysis: ParsedAnalysis) => void; | |||
} | |||
interface State { | |||
@@ -216,7 +216,7 @@ export default class BranchAnalysisList extends React.PureComponent<Props, State | |||
})} | |||
data-date={parseDate(analysis.date).valueOf()} | |||
key={analysis.key} | |||
onClick={() => this.props.onSelectAnalysis(analysis.key)}> | |||
onClick={() => this.props.onSelectAnalysis(analysis)}> | |||
<div className="branch-analysis-time spacer-right"> | |||
<TimeFormatter date={parseDate(analysis.date)} long={false}> | |||
{formattedTime => ( |
@@ -21,8 +21,10 @@ import * as React from 'react'; | |||
import { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons'; | |||
import Modal from 'sonar-ui-common/components/controls/Modal'; | |||
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 { ParsedAnalysis } from '../../projectActivity/utils'; | |||
import { validateDays } from '../utils'; | |||
import BaselineSettingAnalysis from './BaselineSettingAnalysis'; | |||
import BaselineSettingDays from './BaselineSettingDays'; | |||
@@ -32,14 +34,12 @@ import BranchAnalysisList from './BranchAnalysisList'; | |||
interface Props { | |||
branch: T.BranchWithNewCodePeriod; | |||
component: string; | |||
onClose: ( | |||
branch?: string, | |||
newSetting?: { type: T.NewCodePeriodSettingType; value: string | null } | |||
) => void; | |||
onClose: (branch?: string, newSetting?: T.NewCodePeriod) => void; | |||
} | |||
interface State { | |||
analysis: string; | |||
analysisDate?: Date; | |||
days: string; | |||
saving: boolean; | |||
selected?: T.NewCodePeriodSettingType; | |||
@@ -80,7 +80,7 @@ export default class BranchBaselineSettingModal extends React.PureComponent<Prop | |||
case 'SPECIFIC_ANALYSIS': | |||
return this.state.analysis; | |||
default: | |||
return null; | |||
return undefined; | |||
} | |||
} | |||
@@ -88,7 +88,7 @@ export default class BranchBaselineSettingModal extends React.PureComponent<Prop | |||
e.preventDefault(); | |||
const { branch, component } = this.props; | |||
const { selected: type } = this.state; | |||
const { analysisDate, selected: type } = this.state; | |||
const value = this.getSettingValue(); | |||
@@ -104,7 +104,11 @@ export default class BranchBaselineSettingModal extends React.PureComponent<Prop | |||
this.setState({ | |||
saving: false | |||
}); | |||
this.props.onClose(branch.name, { type, value }); | |||
this.props.onClose(branch.name, { | |||
type, | |||
value, | |||
effectiveValue: analysisDate && toNotSoISOString(analysisDate) | |||
}); | |||
}, | |||
() => { | |||
this.setState({ | |||
@@ -117,7 +121,8 @@ export default class BranchBaselineSettingModal extends React.PureComponent<Prop | |||
requestClose = () => this.props.onClose(); | |||
handleSelectAnalysis = (analysis: string) => this.setState({ analysis }); | |||
handleSelectAnalysis = (analysis: ParsedAnalysis) => | |||
this.setState({ analysis: analysis.key, analysisDate: analysis.date }); | |||
handleSelectDays = (days: string) => this.setState({ days }); | |||
@@ -25,12 +25,14 @@ import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { listBranchesNewCodePeriod, resetNewCodePeriod } from '../../../api/newCodePeriod'; | |||
import BranchIcon from '../../../components/icons-components/BranchIcon'; | |||
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; | |||
import { isLongLivingBranch, isMainBranch, sortBranchesAsTree } from '../../../helpers/branches'; | |||
import BranchBaselineSettingModal from './BranchBaselineSettingModal'; | |||
interface Props { | |||
branchLikes: T.BranchLike[]; | |||
component: T.Component; | |||
inheritedSetting: T.NewCodePeriod; | |||
} | |||
interface State { | |||
@@ -78,10 +80,10 @@ export default class BranchList extends React.PureComponent<Props, State> { | |||
if (!newCodePeriod) { | |||
return b; | |||
} | |||
const { type = 'PREVIOUS_VERSION', value = null } = newCodePeriod; | |||
const { type = 'PREVIOUS_VERSION', value, effectiveValue } = newCodePeriod; | |||
return { | |||
...b, | |||
newCodePeriod: { type, value } | |||
newCodePeriod: { type, value, effectiveValue } | |||
}; | |||
}); | |||
@@ -93,10 +95,7 @@ export default class BranchList extends React.PureComponent<Props, State> { | |||
); | |||
} | |||
updateBranchNewCodePeriod = ( | |||
branch: string, | |||
newSetting: { type: T.NewCodePeriodSettingType; value: string | null } | undefined | |||
) => { | |||
updateBranchNewCodePeriod = (branch: string, newSetting: T.NewCodePeriod | undefined) => { | |||
const { branches } = this.state; | |||
const updated = branches.find(b => b.name === branch); | |||
@@ -110,10 +109,7 @@ export default class BranchList extends React.PureComponent<Props, State> { | |||
this.setState({ editedBranch: branch }); | |||
}; | |||
closeEditModal = ( | |||
branch?: string, | |||
newSetting?: { type: T.NewCodePeriodSettingType; value: string | null } | |||
) => { | |||
closeEditModal = (branch?: string, newSetting?: T.NewCodePeriod) => { | |||
if (branch) { | |||
this.setState({ | |||
branches: this.updateBranchNewCodePeriod(branch, newSetting), | |||
@@ -133,13 +129,19 @@ export default class BranchList extends React.PureComponent<Props, State> { | |||
}); | |||
} | |||
renderNewCodePeriodSetting(newCodePeriod: { | |||
type: T.NewCodePeriodSettingType; | |||
value: string | null; | |||
}) { | |||
renderNewCodePeriodSetting(newCodePeriod: T.NewCodePeriod) { | |||
switch (newCodePeriod.type) { | |||
case 'SPECIFIC_ANALYSIS': | |||
return `${translate('baseline.specific_analysis')}: ${newCodePeriod.value}`; | |||
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': | |||
@@ -166,6 +168,7 @@ export default class BranchList extends React.PureComponent<Props, State> { | |||
<thead> | |||
<tr> | |||
<th>{translate('branch_list.branch')}</th> | |||
<th className="thin"> </th> | |||
<th className="thin nowrap huge-spacer-right"> | |||
{translate('branch_list.current_setting')} | |||
</th> | |||
@@ -182,13 +185,16 @@ export default class BranchList extends React.PureComponent<Props, State> { | |||
<div className="badge spacer-left">{translate('branches.main_branch')}</div> | |||
)} | |||
</td> | |||
<td className="huge-spacer-right nowrap"> | |||
{branch.newCodePeriod ? ( | |||
this.renderNewCodePeriodSetting(branch.newCodePeriod) | |||
) : ( | |||
<td> | |||
{!branch.newCodePeriod && ( | |||
<span className="badge badge-info">{translate('default')}</span> | |||
)} | |||
</td> | |||
<td className="huge-spacer-right nowrap"> | |||
{branch.newCodePeriod | |||
? this.renderNewCodePeriodSetting(branch.newCodePeriod) | |||
: this.renderNewCodePeriodSetting(this.props.inheritedSetting)} | |||
</td> | |||
<td className="text-right"> | |||
<ActionsDropdown> | |||
<ActionsDropdownItem onClick={() => this.openEditModal(branch)}> |
@@ -29,7 +29,6 @@ export interface ProjectBaselineSelectorProps { | |||
currentSetting?: T.NewCodePeriodSettingType; | |||
currentSettingValue?: string | number; | |||
days: string; | |||
generalSetting?: { type: T.NewCodePeriodSettingType; value?: string }; | |||
onSelectDays: (value: string) => void; | |||
onSelectSetting: (value: T.NewCodePeriodSettingType) => void; | |||
onSubmit: (e: React.SyntheticEvent<HTMLFormElement>) => void; |
@@ -68,7 +68,7 @@ export class CategoriesList extends React.PureComponent<Props> { | |||
key, | |||
name: getCategoryName(key) | |||
})) | |||
.concat(FIXED_CATEGORIES); | |||
.concat(!this.props.component ? FIXED_CATEGORIES : []); | |||
const sortedCategories = sortBy(categoriesWithName, category => category.name.toLowerCase()); | |||
return ( | |||
<ul className="side-tabs-menu"> |
@@ -97,7 +97,7 @@ export class App extends React.PureComponent<Props & WithRouterProps, State> { | |||
/> | |||
</div> | |||
<div className="side-tabs-main"> | |||
{selectedCategory === 'new_code_period' ? ( | |||
{!this.props.component && selectedCategory === 'new_code_period' ? ( | |||
<NewCodePeriod /> | |||
) : ( | |||
<CategoryDefinitionsList |
@@ -90,7 +90,7 @@ export default class NewCodePeriod extends React.PureComponent<{}, State> { | |||
const { days, selected } = this.state; | |||
const type = selected; | |||
const value = type === 'NUMBER_OF_DAYS' ? days : null; | |||
const value = type === 'NUMBER_OF_DAYS' ? days : undefined; | |||
if (type) { | |||
this.setState({ saving: true, success: false }); |
@@ -108,7 +108,7 @@ it('should submit correctly', async () => { | |||
wrapper.find('form').simulate('submit', { preventDefault }); | |||
expect(preventDefault).toBeCalledTimes(1); | |||
expect(setNewCodePeriod).toBeCalledWith({ type: 'PREVIOUS_VERSION', value: null }); | |||
expect(setNewCodePeriod).toBeCalledWith({ type: 'PREVIOUS_VERSION', value: undefined }); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state('currentSetting')).toEqual(wrapper.state('selected')); | |||
}); |
@@ -1115,6 +1115,8 @@ project_activity.delete_analysis.question=Are you sure you want to delete this a | |||
project_activity.filter_events=Filter events | |||
project_activity.events.tooltip.edit=Edit this event | |||
project_activity.events.tooltip.delete=Delete this event | |||
project_activity.new_code_period_start=New Code Period starts here | |||
project_activity.new_code_period_start.help=The analysis before this mark is the baseline for New Code comparison | |||
project_activity.graphs.issues=Issues | |||
project_activity.graphs.coverage=Coverage |