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.
@@ -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,178 @@ 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
+ typeof opts.context === 'string'
521
+ ? opts.subcontext
522
+ : (
523
+ ((opts.subcontext as any) ?? {})._
524
+ ?? LogBuiltInMetadata.Context.Uncategorized
525
+ )
526
+ ),
527
+ tags: opts.tags ?? [],
528
+ metadata: opts.metadata ?? {},
529
+ };
530
+
531
+ // Type-specific info
532
+ const typeSpecificInfo: LogTypeSpecificInfo = (
533
+ ('error' in opts && opts.error)
534
+ ? {
535
+ type: LogType.Error,
536
+ errorMessage: opts.error.message ?? 'Unknown message',
537
+ errorCode: opts.error.code ?? ReactKitErrorCode.NoCode,
538
+ errorStack: opts.error.stack ?? 'No stack',
539
+ }
540
+ : {
541
+ type: LogType.Action,
542
+ target: (
543
+ (opts as any).target
544
+ ?? LogBuiltInMetadata.Target.NoSpecificTarget
545
+ ),
546
+ action: (
547
+ (opts as any).action
548
+ ?? LogAction.Unknown
549
+ ),
550
+ }
551
+ );
472
552
 
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
- );
553
+ // Source-specific info
554
+ const sourceSpecificInfo: LogSourceSpecificInfo = (
555
+ (opts as any).overrideAsClientEvent
556
+ ? {
557
+ source: LogSource.Client,
558
+ }
559
+ : {
560
+ source: LogSource.Server,
561
+ routePath: req.path,
562
+ routeTemplate: req.route.path,
563
+ }
564
+ );
543
565
 
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);
566
+ // Build log event
567
+ const log: Log = {
568
+ ...mainLogInfo,
569
+ ...typeSpecificInfo,
570
+ ...sourceSpecificInfo,
571
+ };
572
+
573
+ // Either print to console or save to db
574
+ const logCollection = internalGetLogCollection();
575
+ if (logCollection) {
576
+ // Store to the log collection
577
+ await logCollection.insert(log);
560
578
  } else {
561
- console.log('dce-reactkit action log:', log);
579
+ // Print to console
580
+ if (log.type === LogType.Error) {
581
+ console.error('dce-reactkit error log:', log);
582
+ } else {
583
+ console.log('dce-reactkit action log:', log);
584
+ }
562
585
  }
563
- }
564
586
 
565
- // Return log entry
566
- return log;
587
+ // Return log entry
588
+ return log;
589
+ } catch (err) {
590
+ // Print because we cannot store the error
591
+ console.error('Could not log the following:', opts);
592
+
593
+ // Create a dummy log to return
594
+ const dummyMainInfo: LogMainInfo = {
595
+ id: '-1',
596
+ userFirstName: 'Unknown',
597
+ userLastName: 'Unknown',
598
+ userEmail: 'unknown@harvard.edu',
599
+ userId: 1,
600
+ isLearner: false,
601
+ isAdmin: false,
602
+ isTTM: false,
603
+ courseId: 1,
604
+ courseName: 'Unknown',
605
+ browser: {
606
+ name: 'Unknown',
607
+ version: 'Unknown',
608
+ },
609
+ device: {
610
+ isMobile: false,
611
+ os: 'Unknown',
612
+ },
613
+ year: 1,
614
+ month: 1,
615
+ day: 1,
616
+ hour: 1,
617
+ minute: 1,
618
+ timestamp: Date.now(),
619
+ tags: [],
620
+ metadata: {},
621
+ context: LogBuiltInMetadata.Context.Uncategorized,
622
+ subcontext: LogBuiltInMetadata.Context.Uncategorized,
623
+ };
624
+
625
+ const dummyTypeSpecificInfo: LogTypeSpecificInfo = {
626
+ type: LogType.Error,
627
+ errorMessage: 'Unknown',
628
+ errorCode: 'Unknown',
629
+ errorStack: 'No Stack',
630
+ };
631
+
632
+ const dummySourceSpecificInfo: LogSourceSpecificInfo = {
633
+ source: LogSource.Server,
634
+ routePath: req.path,
635
+ routeTemplate: req.route.path,
636
+ };
637
+
638
+ const log: Log = {
639
+ ...dummyMainInfo,
640
+ ...dummyTypeSpecificInfo,
641
+ ...dummySourceSpecificInfo,
642
+ };
643
+
644
+ return log;
645
+ }
567
646
  };
568
647
 
569
648
  /*------------------------------------------------------------------------*/
@@ -617,6 +696,22 @@ const genRouteHandler = (
617
696
  ) => {
618
697
  const html = genErrorPage(opts);
619
698
  send(html, opts.status ?? 500);
699
+
700
+ // Log
701
+ logServerEvent({
702
+ context: LogBuiltInMetadata.Context.ServerRenderedErrorPage,
703
+ error: {
704
+ message: `${opts.title}: ${opts.description}`,
705
+ code: opts.code,
706
+ },
707
+ metadata: {
708
+ title: opts.title,
709
+ description: opts.description,
710
+ code: opts.code,
711
+ pageTitle: opts.pageTitle,
712
+ status: opts.status ?? 500,
713
+ },
714
+ });
620
715
  };
621
716
 
622
717
  // Call the handler
@@ -641,7 +736,15 @@ const genRouteHandler = (
641
736
  } catch (err) {
642
737
  // Send error to client (only if next wasn't called)
643
738
  if (!responseSent) {
644
- return handleError(res, err);
739
+ handleError(res, err);
740
+
741
+ // Log server-side error
742
+ logServerEvent({
743
+ context: LogBuiltInMetadata.Context.ServerEndpointError,
744
+ error: err,
745
+ });
746
+
747
+ return;
645
748
  }
646
749
 
647
750
  // 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,23 @@ 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
+ typeof opts.context === 'string'
28
+ ? opts.subcontext
29
+ : (
30
+ ((opts.subcontext as any) ?? {})._
31
+ ?? LogBuiltInMetadata.Context.Uncategorized
32
+ )
33
+ ),
34
+ tags: JSON.stringify(opts.tags ?? []),
20
35
  metadata: JSON.stringify(opts.metadata ?? {}),
21
36
  errorMessage: (
22
37
  (opts as any).error
@@ -34,8 +49,11 @@ const logClientEvent: LogFunction = async (opts) => {
34
49
  : undefined
35
50
  ),
36
51
  target: (
37
- (opts as any).target
38
- ? (opts as any).target
52
+ (opts as any).action
53
+ ? (
54
+ (opts as any).target
55
+ ?? LogBuiltInMetadata.Target.NoSpecificTarget
56
+ )
39
57
  : undefined
40
58
  ),
41
59
  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 | { _: 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
  ),