@memberjunction/server 2.13.4 → 2.15.2
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.map +1 -1
- package/dist/config.js +6 -1
- package/dist/config.js.map +1 -1
- package/dist/generated/generated.d.ts +73 -61
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +419 -359
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +4 -0
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +35 -9
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/resolvers/AskSkipResolver.d.ts +2 -2
- package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
- package/dist/resolvers/AskSkipResolver.js +27 -5
- package/dist/resolvers/AskSkipResolver.js.map +1 -1
- package/package.json +23 -22
- package/src/config.ts +6 -1
- package/src/generated/generated.ts +265 -229
- package/src/generic/ResolverBase.ts +70 -33
- package/src/resolvers/AskSkipResolver.ts +35 -5
|
@@ -1,18 +1,34 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
BaseEntity,
|
|
3
|
+
BaseEntityEvent,
|
|
4
|
+
CompositeKey,
|
|
5
|
+
EntityFieldTSType,
|
|
6
|
+
EntityPermissionType,
|
|
7
|
+
LogError,
|
|
8
|
+
Metadata,
|
|
9
|
+
RunView,
|
|
10
|
+
RunViewParams,
|
|
11
|
+
RunViewResult,
|
|
12
|
+
UserInfo,
|
|
13
|
+
} from '@memberjunction/core';
|
|
2
14
|
import { AuditLogEntity, UserViewEntity } from '@memberjunction/core-entities';
|
|
3
15
|
import { UserCache } from '@memberjunction/sqlserver-dataprovider';
|
|
4
16
|
import { PubSubEngine } from 'type-graphql';
|
|
5
17
|
import { GraphQLError } from 'graphql';
|
|
6
18
|
import { DataSource } from 'typeorm';
|
|
19
|
+
import { httpTransport, CloudEvent, emitterFor } from 'cloudevents';
|
|
7
20
|
|
|
8
21
|
import { RunViewGenericParams, UserPayload } from '../types.js';
|
|
9
22
|
import { RunDynamicViewInput, RunViewByIDInput, RunViewByNameInput } from './RunViewResolver.js';
|
|
10
23
|
import { DeleteOptionsInput } from './DeleteOptionsInput.js';
|
|
11
|
-
import { MJGlobal } from '@memberjunction/global';
|
|
24
|
+
import { MJEvent, MJEventType, MJGlobal } from '@memberjunction/global';
|
|
12
25
|
import { PUSH_STATUS_UPDATES_TOPIC } from './PushStatusResolver.js';
|
|
13
26
|
import { FieldMapper } from '@memberjunction/graphql-dataprovider';
|
|
14
27
|
|
|
15
28
|
export class ResolverBase {
|
|
29
|
+
private _emit = process.env.CLOUDEVENTS_HTTP_TRANSPORT ? emitterFor(httpTransport(process.env.CLOUDEVENTS_HTTP_TRANSPORT)) : null;
|
|
30
|
+
private _cloudeventsHeaders = process.env.CLOUDEVENTS_HTTP_HEADERS ? JSON.parse(process.env.CLOUDEVENTS_HTTP_HEADERS) : {};
|
|
31
|
+
|
|
16
32
|
protected MapFieldNamesToCodeNames(entityName: string, dataObject: any) {
|
|
17
33
|
// for the given entity name provided, check to see if there are any fields
|
|
18
34
|
// where the code name is different from the field name, and for just those
|
|
@@ -28,7 +44,7 @@ export class ResolverBase {
|
|
|
28
44
|
entityInfo.Fields.forEach((f) => {
|
|
29
45
|
if (dataObject.hasOwnProperty(f.Name)) {
|
|
30
46
|
// GraphQL doesn't allow us to pass back fields with __ so we are mapping our special field cases that start with __mj_ to _mj__ for transport - they are converted back on the other side automatically
|
|
31
|
-
const mappedFieldName = mapper.MapFieldName(f.CodeName)
|
|
47
|
+
const mappedFieldName = mapper.MapFieldName(f.CodeName);
|
|
32
48
|
if (mappedFieldName !== f.Name) {
|
|
33
49
|
dataObject[mappedFieldName] = dataObject[f.Name];
|
|
34
50
|
delete dataObject[f.Name];
|
|
@@ -134,7 +150,7 @@ export class ResolverBase {
|
|
|
134
150
|
if (!entity) throw new Error(`Entity ${viewInput.EntityName} not found in metadata`);
|
|
135
151
|
|
|
136
152
|
const viewInfo: UserViewEntity = {
|
|
137
|
-
ID:
|
|
153
|
+
ID: '',
|
|
138
154
|
Entity: viewInput.EntityName,
|
|
139
155
|
EntityID: entity.ID,
|
|
140
156
|
EntityBaseView: entity.BaseView as string,
|
|
@@ -164,22 +180,23 @@ export class ResolverBase {
|
|
|
164
180
|
}
|
|
165
181
|
}
|
|
166
182
|
|
|
167
|
-
async RunViewsGeneric(
|
|
183
|
+
async RunViewsGeneric(
|
|
184
|
+
viewInputs: (RunViewByNameInput & RunViewByIDInput & RunDynamicViewInput)[],
|
|
185
|
+
dataSource: DataSource,
|
|
186
|
+
userPayload: UserPayload,
|
|
187
|
+
pubSub: PubSubEngine
|
|
188
|
+
) {
|
|
168
189
|
let md: Metadata | null = null;
|
|
169
190
|
let params: RunViewGenericParams[] = [];
|
|
170
|
-
for(const viewInput of viewInputs) {
|
|
191
|
+
for (const viewInput of viewInputs) {
|
|
171
192
|
try {
|
|
172
193
|
let viewInfo: UserViewEntity | null = null;
|
|
173
194
|
|
|
174
|
-
if(viewInput.ViewName) {
|
|
175
|
-
viewInfo = this.safeFirstArrayElement(
|
|
176
|
-
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
else if(viewInput.ViewID) {
|
|
195
|
+
if (viewInput.ViewName) {
|
|
196
|
+
viewInfo = this.safeFirstArrayElement(await this.findBy(dataSource, 'User Views', { Name: viewInput.ViewName }));
|
|
197
|
+
} else if (viewInput.ViewID) {
|
|
180
198
|
viewInfo = this.safeFirstArrayElement(await this.findBy(dataSource, 'User Views', { ID: viewInput.ViewID }));
|
|
181
|
-
}
|
|
182
|
-
else if(viewInput.EntityName) {
|
|
199
|
+
} else if (viewInput.EntityName) {
|
|
183
200
|
md = md || new Metadata();
|
|
184
201
|
const entity = md.Entities.find((e) => e.Name === viewInput.EntityName);
|
|
185
202
|
if (!entity) {
|
|
@@ -188,14 +205,13 @@ export class ResolverBase {
|
|
|
188
205
|
|
|
189
206
|
// only providing a few bits of data here, but it's enough to get the view to run
|
|
190
207
|
viewInfo = {
|
|
191
|
-
ID:
|
|
208
|
+
ID: '',
|
|
192
209
|
Entity: viewInput.EntityName,
|
|
193
210
|
EntityID: entity.ID,
|
|
194
211
|
EntityBaseView: entity.BaseView,
|
|
195
212
|
} as UserViewEntity;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
throw new Error("Unable to determine input type");
|
|
213
|
+
} else {
|
|
214
|
+
throw new Error('Unable to determine input type');
|
|
199
215
|
}
|
|
200
216
|
|
|
201
217
|
params.push({
|
|
@@ -214,9 +230,8 @@ export class ResolverBase {
|
|
|
214
230
|
auditLogDescription: viewInput.AuditLogDescription,
|
|
215
231
|
resultType: viewInput.ResultType,
|
|
216
232
|
userPayload,
|
|
217
|
-
pubSub
|
|
233
|
+
pubSub,
|
|
218
234
|
});
|
|
219
|
-
|
|
220
235
|
} catch (err) {
|
|
221
236
|
LogError(err);
|
|
222
237
|
return null;
|
|
@@ -227,7 +242,26 @@ export class ResolverBase {
|
|
|
227
242
|
return results;
|
|
228
243
|
}
|
|
229
244
|
|
|
245
|
+
protected async EmitCloudEvent({ component, event, eventCode, args }: MJEvent) {
|
|
246
|
+
if (this._emit && event === MJEventType.ComponentEvent && eventCode === BaseEntity.BaseEventCode) {
|
|
247
|
+
const extendedType = args instanceof BaseEntityEvent ? `.${args.type}` : '';
|
|
248
|
+
const type = `MemberJunction.${event}${extendedType}`;
|
|
249
|
+
const source = `${process.env.CLOUDEVENTS_SOURCE ?? 'MemberJunction'}`;
|
|
250
|
+
const [subject, rawData] = args instanceof BaseEntityEvent ? [args.baseEntity.EntityInfo.CodeName, args.payload] : [undefined, args];
|
|
251
|
+
const data = (typeof rawData === 'object' ? rawData : { payload: rawData }) ?? {};
|
|
252
|
+
const cloudEvent = new CloudEvent({ source, subject, type, data });
|
|
230
253
|
|
|
254
|
+
try {
|
|
255
|
+
const cloudeventTransportResponse = await this._emit(cloudEvent, { headers: this._cloudeventsHeaders });
|
|
256
|
+
const cloudeventResponse = JSON.stringify(cloudeventTransportResponse);
|
|
257
|
+
if (/error/i.test(cloudeventResponse)) {
|
|
258
|
+
console.error('CloudEvent ERROR', cloudeventResponse);
|
|
259
|
+
}
|
|
260
|
+
} catch (e) {
|
|
261
|
+
console.error('CloudEvent ERROR', JSON.stringify(e));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
231
265
|
|
|
232
266
|
protected CheckUserReadPermissions(entityName: string, userPayload: UserPayload | null) {
|
|
233
267
|
const md = new Metadata();
|
|
@@ -326,10 +360,12 @@ export class ResolverBase {
|
|
|
326
360
|
const rv = new RunView();
|
|
327
361
|
let RunViewParams: RunViewParams[] = [];
|
|
328
362
|
let contextUser: UserInfo | null = null;
|
|
329
|
-
for(const param of params){
|
|
363
|
+
for (const param of params) {
|
|
330
364
|
if (param.viewInfo && param.userPayload) {
|
|
331
365
|
md = md || new Metadata();
|
|
332
|
-
const user: UserInfo = UserCache.Users.find(
|
|
366
|
+
const user: UserInfo = UserCache.Users.find(
|
|
367
|
+
(u) => u.Email.toLowerCase().trim() === param.userPayload?.email.toLowerCase().trim()
|
|
368
|
+
);
|
|
333
369
|
if (!user) {
|
|
334
370
|
throw new Error(`User ${param.userPayload?.email} not found in metadata`);
|
|
335
371
|
}
|
|
@@ -337,7 +373,7 @@ export class ResolverBase {
|
|
|
337
373
|
contextUser = contextUser || user;
|
|
338
374
|
|
|
339
375
|
const entityInfo = md.Entities.find((e) => e.Name === param.viewInfo.Entity);
|
|
340
|
-
if (!entityInfo){
|
|
376
|
+
if (!entityInfo) {
|
|
341
377
|
throw new Error(`Entity ${param.viewInfo.Entity} not found in metadata`);
|
|
342
378
|
}
|
|
343
379
|
}
|
|
@@ -381,7 +417,7 @@ export class ResolverBase {
|
|
|
381
417
|
|
|
382
418
|
// go through the result and convert all fields that start with __mj_*** to _mj__*** for GraphQL transport
|
|
383
419
|
const mapper = new FieldMapper();
|
|
384
|
-
for(const runViewResult of runViewResults){
|
|
420
|
+
for (const runViewResult of runViewResults) {
|
|
385
421
|
if (runViewResult && runViewResult.Success) {
|
|
386
422
|
for (const result of runViewResult.Results) {
|
|
387
423
|
mapper.MapFields(result);
|
|
@@ -390,8 +426,7 @@ export class ResolverBase {
|
|
|
390
426
|
}
|
|
391
427
|
|
|
392
428
|
return runViewResults;
|
|
393
|
-
}
|
|
394
|
-
catch (err) {
|
|
429
|
+
} catch (err) {
|
|
395
430
|
console.log(err);
|
|
396
431
|
throw err;
|
|
397
432
|
}
|
|
@@ -453,8 +488,9 @@ export class ResolverBase {
|
|
|
453
488
|
auditLog.UserID = userInfo.ID;
|
|
454
489
|
auditLog.AuditLogTypeID = auditLogType.ID;
|
|
455
490
|
|
|
456
|
-
if (authorization)
|
|
491
|
+
if (authorization) {
|
|
457
492
|
auditLog.AuthorizationID = authorization.ID;
|
|
493
|
+
}
|
|
458
494
|
|
|
459
495
|
if (status?.trim().toLowerCase() === 'success') auditLog.Status = 'Success';
|
|
460
496
|
else auditLog.Status = 'Failed';
|
|
@@ -501,8 +537,10 @@ export class ResolverBase {
|
|
|
501
537
|
|
|
502
538
|
protected ListenForEntityMessages(entityObject: BaseEntity, pubSub: PubSubEngine, userPayload: UserPayload) {
|
|
503
539
|
// listen for events from the entityObject in case it is a long running task and we can push messages back to the client via pubSub
|
|
504
|
-
MJGlobal.Instance.GetEventListener(false).subscribe((event) => {
|
|
540
|
+
MJGlobal.Instance.GetEventListener(false).subscribe(async (event) => {
|
|
505
541
|
if (event) {
|
|
542
|
+
await this.EmitCloudEvent(event);
|
|
543
|
+
|
|
506
544
|
if (event.component === entityObject && event.args && event.args.message) {
|
|
507
545
|
// message from our entity object, relay it to the client
|
|
508
546
|
pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
@@ -554,8 +592,9 @@ export class ResolverBase {
|
|
|
554
592
|
const entityInfo = entityObject.EntityInfo;
|
|
555
593
|
const clientNewValues = {};
|
|
556
594
|
Object.keys(input).forEach((key) => {
|
|
557
|
-
if (key !== 'OldValues___')
|
|
595
|
+
if (key !== 'OldValues___') {
|
|
558
596
|
clientNewValues[key] = input[key];
|
|
597
|
+
}
|
|
559
598
|
}); // grab all the props except for the OldValues property
|
|
560
599
|
|
|
561
600
|
if (entityInfo.TrackRecordChanges || !input.OldValues___) {
|
|
@@ -574,8 +613,7 @@ export class ResolverBase {
|
|
|
574
613
|
if (input.OldValues___) {
|
|
575
614
|
// we DO have OldValues, so we need to do a more in depth analysis
|
|
576
615
|
this.TestAndSetClientOldValuesToDBValues(input, clientNewValues, entityObject);
|
|
577
|
-
}
|
|
578
|
-
else {
|
|
616
|
+
} else {
|
|
579
617
|
// no OldValues, so we can just set the new values from input
|
|
580
618
|
entityObject.SetMany(input);
|
|
581
619
|
}
|
|
@@ -585,8 +623,7 @@ export class ResolverBase {
|
|
|
585
623
|
extensions: { code: 'LOAD_ENTITY_ERROR', entityName },
|
|
586
624
|
});
|
|
587
625
|
}
|
|
588
|
-
}
|
|
589
|
-
else {
|
|
626
|
+
} else {
|
|
590
627
|
// we get here if we are NOT tracking changes and we DO have OldValues, so we can load from them
|
|
591
628
|
const oldValues = {};
|
|
592
629
|
// for each item in the oldValues array, add it to the oldValues object
|
|
@@ -318,6 +318,7 @@ export class AskSkipResolver {
|
|
|
318
318
|
AskSkipResolver._maxHistoricalMessages
|
|
319
319
|
);
|
|
320
320
|
|
|
321
|
+
const conversationDetailCount = 1
|
|
321
322
|
const input = this.buildSkipAPIRequest(messages, ConversationId, dataContext, 'initial_request', true, true);
|
|
322
323
|
|
|
323
324
|
return this.HandleSkipRequest(
|
|
@@ -332,7 +333,8 @@ export class AskSkipResolver {
|
|
|
332
333
|
convoEntity,
|
|
333
334
|
convoDetailEntity,
|
|
334
335
|
dataContext,
|
|
335
|
-
dataContextEntity
|
|
336
|
+
dataContextEntity,
|
|
337
|
+
conversationDetailCount,
|
|
336
338
|
);
|
|
337
339
|
}
|
|
338
340
|
|
|
@@ -677,10 +679,34 @@ export class AskSkipResolver {
|
|
|
677
679
|
convoEntity: ConversationEntity,
|
|
678
680
|
convoDetailEntity: ConversationDetailEntity,
|
|
679
681
|
dataContext: DataContext,
|
|
680
|
-
dataContextEntity: DataContextEntity
|
|
682
|
+
dataContextEntity: DataContextEntity,
|
|
683
|
+
conversationDetailCount: number
|
|
681
684
|
): Promise<AskSkipResultType> {
|
|
682
685
|
LogStatus(` >>> HandleSkipRequest: Sending request to Skip API: ${___skipAPIurl}`);
|
|
683
686
|
|
|
687
|
+
if (conversationDetailCount > 10) {
|
|
688
|
+
// At this point it is likely that we are stuck in a loop, so we stop here
|
|
689
|
+
pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
690
|
+
message: JSON.stringify({
|
|
691
|
+
type: 'AskSkip',
|
|
692
|
+
status: 'Error',
|
|
693
|
+
conversationID: ConversationId,
|
|
694
|
+
message: 'Analysis failed to run, please try again later and if this continues, contact your support desk.',
|
|
695
|
+
}),
|
|
696
|
+
sessionId: userPayload.sessionId,
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
return {
|
|
700
|
+
Success: false,
|
|
701
|
+
Status: 'Error',
|
|
702
|
+
Result: `Exceeded maximum attempts to answer the question ${UserQuestion}`,
|
|
703
|
+
ResponsePhase: SkipResponsePhase.AnalysisComplete,
|
|
704
|
+
ConversationId: ConversationId,
|
|
705
|
+
UserMessageConversationDetailId: '',
|
|
706
|
+
AIMessageConversationDetailId: '',
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
684
710
|
const response = await sendPostRequest(
|
|
685
711
|
___skipAPIurl,
|
|
686
712
|
input,
|
|
@@ -736,7 +762,8 @@ export class AskSkipResolver {
|
|
|
736
762
|
convoEntity,
|
|
737
763
|
convoDetailEntity,
|
|
738
764
|
dataContext,
|
|
739
|
-
dataContextEntity
|
|
765
|
+
dataContextEntity,
|
|
766
|
+
conversationDetailCount
|
|
740
767
|
);
|
|
741
768
|
} else if (apiResponse.responsePhase === 'clarifying_question') {
|
|
742
769
|
// need to send the request back to the user for a clarifying question
|
|
@@ -925,7 +952,8 @@ export class AskSkipResolver {
|
|
|
925
952
|
convoEntity: ConversationEntity,
|
|
926
953
|
convoDetailEntity: ConversationDetailEntity,
|
|
927
954
|
dataContext: DataContext,
|
|
928
|
-
dataContextEntity: DataContextEntity
|
|
955
|
+
dataContextEntity: DataContextEntity,
|
|
956
|
+
conversationDetailCount: number
|
|
929
957
|
): Promise<AskSkipResultType> {
|
|
930
958
|
// 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
|
|
931
959
|
try {
|
|
@@ -1039,6 +1067,7 @@ export class AskSkipResolver {
|
|
|
1039
1067
|
apiRequest.dataContext = <DataContext>CopyScalarsAndArrays(dataContext); // we are casting this to DataContext as we're pushing this to the Skip API, and we don't want to send the real DataContext object, just a copy of the scalar and array properties
|
|
1040
1068
|
apiRequest.requestPhase = 'data_gathering_response';
|
|
1041
1069
|
}
|
|
1070
|
+
conversationDetailCount++;
|
|
1042
1071
|
// we have all of the data now, add it to the data context and then submit it back to the Skip API
|
|
1043
1072
|
return this.HandleSkipRequest(
|
|
1044
1073
|
apiRequest,
|
|
@@ -1052,7 +1081,8 @@ export class AskSkipResolver {
|
|
|
1052
1081
|
convoEntity,
|
|
1053
1082
|
convoDetailEntity,
|
|
1054
1083
|
dataContext,
|
|
1055
|
-
dataContextEntity
|
|
1084
|
+
dataContextEntity,
|
|
1085
|
+
conversationDetailCount
|
|
1056
1086
|
);
|
|
1057
1087
|
} catch (e) {
|
|
1058
1088
|
LogError(e);
|