integrationRepos.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import {Fragment, useState} from 'react';
  2. import {Alert} from 'sentry/components/alert';
  3. import {LinkButton} from 'sentry/components/button';
  4. import EmptyMessage from 'sentry/components/emptyMessage';
  5. import LoadingError from 'sentry/components/loadingError';
  6. import LoadingIndicator from 'sentry/components/loadingIndicator';
  7. import Pagination from 'sentry/components/pagination';
  8. import Panel from 'sentry/components/panels/panel';
  9. import PanelBody from 'sentry/components/panels/panelBody';
  10. import PanelHeader from 'sentry/components/panels/panelHeader';
  11. import RepositoryRow from 'sentry/components/repositoryRow';
  12. import {IconCommit} from 'sentry/icons';
  13. import {t} from 'sentry/locale';
  14. import RepositoryStore from 'sentry/stores/repositoryStore';
  15. import type {Integration, Repository} from 'sentry/types/integrations';
  16. import {useApiQuery} from 'sentry/utils/queryClient';
  17. import useOrganization from 'sentry/utils/useOrganization';
  18. import {IntegrationReposAddRepository} from './integrationReposAddRepository';
  19. type Props = {
  20. integration: Integration;
  21. };
  22. function IntegrationRepos(props: Props) {
  23. const [integrationReposErrorStatus, setIntegrationReposeErrorStatus] = useState<
  24. number | null | undefined
  25. >(null);
  26. const {integration} = props;
  27. const organization = useOrganization();
  28. const ENDPOINT = `/organizations/${organization.slug}/repos/`;
  29. const {
  30. data: fetchedItemList,
  31. isPending,
  32. isError,
  33. refetch,
  34. getResponseHeader,
  35. } = useApiQuery<Repository[]>(
  36. [ENDPOINT, {query: {status: 'active', integration_id: integration.id}}],
  37. {
  38. staleTime: 0,
  39. }
  40. );
  41. const [itemListState, setItemList] = useState<Repository[]>([]);
  42. if (isPending) {
  43. return <LoadingIndicator />;
  44. }
  45. if (isError) {
  46. return <LoadingError onRetry={refetch} />;
  47. }
  48. const itemList = itemListState.length ? itemListState : fetchedItemList;
  49. // Called by row to signal repository change.
  50. const onRepositoryChange = (data: Repository) => {
  51. const newItemList = itemList.map(item => {
  52. if (item.id === data.id) {
  53. item.status = data.status;
  54. // allow for custom scm repositories to be updated, and
  55. // url is optional and therefore can be an empty string
  56. item.url = data.url === undefined ? item.url : data.url;
  57. item.name = data.name || item.name;
  58. }
  59. return item;
  60. });
  61. setItemList(newItemList);
  62. RepositoryStore.resetRepositories();
  63. };
  64. const handleAddRepository = (repo: Repository) => {
  65. setItemList([...itemList, repo]);
  66. };
  67. const itemListPageLinks = getResponseHeader?.('Link') ?? undefined;
  68. return (
  69. <Fragment>
  70. {integrationReposErrorStatus === 400 && (
  71. <Alert type="error" showIcon>
  72. {t(
  73. 'We were unable to fetch repositories for this integration. Try again later. If this error continues, please reconnect this integration by uninstalling and then reinstalling.'
  74. )}
  75. </Alert>
  76. )}
  77. <Panel>
  78. <PanelHeader hasButtons>
  79. <div>{t('Repositories')}</div>
  80. <IntegrationReposAddRepository
  81. integration={integration}
  82. currentRepositories={itemList}
  83. onSearchError={setIntegrationReposeErrorStatus}
  84. onAddRepository={handleAddRepository}
  85. />
  86. </PanelHeader>
  87. <PanelBody>
  88. {itemList.length === 0 && (
  89. <EmptyMessage
  90. icon={<IconCommit />}
  91. title={t('Sentry is better with commit data')}
  92. description={t(
  93. 'Add a repository to begin tracking its commit data. Then, set up release tracking to unlock features like suspect commits, suggested issue owners, and deploy emails.'
  94. )}
  95. action={
  96. <LinkButton href="https://docs.sentry.io/product/releases/" external>
  97. {t('Learn More')}
  98. </LinkButton>
  99. }
  100. />
  101. )}
  102. {itemList.map(repo => (
  103. <RepositoryRow
  104. key={repo.id}
  105. repository={repo}
  106. orgSlug={organization.slug}
  107. onRepositoryChange={onRepositoryChange}
  108. />
  109. ))}
  110. </PanelBody>
  111. </Panel>
  112. <Pagination pageLinks={itemListPageLinks} />
  113. </Fragment>
  114. );
  115. }
  116. export default IntegrationRepos;