projectProguard.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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<{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 {organization, params, location} = this.props;
  36. const {projectId} = params;
  37. const endpoints: ReturnType<AsyncView['getEndpoints']> = [
  38. [
  39. 'mappings',
  40. `/projects/${organization.slug}/${projectId}/files/dsyms/`,
  41. {query: {query: location.query.query, file_formats: 'proguard'}},
  42. ],
  43. ];
  44. return endpoints;
  45. }
  46. handleDelete = (id: string) => {
  47. const {organization} = this.props;
  48. const {projectId} = this.props.params;
  49. this.setState({
  50. loading: true,
  51. });
  52. this.api.request(
  53. `/projects/${organization.slug}/${projectId}/files/dsyms/?id=${encodeURIComponent(
  54. id
  55. )}`,
  56. {
  57. method: 'DELETE',
  58. complete: () => this.fetchData(),
  59. }
  60. );
  61. };
  62. handleSearch = (query: string) => {
  63. const {location, router} = this.props;
  64. router.push({
  65. ...location,
  66. query: {...location.query, cursor: undefined, query},
  67. });
  68. };
  69. getQuery() {
  70. const {query} = this.props.location.query;
  71. return typeof query === 'string' ? query : undefined;
  72. }
  73. getEmptyMessage() {
  74. if (this.getQuery()) {
  75. return t('There are no mappings that match your search.');
  76. }
  77. return t('There are no mappings for this project.');
  78. }
  79. renderLoading() {
  80. return this.renderBody();
  81. }
  82. renderMappings() {
  83. const {mappings} = this.state;
  84. const {organization, params} = this.props;
  85. const {projectId} = params;
  86. if (!mappings?.length) {
  87. return null;
  88. }
  89. return mappings.map(mapping => {
  90. const downloadUrl = `${this.api.baseUrl}/projects/${
  91. organization.slug
  92. }/${projectId}/files/dsyms/?id=${encodeURIComponent(mapping.id)}`;
  93. return (
  94. <ProjectProguardRow
  95. mapping={mapping}
  96. downloadUrl={downloadUrl}
  97. onDelete={this.handleDelete}
  98. downloadRole={organization.debugFilesRole}
  99. key={mapping.id}
  100. orgSlug={organization.slug}
  101. />
  102. );
  103. });
  104. }
  105. renderBody() {
  106. const {loading, mappings, mappingsPageLinks} = this.state;
  107. return (
  108. <Fragment>
  109. <SettingsPageHeader
  110. title={t('ProGuard Mappings')}
  111. action={
  112. <SearchBar
  113. placeholder={t('Filter mappings')}
  114. onSearch={this.handleSearch}
  115. query={this.getQuery()}
  116. width="280px"
  117. />
  118. }
  119. />
  120. <TextBlock>
  121. {tct(
  122. `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].`,
  123. {
  124. link: (
  125. <ExternalLink href="https://docs.sentry.io/platforms/android/proguard/" />
  126. ),
  127. }
  128. )}
  129. </TextBlock>
  130. <StyledPanelTable
  131. headers={[
  132. t('Mapping'),
  133. <SizeColumn key="size">{t('File Size')}</SizeColumn>,
  134. '',
  135. ]}
  136. emptyMessage={this.getEmptyMessage()}
  137. isEmpty={mappings?.length === 0}
  138. isLoading={loading}
  139. >
  140. {this.renderMappings()}
  141. </StyledPanelTable>
  142. <Pagination pageLinks={mappingsPageLinks} />
  143. </Fragment>
  144. );
  145. }
  146. }
  147. const StyledPanelTable = styled(PanelTable)`
  148. grid-template-columns: minmax(220px, 1fr) max-content 120px;
  149. `;
  150. const SizeColumn = styled('div')`
  151. text-align: right;
  152. `;
  153. export default ProjectProguard;