*/
import { getJSON, postJSON, post, RequestData } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
-import { Paging, BranchParameters } from '../app/types';
-
-export interface Event {
- key: string;
- name: string;
- category: string;
- description?: string;
-}
-
-export interface Analysis {
- key: string;
- date: string;
- events: Event[];
-}
+import { Paging, BranchParameters, Analysis } from '../app/types';
export function getProjectActivity(
data: { project: string; category?: string; p?: number; ps?: number } & BranchParameters
import { Paging, BranchParameters } from '../app/types';
import throwGlobalError from '../app/utils/throwGlobalError';
-export interface HistoryItem {
- date: Date;
- value?: string;
-}
-
-export interface History {
- [metric: string]: HistoryItem[];
-}
-
interface TimeMachineResponse {
measures: {
metric: string;
- history: HistoryItem[];
+ history: Array<{ date: string; value?: string }>;
}[];
paging: Paging;
}
linkedProjectName?: string;
}
+export interface Analysis {
+ date: string;
+ events: AnalysisEvent[];
+ key: string;
+}
+
+export interface AnalysisEvent {
+ category: string;
+ description?: string;
+ key: string;
+ name: string;
+}
+
export interface AppState {
adminPages?: Extension[];
authenticationError?: boolean;
description?: string;
extensions?: Extension[];
isFavorite?: boolean;
+ leakPeriodDate?: string;
name: string;
path?: string;
refKey?: string;
canApplyPermissionTemplate?: boolean;
extensions?: Extension[];
showBackgroundTasks?: boolean;
+ showHistory?: boolean;
showLinks?: boolean;
showManualMeasures?: boolean;
showQualityGates?: boolean;
import QualityGate from '../qualityGate/QualityGate';
import throwGlobalError from '../../../app/utils/throwGlobalError';
import { getMeasuresAndMeta } from '../../../api/measures';
-import { getAllTimeMachineData, History } from '../../../api/time-machine';
+import { getAllTimeMachineData } from '../../../api/time-machine';
import { parseDate } from '../../../helpers/dates';
import { enhanceMeasuresWithMetrics } from '../../../helpers/measures';
import { getLeakPeriod } from '../../../helpers/periods';
type Props = StateToProps & DispatchToProps & OwnProps;
interface State {
- history?: History;
+ history?: {
+ [metric: string]: Array<{ date: Date; value?: string }>;
+ };
historyStartDate?: Date;
loading: boolean;
measures: MeasureEnhanced[];
metrics: metrics.join()
}).then(r => {
if (this.mounted) {
- const history: History = {};
+ const history: { [metric: string]: Array<{ date: Date; value?: string }> } = {};
r.measures.forEach(measure => {
const measureHistory = measure.history.map(analysis => ({
date: parseDate(analysis.date),
import * as React from 'react';
import { max } from 'd3-array';
import LineChart from '../../../components/charts/LineChart';
-import { HistoryItem } from '../../../api/time-machine';
const HEIGHT = 80;
interface Props {
- history: HistoryItem[];
+ history: Array<{ date: Date; value?: string }>;
before?: Date;
after?: Date;
}
import * as React from 'react';
import { Link } from 'react-router';
import Analysis from './Analysis';
-import { getProjectActivity, Analysis as IAnalysis } from '../../../api/projectActivity';
+import { getProjectActivity } from '../../../api/projectActivity';
import PreviewGraph from '../../../components/preview-graph/PreviewGraph';
import { translate } from '../../../helpers/l10n';
-import { Metric, Component, BranchLike } from '../../../app/types';
-import { History } from '../../../api/time-machine';
+import { Metric, Component, BranchLike, Analysis as AnalysisType } from '../../../app/types';
import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branches';
import { getActivityUrl } from '../../../helpers/urls';
interface Props {
branchLike?: BranchLike;
component: Component;
- history?: History;
+ history?: {
+ [metric: string]: Array<{ date: Date; value?: string }>;
+ };
metrics: { [key: string]: Metric };
qualifier: string;
}
interface State {
- analyses: IAnalysis[];
+ analyses: AnalysisType[];
loading: boolean;
}
);
};
- renderList(analyses: IAnalysis[]) {
+ renderList(analyses: AnalysisType[]) {
if (!analyses.length) {
return <p className="spacer-top note">{translate('no_results')}</p>;
}
import { sortBy } from 'lodash';
import Event from './Event';
import DateTooltipFormatter from '../../../components/intl/DateTooltipFormatter';
-import { Analysis as IAnalysis, Event as IEvent } from '../../../api/projectActivity';
+import { Analysis as AnalysisType } from '../../../app/types';
import { translate } from '../../../helpers/l10n';
interface Props {
- analysis: IAnalysis;
+ analysis: AnalysisType;
qualifier: string;
}
export default function Analysis({ analysis, ...props }: Props) {
- const sortedEvents: Array<IEvent> = sortBy(
+ const sortedEvents = sortBy(
analysis.events,
// versions first
- (event: IEvent) => (event.category === 'VERSION' ? 0 : 1),
+ event => (event.category === 'VERSION' ? 0 : 1),
// then the rest sorted by category
'category'
);
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Event as IEvent } from '../../../api/projectActivity';
import { translate } from '../../../helpers/l10n';
+import { AnalysisEvent } from '../../../app/types';
interface Props {
- event: IEvent;
+ event: AnalysisEvent;
}
export default function Event({ event }: Props) {
getMeasureHistoryUrl
} from '../../../helpers/urls';
import { Component, BranchLike, MeasureEnhanced, Period } from '../../../app/types';
-import { History } from '../../../api/time-machine';
import { getBranchLikeQuery } from '../../../helpers/branches';
export interface EnhanceProps {
component: Component;
measures: MeasureEnhanced[];
leakPeriod?: Period;
- history?: History;
+ history?: {
+ [metric: string]: Array<{ date: Date; value?: string }>;
+ };
historyStartDate?: Date;
}
Organization,
MeasureEnhanced
} from '../../../app/types';
-import { History } from '../../../api/time-machine';
import { translate } from '../../../helpers/l10n';
import { hasPrivateAccess } from '../../../helpers/organizations';
import {
interface OwnProps {
branchLike?: BranchLike;
component: Component;
- history?: History;
+ history?: {
+ [metric: string]: Array<{ date: Date; value?: string }>;
+ };
measures?: MeasureEnhanced[];
metrics?: { [key: string]: Metric };
onComponentChange: (changes: {}) => void;
PROJECT_ACTIVITY_GRAPH_CUSTOM
} from '../../projectActivity/utils';
import PreviewGraph from '../../../components/preview-graph/PreviewGraph';
-import { getAllTimeMachineData, History } from '../../../api/time-machine';
+import { getAllTimeMachineData } from '../../../api/time-machine';
import { Metric } from '../../../app/types';
import { parseDate } from '../../../helpers/dates';
import { translate } from '../../../helpers/l10n';
import { get } from '../../../helpers/storage';
-const AnyPreviewGraph = PreviewGraph as any;
-
interface Props {
component: string;
metrics: { [key: string]: Metric };
}
interface State {
- history?: History;
+ history?: {
+ [metric: string]: Array<{ date: Date; value?: string }>;
+ };
loading: boolean;
}
return getAllTimeMachineData({ component, metrics: graphMetrics.join() }).then(
timeMachine => {
if (this.mounted) {
- const history: History = {};
+ const history: { [metric: string]: Array<{ date: Date; value?: string }> } = {};
timeMachine.measures.forEach(measure => {
const measureHistory = measure.history.map(analysis => ({
date: parseDate(analysis.date),
<i className="spinner" />
) : (
this.state.history !== undefined && (
- <AnyPreviewGraph
+ <PreviewGraph
history={this.state.history}
metrics={this.props.metrics}
project={this.props.component}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-/*:: import type { Event } from './types'; */
-/*:: import type { State } from './components/ProjectActivityAppContainer'; */
-
-export const addCustomEvent = (analysis /*: string */, event /*: Event */) => (
- state /*: State */
-) => ({
- analyses: state.analyses.map(item => {
- if (item.key !== analysis) {
- return item;
- }
- return { ...item, events: [...item.events, event] };
- })
-});
-
-export const deleteEvent = (analysis /*: string */, event /*: string */) => (
- state /*: State */
-) => ({
- analyses: state.analyses.map(item => {
- if (item.key !== analysis) {
- return item;
- }
- return {
- ...item,
- events: item.events.filter(eventItem => eventItem.key !== event)
- };
- })
-});
-
-export const changeEvent = (analysis /*: string */, event /*: Event */) => (
- state /*: State */
-) => ({
- analyses: state.analyses.map(item => {
- if (item.key !== analysis) {
- return item;
- }
- return {
- ...item,
- events: item.events.map(
- eventItem => (eventItem.key === event.key ? { ...eventItem, ...event } : eventItem)
- )
- };
- })
-});
-
-export const deleteAnalysis = (analysis /*: string */) => (state /*: State */) => ({
- analyses: state.analyses.filter(item => item.key !== analysis)
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { State } from './components/ProjectActivityAppContainer';
+import { AnalysisEvent } from '../../app/types';
+
+export function addCustomEvent(analysis: string, event: AnalysisEvent) {
+ return (state: State) => ({
+ analyses: state.analyses.map(item => {
+ if (item.key !== analysis) {
+ return item;
+ }
+ return { ...item, events: [...item.events, event] };
+ })
+ });
+}
+
+export function deleteEvent(analysis: string, event: string) {
+ return (state: State) => ({
+ analyses: state.analyses.map(item => {
+ if (item.key !== analysis) {
+ return item;
+ }
+ return { ...item, events: item.events.filter(eventItem => eventItem.key !== event) };
+ })
+ });
+}
+
+export function changeEvent(analysis: string, event: AnalysisEvent) {
+ return (state: State) => ({
+ analyses: state.analyses.map(item => {
+ if (item.key !== analysis) {
+ return item;
+ }
+ return {
+ ...item,
+ events: item.events.map(
+ eventItem => (eventItem.key === event.key ? { ...eventItem, ...event } : eventItem)
+ )
+ };
+ })
+ });
+}
+
+export function deleteAnalysis(analysis: string) {
+ return (state: State) => ({ analyses: state.analyses.filter(item => item.key !== analysis) });
+}
import Tooltip from '../../../components/controls/Tooltip';
import { DeleteButton, EditButton } from '../../../components/ui/buttons';
import { translate } from '../../../helpers/l10n';
-import { Event as IEvent } from '../../../api/projectActivity';
+import { AnalysisEvent } from '../../../app/types';
interface Props {
analysis: string;
- canAdmin: boolean;
+ canAdmin?: boolean;
changeEvent: (event: string, name: string) => Promise<void>;
deleteEvent: (analysis: string, event: string) => Promise<void>;
- event: IEvent;
- isFirst: boolean;
+ event: AnalysisEvent;
+ isFirst?: boolean;
}
interface State {
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import ProjectEventIcon from '../../../components/icons-components/ProjectEventIcon';
-import { translate } from '../../../helpers/l10n';
-/*:: import type { Event as EventType } from '../types'; */
-
-export default function EventInner(props /*: { event: EventType } */) {
- const { event } = props;
-
- return (
- <div className="project-activity-event-inner">
- <div className="project-activity-event-inner-icon little-spacer-right">
- <ProjectEventIcon
- className={'project-activity-event-icon margin-align ' + event.category}
- />
- </div>
- <span className="project-activity-event-inner-text">
- <span className="note">{translate('event.category', event.category)}:</span>{' '}
- <strong title={event.description}>{event.name}</strong>
- </span>
- </div>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { AnalysisEvent } from '../../../app/types';
+import ProjectEventIcon from '../../../components/icons-components/ProjectEventIcon';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ event: AnalysisEvent;
+}
+
+export default function EventInner({ event }: Props) {
+ return (
+ <div className="project-activity-event-inner">
+ <div className="project-activity-event-inner-icon little-spacer-right">
+ <ProjectEventIcon
+ className={'project-activity-event-icon margin-align ' + event.category}
+ />
+ </div>
+ <span className="project-activity-event-inner-text">
+ <span className="note">{translate('event.category', event.category)}:</span>{' '}
+ <strong title={event.description}>{event.name}</strong>
+ </span>
+ </div>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import { sortBy } from 'lodash';
-import Event from './Event';
-/*:: import type { Event as EventType } from '../types'; */
-
-/*::
-type Props = {
- analysis?: string,
- canAdmin?: boolean,
- changeEvent?: (event: string, name: string) => Promise<*>,
- deleteEvent?: (analysis: string, event: string) => Promise<*>,
- events: Array<EventType>,
- isFirst?: boolean
-};
-*/
-
-export default function Events(props /*: Props */) {
- const sortedEvents = sortBy(
- props.events,
- // versions last
- event => (event.category === 'VERSION' ? 1 : 0),
- // then the rest sorted by category
- 'category'
- );
-
- return (
- <div className="project-activity-events">
- {sortedEvents.map(event => (
- <Event
- analysis={props.analysis}
- canAdmin={props.canAdmin}
- changeEvent={props.changeEvent}
- deleteEvent={props.deleteEvent}
- event={event}
- isFirst={props.isFirst}
- key={event.key}
- />
- ))}
- </div>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { sortBy } from 'lodash';
+import Event from './Event';
+import { AnalysisEvent } from '../../../app/types';
+
+interface Props {
+ analysis: string;
+ canAdmin?: boolean;
+ changeEvent: (event: string, name: string) => Promise<void>;
+ deleteEvent: (analysis: string, event: string) => Promise<void>;
+ events: AnalysisEvent[];
+ isFirst?: boolean;
+}
+
+export default function Events(props: Props) {
+ const sortedEvents = sortBy(
+ props.events,
+ // versions last
+ event => (event.category === 'VERSION' ? 1 : 0),
+ // then the rest sorted by category
+ 'category'
+ );
+
+ return (
+ <div className="project-activity-events">
+ {sortedEvents.map(event => (
+ <Event
+ analysis={props.analysis}
+ canAdmin={props.canAdmin}
+ changeEvent={props.changeEvent}
+ deleteEvent={props.deleteEvent}
+ event={event}
+ isFirst={props.isFirst}
+ key={event.key}
+ />
+ ))}
+ </div>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
-import GraphsTooltips from './GraphsTooltips';
-import GraphsLegendCustom from './GraphsLegendCustom';
-import GraphsLegendStatic from './GraphsLegendStatic';
-import AdvancedTimeline from '../../../components/charts/AdvancedTimeline';
-import { formatMeasure, getShortType } from '../../../helpers/measures';
-/*:: import type { Event, MeasureHistory } from '../types'; */
-/*:: import type { Serie } from '../../../components/charts/AdvancedTimeline'; */
-
-/*::
-type Props = {
- events: Array<Event>,
- graph: string,
- graphEndDate: ?Date,
- graphStartDate: ?Date,
- leakPeriodDate: Date,
- isCustom: boolean,
- measuresHistory: Array<MeasureHistory>,
- metricsType: string,
- removeCustomMetric: (metric: string) => void,
- showAreas: boolean,
- series: Array<Serie>,
- selectedDate?: ?Date,
- updateGraphZoom: (from: ?Date, to: ?Date) => void,
- updateSelectedDate: (selectedDate: ?Date) => void,
- updateTooltip: (selectedDate: ?Date) => void
-};
-*/
-
-/*::
-type State = {
- tooltipIdx: ?number,
- tooltipXPos: ?number
-};
-*/
-
-export default class GraphHistory extends React.PureComponent {
- /*:: props: Props; */
- state /*: State */ = {
- tooltipIdx: null,
- tooltipXPos: null
- };
-
- formatValue = (tick /*: string | number */) =>
- formatMeasure(tick, getShortType(this.props.metricsType));
-
- formatTooltipValue = (tick /*: string | number */) => formatMeasure(tick, this.props.metricsType);
-
- updateTooltip = (
- selectedDate /*: ?Date */,
- tooltipXPos /*: ?number */,
- tooltipIdx /*: ?number */
- ) => {
- this.props.updateTooltip(selectedDate);
- this.setState({ tooltipXPos, tooltipIdx });
- };
-
- render() {
- const { graph, selectedDate, series } = this.props;
- const { tooltipIdx, tooltipXPos } = this.state;
-
- return (
- <div className="project-activity-graph-container">
- {this.props.isCustom ? (
- <GraphsLegendCustom removeMetric={this.props.removeCustomMetric} series={series} />
- ) : (
- <GraphsLegendStatic series={series} />
- )}
- <div className="project-activity-graph">
- <AutoSizer>
- {({ height, width }) => (
- <div>
- <AdvancedTimeline
- endDate={this.props.graphEndDate}
- formatYTick={this.formatValue}
- height={height}
- interpolate="linear"
- leakPeriodDate={this.props.leakPeriodDate}
- metricType={this.props.metricsType}
- selectedDate={selectedDate}
- series={series}
- showAreas={this.props.showAreas}
- startDate={this.props.graphStartDate}
- updateSelectedDate={this.props.updateSelectedDate}
- updateTooltip={this.updateTooltip}
- updateZoom={this.props.updateGraphZoom}
- width={width}
- />
- {selectedDate != null &&
- tooltipXPos != null && (
- <GraphsTooltips
- events={this.props.events}
- formatValue={this.formatTooltipValue}
- graph={graph}
- graphWidth={width}
- measuresHistory={this.props.measuresHistory}
- selectedDate={selectedDate}
- series={series}
- tooltipIdx={tooltipIdx}
- tooltipPos={tooltipXPos}
- />
- )}
- </div>
- )}
- </AutoSizer>
- </div>
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer';
+import GraphsTooltips from './GraphsTooltips';
+import GraphsLegendCustom from './GraphsLegendCustom';
+import GraphsLegendStatic from './GraphsLegendStatic';
+import AdvancedTimeline from '../../../components/charts/AdvancedTimeline';
+import { formatMeasure, getShortType } from '../../../helpers/measures';
+import { MeasureHistory, Serie } from '../utils';
+import { AnalysisEvent } from '../../../app/types';
+
+interface Props {
+ events: AnalysisEvent[];
+ graph: string;
+ graphEndDate?: Date;
+ graphStartDate?: Date;
+ leakPeriodDate?: Date;
+ isCustom: boolean;
+ measuresHistory: MeasureHistory[];
+ metricsType: string;
+ removeCustomMetric: (metric: string) => void;
+ showAreas: boolean;
+ series: Serie[];
+ selectedDate?: Date;
+ updateGraphZoom: (from?: Date, to?: Date) => void;
+ updateSelectedDate: (selectedDate?: Date) => void;
+ updateTooltip: (selectedDate?: Date) => void;
+}
+
+interface State {
+ tooltipIdx?: number;
+ tooltipXPos?: number;
+}
+
+export default class GraphHistory extends React.PureComponent<Props, State> {
+ state: State = {};
+
+ formatValue = (tick: string | number) => {
+ return formatMeasure(tick, getShortType(this.props.metricsType));
+ };
+
+ formatTooltipValue = (tick: string | number) => {
+ return formatMeasure(tick, this.props.metricsType);
+ };
+
+ updateTooltip = (selectedDate?: Date, tooltipXPos?: number, tooltipIdx?: number) => {
+ this.props.updateTooltip(selectedDate);
+ this.setState({ tooltipXPos, tooltipIdx });
+ };
+
+ render() {
+ const { graph, selectedDate, series } = this.props;
+ const { tooltipIdx, tooltipXPos } = this.state;
+
+ return (
+ <div className="project-activity-graph-container">
+ {this.props.isCustom ? (
+ <GraphsLegendCustom removeMetric={this.props.removeCustomMetric} series={series} />
+ ) : (
+ <GraphsLegendStatic series={series} />
+ )}
+ <div className="project-activity-graph">
+ <AutoSizer>
+ {({ height, width }) => (
+ <div>
+ <AdvancedTimeline
+ endDate={this.props.graphEndDate}
+ formatYTick={this.formatValue}
+ height={height}
+ leakPeriodDate={this.props.leakPeriodDate}
+ metricType={this.props.metricsType}
+ selectedDate={selectedDate}
+ series={series}
+ showAreas={this.props.showAreas}
+ startDate={this.props.graphStartDate}
+ updateSelectedDate={this.props.updateSelectedDate}
+ updateTooltip={this.updateTooltip}
+ updateZoom={this.props.updateGraphZoom}
+ width={width}
+ />
+ {selectedDate !== undefined &&
+ tooltipIdx !== undefined &&
+ tooltipXPos !== undefined && (
+ <GraphsTooltips
+ events={this.props.events}
+ formatValue={this.formatTooltipValue}
+ graph={graph}
+ graphWidth={width}
+ measuresHistory={this.props.measuresHistory}
+ selectedDate={selectedDate}
+ series={series}
+ tooltipIdx={tooltipIdx}
+ tooltipPos={tooltipXPos}
+ />
+ )}
+ </div>
+ )}
+ </AutoSizer>
+ </div>
+ </div>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { isEqual, sortBy } from 'lodash';
-import GraphHistory from './GraphHistory';
-import DeferredSpinner from '../../../components/common/DeferredSpinner';
-import { EVENT_TYPES, getSeriesMetricType, hasHistoryData, isCustomGraph } from '../utils';
-import { translate } from '../../../helpers/l10n';
-import { parseDate } from '../../../helpers/dates';
-/*:: import type { Analysis, MeasureHistory } from '../types'; */
-/*:: import type { Serie } from '../../../components/charts/AdvancedTimeline'; */
-
-/*::
-type Props = {
- analyses: Array<Analysis>,
- eventFilter: string,
- graph: string,
- graphs: Array<Array<Serie>>,
- graphEndDate: ?Date,
- graphStartDate: ?Date,
- leakPeriodDate?: Date,
- loading: boolean,
- measuresHistory: Array<MeasureHistory>,
- removeCustomMetric: (metric: string) => void,
- selectedDate: ?Date,
- series: Array<Serie>,
- updateGraphZoom: (from: ?Date, to: ?Date) => void,
- updateSelectedDate: (selectedDate: ?Date) => void
-};
-*/
-
-/*::
-type State = {
- selectedDate?: ?Date
-};
-*/
-
-export default class GraphsHistory extends React.PureComponent {
- /*:: props: Props; */
- /*:: state: State; */
-
- constructor(props /*: Props */) {
- super(props);
- this.state = {
- selectedDate: props.selectedDate
- };
- }
-
- componentWillReceiveProps(nextProps /*: Props */) {
- if (!isEqual(nextProps.selectedDate, this.props.selectedDate)) {
- this.setState({ selectedDate: nextProps.selectedDate });
- }
- }
-
- getEvents = () => {
- const { analyses, eventFilter } = this.props;
- const filteredEvents = analyses.reduce((acc, analysis) => {
- if (analysis.events.length <= 0) {
- return acc;
- }
- let event;
- if (eventFilter) {
- event = analysis.events.filter(event => event.category === eventFilter)[0];
- } else {
- event = sortBy(analysis.events, event => EVENT_TYPES.indexOf(event.category))[0];
- }
- if (!event) {
- return acc;
- }
- return acc.concat({
- className: event.category,
- name: event.name,
- date: parseDate(analysis.date)
- });
- }, []);
- return sortBy(filteredEvents, 'date');
- };
-
- getSelectedDateEvents = () => {
- const { selectedDate } = this.state;
- const { analyses } = this.props;
- if (analyses && selectedDate) {
- const analysis = analyses.find(
- analysis => analysis.date.valueOf() === selectedDate.valueOf()
- );
- if (analysis) {
- return analysis.events;
- }
- }
- return [];
- };
-
- updateTooltip = (selectedDate /*: ?Date */) => this.setState({ selectedDate });
-
- render() {
- const { graph, loading, series } = this.props;
- const isCustom = isCustomGraph(graph);
-
- if (loading) {
- return (
- <div className="project-activity-graph-container">
- <div className="text-center">
- <DeferredSpinner className="" loading={loading} />
- </div>
- </div>
- );
- }
-
- if (!hasHistoryData(series)) {
- return (
- <div className="project-activity-graph-container">
- <div className="note text-center">
- {translate(
- isCustom
- ? 'project_activity.graphs.custom.no_history'
- : 'component_measures.no_history'
- )}
- </div>
- </div>
- );
- }
- const events = this.getSelectedDateEvents();
- const showAreas = ['coverage', 'duplications'].includes(graph);
- return (
- <div className="project-activity-graphs">
- {this.props.graphs.map((series, idx) => (
- <GraphHistory
- events={events}
- graph={graph}
- graphEndDate={this.props.graphEndDate}
- graphStartDate={this.props.graphStartDate}
- isCustom={isCustom}
- key={idx}
- leakPeriodDate={this.props.leakPeriodDate}
- measuresHistory={this.props.measuresHistory}
- metricsType={getSeriesMetricType(series)}
- removeCustomMetric={this.props.removeCustomMetric}
- selectedDate={this.state.selectedDate}
- series={series}
- showAreas={showAreas}
- updateGraphZoom={this.props.updateGraphZoom}
- updateSelectedDate={this.props.updateSelectedDate}
- updateTooltip={this.updateTooltip}
- />
- ))}
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { isEqual } from 'lodash';
+import GraphHistory from './GraphHistory';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import {
+ getSeriesMetricType,
+ hasHistoryData,
+ isCustomGraph,
+ Serie,
+ MeasureHistory,
+ ParsedAnalysis
+} from '../utils';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ analyses: ParsedAnalysis[];
+ eventFilter: string;
+ graph: string;
+ graphs: Serie[][];
+ graphEndDate?: Date;
+ graphStartDate?: Date;
+ leakPeriodDate?: Date;
+ loading: boolean;
+ measuresHistory: MeasureHistory[];
+ removeCustomMetric: (metric: string) => void;
+ selectedDate?: Date;
+ series: Serie[];
+ updateGraphZoom: (from?: Date, to?: Date) => void;
+ updateSelectedDate: (selectedDate?: Date) => void;
+}
+
+interface State {
+ selectedDate?: Date;
+}
+
+export default class GraphsHistory extends React.PureComponent<Props, State> {
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ selectedDate: props.selectedDate
+ };
+ }
+
+ componentWillReceiveProps(nextProps: Props) {
+ if (!isEqual(nextProps.selectedDate, this.props.selectedDate)) {
+ this.setState({ selectedDate: nextProps.selectedDate });
+ }
+ }
+
+ getSelectedDateEvents = () => {
+ const { selectedDate } = this.state;
+ const { analyses } = this.props;
+ if (analyses && selectedDate) {
+ const analysis = analyses.find(
+ analysis => analysis.date.valueOf() === selectedDate.valueOf()
+ );
+ if (analysis) {
+ return analysis.events;
+ }
+ }
+ return [];
+ };
+
+ updateTooltip = (selectedDate?: Date) => {
+ this.setState({ selectedDate });
+ };
+
+ render() {
+ const { graph, loading, series } = this.props;
+ const isCustom = isCustomGraph(graph);
+
+ if (loading) {
+ return (
+ <div className="project-activity-graph-container">
+ <div className="text-center">
+ <DeferredSpinner className="" loading={loading} />
+ </div>
+ </div>
+ );
+ }
+
+ if (!hasHistoryData(series)) {
+ return (
+ <div className="project-activity-graph-container">
+ <div className="note text-center">
+ {translate(
+ isCustom
+ ? 'project_activity.graphs.custom.no_history'
+ : 'component_measures.no_history'
+ )}
+ </div>
+ </div>
+ );
+ }
+ const events = this.getSelectedDateEvents();
+ const showAreas = ['coverage', 'duplications'].includes(graph);
+ return (
+ <div className="project-activity-graphs">
+ {this.props.graphs.map((series, idx) => (
+ <GraphHistory
+ events={events}
+ graph={graph}
+ graphEndDate={this.props.graphEndDate}
+ graphStartDate={this.props.graphStartDate}
+ isCustom={isCustom}
+ key={idx}
+ leakPeriodDate={this.props.leakPeriodDate}
+ measuresHistory={this.props.measuresHistory}
+ metricsType={getSeriesMetricType(series)}
+ removeCustomMetric={this.props.removeCustomMetric}
+ selectedDate={this.state.selectedDate}
+ series={series}
+ showAreas={showAreas}
+ updateGraphZoom={this.props.updateGraphZoom}
+ updateSelectedDate={this.props.updateSelectedDate}
+ updateTooltip={this.updateTooltip}
+ />
+ ))}
+ </div>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import GraphsLegendItem from './GraphsLegendItem';
-import Tooltip from '../../../components/controls/Tooltip';
-import { hasDataValues } from '../utils';
-import { translate } from '../../../helpers/l10n';
-/*:: import type { Serie } from '../../../components/charts/AdvancedTimeline'; */
-
-/*::
-type Props = {
- removeMetric: string => void,
- series: Array<Serie & { translatedName: string }>
-};
-*/
-
-export default function GraphsLegendCustom({ removeMetric, series } /*: Props */) {
- return (
- <div className="project-activity-graph-legends">
- {series.map((serie, idx) => {
- const hasData = hasDataValues(serie);
- const legendItem = (
- <GraphsLegendItem
- metric={serie.name}
- name={serie.translatedName}
- removeMetric={removeMetric}
- showWarning={!hasData}
- style={idx.toString()}
- />
- );
- if (!hasData) {
- return (
- <Tooltip
- key={serie.name}
- overlay={translate('project_activity.graphs.custom.metric_no_history')}>
- <span className="spacer-left spacer-right">{legendItem}</span>
- </Tooltip>
- );
- }
- return (
- <span className="spacer-left spacer-right" key={serie.name}>
- {legendItem}
- </span>
- );
- })}
- </div>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import GraphsLegendItem from './GraphsLegendItem';
+import Tooltip from '../../../components/controls/Tooltip';
+import { hasDataValues, Serie } from '../utils';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ removeMetric: (metric: string) => void;
+ series: Serie[];
+}
+
+export default function GraphsLegendCustom({ removeMetric, series }: Props) {
+ return (
+ <div className="project-activity-graph-legends">
+ {series.map((serie, idx) => {
+ const hasData = hasDataValues(serie);
+ const legendItem = (
+ <GraphsLegendItem
+ metric={serie.name}
+ name={serie.translatedName}
+ removeMetric={removeMetric}
+ showWarning={!hasData}
+ style={idx.toString()}
+ />
+ );
+ if (!hasData) {
+ return (
+ <Tooltip
+ key={serie.name}
+ overlay={translate('project_activity.graphs.custom.metric_no_history')}>
+ <span className="spacer-left spacer-right">{legendItem}</span>
+ </Tooltip>
+ );
+ }
+ return (
+ <span className="spacer-left spacer-right" key={serie.name}>
+ {legendItem}
+ </span>
+ );
+ })}
+ </div>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import GraphsLegendItem from './GraphsLegendItem';
-
-/*::
-type Props = {
- series: Array<{ name: string, translatedName: string }>
-};
-*/
-
-export default function GraphsLegendStatic({ series } /*: Props */) {
- return (
- <div className="project-activity-graph-legends">
- {series.map((serie, idx) => (
- <GraphsLegendItem
- className="big-spacer-left big-spacer-right"
- key={serie.name}
- metric={serie.name}
- name={serie.translatedName}
- style={idx.toString()}
- />
- ))}
- </div>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import GraphsLegendItem from './GraphsLegendItem';
+import { Serie } from '../utils';
+
+interface Props {
+ series: Array<Pick<Serie, 'name' | 'translatedName'>>;
+}
+
+export default function GraphsLegendStatic({ series }: Props) {
+ return (
+ <div className="project-activity-graph-legends">
+ {series.map((serie, idx) => (
+ <GraphsLegendItem
+ className="big-spacer-left big-spacer-right"
+ key={serie.name}
+ metric={serie.name}
+ name={serie.translatedName}
+ style={idx.toString()}
+ />
+ ))}
+ </div>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import GraphsTooltipsContent from './GraphsTooltipsContent';
-import GraphsTooltipsContentEvents from './GraphsTooltipsContentEvents';
-import GraphsTooltipsContentCoverage from './GraphsTooltipsContentCoverage';
-import GraphsTooltipsContentDuplication from './GraphsTooltipsContentDuplication';
-import GraphsTooltipsContentIssues from './GraphsTooltipsContentIssues';
-import { DEFAULT_GRAPH } from '../utils';
-import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
-import { Popup, PopupPlacement } from '../../../components/ui/popups';
-/*:: import type { Event, MeasureHistory } from '../types'; */
-/*:: import type { Serie } from '../../../components/charts/AdvancedTimeline'; */
-
-/*::
-type Props = {
- events: Array<Event>,
- formatValue: (number | string) => string,
- graph: string,
- graphWidth: number,
- measuresHistory: Array<MeasureHistory>,
- selectedDate: Date,
- series: Array<Serie & { translatedName: string }>,
- tooltipIdx: number,
- tooltipPos: number
-};
-*/
-
-const TOOLTIP_WIDTH = 250;
-
-export default class GraphsTooltips extends React.PureComponent {
- /*:: props: Props; */
-
- renderContent() {
- const { tooltipIdx } = this.props;
-
- return this.props.series.map((serie, idx) => {
- const point = serie.data[tooltipIdx];
- if (!point || (!point.y && point.y !== 0)) {
- return null;
- }
- if (this.props.graph === DEFAULT_GRAPH) {
- return (
- <GraphsTooltipsContentIssues
- key={serie.name}
- measuresHistory={this.props.measuresHistory}
- name={serie.name}
- style={idx.toString()}
- tooltipIdx={tooltipIdx}
- translatedName={serie.translatedName}
- value={this.props.formatValue(point.y)}
- />
- );
- } else {
- return (
- <GraphsTooltipsContent
- key={serie.name}
- name={serie.name}
- style={idx.toString()}
- translatedName={serie.translatedName}
- value={this.props.formatValue(point.y)}
- />
- );
- }
- });
- }
-
- render() {
- const { events, measuresHistory, tooltipIdx } = this.props;
- const top = 30;
- let left = this.props.tooltipPos + 60;
- let placement = PopupPlacement.RightTop;
- if (left > this.props.graphWidth - TOOLTIP_WIDTH - 50) {
- left -= TOOLTIP_WIDTH;
- placement = PopupPlacement.LeftTop;
- }
- const tooltipContent = this.renderContent().filter(Boolean);
- const addSeparator = tooltipContent.length > 0;
- return (
- <Popup
- className="disabled-pointer-events"
- placement={placement}
- style={{ top, left, width: TOOLTIP_WIDTH }}>
- <div className="project-activity-graph-tooltip">
- <div className="project-activity-graph-tooltip-title spacer-bottom">
- <DateTimeFormatter date={this.props.selectedDate} />
- </div>
- <table className="width-100">
- <tbody>{tooltipContent}</tbody>
- {this.props.graph === 'coverage' && (
- <GraphsTooltipsContentCoverage
- addSeparator={addSeparator}
- measuresHistory={measuresHistory}
- tooltipIdx={tooltipIdx}
- />
- )}
- {this.props.graph === 'duplications' && (
- <GraphsTooltipsContentDuplication
- addSeparator={addSeparator}
- measuresHistory={measuresHistory}
- tooltipIdx={tooltipIdx}
- />
- )}
- {events &&
- events.length > 0 && (
- <GraphsTooltipsContentEvents addSeparator={addSeparator} events={events} />
- )}
- </table>
- </div>
- </Popup>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import GraphsTooltipsContent from './GraphsTooltipsContent';
+import GraphsTooltipsContentEvents from './GraphsTooltipsContentEvents';
+import GraphsTooltipsContentCoverage from './GraphsTooltipsContentCoverage';
+import GraphsTooltipsContentDuplication from './GraphsTooltipsContentDuplication';
+import GraphsTooltipsContentIssues from './GraphsTooltipsContentIssues';
+import { DEFAULT_GRAPH, MeasureHistory, Serie } from '../utils';
+import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
+import { Popup, PopupPlacement } from '../../../components/ui/popups';
+import { AnalysisEvent } from '../../../app/types';
+
+interface Props {
+ events: AnalysisEvent[];
+ formatValue: (tick: number | string) => string;
+ graph: string;
+ graphWidth: number;
+ measuresHistory: MeasureHistory[];
+ selectedDate: Date;
+ series: Serie[];
+ tooltipIdx: number;
+ tooltipPos: number;
+}
+
+const TOOLTIP_WIDTH = 250;
+
+export default class GraphsTooltips extends React.PureComponent<Props> {
+ renderContent() {
+ const { tooltipIdx } = this.props;
+
+ return this.props.series.map((serie, idx) => {
+ const point = serie.data[tooltipIdx];
+ if (!point || (!point.y && point.y !== 0)) {
+ return null;
+ }
+ if (this.props.graph === DEFAULT_GRAPH) {
+ return (
+ <GraphsTooltipsContentIssues
+ key={serie.name}
+ measuresHistory={this.props.measuresHistory}
+ name={serie.name}
+ style={idx.toString()}
+ tooltipIdx={tooltipIdx}
+ translatedName={serie.translatedName}
+ value={this.props.formatValue(point.y)}
+ />
+ );
+ } else {
+ return (
+ <GraphsTooltipsContent
+ key={serie.name}
+ name={serie.name}
+ style={idx.toString()}
+ translatedName={serie.translatedName}
+ value={this.props.formatValue(point.y)}
+ />
+ );
+ }
+ });
+ }
+
+ render() {
+ const { events, measuresHistory, tooltipIdx } = this.props;
+ const top = 30;
+ let left = this.props.tooltipPos + 60;
+ let placement = PopupPlacement.RightTop;
+ if (left > this.props.graphWidth - TOOLTIP_WIDTH - 50) {
+ left -= TOOLTIP_WIDTH;
+ placement = PopupPlacement.LeftTop;
+ }
+ const tooltipContent = this.renderContent().filter(Boolean);
+ const addSeparator = tooltipContent.length > 0;
+ return (
+ <Popup
+ className="disabled-pointer-events"
+ placement={placement}
+ style={{ top, left, width: TOOLTIP_WIDTH }}>
+ <div className="project-activity-graph-tooltip">
+ <div className="project-activity-graph-tooltip-title spacer-bottom">
+ <DateTimeFormatter date={this.props.selectedDate} />
+ </div>
+ <table className="width-100">
+ <tbody>{tooltipContent}</tbody>
+ {this.props.graph === 'coverage' && (
+ <GraphsTooltipsContentCoverage
+ addSeparator={addSeparator}
+ measuresHistory={measuresHistory}
+ tooltipIdx={tooltipIdx}
+ />
+ )}
+ {this.props.graph === 'duplications' && (
+ <GraphsTooltipsContentDuplication
+ addSeparator={addSeparator}
+ measuresHistory={measuresHistory}
+ tooltipIdx={tooltipIdx}
+ />
+ )}
+ {events &&
+ events.length > 0 && (
+ <GraphsTooltipsContentEvents addSeparator={addSeparator} events={events} />
+ )}
+ </table>
+ </div>
+ </Popup>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-import ChartLegendIcon from '../../../components/icons-components/ChartLegendIcon';
-
-/*::
-type Props = {
- name: string,
- style: string,
- translatedName: string,
- value: string
-};
-*/
-
-export default function GraphsTooltipsContent({ name, style, translatedName, value } /*: Props */) {
- return (
- <tr className="project-activity-graph-tooltip-line" key={name}>
- <td className="thin">
- <ChartLegendIcon
- className={classNames('spacer-right line-chart-legend', 'line-chart-legend-' + style)}
- />
- </td>
- <td className="project-activity-graph-tooltip-value text-right spacer-right thin">{value}</td>
- <td>{translatedName}</td>
- </tr>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import ChartLegendIcon from '../../../components/icons-components/ChartLegendIcon';
+
+interface Props {
+ name: string;
+ style: string;
+ translatedName: string;
+ value: string;
+}
+
+export default function GraphsTooltipsContent({ name, style, translatedName, value }: Props) {
+ return (
+ <tr className="project-activity-graph-tooltip-line" key={name}>
+ <td className="thin">
+ <ChartLegendIcon
+ className={classNames('spacer-right line-chart-legend', 'line-chart-legend-' + style)}
+ />
+ </td>
+ <td className="project-activity-graph-tooltip-value text-right spacer-right thin">{value}</td>
+ <td>{translatedName}</td>
+ </tr>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import { formatMeasure } from '../../../helpers/measures';
-import { translate } from '../../../helpers/l10n';
-/*:: import type { MeasureHistory } from '../types'; */
-
-/*::
-type Props = {
- addSeparator: boolean,
- measuresHistory: Array<MeasureHistory>,
- tooltipIdx: number
-};
-*/
-
-export default function GraphsTooltipsContentCoverage(
- { addSeparator, measuresHistory, tooltipIdx } /*: Props */
-) {
- const uncovered = measuresHistory.find(measure => measure.metric === 'uncovered_lines');
- const coverage = measuresHistory.find(measure => measure.metric === 'coverage');
- if (!uncovered || !uncovered.history[tooltipIdx] || !coverage || !coverage.history[tooltipIdx]) {
- return null;
- }
- const uncoveredValue = uncovered.history[tooltipIdx].value;
- const coverageValue = coverage.history[tooltipIdx].value;
- return (
- <tbody>
- {addSeparator && (
- <tr>
- <td className="project-activity-graph-tooltip-separator" colSpan="3">
- <hr />
- </td>
- </tr>
- )}
- {uncoveredValue && (
- <tr className="project-activity-graph-tooltip-line">
- <td
- className="project-activity-graph-tooltip-value text-right spacer-right thin"
- colSpan="2">
- {formatMeasure(uncoveredValue, 'SHORT_INT')}
- </td>
- <td>{translate('metric.uncovered_lines.name')}</td>
- </tr>
- )}
- {coverageValue && (
- <tr className="project-activity-graph-tooltip-line">
- <td
- className="project-activity-graph-tooltip-value text-right spacer-right thin"
- colSpan="2">
- {formatMeasure(coverageValue, 'PERCENT')}
- </td>
- <td>{translate('metric.coverage.name')}</td>
- </tr>
- )}
- </tbody>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { formatMeasure } from '../../../helpers/measures';
+import { translate } from '../../../helpers/l10n';
+import { MeasureHistory } from '../utils';
+
+interface Props {
+ addSeparator: boolean;
+ measuresHistory: MeasureHistory[];
+ tooltipIdx: number;
+}
+
+export default function GraphsTooltipsContentCoverage({
+ addSeparator,
+ measuresHistory,
+ tooltipIdx
+}: Props) {
+ const uncovered = measuresHistory.find(measure => measure.metric === 'uncovered_lines');
+ const coverage = measuresHistory.find(measure => measure.metric === 'coverage');
+ if (!uncovered || !uncovered.history[tooltipIdx] || !coverage || !coverage.history[tooltipIdx]) {
+ return null;
+ }
+ const uncoveredValue = uncovered.history[tooltipIdx].value;
+ const coverageValue = coverage.history[tooltipIdx].value;
+ return (
+ <tbody>
+ {addSeparator && (
+ <tr>
+ <td className="project-activity-graph-tooltip-separator" colSpan={3}>
+ <hr />
+ </td>
+ </tr>
+ )}
+ {uncoveredValue && (
+ <tr className="project-activity-graph-tooltip-line">
+ <td
+ className="project-activity-graph-tooltip-value text-right spacer-right thin"
+ colSpan={2}>
+ {formatMeasure(uncoveredValue, 'SHORT_INT')}
+ </td>
+ <td>{translate('metric.uncovered_lines.name')}</td>
+ </tr>
+ )}
+ {coverageValue && (
+ <tr className="project-activity-graph-tooltip-line">
+ <td
+ className="project-activity-graph-tooltip-value text-right spacer-right thin"
+ colSpan={2}>
+ {formatMeasure(coverageValue, 'PERCENT')}
+ </td>
+ <td>{translate('metric.coverage.name')}</td>
+ </tr>
+ )}
+ </tbody>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import { formatMeasure } from '../../../helpers/measures';
-import { translate } from '../../../helpers/l10n';
-/*:: import type { MeasureHistory } from '../types'; */
-
-/*::
-type Props = {
- addSeparator: boolean,
- measuresHistory: Array<MeasureHistory>,
- tooltipIdx: number
-};
-*/
-
-export default function GraphsTooltipsContentDuplication(
- { addSeparator, measuresHistory, tooltipIdx } /*: Props */
-) {
- const duplicationDensity = measuresHistory.find(
- measure => measure.metric === 'duplicated_lines_density'
- );
- if (!duplicationDensity || !duplicationDensity.history[tooltipIdx]) {
- return null;
- }
- const duplicationDensityValue = duplicationDensity.history[tooltipIdx].value;
- if (!duplicationDensityValue) {
- return null;
- }
- return (
- <tbody>
- {addSeparator && (
- <tr>
- <td className="project-activity-graph-tooltip-separator" colSpan="3">
- <hr />
- </td>
- </tr>
- )}
- <tr className="project-activity-graph-tooltip-line">
- <td
- className="project-activity-graph-tooltip-value text-right spacer-right thin"
- colSpan="2">
- {formatMeasure(duplicationDensityValue, 'PERCENT')}
- </td>
- <td>{translate('metric.duplicated_lines_density.name')}</td>
- </tr>
- </tbody>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { formatMeasure } from '../../../helpers/measures';
+import { translate } from '../../../helpers/l10n';
+import { MeasureHistory } from '../utils';
+
+interface Props {
+ addSeparator: boolean;
+ measuresHistory: MeasureHistory[];
+ tooltipIdx: number;
+}
+
+export default function GraphsTooltipsContentDuplication({
+ addSeparator,
+ measuresHistory,
+ tooltipIdx
+}: Props) {
+ const duplicationDensity = measuresHistory.find(
+ measure => measure.metric === 'duplicated_lines_density'
+ );
+ if (!duplicationDensity || !duplicationDensity.history[tooltipIdx]) {
+ return null;
+ }
+ const duplicationDensityValue = duplicationDensity.history[tooltipIdx].value;
+ if (!duplicationDensityValue) {
+ return null;
+ }
+ return (
+ <tbody>
+ {addSeparator && (
+ <tr>
+ <td className="project-activity-graph-tooltip-separator" colSpan={3}>
+ <hr />
+ </td>
+ </tr>
+ )}
+ <tr className="project-activity-graph-tooltip-line">
+ <td
+ className="project-activity-graph-tooltip-value text-right spacer-right thin"
+ colSpan={2}>
+ {formatMeasure(duplicationDensityValue, 'PERCENT')}
+ </td>
+ <td>{translate('metric.duplicated_lines_density.name')}</td>
+ </tr>
+ </tbody>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import ProjectEventIcon from '../../../components/icons-components/ProjectEventIcon';
-import { translate } from '../../../helpers/l10n';
-/*:: import type { Event } from '../types'; */
-
-/*::
-type Props = {
- addSeparator: boolean,
- events: Array<Event>
-};
-*/
-
-export default function GraphsTooltipsContentEvents({ addSeparator, events } /*: Props */) {
- return (
- <tbody>
- {addSeparator && (
- <tr>
- <td className="project-activity-graph-tooltip-separator" colSpan="3">
- <hr />
- </td>
- </tr>
- )}
- <tr className="project-activity-graph-tooltip-line">
- <td colSpan="3">
- <span>{translate('events')}:</span>
- {events.map(event => (
- <span className="spacer-left" key={event.key}>
- <ProjectEventIcon className={'project-activity-event-icon ' + event.category} />
- </span>
- ))}
- </td>
- </tr>
- </tbody>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import ProjectEventIcon from '../../../components/icons-components/ProjectEventIcon';
+import { translate } from '../../../helpers/l10n';
+import { AnalysisEvent } from '../../../app/types';
+
+interface Props {
+ addSeparator: boolean;
+ events: AnalysisEvent[];
+}
+
+export default function GraphsTooltipsContentEvents({ addSeparator, events }: Props) {
+ return (
+ <tbody>
+ {addSeparator && (
+ <tr>
+ <td className="project-activity-graph-tooltip-separator" colSpan={3}>
+ <hr />
+ </td>
+ </tr>
+ )}
+ <tr className="project-activity-graph-tooltip-line">
+ <td colSpan={3}>
+ <span>{translate('events')}:</span>
+ {events.map(event => (
+ <span className="spacer-left" key={event.key}>
+ <ProjectEventIcon className={'project-activity-event-icon ' + event.category} />
+ </span>
+ ))}
+ </td>
+ </tr>
+ </tbody>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-import ChartLegendIcon from '../../../components/icons-components/ChartLegendIcon';
-import Rating from '../../../components/ui/Rating';
-/*:: import type { MeasureHistory } from '../types'; */
-
-/*::
-type Props = {
- measuresHistory: Array<MeasureHistory>,
- name: string,
- style: string,
- tooltipIdx: number,
- translatedName: string,
- value: string
-};
-*/
-
-const METRIC_RATING = {
- bugs: 'reliability_rating',
- vulnerabilities: 'security_rating',
- code_smells: 'sqale_rating'
-};
-
-export default function GraphsTooltipsContentIssues(props /*: Props */) {
- const rating = props.measuresHistory.find(
- measure => measure.metric === METRIC_RATING[props.name]
- );
- if (!rating || !rating.history[props.tooltipIdx]) {
- return null;
- }
- const ratingValue = rating.history[props.tooltipIdx].value;
- return (
- <tr className="project-activity-graph-tooltip-issues-line" key={props.name}>
- <td className="thin">
- <ChartLegendIcon
- className={classNames(
- 'spacer-right line-chart-legend',
- 'line-chart-legend-' + props.style
- )}
- />
- </td>
- <td className="text-right spacer-right">
- <span className="project-activity-graph-tooltip-value">{props.value}</span>
- {ratingValue && <Rating className="spacer-left" small={true} value={ratingValue} />}
- </td>
- <td>{props.translatedName}</td>
- </tr>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import ChartLegendIcon from '../../../components/icons-components/ChartLegendIcon';
+import Rating from '../../../components/ui/Rating';
+import { MeasureHistory } from '../utils';
+
+interface Props {
+ measuresHistory: MeasureHistory[];
+ name: string;
+ style: string;
+ tooltipIdx: number;
+ translatedName: string;
+ value: string;
+}
+
+const METRIC_RATING: { [x: string]: string } = {
+ bugs: 'reliability_rating',
+ vulnerabilities: 'security_rating',
+ code_smells: 'sqale_rating' // eslint-disable-line camelcase
+};
+
+export default function GraphsTooltipsContentIssues(props: Props) {
+ const rating = props.measuresHistory.find(
+ measure => measure.metric === METRIC_RATING[props.name]
+ );
+ if (!rating || !rating.history[props.tooltipIdx]) {
+ return null;
+ }
+ const ratingValue = rating.history[props.tooltipIdx].value;
+ return (
+ <tr className="project-activity-graph-tooltip-issues-line" key={props.name}>
+ <td className="thin">
+ <ChartLegendIcon
+ className={classNames(
+ 'spacer-right line-chart-legend',
+ 'line-chart-legend-' + props.style
+ )}
+ />
+ </td>
+ <td className="text-right spacer-right">
+ <span className="project-activity-graph-tooltip-value">{props.value}</span>
+ {ratingValue && <Rating className="spacer-left" small={true} value={ratingValue} />}
+ </td>
+ <td>{props.translatedName}</td>
+ </tr>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-// $FlowFixMe
-import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
-import ZoomTimeLine from '../../../components/charts/ZoomTimeLine';
-import { hasHistoryData } from '../utils';
-/*:: import type { Serie } from '../../../components/charts/AdvancedTimeline'; */
-
-/*::
-type Props = {
- graphEndDate: ?Date,
- graphStartDate: ?Date,
- leakPeriodDate?: Date,
- loading: boolean,
- metricsType: string,
- series: Array<Serie>,
- showAreas?: boolean,
- updateGraphZoom: (from: ?Date, to: ?Date) => void
-};
-*/
-
-export default function GraphsZoom(props /*: Props */) {
- const { loading } = props;
- if (loading || !hasHistoryData(props.series)) {
- return null;
- }
-
- return (
- <div className="project-activity-graph-zoom">
- <AutoSizer disableHeight={true}>
- {({ width }) => (
- <ZoomTimeLine
- endDate={props.graphEndDate}
- height={64}
- interpolate="linear"
- leakPeriodDate={props.leakPeriodDate}
- metricType={props.metricsType}
- padding={[0, 10, 18, 60]}
- series={props.series}
- showAreas={props.showAreas}
- startDate={props.graphStartDate}
- updateZoom={props.updateGraphZoom}
- width={width}
- />
- )}
- </AutoSizer>
- </div>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer';
+import ZoomTimeLine from '../../../components/charts/ZoomTimeLine';
+import { hasHistoryData, Serie } from '../utils';
+
+interface Props {
+ graphEndDate?: Date;
+ graphStartDate?: Date;
+ leakPeriodDate?: Date;
+ loading: boolean;
+ metricsType: string;
+ series: Serie[];
+ showAreas?: boolean;
+ updateGraphZoom: (from?: Date, to?: Date) => void;
+}
+
+export default function GraphsZoom(props: Props) {
+ if (props.loading || !hasHistoryData(props.series)) {
+ return null;
+ }
+
+ return (
+ <div className="project-activity-graph-zoom">
+ <AutoSizer disableHeight={true}>
+ {({ width }) => (
+ <ZoomTimeLine
+ endDate={props.graphEndDate}
+ height={64}
+ leakPeriodDate={props.leakPeriodDate}
+ metricType={props.metricsType}
+ padding={[0, 10, 18, 60]}
+ series={props.series}
+ showAreas={props.showAreas}
+ startDate={props.graphStartDate}
+ updateZoom={props.updateGraphZoom}
+ width={width}
+ />
+ )}
+ </AutoSizer>
+ </div>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-import { throttle } from 'lodash';
-import ProjectActivityAnalysis from './ProjectActivityAnalysis';
-import DateFormatter from '../../../components/intl/DateFormatter';
-import Tooltip from '../../../components/controls/Tooltip';
-import { translate } from '../../../helpers/l10n';
-import { toShortNotSoISOString } from '../../../helpers/dates';
-import {
- activityQueryChanged,
- getAnalysesByVersionByDay,
- selectedDateQueryChanged
-} from '../utils';
-/*:: import type { RawQuery } from '../../../helpers/query'; */
-/*:: import type { Analysis, Query } from '../types'; */
-
-/*::
-type Props = {
- addCustomEvent: (analysis: string, name: string, category?: string) => Promise<*>,
- addVersion: (analysis: string, version: string) => Promise<*>,
- analyses: Array<Analysis>,
- analysesLoading: boolean,
- canAdmin: boolean,
- canDeleteAnalyses: boolean,
- className?: string,
- changeEvent: (event: string, name: string) => Promise<*>,
- deleteAnalysis: (analysis: string) => Promise<*>,
- deleteEvent: (analysis: string, event: string) => Promise<*>,
- initializing: boolean,
- project: { qualifier: string },
- query: Query,
- updateQuery: Object => void
-};
-*/
-
-export default class ProjectActivityAnalysesList extends React.PureComponent {
- /*:: analyses: HTMLCollection<HTMLElement>; */
- /*:: badges: HTMLCollection<HTMLElement>; */
- /*:: props: Props; */
- /*:: scrollContainer: HTMLElement; */
-
- constructor(props /*: Props */) {
- super(props);
- this.handleScroll = throttle(this.handleScroll, 20);
- }
-
- componentDidMount() {
- this.badges = document.getElementsByClassName('project-activity-version-badge');
- this.analyses = document.getElementsByClassName('project-activity-analysis');
- }
-
- componentDidUpdate(prevProps /*: 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)) {
- this.resetScrollTop(0, true);
- }
- }
-
- handleScroll = () => this.updateStickyBadges(true);
-
- resetScrollTop = (newScrollTop /*: number */, forceBadgeAlignement /*: ?boolean */) => {
- this.scrollContainer.scrollTop = newScrollTop;
- for (let i = 1; i < this.badges.length; i++) {
- this.badges[i].removeAttribute('originOffsetTop');
- this.badges[i].classList.remove('sticky');
- }
- this.updateStickyBadges(forceBadgeAlignement);
- };
-
- scrollToDate = (targetDate /*: ?Date */) => {
- if (!this.scrollContainer || !targetDate) {
- 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;
- }
-
- const scrollTop = this.scrollContainer.scrollTop;
- if (scrollTop == null) {
- return;
- }
-
- let newScrollTop;
- for (let i = 1; i < this.badges.length; i++) {
- const badge = this.badges[i];
- let originOffsetTop = badge.getAttribute('originOffsetTop');
- if (originOffsetTop == null) {
- // Set the originOffsetTop attribute, to avoid using getBoundingClientRect
- originOffsetTop = badge.offsetTop;
- badge.setAttribute('originOffsetTop', originOffsetTop.toString());
- }
- if (Number(originOffsetTop) < scrollTop + 18 + i * 2) {
- if (forceBadgeAlignement && !badge.classList.contains('sticky')) {
- newScrollTop = originOffsetTop;
- }
- badge.classList.add('sticky');
- } else {
- badge.classList.remove('sticky');
- }
- }
-
- if (forceBadgeAlignement && newScrollTop != null) {
- this.scrollContainer.scrollTop = newScrollTop - 6;
- }
- };
-
- updateSelectedDate = (date /*: Date */) => this.props.updateQuery({ selectedDate: date });
-
- render() {
- const byVersionByDay = getAnalysesByVersionByDay(this.props.analyses, this.props.query);
- const hasFilteredData =
- byVersionByDay.length > 1 ||
- (byVersionByDay.length === 1 && Object.keys(byVersionByDay[0].byDay).length > 0);
- if (this.props.analyses.length === 0 || !hasFilteredData) {
- return (
- <div className={this.props.className}>
- {this.props.initializing ? (
- <div className="text-center">
- <i className="spinner" />
- </div>
- ) : (
- <span className="note">{translate('no_results')}</span>
- )}
- </div>
- );
- }
-
- const firstAnalysisKey = this.props.analyses[0].key;
- const selectedDate = this.props.query.selectedDate
- ? this.props.query.selectedDate.valueOf()
- : null;
-
- return (
- <ul
- className={classNames('project-activity-versions-list', this.props.className)}
- onScroll={this.handleScroll}
- ref={element => (this.scrollContainer = element)}
- style={{ paddingTop: this.props.project.qualifier === 'TRK' ? 52 : undefined }}>
- {byVersionByDay.map((version, idx) => {
- const days = Object.keys(version.byDay);
- if (days.length <= 0) {
- return null;
- }
- return (
- <li key={version.key || 'noversion'}>
- {version.version && (
- <div className={classNames('project-activity-version-badge', { first: idx === 0 })}>
- <Tooltip
- mouseEnterDelay={0.5}
- overlay={`${translate('version')} ${version.version}`}>
- <span className="badge">{version.version}</span>
- </Tooltip>
- </div>
- )}
- <ul className="project-activity-days-list">
- {days.map(day => (
- <li
- className="project-activity-day"
- data-day={toShortNotSoISOString(Number(day))}
- key={day}>
- <div className="project-activity-date">
- <DateFormatter date={Number(day)} long={true} />
- </div>
- <ul className="project-activity-analyses-list">
- {version.byDay[day] != null &&
- version.byDay[day].map(analysis => (
- <ProjectActivityAnalysis
- addCustomEvent={this.props.addCustomEvent}
- addVersion={this.props.addVersion}
- analysis={analysis}
- canAdmin={this.props.canAdmin}
- canCreateVersion={this.props.project.qualifier === 'TRK'}
- canDeleteAnalyses={this.props.canDeleteAnalyses}
- changeEvent={this.props.changeEvent}
- deleteAnalysis={this.props.deleteAnalysis}
- deleteEvent={this.props.deleteEvent}
- isFirst={analysis.key === firstAnalysisKey}
- key={analysis.key}
- selected={analysis.date.valueOf() === selectedDate}
- updateSelectedDate={this.updateSelectedDate}
- />
- ))}
- </ul>
- </li>
- ))}
- </ul>
- </li>
- );
- })}
- {this.props.analysesLoading && (
- <li className="text-center">
- <i className="spinner" />
- </li>
- )}
- </ul>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { throttle } from 'lodash';
+import ProjectActivityAnalysis from './ProjectActivityAnalysis';
+import DateFormatter from '../../../components/intl/DateFormatter';
+import Tooltip from '../../../components/controls/Tooltip';
+import { translate } from '../../../helpers/l10n';
+import { toShortNotSoISOString } from '../../../helpers/dates';
+import {
+ activityQueryChanged,
+ getAnalysesByVersionByDay,
+ selectedDateQueryChanged,
+ Query,
+ ParsedAnalysis
+} from '../utils';
+
+interface Props {
+ addCustomEvent: (analysis: string, name: string, category?: string) => Promise<void>;
+ addVersion: (analysis: string, version: string) => Promise<void>;
+ analyses: ParsedAnalysis[];
+ analysesLoading: boolean;
+ 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;
+ project: { qualifier: string };
+ query: Query;
+ updateQuery: (changes: Partial<Query>) => void;
+}
+
+export default class ProjectActivityAnalysesList extends React.PureComponent<Props> {
+ analyses?: HTMLCollectionOf<HTMLElement>;
+ badges?: HTMLCollectionOf<HTMLElement>;
+ scrollContainer?: HTMLElement | null;
+
+ constructor(props: Props) {
+ super(props);
+ this.handleScroll = throttle(this.handleScroll, 20);
+ }
+
+ componentDidMount() {
+ this.badges = document.getElementsByClassName(
+ 'project-activity-version-badge'
+ ) as HTMLCollectionOf<HTMLElement>;
+ this.analyses = document.getElementsByClassName(
+ 'project-activity-analysis'
+ ) as HTMLCollectionOf<HTMLElement>;
+ }
+
+ componentDidUpdate(prevProps: 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)) {
+ this.resetScrollTop(0, true);
+ }
+ }
+
+ handleScroll = () => this.updateStickyBadges(true);
+
+ resetScrollTop = (newScrollTop: number, forceBadgeAlignement?: boolean) => {
+ if (this.scrollContainer) {
+ this.scrollContainer.scrollTop = newScrollTop;
+ }
+ if (this.badges) {
+ for (let i = 1; i < this.badges.length; i++) {
+ this.badges[i].removeAttribute('originOffsetTop');
+ this.badges[i].classList.remove('sticky');
+ }
+ }
+ 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;
+ }
+
+ const { scrollTop } = this.scrollContainer;
+ if (scrollTop == null) {
+ return;
+ }
+
+ let newScrollTop;
+ for (let i = 1; i < this.badges.length; i++) {
+ const badge = this.badges[i];
+ let originOffsetTop = badge.getAttribute('originOffsetTop');
+ if (originOffsetTop == null) {
+ // Set the originOffsetTop attribute, to avoid using getBoundingClientRect
+ originOffsetTop = String(badge.offsetTop);
+ badge.setAttribute('originOffsetTop', originOffsetTop);
+ }
+ if (Number(originOffsetTop) < scrollTop + 18 + i * 2) {
+ if (forceBadgeAlignement && !badge.classList.contains('sticky')) {
+ newScrollTop = originOffsetTop;
+ }
+ badge.classList.add('sticky');
+ } else {
+ badge.classList.remove('sticky');
+ }
+ }
+
+ if (forceBadgeAlignement && newScrollTop != null) {
+ this.scrollContainer.scrollTop = Number(newScrollTop) - 6;
+ }
+ };
+
+ updateSelectedDate = (date: Date) => {
+ this.props.updateQuery({ selectedDate: date });
+ };
+
+ render() {
+ const byVersionByDay = getAnalysesByVersionByDay(this.props.analyses, this.props.query);
+ const hasFilteredData =
+ byVersionByDay.length > 1 ||
+ (byVersionByDay.length === 1 && Object.keys(byVersionByDay[0].byDay).length > 0);
+ if (this.props.analyses.length === 0 || !hasFilteredData) {
+ return (
+ <div className={this.props.className}>
+ {this.props.initializing ? (
+ <div className="text-center">
+ <i className="spinner" />
+ </div>
+ ) : (
+ <span className="note">{translate('no_results')}</span>
+ )}
+ </div>
+ );
+ }
+
+ const firstAnalysisKey = this.props.analyses[0].key;
+ const selectedDate = this.props.query.selectedDate
+ ? this.props.query.selectedDate.valueOf()
+ : null;
+
+ return (
+ <ul
+ className={classNames('project-activity-versions-list', this.props.className)}
+ onScroll={this.handleScroll}
+ ref={element => (this.scrollContainer = element)}
+ style={{ paddingTop: this.props.project.qualifier === 'TRK' ? 52 : undefined }}>
+ {byVersionByDay.map((version, idx) => {
+ const days = Object.keys(version.byDay);
+ if (days.length <= 0) {
+ return null;
+ }
+ return (
+ <li key={version.key || 'noversion'}>
+ {version.version && (
+ <div className={classNames('project-activity-version-badge', { first: idx === 0 })}>
+ <Tooltip
+ mouseEnterDelay={0.5}
+ overlay={`${translate('version')} ${version.version}`}>
+ <span className="badge">{version.version}</span>
+ </Tooltip>
+ </div>
+ )}
+ <ul className="project-activity-days-list">
+ {days.map(day => (
+ <li
+ className="project-activity-day"
+ data-day={toShortNotSoISOString(Number(day))}
+ key={day}>
+ <div className="project-activity-date">
+ <DateFormatter date={Number(day)} long={true} />
+ </div>
+ <ul className="project-activity-analyses-list">
+ {version.byDay[day] != null &&
+ version.byDay[day].map(analysis => (
+ <ProjectActivityAnalysis
+ addCustomEvent={this.props.addCustomEvent}
+ addVersion={this.props.addVersion}
+ analysis={analysis}
+ canAdmin={this.props.canAdmin}
+ canCreateVersion={this.props.project.qualifier === 'TRK'}
+ canDeleteAnalyses={this.props.canDeleteAnalyses}
+ changeEvent={this.props.changeEvent}
+ deleteAnalysis={this.props.deleteAnalysis}
+ deleteEvent={this.props.deleteEvent}
+ isFirst={analysis.key === firstAnalysisKey}
+ key={analysis.key}
+ selected={analysis.date.valueOf() === selectedDate}
+ updateSelectedDate={this.updateSelectedDate}
+ />
+ ))}
+ </ul>
+ </li>
+ ))}
+ </ul>
+ </li>
+ );
+ })}
+ {this.props.analysesLoading && (
+ <li className="text-center">
+ <i className="spinner" />
+ </li>
+ )}
+ </ul>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-import Events from './Events';
-import AddEventForm from './forms/AddEventForm';
-import RemoveAnalysisForm from './forms/RemoveAnalysisForm';
-import TimeTooltipFormatter from '../../../components/intl/TimeTooltipFormatter';
-import ActionsDropdown, {
- ActionsDropdownDivider,
- ActionsDropdownItem
-} from '../../../components/controls/ActionsDropdown';
-import { translate } from '../../../helpers/l10n';
-/*:: import type { Analysis } from '../types'; */
-
-/*::
-type Props = {
- addCustomEvent: (analysis: string, name: string, category?: string) => Promise<*>,
- addVersion: (analysis: string, version: string) => Promise<*>,
- analysis: Analysis,
- canAdmin: boolean,
- canDeleteAnalyses: boolean,
- canCreateVersion: boolean,
- changeEvent: (event: string, name: string) => Promise<*>,
- deleteAnalysis: (analysis: string) => Promise<*>,
- deleteEvent: (analysis: string, event: string) => Promise<*>,
- isFirst: boolean,
- selected: boolean,
- updateSelectedDate: Date => void
-};
-
-type State = {
- addEventForm: bool,
- addVersionForm: bool,
- removeAnalysisForm: bool
-}
-*/
-
-export default class ProjectActivityAnalysis extends React.PureComponent {
- mounted /*: boolean */ = false;
- /*:: props: Props; */
- 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 = (e /*: Event */) => e.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 });
- }
- };
-
- handleAddVersionClick = () => {
- this.setState({ addVersionForm: true });
- };
-
- closeAddVersionForm = () => {
- if (this.mounted) {
- this.setState({ addVersionForm: false });
- }
- };
-
- render() {
- const { analysis, isFirst, canAdmin } = this.props;
- const { date, events } = analysis;
- const analysisTitle = translate('project_activity.analysis');
- const hasVersion = events.find(event => event.category === 'VERSION') != null;
-
- const canAddVersion = canAdmin && !hasVersion && this.props.canCreateVersion;
- const canAddEvent = canAdmin;
- const canDeleteAnalyses = this.props.canDeleteAnalyses && !isFirst;
-
- return (
- <li
- className={classNames('project-activity-analysis clearfix', {
- selected: this.props.selected
- })}
- data-date={date.valueOf()}
- onClick={this.handleClick}
- tabIndex="0">
- <div className="project-activity-time spacer-right">
- <TimeTooltipFormatter className="text-middle" date={date} />
- </div>
- <div className="project-activity-analysis-icon spacer-right" title={analysisTitle} />
-
- {(canAddVersion || canAddEvent || canDeleteAnalyses) && (
- <div className="project-activity-analysis-actions big-spacer-right">
- <ActionsDropdown small={true} toggleClassName="js-analysis-actions">
- {canAddVersion && (
- <ActionsDropdownItem className="js-add-event" onClick={this.handleAddVersionClick}>
- {translate('project_activity.add_version')}
- </ActionsDropdownItem>
- )}
- {canAddEvent && (
- <ActionsDropdownItem className="js-add-event" onClick={this.handleAddEventClick}>
- {translate('project_activity.add_custom_event')}
- </ActionsDropdownItem>
- )}
- {(canAddVersion || canAddEvent) && canDeleteAnalyses && <ActionsDropdownDivider />}
- {canDeleteAnalyses && (
- <ActionsDropdownItem
- className="js-delete-analysis"
- destructive={true}
- onClick={this.handleRemoveAnalysisClick}>
- {translate('project_activity.delete_analysis')}
- </ActionsDropdownItem>
- )}
- </ActionsDropdown>
-
- {this.state.addVersionForm && (
- <AddEventForm
- addEvent={this.props.addVersion}
- addEventButtonText="project_activity.add_version"
- analysis={analysis}
- onClose={this.closeAddVersionForm}
- />
- )}
-
- {this.state.addEventForm && (
- <AddEventForm
- addEvent={this.props.addCustomEvent}
- addEventButtonText="project_activity.add_custom_event"
- analysis={analysis}
- onClose={this.closeAddEventForm}
- />
- )}
-
- {this.state.removeAnalysisForm && (
- <RemoveAnalysisForm
- analysis={analysis}
- deleteAnalysis={this.props.deleteAnalysis}
- onClose={this.closeRemoveAnalysisForm}
- />
- )}
- </div>
- )}
-
- {events.length > 0 && (
- <Events
- analysis={analysis.key}
- canAdmin={canAdmin}
- canDeleteAnalyses={this.props.canDeleteAnalyses}
- changeEvent={this.props.changeEvent}
- deleteEvent={this.props.deleteEvent}
- events={events}
- isFirst={this.props.isFirst}
- />
- )}
- </li>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import Events from './Events';
+import AddEventForm from './forms/AddEventForm';
+import RemoveAnalysisForm from './forms/RemoveAnalysisForm';
+import TimeTooltipFormatter from '../../../components/intl/TimeTooltipFormatter';
+import ActionsDropdown, {
+ ActionsDropdownDivider,
+ ActionsDropdownItem
+} from '../../../components/controls/ActionsDropdown';
+import { translate } from '../../../helpers/l10n';
+import { ParsedAnalysis } from '../utils';
+
+interface Props {
+ addCustomEvent: (analysis: string, name: string, category?: string) => Promise<void>;
+ addVersion: (analysis: string, version: string) => Promise<void>;
+ analysis: ParsedAnalysis;
+ canAdmin?: boolean;
+ canDeleteAnalyses?: boolean;
+ canCreateVersion: boolean;
+ changeEvent: (event: string, name: string) => Promise<void>;
+ deleteAnalysis: (analysis: string) => Promise<void>;
+ deleteEvent: (analysis: string, event: string) => Promise<void>;
+ isFirst: boolean;
+ 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 });
+ }
+ };
+
+ handleAddVersionClick = () => {
+ this.setState({ addVersionForm: true });
+ };
+
+ closeAddVersionForm = () => {
+ if (this.mounted) {
+ this.setState({ addVersionForm: false });
+ }
+ };
+
+ render() {
+ const { analysis, isFirst, canAdmin } = this.props;
+ const { date, events } = analysis;
+ const analysisTitle = translate('project_activity.analysis');
+ const hasVersion = events.find(event => event.category === 'VERSION') != null;
+
+ const canAddVersion = canAdmin && !hasVersion && this.props.canCreateVersion;
+ const canAddEvent = canAdmin;
+ const canDeleteAnalyses = this.props.canDeleteAnalyses && !isFirst;
+
+ return (
+ <li
+ className={classNames('project-activity-analysis clearfix', {
+ selected: this.props.selected
+ })}
+ data-date={date.valueOf()}
+ onClick={this.handleClick}
+ tabIndex={0}>
+ <div className="project-activity-time spacer-right">
+ <TimeTooltipFormatter className="text-middle" date={date} />
+ </div>
+ <div className="project-activity-analysis-icon spacer-right" title={analysisTitle} />
+
+ {(canAddVersion || canAddEvent || canDeleteAnalyses) && (
+ <div className="project-activity-analysis-actions big-spacer-right">
+ <ActionsDropdown small={true} toggleClassName="js-analysis-actions">
+ {canAddVersion && (
+ <ActionsDropdownItem className="js-add-event" onClick={this.handleAddVersionClick}>
+ {translate('project_activity.add_version')}
+ </ActionsDropdownItem>
+ )}
+ {canAddEvent && (
+ <ActionsDropdownItem className="js-add-event" onClick={this.handleAddEventClick}>
+ {translate('project_activity.add_custom_event')}
+ </ActionsDropdownItem>
+ )}
+ {(canAddVersion || canAddEvent) && canDeleteAnalyses && <ActionsDropdownDivider />}
+ {canDeleteAnalyses && (
+ <ActionsDropdownItem
+ className="js-delete-analysis"
+ destructive={true}
+ onClick={this.handleRemoveAnalysisClick}>
+ {translate('project_activity.delete_analysis')}
+ </ActionsDropdownItem>
+ )}
+ </ActionsDropdown>
+
+ {this.state.addVersionForm && (
+ <AddEventForm
+ addEvent={this.props.addVersion}
+ addEventButtonText="project_activity.add_version"
+ analysis={analysis}
+ onClose={this.closeAddVersionForm}
+ />
+ )}
+
+ {this.state.addEventForm && (
+ <AddEventForm
+ addEvent={this.props.addCustomEvent}
+ addEventButtonText="project_activity.add_custom_event"
+ analysis={analysis}
+ onClose={this.closeAddEventForm}
+ />
+ )}
+
+ {this.state.removeAnalysisForm && (
+ <RemoveAnalysisForm
+ analysis={analysis}
+ deleteAnalysis={this.props.deleteAnalysis}
+ onClose={this.closeRemoveAnalysisForm}
+ />
+ )}
+ </div>
+ )}
+
+ {events.length > 0 && (
+ <Events
+ analysis={analysis.key}
+ canAdmin={canAdmin}
+ changeEvent={this.props.changeEvent}
+ deleteEvent={this.props.deleteEvent}
+ events={events}
+ isFirst={this.props.isFirst}
+ />
+ )}
+ </li>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import Helmet from 'react-helmet';
-import ProjectActivityPageHeader from './ProjectActivityPageHeader';
-import ProjectActivityAnalysesList from './ProjectActivityAnalysesList';
-import ProjectActivityGraphs from './ProjectActivityGraphs';
-import { parseDate } from '../../../helpers/dates';
-import { translate } from '../../../helpers/l10n';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
-import './projectActivity.css';
-/*:: import type { Analysis, MeasureHistory, Metric, Query } from '../types'; */
-
-/*::
-type Props = {
- addCustomEvent: (analysis: string, name: string, category?: string) => Promise<*>,
- addVersion: (analysis: string, version: string) => Promise<*>,
- analyses: Array<Analysis>,
- analysesLoading: boolean,
- changeEvent: (event: string, name: string) => Promise<*>,
- deleteAnalysis: (analysis: string) => Promise<*>,
- deleteEvent: (analysis: string, event: string) => Promise<*>,
- graphLoading: boolean,
- initializing: boolean,
- project: {
- configuration?: { showHistory: boolean },
- key: string,
- leakPeriodDate?: string,
- qualifier: string
- },
- metrics: Array<Metric>,
- measuresHistory: Array<MeasureHistory>,
- query: Query,
- updateQuery: (newQuery: Query) => void
-};
-*/
-
-export default function ProjectActivityApp(props /*: Props */) {
- const { analyses, measuresHistory, query } = props;
- const { configuration } = props.project;
- const canAdmin =
- (props.project.qualifier === 'TRK' || props.project.qualifier === 'APP') &&
- (configuration ? configuration.showHistory : false);
- const canDeleteAnalyses = configuration ? configuration.showHistory : false;
- return (
- <div className="page page-limited" id="project-activity">
- <Suggestions suggestions="project_activity" />
- <Helmet title={translate('project_activity.page')} />
-
- <ProjectActivityPageHeader
- category={query.category}
- from={query.from}
- project={props.project}
- to={query.to}
- updateQuery={props.updateQuery}
- />
-
- <div className="layout-page project-activity-page">
- <div className="layout-page-side-outer project-activity-page-side-outer boxed-group">
- <ProjectActivityAnalysesList
- addCustomEvent={props.addCustomEvent}
- addVersion={props.addVersion}
- analyses={analyses}
- analysesLoading={props.analysesLoading}
- canAdmin={canAdmin}
- canDeleteAnalyses={canDeleteAnalyses}
- changeEvent={props.changeEvent}
- className="boxed-group-inner"
- deleteAnalysis={props.deleteAnalysis}
- deleteEvent={props.deleteEvent}
- initializing={props.initializing}
- project={props.project}
- query={props.query}
- updateQuery={props.updateQuery}
- />
- </div>
- <div className="project-activity-layout-page-main">
- <ProjectActivityGraphs
- analyses={analyses}
- leakPeriodDate={
- props.project.leakPeriodDate ? parseDate(props.project.leakPeriodDate) : undefined
- }
- loading={props.graphLoading}
- measuresHistory={measuresHistory}
- metrics={props.metrics}
- query={query}
- updateQuery={props.updateQuery}
- />
- </div>
- </div>
- </div>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import Helmet from 'react-helmet';
+import ProjectActivityPageHeader from './ProjectActivityPageHeader';
+import ProjectActivityAnalysesList from './ProjectActivityAnalysesList';
+import ProjectActivityGraphs from './ProjectActivityGraphs';
+import { MeasureHistory, Query, ParsedAnalysis } from '../utils';
+import { parseDate } from '../../../helpers/dates';
+import { translate } from '../../../helpers/l10n';
+import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import { Metric, Component } from '../../../app/types';
+import './projectActivity.css';
+
+interface Props {
+ addCustomEvent: (analysis: string, name: string, category?: string) => Promise<void>;
+ addVersion: (analysis: string, version: string) => Promise<void>;
+ analyses: ParsedAnalysis[];
+ analysesLoading: boolean;
+ changeEvent: (event: string, name: string) => Promise<void>;
+ deleteAnalysis: (analysis: string) => Promise<void>;
+ deleteEvent: (analysis: string, event: string) => Promise<void>;
+ graphLoading: boolean;
+ initializing: boolean;
+ project: Pick<Component, 'configuration' | 'leakPeriodDate' | 'qualifier'>;
+ metrics: Metric[];
+ measuresHistory: MeasureHistory[];
+ query: Query;
+ updateQuery: (changes: Partial<Query>) => void;
+}
+
+export default function ProjectActivityApp(props: Props) {
+ const { analyses, measuresHistory, query } = props;
+ const { configuration } = props.project;
+ const canAdmin =
+ (props.project.qualifier === 'TRK' || props.project.qualifier === 'APP') &&
+ (configuration ? configuration.showHistory : false);
+ const canDeleteAnalyses = configuration ? configuration.showHistory : false;
+ return (
+ <div className="page page-limited" id="project-activity">
+ <Suggestions suggestions="project_activity" />
+ <Helmet title={translate('project_activity.page')} />
+
+ <ProjectActivityPageHeader
+ category={query.category}
+ from={query.from}
+ project={props.project}
+ to={query.to}
+ updateQuery={props.updateQuery}
+ />
+
+ <div className="layout-page project-activity-page">
+ <div className="layout-page-side-outer project-activity-page-side-outer boxed-group">
+ <ProjectActivityAnalysesList
+ addCustomEvent={props.addCustomEvent}
+ addVersion={props.addVersion}
+ analyses={analyses}
+ analysesLoading={props.analysesLoading}
+ canAdmin={canAdmin}
+ canDeleteAnalyses={canDeleteAnalyses}
+ changeEvent={props.changeEvent}
+ className="boxed-group-inner"
+ deleteAnalysis={props.deleteAnalysis}
+ deleteEvent={props.deleteEvent}
+ initializing={props.initializing}
+ project={props.project}
+ query={props.query}
+ updateQuery={props.updateQuery}
+ />
+ </div>
+ <div className="project-activity-layout-page-main">
+ <ProjectActivityGraphs
+ analyses={analyses}
+ leakPeriodDate={
+ props.project.leakPeriodDate ? parseDate(props.project.leakPeriodDate) : undefined
+ }
+ loading={props.graphLoading}
+ measuresHistory={measuresHistory}
+ metrics={props.metrics}
+ query={query}
+ updateQuery={props.updateQuery}
+ />
+ </div>
+ </div>
+ </div>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import PropTypes from 'prop-types';
-import ProjectActivityApp from './ProjectActivityApp';
-import throwGlobalError from '../../../app/utils/throwGlobalError';
-import { getAllTimeMachineData } from '../../../api/time-machine';
-import { getAllMetrics } from '../../../api/metrics';
-import * as api from '../../../api/projectActivity';
-import * as actions from '../actions';
-import { getBranchLikeQuery } from '../../../helpers/branches';
-import { parseDate } from '../../../helpers/dates';
-import { get } from '../../../helpers/storage';
-import {
- customMetricsChanged,
- DEFAULT_GRAPH,
- getHistoryMetrics,
- isCustomGraph,
- parseQuery,
- PROJECT_ACTIVITY_GRAPH,
- PROJECT_ACTIVITY_GRAPH_CUSTOM,
- serializeQuery,
- serializeUrlQuery
-} from '../utils';
-/*:: import type { RawQuery } from '../../../helpers/query'; */
-/*:: import type { Analysis, MeasureHistory, Metric, Paging, Query } from '../types'; */
-
-/*::
-type Component = {
- breadcrumbs: Array<{ key: string, qualifier: string}>,
- configuration?: { showHistory: boolean },
- key: string,
- leakPeriodDate?: string,
- qualifier: string
-};
-
-type Props = {
- branchLike?: { id?: string; name: string },
- location: { pathname: string, query: RawQuery },
- component: Component
-};
-*/
-
-/*::
-export type State = {
- analyses: Array<Analysis>,
- analysesLoading: boolean,
- graphLoading: boolean,
- initialized: boolean,
- metrics: Array<Metric>,
- measuresHistory: Array<MeasureHistory>,
- paging?: Paging,
- query: Query
-};
-*/
-
-export default class ProjectActivityAppContainer extends React.PureComponent {
- /*:: mounted: boolean; */
- /*:: props: Props; */
- /*:: state: State; */
-
- static contextTypes = {
- router: PropTypes.object.isRequired
- };
-
- constructor(props /*: Props */) {
- super(props);
- this.state = {
- analyses: [],
- analysesLoading: false,
- graphLoading: true,
- initialized: false,
- measuresHistory: [],
- metrics: [],
- query: parseQuery(props.location.query)
- };
- }
-
- componentDidMount() {
- this.mounted = true;
- if (this.shouldRedirect()) {
- const newQuery = { ...this.state.query, graph: get(PROJECT_ACTIVITY_GRAPH) || 'issues' };
- if (isCustomGraph(newQuery.graph)) {
- const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM);
- newQuery.customMetrics = customGraphs ? customGraphs.split(',') : [];
- }
- this.context.router.replace({
- pathname: this.props.location.pathname,
- query: {
- ...serializeUrlQuery(newQuery),
- ...getBranchLikeQuery(this.props.branchLike)
- }
- });
- } else {
- this.firstLoadData(this.state.query, this.props.component);
- }
- }
-
- componentWillReceiveProps(nextProps /*: Props */) {
- if (nextProps.location.query !== this.props.location.query) {
- const query = parseQuery(nextProps.location.query);
- if (query.graph !== this.state.query.graph || customMetricsChanged(this.state.query, query)) {
- if (this.state.initialized) {
- this.updateGraphData(query.graph, query.customMetrics);
- } else {
- this.firstLoadData(query, nextProps.component);
- }
- }
- this.setState({ query });
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- addCustomEvent = (analysis /*: string */, name /*: string */, category /*: ?string */) =>
- api
- .createEvent(analysis, name, category)
- .then(
- ({ analysis, ...event }) =>
- this.mounted && this.setState(actions.addCustomEvent(analysis, event))
- );
-
- addVersion = (analysis /*: string */, version /*: string */) =>
- this.addCustomEvent(analysis, version, 'VERSION');
-
- changeEvent = (event /*: string */, name /*: string */) =>
- api
- .changeEvent(event, name)
- .then(
- ({ analysis, ...event }) =>
- this.mounted && this.setState(actions.changeEvent(analysis, event))
- );
-
- deleteAnalysis = (analysis /*: string */) =>
- api.deleteAnalysis(analysis).then(() => {
- if (this.mounted) {
- this.updateGraphData(this.state.query.graph, this.state.query.customMetrics);
- this.setState(actions.deleteAnalysis(analysis));
- }
- });
-
- deleteEvent = (analysis /*: string */, event /*: string */) =>
- api
- .deleteEvent(event)
- .then(() => this.mounted && this.setState(actions.deleteEvent(analysis, event)));
-
- fetchActivity = (
- project /*: string */,
- p /*: number */,
- ps /*: number */,
- additional /*: ?{
- [string]: string
- } */
- ) => {
- const parameters = { project, p, ps, ...getBranchLikeQuery(this.props.branchLike) };
- return api
- .getProjectActivity({ ...additional, ...parameters })
- .then(({ analyses, paging }) => ({
- analyses: analyses.map(analysis => ({ ...analysis, date: parseDate(analysis.date) })),
- paging
- }));
- };
-
- fetchMeasuresHistory = (metrics /*: Array<string> */) => {
- if (metrics.length <= 0) {
- return Promise.resolve([]);
- }
- return getAllTimeMachineData({
- component: this.props.component.key,
- metrics: metrics.join(),
- ...getBranchLikeQuery(this.props.branchLike)
- }).then(
- ({ measures }) =>
- measures.map(measure => ({
- metric: measure.metric,
- history: measure.history.map(analysis => ({
- date: parseDate(analysis.date),
- value: analysis.value
- }))
- })),
- () => {}
- );
- };
-
- loadAllActivities = (
- project /*: string */,
- prevResult /*: ?{ analyses: Array<Analysis>, paging: Paging } */
- ) => {
- if (
- prevResult &&
- prevResult.paging.pageIndex * prevResult.paging.pageSize >= prevResult.paging.total
- ) {
- return Promise.resolve(prevResult);
- }
- const nextPage = prevResult ? prevResult.paging.pageIndex + 1 : 1;
- return this.fetchActivity(project, nextPage, 500).then(result => {
- if (!prevResult) {
- return this.loadAllActivities(project, result);
- }
- return this.loadAllActivities(project, {
- analyses: prevResult.analyses.concat(result.analyses),
- paging: result.paging
- });
- });
- };
-
- getTopLevelComponent = (component /*: Component */) => {
- let current = component.breadcrumbs.length - 1;
- while (
- current > 0 &&
- !['TRK', 'VW', 'APP'].includes(component.breadcrumbs[current].qualifier)
- ) {
- current--;
- }
- return component.breadcrumbs[current].key;
- };
-
- firstLoadData(query /*: Query */, component /*: Component */) {
- const graphMetrics = getHistoryMetrics(query.graph, query.customMetrics);
- const topLevelComponent = this.getTopLevelComponent(component);
- Promise.all([
- this.fetchActivity(topLevelComponent, 1, 100, serializeQuery(query)),
- getAllMetrics(),
- this.fetchMeasuresHistory(graphMetrics)
- ]).then(
- response => {
- if (this.mounted) {
- this.setState({
- analyses: response[0].analyses,
- analysesLoading: true,
- graphLoading: false,
- initialized: true,
- measuresHistory: response[2],
- metrics: response[1],
- paging: response[0].paging
- });
-
- this.loadAllActivities(topLevelComponent).then(({ analyses, paging }) => {
- if (this.mounted) {
- this.setState({
- analyses,
- analysesLoading: false,
- paging
- });
- }
- });
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ initialized: true, analysesLoading: false, graphLoading: false });
- }
- }
- );
- }
-
- updateGraphData = (graph /*: string */, customMetrics /*: Array<string> */) => {
- const graphMetrics = getHistoryMetrics(graph, customMetrics);
- this.setState({ graphLoading: true });
- this.fetchMeasuresHistory(graphMetrics).then(
- (measuresHistory /*: Array<MeasureHistory> */) => {
- if (this.mounted) {
- this.setState({ graphLoading: false, measuresHistory });
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ graphLoading: false, measuresHistory: [] });
- }
- }
- );
- };
-
- updateQuery = (newQuery /*: Query */) => {
- const query = serializeUrlQuery({
- ...this.state.query,
- ...newQuery
- });
- this.context.router.push({
- pathname: this.props.location.pathname,
- query: {
- ...query,
- ...getBranchLikeQuery(this.props.branchLike),
- id: this.props.component.key
- }
- });
- };
-
- shouldRedirect = () => {
- const locationQuery = this.props.location.query;
- if (!locationQuery) {
- return false;
- }
- const filtered = Object.keys(locationQuery).some(
- key => key !== 'id' && locationQuery[key] !== ''
- );
-
- const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM);
- const graph = get(PROJECT_ACTIVITY_GRAPH) || 'issues';
- const emptyCustomGraph =
- isCustomGraph(graph) && customGraphs && customGraphs.split(',').length <= 0;
-
- // if there is no filter, but there are saved preferences in the localStorage
- // also don't redirect to custom if there is no metrics selected for it
- return !filtered && graph != null && graph !== DEFAULT_GRAPH && !emptyCustomGraph;
- };
-
- render() {
- if (this.shouldRedirect()) {
- return null;
- }
-
- return (
- <ProjectActivityApp
- addCustomEvent={this.addCustomEvent}
- addVersion={this.addVersion}
- analyses={this.state.analyses}
- analysesLoading={this.state.analysesLoading}
- changeEvent={this.changeEvent}
- deleteAnalysis={this.deleteAnalysis}
- deleteEvent={this.deleteEvent}
- graphLoading={!this.state.initialized || this.state.graphLoading}
- initializing={!this.state.initialized}
- measuresHistory={this.state.measuresHistory}
- metrics={this.state.metrics}
- project={this.props.component}
- query={this.state.query}
- updateQuery={this.updateQuery}
- />
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { InjectedRouter } from 'react-router';
+import { Location } from 'history';
+import ProjectActivityApp from './ProjectActivityApp';
+import { getAllTimeMachineData } from '../../../api/time-machine';
+import { getAllMetrics } from '../../../api/metrics';
+import * as api from '../../../api/projectActivity';
+import * as actions from '../actions';
+import { getBranchLikeQuery } from '../../../helpers/branches';
+import { parseDate } from '../../../helpers/dates';
+import { get } from '../../../helpers/storage';
+import {
+ customMetricsChanged,
+ DEFAULT_GRAPH,
+ getHistoryMetrics,
+ isCustomGraph,
+ parseQuery,
+ PROJECT_ACTIVITY_GRAPH,
+ PROJECT_ACTIVITY_GRAPH_CUSTOM,
+ serializeQuery,
+ serializeUrlQuery,
+ MeasureHistory,
+ Query,
+ ParsedAnalysis
+} from '../utils';
+import { Metric, Paging, BranchLike, Component } from '../../../app/types';
+import { RawQuery } from '../../../helpers/query';
+
+interface Props {
+ branchLike?: BranchLike;
+ component: Component;
+ location: Location;
+ router: Pick<InjectedRouter, 'push' | 'replace'>;
+}
+
+export interface State {
+ analyses: ParsedAnalysis[];
+ analysesLoading: boolean;
+ graphLoading: boolean;
+ initialized: boolean;
+ metrics: Metric[];
+ measuresHistory: MeasureHistory[];
+ paging?: Paging;
+ query: Query;
+}
+
+export default class ProjectActivityAppContainer extends React.PureComponent<Props, State> {
+ mounted = false;
+
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ analyses: [],
+ analysesLoading: false,
+ graphLoading: true,
+ initialized: false,
+ measuresHistory: [],
+ metrics: [],
+ query: parseQuery(props.location.query)
+ };
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ if (this.shouldRedirect()) {
+ const newQuery = { ...this.state.query, graph: get(PROJECT_ACTIVITY_GRAPH) || 'issues' };
+ if (isCustomGraph(newQuery.graph)) {
+ const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM);
+ newQuery.customMetrics = customGraphs ? customGraphs.split(',') : [];
+ }
+ this.props.router.replace({
+ pathname: this.props.location.pathname,
+ query: {
+ ...serializeUrlQuery(newQuery),
+ ...getBranchLikeQuery(this.props.branchLike)
+ }
+ });
+ } else {
+ this.firstLoadData(this.state.query, this.props.component);
+ }
+ }
+
+ componentWillReceiveProps(nextProps: Props) {
+ if (nextProps.location.query !== this.props.location.query) {
+ const query = parseQuery(nextProps.location.query);
+ if (query.graph !== this.state.query.graph || customMetricsChanged(this.state.query, query)) {
+ if (this.state.initialized) {
+ this.updateGraphData(query.graph, query.customMetrics);
+ } else {
+ this.firstLoadData(query, nextProps.component);
+ }
+ }
+ this.setState({ query });
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ addCustomEvent = (analysis: string, name: string, category?: string) => {
+ return api.createEvent(analysis, name, category).then(({ analysis, ...event }) => {
+ if (this.mounted) {
+ this.setState(actions.addCustomEvent(analysis, event));
+ }
+ });
+ };
+
+ addVersion = (analysis: string, version: string) => {
+ return this.addCustomEvent(analysis, version, 'VERSION');
+ };
+
+ changeEvent = (event: string, name: string) => {
+ return api.changeEvent(event, name).then(({ analysis, ...event }) => {
+ if (this.mounted) {
+ this.setState(actions.changeEvent(analysis, event));
+ }
+ });
+ };
+
+ deleteAnalysis = (analysis: string) => {
+ return api.deleteAnalysis(analysis).then(() => {
+ if (this.mounted) {
+ this.updateGraphData(this.state.query.graph, this.state.query.customMetrics);
+ this.setState(actions.deleteAnalysis(analysis));
+ }
+ });
+ };
+
+ deleteEvent = (analysis: string, event: string) => {
+ return api.deleteEvent(event).then(() => {
+ if (this.mounted) {
+ this.setState(actions.deleteEvent(analysis, event));
+ }
+ });
+ };
+
+ fetchActivity = (project: string, p: number, ps: number, additional?: RawQuery) => {
+ const parameters = { project, p, ps, ...getBranchLikeQuery(this.props.branchLike) };
+ return api
+ .getProjectActivity({ ...additional, ...parameters })
+ .then(({ analyses, paging }) => ({
+ analyses: analyses.map(analysis => ({
+ ...analysis,
+ date: parseDate(analysis.date)
+ })) as ParsedAnalysis[],
+ paging
+ }));
+ };
+
+ fetchMeasuresHistory = (metrics: string[]): Promise<MeasureHistory[]> => {
+ if (metrics.length <= 0) {
+ return Promise.resolve([]);
+ }
+ return getAllTimeMachineData({
+ component: this.props.component.key,
+ metrics: metrics.join(),
+ ...getBranchLikeQuery(this.props.branchLike)
+ }).then(({ measures }) =>
+ measures.map(measure => ({
+ metric: measure.metric,
+ history: measure.history.map(analysis => ({
+ date: parseDate(analysis.date),
+ value: analysis.value!
+ }))
+ }))
+ );
+ };
+
+ loadAllActivities = (
+ project: string,
+ prevResult?: { analyses: ParsedAnalysis[]; paging: Paging }
+ ): Promise<{ analyses: ParsedAnalysis[]; paging: Paging }> => {
+ if (
+ prevResult &&
+ prevResult.paging.pageIndex * prevResult.paging.pageSize >= prevResult.paging.total
+ ) {
+ return Promise.resolve(prevResult);
+ }
+ const nextPage = prevResult ? prevResult.paging.pageIndex + 1 : 1;
+ return this.fetchActivity(project, nextPage, 500).then(result => {
+ if (!prevResult) {
+ return this.loadAllActivities(project, result);
+ }
+ return this.loadAllActivities(project, {
+ analyses: prevResult.analyses.concat(result.analyses),
+ paging: result.paging
+ });
+ });
+ };
+
+ getTopLevelComponent = (component: Component) => {
+ let current = component.breadcrumbs.length - 1;
+ while (
+ current > 0 &&
+ !['TRK', 'VW', 'APP'].includes(component.breadcrumbs[current].qualifier)
+ ) {
+ current--;
+ }
+ return component.breadcrumbs[current].key;
+ };
+
+ firstLoadData(query: Query, component: Component) {
+ const graphMetrics = getHistoryMetrics(query.graph, query.customMetrics);
+ const topLevelComponent = this.getTopLevelComponent(component);
+ Promise.all([
+ this.fetchActivity(topLevelComponent, 1, 100, serializeQuery(query)),
+ getAllMetrics(),
+ this.fetchMeasuresHistory(graphMetrics)
+ ]).then(
+ response => {
+ if (this.mounted) {
+ this.setState({
+ analyses: response[0].analyses,
+ analysesLoading: true,
+ graphLoading: false,
+ initialized: true,
+ measuresHistory: response[2],
+ metrics: response[1],
+ paging: response[0].paging
+ });
+
+ this.loadAllActivities(topLevelComponent).then(({ analyses, paging }) => {
+ if (this.mounted) {
+ this.setState({
+ analyses,
+ analysesLoading: false,
+ paging
+ });
+ }
+ });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ initialized: true, analysesLoading: false, graphLoading: false });
+ }
+ }
+ );
+ }
+
+ updateGraphData = (graph: string, customMetrics: string[]) => {
+ const graphMetrics = getHistoryMetrics(graph, customMetrics);
+ this.setState({ graphLoading: true });
+ this.fetchMeasuresHistory(graphMetrics).then(
+ measuresHistory => {
+ if (this.mounted) {
+ this.setState({ graphLoading: false, measuresHistory });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ graphLoading: false, measuresHistory: [] });
+ }
+ }
+ );
+ };
+
+ updateQuery = (newQuery: Query) => {
+ const query = serializeUrlQuery({
+ ...this.state.query,
+ ...newQuery
+ });
+ this.props.router.push({
+ pathname: this.props.location.pathname,
+ query: {
+ ...query,
+ ...getBranchLikeQuery(this.props.branchLike),
+ id: this.props.component.key
+ }
+ });
+ };
+
+ shouldRedirect = () => {
+ const locationQuery = this.props.location.query;
+ if (!locationQuery) {
+ return false;
+ }
+ const filtered = Object.keys(locationQuery).some(
+ key => key !== 'id' && locationQuery[key] !== ''
+ );
+
+ const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM);
+ const graph = get(PROJECT_ACTIVITY_GRAPH) || 'issues';
+ const emptyCustomGraph =
+ isCustomGraph(graph) && customGraphs && customGraphs.split(',').length <= 0;
+
+ // if there is no filter, but there are saved preferences in the localStorage
+ // also don't redirect to custom if there is no metrics selected for it
+ return !filtered && graph != null && graph !== DEFAULT_GRAPH && !emptyCustomGraph;
+ };
+
+ render() {
+ if (this.shouldRedirect()) {
+ return null;
+ }
+
+ return (
+ <ProjectActivityApp
+ addCustomEvent={this.addCustomEvent}
+ addVersion={this.addVersion}
+ analyses={this.state.analyses}
+ analysesLoading={this.state.analysesLoading}
+ changeEvent={this.changeEvent}
+ deleteAnalysis={this.deleteAnalysis}
+ deleteEvent={this.deleteEvent}
+ graphLoading={!this.state.initialized || this.state.graphLoading}
+ initializing={!this.state.initialized}
+ measuresHistory={this.state.measuresHistory}
+ metrics={this.state.metrics}
+ project={this.props.component}
+ query={this.state.query}
+ updateQuery={this.updateQuery}
+ />
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import DateRangeInput from '../../../components/controls/DateRangeInput';
-import { translate } from '../../../helpers/l10n';
-import { Button } from '../../../components/ui/buttons';
-/*:: import type { RawQuery } from '../../../helpers/query'; */
-
-/*::
-type Props = {
- from: ?Date,
- to: ?Date,
- onChange: RawQuery => void
-};
-*/
-
-export default class ProjectActivityDateInput extends React.PureComponent {
- /*:: props: Props; */
-
- handleChange = ({ from, to } /*: { from?: Date, to?: Date } */) => {
- this.props.onChange({ from, to });
- };
-
- handleResetClick = () => {
- this.props.onChange({ from: null, to: null });
- };
-
- render() {
- return (
- <div>
- <DateRangeInput
- onChange={this.handleChange}
- value={{ from: this.props.from, to: this.props.to }}
- />
- <Button
- className="spacer-left"
- disabled={this.props.from == null && this.props.to == null}
- onClick={this.handleResetClick}>
- {translate('project_activity.reset_dates')}
- </Button>
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import DateRangeInput from '../../../components/controls/DateRangeInput';
+import { translate } from '../../../helpers/l10n';
+import { Button } from '../../../components/ui/buttons';
+import { Query } from '../utils';
+
+interface Props {
+ from?: Date;
+ to?: Date;
+ onChange: (changes: Partial<Query>) => void;
+}
+
+export default class ProjectActivityDateInput extends React.PureComponent<Props> {
+ handleChange = ({ from, to }: { from?: Date; to?: Date }) => {
+ this.props.onChange({ from, to });
+ };
+
+ handleResetClick = () => {
+ this.props.onChange({ from: undefined, to: undefined });
+ };
+
+ render() {
+ return (
+ <div>
+ <DateRangeInput
+ onChange={this.handleChange}
+ value={{ from: this.props.from, to: this.props.to }}
+ />
+ <Button
+ className="spacer-left"
+ disabled={this.props.from === undefined && this.props.to === undefined}
+ onClick={this.handleResetClick}>
+ {translate('project_activity.reset_dates')}
+ </Button>
+ </div>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-//@flow
-import React from 'react';
-import ProjectEventIcon from '../../../components/icons-components/ProjectEventIcon';
-
-/*::
-export type Option = { label: string, value: string };
-*/
-
-/*::
-type Props = {
- option: Option,
- children?: Element | Text,
- className?: string,
- isFocused?: boolean,
- onFocus: (Option, MouseEvent) => void,
- onSelect: (Option, MouseEvent) => void
-};
-*/
-
-export default class ProjectActivityEventSelectOption extends React.PureComponent {
- /*:: props: Props; */
-
- handleMouseDown = (event /*: MouseEvent */) => {
- event.preventDefault();
- event.stopPropagation();
- this.props.onSelect(this.props.option, event);
- };
-
- handleMouseEnter = (event /*: MouseEvent */) => {
- this.props.onFocus(this.props.option, event);
- };
-
- handleMouseMove = (event /*: MouseEvent */) => {
- if (this.props.isFocused) {
- return;
- }
- this.props.onFocus(this.props.option, event);
- };
-
- render() {
- const { option } = this.props;
- return (
- <div
- className={this.props.className}
- onMouseDown={this.handleMouseDown}
- onMouseEnter={this.handleMouseEnter}
- onMouseMove={this.handleMouseMove}
- title={option.label}>
- <ProjectEventIcon className={'project-activity-event-icon ' + option.value} />
- <span className="little-spacer-left">{this.props.children}</span>
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import ProjectEventIcon from '../../../components/icons-components/ProjectEventIcon';
+
+export interface Option {
+ label: string;
+ value: string;
+}
+
+interface Props {
+ option: Option;
+ children?: Element | Text;
+ className?: string;
+ isFocused?: boolean;
+ onFocus: (option: Option, event: React.MouseEvent) => void;
+ onSelect: (option: Option, event: React.MouseEvent) => void;
+}
+
+export default class ProjectActivityEventSelectOption extends React.PureComponent<Props> {
+ handleMouseDown = (event: React.MouseEvent) => {
+ event.preventDefault();
+ event.stopPropagation();
+ this.props.onSelect(this.props.option, event);
+ };
+
+ handleMouseEnter = (event: React.MouseEvent) => {
+ this.props.onFocus(this.props.option, event);
+ };
+
+ handleMouseMove = (event: React.MouseEvent) => {
+ if (this.props.isFocused) {
+ return;
+ }
+ this.props.onFocus(this.props.option, event);
+ };
+
+ render() {
+ const { option } = this.props;
+ return (
+ <div
+ className={this.props.className}
+ onMouseDown={this.handleMouseDown}
+ onMouseEnter={this.handleMouseEnter}
+ onMouseMove={this.handleMouseMove}
+ title={option.label}>
+ <ProjectEventIcon className={'project-activity-event-icon ' + option.value} />
+ <span className="little-spacer-left">{this.props.children}</span>
+ </div>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-//@flow
-import React from 'react';
-import ProjectEventIcon from '../../../components/icons-components/ProjectEventIcon';
-/*:: import type { Option } from './ProjectActivityEventSelectOption'; */
-
-/*::
-type Props = {
- value: Option,
- children?: Element | Text
-};
-*/
-
-export default function ProjectActivityEventSelectValue(props /*: Props */) {
- const { value } = props;
- return (
- <div className="Select-value" title={value.label}>
- <div className="Select-value-label">
- <ProjectEventIcon className={'project-activity-event-icon ' + value.value} />
- <span className="little-spacer-left">{props.children}</span>
- </div>
- </div>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { Option } from './ProjectActivityEventSelectOption';
+import ProjectEventIcon from '../../../components/icons-components/ProjectEventIcon';
+
+interface Props {
+ children?: React.ReactNode;
+ value: Option;
+}
+
+export default function ProjectActivityEventSelectValue({ children, value }: Props) {
+ return (
+ <div className="Select-value" title={value.label}>
+ <div className="Select-value-label">
+ <ProjectEventIcon className={'project-activity-event-icon ' + value.value} />
+ <span className="little-spacer-left">{children}</span>
+ </div>
+ </div>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import { debounce, findLast, maxBy, minBy, sortBy } from 'lodash';
-import ProjectActivityGraphsHeader from './ProjectActivityGraphsHeader';
-import GraphsZoom from './GraphsZoom';
-import GraphsHistory from './GraphsHistory';
-import { get, save } from '../../../helpers/storage';
-import {
- datesQueryChanged,
- generateSeries,
- getDisplayedHistoryMetrics,
- getSeriesMetricType,
- historyQueryChanged,
- isCustomGraph,
- PROJECT_ACTIVITY_GRAPH,
- PROJECT_ACTIVITY_GRAPH_CUSTOM,
- splitSeriesInGraphs
-} from '../utils';
-/*:: import type { RawQuery } from '../../../helpers/query'; */
-/*:: import type { Analysis, MeasureHistory, Metric, Query } from '../types'; */
-/*:: import type { Serie } from '../../../components/charts/AdvancedTimeline'; */
-
-/*::
-type Props = {
- analyses: Array<Analysis>,
- leakPeriodDate?: Date,
- loading: boolean,
- measuresHistory: Array<MeasureHistory>,
- metrics: Array<Metric>,
- query: Query,
- updateQuery: RawQuery => void
-};
-*/
-
-/*::
-type State = {
- graphStartDate: ?Date,
- graphEndDate: ?Date,
- series: Array<Serie>,
- graphs: Array<Array<Serie>>
-};
-*/
-
-const MAX_GRAPH_NB = 2;
-const MAX_SERIES_PER_GRAPH = 3;
-
-export default class ProjectActivityGraphs extends React.PureComponent {
- /*:: props: Props; */
- /*:: state: State; */
-
- constructor(props /*: Props */) {
- super(props);
- const series = generateSeries(
- props.measuresHistory,
- props.query.graph,
- props.metrics,
- getDisplayedHistoryMetrics(props.query.graph, props.query.customMetrics)
- );
- this.state = {
- series,
- graphs: splitSeriesInGraphs(series, MAX_GRAPH_NB, MAX_SERIES_PER_GRAPH),
- ...this.getStateZoomDates(null, props, series)
- };
- this.updateQueryDateRange = debounce(this.updateQueryDateRange, 500);
- }
-
- componentWillReceiveProps(nextProps /*: Props */) {
- let newSeries;
- let newGraphs;
- if (
- nextProps.measuresHistory !== this.props.measuresHistory ||
- historyQueryChanged(this.props.query, nextProps.query)
- ) {
- newSeries = generateSeries(
- nextProps.measuresHistory,
- nextProps.query.graph,
- nextProps.metrics,
- getDisplayedHistoryMetrics(nextProps.query.graph, nextProps.query.customMetrics)
- );
- newGraphs = splitSeriesInGraphs(newSeries, MAX_GRAPH_NB, MAX_SERIES_PER_GRAPH);
- }
-
- const newDates = this.getStateZoomDates(this.props, nextProps, newSeries);
-
- if (newSeries || newDates) {
- let newState = {};
- if (newSeries) {
- newState.series = newSeries;
- newState.graphs = newGraphs;
- }
- if (newDates) {
- newState = { ...newState, ...newDates };
- }
- this.setState(newState);
- }
- }
-
- getStateZoomDates = (
- props /*: ?Props */,
- nextProps /*: Props */,
- newSeries /*: ?Array<Serie> */
- ) => {
- const newDates = { from: nextProps.query.from || null, to: nextProps.query.to || null };
- if (!props || datesQueryChanged(props.query, nextProps.query)) {
- return { graphEndDate: newDates.to, graphStartDate: newDates.from };
- }
-
- if (newDates.to == null && newDates.from == null && newSeries != null) {
- const series = newSeries ? newSeries : this.state.series;
- const firstValid = minBy(series.map(serie => serie.data.find(p => p.y || p.y === 0)), 'x');
- const lastValid = maxBy(
- series.map(serie => findLast(serie.data, p => p.y || p.y === 0)),
- 'x'
- );
- return {
- graphEndDate: lastValid ? lastValid.x : newDates.to,
- graphStartDate: firstValid ? firstValid.x : newDates.from
- };
- }
- return null;
- };
-
- getMetricsTypeFilter = () => {
- if (this.state.graphs.length < MAX_GRAPH_NB) {
- return null;
- }
- return this.state.graphs
- .filter(graph => graph.length < MAX_SERIES_PER_GRAPH)
- .map(graph => graph[0].type);
- };
-
- addCustomMetric = (metric /*: string */) => {
- const customMetrics = [...this.props.query.customMetrics, metric];
- save(PROJECT_ACTIVITY_GRAPH_CUSTOM, customMetrics.join(','));
- this.props.updateQuery({ customMetrics });
- };
-
- removeCustomMetric = (removedMetric /*: string */) => {
- const customMetrics = this.props.query.customMetrics.filter(metric => metric !== removedMetric);
- save(PROJECT_ACTIVITY_GRAPH_CUSTOM, customMetrics.join(','));
- this.props.updateQuery({ customMetrics });
- };
-
- updateGraph = (graph /*: string */) => {
- save(PROJECT_ACTIVITY_GRAPH, graph);
- if (isCustomGraph(graph) && this.props.query.customMetrics.length <= 0) {
- const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM);
- this.props.updateQuery({ graph, customMetrics: customGraphs ? customGraphs.split(',') : [] });
- } else {
- this.props.updateQuery({ graph, customMetrics: [] });
- }
- };
-
- updateGraphZoom = (graphStartDate /*: ?Date */, graphEndDate /*: ?Date */) => {
- if (graphEndDate != null && graphStartDate != null) {
- const msDiff = Math.abs(graphEndDate.valueOf() - graphStartDate.valueOf());
- // 12 hours minimum between the two dates
- if (msDiff < 1000 * 60 * 60 * 12) {
- return;
- }
- }
-
- this.setState({ graphStartDate, graphEndDate });
- this.updateQueryDateRange([graphStartDate, graphEndDate]);
- };
-
- updateSelectedDate = (selectedDate /*: ?Date */) => this.props.updateQuery({ selectedDate });
-
- updateQueryDateRange = (dates /*: Array<?Date> */) => {
- if (dates[0] == null || dates[1] == null) {
- this.props.updateQuery({ from: dates[0], to: dates[1] });
- } else {
- const sortedDates = sortBy(dates);
- this.props.updateQuery({ from: sortedDates[0], to: sortedDates[1] });
- }
- };
-
- render() {
- const { leakPeriodDate, loading, metrics, query } = this.props;
- const { graphEndDate, graphStartDate, series } = this.state;
-
- return (
- <div className="project-activity-layout-page-main-inner boxed-group boxed-group-inner">
- <ProjectActivityGraphsHeader
- addCustomMetric={this.addCustomMetric}
- graph={query.graph}
- metrics={metrics}
- metricsTypeFilter={this.getMetricsTypeFilter()}
- removeCustomMetric={this.removeCustomMetric}
- selectedMetrics={this.props.query.customMetrics}
- updateGraph={this.updateGraph}
- />
- <GraphsHistory
- analyses={this.props.analyses}
- eventFilter={query.category}
- graph={query.graph}
- graphEndDate={graphEndDate}
- graphStartDate={graphStartDate}
- graphs={this.state.graphs}
- leakPeriodDate={leakPeriodDate}
- loading={loading}
- measuresHistory={this.props.measuresHistory}
- removeCustomMetric={this.removeCustomMetric}
- selectedDate={this.props.query.selectedDate}
- series={series}
- updateGraphZoom={this.updateGraphZoom}
- updateSelectedDate={this.updateSelectedDate}
- />
- <GraphsZoom
- graphEndDate={graphEndDate}
- graphStartDate={graphStartDate}
- leakPeriodDate={leakPeriodDate}
- loading={loading}
- metricsType={getSeriesMetricType(series)}
- series={series}
- showAreas={['coverage', 'duplications'].includes(query.graph)}
- updateGraphZoom={this.updateGraphZoom}
- />
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { debounce, findLast, maxBy, minBy, sortBy } from 'lodash';
+import ProjectActivityGraphsHeader from './ProjectActivityGraphsHeader';
+import GraphsZoom from './GraphsZoom';
+import GraphsHistory from './GraphsHistory';
+import { get, save } from '../../../helpers/storage';
+import {
+ datesQueryChanged,
+ generateSeries,
+ getDisplayedHistoryMetrics,
+ getSeriesMetricType,
+ historyQueryChanged,
+ isCustomGraph,
+ PROJECT_ACTIVITY_GRAPH,
+ PROJECT_ACTIVITY_GRAPH_CUSTOM,
+ splitSeriesInGraphs,
+ MeasureHistory,
+ Query,
+ Serie,
+ Point,
+ ParsedAnalysis
+} from '../utils';
+import { Metric } from '../../../app/types';
+
+interface Props {
+ analyses: ParsedAnalysis[];
+ leakPeriodDate?: Date;
+ loading: boolean;
+ measuresHistory: MeasureHistory[];
+ metrics: Metric[];
+ query: Query;
+ updateQuery: (changes: Partial<Query>) => void;
+}
+
+interface State {
+ graphStartDate?: Date;
+ graphEndDate?: Date;
+ series: Serie[];
+ graphs: Serie[][];
+}
+
+const MAX_GRAPH_NB = 2;
+const MAX_SERIES_PER_GRAPH = 3;
+
+export default class ProjectActivityGraphs extends React.PureComponent<Props, State> {
+ constructor(props: Props) {
+ super(props);
+ const series = generateSeries(
+ props.measuresHistory,
+ props.query.graph,
+ props.metrics,
+ getDisplayedHistoryMetrics(props.query.graph, props.query.customMetrics)
+ );
+ this.state = {
+ series,
+ graphs: splitSeriesInGraphs(series, MAX_GRAPH_NB, MAX_SERIES_PER_GRAPH),
+ ...this.getStateZoomDates(undefined, props, series)
+ };
+ this.updateQueryDateRange = debounce(this.updateQueryDateRange, 500);
+ }
+
+ componentWillReceiveProps(nextProps: Props) {
+ let newSeries;
+ let newGraphs;
+ if (
+ nextProps.measuresHistory !== this.props.measuresHistory ||
+ historyQueryChanged(this.props.query, nextProps.query)
+ ) {
+ newSeries = generateSeries(
+ nextProps.measuresHistory,
+ nextProps.query.graph,
+ nextProps.metrics,
+ getDisplayedHistoryMetrics(nextProps.query.graph, nextProps.query.customMetrics)
+ );
+ newGraphs = splitSeriesInGraphs(newSeries, MAX_GRAPH_NB, MAX_SERIES_PER_GRAPH);
+ }
+
+ const newDates = this.getStateZoomDates(this.props, nextProps, newSeries);
+
+ if (newSeries || newDates) {
+ let newState = {} as State;
+ if (newSeries) {
+ newState.series = newSeries;
+ }
+ if (newGraphs) {
+ newState.graphs = newGraphs;
+ }
+ if (newDates) {
+ newState = { ...newState, ...newDates };
+ }
+ this.setState(newState);
+ }
+ }
+
+ getStateZoomDates = (props: Props | undefined, nextProps: Props, newSeries?: Serie[]) => {
+ const newDates = {
+ from: nextProps.query.from || undefined,
+ to: nextProps.query.to || undefined
+ };
+ if (!props || datesQueryChanged(props.query, nextProps.query)) {
+ return { graphEndDate: newDates.to, graphStartDate: newDates.from };
+ }
+
+ if (newDates.to === undefined && newDates.from === undefined && newSeries !== undefined) {
+ const series = newSeries ? newSeries : this.state.series;
+ const firstValid = minBy(
+ series.map(serie => serie.data.find(p => Boolean(p.y || p.y === 0))),
+ 'x'
+ );
+ const lastValid = maxBy<Point>(
+ series.map(serie => findLast(serie.data, p => Boolean(p.y || p.y === 0))!),
+ 'x'
+ );
+ return {
+ graphEndDate: lastValid ? lastValid.x : newDates.to,
+ graphStartDate: firstValid ? firstValid.x : newDates.from
+ };
+ }
+ return null;
+ };
+
+ getMetricsTypeFilter = () => {
+ if (this.state.graphs.length < MAX_GRAPH_NB) {
+ return undefined;
+ }
+ return this.state.graphs
+ .filter(graph => graph.length < MAX_SERIES_PER_GRAPH)
+ .map(graph => graph[0].type);
+ };
+
+ addCustomMetric = (metric: string) => {
+ const customMetrics = [...this.props.query.customMetrics, metric];
+ save(PROJECT_ACTIVITY_GRAPH_CUSTOM, customMetrics.join(','));
+ this.props.updateQuery({ customMetrics });
+ };
+
+ removeCustomMetric = (removedMetric: string) => {
+ const customMetrics = this.props.query.customMetrics.filter(metric => metric !== removedMetric);
+ save(PROJECT_ACTIVITY_GRAPH_CUSTOM, customMetrics.join(','));
+ this.props.updateQuery({ customMetrics });
+ };
+
+ updateGraph = (graph: string) => {
+ save(PROJECT_ACTIVITY_GRAPH, graph);
+ if (isCustomGraph(graph) && this.props.query.customMetrics.length <= 0) {
+ const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM);
+ this.props.updateQuery({ graph, customMetrics: customGraphs ? customGraphs.split(',') : [] });
+ } else {
+ this.props.updateQuery({ graph, customMetrics: [] });
+ }
+ };
+
+ updateGraphZoom = (graphStartDate?: Date, graphEndDate?: Date) => {
+ if (graphEndDate !== undefined && graphStartDate !== undefined) {
+ const msDiff = Math.abs(graphEndDate.valueOf() - graphStartDate.valueOf());
+ // 12 hours minimum between the two dates
+ if (msDiff < 1000 * 60 * 60 * 12) {
+ return;
+ }
+ }
+
+ this.setState({ graphStartDate, graphEndDate });
+ this.updateQueryDateRange([graphStartDate, graphEndDate]);
+ };
+
+ updateSelectedDate = (selectedDate?: Date) => this.props.updateQuery({ selectedDate });
+
+ updateQueryDateRange = (dates: Array<Date | undefined>) => {
+ if (dates[0] === undefined || dates[1] === undefined) {
+ this.props.updateQuery({ from: dates[0], to: dates[1] });
+ } else {
+ const sortedDates = sortBy(dates);
+ this.props.updateQuery({ from: sortedDates[0], to: sortedDates[1] });
+ }
+ };
+
+ render() {
+ const { leakPeriodDate, loading, metrics, query } = this.props;
+ const { graphEndDate, graphStartDate, series } = this.state;
+
+ return (
+ <div className="project-activity-layout-page-main-inner boxed-group boxed-group-inner">
+ <ProjectActivityGraphsHeader
+ addCustomMetric={this.addCustomMetric}
+ graph={query.graph}
+ metrics={metrics}
+ metricsTypeFilter={this.getMetricsTypeFilter()}
+ removeCustomMetric={this.removeCustomMetric}
+ selectedMetrics={this.props.query.customMetrics}
+ updateGraph={this.updateGraph}
+ />
+ <GraphsHistory
+ analyses={this.props.analyses}
+ eventFilter={query.category}
+ graph={query.graph}
+ graphEndDate={graphEndDate}
+ graphStartDate={graphStartDate}
+ graphs={this.state.graphs}
+ leakPeriodDate={leakPeriodDate}
+ loading={loading}
+ measuresHistory={this.props.measuresHistory}
+ removeCustomMetric={this.removeCustomMetric}
+ selectedDate={this.props.query.selectedDate}
+ series={series}
+ updateGraphZoom={this.updateGraphZoom}
+ updateSelectedDate={this.updateSelectedDate}
+ />
+ <GraphsZoom
+ graphEndDate={graphEndDate}
+ graphStartDate={graphStartDate}
+ leakPeriodDate={leakPeriodDate}
+ loading={loading}
+ metricsType={getSeriesMetricType(series)}
+ series={series}
+ showAreas={['coverage', 'duplications'].includes(query.graph)}
+ updateGraphZoom={this.updateGraphZoom}
+ />
+ </div>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import AddGraphMetric from './forms/AddGraphMetric';
-import { isCustomGraph, GRAPH_TYPES } from '../utils';
-import Select from '../../../components/controls/Select';
-import { translate } from '../../../helpers/l10n';
-/*:: import type { Metric } from '../types'; */
-
-/*::
-type Props = {
- addCustomMetric: string => void,
- removeCustomMetric: string => void,
- graph: string,
- metrics: Array<Metric>,
- metricsTypeFilter: ?Array<string>,
- selectedMetrics: Array<string>,
- updateGraph: string => void
-};
-*/
-
-export default class ProjectActivityGraphsHeader extends React.PureComponent {
- /*:: props: Props; */
-
- handleGraphChange = (option /*: { value: string } */) => {
- if (option.value !== this.props.graph) {
- this.props.updateGraph(option.value);
- }
- };
-
- render() {
- const selectOptions = GRAPH_TYPES.map(graph => ({
- label: translate('project_activity.graphs', graph),
- value: graph
- }));
-
- return (
- <header className="page-header">
- <Select
- className="pull-left input-medium"
- clearable={false}
- onChange={this.handleGraphChange}
- options={selectOptions}
- searchable={false}
- value={this.props.graph}
- />
- {isCustomGraph(this.props.graph) && (
- <AddGraphMetric
- addMetric={this.props.addCustomMetric}
- className="pull-left spacer-left"
- metrics={this.props.metrics}
- metricsTypeFilter={this.props.metricsTypeFilter}
- removeMetric={this.props.removeCustomMetric}
- selectedMetrics={this.props.selectedMetrics}
- />
- )}
- </header>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import AddGraphMetric from './forms/AddGraphMetric';
+import { isCustomGraph, GRAPH_TYPES } from '../utils';
+import Select from '../../../components/controls/Select';
+import { translate } from '../../../helpers/l10n';
+import { Metric } from '../../../app/types';
+
+interface Props {
+ addCustomMetric: (metric: string) => void;
+ removeCustomMetric: (metric: string) => void;
+ graph: string;
+ metrics: Metric[];
+ metricsTypeFilter?: string[];
+ selectedMetrics: string[];
+ updateGraph: (graphType: string) => void;
+}
+
+export default class ProjectActivityGraphsHeader extends React.PureComponent<Props> {
+ handleGraphChange = (option: { value: string }) => {
+ if (option.value !== this.props.graph) {
+ this.props.updateGraph(option.value);
+ }
+ };
+
+ render() {
+ const selectOptions = GRAPH_TYPES.map(graph => ({
+ label: translate('project_activity.graphs', graph),
+ value: graph
+ }));
+
+ return (
+ <header className="page-header">
+ <Select
+ className="pull-left input-medium"
+ clearable={false}
+ onChange={this.handleGraphChange}
+ options={selectOptions}
+ searchable={false}
+ value={this.props.graph}
+ />
+ {isCustomGraph(this.props.graph) && (
+ <AddGraphMetric
+ addMetric={this.props.addCustomMetric}
+ className="pull-left spacer-left"
+ metrics={this.props.metrics}
+ metricsTypeFilter={this.props.metricsTypeFilter}
+ removeMetric={this.props.removeCustomMetric}
+ selectedMetrics={this.props.selectedMetrics}
+ />
+ )}
+ </header>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import ListFooter from '../../../components/controls/ListFooter';
-/*:: import type { Paging } from '../types'; */
-
-/*::
-type Props = {
- analyses: Array<*>,
- fetchMoreActivity: () => void,
- paging?: Paging
-};
-*/
-
-export default function ProjectActivityPageFooter(
- { analyses, fetchMoreActivity, paging } /*: Props */
-) {
- if (!paging || analyses.length === 0) {
- return null;
- }
- return <ListFooter count={analyses.length} loadMore={fetchMoreActivity} total={paging.total} />;
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import ListFooter from '../../../components/controls/ListFooter';
+import { Paging } from '../../../app/types';
+
+interface Props {
+ analyses: unknown[];
+ fetchMoreActivity: () => void;
+ paging?: Paging;
+}
+
+export default function ProjectActivityPageFooter({ analyses, fetchMoreActivity, paging }: Props) {
+ if (!paging || analyses.length === 0) {
+ return null;
+ }
+ return <ListFooter count={analyses.length} loadMore={fetchMoreActivity} total={paging.total} />;
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import ProjectActivityEventSelectOption from './ProjectActivityEventSelectOption';
-import ProjectActivityEventSelectValue from './ProjectActivityEventSelectValue';
-import ProjectActivityDateInput from './ProjectActivityDateInput';
-import { EVENT_TYPES, APPLICATION_EVENT_TYPES } from '../utils';
-import Select from '../../../components/controls/Select';
-import { translate } from '../../../helpers/l10n';
-/*:: import type { RawQuery } from '../../../helpers/query'; */
-
-/*::
-type Props = {
- category?: string,
- from: ?Date,
- project: { qualifier: string },
- to: ?Date,
- updateQuery: RawQuery => void
-};
-*/
-
-export default class ProjectActivityPageHeader extends React.PureComponent {
- /*:: options: Array<{ label: string, value: string }>; */
- /*:: props: Props; */
-
- handleCategoryChange = (option /*: ?{ value: string } */) =>
- this.props.updateQuery({ category: option ? option.value : '' });
-
- render() {
- const eventTypes =
- this.props.project.qualifier === 'APP' ? APPLICATION_EVENT_TYPES : EVENT_TYPES;
- this.options = eventTypes.map(category => ({
- label: translate('event.category', category),
- value: category
- }));
-
- return (
- <header className="page-header">
- {!['VW', 'SVW'].includes(this.props.project.qualifier) && (
- <Select
- className="input-medium pull-left big-spacer-right"
- clearable={true}
- onChange={this.handleCategoryChange}
- optionComponent={ProjectActivityEventSelectOption}
- options={this.options}
- placeholder={translate('project_activity.filter_events') + '...'}
- searchable={false}
- value={this.props.category}
- valueComponent={ProjectActivityEventSelectValue}
- />
- )}
- <ProjectActivityDateInput
- className="pull-left"
- from={this.props.from}
- onChange={this.props.updateQuery}
- to={this.props.to}
- />
- </header>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import ProjectActivityEventSelectOption from './ProjectActivityEventSelectOption';
+import ProjectActivityEventSelectValue from './ProjectActivityEventSelectValue';
+import ProjectActivityDateInput from './ProjectActivityDateInput';
+import { EVENT_TYPES, APPLICATION_EVENT_TYPES, Query } from '../utils';
+import Select from '../../../components/controls/Select';
+import { translate } from '../../../helpers/l10n';
+import { Component } from '../../../app/types';
+
+interface Props {
+ category?: string;
+ from?: Date;
+ project: Pick<Component, 'qualifier'>;
+ to?: Date;
+ updateQuery: (changes: Partial<Query>) => void;
+}
+
+export default class ProjectActivityPageHeader extends React.PureComponent<Props> {
+ handleCategoryChange = (option: { value: string } | null) =>
+ this.props.updateQuery({ category: option ? option.value : '' });
+
+ render() {
+ const eventTypes =
+ this.props.project.qualifier === 'APP' ? APPLICATION_EVENT_TYPES : EVENT_TYPES;
+ const options = eventTypes.map(category => ({
+ label: translate('event.category', category),
+ value: category
+ }));
+
+ return (
+ <header className="page-header">
+ {!['VW', 'SVW'].includes(this.props.project.qualifier) && (
+ <Select
+ className="input-medium pull-left big-spacer-right"
+ clearable={true}
+ onChange={this.handleCategoryChange}
+ optionComponent={ProjectActivityEventSelectOption}
+ options={options}
+ placeholder={translate('project_activity.filter_events') + '...'}
+ searchable={false}
+ value={this.props.category}
+ // @ts-ignore react-select typings are incorrect, they expect `props` of `valueComponent` to be exactly `Option`
+ valueComponent={ProjectActivityEventSelectValue}
+ />
+ )}
+ <ProjectActivityDateInput
+ from={this.props.from}
+ onChange={this.props.updateQuery}
+ to={this.props.to}
+ />
+ </header>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import GraphHistory from '../GraphHistory';
-import { DEFAULT_GRAPH } from '../../utils';
-import { parseDate } from '../../../../helpers/dates';
-
-const SERIES = [
- {
- name: 'bugs',
- translatedName: 'metric.bugs.name',
- data: [
- { x: parseDate('2016-10-27T16:33:50+0200'), y: 5 },
- { x: parseDate('2016-10-27T12:21:15+0200'), y: 16 },
- { x: parseDate('2016-10-26T12:17:29+0200'), y: 12 }
- ]
- }
-];
-
-const DEFAULT_PROPS = {
- events: [],
- graph: DEFAULT_GRAPH,
- graphEndDate: null,
- graphStartDate: null,
- leakPeriodDate: '2017-05-16T13:50:02+0200',
- isCustom: false,
- measuresHistory: [],
- metrics: [],
- metricsType: 'INT',
- removeCustomMetric: () => {},
- showAreas: true,
- selectedDate: null,
- series: SERIES,
- updateGraphZoom: () => {},
- updateSelectedDate: () => {},
- updateTooltip: () => {}
-};
-
-it('should correctly render a graph', () => {
- expect(shallow(<GraphHistory {...DEFAULT_PROPS} />)).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import GraphHistory from '../GraphHistory';
+import { DEFAULT_GRAPH } from '../../utils';
+import { parseDate } from '../../../../helpers/dates';
+
+const SERIES = [
+ {
+ name: 'bugs',
+ translatedName: 'metric.bugs.name',
+ data: [
+ { x: parseDate('2016-10-27T16:33:50+0200'), y: 5 },
+ { x: parseDate('2016-10-27T12:21:15+0200'), y: 16 },
+ { x: parseDate('2016-10-26T12:17:29+0200'), y: 12 }
+ ],
+ type: 'INT'
+ }
+];
+
+const DEFAULT_PROPS: GraphHistory['props'] = {
+ events: [],
+ graph: DEFAULT_GRAPH,
+ leakPeriodDate: parseDate('2017-05-16T13:50:02+0200'),
+ isCustom: false,
+ measuresHistory: [],
+ metricsType: 'INT',
+ removeCustomMetric: () => {},
+ showAreas: true,
+ series: SERIES,
+ updateGraphZoom: () => {},
+ updateSelectedDate: () => {},
+ updateTooltip: () => {}
+};
+
+it('should correctly render a graph', () => {
+ expect(shallow(<GraphHistory {...DEFAULT_PROPS} />)).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import GraphsHistory from '../GraphsHistory';
-import { DEFAULT_GRAPH } from '../../utils';
-import { parseDate } from '../../../../helpers/dates';
-
-const ANALYSES = [
- {
- key: 'A1',
- date: parseDate('2016-10-27T16:33:50+0200'),
- events: [
- {
- key: 'E1',
- category: 'VERSION',
- name: '6.5-SNAPSHOT'
- }
- ]
- },
- {
- key: 'A2',
- date: parseDate('2016-10-27T12:21:15+0200'),
- events: []
- },
- {
- key: 'A3',
- date: parseDate('2016-10-26T12:17:29+0200'),
- events: [
- {
- key: 'E2',
- category: 'OTHER',
- name: 'foo'
- },
- {
- key: 'E3',
- category: 'VERSION',
- name: '6.4'
- }
- ]
- }
-];
-
-const SERIES = [
- {
- name: 'bugs',
- translatedName: 'metric.bugs.name',
- data: [
- { x: parseDate('2016-10-27T16:33:50+0200'), y: 5 },
- { x: parseDate('2016-10-27T12:21:15+0200'), y: 16 },
- { x: parseDate('2016-10-26T12:17:29+0200'), y: 12 }
- ]
- }
-];
-
-const DEFAULT_PROPS = {
- analyses: ANALYSES,
- eventFilter: '',
- graph: DEFAULT_GRAPH,
- graphs: [SERIES],
- graphEndDate: null,
- graphStartDate: null,
- leakPeriodDate: '2017-05-16T13:50:02+0200',
- loading: false,
- measuresHistory: [],
- removeCustomMetric: () => {},
- selectedDate: null,
- series: SERIES,
- updateGraphZoom: () => {},
- updateSelectedDate: () => {}
-};
-
-it('should correctly render a graph', () => {
- expect(shallow(<GraphsHistory {...DEFAULT_PROPS} />)).toMatchSnapshot();
-});
-
-it('should correctly render multiple graphs', () => {
- expect(shallow(<GraphsHistory {...DEFAULT_PROPS} graphs={[SERIES, SERIES]} />)).toMatchSnapshot();
-});
-
-it('should correctly filter events', () => {
- expect(
- shallow(<GraphsHistory {...DEFAULT_PROPS} />)
- .instance()
- .getEvents()
- ).toMatchSnapshot();
- expect(
- shallow(<GraphsHistory {...DEFAULT_PROPS} eventFilter="OTHER" />)
- .instance()
- .getEvents()
- ).toMatchSnapshot();
-});
-
-it('should show a loading view instead of the graph', () => {
- expect(
- shallow(<GraphsHistory {...DEFAULT_PROPS} loading={true} />).find('DeferredSpinner')
- ).toHaveLength(1);
-});
-
-it('should show that there is no history data', () => {
- expect(shallow(<GraphsHistory {...DEFAULT_PROPS} series={[]} />)).toMatchSnapshot();
- expect(
- shallow(
- <GraphsHistory
- {...DEFAULT_PROPS}
- series={[
- {
- name: 'bugs',
- translatedName: 'metric.bugs.name',
- data: [{ x: parseDate('2016-10-27T16:33:50+0200'), y: undefined }]
- }
- ]}
- />
- )
- ).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import GraphsHistory from '../GraphsHistory';
+import { DEFAULT_GRAPH } from '../../utils';
+import { parseDate } from '../../../../helpers/dates';
+
+const ANALYSES = [
+ {
+ key: 'A1',
+ date: parseDate('2016-10-27T16:33:50+0200'),
+ events: [
+ {
+ key: 'E1',
+ category: 'VERSION',
+ name: '6.5-SNAPSHOT'
+ }
+ ]
+ },
+ {
+ key: 'A2',
+ date: parseDate('2016-10-27T12:21:15+0200'),
+ events: []
+ },
+ {
+ key: 'A3',
+ date: parseDate('2016-10-26T12:17:29+0200'),
+ events: [
+ {
+ key: 'E2',
+ category: 'OTHER',
+ name: 'foo'
+ },
+ {
+ key: 'E3',
+ category: 'VERSION',
+ name: '6.4'
+ }
+ ]
+ }
+];
+
+const SERIES = [
+ {
+ name: 'bugs',
+ translatedName: 'metric.bugs.name',
+ data: [
+ { x: parseDate('2016-10-27T16:33:50+0200'), y: 5 },
+ { x: parseDate('2016-10-27T12:21:15+0200'), y: 16 },
+ { x: parseDate('2016-10-26T12:17:29+0200'), y: 12 }
+ ],
+ type: 'INT'
+ }
+];
+
+const DEFAULT_PROPS: GraphsHistory['props'] = {
+ analyses: ANALYSES,
+ eventFilter: '',
+ graph: DEFAULT_GRAPH,
+ graphs: [SERIES],
+ leakPeriodDate: parseDate('2017-05-16T13:50:02+0200'),
+ loading: false,
+ measuresHistory: [],
+ removeCustomMetric: () => {},
+ series: SERIES,
+ updateGraphZoom: () => {},
+ updateSelectedDate: () => {}
+};
+
+it('should correctly render a graph', () => {
+ expect(shallow(<GraphsHistory {...DEFAULT_PROPS} />)).toMatchSnapshot();
+});
+
+it('should correctly render multiple graphs', () => {
+ expect(shallow(<GraphsHistory {...DEFAULT_PROPS} graphs={[SERIES, SERIES]} />)).toMatchSnapshot();
+});
+
+it('should show a loading view instead of the graph', () => {
+ expect(
+ shallow(<GraphsHistory {...DEFAULT_PROPS} loading={true} />).find('DeferredSpinner')
+ ).toHaveLength(1);
+});
+
+it('should show that there is no history data', () => {
+ expect(shallow(<GraphsHistory {...DEFAULT_PROPS} series={[]} />)).toMatchSnapshot();
+ expect(
+ shallow(
+ <GraphsHistory
+ {...DEFAULT_PROPS}
+ series={[
+ {
+ name: 'bugs',
+ translatedName: 'metric.bugs.name',
+ data: [{ x: parseDate('2016-10-27T16:33:50+0200'), y: undefined }],
+ type: 'INT'
+ }
+ ]}
+ />
+ )
+ ).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import GraphsLegendCustom from '../GraphsLegendCustom';
-
-const SERIES = [
- { name: 'bugs', translatedName: 'Bugs', data: [{ x: 1, y: 1 }] },
- {
- name: 'my_metric',
- translatedName: 'My Metric',
- data: [{ x: 1, y: 1 }]
- },
- { name: 'foo', translatedName: 'Foo', data: [] }
-];
-
-it('should render correctly the list of series', () => {
- expect(shallow(<GraphsLegendCustom removeMetric={() => {}} series={SERIES} />)).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import GraphsLegendCustom from '../GraphsLegendCustom';
+import { parseDate } from '../../../../helpers/dates';
+
+const SERIES = [
+ {
+ name: 'bugs',
+ translatedName: 'Bugs',
+ data: [{ x: parseDate('2017-05-16T13:50:02+0200'), y: 1 }],
+ type: 'INT'
+ },
+ {
+ name: 'my_metric',
+ translatedName: 'My Metric',
+ data: [{ x: parseDate('2017-05-16T13:50:02+0200'), y: 1 }],
+ type: 'INT'
+ },
+ {
+ name: 'foo',
+ translatedName: 'Foo',
+ data: [],
+ type: 'INT'
+ }
+];
+
+it('should render correctly the list of series', () => {
+ expect(shallow(<GraphsLegendCustom removeMetric={() => {}} series={SERIES} />)).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import GraphsLegendStatic from '../GraphsLegendStatic';
-
-const SERIES = [
- { name: 'bugs', translatedName: 'Bugs', data: [] },
- { name: 'code_smells', translatedName: 'Code Smells', data: [] }
-];
-
-it('should render correctly the list of series', () => {
- expect(shallow(<GraphsLegendStatic series={SERIES} />)).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import GraphsLegendStatic from '../GraphsLegendStatic';
+
+const SERIES = [
+ { name: 'bugs', translatedName: 'Bugs', data: [] },
+ { name: 'code_smells', translatedName: 'Code Smells', data: [] }
+];
+
+it('should render correctly the list of series', () => {
+ expect(shallow(<GraphsLegendStatic series={SERIES} />)).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import GraphsTooltips from '../GraphsTooltips';
-import { DEFAULT_GRAPH } from '../../utils';
-import { parseDate } from '../../../../helpers/dates';
-
-const SERIES_ISSUES = [
- {
- name: 'bugs',
- translatedName: 'Bugs',
- data: [
- {
- x: '2011-10-01T22:01:00.000Z',
- y: 3
- },
- {
- x: '2011-10-25T10:27:41.000Z',
- y: 0
- }
- ]
- },
- {
- name: 'code_smells',
- translatedName: 'Code Smells',
- data: [
- {
- x: '2011-10-01T22:01:00.000Z',
- y: 18
- },
- {
- x: '2011-10-25T10:27:41.000Z',
- y: 15
- }
- ]
- },
- {
- name: 'vulnerabilities',
- translatedName: 'Vulnerabilities',
- data: [
- {
- x: '2011-10-01T22:01:00.000Z',
- y: 0
- },
- {
- x: '2011-10-25T10:27:41.000Z',
- y: 1
- }
- ]
- }
-];
-
-const DEFAULT_PROPS = {
- formatValue: val => 'Formated.' + val,
- graph: DEFAULT_GRAPH,
- graphWidth: 500,
- measuresHistory: [],
- selectedDate: parseDate('2011-10-01T22:01:00.000Z'),
- series: SERIES_ISSUES,
- tooltipIdx: 0,
- tooltipPos: 666
-};
-
-it('should render correctly for issues graphs', () => {
- expect(shallow(<GraphsTooltips {...DEFAULT_PROPS} />)).toMatchSnapshot();
-});
-
-it('should render correctly for random graphs', () => {
- expect(
- shallow(
- <GraphsTooltips
- {...DEFAULT_PROPS}
- graph="random"
- selectedDate={parseDate('2011-10-25T10:27:41.000Z')}
- tooltipIdx={1}
- />
- )
- ).toMatchSnapshot();
-});
-
-it('should not add separators if not needed', () => {
- expect(
- shallow(<GraphsTooltips {...DEFAULT_PROPS} graph="coverage" series={[]} />)
- ).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import GraphsTooltips from '../GraphsTooltips';
+import { DEFAULT_GRAPH } from '../../utils';
+import { parseDate } from '../../../../helpers/dates';
+
+const SERIES_ISSUES = [
+ {
+ name: 'bugs',
+ translatedName: 'Bugs',
+ data: [
+ {
+ x: parseDate('2011-10-01T22:01:00.000Z'),
+ y: 3
+ },
+ {
+ x: parseDate('2011-10-25T10:27:41.000Z'),
+ y: 0
+ }
+ ],
+ type: 'INT'
+ },
+ {
+ name: 'code_smells',
+ translatedName: 'Code Smells',
+ data: [
+ {
+ x: parseDate('2011-10-01T22:01:00.000Z'),
+ y: 18
+ },
+ {
+ x: parseDate('2011-10-25T10:27:41.000Z'),
+ y: 15
+ }
+ ],
+ type: 'INT'
+ },
+ {
+ name: 'vulnerabilities',
+ translatedName: 'Vulnerabilities',
+ data: [
+ {
+ x: parseDate('2011-10-01T22:01:00.000Z'),
+ y: 0
+ },
+ {
+ x: parseDate('2011-10-25T10:27:41.000Z'),
+ y: 1
+ }
+ ],
+ type: 'INT'
+ }
+];
+
+const DEFAULT_PROPS: GraphsTooltips['props'] = {
+ events: [],
+ formatValue: val => 'Formated.' + val,
+ graph: DEFAULT_GRAPH,
+ graphWidth: 500,
+ measuresHistory: [],
+ selectedDate: parseDate('2011-10-01T22:01:00.000Z'),
+ series: SERIES_ISSUES,
+ tooltipIdx: 0,
+ tooltipPos: 666
+};
+
+it('should render correctly for issues graphs', () => {
+ expect(shallow(<GraphsTooltips {...DEFAULT_PROPS} />)).toMatchSnapshot();
+});
+
+it('should render correctly for random graphs', () => {
+ expect(
+ shallow(
+ <GraphsTooltips
+ {...DEFAULT_PROPS}
+ graph="random"
+ selectedDate={parseDate('2011-10-25T10:27:41.000Z')}
+ tooltipIdx={1}
+ />
+ )
+ ).toMatchSnapshot();
+});
+
+it('should not add separators if not needed', () => {
+ expect(
+ shallow(<GraphsTooltips {...DEFAULT_PROPS} graph="coverage" series={[]} />)
+ ).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import GraphsTooltipsContent from '../GraphsTooltipsContent';
-
-const DEFAULT_PROPS = {
- name: 'code_smells',
- style: 1,
- translatedName: 'Code Smells',
- value: '1.2k'
-};
-
-it('should render correctly', () => {
- expect(shallow(<GraphsTooltipsContent {...DEFAULT_PROPS} />)).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import GraphsTooltipsContent from '../GraphsTooltipsContent';
+
+const DEFAULT_PROPS = {
+ name: 'code_smells',
+ style: '1',
+ translatedName: 'Code Smells',
+ value: '1.2k'
+};
+
+it('should render correctly', () => {
+ expect(shallow(<GraphsTooltipsContent {...DEFAULT_PROPS} />)).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import GraphsTooltipsContentCoverage from '../GraphsTooltipsContentCoverage';
-
-const MEASURES_COVERAGE = [
- {
- metric: 'coverage',
- history: [
- {
- date: '2011-10-01T22:01:00.000Z'
- },
- {
- date: '2011-10-25T10:27:41.000Z',
- value: '80.3'
- }
- ]
- },
- {
- metric: 'lines_to_cover',
- history: [
- {
- date: '2011-10-01T22:01:00.000Z',
- value: '60545'
- },
- {
- date: '2011-10-25T10:27:41.000Z',
- value: '65215'
- }
- ]
- },
- {
- metric: 'uncovered_lines',
- history: [
- {
- date: '2011-10-01T22:01:00.000Z',
- value: '40564'
- },
- {
- date: '2011-10-25T10:27:41.000Z',
- value: '10245'
- }
- ]
- }
-];
-
-const DEFAULT_PROPS = {
- addSeparator: true,
- measuresHistory: MEASURES_COVERAGE,
- tooltipIdx: 1
-};
-
-it('should render correctly', () => {
- expect(shallow(<GraphsTooltipsContentCoverage {...DEFAULT_PROPS} />)).toMatchSnapshot();
-});
-
-it('should render correctly when data is missing', () => {
- expect(
- shallow(<GraphsTooltipsContentCoverage {...DEFAULT_PROPS} tooltipIdx={0} />)
- ).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import GraphsTooltipsContentCoverage from '../GraphsTooltipsContentCoverage';
+import { parseDate } from '../../../../helpers/dates';
+
+const MEASURES_COVERAGE = [
+ {
+ metric: 'coverage',
+ history: [
+ { date: parseDate('2011-10-01T22:01:00.000Z') },
+ { date: parseDate('2011-10-25T10:27:41.000Z'), value: '80.3' }
+ ]
+ },
+ {
+ metric: 'lines_to_cover',
+ history: [
+ { date: parseDate('2011-10-01T22:01:00.000Z'), value: '60545' },
+ { date: parseDate('2011-10-25T10:27:41.000Z'), value: '65215' }
+ ]
+ },
+ {
+ metric: 'uncovered_lines',
+ history: [
+ { date: parseDate('2011-10-01T22:01:00.000Z'), value: '40564' },
+ { date: parseDate('2011-10-25T10:27:41.000Z'), value: '10245' }
+ ]
+ }
+];
+
+const DEFAULT_PROPS = {
+ addSeparator: true,
+ measuresHistory: MEASURES_COVERAGE,
+ tooltipIdx: 1
+};
+
+it('should render correctly', () => {
+ expect(shallow(<GraphsTooltipsContentCoverage {...DEFAULT_PROPS} />)).toMatchSnapshot();
+});
+
+it('should render correctly when data is missing', () => {
+ expect(
+ shallow(<GraphsTooltipsContentCoverage {...DEFAULT_PROPS} tooltipIdx={0} />)
+ ).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import GraphsTooltipsContentDuplication from '../GraphsTooltipsContentDuplication';
-
-const MEASURES_DUPLICATION = [
- {
- metric: 'duplicated_lines_density',
- history: [
- {
- date: '2011-10-01T22:01:00.000Z'
- },
- {
- date: '2011-10-25T10:27:41.000Z',
- value: '10245'
- }
- ]
- }
-];
-
-const DEFAULT_PROPS = {
- addSeparator: true,
- measuresHistory: MEASURES_DUPLICATION,
- tooltipIdx: 1
-};
-
-it('should render correctly', () => {
- expect(shallow(<GraphsTooltipsContentDuplication {...DEFAULT_PROPS} />)).toMatchSnapshot();
-});
-
-it('should render null when data is missing', () => {
- expect(
- shallow(<GraphsTooltipsContentDuplication {...DEFAULT_PROPS} tooltipIdx={0} />).type()
- ).toBeNull();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import GraphsTooltipsContentDuplication from '../GraphsTooltipsContentDuplication';
+import { parseDate } from '../../../../helpers/dates';
+
+const MEASURES_DUPLICATION = [
+ {
+ metric: 'duplicated_lines_density',
+ history: [
+ { date: parseDate('2011-10-01T22:01:00.000Z') },
+ { date: parseDate('2011-10-25T10:27:41.000Z'), value: '10245' }
+ ]
+ }
+];
+
+const DEFAULT_PROPS = {
+ addSeparator: true,
+ measuresHistory: MEASURES_DUPLICATION,
+ tooltipIdx: 1
+};
+
+it('should render correctly', () => {
+ expect(shallow(<GraphsTooltipsContentDuplication {...DEFAULT_PROPS} />)).toMatchSnapshot();
+});
+
+it('should render null when data is missing', () => {
+ expect(
+ shallow(<GraphsTooltipsContentDuplication {...DEFAULT_PROPS} tooltipIdx={0} />).type()
+ ).toBeNull();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import GraphsTooltipsContentEvents from '../GraphsTooltipsContentEvents';
-
-const EVENTS = [
- {
- key: '1',
- category: 'VERSION',
- name: '6.5'
- },
- {
- key: '2',
- category: 'OTHER',
- name: 'Foo'
- }
-];
-
-it('should render correctly', () => {
- expect(
- shallow(<GraphsTooltipsContentEvents addSeparator={true} events={EVENTS} />)
- ).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import GraphsTooltipsContentEvents from '../GraphsTooltipsContentEvents';
+
+const EVENTS = [
+ { key: '1', category: 'VERSION', name: '6.5' },
+ { key: '2', category: 'OTHER', name: 'Foo' }
+];
+
+it('should render correctly', () => {
+ expect(
+ shallow(<GraphsTooltipsContentEvents addSeparator={true} events={EVENTS} />)
+ ).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import GraphsTooltipsContentIssues from '../GraphsTooltipsContentIssues';
-
-const MEASURES_ISSUES = [
- {
- metric: 'bugs',
- history: [
- {
- date: '2011-10-01T22:01:00.000Z',
- value: '500'
- },
- {
- date: '2011-10-25T10:27:41.000Z',
- value: '1.2k'
- }
- ]
- },
- {
- metric: 'reliability_rating',
- history: [
- {
- date: '2011-10-01T22:01:00.000Z'
- },
- {
- date: '2011-10-25T10:27:41.000Z',
- value: '5.0'
- }
- ]
- }
-];
-
-const DEFAULT_PROPS = {
- measuresHistory: MEASURES_ISSUES,
- name: 'bugs',
- style: '2',
- tooltipIdx: 1,
- translatedName: 'Bugs',
- value: '1.2k'
-};
-
-it('should render correctly', () => {
- expect(shallow(<GraphsTooltipsContentIssues {...DEFAULT_PROPS} />)).toMatchSnapshot();
-});
-
-it('should render correctly when rating data is missing', () => {
- expect(
- shallow(<GraphsTooltipsContentIssues {...DEFAULT_PROPS} tooltipIdx={0} value="500" />)
- ).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import GraphsTooltipsContentIssues from '../GraphsTooltipsContentIssues';
+import { parseDate } from '../../../../helpers/dates';
+
+const MEASURES_ISSUES = [
+ {
+ metric: 'bugs',
+ history: [
+ { date: parseDate('2011-10-01T22:01:00.000Z'), value: '500' },
+ { date: parseDate('2011-10-25T10:27:41.000Z'), value: '1.2k' }
+ ]
+ },
+ {
+ metric: 'reliability_rating',
+ history: [
+ { date: parseDate('2011-10-01T22:01:00.000Z') },
+ { date: parseDate('2011-10-25T10:27:41.000Z'), value: '5.0' }
+ ]
+ }
+];
+
+const DEFAULT_PROPS = {
+ measuresHistory: MEASURES_ISSUES,
+ name: 'bugs',
+ style: '2',
+ tooltipIdx: 1,
+ translatedName: 'Bugs',
+ value: '1.2k'
+};
+
+it('should render correctly', () => {
+ expect(shallow(<GraphsTooltipsContentIssues {...DEFAULT_PROPS} />)).toMatchSnapshot();
+});
+
+it('should render correctly when rating data is missing', () => {
+ expect(
+ shallow(<GraphsTooltipsContentIssues {...DEFAULT_PROPS} tooltipIdx={0} value="500" />)
+ ).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import ProjectActivityAnalysesList from '../ProjectActivityAnalysesList';
-import { DEFAULT_GRAPH } from '../../utils';
-import * as dates from '../../../../helpers/dates';
-
-const ANALYSES = [
- {
- key: 'A1',
- date: dates.parseDate('2016-10-27T16:33:50+0000'),
- events: [
- {
- key: 'E1',
- category: 'VERSION',
- name: '6.5-SNAPSHOT'
- }
- ]
- },
- {
- key: 'A2',
- date: dates.parseDate('2016-10-27T12:21:15+0000'),
- events: []
- },
- {
- key: 'A3',
- date: dates.parseDate('2016-10-26T12:17:29+0000'),
- events: [
- {
- key: 'E2',
- category: 'VERSION',
- name: '6.4'
- },
- {
- key: 'E3',
- category: 'OTHER',
- name: 'foo'
- }
- ]
- },
- {
- key: 'A4',
- date: dates.parseDate('2016-10-24T16:33:50+0000'),
- events: [
- {
- key: 'E1',
- category: 'QUALITY_GATE',
- name: 'Quality gate changed to red...'
- }
- ]
- }
-];
-
-const DEFAULT_PROPS = {
- addCustomEvent: () => {},
- addVersion: () => {},
- analyses: ANALYSES,
- analysesLoading: false,
- canAdmin: false,
- changeEvent: () => {},
- deleteAnalysis: () => {},
- deleteEvent: () => {},
- inizializing: false,
- project: { qualifier: 'TRK' },
- query: { category: '', graph: DEFAULT_GRAPH, project: 'org.sonarsource.sonarqube:sonarqube' },
- updateQuery: () => {}
-};
-
-window.Number = val => val;
-
-dates.startOfDay = jest.fn(date => {
- const startDay = new Date(date);
- startDay.setUTCHours(0, 0, 0, 0);
- return startDay;
-});
-
-dates.toShortNotSoISOString = date => 'ISO.' + date;
-
-it('should render correctly', () => {
- expect(shallow(<ProjectActivityAnalysesList {...DEFAULT_PROPS} />)).toMatchSnapshot();
-});
-
-it('should correctly filter analyses by category', () => {
- const wrapper = shallow(<ProjectActivityAnalysesList {...DEFAULT_PROPS} />);
- wrapper.setProps({ query: { ...DEFAULT_PROPS.query, category: 'QUALITY_GATE' } });
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should correctly filter analyses by date range', () => {
- const wrapper = shallow(<ProjectActivityAnalysesList {...DEFAULT_PROPS} />);
- wrapper.setProps({
- query: {
- ...DEFAULT_PROPS.query,
- from: dates.parseDate('2016-10-27T16:33:50+0000'),
- to: dates.parseDate('2016-10-27T16:33:50+0000')
- }
- });
- expect(wrapper).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import ProjectActivityAnalysesList from '../ProjectActivityAnalysesList';
+import { DEFAULT_GRAPH } from '../../utils';
+import * as dates from '../../../../helpers/dates';
+
+jest.mock('../../../../helpers/dates', () => {
+ const actual = require.requireActual('../../../../helpers/dates');
+ return Object.assign({}, actual, {
+ startOfDay: (date: Date) => {
+ const startDay = new Date(date);
+ startDay.setUTCHours(0, 0, 0, 0);
+ return startDay;
+ },
+ toShortNotSoISOString: (date: string) => 'ISO.' + date
+ });
+});
+
+const ANALYSES = [
+ {
+ key: 'A1',
+ date: dates.parseDate('2016-10-27T16:33:50+0000'),
+ events: [{ key: 'E1', category: 'VERSION', name: '6.5-SNAPSHOT' }]
+ },
+ { key: 'A2', date: dates.parseDate('2016-10-27T12:21:15+0000'), events: [] },
+ {
+ key: 'A3',
+ date: dates.parseDate('2016-10-26T12:17:29+0000'),
+ events: [
+ { key: 'E2', category: 'VERSION', name: '6.4' },
+ { key: 'E3', category: 'OTHER', name: 'foo' }
+ ]
+ },
+ {
+ key: 'A4',
+ date: dates.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,
+ project: { qualifier: 'TRK' },
+ query: {
+ category: '',
+ customMetrics: [],
+ graph: DEFAULT_GRAPH,
+ project: 'org.sonarsource.sonarqube:sonarqube'
+ },
+ updateQuery: () => {}
+};
+
+it('should render correctly', () => {
+ expect(shallow(<ProjectActivityAnalysesList {...DEFAULT_PROPS} />)).toMatchSnapshot();
+});
+
+it('should correctly filter analyses by category', () => {
+ const wrapper = shallow(<ProjectActivityAnalysesList {...DEFAULT_PROPS} />);
+ wrapper.setProps({ query: { ...DEFAULT_PROPS.query, category: 'QUALITY_GATE' } });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should correctly filter analyses by date range', () => {
+ const wrapper = shallow(<ProjectActivityAnalysesList {...DEFAULT_PROPS} />);
+ wrapper.setProps({
+ query: {
+ ...DEFAULT_PROPS.query,
+ from: dates.parseDate('2016-10-27T16:33:50+0000'),
+ to: dates.parseDate('2016-10-27T16:33:50+0000')
+ }
+ });
+ expect(wrapper).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import ProjectActivityApp from '../ProjectActivityApp';
-import { DEFAULT_GRAPH } from '../../utils';
-import { parseDate } from '../../../../helpers/dates';
-
-const ANALYSES = [
- {
- key: 'A1',
- date: parseDate('2016-10-27T16:33:50+0200'),
- events: [
- {
- key: 'E1',
- category: 'VERSION',
- name: '6.5-SNAPSHOT'
- }
- ]
- },
- {
- key: 'A2',
- date: parseDate('2016-10-27T12:21:15+0200'),
- events: []
- },
- {
- key: 'A3',
- date: parseDate('2016-10-26T12:17:29+0200'),
- events: [
- {
- key: 'E2',
- category: 'VERSION',
- name: '6.4'
- },
- {
- key: 'E3',
- category: 'OTHER',
- name: 'foo'
- }
- ]
- }
-];
-
-const DEFAULT_PROPS = {
- addCustomEvent: () => {},
- addVersion: () => {},
- analyses: ANALYSES,
- analysesLoading: false,
- branch: { isMain: true },
- changeEvent: () => {},
- deleteAnalysis: () => {},
- deleteEvent: () => {},
- graphLoading: false,
- initializing: false,
- project: {
- key: 'org.sonarsource.sonarqube:sonarqube',
- leakPeriodDate: '2017-05-16T13:50:02+0200'
- },
- metrics: [{ key: 'code_smells', name: 'Code Smells', type: 'INT' }],
- measuresHistory: [
- {
- metric: 'code_smells',
- history: [
- { date: parseDate('Fri Mar 04 2016 10:40:12 GMT+0100 (CET)'), value: '1749' },
- { date: parseDate('Fri Mar 04 2016 18:40:16 GMT+0100 (CET)'), value: '2286' }
- ]
- }
- ],
- query: { category: '', graph: DEFAULT_GRAPH, project: 'org.sonarsource.sonarqube:sonarqube' },
- updateQuery: () => {}
-};
-
-it('should render correctly', () => {
- expect(shallow(<ProjectActivityApp {...DEFAULT_PROPS} />)).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import ProjectActivityApp from '../ProjectActivityApp';
+import { DEFAULT_GRAPH } from '../../utils';
+import { parseDate } from '../../../../helpers/dates';
+
+const ANALYSES = [
+ {
+ key: 'A1',
+ date: parseDate('2016-10-27T16:33:50+0200'),
+ events: [
+ {
+ key: 'E1',
+ category: 'VERSION',
+ name: '6.5-SNAPSHOT'
+ }
+ ]
+ },
+ {
+ key: 'A2',
+ date: parseDate('2016-10-27T12:21:15+0200'),
+ events: []
+ },
+ {
+ key: 'A3',
+ date: parseDate('2016-10-26T12:17:29+0200'),
+ events: [
+ {
+ key: 'E2',
+ category: 'VERSION',
+ name: '6.4'
+ },
+ {
+ key: 'E3',
+ category: 'OTHER',
+ name: 'foo'
+ }
+ ]
+ }
+];
+
+const DEFAULT_PROPS = {
+ addCustomEvent: jest.fn().mockResolvedValue(undefined),
+ addVersion: jest.fn().mockResolvedValue(undefined),
+ analyses: ANALYSES,
+ analysesLoading: false,
+ branch: { isMain: true },
+ changeEvent: jest.fn().mockResolvedValue(undefined),
+ deleteAnalysis: jest.fn().mockResolvedValue(undefined),
+ deleteEvent: jest.fn().mockResolvedValue(undefined),
+ graphLoading: false,
+ initializing: false,
+ project: {
+ leakPeriodDate: '2017-05-16T13:50:02+0200',
+ qualifier: 'TRK'
+ },
+ metrics: [{ id: '1', key: 'code_smells', name: 'Code Smells', type: 'INT' }],
+ measuresHistory: [
+ {
+ metric: 'code_smells',
+ history: [
+ { date: parseDate('Fri Mar 04 2016 10:40:12 GMT+0100 (CET)'), value: '1749' },
+ { date: parseDate('Fri Mar 04 2016 18:40:16 GMT+0100 (CET)'), value: '2286' }
+ ]
+ }
+ ],
+ query: {
+ category: '',
+ customMetrics: [],
+ graph: DEFAULT_GRAPH,
+ project: 'org.sonarsource.sonarqube:sonarqube'
+ },
+ updateQuery: () => {}
+};
+
+it('should render correctly', () => {
+ expect(shallow(<ProjectActivityApp {...DEFAULT_PROPS} />)).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallowWithIntl } from '../../../../helpers/testUtils';
-import ProjectActivityDateInput from '../ProjectActivityDateInput';
-import { parseDate } from '../../../../helpers/dates';
-
-it('should render correctly the date inputs', () => {
- expect(
- shallowWithIntl(
- <ProjectActivityDateInput
- from={parseDate('2016-10-27T12:21:15+0000')}
- onChange={() => {}}
- to={parseDate('2016-12-27T12:21:15+0000')}
- />
- )
- ).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallowWithIntl } from '../../../../helpers/testUtils';
+import ProjectActivityDateInput from '../ProjectActivityDateInput';
+import { parseDate } from '../../../../helpers/dates';
+
+it('should render correctly the date inputs', () => {
+ expect(
+ shallowWithIntl(
+ <ProjectActivityDateInput
+ from={parseDate('2016-10-27T12:21:15+0000')}
+ onChange={() => {}}
+ to={parseDate('2016-12-27T12:21:15+0000')}
+ />
+ )
+ ).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import ProjectActivityGraphs from '../ProjectActivityGraphs';
-import { DEFAULT_GRAPH } from '../../utils';
-import { parseDate } from '../../../../helpers/dates';
-
-const ANALYSES = [
- {
- key: 'A1',
- date: '2016-10-27T16:33:50+0200',
- events: [
- {
- key: 'E1',
- category: 'VERSION',
- name: '6.5-SNAPSHOT'
- }
- ]
- },
- {
- key: 'A2',
- date: '2016-10-27T12:21:15+0200',
- events: []
- },
- {
- key: 'A3',
- date: '2016-10-26T12:17:29+0200',
- events: [
- {
- key: 'E2',
- category: 'VERSION',
- name: '6.4'
- },
- {
- key: 'E3',
- category: 'OTHER',
- name: 'foo'
- }
- ]
- }
-];
-
-const METRICS = [{ key: 'code_smells', name: 'Code Smells', type: 'INT' }];
-
-const DEFAULT_PROPS = {
- analyses: ANALYSES,
- leakPeriodDate: '2017-05-16T13:50:02+0200',
- loading: false,
- measuresHistory: [
- {
- metric: 'code_smells',
- history: [
- { date: parseDate('2016-10-26T12:17:29+0200'), value: '2286' },
- { date: parseDate('2016-10-27T12:21:15+0200'), value: '1749' },
- { date: parseDate('2016-10-27T16:33:50+0200'), value: '500' }
- ]
- }
- ],
- metrics: METRICS,
- query: { category: '', graph: DEFAULT_GRAPH, project: 'org.sonarsource.sonarqube:sonarqube' },
- updateQuery: () => {}
-};
-
-it('should render correctly the graph and legends', () => {
- expect(shallow(<ProjectActivityGraphs {...DEFAULT_PROPS} />)).toMatchSnapshot();
-});
-
-it('should render correctly with filter history on dates', () => {
- const wrapper = shallow(
- <ProjectActivityGraphs
- {...DEFAULT_PROPS}
- query={{ ...DEFAULT_PROPS.query, from: '2016-10-27T12:21:15+0200' }}
- />
- );
- expect(wrapper.state()).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import ProjectActivityGraphs from '../ProjectActivityGraphs';
+import { DEFAULT_GRAPH } from '../../utils';
+import { parseDate } from '../../../../helpers/dates';
+
+const ANALYSES = [
+ {
+ key: 'A1',
+ date: parseDate('2016-10-27T16:33:50+0200'),
+ events: [{ key: 'E1', category: 'VERSION', name: '6.5-SNAPSHOT' }]
+ },
+ {
+ key: 'A2',
+ date: parseDate('2016-10-27T12:21:15+0200'),
+ events: []
+ },
+ {
+ key: 'A3',
+ date: parseDate('2016-10-26T12:17:29+0200'),
+ events: [
+ { key: 'E2', category: 'VERSION', name: '6.4' },
+ { key: 'E3', category: 'OTHER', name: 'foo' }
+ ]
+ }
+];
+
+const METRICS = [{ id: '1', key: 'code_smells', name: 'Code Smells', type: 'INT' }];
+
+const DEFAULT_PROPS: ProjectActivityGraphs['props'] = {
+ analyses: ANALYSES,
+ leakPeriodDate: parseDate('2017-05-16T13:50:02+0200'),
+ loading: false,
+ measuresHistory: [
+ {
+ metric: 'code_smells',
+ history: [
+ { date: parseDate('2016-10-26T12:17:29+0200'), value: '2286' },
+ { date: parseDate('2016-10-27T12:21:15+0200'), value: '1749' },
+ { date: parseDate('2016-10-27T16:33:50+0200'), value: '500' }
+ ]
+ }
+ ],
+ metrics: METRICS,
+ query: {
+ category: '',
+ customMetrics: [],
+ graph: DEFAULT_GRAPH,
+ project: 'org.sonarsource.sonarqube:sonarqube'
+ },
+ updateQuery: () => {}
+};
+
+it('should render correctly the graph and legends', () => {
+ expect(shallow(<ProjectActivityGraphs {...DEFAULT_PROPS} />)).toMatchSnapshot();
+});
+
+it('should render correctly with filter history on dates', () => {
+ const wrapper = shallow(
+ <ProjectActivityGraphs
+ {...DEFAULT_PROPS}
+ query={{ ...DEFAULT_PROPS.query, from: parseDate('2016-10-27T12:21:15+0200') }}
+ />
+ );
+ expect(wrapper.state()).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import ProjectActivityPageHeader from '../ProjectActivityPageHeader';
-import { parseDate } from '../../../../helpers/dates';
-
-it('should render correctly the list of series', () => {
- expect(
- shallow(
- <ProjectActivityPageHeader
- category=""
- from={parseDate('2016-10-27T12:21:15+0200')}
- project={{}}
- updateQuery={() => {}}
- />
- )
- ).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import ProjectActivityPageHeader from '../ProjectActivityPageHeader';
+import { parseDate } from '../../../../helpers/dates';
+
+it('should render correctly the list of series', () => {
+ expect(
+ shallow(
+ <ProjectActivityPageHeader
+ category=""
+ from={parseDate('2016-10-27T12:21:15+0200')}
+ project={{ qualifier: 'TRK' }}
+ updateQuery={() => {}}
+ />
+ )
+ ).toMatchSnapshot();
+});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should correctly render a graph 1`] = `
-<div
- className="project-activity-graph-container"
->
- <GraphsLegendStatic
- series={
- Array [
- Object {
- "data": Array [
- Object {
- "x": 2016-10-27T14:33:50.000Z,
- "y": 5,
- },
- Object {
- "x": 2016-10-27T10:21:15.000Z,
- "y": 16,
- },
- Object {
- "x": 2016-10-26T10:17:29.000Z,
- "y": 12,
- },
- ],
- "name": "bugs",
- "translatedName": "metric.bugs.name",
- },
- ]
- }
- />
- <div
- className="project-activity-graph"
- >
- <AutoSizer
- disableHeight={false}
- disableWidth={false}
- onResize={[Function]}
- style={Object {}}
- />
- </div>
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should correctly render a graph 1`] = `
+<div
+ className="project-activity-graph-container"
+>
+ <GraphsLegendStatic
+ series={
+ Array [
+ Object {
+ "data": Array [
+ Object {
+ "x": 2016-10-27T14:33:50.000Z,
+ "y": 5,
+ },
+ Object {
+ "x": 2016-10-27T10:21:15.000Z,
+ "y": 16,
+ },
+ Object {
+ "x": 2016-10-26T10:17:29.000Z,
+ "y": 12,
+ },
+ ],
+ "name": "bugs",
+ "translatedName": "metric.bugs.name",
+ "type": "INT",
+ },
+ ]
+ }
+ />
+ <div
+ className="project-activity-graph"
+ >
+ <AutoSizer
+ disableHeight={false}
+ disableWidth={false}
+ onResize={[Function]}
+ style={Object {}}
+ />
+ </div>
+</div>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should correctly filter events 1`] = `
-Array [
- Object {
- "className": "VERSION",
- "date": 2016-10-26T10:17:29.000Z,
- "name": "6.4",
- },
- Object {
- "className": "VERSION",
- "date": 2016-10-27T14:33:50.000Z,
- "name": "6.5-SNAPSHOT",
- },
-]
-`;
-
-exports[`should correctly filter events 2`] = `
-Array [
- Object {
- "className": "OTHER",
- "date": 2016-10-26T10:17:29.000Z,
- "name": "foo",
- },
-]
-`;
-
-exports[`should correctly render a graph 1`] = `
-<div
- className="project-activity-graphs"
->
- <GraphHistory
- events={Array []}
- graph="issues"
- graphEndDate={null}
- graphStartDate={null}
- isCustom={false}
- key="0"
- leakPeriodDate="2017-05-16T13:50:02+0200"
- measuresHistory={Array []}
- removeCustomMetric={[Function]}
- selectedDate={null}
- series={
- Array [
- Object {
- "data": Array [
- Object {
- "x": 2016-10-27T14:33:50.000Z,
- "y": 5,
- },
- Object {
- "x": 2016-10-27T10:21:15.000Z,
- "y": 16,
- },
- Object {
- "x": 2016-10-26T10:17:29.000Z,
- "y": 12,
- },
- ],
- "name": "bugs",
- "translatedName": "metric.bugs.name",
- },
- ]
- }
- showAreas={false}
- updateGraphZoom={[Function]}
- updateSelectedDate={[Function]}
- updateTooltip={[Function]}
- />
-</div>
-`;
-
-exports[`should correctly render multiple graphs 1`] = `
-<div
- className="project-activity-graphs"
->
- <GraphHistory
- events={Array []}
- graph="issues"
- graphEndDate={null}
- graphStartDate={null}
- isCustom={false}
- key="0"
- leakPeriodDate="2017-05-16T13:50:02+0200"
- measuresHistory={Array []}
- removeCustomMetric={[Function]}
- selectedDate={null}
- series={
- Array [
- Object {
- "data": Array [
- Object {
- "x": 2016-10-27T14:33:50.000Z,
- "y": 5,
- },
- Object {
- "x": 2016-10-27T10:21:15.000Z,
- "y": 16,
- },
- Object {
- "x": 2016-10-26T10:17:29.000Z,
- "y": 12,
- },
- ],
- "name": "bugs",
- "translatedName": "metric.bugs.name",
- },
- ]
- }
- showAreas={false}
- updateGraphZoom={[Function]}
- updateSelectedDate={[Function]}
- updateTooltip={[Function]}
- />
- <GraphHistory
- events={Array []}
- graph="issues"
- graphEndDate={null}
- graphStartDate={null}
- isCustom={false}
- key="1"
- leakPeriodDate="2017-05-16T13:50:02+0200"
- measuresHistory={Array []}
- removeCustomMetric={[Function]}
- selectedDate={null}
- series={
- Array [
- Object {
- "data": Array [
- Object {
- "x": 2016-10-27T14:33:50.000Z,
- "y": 5,
- },
- Object {
- "x": 2016-10-27T10:21:15.000Z,
- "y": 16,
- },
- Object {
- "x": 2016-10-26T10:17:29.000Z,
- "y": 12,
- },
- ],
- "name": "bugs",
- "translatedName": "metric.bugs.name",
- },
- ]
- }
- showAreas={false}
- updateGraphZoom={[Function]}
- updateSelectedDate={[Function]}
- updateTooltip={[Function]}
- />
-</div>
-`;
-
-exports[`should show that there is no history data 1`] = `
-<div
- className="project-activity-graph-container"
->
- <div
- className="note text-center"
- >
- component_measures.no_history
- </div>
-</div>
-`;
-
-exports[`should show that there is no history data 2`] = `
-<div
- className="project-activity-graph-container"
->
- <div
- className="note text-center"
- >
- component_measures.no_history
- </div>
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should correctly render a graph 1`] = `
+<div
+ className="project-activity-graphs"
+>
+ <GraphHistory
+ events={Array []}
+ graph="issues"
+ isCustom={false}
+ key="0"
+ leakPeriodDate={2017-05-16T11:50:02.000Z}
+ measuresHistory={Array []}
+ metricsType="INT"
+ removeCustomMetric={[Function]}
+ series={
+ Array [
+ Object {
+ "data": Array [
+ Object {
+ "x": 2016-10-27T14:33:50.000Z,
+ "y": 5,
+ },
+ Object {
+ "x": 2016-10-27T10:21:15.000Z,
+ "y": 16,
+ },
+ Object {
+ "x": 2016-10-26T10:17:29.000Z,
+ "y": 12,
+ },
+ ],
+ "name": "bugs",
+ "translatedName": "metric.bugs.name",
+ "type": "INT",
+ },
+ ]
+ }
+ showAreas={false}
+ updateGraphZoom={[Function]}
+ updateSelectedDate={[Function]}
+ updateTooltip={[Function]}
+ />
+</div>
+`;
+
+exports[`should correctly render multiple graphs 1`] = `
+<div
+ className="project-activity-graphs"
+>
+ <GraphHistory
+ events={Array []}
+ graph="issues"
+ isCustom={false}
+ key="0"
+ leakPeriodDate={2017-05-16T11:50:02.000Z}
+ measuresHistory={Array []}
+ metricsType="INT"
+ removeCustomMetric={[Function]}
+ series={
+ Array [
+ Object {
+ "data": Array [
+ Object {
+ "x": 2016-10-27T14:33:50.000Z,
+ "y": 5,
+ },
+ Object {
+ "x": 2016-10-27T10:21:15.000Z,
+ "y": 16,
+ },
+ Object {
+ "x": 2016-10-26T10:17:29.000Z,
+ "y": 12,
+ },
+ ],
+ "name": "bugs",
+ "translatedName": "metric.bugs.name",
+ "type": "INT",
+ },
+ ]
+ }
+ showAreas={false}
+ updateGraphZoom={[Function]}
+ updateSelectedDate={[Function]}
+ updateTooltip={[Function]}
+ />
+ <GraphHistory
+ events={Array []}
+ graph="issues"
+ isCustom={false}
+ key="1"
+ leakPeriodDate={2017-05-16T11:50:02.000Z}
+ measuresHistory={Array []}
+ metricsType="INT"
+ removeCustomMetric={[Function]}
+ series={
+ Array [
+ Object {
+ "data": Array [
+ Object {
+ "x": 2016-10-27T14:33:50.000Z,
+ "y": 5,
+ },
+ Object {
+ "x": 2016-10-27T10:21:15.000Z,
+ "y": 16,
+ },
+ Object {
+ "x": 2016-10-26T10:17:29.000Z,
+ "y": 12,
+ },
+ ],
+ "name": "bugs",
+ "translatedName": "metric.bugs.name",
+ "type": "INT",
+ },
+ ]
+ }
+ showAreas={false}
+ updateGraphZoom={[Function]}
+ updateSelectedDate={[Function]}
+ updateTooltip={[Function]}
+ />
+</div>
+`;
+
+exports[`should show that there is no history data 1`] = `
+<div
+ className="project-activity-graph-container"
+>
+ <div
+ className="note text-center"
+ >
+ component_measures.no_history
+ </div>
+</div>
+`;
+
+exports[`should show that there is no history data 2`] = `
+<div
+ className="project-activity-graph-container"
+>
+ <div
+ className="note text-center"
+ >
+ component_measures.no_history
+ </div>
+</div>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly the list of series 1`] = `
-<div
- className="project-activity-graph-legends"
->
- <span
- className="spacer-left spacer-right"
- key="bugs"
- >
- <GraphsLegendItem
- metric="bugs"
- name="Bugs"
- removeMetric={[Function]}
- showWarning={false}
- style="0"
- />
- </span>
- <span
- className="spacer-left spacer-right"
- key="my_metric"
- >
- <GraphsLegendItem
- metric="my_metric"
- name="My Metric"
- removeMetric={[Function]}
- showWarning={false}
- style="1"
- />
- </span>
- <Tooltip
- key="foo"
- overlay="project_activity.graphs.custom.metric_no_history"
- >
- <span
- className="spacer-left spacer-right"
- >
- <GraphsLegendItem
- metric="foo"
- name="Foo"
- removeMetric={[Function]}
- showWarning={true}
- style="2"
- />
- </span>
- </Tooltip>
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly the list of series 1`] = `
+<div
+ className="project-activity-graph-legends"
+>
+ <span
+ className="spacer-left spacer-right"
+ key="bugs"
+ >
+ <GraphsLegendItem
+ metric="bugs"
+ name="Bugs"
+ removeMetric={[Function]}
+ showWarning={false}
+ style="0"
+ />
+ </span>
+ <span
+ className="spacer-left spacer-right"
+ key="my_metric"
+ >
+ <GraphsLegendItem
+ metric="my_metric"
+ name="My Metric"
+ removeMetric={[Function]}
+ showWarning={false}
+ style="1"
+ />
+ </span>
+ <Tooltip
+ key="foo"
+ overlay="project_activity.graphs.custom.metric_no_history"
+ >
+ <span
+ className="spacer-left spacer-right"
+ >
+ <GraphsLegendItem
+ metric="foo"
+ name="Foo"
+ removeMetric={[Function]}
+ showWarning={true}
+ style="2"
+ />
+ </span>
+ </Tooltip>
+</div>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly the list of series 1`] = `
-<div
- className="project-activity-graph-legends"
->
- <GraphsLegendItem
- className="big-spacer-left big-spacer-right"
- key="bugs"
- metric="bugs"
- name="Bugs"
- style="0"
- />
- <GraphsLegendItem
- className="big-spacer-left big-spacer-right"
- key="code_smells"
- metric="code_smells"
- name="Code Smells"
- style="1"
- />
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly the list of series 1`] = `
+<div
+ className="project-activity-graph-legends"
+>
+ <GraphsLegendItem
+ className="big-spacer-left big-spacer-right"
+ key="bugs"
+ metric="bugs"
+ name="Bugs"
+ style="0"
+ />
+ <GraphsLegendItem
+ className="big-spacer-left big-spacer-right"
+ key="code_smells"
+ metric="code_smells"
+ name="Code Smells"
+ style="1"
+ />
+</div>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should not add separators if not needed 1`] = `
-<Popup
- className="disabled-pointer-events"
- placement="left-top"
- style={
- Object {
- "left": 476,
- "top": 30,
- "width": 250,
- }
- }
->
- <div
- className="project-activity-graph-tooltip"
- >
- <div
- className="project-activity-graph-tooltip-title spacer-bottom"
- >
- <DateTimeFormatter
- date={2011-10-01T22:01:00.000Z}
- />
- </div>
- <table
- className="width-100"
- >
- <tbody />
- <GraphsTooltipsContentCoverage
- addSeparator={false}
- measuresHistory={Array []}
- tooltipIdx={0}
- />
- </table>
- </div>
-</Popup>
-`;
-
-exports[`should render correctly for issues graphs 1`] = `
-<Popup
- className="disabled-pointer-events"
- placement="left-top"
- style={
- Object {
- "left": 476,
- "top": 30,
- "width": 250,
- }
- }
->
- <div
- className="project-activity-graph-tooltip"
- >
- <div
- className="project-activity-graph-tooltip-title spacer-bottom"
- >
- <DateTimeFormatter
- date={2011-10-01T22:01:00.000Z}
- />
- </div>
- <table
- className="width-100"
- >
- <tbody>
- <GraphsTooltipsContentIssues
- key="bugs"
- measuresHistory={Array []}
- name="bugs"
- style="0"
- tooltipIdx={0}
- translatedName="Bugs"
- value="Formated.3"
- />
- <GraphsTooltipsContentIssues
- key="code_smells"
- measuresHistory={Array []}
- name="code_smells"
- style="1"
- tooltipIdx={0}
- translatedName="Code Smells"
- value="Formated.18"
- />
- <GraphsTooltipsContentIssues
- key="vulnerabilities"
- measuresHistory={Array []}
- name="vulnerabilities"
- style="2"
- tooltipIdx={0}
- translatedName="Vulnerabilities"
- value="Formated.0"
- />
- </tbody>
- </table>
- </div>
-</Popup>
-`;
-
-exports[`should render correctly for random graphs 1`] = `
-<Popup
- className="disabled-pointer-events"
- placement="left-top"
- style={
- Object {
- "left": 476,
- "top": 30,
- "width": 250,
- }
- }
->
- <div
- className="project-activity-graph-tooltip"
- >
- <div
- className="project-activity-graph-tooltip-title spacer-bottom"
- >
- <DateTimeFormatter
- date={2011-10-25T10:27:41.000Z}
- />
- </div>
- <table
- className="width-100"
- >
- <tbody>
- <GraphsTooltipsContent
- key="bugs"
- name="bugs"
- style="0"
- translatedName="Bugs"
- value="Formated.0"
- />
- <GraphsTooltipsContent
- key="code_smells"
- name="code_smells"
- style="1"
- translatedName="Code Smells"
- value="Formated.15"
- />
- <GraphsTooltipsContent
- key="vulnerabilities"
- name="vulnerabilities"
- style="2"
- translatedName="Vulnerabilities"
- value="Formated.1"
- />
- </tbody>
- </table>
- </div>
-</Popup>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should not add separators if not needed 1`] = `
+<Popup
+ className="disabled-pointer-events"
+ placement="left-top"
+ style={
+ Object {
+ "left": 476,
+ "top": 30,
+ "width": 250,
+ }
+ }
+>
+ <div
+ className="project-activity-graph-tooltip"
+ >
+ <div
+ className="project-activity-graph-tooltip-title spacer-bottom"
+ >
+ <DateTimeFormatter
+ date={2011-10-01T22:01:00.000Z}
+ />
+ </div>
+ <table
+ className="width-100"
+ >
+ <tbody />
+ <GraphsTooltipsContentCoverage
+ addSeparator={false}
+ measuresHistory={Array []}
+ tooltipIdx={0}
+ />
+ </table>
+ </div>
+</Popup>
+`;
+
+exports[`should render correctly for issues graphs 1`] = `
+<Popup
+ className="disabled-pointer-events"
+ placement="left-top"
+ style={
+ Object {
+ "left": 476,
+ "top": 30,
+ "width": 250,
+ }
+ }
+>
+ <div
+ className="project-activity-graph-tooltip"
+ >
+ <div
+ className="project-activity-graph-tooltip-title spacer-bottom"
+ >
+ <DateTimeFormatter
+ date={2011-10-01T22:01:00.000Z}
+ />
+ </div>
+ <table
+ className="width-100"
+ >
+ <tbody>
+ <GraphsTooltipsContentIssues
+ key="bugs"
+ measuresHistory={Array []}
+ name="bugs"
+ style="0"
+ tooltipIdx={0}
+ translatedName="Bugs"
+ value="Formated.3"
+ />
+ <GraphsTooltipsContentIssues
+ key="code_smells"
+ measuresHistory={Array []}
+ name="code_smells"
+ style="1"
+ tooltipIdx={0}
+ translatedName="Code Smells"
+ value="Formated.18"
+ />
+ <GraphsTooltipsContentIssues
+ key="vulnerabilities"
+ measuresHistory={Array []}
+ name="vulnerabilities"
+ style="2"
+ tooltipIdx={0}
+ translatedName="Vulnerabilities"
+ value="Formated.0"
+ />
+ </tbody>
+ </table>
+ </div>
+</Popup>
+`;
+
+exports[`should render correctly for random graphs 1`] = `
+<Popup
+ className="disabled-pointer-events"
+ placement="left-top"
+ style={
+ Object {
+ "left": 476,
+ "top": 30,
+ "width": 250,
+ }
+ }
+>
+ <div
+ className="project-activity-graph-tooltip"
+ >
+ <div
+ className="project-activity-graph-tooltip-title spacer-bottom"
+ >
+ <DateTimeFormatter
+ date={2011-10-25T10:27:41.000Z}
+ />
+ </div>
+ <table
+ className="width-100"
+ >
+ <tbody>
+ <GraphsTooltipsContent
+ key="bugs"
+ name="bugs"
+ style="0"
+ translatedName="Bugs"
+ value="Formated.0"
+ />
+ <GraphsTooltipsContent
+ key="code_smells"
+ name="code_smells"
+ style="1"
+ translatedName="Code Smells"
+ value="Formated.15"
+ />
+ <GraphsTooltipsContent
+ key="vulnerabilities"
+ name="vulnerabilities"
+ style="2"
+ translatedName="Vulnerabilities"
+ value="Formated.1"
+ />
+ </tbody>
+ </table>
+ </div>
+</Popup>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<tr
- className="project-activity-graph-tooltip-line"
- key="code_smells"
->
- <td
- className="thin"
- >
- <ChartLegendIcon
- className="spacer-right line-chart-legend line-chart-legend-1"
- />
- </td>
- <td
- className="project-activity-graph-tooltip-value text-right spacer-right thin"
- >
- 1.2k
- </td>
- <td>
- Code Smells
- </td>
-</tr>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<tr
+ className="project-activity-graph-tooltip-line"
+ key="code_smells"
+>
+ <td
+ className="thin"
+ >
+ <ChartLegendIcon
+ className="spacer-right line-chart-legend line-chart-legend-1"
+ />
+ </td>
+ <td
+ className="project-activity-graph-tooltip-value text-right spacer-right thin"
+ >
+ 1.2k
+ </td>
+ <td>
+ Code Smells
+ </td>
+</tr>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<tbody>
- <tr>
- <td
- className="project-activity-graph-tooltip-separator"
- colSpan="3"
- >
- <hr />
- </td>
- </tr>
- <tr
- className="project-activity-graph-tooltip-line"
- >
- <td
- className="project-activity-graph-tooltip-value text-right spacer-right thin"
- colSpan="2"
- >
- 10short_number_suffix.k
- </td>
- <td>
- metric.uncovered_lines.name
- </td>
- </tr>
- <tr
- className="project-activity-graph-tooltip-line"
- >
- <td
- className="project-activity-graph-tooltip-value text-right spacer-right thin"
- colSpan="2"
- >
- 80.3%
- </td>
- <td>
- metric.coverage.name
- </td>
- </tr>
-</tbody>
-`;
-
-exports[`should render correctly when data is missing 1`] = `
-<tbody>
- <tr>
- <td
- className="project-activity-graph-tooltip-separator"
- colSpan="3"
- >
- <hr />
- </td>
- </tr>
- <tr
- className="project-activity-graph-tooltip-line"
- >
- <td
- className="project-activity-graph-tooltip-value text-right spacer-right thin"
- colSpan="2"
- >
- 41short_number_suffix.k
- </td>
- <td>
- metric.uncovered_lines.name
- </td>
- </tr>
-</tbody>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<tbody>
+ <tr>
+ <td
+ className="project-activity-graph-tooltip-separator"
+ colSpan={3}
+ >
+ <hr />
+ </td>
+ </tr>
+ <tr
+ className="project-activity-graph-tooltip-line"
+ >
+ <td
+ className="project-activity-graph-tooltip-value text-right spacer-right thin"
+ colSpan={2}
+ >
+ 10short_number_suffix.k
+ </td>
+ <td>
+ metric.uncovered_lines.name
+ </td>
+ </tr>
+ <tr
+ className="project-activity-graph-tooltip-line"
+ >
+ <td
+ className="project-activity-graph-tooltip-value text-right spacer-right thin"
+ colSpan={2}
+ >
+ 80.3%
+ </td>
+ <td>
+ metric.coverage.name
+ </td>
+ </tr>
+</tbody>
+`;
+
+exports[`should render correctly when data is missing 1`] = `
+<tbody>
+ <tr>
+ <td
+ className="project-activity-graph-tooltip-separator"
+ colSpan={3}
+ >
+ <hr />
+ </td>
+ </tr>
+ <tr
+ className="project-activity-graph-tooltip-line"
+ >
+ <td
+ className="project-activity-graph-tooltip-value text-right spacer-right thin"
+ colSpan={2}
+ >
+ 41short_number_suffix.k
+ </td>
+ <td>
+ metric.uncovered_lines.name
+ </td>
+ </tr>
+</tbody>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<tbody>
- <tr>
- <td
- className="project-activity-graph-tooltip-separator"
- colSpan="3"
- >
- <hr />
- </td>
- </tr>
- <tr
- className="project-activity-graph-tooltip-line"
- >
- <td
- className="project-activity-graph-tooltip-value text-right spacer-right thin"
- colSpan="2"
- >
- 10,245.0%
- </td>
- <td>
- metric.duplicated_lines_density.name
- </td>
- </tr>
-</tbody>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<tbody>
+ <tr>
+ <td
+ className="project-activity-graph-tooltip-separator"
+ colSpan={3}
+ >
+ <hr />
+ </td>
+ </tr>
+ <tr
+ className="project-activity-graph-tooltip-line"
+ >
+ <td
+ className="project-activity-graph-tooltip-value text-right spacer-right thin"
+ colSpan={2}
+ >
+ 10,245.0%
+ </td>
+ <td>
+ metric.duplicated_lines_density.name
+ </td>
+ </tr>
+</tbody>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<tbody>
- <tr>
- <td
- className="project-activity-graph-tooltip-separator"
- colSpan="3"
- >
- <hr />
- </td>
- </tr>
- <tr
- className="project-activity-graph-tooltip-line"
- >
- <td
- colSpan="3"
- >
- <span>
- events
- :
- </span>
- <span
- className="spacer-left"
- key="1"
- >
- <ProjectEventIcon
- className="project-activity-event-icon VERSION"
- />
- </span>
- <span
- className="spacer-left"
- key="2"
- >
- <ProjectEventIcon
- className="project-activity-event-icon OTHER"
- />
- </span>
- </td>
- </tr>
-</tbody>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<tbody>
+ <tr>
+ <td
+ className="project-activity-graph-tooltip-separator"
+ colSpan={3}
+ >
+ <hr />
+ </td>
+ </tr>
+ <tr
+ className="project-activity-graph-tooltip-line"
+ >
+ <td
+ colSpan={3}
+ >
+ <span>
+ events
+ :
+ </span>
+ <span
+ className="spacer-left"
+ key="1"
+ >
+ <ProjectEventIcon
+ className="project-activity-event-icon VERSION"
+ />
+ </span>
+ <span
+ className="spacer-left"
+ key="2"
+ >
+ <ProjectEventIcon
+ className="project-activity-event-icon OTHER"
+ />
+ </span>
+ </td>
+ </tr>
+</tbody>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<tr
- className="project-activity-graph-tooltip-issues-line"
- key="bugs"
->
- <td
- className="thin"
- >
- <ChartLegendIcon
- className="spacer-right line-chart-legend line-chart-legend-2"
- />
- </td>
- <td
- className="text-right spacer-right"
- >
- <span
- className="project-activity-graph-tooltip-value"
- >
- 1.2k
- </span>
- <Rating
- className="spacer-left"
- small={true}
- value="5.0"
- />
- </td>
- <td>
- Bugs
- </td>
-</tr>
-`;
-
-exports[`should render correctly when rating data is missing 1`] = `
-<tr
- className="project-activity-graph-tooltip-issues-line"
- key="bugs"
->
- <td
- className="thin"
- >
- <ChartLegendIcon
- className="spacer-right line-chart-legend line-chart-legend-2"
- />
- </td>
- <td
- className="text-right spacer-right"
- >
- <span
- className="project-activity-graph-tooltip-value"
- >
- 500
- </span>
- </td>
- <td>
- Bugs
- </td>
-</tr>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<tr
+ className="project-activity-graph-tooltip-issues-line"
+ key="bugs"
+>
+ <td
+ className="thin"
+ >
+ <ChartLegendIcon
+ className="spacer-right line-chart-legend line-chart-legend-2"
+ />
+ </td>
+ <td
+ className="text-right spacer-right"
+ >
+ <span
+ className="project-activity-graph-tooltip-value"
+ >
+ 1.2k
+ </span>
+ <Rating
+ className="spacer-left"
+ small={true}
+ value="5.0"
+ />
+ </td>
+ <td>
+ Bugs
+ </td>
+</tr>
+`;
+
+exports[`should render correctly when rating data is missing 1`] = `
+<tr
+ className="project-activity-graph-tooltip-issues-line"
+ key="bugs"
+>
+ <td
+ className="thin"
+ >
+ <ChartLegendIcon
+ className="spacer-right line-chart-legend line-chart-legend-2"
+ />
+ </td>
+ <td
+ className="text-right spacer-right"
+ >
+ <span
+ className="project-activity-graph-tooltip-value"
+ >
+ 500
+ </span>
+ </td>
+ <td>
+ Bugs
+ </td>
+</tr>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should correctly filter analyses by category 1`] = `
-<ul
- className="project-activity-versions-list"
- onScroll={[Function]}
- style={
- Object {
- "paddingTop": 52,
- }
- }
->
- <li
- key="E2"
- >
- <div
- className="project-activity-version-badge first"
- >
- <Tooltip
- mouseEnterDelay={0.5}
- overlay="version 6.4"
- >
- <span
- className="badge"
- >
- 6.4
- </span>
- </Tooltip>
- </div>
- <ul
- className="project-activity-days-list"
- >
- <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"
- >
- <ProjectActivityAnalysis
- addCustomEvent={[Function]}
- addVersion={[Function]}
- 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",
- }
- }
- canAdmin={false}
- canCreateVersion={true}
- changeEvent={[Function]}
- deleteAnalysis={[Function]}
- deleteEvent={[Function]}
- isFirst={false}
- key="A4"
- selected={false}
- updateSelectedDate={[Function]}
- />
- </ul>
- </li>
- </ul>
- </li>
-</ul>
-`;
-
-exports[`should correctly filter analyses by date range 1`] = `
-<ul
- className="project-activity-versions-list"
- onScroll={[Function]}
- style={
- Object {
- "paddingTop": 52,
- }
- }
->
- <li
- key="E1"
- >
- <div
- className="project-activity-version-badge first"
- >
- <Tooltip
- mouseEnterDelay={0.5}
- overlay="version 6.5-SNAPSHOT"
- >
- <span
- className="badge"
- >
- 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"
- >
- <ProjectActivityAnalysis
- addCustomEvent={[Function]}
- addVersion={[Function]}
- analysis={
- Object {
- "date": 2016-10-27T16:33:50.000Z,
- "events": Array [
- Object {
- "category": "VERSION",
- "key": "E1",
- "name": "6.5-SNAPSHOT",
- },
- ],
- "key": "A1",
- }
- }
- canAdmin={false}
- canCreateVersion={true}
- changeEvent={[Function]}
- deleteAnalysis={[Function]}
- deleteEvent={[Function]}
- isFirst={true}
- key="A1"
- selected={false}
- updateSelectedDate={[Function]}
- />
- </ul>
- </li>
- </ul>
- </li>
-</ul>
-`;
-
-exports[`should render correctly 1`] = `
-<ul
- className="project-activity-versions-list"
- onScroll={[Function]}
- style={
- Object {
- "paddingTop": 52,
- }
- }
->
- <li
- key="E1"
- >
- <div
- className="project-activity-version-badge first"
- >
- <Tooltip
- mouseEnterDelay={0.5}
- overlay="version 6.5-SNAPSHOT"
- >
- <span
- className="badge"
- >
- 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"
- >
- <ProjectActivityAnalysis
- addCustomEvent={[Function]}
- addVersion={[Function]}
- analysis={
- Object {
- "date": 2016-10-27T16:33:50.000Z,
- "events": Array [
- Object {
- "category": "VERSION",
- "key": "E1",
- "name": "6.5-SNAPSHOT",
- },
- ],
- "key": "A1",
- }
- }
- canAdmin={false}
- canCreateVersion={true}
- changeEvent={[Function]}
- deleteAnalysis={[Function]}
- deleteEvent={[Function]}
- isFirst={true}
- key="A1"
- selected={false}
- updateSelectedDate={[Function]}
- />
- <ProjectActivityAnalysis
- addCustomEvent={[Function]}
- addVersion={[Function]}
- analysis={
- Object {
- "date": 2016-10-27T12:21:15.000Z,
- "events": Array [],
- "key": "A2",
- }
- }
- canAdmin={false}
- canCreateVersion={true}
- changeEvent={[Function]}
- deleteAnalysis={[Function]}
- deleteEvent={[Function]}
- 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="badge"
- >
- 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"
- >
- <ProjectActivityAnalysis
- addCustomEvent={[Function]}
- addVersion={[Function]}
- 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",
- }
- }
- canAdmin={false}
- canCreateVersion={true}
- changeEvent={[Function]}
- deleteAnalysis={[Function]}
- deleteEvent={[Function]}
- 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"
- >
- <ProjectActivityAnalysis
- addCustomEvent={[Function]}
- addVersion={[Function]}
- 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",
- }
- }
- canAdmin={false}
- canCreateVersion={true}
- changeEvent={[Function]}
- deleteAnalysis={[Function]}
- deleteEvent={[Function]}
- isFirst={false}
- key="A4"
- selected={false}
- updateSelectedDate={[Function]}
- />
- </ul>
- </li>
- </ul>
- </li>
-</ul>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should correctly filter analyses by category 1`] = `
+<ul
+ className="project-activity-versions-list"
+ onScroll={[Function]}
+ style={
+ Object {
+ "paddingTop": 52,
+ }
+ }
+>
+ <li
+ key="E2"
+ >
+ <div
+ className="project-activity-version-badge first"
+ >
+ <Tooltip
+ mouseEnterDelay={0.5}
+ overlay="version 6.4"
+ >
+ <span
+ className="badge"
+ >
+ 6.4
+ </span>
+ </Tooltip>
+ </div>
+ <ul
+ className="project-activity-days-list"
+ >
+ <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"
+ >
+ <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",
+ }
+ }
+ canAdmin={false}
+ canCreateVersion={true}
+ changeEvent={[MockFunction]}
+ deleteAnalysis={[MockFunction]}
+ deleteEvent={[MockFunction]}
+ isFirst={false}
+ key="A4"
+ selected={false}
+ updateSelectedDate={[Function]}
+ />
+ </ul>
+ </li>
+ </ul>
+ </li>
+</ul>
+`;
+
+exports[`should correctly filter analyses by date range 1`] = `
+<ul
+ className="project-activity-versions-list"
+ onScroll={[Function]}
+ style={
+ Object {
+ "paddingTop": 52,
+ }
+ }
+>
+ <li
+ key="E1"
+ >
+ <div
+ className="project-activity-version-badge first"
+ >
+ <Tooltip
+ mouseEnterDelay={0.5}
+ overlay="version 6.5-SNAPSHOT"
+ >
+ <span
+ className="badge"
+ >
+ 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"
+ >
+ <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",
+ }
+ }
+ canAdmin={false}
+ canCreateVersion={true}
+ changeEvent={[MockFunction]}
+ deleteAnalysis={[MockFunction]}
+ deleteEvent={[MockFunction]}
+ isFirst={true}
+ key="A1"
+ selected={false}
+ updateSelectedDate={[Function]}
+ />
+ </ul>
+ </li>
+ </ul>
+ </li>
+</ul>
+`;
+
+exports[`should render correctly 1`] = `
+<ul
+ className="project-activity-versions-list"
+ onScroll={[Function]}
+ style={
+ Object {
+ "paddingTop": 52,
+ }
+ }
+>
+ <li
+ key="E1"
+ >
+ <div
+ className="project-activity-version-badge first"
+ >
+ <Tooltip
+ mouseEnterDelay={0.5}
+ overlay="version 6.5-SNAPSHOT"
+ >
+ <span
+ className="badge"
+ >
+ 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"
+ >
+ <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",
+ }
+ }
+ canAdmin={false}
+ canCreateVersion={true}
+ changeEvent={[MockFunction]}
+ deleteAnalysis={[MockFunction]}
+ deleteEvent={[MockFunction]}
+ isFirst={true}
+ key="A1"
+ selected={false}
+ updateSelectedDate={[Function]}
+ />
+ <ProjectActivityAnalysis
+ addCustomEvent={[MockFunction]}
+ addVersion={[MockFunction]}
+ analysis={
+ Object {
+ "date": 2016-10-27T12:21:15.000Z,
+ "events": Array [],
+ "key": "A2",
+ }
+ }
+ canAdmin={false}
+ canCreateVersion={true}
+ changeEvent={[MockFunction]}
+ deleteAnalysis={[MockFunction]}
+ deleteEvent={[MockFunction]}
+ 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="badge"
+ >
+ 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"
+ >
+ <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",
+ }
+ }
+ canAdmin={false}
+ canCreateVersion={true}
+ changeEvent={[MockFunction]}
+ deleteAnalysis={[MockFunction]}
+ deleteEvent={[MockFunction]}
+ 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"
+ >
+ <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",
+ }
+ }
+ canAdmin={false}
+ canCreateVersion={true}
+ changeEvent={[MockFunction]}
+ deleteAnalysis={[MockFunction]}
+ deleteEvent={[MockFunction]}
+ isFirst={false}
+ key="A4"
+ selected={false}
+ updateSelectedDate={[Function]}
+ />
+ </ul>
+ </li>
+ </ul>
+ </li>
+</ul>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<div
- className="page page-limited"
- id="project-activity"
->
- <Suggestions
- suggestions="project_activity"
- />
- <HelmetWrapper
- defer={true}
- encodeSpecialCharacters={true}
- title="project_activity.page"
- />
- <ProjectActivityPageHeader
- category=""
- project={
- Object {
- "key": "org.sonarsource.sonarqube:sonarqube",
- "leakPeriodDate": "2017-05-16T13:50:02+0200",
- }
- }
- updateQuery={[Function]}
- />
- <div
- className="layout-page project-activity-page"
- >
- <div
- className="layout-page-side-outer project-activity-page-side-outer boxed-group"
- >
- <ProjectActivityAnalysesList
- addCustomEvent={[Function]}
- addVersion={[Function]}
- analyses={
- Array [
- Object {
- "date": 2016-10-27T14:33:50.000Z,
- "events": Array [
- Object {
- "category": "VERSION",
- "key": "E1",
- "name": "6.5-SNAPSHOT",
- },
- ],
- "key": "A1",
- },
- Object {
- "date": 2016-10-27T10:21:15.000Z,
- "events": Array [],
- "key": "A2",
- },
- Object {
- "date": 2016-10-26T10:17:29.000Z,
- "events": Array [
- Object {
- "category": "VERSION",
- "key": "E2",
- "name": "6.4",
- },
- Object {
- "category": "OTHER",
- "key": "E3",
- "name": "foo",
- },
- ],
- "key": "A3",
- },
- ]
- }
- analysesLoading={false}
- canAdmin={false}
- canDeleteAnalyses={false}
- changeEvent={[Function]}
- className="boxed-group-inner"
- deleteAnalysis={[Function]}
- deleteEvent={[Function]}
- initializing={false}
- project={
- Object {
- "key": "org.sonarsource.sonarqube:sonarqube",
- "leakPeriodDate": "2017-05-16T13:50:02+0200",
- }
- }
- query={
- Object {
- "category": "",
- "graph": "issues",
- "project": "org.sonarsource.sonarqube:sonarqube",
- }
- }
- updateQuery={[Function]}
- />
- </div>
- <div
- className="project-activity-layout-page-main"
- >
- <ProjectActivityGraphs
- analyses={
- Array [
- Object {
- "date": 2016-10-27T14:33:50.000Z,
- "events": Array [
- Object {
- "category": "VERSION",
- "key": "E1",
- "name": "6.5-SNAPSHOT",
- },
- ],
- "key": "A1",
- },
- Object {
- "date": 2016-10-27T10:21:15.000Z,
- "events": Array [],
- "key": "A2",
- },
- Object {
- "date": 2016-10-26T10:17:29.000Z,
- "events": Array [
- Object {
- "category": "VERSION",
- "key": "E2",
- "name": "6.4",
- },
- Object {
- "category": "OTHER",
- "key": "E3",
- "name": "foo",
- },
- ],
- "key": "A3",
- },
- ]
- }
- leakPeriodDate={2017-05-16T11:50:02.000Z}
- loading={false}
- measuresHistory={
- Array [
- Object {
- "history": Array [
- Object {
- "date": 2016-03-04T09:40:12.000Z,
- "value": "1749",
- },
- Object {
- "date": 2016-03-04T17:40:16.000Z,
- "value": "2286",
- },
- ],
- "metric": "code_smells",
- },
- ]
- }
- metrics={
- Array [
- Object {
- "key": "code_smells",
- "name": "Code Smells",
- "type": "INT",
- },
- ]
- }
- query={
- Object {
- "category": "",
- "graph": "issues",
- "project": "org.sonarsource.sonarqube:sonarqube",
- }
- }
- updateQuery={[Function]}
- />
- </div>
- </div>
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="page page-limited"
+ id="project-activity"
+>
+ <Suggestions
+ suggestions="project_activity"
+ />
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="project_activity.page"
+ />
+ <ProjectActivityPageHeader
+ category=""
+ project={
+ Object {
+ "leakPeriodDate": "2017-05-16T13:50:02+0200",
+ "qualifier": "TRK",
+ }
+ }
+ updateQuery={[Function]}
+ />
+ <div
+ className="layout-page project-activity-page"
+ >
+ <div
+ className="layout-page-side-outer project-activity-page-side-outer boxed-group"
+ >
+ <ProjectActivityAnalysesList
+ addCustomEvent={[MockFunction]}
+ addVersion={[MockFunction]}
+ analyses={
+ Array [
+ Object {
+ "date": 2016-10-27T14:33:50.000Z,
+ "events": Array [
+ Object {
+ "category": "VERSION",
+ "key": "E1",
+ "name": "6.5-SNAPSHOT",
+ },
+ ],
+ "key": "A1",
+ },
+ Object {
+ "date": 2016-10-27T10:21:15.000Z,
+ "events": Array [],
+ "key": "A2",
+ },
+ Object {
+ "date": 2016-10-26T10:17:29.000Z,
+ "events": Array [
+ Object {
+ "category": "VERSION",
+ "key": "E2",
+ "name": "6.4",
+ },
+ Object {
+ "category": "OTHER",
+ "key": "E3",
+ "name": "foo",
+ },
+ ],
+ "key": "A3",
+ },
+ ]
+ }
+ analysesLoading={false}
+ canAdmin={false}
+ canDeleteAnalyses={false}
+ changeEvent={[MockFunction]}
+ className="boxed-group-inner"
+ deleteAnalysis={[MockFunction]}
+ deleteEvent={[MockFunction]}
+ initializing={false}
+ project={
+ Object {
+ "leakPeriodDate": "2017-05-16T13:50:02+0200",
+ "qualifier": "TRK",
+ }
+ }
+ query={
+ Object {
+ "category": "",
+ "customMetrics": Array [],
+ "graph": "issues",
+ "project": "org.sonarsource.sonarqube:sonarqube",
+ }
+ }
+ updateQuery={[Function]}
+ />
+ </div>
+ <div
+ className="project-activity-layout-page-main"
+ >
+ <ProjectActivityGraphs
+ analyses={
+ Array [
+ Object {
+ "date": 2016-10-27T14:33:50.000Z,
+ "events": Array [
+ Object {
+ "category": "VERSION",
+ "key": "E1",
+ "name": "6.5-SNAPSHOT",
+ },
+ ],
+ "key": "A1",
+ },
+ Object {
+ "date": 2016-10-27T10:21:15.000Z,
+ "events": Array [],
+ "key": "A2",
+ },
+ Object {
+ "date": 2016-10-26T10:17:29.000Z,
+ "events": Array [
+ Object {
+ "category": "VERSION",
+ "key": "E2",
+ "name": "6.4",
+ },
+ Object {
+ "category": "OTHER",
+ "key": "E3",
+ "name": "foo",
+ },
+ ],
+ "key": "A3",
+ },
+ ]
+ }
+ leakPeriodDate={2017-05-16T11:50:02.000Z}
+ loading={false}
+ measuresHistory={
+ Array [
+ Object {
+ "history": Array [
+ Object {
+ "date": 2016-03-04T09:40:12.000Z,
+ "value": "1749",
+ },
+ Object {
+ "date": 2016-03-04T17:40:16.000Z,
+ "value": "2286",
+ },
+ ],
+ "metric": "code_smells",
+ },
+ ]
+ }
+ metrics={
+ Array [
+ Object {
+ "id": "1",
+ "key": "code_smells",
+ "name": "Code Smells",
+ "type": "INT",
+ },
+ ]
+ }
+ query={
+ Object {
+ "category": "",
+ "customMetrics": Array [],
+ "graph": "issues",
+ "project": "org.sonarsource.sonarqube:sonarqube",
+ }
+ }
+ updateQuery={[Function]}
+ />
+ </div>
+ </div>
+</div>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly the date inputs 1`] = `
-<div>
- <DateRangeInput
- onChange={[Function]}
- value={
- Object {
- "from": 2016-10-27T12:21:15.000Z,
- "to": 2016-12-27T12:21:15.000Z,
- }
- }
- />
- <Button
- className="spacer-left"
- disabled={false}
- onClick={[Function]}
- >
- project_activity.reset_dates
- </Button>
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly the date inputs 1`] = `
+<div>
+ <DateRangeInput
+ onChange={[Function]}
+ value={
+ Object {
+ "from": 2016-10-27T12:21:15.000Z,
+ "to": 2016-12-27T12:21:15.000Z,
+ }
+ }
+ />
+ <Button
+ className="spacer-left"
+ disabled={false}
+ onClick={[Function]}
+ >
+ project_activity.reset_dates
+ </Button>
+</div>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly the graph and legends 1`] = `
-<div
- className="project-activity-layout-page-main-inner boxed-group boxed-group-inner"
->
- <ProjectActivityGraphsHeader
- addCustomMetric={[Function]}
- graph="issues"
- metrics={
- Array [
- Object {
- "key": "code_smells",
- "name": "Code Smells",
- "type": "INT",
- },
- ]
- }
- metricsTypeFilter={null}
- removeCustomMetric={[Function]}
- updateGraph={[Function]}
- />
- <GraphsHistory
- analyses={
- Array [
- Object {
- "date": "2016-10-27T16:33:50+0200",
- "events": Array [
- Object {
- "category": "VERSION",
- "key": "E1",
- "name": "6.5-SNAPSHOT",
- },
- ],
- "key": "A1",
- },
- Object {
- "date": "2016-10-27T12:21:15+0200",
- "events": Array [],
- "key": "A2",
- },
- Object {
- "date": "2016-10-26T12:17:29+0200",
- "events": Array [
- Object {
- "category": "VERSION",
- "key": "E2",
- "name": "6.4",
- },
- Object {
- "category": "OTHER",
- "key": "E3",
- "name": "foo",
- },
- ],
- "key": "A3",
- },
- ]
- }
- eventFilter=""
- graph="issues"
- graphEndDate={null}
- graphStartDate={null}
- graphs={
- Array [
- Array [
- Object {
- "data": Array [
- Object {
- "x": 2016-10-26T10:17:29.000Z,
- "y": 2286,
- },
- Object {
- "x": 2016-10-27T10:21:15.000Z,
- "y": 1749,
- },
- Object {
- "x": 2016-10-27T14:33:50.000Z,
- "y": 500,
- },
- ],
- "name": "code_smells",
- "translatedName": "Code Smells",
- "type": "INT",
- },
- ],
- ]
- }
- leakPeriodDate="2017-05-16T13:50:02+0200"
- loading={false}
- measuresHistory={
- Array [
- Object {
- "history": Array [
- Object {
- "date": 2016-10-26T10:17:29.000Z,
- "value": "2286",
- },
- Object {
- "date": 2016-10-27T10:21:15.000Z,
- "value": "1749",
- },
- Object {
- "date": 2016-10-27T14:33:50.000Z,
- "value": "500",
- },
- ],
- "metric": "code_smells",
- },
- ]
- }
- removeCustomMetric={[Function]}
- series={
- Array [
- Object {
- "data": Array [
- Object {
- "x": 2016-10-26T10:17:29.000Z,
- "y": 2286,
- },
- Object {
- "x": 2016-10-27T10:21:15.000Z,
- "y": 1749,
- },
- Object {
- "x": 2016-10-27T14:33:50.000Z,
- "y": 500,
- },
- ],
- "name": "code_smells",
- "translatedName": "Code Smells",
- "type": "INT",
- },
- ]
- }
- updateGraphZoom={[Function]}
- updateSelectedDate={[Function]}
- />
- <GraphsZoom
- graphEndDate={null}
- graphStartDate={null}
- leakPeriodDate="2017-05-16T13:50:02+0200"
- loading={false}
- metricsType="INT"
- series={
- Array [
- Object {
- "data": Array [
- Object {
- "x": 2016-10-26T10:17:29.000Z,
- "y": 2286,
- },
- Object {
- "x": 2016-10-27T10:21:15.000Z,
- "y": 1749,
- },
- Object {
- "x": 2016-10-27T14:33:50.000Z,
- "y": 500,
- },
- ],
- "name": "code_smells",
- "translatedName": "Code Smells",
- "type": "INT",
- },
- ]
- }
- showAreas={false}
- updateGraphZoom={[Function]}
- />
-</div>
-`;
-
-exports[`should render correctly with filter history on dates 1`] = `
-Object {
- "graphEndDate": null,
- "graphStartDate": "2016-10-27T12:21:15+0200",
- "graphs": Array [
- Array [
- Object {
- "data": Array [
- Object {
- "x": 2016-10-26T10:17:29.000Z,
- "y": 2286,
- },
- Object {
- "x": 2016-10-27T10:21:15.000Z,
- "y": 1749,
- },
- Object {
- "x": 2016-10-27T14:33:50.000Z,
- "y": 500,
- },
- ],
- "name": "code_smells",
- "translatedName": "Code Smells",
- "type": "INT",
- },
- ],
- ],
- "series": Array [
- Object {
- "data": Array [
- Object {
- "x": 2016-10-26T10:17:29.000Z,
- "y": 2286,
- },
- Object {
- "x": 2016-10-27T10:21:15.000Z,
- "y": 1749,
- },
- Object {
- "x": 2016-10-27T14:33:50.000Z,
- "y": 500,
- },
- ],
- "name": "code_smells",
- "translatedName": "Code Smells",
- "type": "INT",
- },
- ],
-}
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly the graph and legends 1`] = `
+<div
+ className="project-activity-layout-page-main-inner boxed-group boxed-group-inner"
+>
+ <ProjectActivityGraphsHeader
+ addCustomMetric={[Function]}
+ graph="issues"
+ metrics={
+ Array [
+ Object {
+ "id": "1",
+ "key": "code_smells",
+ "name": "Code Smells",
+ "type": "INT",
+ },
+ ]
+ }
+ removeCustomMetric={[Function]}
+ selectedMetrics={Array []}
+ updateGraph={[Function]}
+ />
+ <GraphsHistory
+ analyses={
+ Array [
+ Object {
+ "date": 2016-10-27T14:33:50.000Z,
+ "events": Array [
+ Object {
+ "category": "VERSION",
+ "key": "E1",
+ "name": "6.5-SNAPSHOT",
+ },
+ ],
+ "key": "A1",
+ },
+ Object {
+ "date": 2016-10-27T10:21:15.000Z,
+ "events": Array [],
+ "key": "A2",
+ },
+ Object {
+ "date": 2016-10-26T10:17:29.000Z,
+ "events": Array [
+ Object {
+ "category": "VERSION",
+ "key": "E2",
+ "name": "6.4",
+ },
+ Object {
+ "category": "OTHER",
+ "key": "E3",
+ "name": "foo",
+ },
+ ],
+ "key": "A3",
+ },
+ ]
+ }
+ eventFilter=""
+ graph="issues"
+ graphs={
+ Array [
+ Array [
+ Object {
+ "data": Array [
+ Object {
+ "x": 2016-10-26T10:17:29.000Z,
+ "y": 2286,
+ },
+ Object {
+ "x": 2016-10-27T10:21:15.000Z,
+ "y": 1749,
+ },
+ Object {
+ "x": 2016-10-27T14:33:50.000Z,
+ "y": 500,
+ },
+ ],
+ "name": "code_smells",
+ "translatedName": "Code Smells",
+ "type": "INT",
+ },
+ ],
+ ]
+ }
+ leakPeriodDate={2017-05-16T11:50:02.000Z}
+ loading={false}
+ measuresHistory={
+ Array [
+ Object {
+ "history": Array [
+ Object {
+ "date": 2016-10-26T10:17:29.000Z,
+ "value": "2286",
+ },
+ Object {
+ "date": 2016-10-27T10:21:15.000Z,
+ "value": "1749",
+ },
+ Object {
+ "date": 2016-10-27T14:33:50.000Z,
+ "value": "500",
+ },
+ ],
+ "metric": "code_smells",
+ },
+ ]
+ }
+ removeCustomMetric={[Function]}
+ series={
+ Array [
+ Object {
+ "data": Array [
+ Object {
+ "x": 2016-10-26T10:17:29.000Z,
+ "y": 2286,
+ },
+ Object {
+ "x": 2016-10-27T10:21:15.000Z,
+ "y": 1749,
+ },
+ Object {
+ "x": 2016-10-27T14:33:50.000Z,
+ "y": 500,
+ },
+ ],
+ "name": "code_smells",
+ "translatedName": "Code Smells",
+ "type": "INT",
+ },
+ ]
+ }
+ updateGraphZoom={[Function]}
+ updateSelectedDate={[Function]}
+ />
+ <GraphsZoom
+ leakPeriodDate={2017-05-16T11:50:02.000Z}
+ loading={false}
+ metricsType="INT"
+ series={
+ Array [
+ Object {
+ "data": Array [
+ Object {
+ "x": 2016-10-26T10:17:29.000Z,
+ "y": 2286,
+ },
+ Object {
+ "x": 2016-10-27T10:21:15.000Z,
+ "y": 1749,
+ },
+ Object {
+ "x": 2016-10-27T14:33:50.000Z,
+ "y": 500,
+ },
+ ],
+ "name": "code_smells",
+ "translatedName": "Code Smells",
+ "type": "INT",
+ },
+ ]
+ }
+ showAreas={false}
+ updateGraphZoom={[Function]}
+ />
+</div>
+`;
+
+exports[`should render correctly with filter history on dates 1`] = `
+Object {
+ "graphEndDate": undefined,
+ "graphStartDate": 2016-10-27T10:21:15.000Z,
+ "graphs": Array [
+ Array [
+ Object {
+ "data": Array [
+ Object {
+ "x": 2016-10-26T10:17:29.000Z,
+ "y": 2286,
+ },
+ Object {
+ "x": 2016-10-27T10:21:15.000Z,
+ "y": 1749,
+ },
+ Object {
+ "x": 2016-10-27T14:33:50.000Z,
+ "y": 500,
+ },
+ ],
+ "name": "code_smells",
+ "translatedName": "Code Smells",
+ "type": "INT",
+ },
+ ],
+ ],
+ "series": Array [
+ Object {
+ "data": Array [
+ Object {
+ "x": 2016-10-26T10:17:29.000Z,
+ "y": 2286,
+ },
+ Object {
+ "x": 2016-10-27T10:21:15.000Z,
+ "y": 1749,
+ },
+ Object {
+ "x": 2016-10-27T14:33:50.000Z,
+ "y": 500,
+ },
+ ],
+ "name": "code_smells",
+ "translatedName": "Code Smells",
+ "type": "INT",
+ },
+ ],
+}
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly the list of series 1`] = `
-<header
- className="page-header"
->
- <Select
- className="input-medium pull-left big-spacer-right"
- clearable={true}
- onChange={[Function]}
- optionComponent={[Function]}
- options={
- Array [
- Object {
- "label": "event.category.VERSION",
- "value": "VERSION",
- },
- Object {
- "label": "event.category.QUALITY_GATE",
- "value": "QUALITY_GATE",
- },
- Object {
- "label": "event.category.QUALITY_PROFILE",
- "value": "QUALITY_PROFILE",
- },
- Object {
- "label": "event.category.OTHER",
- "value": "OTHER",
- },
- ]
- }
- placeholder="project_activity.filter_events..."
- searchable={false}
- value=""
- valueComponent={[Function]}
- />
- <ProjectActivityDateInput
- className="pull-left"
- from={2016-10-27T10:21:15.000Z}
- onChange={[Function]}
- />
-</header>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly the list of series 1`] = `
+<header
+ className="page-header"
+>
+ <Select
+ className="input-medium pull-left big-spacer-right"
+ clearable={true}
+ onChange={[Function]}
+ optionComponent={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "event.category.VERSION",
+ "value": "VERSION",
+ },
+ Object {
+ "label": "event.category.QUALITY_GATE",
+ "value": "QUALITY_GATE",
+ },
+ Object {
+ "label": "event.category.QUALITY_PROFILE",
+ "value": "QUALITY_PROFILE",
+ },
+ Object {
+ "label": "event.category.OTHER",
+ "value": "OTHER",
+ },
+ ]
+ }
+ placeholder="project_activity.filter_events..."
+ searchable={false}
+ value=""
+ valueComponent={[Function]}
+ />
+ <ProjectActivityDateInput
+ from={2016-10-27T10:21:15.000Z}
+ onChange={[Function]}
+ />
+</header>
+`;
import * as React from 'react';
import ConfirmModal from '../../../../components/controls/ConfirmModal';
import { translate } from '../../../../helpers/l10n';
-import { Analysis } from '../../../../api/projectActivity';
+import { ParsedAnalysis } from '../../utils';
interface Props {
addEvent: (analysis: string, name: string, category?: string) => Promise<void>;
addEventButtonText: string;
- analysis: Analysis;
+ analysis: ParsedAnalysis;
onClose: () => void;
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import { find, sortBy } from 'lodash';
-import AddGraphMetricPopup from './AddGraphMetricPopup';
-import DropdownIcon from '../../../../components/icons-components/DropdownIcon';
-import Dropdown from '../../../../components/controls/Dropdown';
-import { Button } from '../../../../components/ui/buttons';
-import { isDiffMetric } from '../../../../helpers/measures';
-import { getLocalizedMetricName, translate } from '../../../../helpers/l10n';
-/*:: import type { Metric } from '../../types'; */
-
-/*::
-type Props = {
- addMetric: (metric: string) => void,
- className?: string,
- metrics: Array<Metric>,
- metricsTypeFilter: ?Array<string>,
- removeMetric: (metric: string) => void,
- selectedMetrics: Array<string>
-};
-*/
-
-/*::
-type State = {
- query: string,
-};
-*/
-
-export default class AddGraphMetric extends React.PureComponent {
- /*:: props: Props; */
- state /*: State */ = {
- metrics: [],
- query: '',
- selectedMetrics: []
- };
-
- filterSelected = (query /*: string*/, selectedElements /*: string[]*/) => {
- return selectedElements.filter(element =>
- this.getLocalizedMetricNameFromKey(element)
- .toLowerCase()
- .includes(query.toLowerCase())
- );
- };
-
- getPopupPos = (containerPos /*: ClientRect*/) => ({
- top: containerPos.height,
- right: containerPos.width - 240
- });
-
- filterMetricsElements = (
- { metricsTypeFilter, metrics, selectedMetrics } /*: Props */,
- query /*: string*/
- ) => {
- return metrics
- .filter(metric => {
- if (
- metric.hidden ||
- isDiffMetric(metric.key) ||
- ['DATA', 'DISTRIB'].includes(metric.type) ||
- selectedMetrics.includes(metric.key) ||
- !getLocalizedMetricName(metric)
- .toLowerCase()
- .includes(query.toLowerCase())
- ) {
- return false;
- }
- if (metricsTypeFilter && metricsTypeFilter.length > 0) {
- return metricsTypeFilter.includes(metric.type);
- }
- return true;
- })
- .map(metric => metric.key);
- };
-
- getSelectedMetricsElements = (
- metrics /*: Array<Metric> */,
- selectedMetrics /*: Array<string> | null */,
- query /*: string */
- ) => {
- const selected /*: Array<string> */ =
- selectedMetrics === null ? this.props.selectedMetrics : selectedMetrics;
- return metrics.filter(metric => selected.includes(metric.key)).map(metric => metric.key);
- };
-
- getLocalizedMetricNameFromKey = (key /*: string*/) => {
- const metric = find(this.props.metrics, { key });
- return metric === undefined ? key : getLocalizedMetricName(metric);
- };
-
- onSearch = (query /*: string */) => {
- this.setState({ query });
- return Promise.resolve();
- };
-
- onSelect = (metric /*: string */) => {
- this.props.addMetric(metric);
- this.setState(state => {
- return {
- selectedMetrics: sortBy([...state.selectedMetrics, metric]),
- metrics: this.filterMetricsElements(this.props, state.query)
- };
- });
- };
-
- onUnselect = (metric /*: string */) => {
- this.props.removeMetric(metric);
- this.setState(state => {
- return {
- metrics: sortBy([...state.metrics, metric]),
- selectedMetrics: state.selectedMetrics.filter(selected => selected !== metric)
- };
- });
- };
-
- render() {
- const { query } = this.state;
- const filteredMetrics = this.filterMetricsElements(this.props, query);
- const selectedMetrics = this.getSelectedMetricsElements(
- this.props.metrics,
- this.props.selectedMetrics,
- query
- );
- return (
- <Dropdown
- className="display-inline-block"
- overlay={
- <AddGraphMetricPopup
- elements={filteredMetrics}
- filterSelected={this.filterSelected}
- metricsTypeFilter={this.props.metricsTypeFilter}
- onSearch={this.onSearch}
- onSelect={this.onSelect}
- onUnselect={this.onUnselect}
- renderLabel={element => this.getLocalizedMetricNameFromKey(element)}
- selectedElements={selectedMetrics}
- />
- }>
- <Button className="spacer-left">
- <span className="text-ellipsis text-middle">
- {translate('project_activity.graphs.custom.add')}
- </span>
- <DropdownIcon className="text-top little-spacer-left" />
- </Button>
- </Dropdown>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { find, sortBy } from 'lodash';
+import AddGraphMetricPopup from './AddGraphMetricPopup';
+import DropdownIcon from '../../../../components/icons-components/DropdownIcon';
+import Dropdown from '../../../../components/controls/Dropdown';
+import { Button } from '../../../../components/ui/buttons';
+import { isDiffMetric } from '../../../../helpers/measures';
+import { getLocalizedMetricName, translate } from '../../../../helpers/l10n';
+import { Metric } from '../../../../app/types';
+
+interface Props {
+ addMetric: (metric: string) => void;
+ className?: string;
+ metrics: Metric[];
+ metricsTypeFilter?: string[];
+ removeMetric: (metric: string) => void;
+ selectedMetrics: string[];
+}
+
+interface State {
+ metrics: string[];
+ query: string;
+ selectedMetrics: string[];
+}
+
+export default class AddGraphMetric extends React.PureComponent<Props, State> {
+ state: State = {
+ metrics: [],
+ query: '',
+ selectedMetrics: []
+ };
+
+ filterSelected = (query: string, selectedElements: string[]) => {
+ return selectedElements.filter(element =>
+ this.getLocalizedMetricNameFromKey(element)
+ .toLowerCase()
+ .includes(query.toLowerCase())
+ );
+ };
+
+ filterMetricsElements = (
+ { metricsTypeFilter, metrics, selectedMetrics }: Props,
+ query: string
+ ) => {
+ return metrics
+ .filter(metric => {
+ if (
+ metric.hidden ||
+ isDiffMetric(metric.key) ||
+ ['DATA', 'DISTRIB'].includes(metric.type) ||
+ selectedMetrics.includes(metric.key) ||
+ !getLocalizedMetricName(metric)
+ .toLowerCase()
+ .includes(query.toLowerCase())
+ ) {
+ return false;
+ }
+ if (metricsTypeFilter && metricsTypeFilter.length > 0) {
+ return metricsTypeFilter.includes(metric.type);
+ }
+ return true;
+ })
+ .map(metric => metric.key);
+ };
+
+ getSelectedMetricsElements = (metrics: Metric[], selectedMetrics?: string[]) => {
+ const selected = selectedMetrics || this.props.selectedMetrics;
+ return metrics.filter(metric => selected.includes(metric.key)).map(metric => metric.key);
+ };
+
+ getLocalizedMetricNameFromKey = (key: string) => {
+ const metric = find(this.props.metrics, { key });
+ return metric === undefined ? key : getLocalizedMetricName(metric);
+ };
+
+ onSearch = (query: string) => {
+ this.setState({ query });
+ return Promise.resolve();
+ };
+
+ onSelect = (metric: string) => {
+ this.props.addMetric(metric);
+ this.setState(state => {
+ return {
+ selectedMetrics: sortBy([...state.selectedMetrics, metric]),
+ metrics: this.filterMetricsElements(this.props, state.query)
+ };
+ });
+ };
+
+ onUnselect = (metric: string) => {
+ this.props.removeMetric(metric);
+ this.setState(state => {
+ return {
+ metrics: sortBy([...state.metrics, metric]),
+ selectedMetrics: state.selectedMetrics.filter(selected => selected !== metric)
+ };
+ });
+ };
+
+ render() {
+ const { query } = this.state;
+ const filteredMetrics = this.filterMetricsElements(this.props, query);
+ const selectedMetrics = this.getSelectedMetricsElements(
+ this.props.metrics,
+ this.props.selectedMetrics
+ );
+ return (
+ <Dropdown
+ className="display-inline-block"
+ overlay={
+ <AddGraphMetricPopup
+ elements={filteredMetrics}
+ filterSelected={this.filterSelected}
+ metricsTypeFilter={this.props.metricsTypeFilter}
+ onSearch={this.onSearch}
+ onSelect={this.onSelect}
+ onUnselect={this.onUnselect}
+ renderLabel={element => this.getLocalizedMetricNameFromKey(element)}
+ selectedElements={selectedMetrics}
+ />
+ }>
+ <Button className="spacer-left">
+ <span className="text-ellipsis text-middle">
+ {translate('project_activity.graphs.custom.add')}
+ </span>
+ <DropdownIcon className="text-top little-spacer-left" />
+ </Button>
+ </Dropdown>
+ );
+ }
+}
interface Props {
elements: string[];
filterSelected: (query: string, selectedElements: string[]) => string[];
- metricsTypeFilter: string[];
+ metricsTypeFilter?: string[];
onSearch: (query: string) => Promise<void>;
onSelect: (item: string) => void;
onUnselect: (item: string) => void;
{translate('project_activity.graphs.custom.add_metric_info')}
</span>
);
- } else if (metricsTypeFilter != null && metricsTypeFilter.length > 0) {
+ } else if (metricsTypeFilter && metricsTypeFilter.length > 0) {
footerNode = (
<span className="alert alert-info spacer-left spacer-right spacer-top">
{translateWithParameters(
*/
import * as React from 'react';
import { translate } from '../../../../helpers/l10n';
-import { Event } from '../../../../api/projectActivity';
import ConfirmModal from '../../../../components/controls/ConfirmModal';
+import { AnalysisEvent } from '../../../../app/types';
interface Props {
changeEvent: (event: string, name: string) => Promise<void>;
header: string;
- event: Event;
+ event: AnalysisEvent;
onClose: () => void;
}
*/
import * as React from 'react';
import { translate } from '../../../../helpers/l10n';
-import { Analysis } from '../../../../api/projectActivity';
import ConfirmModal from '../../../../components/controls/ConfirmModal';
+import { ParsedAnalysis } from '../../utils';
interface Props {
- analysis: Analysis;
+ analysis: ParsedAnalysis;
deleteAnalysis: (analysis: string) => Promise<void>;
onClose: () => void;
}
*/
import * as React from 'react';
import { translate } from '../../../../helpers/l10n';
-import { Event } from '../../../../api/projectActivity';
import ConfirmModal from '../../../../components/controls/ConfirmModal';
+import { AnalysisEvent } from '../../../../app/types';
interface Props {
analysis: string;
deleteEvent: (analysis: string, event: string) => Promise<void>;
- event: Event;
+ event: AnalysisEvent;
header: string;
removeEventQuestion: string;
onClose: () => void;
const routes = [
{
indexRoute: {
- component: lazyLoad(() => import('./components/ProjectActivityAppContainer') as any)
+ component: lazyLoad(() => import('./components/ProjectActivityAppContainer'))
}
}
];
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-
-/*::
-export type Event = {
- key: string,
- name: string,
- category: string,
- description?: string
-};
-*/
-
-/*::
-export type Analysis = {
- key: string,
- date: Date,
- events: Array<Event>
-};
-*/
-
-/*::
-export type HistoryItem = { date: Date, value: string };
-*/
-
-/*::
-export type MeasureHistory = { metric: string, history: Array<HistoryItem> };
-*/
-
-/*::
-export type Metric = {
- custom?: boolean,
- hidden?: boolean,
- key: string,
- name: string,
- type: string
-};
-*/
-
-/*::
-export type Paging = {
- pageIndex: number,
- pageSize: number,
- total: number
-};
-*/
-
-/*::
-export type Query = {
- category: string,
- customMetrics: Array<string>,
- from?: Date,
- graph: string,
- project: string,
- to?: Date,
- selectedDate?: Date
-};
-*/
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import { chunk, flatMap, groupBy, isEqual, sortBy } from 'lodash';
-import {
- cleanQuery,
- parseAsArray,
- parseAsDate,
- parseAsString,
- serializeStringArray,
- serializeDate,
- serializeString
-} from '../../helpers/query';
-import { parseDate, startOfDay } from '../../helpers/dates';
-import { getLocalizedMetricName, translate } from '../../helpers/l10n';
-/*:: import type { Analysis, MeasureHistory, Metric, Query } from './types'; */
-/*:: import type { RawQuery } from '../../helpers/query'; */
-/*:: import type { Serie } from '../../components/charts/AdvancedTimeline'; */
-
-export const EVENT_TYPES = ['VERSION', 'QUALITY_GATE', 'QUALITY_PROFILE', 'OTHER'];
-export const APPLICATION_EVENT_TYPES = ['QUALITY_GATE', 'OTHER'];
-export const DEFAULT_GRAPH = 'issues';
-export const GRAPH_TYPES = ['issues', 'coverage', 'duplications', 'custom'];
-export const GRAPHS_METRICS_DISPLAYED = {
- issues: ['bugs', 'code_smells', 'vulnerabilities'],
- coverage: ['lines_to_cover', 'uncovered_lines'],
- duplications: ['ncloc', 'duplicated_lines']
-};
-export const GRAPHS_METRICS = {
- issues: GRAPHS_METRICS_DISPLAYED['issues'].concat([
- 'reliability_rating',
- 'security_rating',
- 'sqale_rating'
- ]),
- coverage: GRAPHS_METRICS_DISPLAYED['coverage'].concat(['coverage']),
- duplications: GRAPHS_METRICS_DISPLAYED['duplications'].concat(['duplicated_lines_density'])
-};
-
-export const PROJECT_ACTIVITY_GRAPH = 'sonarqube.project_activity.graph';
-export const PROJECT_ACTIVITY_GRAPH_CUSTOM = 'sonarqube.project_activity.graph.custom';
-
-export const datesQueryChanged = (prevQuery /*: Query */, nextQuery /*: Query */) =>
- !isEqual(prevQuery.from, nextQuery.from) || !isEqual(prevQuery.to, nextQuery.to);
-
-export const activityQueryChanged = (prevQuery /*: Query */, nextQuery /*: Query */) =>
- prevQuery.category !== nextQuery.category || datesQueryChanged(prevQuery, nextQuery);
-
-export const customMetricsChanged = (prevQuery /*: Query */, nextQuery /*: Query */) =>
- !isEqual(prevQuery.customMetrics, nextQuery.customMetrics);
-
-export const hasDataValues = (serie /*: Serie */) =>
- serie.data.some(point => point.y || point.y === 0);
-
-export const hasHistoryData = (series /*: Array<Serie> */) =>
- series.some(serie => serie.data && serie.data.length > 1);
-
-export const hasHistoryDataValue = (series /*: Array<Serie> */) =>
- series.some(serie => serie.data && serie.data.length > 1 && hasDataValues(serie));
-
-export function historyQueryChanged(prevQuery /*: Query */, nextQuery /*: Query */) /*: boolean */ {
- return prevQuery.graph !== nextQuery.graph;
-}
-
-export const isCustomGraph = (graph /*: string */) => graph === 'custom';
-
-export const selectedDateQueryChanged = (prevQuery /*: Query */, nextQuery /*: Query */) =>
- !isEqual(prevQuery.selectedDate, nextQuery.selectedDate);
-
-export const generateCoveredLinesMetric = (
- uncoveredLines /*: MeasureHistory */,
- measuresHistory /*: Array<MeasureHistory> */
-) => {
- const linesToCover = measuresHistory.find(measure => measure.metric === 'lines_to_cover');
- return {
- data: linesToCover
- ? uncoveredLines.history.map((analysis, idx) => ({
- x: analysis.date,
- y: Number(linesToCover.history[idx].value) - Number(analysis.value)
- }))
- : [],
- name: 'covered_lines',
- translatedName: translate('project_activity.custom_metric.covered_lines'),
- type: 'INT'
- };
-};
-
-function findMetric(key /*: string */, metrics /*: Array<Metric> | { [string]: Metric } */) {
- if (Array.isArray(metrics)) {
- return metrics.find(metric => metric.key === key);
- }
- return metrics[key];
-}
-
-export function generateSeries(
- measuresHistory /*: Array<MeasureHistory> */,
- graph /*: string */,
- metrics /*: Array<Metric> | { [string]: Metric } */,
- displayedMetrics /*: Array<string> */
-) /*: Array<Serie> */ {
- if (displayedMetrics.length <= 0 || typeof measuresHistory === 'undefined') {
- return [];
- }
- return sortBy(
- measuresHistory
- .filter(measure => displayedMetrics.indexOf(measure.metric) >= 0)
- .map(measure => {
- if (measure.metric === 'uncovered_lines' && !isCustomGraph(graph)) {
- return generateCoveredLinesMetric(measure, measuresHistory);
- }
- const metric = findMetric(measure.metric, metrics);
- return {
- data: measure.history.map(analysis => ({
- x: analysis.date,
- y: metric && metric.type === 'LEVEL' ? analysis.value : Number(analysis.value)
- })),
- name: measure.metric,
- translatedName: metric ? getLocalizedMetricName(metric) : measure.metric,
- type: metric ? metric.type : 'INT'
- };
- }),
- serie =>
- displayedMetrics.indexOf(serie.name === 'covered_lines' ? 'uncovered_lines' : serie.name)
- );
-}
-
-export const splitSeriesInGraphs = (
- series /*: Array<Serie> */,
- maxGraph /*: number */,
- maxSeries /*: number */
-) =>
- flatMap(groupBy(series, serie => serie.type), type => chunk(type, maxSeries)).slice(0, maxGraph);
-
-export const getSeriesMetricType = (series /*: Array<Serie> */) =>
- series.length > 0 ? series[0].type : 'INT';
-
-export function getAnalysesByVersionByDay(analyses /*: Array<Analysis> */, query /*: Query */) {
- return analyses.reduce((acc, analysis) => {
- let currentVersion = acc[acc.length - 1];
- const versionEvent = analysis.events.find(event => event.category === 'VERSION');
- if (versionEvent) {
- const newVersion = { version: versionEvent.name, key: versionEvent.key, byDay: {} };
- if (!currentVersion || Object.keys(currentVersion.byDay).length > 0) {
- acc.push(newVersion);
- } else {
- acc[acc.length - 1] = newVersion;
- }
- currentVersion = newVersion;
- } else if (!currentVersion) {
- // APPs don't have version events, so let's create a fake one
- currentVersion = { version: null, key: null, byDay: {} };
- acc.push(currentVersion);
- }
-
- const day = startOfDay(parseDate(analysis.date))
- .getTime()
- .toString();
-
- let matchFilters = true;
- if (query.category || query.from || query.to) {
- const isAfterFrom = !query.from || analysis.date >= query.from;
- const isBeforeTo = !query.to || analysis.date <= query.to;
- const hasSelectedCategoryEvents =
- !query.category || analysis.events.find(event => event.category === query.category) != null;
- matchFilters = isAfterFrom && isBeforeTo && hasSelectedCategoryEvents;
- }
-
- if (matchFilters) {
- if (!currentVersion.byDay[day]) {
- currentVersion.byDay[day] = [];
- }
- currentVersion.byDay[day].push(analysis);
- }
- return acc;
- }, []);
-}
-
-export const getDisplayedHistoryMetrics = (
- graph /*: string */,
- customMetrics /*: Array<string> */
-) => (isCustomGraph(graph) ? customMetrics : GRAPHS_METRICS_DISPLAYED[graph]);
-
-export const getHistoryMetrics = (graph /*: string */, customMetrics /*: Array<string> */) =>
- isCustomGraph(graph) ? customMetrics : GRAPHS_METRICS[graph];
-
-const parseGraph = (value /*: ?string */) => {
- const graph = parseAsString(value);
- return GRAPH_TYPES.includes(graph) ? graph : DEFAULT_GRAPH;
-};
-
-const serializeGraph = (value /*: string */) => (value === DEFAULT_GRAPH ? undefined : value);
-
-export function parseQuery(urlQuery /*: RawQuery */) /*: Query */ {
- return {
- category: parseAsString(urlQuery['category']),
- customMetrics: parseAsArray(urlQuery['custom_metrics'], parseAsString),
- from: parseAsDate(urlQuery['from']),
- graph: parseGraph(urlQuery['graph']),
- project: parseAsString(urlQuery['id']),
- to: parseAsDate(urlQuery['to']),
- selectedDate: parseAsDate(urlQuery['selected_date'])
- };
-}
-
-export function serializeQuery(query /*: Query */) /*: RawQuery */ {
- return cleanQuery({
- category: serializeString(query.category),
- from: serializeDate(query.from),
- project: serializeString(query.project),
- to: serializeDate(query.to)
- });
-}
-
-export function serializeUrlQuery(query /*: Query */) /*: RawQuery */ {
- return cleanQuery({
- category: serializeString(query.category),
- custom_metrics: serializeStringArray(query.customMetrics),
- from: serializeDate(query.from),
- graph: serializeGraph(query.graph),
- id: serializeString(query.project),
- to: serializeDate(query.to),
- selected_date: serializeDate(query.selectedDate)
- });
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { chunk, flatMap, groupBy, isEqual, sortBy } from 'lodash';
+import {
+ cleanQuery,
+ parseAsArray,
+ parseAsDate,
+ parseAsString,
+ serializeStringArray,
+ serializeDate,
+ serializeString,
+ RawQuery
+} from '../../helpers/query';
+import { parseDate, startOfDay } from '../../helpers/dates';
+import { getLocalizedMetricName, translate } from '../../helpers/l10n';
+import { Metric, Analysis, Omit } from '../../app/types';
+
+export type ParsedAnalysis = Omit<Analysis, 'date'> & { date: Date };
+
+export interface Query {
+ category: string;
+ customMetrics: string[];
+ from?: Date;
+ graph: string;
+ project: string;
+ selectedDate?: Date;
+ to?: Date;
+}
+
+export interface Point {
+ x: Date;
+ y: number | string | undefined;
+}
+
+export interface Serie {
+ data: Point[];
+ name: string;
+ translatedName: string;
+ type: string;
+}
+
+export interface HistoryItem {
+ date: Date;
+ value?: string;
+}
+
+export interface MeasureHistory {
+ metric: string;
+ history: HistoryItem[];
+}
+
+export const EVENT_TYPES = ['VERSION', 'QUALITY_GATE', 'QUALITY_PROFILE', 'OTHER'];
+export const APPLICATION_EVENT_TYPES = ['QUALITY_GATE', 'OTHER'];
+export const DEFAULT_GRAPH = 'issues';
+export const GRAPH_TYPES = ['issues', 'coverage', 'duplications', 'custom'];
+export const GRAPHS_METRICS_DISPLAYED: { [x: string]: string[] } = {
+ issues: ['bugs', 'code_smells', 'vulnerabilities'],
+ coverage: ['lines_to_cover', 'uncovered_lines'],
+ duplications: ['ncloc', 'duplicated_lines']
+};
+export const GRAPHS_METRICS: { [x: string]: string[] } = {
+ issues: GRAPHS_METRICS_DISPLAYED['issues'].concat([
+ 'reliability_rating',
+ 'security_rating',
+ 'sqale_rating'
+ ]),
+ coverage: GRAPHS_METRICS_DISPLAYED['coverage'].concat(['coverage']),
+ duplications: GRAPHS_METRICS_DISPLAYED['duplications'].concat(['duplicated_lines_density'])
+};
+
+export const PROJECT_ACTIVITY_GRAPH = 'sonarqube.project_activity.graph';
+export const PROJECT_ACTIVITY_GRAPH_CUSTOM = 'sonarqube.project_activity.graph.custom';
+
+export function datesQueryChanged(prevQuery: Query, nextQuery: Query) {
+ return !isEqual(prevQuery.from, nextQuery.from) || !isEqual(prevQuery.to, nextQuery.to);
+}
+
+export function activityQueryChanged(prevQuery: Query, nextQuery: Query) {
+ return prevQuery.category !== nextQuery.category || datesQueryChanged(prevQuery, nextQuery);
+}
+
+export function customMetricsChanged(prevQuery: Query, nextQuery: Query) {
+ return !isEqual(prevQuery.customMetrics, nextQuery.customMetrics);
+}
+
+export function hasDataValues(serie: Serie) {
+ return serie.data.some(point => Boolean(point.y || point.y === 0));
+}
+
+export function hasHistoryData(series: Serie[]) {
+ return series.some(serie => serie.data && serie.data.length > 1);
+}
+
+export function hasHistoryDataValue(series: Serie[]) {
+ return series.some(serie => serie.data && serie.data.length > 1 && hasDataValues(serie));
+}
+
+export function historyQueryChanged(prevQuery: Query, nextQuery: Query) {
+ return prevQuery.graph !== nextQuery.graph;
+}
+
+export function isCustomGraph(graph: string) {
+ return graph === 'custom';
+}
+
+export function selectedDateQueryChanged(prevQuery: Query, nextQuery: Query) {
+ return !isEqual(prevQuery.selectedDate, nextQuery.selectedDate);
+}
+
+export function generateCoveredLinesMetric(
+ uncoveredLines: MeasureHistory,
+ measuresHistory: MeasureHistory[]
+) {
+ const linesToCover = measuresHistory.find(measure => measure.metric === 'lines_to_cover');
+ return {
+ data: linesToCover
+ ? uncoveredLines.history.map((analysis, idx) => ({
+ x: analysis.date,
+ y: Number(linesToCover.history[idx].value) - Number(analysis.value)
+ }))
+ : [],
+ name: 'covered_lines',
+ translatedName: translate('project_activity.custom_metric.covered_lines'),
+ type: 'INT'
+ };
+}
+
+function findMetric(key: string, metrics: Metric[] | { [key: string]: Metric }) {
+ if (Array.isArray(metrics)) {
+ return metrics.find(metric => metric.key === key);
+ }
+ return metrics[key];
+}
+
+export function generateSeries(
+ measuresHistory: MeasureHistory[],
+ graph: string,
+ metrics: Metric[] | { [key: string]: Metric },
+ displayedMetrics: string[]
+): Serie[] {
+ if (displayedMetrics.length <= 0 || typeof measuresHistory === 'undefined') {
+ return [];
+ }
+ return sortBy(
+ measuresHistory
+ .filter(measure => displayedMetrics.indexOf(measure.metric) >= 0)
+ .map(measure => {
+ if (measure.metric === 'uncovered_lines' && !isCustomGraph(graph)) {
+ return generateCoveredLinesMetric(measure, measuresHistory);
+ }
+ const metric = findMetric(measure.metric, metrics);
+ return {
+ data: measure.history.map(analysis => ({
+ x: analysis.date,
+ y: metric && metric.type === 'LEVEL' ? analysis.value : Number(analysis.value)
+ })),
+ name: measure.metric,
+ translatedName: metric ? getLocalizedMetricName(metric) : measure.metric,
+ type: metric ? metric.type : 'INT'
+ };
+ }),
+ serie =>
+ displayedMetrics.indexOf(serie.name === 'covered_lines' ? 'uncovered_lines' : serie.name)
+ );
+}
+
+export function splitSeriesInGraphs(series: Serie[], maxGraph: number, maxSeries: number) {
+ return flatMap(groupBy(series, serie => serie.type), type => chunk(type, maxSeries)).slice(
+ 0,
+ maxGraph
+ );
+}
+
+export function getSeriesMetricType(series: Serie[]) {
+ return series.length > 0 ? series[0].type : 'INT';
+}
+
+interface AnalysesByDay {
+ byDay: { [x: string]: ParsedAnalysis[] };
+ version: string | null;
+ key: string | null;
+}
+
+export function getAnalysesByVersionByDay(analyses: ParsedAnalysis[], query: Query) {
+ return analyses.reduce<AnalysesByDay[]>((acc, analysis) => {
+ let currentVersion = acc[acc.length - 1];
+ const versionEvent = analysis.events.find(event => event.category === 'VERSION');
+ if (versionEvent) {
+ const newVersion = { version: versionEvent.name, key: versionEvent.key, byDay: {} };
+ if (!currentVersion || Object.keys(currentVersion.byDay).length > 0) {
+ acc.push(newVersion);
+ } else {
+ acc[acc.length - 1] = newVersion;
+ }
+ currentVersion = newVersion;
+ } else if (!currentVersion) {
+ // APPs don't have version events, so let's create a fake one
+ currentVersion = { version: null, key: null, byDay: {} };
+ acc.push(currentVersion);
+ }
+
+ const day = startOfDay(parseDate(analysis.date))
+ .getTime()
+ .toString();
+
+ let matchFilters = true;
+ if (query.category || query.from || query.to) {
+ const isAfterFrom = !query.from || analysis.date >= query.from;
+ const isBeforeTo = !query.to || analysis.date <= query.to;
+ const hasSelectedCategoryEvents =
+ !query.category || analysis.events.find(event => event.category === query.category) != null;
+ matchFilters = isAfterFrom && isBeforeTo && hasSelectedCategoryEvents;
+ }
+
+ if (matchFilters) {
+ if (!currentVersion.byDay[day]) {
+ currentVersion.byDay[day] = [];
+ }
+ currentVersion.byDay[day].push(analysis);
+ }
+ return acc;
+ }, []);
+}
+
+export function getDisplayedHistoryMetrics(graph: string, customMetrics: string[]) {
+ return isCustomGraph(graph) ? customMetrics : GRAPHS_METRICS_DISPLAYED[graph];
+}
+
+export function getHistoryMetrics(graph: string, customMetrics: string[]) {
+ return isCustomGraph(graph) ? customMetrics : GRAPHS_METRICS[graph];
+}
+
+function parseGraph(value?: string) {
+ const graph = parseAsString(value);
+ return GRAPH_TYPES.includes(graph) ? graph : DEFAULT_GRAPH;
+}
+
+function serializeGraph(value: string) {
+ return value === DEFAULT_GRAPH ? undefined : value;
+}
+
+export function parseQuery(urlQuery: RawQuery): Query {
+ return {
+ category: parseAsString(urlQuery['category']),
+ customMetrics: parseAsArray(urlQuery['custom_metrics'], parseAsString),
+ from: parseAsDate(urlQuery['from']),
+ graph: parseGraph(urlQuery['graph']),
+ project: parseAsString(urlQuery['id']),
+ to: parseAsDate(urlQuery['to']),
+ selectedDate: parseAsDate(urlQuery['selected_date'])
+ };
+}
+
+export function serializeQuery(query: Query): RawQuery {
+ return cleanQuery({
+ category: serializeString(query.category),
+ from: serializeDate(query.from),
+ project: serializeString(query.project),
+ to: serializeDate(query.to)
+ });
+}
+
+export function serializeUrlQuery(query: Query): RawQuery {
+ return cleanQuery({
+ category: serializeString(query.category),
+ custom_metrics: serializeStringArray(query.customMetrics), // eslint-disable-line camelcase
+ from: serializeDate(query.from),
+ graph: serializeGraph(query.graph),
+ id: serializeString(query.project),
+ to: serializeDate(query.to),
+ selected_date: serializeDate(query.selectedDate) // eslint-disable-line camelcase
+ });
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-import { flatten, isEqual, sortBy, throttle, uniq } from 'lodash';
-import { bisector, extent, max } from 'd3-array';
-import { scaleLinear, scalePoint, scaleTime } from 'd3-scale';
-import { line as d3Line, area, curveBasis } from 'd3-shape';
-import * as theme from '../../app/theme';
-import './LineChart.css';
-import './AdvancedTimeline.css';
-
-/*::
-type Event = { className?: string, name: string, date: Date };
-*/
-/*::
-export type Point = { x: Date, y: number | string };
-*/
-/*::
-export type Serie = { name: string, data: Array<Point>, type: string };
-*/
-/*::
-type Scale = Function;
-*/
-
-/*::
-type Props = {
- basisCurve?: boolean,
- endDate: ?Date,
- events?: Array<Event>,
- eventSize?: number,
- disableZoom?: boolean,
- formatYTick?: number => string,
- hideGrid?: boolean,
- hideXAxis?: boolean,
- height: number,
- width: number,
- leakPeriodDate?: Date,
- // used to avoid same y ticks labels
- maxYTicksCount?: number,
- metricType: string,
- padding: Array<number>,
- selectedDate?: Date,
- series: Array<Serie>,
- showAreas?: boolean,
- showEventMarkers?: boolean,
- startDate: ?Date,
- updateSelectedDate?: (selectedDate: ?Date) => void,
- updateTooltip?: (selectedDate: ?Date, tooltipXPos: ?number, tooltipIdx: ?number) => void,
- updateZoom?: (start: ?Date, endDate: ?Date) => void,
- zoomSpeed: number
-};
-*/
-
-/*::
-type State = {
- maxXRange: Array<number>,
- mouseOver?: boolean,
- mouseOverlayPos?: { [string]: number },
- selectedDate: ?Date,
- selectedDateXPos: ?number,
- selectedDateIdx: ?number,
- yScale: Scale,
- xScale: Scale
-};
-*/
-
-export default class AdvancedTimeline extends React.PureComponent {
- /*:: props: Props; */
- /*:: state: State; */
-
- static defaultProps = {
- eventSize: 8,
- maxYTicksCount: 4,
- padding: [10, 10, 30, 60],
- zoomSpeed: 1
- };
-
- constructor(props /*: Props */) {
- super(props);
- const scales = this.getScales(props);
- const selectedDatePos = this.getSelectedDatePos(scales.xScale, props.selectedDate);
- this.state = { ...scales, ...selectedDatePos };
- this.updateTooltipPos = throttle(this.updateTooltipPos, 40);
- this.handleZoomUpdate = throttle(this.handleZoomUpdate, 40);
- }
-
- componentWillReceiveProps(nextProps /*: Props */) {
- let scales;
- let selectedDatePos;
- if (
- nextProps.metricType !== this.props.metricType ||
- nextProps.startDate !== this.props.startDate ||
- nextProps.endDate !== this.props.endDate ||
- nextProps.width !== this.props.width ||
- nextProps.padding !== this.props.padding ||
- nextProps.height !== this.props.height ||
- nextProps.series !== this.props.series
- ) {
- scales = this.getScales(nextProps);
- if (this.state.selectedDate != null) {
- selectedDatePos = this.getSelectedDatePos(scales.xScale, this.state.selectedDate);
- }
- }
-
- if (!isEqual(nextProps.selectedDate, this.props.selectedDate)) {
- const xScale = scales ? scales.xScale : this.state.xScale;
- selectedDatePos = this.getSelectedDatePos(xScale, nextProps.selectedDate);
- }
-
- if (scales || selectedDatePos) {
- this.setState({ ...(scales || {}), ...(selectedDatePos || {}) });
-
- if (selectedDatePos && nextProps.updateTooltip) {
- nextProps.updateTooltip(
- selectedDatePos.selectedDate,
- selectedDatePos.selectedDateXPos,
- selectedDatePos.selectedDateIdx
- );
- }
- }
- }
-
- getRatingScale = (availableHeight /*: number */) =>
- scalePoint()
- .domain([5, 4, 3, 2, 1])
- .range([availableHeight, 0]);
-
- getLevelScale = (availableHeight /*: number */) =>
- scalePoint()
- .domain(['ERROR', 'WARN', 'OK'])
- .range([availableHeight, 0]);
-
- getYScale = (props /*: Props */, availableHeight /*: number */, flatData /*: Array<Point> */) => {
- if (props.metricType === 'RATING') {
- return this.getRatingScale(availableHeight);
- } else if (props.metricType === 'LEVEL') {
- return this.getLevelScale(availableHeight);
- } else {
- return scaleLinear()
- .range([availableHeight, 0])
- .domain([0, max(flatData, d => d.y) || 1])
- .nice();
- }
- };
-
- getXScale = (
- { startDate, endDate } /*: Props */,
- availableWidth /*: number */,
- flatData /*: Array<Point> */
- ) => {
- const dateRange = extent(flatData, d => d.x);
- const start = startDate && startDate > dateRange[0] ? startDate : dateRange[0];
- const end = endDate && endDate < dateRange[1] ? endDate : dateRange[1];
- const xScale = scaleTime()
- .domain(sortBy([start, end]))
- .range([0, availableWidth])
- .clamp(false);
- return {
- xScale,
- maxXRange: dateRange.map(xScale)
- };
- };
-
- getScales = (props /*: Props */) => {
- const availableWidth = props.width - props.padding[1] - props.padding[3];
- const availableHeight = props.height - props.padding[0] - props.padding[2];
- const flatData = flatten(props.series.map((serie /*: Serie */) => serie.data));
- return {
- ...this.getXScale(props, availableWidth, flatData),
- yScale: this.getYScale(props, availableHeight, flatData)
- };
- };
-
- getSelectedDatePos = (xScale /*: Scale */, selectedDate /*: ?Date */) => {
- const firstSerie = this.props.series[0];
- if (selectedDate && firstSerie) {
- const idx = firstSerie.data.findIndex(
- // $FlowFixMe selectedDate can't be null there
- p => p.x.valueOf() === selectedDate.valueOf()
- );
- const xRange = xScale.range().sort();
- const xPos = xScale(selectedDate);
- if (idx >= 0 && xPos >= xRange[0] && xPos <= xRange[1]) {
- return {
- selectedDate,
- selectedDateXPos: xScale(selectedDate),
- selectedDateIdx: idx
- };
- }
- }
- return { selectedDate: null, selectedDateXPos: null, selectedDateIdx: null };
- };
-
- getEventMarker = (size /*: number */) => {
- const half = size / 2;
- return `M${half} 0 L${size} ${half} L ${half} ${size} L0 ${half} L${half} 0 L${size} ${half}`;
- };
-
- getMouseOverlayPos = (target /*: HTMLElement */) => {
- if (this.state.mouseOverlayPos) {
- return this.state.mouseOverlayPos;
- }
- const pos = target.getBoundingClientRect();
- this.setState({ mouseOverlayPos: pos });
- return pos;
- };
-
- handleWheel = (evt /*: WheelEvent & { target: HTMLElement } */) => {
- evt.preventDefault();
- const { maxXRange, xScale } = this.state;
- const parentBbox = this.getMouseOverlayPos(evt.target);
- const mouseXPos = (evt.pageX - parentBbox.left) / parentBbox.width;
- const xRange = xScale.range();
- const speed = evt.deltaMode
- ? (25 / evt.deltaMode) * this.props.zoomSpeed
- : this.props.zoomSpeed;
- const leftPos = xRange[0] - Math.round(speed * evt.deltaY * mouseXPos);
- const rightPos = xRange[1] + Math.round(speed * evt.deltaY * (1 - mouseXPos));
- const startDate = leftPos > maxXRange[0] ? xScale.invert(leftPos) : null;
- const endDate = rightPos < maxXRange[1] ? xScale.invert(rightPos) : null;
- this.handleZoomUpdate(startDate, endDate);
- };
-
- handleZoomUpdate = (startDate /*: ?Date */, endDate /*: ?Date */) => {
- if (this.props.updateZoom) {
- this.props.updateZoom(startDate, endDate);
- }
- };
-
- handleMouseMove = (evt /*: MouseEvent & { target: HTMLElement } */) => {
- const parentBbox = this.getMouseOverlayPos(evt.target);
- this.updateTooltipPos(evt.pageX - parentBbox.left);
- };
-
- handleMouseEnter = () => this.setState({ mouseOver: true });
-
- handleMouseOut = (evt /*: Event & { relatedTarget: HTMLElement } */) => {
- const { updateTooltip } = this.props;
- if (updateTooltip) {
- this.setState({
- mouseOver: false,
- selectedDate: null,
- selectedDateXPos: null,
- selectedDateIdx: null
- });
- updateTooltip(null, null, null);
- }
- };
-
- handleClick = () => {
- const { updateSelectedDate } = this.props;
- if (updateSelectedDate) {
- updateSelectedDate(this.state.selectedDate);
- }
- };
-
- updateTooltipPos = (xPos /*: number */) => {
- const firstSerie = this.props.series[0];
- if (this.state.mouseOver && firstSerie) {
- const { updateTooltip } = this.props;
- const date = this.state.xScale.invert(xPos);
- const bisectX = bisector(d => d.x).right;
- let idx = bisectX(firstSerie.data, date);
- if (idx >= 0) {
- const previousPoint = firstSerie.data[idx - 1];
- const nextPoint = firstSerie.data[idx];
- if (!nextPoint || (previousPoint && date - previousPoint.x <= nextPoint.x - date)) {
- idx--;
- }
- const selectedDate = firstSerie.data[idx].x;
- const xPos = this.state.xScale(selectedDate);
- this.setState({ selectedDate, selectedDateXPos: xPos, selectedDateIdx: idx });
- if (updateTooltip) {
- updateTooltip(selectedDate, xPos, idx);
- }
- }
- }
- };
-
- renderHorizontalGrid = () => {
- const { formatYTick } = this.props;
- const { xScale, yScale } = this.state;
- const hasTicks = typeof yScale.ticks === 'function';
- let ticks = hasTicks ? yScale.ticks(this.props.maxYTicksCount) : yScale.domain();
-
- if (!ticks.length) {
- ticks.push(yScale.domain()[1]);
- }
-
- // if there are duplicated ticks, that means 4 ticks are too much for this data
- // so let's just use the domain values (min and max)
- if (formatYTick) {
- const formattedTicks = ticks.map(tick => formatYTick(tick));
- if (ticks.length > uniq(formattedTicks).length) {
- ticks = yScale.domain();
- }
- }
-
- return (
- <g>
- {ticks.map(tick => (
- <g key={tick}>
- {formatYTick != null && (
- <text
- className="line-chart-tick line-chart-tick-x"
- dx="-1em"
- dy="0.3em"
- textAnchor="end"
- x={xScale.range()[0]}
- y={yScale(tick)}>
- {formatYTick(tick)}
- </text>
- )}
- <line
- className="line-chart-grid"
- x1={xScale.range()[0]}
- x2={xScale.range()[1]}
- y1={yScale(tick)}
- y2={yScale(tick)}
- />
- </g>
- ))}
- </g>
- );
- };
-
- renderXAxisTicks = () => {
- const { xScale, yScale } = this.state;
- const format = xScale.tickFormat(7);
- const ticks = xScale.ticks(7);
- const y = yScale.range()[0];
- return (
- <g>
- {ticks.slice(0, -1).map((tick, index) => {
- const nextTick = index + 1 < ticks.length ? ticks[index + 1] : xScale.domain()[1];
- const x = (xScale(tick) + xScale(nextTick)) / 2;
- return (
- <text className="line-chart-tick" dy="1.5em" key={index} x={x} y={y}>
- {format(tick)}
- </text>
- );
- })}
- </g>
- );
- };
-
- renderLeak = () => {
- const yRange = this.state.yScale.range();
- const xRange = this.state.xScale.range();
- const leakWidth = xRange[xRange.length - 1] - this.state.xScale(this.props.leakPeriodDate);
- if (leakWidth < 0) {
- return null;
- }
- return (
- <rect
- className="leak-chart-rect"
- fill={theme.leakColor}
- height={yRange[0] - yRange[yRange.length - 1]}
- width={leakWidth}
- x={this.state.xScale(this.props.leakPeriodDate)}
- y={yRange[yRange.length - 1]}
- />
- );
- };
-
- renderLines = () => {
- const lineGenerator = d3Line()
- .defined(d => d.y || d.y === 0)
- .x(d => this.state.xScale(d.x))
- .y(d => this.state.yScale(d.y));
- if (this.props.basisCurve) {
- lineGenerator.curve(curveBasis);
- }
- return (
- <g>
- {this.props.series.map((serie, idx) => (
- <path
- className={classNames('line-chart-path', 'line-chart-path-' + idx)}
- d={lineGenerator(serie.data)}
- key={serie.name}
- />
- ))}
- </g>
- );
- };
-
- renderDots = () => {
- return (
- <g>
- {this.props.series
- .map((serie, serieIdx) =>
- serie.data
- .map((point, idx) => {
- const pointNotDefined = !point.y && point.y !== 0;
- const hasPointBefore =
- serie.data[idx - 1] && (serie.data[idx - 1].y || serie.data[idx - 1].y === 0);
- const hasPointAfter =
- serie.data[idx + 1] && (serie.data[idx + 1].y || serie.data[idx + 1].y === 0);
- if (pointNotDefined || (hasPointBefore || hasPointAfter)) {
- return undefined;
- }
- return (
- <circle
- className={classNames('line-chart-dot', 'line-chart-dot-' + serieIdx)}
- cx={this.state.xScale(point.x)}
- cy={this.state.yScale(point.y)}
- key={serie.name + idx}
- r="2"
- />
- );
- })
- .filter(Boolean)
- )
- .filter(dots => dots.length > 0)}
- </g>
- );
- };
-
- renderAreas = () => {
- const areaGenerator = area()
- .defined(d => d.y || d.y === 0)
- .x(d => this.state.xScale(d.x))
- .y1(d => this.state.yScale(d.y))
- .y0(this.state.yScale(0));
- if (this.props.basisCurve) {
- areaGenerator.curve(curveBasis);
- }
- return (
- <g>
- {this.props.series.map((serie, idx) => (
- <path
- className={classNames('line-chart-area', 'line-chart-area-' + idx)}
- d={areaGenerator(serie.data)}
- key={serie.name}
- />
- ))}
- </g>
- );
- };
-
- renderEvents = () => {
- const { events, eventSize } = this.props;
- if (!events || !eventSize) {
- return null;
- }
- const { xScale, yScale } = this.state;
- const inRangeEvents = events.filter(
- event => event.date >= xScale.domain()[0] && event.date <= xScale.domain()[1]
- );
- const offset = eventSize / 2;
- return (
- <g>
- {inRangeEvents.map((event, idx) => (
- <path
- className={classNames('line-chart-event', event.className)}
- d={this.getEventMarker(eventSize)}
- key={`${idx}-${event.date.getTime()}`}
- transform={`translate(${xScale(event.date) - offset}, ${yScale.range()[0] + offset})`}
- />
- ))}
- </g>
- );
- };
-
- renderSelectedDate = () => {
- const { selectedDateIdx, selectedDateXPos, yScale } = this.state;
- const firstSerie = this.props.series[0];
- if (selectedDateIdx == null || selectedDateXPos == null || !firstSerie) {
- return null;
- }
-
- return (
- <g>
- <line
- className="line-tooltip"
- x1={selectedDateXPos}
- x2={selectedDateXPos}
- y1={yScale.range()[0]}
- y2={yScale.range()[1]}
- />
- {this.props.series.map((serie, idx) => {
- const point = serie.data[selectedDateIdx];
- if (!point || (!point.y && point.y !== 0)) {
- return null;
- }
- return (
- <circle
- className={classNames('line-chart-dot', 'line-chart-dot-' + idx)}
- cx={selectedDateXPos}
- cy={yScale(point.y)}
- key={serie.name}
- r="4"
- />
- );
- })}
- </g>
- );
- };
-
- renderClipPath = () => {
- return (
- <defs>
- <clipPath id="chart-clip">
- <rect
- height={this.state.yScale.range()[0] + 10}
- transform="translate(0,-5)"
- width={this.state.xScale.range()[1]}
- />
- </clipPath>
- </defs>
- );
- };
-
- renderMouseEventsOverlay = (zoomEnabled /*: boolean */) => {
- const mouseEvents = {};
- if (zoomEnabled) {
- mouseEvents.onWheel = this.handleWheel;
- }
- if (this.props.updateTooltip) {
- mouseEvents.onMouseEnter = this.handleMouseEnter;
- mouseEvents.onMouseMove = this.handleMouseMove;
- mouseEvents.onMouseOut = this.handleMouseOut;
- }
- if (this.props.updateSelectedDate) {
- mouseEvents.onClick = this.handleClick;
- }
- return (
- <rect
- className="chart-mouse-events-overlay"
- height={this.state.yScale.range()[0]}
- width={this.state.xScale.range()[1]}
- {...mouseEvents}
- />
- );
- };
-
- render() {
- if (!this.props.width || !this.props.height) {
- return <div />;
- }
- const zoomEnabled = !this.props.disableZoom && this.props.updateZoom != null;
- const isZoomed = this.props.startDate || this.props.endDate;
- return (
- <svg
- className={classNames('line-chart', { 'chart-zoomed': isZoomed })}
- height={this.props.height}
- width={this.props.width}>
- {zoomEnabled && this.renderClipPath()}
- <g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}>
- {this.props.leakPeriodDate != null && this.renderLeak()}
- {!this.props.hideGrid && this.renderHorizontalGrid()}
- {!this.props.hideXAxis && this.renderXAxisTicks()}
- {this.props.showAreas && this.renderAreas()}
- {this.renderLines()}
- {this.renderDots()}
- {this.props.showEventMarkers && this.renderEvents()}
- {this.renderSelectedDate()}
- {this.renderMouseEventsOverlay(zoomEnabled)}
- </g>
- </svg>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { flatten, isEqual, sortBy, throttle, uniq } from 'lodash';
+import { bisector, extent, max } from 'd3-array';
+import { scaleLinear, scalePoint, scaleTime, ScaleTime } from 'd3-scale';
+import { line as d3Line, area, curveBasis } from 'd3-shape';
+import * as theme from '../../app/theme';
+import { Serie, Point } from '../../apps/projectActivity/utils';
+import './LineChart.css';
+import './AdvancedTimeline.css';
+
+export interface Props {
+ basisCurve?: boolean;
+ endDate?: Date;
+ disableZoom?: boolean;
+ formatYTick?: (tick: number | string) => string;
+ hideGrid?: boolean;
+ hideXAxis?: boolean;
+ height: number;
+ width: number;
+ leakPeriodDate?: Date;
+ // used to avoid same y ticks labels
+ maxYTicksCount: number;
+ metricType: string;
+ padding: number[];
+ selectedDate?: Date;
+ series: Serie[];
+ showAreas?: boolean;
+ startDate?: Date;
+ updateSelectedDate?: (selectedDate?: Date) => void;
+ updateTooltip?: (selectedDate?: Date, tooltipXPos?: number, tooltipIdx?: number) => void;
+ updateZoom?: (start?: Date, endDate?: Date) => void;
+ zoomSpeed: number;
+}
+
+type XScale = ScaleTime<number, number>;
+// TODO it should be `ScaleLinear<number, number> | ScalePoint<number> | ScalePoint<string>`, but it's super hard to make it work :'(
+type YScale = any;
+
+interface State {
+ maxXRange: number[];
+ mouseOver?: boolean;
+ mouseOverlayPos?: ClientRect | DOMRect;
+ selectedDate?: Date;
+ selectedDateXPos?: number;
+ selectedDateIdx?: number;
+ yScale: YScale;
+ xScale: XScale;
+}
+
+export default class AdvancedTimeline extends React.PureComponent<Props, State> {
+ static defaultProps = {
+ eventSize: 8,
+ maxYTicksCount: 4,
+ padding: [10, 10, 30, 60],
+ zoomSpeed: 1
+ };
+
+ constructor(props: Props) {
+ super(props);
+ const scales = this.getScales(props);
+ const selectedDatePos = this.getSelectedDatePos(scales.xScale, props.selectedDate);
+ this.state = { ...scales, ...selectedDatePos };
+ this.updateTooltipPos = throttle(this.updateTooltipPos, 40);
+ this.handleZoomUpdate = throttle(this.handleZoomUpdate, 40);
+ }
+
+ componentWillReceiveProps(nextProps: Props) {
+ let scales;
+ let selectedDatePos;
+ if (
+ nextProps.metricType !== this.props.metricType ||
+ nextProps.startDate !== this.props.startDate ||
+ nextProps.endDate !== this.props.endDate ||
+ nextProps.width !== this.props.width ||
+ nextProps.padding !== this.props.padding ||
+ nextProps.height !== this.props.height ||
+ nextProps.series !== this.props.series
+ ) {
+ scales = this.getScales(nextProps);
+ if (this.state.selectedDate != null) {
+ selectedDatePos = this.getSelectedDatePos(scales.xScale, this.state.selectedDate);
+ }
+ }
+
+ if (!isEqual(nextProps.selectedDate, this.props.selectedDate)) {
+ const xScale = scales ? scales.xScale : this.state.xScale;
+ selectedDatePos = this.getSelectedDatePos(xScale, nextProps.selectedDate);
+ }
+
+ if (scales || selectedDatePos) {
+ if (scales) {
+ this.setState({ ...scales });
+ }
+ if (selectedDatePos) {
+ this.setState({ ...selectedDatePos });
+ }
+
+ if (selectedDatePos && nextProps.updateTooltip) {
+ nextProps.updateTooltip(
+ selectedDatePos.selectedDate,
+ selectedDatePos.selectedDateXPos,
+ selectedDatePos.selectedDateIdx
+ );
+ }
+ }
+ }
+
+ getRatingScale = (availableHeight: number) => {
+ return scalePoint<number>()
+ .domain([5, 4, 3, 2, 1])
+ .range([availableHeight, 0]);
+ };
+
+ getLevelScale = (availableHeight: number) => {
+ return scalePoint()
+ .domain(['ERROR', 'WARN', 'OK'])
+ .range([availableHeight, 0]);
+ };
+
+ getYScale = (props: Props, availableHeight: number, flatData: Point[]): YScale => {
+ if (props.metricType === 'RATING') {
+ return this.getRatingScale(availableHeight);
+ } else if (props.metricType === 'LEVEL') {
+ return this.getLevelScale(availableHeight);
+ } else {
+ return scaleLinear()
+ .range([availableHeight, 0])
+ .domain([0, max(flatData, d => Number(d.y || 0)) || 1])
+ .nice();
+ }
+ };
+
+ getXScale = ({ startDate, endDate }: Props, availableWidth: number, flatData: Point[]) => {
+ const dateRange = extent(flatData, d => d.x) as [Date, Date];
+ const start = startDate && startDate > dateRange[0] ? startDate : dateRange[0];
+ const end = endDate && endDate < dateRange[1] ? endDate : dateRange[1];
+ const xScale: ScaleTime<number, number> = scaleTime()
+ .domain(sortBy([start, end]))
+ .range([0, availableWidth])
+ .clamp(false);
+ return {
+ xScale,
+ maxXRange: dateRange.map(xScale)
+ };
+ };
+
+ getScales = (props: Props) => {
+ const availableWidth = props.width - props.padding[1] - props.padding[3];
+ const availableHeight = props.height - props.padding[0] - props.padding[2];
+ const flatData = flatten(props.series.map(serie => serie.data));
+ return {
+ ...this.getXScale(props, availableWidth, flatData),
+ yScale: this.getYScale(props, availableHeight, flatData)
+ };
+ };
+
+ getSelectedDatePos = (xScale: XScale, selectedDate?: Date) => {
+ const firstSerie = this.props.series[0];
+ if (selectedDate && firstSerie) {
+ const idx = firstSerie.data.findIndex(p => p.x.valueOf() === selectedDate.valueOf());
+ const xRange = xScale.range().sort();
+ const xPos = xScale(selectedDate);
+ if (idx >= 0 && xPos >= xRange[0] && xPos <= xRange[1]) {
+ return {
+ selectedDate,
+ selectedDateXPos: xScale(selectedDate),
+ selectedDateIdx: idx
+ };
+ }
+ }
+ return { selectedDate: undefined, selectedDateXPos: undefined, selectedDateIdx: undefined };
+ };
+
+ getEventMarker = (size: number) => {
+ const half = size / 2;
+ return `M${half} 0 L${size} ${half} L ${half} ${size} L0 ${half} L${half} 0 L${size} ${half}`;
+ };
+
+ getMouseOverlayPos = (target: SVGElement) => {
+ if (this.state.mouseOverlayPos) {
+ return this.state.mouseOverlayPos;
+ }
+ const pos = target.getBoundingClientRect();
+ this.setState({ mouseOverlayPos: pos });
+ return pos;
+ };
+
+ handleWheel = (event: React.WheelEvent<SVGElement>) => {
+ event.preventDefault();
+ const { maxXRange, xScale } = this.state;
+ const parentBbox = this.getMouseOverlayPos(event.currentTarget);
+ const mouseXPos = (event.pageX - parentBbox.left) / parentBbox.width;
+ const xRange = xScale.range();
+ const speed = event.deltaMode
+ ? (25 / event.deltaMode) * this.props.zoomSpeed
+ : this.props.zoomSpeed;
+ const leftPos = xRange[0] - Math.round(speed * event.deltaY * mouseXPos);
+ const rightPos = xRange[1] + Math.round(speed * event.deltaY * (1 - mouseXPos));
+ const startDate = leftPos > maxXRange[0] ? xScale.invert(leftPos) : undefined;
+ const endDate = rightPos < maxXRange[1] ? xScale.invert(rightPos) : undefined;
+ this.handleZoomUpdate(startDate, endDate);
+ };
+
+ handleZoomUpdate = (startDate?: Date, endDate?: Date) => {
+ if (this.props.updateZoom) {
+ this.props.updateZoom(startDate, endDate);
+ }
+ };
+
+ handleMouseMove = (event: React.MouseEvent<SVGElement>) => {
+ const parentBbox = this.getMouseOverlayPos(event.currentTarget);
+ this.updateTooltipPos(event.pageX - parentBbox.left);
+ };
+
+ handleMouseEnter = () => {
+ this.setState({ mouseOver: true });
+ };
+
+ handleMouseOut = () => {
+ const { updateTooltip } = this.props;
+ if (updateTooltip) {
+ this.setState({
+ mouseOver: false,
+ selectedDate: undefined,
+ selectedDateXPos: undefined,
+ selectedDateIdx: undefined
+ });
+ updateTooltip(undefined, undefined, undefined);
+ }
+ };
+
+ handleClick = () => {
+ const { updateSelectedDate } = this.props;
+ if (updateSelectedDate) {
+ updateSelectedDate(this.state.selectedDate || undefined);
+ }
+ };
+
+ updateTooltipPos = (xPos: number) => {
+ const firstSerie = this.props.series[0];
+ if (this.state.mouseOver && firstSerie) {
+ const { updateTooltip } = this.props;
+ const date = this.state.xScale.invert(xPos);
+ const bisectX = bisector<Point, Date>(d => d.x).right;
+ let idx = bisectX(firstSerie.data, date);
+ if (idx >= 0) {
+ const previousPoint = firstSerie.data[idx - 1];
+ const nextPoint = firstSerie.data[idx];
+ if (
+ !nextPoint ||
+ (previousPoint &&
+ date.valueOf() - previousPoint.x.valueOf() <= nextPoint.x.valueOf() - date.valueOf())
+ ) {
+ idx--;
+ }
+ const selectedDate = firstSerie.data[idx].x;
+ const xPos = this.state.xScale(selectedDate);
+ this.setState({ selectedDate, selectedDateXPos: xPos, selectedDateIdx: idx });
+ if (updateTooltip) {
+ updateTooltip(selectedDate, xPos, idx);
+ }
+ }
+ }
+ };
+
+ renderHorizontalGrid = () => {
+ const { formatYTick } = this.props;
+ const { xScale, yScale } = this.state;
+ const hasTicks = typeof yScale.ticks === 'function';
+ let ticks: Array<string | number> = hasTicks
+ ? yScale.ticks(this.props.maxYTicksCount)
+ : yScale.domain();
+
+ if (!ticks.length) {
+ ticks.push(yScale.domain()[1]);
+ }
+
+ // if there are duplicated ticks, that means 4 ticks are too much for this data
+ // so let's just use the domain values (min and max)
+ if (formatYTick) {
+ const formattedTicks = ticks.map(tick => formatYTick(tick));
+ if (ticks.length > uniq(formattedTicks).length) {
+ ticks = yScale.domain();
+ }
+ }
+
+ return (
+ <g>
+ {ticks.map(tick => (
+ <g key={tick}>
+ {formatYTick != null && (
+ <text
+ className="line-chart-tick line-chart-tick-x"
+ dx="-1em"
+ dy="0.3em"
+ textAnchor="end"
+ x={xScale.range()[0]}
+ y={yScale(tick)}>
+ {formatYTick(tick)}
+ </text>
+ )}
+ <line
+ className="line-chart-grid"
+ x1={xScale.range()[0]}
+ x2={xScale.range()[1]}
+ y1={yScale(tick)}
+ y2={yScale(tick)}
+ />
+ </g>
+ ))}
+ </g>
+ );
+ };
+
+ renderXAxisTicks = () => {
+ const { xScale, yScale } = this.state;
+ const format = xScale.tickFormat(7);
+ const ticks = xScale.ticks(7);
+ const y = yScale.range()[0];
+ return (
+ <g>
+ {ticks.slice(0, -1).map((tick, index) => {
+ const nextTick = index + 1 < ticks.length ? ticks[index + 1] : xScale.domain()[1];
+ const x = (xScale(tick) + xScale(nextTick)) / 2;
+ return (
+ <text className="line-chart-tick" dy="1.5em" key={index} x={x} y={y}>
+ {format(tick)}
+ </text>
+ );
+ })}
+ </g>
+ );
+ };
+
+ renderLeak = () => {
+ const { leakPeriodDate } = this.props;
+ if (!leakPeriodDate) {
+ return null;
+ }
+ const { xScale, yScale } = this.state;
+ const yRange = yScale.range();
+ const xRange = xScale.range();
+ const leakWidth = xRange[xRange.length - 1] - xScale(leakPeriodDate);
+ if (leakWidth < 0) {
+ return null;
+ }
+ return (
+ <rect
+ className="leak-chart-rect"
+ fill={theme.leakColor}
+ height={yRange[0] - yRange[yRange.length - 1]}
+ width={leakWidth}
+ x={xScale(leakPeriodDate)}
+ y={yRange[yRange.length - 1]}
+ />
+ );
+ };
+
+ renderLines = () => {
+ const lineGenerator = d3Line<Point>()
+ .defined(d => Boolean(d.y || d.y === 0))
+ .x(d => this.state.xScale(d.x))
+ .y(d => this.state.yScale(d.y));
+ if (this.props.basisCurve) {
+ lineGenerator.curve(curveBasis);
+ }
+ return (
+ <g>
+ {this.props.series.map((serie, idx) => (
+ <path
+ className={classNames('line-chart-path', 'line-chart-path-' + idx)}
+ d={lineGenerator(serie.data) || undefined}
+ key={serie.name}
+ />
+ ))}
+ </g>
+ );
+ };
+
+ renderDots = () => {
+ return (
+ <g>
+ {this.props.series
+ .map((serie, serieIdx) =>
+ serie.data
+ .map((point, idx) => {
+ const pointNotDefined = !point.y && point.y !== 0;
+ const hasPointBefore =
+ serie.data[idx - 1] && (serie.data[idx - 1].y || serie.data[idx - 1].y === 0);
+ const hasPointAfter =
+ serie.data[idx + 1] && (serie.data[idx + 1].y || serie.data[idx + 1].y === 0);
+ if (pointNotDefined || (hasPointBefore || hasPointAfter)) {
+ return undefined;
+ }
+ return (
+ <circle
+ className={classNames('line-chart-dot', 'line-chart-dot-' + serieIdx)}
+ cx={this.state.xScale(point.x)}
+ cy={this.state.yScale(point.y)}
+ key={serie.name + idx}
+ r="2"
+ />
+ );
+ })
+ .filter(Boolean)
+ )
+ .filter(dots => dots.length > 0)}
+ </g>
+ );
+ };
+
+ renderAreas = () => {
+ const areaGenerator = area<Point>()
+ .defined(d => Boolean(d.y || d.y === 0))
+ .x(d => this.state.xScale(d.x))
+ .y1(d => this.state.yScale(d.y))
+ .y0(this.state.yScale(0));
+ if (this.props.basisCurve) {
+ areaGenerator.curve(curveBasis);
+ }
+ return (
+ <g>
+ {this.props.series.map((serie, idx) => (
+ <path
+ className={classNames('line-chart-area', 'line-chart-area-' + idx)}
+ d={areaGenerator(serie.data) || undefined}
+ key={serie.name}
+ />
+ ))}
+ </g>
+ );
+ };
+
+ renderSelectedDate = () => {
+ const { selectedDateIdx, selectedDateXPos, yScale } = this.state;
+ const firstSerie = this.props.series[0];
+ if (selectedDateIdx == null || selectedDateXPos == null || !firstSerie) {
+ return null;
+ }
+
+ return (
+ <g>
+ <line
+ className="line-tooltip"
+ x1={selectedDateXPos}
+ x2={selectedDateXPos}
+ y1={yScale.range()[0]}
+ y2={yScale.range()[1]}
+ />
+ {this.props.series.map((serie, idx) => {
+ const point = serie.data[selectedDateIdx];
+ if (!point || (!point.y && point.y !== 0)) {
+ return null;
+ }
+ return (
+ <circle
+ className={classNames('line-chart-dot', 'line-chart-dot-' + idx)}
+ cx={selectedDateXPos}
+ cy={yScale(point.y)}
+ key={serie.name}
+ r="4"
+ />
+ );
+ })}
+ </g>
+ );
+ };
+
+ renderClipPath = () => {
+ return (
+ <defs>
+ <clipPath id="chart-clip">
+ <rect
+ height={this.state.yScale.range()[0] + 10}
+ transform="translate(0,-5)"
+ width={this.state.xScale.range()[1]}
+ />
+ </clipPath>
+ </defs>
+ );
+ };
+
+ renderMouseEventsOverlay = (zoomEnabled: boolean) => {
+ const mouseEvents: Partial<React.SVGProps<SVGRectElement>> = {};
+ if (zoomEnabled) {
+ mouseEvents.onWheel = this.handleWheel;
+ }
+ if (this.props.updateTooltip) {
+ mouseEvents.onMouseEnter = this.handleMouseEnter;
+ mouseEvents.onMouseMove = this.handleMouseMove;
+ mouseEvents.onMouseOut = this.handleMouseOut;
+ }
+ if (this.props.updateSelectedDate) {
+ mouseEvents.onClick = this.handleClick;
+ }
+ return (
+ <rect
+ className="chart-mouse-events-overlay"
+ height={this.state.yScale.range()[0]}
+ width={this.state.xScale.range()[1]}
+ {...mouseEvents}
+ />
+ );
+ };
+
+ render() {
+ if (!this.props.width || !this.props.height) {
+ return <div />;
+ }
+ const zoomEnabled = !this.props.disableZoom && this.props.updateZoom != null;
+ const isZoomed = Boolean(this.props.startDate || this.props.endDate);
+ return (
+ <svg
+ className={classNames('line-chart', { 'chart-zoomed': isZoomed })}
+ height={this.props.height}
+ width={this.props.width}>
+ {zoomEnabled && this.renderClipPath()}
+ <g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}>
+ {this.props.leakPeriodDate != null && this.renderLeak()}
+ {!this.props.hideGrid && this.renderHorizontalGrid()}
+ {!this.props.hideXAxis && this.renderXAxisTicks()}
+ {this.props.showAreas && this.renderAreas()}
+ {this.renderLines()}
+ {this.renderDots()}
+ {this.renderSelectedDate()}
+ {this.renderMouseEventsOverlay(zoomEnabled)}
+ </g>
+ </svg>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-import { flatten, sortBy, throttle } from 'lodash';
-import { extent, max } from 'd3-array';
-import { scaleLinear, scalePoint, scaleTime } from 'd3-scale';
-import { line as d3Line, area, curveBasis } from 'd3-shape';
-import Draggable, { DraggableCore } from 'react-draggable';
-/*:: import type { DraggableData } from 'react-draggable'; */
-/*:: import type { Point, Serie } from './AdvancedTimeline'; */
-import * as theme from '../../app/theme';
-import './LineChart.css';
-import './ZoomTimeLine.css';
-
-/*::
-type Scale = Function;
-*/
-
-/*::
-type Props = {
- basisCurve?: boolean,
- endDate: ?Date,
- height: number,
- width: number,
- leakPeriodDate?: Date,
- padding: Array<number>,
- series: Array<Serie>,
- showAreas?: boolean,
- showXTicks?: boolean,
- startDate: ?Date,
- updateZoom: (start: ?Date, endDate: ?Date) => void
-};
-*/
-
-/*::
-type State = {
- overlayLeftPos: ?number,
- newZoomStart: ?number
-};
-*/
-
-export default class ZoomTimeLine extends React.PureComponent {
- /*:: props: Props; */
- /*:: state: State; */
-
- static defaultProps = {
- padding: [0, 0, 18, 0],
- showXTicks: true
- };
-
- constructor(props /*: Props */) {
- super(props);
- this.state = { overlayLeftPos: null, newZoomStart: null };
- this.handleZoomUpdate = throttle(this.handleZoomUpdate, 40);
- }
-
- getRatingScale = (availableHeight /*: number */) =>
- scalePoint()
- .domain([5, 4, 3, 2, 1])
- .range([availableHeight, 0]);
-
- getLevelScale = (availableHeight /*: number */) =>
- scalePoint()
- .domain(['ERROR', 'WARN', 'OK'])
- .range([availableHeight, 0]);
-
- getYScale = (availableHeight /*: number */, flatData /*: Array<Point> */) => {
- if (this.props.metricType === 'RATING') {
- return this.getRatingScale(availableHeight);
- } else if (this.props.metricType === 'LEVEL') {
- return this.getLevelScale(availableHeight);
- } else {
- return scaleLinear()
- .range([availableHeight, 0])
- .domain([0, max(flatData, d => d.y)])
- .nice();
- }
- };
-
- getXScale = (availableWidth /*: number */, flatData /*: Array<Point> */) =>
- scaleTime()
- .domain(extent(flatData, d => d.x))
- .range([0, availableWidth])
- .clamp(true);
-
- getScales = () => {
- const availableWidth = this.props.width - this.props.padding[1] - this.props.padding[3];
- const availableHeight = this.props.height - this.props.padding[0] - this.props.padding[2];
- const flatData = flatten(this.props.series.map((serie /*: Serie */) => serie.data));
- return {
- xScale: this.getXScale(availableWidth, flatData),
- yScale: this.getYScale(availableHeight, flatData)
- };
- };
-
- getEventMarker = (size /*: number */) => {
- const half = size / 2;
- return `M${half} 0 L${size} ${half} L ${half} ${size} L0 ${half} L${half} 0 L${size} ${half}`;
- };
-
- handleDoubleClick = (xScale /*: Scale */, xDim /*: Array<number> */) => () => {
- this.handleZoomUpdate(xScale, xDim);
- };
-
- handleSelectionDrag = (
- xScale /*: Scale */,
- width /*: number */,
- xDim /*: Array<number> */,
- checkDelta /*: ?boolean */
- ) => (e /*: Event */, data /*: DraggableData */) => {
- if (!checkDelta || data.deltaX) {
- const x = Math.max(xDim[0], Math.min(data.x, xDim[1] - width));
- this.handleZoomUpdate(xScale, [x, width + x]);
- }
- };
-
- handleSelectionHandleDrag = (
- xScale /*: Scale */,
- fixedX /*: number */,
- xDim /*: Array<number> */,
- handleDirection /*: string */,
- checkDelta /*: ?boolean */
- ) => (e /*: Event */, data /*: DraggableData */) => {
- if (!checkDelta || data.deltaX) {
- const x = Math.max(xDim[0], Math.min(data.x, xDim[1]));
- this.handleZoomUpdate(xScale, handleDirection === 'right' ? [fixedX, x] : [x, fixedX]);
- }
- };
-
- handleNewZoomDragStart = (xDim /*: Array<number> */) => (
- e /*: Event */,
- data /*: DraggableData */
- ) => {
- const overlayLeftPos = data.node.getBoundingClientRect().left;
- this.setState({
- overlayLeftPos,
- newZoomStart: Math.round(Math.max(xDim[0], Math.min(data.x - overlayLeftPos, xDim[1])))
- });
- };
-
- handleNewZoomDrag = (xScale /*: Scale */, xDim /*: Array<number> */) => (
- e /*: Event */,
- data /*: DraggableData */
- ) => {
- const { newZoomStart, overlayLeftPos } = this.state;
- if (newZoomStart != null && overlayLeftPos != null && data.deltaX) {
- this.handleZoomUpdate(
- xScale,
- sortBy([newZoomStart, Math.max(xDim[0], Math.min(data.x - overlayLeftPos, xDim[1]))])
- );
- }
- };
-
- handleNewZoomDragEnd = (xScale /*: Scale */, xDim /*: Array<number> */) => (
- e /*: Event */,
- data /*: DraggableData */
- ) => {
- const { newZoomStart, overlayLeftPos } = this.state;
- if (newZoomStart != null && overlayLeftPos != null) {
- const x = Math.round(Math.max(xDim[0], Math.min(data.x - overlayLeftPos, xDim[1])));
- this.handleZoomUpdate(xScale, newZoomStart === x ? xDim : sortBy([newZoomStart, x]));
- this.setState({ newZoomStart: null, overlayLeftPos: null });
- }
- };
-
- handleZoomUpdate = (xScale /*: Scale */, xArray /*: Array<number> */) => {
- const xRange = xScale.range();
- const startDate =
- xArray[0] > xRange[0] && xArray[0] < xRange[xRange.length - 1]
- ? xScale.invert(xArray[0])
- : null;
- const endDate =
- xArray[1] > xRange[0] && xArray[1] < xRange[xRange.length - 1]
- ? xScale.invert(xArray[1])
- : null;
- if (this.props.startDate !== startDate || this.props.endDate !== endDate) {
- this.props.updateZoom(startDate, endDate);
- }
- };
-
- renderBaseLine = (xScale /*: Scale */, yScale /*: Scale */) => {
- return (
- <line
- className="line-chart-grid"
- x1={xScale.range()[0]}
- x2={xScale.range()[1]}
- y1={yScale.range()[0]}
- y2={yScale.range()[0]}
- />
- );
- };
-
- renderTicks = (xScale /*: Scale */, yScale /*: Scale */) => {
- const format = xScale.tickFormat(7);
- const ticks = xScale.ticks(7);
- const y = yScale.range()[0];
- return (
- <g>
- {ticks.slice(0, -1).map((tick, index) => {
- const nextTick = index + 1 < ticks.length ? ticks[index + 1] : xScale.domain()[1];
- const x = (xScale(tick) + xScale(nextTick)) / 2;
- return (
- <text className="chart-zoom-tick" dy="1.3em" key={index} x={x} y={y}>
- {format(tick)}
- </text>
- );
- })}
- </g>
- );
- };
-
- renderLeak = (xScale /*: Scale */, yScale /*: Scale */) => {
- if (!this.props.leakPeriodDate) {
- return null;
- }
- const yRange = yScale.range();
- return (
- <rect
- fill={theme.leakColor}
- height={yRange[0] - yRange[yRange.length - 1]}
- width={xScale.range()[1] - xScale(this.props.leakPeriodDate)}
- x={xScale(this.props.leakPeriodDate)}
- y={yRange[yRange.length - 1]}
- />
- );
- };
-
- renderLines = (xScale /*: Scale */, yScale /*: Scale */) => {
- const lineGenerator = d3Line()
- .defined(d => d.y || d.y === 0)
- .x(d => xScale(d.x))
- .y(d => yScale(d.y));
- if (this.props.basisCurve) {
- lineGenerator.curve(curveBasis);
- }
- return (
- <g>
- {this.props.series.map((serie, idx) => (
- <path
- className={classNames('line-chart-path', 'line-chart-path-' + idx)}
- d={lineGenerator(serie.data)}
- key={serie.name}
- />
- ))}
- </g>
- );
- };
-
- renderAreas = (xScale /*: Scale */, yScale /*: Scale */) => {
- const areaGenerator = area()
- .defined(d => d.y || d.y === 0)
- .x(d => xScale(d.x))
- .y1(d => yScale(d.y))
- .y0(yScale(0));
- if (this.props.basisCurve) {
- areaGenerator.curve(curveBasis);
- }
- return (
- <g>
- {this.props.series.map((serie, idx) => (
- <path
- className={classNames('line-chart-area', 'line-chart-area-' + idx)}
- d={areaGenerator(serie.data)}
- key={serie.name}
- />
- ))}
- </g>
- );
- };
-
- renderZoomHandle = (
- options /*: {
- xScale: Scale,
- xPos: number,
- fixedPos: number,
- yDim: Array<number>,
- xDim: Array<number>,
- direction: string
- } */
- ) => (
- <Draggable
- axis="x"
- bounds={{ left: options.xDim[0], right: options.xDim[1] }}
- onDrag={this.handleSelectionHandleDrag(
- options.xScale,
- options.fixedPos,
- options.xDim,
- options.direction,
- true
- )}
- onStop={this.handleSelectionHandleDrag(
- options.xScale,
- options.fixedPos,
- options.xDim,
- options.direction
- )}
- position={{ x: options.xPos, y: 0 }}>
- <rect
- className="zoom-selection-handle"
- height={options.yDim[0] - options.yDim[1] + 1}
- width={6}
- x={-3}
- y={options.yDim[1]}
- />
- </Draggable>
- );
-
- renderZoom = (xScale /*: Scale */, yScale /*: Scale */) => {
- const xRange = xScale.range();
- const yRange = yScale.range();
- const xDim = [xRange[0], xRange[xRange.length - 1]];
- const yDim = [yRange[0], yRange[yRange.length - 1]];
- const startX = Math.round(this.props.startDate ? xScale(this.props.startDate) : xDim[0]);
- const endX = Math.round(this.props.endDate ? xScale(this.props.endDate) : xDim[1]);
- const xArray = sortBy([startX, endX]);
- const zoomBoxWidth = xArray[1] - xArray[0];
- const showZoomArea =
- this.state.newZoomStart == null ||
- this.state.newZoomStart === startX ||
- this.state.newZoomStart === endX;
-
- return (
- <g className="chart-zoom">
- <DraggableCore
- onDrag={this.handleNewZoomDrag(xScale, xDim)}
- onStart={this.handleNewZoomDragStart(xDim)}
- onStop={this.handleNewZoomDragEnd(xScale, xDim)}>
- <rect
- className="zoom-overlay"
- height={yDim[0] - yDim[1]}
- width={xDim[1] - xDim[0]}
- x={xDim[0]}
- y={yDim[1]}
- />
- </DraggableCore>
- {showZoomArea && (
- <Draggable
- axis="x"
- bounds={{ left: xDim[0], right: Math.floor(xDim[1] - zoomBoxWidth) }}
- onDrag={this.handleSelectionDrag(xScale, zoomBoxWidth, xDim, true)}
- onStop={this.handleSelectionDrag(xScale, zoomBoxWidth, xDim)}
- position={{ x: xArray[0], y: 0 }}>
- <rect
- className="zoom-selection"
- height={yDim[0] - yDim[1] + 1}
- onDoubleClick={this.handleDoubleClick(xScale, xDim)}
- width={zoomBoxWidth}
- x={0}
- y={yDim[1]}
- />
- </Draggable>
- )}
- {showZoomArea &&
- this.renderZoomHandle({
- xScale,
- xPos: startX,
- fixedPos: endX,
- xDim,
- yDim,
- direction: 'left'
- })}
- {showZoomArea &&
- this.renderZoomHandle({
- xScale,
- xPos: endX,
- fixedPos: startX,
- xDim,
- yDim,
- direction: 'right'
- })}
- </g>
- );
- };
-
- render() {
- if (!this.props.width || !this.props.height) {
- return <div />;
- }
-
- const { xScale, yScale } = this.getScales();
-
- return (
- <svg className="line-chart " height={this.props.height} width={this.props.width}>
- <g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0] + 2})`}>
- {this.renderLeak(xScale, yScale)}
- {this.renderBaseLine(xScale, yScale)}
- {this.props.showXTicks && this.renderTicks(xScale, yScale)}
- {this.props.showAreas && this.renderAreas(xScale, yScale)}
- {this.renderLines(xScale, yScale)}
- {this.renderZoom(xScale, yScale)}
- </g>
- </svg>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { flatten, sortBy, throttle } from 'lodash';
+import { extent, max } from 'd3-array';
+import { scaleLinear, scalePoint, scaleTime, ScaleTime } from 'd3-scale';
+import { line as d3Line, area, curveBasis } from 'd3-shape';
+import Draggable, { DraggableCore, DraggableBounds, DraggableData } from 'react-draggable';
+import * as theme from '../../app/theme';
+import { Serie, Point } from '../../apps/projectActivity/utils';
+import './LineChart.css';
+import './ZoomTimeLine.css';
+
+export interface Props {
+ basisCurve?: boolean;
+ endDate?: Date;
+ height: number;
+ leakPeriodDate?: Date;
+ metricType: string;
+ padding: number[];
+ series: Serie[];
+ showAreas?: boolean;
+ showXTicks: boolean;
+ startDate?: Date;
+ updateZoom: (start?: Date, endDate?: Date) => void;
+ width: number;
+}
+
+interface State {
+ overlayLeftPos?: number;
+ newZoomStart?: number;
+}
+
+type XScale = ScaleTime<number, number>;
+// TODO it should be `ScaleLinear<number, number> | ScalePoint<number> | ScalePoint<string>`, but it's super hard to make it work :'(
+type YScale = any;
+
+export default class ZoomTimeLine extends React.PureComponent<Props, State> {
+ static defaultProps = {
+ padding: [0, 0, 18, 0],
+ showXTicks: true
+ };
+
+ constructor(props: Props) {
+ super(props);
+ this.state = {};
+ this.handleZoomUpdate = throttle(this.handleZoomUpdate, 40);
+ }
+
+ getRatingScale = (availableHeight: number) => {
+ return scalePoint<number>()
+ .domain([5, 4, 3, 2, 1])
+ .range([availableHeight, 0]);
+ };
+
+ getLevelScale = (availableHeight: number) => {
+ return scalePoint()
+ .domain(['ERROR', 'WARN', 'OK'])
+ .range([availableHeight, 0]);
+ };
+
+ getYScale = (availableHeight: number, flatData: Point[]): YScale => {
+ if (this.props.metricType === 'RATING') {
+ return this.getRatingScale(availableHeight);
+ } else if (this.props.metricType === 'LEVEL') {
+ return this.getLevelScale(availableHeight);
+ } else {
+ return scaleLinear()
+ .range([availableHeight, 0])
+ .domain([0, max(flatData, d => Number(d.y || 0)) as number])
+ .nice();
+ }
+ };
+
+ getXScale = (availableWidth: number, flatData: Point[]): XScale => {
+ return scaleTime()
+ .domain(extent(flatData, d => d.x) as [Date, Date])
+ .range([0, availableWidth])
+ .clamp(true);
+ };
+
+ getScales = () => {
+ const availableWidth = this.props.width - this.props.padding[1] - this.props.padding[3];
+ const availableHeight = this.props.height - this.props.padding[0] - this.props.padding[2];
+ const flatData = flatten(this.props.series.map(serie => serie.data));
+ return {
+ xScale: this.getXScale(availableWidth, flatData),
+ yScale: this.getYScale(availableHeight, flatData)
+ };
+ };
+
+ getEventMarker = (size: number) => {
+ const half = size / 2;
+ return `M${half} 0 L${size} ${half} L ${half} ${size} L0 ${half} L${half} 0 L${size} ${half}`;
+ };
+
+ handleDoubleClick = (xScale: XScale, xDim: number[]) => () => {
+ this.handleZoomUpdate(xScale, xDim);
+ };
+
+ handleSelectionDrag = (xScale: XScale, width: number, xDim: number[], checkDelta?: boolean) => (
+ _: MouseEvent,
+ data: DraggableData
+ ) => {
+ if (!checkDelta || data.deltaX) {
+ const x = Math.max(xDim[0], Math.min(data.x, xDim[1] - width));
+ this.handleZoomUpdate(xScale, [x, width + x]);
+ }
+ };
+
+ handleSelectionHandleDrag = (
+ xScale: XScale,
+ fixedX: number,
+ xDim: number[],
+ handleDirection: string,
+ checkDelta?: boolean
+ ) => (_: MouseEvent, data: DraggableData) => {
+ if (!checkDelta || data.deltaX) {
+ const x = Math.max(xDim[0], Math.min(data.x, xDim[1]));
+ this.handleZoomUpdate(xScale, handleDirection === 'right' ? [fixedX, x] : [x, fixedX]);
+ }
+ };
+
+ handleNewZoomDragStart = (xDim: number[]) => (_: MouseEvent, data: DraggableData) => {
+ const overlayLeftPos = data.node.getBoundingClientRect().left;
+ this.setState({
+ overlayLeftPos,
+ newZoomStart: Math.round(Math.max(xDim[0], Math.min(data.x - overlayLeftPos, xDim[1])))
+ });
+ };
+
+ handleNewZoomDrag = (xScale: XScale, xDim: number[]) => (_: MouseEvent, data: DraggableData) => {
+ const { newZoomStart, overlayLeftPos } = this.state;
+ if (newZoomStart != null && overlayLeftPos != null && data.deltaX) {
+ this.handleZoomUpdate(
+ xScale,
+ sortBy([newZoomStart, Math.max(xDim[0], Math.min(data.x - overlayLeftPos, xDim[1]))])
+ );
+ }
+ };
+
+ handleNewZoomDragEnd = (xScale: XScale, xDim: number[]) => (
+ _: MouseEvent,
+ data: DraggableData
+ ) => {
+ const { newZoomStart, overlayLeftPos } = this.state;
+ if (newZoomStart !== undefined && overlayLeftPos !== undefined) {
+ const x = Math.round(Math.max(xDim[0], Math.min(data.x - overlayLeftPos, xDim[1])));
+ this.handleZoomUpdate(xScale, newZoomStart === x ? xDim : sortBy([newZoomStart, x]));
+ this.setState({ newZoomStart: undefined, overlayLeftPos: undefined });
+ }
+ };
+
+ handleZoomUpdate = (xScale: XScale, xArray: number[]) => {
+ const xRange = xScale.range();
+ const startDate =
+ xArray[0] > xRange[0] && xArray[0] < xRange[xRange.length - 1]
+ ? xScale.invert(xArray[0])
+ : undefined;
+ const endDate =
+ xArray[1] > xRange[0] && xArray[1] < xRange[xRange.length - 1]
+ ? xScale.invert(xArray[1])
+ : undefined;
+ if (this.props.startDate !== startDate || this.props.endDate !== endDate) {
+ this.props.updateZoom(startDate, endDate);
+ }
+ };
+
+ renderBaseLine = (xScale: XScale, yScale: YScale) => {
+ return (
+ <line
+ className="line-chart-grid"
+ x1={xScale.range()[0]}
+ x2={xScale.range()[1]}
+ y1={yScale.range()[0]}
+ y2={yScale.range()[0]}
+ />
+ );
+ };
+
+ renderTicks = (xScale: XScale, yScale: YScale) => {
+ const format = xScale.tickFormat(7);
+ const ticks = xScale.ticks(7);
+ const y = yScale.range()[0];
+ return (
+ <g>
+ {ticks.slice(0, -1).map((tick, index) => {
+ const nextTick = index + 1 < ticks.length ? ticks[index + 1] : xScale.domain()[1];
+ const x = (xScale(tick) + xScale(nextTick)) / 2;
+ return (
+ <text className="chart-zoom-tick" dy="1.3em" key={index} x={x} y={y}>
+ {format(tick)}
+ </text>
+ );
+ })}
+ </g>
+ );
+ };
+
+ renderLeak = (xScale: XScale, yScale: YScale) => {
+ if (!this.props.leakPeriodDate) {
+ return null;
+ }
+ const yRange = yScale.range();
+ return (
+ <rect
+ fill={theme.leakColor}
+ height={yRange[0] - yRange[yRange.length - 1]}
+ width={xScale.range()[1] - xScale(this.props.leakPeriodDate)}
+ x={xScale(this.props.leakPeriodDate)}
+ y={yRange[yRange.length - 1]}
+ />
+ );
+ };
+
+ renderLines = (xScale: XScale, yScale: YScale) => {
+ const lineGenerator = d3Line<Point>()
+ .defined(d => Boolean(d.y || d.y === 0))
+ .x(d => xScale(d.x))
+ .y(d => yScale(d.y));
+ if (this.props.basisCurve) {
+ lineGenerator.curve(curveBasis);
+ }
+ return (
+ <g>
+ {this.props.series.map((serie, idx) => (
+ <path
+ className={classNames('line-chart-path', 'line-chart-path-' + idx)}
+ d={lineGenerator(serie.data) || undefined}
+ key={serie.name}
+ />
+ ))}
+ </g>
+ );
+ };
+
+ renderAreas = (xScale: XScale, yScale: YScale) => {
+ const areaGenerator = area<Point>()
+ .defined(d => Boolean(d.y || d.y === 0))
+ .x(d => xScale(d.x))
+ .y1(d => yScale(d.y))
+ .y0(yScale(0));
+ if (this.props.basisCurve) {
+ areaGenerator.curve(curveBasis);
+ }
+ return (
+ <g>
+ {this.props.series.map((serie, idx) => (
+ <path
+ className={classNames('line-chart-area', 'line-chart-area-' + idx)}
+ d={areaGenerator(serie.data) || undefined}
+ key={serie.name}
+ />
+ ))}
+ </g>
+ );
+ };
+
+ renderZoomHandle = (options: {
+ xScale: XScale;
+ xPos: number;
+ fixedPos: number;
+ yDim: number[];
+ xDim: number[];
+ direction: string;
+ }) => (
+ <Draggable
+ axis="x"
+ bounds={{ left: options.xDim[0], right: options.xDim[1] } as DraggableBounds}
+ onDrag={this.handleSelectionHandleDrag(
+ options.xScale,
+ options.fixedPos,
+ options.xDim,
+ options.direction,
+ true
+ )}
+ onStop={this.handleSelectionHandleDrag(
+ options.xScale,
+ options.fixedPos,
+ options.xDim,
+ options.direction
+ )}
+ position={{ x: options.xPos, y: 0 }}>
+ <rect
+ className="zoom-selection-handle"
+ height={options.yDim[0] - options.yDim[1] + 1}
+ width={6}
+ x={-3}
+ y={options.yDim[1]}
+ />
+ </Draggable>
+ );
+
+ renderZoom = (xScale: XScale, yScale: YScale) => {
+ const xRange = xScale.range();
+ const yRange = yScale.range();
+ const xDim = [xRange[0], xRange[xRange.length - 1]];
+ const yDim = [yRange[0], yRange[yRange.length - 1]];
+ const startX = Math.round(this.props.startDate ? xScale(this.props.startDate) : xDim[0]);
+ const endX = Math.round(this.props.endDate ? xScale(this.props.endDate) : xDim[1]);
+ const xArray = sortBy([startX, endX]);
+ const zoomBoxWidth = xArray[1] - xArray[0];
+ const showZoomArea =
+ this.state.newZoomStart == null ||
+ this.state.newZoomStart === startX ||
+ this.state.newZoomStart === endX;
+
+ return (
+ <g className="chart-zoom">
+ <DraggableCore
+ onDrag={this.handleNewZoomDrag(xScale, xDim)}
+ onStart={this.handleNewZoomDragStart(xDim)}
+ onStop={this.handleNewZoomDragEnd(xScale, xDim)}>
+ <rect
+ className="zoom-overlay"
+ height={yDim[0] - yDim[1]}
+ width={xDim[1] - xDim[0]}
+ x={xDim[0]}
+ y={yDim[1]}
+ />
+ </DraggableCore>
+ {showZoomArea && (
+ <Draggable
+ axis="x"
+ bounds={{ left: xDim[0], right: Math.floor(xDim[1] - zoomBoxWidth) } as DraggableBounds}
+ onDrag={this.handleSelectionDrag(xScale, zoomBoxWidth, xDim, true)}
+ onStop={this.handleSelectionDrag(xScale, zoomBoxWidth, xDim)}
+ position={{ x: xArray[0], y: 0 }}>
+ <rect
+ className="zoom-selection"
+ height={yDim[0] - yDim[1] + 1}
+ onDoubleClick={this.handleDoubleClick(xScale, xDim)}
+ width={zoomBoxWidth}
+ x={0}
+ y={yDim[1]}
+ />
+ </Draggable>
+ )}
+ {showZoomArea &&
+ this.renderZoomHandle({
+ xScale,
+ xPos: startX,
+ fixedPos: endX,
+ xDim,
+ yDim,
+ direction: 'left'
+ })}
+ {showZoomArea &&
+ this.renderZoomHandle({
+ xScale,
+ xPos: endX,
+ fixedPos: startX,
+ xDim,
+ yDim,
+ direction: 'right'
+ })}
+ </g>
+ );
+ };
+
+ render() {
+ if (!this.props.width || !this.props.height) {
+ return <div />;
+ }
+
+ const { xScale, yScale } = this.getScales();
+
+ return (
+ <svg className="line-chart " height={this.props.height} width={this.props.width}>
+ <g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0] + 2})`}>
+ {this.renderLeak(xScale, yScale)}
+ {this.renderBaseLine(xScale, yScale)}
+ {this.props.showXTicks && this.renderTicks(xScale, yScale)}
+ {this.props.showAreas && this.renderAreas(xScale, yScale)}
+ {this.renderLines(xScale, yScale)}
+ {this.renderZoom(xScale, yScale)}
+ </g>
+ </svg>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import * as React from 'react';
-import { History } from '../../api/time-machine';
-import { Metric, BranchLike } from '../../app/types';
-
-interface Props {
- branchLike?: BranchLike;
- history?: History;
- metrics: { [key: string]: Metric };
- project: string;
- renderWhenEmpty?: () => void;
-}
-
-export default class PreviewGraph extends React.Component<Props> {}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import { minBy } from 'lodash';
-import * as PropTypes from 'prop-types';
-// $FlowFixMe
-import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
-import PreviewGraphTooltips from './PreviewGraphTooltips';
-import AdvancedTimeline from '../charts/AdvancedTimeline';
-import {
- DEFAULT_GRAPH,
- getDisplayedHistoryMetrics,
- generateSeries,
- getSeriesMetricType,
- hasHistoryDataValue,
- PROJECT_ACTIVITY_GRAPH,
- PROJECT_ACTIVITY_GRAPH_CUSTOM,
- splitSeriesInGraphs
-} from '../../apps/projectActivity/utils';
-import { get } from '../../helpers/storage';
-import { formatMeasure, getShortType } from '../../helpers/measures';
-import { getBranchLikeQuery } from '../../helpers/branches';
-/*:: import type { Serie } from '../charts/AdvancedTimeline'; */
-
-/*::
-type History = { [string]: Array<{ date: Date, value: string }> };
-*/
-
-/*::
-type Metric = {
- custom?: boolean,
- hidden?: boolean,
- key: string,
- name: string,
- type: string
-};
-*/
-
-/*::
-type Props = {
- branchLike?: { id?: string; name: string },
- history: ?History,
- metrics: { [string]: Metric },
- project: string,
- renderWhenEmpty?: () => void
-};
-*/
-
-/*::
-type State = {
- customMetrics: Array<string>,
- graph: string,
- selectedDate: ?Date,
- series: Array<Serie>,
- tooltipIdx: ?number,
- tooltipXPos: ?number
-};
-*/
-
-const GRAPH_PADDING = [4, 0, 4, 0];
-const MAX_GRAPH_NB = 1;
-const MAX_SERIES_PER_GRAPH = 3;
-
-export default class PreviewGraph extends React.PureComponent {
- /*:: props: Props; */
- /*:: state: State; */
-
- static contextTypes = {
- router: PropTypes.object
- };
-
- constructor(props /*: Props */) {
- super(props);
- const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM);
- const graph = get(PROJECT_ACTIVITY_GRAPH) || 'issues';
- const customMetrics = customGraphs ? customGraphs.split(',') : [];
- const series = splitSeriesInGraphs(
- this.getSeries(props.history, graph, customMetrics, props.metrics),
- MAX_GRAPH_NB,
- MAX_SERIES_PER_GRAPH
- );
- this.state = {
- customMetrics,
- graph,
- selectedDate: null,
- series: series.length > 0 ? series[0] : [],
- tooltipIdx: null,
- tooltipXPos: null
- };
- }
-
- componentWillReceiveProps(nextProps /*: Props */) {
- if (nextProps.history !== this.props.history || nextProps.metrics !== this.props.metrics) {
- const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM);
- const graph = get(PROJECT_ACTIVITY_GRAPH) || 'issues';
- const customMetrics = customGraphs ? customGraphs.split(',') : [];
- const series = splitSeriesInGraphs(
- this.getSeries(nextProps.history, graph, customMetrics, nextProps.metrics),
- MAX_GRAPH_NB,
- MAX_SERIES_PER_GRAPH
- );
- this.setState({
- customMetrics,
- graph,
- series: series.length > 0 ? series[0] : []
- });
- }
- }
-
- formatValue = (tick /*: number | string */) =>
- formatMeasure(tick, getShortType(getSeriesMetricType(this.state.series)));
-
- getDisplayedMetrics = (graph /*: string */, customMetrics /*: Array<string> */) => {
- const metrics /*: Array<string> */ = getDisplayedHistoryMetrics(graph, customMetrics);
- if (!metrics || metrics.length <= 0) {
- return getDisplayedHistoryMetrics(DEFAULT_GRAPH, customMetrics);
- }
- return metrics;
- };
-
- getSeries = (
- history /*: ?History */,
- graph /*: string */,
- customMetrics /*: Array<string> */,
- metrics /*: { [string]: Metric } */
- ) => {
- const myHistory = history;
- if (!myHistory) {
- return [];
- }
- const displayedMetrics = this.getDisplayedMetrics(graph, customMetrics);
- const firstValid = minBy(
- displayedMetrics.map(metric => myHistory[metric].find(p => p.value || p.value === 0)),
- 'date'
- );
- const measureHistory = displayedMetrics.map(metric => ({
- metric,
- history: firstValid
- ? myHistory[metric].filter(p => p.date >= firstValid.date)
- : myHistory[metric]
- }));
- return generateSeries(measureHistory, graph, metrics, displayedMetrics);
- };
-
- handleClick = () => {
- this.context.router.push({
- pathname: '/project/activity',
- query: { id: this.props.project, ...getBranchLikeQuery(this.props.branchLike) }
- });
- };
-
- updateTooltip = (
- selectedDate /*: ?Date */,
- tooltipXPos /*: ?number */,
- tooltipIdx /*: ?number */
- ) => this.setState({ selectedDate, tooltipXPos, tooltipIdx });
-
- renderTimeline() {
- const { graph, selectedDate, series, tooltipIdx, tooltipXPos } = this.state;
- return (
- <AutoSizer disableHeight={true}>
- {({ width }) => (
- <div>
- <AdvancedTimeline
- endDate={null}
- height={80}
- hideGrid={true}
- hideXAxis={true}
- interpolate="linear"
- metricType={getSeriesMetricType(series)}
- padding={GRAPH_PADDING}
- series={series}
- showAreas={['coverage', 'duplications'].includes(graph)}
- startDate={null}
- updateTooltip={this.updateTooltip}
- width={width}
- />
- {selectedDate != null &&
- tooltipXPos != null &&
- tooltipIdx != null && (
- <PreviewGraphTooltips
- formatValue={this.formatValue}
- graph={graph}
- graphWidth={width}
- metrics={this.props.metrics}
- selectedDate={selectedDate}
- series={series}
- tooltipIdx={tooltipIdx}
- tooltipPos={tooltipXPos}
- />
- )}
- </div>
- )}
- </AutoSizer>
- );
- }
-
- render() {
- const { series } = this.state;
- if (!hasHistoryDataValue(series)) {
- return this.props.renderWhenEmpty ? this.props.renderWhenEmpty() : null;
- }
-
- return (
- <div
- className="overview-analysis-graph big-spacer-bottom spacer-top"
- onClick={this.handleClick}
- role="link"
- tabIndex={0}>
- {this.renderTimeline()}
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { minBy } from 'lodash';
+import * as PropTypes from 'prop-types';
+import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer';
+import PreviewGraphTooltips from './PreviewGraphTooltips';
+import AdvancedTimeline from '../charts/AdvancedTimeline';
+import {
+ DEFAULT_GRAPH,
+ getDisplayedHistoryMetrics,
+ generateSeries,
+ getSeriesMetricType,
+ hasHistoryDataValue,
+ PROJECT_ACTIVITY_GRAPH,
+ PROJECT_ACTIVITY_GRAPH_CUSTOM,
+ splitSeriesInGraphs,
+ Serie
+} from '../../apps/projectActivity/utils';
+import { get } from '../../helpers/storage';
+import { formatMeasure, getShortType } from '../../helpers/measures';
+import { getBranchLikeQuery } from '../../helpers/branches';
+import { BranchLike, Metric } from '../../app/types';
+
+interface History {
+ [x: string]: Array<{ date: Date; value?: string }>;
+}
+
+interface Props {
+ branchLike?: BranchLike;
+ history?: History;
+ metrics: { [key: string]: Metric };
+ project: string;
+ renderWhenEmpty?: () => React.ReactNode;
+}
+
+interface State {
+ customMetrics: string[];
+ graph: string;
+ selectedDate?: Date;
+ series: Serie[];
+ tooltipIdx?: number;
+ tooltipXPos?: number;
+}
+
+const GRAPH_PADDING = [4, 0, 4, 0];
+const MAX_GRAPH_NB = 1;
+const MAX_SERIES_PER_GRAPH = 3;
+
+export default class PreviewGraph extends React.PureComponent<Props, State> {
+ static contextTypes = {
+ router: PropTypes.object
+ };
+
+ constructor(props: Props) {
+ super(props);
+ const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM);
+ const graph = get(PROJECT_ACTIVITY_GRAPH) || 'issues';
+ const customMetrics = customGraphs ? customGraphs.split(',') : [];
+ const series = splitSeriesInGraphs(
+ this.getSeries(props.history, graph, customMetrics, props.metrics),
+ MAX_GRAPH_NB,
+ MAX_SERIES_PER_GRAPH
+ );
+ this.state = {
+ customMetrics,
+ graph,
+ series: series.length > 0 ? series[0] : []
+ };
+ }
+
+ componentWillReceiveProps(nextProps: Props) {
+ if (nextProps.history !== this.props.history || nextProps.metrics !== this.props.metrics) {
+ const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM);
+ const graph = get(PROJECT_ACTIVITY_GRAPH) || 'issues';
+ const customMetrics = customGraphs ? customGraphs.split(',') : [];
+ const series = splitSeriesInGraphs(
+ this.getSeries(nextProps.history, graph, customMetrics, nextProps.metrics),
+ MAX_GRAPH_NB,
+ MAX_SERIES_PER_GRAPH
+ );
+ this.setState({
+ customMetrics,
+ graph,
+ series: series.length > 0 ? series[0] : []
+ });
+ }
+ }
+
+ formatValue = (tick: number | string) => {
+ return formatMeasure(tick, getShortType(getSeriesMetricType(this.state.series)));
+ };
+
+ getDisplayedMetrics = (graph: string, customMetrics: string[]) => {
+ const metrics = getDisplayedHistoryMetrics(graph, customMetrics);
+ if (!metrics || metrics.length <= 0) {
+ return getDisplayedHistoryMetrics(DEFAULT_GRAPH, customMetrics);
+ }
+ return metrics;
+ };
+
+ getSeries = (
+ history: History | undefined,
+ graph: string,
+ customMetrics: string[],
+ metrics: { [x: string]: Metric }
+ ) => {
+ const myHistory = history;
+ if (!myHistory) {
+ return [];
+ }
+ const displayedMetrics = this.getDisplayedMetrics(graph, customMetrics);
+ const firstValid = minBy(
+ displayedMetrics.map(metric => myHistory[metric].find(p => p.value !== undefined)),
+ 'date'
+ );
+ const measureHistory = displayedMetrics.map(metric => ({
+ metric,
+ history: firstValid
+ ? myHistory[metric].filter(p => p.date >= firstValid.date)
+ : myHistory[metric]
+ }));
+ return generateSeries(measureHistory, graph, metrics, displayedMetrics);
+ };
+
+ handleClick = () => {
+ this.context.router.push({
+ pathname: '/project/activity',
+ query: { id: this.props.project, ...getBranchLikeQuery(this.props.branchLike) }
+ });
+ };
+
+ updateTooltip = (selectedDate?: Date, tooltipXPos?: number, tooltipIdx?: number) =>
+ this.setState({ selectedDate, tooltipXPos, tooltipIdx });
+
+ renderTimeline() {
+ const { graph, selectedDate, series, tooltipIdx, tooltipXPos } = this.state;
+ return (
+ <AutoSizer disableHeight={true}>
+ {({ width }) => (
+ <div>
+ <AdvancedTimeline
+ height={80}
+ hideGrid={true}
+ hideXAxis={true}
+ metricType={getSeriesMetricType(series)}
+ padding={GRAPH_PADDING}
+ series={series}
+ showAreas={['coverage', 'duplications'].includes(graph)}
+ updateTooltip={this.updateTooltip}
+ width={width}
+ />
+ {selectedDate !== undefined &&
+ tooltipXPos !== undefined &&
+ tooltipIdx !== undefined && (
+ <PreviewGraphTooltips
+ formatValue={this.formatValue}
+ graph={graph}
+ graphWidth={width}
+ selectedDate={selectedDate}
+ series={series}
+ tooltipIdx={tooltipIdx}
+ tooltipPos={tooltipXPos}
+ />
+ )}
+ </div>
+ )}
+ </AutoSizer>
+ );
+ }
+
+ render() {
+ const { series } = this.state;
+ if (!hasHistoryDataValue(series)) {
+ return this.props.renderWhenEmpty ? this.props.renderWhenEmpty() : null;
+ }
+
+ return (
+ <div
+ className="overview-analysis-graph big-spacer-bottom spacer-top"
+ onClick={this.handleClick}
+ role="link"
+ tabIndex={0}>
+ {this.renderTimeline()}
+ </div>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import PreviewGraphTooltipsContent from './PreviewGraphTooltipsContent';
-import DateFormatter from '../intl/DateFormatter';
-import { Popup, PopupPlacement } from '../ui/popups';
-/*:: import type { Metric } from '../types'; */
-/*:: import type { Serie } from '../charts/AdvancedTimeline'; */
-
-/*::
-type Props = {
- formatValue: (number | string) => string,
- graph: string,
- graphWidth: number,
- metrics: Array<Metric>,
- selectedDate: Date,
- series: Array<Serie & { translatedName: string }>,
- tooltipIdx: number,
- tooltipPos: number
-};
-*/
-
-const TOOLTIP_WIDTH = 160;
-
-export default class PreviewGraphTooltips extends React.PureComponent {
- /*:: props: Props; */
-
- render() {
- const { tooltipIdx } = this.props;
- const top = 16;
- let left = this.props.tooltipPos;
- let placement = PopupPlacement.RightTop;
- if (left > this.props.graphWidth - TOOLTIP_WIDTH) {
- left -= TOOLTIP_WIDTH;
- placement = PopupPlacement.LeftTop;
- }
-
- return (
- <Popup
- className="overview-analysis-graph-popup disabled-pointer-events"
- placement={placement}
- style={{ top, left, width: TOOLTIP_WIDTH }}>
- <div className="overview-analysis-graph-tooltip">
- <div className="overview-analysis-graph-tooltip-title">
- <DateFormatter date={this.props.selectedDate} long={true} />
- </div>
- <table className="width-100">
- <tbody>
- {this.props.series.map((serie, idx) => {
- const point = serie.data[tooltipIdx];
- if (!point || (!point.y && point.y !== 0)) {
- return null;
- }
- return (
- <PreviewGraphTooltipsContent
- key={serie.name}
- style={idx.toString()}
- translatedName={serie.translatedName}
- value={this.props.formatValue(point.y)}
- />
- );
- })}
- </tbody>
- </table>
- </div>
- </Popup>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import PreviewGraphTooltipsContent from './PreviewGraphTooltipsContent';
+import DateFormatter from '../intl/DateFormatter';
+import { Popup, PopupPlacement } from '../ui/popups';
+import { Serie } from '../../apps/projectActivity/utils';
+
+interface Props {
+ formatValue: (value: number | string) => string;
+ graph: string;
+ graphWidth: number;
+ selectedDate: Date;
+ series: Serie[];
+ tooltipIdx: number;
+ tooltipPos: number;
+}
+
+const TOOLTIP_WIDTH = 160;
+
+export default class PreviewGraphTooltips extends React.PureComponent<Props> {
+ render() {
+ const { tooltipIdx } = this.props;
+ const top = 16;
+ let left = this.props.tooltipPos;
+ let placement = PopupPlacement.RightTop;
+ if (left > this.props.graphWidth - TOOLTIP_WIDTH) {
+ left -= TOOLTIP_WIDTH;
+ placement = PopupPlacement.LeftTop;
+ }
+
+ return (
+ <Popup
+ className="overview-analysis-graph-popup disabled-pointer-events"
+ placement={placement}
+ style={{ top, left, width: TOOLTIP_WIDTH }}>
+ <div className="overview-analysis-graph-tooltip">
+ <div className="overview-analysis-graph-tooltip-title">
+ <DateFormatter date={this.props.selectedDate} long={true} />
+ </div>
+ <table className="width-100">
+ <tbody>
+ {this.props.series.map((serie, idx) => {
+ const point = serie.data[tooltipIdx];
+ if (!point || (!point.y && point.y !== 0)) {
+ return null;
+ }
+ return (
+ <PreviewGraphTooltipsContent
+ key={serie.name}
+ style={idx.toString()}
+ translatedName={serie.translatedName}
+ value={this.props.formatValue(point.y)}
+ />
+ );
+ })}
+ </tbody>
+ </table>
+ </div>
+ </Popup>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import React from 'react';
-import ChartLegendIcon from '../icons-components/ChartLegendIcon';
-
-/*::
-type Props = {
- style: string,
- translatedName: string,
- value: string
-};
-*/
-
-export default function PreviewGraphTooltipsContent({ style, translatedName, value } /*: Props */) {
- return (
- <tr className="overview-analysis-graph-tooltip-line">
- <td className="thin">
- <ChartLegendIcon
- className={'little-spacer-right line-chart-legend line-chart-legend-' + style}
- />
- </td>
- <td className="overview-analysis-graph-tooltip-value text-right little-spacer-right thin">
- {value}
- </td>
- <td className="text-ellipsis overview-analysis-graph-tooltip-description">
- {translatedName}
- </td>
- </tr>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import ChartLegendIcon from '../icons-components/ChartLegendIcon';
+
+interface Props {
+ style: string;
+ translatedName: string;
+ value: string;
+}
+
+export default function PreviewGraphTooltipsContent({ style, translatedName, value }: Props) {
+ return (
+ <tr className="overview-analysis-graph-tooltip-line">
+ <td className="thin">
+ <ChartLegendIcon
+ className={'little-spacer-right line-chart-legend line-chart-legend-' + style}
+ />
+ </td>
+ <td className="overview-analysis-graph-tooltip-value text-right little-spacer-right thin">
+ {value}
+ </td>
+ <td className="text-ellipsis overview-analysis-graph-tooltip-description">
+ {translatedName}
+ </td>
+ </tr>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import PreviewGraphTooltips from '../PreviewGraphTooltips';
-import { DEFAULT_GRAPH } from '../../../apps/projectActivity/utils';
-import { parseDate } from '../../../helpers/dates';
-
-const SERIES_ISSUES = [
- {
- name: 'code_smells',
- data: [
- {
- x: '2011-10-01T22:01:00.000Z',
- y: 18
- },
- {
- x: '2011-10-25T10:27:41.000Z',
- y: 15
- }
- ],
- translatedName: 'Code Smells'
- },
- {
- name: 'bugs',
- data: [
- {
- x: '2011-10-01T22:01:00.000Z',
- y: 3
- },
- {
- x: '2011-10-25T10:27:41.000Z',
- y: 0
- }
- ],
- translatedName: 'Bugs'
- },
- {
- name: 'vulnerabilities',
- data: [
- {
- x: '2011-10-01T22:01:00.000Z',
- y: 0
- },
- {
- x: '2011-10-25T10:27:41.000Z',
- y: 1
- }
- ],
- translatedName: 'Vulnerabilities'
- }
-];
-
-const METRICS = [
- { key: 'code_smells', name: 'Code Smells', type: 'INT' },
- { key: 'bugs', name: 'Bugs', type: 'INT' },
- { key: 'vulnerabilities', name: 'Vulnerabilities', type: 'INT', custom: true }
-];
-
-const DEFAULT_PROPS = {
- formatValue: val => 'Formated.' + val,
- graph: DEFAULT_GRAPH,
- graphWidth: 150,
- metrics: METRICS,
- selectedDate: parseDate('2011-10-01T22:01:00.000Z'),
- series: SERIES_ISSUES,
- tooltipIdx: 0,
- tooltipPos: 25
-};
-
-it('should render correctly', () => {
- expect(
- shallow(
- <PreviewGraphTooltips
- {...DEFAULT_PROPS}
- graph="random"
- selectedDate={parseDate('2011-10-25T10:27:41.000Z')}
- tooltipIdx={1}
- />
- )
- ).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import PreviewGraphTooltips from '../PreviewGraphTooltips';
+import { DEFAULT_GRAPH } from '../../../apps/projectActivity/utils';
+import { parseDate } from '../../../helpers/dates';
+
+const SERIES_ISSUES = [
+ {
+ name: 'code_smells',
+ data: [
+ { x: parseDate('2011-10-01T22:01:00.000Z'), y: 18 },
+ { x: parseDate('2011-10-25T10:27:41.000Z'), y: 15 }
+ ],
+ translatedName: 'Code Smells',
+ type: 'INT'
+ },
+ {
+ name: 'bugs',
+ data: [
+ { x: parseDate('2011-10-01T22:01:00.000Z'), y: 3 },
+ { x: parseDate('2011-10-25T10:27:41.000Z'), y: 0 }
+ ],
+ translatedName: 'Bugs',
+ type: 'INT'
+ },
+ {
+ name: 'vulnerabilities',
+ data: [
+ { x: parseDate('2011-10-01T22:01:00.000Z'), y: 0 },
+ { x: parseDate('2011-10-25T10:27:41.000Z'), y: 1 }
+ ],
+ translatedName: 'Vulnerabilities',
+ type: 'INT'
+ }
+];
+
+const DEFAULT_PROPS: PreviewGraphTooltips['props'] = {
+ formatValue: (val: string) => 'Formated.' + val,
+ graph: DEFAULT_GRAPH,
+ graphWidth: 150,
+ selectedDate: parseDate('2011-10-01T22:01:00.000Z'),
+ series: SERIES_ISSUES,
+ tooltipIdx: 0,
+ tooltipPos: 25
+};
+
+it('should render correctly', () => {
+ expect(
+ shallow(
+ <PreviewGraphTooltips
+ {...DEFAULT_PROPS}
+ graph="random"
+ selectedDate={parseDate('2011-10-25T10:27:41.000Z')}
+ tooltipIdx={1}
+ />
+ )
+ ).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 React from 'react';
-import { shallow } from 'enzyme';
-import PreviewGraphTooltipsContent from '../PreviewGraphTooltipsContent';
-
-const DEFAULT_PROPS = {
- style: 1,
- translatedName: 'Code Smells',
- value: '1.2k'
-};
-
-it('should render correctly', () => {
- expect(shallow(<PreviewGraphTooltipsContent {...DEFAULT_PROPS} />)).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import PreviewGraphTooltipsContent from '../PreviewGraphTooltipsContent';
+
+const DEFAULT_PROPS = {
+ style: '1',
+ translatedName: 'Code Smells',
+ value: '1.2k'
+};
+
+it('should render correctly', () => {
+ expect(shallow(<PreviewGraphTooltipsContent {...DEFAULT_PROPS} />)).toMatchSnapshot();
+});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<Popup
- className="overview-analysis-graph-popup disabled-pointer-events"
- placement="left-top"
- style={
- Object {
- "left": -135,
- "top": 16,
- "width": 160,
- }
- }
->
- <div
- className="overview-analysis-graph-tooltip"
- >
- <div
- className="overview-analysis-graph-tooltip-title"
- >
- <DateFormatter
- date={2011-10-25T10:27:41.000Z}
- long={true}
- />
- </div>
- <table
- className="width-100"
- >
- <tbody>
- <PreviewGraphTooltipsContent
- key="code_smells"
- style="0"
- translatedName="Code Smells"
- value="Formated.15"
- />
- <PreviewGraphTooltipsContent
- key="bugs"
- style="1"
- translatedName="Bugs"
- value="Formated.0"
- />
- <PreviewGraphTooltipsContent
- key="vulnerabilities"
- style="2"
- translatedName="Vulnerabilities"
- value="Formated.1"
- />
- </tbody>
- </table>
- </div>
-</Popup>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Popup
+ className="overview-analysis-graph-popup disabled-pointer-events"
+ placement="left-top"
+ style={
+ Object {
+ "left": -135,
+ "top": 16,
+ "width": 160,
+ }
+ }
+>
+ <div
+ className="overview-analysis-graph-tooltip"
+ >
+ <div
+ className="overview-analysis-graph-tooltip-title"
+ >
+ <DateFormatter
+ date={2011-10-25T10:27:41.000Z}
+ long={true}
+ />
+ </div>
+ <table
+ className="width-100"
+ >
+ <tbody>
+ <PreviewGraphTooltipsContent
+ key="code_smells"
+ style="0"
+ translatedName="Code Smells"
+ value="Formated.15"
+ />
+ <PreviewGraphTooltipsContent
+ key="bugs"
+ style="1"
+ translatedName="Bugs"
+ value="Formated.0"
+ />
+ <PreviewGraphTooltipsContent
+ key="vulnerabilities"
+ style="2"
+ translatedName="Vulnerabilities"
+ value="Formated.1"
+ />
+ </tbody>
+ </table>
+ </div>
+</Popup>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<tr
- className="overview-analysis-graph-tooltip-line"
->
- <td
- className="thin"
- >
- <ChartLegendIcon
- className="little-spacer-right line-chart-legend line-chart-legend-1"
- />
- </td>
- <td
- className="overview-analysis-graph-tooltip-value text-right little-spacer-right thin"
- >
- 1.2k
- </td>
- <td
- className="text-ellipsis overview-analysis-graph-tooltip-description"
- >
- Code Smells
- </td>
-</tr>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<tr
+ className="overview-analysis-graph-tooltip-line"
+>
+ <td
+ className="thin"
+ >
+ <ChartLegendIcon
+ className="little-spacer-right line-chart-legend line-chart-legend-1"
+ />
+ </td>
+ <td
+ className="overview-analysis-graph-tooltip-value text-right little-spacer-right thin"
+ >
+ 1.2k
+ </td>
+ <td
+ className="text-ellipsis overview-analysis-graph-tooltip-description"
+ >
+ Code Smells
+ </td>
+</tr>
+`;