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)};
`;