test deleteMissing with schema changes
This commit is contained in:
@@ -2,29 +2,28 @@
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import ApiClient from "@/utils/ApiClient";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import OverlayPanel from "@/components/base/OverlayPanel.svelte";
|
||||
import { addSuccessToast } from "@/stores/toasts";
|
||||
import { confirm } from "@/stores/confirmation";
|
||||
import OverlayPanel from "@/components/base/OverlayPanel.svelte";
|
||||
import CollectionsDiffTable from "@/components/collections/CollectionsDiffTable.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let panel;
|
||||
let oldCollections = [];
|
||||
let newCollections = [];
|
||||
let changes = [];
|
||||
let pairs = [];
|
||||
let deleteMissing = false;
|
||||
let isImporting = false;
|
||||
|
||||
$: if (Array.isArray(oldCollections) && Array.isArray(newCollections)) {
|
||||
loadChanges();
|
||||
loadPairs();
|
||||
}
|
||||
|
||||
$: deletedCollections = oldCollections.filter((old) => {
|
||||
return !CommonHelper.findByKey(newCollections, "id", old.id)?.id;
|
||||
});
|
||||
|
||||
export function show(a, b) {
|
||||
oldCollections = a;
|
||||
newCollections = b;
|
||||
export function show(oldCollectionsArg, newCollectionsArg, deleteMissingArg = false) {
|
||||
oldCollections = oldCollectionsArg;
|
||||
newCollections = newCollectionsArg;
|
||||
deleteMissing = deleteMissingArg;
|
||||
|
||||
panel?.show();
|
||||
}
|
||||
@@ -33,25 +32,23 @@
|
||||
return panel?.hide();
|
||||
}
|
||||
|
||||
function loadChanges() {
|
||||
changes = [];
|
||||
function loadPairs() {
|
||||
pairs = [];
|
||||
|
||||
// add deleted and modified collections
|
||||
for (const oldCollection of oldCollections) {
|
||||
const newCollection = CommonHelper.findByKey(newCollections, "id", oldCollection.id) || null;
|
||||
if (!newCollection?.id || JSON.stringify(oldCollection) != JSON.stringify(newCollection)) {
|
||||
changes.push({
|
||||
old: oldCollection,
|
||||
new: newCollection,
|
||||
});
|
||||
}
|
||||
pairs.push({
|
||||
old: oldCollection,
|
||||
new: newCollection,
|
||||
});
|
||||
}
|
||||
|
||||
// add only new collections
|
||||
for (const newCollection of newCollections) {
|
||||
const oldCollection = CommonHelper.findByKey(oldCollections, "id", newCollection.id) || null;
|
||||
if (!oldCollection?.id) {
|
||||
changes.push({
|
||||
pairs.push({
|
||||
old: oldCollection,
|
||||
new: newCollection,
|
||||
});
|
||||
@@ -59,63 +56,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
function diffsToHtml(diffs, ops = [window.DIFF_INSERT, window.DIFF_DELETE, window.DIFF_EQUAL]) {
|
||||
const html = [];
|
||||
const pattern_amp = /&/g;
|
||||
const pattern_lt = /</g;
|
||||
const pattern_gt = />/g;
|
||||
const pattern_para = /\n/g;
|
||||
|
||||
for (let i = 0; i < diffs.length; i++) {
|
||||
const op = diffs[i][0]; // operation (insert, delete, equal)
|
||||
|
||||
if (!ops.includes(op)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const text = diffs[i][1]
|
||||
.replace(pattern_amp, "&")
|
||||
.replace(pattern_lt, "<")
|
||||
.replace(pattern_gt, ">")
|
||||
.replace(pattern_para, "<br>");
|
||||
|
||||
switch (op) {
|
||||
case DIFF_INSERT:
|
||||
html[i] = '<ins class="block">' + text + "</ins>";
|
||||
break;
|
||||
case DIFF_DELETE:
|
||||
html[i] = '<del class="block">' + text + "</del>";
|
||||
break;
|
||||
case DIFF_EQUAL:
|
||||
html[i] = text;
|
||||
break;
|
||||
function submitWithConfirm() {
|
||||
// find deleted fields
|
||||
const deletedFieldNames = [];
|
||||
if (deleteMissing) {
|
||||
for (const old of oldCollections) {
|
||||
const imported = !CommonHelper.findByKey(newCollections, "id", old.id);
|
||||
if (!imported) {
|
||||
// add all fields
|
||||
deletedFieldNames.push(old.name + ".*");
|
||||
} else {
|
||||
// add only deleted fields
|
||||
const schema = Array.isArray(old.schema) ? old.schema : [];
|
||||
for (const field of schema) {
|
||||
if (!CommonHelper.findByKey(imported.schema, "id", field.id)) {
|
||||
deletedFieldNames.push(old.name + "." + field.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return html.join("");
|
||||
}
|
||||
|
||||
function diff(obj1, obj2, ops = [window.DIFF_INSERT, window.DIFF_DELETE, window.DIFF_EQUAL]) {
|
||||
const dmp = new diff_match_patch();
|
||||
const lines = dmp.diff_linesToChars_(
|
||||
obj1 ? JSON.stringify(obj1, null, 4) : "",
|
||||
obj2 ? JSON.stringify(obj2, null, 4) : ""
|
||||
);
|
||||
const diffs = dmp.diff_main(lines.chars1, lines.chars2, false);
|
||||
|
||||
dmp.diff_charsToLines_(diffs, lines.lineArray);
|
||||
|
||||
return diffsToHtml(diffs, ops);
|
||||
}
|
||||
|
||||
function submitWithConfirm() {
|
||||
if (deletedCollections.length) {
|
||||
const deletedNames = deletedCollections.map((c) => c.name);
|
||||
|
||||
if (deletedFieldNames.length) {
|
||||
confirm(
|
||||
`Do you really want to delete the following collections and their related records data:\n- ${deletedNames.join(
|
||||
`Do you really want to delete the following collection fields and their related records data:\n- ${deletedFieldNames.join(
|
||||
"\n- "
|
||||
)}?`,
|
||||
)}`,
|
||||
() => {
|
||||
submit();
|
||||
}
|
||||
@@ -133,8 +99,8 @@
|
||||
isImporting = true;
|
||||
|
||||
try {
|
||||
await ApiClient.collections.import(newCollections, true);
|
||||
addSuccessToast("Successfully imported the collections configuration.");
|
||||
await ApiClient.collections.import(newCollections, deleteMissing);
|
||||
addSuccessToast("Successfully imported collections configuration.");
|
||||
dispatch("submit");
|
||||
} catch (err) {
|
||||
ApiClient.errorResponseHandler(err);
|
||||
@@ -148,7 +114,7 @@
|
||||
|
||||
<OverlayPanel
|
||||
bind:this={panel}
|
||||
class="full-width-popup import-popup"
|
||||
class="full-width-popup import-popup"
|
||||
overlayClose={false}
|
||||
escClose={!isImporting}
|
||||
beforeHide={() => !isImporting}
|
||||
@@ -160,40 +126,9 @@
|
||||
<h4 class="center txt-break">Side-by-side diff</h4>
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="grid grid-sm m-b-sm">
|
||||
{#each changes as pair (pair.old?.id + pair.new?.id)}
|
||||
<div class="col-12">
|
||||
<div class="flex flex-gap-10">
|
||||
{#if !pair.old?.id}
|
||||
<span class="label label-success">New</span>
|
||||
<strong>{pair.new?.name}</strong>
|
||||
{:else if !pair.new?.id}
|
||||
<span class="label label-danger">Deleted</span>
|
||||
<strong>{pair.old?.name}</strong>
|
||||
{:else}
|
||||
<span class="label label-warning">Modified</span>
|
||||
<div class="inline-flex fleg-gap-5">
|
||||
{#if pair.old.name !== pair.new.name}
|
||||
<strong class="txt-strikethrough txt-hint">{pair.old.name}</strong>
|
||||
<i class="ri-arrow-right-line txt-sm" />
|
||||
{/if}
|
||||
<strong class="txt">{pair.new.name}</strong>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 p-b-10">
|
||||
<code class="code-block">
|
||||
{@html diff(pair.old, pair.new, [window.DIFF_DELETE, window.DIFF_EQUAL]) || "N/A"}
|
||||
</code>
|
||||
</div>
|
||||
<div class="col-6 p-b-10">
|
||||
<code class="code-block">
|
||||
{@html diff(pair.old, pair.new, [window.DIFF_INSERT, window.DIFF_EQUAL]) || "N/A"}
|
||||
</code>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#each pairs as pair}
|
||||
<CollectionsDiffTable collectionA={pair.old} collectionB={pair.new} {deleteMissing} />
|
||||
{/each}
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<button type="button" class="btn btn-secondary" on:click={hide} disabled={isImporting}>Close</button>
|
||||
@@ -208,10 +143,3 @@
|
||||
</button>
|
||||
</svelte:fragment>
|
||||
</OverlayPanel>
|
||||
|
||||
<style>
|
||||
code {
|
||||
color: var(--txtHintColor);
|
||||
min-height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
let collections = [];
|
||||
let isLoadingCollections = false;
|
||||
|
||||
$: schema = JSON.stringify(collections, null, 2);
|
||||
$: schema = JSON.stringify(collections, null, 4);
|
||||
|
||||
loadCollections();
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
let isLoadingFile = false;
|
||||
let newCollections = [];
|
||||
let oldCollections = [];
|
||||
let collectionsToModify = [];
|
||||
let deleteMissing = false;
|
||||
let collectionsToChange = [];
|
||||
let isLoadingOldCollections = false;
|
||||
|
||||
$: if (typeof schemas !== "undefined") {
|
||||
@@ -44,10 +45,32 @@
|
||||
}
|
||||
|
||||
$: hasChanges =
|
||||
!!schemas && (collectionsToDelete.length || collectionsToAdd.length || collectionsToModify.length);
|
||||
!!schemas && (collectionsToDelete.length || collectionsToAdd.length || collectionsToChange.length);
|
||||
|
||||
$: canImport = !isLoadingOldCollections && isValid && hasChanges;
|
||||
|
||||
$: idReplacableCollections = newCollections.filter((collection) => {
|
||||
const old = CommonHelper.findByKey(oldCollections, "name", collection.name);
|
||||
if (!old?.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (old.id != collection.id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const oldSchema = Array.isArray(old.schema) ? old.schema : [];
|
||||
const newSchema = Array.isArray(collection.schema) ? collection.schema : [];
|
||||
for (const field of newSchema) {
|
||||
const oldField = CommonHelper.findByKey(oldSchema, "name", field.name);
|
||||
if (oldField && field.id != oldField.id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
loadOldCollections();
|
||||
|
||||
async function loadOldCollections() {
|
||||
@@ -68,7 +91,7 @@
|
||||
}
|
||||
|
||||
function loadCollectionsToModify() {
|
||||
collectionsToModify = [];
|
||||
collectionsToChange = [];
|
||||
|
||||
if (!isValid) {
|
||||
return;
|
||||
@@ -85,7 +108,7 @@
|
||||
continue;
|
||||
}
|
||||
|
||||
collectionsToModify.push({
|
||||
collectionsToChange.push({
|
||||
new: newCollection,
|
||||
old: oldCollection,
|
||||
});
|
||||
@@ -105,13 +128,52 @@
|
||||
newCollections = CommonHelper.filterDuplicatesByKey(newCollections);
|
||||
}
|
||||
|
||||
// delete timestamps
|
||||
// normalizations
|
||||
for (let collection of newCollections) {
|
||||
// delete timestamps
|
||||
delete collection.created;
|
||||
delete collection.updated;
|
||||
|
||||
// merge fields with duplicated ids
|
||||
collection.schema = CommonHelper.filterDuplicatesByKey(collection.schema);
|
||||
}
|
||||
}
|
||||
|
||||
function replaceIds() {
|
||||
for (let collection of newCollections) {
|
||||
const old = CommonHelper.findByKey(oldCollections, "name", collection.name);
|
||||
if (!old?.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const originalId = collection.id;
|
||||
const replacedId = old.id;
|
||||
collection.id = replacedId;
|
||||
|
||||
// replace field ids
|
||||
const oldSchema = Array.isArray(old.schema) ? old.schema : [];
|
||||
const newSchema = Array.isArray(collection.schema) ? collection.schema : [];
|
||||
for (const field of newSchema) {
|
||||
const oldField = CommonHelper.findByKey(oldSchema, "name", field.name);
|
||||
field.id = oldField.id;
|
||||
}
|
||||
|
||||
// update references
|
||||
for (let ref of newCollections) {
|
||||
if (!Array.isArray(ref.schema)) {
|
||||
continue;
|
||||
}
|
||||
for (let field of ref.schema) {
|
||||
if (field.options?.collectionId === originalId) {
|
||||
field.options.collectionId = replacedId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
schemas = JSON.stringify(newCollections, null, 4);
|
||||
}
|
||||
|
||||
function loadFile(file) {
|
||||
isLoadingFile = true;
|
||||
|
||||
@@ -207,6 +269,14 @@
|
||||
{/if}
|
||||
</Field>
|
||||
|
||||
<Field class="form-field form-field-toggle" let:uniqueId>
|
||||
<input type="checkbox" id={uniqueId} bind:checked={deleteMissing} disabled={!isValid} />
|
||||
<label for={uniqueId}>
|
||||
Delete all collections and fields that are not present in the above imported
|
||||
configuration
|
||||
</label>
|
||||
</Field>
|
||||
|
||||
{#if isValid && newCollections.length && !hasChanges}
|
||||
<div class="alert alert-info">
|
||||
<div class="icon">
|
||||
@@ -234,10 +304,10 @@
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#if collectionsToModify.length}
|
||||
{#each collectionsToModify as pair (pair.old.id + pair.new.id)}
|
||||
{#if collectionsToChange.length}
|
||||
{#each collectionsToChange as pair (pair.old.id + pair.new.id)}
|
||||
<div class="list-item">
|
||||
<span class="label label-warning list-label">Modified</span>
|
||||
<span class="label label-warning list-label">Changed</span>
|
||||
<strong>
|
||||
{#if pair.old.name !== pair.new.name}
|
||||
<span class="txt-strikethrough txt-hint">{pair.old.name}</span> -
|
||||
@@ -254,7 +324,7 @@
|
||||
{#if collectionsToAdd.length}
|
||||
{#each collectionsToAdd as collection (collection.id)}
|
||||
<div class="list-item">
|
||||
<span class="label label-success list-label">New</span>
|
||||
<span class="label label-success list-label">Added</span>
|
||||
<strong>{collection.name}</strong>
|
||||
{#if collection.id}
|
||||
<small class="txt-hint">({collection.id})</small>
|
||||
@@ -265,6 +335,26 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if idReplacableCollections.length}
|
||||
<div class="alert alert-warning">
|
||||
<div class="icon">
|
||||
<i class="ri-error-warning-line" />
|
||||
</div>
|
||||
<div class="content">
|
||||
<string>
|
||||
Some of the imported collections shares the same name but has different IDs.
|
||||
</string>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-warning btn-sm btn-outline"
|
||||
on:click={() => replaceIds()}
|
||||
>
|
||||
<span class="txt">Replace and keep old ids</span>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex m-t-base">
|
||||
{#if !!schemas}
|
||||
<button type="button" class="btn btn-secondary link-hint" on:click={() => clear()}>
|
||||
@@ -276,7 +366,7 @@
|
||||
type="button"
|
||||
class="btn btn-expanded btn-warning m-l-auto"
|
||||
disabled={!canImport}
|
||||
on:click={() => importPopup?.show(oldCollections, newCollections)}
|
||||
on:click={() => importPopup?.show(oldCollections, newCollections, deleteMissing)}
|
||||
>
|
||||
<span class="txt">Review</span>
|
||||
</button>
|
||||
|
||||
@@ -29,7 +29,10 @@
|
||||
<span class="txt">Files storage</span>
|
||||
</a>
|
||||
|
||||
<div class="sidebar-title">Sync</div>
|
||||
<div class="sidebar-title">
|
||||
<span class="txt">Sync</span>
|
||||
<small class="label label-danger label-compact">Experimental</small>
|
||||
</div>
|
||||
<a
|
||||
href="/settings/export-collections"
|
||||
class="sidebar-list-item"
|
||||
|
||||
Reference in New Issue
Block a user