import {Fragment} from 'react'; import styled from '@emotion/styled'; import type {Location} from 'history'; import pick from 'lodash/pick'; import {fetchAnyReleaseExistence} from 'sentry/actionCreators/projects'; import {SectionHeading} from 'sentry/components/charts/styles'; import {DateTime} from 'sentry/components/dateTime'; import DeprecatedAsyncComponent from 'sentry/components/deprecatedAsyncComponent'; import EmptyStateWarning from 'sentry/components/emptyStateWarning'; import Placeholder from 'sentry/components/placeholder'; import TextOverflow from 'sentry/components/textOverflow'; import Version from 'sentry/components/version'; import {URL_PARAM} from 'sentry/constants/pageFilters'; import {IconOpen} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; import type {Release} from 'sentry/types/release'; import MissingReleasesButtons from './missingFeatureButtons/missingReleasesButtons'; import {SectionHeadingLink, SectionHeadingWrapper, SidebarSection} from './styles'; import {didProjectOrEnvironmentChange} from './utils'; const PLACEHOLDER_AND_EMPTY_HEIGHT = '160px'; type Props = DeprecatedAsyncComponent['props'] & { isProjectStabilized: boolean; location: Location; organization: Organization; projectSlug: string; project?: Project; }; type State = { releases: Release[] | null; hasOlderReleases?: boolean; } & DeprecatedAsyncComponent['state']; class ProjectLatestReleases extends DeprecatedAsyncComponent { shouldComponentUpdate(nextProps: Props, nextState: State) { const {location, isProjectStabilized} = this.props; // TODO(project-detail): we temporarily removed refetching based on timeselector if ( this.state !== nextState || didProjectOrEnvironmentChange(location, nextProps.location) || isProjectStabilized !== nextProps.isProjectStabilized ) { return true; } return false; } componentDidUpdate(prevProps: Props) { const {location, isProjectStabilized} = this.props; if ( didProjectOrEnvironmentChange(prevProps.location, location) || prevProps.isProjectStabilized !== isProjectStabilized ) { this.remountComponent(); } } getEndpoints(): ReturnType { const {location, organization, projectSlug, isProjectStabilized} = this.props; if (!isProjectStabilized) { return []; } const query = { ...pick(location.query, Object.values(URL_PARAM)), per_page: 5, }; // TODO(project-detail): this does not filter releases for the given time return [ ['releases', `/projects/${organization.slug}/${projectSlug}/releases/`, {query}], ]; } /** * If our releases are empty, determine if we had a release in the last 90 days (empty message differs then) */ async onLoadAllEndpointsSuccess() { const {releases} = this.state; const {organization, project, isProjectStabilized} = this.props; if (!isProjectStabilized) { return; } if ((releases ?? []).length !== 0 || !project?.id) { this.setState({hasOlderReleases: true}); return; } this.setState({loading: true}); const hasOlderReleases = await fetchAnyReleaseExistence( this.api, organization.slug, project.id ); this.setState({hasOlderReleases, loading: false}); } get releasesLink() { const {organization} = this.props; // as this is a link to latest releases, we want to only preserve project and environment return { pathname: `/organizations/${organization.slug}/releases/`, query: { statsPeriod: undefined, start: undefined, end: undefined, utc: undefined, }, }; } renderReleaseRow = (release: Release) => { const {project} = this.props; const {lastDeploy, dateCreated} = release; return ( ); }; renderInnerBody() { const {organization, project, isProjectStabilized} = this.props; const {loading, releases, hasOlderReleases} = this.state; const checkingForOlderReleases = !(releases ?? []).length && hasOlderReleases === undefined; const showLoadingIndicator = loading || checkingForOlderReleases || !isProjectStabilized; if (showLoadingIndicator) { return ; } if (!hasOlderReleases) { return ( ); } if (!releases || releases.length === 0) { return ( {t('No releases found')} ); } return {releases.map(this.renderReleaseRow)}; } renderLoading() { return this.renderBody(); } renderBody() { return ( {t('Latest Releases')}
{this.renderInnerBody()}
); } } const ReleasesTable = styled('div')` display: grid; font-size: ${p => p.theme.fontSizeMedium}; white-space: nowrap; grid-template-columns: 1fr auto; margin-bottom: ${space(2)}; & > * { padding: ${space(0.5)} ${space(1)}; height: 32px; } & > *:nth-child(2n + 2) { text-align: right; } & > *:nth-child(4n + 1), & > *:nth-child(4n + 2) { background-color: ${p => p.theme.rowBackground}; } `; const StyledVersion = styled(Version)` ${p => p.theme.overflowEllipsis} line-height: 1.6; font-variant-numeric: tabular-nums; `; const StyledEmptyStateWarning = styled(EmptyStateWarning)` height: ${PLACEHOLDER_AND_EMPTY_HEIGHT}; justify-content: center; `; export default ProjectLatestReleases;