dce-reactkit 3.2.0-beta.1 → 3.2.0-beta.10

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.
@@ -0,0 +1,6 @@
1
+ {
2
+ "cSpell.words": [
3
+ "Subcontext",
4
+ "Uncategorized"
5
+ ]
6
+ }
package/dist/cjs/index.js CHANGED
@@ -2203,10 +2203,10 @@ const initServer = (opts) => {
2203
2203
  /**
2204
2204
  * Log an event
2205
2205
  * @author Gabe Abrams
2206
- * @param {string} category Category of the event (each app determines how to
2207
- * categorize its events)
2208
- * @param {string} subcategory Subcategory of the event (each app determines
2209
- * how to categorize its events)
2206
+ * @param {string} context Context of the event (each app determines how to
2207
+ * organize its contexts)
2208
+ * @param {string} subcontext Subcontext of the event (each app determines
2209
+ * how to organize its subcontexts)
2210
2210
  * @param {string} tags stringified list of tags that apply to this action
2211
2211
  * (each app determines tag usage)
2212
2212
  * @param {string} metadata stringified object containing optional custom metadata
@@ -2220,8 +2220,8 @@ const initServer = (opts) => {
2220
2220
  */
2221
2221
  opts.app.post(LOG_ROUTE_PATH, genRouteHandler({
2222
2222
  paramTypes: {
2223
- category: ParamType$1.String,
2224
- subcategory: ParamType$1.String,
2223
+ context: ParamType$1.String,
2224
+ subcontext: ParamType$1.String,
2225
2225
  tags: ParamType$1.JSON,
2226
2226
  metadata: ParamType$1.JSON,
2227
2227
  errorMessage: ParamType$1.StringOptional,
@@ -2234,8 +2234,8 @@ const initServer = (opts) => {
2234
2234
  const log = logServerEvent((params.errorMessage || params.errorCode || params.errorStack)
2235
2235
  // Error
2236
2236
  ? {
2237
- category: params.category,
2238
- subcategory: params.subcategory,
2237
+ context: params.context,
2238
+ subcontext: params.subcontext,
2239
2239
  tags: params.tags,
2240
2240
  metadata: params.metadata,
2241
2241
  error: {
@@ -2246,8 +2246,8 @@ const initServer = (opts) => {
2246
2246
  }
2247
2247
  // Action
2248
2248
  : {
2249
- category: params.category,
2250
- subcategory: params.subcategory,
2249
+ context: params.context,
2250
+ subcontext: params.subcontext,
2251
2251
  tags: params.tags,
2252
2252
  metadata: params.metadata,
2253
2253
  target: params.target,
@@ -2581,6 +2581,64 @@ var LogSource;
2581
2581
  })(LogSource || (LogSource = {}));
2582
2582
  var LogSource$1 = LogSource;
2583
2583
 
2584
+ /**
2585
+ * Built-in metadata for logs
2586
+ * @author Gabe Abrams
2587
+ */
2588
+ const LogBuiltInMetadata = {
2589
+ // Contexts
2590
+ Context: {
2591
+ Uncategorized: 'n/a',
2592
+ ServerRenderedErrorPage: '_server-rendered-error-page',
2593
+ ServerEndpointError: '_server-endpoint-error',
2594
+ },
2595
+ // Targets
2596
+ Target: {
2597
+ NoSpecificTarget: 'n/a',
2598
+ },
2599
+ };
2600
+
2601
+ /**
2602
+ * Types of actions
2603
+ * @author Gabe Abrams
2604
+ */
2605
+ var LogAction;
2606
+ (function (LogAction) {
2607
+ // Target was opened by the user (it was not on screen, but now it is)
2608
+ LogAction["Open"] = "open";
2609
+ // Target was closed by the user (it was on screen, but now it is not)
2610
+ LogAction["Close"] = "close";
2611
+ // Target was cancelled by the user (it was on closed without saving)
2612
+ LogAction["Cancel"] = "cancel";
2613
+ // Target was expanded by the user (it always remains on screen, but size was changed)
2614
+ LogAction["Expand"] = "expand";
2615
+ // Target was collapsed by the user (it always remains on screen, but size was changed)
2616
+ LogAction["Collapse"] = "collapse";
2617
+ // Target was viewed by the user (only for items that are not opened or closed, those must use Open/Close actions)
2618
+ LogAction["View"] = "view";
2619
+ // Target interrupted the user (popup, dialog, validation message, etc. appeared without user prompting)
2620
+ LogAction["Interrupt"] = "interrupt";
2621
+ // Target was created by the user (it did not exist before)
2622
+ LogAction["Create"] = "create";
2623
+ // Target was edited by the user (it existed and was changed)
2624
+ LogAction["Edit"] = "edit";
2625
+ // Target was deleted by the user (it existed and now it doesn't)
2626
+ LogAction["Delete"] = "delete";
2627
+ // Target was added by the user (it already existed and was added to another place)
2628
+ LogAction["Add"] = "add";
2629
+ // Target was removed by the user (it was removed from something but still exists)
2630
+ LogAction["Remove"] = "remove";
2631
+ // Target was activated by the user (click, check, tap, keypress, etc.)
2632
+ LogAction["Activate"] = "activate";
2633
+ // Target was deactivated by the user (click away, uncheck, tap outside of, tab away, etc.)
2634
+ LogAction["Deactivate"] = "deactivate";
2635
+ // User showed interest in a target (hover, peek, etc.)
2636
+ LogAction["Peek"] = "peek";
2637
+ // Unknown action
2638
+ LogAction["Unknown"] = "unknown";
2639
+ })(LogAction || (LogAction = {}));
2640
+ var LogAction$1 = LogAction;
2641
+
2584
2642
  /**
2585
2643
  * Generate an express API route handler
2586
2644
  * @author Gabe Abrams
@@ -2905,82 +2963,136 @@ const genRouteHandler = (opts) => {
2905
2963
  * @author Gabe Abrams
2906
2964
  */
2907
2965
  const logServerEvent = (opts) => __awaiter(void 0, void 0, void 0, function* () {
2966
+ var _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
2908
2967
  // NOTE: internally, we slip through an opts.overrideAsClientEvent boolean
2909
2968
  // that indicates that this is actually a client event, but we don't
2910
2969
  // include that in the LogFunction type because this is internal and
2911
2970
  // hidden from users
2912
- var _c, _d, _e, _f, _g, _h, _j, _k;
2913
- // Parse user agent
2914
- const { browser, device, } = parseUserAgent(req.headers['user-agent']);
2915
- // Get time info in ET
2916
- const { timestamp, year, month, day, hour, minute, } = getTimeInfoInET();
2917
- // Main log info
2918
- const mainLogInfo = {
2919
- id: `${launchInfo.userId}-${Date.now()}-${Math.floor(Math.random() * 100000)}-${Math.floor(Math.random() * 100000)}`,
2920
- userFirstName: launchInfo.userFirstName,
2921
- userLastName: launchInfo.userLastName,
2922
- userEmail: launchInfo.userEmail,
2923
- userId: launchInfo.userId,
2924
- isLearner: !!launchInfo.isLearner,
2925
- isAdmin: !!launchInfo.isAdmin,
2926
- isTTM: !!launchInfo.isTTM,
2927
- courseId: launchInfo.courseId,
2928
- courseName: launchInfo.courseName,
2929
- browser,
2930
- device,
2931
- year,
2932
- month,
2933
- day,
2934
- hour,
2935
- minute,
2936
- timestamp,
2937
- category: opts.category,
2938
- subcategory: (_c = opts.subcategory) !== null && _c !== void 0 ? _c : 'none',
2939
- tags: (_d = opts.tags) !== null && _d !== void 0 ? _d : [],
2940
- metadata: (_e = opts.metadata) !== null && _e !== void 0 ? _e : {},
2941
- };
2942
- // Type-specific info
2943
- const typeSpecificInfo = (('error' in opts && opts.error)
2944
- ? {
2945
- type: LogType$1.Error,
2946
- errorMessage: (_f = opts.error.message) !== null && _f !== void 0 ? _f : 'Unknown message',
2947
- errorCode: (_g = opts.error.code) !== null && _g !== void 0 ? _g : ReactKitErrorCode$1.NoCode,
2948
- errorStack: (_h = opts.error.stack) !== null && _h !== void 0 ? _h : 'No stack',
2971
+ try {
2972
+ // Parse user agent
2973
+ const { browser, device, } = parseUserAgent(req.headers['user-agent']);
2974
+ // Get time info in ET
2975
+ const { timestamp, year, month, day, hour, minute, } = getTimeInfoInET();
2976
+ // Main log info
2977
+ const mainLogInfo = {
2978
+ id: `${launchInfo.userId}-${Date.now()}-${Math.floor(Math.random() * 100000)}-${Math.floor(Math.random() * 100000)}`,
2979
+ userFirstName: launchInfo.userFirstName,
2980
+ userLastName: launchInfo.userLastName,
2981
+ userEmail: launchInfo.userEmail,
2982
+ userId: launchInfo.userId,
2983
+ isLearner: !!launchInfo.isLearner,
2984
+ isAdmin: !!launchInfo.isAdmin,
2985
+ isTTM: !!launchInfo.isTTM,
2986
+ courseId: launchInfo.courseId,
2987
+ courseName: launchInfo.courseName,
2988
+ browser,
2989
+ device,
2990
+ year,
2991
+ month,
2992
+ day,
2993
+ hour,
2994
+ minute,
2995
+ timestamp,
2996
+ context: (typeof opts.context === 'string'
2997
+ ? opts.context
2998
+ : ((_d = ((_c = opts.context) !== null && _c !== void 0 ? _c : {})._) !== null && _d !== void 0 ? _d : LogBuiltInMetadata.Context.Uncategorized)),
2999
+ subcontext: (typeof opts.context === 'string'
3000
+ ? opts.subcontext
3001
+ : ((_f = ((_e = opts.subcontext) !== null && _e !== void 0 ? _e : {})._) !== null && _f !== void 0 ? _f : LogBuiltInMetadata.Context.Uncategorized)),
3002
+ tags: (_g = opts.tags) !== null && _g !== void 0 ? _g : [],
3003
+ metadata: (_h = opts.metadata) !== null && _h !== void 0 ? _h : {},
3004
+ };
3005
+ // Type-specific info
3006
+ const typeSpecificInfo = (('error' in opts && opts.error)
3007
+ ? {
3008
+ type: LogType$1.Error,
3009
+ errorMessage: (_j = opts.error.message) !== null && _j !== void 0 ? _j : 'Unknown message',
3010
+ errorCode: (_k = opts.error.code) !== null && _k !== void 0 ? _k : ReactKitErrorCode$1.NoCode,
3011
+ errorStack: (_l = opts.error.stack) !== null && _l !== void 0 ? _l : 'No stack',
3012
+ }
3013
+ : {
3014
+ type: LogType$1.Action,
3015
+ target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.NoSpecificTarget),
3016
+ action: ((_o = opts.action) !== null && _o !== void 0 ? _o : LogAction$1.Unknown),
3017
+ });
3018
+ // Source-specific info
3019
+ const sourceSpecificInfo = (opts.overrideAsClientEvent
3020
+ ? {
3021
+ source: LogSource$1.Client,
3022
+ }
3023
+ : {
3024
+ source: LogSource$1.Server,
3025
+ routePath: req.path,
3026
+ routeTemplate: req.route.path,
3027
+ });
3028
+ // Build log event
3029
+ const log = Object.assign(Object.assign(Object.assign({}, mainLogInfo), typeSpecificInfo), sourceSpecificInfo);
3030
+ // Either print to console or save to db
3031
+ const logCollection = internalGetLogCollection();
3032
+ if (logCollection) {
3033
+ // Store to the log collection
3034
+ yield logCollection.insert(log);
2949
3035
  }
2950
- : {
2951
- type: LogType$1.Action,
2952
- target: (_j = opts.target) !== null && _j !== void 0 ? _j : 'Unknown',
2953
- action: (_k = opts.action) !== null && _k !== void 0 ? _k : 'Unknown'
2954
- });
2955
- // Source-specific info
2956
- const sourceSpecificInfo = (opts.overrideAsClientEvent
2957
- ? {
2958
- source: LogSource$1.Client,
3036
+ else {
3037
+ // Print to console
3038
+ if (log.type === LogType$1.Error) {
3039
+ console.error('dce-reactkit error log:', log);
3040
+ }
3041
+ else {
3042
+ console.log('dce-reactkit action log:', log);
3043
+ }
2959
3044
  }
2960
- : {
3045
+ // Return log entry
3046
+ return log;
3047
+ }
3048
+ catch (err) {
3049
+ // Print because we cannot store the error
3050
+ console.error('Could not log the following:', opts);
3051
+ // Create a dummy log to return
3052
+ const dummyMainInfo = {
3053
+ id: '-1',
3054
+ userFirstName: 'Unknown',
3055
+ userLastName: 'Unknown',
3056
+ userEmail: 'unknown@harvard.edu',
3057
+ userId: 1,
3058
+ isLearner: false,
3059
+ isAdmin: false,
3060
+ isTTM: false,
3061
+ courseId: 1,
3062
+ courseName: 'Unknown',
3063
+ browser: {
3064
+ name: 'Unknown',
3065
+ version: 'Unknown',
3066
+ },
3067
+ device: {
3068
+ isMobile: false,
3069
+ os: 'Unknown',
3070
+ },
3071
+ year: 1,
3072
+ month: 1,
3073
+ day: 1,
3074
+ hour: 1,
3075
+ minute: 1,
3076
+ timestamp: Date.now(),
3077
+ tags: [],
3078
+ metadata: {},
3079
+ context: LogBuiltInMetadata.Context.Uncategorized,
3080
+ subcontext: LogBuiltInMetadata.Context.Uncategorized,
3081
+ };
3082
+ const dummyTypeSpecificInfo = {
3083
+ type: LogType$1.Error,
3084
+ errorMessage: 'Unknown',
3085
+ errorCode: 'Unknown',
3086
+ errorStack: 'No Stack',
3087
+ };
3088
+ const dummySourceSpecificInfo = {
2961
3089
  source: LogSource$1.Server,
2962
3090
  routePath: req.path,
2963
3091
  routeTemplate: req.route.path,
2964
- });
2965
- // Build log event
2966
- const log = Object.assign(Object.assign(Object.assign({}, mainLogInfo), typeSpecificInfo), sourceSpecificInfo);
2967
- // Either print to console or save to db
2968
- const logCollection = internalGetLogCollection();
2969
- if (logCollection) {
2970
- // Store to the log collection
2971
- yield logCollection.insert(log);
2972
- }
2973
- else {
2974
- // Print to console
2975
- if (log.type === LogType$1.Error) {
2976
- console.error('dce-reactkit error log:', log);
2977
- }
2978
- else {
2979
- console.log('dce-reactkit action log:', log);
2980
- }
3092
+ };
3093
+ const log = Object.assign(Object.assign(Object.assign({}, dummyMainInfo), dummyTypeSpecificInfo), dummySourceSpecificInfo);
3094
+ return log;
2981
3095
  }
2982
- // Return log entry
2983
- return log;
2984
3096
  });
2985
3097
  /*------------------------------------------------------------------------*/
2986
3098
  /* Call handler */
@@ -3019,9 +3131,24 @@ const genRouteHandler = (opts) => {
3019
3131
  * @param [opts.status=500] http status code
3020
3132
  */
3021
3133
  const renderErrorPage = (opts = {}) => {
3022
- var _a;
3134
+ var _a, _b;
3023
3135
  const html = genErrorPage(opts);
3024
3136
  send(html, (_a = opts.status) !== null && _a !== void 0 ? _a : 500);
3137
+ // Log
3138
+ logServerEvent({
3139
+ context: LogBuiltInMetadata.Context.ServerRenderedErrorPage,
3140
+ error: {
3141
+ message: `${opts.title}: ${opts.description}`,
3142
+ code: opts.code,
3143
+ },
3144
+ metadata: {
3145
+ title: opts.title,
3146
+ description: opts.description,
3147
+ code: opts.code,
3148
+ pageTitle: opts.pageTitle,
3149
+ status: (_b = opts.status) !== null && _b !== void 0 ? _b : 500,
3150
+ },
3151
+ });
3025
3152
  };
3026
3153
  // Call the handler
3027
3154
  try {
@@ -3045,7 +3172,13 @@ const genRouteHandler = (opts) => {
3045
3172
  catch (err) {
3046
3173
  // Send error to client (only if next wasn't called)
3047
3174
  if (!responseSent) {
3048
- return handleError(res, err);
3175
+ handleError(res, err);
3176
+ // Log server-side error
3177
+ logServerEvent({
3178
+ context: LogBuiltInMetadata.Context.ServerEndpointError,
3179
+ error: err,
3180
+ });
3181
+ return;
3049
3182
  }
3050
3183
  // Log error that was not responded with
3051
3184
  console.log('Error occurred but could not be sent to client because a response was already sent:', err);
@@ -3239,6 +3372,61 @@ const parallelLimit = (taskFunctions, limit) => __awaiter(void 0, void 0, void 0
3239
3372
  return results;
3240
3373
  });
3241
3374
 
3375
+ /**
3376
+ * Log a user action on the client (cannot be used on the server)
3377
+ * @author Gabe Abrams
3378
+ */
3379
+ const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* () {
3380
+ var _a, _b, _c, _d, _e, _f, _g;
3381
+ return visitServerEndpoint({
3382
+ path: LOG_ROUTE_PATH,
3383
+ method: 'POST',
3384
+ params: {
3385
+ context: (typeof opts.context === 'string'
3386
+ ? opts.context
3387
+ : ((_b = ((_a = opts.context) !== null && _a !== void 0 ? _a : {})._) !== null && _b !== void 0 ? _b : LogBuiltInMetadata.Context.Uncategorized)),
3388
+ subcontext: (typeof opts.context === 'string'
3389
+ ? opts.subcontext
3390
+ : ((_d = ((_c = opts.subcontext) !== null && _c !== void 0 ? _c : {})._) !== null && _d !== void 0 ? _d : LogBuiltInMetadata.Context.Uncategorized)),
3391
+ tags: JSON.stringify((_e = opts.tags) !== null && _e !== void 0 ? _e : []),
3392
+ metadata: JSON.stringify((_f = opts.metadata) !== null && _f !== void 0 ? _f : {}),
3393
+ errorMessage: (opts.error
3394
+ ? opts.error.message
3395
+ : undefined),
3396
+ errorCode: (opts.error
3397
+ ? opts.error.code
3398
+ : undefined),
3399
+ errorStack: (opts.error
3400
+ ? opts.error.stack
3401
+ : undefined),
3402
+ target: (opts.action
3403
+ ? ((_g = opts.target) !== null && _g !== void 0 ? _g : LogBuiltInMetadata.Target.NoSpecificTarget)
3404
+ : undefined),
3405
+ action: (opts.action
3406
+ ? opts.action
3407
+ : undefined),
3408
+ },
3409
+ });
3410
+ });
3411
+
3412
+ /**
3413
+ * Initialize a log collection given the dce-mango Collection class
3414
+ * @author Gabe Abrams
3415
+ * @param Collection the Collection class from dce-mango
3416
+ * @returns initialized logCollection
3417
+ */
3418
+ const initLogCollection = (Collection) => {
3419
+ return new Collection('Log', {
3420
+ uniqueIndexKey: 'id',
3421
+ indexKeys: [
3422
+ 'courseId',
3423
+ 'context',
3424
+ 'subcontext',
3425
+ 'tags',
3426
+ ],
3427
+ });
3428
+ };
3429
+
3242
3430
  /**
3243
3431
  * Days of the week
3244
3432
  * @author Gabe Abrams
@@ -3255,45 +3443,6 @@ var DayOfWeek;
3255
3443
  })(DayOfWeek || (DayOfWeek = {}));
3256
3444
  var DayOfWeek$1 = DayOfWeek;
3257
3445
 
3258
- /**
3259
- * Types of actions
3260
- * @author Gabe Abrams
3261
- */
3262
- var LogAction;
3263
- (function (LogAction) {
3264
- // Target was opened by the user (it was not on screen, but now it is)
3265
- LogAction["Open"] = "open";
3266
- // Target was closed by the user (it was on screen, but now it is not)
3267
- LogAction["Close"] = "close";
3268
- // Target was expanded by the user (it always remains on screen, but size was changed)
3269
- LogAction["Expand"] = "expand";
3270
- // Target was collapsed by the user (it always remains on screen, but size was changed)
3271
- LogAction["Collapse"] = "collapse";
3272
- // Target was viewed by the user (only for items that are not opened or closed, those must use Open/Close actions)
3273
- LogAction["View"] = "view";
3274
- // Target interrupted the user (popup, dialog, validation message, etc. appeared without user prompting)
3275
- LogAction["Interrupt"] = "interrupt";
3276
- // Target was created by the user (it did not exist before)
3277
- LogAction["Create"] = "create";
3278
- // Target was edited by the user (it existed and was changed)
3279
- LogAction["Edit"] = "edit";
3280
- // Target was deleted by the user (it existed and now it doesn't)
3281
- LogAction["Delete"] = "delete";
3282
- // Target was added by the user (it already existed and was added to another place)
3283
- LogAction["Add"] = "add";
3284
- // Target was removed by the user (it was removed from something but still exists)
3285
- LogAction["Remove"] = "remove";
3286
- // Target was activated by the user (click, check, tap, keypress, etc.)
3287
- LogAction["Activate"] = "activate";
3288
- // Target was deactivated by the user (click away, uncheck, tap outside of, tab away, etc.)
3289
- LogAction["Deactivate"] = "deactivate";
3290
- // User showed interest in a target (hover, peek, etc.)
3291
- LogAction["Peek"] = "peek";
3292
- // Unknown action
3293
- LogAction["Unknown"] = "unknown";
3294
- })(LogAction || (LogAction = {}));
3295
- var LogAction$1 = LogAction;
3296
-
3297
3446
  exports.AppWrapper = AppWrapper;
3298
3447
  exports.ButtonInputGroup = ButtonInputGroup;
3299
3448
  exports.CheckboxButton = CheckboxButton;
@@ -3307,6 +3456,7 @@ exports.HOUR_IN_MS = HOUR_IN_MS;
3307
3456
  exports.ItemPicker = ItemPicker;
3308
3457
  exports.LoadingSpinner = LoadingSpinner;
3309
3458
  exports.LogAction = LogAction$1;
3459
+ exports.LogBuiltInMetadata = LogBuiltInMetadata;
3310
3460
  exports.LogSource = LogSource$1;
3311
3461
  exports.LogType = LogType$1;
3312
3462
  exports.MINUTE_IN_MS = MINUTE_IN_MS;
@@ -3337,7 +3487,9 @@ exports.getPartOfDay = getPartOfDay;
3337
3487
  exports.getTimeInfoInET = getTimeInfoInET;
3338
3488
  exports.handleError = handleError;
3339
3489
  exports.handleSuccess = handleSuccess;
3490
+ exports.initLogCollection = initLogCollection;
3340
3491
  exports.initServer = initServer;
3492
+ exports.logClientEvent = logClientEvent;
3341
3493
  exports.onlyKeepLetters = onlyKeepLetters;
3342
3494
  exports.padDecimalZeros = padDecimalZeros;
3343
3495
  exports.padZerosLeft = padZerosLeft;