@@ -344,6 +344,11 @@ th.huge-spacer-right { | |||
align-items: stretch; | |||
} | |||
.display-flex-start { | |||
display: flex !important; | |||
align-items: flex-start; | |||
} | |||
.display-inline-flex-baseline { | |||
display: inline-flex !important; | |||
align-items: baseline; |
@@ -17,14 +17,12 @@ | |||
* 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 { FormattedMessage } from 'react-intl'; | |||
import { Link } from 'react-router'; | |||
import { ResetButtonLink } from 'sonar-ui-common/components/controls/buttons'; | |||
import BranchIcon from 'sonar-ui-common/components/icons/BranchIcon'; | |||
import DropdownIcon from 'sonar-ui-common/components/icons/DropdownIcon'; | |||
import ProjectEventIcon from 'sonar-ui-common/components/icons/ProjectEventIcon'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { limitComponentName } from 'sonar-ui-common/helpers/path'; | |||
import { isMainBranch } from '../../../helpers/branch-like'; | |||
@@ -136,22 +134,10 @@ export class DefinitionChangeEventInner extends React.PureComponent<Props, State | |||
const { event } = this.props; | |||
const { expanded } = this.state; | |||
return ( | |||
<div className="project-activity-event-inner"> | |||
<div className="project-activity-event-inner-main"> | |||
<ProjectEventIcon | |||
className={classNames( | |||
'project-activity-event-icon', | |||
'little-spacer-right', | |||
event.category | |||
)} | |||
/> | |||
<div className="project-activity-event-inner-text flex-1"> | |||
<span className="note little-spacer-right"> | |||
{translate('event.category', event.category)} | |||
</span> | |||
</div> | |||
<> | |||
<span className="note">{translate('event.category', event.category)}:</span> | |||
<div> | |||
<ResetButtonLink | |||
className="project-activity-event-inner-more-link" | |||
onClick={this.toggleProjectsList} | |||
@@ -162,15 +148,15 @@ export class DefinitionChangeEventInner extends React.PureComponent<Props, State | |||
</div> | |||
{expanded && ( | |||
<ul className="project-activity-event-inner-more-content"> | |||
<ul className="spacer-left spacer-top"> | |||
{event.definitionChange.projects.map(project => ( | |||
<li className="display-flex-center little-spacer-top" key={project.key}> | |||
<li className="display-flex-center spacer-top" key={project.key}> | |||
{this.renderProjectChange(project)} | |||
</li> | |||
))} | |||
</ul> | |||
)} | |||
</div> | |||
</> | |||
); | |||
} | |||
} |
@@ -17,120 +17,101 @@ | |||
* 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 { DeleteButton, EditButton } from 'sonar-ui-common/components/controls/buttons'; | |||
import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; | |||
import ProjectEventIcon from 'sonar-ui-common/components/icons/ProjectEventIcon'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import EventInner from './EventInner'; | |||
import ChangeEventForm from './forms/ChangeEventForm'; | |||
import RemoveEventForm from './forms/RemoveEventForm'; | |||
interface Props { | |||
analysis: string; | |||
export interface EventProps { | |||
analysisKey: string; | |||
canAdmin?: boolean; | |||
changeEvent: (event: string, name: string) => Promise<void>; | |||
deleteEvent: (analysis: string, event: string) => Promise<void>; | |||
event: T.AnalysisEvent; | |||
isFirst?: boolean; | |||
onChange?: (event: string, name: string) => Promise<void>; | |||
onDelete?: (analysisKey: string, event: string) => Promise<void>; | |||
} | |||
interface State { | |||
changing: boolean; | |||
deleting: boolean; | |||
} | |||
export default class Event extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = { changing: false, deleting: false }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
startChanging = () => { | |||
this.setState({ changing: true }); | |||
}; | |||
stopChanging = () => { | |||
if (this.mounted) { | |||
this.setState({ changing: false }); | |||
} | |||
}; | |||
export function Event(props: EventProps) { | |||
const { analysisKey, event, canAdmin, isFirst } = props; | |||
startDeleting = () => { | |||
this.setState({ deleting: true }); | |||
}; | |||
const [changing, setChanging] = React.useState(false); | |||
const [deleting, setDeleting] = React.useState(false); | |||
stopDeleting = () => { | |||
if (this.mounted) { | |||
this.setState({ deleting: false }); | |||
} | |||
}; | |||
const isOther = event.category === 'OTHER'; | |||
const isVersion = event.category === 'VERSION'; | |||
const canChange = (isOther || isVersion) && props.onChange; | |||
const canDelete = (isOther || (isVersion && !isFirst)) && props.onDelete; | |||
const showActions = canAdmin && (canChange || canDelete); | |||
render() { | |||
const { event, canAdmin } = this.props; | |||
const isOther = event.category === 'OTHER'; | |||
const isVersion = !isOther && event.category === 'VERSION'; | |||
const canChange = isOther || isVersion; | |||
const canDelete = isOther || (isVersion && !this.props.isFirst); | |||
const showActions = canAdmin && (canChange || canDelete); | |||
return ( | |||
<div className="project-activity-event"> | |||
<ProjectEventIcon | |||
className={classNames( | |||
'project-activity-event-icon little-spacer-right text-middle', | |||
event.category | |||
)} | |||
/> | |||
return ( | |||
<div className="project-activity-event"> | |||
<EventInner event={this.props.event} /> | |||
<EventInner event={event} /> | |||
{showActions && ( | |||
<div className="project-activity-event-actions spacer-left"> | |||
{canChange && ( | |||
<Tooltip overlay={translate('project_activity.events.tooltip.edit')}> | |||
<EditButton className="js-change-event button-small" onClick={this.startChanging} /> | |||
</Tooltip> | |||
)} | |||
{canDelete && ( | |||
<Tooltip overlay={translate('project_activity.events.tooltip.delete')}> | |||
<DeleteButton | |||
className="js-delete-event button-small" | |||
onClick={this.startDeleting} | |||
/> | |||
</Tooltip> | |||
)} | |||
</div> | |||
)} | |||
{showActions && ( | |||
<span className="nowrap"> | |||
{canChange && ( | |||
<EditButton | |||
aria-label={translate('project_activity.events.tooltip.edit')} | |||
className="button-small" | |||
data-test="project-activity__edit-event" | |||
onClick={() => setChanging(true)} | |||
stopPropagation={true} | |||
/> | |||
)} | |||
{canDelete && ( | |||
<DeleteButton | |||
aria-label={translate('project_activity.events.tooltip.delete')} | |||
className="button-small" | |||
data-test="project-activity__delete-event" | |||
onClick={() => setDeleting(true)} | |||
stopPropagation={true} | |||
/> | |||
)} | |||
</span> | |||
)} | |||
{this.state.changing && ( | |||
<ChangeEventForm | |||
changeEvent={this.props.changeEvent} | |||
event={this.props.event} | |||
header={ | |||
isVersion | |||
? translate('project_activity.change_version') | |||
: translate('project_activity.change_custom_event') | |||
} | |||
onClose={this.stopChanging} | |||
/> | |||
)} | |||
{changing && props.onChange && ( | |||
<ChangeEventForm | |||
changeEvent={props.onChange} | |||
event={event} | |||
header={ | |||
isVersion | |||
? translate('project_activity.change_version') | |||
: translate('project_activity.change_custom_event') | |||
} | |||
onClose={() => setChanging(false)} | |||
/> | |||
)} | |||
{this.state.deleting && ( | |||
<RemoveEventForm | |||
analysis={this.props.analysis} | |||
deleteEvent={this.props.deleteEvent} | |||
event={this.props.event} | |||
header={ | |||
isVersion | |||
? translate('project_activity.remove_version') | |||
: translate('project_activity.remove_custom_event') | |||
} | |||
onClose={this.stopDeleting} | |||
removeEventQuestion={`project_activity.${ | |||
isVersion ? 'remove_version' : 'remove_custom_event' | |||
}.question`} | |||
/> | |||
)} | |||
</div> | |||
); | |||
} | |||
{deleting && props.onDelete && ( | |||
<RemoveEventForm | |||
analysisKey={analysisKey} | |||
event={event} | |||
header={ | |||
isVersion | |||
? translate('project_activity.remove_version') | |||
: translate('project_activity.remove_custom_event') | |||
} | |||
onClose={() => setDeleting(false)} | |||
onConfirm={props.onDelete} | |||
removeEventQuestion={translate( | |||
`project_activity.${isVersion ? 'remove_version' : 'remove_custom_event'}.question` | |||
)} | |||
/> | |||
)} | |||
</div> | |||
); | |||
} | |||
export default React.memo(Event); |
@@ -17,19 +17,18 @@ | |||
* 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 ProjectEventIcon from 'sonar-ui-common/components/icons/ProjectEventIcon'; | |||
import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { ComponentContext } from '../../../app/components/ComponentContext'; | |||
import { DefinitionChangeEventInner, isDefinitionChangeEvent } from './DefinitionChangeEventInner'; | |||
import { isRichQualityGateEvent, RichQualityGateEventInner } from './RichQualityGateEventInner'; | |||
interface Props { | |||
export interface EventInnerProps { | |||
event: T.AnalysisEvent; | |||
} | |||
export default function EventInner({ event }: Props) { | |||
export default function EventInner({ event }: EventInnerProps) { | |||
if (isRichQualityGateEvent(event)) { | |||
return <RichQualityGateEventInner event={event} />; | |||
} else if (isDefinitionChangeEvent(event)) { | |||
@@ -39,25 +38,14 @@ export default function EventInner({ event }: Props) { | |||
</ComponentContext.Consumer> | |||
); | |||
} else { | |||
return ( | |||
<div className="project-activity-event-inner"> | |||
<div className="project-activity-event-inner-main"> | |||
<ProjectEventIcon | |||
className={classNames( | |||
'project-activity-event-icon', | |||
'little-spacer-right', | |||
event.category | |||
)} | |||
/> | |||
<span className="project-activity-event-inner-text"> | |||
<span className="note little-spacer-right"> | |||
{translate('event.category', event.category)}: | |||
</span> | |||
<strong title={event.description}>{event.name}</strong> | |||
</span> | |||
</div> | |||
</div> | |||
const content = ( | |||
<span className="text-middle"> | |||
<span className="note little-spacer-right"> | |||
{translate('event.category', event.category)}: | |||
</span> | |||
<strong className="spacer-right">{event.name}</strong> | |||
</span> | |||
); | |||
return event.description ? <Tooltip overlay={event.description}>{content}</Tooltip> : content; | |||
} | |||
} |
@@ -21,18 +21,20 @@ import { sortBy } from 'lodash'; | |||
import * as React from 'react'; | |||
import Event from './Event'; | |||
interface Props { | |||
analysis: string; | |||
export interface EventsProps { | |||
analysisKey: string; | |||
canAdmin?: boolean; | |||
changeEvent: (event: string, name: string) => Promise<void>; | |||
deleteEvent: (analysis: string, event: string) => Promise<void>; | |||
events: T.AnalysisEvent[]; | |||
isFirst?: boolean; | |||
onChange?: (event: string, name: string) => Promise<void>; | |||
onDelete?: (analysis: string, event: string) => Promise<void>; | |||
} | |||
export default function Events(props: Props) { | |||
export function Events(props: EventsProps) { | |||
const { analysisKey, canAdmin, events, isFirst } = props; | |||
const sortedEvents = sortBy( | |||
props.events, | |||
events, | |||
// versions last | |||
event => (event.category === 'VERSION' ? 1 : 0), | |||
// then the rest sorted by category | |||
@@ -40,18 +42,20 @@ export default function Events(props: Props) { | |||
); | |||
return ( | |||
<div className="project-activity-events"> | |||
<div className="big-spacer-top"> | |||
{sortedEvents.map(event => ( | |||
<Event | |||
analysis={props.analysis} | |||
canAdmin={props.canAdmin} | |||
changeEvent={props.changeEvent} | |||
deleteEvent={props.deleteEvent} | |||
analysisKey={analysisKey} | |||
canAdmin={canAdmin} | |||
event={event} | |||
isFirst={props.isFirst} | |||
isFirst={isFirst} | |||
key={event.key} | |||
onChange={props.onChange} | |||
onDelete={props.onDelete} | |||
/> | |||
))} | |||
</div> | |||
); | |||
} | |||
export default React.memo(Events); |
@@ -25,12 +25,8 @@ import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; | |||
import { toShortNotSoISOString } from 'sonar-ui-common/helpers/dates'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import DateFormatter from '../../../components/intl/DateFormatter'; | |||
import { | |||
activityQueryChanged, | |||
getAnalysesByVersionByDay, | |||
Query, | |||
selectedDateQueryChanged | |||
} from '../utils'; | |||
import { ComponentQualifier } from '../../../types/component'; | |||
import { activityQueryChanged, getAnalysesByVersionByDay, Query } from '../utils'; | |||
import ProjectActivityAnalysis from './ProjectActivityAnalysis'; | |||
interface Props { | |||
@@ -41,7 +37,6 @@ interface Props { | |||
canAdmin?: boolean; | |||
canDeleteAnalyses?: boolean; | |||
changeEvent: (event: string, name: string) => Promise<void>; | |||
className?: string; | |||
deleteAnalysis: (analysis: string) => Promise<void>; | |||
deleteEvent: (analysis: string, event: string) => Promise<void>; | |||
initializing: boolean; | |||
@@ -54,7 +49,7 @@ interface Props { | |||
export default class ProjectActivityAnalysesList extends React.PureComponent<Props> { | |||
analyses?: HTMLCollectionOf<HTMLElement>; | |||
badges?: HTMLCollectionOf<HTMLElement>; | |||
scrollContainer?: HTMLElement | null; | |||
scrollContainer?: HTMLUListElement | null; | |||
constructor(props: Props) { | |||
super(props); | |||
@@ -74,13 +69,7 @@ export default class ProjectActivityAnalysesList extends React.PureComponent<Pro | |||
if (!this.scrollContainer) { | |||
return; | |||
} | |||
if ( | |||
this.props.query.selectedDate && | |||
(selectedDateQueryChanged(prevProps.query, this.props.query) || | |||
prevProps.analyses !== this.props.analyses) | |||
) { | |||
this.scrollToDate(this.props.query.selectedDate); | |||
} else if (activityQueryChanged(prevProps.query, this.props.query)) { | |||
if (activityQueryChanged(prevProps.query, this.props.query)) { | |||
this.resetScrollTop(0, true); | |||
} | |||
} | |||
@@ -100,24 +89,6 @@ export default class ProjectActivityAnalysesList extends React.PureComponent<Pro | |||
this.updateStickyBadges(forceBadgeAlignement); | |||
}; | |||
scrollToDate = (targetDate?: Date) => { | |||
if (!this.scrollContainer || !targetDate || !this.analyses) { | |||
return; | |||
} | |||
const date = targetDate.valueOf(); | |||
for (let i = 1; i < this.analyses.length; i++) { | |||
if (Number(this.analyses[i].getAttribute('data-date')) === date) { | |||
const containerHeight = this.scrollContainer.offsetHeight - 100; | |||
const scrollDiff = Math.abs(this.scrollContainer.scrollTop - this.analyses[i].offsetTop); | |||
// Center only the extremities and the ones outside of the container | |||
if (scrollDiff > containerHeight || scrollDiff < 100) { | |||
this.resetScrollTop(this.analyses[i].offsetTop - containerHeight / 2); | |||
} | |||
break; | |||
} | |||
} | |||
}; | |||
updateStickyBadges = (forceBadgeAlignement?: boolean) => { | |||
if (!this.scrollContainer || !this.badges) { | |||
return; | |||
@@ -173,7 +144,7 @@ export default class ProjectActivityAnalysesList extends React.PureComponent<Pro | |||
addVersion={this.props.addVersion} | |||
analysis={analysis} | |||
canAdmin={this.props.canAdmin} | |||
canCreateVersion={this.props.project.qualifier === 'TRK'} | |||
canCreateVersion={this.props.project.qualifier === ComponentQualifier.Project} | |||
canDeleteAnalyses={this.props.canDeleteAnalyses} | |||
changeEvent={this.props.changeEvent} | |||
deleteAnalysis={this.props.deleteAnalysis} | |||
@@ -181,6 +152,7 @@ export default class ProjectActivityAnalysesList extends React.PureComponent<Pro | |||
isBaseline={this.shouldRenderBaselineMarker(analysis)} | |||
isFirst={analysis.key === firstAnalysisKey} | |||
key={analysis.key} | |||
parentScrollContainer={this.scrollContainer} | |||
selected={analysis.date.valueOf() === selectedDate} | |||
updateSelectedDate={this.updateSelectedDate} | |||
/> | |||
@@ -194,7 +166,7 @@ export default class ProjectActivityAnalysesList extends React.PureComponent<Pro | |||
(byVersionByDay.length === 1 && Object.keys(byVersionByDay[0].byDay).length > 0); | |||
if (this.props.analyses.length === 0 || !hasFilteredData) { | |||
return ( | |||
<div className={this.props.className}> | |||
<div className="boxed-group-inner"> | |||
{this.props.initializing ? ( | |||
<div className="text-center"> | |||
<i className="spinner" /> | |||
@@ -208,10 +180,12 @@ export default class ProjectActivityAnalysesList extends React.PureComponent<Pro | |||
return ( | |||
<ul | |||
className={classNames('project-activity-versions-list', this.props.className)} | |||
className="project-activity-versions-list" | |||
onScroll={this.handleScroll} | |||
ref={element => (this.scrollContainer = element)} | |||
style={{ paddingTop: this.props.project.qualifier === 'TRK' ? 52 : undefined }}> | |||
style={{ | |||
paddingTop: this.props.project.qualifier === ComponentQualifier.Project ? 52 : undefined | |||
}}> | |||
{byVersionByDay.map((version, idx) => { | |||
const days = Object.keys(version.byDay); | |||
if (days.length <= 0) { |
@@ -23,16 +23,18 @@ import ActionsDropdown, { | |||
ActionsDropdownDivider, | |||
ActionsDropdownItem | |||
} from 'sonar-ui-common/components/controls/ActionsDropdown'; | |||
import ClickEventBoundary from 'sonar-ui-common/components/controls/ClickEventBoundary'; | |||
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; | |||
import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; | |||
import { PopupPlacement } from 'sonar-ui-common/components/ui/popups'; | |||
import { parseDate } from 'sonar-ui-common/helpers/dates'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { scrollToElement } from 'sonar-ui-common/helpers/scrolling'; | |||
import TimeFormatter from '../../../components/intl/TimeFormatter'; | |||
import Events from './Events'; | |||
import AddEventForm from './forms/AddEventForm'; | |||
import RemoveAnalysisForm from './forms/RemoveAnalysisForm'; | |||
interface Props { | |||
export interface ProjectActivityAnalysisProps { | |||
addCustomEvent: (analysis: string, name: string, category?: string) => Promise<void>; | |||
addVersion: (analysis: string, version: string) => Promise<void>; | |||
analysis: T.ParsedAnalysis; | |||
@@ -44,141 +46,93 @@ interface Props { | |||
deleteEvent: (analysis: string, event: string) => Promise<void>; | |||
isBaseline: boolean; | |||
isFirst: boolean; | |||
parentScrollContainer?: HTMLElement | null; | |||
selected: boolean; | |||
updateSelectedDate: (date: Date) => void; | |||
} | |||
interface State { | |||
addEventForm: boolean; | |||
addVersionForm: boolean; | |||
removeAnalysisForm: boolean; | |||
} | |||
export default class ProjectActivityAnalysis extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = { | |||
addEventForm: false, | |||
addVersionForm: false, | |||
removeAnalysisForm: false | |||
}; | |||
componentDidMount() { | |||
this.mounted = true; | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
handleClick = () => { | |||
this.props.updateSelectedDate(this.props.analysis.date); | |||
}; | |||
stopPropagation = (event: React.SyntheticEvent) => { | |||
event.stopPropagation(); | |||
}; | |||
handleRemoveAnalysisClick = () => { | |||
this.setState({ removeAnalysisForm: true }); | |||
}; | |||
closeRemoveAnalysisForm = () => { | |||
if (this.mounted) { | |||
this.setState({ removeAnalysisForm: false }); | |||
} | |||
}; | |||
handleAddEventClick = () => { | |||
this.setState({ addEventForm: true }); | |||
}; | |||
closeAddEventForm = () => { | |||
if (this.mounted) { | |||
this.setState({ addEventForm: false }); | |||
export function ProjectActivityAnalysis(props: ProjectActivityAnalysisProps) { | |||
let node: HTMLLIElement | null = null; | |||
const { | |||
analysis, | |||
isBaseline, | |||
isFirst, | |||
canAdmin, | |||
canCreateVersion, | |||
parentScrollContainer, | |||
selected | |||
} = props; | |||
React.useEffect(() => { | |||
if (node && parentScrollContainer && selected) { | |||
const { height } = node.getBoundingClientRect(); | |||
scrollToElement(node, { | |||
bottomOffset: height + 20, | |||
topOffset: 60, | |||
parent: parentScrollContainer, | |||
smooth: false | |||
}); | |||
} | |||
}; | |||
handleAddVersionClick = () => { | |||
this.setState({ addVersionForm: 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" | |||
/> | |||
}); | |||
const [addEventForm, setAddEventForm] = React.useState(false); | |||
const [addVersionForm, setAddVersionForm] = React.useState(false); | |||
const [removeAnalysisForm, setRemoveAnalysisForm] = React.useState(false); | |||
const parsedDate = parseDate(analysis.date); | |||
const hasVersion = analysis.events.find(event => event.category === 'VERSION') != null; | |||
const canAddVersion = canAdmin && !hasVersion && canCreateVersion; | |||
const canAddEvent = canAdmin; | |||
const canDeleteAnalyses = | |||
props.canDeleteAnalyses && !isFirst && !analysis.manualNewCodePeriodBaseline; | |||
return ( | |||
<li | |||
className={classNames('project-activity-analysis bordered-top bordered-bottom', { | |||
selected | |||
})} | |||
onClick={() => props.updateSelectedDate(analysis.date)} | |||
ref={ref => (node = ref)}> | |||
<div className="display-flex-center display-flex-space-between"> | |||
<div className="project-activity-time"> | |||
<TimeFormatter date={parsedDate} long={false}> | |||
{formattedTime => ( | |||
<time className="text-middle" dateTime={parsedDate.toISOString()}> | |||
{formattedTime} | |||
</time> | |||
)} | |||
</TimeFormatter> | |||
</div> | |||
</div> | |||
); | |||
} | |||
render() { | |||
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; | |||
const canAddVersion = canAdmin && !hasVersion && canCreateVersion; | |||
const canAddEvent = canAdmin; | |||
const canDeleteAnalyses = | |||
this.props.canDeleteAnalyses && !isFirst && !analysis.manualNewCodePeriodBaseline; | |||
let tooltipContent = <TimeFormatter date={parsedDate} long={true} />; | |||
if (analysis.buildString) { | |||
tooltipContent = ( | |||
<> | |||
{tooltipContent} | |||
<br /> | |||
{translateWithParameters( | |||
'project_activity.analysis_build_string_X', | |||
analysis.buildString | |||
)} | |||
</> | |||
); | |||
} | |||
return ( | |||
<Tooltip mouseEnterDelay={0.5} overlay={tooltipContent} placement="left"> | |||
<li | |||
className={classNames('project-activity-analysis', { selected: this.props.selected })} | |||
data-date={parsedDate.valueOf()} | |||
onClick={this.handleClick} | |||
tabIndex={0}> | |||
<div className="project-activity-time spacer-right"> | |||
<TimeFormatter date={parsedDate} long={false}> | |||
{formattedTime => ( | |||
<time className="text-middle" dateTime={parsedDate.toISOString()}> | |||
{formattedTime} | |||
</time> | |||
)} | |||
</TimeFormatter> | |||
{analysis.buildString && ( | |||
<div className="flex-shrink small text-muted text-ellipsis"> | |||
{translateWithParameters( | |||
'project_activity.analysis_build_string_X', | |||
analysis.buildString | |||
)} | |||
</div> | |||
{(canAddVersion || canAddEvent || canDeleteAnalyses) && ( | |||
<div className="project-activity-analysis-actions big-spacer-right"> | |||
<ActionsDropdown small={true} toggleClassName="js-analysis-actions"> | |||
)} | |||
{(canAddVersion || canAddEvent || canDeleteAnalyses) && ( | |||
<ClickEventBoundary> | |||
<div className="project-activity-analysis-actions big-spacer-left"> | |||
<ActionsDropdown | |||
overlayPlacement={PopupPlacement.BottomRight} | |||
small={true} | |||
toggleClassName="js-analysis-actions"> | |||
{canAddVersion && ( | |||
<ActionsDropdownItem | |||
className="js-add-event" | |||
onClick={this.handleAddVersionClick}> | |||
className="js-add-version" | |||
onClick={() => setAddVersionForm(true)}> | |||
{translate('project_activity.add_version')} | |||
</ActionsDropdownItem> | |||
)} | |||
{canAddEvent && ( | |||
<ActionsDropdownItem className="js-add-event" onClick={this.handleAddEventClick}> | |||
<ActionsDropdownItem | |||
className="js-add-event" | |||
onClick={() => setAddEventForm(true)}> | |||
{translate('project_activity.add_custom_event')} | |||
</ActionsDropdownItem> | |||
)} | |||
@@ -187,54 +141,69 @@ export default class ProjectActivityAnalysis extends React.PureComponent<Props, | |||
<ActionsDropdownItem | |||
className="js-delete-analysis" | |||
destructive={true} | |||
onClick={this.handleRemoveAnalysisClick}> | |||
onClick={() => setRemoveAnalysisForm(true)}> | |||
{translate('project_activity.delete_analysis')} | |||
</ActionsDropdownItem> | |||
)} | |||
</ActionsDropdown> | |||
{this.state.addVersionForm && ( | |||
{addVersionForm && ( | |||
<AddEventForm | |||
addEvent={this.props.addVersion} | |||
addEvent={props.addVersion} | |||
addEventButtonText="project_activity.add_version" | |||
analysis={analysis} | |||
onClose={this.closeAddVersionForm} | |||
onClose={() => setAddVersionForm(false)} | |||
/> | |||
)} | |||
{this.state.addEventForm && ( | |||
{addEventForm && ( | |||
<AddEventForm | |||
addEvent={this.props.addCustomEvent} | |||
addEvent={props.addCustomEvent} | |||
addEventButtonText="project_activity.add_custom_event" | |||
analysis={analysis} | |||
onClose={this.closeAddEventForm} | |||
onClose={() => setAddEventForm(false)} | |||
/> | |||
)} | |||
{this.state.removeAnalysisForm && ( | |||
{removeAnalysisForm && ( | |||
<RemoveAnalysisForm | |||
analysis={analysis} | |||
deleteAnalysis={this.props.deleteAnalysis} | |||
onClose={this.closeRemoveAnalysisForm} | |||
deleteAnalysis={props.deleteAnalysis} | |||
onClose={() => setRemoveAnalysisForm(false)} | |||
/> | |||
)} | |||
</div> | |||
)} | |||
{events.length > 0 && ( | |||
<Events | |||
analysis={analysis.key} | |||
canAdmin={canAdmin} | |||
changeEvent={this.props.changeEvent} | |||
deleteEvent={this.props.deleteEvent} | |||
events={events} | |||
isFirst={this.props.isFirst} | |||
/> | |||
)} | |||
</ClickEventBoundary> | |||
)} | |||
</div> | |||
{isBaseline && this.renderBaselineMarker()} | |||
</li> | |||
</Tooltip> | |||
); | |||
} | |||
{analysis.events.length > 0 && ( | |||
<Events | |||
analysisKey={analysis.key} | |||
canAdmin={canAdmin} | |||
events={analysis.events} | |||
isFirst={isFirst} | |||
onChange={props.changeEvent} | |||
onDelete={props.deleteEvent} | |||
/> | |||
)} | |||
{isBaseline && ( | |||
<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> | |||
)} | |||
</li> | |||
); | |||
} | |||
export default React.memo(ProjectActivityAnalysis); |
@@ -78,7 +78,6 @@ export default function ProjectActivityApp(props: Props) { | |||
canAdmin={canAdmin} | |||
canDeleteAnalyses={canDeleteAnalyses} | |||
changeEvent={props.changeEvent} | |||
className="boxed-group-inner" | |||
deleteAnalysis={props.deleteAnalysis} | |||
deleteEvent={props.deleteEvent} | |||
initializing={props.initializing} |
@@ -17,13 +17,11 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { Link } from 'react-router'; | |||
import { ResetButtonLink } from 'sonar-ui-common/components/controls/buttons'; | |||
import DropdownIcon from 'sonar-ui-common/components/icons/DropdownIcon'; | |||
import ProjectEventIcon from 'sonar-ui-common/components/icons/ProjectEventIcon'; | |||
import Level from 'sonar-ui-common/components/ui/Level'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
@@ -57,31 +55,19 @@ export class RichQualityGateEventInner extends React.PureComponent<Props, State> | |||
const { event } = this.props; | |||
const { expanded } = this.state; | |||
return ( | |||
<div className="project-activity-event-inner"> | |||
<div className="project-activity-event-inner-main"> | |||
<ProjectEventIcon | |||
className={classNames( | |||
'project-activity-event-icon', | |||
'little-spacer-right', | |||
event.category | |||
)} | |||
<> | |||
<span className="note spacer-right">{translate('event.category', event.category)}:</span> | |||
{event.qualityGate.stillFailing ? ( | |||
<FormattedMessage | |||
defaultMessage={translate('event.quality_gate.still_x')} | |||
id="event.quality_gate.still_x" | |||
values={{ status: <Level level={event.qualityGate.status} small={true} /> }} | |||
/> | |||
) : ( | |||
<Level level={event.qualityGate.status} small={true} /> | |||
)} | |||
<div className="project-activity-event-inner-text flex-1"> | |||
<span className="note little-spacer-right"> | |||
{translate('event.category', event.category)}: | |||
</span> | |||
{event.qualityGate.stillFailing ? ( | |||
<FormattedMessage | |||
defaultMessage={translate('event.quality_gate.still_x')} | |||
id="event.quality_gate.still_x" | |||
values={{ status: <Level level={event.qualityGate.status} small={true} /> }} | |||
/> | |||
) : ( | |||
<Level level={event.qualityGate.status} small={true} /> | |||
)} | |||
</div> | |||
<div> | |||
{event.qualityGate.failing.length > 0 && ( | |||
<ResetButtonLink | |||
className="project-activity-event-inner-more-link" | |||
@@ -94,14 +80,10 @@ export class RichQualityGateEventInner extends React.PureComponent<Props, State> | |||
</div> | |||
{expanded && ( | |||
<ul className="project-activity-event-inner-more-content"> | |||
<ul className="spacer-left spacer-top"> | |||
{event.qualityGate.failing.map(project => ( | |||
<li className="display-flex-center little-spacer-top" key={project.key}> | |||
<Level | |||
className="little-spacer-right" | |||
level={event.qualityGate.status} | |||
small={true} | |||
/> | |||
<li className="display-flex-center spacer-top" key={project.key}> | |||
<Level className="spacer-right" level={event.qualityGate.status} small={true} /> | |||
<div className="flex-1 text-ellipsis"> | |||
<Link | |||
onClick={this.stopPropagation} | |||
@@ -114,7 +96,7 @@ export class RichQualityGateEventInner extends React.PureComponent<Props, State> | |||
))} | |||
</ul> | |||
)} | |||
</div> | |||
</> | |||
); | |||
} | |||
} |
@@ -0,0 +1,106 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 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 { DeleteButton, EditButton } from 'sonar-ui-common/components/controls/buttons'; | |||
import { click } from 'sonar-ui-common/helpers/testUtils'; | |||
import { mockAnalysisEvent } from '../../../../helpers/testMocks'; | |||
import { Event, EventProps } from '../Event'; | |||
import ChangeEventForm from '../forms/ChangeEventForm'; | |||
import RemoveEventForm from '../forms/RemoveEventForm'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ canAdmin: true })).toMatchSnapshot('with admin options'); | |||
}); | |||
it('should correctly allow deletion', () => { | |||
expect( | |||
shallowRender({ | |||
canAdmin: true, | |||
event: mockAnalysisEvent({ category: 'VERSION' }), | |||
isFirst: true | |||
}) | |||
.find(DeleteButton) | |||
.exists() | |||
).toBe(false); | |||
expect( | |||
shallowRender({ canAdmin: true, event: mockAnalysisEvent() }) | |||
.find(DeleteButton) | |||
.exists() | |||
).toBe(false); | |||
expect( | |||
shallowRender({ canAdmin: true }) | |||
.find(DeleteButton) | |||
.exists() | |||
).toBe(true); | |||
}); | |||
it('should correctly allow edition', () => { | |||
expect( | |||
shallowRender({ canAdmin: true }) | |||
.find(EditButton) | |||
.exists() | |||
).toBe(true); | |||
expect( | |||
shallowRender({ canAdmin: true, isFirst: true }) | |||
.find(EditButton) | |||
.exists() | |||
).toBe(true); | |||
expect( | |||
shallowRender({ canAdmin: true, event: mockAnalysisEvent() }) | |||
.find(EditButton) | |||
.exists() | |||
).toBe(false); | |||
}); | |||
it('should correctly show edit form', () => { | |||
const wrapper = shallowRender({ canAdmin: true }); | |||
click(wrapper.find(EditButton)); | |||
const changeEventForm = wrapper.find(ChangeEventForm); | |||
expect(changeEventForm.exists()).toBe(true); | |||
changeEventForm.prop('onClose')(); | |||
expect(wrapper.find(ChangeEventForm).exists()).toBe(false); | |||
}); | |||
it('should correctly show delete form', () => { | |||
const wrapper = shallowRender({ canAdmin: true }); | |||
click(wrapper.find(DeleteButton)); | |||
const removeEventForm = wrapper.find(RemoveEventForm); | |||
expect(removeEventForm.exists()).toBe(true); | |||
removeEventForm.prop('onClose')(); | |||
expect(wrapper.find(RemoveEventForm).exists()).toBe(false); | |||
}); | |||
function shallowRender(props: Partial<EventProps> = {}) { | |||
return shallow<Event>( | |||
<Event | |||
analysisKey="foo" | |||
event={mockAnalysisEvent({ category: 'OTHER' })} | |||
onChange={jest.fn()} | |||
onDelete={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,75 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 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 } from '../../../../helpers/testMocks'; | |||
import { BranchLike } from '../../../../types/branch-like'; | |||
import EventInner, { EventInnerProps } from '../EventInner'; | |||
jest.mock('../../../../app/components/ComponentContext', () => { | |||
const { mockBranch } = jest.requireActual('../../../../helpers/mocks/branch-like'); | |||
return { | |||
ComponentContext: { | |||
Consumer: ({ | |||
children | |||
}: { | |||
children: (props: { branchLike: BranchLike }) => React.ReactNode; | |||
}) => { | |||
return children({ branchLike: mockBranch() }); | |||
} | |||
} | |||
}; | |||
}); | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect( | |||
shallowRender({ | |||
event: mockAnalysisEvent({ | |||
category: 'VERSION', | |||
description: undefined, | |||
qualityGate: undefined | |||
}) | |||
}) | |||
).toMatchSnapshot('no description'); | |||
expect(shallowRender({ event: mockAnalysisEvent() })).toMatchSnapshot('rich quality gate'); | |||
expect( | |||
shallowRender({ | |||
event: mockAnalysisEvent({ | |||
category: 'DEFINITION_CHANGE', | |||
definitionChange: { | |||
projects: [{ changeType: 'ADDED', key: 'foo', name: 'Foo' }] | |||
}, | |||
qualityGate: undefined | |||
}) | |||
}) | |||
.find('Consumer') | |||
.dive() | |||
).toMatchSnapshot('definition change'); | |||
}); | |||
function shallowRender(props: Partial<EventInnerProps> = {}) { | |||
return shallow( | |||
<EventInner | |||
event={mockAnalysisEvent({ category: 'VERSION', qualityGate: undefined })} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,43 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 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 } from '../../../../helpers/testMocks'; | |||
import { Events, EventsProps } from '../Events'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
function shallowRender(props: Partial<EventsProps> = {}) { | |||
return shallow( | |||
<Events | |||
analysisKey="foo" | |||
events={[ | |||
mockAnalysisEvent(), | |||
mockAnalysisEvent({ category: 'VERSION' }), | |||
mockAnalysisEvent({ category: 'OTHER' }) | |||
]} | |||
onChange={jest.fn()} | |||
onDelete={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -20,6 +20,8 @@ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { parseDate } from 'sonar-ui-common/helpers/dates'; | |||
import { mockParsedAnalysis } from '../../../../helpers/testMocks'; | |||
import { ComponentQualifier } from '../../../../types/component'; | |||
import { DEFAULT_GRAPH } from '../../utils'; | |||
import ProjectActivityAnalysesList from '../ProjectActivityAnalysesList'; | |||
@@ -36,67 +38,97 @@ jest.mock('sonar-ui-common/helpers/dates', () => { | |||
const DATE = parseDate('2016-10-27T16:33:50+0000'); | |||
const ANALYSES = [ | |||
{ | |||
key: 'A1', | |||
date: DATE, | |||
events: [{ key: 'E1', category: 'VERSION', name: '6.5-SNAPSHOT' }] | |||
}, | |||
{ key: 'A2', date: parseDate('2016-10-27T12:21:15+0000'), events: [] }, | |||
{ | |||
key: 'A3', | |||
date: parseDate('2016-10-26T12:17:29+0000'), | |||
events: [ | |||
{ key: 'E2', category: 'VERSION', name: '6.4' }, | |||
{ key: 'E3', category: 'OTHER', name: 'foo' } | |||
] | |||
}, | |||
{ | |||
key: 'A4', | |||
date: parseDate('2016-10-24T16:33:50+0000'), | |||
events: [{ key: 'E1', category: 'QUALITY_GATE', name: 'Quality gate changed to red...' }] | |||
} | |||
]; | |||
const DEFAULT_PROPS: ProjectActivityAnalysesList['props'] = { | |||
addCustomEvent: jest.fn().mockResolvedValue(undefined), | |||
addVersion: jest.fn().mockResolvedValue(undefined), | |||
analyses: ANALYSES, | |||
analysesLoading: false, | |||
canAdmin: false, | |||
changeEvent: jest.fn().mockResolvedValue(undefined), | |||
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: '', | |||
customMetrics: [], | |||
graph: DEFAULT_GRAPH, | |||
project: 'org.sonarsource.sonarqube:sonarqube' | |||
}, | |||
updateQuery: () => {} | |||
const DEFAULT_QUERY = { | |||
category: '', | |||
customMetrics: [], | |||
graph: DEFAULT_GRAPH, | |||
project: 'org.sonarsource.sonarqube:sonarqube' | |||
}; | |||
it('should render correctly', () => { | |||
expect(shallow(<ProjectActivityAnalysesList {...DEFAULT_PROPS} />)).toMatchSnapshot(); | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ project: { qualifier: ComponentQualifier.Application } })).toMatchSnapshot( | |||
'application' | |||
); | |||
expect(shallowRender({ analyses: [], initializing: true })).toMatchSnapshot('loading'); | |||
expect(shallowRender({ analyses: [] })).toMatchSnapshot('no analyses'); | |||
}); | |||
it('should correctly filter analyses by category', () => { | |||
const wrapper = shallow(<ProjectActivityAnalysesList {...DEFAULT_PROPS} />); | |||
wrapper.setProps({ query: { ...DEFAULT_PROPS.query, category: 'QUALITY_GATE' } }); | |||
const wrapper = shallowRender(); | |||
wrapper.setProps({ query: { ...DEFAULT_QUERY, category: 'QUALITY_GATE' } }); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should correctly filter analyses by date range', () => { | |||
const wrapper = shallow(<ProjectActivityAnalysesList {...DEFAULT_PROPS} />); | |||
const wrapper = shallowRender(); | |||
wrapper.setProps({ | |||
query: { | |||
...DEFAULT_PROPS.query, | |||
...DEFAULT_QUERY, | |||
from: DATE, | |||
to: DATE | |||
} | |||
}); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should correctly update the selected date', () => { | |||
const selectedDate = new Date(); | |||
const updateQuery = jest.fn(); | |||
const wrapper = shallowRender({ updateQuery }); | |||
wrapper.instance().updateSelectedDate(selectedDate); | |||
expect(updateQuery).toBeCalledWith({ selectedDate }); | |||
}); | |||
it('should correctly reset scroll if filters change', () => { | |||
const wrapper = shallowRender(); | |||
const scrollContainer = document.createElement('ul'); | |||
scrollContainer.scrollTop = 100; | |||
// Saves us a call to mount(). | |||
wrapper.instance().scrollContainer = scrollContainer; | |||
wrapper.setProps({ query: { ...DEFAULT_QUERY, category: 'OTHER' } }); | |||
expect(scrollContainer.scrollTop).toBe(0); | |||
}); | |||
function shallowRender(props: Partial<ProjectActivityAnalysesList['props']> = {}) { | |||
return shallow<ProjectActivityAnalysesList>( | |||
<ProjectActivityAnalysesList | |||
addCustomEvent={jest.fn().mockResolvedValue(undefined)} | |||
addVersion={jest.fn().mockResolvedValue(undefined)} | |||
analyses={[ | |||
mockParsedAnalysis({ | |||
key: 'A1', | |||
date: DATE, | |||
events: [{ key: 'E1', category: 'VERSION', name: '6.5-SNAPSHOT' }] | |||
}), | |||
mockParsedAnalysis({ key: 'A2', date: parseDate('2016-10-27T12:21:15+0000') }), | |||
mockParsedAnalysis({ | |||
key: 'A3', | |||
date: parseDate('2016-10-26T12:17:29+0000'), | |||
events: [ | |||
{ key: 'E2', category: 'VERSION', name: '6.4' }, | |||
{ key: 'E3', category: 'OTHER', name: 'foo' } | |||
] | |||
}), | |||
mockParsedAnalysis({ | |||
key: 'A4', | |||
date: parseDate('2016-10-24T16:33:50+0000'), | |||
events: [{ key: 'E1', category: 'QUALITY_GATE', name: 'Quality gate changed to red...' }] | |||
}) | |||
]} | |||
analysesLoading={false} | |||
canAdmin={false} | |||
changeEvent={jest.fn().mockResolvedValue(undefined)} | |||
deleteAnalysis={jest.fn().mockResolvedValue(undefined)} | |||
deleteEvent={jest.fn().mockResolvedValue(undefined)} | |||
initializing={false} | |||
leakPeriodDate={parseDate('2016-10-27T12:21:15+0000')} | |||
project={{ qualifier: ComponentQualifier.Project }} | |||
query={DEFAULT_QUERY} | |||
updateQuery={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -17,11 +17,17 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
/* eslint-disable sonarjs/no-duplicate-string */ | |||
import { mount, shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||
import { IntlProvider } from 'react-intl'; | |||
import { scrollToElement } from 'sonar-ui-common/helpers/scrolling'; | |||
import { click } from 'sonar-ui-common/helpers/testUtils'; | |||
import TimeFormatter from '../../../../components/intl/TimeFormatter'; | |||
import { mockAnalysisEvent, mockParsedAnalysis } from '../../../../helpers/testMocks'; | |||
import ProjectActivityAnalysis from '../ProjectActivityAnalysis'; | |||
import AddEventForm from '../forms/AddEventForm'; | |||
import RemoveAnalysisForm from '../forms/RemoveAnalysisForm'; | |||
import { ProjectActivityAnalysis, ProjectActivityAnalysisProps } from '../ProjectActivityAnalysis'; | |||
jest.mock('sonar-ui-common/helpers/dates', () => ({ | |||
parseDate: () => ({ | |||
@@ -30,15 +36,35 @@ jest.mock('sonar-ui-common/helpers/dates', () => ({ | |||
}) | |||
})); | |||
jest.mock('sonar-ui-common/helpers/scrolling', () => ({ | |||
scrollToElement: jest.fn() | |||
})); | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect( | |||
shallowRender({ analysis: mockParsedAnalysis({ events: [mockAnalysisEvent()] }) }) | |||
).toMatchSnapshot(); | |||
).toMatchSnapshot('with events'); | |||
expect( | |||
shallowRender({ analysis: mockParsedAnalysis({ buildString: '1.0.234' }) }) | |||
).toMatchSnapshot(); | |||
expect(shallowRender({ isBaseline: true })).toMatchSnapshot(); | |||
).toMatchSnapshot('with build string'); | |||
expect(shallowRender({ isBaseline: true })).toMatchSnapshot('with baseline marker'); | |||
expect( | |||
shallowRender({ | |||
canAdmin: true, | |||
canCreateVersion: true, | |||
canDeleteAnalyses: true | |||
}) | |||
).toMatchSnapshot('with admin options'); | |||
const timeFormatter = shallowRender() | |||
.find(TimeFormatter) | |||
.prop('children'); | |||
if (!timeFormatter) { | |||
fail('TimeFormatter instance not found'); | |||
} else { | |||
expect(timeFormatter('formatted_time')).toMatchSnapshot('formatted time'); | |||
} | |||
}); | |||
it('should show the correct admin options', () => { | |||
@@ -47,24 +73,27 @@ it('should show the correct admin options', () => { | |||
canCreateVersion: true, | |||
canDeleteAnalyses: true | |||
}); | |||
const instance = wrapper.instance(); | |||
expect(wrapper).toMatchSnapshot(); | |||
instance.setState({ addEventForm: true }); | |||
waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot(); | |||
instance.setState({ addEventForm: false }); | |||
expect(wrapper.find('.js-add-version').exists()).toBe(true); | |||
click(wrapper.find('.js-add-version')); | |||
const addVersionForm = wrapper.find(AddEventForm); | |||
expect(addVersionForm.exists()).toBe(true); | |||
addVersionForm.prop('onClose')(); | |||
expect(wrapper.find(AddEventForm).exists()).toBe(false); | |||
instance.setState({ removeAnalysisForm: true }); | |||
waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot(); | |||
instance.setState({ removeAnalysisForm: false }); | |||
expect(wrapper.find('.js-add-event').exists()).toBe(true); | |||
click(wrapper.find('.js-add-event')); | |||
const addEventForm = wrapper.find(AddEventForm); | |||
expect(addEventForm.exists()).toBe(true); | |||
addEventForm.prop('onClose')(); | |||
expect(wrapper.find(AddEventForm).exists()).toBe(false); | |||
instance.setState({ addVersionForm: true }); | |||
waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot(); | |||
instance.setState({ addVersionForm: false }); | |||
expect(wrapper.find('.js-delete-analysis').exists()).toBe(true); | |||
click(wrapper.find('.js-delete-analysis')); | |||
const removeAnalysisForm = wrapper.find(RemoveAnalysisForm); | |||
expect(removeAnalysisForm.exists()).toBe(true); | |||
removeAnalysisForm.prop('onClose')(); | |||
expect(wrapper.find(RemoveAnalysisForm).exists()).toBe(false); | |||
}); | |||
it('should not allow the first item to be deleted', () => { | |||
@@ -75,11 +104,34 @@ it('should not allow the first item to be deleted', () => { | |||
canDeleteAnalyses: true, | |||
isFirst: true | |||
}) | |||
).toMatchSnapshot(); | |||
.find('.js-delete-analysis') | |||
.exists() | |||
).toBe(false); | |||
}); | |||
function shallowRender(props: Partial<ProjectActivityAnalysis['props']> = {}) { | |||
return shallow( | |||
it('should be clickable', () => { | |||
const date = new Date('2018-03-01T09:37:01+0100'); | |||
const updateSelectedDate = jest.fn(); | |||
const wrapper = shallowRender({ analysis: mockParsedAnalysis({ date }), updateSelectedDate }); | |||
click(wrapper); | |||
expect(updateSelectedDate).toBeCalledWith(date); | |||
}); | |||
it('should trigger a scroll to itself if selected', () => { | |||
mountRender({ parentScrollContainer: document.createElement('ul'), selected: true }); | |||
expect(scrollToElement).toBeCalled(); | |||
}); | |||
function shallowRender(props: Partial<ProjectActivityAnalysisProps> = {}) { | |||
return shallow(createComponent(props)); | |||
} | |||
function mountRender(props: Partial<ProjectActivityAnalysisProps> = {}) { | |||
return mount(<IntlProvider locale="en">{createComponent(props)}</IntlProvider>); | |||
} | |||
function createComponent(props: Partial<ProjectActivityAnalysisProps> = {}) { | |||
return ( | |||
<ProjectActivityAnalysis | |||
addCustomEvent={jest.fn()} | |||
addVersion={jest.fn()} |
@@ -1,24 +1,14 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render 1`] = ` | |||
<div | |||
className="project-activity-event-inner" | |||
> | |||
<div | |||
className="project-activity-event-inner-main" | |||
<Fragment> | |||
<span | |||
className="note" | |||
> | |||
<ProjectEventIcon | |||
className="project-activity-event-icon little-spacer-right DEFINITION_CHANGE" | |||
/> | |||
<div | |||
className="project-activity-event-inner-text flex-1" | |||
> | |||
<span | |||
className="note little-spacer-right" | |||
> | |||
event.category.DEFINITION_CHANGE | |||
</span> | |||
</div> | |||
event.category.DEFINITION_CHANGE | |||
: | |||
</span> | |||
<div> | |||
<ResetButtonLink | |||
className="project-activity-event-inner-more-link" | |||
onClick={[Function]} | |||
@@ -31,28 +21,18 @@ exports[`should render 1`] = ` | |||
/> | |||
</ResetButtonLink> | |||
</div> | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should render 2`] = ` | |||
<div | |||
className="project-activity-event-inner" | |||
> | |||
<div | |||
className="project-activity-event-inner-main" | |||
<Fragment> | |||
<span | |||
className="note" | |||
> | |||
<ProjectEventIcon | |||
className="project-activity-event-icon little-spacer-right DEFINITION_CHANGE" | |||
/> | |||
<div | |||
className="project-activity-event-inner-text flex-1" | |||
> | |||
<span | |||
className="note little-spacer-right" | |||
> | |||
event.category.DEFINITION_CHANGE | |||
</span> | |||
</div> | |||
event.category.DEFINITION_CHANGE | |||
: | |||
</span> | |||
<div> | |||
<ResetButtonLink | |||
className="project-activity-event-inner-more-link" | |||
onClick={[Function]} | |||
@@ -66,10 +46,10 @@ exports[`should render 2`] = ` | |||
</ResetButtonLink> | |||
</div> | |||
<ul | |||
className="project-activity-event-inner-more-content" | |||
className="spacer-left spacer-top" | |||
> | |||
<li | |||
className="display-flex-center little-spacer-top" | |||
className="display-flex-center spacer-top" | |||
key="foo" | |||
> | |||
<div | |||
@@ -112,7 +92,7 @@ exports[`should render 2`] = ` | |||
</div> | |||
</li> | |||
<li | |||
className="display-flex-center little-spacer-top" | |||
className="display-flex-center spacer-top" | |||
key="bar" | |||
> | |||
<div | |||
@@ -155,28 +135,18 @@ exports[`should render 2`] = ` | |||
</div> | |||
</li> | |||
</ul> | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should render for a branch 1`] = ` | |||
<div | |||
className="project-activity-event-inner" | |||
> | |||
<div | |||
className="project-activity-event-inner-main" | |||
<Fragment> | |||
<span | |||
className="note" | |||
> | |||
<ProjectEventIcon | |||
className="project-activity-event-icon little-spacer-right DEFINITION_CHANGE" | |||
/> | |||
<div | |||
className="project-activity-event-inner-text flex-1" | |||
> | |||
<span | |||
className="note little-spacer-right" | |||
> | |||
event.category.DEFINITION_CHANGE | |||
</span> | |||
</div> | |||
event.category.DEFINITION_CHANGE | |||
: | |||
</span> | |||
<div> | |||
<ResetButtonLink | |||
className="project-activity-event-inner-more-link" | |||
onClick={[Function]} | |||
@@ -190,10 +160,10 @@ exports[`should render for a branch 1`] = ` | |||
</ResetButtonLink> | |||
</div> | |||
<ul | |||
className="project-activity-event-inner-more-content" | |||
className="spacer-left spacer-top" | |||
> | |||
<li | |||
className="display-flex-center little-spacer-top" | |||
className="display-flex-center spacer-top" | |||
key="foo" | |||
> | |||
<div | |||
@@ -236,7 +206,7 @@ exports[`should render for a branch 1`] = ` | |||
</div> | |||
</li> | |||
<li | |||
className="display-flex-center little-spacer-top" | |||
className="display-flex-center spacer-top" | |||
key="bar" | |||
> | |||
<FormattedMessage | |||
@@ -284,5 +254,5 @@ exports[`should render for a branch 1`] = ` | |||
/> | |||
</li> | |||
</ul> | |||
</div> | |||
</Fragment> | |||
`; |
@@ -0,0 +1,91 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<div | |||
className="project-activity-event" | |||
> | |||
<ProjectEventIcon | |||
className="project-activity-event-icon little-spacer-right text-middle OTHER" | |||
/> | |||
<EventInner | |||
event={ | |||
Object { | |||
"category": "OTHER", | |||
"description": "Lorem ipsum dolor sit amet", | |||
"key": "E11", | |||
"name": "Lorem ipsum", | |||
"qualityGate": Object { | |||
"failing": Array [ | |||
Object { | |||
"branch": "master", | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"branch": "feature/bar", | |||
"key": "bar", | |||
"name": "Bar", | |||
}, | |||
], | |||
"status": "ERROR", | |||
"stillFailing": true, | |||
}, | |||
} | |||
} | |||
/> | |||
</div> | |||
`; | |||
exports[`should render correctly: with admin options 1`] = ` | |||
<div | |||
className="project-activity-event" | |||
> | |||
<ProjectEventIcon | |||
className="project-activity-event-icon little-spacer-right text-middle OTHER" | |||
/> | |||
<EventInner | |||
event={ | |||
Object { | |||
"category": "OTHER", | |||
"description": "Lorem ipsum dolor sit amet", | |||
"key": "E11", | |||
"name": "Lorem ipsum", | |||
"qualityGate": Object { | |||
"failing": Array [ | |||
Object { | |||
"branch": "master", | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"branch": "feature/bar", | |||
"key": "bar", | |||
"name": "Bar", | |||
}, | |||
], | |||
"status": "ERROR", | |||
"stillFailing": true, | |||
}, | |||
} | |||
} | |||
/> | |||
<span | |||
className="nowrap" | |||
> | |||
<EditButton | |||
aria-label="project_activity.events.tooltip.edit" | |||
className="button-small" | |||
data-test="project-activity__edit-event" | |||
onClick={[Function]} | |||
stopPropagation={true} | |||
/> | |||
<DeleteButton | |||
aria-label="project_activity.events.tooltip.delete" | |||
className="button-small" | |||
data-test="project-activity__delete-event" | |||
onClick={[Function]} | |||
stopPropagation={true} | |||
/> | |||
</span> | |||
</div> | |||
`; |
@@ -0,0 +1,101 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<Tooltip | |||
overlay="Lorem ipsum dolor sit amet" | |||
> | |||
<span | |||
className="text-middle" | |||
> | |||
<span | |||
className="note little-spacer-right" | |||
> | |||
event.category.VERSION | |||
: | |||
</span> | |||
<strong | |||
className="spacer-right" | |||
> | |||
Lorem ipsum | |||
</strong> | |||
</span> | |||
</Tooltip> | |||
`; | |||
exports[`should render correctly: definition change 1`] = ` | |||
<DefinitionChangeEventInner | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": false, | |||
"name": "branch-6.7", | |||
} | |||
} | |||
event={ | |||
Object { | |||
"category": "DEFINITION_CHANGE", | |||
"definitionChange": Object { | |||
"projects": Array [ | |||
Object { | |||
"changeType": "ADDED", | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
], | |||
}, | |||
"description": "Lorem ipsum dolor sit amet", | |||
"key": "E11", | |||
"name": "Lorem ipsum", | |||
"qualityGate": undefined, | |||
} | |||
} | |||
/> | |||
`; | |||
exports[`should render correctly: no description 1`] = ` | |||
<span | |||
className="text-middle" | |||
> | |||
<span | |||
className="note little-spacer-right" | |||
> | |||
event.category.VERSION | |||
: | |||
</span> | |||
<strong | |||
className="spacer-right" | |||
> | |||
Lorem ipsum | |||
</strong> | |||
</span> | |||
`; | |||
exports[`should render correctly: rich quality gate 1`] = ` | |||
<RichQualityGateEventInner | |||
event={ | |||
Object { | |||
"category": "QUALITY_GATE", | |||
"description": "Lorem ipsum dolor sit amet", | |||
"key": "E11", | |||
"name": "Lorem ipsum", | |||
"qualityGate": Object { | |||
"failing": Array [ | |||
Object { | |||
"branch": "master", | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"branch": "feature/bar", | |||
"key": "bar", | |||
"name": "Bar", | |||
}, | |||
], | |||
"status": "ERROR", | |||
"stillFailing": true, | |||
}, | |||
} | |||
} | |||
/> | |||
`; |
@@ -0,0 +1,98 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<div | |||
className="big-spacer-top" | |||
> | |||
<Memo(Event) | |||
analysisKey="foo" | |||
event={ | |||
Object { | |||
"category": "OTHER", | |||
"description": "Lorem ipsum dolor sit amet", | |||
"key": "E11", | |||
"name": "Lorem ipsum", | |||
"qualityGate": Object { | |||
"failing": Array [ | |||
Object { | |||
"branch": "master", | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"branch": "feature/bar", | |||
"key": "bar", | |||
"name": "Bar", | |||
}, | |||
], | |||
"status": "ERROR", | |||
"stillFailing": true, | |||
}, | |||
} | |||
} | |||
key="E11" | |||
onChange={[MockFunction]} | |||
onDelete={[MockFunction]} | |||
/> | |||
<Memo(Event) | |||
analysisKey="foo" | |||
event={ | |||
Object { | |||
"category": "QUALITY_GATE", | |||
"description": "Lorem ipsum dolor sit amet", | |||
"key": "E11", | |||
"name": "Lorem ipsum", | |||
"qualityGate": Object { | |||
"failing": Array [ | |||
Object { | |||
"branch": "master", | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"branch": "feature/bar", | |||
"key": "bar", | |||
"name": "Bar", | |||
}, | |||
], | |||
"status": "ERROR", | |||
"stillFailing": true, | |||
}, | |||
} | |||
} | |||
key="E11" | |||
onChange={[MockFunction]} | |||
onDelete={[MockFunction]} | |||
/> | |||
<Memo(Event) | |||
analysisKey="foo" | |||
event={ | |||
Object { | |||
"category": "VERSION", | |||
"description": "Lorem ipsum dolor sit amet", | |||
"key": "E11", | |||
"name": "Lorem ipsum", | |||
"qualityGate": Object { | |||
"failing": Array [ | |||
Object { | |||
"branch": "master", | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"branch": "feature/bar", | |||
"key": "bar", | |||
"name": "Bar", | |||
}, | |||
], | |||
"status": "ERROR", | |||
"stillFailing": true, | |||
}, | |||
} | |||
} | |||
key="E11" | |||
onChange={[MockFunction]} | |||
onDelete={[MockFunction]} | |||
/> | |||
</div> | |||
`; |
@@ -46,7 +46,7 @@ exports[`should correctly filter analyses by category 1`] = ` | |||
<ul | |||
className="project-activity-analyses-list" | |||
> | |||
<ProjectActivityAnalysis | |||
<Memo(ProjectActivityAnalysis) | |||
addCustomEvent={[MockFunction]} | |||
addVersion={[MockFunction]} | |||
analysis={ | |||
@@ -60,6 +60,7 @@ exports[`should correctly filter analyses by category 1`] = ` | |||
}, | |||
], | |||
"key": "A4", | |||
"projectVersion": "1.0", | |||
} | |||
} | |||
canAdmin={false} | |||
@@ -126,7 +127,7 @@ exports[`should correctly filter analyses by date range 1`] = ` | |||
<ul | |||
className="project-activity-analyses-list" | |||
> | |||
<ProjectActivityAnalysis | |||
<Memo(ProjectActivityAnalysis) | |||
addCustomEvent={[MockFunction]} | |||
addVersion={[MockFunction]} | |||
analysis={ | |||
@@ -140,6 +141,7 @@ exports[`should correctly filter analyses by date range 1`] = ` | |||
}, | |||
], | |||
"key": "A1", | |||
"projectVersion": "1.0", | |||
} | |||
} | |||
canAdmin={false} | |||
@@ -160,7 +162,229 @@ exports[`should correctly filter analyses by date range 1`] = ` | |||
</ul> | |||
`; | |||
exports[`should render correctly 1`] = ` | |||
exports[`should render correctly: application 1`] = ` | |||
<ul | |||
className="project-activity-versions-list" | |||
onScroll={[Function]} | |||
style={ | |||
Object { | |||
"paddingTop": undefined, | |||
} | |||
} | |||
> | |||
<li | |||
key="E1" | |||
> | |||
<div | |||
className="project-activity-version-badge first" | |||
> | |||
<Tooltip | |||
mouseEnterDelay={0.5} | |||
overlay="version 6.5-SNAPSHOT" | |||
> | |||
<span | |||
className="analysis-version" | |||
> | |||
6.5-SNAPSHOT | |||
</span> | |||
</Tooltip> | |||
</div> | |||
<ul | |||
className="project-activity-days-list" | |||
> | |||
<li | |||
className="project-activity-day" | |||
data-day="ISO.1477526400000" | |||
key="1477526400000" | |||
> | |||
<div | |||
className="project-activity-date" | |||
> | |||
<DateFormatter | |||
date={1477526400000} | |||
long={true} | |||
/> | |||
</div> | |||
<ul | |||
className="project-activity-analyses-list" | |||
> | |||
<Memo(ProjectActivityAnalysis) | |||
addCustomEvent={[MockFunction]} | |||
addVersion={[MockFunction]} | |||
analysis={ | |||
Object { | |||
"date": 2016-10-27T16:33:50.000Z, | |||
"events": Array [ | |||
Object { | |||
"category": "VERSION", | |||
"key": "E1", | |||
"name": "6.5-SNAPSHOT", | |||
}, | |||
], | |||
"key": "A1", | |||
"projectVersion": "1.0", | |||
} | |||
} | |||
canAdmin={false} | |||
canCreateVersion={false} | |||
changeEvent={[MockFunction]} | |||
deleteAnalysis={[MockFunction]} | |||
deleteEvent={[MockFunction]} | |||
isBaseline={false} | |||
isFirst={true} | |||
key="A1" | |||
selected={false} | |||
updateSelectedDate={[Function]} | |||
/> | |||
<Memo(ProjectActivityAnalysis) | |||
addCustomEvent={[MockFunction]} | |||
addVersion={[MockFunction]} | |||
analysis={ | |||
Object { | |||
"date": 2016-10-27T12:21:15.000Z, | |||
"events": Array [], | |||
"key": "A2", | |||
"projectVersion": "1.0", | |||
} | |||
} | |||
canAdmin={false} | |||
canCreateVersion={false} | |||
changeEvent={[MockFunction]} | |||
deleteAnalysis={[MockFunction]} | |||
deleteEvent={[MockFunction]} | |||
isBaseline={true} | |||
isFirst={false} | |||
key="A2" | |||
selected={false} | |||
updateSelectedDate={[Function]} | |||
/> | |||
</ul> | |||
</li> | |||
</ul> | |||
</li> | |||
<li | |||
key="E2" | |||
> | |||
<div | |||
className="project-activity-version-badge" | |||
> | |||
<Tooltip | |||
mouseEnterDelay={0.5} | |||
overlay="version 6.4" | |||
> | |||
<span | |||
className="analysis-version" | |||
> | |||
6.4 | |||
</span> | |||
</Tooltip> | |||
</div> | |||
<ul | |||
className="project-activity-days-list" | |||
> | |||
<li | |||
className="project-activity-day" | |||
data-day="ISO.1477440000000" | |||
key="1477440000000" | |||
> | |||
<div | |||
className="project-activity-date" | |||
> | |||
<DateFormatter | |||
date={1477440000000} | |||
long={true} | |||
/> | |||
</div> | |||
<ul | |||
className="project-activity-analyses-list" | |||
> | |||
<Memo(ProjectActivityAnalysis) | |||
addCustomEvent={[MockFunction]} | |||
addVersion={[MockFunction]} | |||
analysis={ | |||
Object { | |||
"date": 2016-10-26T12:17:29.000Z, | |||
"events": Array [ | |||
Object { | |||
"category": "VERSION", | |||
"key": "E2", | |||
"name": "6.4", | |||
}, | |||
Object { | |||
"category": "OTHER", | |||
"key": "E3", | |||
"name": "foo", | |||
}, | |||
], | |||
"key": "A3", | |||
"projectVersion": "1.0", | |||
} | |||
} | |||
canAdmin={false} | |||
canCreateVersion={false} | |||
changeEvent={[MockFunction]} | |||
deleteAnalysis={[MockFunction]} | |||
deleteEvent={[MockFunction]} | |||
isBaseline={false} | |||
isFirst={false} | |||
key="A3" | |||
selected={false} | |||
updateSelectedDate={[Function]} | |||
/> | |||
</ul> | |||
</li> | |||
<li | |||
className="project-activity-day" | |||
data-day="ISO.1477267200000" | |||
key="1477267200000" | |||
> | |||
<div | |||
className="project-activity-date" | |||
> | |||
<DateFormatter | |||
date={1477267200000} | |||
long={true} | |||
/> | |||
</div> | |||
<ul | |||
className="project-activity-analyses-list" | |||
> | |||
<Memo(ProjectActivityAnalysis) | |||
addCustomEvent={[MockFunction]} | |||
addVersion={[MockFunction]} | |||
analysis={ | |||
Object { | |||
"date": 2016-10-24T16:33:50.000Z, | |||
"events": Array [ | |||
Object { | |||
"category": "QUALITY_GATE", | |||
"key": "E1", | |||
"name": "Quality gate changed to red...", | |||
}, | |||
], | |||
"key": "A4", | |||
"projectVersion": "1.0", | |||
} | |||
} | |||
canAdmin={false} | |||
canCreateVersion={false} | |||
changeEvent={[MockFunction]} | |||
deleteAnalysis={[MockFunction]} | |||
deleteEvent={[MockFunction]} | |||
isBaseline={false} | |||
isFirst={false} | |||
key="A4" | |||
selected={false} | |||
updateSelectedDate={[Function]} | |||
/> | |||
</ul> | |||
</li> | |||
</ul> | |||
</li> | |||
</ul> | |||
`; | |||
exports[`should render correctly: default 1`] = ` | |||
<ul | |||
className="project-activity-versions-list" | |||
onScroll={[Function]} | |||
@@ -206,7 +430,7 @@ exports[`should render correctly 1`] = ` | |||
<ul | |||
className="project-activity-analyses-list" | |||
> | |||
<ProjectActivityAnalysis | |||
<Memo(ProjectActivityAnalysis) | |||
addCustomEvent={[MockFunction]} | |||
addVersion={[MockFunction]} | |||
analysis={ | |||
@@ -220,6 +444,7 @@ exports[`should render correctly 1`] = ` | |||
}, | |||
], | |||
"key": "A1", | |||
"projectVersion": "1.0", | |||
} | |||
} | |||
canAdmin={false} | |||
@@ -233,7 +458,7 @@ exports[`should render correctly 1`] = ` | |||
selected={false} | |||
updateSelectedDate={[Function]} | |||
/> | |||
<ProjectActivityAnalysis | |||
<Memo(ProjectActivityAnalysis) | |||
addCustomEvent={[MockFunction]} | |||
addVersion={[MockFunction]} | |||
analysis={ | |||
@@ -241,6 +466,7 @@ exports[`should render correctly 1`] = ` | |||
"date": 2016-10-27T12:21:15.000Z, | |||
"events": Array [], | |||
"key": "A2", | |||
"projectVersion": "1.0", | |||
} | |||
} | |||
canAdmin={false} | |||
@@ -294,7 +520,7 @@ exports[`should render correctly 1`] = ` | |||
<ul | |||
className="project-activity-analyses-list" | |||
> | |||
<ProjectActivityAnalysis | |||
<Memo(ProjectActivityAnalysis) | |||
addCustomEvent={[MockFunction]} | |||
addVersion={[MockFunction]} | |||
analysis={ | |||
@@ -313,6 +539,7 @@ exports[`should render correctly 1`] = ` | |||
}, | |||
], | |||
"key": "A3", | |||
"projectVersion": "1.0", | |||
} | |||
} | |||
canAdmin={false} | |||
@@ -344,7 +571,7 @@ exports[`should render correctly 1`] = ` | |||
<ul | |||
className="project-activity-analyses-list" | |||
> | |||
<ProjectActivityAnalysis | |||
<Memo(ProjectActivityAnalysis) | |||
addCustomEvent={[MockFunction]} | |||
addVersion={[MockFunction]} | |||
analysis={ | |||
@@ -358,6 +585,7 @@ exports[`should render correctly 1`] = ` | |||
}, | |||
], | |||
"key": "A4", | |||
"projectVersion": "1.0", | |||
} | |||
} | |||
canAdmin={false} | |||
@@ -377,3 +605,29 @@ exports[`should render correctly 1`] = ` | |||
</li> | |||
</ul> | |||
`; | |||
exports[`should render correctly: loading 1`] = ` | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<div | |||
className="text-center" | |||
> | |||
<i | |||
className="spinner" | |||
/> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly: no analyses 1`] = ` | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<span | |||
className="note" | |||
> | |||
no_results | |||
</span> | |||
</div> | |||
`; |
@@ -1,132 +1,15 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should not allow the first item to be deleted 1`] = ` | |||
<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="project-activity-analysis-actions big-spacer-right" | |||
> | |||
<ActionsDropdown | |||
small={true} | |||
toggleClassName="js-analysis-actions" | |||
> | |||
<ActionsDropdownItem | |||
className="js-add-event" | |||
onClick={[Function]} | |||
> | |||
project_activity.add_version | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="js-add-event" | |||
onClick={[Function]} | |||
> | |||
project_activity.add_custom_event | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
</div> | |||
</li> | |||
</Tooltip> | |||
`; | |||
exports[`should render correctly 1`] = ` | |||
<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> | |||
</li> | |||
</Tooltip> | |||
`; | |||
exports[`should render correctly 2`] = ` | |||
<Tooltip | |||
mouseEnterDelay={0.5} | |||
overlay={ | |||
<TimeFormatter | |||
date={ | |||
Object { | |||
"toISOString": [Function], | |||
"valueOf": [Function], | |||
} | |||
} | |||
long={true} | |||
/> | |||
} | |||
placement="left" | |||
exports[`should render correctly: default 1`] = ` | |||
<li | |||
className="project-activity-analysis bordered-top bordered-bottom" | |||
onClick={[Function]} | |||
> | |||
<li | |||
className="project-activity-analysis" | |||
data-date={1546333200000} | |||
onClick={[Function]} | |||
tabIndex={0} | |||
<div | |||
className="display-flex-center display-flex-space-between" | |||
> | |||
<div | |||
className="project-activity-time spacer-right" | |||
className="project-activity-time" | |||
> | |||
<TimeFormatter | |||
date={ | |||
@@ -140,111 +23,29 @@ exports[`should render correctly 2`] = ` | |||
<Component /> | |||
</TimeFormatter> | |||
</div> | |||
<Events | |||
analysis="foo" | |||
changeEvent={[MockFunction]} | |||
deleteEvent={[MockFunction]} | |||
events={ | |||
Array [ | |||
Object { | |||
"category": "QUALITY_GATE", | |||
"description": "Lorem ipsum dolor sit amet", | |||
"key": "E11", | |||
"name": "Lorem ipsum", | |||
"qualityGate": Object { | |||
"failing": Array [ | |||
Object { | |||
"branch": "master", | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"branch": "feature/bar", | |||
"key": "bar", | |||
"name": "Bar", | |||
}, | |||
], | |||
"status": "ERROR", | |||
"stillFailing": true, | |||
}, | |||
}, | |||
] | |||
} | |||
isFirst={false} | |||
/> | |||
</li> | |||
</Tooltip> | |||
</div> | |||
</li> | |||
`; | |||
exports[`should render correctly 3`] = ` | |||
<Tooltip | |||
mouseEnterDelay={0.5} | |||
overlay={ | |||
<React.Fragment> | |||
<TimeFormatter | |||
date={ | |||
Object { | |||
"toISOString": [Function], | |||
"valueOf": [Function], | |||
} | |||
} | |||
long={true} | |||
/> | |||
<br /> | |||
project_activity.analysis_build_string_X.1.0.234 | |||
</React.Fragment> | |||
} | |||
placement="left" | |||
exports[`should render correctly: formatted time 1`] = ` | |||
<time | |||
className="text-middle" | |||
dateTime="2019-01-01T09:00:00.000Z" | |||
> | |||
<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> | |||
</li> | |||
</Tooltip> | |||
formatted_time | |||
</time> | |||
`; | |||
exports[`should render correctly 4`] = ` | |||
<Tooltip | |||
mouseEnterDelay={0.5} | |||
overlay={ | |||
<TimeFormatter | |||
date={ | |||
Object { | |||
"toISOString": [Function], | |||
"valueOf": [Function], | |||
} | |||
} | |||
long={true} | |||
/> | |||
} | |||
placement="left" | |||
exports[`should render correctly: with admin options 1`] = ` | |||
<li | |||
className="project-activity-analysis bordered-top bordered-bottom" | |||
onClick={[Function]} | |||
> | |||
<li | |||
className="project-activity-analysis" | |||
data-date={1546333200000} | |||
onClick={[Function]} | |||
tabIndex={0} | |||
<div | |||
className="display-flex-center display-flex-space-between" | |||
> | |||
<div | |||
className="project-activity-time spacer-right" | |||
className="project-activity-time" | |||
> | |||
<TimeFormatter | |||
date={ | |||
@@ -258,52 +59,52 @@ exports[`should render correctly 4`] = ` | |||
<Component /> | |||
</TimeFormatter> | |||
</div> | |||
<div | |||
className="baseline-marker" | |||
> | |||
<ClickEventBoundary> | |||
<div | |||
className="wedge" | |||
/> | |||
<hr /> | |||
<div | |||
className="label display-flex-center" | |||
className="project-activity-analysis-actions big-spacer-left" | |||
> | |||
project_activity.new_code_period_start | |||
<HelpTooltip | |||
className="little-spacer-left" | |||
overlay="project_activity.new_code_period_start.help" | |||
placement="top" | |||
/> | |||
<ActionsDropdown | |||
overlayPlacement="bottom-right" | |||
small={true} | |||
toggleClassName="js-analysis-actions" | |||
> | |||
<ActionsDropdownItem | |||
className="js-add-version" | |||
onClick={[Function]} | |||
> | |||
project_activity.add_version | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="js-add-event" | |||
onClick={[Function]} | |||
> | |||
project_activity.add_custom_event | |||
</ActionsDropdownItem> | |||
<ActionsDropdownDivider /> | |||
<ActionsDropdownItem | |||
className="js-delete-analysis" | |||
destructive={true} | |||
onClick={[Function]} | |||
> | |||
project_activity.delete_analysis | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
</div> | |||
</div> | |||
</li> | |||
</Tooltip> | |||
</ClickEventBoundary> | |||
</div> | |||
</li> | |||
`; | |||
exports[`should show the correct admin options 1`] = ` | |||
<Tooltip | |||
mouseEnterDelay={0.5} | |||
overlay={ | |||
<TimeFormatter | |||
date={ | |||
Object { | |||
"toISOString": [Function], | |||
"valueOf": [Function], | |||
} | |||
} | |||
long={true} | |||
/> | |||
} | |||
placement="left" | |||
exports[`should render correctly: with baseline marker 1`] = ` | |||
<li | |||
className="project-activity-analysis bordered-top bordered-bottom" | |||
onClick={[Function]} | |||
> | |||
<li | |||
className="project-activity-analysis" | |||
data-date={1546333200000} | |||
onClick={[Function]} | |||
tabIndex={0} | |||
<div | |||
className="display-flex-center display-flex-space-between" | |||
> | |||
<div | |||
className="project-activity-time spacer-right" | |||
className="project-activity-time" | |||
> | |||
<TimeFormatter | |||
date={ | |||
@@ -317,146 +118,38 @@ exports[`should show the correct admin options 1`] = ` | |||
<Component /> | |||
</TimeFormatter> | |||
</div> | |||
<div | |||
className="project-activity-analysis-actions big-spacer-right" | |||
> | |||
<ActionsDropdown | |||
small={true} | |||
toggleClassName="js-analysis-actions" | |||
> | |||
<ActionsDropdownItem | |||
className="js-add-event" | |||
onClick={[Function]} | |||
> | |||
project_activity.add_version | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="js-add-event" | |||
onClick={[Function]} | |||
> | |||
project_activity.add_custom_event | |||
</ActionsDropdownItem> | |||
<ActionsDropdownDivider /> | |||
<ActionsDropdownItem | |||
className="js-delete-analysis" | |||
destructive={true} | |||
onClick={[Function]} | |||
> | |||
project_activity.delete_analysis | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
</div> | |||
</li> | |||
</Tooltip> | |||
`; | |||
exports[`should show the correct admin options 2`] = ` | |||
<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> | |||
<div | |||
className="baseline-marker" | |||
> | |||
<div | |||
className="project-activity-time spacer-right" | |||
> | |||
<TimeFormatter | |||
date={ | |||
Object { | |||
"toISOString": [Function], | |||
"valueOf": [Function], | |||
} | |||
} | |||
long={false} | |||
> | |||
<Component /> | |||
</TimeFormatter> | |||
</div> | |||
className="wedge" | |||
/> | |||
<hr /> | |||
<div | |||
className="project-activity-analysis-actions big-spacer-right" | |||
className="label display-flex-center" | |||
> | |||
<ActionsDropdown | |||
small={true} | |||
toggleClassName="js-analysis-actions" | |||
> | |||
<ActionsDropdownItem | |||
className="js-add-event" | |||
onClick={[Function]} | |||
> | |||
project_activity.add_version | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="js-add-event" | |||
onClick={[Function]} | |||
> | |||
project_activity.add_custom_event | |||
</ActionsDropdownItem> | |||
<ActionsDropdownDivider /> | |||
<ActionsDropdownItem | |||
className="js-delete-analysis" | |||
destructive={true} | |||
onClick={[Function]} | |||
> | |||
project_activity.delete_analysis | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
<AddEventForm | |||
addEvent={[MockFunction]} | |||
addEventButtonText="project_activity.add_custom_event" | |||
analysis={ | |||
Object { | |||
"date": 2017-03-01T08:37:01.000Z, | |||
"events": Array [], | |||
"key": "foo", | |||
"projectVersion": "1.0", | |||
} | |||
} | |||
onClose={[Function]} | |||
project_activity.new_code_period_start | |||
<HelpTooltip | |||
className="little-spacer-left" | |||
overlay="project_activity.new_code_period_start.help" | |||
placement="top" | |||
/> | |||
</div> | |||
</li> | |||
</Tooltip> | |||
</div> | |||
</li> | |||
`; | |||
exports[`should show the correct admin options 3`] = ` | |||
<Tooltip | |||
mouseEnterDelay={0.5} | |||
overlay={ | |||
<TimeFormatter | |||
date={ | |||
Object { | |||
"toISOString": [Function], | |||
"valueOf": [Function], | |||
} | |||
} | |||
long={true} | |||
/> | |||
} | |||
placement="left" | |||
exports[`should render correctly: with build string 1`] = ` | |||
<li | |||
className="project-activity-analysis bordered-top bordered-bottom" | |||
onClick={[Function]} | |||
> | |||
<li | |||
className="project-activity-analysis" | |||
data-date={1546333200000} | |||
onClick={[Function]} | |||
tabIndex={0} | |||
<div | |||
className="display-flex-center display-flex-space-between" | |||
> | |||
<div | |||
className="project-activity-time spacer-right" | |||
className="project-activity-time" | |||
> | |||
<TimeFormatter | |||
date={ | |||
@@ -471,74 +164,24 @@ exports[`should show the correct admin options 3`] = ` | |||
</TimeFormatter> | |||
</div> | |||
<div | |||
className="project-activity-analysis-actions big-spacer-right" | |||
className="flex-shrink small text-muted text-ellipsis" | |||
> | |||
<ActionsDropdown | |||
small={true} | |||
toggleClassName="js-analysis-actions" | |||
> | |||
<ActionsDropdownItem | |||
className="js-add-event" | |||
onClick={[Function]} | |||
> | |||
project_activity.add_version | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="js-add-event" | |||
onClick={[Function]} | |||
> | |||
project_activity.add_custom_event | |||
</ActionsDropdownItem> | |||
<ActionsDropdownDivider /> | |||
<ActionsDropdownItem | |||
className="js-delete-analysis" | |||
destructive={true} | |||
onClick={[Function]} | |||
> | |||
project_activity.delete_analysis | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
<RemoveAnalysisForm | |||
analysis={ | |||
Object { | |||
"date": 2017-03-01T08:37:01.000Z, | |||
"events": Array [], | |||
"key": "foo", | |||
"projectVersion": "1.0", | |||
} | |||
} | |||
deleteAnalysis={[MockFunction]} | |||
onClose={[Function]} | |||
/> | |||
project_activity.analysis_build_string_X.1.0.234 | |||
</div> | |||
</li> | |||
</Tooltip> | |||
</div> | |||
</li> | |||
`; | |||
exports[`should show the correct admin options 4`] = ` | |||
<Tooltip | |||
mouseEnterDelay={0.5} | |||
overlay={ | |||
<TimeFormatter | |||
date={ | |||
Object { | |||
"toISOString": [Function], | |||
"valueOf": [Function], | |||
} | |||
} | |||
long={true} | |||
/> | |||
} | |||
placement="left" | |||
exports[`should render correctly: with events 1`] = ` | |||
<li | |||
className="project-activity-analysis bordered-top bordered-bottom" | |||
onClick={[Function]} | |||
> | |||
<li | |||
className="project-activity-analysis" | |||
data-date={1546333200000} | |||
onClick={[Function]} | |||
tabIndex={0} | |||
<div | |||
className="display-flex-center display-flex-space-between" | |||
> | |||
<div | |||
className="project-activity-time spacer-right" | |||
className="project-activity-time" | |||
> | |||
<TimeFormatter | |||
date={ | |||
@@ -552,48 +195,38 @@ exports[`should show the correct admin options 4`] = ` | |||
<Component /> | |||
</TimeFormatter> | |||
</div> | |||
<div | |||
className="project-activity-analysis-actions big-spacer-right" | |||
> | |||
<ActionsDropdown | |||
small={true} | |||
toggleClassName="js-analysis-actions" | |||
> | |||
<ActionsDropdownItem | |||
className="js-add-event" | |||
onClick={[Function]} | |||
> | |||
project_activity.add_version | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="js-add-event" | |||
onClick={[Function]} | |||
> | |||
project_activity.add_custom_event | |||
</ActionsDropdownItem> | |||
<ActionsDropdownDivider /> | |||
<ActionsDropdownItem | |||
className="js-delete-analysis" | |||
destructive={true} | |||
onClick={[Function]} | |||
> | |||
project_activity.delete_analysis | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
<AddEventForm | |||
addEvent={[MockFunction]} | |||
addEventButtonText="project_activity.add_version" | |||
analysis={ | |||
Object { | |||
"date": 2017-03-01T08:37:01.000Z, | |||
"events": Array [], | |||
"key": "foo", | |||
"projectVersion": "1.0", | |||
} | |||
} | |||
onClose={[Function]} | |||
/> | |||
</div> | |||
</li> | |||
</Tooltip> | |||
</div> | |||
<Memo(Events) | |||
analysisKey="foo" | |||
events={ | |||
Array [ | |||
Object { | |||
"category": "QUALITY_GATE", | |||
"description": "Lorem ipsum dolor sit amet", | |||
"key": "E11", | |||
"name": "Lorem ipsum", | |||
"qualityGate": Object { | |||
"failing": Array [ | |||
Object { | |||
"branch": "master", | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"branch": "feature/bar", | |||
"key": "bar", | |||
"name": "Bar", | |||
}, | |||
], | |||
"status": "ERROR", | |||
"stillFailing": true, | |||
}, | |||
}, | |||
] | |||
} | |||
isFirst={false} | |||
onChange={[MockFunction]} | |||
onDelete={[MockFunction]} | |||
/> | |||
</li> | |||
`; |
@@ -76,7 +76,6 @@ exports[`should render correctly 1`] = ` | |||
canAdmin={false} | |||
canDeleteAnalyses={false} | |||
changeEvent={[MockFunction]} | |||
className="boxed-group-inner" | |||
deleteAnalysis={[MockFunction]} | |||
deleteEvent={[MockFunction]} | |||
initializing={false} |
@@ -1,37 +1,26 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render 1`] = ` | |||
<div | |||
className="project-activity-event-inner" | |||
> | |||
<div | |||
className="project-activity-event-inner-main" | |||
<Fragment> | |||
<span | |||
className="note spacer-right" | |||
> | |||
<ProjectEventIcon | |||
className="project-activity-event-icon little-spacer-right QUALITY_GATE" | |||
/> | |||
<div | |||
className="project-activity-event-inner-text flex-1" | |||
> | |||
<span | |||
className="note little-spacer-right" | |||
> | |||
event.category.QUALITY_GATE | |||
: | |||
</span> | |||
<FormattedMessage | |||
defaultMessage="event.quality_gate.still_x" | |||
id="event.quality_gate.still_x" | |||
values={ | |||
Object { | |||
"status": <Level | |||
level="ERROR" | |||
small={true} | |||
/>, | |||
} | |||
} | |||
/> | |||
</div> | |||
event.category.QUALITY_GATE | |||
: | |||
</span> | |||
<FormattedMessage | |||
defaultMessage="event.quality_gate.still_x" | |||
id="event.quality_gate.still_x" | |||
values={ | |||
Object { | |||
"status": <Level | |||
level="ERROR" | |||
small={true} | |||
/>, | |||
} | |||
} | |||
/> | |||
<div> | |||
<ResetButtonLink | |||
className="project-activity-event-inner-more-link" | |||
onClick={[Function]} | |||
@@ -44,41 +33,30 @@ exports[`should render 1`] = ` | |||
/> | |||
</ResetButtonLink> | |||
</div> | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should render 2`] = ` | |||
<div | |||
className="project-activity-event-inner" | |||
> | |||
<div | |||
className="project-activity-event-inner-main" | |||
<Fragment> | |||
<span | |||
className="note spacer-right" | |||
> | |||
<ProjectEventIcon | |||
className="project-activity-event-icon little-spacer-right QUALITY_GATE" | |||
/> | |||
<div | |||
className="project-activity-event-inner-text flex-1" | |||
> | |||
<span | |||
className="note little-spacer-right" | |||
> | |||
event.category.QUALITY_GATE | |||
: | |||
</span> | |||
<FormattedMessage | |||
defaultMessage="event.quality_gate.still_x" | |||
id="event.quality_gate.still_x" | |||
values={ | |||
Object { | |||
"status": <Level | |||
level="ERROR" | |||
small={true} | |||
/>, | |||
} | |||
} | |||
/> | |||
</div> | |||
event.category.QUALITY_GATE | |||
: | |||
</span> | |||
<FormattedMessage | |||
defaultMessage="event.quality_gate.still_x" | |||
id="event.quality_gate.still_x" | |||
values={ | |||
Object { | |||
"status": <Level | |||
level="ERROR" | |||
small={true} | |||
/>, | |||
} | |||
} | |||
/> | |||
<div> | |||
<ResetButtonLink | |||
className="project-activity-event-inner-more-link" | |||
onClick={[Function]} | |||
@@ -92,14 +70,14 @@ exports[`should render 2`] = ` | |||
</ResetButtonLink> | |||
</div> | |||
<ul | |||
className="project-activity-event-inner-more-content" | |||
className="spacer-left spacer-top" | |||
> | |||
<li | |||
className="display-flex-center little-spacer-top" | |||
className="display-flex-center spacer-top" | |||
key="foo" | |||
> | |||
<Level | |||
className="little-spacer-right" | |||
className="spacer-right" | |||
level="ERROR" | |||
small={true} | |||
/> | |||
@@ -126,11 +104,11 @@ exports[`should render 2`] = ` | |||
</div> | |||
</li> | |||
<li | |||
className="display-flex-center little-spacer-top" | |||
className="display-flex-center spacer-top" | |||
key="bar" | |||
> | |||
<Level | |||
className="little-spacer-right" | |||
className="spacer-right" | |||
level="ERROR" | |||
small={true} | |||
/> | |||
@@ -157,5 +135,5 @@ exports[`should render 2`] = ` | |||
</div> | |||
</li> | |||
</ul> | |||
</div> | |||
</Fragment> | |||
`; |
@@ -21,30 +21,25 @@ import * as React from 'react'; | |||
import ConfirmModal from 'sonar-ui-common/components/controls/ConfirmModal'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
interface Props { | |||
analysis: string; | |||
deleteEvent: (analysis: string, event: string) => Promise<void>; | |||
export interface RemoveEventFormProps { | |||
analysisKey: string; | |||
event: T.AnalysisEvent; | |||
header: string; | |||
removeEventQuestion: string; | |||
onClose: () => void; | |||
onConfirm: (analysis: string, event: string) => Promise<void>; | |||
} | |||
export default class RemoveEventForm extends React.PureComponent<Props> { | |||
handleSubmit = () => { | |||
return this.props.deleteEvent(this.props.analysis, this.props.event.key); | |||
}; | |||
render() { | |||
return ( | |||
<ConfirmModal | |||
confirmButtonText={translate('delete')} | |||
header={this.props.header} | |||
isDestructive={true} | |||
onClose={this.props.onClose} | |||
onConfirm={this.handleSubmit}> | |||
{translate(this.props.removeEventQuestion)} | |||
</ConfirmModal> | |||
); | |||
} | |||
export default function RemoveEventForm(props: RemoveEventFormProps) { | |||
const { analysisKey, event, header, removeEventQuestion } = props; | |||
return ( | |||
<ConfirmModal | |||
confirmButtonText={translate('delete')} | |||
header={header} | |||
isDestructive={true} | |||
onClose={props.onClose} | |||
onConfirm={() => props.onConfirm(analysisKey, event.key)}> | |||
{removeEventQuestion} | |||
</ConfirmModal> | |||
); | |||
} |
@@ -0,0 +1,56 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 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 ConfirmModal from 'sonar-ui-common/components/controls/ConfirmModal'; | |||
import { mockAnalysisEvent } from '../../../../../helpers/testMocks'; | |||
import RemoveEventForm, { RemoveEventFormProps } from '../RemoveEventForm'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
it('should correctly confirm', () => { | |||
const onConfirm = jest.fn(); | |||
const wrapper = shallowRender({ onConfirm }); | |||
wrapper.find(ConfirmModal).prop('onConfirm')(); | |||
expect(onConfirm).toBeCalledWith('foo', 'bar'); | |||
}); | |||
it('should correctly cancel', () => { | |||
const onClose = jest.fn(); | |||
const wrapper = shallowRender({ onClose }); | |||
wrapper.find(ConfirmModal).prop('onClose')(); | |||
expect(onClose).toBeCalled(); | |||
}); | |||
function shallowRender(props: Partial<RemoveEventFormProps> = {}) { | |||
return shallow( | |||
<RemoveEventForm | |||
analysisKey="foo" | |||
event={mockAnalysisEvent({ key: 'bar' })} | |||
header="Remove foo" | |||
onClose={jest.fn()} | |||
onConfirm={jest.fn()} | |||
removeEventQuestion="Remove foo?" | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,13 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<ConfirmModal | |||
confirmButtonText="delete" | |||
header="Remove foo" | |||
isDestructive={true} | |||
onClose={[MockFunction]} | |||
onConfirm={[Function]} | |||
> | |||
Remove foo? | |||
</ConfirmModal> | |||
`; |
@@ -31,11 +31,6 @@ | |||
align-items: stretch; | |||
} | |||
.project-activity-page-side-outer > .boxed-group-inner { | |||
padding-left: 12px; | |||
padding-right: 15px; | |||
} | |||
.project-activity-layout-page-main { | |||
flex-grow: 1; | |||
min-width: 640px; | |||
@@ -59,6 +54,8 @@ | |||
overflow: auto; | |||
flex-grow: 1; | |||
flex-shrink: 0; | |||
padding: calc(2 * var(--gridSize)) calc(2 * var(--gridSize)) calc(2 * var(--gridSize)) | |||
calc(1.5 * var(--gridSize)); | |||
} | |||
.project-activity-day { | |||
@@ -71,23 +68,21 @@ | |||
} | |||
.project-activity-date { | |||
margin-bottom: 16px; | |||
font-size: 15px; | |||
margin-bottom: calc(2 * var(--gridSize)); | |||
font-size: var(--bigFontSize); | |||
font-weight: bold; | |||
} | |||
.project-activity-analysis { | |||
position: relative; | |||
display: flex; | |||
min-height: var(--smallControlHeight); | |||
padding: var(--gridSize) calc(0.5 * var(--gridSize)); | |||
border-top: 1px solid var(--barBorderColor); | |||
border-bottom: 1px solid var(--barBorderColor); | |||
padding: calc(2 * var(--gridSize)); | |||
cursor: pointer; | |||
} | |||
.project-activity-analysis.selected { | |||
background-color: #ecf6fe; | |||
cursor: default; | |||
} | |||
.project-activity-analysis:focus { | |||
@@ -103,69 +98,26 @@ | |||
} | |||
.project-activity-analysis-actions { | |||
flex-shrink: 0; | |||
flex-grow: 0; | |||
height: var(--smallControlHeight); | |||
} | |||
.project-activity-time { | |||
flex-shrink: 0; | |||
flex-grow: 0; | |||
width: 58px; | |||
height: var(--smallControlHeight); | |||
line-height: var(--smallControlHeight); | |||
box-sizing: border-box; | |||
font-size: var(--smallFontSize); | |||
font-weight: bold; | |||
text-align: right; | |||
} | |||
.project-activity-events { | |||
flex: 1; | |||
min-width: 0; | |||
} | |||
.project-activity-event { | |||
line-height: var(--smallControlHeight); | |||
display: flex; | |||
text-indent: -20px; | |||
padding-left: 20px; | |||
} | |||
.project-activity-event + .project-activity-event { | |||
margin-top: 4px; | |||
} | |||
.project-activity-event-inner { | |||
flex: 1; | |||
min-width: 0; | |||
} | |||
.project-activity-event-inner-main { | |||
display: flex; | |||
align-items: flex-start; | |||
} | |||
.project-activity-event-icon { | |||
flex-shrink: 0; | |||
flex-grow: 0; | |||
margin-top: calc(0.5 * var(--smallControlHeight) - 7px); | |||
} | |||
.project-activity-event-inner-text { | |||
line-height: var(--smallControlHeight); | |||
margin-top: var(--gridSize); | |||
} | |||
.project-activity-event-inner-more-link { | |||
line-height: 16px; | |||
margin-top: 2px; | |||
} | |||
.project-activity-event-inner-more-content { | |||
margin-left: 18px; | |||
} | |||
.project-activity-event-actions { | |||
flex-shrink: 0; | |||
flex-grow: 0; | |||
} | |||
.project-activity-event-icon.VERSION { | |||
@@ -189,9 +141,9 @@ | |||
} | |||
.project-activity-version-badge { | |||
margin-left: -12px; | |||
padding-top: 8px; | |||
padding-bottom: 8px; | |||
margin-left: calc(-1.5 * var(--gridSize)); | |||
padding-top: var(--gridSize); | |||
padding-bottom: var(--gridSize); | |||
background-color: white; | |||
} | |||
@@ -199,9 +151,9 @@ | |||
.project-activity-version-badge.first { | |||
position: absolute; | |||
top: 0; | |||
left: 12px; | |||
right: 16px; | |||
padding-top: 24px; | |||
left: calc(1.5 * var(--gridSize)); | |||
right: calc(2 * var(--gridSize)); | |||
padding-top: calc(3 * var(--gridSize)); | |||
z-index: var(--belowNormalZIndex); | |||
} | |||
@@ -247,9 +247,7 @@ export default class BranchAnalysisList extends React.PureComponent<Props, State | |||
{analysis.events.length > 0 && ( | |||
<Events | |||
analysis={analysis.key} | |||
changeEvent={() => Promise.resolve()} | |||
deleteEvent={() => Promise.resolve()} | |||
analysisKey={analysis.key} | |||
events={analysis.events} | |||
isFirst={analyses[0].key === analysis.key} | |||
/> |