Skip to content

Commit 3da5a26

Browse files
authored
feat(ui): Refresh ProGuard settings UI (#23835)
1 parent d825728 commit 3da5a26

File tree

3 files changed

+158
-81
lines changed

3 files changed

+158
-81
lines changed

src/sentry/static/sentry/app/views/settings/project/navigationConfiguration.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,15 @@ export default function getConfiguration({
101101
path: `${pathPrefix}/debug-symbols/`,
102102
title: t('Debug Files'),
103103
},
104-
{
105-
path: `${pathPrefix}/source-maps/`,
106-
title: t('Source Maps'),
107-
},
108104
{
109105
path: `${pathPrefix}/proguard/`,
110106
title: t('ProGuard'),
111107
show: () => !!organization?.features?.includes('android-mappings'),
112108
},
109+
{
110+
path: `${pathPrefix}/source-maps/`,
111+
title: t('Source Maps'),
112+
},
113113
],
114114
},
115115
{

src/sentry/static/sentry/app/views/settings/projectProguard/projectProguard.tsx

Lines changed: 32 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,19 @@ import React from 'react';
22
import {RouteComponentProps} from 'react-router';
33
import styled from '@emotion/styled';
44

5-
import Checkbox from 'app/components/checkbox';
5+
import ExternalLink from 'app/components/links/externalLink';
66
import Pagination from 'app/components/pagination';
77
import {PanelTable} from 'app/components/panels';
88
import SearchBar from 'app/components/searchBar';
9-
import {t} from 'app/locale';
10-
import space from 'app/styles/space';
9+
import {t, tct} from 'app/locale';
1110
import {Organization, Project} from 'app/types';
1211
import {DebugFile} from 'app/types/debugFiles';
1312
import routeTitleGen from 'app/utils/routeTitle';
1413
import AsyncView from 'app/views/asyncView';
1514
import SettingsPageHeader from 'app/views/settings/components/settingsPageHeader';
1615
import TextBlock from 'app/views/settings/components/text/textBlock';
17-
// TODO(android-mappings): use own components once we decide how this should look like
18-
import DebugFileRow from 'app/views/settings/projectDebugFiles/debugFileRow';
16+
17+
import ProjectProguardRow from './projectProguardRow';
1918

2019
type Props = RouteComponentProps<{orgId: string; projectId: string}, {}> & {
2120
organization: Organization;
@@ -24,7 +23,6 @@ type Props = RouteComponentProps<{orgId: string; projectId: string}, {}> & {
2423

2524
type State = AsyncView['state'] & {
2625
mappings: DebugFile[];
27-
showDetails: boolean;
2826
};
2927

3028
class ProjectProguard extends AsyncView<Props, State> {
@@ -38,7 +36,6 @@ class ProjectProguard extends AsyncView<Props, State> {
3836
return {
3937
...super.getDefaultState(),
4038
mappings: [],
41-
showDetails: false,
4239
};
4340
}
4441

@@ -101,7 +98,7 @@ class ProjectProguard extends AsyncView<Props, State> {
10198
}
10299

103100
renderMappings() {
104-
const {mappings, showDetails} = this.state;
101+
const {mappings} = this.state;
105102
const {organization, params} = this.props;
106103
const {orgId, projectId} = params;
107104

@@ -115,58 +112,50 @@ class ProjectProguard extends AsyncView<Props, State> {
115112
}/projects/${orgId}/${projectId}/files/dsyms/?id=${encodeURIComponent(mapping.id)}`;
116113

117114
return (
118-
<DebugFileRow
119-
debugFile={mapping}
120-
showDetails={showDetails}
115+
<ProjectProguardRow
116+
mapping={mapping}
121117
downloadUrl={downloadUrl}
122-
downloadRole={organization.debugFilesRole}
123118
onDelete={this.handleDelete}
119+
downloadRole={organization.debugFilesRole}
124120
key={mapping.id}
125121
/>
126122
);
127123
});
128124
}
129125

130126
renderBody() {
131-
const {loading, showDetails, mappings, mappingsPageLinks} = this.state;
127+
const {loading, mappings, mappingsPageLinks} = this.state;
132128

133129
return (
134130
<React.Fragment>
135-
<SettingsPageHeader title={t('ProGuard Mappings')} />
136-
137-
<TextBlock>
138-
{t(
139-
`ProGuard mapping files are used to convert minified classes, methods and field names into a human readable format.`
140-
)}
141-
</TextBlock>
142-
143-
<Wrapper>
144-
<TextBlock noMargin>{t('Uploaded mappings')}:</TextBlock>
145-
146-
<Filters>
147-
<Label>
148-
<Checkbox
149-
checked={showDetails}
150-
onChange={e => {
151-
this.setState({showDetails: (e.target as HTMLInputElement).checked});
152-
}}
153-
/>
154-
{t('show details')}
155-
</Label>
156-
131+
<SettingsPageHeader
132+
title={t('ProGuard Mappings')}
133+
action={
157134
<SearchBar
158-
placeholder={t('Search mappings')}
135+
placeholder={t('Filter mappings')}
159136
onSearch={this.handleSearch}
160137
query={this.getQuery()}
138+
width="280px"
161139
/>
162-
</Filters>
163-
</Wrapper>
140+
}
141+
/>
142+
143+
<TextBlock>
144+
{tct(
145+
`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].`,
146+
{
147+
link: (
148+
<ExternalLink href="https://docs.sentry.io/platforms/android/proguard/" />
149+
),
150+
}
151+
)}
152+
</TextBlock>
164153

165154
<StyledPanelTable
166155
headers={[
167-
t('Debug ID'),
168-
t('Information'),
169-
<Actions key="actions">{t('Actions')}</Actions>,
156+
t('Mapping'),
157+
<SizeColumn key="size">{t('File Size')}</SizeColumn>,
158+
'',
170159
]}
171160
emptyMessage={this.getEmptyMessage()}
172161
isEmpty={mappings?.length === 0}
@@ -181,45 +170,11 @@ class ProjectProguard extends AsyncView<Props, State> {
181170
}
182171

183172
const StyledPanelTable = styled(PanelTable)`
184-
grid-template-columns: 37% 1fr auto;
173+
grid-template-columns: minmax(220px, 1fr) max-content 120px;
185174
`;
186175

187-
const Actions = styled('div')`
176+
const SizeColumn = styled('div')`
188177
text-align: right;
189178
`;
190179

191-
const Wrapper = styled('div')`
192-
display: grid;
193-
grid-template-columns: auto 1fr;
194-
grid-gap: ${space(4)};
195-
align-items: center;
196-
margin-top: ${space(4)};
197-
margin-bottom: ${space(1)};
198-
@media (max-width: ${p => p.theme.breakpoints[0]}) {
199-
display: block;
200-
}
201-
`;
202-
203-
const Filters = styled('div')`
204-
display: grid;
205-
grid-template-columns: min-content minmax(200px, 400px);
206-
align-items: center;
207-
justify-content: flex-end;
208-
grid-gap: ${space(2)};
209-
@media (max-width: ${p => p.theme.breakpoints[0]}) {
210-
grid-template-columns: min-content 1fr;
211-
}
212-
`;
213-
214-
const Label = styled('label')`
215-
font-weight: normal;
216-
display: flex;
217-
margin-bottom: 0;
218-
white-space: nowrap;
219-
input {
220-
margin-top: 0;
221-
margin-right: ${space(1)};
222-
}
223-
`;
224-
225180
export default ProjectProguard;
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import React from 'react';
2+
import styled from '@emotion/styled';
3+
4+
import Access from 'app/components/acl/access';
5+
import Role from 'app/components/acl/role';
6+
import Button from 'app/components/button';
7+
import ButtonBar from 'app/components/buttonBar';
8+
import Confirm from 'app/components/confirm';
9+
import FileSize from 'app/components/fileSize';
10+
import TimeSince from 'app/components/timeSince';
11+
import Tooltip from 'app/components/tooltip';
12+
import {IconClock, IconDelete, IconDownload} from 'app/icons';
13+
import {t} from 'app/locale';
14+
import space from 'app/styles/space';
15+
import {DebugFile} from 'app/types/debugFiles';
16+
17+
type Props = {
18+
mapping: DebugFile;
19+
onDelete: (id: string) => void;
20+
downloadUrl: string;
21+
downloadRole: string;
22+
};
23+
24+
const ProjectProguardRow = ({mapping, onDelete, downloadUrl, downloadRole}: Props) => {
25+
const {id, debugId, uuid, size, dateCreated} = mapping;
26+
27+
const handleDeleteClick = () => {
28+
onDelete(id);
29+
};
30+
31+
return (
32+
<React.Fragment>
33+
<NameColumn>
34+
<Name>{debugId || uuid || `(${t('empty')})`}</Name>
35+
<TimeWrapper>
36+
<IconClock size="sm" />
37+
<TimeSince date={dateCreated} />
38+
</TimeWrapper>
39+
</NameColumn>
40+
<SizeColumn>
41+
<FileSize bytes={size} />
42+
</SizeColumn>
43+
<ActionsColumn>
44+
<ButtonBar gap={0.5}>
45+
<Role role={downloadRole}>
46+
{({hasRole}) => (
47+
<Tooltip
48+
title={t('You do not have permission to download mappings.')}
49+
disabled={hasRole}
50+
>
51+
<Button
52+
size="small"
53+
icon={<IconDownload size="sm" />}
54+
disabled={!hasRole}
55+
href={downloadUrl}
56+
title={t('Download Mapping')}
57+
/>
58+
</Tooltip>
59+
)}
60+
</Role>
61+
62+
<Access access={['project:releases']}>
63+
{({hasAccess}) => (
64+
<Tooltip
65+
disabled={hasAccess}
66+
title={t('You do not have permission to delete mappings.')}
67+
>
68+
<Confirm
69+
message={t('Are you sure you want to remove this mapping?')}
70+
onConfirm={handleDeleteClick}
71+
disabled={!hasAccess}
72+
>
73+
<Button
74+
size="small"
75+
icon={<IconDelete size="sm" />}
76+
title={t('Remove Mapping')}
77+
label={t('Remove Mapping')}
78+
disabled={!hasAccess}
79+
/>
80+
</Confirm>
81+
</Tooltip>
82+
)}
83+
</Access>
84+
</ButtonBar>
85+
</ActionsColumn>
86+
</React.Fragment>
87+
);
88+
};
89+
90+
const NameColumn = styled('div')`
91+
display: flex;
92+
flex-direction: column;
93+
align-items: flex-start;
94+
justify-content: center;
95+
`;
96+
97+
const SizeColumn = styled('div')`
98+
display: flex;
99+
justify-content: flex-end;
100+
text-align: right;
101+
align-items: center;
102+
`;
103+
104+
const ActionsColumn = styled(SizeColumn)``;
105+
106+
const Name = styled('div')`
107+
padding-right: ${space(4)};
108+
overflow-wrap: break-word;
109+
word-break: break-all;
110+
`;
111+
112+
const TimeWrapper = styled('div')`
113+
display: grid;
114+
grid-gap: ${space(0.5)};
115+
grid-template-columns: min-content 1fr;
116+
font-size: ${p => p.theme.fontSizeMedium};
117+
align-items: center;
118+
color: ${p => p.theme.subText};
119+
margin-top: ${space(1)};
120+
`;
121+
122+
export default ProjectProguardRow;

0 commit comments

Comments
 (0)