feat: implement LLM table translation service with searchable datasource dropdown, i18n, sidebar, and RBAC
- Add backend plugin modules: preview, executor, orchestrator, events, sql_generator, superset_executor, dictionary, scheduler, metrics - Add API routes for translate jobs, runs, dictionaries, preview, schedule, metrics, corrections - Add ORM models (12 tables) and Pydantic schemas (15 DTOs) - Register translate router in app.py with RBAC permission guards - Add frontend pages: job list, job config, dictionary list/editor, history - Add 7 reusable Svelte 5 components: Preview, RunProgress, RunResult, TermCorrection, BulkCorrection, ScheduleConfig, MetricsDashboard - Add searchable datasource dropdown with Superset API integration - Add i18n (en/ru) with ~300 keys across common, config, jobs, dictionaries, preview namespaces - Add Translation sidebar category with Jobs/Dictionaries/History sub-items - Hide health monitor error toast via suppressToast API option - Add 69 backend tests and 44 frontend test files - Fix: SupersetClient env resolution (string -> Environment object) - Fix: Dataset detail API returns proper database dict - Fix: Database dialect extraction fallback when metadata incomplete
This commit is contained in:
583
frontend/src/lib/api/translate.js
Normal file
583
frontend/src/lib/api/translate.js
Normal file
@@ -0,0 +1,583 @@
|
||||
// [DEF:TranslateApi:Module]
|
||||
// @COMPLEXITY: 3
|
||||
// @SEMANTICS: frontend, api_client, translate, crud
|
||||
// @PURPOSE: API client for translation job CRUD, datasource column queries, and dictionary management.
|
||||
// @LAYER: Infra
|
||||
// @RELATION: DEPENDS_ON -> [api_module]
|
||||
// @PRE: Shared API wrapper is configured for the current frontend auth/session context.
|
||||
// @POST: Translate API helpers return backend payloads or normalized UI-safe errors.
|
||||
|
||||
import { api } from '$lib/api.js';
|
||||
|
||||
// [DEF:normalizeTranslateError:Function]
|
||||
// @PURPOSE: Convert unknown API exceptions into deterministic UI-consumable error objects.
|
||||
// @PRE: error may be Error/string/object.
|
||||
// @POST: Returns structured error object with message and code.
|
||||
function normalizeTranslateError(error, defaultMessage = 'Translation API error') {
|
||||
const message =
|
||||
(error && typeof error.message === 'string' && error.message) ||
|
||||
(typeof error === 'string' && error) ||
|
||||
defaultMessage;
|
||||
|
||||
return { message, code: 'TRANSLATE_API_ERROR', retryable: true };
|
||||
}
|
||||
// [/DEF:normalizeTranslateError:Function]
|
||||
|
||||
// [DEF:fetchJobs:Function]
|
||||
// @PURPOSE: Fetch paginated list of translation jobs with optional status filter.
|
||||
// @PRE: Valid auth context.
|
||||
// @POST: Returns array of job objects.
|
||||
export async function fetchJobs(options = {}) {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
if (options.status) params.append('status', options.status);
|
||||
const query = params.toString();
|
||||
return await api.fetchApi(`/translate/jobs${query ? `?${query}` : ''}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to load translation jobs');
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchJobs:Function]
|
||||
|
||||
// [DEF:createJob:Function]
|
||||
// @PURPOSE: Create a new translation job.
|
||||
// @PRE: payload contains valid job configuration.
|
||||
// @POST: Returns the created job object.
|
||||
export async function createJob(payload) {
|
||||
try {
|
||||
return await api.postApi('/translate/jobs', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to create translation job');
|
||||
}
|
||||
}
|
||||
// [/DEF:createJob:Function]
|
||||
|
||||
// [DEF:updateJob:Function]
|
||||
// @PURPOSE: Update an existing translation job.
|
||||
// @PRE: jobId is non-empty string; payload contains fields to update.
|
||||
// @POST: Returns the updated job object.
|
||||
export async function updateJob(jobId, payload) {
|
||||
try {
|
||||
return await api.requestApi(`/translate/jobs/${jobId}`, 'PUT', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to update translation job');
|
||||
}
|
||||
}
|
||||
// [/DEF:updateJob:Function]
|
||||
|
||||
// [DEF:deleteJob:Function]
|
||||
// @PURPOSE: Delete a translation job.
|
||||
// @PRE: jobId is non-empty string.
|
||||
// @POST: Returns null on success.
|
||||
export async function deleteJob(jobId) {
|
||||
try {
|
||||
return await api.deleteApi(`/translate/jobs/${jobId}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to delete translation job');
|
||||
}
|
||||
}
|
||||
// [/DEF:deleteJob:Function]
|
||||
|
||||
// [DEF:duplicateJob:Function]
|
||||
// @PURPOSE: Duplicate a translation job.
|
||||
// @PRE: jobId is non-empty string.
|
||||
// @POST: Returns the duplicated job summary.
|
||||
export async function duplicateJob(jobId) {
|
||||
try {
|
||||
return await api.postApi(`/translate/jobs/${jobId}/duplicate`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to duplicate translation job');
|
||||
}
|
||||
}
|
||||
// [/DEF:duplicateJob:Function]
|
||||
|
||||
// [DEF:fetchDatasourceColumns:Function]
|
||||
|
||||
export async function fetchDatasources(envId, search = '') {
|
||||
try {
|
||||
return await api.fetchApi(
|
||||
`/translate/datasources?env_id=${encodeURIComponent(envId)}&search=${encodeURIComponent(search)}`
|
||||
);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch datasources');
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchDatasourceColumns(datasourceId, envId) {
|
||||
try {
|
||||
return await api.fetchApi(
|
||||
`/translate/datasources/${datasourceId}/columns?env_id=${encodeURIComponent(envId)}`
|
||||
);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch datasource columns');
|
||||
}
|
||||
}
|
||||
|
||||
// [DEF:fetchPreview:Function]
|
||||
// @PURPOSE: Trigger a translation preview for a job.
|
||||
// @PRE: jobId is non-empty string.
|
||||
// @POST: Returns preview session with rows and cost estimate.
|
||||
export async function fetchPreview(jobId, sampleSize = 10) {
|
||||
try {
|
||||
return await api.postApi(`/translate/jobs/${jobId}/preview`, { sample_size: sampleSize });
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to run preview');
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchPreview:Function]
|
||||
|
||||
// [DEF:approveRow:Function]
|
||||
// @PURPOSE: Approve a preview row.
|
||||
// @PRE: jobId and rowKey are non-empty strings.
|
||||
// @POST: Returns updated row.
|
||||
export async function approveRow(jobId, rowKey) {
|
||||
try {
|
||||
return await api.requestApi(`/translate/jobs/${jobId}/preview/rows/${rowKey}`, 'PUT', {
|
||||
action: 'approve'
|
||||
});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to approve row');
|
||||
}
|
||||
}
|
||||
// [/DEF:approveRow:Function]
|
||||
|
||||
// [DEF:editRow:Function]
|
||||
// @PURPOSE: Edit a preview row's translation.
|
||||
// @PRE: jobId and rowKey are non-empty strings; translation is non-empty.
|
||||
// @POST: Returns updated row.
|
||||
export async function editRow(jobId, rowKey, translation) {
|
||||
try {
|
||||
return await api.requestApi(`/translate/jobs/${jobId}/preview/rows/${rowKey}`, 'PUT', {
|
||||
action: 'edit',
|
||||
translation: translation
|
||||
});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to edit row');
|
||||
}
|
||||
}
|
||||
// [/DEF:editRow:Function]
|
||||
|
||||
// [DEF:rejectRow:Function]
|
||||
// @PURPOSE: Reject a preview row.
|
||||
// @PRE: jobId and rowKey are non-empty strings.
|
||||
// @POST: Returns updated row.
|
||||
export async function rejectRow(jobId, rowKey) {
|
||||
try {
|
||||
return await api.requestApi(`/translate/jobs/${jobId}/preview/rows/${rowKey}`, 'PUT', {
|
||||
action: 'reject'
|
||||
});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to reject row');
|
||||
}
|
||||
}
|
||||
// [/DEF:rejectRow:Function]
|
||||
|
||||
// [DEF:acceptPreview:Function]
|
||||
// @PURPOSE: Accept a preview session, enabling full translation execution.
|
||||
// @PRE: jobId is non-empty string.
|
||||
// @POST: Returns accepted preview session.
|
||||
export async function acceptPreview(jobId) {
|
||||
try {
|
||||
return await api.postApi(`/translate/jobs/${jobId}/preview/accept`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to accept preview');
|
||||
}
|
||||
}
|
||||
// [/DEF:acceptPreview:Function]
|
||||
|
||||
// [DEF:fetchPreviewRecords:Function]
|
||||
// @PURPOSE: Fetch records for a preview session.
|
||||
// @PRE: sessionId is non-empty string.
|
||||
// @POST: Returns list of preview records.
|
||||
export async function fetchPreviewRecords(sessionId) {
|
||||
try {
|
||||
return await api.fetchApi(`/translate/preview/${sessionId}/records`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch preview records');
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchPreviewRecords:Function]
|
||||
// [/DEF:fetchDatasourceColumns:Function]
|
||||
|
||||
|
||||
// [DEF:triggerRun:Function]
|
||||
// @PURPOSE: Trigger a full translation run for a job.
|
||||
// @PRE: jobId is non-empty string.
|
||||
// @POST: Returns the created run object.
|
||||
export async function triggerRun(jobId) {
|
||||
try {
|
||||
return await api.postApi(`/translate/jobs/${jobId}/run`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to start translation run');
|
||||
}
|
||||
}
|
||||
// [/DEF:triggerRun:Function]
|
||||
|
||||
// [DEF:fetchRunStatus:Function]
|
||||
// @PURPOSE: Fetch status and statistics for a translation run.
|
||||
// @PRE: runId is non-empty string.
|
||||
// @POST: Returns run details with statistics.
|
||||
export async function fetchRunStatus(runId) {
|
||||
try {
|
||||
return await api.fetchApi(`/translate/runs/${runId}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch run status');
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchRunStatus:Function]
|
||||
|
||||
// [DEF:fetchRunHistory:Function]
|
||||
// @PURPOSE: Fetch run history for a job.
|
||||
// @PRE: jobId is non-empty string.
|
||||
// @POST: Returns paginated list of runs.
|
||||
export async function fetchRunHistory(jobId, options = {}) {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
const query = params.toString();
|
||||
return await api.fetchApi(`/translate/jobs/${jobId}/runs${query ? `?${query}` : ''}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch run history');
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchRunHistory:Function]
|
||||
|
||||
// [DEF:fetchRunRecords:Function]
|
||||
// @PURPOSE: Fetch paginated records for a translation run.
|
||||
// @PRE: runId is non-empty string.
|
||||
// @POST: Returns paginated records.
|
||||
export async function fetchRunRecords(runId, options = {}) {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
if (options.status) params.append('status', options.status);
|
||||
const query = params.toString();
|
||||
return await api.fetchApi(`/translate/runs/${runId}/records${query ? `?${query}` : ''}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch run records');
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchRunRecords:Function]
|
||||
|
||||
// [DEF:retryFailedBatches:Function]
|
||||
// @PURPOSE: Retry failed batches in a translation run.
|
||||
// @PRE: runId is non-empty string.
|
||||
// @POST: Returns updated run.
|
||||
export async function retryFailedBatches(runId) {
|
||||
try {
|
||||
return await api.postApi(`/translate/runs/${runId}/retry`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to retry batches');
|
||||
}
|
||||
}
|
||||
// [/DEF:retryFailedBatches:Function]
|
||||
|
||||
// [DEF:retryInsert:Function]
|
||||
// @PURPOSE: Retry the SQL insert phase for a run.
|
||||
// @PRE: runId is non-empty string.
|
||||
// @POST: Returns updated run.
|
||||
export async function retryInsert(runId) {
|
||||
try {
|
||||
return await api.postApi(`/translate/runs/${runId}/retry-insert`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to retry insert');
|
||||
}
|
||||
}
|
||||
// [/DEF:retryInsert:Function]
|
||||
|
||||
// [DEF:cancelRun:Function]
|
||||
// @PURPOSE: Cancel a running translation.
|
||||
// @PRE: runId is non-empty string.
|
||||
// @POST: Returns updated run.
|
||||
export async function cancelRun(runId) {
|
||||
try {
|
||||
return await api.postApi(`/translate/runs/${runId}/cancel`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to cancel run');
|
||||
}
|
||||
}
|
||||
// [/DEF:cancelRun:Function]
|
||||
|
||||
// [DEF:fetchRunBatches:Function]
|
||||
// @PURPOSE: Fetch batches for a translation run.
|
||||
// @PRE: runId is non-empty string.
|
||||
// @POST: Returns list of batches.
|
||||
export async function fetchRunBatches(runId) {
|
||||
try {
|
||||
return await api.fetchApi(`/translate/runs/${runId}/batches`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch run batches');
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchRunBatches:Function]
|
||||
|
||||
|
||||
// [DEF:dictionaryApi:Variable]
|
||||
// @PURPOSE: Namespaced API helpers for terminology dictionary CRUD operations.
|
||||
export const dictionaryApi = {
|
||||
async fetchDictionaries(options = {}) {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
const query = params.toString();
|
||||
return await api.fetchApi(`/translate/dictionaries${query ? `?${query}` : ''}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to load dictionaries');
|
||||
}
|
||||
},
|
||||
|
||||
async createDictionary(payload) {
|
||||
try {
|
||||
return await api.postApi('/translate/dictionaries', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to create dictionary');
|
||||
}
|
||||
},
|
||||
|
||||
async getDictionary(dictionaryId) {
|
||||
try {
|
||||
return await api.fetchApi(`/translate/dictionaries/${dictionaryId}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to load dictionary');
|
||||
}
|
||||
},
|
||||
|
||||
async updateDictionary(dictionaryId, payload) {
|
||||
try {
|
||||
return await api.requestApi(`/translate/dictionaries/${dictionaryId}`, 'PUT', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to update dictionary');
|
||||
}
|
||||
},
|
||||
|
||||
async deleteDictionary(dictionaryId) {
|
||||
try {
|
||||
return await api.deleteApi(`/translate/dictionaries/${dictionaryId}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to delete dictionary');
|
||||
}
|
||||
},
|
||||
|
||||
async fetchEntries(dictionaryId, options = {}) {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
const query = params.toString();
|
||||
return await api.fetchApi(
|
||||
`/translate/dictionaries/${dictionaryId}/entries${query ? `?${query}` : ''}`
|
||||
);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to load dictionary entries');
|
||||
}
|
||||
},
|
||||
|
||||
async createEntry(dictionaryId, payload) {
|
||||
try {
|
||||
return await api.postApi(`/translate/dictionaries/${dictionaryId}/entries`, payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to create dictionary entry');
|
||||
}
|
||||
},
|
||||
|
||||
async updateEntry(dictionaryId, entryId, payload) {
|
||||
try {
|
||||
return await api.requestApi(
|
||||
`/translate/dictionaries/${dictionaryId}/entries/${entryId}`,
|
||||
'PUT',
|
||||
payload
|
||||
);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to update dictionary entry');
|
||||
}
|
||||
},
|
||||
|
||||
async deleteEntry(dictionaryId, entryId) {
|
||||
try {
|
||||
return await api.deleteApi(`/translate/dictionaries/${dictionaryId}/entries/${entryId}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to delete dictionary entry');
|
||||
}
|
||||
},
|
||||
|
||||
async importEntries(dictionaryId, payload) {
|
||||
try {
|
||||
return await api.postApi(`/translate/dictionaries/${dictionaryId}/import`, payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to import dictionary entries');
|
||||
}
|
||||
},
|
||||
};
|
||||
// [/DEF:dictionaryApi:Variable]
|
||||
|
||||
// ============================================================
|
||||
// Corrections API
|
||||
// ============================================================
|
||||
|
||||
// [DEF:submitCorrection:Function]
|
||||
// @PURPOSE: Submit a single term correction.
|
||||
// @PRE: payload contains source_term, incorrect_target_term, corrected_target_term, dictionary_id.
|
||||
// @POST: Returns correction result with conflict info.
|
||||
export async function submitCorrection(payload) {
|
||||
try {
|
||||
return await api.postApi('/translate/corrections', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to submit correction');
|
||||
}
|
||||
}
|
||||
// [/DEF:submitCorrection:Function]
|
||||
|
||||
// [DEF:submitBulkCorrections:Function]
|
||||
// @PURPOSE: Submit multiple term corrections atomically.
|
||||
// @PRE: payload contains corrections array and dictionary_id.
|
||||
// @POST: Returns bulk result with conflict info.
|
||||
export async function submitBulkCorrections(payload) {
|
||||
try {
|
||||
return await api.postApi('/translate/corrections/bulk', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to submit bulk corrections');
|
||||
}
|
||||
}
|
||||
// [/DEF:submitBulkCorrections:Function]
|
||||
|
||||
// ============================================================
|
||||
// Schedule API
|
||||
// ============================================================
|
||||
|
||||
// [DEF:fetchSchedule:Function]
|
||||
// @PURPOSE: Fetch schedule for a translation job.
|
||||
export async function fetchSchedule(jobId) {
|
||||
try {
|
||||
return await api.fetchApi(`/translate/jobs/${jobId}/schedule`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch schedule');
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchSchedule:Function]
|
||||
|
||||
// [DEF:setSchedule:Function]
|
||||
// @PURPOSE: Create or update schedule for a translation job.
|
||||
export async function setSchedule(jobId, payload) {
|
||||
try {
|
||||
return await api.requestApi(`/translate/jobs/${jobId}/schedule`, 'PUT', payload);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to set schedule');
|
||||
}
|
||||
}
|
||||
// [/DEF:setSchedule:Function]
|
||||
|
||||
// [DEF:deleteSchedule:Function]
|
||||
// @PURPOSE: Delete schedule for a translation job.
|
||||
export async function deleteSchedule(jobId) {
|
||||
try {
|
||||
return await api.deleteApi(`/translate/jobs/${jobId}/schedule`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to delete schedule');
|
||||
}
|
||||
}
|
||||
// [/DEF:deleteSchedule:Function]
|
||||
|
||||
// [DEF:enableSchedule:Function]
|
||||
// @PURPOSE: Enable a schedule.
|
||||
export async function enableSchedule(jobId) {
|
||||
try {
|
||||
return await api.postApi(`/translate/jobs/${jobId}/schedule/enable`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to enable schedule');
|
||||
}
|
||||
}
|
||||
// [/DEF:enableSchedule:Function]
|
||||
|
||||
// [DEF:disableSchedule:Function]
|
||||
// @PURPOSE: Disable a schedule.
|
||||
export async function disableSchedule(jobId) {
|
||||
try {
|
||||
return await api.postApi(`/translate/jobs/${jobId}/schedule/disable`, {});
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to disable schedule');
|
||||
}
|
||||
}
|
||||
// [/DEF:disableSchedule:Function]
|
||||
|
||||
// [DEF:fetchNextExecutions:Function]
|
||||
// @PURPOSE: Preview next N schedule executions.
|
||||
export async function fetchNextExecutions(jobId, n = 3) {
|
||||
try {
|
||||
return await api.fetchApi(`/translate/jobs/${jobId}/schedule/next-executions?n=${n}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch next executions');
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchNextExecutions:Function]
|
||||
|
||||
// ============================================================
|
||||
// History + Metrics API
|
||||
// ============================================================
|
||||
|
||||
// [DEF:fetchAllRuns:Function]
|
||||
// @PURPOSE: Fetch all runs with cross-job filtering and pagination.
|
||||
export async function fetchAllRuns(options = {}) {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append('page', String(options.page));
|
||||
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
||||
if (options.job_id) params.append('job_id', options.job_id);
|
||||
if (options.status) params.append('status', options.status);
|
||||
if (options.trigger_type) params.append('trigger_type', options.trigger_type);
|
||||
if (options.created_by) params.append('created_by', options.created_by);
|
||||
const query = params.toString();
|
||||
return await api.fetchApi(`/translate/runs${query ? `?${query}` : ''}`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch runs');
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchAllRuns:Function]
|
||||
|
||||
// [DEF:fetchRunDetail:Function]
|
||||
// @PURPOSE: Fetch detailed run info with config_snapshot, records, events.
|
||||
export async function fetchRunDetail(runId) {
|
||||
try {
|
||||
return await api.fetchApi(`/translate/runs/${runId}/detail`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch run detail');
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchRunDetail:Function]
|
||||
|
||||
// [DEF:fetchJobMetrics:Function]
|
||||
// @PURPOSE: Fetch aggregated metrics for a specific job.
|
||||
export async function fetchJobMetrics(jobId) {
|
||||
try {
|
||||
return await api.fetchApi(`/translate/jobs/${jobId}/metrics`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch job metrics');
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchJobMetrics:Function]
|
||||
|
||||
// [DEF:fetchAllMetrics:Function]
|
||||
// @PURPOSE: Fetch aggregated metrics for all jobs.
|
||||
export async function fetchAllMetrics() {
|
||||
try {
|
||||
return await api.fetchApi('/translate/metrics');
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to fetch metrics');
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchAllMetrics:Function]
|
||||
|
||||
// [DEF:downloadSkippedCsv:Function]
|
||||
// @PURPOSE: Download skipped records CSV for a run.
|
||||
export async function downloadSkippedCsv(runId) {
|
||||
try {
|
||||
return await api.fetchApi(`/translate/runs/${runId}/skipped.csv`);
|
||||
} catch (error) {
|
||||
throw normalizeTranslateError(error, 'Failed to download skipped CSV');
|
||||
}
|
||||
}
|
||||
// [/DEF:downloadSkippedCsv:Function]
|
||||
Reference in New Issue
Block a user