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' },
|
{ 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', () => {
|
export const useCloudConnectionsStore = defineStore('cloudConnections', () => {
|
||||||
const connections = ref([])
|
const connections = ref([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -59,6 +91,53 @@ export const useCloudConnectionsStore = defineStore('cloudConnections', () => {
|
|||||||
/** 'cached' | 'cloud_only' | null */
|
/** 'cached' | 'cloud_only' | null */
|
||||||
const byteAvailability = ref(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
|
* D-12: Connection health state map — single source for both browser compact status
|
||||||
* and Settings full diagnostics.
|
* 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 }
|
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 {
|
return {
|
||||||
connections,
|
connections,
|
||||||
loading,
|
loading,
|
||||||
@@ -338,5 +574,26 @@ export const useCloudConnectionsStore = defineStore('cloudConnections', () => {
|
|||||||
markReconnecting,
|
markReconnecting,
|
||||||
testConnection,
|
testConnection,
|
||||||
reconnect,
|
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