|
| 1 | +import type { |
| 2 | + ExportToS3Metadata, |
| 3 | + ExportToYtMetadata, |
| 4 | + ImportFromS3Metadata, |
| 5 | + IndexBuildMetadata, |
| 6 | + TOperation, |
| 7 | +} from '../../types/api/operations'; |
| 8 | +import {OPERATION_METADATA_TYPE_URLS} from '../../types/api/operations'; |
| 9 | + |
| 10 | +// Type guards for operation metadata kinds |
| 11 | +export function isIndexBuildMetadata( |
| 12 | + metadata: TOperation['metadata'], |
| 13 | +): metadata is IndexBuildMetadata { |
| 14 | + if (!metadata) { |
| 15 | + return false; |
| 16 | + } |
| 17 | + |
| 18 | + return metadata['@type'] === OPERATION_METADATA_TYPE_URLS.IndexBuild; |
| 19 | +} |
| 20 | + |
| 21 | +export function isImportFromS3Metadata( |
| 22 | + metadata: TOperation['metadata'], |
| 23 | +): metadata is ImportFromS3Metadata { |
| 24 | + if (!metadata) { |
| 25 | + return false; |
| 26 | + } |
| 27 | + |
| 28 | + return metadata['@type'] === OPERATION_METADATA_TYPE_URLS.ImportFromS3; |
| 29 | +} |
| 30 | + |
| 31 | +export function isExportToS3Metadata( |
| 32 | + metadata: TOperation['metadata'], |
| 33 | +): metadata is ExportToS3Metadata { |
| 34 | + if (!metadata) { |
| 35 | + return false; |
| 36 | + } |
| 37 | + |
| 38 | + return metadata['@type'] === OPERATION_METADATA_TYPE_URLS.ExportToS3; |
| 39 | +} |
| 40 | + |
| 41 | +export function isExportToYtMetadata( |
| 42 | + metadata: TOperation['metadata'], |
| 43 | +): metadata is ExportToYtMetadata { |
| 44 | + if (!metadata) { |
| 45 | + return false; |
| 46 | + } |
| 47 | + |
| 48 | + return metadata['@type'] === OPERATION_METADATA_TYPE_URLS.ExportToYt; |
| 49 | +} |
| 50 | + |
| 51 | +export function isImportExportMetadata( |
| 52 | + metadata: TOperation['metadata'], |
| 53 | +): metadata is ImportFromS3Metadata | ExportToS3Metadata | ExportToYtMetadata { |
| 54 | + return ( |
| 55 | + isImportFromS3Metadata(metadata) || |
| 56 | + isExportToS3Metadata(metadata) || |
| 57 | + isExportToYtMetadata(metadata) |
| 58 | + ); |
| 59 | +} |
| 60 | + |
| 61 | +// i18n keys for import/export progress enum values |
| 62 | +// value_progress_unspecified, value_progress_preparing, etc. |
| 63 | +export type OperationProgressKey = |
| 64 | + | 'value_progress_unspecified' |
| 65 | + | 'value_progress_preparing' |
| 66 | + | 'value_progress_transfer_data' |
| 67 | + | 'value_progress_build_indexes' |
| 68 | + | 'value_progress_done' |
| 69 | + | 'value_progress_cancellation' |
| 70 | + | 'value_progress_cancelled' |
| 71 | + | 'value_progress_create_changefeeds'; |
| 72 | + |
| 73 | +/** |
| 74 | + * Calculate progress percentage from Import/Export metadata |
| 75 | + * |
| 76 | + * Calculates overall progress based on items_progress array: |
| 77 | + * - Sums all parts_total and parts_completed across all items |
| 78 | + * - Returns percentage rounded to nearest integer |
| 79 | + * |
| 80 | + * @param metadata - Import/Export operation metadata |
| 81 | + * @returns Progress percentage (0-100) or null if cannot be calculated |
| 82 | + */ |
| 83 | +export function calculateImportExportProgress( |
| 84 | + metadata: ImportFromS3Metadata | ExportToS3Metadata | ExportToYtMetadata | undefined, |
| 85 | +): number | null { |
| 86 | + if (!metadata?.items_progress || metadata.items_progress.length === 0) { |
| 87 | + return null; |
| 88 | + } |
| 89 | + |
| 90 | + let totalParts = 0; |
| 91 | + let completedParts = 0; |
| 92 | + |
| 93 | + for (const item of metadata.items_progress) { |
| 94 | + if (item.parts_total !== undefined && item.parts_total > 0) { |
| 95 | + totalParts += item.parts_total; |
| 96 | + completedParts += item.parts_completed || 0; |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + if (totalParts === 0) { |
| 101 | + return null; |
| 102 | + } |
| 103 | + |
| 104 | + return Math.round((completedParts / totalParts) * 100); |
| 105 | +} |
| 106 | + |
| 107 | +/** |
| 108 | + * Get progress display value for an operation |
| 109 | + * |
| 110 | + * Handles different progress formats: |
| 111 | + * - BuildIndex: numeric progress (0-100) -> "75%" |
| 112 | + * - Import/Export: calculated from items_progress -> "45%" or enum value -> "Done" |
| 113 | + * |
| 114 | + * @param operation - Operation to get progress for |
| 115 | + * @param translateProgress - Function to translate progress enum values (i18n) |
| 116 | + * @returns Formatted progress string or null if no progress available |
| 117 | + */ |
| 118 | +export function getOperationProgress( |
| 119 | + operation: TOperation, |
| 120 | + translateProgress: (key: OperationProgressKey) => string, |
| 121 | +): string | null { |
| 122 | + const metadata = operation.metadata; |
| 123 | + |
| 124 | + if (!metadata) { |
| 125 | + return null; |
| 126 | + } |
| 127 | + |
| 128 | + if (isIndexBuildMetadata(metadata)) { |
| 129 | + if (typeof metadata.progress === 'number') { |
| 130 | + return `${Math.round(metadata.progress)}%`; |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + // Import/Export: calculate from items_progress or show enum value |
| 135 | + if (isImportExportMetadata(metadata)) { |
| 136 | + // Try to calculate percentage from items_progress |
| 137 | + const calculatedProgress = calculateImportExportProgress(metadata); |
| 138 | + if (calculatedProgress !== null) { |
| 139 | + return `${calculatedProgress}%`; |
| 140 | + } |
| 141 | + |
| 142 | + // Fallback to enum progress value |
| 143 | + if (metadata.progress) { |
| 144 | + const progressValue = |
| 145 | + typeof metadata.progress === 'string' |
| 146 | + ? metadata.progress |
| 147 | + : String(metadata.progress); |
| 148 | + |
| 149 | + // Backend enums are usually PROGRESS_DONE, PROGRESS_PREPARING, etc. |
| 150 | + // Normalize by stripping optional PROGRESS_ prefix and lowercasing. |
| 151 | + // Both "PROGRESS_DONE" and "DONE" will map to "value_progress_done". |
| 152 | + const base = progressValue.replace(/^PROGRESS_/, '').toLowerCase(); // done |
| 153 | + const i18nKey = `value_progress_${base}` as OperationProgressKey; |
| 154 | + |
| 155 | + try { |
| 156 | + const translated = translateProgress(i18nKey); |
| 157 | + if (translated && translated !== i18nKey) { |
| 158 | + return translated; |
| 159 | + } |
| 160 | + } catch {} |
| 161 | + |
| 162 | + return progressValue; |
| 163 | + } |
| 164 | + } |
| 165 | + |
| 166 | + return null; |
| 167 | +} |
0 commit comments