ngx-dynamic-entity 2.5.0 → 3.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { InjectionToken, makeEnvironmentProviders, inject, Injectable, signal, Input, ChangeDetectionStrategy, Component, ChangeDetectorRef, DestroyRef, Injector, isDevMode, ViewContainerRef, ViewChild, forwardRef, Optional, Inject, EventEmitter, ElementRef, computed, afterNextRender, HostListener, ViewChildren, Output } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
4
|
import { ReactiveFormsModule, Validators, FormBuilder, FormGroup, FormArray, FormsModule } from '@angular/forms';
|
|
5
|
-
import { resolveLabel, formatDisplayValue, normalizeLookupValues, lookupValuesToOptions, valuesMatch, resolveOptionLabel, resolveOptionValue, buildReferenceCacheKey, normalizeReferenceOptions, entityKeyFromCacheKey, applyCascadeFilter, resolveEffectiveMask, shouldMaskField, findTab, isValidUrl, isValidPhone, fileRefName, sliderBounds, ratingScale, normalizeHexColor, normalizeTags, evaluateFormRules, filterRulesForTab, evaluateFieldVisibility, fieldsUnderTab, getTabPath, fieldRefFor, flattenFieldValues, parseFieldRef, placeTabFields, normalizeArrayStructures, getTabData, getValueByPath, normalizeConfigOptions, migrateRecord, ambiguousFieldIds, toRefToken, collectFieldScopes, applyAutoPatch, refOf, isUnsafePath, valuesEqual, parseCsv, arrayBoundFor, deriveImportColumns, suggestMapping, cellText, applyMapping, decimalMarkFor, toCsv, stripIndices, CORE_VERSION, DEFAULT_ARRAY_ROWS, upgradeLegacyRefs, coerceCell, buildTemplateSpec, collectLeafTargets, formatConfigProblems } from '@dynamic-entity/core';
|
|
5
|
+
import { resolveLabel, formatDisplayValue, normalizeLookupValues, lookupValuesToOptions, valuesMatch, resolveOptionLabel, resolveOptionValue, buildReferenceCacheKey, normalizeReferenceOptions, entityKeyFromCacheKey, applyCascadeFilter, resolveEffectiveMask, shouldMaskField, findTab, isValidUrl, isValidPhone, fileRefName, sliderBounds, ratingScale, normalizeHexColor, normalizeTags, evaluateFormRules, filterRulesForTab, evaluateFieldVisibility, fieldsUnderTab, getTabPath, fieldRefFor, flattenFieldValues, parseFieldRef, placeTabFields, normalizeArrayStructures, getTabData, getValueByPath, normalizeConfigOptions, migrateRecord, ambiguousFieldIds, toRefToken, collectFieldScopes, applyAutoPatch, refOf, isUnsafePath, valuesEqual, parseCsv, arrayBoundFor, deriveImportColumns, suggestMapping, cellText, applyMapping, decimalMarkFor, toCsv, stripIndices, CORE_VERSION, DEFAULT_ARRAY_ROWS, planForServer, upgradeLegacyRefs, coerceCell, buildTemplateSpec, collectLeafTargets, formatConfigProblems } from '@dynamic-entity/core';
|
|
6
6
|
export { formatDisplayValue, normalizeConfig, normalizeOption, resolveLabel, resolveOptionLabel, resolveOptionValue, valuesMatch } from '@dynamic-entity/core';
|
|
7
7
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
8
8
|
import { Subject, isObservable, firstValueFrom } from 'rxjs';
|
|
@@ -10178,6 +10178,10 @@ class LocalImportTransport {
|
|
|
10178
10178
|
pending.catch(() => this.parsed.delete(file));
|
|
10179
10179
|
return pending;
|
|
10180
10180
|
}
|
|
10181
|
+
/**
|
|
10182
|
+
* The suggestion made here is always plan v2, and the return type says so. The contract's is
|
|
10183
|
+
* a plan in either shape, because a 2.x server sends the older one.
|
|
10184
|
+
*/
|
|
10181
10185
|
async preview(file, context) {
|
|
10182
10186
|
const sheet = await this.read(file);
|
|
10183
10187
|
// Sized from the headers exactly as the server sizes them, so both offer the same slots.
|
|
@@ -10314,6 +10318,8 @@ function selectionOf(spec, context, fields) {
|
|
|
10314
10318
|
* to resolve — and a consumer who wants two of them, pointed at two backends, gets that for
|
|
10315
10319
|
* free.
|
|
10316
10320
|
*/
|
|
10321
|
+
/** What a Dynamic Entity server calls its adapter in the `adapter` stamp on every answer. */
|
|
10322
|
+
const ADAPTER_NAME = 'dynamic-entity';
|
|
10317
10323
|
/** The first release whose server says which date order it reads with, and refuses to guess one. */
|
|
10318
10324
|
const DATE_ORDER_SINCE = [2, 5];
|
|
10319
10325
|
/** Whether an engine version is from before the server stopped reading dates month first. */
|
|
@@ -10330,6 +10336,12 @@ class HttpImportTransport {
|
|
|
10330
10336
|
* Keyed on the `File`, so it lives exactly as long as the file does.
|
|
10331
10337
|
*/
|
|
10332
10338
|
reported = new WeakMap();
|
|
10339
|
+
/**
|
|
10340
|
+
* The plan versions each previewed file's server said it reads. A file with no entry was
|
|
10341
|
+
* never previewed here, and one whose server said nothing is from before plan v2: either
|
|
10342
|
+
* way `commit` sends nothing that only a v2 reader would understand.
|
|
10343
|
+
*/
|
|
10344
|
+
planVersions = new WeakMap();
|
|
10333
10345
|
constructor(options) {
|
|
10334
10346
|
this.options = options;
|
|
10335
10347
|
}
|
|
@@ -10382,6 +10394,22 @@ class HttpImportTransport {
|
|
|
10382
10394
|
`was built against ${CORE_VERSION}. The two halves of an import are supposed to be the ` +
|
|
10383
10395
|
'same engine — bring them into step before trusting a difference in what they produce.');
|
|
10384
10396
|
}
|
|
10397
|
+
/**
|
|
10398
|
+
* Warn when the server runs another adapter, or this one at another major version.
|
|
10399
|
+
*
|
|
10400
|
+
* Since 3.0 a server says which adapter turned its rows into records. A server from before
|
|
10401
|
+
* that says nothing, and `checkEngine` has already compared the one version it does report.
|
|
10402
|
+
*/
|
|
10403
|
+
checkAdapter(adapter) {
|
|
10404
|
+
if (!adapter)
|
|
10405
|
+
return;
|
|
10406
|
+
const major = (version) => String(version ?? '').split('.')[0];
|
|
10407
|
+
if (adapter.name === ADAPTER_NAME && major(adapter.version) === major(CORE_VERSION))
|
|
10408
|
+
return;
|
|
10409
|
+
this.warn(`[ngx-dynamic-entity] The import server reads rows with the adapter ${adapter.name} ${adapter.version}; ` +
|
|
10410
|
+
`this app expects ${ADAPTER_NAME} ${major(CORE_VERSION)}.x. What the server imports may differ from ` +
|
|
10411
|
+
'what the review step showed. Bring the two into step.');
|
|
10412
|
+
}
|
|
10385
10413
|
async preview(file, context) {
|
|
10386
10414
|
const body = new FormData();
|
|
10387
10415
|
body.append('file', file, file.name || 'upload');
|
|
@@ -10394,6 +10422,8 @@ class HttpImportTransport {
|
|
|
10394
10422
|
throw await failureOf(response);
|
|
10395
10423
|
const preview = (await response.json());
|
|
10396
10424
|
this.checkEngine(preview.engineVersion);
|
|
10425
|
+
this.checkAdapter(preview.adapter);
|
|
10426
|
+
this.planVersions.set(file, Array.isArray(preview.planVersions) ? preview.planVersions : undefined);
|
|
10397
10427
|
this.reported.set(file, preview.dateOrder);
|
|
10398
10428
|
this.checkDateOrder(context?.dateOrder, preview.dateOrder, preview.engineVersion);
|
|
10399
10429
|
return {
|
|
@@ -10415,11 +10445,21 @@ class HttpImportTransport {
|
|
|
10415
10445
|
throw new Error(`This app reads dates as ${context.dateOrder} but the import server reads them as ${reported}. ` +
|
|
10416
10446
|
'Nothing was imported. Set the same dateOrder on the wizard and on the server\'s router.');
|
|
10417
10447
|
}
|
|
10448
|
+
// Also before a byte is sent. A server from before plan v2 ignores what it does not know,
|
|
10449
|
+
// so a plan that groups rows would import one record per row there and report success.
|
|
10450
|
+
const sendable = planForServer(plan, this.planVersions.get(file));
|
|
10451
|
+
if (sendable.refused) {
|
|
10452
|
+
throw new Error(`This mapping uses ${sendable.refused.join(', ')}, which the import server does not read: it is from ` +
|
|
10453
|
+
'before mapping plan v2 and would ignore it. Nothing was imported. Upgrade the server to ' +
|
|
10454
|
+
'@dynamic-entity/server 3.0 or later.');
|
|
10455
|
+
}
|
|
10418
10456
|
const body = new FormData();
|
|
10419
10457
|
// **The plan first, and the file second.** The server starts importing while the file is
|
|
10420
10458
|
// still arriving — that is the whole reason it exists — so the plan has to have arrived
|
|
10421
10459
|
// already. `FormData` preserves append order, so this ordering is the contract.
|
|
10422
|
-
|
|
10460
|
+
// As written for a server that reads plan v2, and in 2.x form (`entity`, `configVersion`)
|
|
10461
|
+
// for one that does not: the same mapping, in the words that server knows.
|
|
10462
|
+
body.append('plan', JSON.stringify(sendable.plan));
|
|
10423
10463
|
body.append('file', file, file.name || 'upload');
|
|
10424
10464
|
const response = await this.send(`${this.base}/${this.entityOf(context)}/import`, {
|
|
10425
10465
|
method: 'POST',
|
|
@@ -10430,6 +10470,7 @@ class HttpImportTransport {
|
|
|
10430
10470
|
throw await failureOf(response);
|
|
10431
10471
|
const result = (await response.json());
|
|
10432
10472
|
this.checkEngine(result.engineVersion);
|
|
10473
|
+
this.checkAdapter(result.adapter);
|
|
10433
10474
|
return {
|
|
10434
10475
|
// Empty, and not because nothing was imported. The server streamed the file precisely so
|
|
10435
10476
|
// that fifty thousand records never had to exist at once; sending them back would undo
|
|
@@ -10443,6 +10484,8 @@ class HttpImportTransport {
|
|
|
10443
10484
|
errorCount: result.errorCount ?? result.errors?.length ?? 0,
|
|
10444
10485
|
truncated: result.truncated ?? false,
|
|
10445
10486
|
planProblems: result.planProblems ?? [],
|
|
10487
|
+
// Only a 3.0 server sends them, and only a run with something to warn about has any.
|
|
10488
|
+
...(result.warnings?.length ? { warnings: result.warnings } : {}),
|
|
10446
10489
|
};
|
|
10447
10490
|
}
|
|
10448
10491
|
async template(spec, format, context, fields) {
|
|
@@ -10587,10 +10630,10 @@ const CONSTANT = -1;
|
|
|
10587
10630
|
class ImportMapperComponent {
|
|
10588
10631
|
config;
|
|
10589
10632
|
headers = [];
|
|
10590
|
-
/** The starting point, normally `suggestMapping`'s output. */
|
|
10633
|
+
/** The starting point, normally `suggestMapping`'s output. A stored 2.x plan is taken too. */
|
|
10591
10634
|
plan = null;
|
|
10592
10635
|
language = 'en';
|
|
10593
|
-
/** Emitted whenever the user changes a selection. */
|
|
10636
|
+
/** Emitted whenever the user changes a selection. Always plan v2, whichever shape came in. */
|
|
10594
10637
|
planChange = new EventEmitter();
|
|
10595
10638
|
ui = inject(UiTextService);
|
|
10596
10639
|
rows = signal([], ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
@@ -10703,6 +10746,10 @@ class ImportMapperComponent {
|
|
|
10703
10746
|
* Every entry of the incoming plan survives in its own position — a fixed value, a ref the
|
|
10704
10747
|
* config no longer has — unless the user changed or removed it, and an untouched entry is
|
|
10705
10748
|
* emitted exactly as it arrived. New selections follow, in field order.
|
|
10749
|
+
*
|
|
10750
|
+
* What is emitted is plan v2, whichever shape came in: `target` and `schemaVersion`, where a
|
|
10751
|
+
* 2.x plan said `entity` and `configVersion`. Anything else the incoming plan carried, such
|
|
10752
|
+
* as its lists and its grouping, is kept.
|
|
10706
10753
|
*/
|
|
10707
10754
|
toPlan() {
|
|
10708
10755
|
const rowByRef = new Map(this.rows().map(row => [row.column.ref, row]));
|
|
@@ -10740,10 +10787,13 @@ class ImportMapperComponent {
|
|
|
10740
10787
|
if (entry)
|
|
10741
10788
|
entries.push(entry);
|
|
10742
10789
|
}
|
|
10790
|
+
// The 2.x names go: a plan carrying both is one the reader has to warn about.
|
|
10791
|
+
const kept = Object.fromEntries(Object.entries(this.incoming ?? {}).filter(([key]) => key !== 'entity' && key !== 'configVersion'));
|
|
10743
10792
|
return {
|
|
10744
|
-
...
|
|
10745
|
-
|
|
10746
|
-
|
|
10793
|
+
...kept,
|
|
10794
|
+
planVersion: 2,
|
|
10795
|
+
target: this.config?.entity ?? '',
|
|
10796
|
+
schemaVersion: this.config?.version,
|
|
10747
10797
|
sourceHeaders: [...this.headers],
|
|
10748
10798
|
entries,
|
|
10749
10799
|
};
|
|
@@ -11042,7 +11092,8 @@ class ImportPreviewComponent {
|
|
|
11042
11092
|
lang: this.language,
|
|
11043
11093
|
lookups: this.lookups,
|
|
11044
11094
|
decimal: this.decimal,
|
|
11045
|
-
|
|
11095
|
+
// The column's own order when its plan entry states one, as the commit reads it.
|
|
11096
|
+
dateOrder: entry.dateOrder ?? this.dateOrder,
|
|
11046
11097
|
});
|
|
11047
11098
|
if ('error' in outcome)
|
|
11048
11099
|
return { text: outcome.error, failed: true };
|