projectProguard.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import {Fragment} from 'react';
  2. import {RouteComponentProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import ExternalLink from 'sentry/components/links/externalLink';
  5. import Pagination from 'sentry/components/pagination';
  6. import {PanelTable} from 'sentry/components/panels';
  7. import SearchBar from 'sentry/components/searchBar';
  8. import {t, tct} from 'sentry/locale';
  9. import {Organization, Project} from 'sentry/types';
  10. import {DebugFile} from 'sentry/types/debugFiles';
  11. import routeTitleGen from 'sentry/utils/routeTitle';
  12. import AsyncView from 'sentry/views/asyncView';
  13. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  14. import TextBlock from 'sentry/views/settings/components/text/textBlock';
  15. import ProjectProguardRow from './projectProguardRow';
  16. type Props = RouteComponentProps<{orgId: string; projectId: string}, {}> & {
  17. organization: Organization;
  18. project: Project;
  19. };
  20. type State = AsyncView['state'] & {
  21. mappings: DebugFile[];
  22. };
  23. class ProjectProguard extends AsyncView<Props, State> {
  24. getTitle() {
  25. const {projectId} = this.props.params;
  26. return routeTitleGen(t('ProGuard Mappings'), projectId, false);
  27. }
  28. getDefaultState(): State {
  29. return {
  30. ...super.getDefaultState(),
  31. mappings: [],
  32. };
  33. }
  34. getEndpoints(): ReturnType<AsyncView['getEndpoints']> {
  35. const {params, location} = this.props;
  36. const {orgId, projectId} = params;
  37. const endpoints: ReturnType<AsyncView['getEndpoints']> = [
  38. [
  39. 'mappings',
  40. `/projects/${orgId}/${projectId}/files/dsyms/`,
  41. {query: {query: location.query.query, file_formats: 'proguard'}},
  42. ],
  43. ];
  44. return endpoints;
  45. }
  46. handleDelete = (id: string) => {
  47. const {orgId, projectId} = this.props.params;
  48. this.setState({
  49. loading: true,
  50. });
  51. this.api.request(
  52. `/projects/${orgId}/${projectId}/files/dsyms/?id=${encodeURIComponent(id)}`,
  53. {
  54. method: 'DELETE',
  55. complete: () => this.fetchData(),
  56. }
  57. );
  58. };
  59. handleSearch = (query: string) => {
  60. const {location, router} = this.props;
  61. router.push({
  62. ...location,
  63. query: {...location.query, cursor: undefined, query},
  64. });
  65. };
  66. getQuery() {
  67. const {query} = this.props.location.query;
  68. return typeof query === 'string' ? query : undefined;
  69. }
  70. getEmptyMessage() {
  71. if (this.getQuery()) {
  72. return t('There are no mappings that match your search.');
  73. }
  74. return t('There are no mappings for this project.');
  75. }
  76. renderLoading() {
  77. return this.renderBody();
  78. }
  79. renderMappings() {
  80. const {mappings} = this.state;
  81. const {organization, params} = this.props;
  82. const {orgId, projectId} = params;
  83. if (!mappings?.length) {
  84. return null;
  85. }
  86. return mappings.map(mapping => {
  87. const downloadUrl = `${
  88. this.api.baseUrl
  89. }/projects/${orgId}/${projectId}/files/dsyms/?id=${encodeURIComponent(mapping.id)}`;
  90. return (
  91. <ProjectProguardRow
  92. mapping={mapping}
  93. downloadUrl={downloadUrl}
  94. onDelete={this.handleDelete}
  95. downloadRole={organization.debugFilesRole}
  96. key={mapping.id}
  97. orgSlug={organization.slug}
  98. />
  99. );
  100. });
  101. }
  102. renderBody() {
  103. const {loading, mappings, mappingsPageLinks} = this.state;
  104. return (
  105. <Fragment>
  106. <SettingsPageHeader
  107. title={t('ProGuard Mappings')}
  108. action={
  109. <SearchBar
  110. placeholder={t('Filter mappings')}
  111. onSearch={this.handleSearch}
  112. query={this.getQuery()}
  113. width="280px"
  114. />
  115. }
  116. />
  117. <TextBlock>
  118. {tct(
  119. `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].`,
  120. {
  121. link: (
  122. <ExternalLink href="https://docs.sentry.io/platforms/android/proguard/" />
  123. ),
  124. }
  125. )}
  126. </TextBlock>
  127. <StyledPanelTable
  128. headers={[
  129. t('Mapping'),
  130. <SizeColumn key="size">{t('File Size')}</SizeColumn>,
  131. '',
  132. ]}
  133. emptyMessage={this.getEmptyMessage()}
  134. isEmpty={mappings?.length === 0}
  135. isLoading={loading}
  136. >
  137. {this.renderMappings()}
  138. </StyledPanelTable>
  139. <Pagination pageLinks={mappingsPageLinks} />
  140. </Fragment>
  141. );
  142. }
  143. }
  144. const StyledPanelTable = styled(PanelTable)`
  145. grid-template-columns: minmax(220px, 1fr) max-content 120px;
  146. `;
  147. const SizeColumn = styled('div')`
  148. text-align: right;
  149. `;
  150. export default ProjectProguard;