dce-reactkit 3.1.20 → 3.2.0-beta.1

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 (52) hide show
  1. package/dist/cjs/index.js +363 -19
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/constants/LOG_ROUTE_PATH.d.ts +6 -0
  4. package/dist/cjs/types/constants/ROUTE_PATH_PREFIX.d.ts +6 -0
  5. package/dist/cjs/types/helpers/genRouteHandler.d.ts +2 -0
  6. package/dist/cjs/types/helpers/logClientEvent.d.ts +7 -0
  7. package/dist/cjs/types/helpers/parseUserAgent.d.ts +17 -0
  8. package/dist/cjs/types/index.d.ts +5 -1
  9. package/dist/cjs/types/server/initServer.d.ts +13 -0
  10. package/dist/cjs/types/types/Log/LogMainInfo.d.ts +37 -0
  11. package/dist/cjs/types/types/Log/LogSourceSpecificInfo.d.ts +13 -0
  12. package/dist/cjs/types/types/Log/LogTypeSpecificInfo.d.ts +17 -0
  13. package/dist/cjs/types/types/Log/index.d.ts +10 -0
  14. package/dist/cjs/types/types/LogAction.d.ts +22 -0
  15. package/dist/cjs/types/types/LogFunction.d.ts +20 -0
  16. package/dist/cjs/types/types/LogSource.d.ts +9 -0
  17. package/dist/cjs/types/types/LogType.d.ts +9 -0
  18. package/dist/esm/index.js +361 -20
  19. package/dist/esm/index.js.map +1 -1
  20. package/dist/esm/types/constants/LOG_ROUTE_PATH.d.ts +6 -0
  21. package/dist/esm/types/constants/ROUTE_PATH_PREFIX.d.ts +6 -0
  22. package/dist/esm/types/helpers/genRouteHandler.d.ts +2 -0
  23. package/dist/esm/types/helpers/logClientEvent.d.ts +7 -0
  24. package/dist/esm/types/helpers/parseUserAgent.d.ts +17 -0
  25. package/dist/esm/types/index.d.ts +5 -1
  26. package/dist/esm/types/server/initServer.d.ts +13 -0
  27. package/dist/esm/types/types/Log/LogMainInfo.d.ts +37 -0
  28. package/dist/esm/types/types/Log/LogSourceSpecificInfo.d.ts +13 -0
  29. package/dist/esm/types/types/Log/LogTypeSpecificInfo.d.ts +17 -0
  30. package/dist/esm/types/types/Log/index.d.ts +10 -0
  31. package/dist/esm/types/types/LogAction.d.ts +22 -0
  32. package/dist/esm/types/types/LogFunction.d.ts +20 -0
  33. package/dist/esm/types/types/LogSource.d.ts +9 -0
  34. package/dist/esm/types/types/LogType.d.ts +9 -0
  35. package/dist/index.d.ts +137 -1
  36. package/package.json +1 -1
  37. package/src/components/AppWrapper.tsx +1 -1
  38. package/src/constants/LOG_ROUTE_PATH.ts +9 -0
  39. package/src/constants/ROUTE_PATH_PREFIX.ts +7 -0
  40. package/src/helpers/genRouteHandler.ts +129 -1
  41. package/src/helpers/logClientEvent.tsx +50 -0
  42. package/src/helpers/parseUserAgent.ts +108 -0
  43. package/src/index.ts +8 -0
  44. package/src/server/initServer.ts +90 -0
  45. package/src/types/Log/LogMainInfo.ts +64 -0
  46. package/src/types/Log/LogSourceSpecificInfo.ts +25 -0
  47. package/src/types/Log/LogTypeSpecificInfo.ts +33 -0
  48. package/src/types/Log/index.ts +17 -0
  49. package/src/types/LogAction.ts +38 -0
  50. package/src/types/LogFunction.ts +41 -0
  51. package/src/types/LogSource.ts +12 -0
  52. package/src/types/LogType.ts +12 -0
package/dist/index.d.ts CHANGED
@@ -515,6 +515,135 @@ declare enum ParamType {
515
515
  StringOptional = "string-optional"
516
516
  }
517
517
 
518
+ /**
519
+ * Main information in a log event
520
+ * @author Gabe Abrams
521
+ */
522
+ declare type LogMainInfo = {
523
+ id: string;
524
+ userFirstName: string;
525
+ userLastName: string;
526
+ userEmail: string;
527
+ userId: number;
528
+ isLearner: boolean;
529
+ isAdmin: boolean;
530
+ isTTM: boolean;
531
+ courseId: number;
532
+ courseName: string;
533
+ browser: {
534
+ name: string;
535
+ version: string;
536
+ };
537
+ device: {
538
+ os: string;
539
+ isMobile: boolean;
540
+ };
541
+ year: number;
542
+ month: number;
543
+ day: number;
544
+ hour: number;
545
+ minute: number;
546
+ timestamp: number;
547
+ category: string;
548
+ subcategory: string;
549
+ tags: string[];
550
+ metadata?: {
551
+ [k: string]: any;
552
+ };
553
+ };
554
+
555
+ /**
556
+ * Source of a log event
557
+ * @author Gabe Abrams
558
+ */
559
+ declare enum LogSource {
560
+ Client = "client",
561
+ Server = "server"
562
+ }
563
+
564
+ /**
565
+ * Log info that is specific to the type of source
566
+ * @author Gabe Abrams
567
+ */
568
+ declare type LogSourceSpecificInfo = ({
569
+ source: LogSource.Client;
570
+ } | {
571
+ source: LogSource.Server;
572
+ routePath: string;
573
+ routeTemplate: string;
574
+ });
575
+
576
+ /**
577
+ * Types of actions
578
+ * @author Gabe Abrams
579
+ */
580
+ declare enum LogAction {
581
+ Open = "open",
582
+ Close = "close",
583
+ Expand = "expand",
584
+ Collapse = "collapse",
585
+ View = "view",
586
+ Interrupt = "interrupt",
587
+ Create = "create",
588
+ Edit = "edit",
589
+ Delete = "delete",
590
+ Add = "add",
591
+ Remove = "remove",
592
+ Activate = "activate",
593
+ Deactivate = "deactivate",
594
+ Peek = "peek",
595
+ Unknown = "unknown"
596
+ }
597
+
598
+ /**
599
+ * Type of a log event
600
+ * @author Gabe Abrams
601
+ */
602
+ declare enum LogType {
603
+ Action = "action",
604
+ Error = "error"
605
+ }
606
+
607
+ /**
608
+ * Log info that is specific to the type of log
609
+ * @author Gabe Abrams
610
+ */
611
+ declare type LogTypeSpecificInfo = ({
612
+ type: LogType.Error;
613
+ errorMessage: string;
614
+ errorCode: string;
615
+ errorStack: string;
616
+ } | {
617
+ type: LogType.Action;
618
+ target: string;
619
+ action: LogAction;
620
+ });
621
+
622
+ /**
623
+ * A single log event corresponding to an action performed by a user or an
624
+ * error encountered by a user
625
+ * @author Gabe Abrams
626
+ */
627
+ declare type Log = (LogMainInfo & LogSourceSpecificInfo & LogTypeSpecificInfo);
628
+
629
+ /**
630
+ * Type of a log action function
631
+ * @author Gabe Abrams
632
+ */
633
+ declare type LogFunction = (opts: ({
634
+ category: string;
635
+ subcategory?: string;
636
+ tags?: string[];
637
+ metadata?: {
638
+ [k: string]: any;
639
+ };
640
+ } & ({
641
+ error: any;
642
+ } | {
643
+ target: string;
644
+ action: LogAction;
645
+ }))) => Promise<Log>;
646
+
518
647
  /**
519
648
  * Generate an express API route handler
520
649
  * @author Gabe Abrams
@@ -556,6 +685,7 @@ declare const genRouteHandler: (opts: {
556
685
  pageTitle?: string | undefined;
557
686
  status?: number | undefined;
558
687
  } | undefined) => void;
688
+ logServerEvent: LogFunction;
559
689
  }) => any;
560
690
  skipSessionCheck?: boolean | undefined;
561
691
  }) => (req: any, res: any, next: () => void) => Promise<undefined>;
@@ -594,10 +724,16 @@ declare type GetLaunchInfoFunction = (req: any) => {
594
724
  * Prepare dce-reactkit to run on the server
595
725
  * @author Gabe Abrams
596
726
  * @param opts object containing all arguments
727
+ * @param opts.app express app from inside of the postprocessor function that
728
+ * we will add routes to
597
729
  * @param opts.getLaunchInfo CACCL LTI's get launch info function
730
+ * @param [opts.logCollection] mongo collection from dce-mango to use for
731
+ * storing logs. If none is included, logs are written to the console
598
732
  */
599
733
  declare const initServer: (opts: {
734
+ app: any;
600
735
  getLaunchInfo: GetLaunchInfoFunction;
736
+ logCollection?: any;
601
737
  }) => void;
602
738
 
603
739
  /**
@@ -727,4 +863,4 @@ declare enum DayOfWeek {
727
863
  Sunday = "u"
728
864
  }
729
865
 
730
- export { AppWrapper, ButtonInputGroup, CheckboxButton, CopiableBox, DAY_IN_MS, DayOfWeek, Drawer, ErrorBox, ErrorWithCode, HOUR_IN_MS, ItemPicker, LoadingSpinner, MINUTE_IN_MS, Modal, ModalButtonType, ModalSize, ModalType, ParamType, PickableItem, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode, SimpleDateChooser, TabBox, Variant, abbreviate, alert, avg, ceilToNumDecimals, confirm, floorToNumDecimals, forceNumIntoBounds, genRouteHandler, getHumanReadableDate, getOrdinal, getPartOfDay, getTimeInfoInET, handleError, handleSuccess, initServer, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, roundToNumDecimals, showFatalError, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, visitServerEndpoint, waitMs };
866
+ export { AppWrapper, ButtonInputGroup, CheckboxButton, CopiableBox, DAY_IN_MS, DayOfWeek, Drawer, ErrorBox, ErrorWithCode, HOUR_IN_MS, ItemPicker, LoadingSpinner, Log, LogAction, LogSource, LogType, MINUTE_IN_MS, Modal, ModalButtonType, ModalSize, ModalType, ParamType, PickableItem, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode, SimpleDateChooser, TabBox, Variant, abbreviate, alert, avg, ceilToNumDecimals, confirm, floorToNumDecimals, forceNumIntoBounds, genRouteHandler, getHumanReadableDate, getOrdinal, getPartOfDay, getTimeInfoInET, handleError, handleSuccess, initServer, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, roundToNumDecimals, showFatalError, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, visitServerEndpoint, waitMs };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dce-reactkit",
3
- "version": "3.1.20",
3
+ "version": "3.2.0-beta.1",
4
4
  "description": "Shared components for Harvard DCE apps",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -303,7 +303,7 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
303
303
  undefined
304
304
  | {
305
305
  title: string,
306
- text: string
306
+ text: string,
307
307
  }
308
308
  >(undefined);
309
309
  setAlertInfo = setAlertInfoInner;
@@ -0,0 +1,9 @@
1
+ import ROUTE_PATH_PREFIX from './ROUTE_PATH_PREFIX';
2
+
3
+ /**
4
+ * Path of the route for storing client-side logs
5
+ * @author Gabe Abrams
6
+ */
7
+ const LOG_ROUTE_PATH = `${ROUTE_PATH_PREFIX}/log`;
8
+
9
+ export default LOG_ROUTE_PATH;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Path that all routes start with
3
+ * @author Gabe Abrams
4
+ */
5
+ const ROUTE_PATH_PREFIX = '/dce-reactkit';
6
+
7
+ export default ROUTE_PATH_PREFIX;
@@ -1,5 +1,8 @@
1
1
  // Import caccl functions
2
- import { cacclGetLaunchInfo } from '../server/initServer';
2
+ import {
3
+ cacclGetLaunchInfo,
4
+ internalGetLogCollection,
5
+ } from '../server/initServer';
3
6
 
4
7
  // Import shared types
5
8
  import ReactKitErrorCode from '../types/ReactKitErrorCode';
@@ -9,6 +12,17 @@ import ParamType from '../types/ParamType';
9
12
  import handleError from './handleError';
10
13
  import handleSuccess from './handleSuccess';
11
14
  import genErrorPage from '../html/genErrorPage';
15
+ import parseUserAgent from './parseUserAgent';
16
+ import getTimeInfoInET from './getTimeInfoInET';
17
+
18
+ // Import shared types
19
+ import LogFunction from '../types/LogFunction';
20
+ import Log from '../types/Log';
21
+ import LogType from '../types/LogType';
22
+ import LogSource from '../types/LogSource';
23
+ import LogTypeSpecificInfo from '../types/Log/LogTypeSpecificInfo';
24
+ import LogMainInfo from '../types/Log/LogMainInfo';
25
+ import LogSourceSpecificInfo from '../types/Log/LogSourceSpecificInfo';
12
26
 
13
27
  /**
14
28
  * Generate an express API route handler
@@ -55,6 +69,7 @@ const genRouteHandler = (
55
69
  status?: number,
56
70
  },
57
71
  ) => void,
72
+ logServerEvent: LogFunction,
58
73
  },
59
74
  ) => any,
60
75
  skipSessionCheck?: boolean,
@@ -439,6 +454,118 @@ const genRouteHandler = (
439
454
  );
440
455
  }
441
456
 
457
+ /*----------------------------------------*/
458
+ /* Log Handler */
459
+ /*----------------------------------------*/
460
+
461
+ // Create a log handler function
462
+
463
+ /**
464
+ * Log an event on the server
465
+ * @author Gabe Abrams
466
+ */
467
+ const logServerEvent: LogFunction = async (opts) => {
468
+ // NOTE: internally, we slip through an opts.overrideAsClientEvent boolean
469
+ // that indicates that this is actually a client event, but we don't
470
+ // include that in the LogFunction type because this is internal and
471
+ // hidden from users
472
+
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
+ );
543
+
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);
560
+ } else {
561
+ console.log('dce-reactkit action log:', log);
562
+ }
563
+ }
564
+
565
+ // Return log entry
566
+ return log;
567
+ };
568
+
442
569
  /*------------------------------------------------------------------------*/
443
570
  /* Call handler */
444
571
  /*------------------------------------------------------------------------*/
@@ -504,6 +631,7 @@ const genRouteHandler = (
504
631
  },
505
632
  redirect,
506
633
  renderErrorPage,
634
+ logServerEvent,
507
635
  });
508
636
 
509
637
  // Send results to client (only if next wasn't called)
@@ -0,0 +1,50 @@
1
+ // Import shared types
2
+ import LOG_ROUTE_PATH from '../constants/LOG_ROUTE_PATH';
3
+ import LogFunction from '../types/LogFunction';
4
+
5
+ // Import shared functions
6
+ import visitServerEndpoint from './visitServerEndpoint';
7
+
8
+ /**
9
+ * Log a user action on the client (cannot be used on the server)
10
+ * @author Gabe Abrams
11
+ */
12
+ const logClientEvent: LogFunction = async (opts) => {
13
+ return visitServerEndpoint({
14
+ path: LOG_ROUTE_PATH,
15
+ method: 'POST',
16
+ params: {
17
+ category: opts.category,
18
+ subcategory: opts.subcategory,
19
+ tags: JSON.stringify(opts.tags),
20
+ metadata: JSON.stringify(opts.metadata ?? {}),
21
+ errorMessage: (
22
+ (opts as any).error
23
+ ? (opts as any).error.message
24
+ : undefined
25
+ ),
26
+ errorCode: (
27
+ (opts as any).error
28
+ ? (opts as any).error.code
29
+ : undefined
30
+ ),
31
+ errorStack: (
32
+ (opts as any).error
33
+ ? (opts as any).error.stack
34
+ : undefined
35
+ ),
36
+ target: (
37
+ (opts as any).target
38
+ ? (opts as any).target
39
+ : undefined
40
+ ),
41
+ action: (
42
+ (opts as any).action
43
+ ? (opts as any).action
44
+ : undefined
45
+ ),
46
+ },
47
+ });
48
+ };
49
+
50
+ export default logClientEvent;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Perform a rudimentary parsing of the user's browser agent string
3
+ * @author Gabe Abrams
4
+ * @param userAgent the user's browser agent
5
+ * @returns user info
6
+ */
7
+ const parseUserAgent = (userAgent: string) => {
8
+ /* ------------- Browser ------------ */
9
+
10
+ let browser: { name: string, version: string } = {
11
+ name: 'Unknown',
12
+ version: 'Unknown',
13
+ };
14
+
15
+ // Parse user agent
16
+ let verOffset: number;
17
+ let nameOffset: number;
18
+ if ((verOffset = userAgent.indexOf('Opera')) !== -1) {
19
+ // In Opera, the true version is after 'Opera' or after 'Version'
20
+ browser = {
21
+ name: 'Opera',
22
+ version: userAgent.substring(verOffset + 6),
23
+ };
24
+ if ((verOffset = userAgent.indexOf('Version')) !== -1) {
25
+ browser.version = userAgent.substring(verOffset + 8);
26
+ }
27
+ } else if ((verOffset = userAgent.indexOf('MSIE')) !== -1) {
28
+ // In MSIE, the true version is after 'MSIE' in userAgent
29
+ browser = {
30
+ name: 'Internet Explorer',
31
+ version: userAgent.substring(verOffset + 5),
32
+ };
33
+ } else if ((verOffset = userAgent.indexOf('Chrome')) !== -1) {
34
+ // In Chrome, the true version is after 'Chrome'
35
+ browser = {
36
+ name: 'Chrome',
37
+ version: userAgent.substring(verOffset + 7),
38
+ };
39
+ } else if ((verOffset = userAgent.indexOf('Safari')) !== -1) {
40
+ // In Safari, the true version is after 'Safari' or after 'Version'
41
+ browser = {
42
+ name: 'Safari',
43
+ version: userAgent.substring(verOffset + 7),
44
+ };
45
+ if ((verOffset = userAgent.indexOf('Version')) !== -1) {
46
+ browser.version = userAgent.substring(verOffset + 8);
47
+ }
48
+ } else if ((verOffset = userAgent.indexOf('Firefox')) != -1) {
49
+ // In Firefox, the true version is after 'Firefox'
50
+ browser = {
51
+ name: 'Firefox',
52
+ version: userAgent.substring(verOffset + 8),
53
+ };
54
+ } else if (
55
+ (nameOffset = userAgent.lastIndexOf(' ') + 1)
56
+ < (verOffset = userAgent.lastIndexOf('/'))
57
+ ) {
58
+ browser = {
59
+ name: userAgent.substring(nameOffset, verOffset),
60
+ version: userAgent.substring(verOffset + 1),
61
+ };
62
+ }
63
+
64
+ // Postprocess version
65
+ // trim the fullVersion string at semicolon/space if present
66
+ let ix: number;
67
+ if ((ix = browser.version.indexOf(';')) !== -1) {
68
+ browser.version = browser.version.substring(0, ix);
69
+ }
70
+ if ((ix = browser.version.indexOf(' ')) !== -1) {
71
+ browser.version = browser.version.substring(0, ix);
72
+ }
73
+
74
+ /* ------------- Device ------------- */
75
+
76
+ // Detect os
77
+ let os = 'Unknown';
78
+ if (userAgent.includes('Linux')) {
79
+ os = 'Linux';
80
+ } else if (userAgent.includes('like Mac')) {
81
+ os = 'iOS';
82
+ } else if (userAgent.includes('Mac')) {
83
+ os = 'Mac';
84
+ } else if (userAgent.includes('Android')) {
85
+ os = 'Android';
86
+ } else if (userAgent.includes('Win')) {
87
+ os = 'Win';
88
+ }
89
+
90
+ // Check if mobile
91
+ const isMobile = !!userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry)/);
92
+
93
+ // Device
94
+ const device = {
95
+ isMobile,
96
+ os,
97
+ };
98
+
99
+ /* ------------- Finish ------------- */
100
+
101
+ // Return info
102
+ return {
103
+ browser,
104
+ device,
105
+ };
106
+ };
107
+
108
+ export default parseUserAgent;
package/src/index.ts CHANGED
@@ -57,6 +57,10 @@ import ReactKitErrorCode from './types/ReactKitErrorCode';
57
57
  import Variant from './types/Variant';
58
58
  import ParamType from './types/ParamType';
59
59
  import DayOfWeek from './types/DayOfWeek';
60
+ import Log from './types/Log';
61
+ import LogType from './types/LogType';
62
+ import LogSource from './types/LogSource';
63
+ import LogAction from './types/LogAction';
60
64
 
61
65
  // Component-specific-types
62
66
  import PickableItem from './components/ItemPicker/types/PickableItem';
@@ -123,6 +127,10 @@ export {
123
127
  ReactKitErrorCode,
124
128
  Variant,
125
129
  DayOfWeek,
130
+ Log,
131
+ LogType,
132
+ LogSource,
133
+ LogAction,
126
134
  // Component-specific-types
127
135
  PickableItem,
128
136
  // Server types
@@ -1,5 +1,12 @@
1
1
  // Import custom error
2
+ import LOG_ROUTE_PATH from '../constants/LOG_ROUTE_PATH';
2
3
  import ErrorWithCode from '../errors/ErrorWithCode';
4
+
5
+ // Import shared helpers
6
+ import genRouteHandler from '../helpers/genRouteHandler';
7
+ import ParamType from '../types/ParamType';
8
+
9
+ // Import shared types
3
10
  import ReactKitErrorCode from '../types/ReactKitErrorCode';
4
11
 
5
12
  // Types
@@ -11,6 +18,9 @@ type GetLaunchInfoFunction = (req: any) => {
11
18
  // Stored copy of caccl functions
12
19
  let _cacclGetLaunchInfo: GetLaunchInfoFunction;
13
20
 
21
+ // Stored copy of dce-mango log collection
22
+ let _logCollection: any;
23
+
14
24
  /*------------------------------------------------------------------------*/
15
25
  /* Helpers */
16
26
  /*------------------------------------------------------------------------*/
@@ -32,6 +42,16 @@ export const cacclGetLaunchInfo: GetLaunchInfoFunction = (req: any) => {
32
42
  return _cacclGetLaunchInfo(req);
33
43
  };
34
44
 
45
+ /**
46
+ * Get log collection
47
+ * @author Gabe Abrams
48
+ * @returns log collection if one was included during launch or null if we don't
49
+ * have a log collection (yet)
50
+ */
51
+ export const internalGetLogCollection = () => {
52
+ return _logCollection ?? null;
53
+ };
54
+
35
55
  /*------------------------------------------------------------------------*/
36
56
  /* Main */
37
57
  /*------------------------------------------------------------------------*/
@@ -40,14 +60,84 @@ export const cacclGetLaunchInfo: GetLaunchInfoFunction = (req: any) => {
40
60
  * Prepare dce-reactkit to run on the server
41
61
  * @author Gabe Abrams
42
62
  * @param opts object containing all arguments
63
+ * @param opts.app express app from inside of the postprocessor function that
64
+ * we will add routes to
43
65
  * @param opts.getLaunchInfo CACCL LTI's get launch info function
66
+ * @param [opts.logCollection] mongo collection from dce-mango to use for
67
+ * storing logs. If none is included, logs are written to the console
44
68
  */
45
69
  const initServer = (
46
70
  opts: {
71
+ app: any,
47
72
  getLaunchInfo: GetLaunchInfoFunction,
73
+ logCollection?: any,
48
74
  },
49
75
  ) => {
50
76
  _cacclGetLaunchInfo = opts.getLaunchInfo;
77
+ _logCollection = opts.logCollection;
78
+
79
+ /**
80
+ * Log an event
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)
86
+ * @param {string} tags stringified list of tags that apply to this action
87
+ * (each app determines tag usage)
88
+ * @param {string} metadata stringified object containing optional custom metadata
89
+ * @param {string} [errorMessage] error message if type is an error
90
+ * @param {string} [errorCode] error code if type is an error
91
+ * @param {string} [errorStack] error stack if type is an error
92
+ * @param {string} [target] Target of the action (each app determines the list
93
+ * of targets) These are usually buttons, panels, elements, etc.
94
+ * @param {LogAction} [action] the type of action performed on the target
95
+ * @returns {Log}
96
+ */
97
+ opts.app.post(
98
+ LOG_ROUTE_PATH,
99
+ genRouteHandler({
100
+ paramTypes: {
101
+ category: ParamType.String,
102
+ subcategory: ParamType.String,
103
+ tags: ParamType.JSON,
104
+ metadata: ParamType.JSON,
105
+ errorMessage: ParamType.StringOptional,
106
+ errorCode: ParamType.StringOptional,
107
+ errorStack: ParamType.StringOptional,
108
+ target: ParamType.StringOptional,
109
+ action: ParamType.StringOptional,
110
+ },
111
+ handler: ({ params, logServerEvent }) => {
112
+ const log = logServerEvent(
113
+ (params.errorMessage || params.errorCode || params.errorStack)
114
+ // Error
115
+ ? {
116
+ category: params.category,
117
+ subcategory: params.subcategory,
118
+ tags: params.tags,
119
+ metadata: params.metadata,
120
+ error: {
121
+ message: params.errorMessage,
122
+ code: params.errorCode,
123
+ stack: params.errorStack,
124
+ },
125
+ }
126
+ // Action
127
+ : {
128
+ category: params.category,
129
+ subcategory: params.subcategory,
130
+ tags: params.tags,
131
+ metadata: params.metadata,
132
+ target: params.target,
133
+ action: params.action,
134
+ }
135
+ );
136
+
137
+ return log;
138
+ },
139
+ }),
140
+ );
51
141
  };
52
142
 
53
143
  export default initServer;