Browse Source

SONAR-17011 [894182] Detailed alternative description is missing

tags/9.7.0.61563
Wouter Admiraal 1 year ago
parent
commit
10630ace5b

+ 1
- 0
server/sonar-web/src/main/js/apps/overview/branches/ActivityPanel.tsx View File

@@ -98,6 +98,7 @@ export function ActivityPanel(props: ActivityPanelProps) {
'overview.activity.graph_shows_data_for_x',
displayedMetrics.map(metricKey => localizeMetric(metricKey)).join(', ')
)}
canShowDataAsTable={false}
graph={graph}
graphs={graphs}
leakPeriodDate={shownLeakPeriodDate}

+ 2
- 0
server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/ActivityPanel-test.tsx.snap View File

@@ -39,6 +39,7 @@ exports[`should render correctly 1`] = `
<GraphsHistory
analyses={Array []}
ariaLabel="overview.activity.graph_shows_data_for_x.metric.bugs.name, metric.code_smells.name, metric.vulnerabilities.name"
canShowDataAsTable={false}
graph="issues"
graphs={
Array [
@@ -208,6 +209,7 @@ exports[`should render correctly 2`] = `
<GraphsHistory
analyses={Array []}
ariaLabel="overview.activity.graph_shows_data_for_x.metric.bugs.name, metric.code_smells.name, metric.vulnerabilities.name"
canShowDataAsTable={false}
graph="issues"
graphs={
Array [

+ 188
- 0
server/sonar-web/src/main/js/components/activity-graph/DataTableModal.tsx View File

@@ -0,0 +1,188 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { filter, slice, sortBy } from 'lodash';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import EventInner from '../../apps/projectActivity/components/EventInner';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { formatMeasure } from '../../helpers/measures';
import { ParsedAnalysis, Serie } from '../../types/project-activity';
import { Button } from '../controls/buttons';
import Modal from '../controls/Modal';
import DateFormatter from '../intl/DateFormatter';
import TimeFormatter from '../intl/TimeFormatter';
import { Alert } from '../ui/Alert';
import { getAnalysisEventsForDate } from './utils';

export interface DataTableModalProps {
analyses: ParsedAnalysis[];
graphEndDate?: Date;
graphStartDate?: Date;
series: Serie[];
onClose: () => void;
}

type DataTableEntry = { date: Date } & { [x: string]: string | undefined };

const MAX_DATA_TABLE_ROWS = 100;

export default function DataTableModal(props: DataTableModalProps) {
const { analyses, series, graphEndDate, graphStartDate } = props;

if (series.length === 0) {
return renderModal(
props,
<Alert variant="info">
{translate('project_activity.graphs.data_table.no_data_warning')}
</Alert>
);
}

const tableData = series.reduce((acc, serie) => {
const data = filter(
serie.data,
// Make sure we respect the date filtering. On the graph, this is done by dynamically
// "zooming" on the series. Here, we actually have to "cut off" part of the serie's
// data points.
({ x }) => {
if (graphEndDate && x > graphEndDate) {
return false;
}
if (graphStartDate && x < graphStartDate) {
return false;
}
return true;
}
);

data.forEach(({ x, y }) => {
const key = x.getTime();
if (acc[key] === undefined) {
acc[key] = { date: x } as DataTableEntry;
}

if (y !== undefined && !(typeof y === 'number' && isNaN(y))) {
acc[key][serie.name] = formatMeasure(y, serie.type);
}
});

return acc;
}, {} as { [x: number]: DataTableEntry });

const metrics = series.map(({ name }) => name);
const rows = slice(
sortBy(Object.values(tableData), ({ date }) => -date),
0,
MAX_DATA_TABLE_ROWS
).map(({ date, ...values }) => (
<tr key={date.getTime()}>
<td className="nowrap">
<DateFormatter long={true} date={date} />
<div className="small note">
<TimeFormatter date={date} />
</div>
</td>
{metrics.map(metric => (
<td key={metric} className="thin nowrap">
{values[metric] || '-'}
</td>
))}
<td>
<ul>
{getAnalysisEventsForDate(analyses, date).map(event => (
<li className="little-spacer-bottom" key={event.key}>
<EventInner event={event} readonly={true} />
</li>
))}
</ul>
</td>
</tr>
));

const rowCount = rows.length;

if (rowCount === 0) {
const start = graphStartDate && <DateFormatter long={true} date={graphStartDate} />;
const end = graphEndDate && <DateFormatter long={true} date={graphEndDate} />;
let suffix = '';
if (start && end) {
suffix = '_x_y';
} else if (start) {
suffix = '_x';
} else if (end) {
suffix = '_y';
}
return renderModal(
props,
<Alert variant="info">
<FormattedMessage
defaultMessage={translate(
`project_activity.graphs.data_table.no_data_warning_check_dates${suffix}`
)}
id={`project_activity.graphs.data_table.no_data_warning_check_dates${suffix}`}
values={{ start, end }}
/>
</Alert>
);
}

return renderModal(
props,
<>
{rowCount === MAX_DATA_TABLE_ROWS && (
<Alert variant="info">
{translateWithParameters(
'project_activity.graphs.data_table.max_lines_warning',
MAX_DATA_TABLE_ROWS
)}
</Alert>
)}
<table className="spacer-top data zebra">
<thead>
<tr>
<th>{translate('date')}</th>
{series.map(serie => (
<th key={serie.name} className="thin nowrap">
{serie.translatedName}
</th>
))}
<th>{translate('events')}</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
</>
);
}

function renderModal(props: DataTableModalProps, children: React.ReactNode) {
const heading = translate('project_activity.graphs.data_table.title');
return (
<Modal onRequestClose={props.onClose} contentLabel={heading} size="medium">
<div className="modal-head">
<h2>{heading}</h2>
</div>
<div className="modal-body modal-container">{children}</div>
<div className="modal-foot">
<Button onClick={props.onClose}>{translate('close')}</Button>
</div>
</Modal>
);
}

+ 29
- 3
server/sonar-web/src/main/js/components/activity-graph/GraphHistory.tsx View File

@@ -20,15 +20,21 @@
import * as React from 'react';
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer';
import AdvancedTimeline from '../../components/charts/AdvancedTimeline';
import { translate } from '../../helpers/l10n';
import { formatMeasure, getShortType } from '../../helpers/measures';
import { MeasureHistory, Serie } from '../../types/project-activity';
import { AnalysisEvent } from '../../types/types';
import { ParsedAnalysis } from '../../types/types';
import { Button } from '../controls/buttons';
import ModalButton from '../controls/ModalButton';
import DataTableModal from './DataTableModal';
import GraphsLegendCustom from './GraphsLegendCustom';
import GraphsLegendStatic from './GraphsLegendStatic';
import GraphsTooltips from './GraphsTooltips';
import { getAnalysisEventsForDate } from './utils';

interface Props {
events: AnalysisEvent[];
analyses: ParsedAnalysis[];
canShowDataAsTable?: boolean;
graph: string;
graphEndDate?: Date;
graphStartDate?: Date;
@@ -69,7 +75,8 @@ export default class GraphHistory extends React.PureComponent<Props, State> {

render() {
const {
events,
analyses,
canShowDataAsTable = true,
graph,
graphEndDate,
graphStartDate,
@@ -83,6 +90,7 @@ export default class GraphHistory extends React.PureComponent<Props, State> {
graphDescription
} = this.props;
const { tooltipIdx, tooltipXPos } = this.state;
const events = getAnalysisEventsForDate(analyses, selectedDate);

return (
<div className="activity-graph-container flex-grow display-flex-column display-flex-stretch display-flex-justify-center">
@@ -132,6 +140,24 @@ export default class GraphHistory extends React.PureComponent<Props, State> {
)}
</AutoSizer>
</div>
{canShowDataAsTable && (
<ModalButton
modal={({ onClose }) => (
<DataTableModal
analyses={analyses}
graphEndDate={graphEndDate}
graphStartDate={graphStartDate}
series={series}
onClose={onClose}
/>
)}>
{({ onClick }) => (
<Button className="a11y-hidden" onClick={onClick}>
{translate('project_activity.graphs.open_in_table')}
</Button>
)}
</ModalButton>
)}
</div>
);
}

+ 5
- 16
server/sonar-web/src/main/js/components/activity-graph/GraphsHistory.tsx View File

@@ -31,6 +31,7 @@ import { getSeriesMetricType, hasHistoryData, isCustomGraph } from './utils';
interface Props {
analyses: ParsedAnalysis[];
ariaLabel?: string;
canShowDataAsTable?: boolean;
graph: GraphType;
graphs: Serie[][];
graphEndDate?: Date;
@@ -63,31 +64,19 @@ export default class GraphsHistory extends React.PureComponent<Props, State> {
}
}

getSelectedDateEvents = () => {
const { selectedDate } = this.state;
const { analyses } = this.props;
if (analyses && selectedDate) {
const analysis = analyses.find(a => a.date.valueOf() === selectedDate.valueOf());
if (analysis) {
return analysis.events;
}
}
return [];
};

updateTooltip = (selectedDate?: Date) => {
this.setState({ selectedDate });
};

render() {
const { graph, loading, series, ariaLabel } = this.props;
const { analyses, graph, loading, series, ariaLabel, canShowDataAsTable } = this.props;
const isCustom = isCustomGraph(graph);

if (loading) {
return (
<div className="activity-graph-container flex-grow display-flex-column display-flex-stretch display-flex-justify-center">
<div className="text-center">
<DeferredSpinner loading={loading} />
<DeferredSpinner ariaLabel={translate('loading')} loading={loading} />
</div>
</div>
);
@@ -114,14 +103,14 @@ export default class GraphsHistory extends React.PureComponent<Props, State> {
</div>
);
}
const events = this.getSelectedDateEvents();
const showAreas = [GraphType.coverage, GraphType.duplications].includes(graph);
return (
<div className="display-flex-justify-center display-flex-column display-flex-stretch flex-grow">
{this.props.graphs.map((graphSeries, idx) => {
return (
<GraphHistory
events={events}
analyses={analyses}
canShowDataAsTable={canShowDataAsTable}
graph={graph}
graphEndDate={this.props.graphEndDate}
graphStartDate={this.props.graphStartDate}

+ 4
- 3
server/sonar-web/src/main/js/components/activity-graph/GraphsZoom.tsx View File

@@ -23,7 +23,7 @@ import ZoomTimeLine from '../../components/charts/ZoomTimeLine';
import { Serie } from '../../types/project-activity';
import { hasHistoryData } from './utils';

interface Props {
interface GraphsZoomProps {
graphEndDate?: Date;
graphStartDate?: Date;
leakPeriodDate?: Date;
@@ -34,13 +34,14 @@ interface Props {
updateGraphZoom: (from?: Date, to?: Date) => void;
}

export default function GraphsZoom(props: Props) {
export default function GraphsZoom(props: GraphsZoomProps) {
if (props.loading || !hasHistoryData(props.series)) {
return null;
}

return (
<div className="activity-graph-zoom">
// We hide this for screen readers; they should use date inputs instead.
<div className="activity-graph-zoom" aria-hidden={true}>
<AutoSizer disableHeight={true}>
{({ width }) => (
<ZoomTimeLine

+ 26
- 0
server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphHistory-test.tsx.snap View File

@@ -36,6 +36,19 @@ exports[`should correctly render a graph 1`] = `
<Component />
</AutoSizer>
</div>
<div
className="spacer-top big-spacer-bottom small"
>
<div
className="display-flex-justify-center"
>
<ModalButton
modal={[Function]}
>
<Component />
</ModalButton>
</div>
</div>
</div>
`;

@@ -76,5 +89,18 @@ exports[`should correctly render a graph: custom 1`] = `
<Component />
</AutoSizer>
</div>
<div
className="spacer-top big-spacer-bottom small"
>
<div
className="display-flex-justify-center"
>
<ModalButton
modal={[Function]}
>
<Component />
</ModalButton>
</div>
</div>
</div>
`;

+ 108
- 3
server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsHistory-test.tsx.snap View File

@@ -5,8 +5,43 @@ exports[`should correctly render a graph 1`] = `
className="display-flex-justify-center display-flex-column display-flex-stretch flex-grow"
>
<GraphHistory
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": "OTHER",
"key": "E2",
"name": "foo",
},
Object {
"category": "VERSION",
"key": "E3",
"name": "6.4",
},
],
"key": "A3",
},
]
}
ariaLabel="project_activity.graphs.explanation_x.metric.bugs.name"
events={Array []}
graph="issues"
isCustom={false}
key="0"
@@ -50,8 +85,43 @@ exports[`should correctly render multiple graphs 1`] = `
className="display-flex-justify-center display-flex-column display-flex-stretch flex-grow"
>
<GraphHistory
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": "OTHER",
"key": "E2",
"name": "foo",
},
Object {
"category": "VERSION",
"key": "E3",
"name": "6.4",
},
],
"key": "A3",
},
]
}
ariaLabel="project_activity.graphs.explanation_x.metric.bugs.name"
events={Array []}
graph="issues"
isCustom={false}
key="0"
@@ -88,8 +158,43 @@ exports[`should correctly render multiple graphs 1`] = `
updateTooltip={[Function]}
/>
<GraphHistory
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": "OTHER",
"key": "E2",
"name": "foo",
},
Object {
"category": "VERSION",
"key": "E3",
"name": "6.4",
},
],
"key": "A3",
},
]
}
ariaLabel="project_activity.graphs.explanation_x.metric.bugs.name"
events={Array []}
graph="issues"
isCustom={false}
key="1"

+ 11
- 1
server/sonar-web/src/main/js/components/activity-graph/utils.ts View File

@@ -23,7 +23,7 @@ import { localizeMetric } from '../../helpers/measures';
import { get, save } from '../../helpers/storage';
import { MetricKey } from '../../types/metrics';
import { GraphType, MeasureHistory, Serie } from '../../types/project-activity';
import { Dict, Metric } from '../../types/types';
import { Dict, Metric, ParsedAnalysis } from '../../types/types';

export const DEFAULT_GRAPH = GraphType.issues;

@@ -159,6 +159,16 @@ export function getActivityGraph(
};
}

export function getAnalysisEventsForDate(analyses: ParsedAnalysis[], date?: Date) {
if (date) {
const analysis = analyses.find(a => a.date.valueOf() === date.valueOf());
if (analysis) {
return analysis.events;
}
}
return [];
}

function findMetric(key: string, metrics: Metric[] | Dict<Metric>) {
if (Array.isArray(metrics)) {
return metrics.find(metric => metric.key === key);

+ 8
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -112,6 +112,7 @@ list_of_issues=List of issues
list_of_projects=List of projects
load_more=Load more
load_verb=Load
loading=Loading
login=Login
major=Major
manual=Manual
@@ -1551,6 +1552,13 @@ project_activity.graphs.custom.no_history=There isn't enough data to generate an
project_activity.graphs.custom.metric_no_history=This metric has no historical data to display.
project_activity.graphs.custom.search=Search for a metric by name
project_activity.graphs.custom.type_x_message=Only "{0}" metrics are available with your current selection.
project_activity.graphs.open_in_table=Show the graph data in a table
project_activity.graphs.data_table.title=Graph data in table format
project_activity.graphs.data_table.max_lines_warning=Only the {0} most recent data entries are shown. If you want to see different data, change the date filters on the main page.
project_activity.graphs.data_table.no_data_warning=There is no data for the selected series.
project_activity.graphs.data_table.no_data_warning_check_dates_x=There is no data for the selected date range (everything after {start}). Try modifying the date filters on the main page.
project_activity.graphs.data_table.no_data_warning_check_dates_y=There is no data for the selected date range (everything before {end}). Try modifying the date filters on the main page.
project_activity.graphs.data_table.no_data_warning_check_dates_x_y=There is no data for the selected date range ({start} to {end}). Try modifying the date filters on the main page.

project_activity.custom_metric.covered_lines=Covered Lines


Loading…
Cancel
Save