@memberjunction/server 0.9.93 → 0.9.95

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.
Files changed (47) hide show
  1. package/build.log.json +6 -0
  2. package/dist/generated/generated.js +18854 -0
  3. package/dist/generated/generated.js.map +1 -0
  4. package/dist/generic/PushStatusResolver.js +59 -0
  5. package/dist/generic/PushStatusResolver.js.map +1 -0
  6. package/dist/generic/ResolverBase.js +208 -0
  7. package/dist/generic/ResolverBase.js.map +1 -0
  8. package/dist/generic/RunViewResolver.js +401 -0
  9. package/dist/generic/RunViewResolver.js.map +1 -0
  10. package/dist/index.js +20 -3
  11. package/dist/index.js.map +1 -1
  12. package/dist/resolvers/AskSkipResolver.js +247 -0
  13. package/dist/resolvers/AskSkipResolver.js.map +1 -0
  14. package/dist/resolvers/ColorResolver.js +94 -0
  15. package/dist/resolvers/ColorResolver.js.map +1 -0
  16. package/dist/resolvers/DatasetResolver.js +168 -0
  17. package/dist/resolvers/DatasetResolver.js.map +1 -0
  18. package/dist/resolvers/EntityRecordNameResolver.js +111 -0
  19. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -0
  20. package/dist/resolvers/EntityResolver.js +60 -0
  21. package/dist/resolvers/EntityResolver.js.map +1 -0
  22. package/dist/resolvers/MergeRecordsResolver.js +256 -0
  23. package/dist/resolvers/MergeRecordsResolver.js.map +1 -0
  24. package/dist/resolvers/ReportResolver.js +74 -0
  25. package/dist/resolvers/ReportResolver.js.map +1 -0
  26. package/dist/resolvers/UserFavoriteResolver.js +185 -0
  27. package/dist/resolvers/UserFavoriteResolver.js.map +1 -0
  28. package/dist/resolvers/UserResolver.js +70 -0
  29. package/dist/resolvers/UserResolver.js.map +1 -0
  30. package/dist/resolvers/UserViewResolver.js +102 -0
  31. package/dist/resolvers/UserViewResolver.js.map +1 -0
  32. package/package.json +6 -6
  33. package/src/generated/generated.ts +13928 -0
  34. package/src/generic/PushStatusResolver.ts +41 -0
  35. package/src/generic/ResolverBase.ts +285 -0
  36. package/src/generic/RunViewResolver.ts +350 -0
  37. package/src/index.ts +31 -3
  38. package/src/resolvers/AskSkipResolver.ts +237 -0
  39. package/src/resolvers/ColorResolver.ts +72 -0
  40. package/src/resolvers/DatasetResolver.ts +114 -0
  41. package/src/resolvers/EntityRecordNameResolver.ts +73 -0
  42. package/src/resolvers/EntityResolver.ts +34 -0
  43. package/src/resolvers/MergeRecordsResolver.ts +178 -0
  44. package/src/resolvers/ReportResolver.ts +56 -0
  45. package/src/resolvers/UserFavoriteResolver.ts +123 -0
  46. package/src/resolvers/UserResolver.ts +29 -0
  47. package/src/resolvers/UserViewResolver.ts +63 -0
@@ -0,0 +1,41 @@
1
+ import { Arg, Field, ID, ObjectType, Resolver, ResolverFilterData, Root, Subscription } from 'type-graphql';
2
+
3
+ export const PUSH_STATUS_UPDATES_TOPIC = 'PUSH_STATUS_UPDATES';
4
+
5
+ @ObjectType()
6
+ export class PushStatusNotification {
7
+ @Field(() => String, { nullable: true })
8
+ message?: string;
9
+
10
+ @Field((_type) => Date)
11
+ date!: Date;
12
+
13
+ @Field((_type) => ID)
14
+ sessionId!: string;
15
+ }
16
+
17
+ export interface PushStatusNotificationPayload {
18
+ message?: string;
19
+ sessionId: string;
20
+ }
21
+
22
+ interface PushStatusNotificationArgs {
23
+ sessionId: string;
24
+ }
25
+
26
+ @Resolver()
27
+ export class PushStatusResolver {
28
+ @Subscription(() => PushStatusNotification, {
29
+ topics: PUSH_STATUS_UPDATES_TOPIC,
30
+ filter: ({ payload, args, context }: ResolverFilterData<PushStatusNotificationPayload, PushStatusNotificationArgs, any>) => {
31
+ console.log('context', context);
32
+ return payload.sessionId === args.sessionId;
33
+ },
34
+ })
35
+ statusUpdates(
36
+ @Root() { message }: PushStatusNotificationPayload,
37
+ @Arg('sessionId', () => String) sessionId: string
38
+ ): PushStatusNotification {
39
+ return { message, date: new Date(), sessionId };
40
+ }
41
+ }
@@ -0,0 +1,285 @@
1
+ import { EntityPermissionType, Metadata, RunView, UserInfo } from '@memberjunction/core';
2
+ import { AuditLogEntity, UserViewEntity } from '@memberjunction/core-entities';
3
+ import { UserCache } from '@memberjunction/sqlserver-dataprovider';
4
+ import { PubSubEngine } from 'type-graphql';
5
+ import { DataSource } from 'typeorm';
6
+
7
+ import { UserPayload } from '../types';
8
+ import { RunDynamicViewInput, RunViewByIDInput, RunViewByNameInput } from './RunViewResolver';
9
+
10
+ export class ResolverBase {
11
+ async findBy(dataSource: DataSource, entity: string, params: any) {
12
+ // build the SQL query based on the params passed in
13
+ const md = new Metadata();
14
+ const e = md.Entities.find((e) => e.Name === entity);
15
+ if (!e) throw new Error(`Entity ${entity} not found in metadata`);
16
+ // now build a SQL string using the entityInfo and using the properties in the params object
17
+ let sql = `SELECT * FROM ${e.SchemaName}.${e.BaseView} WHERE `;
18
+ const keys = Object.keys(params);
19
+ keys.forEach((k, i) => {
20
+ if (i > 0) sql += ' AND ';
21
+ // look up the field in the entityInfo to see if it needs quotes
22
+ const field = e.Fields.find((f) => f.Name === k);
23
+ if (!field) throw new Error(`Field ${k} not found in entity ${entity}`);
24
+ const quotes = field.NeedsQuotes ? "'" : '';
25
+ sql += `${k} = ${quotes}${params[k]}${quotes}`;
26
+ });
27
+
28
+ // ok, now we have a SQL string, run it and return the results
29
+ const result = await dataSource.query(sql);
30
+ return result;
31
+ }
32
+
33
+ async RunViewByNameGeneric(viewInput: RunViewByNameInput, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
34
+ try {
35
+ const viewInfo: UserViewEntity = this.safeFirstArrayElement(await this.findBy(dataSource, 'User Views', { Name: viewInput.ViewName }));;
36
+ return this.RunViewGenericInternal(
37
+ viewInfo,
38
+ dataSource,
39
+ viewInput.ExtraFilter,
40
+ viewInput.OrderBy,
41
+ viewInput.UserSearchString,
42
+ viewInput.ExcludeUserViewRunID,
43
+ viewInput.OverrideExcludeFilter,
44
+ viewInput.SaveViewResults,
45
+ viewInput.Fields,
46
+ viewInput.IgnoreMaxRows,
47
+ viewInput.ExcludeDataFromAllPriorViewRuns,
48
+ viewInput.ForceAuditLog,
49
+ viewInput.AuditLogDescription,
50
+ userPayload,
51
+ pubSub
52
+ );
53
+ } catch (err) {
54
+ console.log(err);
55
+ return null;
56
+ }
57
+ }
58
+
59
+ async RunViewByIDGeneric(viewInput: RunViewByIDInput, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
60
+ try {
61
+ const viewInfo: UserViewEntity = this.safeFirstArrayElement(await this.findBy(dataSource, 'User Views', { ID: viewInput.ViewID }));
62
+ return this.RunViewGenericInternal(
63
+ viewInfo,
64
+ dataSource,
65
+ viewInput.ExtraFilter,
66
+ viewInput.OrderBy,
67
+ viewInput.UserSearchString,
68
+ viewInput.ExcludeUserViewRunID,
69
+ viewInput.OverrideExcludeFilter,
70
+ viewInput.SaveViewResults,
71
+ viewInput.Fields,
72
+ viewInput.IgnoreMaxRows,
73
+ viewInput.ExcludeDataFromAllPriorViewRuns,
74
+ viewInput.ForceAuditLog,
75
+ viewInput.AuditLogDescription,
76
+ userPayload,
77
+ pubSub
78
+ );
79
+ } catch (err) {
80
+ console.log(err);
81
+ return null;
82
+ }
83
+ }
84
+
85
+ async RunDynamicViewGeneric(viewInput: RunDynamicViewInput, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
86
+ try {
87
+ const md = new Metadata();
88
+ const entity = md.Entities.find((e) => e.Name === viewInput.EntityName);
89
+ if (!entity) throw new Error(`Entity ${viewInput.EntityName} not found in metadata`);
90
+
91
+ const viewInfo: UserViewEntity = {
92
+ ID: -1,
93
+ Entity: viewInput.EntityName,
94
+ EntityID: entity.ID as number,
95
+ EntityBaseView: entity.BaseView as string,
96
+ } as UserViewEntity; // only providing a few bits of data here, but it's enough to get the view to run
97
+
98
+ return this.RunViewGenericInternal(
99
+ viewInfo,
100
+ dataSource,
101
+ viewInput.ExtraFilter,
102
+ viewInput.OrderBy,
103
+ viewInput.UserSearchString,
104
+ viewInput.ExcludeUserViewRunID,
105
+ viewInput.OverrideExcludeFilter,
106
+ false,
107
+ viewInput.Fields,
108
+ viewInput.IgnoreMaxRows,
109
+ false,
110
+ viewInput.ForceAuditLog,
111
+ viewInput.AuditLogDescription,
112
+ userPayload,
113
+ pubSub
114
+ );
115
+ } catch (err) {
116
+ console.log(err);
117
+ return null;
118
+ }
119
+ }
120
+
121
+ protected CheckUserReadPermissions(entityName: string, userPayload: UserPayload | null) {
122
+ const md = new Metadata();
123
+ const entityInfo = md.Entities.find((e) => e.Name === entityName);
124
+ if (!userPayload) throw new Error(`userPayload is null`);
125
+
126
+ // first check permissions, the logged in user must have read permissions on the entity to run the view
127
+ if (entityInfo) {
128
+ 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
129
+ if (!userInfo) throw new Error(`User ${userPayload.email} not found in metadata`);
130
+
131
+ const userPermissions = entityInfo.GetUserPermisions(userInfo);
132
+ if (!userPermissions.CanRead) throw new Error(`User ${userPayload.email} does not have read permissions on ${entityInfo.Name}`);
133
+ } else throw new Error(`Entity not found in metadata`);
134
+ }
135
+
136
+ protected async RunViewGenericInternal(
137
+ viewInfo: UserViewEntity,
138
+ dataSource: DataSource,
139
+ extraFilter: string,
140
+ orderBy: string,
141
+ userSearchString: string,
142
+ excludeUserViewRunID: number | undefined,
143
+ overrideExcludeFilter: string | undefined,
144
+ saveViewResults: boolean | undefined,
145
+ fields: string[] | undefined,
146
+ ignoreMaxRows: boolean | undefined,
147
+ excludeDataFromAllPriorViewRuns: boolean | undefined,
148
+ forceAuditLog: boolean | undefined,
149
+ auditLogDescription: string | undefined,
150
+ userPayload: UserPayload | null,
151
+ pubSub: PubSubEngine
152
+ ) {
153
+ try {
154
+ if (viewInfo && userPayload) {
155
+ const md = new Metadata();
156
+ const user = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
157
+ if (!user) throw new Error(`User ${userPayload?.email} not found in metadata`);
158
+
159
+ const entityInfo = md.Entities.find((e) => e.Name === viewInfo.Entity);
160
+ if (!entityInfo) throw new Error(`Entity ${viewInfo.Entity} not found in metadata`);
161
+
162
+ const rv = new RunView();
163
+ const result = await rv.RunView(
164
+ {
165
+ ViewID: viewInfo.ID,
166
+ ViewName: viewInfo.Name,
167
+ EntityName: viewInfo.Entity,
168
+ ExtraFilter: extraFilter,
169
+ OrderBy: orderBy,
170
+ Fields: fields,
171
+ UserSearchString: userSearchString,
172
+ ExcludeUserViewRunID: excludeUserViewRunID,
173
+ OverrideExcludeFilter: overrideExcludeFilter,
174
+ SaveViewResults: saveViewResults,
175
+ ExcludeDataFromAllPriorViewRuns: excludeDataFromAllPriorViewRuns,
176
+ IgnoreMaxRows: ignoreMaxRows,
177
+ ForceAuditLog: forceAuditLog,
178
+ AuditLogDescription: auditLogDescription,
179
+ },
180
+ user
181
+ );
182
+
183
+ return result;
184
+ } else return null;
185
+ } catch (err) {
186
+ console.log(err);
187
+ throw err;
188
+ }
189
+ }
190
+
191
+ protected async createRecordAccessAuditLogRecord(userPayload: UserPayload, entityName: string, recordId: any): Promise<any> {
192
+ try {
193
+ const md = new Metadata();
194
+ const entityInfo = md.Entities.find((e) => e.Name.trim().toLowerCase() === entityName.trim().toLowerCase());
195
+ if (!entityInfo) throw new Error(`Entity ${entityName} not found in metadata`);
196
+
197
+ if (entityInfo.AuditRecordAccess) {
198
+ const userInfo = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
199
+ const auditLogTypeName = 'Record Accessed';
200
+ const auditLogType = md.AuditLogTypes.find((a) => a.Name.trim().toLowerCase() === auditLogTypeName.trim().toLowerCase());
201
+
202
+ if (!userInfo) throw new Error(`User ${userPayload?.email} not found in metadata`);
203
+ if (!auditLogType) throw new Error(`Audit Log Type ${auditLogTypeName} not found in metadata`);
204
+
205
+ return await this.createAuditLogRecord(userPayload, null, auditLogTypeName, 'Success', null, entityInfo.ID, recordId);
206
+ }
207
+ } catch (e) {
208
+ console.log(e);
209
+ }
210
+ }
211
+
212
+ protected getRowLevelSecurityWhereClause(entityName: string, userPayload: UserPayload, type: EntityPermissionType, returnPrefix: string) {
213
+ const md = new Metadata();
214
+ const entityInfo = md.Entities.find((e) => e.Name.trim().toLowerCase() === entityName.trim().toLowerCase());
215
+ if (!entityInfo) throw new Error(`Entity ${entityName} not found in metadata`);
216
+ const user = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
217
+ if (!user) throw new Error(`User ${userPayload?.email} not found in metadata`);
218
+
219
+ return entityInfo.GetUserRowLevelSecurityWhereClause(user, type, returnPrefix);
220
+ }
221
+
222
+ protected async createAuditLogRecord(
223
+ userPayload: UserPayload,
224
+ authorizationName: string | null,
225
+ auditLogTypeName: string,
226
+ status: string,
227
+ details: string | null,
228
+ entityId: number,
229
+ recordId: any | null
230
+ ): Promise<any> {
231
+ try {
232
+ const md = new Metadata();
233
+ const userInfo = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
234
+ const authorization = authorizationName
235
+ ? md.Authorizations.find((a) => a.Name.trim().toLowerCase() === authorizationName.trim().toLowerCase())
236
+ : null;
237
+ const auditLogType = md.AuditLogTypes.find((a) => a.Name.trim().toLowerCase() === auditLogTypeName.trim().toLowerCase());
238
+
239
+ if (!userInfo) throw new Error(`User ${userPayload?.email} not found in metadata`);
240
+ if (!auditLogType) throw new Error(`Audit Log Type ${auditLogTypeName} not found in metadata`);
241
+
242
+ const auditLog = <AuditLogEntity>await md.GetEntityObject('Audit Logs', userInfo); // must pass user context on back end as we're not authenticated the same way as the front end
243
+ auditLog.NewRecord();
244
+ auditLog.UserID = userInfo.ID;
245
+ auditLog.AuditLogTypeName = auditLogType.Name;
246
+ if (authorization) auditLog.AuthorizationName = authorization.Name;
247
+ auditLog.Status = status;
248
+ if (details) auditLog.Details = details;
249
+ auditLog.EntityID = entityId;
250
+ if (recordId) auditLog.RecordID = recordId;
251
+
252
+ if (await auditLog.Save()) return auditLog;
253
+ else throw new Error(`Error saving audit log record`);
254
+ } catch (err) {
255
+ console.log(err);
256
+ return null;
257
+ }
258
+ }
259
+
260
+ protected safeFirstArrayElement(arr: any[]) {
261
+ if (arr && arr.length > 0) {
262
+ return arr[0];
263
+ }
264
+ return null;
265
+ }
266
+
267
+ protected packageSPParam(paramValue: any, quoteString: string) {
268
+ return paramValue === null || paramValue === undefined ? null : quoteString + paramValue + quoteString;
269
+ }
270
+
271
+ protected GetUserFromEmail(email: string): UserInfo | undefined {
272
+ const md = new Metadata();
273
+ return UserCache.Users.find((u) => u.Email.toLowerCase().trim() === email.toLowerCase().trim());
274
+ }
275
+ protected GetUserFromPayload(userPayload: UserPayload): UserInfo | undefined {
276
+ if (!userPayload || !userPayload.email) return undefined;
277
+
278
+ const md = new Metadata();
279
+ return UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload.email.toLowerCase().trim());
280
+ }
281
+
282
+ public get MJCoreSchema(): string {
283
+ return Metadata.Provider.ConfigData.MJCoreSchemaName;
284
+ }
285
+ }
@@ -0,0 +1,350 @@
1
+ import { Arg, Ctx, Field, InputType, Int, ObjectType, PubSubEngine, Query, Resolver } from 'type-graphql';
2
+ import { AppContext } from '../types';
3
+ import { ResolverBase } from './ResolverBase';
4
+
5
+ /********************************************************************************
6
+ * The PURPOSE of this resolver is to provide a generic way to run a view and return the results.
7
+ * The best practice is to use the strongly typed sub-class of this resolver for each entity.
8
+ * that way you get back strongly typed results. If you need a generic way to call a view and get
9
+ * back the results, and have your own type checking in place, this resolver can be used.
10
+ *
11
+ */
12
+ //****************************************************************************
13
+ // INPUT TYPE for Running Views
14
+ //****************************************************************************
15
+ @InputType()
16
+ export class RunViewByIDInput {
17
+ @Field(() => Int)
18
+ ViewID: number;
19
+
20
+ @Field(() => String, {
21
+ nullable: true,
22
+ description:
23
+ 'Optional, pass in a valid condition to append to the view WHERE clause. For example, UpdatedAt >= Some Date - if not provided, no filter is applied',
24
+ })
25
+ ExtraFilter: string;
26
+
27
+ @Field(() => String, {
28
+ nullable: true,
29
+ description:
30
+ 'Optional, pass in a valid order by clause sort the results on the server. For example, CreatedAt DESC to order by row creation date in reverse order. Any Valid SQL Order By clause is okay - if not provided, no server-side sorting is applied',
31
+ })
32
+ OrderBy: string;
33
+
34
+ @Field(() => [String], {
35
+ nullable: true,
36
+ description:
37
+ 'Optional, array of entity field names, if not provided, ID and all other columns used in the view columns are returned. If provided, only the fields in the array are returned.',
38
+ })
39
+ Fields?: string[];
40
+
41
+ @Field(() => String, { nullable: true })
42
+ UserSearchString: string;
43
+
44
+ @Field(() => Int, { nullable: true, description: 'Pass in a UserViewRun ID value to exclude all records from that run from results' })
45
+ ExcludeUserViewRunID?: number;
46
+
47
+ @Field(() => String, {
48
+ nullable: true,
49
+ description:
50
+ 'Pass in a valid condition to append to the view WHERE clause to override the Exclude List. For example, UpdatedAt >= Some Date',
51
+ })
52
+ OverrideExcludeFilter?: string;
53
+
54
+ @Field(() => Boolean, {
55
+ nullable: true,
56
+ description:
57
+ 'If set to True, the results of this view are saved into a new UserViewRun record and the UserViewRun.ID is passed back in the results.',
58
+ })
59
+ SaveViewResults?: boolean;
60
+
61
+ @Field(() => Boolean, {
62
+ nullable: true,
63
+ description:
64
+ 'if set to true, the resulting data will filter out ANY records that were ever returned by this view, when the SaveViewResults property was set to true. This is useful if you want to run a particular view over time and make sure the results returned each time are new to the view.',
65
+ })
66
+ ExcludeDataFromAllPriorViewRuns?: boolean;
67
+
68
+ @Field(() => Boolean, {
69
+ nullable: true,
70
+ description:
71
+ 'if set to true, if there IS any UserViewMaxRows property set for the entity in question, it will be IGNORED. This is useful in scenarios where you want to programmatically run a view and get ALL the data back, regardless of the MaxRows setting on the entity.',
72
+ })
73
+ IgnoreMaxRows?: boolean;
74
+
75
+ @Field(() => Boolean, {
76
+ nullable: true,
77
+ description:
78
+ 'If set to true, an Audit Log record will be created for the view run, regardless of the property settings in the entity for auditing view runs',
79
+ })
80
+ ForceAuditLog?: boolean;
81
+
82
+ @Field(() => String, {
83
+ nullable: true,
84
+ description:
85
+ "if provided and either ForceAuditLog is set, or the entity's property settings for logging view runs are set to true, this will be used as the Audit Log Description.",
86
+ })
87
+ AuditLogDescription?: string;
88
+ }
89
+
90
+ @InputType()
91
+ export class RunViewByNameInput {
92
+ @Field(() => String)
93
+ ViewName: string;
94
+
95
+ @Field(() => String, {
96
+ nullable: true,
97
+ description:
98
+ 'Optional, pass in a valid condition to append to the view WHERE clause. For example, UpdatedAt >= Some Date - if not provided, no filter is applied',
99
+ })
100
+ ExtraFilter: string;
101
+
102
+ @Field(() => String, {
103
+ nullable: true,
104
+ description:
105
+ 'Optional, pass in a valid order by clause sort the results on the server. For example, CreatedAt DESC to order by row creation date in reverse order. Any Valid SQL Order By clause is okay - if not provided, no server-side sorting is applied',
106
+ })
107
+ OrderBy: string;
108
+
109
+ @Field(() => [String], {
110
+ nullable: true,
111
+ description:
112
+ 'Optional, array of entity field names, if not provided, ID and all other columns used in the view are returned. If provided, only the fields in the array are returned.',
113
+ })
114
+ Fields?: string[];
115
+
116
+ @Field(() => String, { nullable: true })
117
+ UserSearchString: string;
118
+
119
+ @Field(() => Int, { nullable: true, description: 'Pass in a UserViewRun ID value to exclude all records from that run from results' })
120
+ ExcludeUserViewRunID?: number;
121
+
122
+ @Field(() => String, {
123
+ nullable: true,
124
+ description:
125
+ 'Pass in a valid condition to append to the view WHERE clause to override the Exclude List. For example, UpdatedAt >= Some Date',
126
+ })
127
+ OverrideExcludeFilter?: string;
128
+
129
+ @Field(() => Boolean, {
130
+ nullable: true,
131
+ description:
132
+ 'If set to True, the results of this view are saved into a new UserViewRun record and the UserViewRun.ID is passed back in the results.',
133
+ })
134
+ SaveViewResults?: boolean;
135
+
136
+ @Field(() => Boolean, {
137
+ nullable: true,
138
+ description:
139
+ 'if set to true, the resulting data will filter out ANY records that were ever returned by this view, when the SaveViewResults property was set to true. This is useful if you want to run a particular view over time and make sure the results returned each time are new to the view.',
140
+ })
141
+ ExcludeDataFromAllPriorViewRuns?: boolean;
142
+
143
+ @Field(() => Boolean, {
144
+ nullable: true,
145
+ description:
146
+ 'if set to true, if there IS any UserViewMaxRows property set for the entity in question, it will be IGNORED. This is useful in scenarios where you want to programmatically run a view and get ALL the data back, regardless of the MaxRows setting on the entity.',
147
+ })
148
+ IgnoreMaxRows?: boolean;
149
+
150
+ @Field(() => Boolean, {
151
+ nullable: true,
152
+ description:
153
+ 'If set to true, an Audit Log record will be created for the view run, regardless of the property settings in the entity for auditing view runs',
154
+ })
155
+ ForceAuditLog?: boolean;
156
+
157
+ @Field(() => String, {
158
+ nullable: true,
159
+ description:
160
+ "if provided and either ForceAuditLog is set, or the entity's property settings for logging view runs are set to true, this will be used as the Audit Log Description.",
161
+ })
162
+ AuditLogDescription?: string;
163
+ }
164
+ @InputType()
165
+ export class RunDynamicViewInput {
166
+ @Field(() => String)
167
+ EntityName: string;
168
+
169
+ @Field(() => String, {
170
+ nullable: true,
171
+ description:
172
+ 'Optional, pass in a valid condition to use as the view WHERE clause. For example, UpdatedAt >= Some Date - if not provided, no filter is applied',
173
+ })
174
+ ExtraFilter: string;
175
+
176
+ @Field(() => String, {
177
+ nullable: true,
178
+ description:
179
+ 'Optional, pass in a valid order by clause sort the results on the server. For example, CreatedAt DESC to order by row creation date in reverse order. Any Valid SQL Order By clause is okay - if not provided, no server-side sorting is applied',
180
+ })
181
+ OrderBy: string;
182
+
183
+ @Field(() => [String], {
184
+ nullable: true,
185
+ description:
186
+ 'Optional, array of entity field names, if not provided, all columns are returned. If provided, only the fields in the array are returned.',
187
+ })
188
+ Fields?: string[];
189
+
190
+ @Field(() => String, { nullable: true })
191
+ UserSearchString: string;
192
+
193
+ @Field(() => Int, { nullable: true, description: 'Pass in a UserViewRun ID value to exclude all records from that run from results' })
194
+ ExcludeUserViewRunID?: number;
195
+
196
+ @Field(() => String, {
197
+ nullable: true,
198
+ description:
199
+ 'Pass in a valid condition to append to the view WHERE clause to override the Exclude List. For example, UpdatedAt >= Some Date',
200
+ })
201
+ OverrideExcludeFilter?: string;
202
+
203
+ @Field(() => Boolean, {
204
+ nullable: true,
205
+ description:
206
+ 'if set to true, if there IS any UserViewMaxRows property set for the entity in question, it will be IGNORED. This is useful in scenarios where you want to programmatically run a view and get ALL the data back, regardless of the MaxRows setting on the entity.',
207
+ })
208
+ IgnoreMaxRows?: boolean;
209
+
210
+ @Field(() => Boolean, {
211
+ nullable: true,
212
+ description:
213
+ 'If set to true, an Audit Log record will be created for the view run, regardless of the property settings in the entity for auditing view runs',
214
+ })
215
+ ForceAuditLog?: boolean;
216
+
217
+ @Field(() => String, {
218
+ nullable: true,
219
+ description:
220
+ "if provided and either ForceAuditLog is set, or the entity's property settings for logging view runs are set to true, this will be used as the Audit Log Description.",
221
+ })
222
+ AuditLogDescription?: string;
223
+ }
224
+
225
+ @ObjectType()
226
+ export class RunViewResultRow {
227
+ @Field(() => Int)
228
+ ID: number;
229
+
230
+ @Field(() => Int)
231
+ EntityID: number;
232
+
233
+ @Field(() => String)
234
+ Data: string;
235
+ }
236
+
237
+ @ObjectType()
238
+ export class RunViewResult {
239
+ @Field(() => [RunViewResultRow])
240
+ Results: RunViewResultRow[];
241
+
242
+ @Field(() => Int, { nullable: true })
243
+ UserViewRunID?: number;
244
+
245
+ @Field(() => Int, { nullable: true })
246
+ RowCount: number;
247
+
248
+ @Field(() => Int, { nullable: true })
249
+ TotalRowCount: number;
250
+
251
+ @Field(() => Int, { nullable: true })
252
+ ExecutionTime: number;
253
+
254
+ @Field(() => String, { nullable: true })
255
+ ErrorMessage?: string;
256
+
257
+ @Field(() => Boolean, { nullable: false })
258
+ Success: boolean;
259
+ }
260
+
261
+ @Resolver(RunViewResultRow)
262
+ export class RunViewResolver extends ResolverBase {
263
+ @Query(() => RunViewResult)
264
+ async RunViewByName(
265
+ @Arg('input', () => RunViewByNameInput) input: RunViewByNameInput,
266
+ @Ctx() { dataSource, userPayload }: AppContext,
267
+ pubSub: PubSubEngine
268
+ ) {
269
+ try {
270
+ const rawData = await super.RunViewByNameGeneric(input, dataSource, userPayload, pubSub);
271
+ if (rawData === null) return null;
272
+
273
+ const entityId = await dataSource.query(`SELECT EntityID from [${this.MJCoreSchema}].vwUserViews WHERE Name='${input.ViewName}'`);
274
+ const returnData = this.processRawData(rawData.Results, entityId[0].EntityID);
275
+ return {
276
+ Results: returnData,
277
+ UserViewRunID: rawData?.UserViewRunID,
278
+ RowCount: rawData?.RowCount,
279
+ TotalRowCount: rawData?.TotalRowCount,
280
+ ExecutionTime: rawData?.ExecutionTime,
281
+ };
282
+ } catch (err) {
283
+ console.log(err);
284
+ return null;
285
+ }
286
+ }
287
+
288
+ @Query(() => RunViewResult)
289
+ async RunViewByID(
290
+ @Arg('input', () => RunViewByIDInput) input: RunViewByIDInput,
291
+ @Ctx() { dataSource, userPayload }: AppContext,
292
+ pubSub: PubSubEngine
293
+ ) {
294
+ try {
295
+ const rawData = await super.RunViewByIDGeneric(input, dataSource, userPayload, pubSub);
296
+ if (rawData === null) return null;
297
+
298
+ const entityId = await dataSource.query(`SELECT EntityID from [${this.MJCoreSchema}].vwUserViews WHERE ID=${input.ViewID}`);
299
+ const returnData = this.processRawData(rawData.Results, entityId[0].EntityID);
300
+ return {
301
+ Results: returnData,
302
+ UserViewRunID: rawData?.UserViewRunID,
303
+ RowCount: rawData?.RowCount,
304
+ TotalRowCount: rawData?.TotalRowCount,
305
+ ExecutionTime: rawData?.ExecutionTime,
306
+ };
307
+ } catch (err) {
308
+ console.log(err);
309
+ return null;
310
+ }
311
+ }
312
+
313
+ @Query(() => RunViewResult)
314
+ async RunDynamicView(
315
+ @Arg('input', () => RunDynamicViewInput) input: RunDynamicViewInput,
316
+ @Ctx() { dataSource, userPayload }: AppContext,
317
+ pubSub: PubSubEngine
318
+ ) {
319
+ try {
320
+ const rawData = await super.RunDynamicViewGeneric(input, dataSource, userPayload, pubSub);
321
+ if (rawData === null) return null;
322
+
323
+ const entityId = await dataSource.query(`SELECT ID from [${this.MJCoreSchema}].vwEntities WHERE Name='${input.EntityName}'`);
324
+ const returnData = this.processRawData(rawData.Results, entityId[0].EntityID);
325
+ return {
326
+ Results: returnData,
327
+ UserViewRunID: rawData?.UserViewRunID,
328
+ RowCount: rawData?.RowCount,
329
+ TotalRowCount: rawData?.TotalRowCount,
330
+ ExecutionTime: rawData?.ExecutionTime,
331
+ };
332
+ } catch (err) {
333
+ console.log(err);
334
+ return null;
335
+ }
336
+ }
337
+
338
+ protected processRawData(rawData: any[], entityId: number): RunViewResultRow[] {
339
+ const returnResult = [];
340
+ for (let i = 0; i < rawData.length; i++) {
341
+ const row = rawData[i];
342
+ returnResult.push({
343
+ ID: row.ID,
344
+ EntityID: entityId,
345
+ Data: JSON.stringify(row),
346
+ });
347
+ }
348
+ return returnResult;
349
+ }
350
+ }