/* * SonarQube * Copyright (C) 2009-2024 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 styled from '@emotion/styled'; import classNames from 'classnames'; import { isEqual } from 'date-fns'; import { Badge, HelperHintIcon, LightLabel, Spinner, themeColor } from 'design-system'; import * as React from 'react'; import Tooltip from '../../../components/controls/Tooltip'; import DateFormatter from '../../../components/intl/DateFormatter'; import { toShortISO8601String } from '../../../helpers/dates'; import { translate } from '../../../helpers/l10n'; import { ComponentQualifier } from '../../../types/component'; import { ParsedAnalysis } from '../../../types/project-activity'; import { AnalysesByDay, Query, activityQueryChanged, getAnalysesByVersionByDay } from '../utils'; import ProjectActivityAnalysis, { BaselineMarker } from './ProjectActivityAnalysis'; interface Props { analyses: ParsedAnalysis[]; analysesLoading: boolean; canAdmin?: boolean; canDeleteAnalyses?: boolean; initializing: boolean; leakPeriodDate?: Date; project: { qualifier: string }; query: Query; onUpdateQuery: (changes: Partial) => void; } const LIST_MARGIN_TOP = 24; export default class ProjectActivityAnalysesList extends React.PureComponent { scrollContainer?: HTMLUListElement | null; componentDidUpdate(prevProps: Props) { const selectedDate = this.props.query.selectedDate ? this.props.query.selectedDate.valueOf() : null; if ( this.scrollContainer && activityQueryChanged(prevProps.query, this.props.query) && !this.props.analyses.some(({ date }) => date.valueOf() === selectedDate) ) { this.scrollContainer.scrollTop = 0; } } handleUpdateSelectedDate = (date: Date) => { this.props.onUpdateQuery({ selectedDate: date }); }; getNewCodePeriodStartKey(versionByDay: AnalysesByDay[]): { firstNewCodeAnalysisKey: string | undefined; baselineAnalysisKey: string | undefined; } { const { leakPeriodDate } = this.props; if (!leakPeriodDate) { return { firstNewCodeAnalysisKey: undefined, baselineAnalysisKey: undefined }; } // In response, the first new code analysis comes before the baseline analysis // This variable is to track the previous analysis and return when next is baseline analysis let prevAnalysis; for (const version of versionByDay) { const days = Object.keys(version.byDay); for (const day of days) { for (const analysis of version.byDay[day]) { if (isEqual(leakPeriodDate, analysis.date)) { return { firstNewCodeAnalysisKey: prevAnalysis?.key, baselineAnalysisKey: analysis.key, }; } prevAnalysis = analysis; } } } return { firstNewCodeAnalysisKey: undefined, baselineAnalysisKey: undefined }; } renderAnalysis(analysis: ParsedAnalysis, newCodeKey?: string) { const firstAnalysisKey = this.props.analyses[0].key; const selectedDate = this.props.query.selectedDate ? this.props.query.selectedDate.valueOf() : null; return ( ); } render() { const byVersionByDay = getAnalysesByVersionByDay(this.props.analyses, this.props.query); const newCodePeriod = this.getNewCodePeriodStartKey(byVersionByDay); const hasFilteredData = byVersionByDay.length > 1 || (byVersionByDay.length === 1 && Object.keys(byVersionByDay[0].byDay).length > 0); if (this.props.analyses.length === 0 || !hasFilteredData) { return (
{this.props.initializing ? (
) : (
{translate('no_results')}
)}
); } return (
    (this.scrollContainer = element)} style={{ height: 'calc(100vh - 250px)', marginTop: this.props.project.qualifier === ComponentQualifier.Project ? LIST_MARGIN_TOP : undefined, }} > {newCodePeriod.baselineAnalysisKey !== undefined && newCodePeriod.firstNewCodeAnalysisKey === undefined && ( {translate('project_activity.new_code_period_start')} )} {byVersionByDay.map((version, idx) => { const days = Object.keys(version.byDay); if (days.length <= 0) { return null; } return (
  • {version.version && ( {version.version} )}
      {days.map((day) => (
      • {version.byDay[day]?.map((analysis) => this.renderAnalysis(analysis, newCodePeriod.firstNewCodeAnalysisKey), )}
    • ))}
  • ); })} {this.props.analysesLoading && (
  • )}
); } } const VersionTagStyled = styled.div` background-color: ${themeColor('backgroundSecondary')}; `;