mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
194 lines
5.7 KiB
TypeScript
194 lines
5.7 KiB
TypeScript
import {Fragment, useCallback, useState} from 'react';
|
|
import type {RouteComponentProps} from 'react-router';
|
|
import styled from '@emotion/styled';
|
|
|
|
import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
|
|
import ExternalLink from 'sentry/components/links/externalLink';
|
|
import Pagination from 'sentry/components/pagination';
|
|
import PanelTable from 'sentry/components/panels/panelTable';
|
|
import SearchBar from 'sentry/components/searchBar';
|
|
import {t, tct} from 'sentry/locale';
|
|
import type {DebugIdBundleAssociation, Organization, Project} from 'sentry/types';
|
|
import type {DebugFile} from 'sentry/types/debugFiles';
|
|
import type {ApiQueryKey} from 'sentry/utils/queryClient';
|
|
import {useApiQuery, useQueries} from 'sentry/utils/queryClient';
|
|
import useApi from 'sentry/utils/useApi';
|
|
import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
|
|
import TextBlock from 'sentry/views/settings/components/text/textBlock';
|
|
|
|
import ProjectProguardRow from './projectProguardRow';
|
|
|
|
export type ProjectProguardProps = RouteComponentProps<{projectId: string}, {}> & {
|
|
organization: Organization;
|
|
project: Project;
|
|
};
|
|
|
|
export type ProguardMappingAssociation = {
|
|
releases: string[];
|
|
};
|
|
|
|
export type AssociatedMapping = {
|
|
mapping?: DebugFile;
|
|
releaseAssociation?: DebugIdBundleAssociation[];
|
|
};
|
|
|
|
function ProjectProguard({organization, location, router, params}: ProjectProguardProps) {
|
|
const api = useApi();
|
|
const {projectId} = params;
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const {
|
|
data: mappings,
|
|
isLoading: dataLoading,
|
|
getResponseHeader,
|
|
refetch: fetchData,
|
|
} = useApiQuery<DebugFile[]>(
|
|
[
|
|
`/projects/${organization.slug}/${projectId}/files/dsyms/`,
|
|
{
|
|
query: {
|
|
query: location.query.query,
|
|
file_formats: 'proguard',
|
|
cursor: location.query.cursor,
|
|
},
|
|
},
|
|
],
|
|
{
|
|
staleTime: 0,
|
|
}
|
|
);
|
|
|
|
const mappingsPageLinks = getResponseHeader?.('Link');
|
|
|
|
const associationsResults = useQueries({
|
|
queries:
|
|
mappings?.map(mapping => {
|
|
const queryKey = [
|
|
`/projects/${organization.slug}/${projectId}/files/proguard-artifact-releases/`,
|
|
{
|
|
query: {
|
|
proguard_uuid: mapping.uuid,
|
|
},
|
|
},
|
|
] as ApiQueryKey;
|
|
return {
|
|
queryKey,
|
|
queryFn: () =>
|
|
api.requestPromise(queryKey[0], {
|
|
method: 'GET',
|
|
query: queryKey[1]?.query,
|
|
}) as Promise<ProguardMappingAssociation>,
|
|
};
|
|
}) ?? [],
|
|
});
|
|
|
|
const associationsFetched = associationsResults.every(result => result.isFetched);
|
|
|
|
const handleSearch = useCallback(
|
|
(query: string) => {
|
|
router.push({
|
|
...location,
|
|
query: {...location.query, cursor: undefined, query: query || undefined},
|
|
});
|
|
},
|
|
[location, router]
|
|
);
|
|
|
|
const handleDelete = useCallback(
|
|
async (id: string) => {
|
|
setLoading(true);
|
|
try {
|
|
await api.requestPromise(
|
|
`/projects/${
|
|
organization.slug
|
|
}/${projectId}/files/dsyms/?id=${encodeURIComponent(id)}`,
|
|
{
|
|
method: 'DELETE',
|
|
}
|
|
);
|
|
setLoading(false);
|
|
addSuccessMessage('Successfully deleted the mapping file');
|
|
fetchData();
|
|
} catch {
|
|
setLoading(false);
|
|
addErrorMessage('An error occurred while deleting the mapping file');
|
|
}
|
|
},
|
|
[api, fetchData, organization.slug, projectId]
|
|
);
|
|
|
|
const query =
|
|
typeof location.query.query === 'string' ? location.query.query : undefined;
|
|
|
|
const isLoading = loading || dataLoading || !associationsFetched;
|
|
|
|
return (
|
|
<Fragment>
|
|
<SettingsPageHeader
|
|
title={t('ProGuard Mappings')}
|
|
action={
|
|
<SearchBar
|
|
placeholder={t('Filter mappings')}
|
|
onSearch={handleSearch}
|
|
query={query}
|
|
width="280px"
|
|
/>
|
|
}
|
|
/>
|
|
|
|
<TextBlock>
|
|
{tct(
|
|
`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].`,
|
|
{
|
|
link: (
|
|
<ExternalLink href="https://docs.sentry.io/platforms/android/proguard/" />
|
|
),
|
|
}
|
|
)}
|
|
</TextBlock>
|
|
|
|
<StyledPanelTable
|
|
headers={[t('Mapping'), <SizeColumn key="size">{t('File Size')}</SizeColumn>, '']}
|
|
emptyMessage={
|
|
query
|
|
? t('There are no mappings that match your search.')
|
|
: t('There are no mappings for this project.')
|
|
}
|
|
isEmpty={mappings?.length === 0}
|
|
isLoading={isLoading}
|
|
>
|
|
{!mappings?.length
|
|
? null
|
|
: mappings.map((mapping, index) => {
|
|
const downloadUrl = `${api.baseUrl}/projects/${
|
|
organization.slug
|
|
}/${projectId}/files/dsyms/?id=${encodeURIComponent(mapping.id)}`;
|
|
|
|
return (
|
|
<ProjectProguardRow
|
|
mapping={mapping}
|
|
associations={associationsResults[index].data}
|
|
downloadUrl={downloadUrl}
|
|
onDelete={handleDelete}
|
|
downloadRole={organization.debugFilesRole}
|
|
key={mapping.id}
|
|
orgSlug={organization.slug}
|
|
/>
|
|
);
|
|
})}
|
|
</StyledPanelTable>
|
|
<Pagination pageLinks={mappingsPageLinks} />
|
|
</Fragment>
|
|
);
|
|
}
|
|
|
|
export default ProjectProguard;
|
|
|
|
const StyledPanelTable = styled(PanelTable)`
|
|
grid-template-columns: minmax(220px, 1fr) max-content 120px;
|
|
`;
|
|
|
|
const SizeColumn = styled('div')`
|
|
text-align: right;
|
|
`;
|