index.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import {Fragment, useCallback, useState} from 'react';
  2. import {RouteComponentProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  5. import ExternalLink from 'sentry/components/links/externalLink';
  6. import Pagination from 'sentry/components/pagination';
  7. import PanelTable from 'sentry/components/panels/panelTable';
  8. import SearchBar from 'sentry/components/searchBar';
  9. import {t, tct} from 'sentry/locale';
  10. import {Organization, Project} from 'sentry/types';
  11. import {DebugFile} from 'sentry/types/debugFiles';
  12. import {useApiQuery} from 'sentry/utils/queryClient';
  13. import useApi from 'sentry/utils/useApi';
  14. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  15. import TextBlock from 'sentry/views/settings/components/text/textBlock';
  16. import ProjectProguardRow from './projectProguardRow';
  17. export type ProjectProguardProps = RouteComponentProps<{projectId: string}, {}> & {
  18. organization: Organization;
  19. project: Project;
  20. };
  21. function ProjectProguard({organization, location, router, params}: ProjectProguardProps) {
  22. const api = useApi();
  23. const {projectId} = params;
  24. const [loading, setLoading] = useState(false);
  25. const {
  26. data: mappings,
  27. isLoading: dataLoading,
  28. getResponseHeader,
  29. refetch: fetchData,
  30. } = useApiQuery<DebugFile[]>(
  31. [
  32. `/projects/${organization.slug}/${projectId}/files/dsyms/`,
  33. {query: {query: location.query.query, file_formats: 'proguard'}},
  34. ],
  35. {
  36. staleTime: 0,
  37. }
  38. );
  39. const mappingsPageLinks = getResponseHeader?.('Link');
  40. const handleSearch = useCallback(
  41. (query: string) => {
  42. router.push({
  43. ...location,
  44. query: {...location.query, cursor: undefined, query},
  45. });
  46. },
  47. [location, router]
  48. );
  49. const handleDelete = useCallback(
  50. async (id: string) => {
  51. setLoading(true);
  52. try {
  53. await api.requestPromise(
  54. `/projects/${
  55. organization.slug
  56. }/${projectId}/files/dsyms/?id=${encodeURIComponent(id)}`,
  57. {
  58. method: 'DELETE',
  59. }
  60. );
  61. setLoading(false);
  62. addSuccessMessage('Successfully deleted the mapping file');
  63. fetchData();
  64. } catch {
  65. setLoading(false);
  66. addErrorMessage('An error occurred while deleting the mapping file');
  67. }
  68. },
  69. [api, fetchData, organization.slug, projectId]
  70. );
  71. const query =
  72. typeof location.query.query === 'string' ? location.query.query : undefined;
  73. const isLoading = loading || dataLoading;
  74. return (
  75. <Fragment>
  76. <SettingsPageHeader
  77. title={t('ProGuard Mappings')}
  78. action={
  79. <SearchBar
  80. placeholder={t('Filter mappings')}
  81. onSearch={handleSearch}
  82. query={query}
  83. width="280px"
  84. />
  85. }
  86. />
  87. <TextBlock>
  88. {tct(
  89. `ProGuard mapping files are used to convert minified classes, methods and field names into a human readable format. To learn more about proguard mapping files, [link: read the docs].`,
  90. {
  91. link: (
  92. <ExternalLink href="https://docs.sentry.io/platforms/android/proguard/" />
  93. ),
  94. }
  95. )}
  96. </TextBlock>
  97. <StyledPanelTable
  98. headers={[t('Mapping'), <SizeColumn key="size">{t('File Size')}</SizeColumn>, '']}
  99. emptyMessage={
  100. query
  101. ? t('There are no mappings that match your search.')
  102. : t('There are no mappings for this project.')
  103. }
  104. isEmpty={mappings?.length === 0}
  105. isLoading={isLoading}
  106. >
  107. {!mappings?.length
  108. ? null
  109. : mappings.map(mapping => {
  110. const downloadUrl = `${api.baseUrl}/projects/${
  111. organization.slug
  112. }/${projectId}/files/dsyms/?id=${encodeURIComponent(mapping.id)}`;
  113. return (
  114. <ProjectProguardRow
  115. mapping={mapping}
  116. downloadUrl={downloadUrl}
  117. onDelete={handleDelete}
  118. downloadRole={organization.debugFilesRole}
  119. key={mapping.id}
  120. orgSlug={organization.slug}
  121. />
  122. );
  123. })}
  124. </StyledPanelTable>
  125. <Pagination pageLinks={mappingsPageLinks} />
  126. </Fragment>
  127. );
  128. }
  129. export default ProjectProguard;
  130. const StyledPanelTable = styled(PanelTable)`
  131. grid-template-columns: minmax(220px, 1fr) max-content 120px;
  132. `;
  133. const SizeColumn = styled('div')`
  134. text-align: right;
  135. `;