mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
277 lines
7.5 KiB
TypeScript
277 lines
7.5 KiB
TypeScript
import {Fragment} from 'react';
|
|
import type {RouteComponentProps} from 'react-router';
|
|
import styled from '@emotion/styled';
|
|
|
|
import {addErrorMessage} from 'sentry/actionCreators/indicator';
|
|
import Checkbox from 'sentry/components/checkbox';
|
|
import Pagination from 'sentry/components/pagination';
|
|
import PanelTable from 'sentry/components/panels/panelTable';
|
|
import SearchBar from 'sentry/components/searchBar';
|
|
import {t} from 'sentry/locale';
|
|
import ProjectsStore from 'sentry/stores/projectsStore';
|
|
import {space} from 'sentry/styles/space';
|
|
import type {Organization, Project} from 'sentry/types';
|
|
import type {BuiltinSymbolSource, CustomRepo, DebugFile} from 'sentry/types/debugFiles';
|
|
import routeTitleGen from 'sentry/utils/routeTitle';
|
|
import DeprecatedAsyncView from 'sentry/views/deprecatedAsyncView';
|
|
import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
|
|
import TextBlock from 'sentry/views/settings/components/text/textBlock';
|
|
import PermissionAlert from 'sentry/views/settings/project/permissionAlert';
|
|
|
|
import DebugFileRow from './debugFileRow';
|
|
import Sources from './sources';
|
|
|
|
type Props = RouteComponentProps<{projectId: string}, {}> & {
|
|
organization: Organization;
|
|
project: Project;
|
|
};
|
|
|
|
type State = DeprecatedAsyncView['state'] & {
|
|
debugFiles: DebugFile[] | null;
|
|
project: Project;
|
|
showDetails: boolean;
|
|
builtinSymbolSources?: BuiltinSymbolSource[] | null;
|
|
};
|
|
|
|
class ProjectDebugSymbols extends DeprecatedAsyncView<Props, State> {
|
|
getTitle() {
|
|
const {projectId} = this.props.params;
|
|
|
|
return routeTitleGen(t('Debug Files'), projectId, false);
|
|
}
|
|
|
|
getDefaultState() {
|
|
return {
|
|
...super.getDefaultState(),
|
|
project: this.props.project,
|
|
showDetails: false,
|
|
};
|
|
}
|
|
|
|
getEndpoints(): ReturnType<DeprecatedAsyncView['getEndpoints']> {
|
|
const {organization, params, location} = this.props;
|
|
const {builtinSymbolSources} = this.state || {};
|
|
const {query} = location.query;
|
|
|
|
const endpoints: ReturnType<DeprecatedAsyncView['getEndpoints']> = [
|
|
[
|
|
'debugFiles',
|
|
`/projects/${organization.slug}/${params.projectId}/files/dsyms/`,
|
|
{
|
|
query: {query},
|
|
},
|
|
],
|
|
];
|
|
|
|
if (!builtinSymbolSources && organization.features.includes('symbol-sources')) {
|
|
endpoints.push([
|
|
'builtinSymbolSources',
|
|
`/organizations/${organization.slug}/builtin-symbol-sources/`,
|
|
{},
|
|
]);
|
|
}
|
|
|
|
return endpoints;
|
|
}
|
|
|
|
handleDelete = (id: string) => {
|
|
const {organization, params} = this.props;
|
|
|
|
this.setState({
|
|
loading: true,
|
|
});
|
|
|
|
this.api.request(
|
|
`/projects/${organization.slug}/${params.projectId}/files/dsyms/?id=${id}`,
|
|
{
|
|
method: 'DELETE',
|
|
complete: () => this.fetchData(),
|
|
}
|
|
);
|
|
};
|
|
|
|
handleSearch = (query: string) => {
|
|
const {location, router} = this.props;
|
|
|
|
router.push({
|
|
...location,
|
|
query: {...location.query, cursor: undefined, query},
|
|
});
|
|
};
|
|
|
|
async fetchProject() {
|
|
const {organization, params} = this.props;
|
|
try {
|
|
const updatedProject = await this.api.requestPromise(
|
|
`/projects/${organization.slug}/${params.projectId}/`
|
|
);
|
|
ProjectsStore.onUpdateSuccess(updatedProject);
|
|
} catch {
|
|
addErrorMessage(t('An error occurred while fetching project data'));
|
|
}
|
|
}
|
|
|
|
getQuery() {
|
|
const {query} = this.props.location.query;
|
|
|
|
return typeof query === 'string' ? query : undefined;
|
|
}
|
|
|
|
getEmptyMessage() {
|
|
if (this.getQuery()) {
|
|
return t('There are no debug symbols that match your search.');
|
|
}
|
|
|
|
return t('There are no debug symbols for this project.');
|
|
}
|
|
|
|
renderLoading() {
|
|
return this.renderBody();
|
|
}
|
|
|
|
renderDebugFiles() {
|
|
const {debugFiles, showDetails} = this.state;
|
|
const {organization, params, project} = this.props;
|
|
|
|
if (!debugFiles?.length) {
|
|
return null;
|
|
}
|
|
|
|
return debugFiles.map(debugFile => {
|
|
const downloadUrl = `${this.api.baseUrl}/projects/${organization.slug}/${params.projectId}/files/dsyms/?id=${debugFile.id}`;
|
|
|
|
return (
|
|
<DebugFileRow
|
|
debugFile={debugFile}
|
|
showDetails={showDetails}
|
|
downloadUrl={downloadUrl}
|
|
downloadRole={organization.debugFilesRole}
|
|
onDelete={this.handleDelete}
|
|
key={debugFile.id}
|
|
orgSlug={organization.slug}
|
|
project={project}
|
|
/>
|
|
);
|
|
});
|
|
}
|
|
|
|
renderBody() {
|
|
const {organization, project, router, location} = this.props;
|
|
const {loading, showDetails, builtinSymbolSources, debugFiles, debugFilesPageLinks} =
|
|
this.state;
|
|
|
|
return (
|
|
<Fragment>
|
|
<SettingsPageHeader title={t('Debug Information Files')} />
|
|
|
|
<TextBlock>
|
|
{t(`
|
|
Debug information files are used to convert addresses and minified
|
|
function names from native crash reports into function names and
|
|
locations.
|
|
`)}
|
|
</TextBlock>
|
|
|
|
{organization.features.includes('symbol-sources') && (
|
|
<Fragment>
|
|
<PermissionAlert project={project} />
|
|
|
|
<Sources
|
|
api={this.api}
|
|
location={location}
|
|
router={router}
|
|
project={project}
|
|
organization={organization}
|
|
customRepositories={
|
|
(project.symbolSources
|
|
? JSON.parse(project.symbolSources)
|
|
: []) as CustomRepo[]
|
|
}
|
|
builtinSymbolSources={project.builtinSymbolSources ?? []}
|
|
builtinSymbolSourceOptions={builtinSymbolSources ?? []}
|
|
isLoading={loading}
|
|
/>
|
|
</Fragment>
|
|
)}
|
|
|
|
<Wrapper>
|
|
<TextBlock noMargin>{t('Uploaded debug information files')}</TextBlock>
|
|
<Filters>
|
|
<Label>
|
|
<Checkbox
|
|
checked={showDetails}
|
|
onChange={e => {
|
|
this.setState({showDetails: (e.target as HTMLInputElement).checked});
|
|
}}
|
|
/>
|
|
{t('show details')}
|
|
</Label>
|
|
|
|
<SearchBar
|
|
placeholder={t('Search DIFs')}
|
|
onSearch={this.handleSearch}
|
|
query={this.getQuery()}
|
|
/>
|
|
</Filters>
|
|
</Wrapper>
|
|
|
|
<StyledPanelTable
|
|
headers={[
|
|
t('Debug ID'),
|
|
t('Information'),
|
|
<Actions key="actions">{t('Actions')}</Actions>,
|
|
]}
|
|
emptyMessage={this.getEmptyMessage()}
|
|
isEmpty={debugFiles?.length === 0}
|
|
isLoading={loading}
|
|
>
|
|
{this.renderDebugFiles()}
|
|
</StyledPanelTable>
|
|
<Pagination pageLinks={debugFilesPageLinks} />
|
|
</Fragment>
|
|
);
|
|
}
|
|
}
|
|
|
|
const StyledPanelTable = styled(PanelTable)`
|
|
grid-template-columns: 37% 1fr auto;
|
|
`;
|
|
|
|
const Actions = styled('div')`
|
|
text-align: right;
|
|
`;
|
|
|
|
const Wrapper = styled('div')`
|
|
display: grid;
|
|
grid-template-columns: auto 1fr;
|
|
gap: ${space(4)};
|
|
align-items: center;
|
|
margin-top: ${space(4)};
|
|
margin-bottom: ${space(1)};
|
|
@media (max-width: ${p => p.theme.breakpoints.small}) {
|
|
display: block;
|
|
}
|
|
`;
|
|
|
|
const Filters = styled('div')`
|
|
display: grid;
|
|
grid-template-columns: min-content minmax(200px, 400px);
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
gap: ${space(2)};
|
|
@media (max-width: ${p => p.theme.breakpoints.small}) {
|
|
grid-template-columns: min-content 1fr;
|
|
}
|
|
`;
|
|
|
|
const Label = styled('label')`
|
|
font-weight: normal;
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 0;
|
|
white-space: nowrap;
|
|
gap: ${space(1)};
|
|
`;
|
|
|
|
export default ProjectDebugSymbols;
|