align-items: stretch;
}
+.display-flex-start {
+ display: flex !important;
+ align-items: flex-start;
+}
+
.display-inline-flex-baseline {
display: inline-flex !important;
align-items: baseline;
* 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';
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}
</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>
+ </>
);
}
}
* 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);
* 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)) {
</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;
}
}
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
);
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);
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 {
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;
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);
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);
}
}
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;
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}
isBaseline={this.shouldRenderBaselineMarker(analysis)}
isFirst={analysis.key === firstAnalysisKey}
key={analysis.key}
+ parentScrollContainer={this.scrollContainer}
selected={analysis.date.valueOf() === selectedDate}
updateSelectedDate={this.updateSelectedDate}
/>
(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" />
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) {
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;
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>
)}
<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);
canAdmin={canAdmin}
canDeleteAnalyses={canDeleteAnalyses}
changeEvent={props.changeEvent}
- className="boxed-group-inner"
deleteAnalysis={props.deleteAnalysis}
deleteEvent={props.deleteEvent}
initializing={props.initializing}
* 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';
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"
</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}
))}
</ul>
)}
- </div>
+ </>
);
}
}
--- /dev/null
+/*
+ * 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}
+ />
+ );
+}
--- /dev/null
+/*
+ * 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}
+ />
+ );
+}
--- /dev/null
+/*
+ * 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}
+ />
+ );
+}
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';
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}
+ />
+ );
+}
* 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: () => ({
})
}));
+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', () => {
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', () => {
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()}
// 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]}
/>
</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]}
</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
</div>
</li>
<li
- className="display-flex-center little-spacer-top"
+ className="display-flex-center spacer-top"
key="bar"
>
<div
</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]}
</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
</div>
</li>
<li
- className="display-flex-center little-spacer-top"
+ className="display-flex-center spacer-top"
key="bar"
>
<FormattedMessage
/>
</li>
</ul>
-</div>
+</Fragment>
`;
--- /dev/null
+// 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>
+`;
--- /dev/null
+// 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,
+ },
+ }
+ }
+/>
+`;
--- /dev/null
+// 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>
+`;
<ul
className="project-activity-analyses-list"
>
- <ProjectActivityAnalysis
+ <Memo(ProjectActivityAnalysis)
addCustomEvent={[MockFunction]}
addVersion={[MockFunction]}
analysis={
},
],
"key": "A4",
+ "projectVersion": "1.0",
}
}
canAdmin={false}
<ul
className="project-activity-analyses-list"
>
- <ProjectActivityAnalysis
+ <Memo(ProjectActivityAnalysis)
addCustomEvent={[MockFunction]}
addVersion={[MockFunction]}
analysis={
},
],
"key": "A1",
+ "projectVersion": "1.0",
}
}
canAdmin={false}
</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]}
<ul
className="project-activity-analyses-list"
>
- <ProjectActivityAnalysis
+ <Memo(ProjectActivityAnalysis)
addCustomEvent={[MockFunction]}
addVersion={[MockFunction]}
analysis={
},
],
"key": "A1",
+ "projectVersion": "1.0",
}
}
canAdmin={false}
selected={false}
updateSelectedDate={[Function]}
/>
- <ProjectActivityAnalysis
+ <Memo(ProjectActivityAnalysis)
addCustomEvent={[MockFunction]}
addVersion={[MockFunction]}
analysis={
"date": 2016-10-27T12:21:15.000Z,
"events": Array [],
"key": "A2",
+ "projectVersion": "1.0",
}
}
canAdmin={false}
<ul
className="project-activity-analyses-list"
>
- <ProjectActivityAnalysis
+ <Memo(ProjectActivityAnalysis)
addCustomEvent={[MockFunction]}
addVersion={[MockFunction]}
analysis={
},
],
"key": "A3",
+ "projectVersion": "1.0",
}
}
canAdmin={false}
<ul
className="project-activity-analyses-list"
>
- <ProjectActivityAnalysis
+ <Memo(ProjectActivityAnalysis)
addCustomEvent={[MockFunction]}
addVersion={[MockFunction]}
analysis={
},
],
"key": "A4",
+ "projectVersion": "1.0",
}
}
canAdmin={false}
</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>
+`;
// 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={
<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={
<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={
<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={
</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={
<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>
`;
canAdmin={false}
canDeleteAnalyses={false}
changeEvent={[MockFunction]}
- className="boxed-group-inner"
deleteAnalysis={[MockFunction]}
deleteEvent={[MockFunction]}
initializing={false}
// 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]}
/>
</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]}
</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}
/>
</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}
/>
</div>
</li>
</ul>
-</div>
+</Fragment>
`;
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>
+ );
}
--- /dev/null
+/*
+ * 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}
+ />
+ );
+}
--- /dev/null
+// 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>
+`;
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;
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 {
}
.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 {
}
.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 {
}
.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;
}
.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);
}
{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}
/>