{ "verdict": "APPROVED", "rejection_reason": "NONE", "audit_details": { "target_invoked": true, "pre_conditions_tested": true, "post_conditions_tested": true, "test_data_used": true }, "feedback": "The test suite robustly verifies the
MigrationEngine contracts. It avoids Tautologies by cleanly substituting IdMappingService without mocking the engine itself. Cross-filter parsing asserts against hard-coded, predefined validation dictionaries (no Logic Mirroring). It successfully addresses @PRE negative cases (e.g. invalid zip paths, missing YAMLs) and rigorously validates @POST file transformations (e.g. in-place UUID substitutions and archive reconstruction)." }
This commit is contained in:
@@ -148,17 +148,28 @@
|
||||
// Migration Settings State
|
||||
let migrationCron = "0 2 * * *";
|
||||
let displayMappings = [];
|
||||
let mappingsTotal = 0;
|
||||
let mappingsPage = 0;
|
||||
let mappingsPageSize = 25;
|
||||
let mappingsSearch = "";
|
||||
let mappingsEnvFilter = "";
|
||||
let mappingsTypeFilter = "";
|
||||
let isSavingMigration = false;
|
||||
let isLoadingMigration = false;
|
||||
let isSyncing = false;
|
||||
let searchTimeout = null;
|
||||
|
||||
$: mappingsTotalPages = Math.max(
|
||||
1,
|
||||
Math.ceil(mappingsTotal / mappingsPageSize),
|
||||
);
|
||||
|
||||
async function loadMigrationSettings() {
|
||||
isLoadingMigration = true;
|
||||
try {
|
||||
const settingsRes = await api.requestApi("/migration/settings");
|
||||
migrationCron = settingsRes.cron;
|
||||
const mappingsRes = await api.requestApi("/migration/mappings-data");
|
||||
displayMappings = mappingsRes;
|
||||
await loadMappingsPage();
|
||||
} catch (err) {
|
||||
console.error("[SettingsPage][Migration] Failed to load:", err);
|
||||
} finally {
|
||||
@@ -166,6 +177,43 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMappingsPage() {
|
||||
try {
|
||||
const skip = mappingsPage * mappingsPageSize;
|
||||
let url = `/migration/mappings-data?skip=${skip}&limit=${mappingsPageSize}`;
|
||||
if (mappingsSearch)
|
||||
url += `&search=${encodeURIComponent(mappingsSearch)}`;
|
||||
if (mappingsEnvFilter)
|
||||
url += `&env_id=${encodeURIComponent(mappingsEnvFilter)}`;
|
||||
if (mappingsTypeFilter)
|
||||
url += `&resource_type=${encodeURIComponent(mappingsTypeFilter)}`;
|
||||
const res = await api.requestApi(url);
|
||||
displayMappings = res.items || [];
|
||||
mappingsTotal = res.total || 0;
|
||||
} catch (err) {
|
||||
console.error("[SettingsPage][Migration] Failed to load mappings:", err);
|
||||
}
|
||||
}
|
||||
|
||||
function onMappingsSearchInput(e) {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
mappingsPage = 0;
|
||||
loadMappingsPage();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function onMappingsFilterChange() {
|
||||
mappingsPage = 0;
|
||||
loadMappingsPage();
|
||||
}
|
||||
|
||||
function goToMappingsPage(page) {
|
||||
if (page < 0 || page >= mappingsTotalPages) return;
|
||||
mappingsPage = page;
|
||||
loadMappingsPage();
|
||||
}
|
||||
|
||||
async function saveMigrationSettings() {
|
||||
isSavingMigration = true;
|
||||
try {
|
||||
@@ -1076,7 +1124,12 @@
|
||||
<h3
|
||||
class="text-lg font-medium mb-4 flex items-center justify-between"
|
||||
>
|
||||
<span>Synchronized Resources</span>
|
||||
<span
|
||||
>Synchronized Resources <span
|
||||
class="text-sm font-normal text-gray-500"
|
||||
>({mappingsTotal})</span
|
||||
></span
|
||||
>
|
||||
<button
|
||||
on:click={loadMigrationSettings}
|
||||
class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center gap-1"
|
||||
@@ -1098,6 +1151,38 @@
|
||||
</button>
|
||||
</h3>
|
||||
|
||||
<!-- Search and Filters -->
|
||||
<div class="flex flex-wrap gap-3 mb-4">
|
||||
<div class="flex-1 min-w-[200px]">
|
||||
<input
|
||||
type="text"
|
||||
bind:value={mappingsSearch}
|
||||
on:input={onMappingsSearchInput}
|
||||
placeholder="Search by name or UUID..."
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:ring-indigo-500 focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<select
|
||||
bind:value={mappingsEnvFilter}
|
||||
on:change={onMappingsFilterChange}
|
||||
class="px-3 py-2 border border-gray-300 rounded-md text-sm bg-white focus:ring-indigo-500 focus:border-indigo-500"
|
||||
>
|
||||
<option value="">All Environments</option>
|
||||
<option value="ss1">ss1</option>
|
||||
<option value="ss2">ss2</option>
|
||||
</select>
|
||||
<select
|
||||
bind:value={mappingsTypeFilter}
|
||||
on:change={onMappingsFilterChange}
|
||||
class="px-3 py-2 border border-gray-300 rounded-md text-sm bg-white focus:ring-indigo-500 focus:border-indigo-500"
|
||||
>
|
||||
<option value="">All Types</option>
|
||||
<option value="chart">Chart</option>
|
||||
<option value="dataset">Dataset</option>
|
||||
<option value="dashboard">Dashboard</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto border border-gray-200 rounded-lg">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="bg-gray-100">
|
||||
@@ -1138,7 +1223,11 @@
|
||||
><td
|
||||
colspan="5"
|
||||
class="px-6 py-8 text-center text-gray-500"
|
||||
>No synchronized resources found.</td
|
||||
>{mappingsSearch ||
|
||||
mappingsEnvFilter ||
|
||||
mappingsTypeFilter
|
||||
? "No matching resources found."
|
||||
: "No synchronized resources found."}</td
|
||||
></tr
|
||||
>
|
||||
{:else}
|
||||
@@ -1150,7 +1239,12 @@
|
||||
>
|
||||
<td class="px-6 py-4 whitespace-nowrap"
|
||||
><span
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {mapping.resource_type ===
|
||||
'chart'
|
||||
? 'bg-blue-100 text-blue-800'
|
||||
: mapping.resource_type === 'dataset'
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-purple-100 text-purple-800'}"
|
||||
>{mapping.resource_type}</span
|
||||
></td
|
||||
>
|
||||
@@ -1171,6 +1265,55 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination Controls -->
|
||||
{#if mappingsTotal > mappingsPageSize}
|
||||
<div
|
||||
class="flex items-center justify-between mt-4 text-sm text-gray-600"
|
||||
>
|
||||
<span
|
||||
>Showing {mappingsPage * mappingsPageSize + 1}–{Math.min(
|
||||
(mappingsPage + 1) * mappingsPageSize,
|
||||
mappingsTotal,
|
||||
)} of {mappingsTotal}</span
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
on:click={() => goToMappingsPage(0)}
|
||||
disabled={mappingsPage === 0}
|
||||
class="px-2 py-1 rounded border {mappingsPage === 0
|
||||
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
||||
: 'bg-white hover:bg-gray-50 text-gray-700'}">«</button
|
||||
>
|
||||
<button
|
||||
on:click={() => goToMappingsPage(mappingsPage - 1)}
|
||||
disabled={mappingsPage === 0}
|
||||
class="px-2 py-1 rounded border {mappingsPage === 0
|
||||
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
||||
: 'bg-white hover:bg-gray-50 text-gray-700'}">‹</button
|
||||
>
|
||||
<span class="px-3 py-1 font-medium"
|
||||
>{mappingsPage + 1} / {mappingsTotalPages}</span
|
||||
>
|
||||
<button
|
||||
on:click={() => goToMappingsPage(mappingsPage + 1)}
|
||||
disabled={mappingsPage >= mappingsTotalPages - 1}
|
||||
class="px-2 py-1 rounded border {mappingsPage >=
|
||||
mappingsTotalPages - 1
|
||||
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
||||
: 'bg-white hover:bg-gray-50 text-gray-700'}">›</button
|
||||
>
|
||||
<button
|
||||
on:click={() => goToMappingsPage(mappingsTotalPages - 1)}
|
||||
disabled={mappingsPage >= mappingsTotalPages - 1}
|
||||
class="px-2 py-1 rounded border {mappingsPage >=
|
||||
mappingsTotalPages - 1
|
||||
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
||||
: 'bg-white hover:bg-gray-50 text-gray-700'}">»</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else if activeTab === "storage"}
|
||||
|
||||
Reference in New Issue
Block a user