diff --git a/static/app/utils/useReleaseRepositories.tsx b/static/app/utils/useReleaseRepositories.tsx new file mode 100644 index 0000000000000..d26ec4ef06b9a --- /dev/null +++ b/static/app/utils/useReleaseRepositories.tsx @@ -0,0 +1,44 @@ +import type {Repository} from 'sentry/types/integrations'; +import { + type ApiQueryKey, + useApiQuery, + type UseApiQueryOptions, +} from 'sentry/utils/queryClient'; + +function getReleaseRepositoriesQueryKey({ + orgSlug, + projectSlug, + release, +}: { + orgSlug: string; + projectSlug: string; + release: string; +}): ApiQueryKey { + return [`/projects/${orgSlug}/${projectSlug}/releases/${release}/repositories/`]; +} + +interface UseReleaseReposProps { + orgSlug: string; + projectSlug: string; + release: string; + options?: Partial>; +} + +export function useReleaseRepositories({ + orgSlug, + projectSlug, + release, + options, +}: UseReleaseReposProps) { + return useApiQuery( + getReleaseRepositoriesQueryKey({ + orgSlug, + projectSlug, + release: encodeURIComponent(release), + }), + { + staleTime: Infinity, + ...options, + } + ); +} diff --git a/static/app/utils/withRepositories.spec.tsx b/static/app/utils/withRepositories.spec.tsx deleted file mode 100644 index aac95f0ca3448..0000000000000 --- a/static/app/utils/withRepositories.spec.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import {OrganizationFixture} from 'sentry-fixture/organization'; - -import {render, waitFor} from 'sentry-test/reactTestingLibrary'; - -import RepositoryStore from 'sentry/stores/repositoryStore'; -import withRepositories from 'sentry/utils/withRepositories'; - -describe('withRepositories HoC', function () { - const organization = OrganizationFixture(); - const orgSlug = organization.slug; - const repoUrl = `/organizations/${orgSlug}/repos/`; - - const api = new MockApiClient(); - const mockData = [{id: '1'}]; - - beforeEach(() => { - MockApiClient.clearMockResponses(); - MockApiClient.addMockResponse({ - url: repoUrl, - body: mockData, - }); - - jest.restoreAllMocks(); - RepositoryStore.init?.(); - }); - - it('adds repositories prop', async function () { - const Component = jest.fn(() => null); - const Container = withRepositories(Component); - render(); - - await waitFor(() => - expect(Component).toHaveBeenCalledWith( - expect.objectContaining({ - repositories: mockData, - repositoriesLoading: false, - repositoriesError: undefined, - }), - {} - ) - ); - }); - - it('prevents repeated calls', async function () { - function Component() { - return null; - } - const Container = withRepositories(Component); - - jest.spyOn(api, 'requestPromise'); - jest.spyOn(Container.prototype, 'fetchRepositories'); - - // Mount and run component - render(); - - // Mount and run duplicates - render(); - render(); - - await waitFor(() => expect(api.requestPromise).toHaveBeenCalledTimes(1)); - expect(Container.prototype.fetchRepositories).toHaveBeenCalledTimes(3); - }); - - /** - * Same as 'prevents repeated calls', but with the async fetch/checks - * happening on same tick. - * - * Additionally, this test checks that withRepositories.fetchRepositories does - * not check for (store.orgSlug !== orgSlug) as the short-circuit does not - * change the value for orgSlug - */ - it('prevents simultaneous calls', async function () { - function Component() { - return null; - } - const Container = withRepositories(Component); - - jest.spyOn(api, 'requestPromise'); - jest.spyOn(Container.prototype, 'componentDidMount'); - - // Mount and run duplicates - render(); - render(); - render(); - - await waitFor(() => expect(api.requestPromise).toHaveBeenCalledTimes(1)); - expect(Container.prototype.componentDidMount).toHaveBeenCalledTimes(3); - }); -}); diff --git a/static/app/utils/withRepositories.tsx b/static/app/utils/withRepositories.tsx deleted file mode 100644 index b2e121ec32335..0000000000000 --- a/static/app/utils/withRepositories.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import {Component} from 'react'; - -import {getRepositories} from 'sentry/actionCreators/repositories'; -import type {Client} from 'sentry/api'; -import RepositoryStore from 'sentry/stores/repositoryStore'; -import type {Repository} from 'sentry/types/integrations'; -import type {Organization} from 'sentry/types/organization'; -import getDisplayName from 'sentry/utils/getDisplayName'; - -type DependentProps = { - api: Client; - organization: Organization; -}; - -type InjectedProps = { - repositories?: Repository[]; - repositoriesError?: Error; - repositoriesLoading?: boolean; -}; - -const INITIAL_STATE: InjectedProps = { - repositories: undefined, - repositoriesLoading: undefined, - repositoriesError: undefined, -}; - -function withRepositories

( - WrappedComponent: React.ComponentType

-) { - class WithRepositories extends Component

{ - static displayName = `withRepositories(${getDisplayName(WrappedComponent)})`; - - constructor(props: P & DependentProps, context: any) { - super(props, context); - - const repoData = RepositoryStore.get(); - - this.state = - repoData.orgSlug === props.organization.slug - ? {...INITIAL_STATE, ...repoData} - : {...INITIAL_STATE}; - } - - componentDidMount() { - const {organization} = this.props; - const orgSlug = organization.slug; - const repoData = RepositoryStore.get(); - - if (repoData.orgSlug !== orgSlug) { - RepositoryStore.resetRepositories(); - } - - // XXX(leedongwei): Do not move this function call unless you modify the - // unit test named "prevents repeated calls" - this.fetchRepositories(); - } - componentWillUnmount() { - this.unsubscribe(); - } - unsubscribe = RepositoryStore.listen(() => this.onStoreUpdate(), undefined); - - fetchRepositories() { - const {api, organization} = this.props; - const orgSlug = organization.slug; - const repoData = RepositoryStore.get(); - - // XXX(leedongwei): Do not check the orgSlug here. It would have been - // verified at `getInitialState`. The short-circuit hack in actionCreator - // does not update the orgSlug in the store. - if ( - (!repoData.repositories && !repoData.repositoriesLoading) || - repoData.repositoriesError - ) { - getRepositories(api, {orgSlug}); - } - } - - onStoreUpdate() { - const repoData = RepositoryStore.get(); - this.setState({...repoData}); - } - - render() { - return ; - } - } - - return WithRepositories; -} - -export default withRepositories; diff --git a/static/app/views/releases/detail/commitsAndFiles/commits.spec.tsx b/static/app/views/releases/detail/commitsAndFiles/commits.spec.tsx index c73784190f458..71095d07127ff 100644 --- a/static/app/views/releases/detail/commitsAndFiles/commits.spec.tsx +++ b/static/app/views/releases/detail/commitsAndFiles/commits.spec.tsx @@ -7,7 +7,6 @@ import {initializeOrg} from 'sentry-test/initializeOrg'; import {render, screen} from 'sentry-test/reactTestingLibrary'; import selectEvent from 'sentry-test/selectEvent'; -import RepositoryStore from 'sentry/stores/repositoryStore'; import type {ReleaseProject} from 'sentry/types/release'; import {ReleaseContext} from 'sentry/views/releases/detail'; @@ -16,7 +15,7 @@ import Commits from './commits'; describe('Commits', () => { const release = ReleaseFixture(); const project = ReleaseProjectFixture() as Required; - const {routerProps, router, organization} = initializeOrg({ + const {router, organization} = initializeOrg({ router: {params: {release: release.version}}, }); const repos = [RepositoryFixture({integrationId: '1'})]; @@ -34,7 +33,7 @@ describe('Commits', () => { releaseMeta: {} as any, }} > - + , {router} ); @@ -46,7 +45,12 @@ describe('Commits', () => { url: `/organizations/${organization.slug}/repos/`, body: repos, }); - RepositoryStore.init(); + MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${project.slug}/releases/${encodeURIComponent( + release.version + )}/repositories/`, + body: repos, + }); }); it('should render no repositories message', async () => { @@ -133,7 +137,7 @@ describe('Commits', () => { integrationId: '1', }); // Current repo is stored in query parameter activeRepo - const {router: newRouterContext, routerProps: newRouterProps} = initializeOrg({ + const {router: newRouterContext} = initializeOrg({ router: { params: {release: release.version}, location: {query: {activeRepo: otherRepo.name}}, @@ -168,7 +172,7 @@ describe('Commits', () => { releaseMeta: {} as any, }} > - + , {router: newRouterContext} ); diff --git a/static/app/views/releases/detail/commitsAndFiles/commits.tsx b/static/app/views/releases/detail/commitsAndFiles/commits.tsx index 2e42b40a9ae3d..80e2bf646b44e 100644 --- a/static/app/views/releases/detail/commitsAndFiles/commits.tsx +++ b/static/app/views/releases/detail/commitsAndFiles/commits.tsx @@ -1,6 +1,5 @@ -import {Fragment} from 'react'; +import {Fragment, useContext} from 'react'; import styled from '@emotion/styled'; -import type {Location} from 'history'; import * as Layout from 'sentry/components/layouts/thirds'; import LoadingError from 'sentry/components/loadingError'; @@ -13,33 +12,35 @@ import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Commit, Repository} from 'sentry/types/integrations'; -import type {RouteComponentProps} from 'sentry/types/legacyReactRouter'; +import type {Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; import {useApiQuery} from 'sentry/utils/queryClient'; import routeTitleGen from 'sentry/utils/routeTitle'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import {useParams} from 'sentry/utils/useParams'; +import {useReleaseRepositories} from 'sentry/utils/useReleaseRepositories'; +import {useRepositories} from 'sentry/utils/useRepositories'; import {formatVersion} from 'sentry/utils/versions/formatVersion'; +import {ReleaseContext} from 'sentry/views/releases/detail'; import {ReleaseCommit} from 'sentry/views/releases/detail/commitsAndFiles/releaseCommit'; import {getCommitsByRepository, getQuery, getReposToRender} from '../utils'; -import EmptyState from './emptyState'; +import {EmptyState, NoReleaseRepos, NoRepositories} from './emptyState'; import RepositorySwitcher from './repositorySwitcher'; -import withReleaseRepos from './withReleaseRepos'; -interface CommitsProps extends RouteComponentProps<{release: string}, {}> { - location: Location; +interface CommitsProps { + organization: Organization; projectSlug: Project['slug']; releaseRepos: Repository[]; - activeReleaseRepo?: Repository; } -function Commits({activeReleaseRepo, releaseRepos, projectSlug}: CommitsProps) { +function CommitsList({organization, releaseRepos, projectSlug}: CommitsProps) { const location = useLocation(); const params = useParams<{release: string}>(); - const organization = useOrganization(); + const activeReleaseRepo = + releaseRepos.find(repo => repo.name === location.query.activeRepo) ?? releaseRepos[0]; const query = getQuery({location, activeRepository: activeReleaseRepo}); const { @@ -114,8 +115,68 @@ function Commits({activeReleaseRepo, releaseRepos, projectSlug}: CommitsProps) { ); } +function Commits() { + const organization = useOrganization(); + const params = useParams<{release: string}>(); + const releaseContext = useContext(ReleaseContext); + const { + data: repositories, + isLoading: isLoadingRepositories, + isError: isRepositoriesError, + refetch: refetchRepositories, + } = useRepositories({ + orgSlug: organization.slug, + }); + const { + data: releaseRepos, + isLoading: isLoadingReleaseRepos, + isError: isReleaseReposError, + refetch: refetchReleaseRepos, + } = useReleaseRepositories({ + orgSlug: organization.slug, + projectSlug: releaseContext.project.slug, + release: params.release, + options: { + enabled: !!releaseContext.project.slug, + }, + }); + + if (isLoadingReleaseRepos || isLoadingRepositories) { + return ; + } + + if (isRepositoriesError || isReleaseReposError) { + return ( + { + refetchRepositories(); + refetchReleaseRepos(); + }} + /> + ); + } + + const noReleaseReposFound = !releaseRepos?.length; + if (noReleaseReposFound) { + return ; + } + + const noRepositoryOrgRelatedFound = !repositories?.length; + if (noRepositoryOrgRelatedFound) { + return ; + } + + return ( + + ); +} + const Actions = styled('div')` margin-bottom: ${space(2)}; `; -export default withReleaseRepos(Commits); +export default Commits; diff --git a/static/app/views/releases/detail/commitsAndFiles/emptyState.tsx b/static/app/views/releases/detail/commitsAndFiles/emptyState.tsx index 8c0db3a786889..25043771ef4dd 100644 --- a/static/app/views/releases/detail/commitsAndFiles/emptyState.tsx +++ b/static/app/views/releases/detail/commitsAndFiles/emptyState.tsx @@ -1,12 +1,17 @@ +import {LinkButton} from 'sentry/components/button'; +import EmptyMessage from 'sentry/components/emptyMessage'; import EmptyStateWarning from 'sentry/components/emptyStateWarning'; +import {Body, Main} from 'sentry/components/layouts/thirds'; import Panel from 'sentry/components/panels/panel'; import PanelBody from 'sentry/components/panels/panelBody'; +import {IconCommit} from 'sentry/icons'; +import {t} from 'sentry/locale'; type Props = { children: React.ReactNode; }; -function EmptyState({children}: Props) { +export function EmptyState({children}: Props) { return ( @@ -18,4 +23,41 @@ function EmptyState({children}: Props) { ); } -export default EmptyState; +export function NoReleaseRepos() { + return ( + +

+ + } + title={t('Releases are better with commit data!')} + description={t('No commits associated with this release have been found.')} + /> + +
+ + ); +} + +export function NoRepositories({orgSlug}: {orgSlug: string}) { + return ( + +
+ + } + title={t('Releases are better with commit data!')} + description={t( + 'Connect a repository to see commit info, files changed, and authors involved in future releases.' + )} + action={ + + {t('Connect a repository')} + + } + /> + +
+ + ); +} diff --git a/static/app/views/releases/detail/commitsAndFiles/filesChanged.spec.tsx b/static/app/views/releases/detail/commitsAndFiles/filesChanged.spec.tsx index 06861a32b707a..5ad4d8b49a3be 100644 --- a/static/app/views/releases/detail/commitsAndFiles/filesChanged.spec.tsx +++ b/static/app/views/releases/detail/commitsAndFiles/filesChanged.spec.tsx @@ -7,7 +7,6 @@ import {initializeOrg} from 'sentry-test/initializeOrg'; import {render, screen} from 'sentry-test/reactTestingLibrary'; import selectEvent from 'sentry-test/selectEvent'; -import RepositoryStore from 'sentry/stores/repositoryStore'; import type {CommitFile} from 'sentry/types/integrations'; import type {ReleaseProject} from 'sentry/types/release'; import {ReleaseContext} from 'sentry/views/releases/detail'; @@ -30,7 +29,7 @@ function CommitFileFixture(params: Partial = {}): CommitFile { describe('FilesChanged', () => { const release = ReleaseFixture(); const project = ReleaseProjectFixture() as Required; - const {routerProps, router, organization} = initializeOrg({ + const {router, organization} = initializeOrg({ router: {params: {release: release.version}}, }); const repos = [RepositoryFixture({integrationId: '1'})]; @@ -48,12 +47,7 @@ describe('FilesChanged', () => { releaseMeta: {} as any, }} > - + , {router} ); @@ -65,7 +59,12 @@ describe('FilesChanged', () => { url: `/organizations/${organization.slug}/repos/`, body: repos, }); - RepositoryStore.init(); + MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${project.slug}/releases/${encodeURIComponent( + release.version + )}/repositories/`, + body: repos, + }); }); it('should render no repositories message', async () => { diff --git a/static/app/views/releases/detail/commitsAndFiles/filesChanged.tsx b/static/app/views/releases/detail/commitsAndFiles/filesChanged.tsx index cc56b5a7863a0..d1da9709edc91 100644 --- a/static/app/views/releases/detail/commitsAndFiles/filesChanged.tsx +++ b/static/app/views/releases/detail/commitsAndFiles/filesChanged.tsx @@ -1,4 +1,4 @@ -import {Fragment} from 'react'; +import {Fragment, useContext} from 'react'; import * as Layout from 'sentry/components/layouts/thirds'; import LoadingError from 'sentry/components/loadingError'; @@ -10,7 +10,6 @@ import PanelHeader from 'sentry/components/panels/panelHeader'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {t, tn} from 'sentry/locale'; import type {CommitFile, Repository} from 'sentry/types/integrations'; -import type {RouteComponentProps} from 'sentry/types/legacyReactRouter'; import type {Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; import {useApiQuery} from 'sentry/utils/queryClient'; @@ -18,27 +17,28 @@ import routeTitleGen from 'sentry/utils/routeTitle'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import {useParams} from 'sentry/utils/useParams'; +import {useReleaseRepositories} from 'sentry/utils/useReleaseRepositories'; +import {useRepositories} from 'sentry/utils/useRepositories'; import {formatVersion} from 'sentry/utils/versions/formatVersion'; +import {ReleaseContext} from 'sentry/views/releases/detail'; import {getFilesByRepository, getQuery, getReposToRender} from '../utils'; -import EmptyState from './emptyState'; +import {EmptyState, NoReleaseRepos, NoRepositories} from './emptyState'; import FileChange from './fileChange'; import RepositorySwitcher from './repositorySwitcher'; -import withReleaseRepos from './withReleaseRepos'; -// TODO(scttcper): Some props are no longer used, but required because of the HoC -interface FilesChangedProps extends RouteComponentProps<{release: string}, {}> { - orgSlug: Organization['slug']; +interface FilesChangedProps { + organization: Organization; projectSlug: Project['slug']; releaseRepos: Repository[]; - activeReleaseRepo?: Repository; } -function FilesChanged({activeReleaseRepo, releaseRepos, projectSlug}: FilesChangedProps) { +function FilesChangedList({organization, releaseRepos, projectSlug}: FilesChangedProps) { const location = useLocation(); const params = useParams<{release: string}>(); - const organization = useOrganization(); + const activeReleaseRepo = + releaseRepos.find(repo => repo.name === location.query.activeRepo) ?? releaseRepos[0]; const query = getQuery({location, activeRepository: activeReleaseRepo}); const { @@ -74,13 +74,15 @@ function FilesChanged({activeReleaseRepo, releaseRepos, projectSlug}: FilesChang )} /> - - {releaseRepos.length > 1 && ( + {releaseRepos.length > 1 && ( + - )} + + )} + {fileListError && } {isLoadingFileList ? ( @@ -129,4 +131,64 @@ function FilesChanged({activeReleaseRepo, releaseRepos, projectSlug}: FilesChang ); } -export default withReleaseRepos(FilesChanged); +function FilesChanged() { + const organization = useOrganization(); + const params = useParams<{release: string}>(); + const releaseContext = useContext(ReleaseContext); + const { + data: repositories, + isLoading: isLoadingRepositories, + isError: isRepositoriesError, + refetch: refetchRepositories, + } = useRepositories({ + orgSlug: organization.slug, + }); + const { + data: releaseRepos, + isLoading: isLoadingReleaseRepos, + isError: isReleaseReposError, + refetch: refetchReleaseRepos, + } = useReleaseRepositories({ + orgSlug: organization.slug, + projectSlug: releaseContext.project.slug, + release: params.release, + options: { + enabled: !!releaseContext.project.slug, + }, + }); + + if (isLoadingReleaseRepos || isLoadingRepositories) { + return ; + } + + if (isRepositoriesError || isReleaseReposError) { + return ( + { + refetchRepositories(); + refetchReleaseRepos(); + }} + /> + ); + } + + const noReleaseReposFound = !releaseRepos?.length; + if (noReleaseReposFound) { + return ; + } + + const noRepositoryOrgRelatedFound = !repositories?.length; + if (noRepositoryOrgRelatedFound) { + return ; + } + + return ( + + ); +} + +export default FilesChanged; diff --git a/static/app/views/releases/detail/commitsAndFiles/withReleaseRepos.tsx b/static/app/views/releases/detail/commitsAndFiles/withReleaseRepos.tsx deleted file mode 100644 index 92eb7f96507ff..0000000000000 --- a/static/app/views/releases/detail/commitsAndFiles/withReleaseRepos.tsx +++ /dev/null @@ -1,226 +0,0 @@ -import {Component} from 'react'; -import * as Sentry from '@sentry/react'; - -import {addErrorMessage} from 'sentry/actionCreators/indicator'; -import type {Client} from 'sentry/api'; -import {LinkButton} from 'sentry/components/button'; -import EmptyMessage from 'sentry/components/emptyMessage'; -import {Body, Main} from 'sentry/components/layouts/thirds'; -import LoadingIndicator from 'sentry/components/loadingIndicator'; -import Panel from 'sentry/components/panels/panel'; -import {IconCommit} from 'sentry/icons'; -import {t} from 'sentry/locale'; -import type {Repository} from 'sentry/types/integrations'; -import type {RouteComponentProps} from 'sentry/types/legacyReactRouter'; -import type {Organization} from 'sentry/types/organization'; -import getDisplayName from 'sentry/utils/getDisplayName'; -import withApi from 'sentry/utils/withApi'; -import withOrganization from 'sentry/utils/withOrganization'; -import withRepositories from 'sentry/utils/withRepositories'; - -import {ReleaseContext} from '..'; - -// These props are required when using this HoC -type DependentProps = RouteComponentProps<{release: string}, {}>; - -type HoCsProps = { - api: Client; - organization: Organization; - repositories?: Repository[]; - repositoriesError?: Error; - repositoriesLoading?: boolean; -}; - -type State = { - isLoading: boolean; - releaseRepos: Repository[]; - activeReleaseRepo?: Repository; -}; - -function withReleaseRepos

( - WrappedComponent: React.ComponentType

-) { - class WithReleaseRepos extends Component

{ - static displayName = `withReleaseRepos(${getDisplayName(WrappedComponent)})`; - - state: State = { - releaseRepos: [], - isLoading: true, - }; - - componentDidMount() { - this.fetchReleaseRepos(); - } - - componentDidUpdate(prevProps: P & HoCsProps, prevState: State) { - if ( - this.props.params.release !== prevProps.params.release || - (!!prevProps.repositoriesLoading && !this.props.repositoriesLoading) - ) { - this.fetchReleaseRepos(); - return; - } - - if ( - prevState.releaseRepos.length !== this.state.releaseRepos.length || - prevProps.location.query?.activeRepo !== this.props.location.query?.activeRepo - ) { - this.setActiveReleaseRepo(this.props); - } - } - - declare context: React.ContextType; - static contextType = ReleaseContext; - - setActiveReleaseRepo(props: P & HoCsProps) { - const {releaseRepos, activeReleaseRepo} = this.state; - - if (!releaseRepos.length) { - return; - } - - const activeCommitRepo = props.location.query?.activeRepo; - - if (!activeCommitRepo) { - this.setState({ - activeReleaseRepo: releaseRepos[0] ?? null, - }); - return; - } - - if (activeCommitRepo === activeReleaseRepo?.name) { - return; - } - - const matchedRepository = releaseRepos.find( - commitRepo => commitRepo.name === activeCommitRepo - ); - - if (matchedRepository) { - this.setState({ - activeReleaseRepo: matchedRepository, - }); - return; - } - - addErrorMessage(t('The repository you were looking for was not found.')); - } - - async fetchReleaseRepos() { - const {params, api, organization, repositories, repositoriesLoading} = this.props; - - if (repositoriesLoading === undefined || repositoriesLoading === true) { - return; - } - - if (!repositories?.length) { - this.setState({isLoading: false}); - return; - } - - const {release} = params; - const {project} = this.context; - - this.setState({isLoading: true}); - - try { - const releasePath = encodeURIComponent(release); - const releaseRepos = await api.requestPromise( - `/projects/${organization.slug}/${project.slug}/releases/${releasePath}/repositories/` - ); - this.setState({releaseRepos, isLoading: false}); - this.setActiveReleaseRepo(this.props); - } catch (error) { - Sentry.captureException(error); - addErrorMessage( - t( - 'An error occurred while trying to fetch the repositories of the release: %s', - release - ) - ); - } - } - - render() { - const {isLoading, activeReleaseRepo, releaseRepos} = this.state; - const {repositoriesLoading, repositories, params, router, location, organization} = - this.props; - - if (isLoading || repositoriesLoading) { - return ; - } - - const noRepositoryOrgRelatedFound = !repositories?.length; - - if (noRepositoryOrgRelatedFound) { - return ( - -

- - } - title={t('Releases are better with commit data!')} - description={t( - 'Connect a repository to see commit info, files changed, and authors involved in future releases.' - )} - action={ - - {t('Connect a repository')} - - } - /> - -
- - ); - } - - const noReleaseReposFound = !releaseRepos.length; - - if (noReleaseReposFound) { - return ( - -
- - } - title={t('Releases are better with commit data!')} - description={t( - 'No commits associated with this release have been found.' - )} - /> - -
- - ); - } - - if (activeReleaseRepo === undefined) { - return ; - } - - const {release} = params; - const orgSlug = organization.slug; - - return ( - - ); - } - } - - return withApi(withOrganization(withRepositories(WithReleaseRepos))); -} - -export default withReleaseRepos; diff --git a/static/app/views/releases/detail/overview/releaseIssues.tsx b/static/app/views/releases/detail/overview/releaseIssues.tsx index 13d7bb6e6081f..e12c770c6b41a 100644 --- a/static/app/views/releases/detail/overview/releaseIssues.tsx +++ b/static/app/views/releases/detail/overview/releaseIssues.tsx @@ -24,7 +24,7 @@ import {IssueSortOptions} from 'sentry/views/issueList/utils'; import type {ReleaseBounds} from '../../utils'; import {getReleaseParams} from '../../utils'; -import EmptyState from '../commitsAndFiles/emptyState'; +import {EmptyState} from '../commitsAndFiles/emptyState'; enum IssuesType { NEW = 'new',