|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- /*
- * 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<Query>) => void;
- }
-
- const LIST_MARGIN_TOP = 24;
-
- export default class ProjectActivityAnalysesList extends React.PureComponent<Props> {
- 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 (
- <ProjectActivityAnalysis
- analysis={analysis}
- canAdmin={this.props.canAdmin}
- canCreateVersion={this.props.project.qualifier === ComponentQualifier.Project}
- canDeleteAnalyses={this.props.canDeleteAnalyses}
- isBaseline={analysis.key === newCodeKey}
- isFirst={analysis.key === firstAnalysisKey}
- key={analysis.key}
- selected={analysis.date.valueOf() === selectedDate}
- onUpdateSelectedDate={this.handleUpdateSelectedDate}
- />
- );
- }
-
- 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 (
- <div>
- {this.props.initializing ? (
- <div className="sw-p-4 sw-body-sm">
- <Spinner />
- </div>
- ) : (
- <div className="sw-p-4 sw-body-sm">
- <LightLabel>{translate('no_results')}</LightLabel>
- </div>
- )}
- </div>
- );
- }
-
- return (
- <ul
- className="it__project-activity-versions-list sw-box-border sw-overflow-auto sw-grow sw-shrink-0 sw-py-0 sw-px-4"
- ref={(element) => (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 && (
- <BaselineMarker className="sw-body-sm sw-mb-2">
- <span className="sw-py-1/2 sw-px-1">
- {translate('project_activity.new_code_period_start')}
- </span>
- <Tooltip
- overlay={translate('project_activity.new_code_period_start.help')}
- placement="top"
- >
- <HelperHintIcon className="sw-ml-1" />
- </Tooltip>
- </BaselineMarker>
- )}
-
- {byVersionByDay.map((version, idx) => {
- const days = Object.keys(version.byDay);
- if (days.length <= 0) {
- return null;
- }
-
- return (
- <li key={version.key || 'noversion'}>
- {version.version && (
- <VersionTagStyled
- className={classNames(
- 'sw-sticky sw-top-0 sw-left-0 sw-pb-1 -sw-ml-4 sw-z-normal',
- {
- 'sw-top-0 sw-pt-0': idx === 0,
- },
- )}
- >
- <Tooltip
- mouseEnterDelay={0.5}
- overlay={`${translate('version')} ${version.version}`}
- >
- <Badge className="sw-p-1">{version.version}</Badge>
- </Tooltip>
- </VersionTagStyled>
- )}
- <ul className="it__project-activity-days-list">
- {days.map((day) => (
- <li
- className="it__project-activity-day sw-mt-1 sw-mb-4"
- data-day={toShortISO8601String(Number(day))}
- key={day}
- >
- <div className="sw-body-md-highlight sw-mb-3">
- <DateFormatter date={Number(day)} long />
- </div>
- <ul className="it__project-activity-analyses-list">
- {version.byDay[day]?.map((analysis) =>
- this.renderAnalysis(analysis, newCodePeriod.firstNewCodeAnalysisKey),
- )}
- </ul>
- </li>
- ))}
- </ul>
- </li>
- );
- })}
- {this.props.analysesLoading && (
- <li className="sw-text-center">
- <Spinner />
- </li>
- )}
- </ul>
- );
- }
- }
-
- const VersionTagStyled = styled.div`
- background-color: ${themeColor('backgroundSecondary')};
- `;
|