import React from 'react';
import {Flex, Icon, Text} from '@gravity-ui/uikit';
import {skipToken} from '@reduxjs/toolkit/query';
import {isNil} from 'lodash';
import {LoaderWrapper} from '../../../../../components/LoaderWrapper/LoaderWrapper';
import {topicApi} from '../../../../../store/reducers/topic/topic';
import type {TopicDataRequest} from '../../../../../types/api/topic';
import {isResponseError} from '../../../../../utils/response';
import {safeParseNumber} from '../../../../../utils/utils';
import i18n from '../i18n';
import {useTopicDataQueryParams} from '../useTopicDataQueryParams';
import {TopicMessage} from './components/TopicMessage';
import {TopicMessageGeneralInfo} from './components/TopicMessageGeneralInfo';
import {TopicMessageMetadata} from './components/TopicMessageMetadata';
import {MESSAGE_SIZE_LIMIT, b} from './shared';
import cryCatIcon from '../../../../../assets/icons/cry-cat.svg';
import './TopicMessageDetails.scss';
interface TopicMessageDetailsProps {
database: string;
path: string;
clusterName?: string;
useMeta?: boolean;
}
export function TopicMessageDetails({
database,
path,
clusterName,
useMeta,
}: TopicMessageDetailsProps) {
const {selectedPartition, activeOffset} = useTopicDataQueryParams();
const queryParams = React.useMemo(() => {
if (isNil(selectedPartition) || isNil(activeOffset)) {
return skipToken;
}
const params: TopicDataRequest & {clusterName?: string; useMeta?: boolean} = {
database,
path,
clusterName,
partition: selectedPartition,
limit: 1,
message_size_limit: MESSAGE_SIZE_LIMIT,
useMeta,
};
const parsedOffset = safeParseNumber(activeOffset);
params.offset = parsedOffset;
params.last_offset = parsedOffset + 1;
return params;
}, [selectedPartition, activeOffset, database, path, clusterName, useMeta]);
const {currentData, error, isFetching} = topicApi.useGetTopicDataQuery(queryParams);
const messageDetails = currentData?.Messages?.[0];
const renderMessageGeneralInfo = () => {
if (!messageDetails) {
return null;
}
return (
);
};
const renderMessageMeta = () => {
const metadata = messageDetails?.MessageMetadata;
if (!metadata) {
return null;
}
return ;
};
const renderMessage = () => {
const message = messageDetails?.Message;
const generalSchematizeError = currentData?.SchematizeError;
const messageSchematizeError = messageDetails?.SchematizeError;
// A message is schematized (a parsed JSON value, including string
// primitives) when the response carries schema context (`SchemaPath`)
// and neither the response nor the message reported a schematization
// error. Such values must be rendered as-is, without base64 decoding.
const isSchematized =
Boolean(currentData?.SchemaPath) && !generalSchematizeError && !messageSchematizeError;
// Render the section whenever there is a message OR a schematization
// error to report. Use isNil instead of a broad falsy check so valid
// falsy schematized values (0, false, '') are not dropped.
if (isNil(message) && !generalSchematizeError && !messageSchematizeError) {
return null;
}
return (
);
};
const renderContent = () => {
const responseError = isResponseError(error);
const notFoundError = responseError && error.status === 404;
const noMessages = currentData && !currentData.Messages?.length;
const isEmpty = Boolean(noMessages || notFoundError);
if (isEmpty) {
return (
{i18n('context_message-not-found', {offset: activeOffset})}
);
} else if (error) {
let text = i18n('context_get-data-error');
if (responseError && typeof error.data === 'string') {
text = error.data;
} else if (responseError && error.statusText) {
text = error.statusText;
}
return (
{text}
);
}
return (
{renderMessageGeneralInfo()}
{renderMessageMeta()}
{renderMessage()}
);
};
return (
{renderContent()}
);
}