@memberjunction/server 2.20.2 → 2.21.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/config.d.ts +5 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/generated/generated.d.ts +122 -57
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +1287 -694
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +8 -4
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/resolvers/AskSkipResolver.d.ts +14 -5
- package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
- package/dist/resolvers/AskSkipResolver.js +250 -61
- package/dist/resolvers/AskSkipResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts +90 -0
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -0
- package/dist/resolvers/GetDataResolver.js +294 -0
- package/dist/resolvers/GetDataResolver.js.map +1 -0
- package/dist/resolvers/SyncDataResolver.d.ts +47 -0
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -0
- package/dist/resolvers/SyncDataResolver.js +345 -0
- package/dist/resolvers/SyncDataResolver.js.map +1 -0
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/package.json +23 -22
- package/src/config.ts +2 -0
- package/src/generated/generated.ts +899 -610
- package/src/generic/ResolverBase.ts +14 -5
- package/src/index.ts +2 -0
- package/src/resolvers/AskSkipResolver.ts +300 -66
- package/src/resolvers/GetDataResolver.ts +245 -0
- package/src/resolvers/SyncDataResolver.ts +337 -0
- package/src/resolvers/SyncRolesUsersResolver.ts +2 -2
|
@@ -269,16 +269,25 @@ export class ResolverBase {
|
|
|
269
269
|
protected CheckUserReadPermissions(entityName: string, userPayload: UserPayload | null) {
|
|
270
270
|
const md = new Metadata();
|
|
271
271
|
const entityInfo = md.Entities.find((e) => e.Name === entityName);
|
|
272
|
-
if (!userPayload)
|
|
273
|
-
|
|
272
|
+
if (!userPayload) {
|
|
273
|
+
throw new Error(`userPayload is null`);
|
|
274
|
+
}
|
|
275
|
+
|
|
274
276
|
// first check permissions, the logged in user must have read permissions on the entity to run the view
|
|
275
277
|
if (entityInfo) {
|
|
276
278
|
const userInfo = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload.email.toLowerCase().trim()); // get the user record from MD so we have ROLES attached, don't use the one from payload directly
|
|
277
|
-
if (!userInfo)
|
|
279
|
+
if (!userInfo) {
|
|
280
|
+
throw new Error(`User ${userPayload.email} not found in metadata`);
|
|
281
|
+
}
|
|
278
282
|
|
|
279
283
|
const userPermissions = entityInfo.GetUserPermisions(userInfo);
|
|
280
|
-
if (!userPermissions.CanRead)
|
|
281
|
-
|
|
284
|
+
if (!userPermissions.CanRead) {
|
|
285
|
+
throw new Error(`User ${userPayload.email} does not have read permissions on ${entityInfo.Name}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
throw new Error(`Entity not found in metadata`);
|
|
290
|
+
}
|
|
282
291
|
}
|
|
283
292
|
|
|
284
293
|
protected async RunViewGenericInternal(
|
package/src/index.ts
CHANGED
|
@@ -61,6 +61,8 @@ export * from './resolvers/EntityRecordNameResolver.js';
|
|
|
61
61
|
export * from './resolvers/MergeRecordsResolver.js';
|
|
62
62
|
export * from './resolvers/ReportResolver.js';
|
|
63
63
|
export * from './resolvers/SyncRolesUsersResolver.js';
|
|
64
|
+
export * from './resolvers/SyncDataResolver.js';
|
|
65
|
+
export * from './resolvers/GetDataResolver.js';
|
|
64
66
|
|
|
65
67
|
export * from './generated/generated.js';
|
|
66
68
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Arg, Ctx, Field, Int, ObjectType, PubSub, PubSubEngine, Query, Resolver } from 'type-graphql';
|
|
2
|
-
import { LogError, LogStatus, Metadata, KeyValuePair, RunView, UserInfo, CompositeKey } from '@memberjunction/core';
|
|
2
|
+
import { LogError, LogStatus, Metadata, KeyValuePair, RunView, UserInfo, CompositeKey, EntityFieldInfo, EntityInfo, EntityRelationshipInfo } from '@memberjunction/core';
|
|
3
3
|
import { AppContext, UserPayload } from '../types.js';
|
|
4
|
+
import { BehaviorSubject } from 'rxjs';
|
|
5
|
+
import { take } from 'rxjs/operators';
|
|
4
6
|
import { UserCache } from '@memberjunction/sqlserver-dataprovider';
|
|
5
7
|
import { DataContext } from '@memberjunction/data-context';
|
|
6
8
|
import { LoadDataContextItemsServer } from '@memberjunction/data-context-server';
|
|
@@ -20,6 +22,9 @@ import {
|
|
|
20
22
|
SkipRequestPhase,
|
|
21
23
|
SkipAPIAgentNote,
|
|
22
24
|
SkipAPIAgentNoteType,
|
|
25
|
+
SkipEntityFieldInfo,
|
|
26
|
+
SkipEntityRelationshipInfo,
|
|
27
|
+
SkipEntityFieldValueInfo,
|
|
23
28
|
} from '@memberjunction/skip-types';
|
|
24
29
|
|
|
25
30
|
import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
|
|
@@ -31,7 +36,7 @@ import {
|
|
|
31
36
|
UserNotificationEntity,
|
|
32
37
|
} from '@memberjunction/core-entities';
|
|
33
38
|
import { DataSource } from 'typeorm';
|
|
34
|
-
import { ___skipAPIOrgId, ___skipAPIurl, configInfo, mj_core_schema } from '../config.js';
|
|
39
|
+
import { ___skipAPIOrgId, ___skipAPIurl, apiKey, baseUrl, configInfo, graphqlPort, mj_core_schema } from '../config.js';
|
|
35
40
|
|
|
36
41
|
import { registerEnumType } from 'type-graphql';
|
|
37
42
|
import { MJGlobal, CopyScalarsAndArrays } from '@memberjunction/global';
|
|
@@ -39,6 +44,7 @@ import { sendPostRequest } from '../util.js';
|
|
|
39
44
|
import { GetAIAPIKey } from '@memberjunction/ai';
|
|
40
45
|
import { CompositeKeyInputType } from '../generic/KeyInputOutputTypes.js';
|
|
41
46
|
import { AIAgentEntityExtended, AIEngine } from '@memberjunction/aiengine';
|
|
47
|
+
import { deleteAccessToken, GetDataAccessToken, registerAccessToken, tokenExists } from './GetDataResolver.js';
|
|
42
48
|
|
|
43
49
|
enum SkipResponsePhase {
|
|
44
50
|
ClarifyingQuestion = 'clarifying_question',
|
|
@@ -149,7 +155,7 @@ export class AskSkipResolver {
|
|
|
149
155
|
}
|
|
150
156
|
}
|
|
151
157
|
|
|
152
|
-
const input = await this.buildSkipAPIRequest(messages, ConversationId, dataContext, 'chat_with_a_record', false, false, false, user);
|
|
158
|
+
const input = await this.buildSkipAPIRequest(messages, ConversationId, dataContext, 'chat_with_a_record', false, false, false, user, dataSource, false, false);
|
|
153
159
|
messages.push({
|
|
154
160
|
content: UserQuestion,
|
|
155
161
|
role: 'user',
|
|
@@ -230,11 +236,32 @@ export class AskSkipResolver {
|
|
|
230
236
|
includeEntities: boolean,
|
|
231
237
|
includeQueries: boolean,
|
|
232
238
|
includeNotes: boolean,
|
|
233
|
-
contextUser: UserInfo
|
|
239
|
+
contextUser: UserInfo,
|
|
240
|
+
dataSource: DataSource,
|
|
241
|
+
forceEntitiesRefresh: boolean = false,
|
|
242
|
+
includeCallBackKeyAndAccessToken: boolean = false
|
|
234
243
|
): Promise<SkipAPIRequest> {
|
|
235
|
-
const entities = includeEntities ? this.BuildSkipEntities() : [];
|
|
244
|
+
const entities = includeEntities ? await this.BuildSkipEntities(dataSource, forceEntitiesRefresh) : [];
|
|
236
245
|
const queries = includeQueries ? this.BuildSkipQueries() : [];
|
|
237
246
|
const {notes, noteTypes} = includeNotes ? await this.BuildSkipAgentNotes(contextUser) : {notes: [], noteTypes: []};
|
|
247
|
+
|
|
248
|
+
// setup a secure access token that is short lived for use with the Skip API
|
|
249
|
+
let accessToken: GetDataAccessToken;
|
|
250
|
+
if (includeCallBackKeyAndAccessToken) {
|
|
251
|
+
accessToken = registerAccessToken(
|
|
252
|
+
undefined,
|
|
253
|
+
1000 * 60 * 10 /*10 minutes*/,
|
|
254
|
+
{
|
|
255
|
+
type: 'skip_api_request',
|
|
256
|
+
userEmail: contextUser.Email,
|
|
257
|
+
userName: contextUser.Name,
|
|
258
|
+
userID: contextUser.ID,
|
|
259
|
+
conversationId: conversationId,
|
|
260
|
+
requestPhase: requestPhase,
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
238
265
|
const input: SkipAPIRequest = {
|
|
239
266
|
apiKeys: this.buildSkipAPIKeys(),
|
|
240
267
|
organizationInfo: configInfo?.askSkip?.organizationInfo,
|
|
@@ -246,7 +273,10 @@ export class AskSkipResolver {
|
|
|
246
273
|
entities: entities,
|
|
247
274
|
queries: queries,
|
|
248
275
|
notes: notes,
|
|
249
|
-
noteTypes: noteTypes
|
|
276
|
+
noteTypes: noteTypes,
|
|
277
|
+
callingServerURL: accessToken ? `${baseUrl}:${graphqlPort}` : undefined,
|
|
278
|
+
callingServerAPIKey: accessToken ? apiKey : undefined,
|
|
279
|
+
callingServerAccessToken: accessToken ? accessToken.Token : undefined
|
|
250
280
|
};
|
|
251
281
|
return input;
|
|
252
282
|
}
|
|
@@ -268,7 +298,7 @@ export class AskSkipResolver {
|
|
|
268
298
|
if (!user) throw new Error(`User ${userPayload.email} not found in UserCache`);
|
|
269
299
|
const dataContext: DataContext = new DataContext();
|
|
270
300
|
await dataContext.Load(DataContextId, dataSource, true, false, 0, user);
|
|
271
|
-
const input = <SkipAPIRunScriptRequest>await this.buildSkipAPIRequest([], '', dataContext, 'run_existing_script', false, false, false, user);
|
|
301
|
+
const input = <SkipAPIRunScriptRequest>await this.buildSkipAPIRequest([], '', dataContext, 'run_existing_script', false, false, false, user, dataSource, false, false);
|
|
272
302
|
input.scriptText = ScriptText;
|
|
273
303
|
return this.handleSimpleSkipPostRequest(input);
|
|
274
304
|
}
|
|
@@ -304,7 +334,8 @@ export class AskSkipResolver {
|
|
|
304
334
|
@Arg('ConversationId', () => String) ConversationId: string,
|
|
305
335
|
@Ctx() { dataSource, userPayload }: AppContext,
|
|
306
336
|
@PubSub() pubSub: PubSubEngine,
|
|
307
|
-
@Arg('DataContextId', () => String, { nullable: true }) DataContextId?: string
|
|
337
|
+
@Arg('DataContextId', () => String, { nullable: true }) DataContextId?: string,
|
|
338
|
+
@Arg('ForceEntityRefresh', () => Boolean, { nullable: true }) ForceEntityRefresh?: boolean
|
|
308
339
|
) {
|
|
309
340
|
const md = new Metadata();
|
|
310
341
|
const user = UserCache.Instance.Users.find((u) => u.Email.trim().toLowerCase() === userPayload.email.trim().toLowerCase());
|
|
@@ -328,7 +359,7 @@ export class AskSkipResolver {
|
|
|
328
359
|
);
|
|
329
360
|
|
|
330
361
|
const conversationDetailCount = 1
|
|
331
|
-
const input = await this.buildSkipAPIRequest(messages, ConversationId, dataContext, 'initial_request', true, true, true, user);
|
|
362
|
+
const input = await this.buildSkipAPIRequest(messages, ConversationId, dataContext, 'initial_request', true, true, true, user, dataSource, ForceEntityRefresh === undefined ? false : ForceEntityRefresh, true);
|
|
332
363
|
|
|
333
364
|
return this.HandleSkipRequest(
|
|
334
365
|
input,
|
|
@@ -436,77 +467,274 @@ export class AskSkipResolver {
|
|
|
436
467
|
}
|
|
437
468
|
}
|
|
438
469
|
catch (e) {
|
|
439
|
-
LogError(e);
|
|
470
|
+
LogError(`AskSkipResolver::BuildSkipAgentNotes: ${e}`);
|
|
440
471
|
return {notes: [], noteTypes: []}; // non- fatal error just return empty arrays
|
|
441
472
|
}
|
|
442
473
|
}
|
|
443
474
|
|
|
444
|
-
protected
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
475
|
+
protected async PackEntityRows(e: EntityInfo, dataSource: DataSource): Promise<any[]> {
|
|
476
|
+
try {
|
|
477
|
+
if (e.RowsToPackWithSchema === 'None')
|
|
478
|
+
return [];
|
|
479
|
+
|
|
480
|
+
// only include columns that have a scopes including either All and/or AI or have Null for ScopeDefault
|
|
481
|
+
const fields = e.Fields.filter((f) => {
|
|
482
|
+
const scopes = f.ScopeDefault?.split(',').map((s) => s.trim().toLowerCase());
|
|
483
|
+
return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai');
|
|
484
|
+
}).map(f => `[${f.Name}]`).join(',');
|
|
485
|
+
|
|
486
|
+
// now run the query based on the row packing method
|
|
487
|
+
let sql: string = '';
|
|
488
|
+
switch (e.RowsToPackWithSchema) {
|
|
489
|
+
case 'All':
|
|
490
|
+
sql = `SELECT ${fields} FROM ${e.SchemaName}.${e.BaseView}`;
|
|
491
|
+
break;
|
|
492
|
+
case 'Sample':
|
|
493
|
+
switch (e.RowsToPackSampleMethod) {
|
|
494
|
+
case 'random':
|
|
495
|
+
sql = `SELECT TOP ${e.RowsToPackSampleCount} ${fields} FROM [${e.SchemaName}].[${e.BaseView}] ORDER BY newid()`; // SQL Server newid() function returns a new uniqueidentifier value for each row and when sorted it will be random
|
|
496
|
+
break;
|
|
497
|
+
case 'top n':
|
|
498
|
+
const orderBy = e.RowsToPackSampleOrder ? ` ORDER BY [${e.RowsToPackSampleOrder}]` : '';
|
|
499
|
+
sql = `SELECT TOP ${e.RowsToPackSampleCount} ${fields} FROM [${e.SchemaName}].[${e.BaseView}]${orderBy}`;
|
|
500
|
+
break;
|
|
501
|
+
case 'bottom n':
|
|
502
|
+
const firstPrimaryKey = e.FirstPrimaryKey.Name;
|
|
503
|
+
const innerOrderBy = e.RowsToPackSampleOrder ? `[${e.RowsToPackSampleOrder}]` : `[${firstPrimaryKey}] DESC`;
|
|
504
|
+
sql = `SELECT * FROM (
|
|
505
|
+
SELECT TOP ${e.RowsToPackSampleCount} ${fields}
|
|
506
|
+
FROM [${e.SchemaName}].[${e.BaseView}]
|
|
507
|
+
ORDER BY ${innerOrderBy}
|
|
508
|
+
) sub
|
|
509
|
+
ORDER BY [${firstPrimaryKey}] ASC;`;
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const result = await dataSource.query(sql);
|
|
514
|
+
if (!result) {
|
|
515
|
+
return [];
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
return result;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
catch (e) {
|
|
522
|
+
LogError(`AskSkipResolver::PackEntityRows: ${e}`);
|
|
523
|
+
return [];
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
protected async PackFieldPossibleValues(f: EntityFieldInfo, dataSource: DataSource): Promise<SkipEntityFieldValueInfo[]> {
|
|
528
|
+
try {
|
|
529
|
+
if (f.ValuesToPackWithSchema === 'None') {
|
|
530
|
+
return []; // don't pack anything
|
|
531
|
+
}
|
|
532
|
+
else if (f.ValuesToPackWithSchema === 'All') {
|
|
533
|
+
// wants ALL of the distinct values
|
|
534
|
+
return await this.GetFieldDistinctValues(f, dataSource);
|
|
535
|
+
}
|
|
536
|
+
else if (f.ValuesToPackWithSchema === 'Auto') {
|
|
537
|
+
// default setting - pack based on the ValueListType
|
|
538
|
+
if (f.ValueListTypeEnum === 'List') {
|
|
539
|
+
// simple list of values in the Entity Field Values table
|
|
540
|
+
return f.EntityFieldValues.map((v) => {
|
|
541
|
+
return {value: v.Value, displayValue: v.Value};
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
else if (f.ValueListTypeEnum === 'ListOrUserEntry') {
|
|
545
|
+
// could be a user provided value, OR the values in the list of possible values.
|
|
546
|
+
// get the distinct list of values from the DB and concat that with the f.EntityFieldValues array - deduped and return
|
|
547
|
+
const values = await this.GetFieldDistinctValues(f, dataSource);
|
|
548
|
+
if (!values || values.length === 0) {
|
|
549
|
+
// no result, just return the EntityFieldValues
|
|
550
|
+
return f.EntityFieldValues.map((v) => {
|
|
551
|
+
return {value: v.Value, displayValue: v.Value};
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
return [...new Set([...f.EntityFieldValues.map((v) => {
|
|
556
|
+
return {value: v.Value, displayValue: v.Value};
|
|
557
|
+
}), ...values])];
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return []; // if we get here, nothing to pack
|
|
562
|
+
}
|
|
563
|
+
catch (e) {
|
|
564
|
+
LogError(`AskSkipResolver::PackFieldPossibleValues: ${e}`);
|
|
565
|
+
return [];
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
protected async GetFieldDistinctValues(f: EntityFieldInfo, dataSource: DataSource): Promise<SkipEntityFieldValueInfo[]> {
|
|
570
|
+
try {
|
|
571
|
+
const sql = `SELECT DISTINCT ${f.Name} FROM ${f.SchemaName}.${f.BaseView}`;
|
|
572
|
+
const result = await dataSource.query(sql);
|
|
573
|
+
if (!result) {
|
|
574
|
+
return [];
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
return result.map((r) => {
|
|
578
|
+
return {
|
|
579
|
+
value: r[f.Name],
|
|
580
|
+
displayValue: r[f.Name]
|
|
581
|
+
};
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
catch (e) {
|
|
586
|
+
LogError(`AskSkipResolver::GetFieldDistinctValues: ${e}`);
|
|
587
|
+
return [];
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
// SKIP ENTITIES CACHING
|
|
593
|
+
// Static variables shared across all instances
|
|
594
|
+
private static __skipEntitiesCache$: BehaviorSubject<Promise<SkipEntityInfo[]> | null> = new BehaviorSubject<Promise<SkipEntityInfo[]> | null>(null);
|
|
595
|
+
private static __lastRefreshTime: number = 0;
|
|
596
|
+
|
|
597
|
+
private async refreshSkipEntities(dataSource: DataSource): Promise<SkipEntityInfo[]> {
|
|
598
|
+
try {
|
|
599
|
+
const md = new Metadata();
|
|
600
|
+
const skipSpecialIncludeEntities = (configInfo.askSkip?.entitiesToSendSkip?.includeEntitiesFromExcludedSchemas ?? [])
|
|
601
|
+
.map((e) => e.trim().toLowerCase());
|
|
602
|
+
|
|
603
|
+
// get the list of entities
|
|
604
|
+
const entities = md.Entities.filter((e) => {
|
|
605
|
+
if (e.SchemaName !== mj_core_schema || skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase())) {
|
|
606
|
+
const scopes = e.ScopeDefault?.split(',').map((s) => s.trim().toLowerCase()) ?? ['all'];
|
|
607
|
+
return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai') || skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase());
|
|
608
|
+
}
|
|
609
|
+
return false;
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// now we have our list of entities, pack em up
|
|
613
|
+
const result = await Promise.all(entities.map((e) => this.PackSingleSkipEntityInfo(e, dataSource)));
|
|
614
|
+
|
|
615
|
+
AskSkipResolver.__lastRefreshTime = Date.now(); // Update last refresh time
|
|
616
|
+
return result;
|
|
617
|
+
}
|
|
618
|
+
catch (e) {
|
|
619
|
+
LogError(`AskSkipResolver::refreshSkipEntities: ${e}`);
|
|
620
|
+
return [];
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
public async BuildSkipEntities(dataSource: DataSource, forceRefresh: boolean = false, refreshIntervalMinutes: number = 15): Promise<SkipEntityInfo[]> {
|
|
625
|
+
try {
|
|
626
|
+
const now = Date.now();
|
|
627
|
+
const cacheExpired = (now - AskSkipResolver.__lastRefreshTime) > (refreshIntervalMinutes * 60 * 1000);
|
|
628
|
+
|
|
629
|
+
// If force refresh is requested OR cache expired OR cache is empty, refresh
|
|
630
|
+
if (forceRefresh || cacheExpired || AskSkipResolver.__skipEntitiesCache$.value === null) {
|
|
631
|
+
console.log(`Forcing Skip Entities refresh: ${forceRefresh}, Cache Expired: ${cacheExpired}`);
|
|
632
|
+
const newData = this.refreshSkipEntities(dataSource);
|
|
633
|
+
AskSkipResolver.__skipEntitiesCache$.next(newData);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return AskSkipResolver.__skipEntitiesCache$.pipe(take(1)).toPromise();
|
|
637
|
+
}
|
|
638
|
+
catch (e) {
|
|
639
|
+
LogError(`AskSkipResolver::BuildSkipEntities: ${e}`);
|
|
640
|
+
return [];
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
protected async PackSingleSkipEntityInfo(e: EntityInfo, dataSource: DataSource): Promise<SkipEntityInfo> {
|
|
645
|
+
try {
|
|
453
646
|
const ret: SkipEntityInfo = {
|
|
454
647
|
id: e.ID,
|
|
455
648
|
name: e.Name,
|
|
456
649
|
schemaName: e.SchemaName,
|
|
457
650
|
baseView: e.BaseView,
|
|
458
651
|
description: e.Description,
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
description: f.Description,
|
|
469
|
-
isPrimaryKey: f.IsPrimaryKey,
|
|
470
|
-
allowsNull: f.AllowsNull,
|
|
471
|
-
isUnique: f.IsUnique,
|
|
472
|
-
length: f.Length,
|
|
473
|
-
precision: f.Precision,
|
|
474
|
-
scale: f.Scale,
|
|
475
|
-
sqlFullType: f.SQLFullType,
|
|
476
|
-
defaultValue: f.DefaultValue,
|
|
477
|
-
autoIncrement: f.AutoIncrement,
|
|
478
|
-
valueListType: f.ValueListType,
|
|
479
|
-
extendedType: f.ExtendedType,
|
|
480
|
-
defaultInView: f.DefaultInView,
|
|
481
|
-
defaultColumnWidth: f.DefaultColumnWidth,
|
|
482
|
-
isVirtual: f.IsVirtual,
|
|
483
|
-
isNameField: f.IsNameField,
|
|
484
|
-
relatedEntityID: f.RelatedEntityID,
|
|
485
|
-
relatedEntityFieldName: f.RelatedEntityFieldName,
|
|
486
|
-
relatedEntity: f.RelatedEntity,
|
|
487
|
-
relatedEntitySchemaName: f.RelatedEntitySchemaName,
|
|
488
|
-
relatedEntityBaseView: f.RelatedEntityBaseView,
|
|
489
|
-
};
|
|
490
|
-
}),
|
|
652
|
+
|
|
653
|
+
fields: await Promise.all(e.Fields.filter(f => {
|
|
654
|
+
// we want to check the scopes for the field level and make sure it is either All or AI or has both
|
|
655
|
+
const scopes = f.ScopeDefault?.split(',').map((s) => s.trim().toLowerCase());
|
|
656
|
+
return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai');
|
|
657
|
+
}).map(f => {
|
|
658
|
+
return this.PackSingleSkipEntityField(f, dataSource);
|
|
659
|
+
})),
|
|
660
|
+
|
|
491
661
|
relatedEntities: e.RelatedEntities.map((r) => {
|
|
492
|
-
return
|
|
493
|
-
entityID: r.EntityID,
|
|
494
|
-
relatedEntityID: r.RelatedEntityID,
|
|
495
|
-
type: r.Type,
|
|
496
|
-
entityKeyField: r.EntityKeyField,
|
|
497
|
-
relatedEntityJoinField: r.RelatedEntityJoinField,
|
|
498
|
-
joinView: r.JoinView,
|
|
499
|
-
joinEntityJoinField: r.JoinEntityJoinField,
|
|
500
|
-
joinEntityInverseJoinField: r.JoinEntityInverseJoinField,
|
|
501
|
-
entity: r.Entity,
|
|
502
|
-
entityBaseView: r.EntityBaseView,
|
|
503
|
-
relatedEntity: r.RelatedEntity,
|
|
504
|
-
relatedEntityBaseView: r.RelatedEntityBaseView,
|
|
505
|
-
};
|
|
662
|
+
return this.PackSingleSkipEntityRelationship(r);
|
|
506
663
|
}),
|
|
664
|
+
|
|
665
|
+
rowsPacked: e.RowsToPackWithSchema,
|
|
666
|
+
rowsSampleMethod: e.RowsToPackSampleMethod,
|
|
667
|
+
rows: await this.PackEntityRows(e, dataSource)
|
|
507
668
|
};
|
|
508
669
|
return ret;
|
|
509
|
-
}
|
|
670
|
+
}
|
|
671
|
+
catch (e) {
|
|
672
|
+
LogError(`AskSkipResolver::PackSingleSkipEntityInfo: ${e}`);
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
protected PackSingleSkipEntityRelationship(r: EntityRelationshipInfo): SkipEntityRelationshipInfo {
|
|
678
|
+
try {
|
|
679
|
+
return {
|
|
680
|
+
entityID: r.EntityID,
|
|
681
|
+
relatedEntityID: r.RelatedEntityID,
|
|
682
|
+
type: r.Type,
|
|
683
|
+
entityKeyField: r.EntityKeyField,
|
|
684
|
+
relatedEntityJoinField: r.RelatedEntityJoinField,
|
|
685
|
+
joinView: r.JoinView,
|
|
686
|
+
joinEntityJoinField: r.JoinEntityJoinField,
|
|
687
|
+
joinEntityInverseJoinField: r.JoinEntityInverseJoinField,
|
|
688
|
+
entity: r.Entity,
|
|
689
|
+
entityBaseView: r.EntityBaseView,
|
|
690
|
+
relatedEntity: r.RelatedEntity,
|
|
691
|
+
relatedEntityBaseView: r.RelatedEntityBaseView,
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
catch (e) {
|
|
695
|
+
LogError(`AskSkipResolver::PackSingleSkipEntityRelationship: ${e}`);
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
protected async PackSingleSkipEntityField(f: EntityFieldInfo, dataSource: DataSource): Promise<SkipEntityFieldInfo> {
|
|
701
|
+
try {
|
|
702
|
+
return {
|
|
703
|
+
//id: f.ID,
|
|
704
|
+
entityID: f.EntityID,
|
|
705
|
+
sequence: f.Sequence,
|
|
706
|
+
name: f.Name,
|
|
707
|
+
displayName: f.DisplayName,
|
|
708
|
+
category: f.Category,
|
|
709
|
+
type: f.Type,
|
|
710
|
+
description: f.Description,
|
|
711
|
+
isPrimaryKey: f.IsPrimaryKey,
|
|
712
|
+
allowsNull: f.AllowsNull,
|
|
713
|
+
isUnique: f.IsUnique,
|
|
714
|
+
length: f.Length,
|
|
715
|
+
precision: f.Precision,
|
|
716
|
+
scale: f.Scale,
|
|
717
|
+
sqlFullType: f.SQLFullType,
|
|
718
|
+
defaultValue: f.DefaultValue,
|
|
719
|
+
autoIncrement: f.AutoIncrement,
|
|
720
|
+
valueListType: f.ValueListType,
|
|
721
|
+
extendedType: f.ExtendedType,
|
|
722
|
+
defaultInView: f.DefaultInView,
|
|
723
|
+
defaultColumnWidth: f.DefaultColumnWidth,
|
|
724
|
+
isVirtual: f.IsVirtual,
|
|
725
|
+
isNameField: f.IsNameField,
|
|
726
|
+
relatedEntityID: f.RelatedEntityID,
|
|
727
|
+
relatedEntityFieldName: f.RelatedEntityFieldName,
|
|
728
|
+
relatedEntity: f.RelatedEntity,
|
|
729
|
+
relatedEntitySchemaName: f.RelatedEntitySchemaName,
|
|
730
|
+
relatedEntityBaseView: f.RelatedEntityBaseView,
|
|
731
|
+
possibleValues: await this.PackFieldPossibleValues(f, dataSource),
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
catch (e) {
|
|
735
|
+
LogError(`AskSkipResolver::PackSingleSkipEntityField: ${e}`);
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
510
738
|
}
|
|
511
739
|
|
|
512
740
|
protected async HandleSkipInitialObjectLoading(
|
|
@@ -940,6 +1168,12 @@ export class AskSkipResolver {
|
|
|
940
1168
|
// analysis is complete
|
|
941
1169
|
// all done, wrap things up
|
|
942
1170
|
const md = new Metadata();
|
|
1171
|
+
|
|
1172
|
+
// if we created an access token, it will expire soon anyway but let's remove it for extra safety now
|
|
1173
|
+
if (apiRequest.callingServerAccessToken && tokenExists(apiRequest.callingServerAccessToken)) {
|
|
1174
|
+
deleteAccessToken(apiRequest.callingServerAccessToken);
|
|
1175
|
+
}
|
|
1176
|
+
|
|
943
1177
|
const { AIMessageConversationDetailID } = await this.FinishConversationAndNotifyUser(
|
|
944
1178
|
apiResponse,
|
|
945
1179
|
dataContext,
|