import {Ban, CircleStop} from '@gravity-ui/icons'; import type {Column as DataTableColumn} from '@gravity-ui/react-data-table'; import {ActionTooltip, Flex, Icon, Text} from '@gravity-ui/uikit'; import {ButtonWithConfirmDialog} from '../../components/ButtonWithConfirmDialog/ButtonWithConfirmDialog'; import {CellWithPopover} from '../../components/CellWithPopover/CellWithPopover'; import {operationsApi} from '../../store/reducers/operations'; import type { CompactMetadata, IndexBuildMetadata, OperationKind, TOperation, } from '../../types/api/operations'; import {EStatusCode} from '../../types/api/operations'; import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants'; import createToast from '../../utils/createToast'; import {formatDateTime, formatDurationMs} from '../../utils/dataFormatters/dataFormatters'; import {parseProtobufTimestampToMs} from '../../utils/timeParsers'; import {COLUMNS_NAMES, COLUMNS_TITLES} from './constants'; import i18n from './i18n'; import {getOperationProgress, isIndexBuildMetadata} from './utils'; import './Operations.scss'; function renderStatusCell(row: TOperation) { const progress = getOperationProgress(row, i18n); if (row.ready) { if (!row.status) { return EMPTY_DATA_PLACEHOLDER; } return ( {row.status} ); } if (row.status && row.status !== EStatusCode.SUCCESS) { return {row.status}; } if (progress !== null) { return progress; } return i18n('label_in-progress'); } export function getColumns({ database, kind, }: { database: string; kind: OperationKind; }): DataTableColumn[] { const isBuildIndex = kind === 'buildindex'; const isCompaction = kind === 'compaction'; // Helper function to get description tooltip content (buildindex-only) const getDescriptionTooltip = (operation: TOperation): string => { const {metadata} = operation; if (!isIndexBuildMetadata(metadata) || !metadata.description) { return ''; } return JSON.stringify(metadata.description, null, 2); }; const columns: DataTableColumn[] = [ { name: COLUMNS_NAMES.ID, header: COLUMNS_TITLES[COLUMNS_NAMES.ID], width: 340, render: ({row}) => { if (!row.id) { return EMPTY_DATA_PLACEHOLDER; } const tooltipContent = isBuildIndex ? getDescriptionTooltip(row) || row.id : row.id; return ( {row.id} ); }, }, { name: COLUMNS_NAMES.STATUS, header: COLUMNS_TITLES[COLUMNS_NAMES.STATUS], render: ({row}) => { return renderStatusCell(row); }, }, ]; // Add state column for kinds that expose a state (buildindex, compaction) if (isBuildIndex || isCompaction) { columns.push({ name: COLUMNS_NAMES.STATE, header: COLUMNS_TITLES[COLUMNS_NAMES.STATE], render: ({row}) => { const metadata = row.metadata as IndexBuildMetadata | CompactMetadata | undefined; if (!metadata?.state) { return EMPTY_DATA_PLACEHOLDER; } return metadata.state; }, }); } // Add standard columns columns.push( { name: COLUMNS_NAMES.CREATED_BY, header: COLUMNS_TITLES[COLUMNS_NAMES.CREATED_BY], render: ({row}) => { if (!row.created_by) { return EMPTY_DATA_PLACEHOLDER; } return row.created_by; }, }, { name: COLUMNS_NAMES.CREATE_TIME, header: COLUMNS_TITLES[COLUMNS_NAMES.CREATE_TIME], render: ({row}) => { if (!row.create_time) { return EMPTY_DATA_PLACEHOLDER; } return formatDateTime(parseProtobufTimestampToMs(row.create_time)); }, }, { name: COLUMNS_NAMES.END_TIME, header: COLUMNS_TITLES[COLUMNS_NAMES.END_TIME], render: ({row}) => { if (!row.end_time) { return EMPTY_DATA_PLACEHOLDER; } return formatDateTime(parseProtobufTimestampToMs(row.end_time)); }, }, { name: COLUMNS_NAMES.DURATION, header: COLUMNS_TITLES[COLUMNS_NAMES.DURATION], render: ({row}) => { let durationValue = 0; if (!row.create_time) { return EMPTY_DATA_PLACEHOLDER; } const createTime = parseProtobufTimestampToMs(row.create_time); if (row.end_time) { const endTime = parseProtobufTimestampToMs(row.end_time); durationValue = endTime - createTime; } else { durationValue = Date.now() - createTime; } const safeDurationMs = durationValue < 0 ? 0 : durationValue; const durationFormatted = formatDurationMs(safeDurationMs); return row.end_time ? durationFormatted : i18n('label_duration-ongoing', {value: durationFormatted}); }, }, ); // Add Actions column columns.push({ name: 'Actions', sortable: false, resizeable: false, header: '', render: ({row}) => { return ; }, }); return columns; } interface OperationsActionsProps { operation: TOperation; database: string; } function OperationsActions({operation, database}: OperationsActionsProps) { const [cancelOperation, {isLoading: isCancelLoading}] = operationsApi.useCancelOperationMutation(); const [forgetOperation, {isLoading: isForgetLoading}] = operationsApi.useForgetOperationMutation(); const id = operation.id; if (!id) { return null; } const isForgetButtonDisabled = isForgetLoading; const isCancelButtonDisabled = isCancelLoading || operation.ready === true; return ( forgetOperation({id, database}) .unwrap() .then(() => { createToast({ name: 'Forgotten', title: i18n('text_forgotten', {id}), theme: 'success', }); }) } buttonDisabled={isForgetButtonDisabled} > cancelOperation({id, database}) .unwrap() .then(() => { createToast({ name: 'Cancelled', title: i18n('text_cancelled', {id}), theme: 'success', }); }) } buttonDisabled={isCancelButtonDisabled} > ); }