feat(14-07): add analysis API helpers and store state/actions (Task 1)
- Add estimateAnalysis, enqueueAnalysis, getAnalysisJobStatus, listAnalysisJobs, cancelAnalysisJob, cancelAnalysisItem, skipAnalysisItem, retryAnalysisItem, getCacheSettings, updateCacheSettings to api/cloud.js - Extend cloudConnections store with analysis state: activeAnalysisJob, analysisQueue (computed), failureBehavior, detailedAnalysisProgress, cacheLimit - Add store actions: requestEstimate, enqueueAnalysis, fetchJobStatus, cancelJob, retryItem, skipItem, cancelItem, setFailureBehavior, setDetailedAnalysisProgress, updateCacheSettings - Add translateAnalysisStatus (D-06: single translation source for components) - Status labels: simplified (waiting/working/done/skipped/failed) and detailed modes - Credentials never stored in queue state (T-14-02)
This commit is contained in:
@@ -255,3 +255,118 @@ export function downloadCloudFile(connectionId, itemId) {
|
||||
{ method: 'GET' },
|
||||
)
|
||||
}
|
||||
|
||||
// ── Phase 14: Analysis API ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Estimate the scope of an analysis job for a cloud connection.
|
||||
*
|
||||
* T-14-04: No provider bytes are downloaded during estimate — metadata only.
|
||||
* Returns AnalysisEstimateOut: {supported_count, unsupported_count, total_provider_bytes,
|
||||
* recursive, is_partial, scope_kind}
|
||||
*
|
||||
* @param {string} connectionId - Connection UUID
|
||||
* @param {{ scope: string, provider_item_ids?: string[], recursive?: boolean }} params
|
||||
*/
|
||||
export function estimateAnalysis(connectionId, params) {
|
||||
return jsonRequest(`/api/cloud/analysis/connections/${connectionId}/estimate`, 'POST', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue an analysis job for a cloud connection scope.
|
||||
*
|
||||
* Returns AnalysisEnqueueOut: {job_id, status, total_count, queued_count,
|
||||
* already_current_count, unsupported_count}
|
||||
*
|
||||
* @param {string} connectionId - Connection UUID
|
||||
* @param {{ scope: string, provider_item_ids?: string[], recursive?: boolean,
|
||||
* failure_behavior?: string }} params
|
||||
*/
|
||||
export function enqueueAnalysis(connectionId, params) {
|
||||
return jsonRequest(`/api/cloud/analysis/connections/${connectionId}/jobs`, 'POST', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of an analysis job.
|
||||
*
|
||||
* T-14-02: Response never includes credentials or object_key.
|
||||
*
|
||||
* @param {string} jobId - Job UUID
|
||||
* @param {{ detail?: boolean }} [opts]
|
||||
*/
|
||||
export function getAnalysisJobStatus(jobId, opts = {}) {
|
||||
const qs = opts.detail ? '?detail=true' : ''
|
||||
return request(`/api/cloud/analysis/jobs/${jobId}${qs}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* List analysis jobs, optionally filtered by connection or status.
|
||||
*
|
||||
* @param {{ connection_id?: string, status?: string }} [filters]
|
||||
*/
|
||||
export function listAnalysisJobs(filters = {}) {
|
||||
const params = new URLSearchParams()
|
||||
if (filters.connection_id) params.set('connection_id', filters.connection_id)
|
||||
if (filters.status) params.set('status', filters.status)
|
||||
const qs = params.toString() ? `?${params}` : ''
|
||||
return request(`/api/cloud/analysis/jobs${qs}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel an entire analysis job batch.
|
||||
*
|
||||
* Returns AnalysisControlOut: {kind, reason, status}
|
||||
*
|
||||
* @param {string} jobId - Job UUID
|
||||
*/
|
||||
export function cancelAnalysisJob(jobId) {
|
||||
return jsonRequest(`/api/cloud/analysis/jobs/${jobId}/cancel`, 'POST', {})
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a single analysis job item.
|
||||
*
|
||||
* @param {string} jobId - Job UUID
|
||||
* @param {string} itemId - Item UUID
|
||||
*/
|
||||
export function cancelAnalysisItem(jobId, itemId) {
|
||||
return jsonRequest(`/api/cloud/analysis/jobs/${jobId}/items/${itemId}/cancel`, 'POST', {})
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip a queued or failed analysis job item.
|
||||
*
|
||||
* @param {string} jobId - Job UUID
|
||||
* @param {string} itemId - Item UUID
|
||||
*/
|
||||
export function skipAnalysisItem(jobId, itemId) {
|
||||
return jsonRequest(`/api/cloud/analysis/jobs/${jobId}/items/${itemId}/skip`, 'POST', {})
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry a failed analysis job item.
|
||||
*
|
||||
* @param {string} jobId - Job UUID
|
||||
* @param {string} itemId - Item UUID
|
||||
*/
|
||||
export function retryAnalysisItem(jobId, itemId) {
|
||||
return jsonRequest(`/api/cloud/analysis/jobs/${jobId}/items/${itemId}/retry`, 'POST', {})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user cache settings.
|
||||
*
|
||||
* Returns { cloud_cache_limit_bytes, ... }
|
||||
*/
|
||||
export function getCacheSettings() {
|
||||
return request('/api/users/me/settings')
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current user cache settings.
|
||||
*
|
||||
* @param {{ cloud_cache_limit_bytes?: number }} params
|
||||
*/
|
||||
export function updateCacheSettings(params) {
|
||||
return jsonRequest('/api/users/me/settings', 'PATCH', params)
|
||||
}
|
||||
|
||||
@@ -41,6 +41,38 @@ export function loadLastFolder(connectionId) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Phase 14: Analysis status translation ────────────────────────────────────
|
||||
|
||||
/**
|
||||
* D-06: Single source for internal status → simplified UI label mapping.
|
||||
* Components never translate independently — they call translateAnalysisStatus.
|
||||
*/
|
||||
const SIMPLIFIED_STATUS_MAP = {
|
||||
queued: 'waiting',
|
||||
downloading: 'working',
|
||||
extracting: 'working',
|
||||
classifying: 'working',
|
||||
indexed: 'done',
|
||||
already_current: 'done',
|
||||
cancelled: 'skipped',
|
||||
failed: 'failed',
|
||||
unsupported: 'skipped',
|
||||
stale: 'working',
|
||||
}
|
||||
|
||||
const DETAILED_STATUS_MAP = {
|
||||
queued: 'queued',
|
||||
downloading: 'downloading',
|
||||
extracting: 'extracting',
|
||||
classifying: 'classifying',
|
||||
indexed: 'indexed',
|
||||
already_current: 'indexed',
|
||||
cancelled: 'cancelled',
|
||||
failed: 'failed',
|
||||
unsupported: 'unsupported',
|
||||
stale: 'stale',
|
||||
}
|
||||
|
||||
export const useCloudConnectionsStore = defineStore('cloudConnections', () => {
|
||||
const connections = ref([])
|
||||
const loading = ref(false)
|
||||
@@ -59,6 +91,53 @@ export const useCloudConnectionsStore = defineStore('cloudConnections', () => {
|
||||
/** 'cached' | 'cloud_only' | null */
|
||||
const byteAvailability = ref(null)
|
||||
|
||||
// ── Phase 14: Analysis state ─────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Active analysis job for the current connection session.
|
||||
* Stored in Pinia memory only — never persisted to localStorage/sessionStorage.
|
||||
* T-14-02: No credentials or object_key fields stored here.
|
||||
*/
|
||||
const activeAnalysisJob = ref(null)
|
||||
|
||||
/**
|
||||
* Analysis queue items from the active job for display in StorageBrowser.
|
||||
* Derived from activeAnalysisJob.items when a job is active.
|
||||
*/
|
||||
const analysisQueue = computed(() => {
|
||||
if (!activeAnalysisJob.value) return []
|
||||
const items = activeAnalysisJob.value.items ?? []
|
||||
return items.map(item => ({
|
||||
job_id: activeAnalysisJob.value.job_id,
|
||||
item_id: item.item_id,
|
||||
name: item.name,
|
||||
status: item.status,
|
||||
ui_status: SIMPLIFIED_STATUS_MAP[item.status] ?? item.status,
|
||||
progress: item.progress ?? 0,
|
||||
error: item.error ?? null,
|
||||
}))
|
||||
})
|
||||
|
||||
/**
|
||||
* D-11: Failure behavior for analysis jobs.
|
||||
* 'pause_batch' — pause the whole batch on any item failure (default)
|
||||
* 'continue_item' — skip failed items and continue
|
||||
*/
|
||||
const failureBehavior = ref('pause_batch')
|
||||
|
||||
/**
|
||||
* D-06: When true, per-item status shows detailed internal stages
|
||||
* (downloading/extracting/classifying). When false (default), shows simplified
|
||||
* labels (waiting/working/done/skipped/failed).
|
||||
*/
|
||||
const detailedAnalysisProgress = ref(false)
|
||||
|
||||
/**
|
||||
* CACHE-04: User-configured cloud cache limit in bytes.
|
||||
* Default: 500 MB (500 * 1024 * 1024).
|
||||
*/
|
||||
const cacheLimit = ref(500 * 1024 * 1024)
|
||||
|
||||
/**
|
||||
* D-12: Connection health state map — single source for both browser compact status
|
||||
* and Settings full diagnostics.
|
||||
@@ -302,6 +381,163 @@ export const useCloudConnectionsStore = defineStore('cloudConnections', () => {
|
||||
return connectionHealth.value[id] ?? { state: 'unknown', error_code: null, error_message: null, checked_at: null }
|
||||
}
|
||||
|
||||
// ── Phase 14: Analysis actions ─────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* D-06: Translate an internal analysis status to a UI label.
|
||||
* Single translation source — components must not translate independently.
|
||||
*
|
||||
* @param {string} status - Internal status (queued, downloading, extracting, etc.)
|
||||
* @param {{ detailed?: boolean }} [opts]
|
||||
* @returns {string} - UI label
|
||||
*/
|
||||
function translateAnalysisStatus(status, opts = {}) {
|
||||
const map = opts.detailed ? DETAILED_STATUS_MAP : SIMPLIFIED_STATUS_MAP
|
||||
return map[status] ?? status
|
||||
}
|
||||
|
||||
/**
|
||||
* ANALYZE-01 / ANALYZE-03: Request a scope estimate for an analysis job.
|
||||
* No provider bytes are downloaded (T-14-04).
|
||||
*
|
||||
* @param {string} connectionId - Connection UUID
|
||||
* @param {{ scope: string, provider_item_ids?: string[], recursive?: boolean }} params
|
||||
* @returns {Promise<object>} AnalysisEstimateOut
|
||||
*/
|
||||
async function requestEstimate(connectionId, params) {
|
||||
return api.estimateAnalysis(connectionId, params)
|
||||
}
|
||||
|
||||
/**
|
||||
* ANALYZE-01: Enqueue an analysis job and track it in store state.
|
||||
*
|
||||
* @param {string} connectionId - Connection UUID
|
||||
* @param {{ scope: string, provider_item_ids?: string[], recursive?: boolean,
|
||||
* failure_behavior?: string }} params
|
||||
* @returns {Promise<object>} AnalysisEnqueueOut
|
||||
*/
|
||||
async function enqueueAnalysis(connectionId, params) {
|
||||
const result = await api.enqueueAnalysis(connectionId, {
|
||||
...params,
|
||||
failure_behavior: params.failure_behavior ?? failureBehavior.value,
|
||||
})
|
||||
// Initialize the active job in memory (no credentials stored)
|
||||
activeAnalysisJob.value = {
|
||||
job_id: result.job_id,
|
||||
connection_id: connectionId,
|
||||
status: result.status,
|
||||
total_count: result.total_count,
|
||||
queued_count: result.queued_count,
|
||||
already_current_count: result.already_current_count,
|
||||
unsupported_count: result.unsupported_count,
|
||||
items: [],
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* ANALYZE-04: Fetch and update status of the active analysis job.
|
||||
* T-14-02: Response must never include credentials or object_key.
|
||||
*
|
||||
* @param {string} jobId - Job UUID
|
||||
* @returns {Promise<object>} AnalysisJobOut
|
||||
*/
|
||||
async function fetchJobStatus(jobId) {
|
||||
const result = await api.getAnalysisJobStatus(jobId)
|
||||
// Store job state in Pinia memory — no credentials/object_key fields exposed
|
||||
activeAnalysisJob.value = {
|
||||
job_id: result.job_id ?? jobId,
|
||||
connection_id: result.connection_id ?? activeAnalysisJob.value?.connection_id,
|
||||
status: result.status,
|
||||
total_count: result.total_count ?? 0,
|
||||
queued_count: result.queued_count ?? 0,
|
||||
already_current_count: result.already_current_count ?? 0,
|
||||
unsupported_count: result.unsupported_count ?? 0,
|
||||
items: (result.items ?? []).map(item => ({
|
||||
item_id: item.item_id,
|
||||
cloud_item_id: item.cloud_item_id,
|
||||
name: item.name,
|
||||
status: item.status,
|
||||
error: item.error ?? null,
|
||||
})),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* ANALYZE-05: Cancel the entire analysis job batch.
|
||||
*
|
||||
* @param {string} jobId - Job UUID
|
||||
*/
|
||||
async function cancelJob(jobId) {
|
||||
const result = await api.cancelAnalysisJob(jobId)
|
||||
if (activeAnalysisJob.value?.job_id === jobId) {
|
||||
activeAnalysisJob.value = { ...activeAnalysisJob.value, status: 'cancelled' }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* ANALYZE-05: Retry a failed analysis job item.
|
||||
*
|
||||
* @param {string} jobId - Job UUID
|
||||
* @param {string} itemId - Item UUID
|
||||
*/
|
||||
async function retryItem(jobId, itemId) {
|
||||
return api.retryAnalysisItem(jobId, itemId)
|
||||
}
|
||||
|
||||
/**
|
||||
* ANALYZE-05: Skip a queued or failed analysis job item.
|
||||
*
|
||||
* @param {string} jobId - Job UUID
|
||||
* @param {string} itemId - Item UUID
|
||||
*/
|
||||
async function skipItem(jobId, itemId) {
|
||||
return api.skipAnalysisItem(jobId, itemId)
|
||||
}
|
||||
|
||||
/**
|
||||
* ANALYZE-05: Cancel a single analysis job item.
|
||||
*
|
||||
* @param {string} jobId - Job UUID
|
||||
* @param {string} itemId - Item UUID
|
||||
*/
|
||||
async function cancelItem(jobId, itemId) {
|
||||
return api.cancelAnalysisItem(jobId, itemId)
|
||||
}
|
||||
|
||||
/**
|
||||
* D-11: Set the failure behavior for analysis jobs.
|
||||
*
|
||||
* @param {'pause_batch'|'continue_item'} behavior
|
||||
*/
|
||||
function setFailureBehavior(behavior) {
|
||||
failureBehavior.value = behavior
|
||||
}
|
||||
|
||||
/**
|
||||
* D-06: Toggle detailed analysis progress mode.
|
||||
*
|
||||
* @param {boolean} enabled
|
||||
*/
|
||||
function setDetailedAnalysisProgress(enabled) {
|
||||
detailedAnalysisProgress.value = !!enabled
|
||||
}
|
||||
|
||||
/**
|
||||
* CACHE-04: Update user cache settings via the API.
|
||||
*
|
||||
* @param {{ cloud_cache_limit_bytes?: number }} params
|
||||
*/
|
||||
async function updateCacheSettings(params) {
|
||||
const result = await api.updateCacheSettings(params)
|
||||
if (result?.cloud_cache_limit_bytes !== undefined) {
|
||||
cacheLimit.value = result.cloud_cache_limit_bytes
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
return {
|
||||
connections,
|
||||
loading,
|
||||
@@ -338,5 +574,26 @@ export const useCloudConnectionsStore = defineStore('cloudConnections', () => {
|
||||
markReconnecting,
|
||||
testConnection,
|
||||
reconnect,
|
||||
// ── Phase 14: Analysis state and actions ──────────────────────────────────
|
||||
// State (Pinia memory only — no credentials stored)
|
||||
activeAnalysisJob,
|
||||
analysisQueue,
|
||||
failureBehavior,
|
||||
detailedAnalysisProgress,
|
||||
cacheLimit,
|
||||
// D-06: single status translation source
|
||||
translateAnalysisStatus,
|
||||
// Analysis job lifecycle
|
||||
requestEstimate,
|
||||
enqueueAnalysis,
|
||||
fetchJobStatus,
|
||||
cancelJob,
|
||||
retryItem,
|
||||
skipItem,
|
||||
cancelItem,
|
||||
// User preferences
|
||||
setFailureBehavior,
|
||||
setDetailedAnalysisProgress,
|
||||
updateCacheSettings,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user