@sinequa/atomic-angular 1.14.0 → 1.14.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.
- package/fesm2022/sinequa-atomic-angular.mjs +103 -78
- package/fesm2022/sinequa-atomic-angular.mjs.map +1 -1
- package/index.d.ts +173 -163
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import highlightWords from 'highlight-words';
|
|
|
7
7
|
import { ActivatedRoute, Router, NavigationEnd, RouterLink, RouterLinkActive, RouterModule } from '@angular/router';
|
|
8
8
|
import { withDevtools } from '@angular-architects/ngrx-toolkit';
|
|
9
9
|
import { signalStore, signalStoreFeature, withState, withMethods, patchState, getState, withComputed } from '@ngrx/signals';
|
|
10
|
-
import { EngineType, fetchApp, extraColumns, sysLang, fetchPrincipal, globalConfig, filter, getQueryParamsFromUrl, isRedirectPending, info, clearSessionTokens, login, error, setGlobalConfig, initializeAppConfig, notify, addConcepts, queryParamsFromUrl, patchUserSettings, deleteUserSettings, fetchUserSettings, warn, buildPathsAndLevels, escapeExpr, fetchQuery, isAuthenticated, isExpired, debug, AuditEventType, fetchSuggest, isObject, Audit, getMetadata, bisect, isNotInputEvent, fetchSponsoredLinks, translateAggregationToDateOptions, aggItemRegex, parseValueAndOperatorFromItem, fetchSuggestField, fetchSimilarDocuments, logout, fetchChangePassword, fetchSendPasswordResetEmail, expiresSoon, suggestionsToTreeAggregationNodes, labels, fetchLabels, guid, getRelativeDate, createUserProfile, deleteUserProfileProperty, patchUserProfile, isJsonable, addAuditAdditionalInfo, getToken, setToken, createHeaders } from '@sinequa/atomic';
|
|
10
|
+
import { EngineType, fetchApp, extraColumns, sysLang, fetchPrincipal, globalConfig, filter, getQueryParamsFromUrl, isRedirectPending, info, clearSessionTokens, login, error, setGlobalConfig, initializeAppConfig, notify, addConcepts, queryParamsFromUrl, patchUserSettings, deleteUserSettings, fetchUserSettings, warn, buildPathsAndLevels, escapeExpr, fetchQuery, flattenLegacyFilterValues, isAuthenticated, isExpired, debug, AuditEventType, fetchSuggest, isObject, Audit, getMetadata, bisect, isNotInputEvent, fetchSponsoredLinks, translateAggregationToDateOptions, aggItemRegex, parseValueAndOperatorFromItem, fetchSuggestField, fetchSimilarDocuments, logout, fetchChangePassword, fetchSendPasswordResetEmail, expiresSoon, suggestionsToTreeAggregationNodes, labels, fetchLabels, guid, getRelativeDate, createUserProfile, deleteUserProfileProperty, patchUserProfile, isJsonable, addAuditAdditionalInfo, getToken, setToken, createHeaders } from '@sinequa/atomic';
|
|
11
11
|
import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
|
|
12
12
|
import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE, DATE_PIPE_DEFAULT_OPTIONS, Location, NgTemplateOutlet, NgStyle, NgClass, NgComponentOutlet } from '@angular/common';
|
|
13
13
|
import { HttpClient, HttpParams, httpResource, HttpResponse, HttpContextToken, HttpHeaders } from '@angular/common/http';
|
|
@@ -3526,7 +3526,7 @@ class AggregationsService {
|
|
|
3526
3526
|
else {
|
|
3527
3527
|
// get current filters for the aggregation
|
|
3528
3528
|
const filters = this.queryParamsStore.getFilter({ field: agg.column, name: agg.name });
|
|
3529
|
-
flattenedValues = filters ?
|
|
3529
|
+
flattenedValues = filters ? flattenLegacyFilterValues([filters]) : [];
|
|
3530
3530
|
}
|
|
3531
3531
|
if (agg.isTree) {
|
|
3532
3532
|
const selectedAgg = agg.items.map(item => this.selectNode(item, flattenedValues));
|
|
@@ -3563,34 +3563,44 @@ class AggregationsService {
|
|
|
3563
3563
|
*/
|
|
3564
3564
|
toFilter(item, field, name, isDistribution) {
|
|
3565
3565
|
if (isDistribution) {
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
if (filters.length === 2) {
|
|
3575
|
-
return {
|
|
3576
|
-
operator: "and",
|
|
3577
|
-
filters,
|
|
3578
|
-
display: filters[0].display || filters[0].value,
|
|
3579
|
-
field,
|
|
3580
|
-
name
|
|
3581
|
-
};
|
|
3582
|
-
}
|
|
3583
|
-
else if (filters.length === 1) {
|
|
3584
|
-
return { ...filters[0], field, name };
|
|
3585
|
-
}
|
|
3586
|
-
throw new Error("Failed to parse distribution expression");
|
|
3566
|
+
// Parsing lives in @sinequa/atomic — `filter.distribution` is the single implementation, and it
|
|
3567
|
+
// recognises all four bounds where this method only ever produced `gte` or `lt`, reading `>` as
|
|
3568
|
+
// `gte` and `<=` as `lt`. What is added here is what the store needs and a `Filter` has no room
|
|
3569
|
+
// for: the aggregation's `name` on every leaf, which is what makes `updateFilter` replace the
|
|
3570
|
+
// entry rather than merge into it, and the human-readable `display`.
|
|
3571
|
+
const parsed = filter.distribution(field, item.value);
|
|
3572
|
+
if (parsed) {
|
|
3573
|
+
return this.nameLeaves(parsed, field, name, item.display || String(item.value ?? ""));
|
|
3587
3574
|
}
|
|
3575
|
+
throw new Error("Failed to parse distribution expression");
|
|
3588
3576
|
}
|
|
3589
3577
|
if (typeof item.value === "string") {
|
|
3590
3578
|
return { name, field, value: item.value, operator: "contains", display: item.display };
|
|
3591
3579
|
}
|
|
3592
3580
|
return { name, field, value: item.value, display: item.display };
|
|
3593
3581
|
}
|
|
3582
|
+
/**
|
|
3583
|
+
* Stamps the aggregation's `name` and the item's `display` onto a filter parsed by
|
|
3584
|
+
* `filter.distribution`.
|
|
3585
|
+
*
|
|
3586
|
+
* A `Filter` carries neither, and the store needs both: `name` is what
|
|
3587
|
+
* `QueryParamsStore.updateFilter` matches on to **replace** an aggregation's entry instead of merging
|
|
3588
|
+
* into it — the only way a selection can shrink — and `display` is the label the applied-filter chips
|
|
3589
|
+
* render. A combined filter also keeps `field` at its root, which is how the store finds the entry.
|
|
3590
|
+
*/
|
|
3591
|
+
nameLeaves(parsed, field, name, display) {
|
|
3592
|
+
const leaves = parsed.filters;
|
|
3593
|
+
if (leaves?.length) {
|
|
3594
|
+
return {
|
|
3595
|
+
...parsed,
|
|
3596
|
+
field,
|
|
3597
|
+
name,
|
|
3598
|
+
display,
|
|
3599
|
+
filters: leaves.map(leaf => ({ ...leaf, field, name, display }))
|
|
3600
|
+
};
|
|
3601
|
+
}
|
|
3602
|
+
return { ...parsed, field, name, display };
|
|
3603
|
+
}
|
|
3594
3604
|
findNode(items, path = "") {
|
|
3595
3605
|
if (!items || items.length === 0) {
|
|
3596
3606
|
return undefined;
|
|
@@ -3638,27 +3648,6 @@ class AggregationsService {
|
|
|
3638
3648
|
}
|
|
3639
3649
|
return node;
|
|
3640
3650
|
}
|
|
3641
|
-
flattenFilters(filters) {
|
|
3642
|
-
const flattenedValues = [];
|
|
3643
|
-
function extractValues(filters) {
|
|
3644
|
-
for (const filter of filters) {
|
|
3645
|
-
if (filter.value) {
|
|
3646
|
-
flattenedValues.push(filter.value);
|
|
3647
|
-
if (filter.display)
|
|
3648
|
-
flattenedValues.push(filter.display);
|
|
3649
|
-
// we handle "values" field for trees
|
|
3650
|
-
}
|
|
3651
|
-
else if (filter.values) {
|
|
3652
|
-
flattenedValues.push(...filter.values);
|
|
3653
|
-
}
|
|
3654
|
-
if (filter.filters) {
|
|
3655
|
-
extractValues(filter.filters);
|
|
3656
|
-
}
|
|
3657
|
-
}
|
|
3658
|
-
}
|
|
3659
|
-
extractValues(filters);
|
|
3660
|
-
return flattenedValues;
|
|
3661
|
-
}
|
|
3662
3651
|
/**
|
|
3663
3652
|
* Recursively opens parent nodes in a tree structure if any of their child nodes are selected or opened.
|
|
3664
3653
|
*
|
|
@@ -5397,6 +5386,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImpo
|
|
|
5397
5386
|
|
|
5398
5387
|
class UserProfileService {
|
|
5399
5388
|
API_URL = `${globalConfig.backendUrl}/api/v2/user-profile`;
|
|
5389
|
+
// Keyed by the userId signal's identity rather than its value: every current caller
|
|
5390
|
+
// passes the same PrincipalStore.userId signal, so sharing the resource here collapses
|
|
5391
|
+
// what would otherwise be one httpResource (and one HTTP request) per caller into one
|
|
5392
|
+
// for the whole app -- and lets a `.set()` from one consumer (e.g. the profile edit
|
|
5393
|
+
// form) show up everywhere else that reads this resource.
|
|
5394
|
+
resources = new WeakMap();
|
|
5400
5395
|
/**
|
|
5401
5396
|
* Retrieves the user profile by user ID.
|
|
5402
5397
|
*
|
|
@@ -5404,11 +5399,16 @@ class UserProfileService {
|
|
|
5404
5399
|
* @returns An Observable that emits an the UserProfile object.
|
|
5405
5400
|
*/
|
|
5406
5401
|
getUserProfile(userId) {
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5402
|
+
let resource = this.resources.get(userId);
|
|
5403
|
+
if (!resource) {
|
|
5404
|
+
resource = httpResource(() => (userId() ? `${this.API_URL}/${userId()}` : undefined), {
|
|
5405
|
+
parse: response => {
|
|
5406
|
+
return response;
|
|
5407
|
+
}
|
|
5408
|
+
});
|
|
5409
|
+
this.resources.set(userId, resource);
|
|
5410
|
+
}
|
|
5411
|
+
return resource;
|
|
5412
5412
|
}
|
|
5413
5413
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: UserProfileService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
5414
5414
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: UserProfileService, providedIn: "root" });
|
|
@@ -9475,41 +9475,57 @@ function injectAggregationBase(refs) {
|
|
|
9475
9475
|
}
|
|
9476
9476
|
function addCurrentFiltersToItems(aggregation) {
|
|
9477
9477
|
const currentItems = aggregation?.items || [];
|
|
9478
|
-
if (
|
|
9479
|
-
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
|
|
9485
|
-
|
|
9486
|
-
|
|
9487
|
-
|
|
9488
|
-
|
|
9489
|
-
|
|
9490
|
-
|
|
9478
|
+
if (aggregation?.isTree) {
|
|
9479
|
+
return currentItems;
|
|
9480
|
+
}
|
|
9481
|
+
const currentFilters = isControlled() ? selectionAsFilter() : queryParamsStore.getFilter({ field: aggregation?.column, name: aggregation?.name });
|
|
9482
|
+
if (aggregation?.isDistribution) {
|
|
9483
|
+
// A distribution item's `value` is the full backend expression (e.g.
|
|
9484
|
+
// `size`10 Kb to 100 Kb`:(>= 10240 AND < 102400)`), not comparable to what the applied
|
|
9485
|
+
// filter stores — its leaves only ever carry the bucket's numeric thresholds. Flatten the
|
|
9486
|
+
// filter and compare on `display` instead (or `value` when `valuesAreExpressions` is not
|
|
9487
|
+
// set) — same strategy as `AggregationsService.enrichAggregation`.
|
|
9488
|
+
const flattenedValues = currentFilters ? flattenLegacyFilterValues([currentFilters]) : [];
|
|
9489
|
+
currentItems.forEach(item => {
|
|
9490
|
+
const valueToSearch = aggregation.valuesAreExpressions ? item.display : item.value;
|
|
9491
|
+
if (flattenedValues.includes(valueToSearch?.toString() ?? "")) {
|
|
9492
|
+
item.$selected = true;
|
|
9491
9493
|
}
|
|
9492
|
-
|
|
9493
|
-
|
|
9494
|
-
|
|
9495
|
-
|
|
9496
|
-
|
|
9497
|
-
|
|
9498
|
-
|
|
9499
|
-
}
|
|
9500
|
-
else {
|
|
9501
|
-
found.$selected = true;
|
|
9502
|
-
}
|
|
9503
|
-
});
|
|
9504
|
-
}
|
|
9505
|
-
else if (currentFilters.value) {
|
|
9506
|
-
const found = currentItems.find(item => item.value?.toString().toLocaleLowerCase() === currentFilters.value?.toLocaleLowerCase());
|
|
9494
|
+
});
|
|
9495
|
+
return currentItems;
|
|
9496
|
+
}
|
|
9497
|
+
if (currentFilters) {
|
|
9498
|
+
if (Array.isArray(currentFilters.filters) && currentFilters.filters.length) {
|
|
9499
|
+
currentFilters.filters.forEach((filter) => {
|
|
9500
|
+
const found = currentItems.find(item => item.value?.toString().toLocaleLowerCase() === filter.value?.toLocaleLowerCase());
|
|
9507
9501
|
if (!found) {
|
|
9508
|
-
currentItems.
|
|
9502
|
+
currentItems.unshift({ value: filter.value, display: filter.display, $selected: true });
|
|
9509
9503
|
}
|
|
9510
9504
|
else {
|
|
9511
9505
|
found.$selected = true;
|
|
9512
9506
|
}
|
|
9507
|
+
});
|
|
9508
|
+
}
|
|
9509
|
+
else if (Array.isArray(currentFilters.values) && currentFilters.values.length) {
|
|
9510
|
+
// multiple values stored as a string array, e.g. { values: ["alice_martin", "caroline_dubois"] }
|
|
9511
|
+
// (no `filters` sub-array and no single `value`) — mark each matching item as selected
|
|
9512
|
+
currentFilters.values.forEach(value => {
|
|
9513
|
+
const found = currentItems.find(item => item.value?.toString().toLocaleLowerCase() === value?.toString().toLocaleLowerCase());
|
|
9514
|
+
if (!found) {
|
|
9515
|
+
currentItems.unshift({ value, display: value, $selected: true });
|
|
9516
|
+
}
|
|
9517
|
+
else {
|
|
9518
|
+
found.$selected = true;
|
|
9519
|
+
}
|
|
9520
|
+
});
|
|
9521
|
+
}
|
|
9522
|
+
else if (currentFilters.value) {
|
|
9523
|
+
const found = currentItems.find(item => item.value?.toString().toLocaleLowerCase() === currentFilters.value?.toLocaleLowerCase());
|
|
9524
|
+
if (!found) {
|
|
9525
|
+
currentItems.push({ value: currentFilters.value, display: currentFilters.display, $selected: true });
|
|
9526
|
+
}
|
|
9527
|
+
else {
|
|
9528
|
+
found.$selected = true;
|
|
9513
9529
|
}
|
|
9514
9530
|
}
|
|
9515
9531
|
}
|
|
@@ -15598,6 +15614,15 @@ class AggregationListComponent {
|
|
|
15598
15614
|
const filters = this.queryParamsStore.getFilter({ field: field ?? undefined, name: this.listItemName(item) });
|
|
15599
15615
|
if (!filters)
|
|
15600
15616
|
return false;
|
|
15617
|
+
const agg = this.aggregation();
|
|
15618
|
+
if (agg?.isDistribution) {
|
|
15619
|
+
// Same reasoning as `injectAggregationBase.addCurrentFiltersToItems`: a distribution item's
|
|
15620
|
+
// `value` is the full backend expression, not comparable to the applied filter's stored
|
|
15621
|
+
// thresholds — flatten it and compare on `display` (or `value` without `valuesAreExpressions`).
|
|
15622
|
+
const flattenedValues = flattenLegacyFilterValues([filters]);
|
|
15623
|
+
const valueToSearch = agg.valuesAreExpressions ? item.display : item.value;
|
|
15624
|
+
return flattenedValues.includes(valueToSearch?.toString() ?? "");
|
|
15625
|
+
}
|
|
15601
15626
|
const values = [item.value];
|
|
15602
15627
|
return values.some(v => v === filters.value) || !!(filters.values?.length && filters.values.some((value) => values.some(v => v === value)));
|
|
15603
15628
|
}
|