@memberjunction/server 0.9.165 → 0.9.168
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/build.log.json +6 -0
- package/dist/generated/generated.js +182 -58
- package/dist/generated/generated.js.map +1 -1
- package/dist/resolvers/AskSkipResolver.js +86 -36
- package/dist/resolvers/AskSkipResolver.js.map +1 -1
- package/package.json +8 -7
- package/src/generated/generated.ts +139 -56
- package/src/resolvers/AskSkipResolver.ts +106 -51
|
@@ -2,7 +2,8 @@ import { Arg, Ctx, Field, Int, ObjectType, PubSub, PubSubEngine, Query, Resolver
|
|
|
2
2
|
import { LogError, LogStatus, Metadata, RunQuery, RunQueryParams, RunView, UserInfo } from '@memberjunction/core';
|
|
3
3
|
import { AppContext, UserPayload } from '../types';
|
|
4
4
|
import { UserCache } from '@memberjunction/sqlserver-dataprovider';
|
|
5
|
-
import {
|
|
5
|
+
import { DataContext, DataContextItem } from '@memberjunction/data-context'
|
|
6
|
+
import { SkipAPIRequest, SkipAPIResponse, SkipMessage, SkipAPIAnalysisCompleteResponse, SkipAPIDataRequestResponse, SkipAPIClarifyingQuestionResponse } from '@memberjunction/skip-types';
|
|
6
7
|
import axios from 'axios';
|
|
7
8
|
import zlib from 'zlib';
|
|
8
9
|
import { promisify } from 'util';
|
|
@@ -10,7 +11,7 @@ import { promisify } from 'util';
|
|
|
10
11
|
const gzip = promisify(zlib.gzip);
|
|
11
12
|
|
|
12
13
|
import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver';
|
|
13
|
-
import { ConversationDetailEntity, ConversationEntity, DataContextEntity, DataContextItemEntity, UserNotificationEntity, UserViewEntityExtended } from '@memberjunction/core-entities';
|
|
14
|
+
import { ConversationDetailEntity, ConversationEntity, DataContextEntity, DataContextItemEntity, UserNotificationEntity, UserViewEntity, UserViewEntityExtended } from '@memberjunction/core-entities';
|
|
14
15
|
import { DataSource } from 'typeorm';
|
|
15
16
|
import { ___skipAPIOrgId, ___skipAPIurl } from '../config';
|
|
16
17
|
|
|
@@ -64,16 +65,16 @@ export class AskSkipResolver {
|
|
|
64
65
|
@Query(() => AskSkipResultType)
|
|
65
66
|
async ExecuteAskSkipAnalysisQuery(
|
|
66
67
|
@Arg('UserQuestion', () => String) UserQuestion: string,
|
|
67
|
-
@Arg('ViewId', () => Int) ViewId: number,
|
|
68
68
|
@Arg('ConversationId', () => Int) ConversationId: number,
|
|
69
69
|
@Ctx() { dataSource, userPayload }: AppContext,
|
|
70
|
-
@PubSub() pubSub: PubSubEngine
|
|
70
|
+
@PubSub() pubSub: PubSubEngine,
|
|
71
|
+
@Arg('DataContextId', () => Int, { nullable: true }) DataContextId?: number
|
|
71
72
|
) {
|
|
72
73
|
const md = new Metadata();
|
|
73
74
|
const user = UserCache.Instance.Users.find((u) => u.Email === userPayload.email);
|
|
74
75
|
if (!user) throw new Error(`User ${userPayload.email} not found in UserCache`);
|
|
75
76
|
|
|
76
|
-
const {convoEntity, dataContextEntity, convoDetailEntity, dataContext} = await this.
|
|
77
|
+
const {convoEntity, dataContextEntity, convoDetailEntity, dataContext} = await this.HandleSkipInitialObjectLoading(dataSource, ConversationId, UserQuestion, user, userPayload, md, DataContextId);
|
|
77
78
|
|
|
78
79
|
const OrganizationId = ___skipAPIOrgId;
|
|
79
80
|
|
|
@@ -100,16 +101,17 @@ export class AskSkipResolver {
|
|
|
100
101
|
return this.HandleSkipRequest(input, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, md, convoEntity, convoDetailEntity, dataContext, dataContextEntity);
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
104
|
+
|
|
105
|
+
protected async HandleSkipInitialObjectLoading(dataSource: DataSource,
|
|
106
|
+
ConversationId: number,
|
|
107
|
+
UserQuestion: string,
|
|
108
|
+
user: UserInfo,
|
|
109
|
+
userPayload: UserPayload,
|
|
110
|
+
md: Metadata,
|
|
111
|
+
DataContextId: number): Promise<{convoEntity: ConversationEntity,
|
|
112
|
+
dataContextEntity: DataContextEntity,
|
|
113
|
+
convoDetailEntity: ConversationDetailEntity,
|
|
114
|
+
dataContext: DataContext}> {
|
|
113
115
|
const convoEntity = <ConversationEntity>await md.GetEntityObject('Conversations', user);
|
|
114
116
|
let dataContextEntity: DataContextEntity;
|
|
115
117
|
|
|
@@ -121,21 +123,27 @@ export class AskSkipResolver {
|
|
|
121
123
|
convoEntity.Name = AskSkipResolver._defaultNewChatName;
|
|
122
124
|
|
|
123
125
|
dataContextEntity = await md.GetEntityObject<DataContextEntity>('Data Contexts', user);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
126
|
+
if (!DataContextId || DataContextId <= 0) {
|
|
127
|
+
dataContextEntity.NewRecord();
|
|
128
|
+
dataContextEntity.UserID = user.ID;
|
|
129
|
+
dataContextEntity.Name = 'Data Context for Skip Conversation';
|
|
130
|
+
if (!await dataContextEntity.Save())
|
|
131
|
+
throw new Error(`Creating a new data context failed`);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
await dataContextEntity.Load(DataContextId);
|
|
135
|
+
}
|
|
136
|
+
convoEntity.DataContextID = dataContextEntity.ID;
|
|
137
|
+
if (await convoEntity.Save()) {
|
|
138
|
+
ConversationId = convoEntity.ID;
|
|
139
|
+
if (!DataContextId || dataContextEntity.ID <= 0) {
|
|
140
|
+
// only do this if we created a new data context for this conversation
|
|
131
141
|
dataContextEntity.Name += ` ${ConversationId}`;
|
|
132
|
-
await dataContextEntity.Save();
|
|
142
|
+
await dataContextEntity.Save();
|
|
133
143
|
}
|
|
134
|
-
else
|
|
135
|
-
throw new Error(`Creating a new conversation failed`);
|
|
136
144
|
}
|
|
137
|
-
else
|
|
138
|
-
throw new Error(`Creating a new
|
|
145
|
+
else
|
|
146
|
+
throw new Error(`Creating a new conversation failed`);
|
|
139
147
|
}
|
|
140
148
|
else {
|
|
141
149
|
throw new Error(`User ${userPayload.email} not found in UserCache`);
|
|
@@ -143,6 +151,11 @@ export class AskSkipResolver {
|
|
|
143
151
|
} else {
|
|
144
152
|
await convoEntity.Load(ConversationId); // load the existing conversation, will need it later
|
|
145
153
|
dataContextEntity = await md.GetEntityObject<DataContextEntity>('Data Contexts', user);
|
|
154
|
+
|
|
155
|
+
// note - we ignore the parameter DataContextId if it is passed in, we will use the data context from the conversation that is saved. If a user wants to change the data context for a convo, they can do that elsewhere
|
|
156
|
+
if (DataContextId && DataContextId > 0 && DataContextId !== convoEntity.DataContextID)
|
|
157
|
+
console.log(`AskSkipResolver: DataContextId ${DataContextId} was passed in but it was ignored because it was different than the DataContextID in the conversation ${convoEntity.DataContextID}`);
|
|
158
|
+
|
|
146
159
|
await dataContextEntity.Load(convoEntity.DataContextID);
|
|
147
160
|
}
|
|
148
161
|
|
|
@@ -156,7 +169,7 @@ export class AskSkipResolver {
|
|
|
156
169
|
convoDetailEntity.Set('Sequence', 1); // using weakly typed here because we're going to get rid of this field soon
|
|
157
170
|
await convoDetailEntity.Save();
|
|
158
171
|
|
|
159
|
-
const dataContext:
|
|
172
|
+
const dataContext: DataContext = new DataContext();
|
|
160
173
|
const dciEntityInfo = md.Entities.find((e) => e.Name === 'Data Context Items');
|
|
161
174
|
if (!dciEntityInfo)
|
|
162
175
|
throw new Error(`Data Context Items entity not found`);
|
|
@@ -167,25 +180,52 @@ export class AskSkipResolver {
|
|
|
167
180
|
throw new Error(`Error running SQL: ${sql}`);
|
|
168
181
|
else {
|
|
169
182
|
for (const r of result) {
|
|
170
|
-
const item = new
|
|
183
|
+
const item = new DataContextItem();
|
|
171
184
|
item.Type = r.Type;
|
|
172
|
-
item.
|
|
173
|
-
|
|
185
|
+
switch (item.Type) {
|
|
186
|
+
case 'full_entity':
|
|
187
|
+
item.EntityID = r.EntityID;
|
|
188
|
+
break;
|
|
189
|
+
case 'single_record':
|
|
190
|
+
item.RecordID = r.RecordID;
|
|
191
|
+
item.EntityID = r.EntityID;
|
|
192
|
+
break;
|
|
193
|
+
case 'query':
|
|
194
|
+
item.QueryID = r.QueryID; // map the QueryID in our database to the RecordID field in the object model for runtime use
|
|
195
|
+
break;
|
|
196
|
+
case 'sql':
|
|
197
|
+
item.SQL = r.SQL;
|
|
198
|
+
break;
|
|
199
|
+
case 'view':
|
|
200
|
+
item.ViewID = r.ViewID;
|
|
201
|
+
item.EntityID = r.EntityID;
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
if (item.EntityID) {
|
|
205
|
+
item.Entity = md.Entities.find((e) => e.ID === item.EntityID);
|
|
206
|
+
item.EntityName = item.Entity.Name;
|
|
207
|
+
if (item.Type === 'full_entity')
|
|
208
|
+
item.RecordName = item.EntityName;
|
|
209
|
+
}
|
|
210
|
+
if (item.Type === 'query' && item.QueryID) {
|
|
211
|
+
const q = md.Queries.find((q) => q.ID === item.QueryID);
|
|
212
|
+
item.RecordName = q?.Name;
|
|
213
|
+
}
|
|
214
|
+
if (item.Type === 'view' && item.ViewID) {
|
|
215
|
+
const v = await md.GetEntityObject<UserViewEntityExtended>('User Views', user);
|
|
216
|
+
await v.Load(item.ViewID);
|
|
217
|
+
item.RecordName = v.Name;
|
|
218
|
+
item.ViewEntity = v;
|
|
219
|
+
}
|
|
174
220
|
item.Data = r.Data && r.Data.length > 0 ? JSON.parse(r.Data) : item.Data; // parse the stored data if it was saved, otherwise leave it to whatever the object's default is
|
|
175
221
|
item.AdditionalDescription = r.AdditionalDescription;
|
|
222
|
+
item.DataContextItemID = r.ID;
|
|
176
223
|
dataContext.Items.push(item);
|
|
177
224
|
}
|
|
178
225
|
}
|
|
179
226
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
dataContext.Items.push(
|
|
183
|
-
{
|
|
184
|
-
Type: 'view',
|
|
185
|
-
RecordID: ViewId,
|
|
186
|
-
Data: await this.getViewData(ViewId, user),
|
|
187
|
-
} as SkipDataContextItem
|
|
188
|
-
);
|
|
227
|
+
|
|
228
|
+
/// TODO next up we need to modify MJExplorer to handle the data context stuff and then we can finish this method
|
|
189
229
|
|
|
190
230
|
return {dataContext, convoEntity, dataContextEntity, convoDetailEntity};
|
|
191
231
|
}
|
|
@@ -247,7 +287,7 @@ export class AskSkipResolver {
|
|
|
247
287
|
protected async HandleSkipRequest(input: SkipAPIRequest, UserQuestion: string, user: UserInfo, dataSource: DataSource,
|
|
248
288
|
ConversationId: number, userPayload: UserPayload, pubSub: PubSubEngine, md: Metadata,
|
|
249
289
|
convoEntity: ConversationEntity, convoDetailEntity: ConversationDetailEntity,
|
|
250
|
-
dataContext:
|
|
290
|
+
dataContext: DataContext, dataContextEntity: DataContextEntity): Promise<AskSkipResultType> {
|
|
251
291
|
LogStatus(`Sending request to Skip API: ${___skipAPIurl}`)
|
|
252
292
|
|
|
253
293
|
// Convert JSON payload to a Buffer and compress it
|
|
@@ -331,7 +371,7 @@ export class AskSkipResolver {
|
|
|
331
371
|
|
|
332
372
|
protected async HandleAnalysisComplete(apiRequest: SkipAPIRequest, apiResponse: SkipAPIAnalysisCompleteResponse, UserQuestion: string, user: UserInfo, dataSource: DataSource,
|
|
333
373
|
ConversationId: number, userPayload: UserPayload, pubSub: PubSubEngine, convoEntity: ConversationEntity, convoDetailEntity: ConversationDetailEntity,
|
|
334
|
-
dataContext:
|
|
374
|
+
dataContext: DataContext, dataContextEntity: DataContextEntity): Promise<AskSkipResultType> {
|
|
335
375
|
// analysis is complete
|
|
336
376
|
// all done, wrap things up
|
|
337
377
|
const md = new Metadata();
|
|
@@ -383,7 +423,7 @@ export class AskSkipResolver {
|
|
|
383
423
|
|
|
384
424
|
protected async HandleDataRequestPhase(apiRequest: SkipAPIRequest, apiResponse: SkipAPIDataRequestResponse, UserQuestion: string, user: UserInfo, dataSource: DataSource,
|
|
385
425
|
ConversationId: number, userPayload: UserPayload, pubSub: PubSubEngine, convoEntity: ConversationEntity, convoDetailEntity: ConversationDetailEntity,
|
|
386
|
-
dataContext:
|
|
426
|
+
dataContext: DataContext, dataContextEntity: DataContextEntity): Promise<AskSkipResultType> {
|
|
387
427
|
// our job in this method is to go through each of the data requests from the Skip API, get the data, and then go back to the Skip API again and to the next phase
|
|
388
428
|
try {
|
|
389
429
|
const _maxDataGatheringRetries = 5;
|
|
@@ -407,10 +447,10 @@ export class AskSkipResolver {
|
|
|
407
447
|
if (!result)
|
|
408
448
|
throw new Error(`Error running SQL: ${sql}`);
|
|
409
449
|
|
|
410
|
-
const item = new
|
|
450
|
+
const item = new DataContextItem();
|
|
411
451
|
item.Type = 'sql';
|
|
412
452
|
item.Data = result;
|
|
413
|
-
item.
|
|
453
|
+
item.SQL = dr.text;
|
|
414
454
|
item.AdditionalDescription = dr.description;
|
|
415
455
|
dataContext.Items.push(item);
|
|
416
456
|
break;
|
|
@@ -421,10 +461,10 @@ export class AskSkipResolver {
|
|
|
421
461
|
const rq = new RunQuery();
|
|
422
462
|
const result = await rq.RunQuery({QueryID: query.ID}, user)
|
|
423
463
|
if (result && result.Success) {
|
|
424
|
-
const item = new
|
|
464
|
+
const item = new DataContextItem();
|
|
425
465
|
item.Type = 'query';
|
|
426
466
|
item.Data = result.Results;
|
|
427
|
-
item.
|
|
467
|
+
item.QueryID = query.ID;
|
|
428
468
|
item.RecordName = query.Name;
|
|
429
469
|
item.AdditionalDescription = dr.description;
|
|
430
470
|
dataContext.Items.push(item);
|
|
@@ -491,7 +531,7 @@ export class AskSkipResolver {
|
|
|
491
531
|
* @param userPayload
|
|
492
532
|
* @returns
|
|
493
533
|
*/
|
|
494
|
-
protected async FinishConversationAndNotifyUser(apiResponse: SkipAPIAnalysisCompleteResponse, dataContext:
|
|
534
|
+
protected async FinishConversationAndNotifyUser(apiResponse: SkipAPIAnalysisCompleteResponse, dataContext: DataContext, dataContextEntity: DataContextEntity, md: Metadata, user: UserInfo, convoEntity: ConversationEntity, pubSub: PubSubEngine, userPayload: UserPayload): Promise<{AIMessageConversationDetailID: number}> {
|
|
495
535
|
const sTitle = apiResponse.reportTitle;
|
|
496
536
|
const sResult = JSON.stringify(apiResponse);
|
|
497
537
|
|
|
@@ -532,9 +572,24 @@ export class AskSkipResolver {
|
|
|
532
572
|
dciEntity.NewRecord();
|
|
533
573
|
dciEntity.DataContextID = dataContextEntity.ID;
|
|
534
574
|
dciEntity.Type = item.Type;
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
575
|
+
switch (item.Type) {
|
|
576
|
+
case 'full_entity':
|
|
577
|
+
case 'single_record':
|
|
578
|
+
const e = item.Entity || md.Entities.find((e) => e.Name === item.EntityName);
|
|
579
|
+
dciEntity.EntityID = e.ID;
|
|
580
|
+
if (item.Type === 'single_record')
|
|
581
|
+
dciEntity.RecordID = item.RecordID;
|
|
582
|
+
break;
|
|
583
|
+
case 'view':
|
|
584
|
+
dciEntity.ViewID = item.ViewID;
|
|
585
|
+
break;
|
|
586
|
+
case 'query':
|
|
587
|
+
dciEntity.QueryID = item.QueryID;
|
|
588
|
+
break;
|
|
589
|
+
case 'sql':
|
|
590
|
+
dciEntity.SQL = item.SQL;
|
|
591
|
+
break;
|
|
592
|
+
}
|
|
538
593
|
dciEntity.DataJSON = JSON.stringify(item.Data);
|
|
539
594
|
await dciEntity.Save();
|
|
540
595
|
}
|