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

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.
@@ -23,6 +23,8 @@ import LogSource from '../types/LogSource';
23
23
  import LogTypeSpecificInfo from '../types/Log/LogTypeSpecificInfo';
24
24
  import LogMainInfo from '../types/Log/LogMainInfo';
25
25
  import LogSourceSpecificInfo from '../types/Log/LogSourceSpecificInfo';
26
+ import LogBuiltInMetadata from '../types/LogBuiltInMetadata';
27
+ import LogAction from '../types/LogAction';
26
28
 
27
29
  /**
28
30
  * Generate an express API route handler
@@ -469,101 +471,174 @@ const genRouteHandler = (
469
471
  // that indicates that this is actually a client event, but we don't
470
472
  // include that in the LogFunction type because this is internal and
471
473
  // hidden from users
474
+ try {
475
+ // Parse user agent
476
+ const {
477
+ browser,
478
+ device,
479
+ } = parseUserAgent(req.headers['user-agent']);
480
+
481
+ // Get time info in ET
482
+ const {
483
+ timestamp,
484
+ year,
485
+ month,
486
+ day,
487
+ hour,
488
+ minute,
489
+ } = getTimeInfoInET();
490
+
491
+ // Main log info
492
+ const mainLogInfo: LogMainInfo = {
493
+ id: `${launchInfo.userId}-${Date.now()}-${Math.floor(Math.random() * 100000)}-${Math.floor(Math.random() * 100000)}`,
494
+ userFirstName: launchInfo.userFirstName,
495
+ userLastName: launchInfo.userLastName,
496
+ userEmail: launchInfo.userEmail,
497
+ userId: launchInfo.userId,
498
+ isLearner: !!launchInfo.isLearner,
499
+ isAdmin: !!launchInfo.isAdmin,
500
+ isTTM: !!launchInfo.isTTM,
501
+ courseId: launchInfo.courseId,
502
+ courseName: launchInfo.courseName,
503
+ browser,
504
+ device,
505
+ year,
506
+ month,
507
+ day,
508
+ hour,
509
+ minute,
510
+ timestamp,
511
+ context: (
512
+ typeof opts.context === 'string'
513
+ ? opts.context
514
+ : (
515
+ ((opts.context as any) ?? {})._
516
+ ?? LogBuiltInMetadata.Context.Uncategorized
517
+ )
518
+ ),
519
+ subcontext: (
520
+ opts.subcontext
521
+ ?? LogBuiltInMetadata.Context.Uncategorized
522
+ ),
523
+ tags: opts.tags ?? [],
524
+ metadata: opts.metadata ?? {},
525
+ };
526
+
527
+ // Type-specific info
528
+ const typeSpecificInfo: LogTypeSpecificInfo = (
529
+ ('error' in opts && opts.error)
530
+ ? {
531
+ type: LogType.Error,
532
+ errorMessage: opts.error.message ?? 'Unknown message',
533
+ errorCode: opts.error.code ?? ReactKitErrorCode.NoCode,
534
+ errorStack: opts.error.stack ?? 'No stack',
535
+ }
536
+ : {
537
+ type: LogType.Action,
538
+ target: (
539
+ (opts as any).target
540
+ ?? LogBuiltInMetadata.Target.NoSpecificTarget
541
+ ),
542
+ action: (
543
+ (opts as any).action
544
+ ?? LogAction.Unknown
545
+ ),
546
+ }
547
+ );
472
548
 
473
- // Parse user agent
474
- const {
475
- browser,
476
- device,
477
- } = parseUserAgent(req.headers['user-agent']);
478
-
479
- // Get time info in ET
480
- const {
481
- timestamp,
482
- year,
483
- month,
484
- day,
485
- hour,
486
- minute,
487
- } = getTimeInfoInET();
488
-
489
- // Main log info
490
- const mainLogInfo: LogMainInfo = {
491
- id: `${launchInfo.userId}-${Date.now()}-${Math.floor(Math.random() * 100000)}-${Math.floor(Math.random() * 100000)}`,
492
- userFirstName: launchInfo.userFirstName,
493
- userLastName: launchInfo.userLastName,
494
- userEmail: launchInfo.userEmail,
495
- userId: launchInfo.userId,
496
- isLearner: !!launchInfo.isLearner,
497
- isAdmin: !!launchInfo.isAdmin,
498
- isTTM: !!launchInfo.isTTM,
499
- courseId: launchInfo.courseId,
500
- courseName: launchInfo.courseName,
501
- browser,
502
- device,
503
- year,
504
- month,
505
- day,
506
- hour,
507
- minute,
508
- timestamp,
509
- category: opts.category,
510
- subcategory: opts.subcategory ?? 'none',
511
- tags: opts.tags ?? [],
512
- metadata: opts.metadata ?? {},
513
- };
514
-
515
- // Type-specific info
516
- const typeSpecificInfo: LogTypeSpecificInfo = (
517
- ('error' in opts && opts.error)
518
- ? {
519
- type: LogType.Error,
520
- errorMessage: opts.error.message ?? 'Unknown message',
521
- errorCode: opts.error.code ?? ReactKitErrorCode.NoCode,
522
- errorStack: opts.error.stack ?? 'No stack',
523
- }
524
- : {
525
- type: LogType.Action,
526
- target: (opts as any).target ?? 'Unknown',
527
- action: (opts as any).action ?? 'Unknown'
528
- }
529
- );
530
-
531
- // Source-specific info
532
- const sourceSpecificInfo: LogSourceSpecificInfo = (
533
- (opts as any).overrideAsClientEvent
534
- ? {
535
- source: LogSource.Client,
536
- }
537
- : {
538
- source: LogSource.Server,
539
- routePath: req.path,
540
- routeTemplate: req.route.path,
541
- }
542
- );
549
+ // Source-specific info
550
+ const sourceSpecificInfo: LogSourceSpecificInfo = (
551
+ (opts as any).overrideAsClientEvent
552
+ ? {
553
+ source: LogSource.Client,
554
+ }
555
+ : {
556
+ source: LogSource.Server,
557
+ routePath: req.path,
558
+ routeTemplate: req.route.path,
559
+ }
560
+ );
543
561
 
544
- // Build log event
545
- const log: Log = {
546
- ...mainLogInfo,
547
- ...typeSpecificInfo,
548
- ...sourceSpecificInfo,
549
- };
550
-
551
- // Either print to console or save to db
552
- const logCollection = internalGetLogCollection();
553
- if (logCollection) {
554
- // Store to the log collection
555
- await logCollection.insert(log);
556
- } else {
557
- // Print to console
558
- if (log.type === LogType.Error) {
559
- console.error('dce-reactkit error log:', log);
562
+ // Build log event
563
+ const log: Log = {
564
+ ...mainLogInfo,
565
+ ...typeSpecificInfo,
566
+ ...sourceSpecificInfo,
567
+ };
568
+
569
+ // Either print to console or save to db
570
+ const logCollection = internalGetLogCollection();
571
+ if (logCollection) {
572
+ // Store to the log collection
573
+ await logCollection.insert(log);
560
574
  } else {
561
- console.log('dce-reactkit action log:', log);
575
+ // Print to console
576
+ if (log.type === LogType.Error) {
577
+ console.error('dce-reactkit error log:', log);
578
+ } else {
579
+ console.log('dce-reactkit action log:', log);
580
+ }
562
581
  }
563
- }
564
582
 
565
- // Return log entry
566
- return log;
583
+ // Return log entry
584
+ return log;
585
+ } catch (err) {
586
+ // Print because we cannot store the error
587
+ console.error('Could not log the following:', opts);
588
+
589
+ // Create a dummy log to return
590
+ const dummyMainInfo: LogMainInfo = {
591
+ id: '-1',
592
+ userFirstName: 'Unknown',
593
+ userLastName: 'Unknown',
594
+ userEmail: 'unknown@harvard.edu',
595
+ userId: 1,
596
+ isLearner: false,
597
+ isAdmin: false,
598
+ isTTM: false,
599
+ courseId: 1,
600
+ courseName: 'Unknown',
601
+ browser: {
602
+ name: 'Unknown',
603
+ version: 'Unknown',
604
+ },
605
+ device: {
606
+ isMobile: false,
607
+ os: 'Unknown',
608
+ },
609
+ year: 1,
610
+ month: 1,
611
+ day: 1,
612
+ hour: 1,
613
+ minute: 1,
614
+ timestamp: Date.now(),
615
+ tags: [],
616
+ metadata: {},
617
+ context: LogBuiltInMetadata.Context.Uncategorized,
618
+ subcontext: LogBuiltInMetadata.Context.Uncategorized,
619
+ };
620
+
621
+ const dummyTypeSpecificInfo: LogTypeSpecificInfo = {
622
+ type: LogType.Error,
623
+ errorMessage: 'Unknown',
624
+ errorCode: 'Unknown',
625
+ errorStack: 'No Stack',
626
+ };
627
+
628
+ const dummySourceSpecificInfo: LogSourceSpecificInfo = {
629
+ source: LogSource.Server,
630
+ routePath: req.path,
631
+ routeTemplate: req.route.path,
632
+ };
633
+
634
+ const log: Log = {
635
+ ...dummyMainInfo,
636
+ ...dummyTypeSpecificInfo,
637
+ ...dummySourceSpecificInfo,
638
+ };
639
+
640
+ return log;
641
+ }
567
642
  };
568
643
 
569
644
  /*------------------------------------------------------------------------*/
@@ -617,6 +692,22 @@ const genRouteHandler = (
617
692
  ) => {
618
693
  const html = genErrorPage(opts);
619
694
  send(html, opts.status ?? 500);
695
+
696
+ // Log
697
+ logServerEvent({
698
+ context: LogBuiltInMetadata.Context.ServerRenderedErrorPage,
699
+ error: {
700
+ message: `${opts.title}: ${opts.description}`,
701
+ code: opts.code,
702
+ },
703
+ metadata: {
704
+ title: opts.title,
705
+ description: opts.description,
706
+ code: opts.code,
707
+ pageTitle: opts.pageTitle,
708
+ status: opts.status ?? 500,
709
+ },
710
+ });
620
711
  };
621
712
 
622
713
  // Call the handler
@@ -641,7 +732,15 @@ const genRouteHandler = (
641
732
  } catch (err) {
642
733
  // Send error to client (only if next wasn't called)
643
734
  if (!responseSent) {
644
- return handleError(res, err);
735
+ handleError(res, err);
736
+
737
+ // Log server-side error
738
+ logServerEvent({
739
+ context: LogBuiltInMetadata.Context.ServerEndpointError,
740
+ error: err,
741
+ });
742
+
743
+ return;
645
744
  }
646
745
 
647
746
  // Log error that was not responded with
@@ -1,5 +1,6 @@
1
1
  // Import shared types
2
2
  import LOG_ROUTE_PATH from '../constants/LOG_ROUTE_PATH';
3
+ import LogBuiltInMetadata from '../types/LogBuiltInMetadata';
3
4
  import LogFunction from '../types/LogFunction';
4
5
 
5
6
  // Import shared functions
@@ -14,9 +15,19 @@ const logClientEvent: LogFunction = async (opts) => {
14
15
  path: LOG_ROUTE_PATH,
15
16
  method: 'POST',
16
17
  params: {
17
- category: opts.category,
18
- subcategory: opts.subcategory,
19
- tags: JSON.stringify(opts.tags),
18
+ context: (
19
+ typeof opts.context === 'string'
20
+ ? opts.context
21
+ : (
22
+ ((opts.context as any) ?? {})._
23
+ ?? LogBuiltInMetadata.Context.Uncategorized
24
+ )
25
+ ),
26
+ subcontext: (
27
+ opts.subcontext
28
+ ?? LogBuiltInMetadata.Context.Uncategorized
29
+ ),
30
+ tags: JSON.stringify(opts.tags ?? []),
20
31
  metadata: JSON.stringify(opts.metadata ?? {}),
21
32
  errorMessage: (
22
33
  (opts as any).error
@@ -34,8 +45,11 @@ const logClientEvent: LogFunction = async (opts) => {
34
45
  : undefined
35
46
  ),
36
47
  target: (
37
- (opts as any).target
38
- ? (opts as any).target
48
+ (opts as any).action
49
+ ? (
50
+ (opts as any).target
51
+ ?? LogBuiltInMetadata.Target.NoSpecificTarget
52
+ )
39
53
  : undefined
40
54
  ),
41
55
  action: (
package/src/index.ts CHANGED
@@ -48,6 +48,8 @@ import getPartOfDay from './helpers/getPartOfDay';
48
48
  import stringsToHumanReadableList from './helpers/stringsToHumanReadableList';
49
49
  import onlyKeepLetters from './helpers/onlyKeepLetters';
50
50
  import parallelLimit from './helpers/parallelLimit';
51
+ import logClientEvent from './helpers/logClientEvent';
52
+ import initLogCollection from './server/initLogCollection';
51
53
 
52
54
  // Import types
53
55
  import ModalButtonType from './types/ModalButtonType';
@@ -61,6 +63,7 @@ import Log from './types/Log';
61
63
  import LogType from './types/LogType';
62
64
  import LogSource from './types/LogSource';
63
65
  import LogAction from './types/LogAction';
66
+ import LogBuiltInMetadata from './types/LogBuiltInMetadata';
64
67
 
65
68
  // Component-specific-types
66
69
  import PickableItem from './components/ItemPicker/types/PickableItem';
@@ -115,11 +118,13 @@ export {
115
118
  parallelLimit,
116
119
  // Client helpers
117
120
  visitServerEndpoint,
121
+ logClientEvent,
118
122
  // Server helpers
119
123
  initServer,
120
124
  genRouteHandler,
121
125
  handleError,
122
126
  handleSuccess,
127
+ initLogCollection,
123
128
  // Types
124
129
  ModalButtonType,
125
130
  ModalSize,
@@ -131,6 +136,7 @@ export {
131
136
  LogType,
132
137
  LogSource,
133
138
  LogAction,
139
+ LogBuiltInMetadata,
134
140
  // Component-specific-types
135
141
  PickableItem,
136
142
  // Server types
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Initialize a log collection given the dce-mango Collection class
3
+ * @author Gabe Abrams
4
+ * @param Collection the Collection class from dce-mango
5
+ * @returns initialized logCollection
6
+ */
7
+ const initLogCollection = (Collection: any) => {
8
+ return new Collection(
9
+ 'Log',
10
+ {
11
+ uniqueIndexKey: 'id',
12
+ indexKeys: [
13
+ 'courseId',
14
+ 'context',
15
+ 'subcontext',
16
+ 'tags',
17
+ ],
18
+ },
19
+ );
20
+ };
21
+
22
+ export default initLogCollection;
@@ -79,10 +79,10 @@ const initServer = (
79
79
  /**
80
80
  * Log an event
81
81
  * @author Gabe Abrams
82
- * @param {string} category Category of the event (each app determines how to
83
- * categorize its events)
84
- * @param {string} subcategory Subcategory of the event (each app determines
85
- * how to categorize its events)
82
+ * @param {string} context Context of the event (each app determines how to
83
+ * organize its contexts)
84
+ * @param {string} subcontext Subcontext of the event (each app determines
85
+ * how to organize its subcontexts)
86
86
  * @param {string} tags stringified list of tags that apply to this action
87
87
  * (each app determines tag usage)
88
88
  * @param {string} metadata stringified object containing optional custom metadata
@@ -98,8 +98,8 @@ const initServer = (
98
98
  LOG_ROUTE_PATH,
99
99
  genRouteHandler({
100
100
  paramTypes: {
101
- category: ParamType.String,
102
- subcategory: ParamType.String,
101
+ context: ParamType.String,
102
+ subcontext: ParamType.String,
103
103
  tags: ParamType.JSON,
104
104
  metadata: ParamType.JSON,
105
105
  errorMessage: ParamType.StringOptional,
@@ -113,8 +113,8 @@ const initServer = (
113
113
  (params.errorMessage || params.errorCode || params.errorStack)
114
114
  // Error
115
115
  ? {
116
- category: params.category,
117
- subcategory: params.subcategory,
116
+ context: params.context,
117
+ subcontext: params.subcontext,
118
118
  tags: params.tags,
119
119
  metadata: params.metadata,
120
120
  error: {
@@ -125,8 +125,8 @@ const initServer = (
125
125
  }
126
126
  // Action
127
127
  : {
128
- category: params.category,
129
- subcategory: params.subcategory,
128
+ context: params.context,
129
+ subcontext: params.subcontext,
130
130
  tags: params.tags,
131
131
  metadata: params.metadata,
132
132
  target: params.target,
@@ -49,10 +49,10 @@ type LogMainInfo = {
49
49
  minute: number,
50
50
  // Timestamp of event (ms since epoch)
51
51
  timestamp: number,
52
- // Category of the event (each app determines how to categorize its events)
53
- category: string,
54
- // Subcategory of the event (each app determines how to categorize its events)
55
- subcategory: string,
52
+ // Context of the event (each app determines how to organize contexts)
53
+ context: string,
54
+ // Subcontext of the event (each app determines how to organize subcontexts)
55
+ subcontext: string,
56
56
  // List of tags that apply to this action (each app determines tag usage)
57
57
  tags: string[],
58
58
  // Additional optional custom metadata
@@ -7,6 +7,8 @@ enum LogAction {
7
7
  Open = 'open',
8
8
  // Target was closed by the user (it was on screen, but now it is not)
9
9
  Close = 'close',
10
+ // Target was cancelled by the user (it was on closed without saving)
11
+ Cancel = 'cancel',
10
12
  // Target was expanded by the user (it always remains on screen, but size was changed)
11
13
  Expand = 'expand',
12
14
  // Target was collapsed by the user (it always remains on screen, but size was changed)
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Built-in metadata for logs
3
+ * @author Gabe Abrams
4
+ */
5
+ const LogBuiltInMetadata = {
6
+ // Contexts
7
+ Context: {
8
+ Uncategorized: 'n/a',
9
+ ServerRenderedErrorPage: '_server-rendered-error-page',
10
+ ServerEndpointError: '_server-endpoint-error',
11
+ },
12
+ // Targets
13
+ Target: {
14
+ NoSpecificTarget: 'n/a',
15
+ },
16
+ };
17
+
18
+ export default LogBuiltInMetadata;
@@ -10,10 +10,10 @@ type LogFunction = (
10
10
  opts: (
11
11
  // Shared info
12
12
  {
13
- // Category of the event (each app determines how to categorize its events)
14
- category: string,
15
- // Subcategory of the event (each app determines how to categorize its events)
16
- subcategory?: string,
13
+ // Context of the event (each app determines how to organize contexts)
14
+ context: string | { _: string },
15
+ // Subcontext of the event (each app determines how to organize subcontexts)
16
+ subcontext?: string,
17
17
  // List of tags that apply to this action (each app determines tag usage)
18
18
  tags?: string[],
19
19
  // Additional optional custom metadata
@@ -28,11 +28,13 @@ type LogFunction = (
28
28
  }
29
29
  // Action
30
30
  | {
31
+ // The type of action performed
32
+ action: LogAction,
31
33
  // Target of the action (each app determines the list of targets)
32
34
  // These are usually buttons, panels, elements, etc.
33
- target: string,
34
- // The type of action performed on the target
35
- action: LogAction,
35
+ // If no target is included, this should indicate that this is an action
36
+ // being performed on the whole feature (open/close/etc)
37
+ target?: string,
36
38
  }
37
39
  )
38
40
  ),