@memberjunction/ng-search 5.23.0 → 5.25.0
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/dist/__tests__/search-results.component.test.d.ts +2 -0
- package/dist/__tests__/search-results.component.test.d.ts.map +1 -0
- package/dist/__tests__/search-results.component.test.js +263 -0
- package/dist/__tests__/search-results.component.test.js.map +1 -0
- package/dist/__tests__/search-types.test.js +6 -2
- package/dist/__tests__/search-types.test.js.map +1 -1
- package/dist/__tests__/search.service.test.js +167 -13
- package/dist/__tests__/search.service.test.js.map +1 -1
- package/dist/lib/module.d.ts +7 -4
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/module.js +19 -4
- package/dist/lib/module.js.map +1 -1
- package/dist/lib/search-composite.component.d.ts +79 -0
- package/dist/lib/search-composite.component.d.ts.map +1 -0
- package/dist/lib/search-composite.component.js +258 -0
- package/dist/lib/search-composite.component.js.map +1 -0
- package/dist/lib/search-filter.component.d.ts +19 -1
- package/dist/lib/search-filter.component.d.ts.map +1 -1
- package/dist/lib/search-filter.component.js +195 -44
- package/dist/lib/search-filter.component.js.map +1 -1
- package/dist/lib/search-input.component.d.ts +61 -0
- package/dist/lib/search-input.component.d.ts.map +1 -0
- package/dist/lib/search-input.component.js +192 -0
- package/dist/lib/search-input.component.js.map +1 -0
- package/dist/lib/search-overlay.component.js +2 -2
- package/dist/lib/search-results.component.d.ts +59 -7
- package/dist/lib/search-results.component.d.ts.map +1 -1
- package/dist/lib/search-results.component.js +530 -176
- package/dist/lib/search-results.component.js.map +1 -1
- package/dist/lib/search-suggest.component.d.ts +95 -0
- package/dist/lib/search-suggest.component.d.ts.map +1 -0
- package/dist/lib/search-suggest.component.js +491 -0
- package/dist/lib/search-suggest.component.js.map +1 -0
- package/dist/lib/search-types.d.ts +29 -1
- package/dist/lib/search-types.d.ts.map +1 -1
- package/dist/lib/search-types.js.map +1 -1
- package/dist/lib/search.service.d.ts +27 -5
- package/dist/lib/search.service.d.ts.map +1 -1
- package/dist/lib/search.service.js +222 -91
- package/dist/lib/search.service.js.map +1 -1
- package/dist/public-api.d.ts +3 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +3 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +6 -4
|
@@ -18,6 +18,8 @@ export interface SearchResultItem {
|
|
|
18
18
|
RecordID: string;
|
|
19
19
|
/** Classification of the result source */
|
|
20
20
|
SourceType: 'entity' | 'content-item' | 'file' | 'web-page';
|
|
21
|
+
/** Discriminator for UI rendering: 'entity-record', 'storage-file', or 'content-item' */
|
|
22
|
+
ResultType?: string;
|
|
21
23
|
/** Relevance score between 0 and 1 */
|
|
22
24
|
Score: number;
|
|
23
25
|
/** Breakdown of how the score was computed */
|
|
@@ -32,6 +34,14 @@ export interface SearchResultItem {
|
|
|
32
34
|
RecordName?: string;
|
|
33
35
|
/** When this result was matched */
|
|
34
36
|
MatchedAt: Date;
|
|
37
|
+
/** Raw vector metadata JSON — contains all entity fields stored in the vector DB */
|
|
38
|
+
RawMetadata?: string;
|
|
39
|
+
/** ID of the SearchProvider metadata record that produced this result */
|
|
40
|
+
ProviderId?: string;
|
|
41
|
+
/** Display label from the SearchProvider metadata (e.g., "Database", "Semantic Search") */
|
|
42
|
+
ProviderLabel?: string;
|
|
43
|
+
/** Font Awesome icon class from the SearchProvider metadata */
|
|
44
|
+
ProviderIcon?: string;
|
|
35
45
|
}
|
|
36
46
|
/** Breakdown of score contribution from each search source */
|
|
37
47
|
export interface ScoreBreakdown {
|
|
@@ -83,7 +93,7 @@ export interface SearchRequest {
|
|
|
83
93
|
/** Active filter selections */
|
|
84
94
|
ActiveFilters: Record<string, string[]>;
|
|
85
95
|
/** Which search sources to include */
|
|
86
|
-
IncludeSources: ('vector' | 'fulltext' | 'entity')[];
|
|
96
|
+
IncludeSources: ('vector' | 'fulltext' | 'entity' | 'storage')[];
|
|
87
97
|
/**
|
|
88
98
|
* Minimum relevance score (0-1) to include in results.
|
|
89
99
|
* Results below this threshold are filtered out.
|
|
@@ -91,6 +101,21 @@ export interface SearchRequest {
|
|
|
91
101
|
*/
|
|
92
102
|
MinScore?: number;
|
|
93
103
|
}
|
|
104
|
+
/** Metadata about an active search provider from the server */
|
|
105
|
+
export interface SearchProviderInfo {
|
|
106
|
+
/** SearchProvider record ID */
|
|
107
|
+
ID: string;
|
|
108
|
+
/** Provider name */
|
|
109
|
+
Name: string;
|
|
110
|
+
/** UI display label */
|
|
111
|
+
DisplayName: string;
|
|
112
|
+
/** Font Awesome icon class */
|
|
113
|
+
Icon: string;
|
|
114
|
+
/** The SourceType key this provider uses */
|
|
115
|
+
SourceType: string;
|
|
116
|
+
/** Priority (lower = higher) */
|
|
117
|
+
Priority: number;
|
|
118
|
+
}
|
|
94
119
|
/** Search response from the service */
|
|
95
120
|
export interface SearchResponse {
|
|
96
121
|
/** Whether the search succeeded */
|
|
@@ -110,7 +135,10 @@ export interface SearchResponse {
|
|
|
110
135
|
Vector: number;
|
|
111
136
|
FullText: number;
|
|
112
137
|
Entity: number;
|
|
138
|
+
Storage: number;
|
|
113
139
|
};
|
|
140
|
+
/** Active search providers metadata (for UI labels and icons) */
|
|
141
|
+
Providers: SearchProviderInfo[];
|
|
114
142
|
/** Error message if Success is false */
|
|
115
143
|
ErrorMessage?: string;
|
|
116
144
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-types.d.ts","sourceRoot":"","sources":["../../src/lib/search-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,6CAA6C;AAC7C,MAAM,WAAW,gBAAgB;IAC7B,wBAAwB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,0CAA0C;IAC1C,UAAU,EAAE,QAAQ,GAAG,cAAc,GAAG,MAAM,GAAG,UAAU,CAAC;IAC5D,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,cAAc,EAAE,cAAc,CAAC;IAC/B,0BAA0B;IAC1B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,SAAS,EAAE,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"search-types.d.ts","sourceRoot":"","sources":["../../src/lib/search-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-types.js","sourceRoot":"","sources":["../../src/lib/search-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG","sourcesContent":["/**\n * @fileoverview Shared types for the ng-search package.\n *\n * Defines the data contracts for search results, filters,\n * and events used across all search components.\n */\n\n/** Represents a single search result item */\nexport interface SearchResultItem {\n /** Unique identifier */\n ID: string;\n /** Display title */\n Title: string;\n /** Short text snippet showing match context */\n Snippet: string;\n /** The entity name this result belongs to */\n EntityName: string;\n /** The primary key of the source record */\n RecordID: string;\n /** Classification of the result source */\n SourceType: 'entity' | 'content-item' | 'file' | 'web-page';\n /** Relevance score between 0 and 1 */\n Score: number;\n /** Breakdown of how the score was computed */\n ScoreBreakdown: ScoreBreakdown;\n /** Human-readable tags */\n Tags: string[];\n /** Font Awesome icon class for the source type */\n SourceIcon: string;\n /** Entity-specific icon from EntityInfo.Icon (e.g., \"fa-solid fa-robot\") */\n EntityIcon?: string;\n /** Human-readable record name resolved from EntityRecordName */\n RecordName?: string;\n /** When this result was matched */\n MatchedAt: Date;\n}\n\n/** Breakdown of score contribution from each search source */\nexport interface ScoreBreakdown {\n Vector?: number;\n FullText?: number;\n Entity?: number;\n}\n\n/** Grouped search results by source type */\nexport interface SearchResultGroup {\n /** The source type label */\n Label: string;\n /** Font Awesome icon class */\n Icon: string;\n /** The source type key */\n SourceType: string;\n /** Results in this group */\n Results: SearchResultItem[];\n /** Total count for this source (may exceed Results.length if paginated) */\n TotalCount: number;\n}\n\n/** Faceted filter configuration */\nexport interface SearchFilter {\n /** Filter category name */\n Category: string;\n /** Filter options within this category */\n Options: SearchFilterOption[];\n /** Whether multiple options can be selected */\n MultiSelect: boolean;\n}\n\n/** Single filter option within a category */\nexport interface SearchFilterOption {\n /** Display label */\n Label: string;\n /** Value to filter by */\n Value: string;\n /** Count of results matching this option */\n Count: number;\n /** Whether this option is currently selected */\n IsSelected: boolean;\n /** Font Awesome icon class */\n Icon?: string;\n}\n\n/** Search request parameters */\nexport interface SearchRequest {\n /** The search query string */\n Query: string;\n /** Maximum number of results to return */\n MaxResults: number;\n /** Active filter selections */\n ActiveFilters: Record<string, string[]>;\n /** Which search sources to include */\n IncludeSources: ('vector' | 'fulltext' | 'entity')[];\n /**\n * Minimum relevance score (0-1) to include in results.\n * Results below this threshold are filtered out.\n * Default: 0.35 (35%).\n */\n MinScore?: number;\n}\n\n/** Search response from the service */\nexport interface SearchResponse {\n /** Whether the search succeeded */\n Success: boolean;\n /** The search results */\n Results: SearchResultItem[];\n /** Grouped results by source type */\n Groups: SearchResultGroup[];\n /** Available filters based on results */\n Filters: SearchFilter[];\n /** Total result count across all sources */\n TotalCount: number;\n /** Time taken in milliseconds */\n ElapsedMs: number;\n /** Count of results from each source */\n SourceCounts: { Vector: number; FullText: number; Entity: number };\n /** Error message if Success is false */\n ErrorMessage?: string;\n}\n\n/** Event emitted when a search result is selected */\nexport interface SearchResultSelectedEvent {\n /** The selected result item */\n Result: SearchResultItem;\n /** Whether to open in a new tab */\n OpenInNewTab: boolean;\n}\n\n/** Event emitted when filters change */\nexport interface SearchFilterChangeEvent {\n /** The filter category that changed */\n Category: string;\n /** The new selected values */\n SelectedValues: string[];\n}\n\n/** Event emitted when search is executed */\nexport interface SearchExecutedEvent {\n /** The search query */\n Query: string;\n /** Active filters */\n Filters: Record<string, string[]>;\n /** Number of results returned */\n ResultCount: number;\n /** Time taken in ms */\n ElapsedMs: number;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"search-types.js","sourceRoot":"","sources":["../../src/lib/search-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG","sourcesContent":["/**\n * @fileoverview Shared types for the ng-search package.\n *\n * Defines the data contracts for search results, filters,\n * and events used across all search components.\n */\n\n/** Represents a single search result item */\nexport interface SearchResultItem {\n /** Unique identifier */\n ID: string;\n /** Display title */\n Title: string;\n /** Short text snippet showing match context */\n Snippet: string;\n /** The entity name this result belongs to */\n EntityName: string;\n /** The primary key of the source record */\n RecordID: string;\n /** Classification of the result source */\n SourceType: 'entity' | 'content-item' | 'file' | 'web-page';\n /** Discriminator for UI rendering: 'entity-record', 'storage-file', or 'content-item' */\n ResultType?: string;\n /** Relevance score between 0 and 1 */\n Score: number;\n /** Breakdown of how the score was computed */\n ScoreBreakdown: ScoreBreakdown;\n /** Human-readable tags */\n Tags: string[];\n /** Font Awesome icon class for the source type */\n SourceIcon: string;\n /** Entity-specific icon from EntityInfo.Icon (e.g., \"fa-solid fa-robot\") */\n EntityIcon?: string;\n /** Human-readable record name resolved from EntityRecordName */\n RecordName?: string;\n /** When this result was matched */\n MatchedAt: Date;\n /** Raw vector metadata JSON — contains all entity fields stored in the vector DB */\n RawMetadata?: string;\n /** ID of the SearchProvider metadata record that produced this result */\n ProviderId?: string;\n /** Display label from the SearchProvider metadata (e.g., \"Database\", \"Semantic Search\") */\n ProviderLabel?: string;\n /** Font Awesome icon class from the SearchProvider metadata */\n ProviderIcon?: string;\n}\n\n/** Breakdown of score contribution from each search source */\nexport interface ScoreBreakdown {\n Vector?: number;\n FullText?: number;\n Entity?: number;\n}\n\n/** Grouped search results by source type */\nexport interface SearchResultGroup {\n /** The source type label */\n Label: string;\n /** Font Awesome icon class */\n Icon: string;\n /** The source type key */\n SourceType: string;\n /** Results in this group */\n Results: SearchResultItem[];\n /** Total count for this source (may exceed Results.length if paginated) */\n TotalCount: number;\n}\n\n/** Faceted filter configuration */\nexport interface SearchFilter {\n /** Filter category name */\n Category: string;\n /** Filter options within this category */\n Options: SearchFilterOption[];\n /** Whether multiple options can be selected */\n MultiSelect: boolean;\n}\n\n/** Single filter option within a category */\nexport interface SearchFilterOption {\n /** Display label */\n Label: string;\n /** Value to filter by */\n Value: string;\n /** Count of results matching this option */\n Count: number;\n /** Whether this option is currently selected */\n IsSelected: boolean;\n /** Font Awesome icon class */\n Icon?: string;\n}\n\n/** Search request parameters */\nexport interface SearchRequest {\n /** The search query string */\n Query: string;\n /** Maximum number of results to return */\n MaxResults: number;\n /** Active filter selections */\n ActiveFilters: Record<string, string[]>;\n /** Which search sources to include */\n IncludeSources: ('vector' | 'fulltext' | 'entity' | 'storage')[];\n /**\n * Minimum relevance score (0-1) to include in results.\n * Results below this threshold are filtered out.\n * Default: 0.35 (35%).\n */\n MinScore?: number;\n}\n\n/** Metadata about an active search provider from the server */\nexport interface SearchProviderInfo {\n /** SearchProvider record ID */\n ID: string;\n /** Provider name */\n Name: string;\n /** UI display label */\n DisplayName: string;\n /** Font Awesome icon class */\n Icon: string;\n /** The SourceType key this provider uses */\n SourceType: string;\n /** Priority (lower = higher) */\n Priority: number;\n}\n\n/** Search response from the service */\nexport interface SearchResponse {\n /** Whether the search succeeded */\n Success: boolean;\n /** The search results */\n Results: SearchResultItem[];\n /** Grouped results by source type */\n Groups: SearchResultGroup[];\n /** Available filters based on results */\n Filters: SearchFilter[];\n /** Total result count across all sources */\n TotalCount: number;\n /** Time taken in milliseconds */\n ElapsedMs: number;\n /** Count of results from each source */\n SourceCounts: { Vector: number; FullText: number; Entity: number; Storage: number };\n /** Active search providers metadata (for UI labels and icons) */\n Providers: SearchProviderInfo[];\n /** Error message if Success is false */\n ErrorMessage?: string;\n}\n\n/** Event emitted when a search result is selected */\nexport interface SearchResultSelectedEvent {\n /** The selected result item */\n Result: SearchResultItem;\n /** Whether to open in a new tab */\n OpenInNewTab: boolean;\n}\n\n/** Event emitted when filters change */\nexport interface SearchFilterChangeEvent {\n /** The filter category that changed */\n Category: string;\n /** The new selected values */\n SelectedValues: string[];\n}\n\n/** Event emitted when search is executed */\nexport interface SearchExecutedEvent {\n /** The search query */\n Query: string;\n /** Active filters */\n Filters: Record<string, string[]>;\n /** Number of results returned */\n ResultCount: number;\n /** Time taken in ms */\n ElapsedMs: number;\n}\n"]}
|
|
@@ -12,18 +12,27 @@ export declare class SearchService {
|
|
|
12
12
|
SearchResults$: BehaviorSubject<SearchResponse | null>;
|
|
13
13
|
/** Whether a search is in progress */
|
|
14
14
|
IsSearching$: BehaviorSubject<boolean>;
|
|
15
|
-
/** Recent search history (
|
|
15
|
+
/** Recent search history (persisted via UserInfoEngine, max 20 entries) */
|
|
16
16
|
RecentSearches$: BehaviorSubject<RecentSearch[]>;
|
|
17
17
|
/** Error stream */
|
|
18
18
|
Errors$: Subject<string>;
|
|
19
19
|
private readonly maxRecentSearches;
|
|
20
|
+
private recentSearchesLoaded;
|
|
21
|
+
/** Cached provider metadata keyed by SourceType, populated from search responses */
|
|
22
|
+
private providersBySourceType;
|
|
20
23
|
/**
|
|
21
24
|
* Execute a search request via the SearchKnowledge GraphQL mutation.
|
|
22
25
|
*/
|
|
23
26
|
ExecuteSearch(request: SearchRequest): Promise<SearchResponse>;
|
|
27
|
+
/**
|
|
28
|
+
* Lightweight preview search for autocomplete/typeahead.
|
|
29
|
+
* Does NOT update SearchResults$ or IsSearching$ observables,
|
|
30
|
+
* and does NOT add to recent search history.
|
|
31
|
+
*/
|
|
32
|
+
PreviewSearch(query: string, maxResults?: number): Promise<SearchResponse>;
|
|
24
33
|
/** Clear the current search results */
|
|
25
34
|
ClearResults(): void;
|
|
26
|
-
/** Clear recent search history */
|
|
35
|
+
/** Clear recent search history (memory and persisted) */
|
|
27
36
|
ClearRecentSearches(): void;
|
|
28
37
|
/**
|
|
29
38
|
* Group flat results by source type for display.
|
|
@@ -37,15 +46,28 @@ export declare class SearchService {
|
|
|
37
46
|
GetSourceIcon(sourceType: string): string;
|
|
38
47
|
private buildSourceTypeFilter;
|
|
39
48
|
private buildEntityNameFilter;
|
|
49
|
+
private buildFileTypeFilter;
|
|
40
50
|
private buildTagFilter;
|
|
51
|
+
/**
|
|
52
|
+
* Load persisted recent searches from UserInfoEngine.
|
|
53
|
+
* Safe to call multiple times — only loads once.
|
|
54
|
+
*/
|
|
55
|
+
LoadRecentSearches(): Promise<void>;
|
|
41
56
|
private addToRecentSearches;
|
|
57
|
+
private persistRecentSearches;
|
|
58
|
+
/** Cache provider metadata by SourceType for use in GroupResults/BuildFilters */
|
|
59
|
+
private cacheProviders;
|
|
42
60
|
private createEmptyResponse;
|
|
43
61
|
/**
|
|
44
|
-
* Execute search via the
|
|
62
|
+
* Execute search via the GraphQLSearchClient.
|
|
45
63
|
*/
|
|
46
64
|
private executeGraphQLSearch;
|
|
47
|
-
/**
|
|
48
|
-
private
|
|
65
|
+
/** Convert a SearchClientResponse into the SearchResponse shape used by this service */
|
|
66
|
+
private mapClientResponseToSearchResponse;
|
|
67
|
+
/** Map a single SearchClientResultItem to a SearchResultItem */
|
|
68
|
+
private mapClientResultItem;
|
|
69
|
+
/** Build the client filters from active filter selections */
|
|
70
|
+
private buildClientFilters;
|
|
49
71
|
static ɵfac: i0.ɵɵFactoryDeclaration<SearchService, never>;
|
|
50
72
|
static ɵprov: i0.ɵɵInjectableDeclaration<SearchService>;
|
|
51
73
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.service.d.ts","sourceRoot":"","sources":["../../src/lib/search.service.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"search.service.d.ts","sourceRoot":"","sources":["../../src/lib/search.service.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAUhD,OAAO,EACH,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EAGf,MAAM,gBAAgB,CAAC;;AA8BxB,sCAAsC;AACtC,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACvB;AASD,qBACa,aAAa;IACtB,8BAA8B;IACvB,cAAc,yCAAoD;IAEzE,sCAAsC;IAC/B,YAAY,2BAAuC;IAE1D,2EAA2E;IACpE,eAAe,kCAA2C;IAEjE,mBAAmB;IACZ,OAAO,kBAAyB;IAEvC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAM;IACxC,OAAO,CAAC,oBAAoB,CAAS;IAErC,oFAAoF;IACpF,OAAO,CAAC,qBAAqB,CAAyC;IAEtE;;OAEG;IACU,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;IAuB3E;;;;OAIG;IACU,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,GAAE,MAAU,GAAG,OAAO,CAAC,cAAc,CAAC;IAoB1F,uCAAuC;IAChC,YAAY,IAAI,IAAI;IAI3B,yDAAyD;IAClD,mBAAmB,IAAI,IAAI;IAKlC;;OAEG;IACI,YAAY,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,iBAAiB,EAAE;IA0BrE;;OAEG;IACI,YAAY,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,YAAY,EAAE;IAShE,2CAA2C;IACpC,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAIhD,OAAO,CAAC,qBAAqB;IAkB7B,OAAO,CAAC,qBAAqB;IAkC7B,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,cAAc;IAoBtB;;;OAGG;IACU,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IA+BhD,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,qBAAqB;IAa7B,iFAAiF;IACjF,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,mBAAmB;IAc3B;;OAEG;YACW,oBAAoB;IAwBlC,wFAAwF;IACxF,OAAO,CAAC,iCAAiC;IAsCzC,gEAAgE;IAChE,OAAO,CAAC,mBAAmB;IAuB3B,6DAA6D;IAC7D,OAAO,CAAC,kBAAkB;yCAvYjB,aAAa;6CAAb,aAAa;CA+ZzB"}
|
|
@@ -6,34 +6,48 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { Injectable } from '@angular/core';
|
|
8
8
|
import { BehaviorSubject, Subject } from 'rxjs';
|
|
9
|
-
import { Metadata } from '@memberjunction/core';
|
|
9
|
+
import { Metadata, StartupManager } from '@memberjunction/core';
|
|
10
|
+
import { UserInfoEngine } from '@memberjunction/core-entities';
|
|
11
|
+
import { MJEventType, MJGlobal } from '@memberjunction/global';
|
|
12
|
+
import { GraphQLSearchClient, GraphQLDataProvider } from '@memberjunction/graphql-dataprovider';
|
|
10
13
|
import * as i0 from "@angular/core";
|
|
11
14
|
/** Default minimum relevance score threshold (0-1). Results below this are filtered out. */
|
|
12
15
|
const DEFAULT_MIN_SCORE = 0.35;
|
|
13
|
-
/**
|
|
14
|
-
const
|
|
16
|
+
/** Fallback icon mapping for source types (used when provider metadata is not available) */
|
|
17
|
+
const FALLBACK_SOURCE_ICONS = {
|
|
15
18
|
'entity': 'fa-solid fa-database',
|
|
19
|
+
'vector': 'fa-solid fa-brain',
|
|
20
|
+
'fulltext': 'fa-solid fa-magnifying-glass',
|
|
16
21
|
'content-item': 'fa-solid fa-file-lines',
|
|
17
22
|
'file': 'fa-solid fa-file',
|
|
18
23
|
'web-page': 'fa-solid fa-globe',
|
|
24
|
+
'storage': 'fa-solid fa-folder-open',
|
|
19
25
|
};
|
|
20
|
-
/**
|
|
21
|
-
const
|
|
22
|
-
'entity': '
|
|
26
|
+
/** Fallback labels for source types (used when provider metadata is not available) */
|
|
27
|
+
const FALLBACK_SOURCE_LABELS = {
|
|
28
|
+
'entity': 'Database',
|
|
29
|
+
'vector': 'Semantic Search',
|
|
30
|
+
'fulltext': 'Full-Text Search',
|
|
23
31
|
'content-item': 'Content Items',
|
|
24
32
|
'file': 'Documents',
|
|
25
33
|
'web-page': 'Web Pages',
|
|
34
|
+
'storage': 'File Storage',
|
|
26
35
|
};
|
|
36
|
+
/** User setting key for persisting recent searches */
|
|
37
|
+
const RECENT_SEARCHES_KEY = 'search.recentSearches';
|
|
27
38
|
export class SearchService {
|
|
28
39
|
/** Current search response */
|
|
29
40
|
SearchResults$ = new BehaviorSubject(null);
|
|
30
41
|
/** Whether a search is in progress */
|
|
31
42
|
IsSearching$ = new BehaviorSubject(false);
|
|
32
|
-
/** Recent search history (
|
|
43
|
+
/** Recent search history (persisted via UserInfoEngine, max 20 entries) */
|
|
33
44
|
RecentSearches$ = new BehaviorSubject([]);
|
|
34
45
|
/** Error stream */
|
|
35
46
|
Errors$ = new Subject();
|
|
36
47
|
maxRecentSearches = 20;
|
|
48
|
+
recentSearchesLoaded = false;
|
|
49
|
+
/** Cached provider metadata keyed by SourceType, populated from search responses */
|
|
50
|
+
providersBySourceType = new Map();
|
|
37
51
|
/**
|
|
38
52
|
* Execute a search request via the SearchKnowledge GraphQL mutation.
|
|
39
53
|
*/
|
|
@@ -58,13 +72,37 @@ export class SearchService {
|
|
|
58
72
|
this.IsSearching$.next(false);
|
|
59
73
|
}
|
|
60
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Lightweight preview search for autocomplete/typeahead.
|
|
77
|
+
* Does NOT update SearchResults$ or IsSearching$ observables,
|
|
78
|
+
* and does NOT add to recent search history.
|
|
79
|
+
*/
|
|
80
|
+
async PreviewSearch(query, maxResults = 8) {
|
|
81
|
+
try {
|
|
82
|
+
if (!query.trim()) {
|
|
83
|
+
return this.createEmptyResponse();
|
|
84
|
+
}
|
|
85
|
+
const provider = Metadata.Provider;
|
|
86
|
+
if (!(provider instanceof GraphQLDataProvider)) {
|
|
87
|
+
return this.createEmptyResponse('GraphQL provider not available');
|
|
88
|
+
}
|
|
89
|
+
const client = new GraphQLSearchClient(provider);
|
|
90
|
+
const clientResponse = await client.PreviewSearch(query, maxResults);
|
|
91
|
+
return this.mapClientResponseToSearchResponse(clientResponse);
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
const errorMessage = err instanceof Error ? err.message : 'Preview search failed';
|
|
95
|
+
return this.createEmptyResponse(errorMessage);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
61
98
|
/** Clear the current search results */
|
|
62
99
|
ClearResults() {
|
|
63
100
|
this.SearchResults$.next(null);
|
|
64
101
|
}
|
|
65
|
-
/** Clear recent search history */
|
|
102
|
+
/** Clear recent search history (memory and persisted) */
|
|
66
103
|
ClearRecentSearches() {
|
|
67
104
|
this.RecentSearches$.next([]);
|
|
105
|
+
this.persistRecentSearches([]);
|
|
68
106
|
}
|
|
69
107
|
/**
|
|
70
108
|
* Group flat results by source type for display.
|
|
@@ -81,13 +119,17 @@ export class SearchService {
|
|
|
81
119
|
groupMap.set(key, [result]);
|
|
82
120
|
}
|
|
83
121
|
}
|
|
84
|
-
return Array.from(groupMap.entries()).map(([sourceType, items]) =>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
122
|
+
return Array.from(groupMap.entries()).map(([sourceType, items]) => {
|
|
123
|
+
// Priority: cached provider metadata > per-result metadata > fallback maps
|
|
124
|
+
const cached = this.providersBySourceType.get(sourceType);
|
|
125
|
+
return {
|
|
126
|
+
Label: cached?.DisplayName ?? items[0]?.ProviderLabel ?? FALLBACK_SOURCE_LABELS[sourceType] ?? sourceType,
|
|
127
|
+
Icon: cached?.Icon ?? items[0]?.ProviderIcon ?? FALLBACK_SOURCE_ICONS[sourceType] ?? 'fa-solid fa-circle',
|
|
128
|
+
SourceType: sourceType,
|
|
129
|
+
Results: items,
|
|
130
|
+
TotalCount: items.length
|
|
131
|
+
};
|
|
132
|
+
});
|
|
91
133
|
}
|
|
92
134
|
/**
|
|
93
135
|
* Build filter facets from a set of results.
|
|
@@ -96,31 +138,41 @@ export class SearchService {
|
|
|
96
138
|
return [
|
|
97
139
|
this.buildSourceTypeFilter(results),
|
|
98
140
|
this.buildEntityNameFilter(results),
|
|
141
|
+
this.buildFileTypeFilter(results),
|
|
99
142
|
this.buildTagFilter(results)
|
|
100
|
-
];
|
|
143
|
+
].filter(f => f.Options.length > 0); // Hide categories with no options
|
|
101
144
|
}
|
|
102
145
|
/** Get the icon for a given source type */
|
|
103
146
|
GetSourceIcon(sourceType) {
|
|
104
|
-
return
|
|
147
|
+
return this.providersBySourceType.get(sourceType)?.Icon ?? FALLBACK_SOURCE_ICONS[sourceType] ?? 'fa-solid fa-circle';
|
|
105
148
|
}
|
|
106
149
|
buildSourceTypeFilter(results) {
|
|
107
150
|
const counts = new Map();
|
|
108
151
|
for (const r of results) {
|
|
109
152
|
counts.set(r.SourceType, (counts.get(r.SourceType) ?? 0) + 1);
|
|
110
153
|
}
|
|
111
|
-
const options = Array.from(counts.entries()).map(([type, count]) =>
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
154
|
+
const options = Array.from(counts.entries()).map(([type, count]) => {
|
|
155
|
+
const cached = this.providersBySourceType.get(type);
|
|
156
|
+
return {
|
|
157
|
+
Label: cached?.DisplayName ?? FALLBACK_SOURCE_LABELS[type] ?? type,
|
|
158
|
+
Value: type,
|
|
159
|
+
Count: count,
|
|
160
|
+
IsSelected: false,
|
|
161
|
+
Icon: cached?.Icon ?? FALLBACK_SOURCE_ICONS[type]
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
return { Category: 'Source', Options: options, MultiSelect: true };
|
|
119
165
|
}
|
|
120
166
|
buildEntityNameFilter(results) {
|
|
121
167
|
const counts = new Map();
|
|
168
|
+
let fileCount = 0;
|
|
122
169
|
for (const r of results) {
|
|
123
|
-
|
|
170
|
+
if (r.ResultType === 'storage-file') {
|
|
171
|
+
fileCount++;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
counts.set(r.EntityName, (counts.get(r.EntityName) ?? 0) + 1);
|
|
175
|
+
}
|
|
124
176
|
}
|
|
125
177
|
const options = Array.from(counts.entries())
|
|
126
178
|
.sort((a, b) => b[1] - a[1])
|
|
@@ -131,8 +183,39 @@ export class SearchService {
|
|
|
131
183
|
IsSelected: false,
|
|
132
184
|
Icon: 'fa-solid fa-table'
|
|
133
185
|
}));
|
|
186
|
+
// Add a single "Files" entry for all storage file results
|
|
187
|
+
if (fileCount > 0) {
|
|
188
|
+
options.push({
|
|
189
|
+
Label: 'Files',
|
|
190
|
+
Value: '__storage-files__',
|
|
191
|
+
Count: fileCount,
|
|
192
|
+
IsSelected: false,
|
|
193
|
+
Icon: 'fa-solid fa-file'
|
|
194
|
+
});
|
|
195
|
+
}
|
|
134
196
|
return { Category: 'Entity', Options: options, MultiSelect: true };
|
|
135
197
|
}
|
|
198
|
+
buildFileTypeFilter(results) {
|
|
199
|
+
const counts = new Map();
|
|
200
|
+
for (const r of results) {
|
|
201
|
+
if (r.ResultType !== 'storage-file')
|
|
202
|
+
continue;
|
|
203
|
+
const ext = r.Title?.split('.').pop()?.toUpperCase();
|
|
204
|
+
if (ext && ext.length <= 6) {
|
|
205
|
+
counts.set(ext, (counts.get(ext) ?? 0) + 1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const options = Array.from(counts.entries())
|
|
209
|
+
.sort((a, b) => b[1] - a[1])
|
|
210
|
+
.map(([ext, count]) => ({
|
|
211
|
+
Label: ext,
|
|
212
|
+
Value: ext,
|
|
213
|
+
Count: count,
|
|
214
|
+
IsSelected: false,
|
|
215
|
+
Icon: 'fa-solid fa-file'
|
|
216
|
+
}));
|
|
217
|
+
return { Category: 'File Type', Options: options, MultiSelect: true };
|
|
218
|
+
}
|
|
136
219
|
buildTagFilter(results) {
|
|
137
220
|
const counts = new Map();
|
|
138
221
|
for (const r of results) {
|
|
@@ -152,6 +235,38 @@ export class SearchService {
|
|
|
152
235
|
}));
|
|
153
236
|
return { Category: 'Tags', Options: options, MultiSelect: true };
|
|
154
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Load persisted recent searches from UserInfoEngine.
|
|
240
|
+
* Safe to call multiple times — only loads once.
|
|
241
|
+
*/
|
|
242
|
+
async LoadRecentSearches() {
|
|
243
|
+
if (this.recentSearchesLoaded)
|
|
244
|
+
return;
|
|
245
|
+
try {
|
|
246
|
+
MJGlobal.Instance.GetEventListener(true).subscribe(async (event) => {
|
|
247
|
+
if (event.event === MJEventType.LoggedIn) {
|
|
248
|
+
await StartupManager.Instance.Startup();
|
|
249
|
+
const engine = UserInfoEngine.Instance;
|
|
250
|
+
console.log('[SearchService] After Startup — engine.Loaded:', engine.Loaded, 'UserSettings count:', engine.UserSettings.length);
|
|
251
|
+
const json = engine.GetSetting(RECENT_SEARCHES_KEY);
|
|
252
|
+
console.log('[SearchService] GetSetting("' + RECENT_SEARCHES_KEY + '"):', json?.substring(0, 100));
|
|
253
|
+
if (json) {
|
|
254
|
+
const parsed = JSON.parse(json);
|
|
255
|
+
const searches = parsed.map(s => ({
|
|
256
|
+
Query: s.Query,
|
|
257
|
+
Timestamp: new Date(s.Timestamp),
|
|
258
|
+
ResultCount: s.ResultCount
|
|
259
|
+
}));
|
|
260
|
+
this.RecentSearches$.next(searches);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
console.error('[SearchService] LoadRecentSearches error:', err);
|
|
267
|
+
}
|
|
268
|
+
this.recentSearchesLoaded = true;
|
|
269
|
+
}
|
|
155
270
|
addToRecentSearches(query, resultCount) {
|
|
156
271
|
const current = this.RecentSearches$.value;
|
|
157
272
|
const filtered = current.filter(s => s.Query !== query);
|
|
@@ -160,6 +275,26 @@ export class SearchService {
|
|
|
160
275
|
...filtered
|
|
161
276
|
].slice(0, this.maxRecentSearches);
|
|
162
277
|
this.RecentSearches$.next(updated);
|
|
278
|
+
this.persistRecentSearches(updated);
|
|
279
|
+
}
|
|
280
|
+
persistRecentSearches(searches) {
|
|
281
|
+
try {
|
|
282
|
+
const json = searches.map(s => ({
|
|
283
|
+
Query: s.Query,
|
|
284
|
+
Timestamp: s.Timestamp.toISOString(),
|
|
285
|
+
ResultCount: s.ResultCount
|
|
286
|
+
}));
|
|
287
|
+
UserInfoEngine.Instance.SetSettingDebounced(RECENT_SEARCHES_KEY, JSON.stringify(json));
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
// Silently fail if UserInfoEngine not available
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/** Cache provider metadata by SourceType for use in GroupResults/BuildFilters */
|
|
294
|
+
cacheProviders(providers) {
|
|
295
|
+
for (const p of providers) {
|
|
296
|
+
this.providersBySourceType.set(p.SourceType, p);
|
|
297
|
+
}
|
|
163
298
|
}
|
|
164
299
|
createEmptyResponse(errorMessage) {
|
|
165
300
|
return {
|
|
@@ -169,105 +304,101 @@ export class SearchService {
|
|
|
169
304
|
Filters: [],
|
|
170
305
|
TotalCount: 0,
|
|
171
306
|
ElapsedMs: 0,
|
|
172
|
-
SourceCounts: { Vector: 0, FullText: 0, Entity: 0 },
|
|
307
|
+
SourceCounts: { Vector: 0, FullText: 0, Entity: 0, Storage: 0 },
|
|
308
|
+
Providers: [],
|
|
173
309
|
ErrorMessage: errorMessage
|
|
174
310
|
};
|
|
175
311
|
}
|
|
176
312
|
/**
|
|
177
|
-
* Execute search via the
|
|
313
|
+
* Execute search via the GraphQLSearchClient.
|
|
178
314
|
*/
|
|
179
315
|
async executeGraphQLSearch(request) {
|
|
180
316
|
if (!request.Query.trim()) {
|
|
181
317
|
return this.createEmptyResponse();
|
|
182
318
|
}
|
|
183
319
|
const provider = Metadata.Provider;
|
|
184
|
-
if (!provider
|
|
320
|
+
if (!(provider instanceof GraphQLDataProvider)) {
|
|
185
321
|
return this.createEmptyResponse('GraphQL provider not available');
|
|
186
322
|
}
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
SearchKnowledge(query: $query, maxResults: $maxResults, filters: $filters, minScore: $minScore) {
|
|
190
|
-
Success
|
|
191
|
-
TotalCount
|
|
192
|
-
ElapsedMs
|
|
193
|
-
ErrorMessage
|
|
194
|
-
SourceCounts {
|
|
195
|
-
Vector
|
|
196
|
-
FullText
|
|
197
|
-
Entity
|
|
198
|
-
}
|
|
199
|
-
Results {
|
|
200
|
-
ID
|
|
201
|
-
EntityName
|
|
202
|
-
RecordID
|
|
203
|
-
SourceType
|
|
204
|
-
Title
|
|
205
|
-
Snippet
|
|
206
|
-
Score
|
|
207
|
-
ScoreBreakdown {
|
|
208
|
-
Vector
|
|
209
|
-
FullText
|
|
210
|
-
Entity
|
|
211
|
-
}
|
|
212
|
-
Tags
|
|
213
|
-
EntityIcon
|
|
214
|
-
RecordName
|
|
215
|
-
MatchedAt
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
`;
|
|
220
|
-
const filtersInput = this.buildFiltersInput(request);
|
|
323
|
+
const client = new GraphQLSearchClient(provider);
|
|
324
|
+
const filters = this.buildClientFilters(request);
|
|
221
325
|
const minScore = request.MinScore ?? DEFAULT_MIN_SCORE;
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
326
|
+
const clientResponse = await client.ExecuteSearch({
|
|
327
|
+
Query: request.Query,
|
|
328
|
+
MaxResults: request.MaxResults || 20,
|
|
329
|
+
MinScore: minScore > 0 ? minScore : undefined,
|
|
330
|
+
Filters: filters
|
|
331
|
+
});
|
|
332
|
+
return this.mapClientResponseToSearchResponse(clientResponse);
|
|
333
|
+
}
|
|
334
|
+
/** Convert a SearchClientResponse into the SearchResponse shape used by this service */
|
|
335
|
+
mapClientResponseToSearchResponse(clientResponse) {
|
|
336
|
+
if (!clientResponse.Success) {
|
|
337
|
+
return this.createEmptyResponse(clientResponse.ErrorMessage ?? 'Search failed');
|
|
232
338
|
}
|
|
233
|
-
|
|
339
|
+
// Cache provider metadata for use in GroupResults/BuildFilters
|
|
340
|
+
const providers = (clientResponse.Providers ?? []).map(p => ({
|
|
341
|
+
ID: p.ID,
|
|
342
|
+
Name: p.Name,
|
|
343
|
+
DisplayName: p.DisplayName,
|
|
344
|
+
Icon: p.Icon,
|
|
345
|
+
SourceType: p.SourceType,
|
|
346
|
+
Priority: p.Priority,
|
|
347
|
+
}));
|
|
348
|
+
this.cacheProviders(providers);
|
|
349
|
+
const results = (clientResponse.Results ?? []).map(r => this.mapClientResultItem(r));
|
|
350
|
+
const groups = this.GroupResults(results);
|
|
351
|
+
const filters = this.BuildFilters(results);
|
|
352
|
+
return {
|
|
353
|
+
Success: true,
|
|
354
|
+
Results: results,
|
|
355
|
+
Groups: groups,
|
|
356
|
+
Filters: filters,
|
|
357
|
+
TotalCount: clientResponse.TotalCount,
|
|
358
|
+
ElapsedMs: clientResponse.ElapsedMs,
|
|
359
|
+
SourceCounts: {
|
|
360
|
+
Vector: clientResponse.SourceCounts.Vector,
|
|
361
|
+
FullText: clientResponse.SourceCounts.FullText,
|
|
362
|
+
Entity: clientResponse.SourceCounts.Entity,
|
|
363
|
+
Storage: clientResponse.SourceCounts.Storage
|
|
364
|
+
},
|
|
365
|
+
Providers: providers,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/** Map a single SearchClientResultItem to a SearchResultItem */
|
|
369
|
+
mapClientResultItem(r) {
|
|
370
|
+
return {
|
|
234
371
|
ID: r.ID,
|
|
235
372
|
Title: r.RecordName || r.Title,
|
|
236
373
|
Snippet: r.Snippet,
|
|
237
374
|
EntityName: r.EntityName,
|
|
238
375
|
RecordID: r.RecordID,
|
|
239
376
|
SourceType: r.SourceType || 'entity',
|
|
377
|
+
ResultType: r.ResultType,
|
|
240
378
|
Score: r.Score,
|
|
241
379
|
ScoreBreakdown: r.ScoreBreakdown ?? {},
|
|
242
380
|
Tags: r.Tags ?? [],
|
|
243
|
-
SourceIcon: r.EntityIcon ||
|
|
381
|
+
SourceIcon: r.EntityIcon || r.ProviderIcon || FALLBACK_SOURCE_ICONS[r.SourceType] || 'fa-solid fa-database',
|
|
244
382
|
EntityIcon: r.EntityIcon,
|
|
245
383
|
RecordName: r.RecordName,
|
|
246
384
|
MatchedAt: new Date(r.MatchedAt),
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
Success: true,
|
|
252
|
-
Results: results,
|
|
253
|
-
Groups: groups,
|
|
254
|
-
Filters: filters,
|
|
255
|
-
TotalCount: data.TotalCount,
|
|
256
|
-
ElapsedMs: data.ElapsedMs,
|
|
257
|
-
SourceCounts: data.SourceCounts,
|
|
385
|
+
RawMetadata: r.RawMetadata,
|
|
386
|
+
ProviderId: r.ProviderId,
|
|
387
|
+
ProviderLabel: r.ProviderLabel,
|
|
388
|
+
ProviderIcon: r.ProviderIcon,
|
|
258
389
|
};
|
|
259
390
|
}
|
|
260
|
-
/** Build the
|
|
261
|
-
|
|
391
|
+
/** Build the client filters from active filter selections */
|
|
392
|
+
buildClientFilters(request) {
|
|
262
393
|
if (!request.ActiveFilters || Object.keys(request.ActiveFilters).length === 0) {
|
|
263
|
-
return
|
|
394
|
+
return undefined;
|
|
264
395
|
}
|
|
265
396
|
const result = {};
|
|
266
397
|
const entityFilters = request.ActiveFilters['Entity'];
|
|
267
398
|
if (entityFilters?.length) {
|
|
268
399
|
result.EntityNames = entityFilters;
|
|
269
400
|
}
|
|
270
|
-
const sourceFilters = request.ActiveFilters['Source
|
|
401
|
+
const sourceFilters = request.ActiveFilters['Source'];
|
|
271
402
|
if (sourceFilters?.length) {
|
|
272
403
|
result.SourceTypes = sourceFilters;
|
|
273
404
|
}
|
|
@@ -275,7 +406,7 @@ export class SearchService {
|
|
|
275
406
|
if (tagFilters?.length) {
|
|
276
407
|
result.Tags = tagFilters;
|
|
277
408
|
}
|
|
278
|
-
return Object.keys(result).length > 0 ? result :
|
|
409
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
279
410
|
}
|
|
280
411
|
static ɵfac = function SearchService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || SearchService)(); };
|
|
281
412
|
static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: SearchService, factory: SearchService.ɵfac, providedIn: 'root' });
|