import {Fragment, useCallback, useState} from 'react';
import {useTheme} from '@emotion/react';
import EmptyStateWarning, {EmptyStreamWrapper} from 'sentry/components/emptyStateWarning';
import ExternalLink from 'sentry/components/links/externalLink';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import Pagination from 'sentry/components/pagination';
import {LOGS_PROPS_DOCS_URL} from 'sentry/constants';
import {IconArrow, IconWarning} from 'sentry/icons';
import {IconChevron} from 'sentry/icons/iconChevron';
import {t, tct} from 'sentry/locale';
import {defined} from 'sentry/utils';
import {
useLogsCursor,
useLogsSearch,
useLogsSortBys,
useSetLogsCursor,
useSetLogsSortBys,
} from 'sentry/views/explore/contexts/logs/logsPageParams';
import {
bodyRenderer,
HiddenLogAttributes,
LogAttributesRendererMap,
severityTextRenderer,
TimestampRenderer,
} from 'sentry/views/explore/logs/fieldRenderers';
import {LogFieldsTree} from 'sentry/views/explore/logs/logFieldsTree';
import {
type OurLogFieldKey,
OurLogKnownFieldKey,
type OurLogsResponseItem,
} from 'sentry/views/explore/logs/types';
import {
useExploreLogsTable,
useExploreLogsTableRow,
} from 'sentry/views/explore/logs/useLogsQuery';
import {EmptyStateText} from 'sentry/views/traces/styles';
import {
DetailsFooter,
DetailsGrid,
DetailsWrapper,
getLogColors,
HeaderCell,
LogDetailsTitle,
LogPanelContent,
StyledChevronButton,
StyledPanel,
StyledPanelItem,
} from './styles';
import {getLogBodySearchTerms, getLogSeverityLevel} from './utils';
type LogsRowProps = {
dataRow: OurLogsResponseItem;
highlightTerms: string[];
};
export function LogsTable() {
const search = useLogsSearch();
const cursor = useLogsCursor();
const setCursor = useSetLogsCursor();
const {data, isError, isPending, pageLinks} = useExploreLogsTable({
limit: 100,
search,
cursor,
});
const isEmpty = !isPending && !isError && (data?.length ?? 0) === 0;
const highlightTerms = getLogBodySearchTerms(search);
const sortBys = useLogsSortBys();
const setSortBys = useSetLogsSortBys();
const headers: Array<{align: 'left' | 'right'; field: OurLogFieldKey; label: string}> =
[
{field: OurLogKnownFieldKey.SEVERITY_NUMBER, label: t('Severity'), align: 'left'},
{field: OurLogKnownFieldKey.BODY, label: t('Message'), align: 'left'},
{field: OurLogKnownFieldKey.TIMESTAMP, label: t('Timestamp'), align: 'right'},
];
return (
{headers.map((header, index) => {
const direction = sortBys.find(s => s.field === header.field)?.kind;
return (
setSortBys([{field: header.field}])}
>
{header.label}
{defined(direction) && (
)}
);
})}
{isPending && (
)}
{isError && (
)}
{isEmpty && (
{t('No logs found')}
{tct('Try adjusting your filters or refer to [docSearchProps].', {
docSearchProps: (
{t('docs for search properties')}
),
})}
)}
{data?.map((row, index) => (
))}
);
}
function LogsRow({dataRow, highlightTerms}: LogsRowProps) {
const [expanded, setExpanded] = useState(false);
const onClickExpand = useCallback(() => setExpanded(e => !e), [setExpanded]);
const theme = useTheme();
const severityNumber = dataRow[OurLogKnownFieldKey.SEVERITY_NUMBER];
const severityText = dataRow[OurLogKnownFieldKey.SEVERITY_TEXT];
const level = getLogSeverityLevel(
typeof severityNumber === 'number' ? severityNumber : null,
typeof severityText === 'string' ? severityText : null
);
const logColors = getLogColors(level, theme);
return (
}
aria-label={t('Toggle trace details')}
aria-expanded={expanded}
size="zero"
borderless
/>
{severityTextRenderer({
attribute_value: severityText,
tableResultLogRow: dataRow,
extra: {
highlightTerms,
logColors,
useFullSeverityText: false,
renderSeverityCircle: true,
},
})}
{bodyRenderer({
attribute_value: dataRow[OurLogKnownFieldKey.BODY],
extra: {
highlightTerms,
logColors,
wrapBody: false,
},
})}
{expanded && }
);
}
function LogDetails({
dataRow,
highlightTerms,
}: {
dataRow: OurLogsResponseItem;
highlightTerms: string[];
}) {
const severityNumber = dataRow[OurLogKnownFieldKey.SEVERITY_NUMBER];
const severityText = dataRow[OurLogKnownFieldKey.SEVERITY_TEXT];
const level = getLogSeverityLevel(
typeof severityNumber === 'number' ? severityNumber : null,
typeof severityText === 'string' ? severityText : null
);
const missingLogId = !dataRow[OurLogKnownFieldKey.ID];
const {data, isPending} = useExploreLogsTableRow({
log_id: String(dataRow[OurLogKnownFieldKey.ID] ?? ''),
project_id: String(dataRow[OurLogKnownFieldKey.PROJECT_ID] ?? ''),
enabled: !missingLogId,
});
const theme = useTheme();
const logColors = getLogColors(level, theme);
if (missingLogId) {
return (
);
}
return (
{isPending && }
{!isPending && data && (
{t('Log')}
{bodyRenderer({
attribute_value: dataRow[OurLogKnownFieldKey.BODY],
extra: {
highlightTerms,
logColors,
wrapBody: true,
},
})}
)}
);
}