feat(14-08): add Analysis Settings section to SettingsCloudTab
- Add cache limit number input with tier cap validation and usage display - Add segmented control for simplified vs detailed analysis progress (D-06) - Add segmented control for pause-batch vs continue-item failure behavior (D-11) - Load cache settings on mount via loadCacheSettings; surface API errors - Keep existing connection health/reconnect/disconnect UX intact - All 471 frontend tests pass; no regressions
This commit is contained in:
@@ -321,6 +321,145 @@
|
|||||||
@close="closeModal"
|
@close="closeModal"
|
||||||
@connected="handleConnected"
|
@connected="handleConnected"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- Phase 14: Analysis and cache settings -->
|
||||||
|
<section
|
||||||
|
data-test="cache-settings-section"
|
||||||
|
class="bg-white border border-gray-200 rounded-xl p-6 mt-4"
|
||||||
|
>
|
||||||
|
<h3 class="text-lg font-semibold text-gray-800 mb-1">Analysis Settings</h3>
|
||||||
|
|
||||||
|
<!-- Settings error banner -->
|
||||||
|
<div
|
||||||
|
v-if="store.cacheSettingsError"
|
||||||
|
data-test="cache-settings-error"
|
||||||
|
class="mb-4 p-3 rounded-lg bg-red-50 border border-red-200 text-sm text-red-700"
|
||||||
|
>
|
||||||
|
{{ store.cacheSettingsError }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-5">
|
||||||
|
|
||||||
|
<!-- Cache limit -->
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
for="cache-limit-input"
|
||||||
|
class="block text-sm font-medium text-gray-700 mb-1"
|
||||||
|
>
|
||||||
|
Cache limit (MB)
|
||||||
|
</label>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<input
|
||||||
|
id="cache-limit-input"
|
||||||
|
data-test="cache-limit-input"
|
||||||
|
type="number"
|
||||||
|
:min="1"
|
||||||
|
:max="cacheLimitMaxMB"
|
||||||
|
:value="cacheLimitMB"
|
||||||
|
@change="handleCacheLimitChange"
|
||||||
|
class="w-28 border border-gray-300 rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||||
|
aria-label="Cache limit in megabytes"
|
||||||
|
/>
|
||||||
|
<span class="text-xs text-gray-500">
|
||||||
|
Max: {{ cacheLimitMaxMB }} MB
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="cacheLimitError"
|
||||||
|
data-test="cache-limit-error"
|
||||||
|
class="text-xs text-red-600"
|
||||||
|
>
|
||||||
|
{{ cacheLimitError }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
v-if="store.cacheUsage"
|
||||||
|
data-test="cache-usage"
|
||||||
|
class="text-xs text-gray-500 mt-1"
|
||||||
|
>
|
||||||
|
Currently using {{ cacheUsageMB }} MB of {{ cacheLimitMB }} MB
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Analysis progress detail -->
|
||||||
|
<div>
|
||||||
|
<span class="block text-sm font-medium text-gray-700 mb-1" id="progress-detail-label">
|
||||||
|
Analysis progress detail
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="flex rounded-lg border border-gray-200 overflow-hidden w-fit"
|
||||||
|
role="group"
|
||||||
|
aria-labelledby="progress-detail-label"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
data-test="progress-simplified"
|
||||||
|
:class="[
|
||||||
|
'px-4 py-1.5 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-indigo-500',
|
||||||
|
!store.detailedAnalysisProgress
|
||||||
|
? 'bg-indigo-600 text-white font-medium'
|
||||||
|
: 'bg-white text-gray-700 hover:bg-gray-50',
|
||||||
|
]"
|
||||||
|
:aria-pressed="!store.detailedAnalysisProgress"
|
||||||
|
@click="handleProgressDetail(false)"
|
||||||
|
>
|
||||||
|
Simplified
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
data-test="progress-detailed"
|
||||||
|
:class="[
|
||||||
|
'px-4 py-1.5 text-sm border-l border-gray-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-indigo-500',
|
||||||
|
store.detailedAnalysisProgress
|
||||||
|
? 'bg-indigo-600 text-white font-medium'
|
||||||
|
: 'bg-white text-gray-700 hover:bg-gray-50',
|
||||||
|
]"
|
||||||
|
:aria-pressed="store.detailedAnalysisProgress"
|
||||||
|
@click="handleProgressDetail(true)"
|
||||||
|
>
|
||||||
|
Detailed
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Failure behavior -->
|
||||||
|
<div>
|
||||||
|
<span class="block text-sm font-medium text-gray-700 mb-1" id="failure-behavior-label">
|
||||||
|
On analysis failure
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="flex rounded-lg border border-gray-200 overflow-hidden w-fit"
|
||||||
|
role="group"
|
||||||
|
aria-labelledby="failure-behavior-label"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
data-test="failure-pause-batch"
|
||||||
|
:class="[
|
||||||
|
'px-4 py-1.5 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-indigo-500',
|
||||||
|
store.failureBehavior === 'pause_batch'
|
||||||
|
? 'bg-indigo-600 text-white font-medium'
|
||||||
|
: 'bg-white text-gray-700 hover:bg-gray-50',
|
||||||
|
]"
|
||||||
|
:aria-pressed="store.failureBehavior === 'pause_batch'"
|
||||||
|
@click="handleFailureBehavior('pause_batch')"
|
||||||
|
>
|
||||||
|
Pause batch
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
data-test="failure-continue-item"
|
||||||
|
:class="[
|
||||||
|
'px-4 py-1.5 text-sm border-l border-gray-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-indigo-500',
|
||||||
|
store.failureBehavior === 'continue_item'
|
||||||
|
? 'bg-indigo-600 text-white font-medium'
|
||||||
|
: 'bg-white text-gray-700 hover:bg-gray-50',
|
||||||
|
]"
|
||||||
|
:aria-pressed="store.failureBehavior === 'continue_item'"
|
||||||
|
@click="handleFailureBehavior('continue_item')"
|
||||||
|
>
|
||||||
|
Skip and continue
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -334,6 +473,31 @@ import CloudCredentialModal from '../cloud/CloudCredentialModal.vue'
|
|||||||
|
|
||||||
const store = useCloudConnectionsStore()
|
const store = useCloudConnectionsStore()
|
||||||
|
|
||||||
|
// ── Phase 14: Cache settings computed helpers ─────────────────────────────────
|
||||||
|
|
||||||
|
/** Default tier cap: 2 GB in MB if server hasn't responded yet. */
|
||||||
|
const DEFAULT_TIER_CAP_BYTES = 2 * 1024 * 1024 * 1024
|
||||||
|
|
||||||
|
/** Tier cap in MB (from server or fallback). */
|
||||||
|
const cacheLimitMaxMB = computed(() => {
|
||||||
|
const cap = store.tierCapBytes ?? DEFAULT_TIER_CAP_BYTES
|
||||||
|
return Math.floor(cap / (1024 * 1024))
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Current cache limit in MB. */
|
||||||
|
const cacheLimitMB = computed(() => {
|
||||||
|
return Math.round(store.cacheLimit / (1024 * 1024))
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Current cache usage in MB (from server). */
|
||||||
|
const cacheUsageMB = computed(() => {
|
||||||
|
if (!store.cacheUsage) return 0
|
||||||
|
return Math.round(store.cacheUsage.total_bytes / (1024 * 1024))
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Local validation error for cache limit input. */
|
||||||
|
const cacheLimitError = ref('')
|
||||||
|
|
||||||
const PROVIDERS = [
|
const PROVIDERS = [
|
||||||
{ key: 'google_drive', label: 'Google Drive', iconColor: 'text-blue-500' },
|
{ key: 'google_drive', label: 'Google Drive', iconColor: 'text-blue-500' },
|
||||||
{ key: 'onedrive', label: 'OneDrive', iconColor: 'text-sky-500' },
|
{ key: 'onedrive', label: 'OneDrive', iconColor: 'text-sky-500' },
|
||||||
@@ -358,6 +522,10 @@ const testingId = ref(null)
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
store.fetchConnections()
|
store.fetchConnections()
|
||||||
|
// Phase 14: hydrate server-authoritative cache settings on mount
|
||||||
|
if (typeof store.loadCacheSettings === 'function') {
|
||||||
|
store.loadCacheSettings()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/** All connections for a given provider key (may be multiple). */
|
/** All connections for a given provider key (may be multiple). */
|
||||||
@@ -530,4 +698,69 @@ async function submitRename(id) {
|
|||||||
renameError.value = e.message || 'Failed to rename connection'
|
renameError.value = e.message || 'Failed to rename connection'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Phase 14: Cache settings handlers ────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle cache limit input change — validate against tier cap, then persist.
|
||||||
|
*/
|
||||||
|
async function handleCacheLimitChange(event) {
|
||||||
|
cacheLimitError.value = ''
|
||||||
|
const rawValue = parseInt(event.target.value, 10)
|
||||||
|
if (isNaN(rawValue) || rawValue < 1) {
|
||||||
|
cacheLimitError.value = 'Enter a value of at least 1 MB'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (rawValue > cacheLimitMaxMB.value) {
|
||||||
|
cacheLimitError.value = `Maximum cache limit is ${cacheLimitMaxMB.value} MB`
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const bytes = rawValue * 1024 * 1024
|
||||||
|
try {
|
||||||
|
if (typeof store.updateCacheSettings === 'function') {
|
||||||
|
await store.updateCacheSettings({ cloud_cache_limit_bytes: bytes })
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// store.cacheSettingsError set by the action; input reverts on next render
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle simplified vs detailed analysis progress mode.
|
||||||
|
*/
|
||||||
|
async function handleProgressDetail(enabled) {
|
||||||
|
if (typeof store.setDetailedAnalysisProgress === 'function') {
|
||||||
|
store.setDetailedAnalysisProgress(enabled)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (typeof store.updateCacheSettings === 'function') {
|
||||||
|
await store.updateCacheSettings({ analysis_progress_detail: enabled })
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Revert local state on API failure
|
||||||
|
if (typeof store.setDetailedAnalysisProgress === 'function') {
|
||||||
|
store.setDetailedAnalysisProgress(!enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change failure behavior for analysis jobs.
|
||||||
|
*/
|
||||||
|
async function handleFailureBehavior(behavior) {
|
||||||
|
const previous = store.failureBehavior
|
||||||
|
if (typeof store.setFailureBehavior === 'function') {
|
||||||
|
store.setFailureBehavior(behavior)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (typeof store.updateCacheSettings === 'function') {
|
||||||
|
await store.updateCacheSettings({ analysis_failure_behavior: behavior })
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Revert on failure
|
||||||
|
if (typeof store.setFailureBehavior === 'function') {
|
||||||
|
store.setFailureBehavior(previous)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user