import {Fragment} from 'react'; import styled from '@emotion/styled'; import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator'; import {openModal} from 'sentry/actionCreators/modal'; import {DropdownMenu} from 'sentry/components/dropdownMenu'; import ExternalLink from 'sentry/components/links/externalLink'; import {PanelTable} from 'sentry/components/panels/panelTable'; import TimeSince from 'sentry/components/timeSince'; import {IconEllipsis, IconOpen} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {CodeOwner, CodeownersFile} from 'sentry/types/integrations'; import type {Project} from 'sentry/types/project'; import {getCodeOwnerIcon} from 'sentry/utils/integrationUtil'; import useApi from 'sentry/utils/useApi'; import useOrganization from 'sentry/utils/useOrganization'; import ViewCodeOwnerModal, {modalCss} from './viewCodeOwnerModal'; interface CodeOwnerFileTableProps { codeowners: CodeOwner[]; disabled: boolean; onDelete: (data: CodeOwner) => void; onUpdate: (data: CodeOwner) => void; project: Project; } /** * A list of codeowner files being used for this project * If you're looking for ownership rules table see `OwnershipRulesTable` */ export function CodeOwnerFileTable({ codeowners, project, onUpdate, onDelete, disabled, }: CodeOwnerFileTableProps) { const api = useApi(); const organization = useOrganization(); // Do we need an empty state instead? if (codeowners.length === 0) { return null; } const handleView = (codeowner: CodeOwner) => () => { // Open modal with codeowner file openModal(deps => , {modalCss}); }; const handleSync = (codeowner: CodeOwner) => async () => { try { const codeownerFile: CodeownersFile = await api.requestPromise( `/organizations/${organization.slug}/code-mappings/${codeowner.codeMappingId}/codeowners/`, { method: 'GET', } ); const data = await api.requestPromise( `/projects/${organization.slug}/${project.slug}/codeowners/${codeowner.id}/`, { method: 'PUT', data: {raw: codeownerFile.raw, date_updated: new Date().toISOString()}, } ); onUpdate({...codeowner, ...data}); addSuccessMessage(t('CODEOWNERS file sync successful.')); } catch (_err) { addErrorMessage(t('An error occurred trying to sync CODEOWNERS file.')); } }; const handleDelete = (codeowner: CodeOwner) => async () => { try { await api.requestPromise( `/projects/${organization.slug}/${project.slug}/codeowners/${codeowner.id}/`, { method: 'DELETE', } ); onDelete(codeowner); addSuccessMessage(t('Deletion successful')); } catch { // no 4xx errors should happen on delete addErrorMessage(t('An error occurred')); } }; return ( {codeowners.map(codeowner => ( {getCodeOwnerIcon(codeowner.provider)} {codeowner.codeMapping?.repoName} {codeowner.codeMapping?.stackRoot} {codeowner.codeMapping?.sourceRoot} {codeowner.codeOwnersUrl === 'unknown' ? null : ( {t( 'View in %s', codeowner.codeMapping?.provider?.name ?? codeowner.provider )} )} , showChevron: false, disabled, }} disabledKeys={disabled ? ['sync', 'delete'] : []} /> ))} ); } const StyledPanelTable = styled(PanelTable)` grid-template-columns: 1fr 1fr 1fr auto min-content min-content; position: static; overflow: auto; white-space: nowrap; `; const FlexCenter = styled('div')` display: flex; align-items: center; gap: ${space(1)}; `; const StyledExternalLink = styled(ExternalLink)` display: flex; align-items: center; gap: ${space(1)}; `;