123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- import {Fragment, useCallback} from 'react';
- import {RouteComponentProps} from 'react-router';
- import styled from '@emotion/styled';
- import {Role} from 'sentry/components/acl/role';
- import {Button} from 'sentry/components/button';
- import FileSize from 'sentry/components/fileSize';
- import Link from 'sentry/components/links/link';
- import Pagination from 'sentry/components/pagination';
- import {PanelTable} from 'sentry/components/panels';
- import SearchBar from 'sentry/components/searchBar';
- import Tag from 'sentry/components/tag';
- import TimeSince from 'sentry/components/timeSince';
- import {Tooltip} from 'sentry/components/tooltip';
- import {IconClock, IconDownload} from 'sentry/icons';
- import {t, tct} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import {Project} from 'sentry/types';
- import {useQuery} from 'sentry/utils/queryClient';
- import {decodeScalar} from 'sentry/utils/queryString';
- import useApi from 'sentry/utils/useApi';
- import useOrganization from 'sentry/utils/useOrganization';
- type Props = RouteComponentProps<{bundleId: string}, {}> & {
- project: Project;
- tab: 'release' | 'debug-id';
- };
- export function ProjectSourceMapsArtifacts({
- params,
- location,
- router,
- project,
- tab,
- }: Props) {
- const api = useApi();
- const organization = useOrganization();
- const tabDebugIdBundlesActive = tab === 'debug-id';
- const query = decodeScalar(location.query.query);
- const cursor = location.query.cursor ?? '';
- const downloadRole = organization.debugFilesRole;
- const artifactsEndpoint = `/projects/${organization.slug}/${
- project.slug
- }/releases/${encodeURIComponent(params.bundleId)}/files/`;
- const debugIdBundlesEndpoint = ``;
- const {data: artifactsData, isLoading: artifactsLoading} = useQuery(
- [
- artifactsEndpoint,
- {
- query: {query, cursor},
- },
- ],
- () => {
- return api.requestPromise(artifactsEndpoint, {
- query: {query, cursor},
- includeAllArgs: true,
- });
- },
- {
- staleTime: 0,
- keepPreviousData: true,
- enabled: !tabDebugIdBundlesActive,
- }
- );
- const {data: debugIdBundlesData, isLoading: debugIdBundlesLoading} = useQuery(
- [
- debugIdBundlesEndpoint,
- {
- query: {query, cursor},
- },
- ],
- () => {
- return api.requestPromise(debugIdBundlesEndpoint, {
- query: {query, cursor},
- includeAllArgs: true,
- });
- },
- {
- staleTime: 0,
- keepPreviousData: true,
- enabled: tabDebugIdBundlesActive,
- }
- );
- const data = tabDebugIdBundlesActive
- ? debugIdBundlesData?.[0] ?? []
- : artifactsData?.[0] ?? [];
- const pageLinks = tabDebugIdBundlesActive
- ? debugIdBundlesData?.[2]?.getResponseHeader('Link') ?? ''
- : artifactsData?.[2]?.getResponseHeader('Link') ?? '';
- const loading = tabDebugIdBundlesActive ? debugIdBundlesLoading : artifactsLoading;
- const handleSearch = useCallback(
- (newQuery: string) => {
- router.push({
- ...location,
- query: {...location.query, cursor: undefined, query: newQuery},
- });
- },
- [router, location]
- );
- return (
- <Fragment>
- <SearchBarWithMarginBottom
- placeholder={t('Search')}
- onSearch={handleSearch}
- query={query}
- />
- <StyledPanelTable
- headers={[
- t('Artifact'),
- <SizeColumn key="file-size">{t('File Size')}</SizeColumn>,
- '',
- ]}
- emptyMessage={
- query
- ? t('No artifacts match your search query.')
- : t('There are no artifacts in this archive.')
- }
- isEmpty={data.length === 0}
- isLoading={loading}
- >
- {data.map(({id, size, name, dist, dateCreated}) => {
- const downloadUrl = `${api.baseUrl}/projects/${organization.slug}/${
- project.slug
- }/releases/${encodeURIComponent(name)}/files/${id}/?download=1`;
- return (
- <Fragment key={id}>
- <NameColumn>
- <Name>{name || `(${t('empty')})`}</Name>
- <TimeAndDistWrapper>
- <TimeWrapper>
- <IconClock size="sm" />
- <TimeSince date={dateCreated} />
- </TimeWrapper>
- <StyledTag
- type={dist ? 'info' : undefined}
- tooltipText={dist ? undefined : t('No distribution set')}
- >
- {dist ?? t('none')}
- </StyledTag>
- </TimeAndDistWrapper>
- </NameColumn>
- <SizeColumn>
- <FileSize bytes={size} />
- </SizeColumn>
- <ActionsColumn>
- <Role role={downloadRole}>
- {({hasRole}) => (
- <Tooltip
- title={tct(
- 'Artifacts can only be downloaded by users with organization [downloadRole] role[orHigher]. This can be changed in [settingsLink:Debug Files Access] settings.',
- {
- downloadRole,
- orHigher: downloadRole !== 'owner' ? ` ${t('or higher')}` : '',
- settingsLink: (
- <Link to={`/settings/${organization.slug}/#debugFilesRole`} />
- ),
- }
- )}
- disabled={hasRole}
- isHoverable
- >
- <Button
- size="sm"
- icon={<IconDownload size="sm" />}
- disabled={!hasRole}
- href={downloadUrl}
- title={hasRole ? t('Download Artifact') : undefined}
- aria-label={t('Download Artifact')}
- />
- </Tooltip>
- )}
- </Role>
- </ActionsColumn>
- </Fragment>
- );
- })}
- </StyledPanelTable>
- <Pagination pageLinks={pageLinks} />
- </Fragment>
- );
- }
- const StyledPanelTable = styled(PanelTable)`
- grid-template-columns: minmax(220px, 1fr) minmax(120px, max-content) minmax(
- 74px,
- max-content
- );
- `;
- const Column = styled('div')`
- display: flex;
- align-items: center;
- overflow: hidden;
- `;
- const ActionsColumn = styled(Column)`
- justify-content: flex-end;
- `;
- const SearchBarWithMarginBottom = styled(SearchBar)`
- margin-bottom: ${space(3)};
- `;
- const SizeColumn = styled('div')`
- display: flex;
- justify-content: flex-end;
- text-align: right;
- align-items: center;
- `;
- const Name = styled('div')`
- padding-right: ${space(4)};
- overflow-wrap: break-word;
- word-break: break-all;
- `;
- const TimeAndDistWrapper = styled('div')`
- width: 100%;
- display: flex;
- margin-top: ${space(1)};
- align-items: center;
- `;
- const TimeWrapper = styled('div')`
- display: grid;
- gap: ${space(0.5)};
- grid-template-columns: min-content 1fr;
- font-size: ${p => p.theme.fontSizeMedium};
- align-items: center;
- color: ${p => p.theme.subText};
- `;
- const StyledTag = styled(Tag)`
- margin-left: ${space(1)};
- `;
- const NameColumn = styled('div')`
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- justify-content: center;
- `;
|