dce-reactkit 3.9.4-beta-logreviewer.5 → 3.9.4-beta-logreviewer.7

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 (35) hide show
  1. package/dist/cjs/index.js +256 -67
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/client/initClient.d.ts +16 -2
  4. package/dist/cjs/types/components/AppWrapper.d.ts +32 -0
  5. package/dist/cjs/types/components/CheckboxButton.d.ts +1 -0
  6. package/dist/cjs/types/components/RadioButton.d.ts +2 -0
  7. package/dist/cjs/types/constants/LOG_REVIEW_PAGE_SIZE.d.ts +6 -0
  8. package/dist/cjs/types/index.d.ts +2 -2
  9. package/dist/cjs/types/types/ModalType.d.ts +1 -0
  10. package/dist/esm/index.js +256 -68
  11. package/dist/esm/index.js.map +1 -1
  12. package/dist/esm/types/client/initClient.d.ts +16 -2
  13. package/dist/esm/types/components/AppWrapper.d.ts +32 -0
  14. package/dist/esm/types/components/CheckboxButton.d.ts +1 -0
  15. package/dist/esm/types/components/RadioButton.d.ts +2 -0
  16. package/dist/esm/types/constants/LOG_REVIEW_PAGE_SIZE.d.ts +6 -0
  17. package/dist/esm/types/index.d.ts +2 -2
  18. package/dist/esm/types/types/ModalType.d.ts +1 -0
  19. package/dist/index.d.ts +47 -3
  20. package/package.json +1 -1
  21. package/src/client/initClient.tsx +49 -11
  22. package/src/components/AppWrapper.tsx +258 -2
  23. package/src/components/CheckboxButton.tsx +24 -6
  24. package/src/components/LogReviewer.tsx +84 -60
  25. package/src/components/Modal/index.tsx +3 -0
  26. package/src/components/RadioButton.tsx +33 -6
  27. package/src/components/SimpleDateChooser.tsx +8 -2
  28. package/src/constants/LOG_REVIEW_PAGE_SIZE.ts +7 -0
  29. package/src/helpers/genRouteHandler.ts +7 -7
  30. package/src/helpers/logClientEvent.tsx +8 -0
  31. package/src/helpers/visitServerEndpoint.tsx +1 -0
  32. package/src/index.ts +2 -0
  33. package/src/server/initLogCollection.ts +5 -0
  34. package/src/server/initServer.ts +3 -2
  35. package/src/types/ModalType.tsx +1 -0
package/dist/cjs/index.js CHANGED
@@ -186,6 +186,7 @@ var ModalButtonType$1 = ModalButtonType;
186
186
  var ModalType;
187
187
  (function (ModalType) {
188
188
  ModalType["Okay"] = "okay";
189
+ ModalType["Cancel"] = "cancel";
189
190
  ModalType["OkayCancel"] = "okay-cancel";
190
191
  ModalType["YesNo"] = "yes-no";
191
192
  ModalType["YesNoCancel"] = "yes-no-cancel";
@@ -316,11 +317,23 @@ const isDarkModeOn = () => {
316
317
  * @param opts object containing all arguments
317
318
  * @param opts.sendRequest caccl send request functions
318
319
  * @param [opts.sessionExpiredMessage] a custom session expired message
320
+ * @param [opts.darkModeOn] if true, dark mode is enabled
321
+ * @param [opts.noServer] if true, there is no server for this app
319
322
  */
320
323
  const initClient = (opts) => {
321
- // Store values
322
- storedSendRequest = opts.sendRequest;
323
- sessionExpiredMessage = opts.sessionExpiredMessage;
324
+ // Handle separately if there is no server
325
+ if (opts.noServer) {
326
+ // Store values
327
+ storedSendRequest = () => __awaiter(void 0, void 0, void 0, function* () {
328
+ throw new Error('Cannot send requests because there is no server');
329
+ });
330
+ }
331
+ else {
332
+ // Store values
333
+ storedSendRequest = opts.sendRequest;
334
+ sessionExpiredMessage = opts.sessionExpiredMessage;
335
+ }
336
+ // Handle universal parts
324
337
  darkModeOn = !!opts.darkModeOn;
325
338
  // Mark as initialized
326
339
  onInitialized(null);
@@ -340,6 +353,9 @@ const modalTypeToModalButtonTypes = {
340
353
  [ModalType$1.Okay]: [
341
354
  ModalButtonType$1.Okay,
342
355
  ],
356
+ [ModalType$1.Cancel]: [
357
+ ModalButtonType$1.Cancel,
358
+ ],
343
359
  [ModalType$1.OkayCancel]: [
344
360
  ModalButtonType$1.Okay,
345
361
  ModalButtonType$1.Cancel,
@@ -781,6 +797,7 @@ const _setStubResponse = (opts) => {
781
797
  */
782
798
  const visitServerEndpoint = (opts) => __awaiter(void 0, void 0, void 0, function* () {
783
799
  var _a, _b, _c;
800
+ // Set default method
784
801
  const method = ((_a = opts.method) !== null && _a !== void 0 ? _a : 'GET');
785
802
  // Handle stubs
786
803
  const stubResponse = (_b = stubResponses[method]) === null || _b === void 0 ? void 0 : _b[opts.path];
@@ -1080,6 +1097,92 @@ const confirm = (title, text, opts) => __awaiter(void 0, void 0, void 0, functio
1080
1097
  });
1081
1098
  });
1082
1099
  /*----------------------------------------*/
1100
+ /* --------------- Prompt -------------- */
1101
+ /*----------------------------------------*/
1102
+ // Stored copies of setters
1103
+ let setPromptInfo;
1104
+ // Function to call when prompt is closed
1105
+ let onPromptClosed;
1106
+ /**
1107
+ * Show a prompt modal asking the user for input
1108
+ * @author Yuen Ler Chow
1109
+ * @param title the title text to display at the top of the prompt
1110
+ * @param [opts={}] additional options for the prompt dialog
1111
+ * @param [opts.textAboveInputField] the text to display in the prompt
1112
+ * @param [opts.defaultText] the default text for the input field
1113
+ * @param [opts.placeholder] the placeholder text for the input field
1114
+ * @param [opts.confirmButtonText=Okay] the text of the confirm button
1115
+ * @param [opts.confirmButtonVariant=Variant.Dark] the variant of the confirm button
1116
+ * @param [opts.cancelButtonText=Cancel] the text of the cancel button
1117
+ * @param [opts.cancelButtonVariant=Variant.Secondary] the variant of the cancel button
1118
+ * @param [opts.minNumChars] the minimum number of characters required for
1119
+ * the input to be valid
1120
+ * @param [opts.findValidationError] a function that takes the input text and
1121
+ * returns an error message if the input is invalid, returns undefined if the
1122
+ * input is valid
1123
+ * @param [opts.ariaLabel] the aria label for the input field
1124
+ * @returns Promise that resolves with the input string or null if canceled
1125
+ */
1126
+ const prompt = (title, opts) => __awaiter(void 0, void 0, void 0, function* () {
1127
+ var _a, _b;
1128
+ // Wait for helper to exist
1129
+ yield waitForHelper(() => {
1130
+ return !!setPromptInfo;
1131
+ });
1132
+ // Fallback if prompt is not available
1133
+ if (!setPromptInfo) {
1134
+ const resultPassesValidation = false;
1135
+ while (!resultPassesValidation) {
1136
+ // eslint-disable-next-line no-alert
1137
+ const result = window.prompt(`${title}\n\n${(_a = opts === null || opts === void 0 ? void 0 : opts.textAboveInputField) !== null && _a !== void 0 ? _a : ''}`, (_b = opts === null || opts === void 0 ? void 0 : opts.defaultText) !== null && _b !== void 0 ? _b : '');
1138
+ // Exit loop if user cancels
1139
+ if (result === null) {
1140
+ return null;
1141
+ }
1142
+ // Validate min num chars
1143
+ const minNumCharsValidationError = (((opts === null || opts === void 0 ? void 0 : opts.minNumChars) && result.length < opts.minNumChars)
1144
+ ? `Please enter at least ${opts.minNumChars} characters.`
1145
+ : undefined);
1146
+ // Run custom validation
1147
+ const customValidationError = ((opts === null || opts === void 0 ? void 0 : opts.findValidationError)
1148
+ && opts.findValidationError(result));
1149
+ // Show validation issue
1150
+ if (minNumCharsValidationError || customValidationError) {
1151
+ // Create error message
1152
+ const errorMessage = ([
1153
+ minNumCharsValidationError,
1154
+ customValidationError,
1155
+ ]
1156
+ // Filter out undefined messages
1157
+ .filter((msg) => {
1158
+ return !!msg;
1159
+ })
1160
+ // Join messages with newlines
1161
+ .join('\n'));
1162
+ // Show alert
1163
+ alert('Invalid Input', errorMessage);
1164
+ }
1165
+ else {
1166
+ return result;
1167
+ }
1168
+ }
1169
+ }
1170
+ // Return promise that resolves with result of prompt
1171
+ return new Promise((resolve) => {
1172
+ var _a;
1173
+ // Setup handler
1174
+ onPromptClosed = (result) => {
1175
+ resolve(result);
1176
+ };
1177
+ // Show the prompt
1178
+ setPromptInfo({
1179
+ title,
1180
+ currentInputFieldText: ((_a = opts === null || opts === void 0 ? void 0 : opts.defaultText) !== null && _a !== void 0 ? _a : ''),
1181
+ opts: (opts !== null && opts !== void 0 ? opts : {}),
1182
+ });
1183
+ });
1184
+ });
1185
+ /*----------------------------------------*/
1083
1186
  /* ------------- Fatal Error ------------ */
1084
1187
  /*----------------------------------------*/
1085
1188
  // Stored copies of setters
@@ -1095,14 +1198,14 @@ const fatalErrorHandlers = [];
1095
1198
  * @param [errorTitle] title of the error box
1096
1199
  */
1097
1200
  const showFatalError = (error, errorTitle = 'An Error Occurred') => __awaiter(void 0, void 0, void 0, function* () {
1098
- var _a, _b;
1201
+ var _c, _d;
1099
1202
  // Determine message and code
1100
1203
  const message = (typeof error === 'string'
1101
1204
  ? error.trim()
1102
- : String((_a = error.message) !== null && _a !== void 0 ? _a : 'An unknown error occurred.'));
1205
+ : String((_c = error.message) !== null && _c !== void 0 ? _c : 'An unknown error occurred.'));
1103
1206
  const code = (typeof error === 'string'
1104
1207
  ? ReactKitErrorCode$1.NoCode
1105
- : String((_b = error.code) !== null && _b !== void 0 ? _b : ReactKitErrorCode$1.NoCode));
1208
+ : String((_d = error.code) !== null && _d !== void 0 ? _d : ReactKitErrorCode$1.NoCode));
1106
1209
  // Call all fatal error listeners
1107
1210
  try {
1108
1211
  fatalErrorHandlers.forEach((handler) => {
@@ -1229,6 +1332,9 @@ const AppWrapper = (props) => {
1229
1332
  // Confirm
1230
1333
  const [confirmInfo, setConfirmInfoInner,] = React.useState(undefined);
1231
1334
  setConfirmInfo = setConfirmInfoInner;
1335
+ // Prompt
1336
+ const [promptInfo, setPromptInfoInner] = React.useState(undefined);
1337
+ setPromptInfo = setPromptInfoInner;
1232
1338
  // Session expired
1233
1339
  const [sessionHasExpired, setSessionHasExpiredInner,] = React.useState(false);
1234
1340
  setSessionHasExpired = setSessionHasExpiredInner;
@@ -1259,6 +1365,42 @@ const AppWrapper = (props) => {
1259
1365
  }
1260
1366
  }, onTopOfOtherModals: true, dontAllowBackdropExit: true }, confirmInfo.text));
1261
1367
  }
1368
+ /* ------------- Prompt ------------ */
1369
+ if (promptInfo) {
1370
+ // Run min char validation
1371
+ const minNumCharsValidationError = ((promptInfo.opts.minNumChars
1372
+ && promptInfo.currentInputFieldText.length < promptInfo.opts.minNumChars)
1373
+ ? `Please enter at least ${promptInfo.opts.minNumChars} characters.`
1374
+ : undefined);
1375
+ // Run custom validation
1376
+ const customValidationError = (promptInfo.opts.findValidationError
1377
+ && promptInfo.opts.findValidationError(promptInfo.currentInputFieldText));
1378
+ modal = (React__default["default"].createElement(Modal, { key: `prompt-${promptInfo.title}`, title: promptInfo.title,
1379
+ // Don't show ok button if there is a validation error
1380
+ type: ((customValidationError || minNumCharsValidationError)
1381
+ ? ModalType$1.Cancel
1382
+ : ModalType$1.OkayCancel), okayLabel: promptInfo.opts.confirmButtonText, okayVariant: promptInfo.opts.confirmButtonVariant, cancelLabel: promptInfo.opts.cancelButtonText, cancelVariant: promptInfo.opts.cancelButtonVariant, onClose: (buttonType) => {
1383
+ // Get result
1384
+ const result = (buttonType === ModalButtonType$1.Okay
1385
+ ? promptInfo.currentInputFieldText
1386
+ : null);
1387
+ // Close prompt
1388
+ setPromptInfo(undefined);
1389
+ // Call handler
1390
+ if (onPromptClosed) {
1391
+ onPromptClosed(result);
1392
+ }
1393
+ }, onTopOfOtherModals: true, dontAllowBackdropExit: true },
1394
+ React__default["default"].createElement("div", null,
1395
+ promptInfo.opts.textAboveInputField && (React__default["default"].createElement("div", null, promptInfo.opts.textAboveInputField)),
1396
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": promptInfo.opts.ariaLabel, placeholder: promptInfo.opts.placeholder, value: promptInfo.currentInputFieldText, onChange: (e) => {
1397
+ return setPromptInfo(Object.assign(Object.assign({}, promptInfo), { currentInputFieldText: e.target.value }));
1398
+ },
1399
+ // eslint-disable-next-line jsx-a11y/no-autofocus
1400
+ autoFocus: true }),
1401
+ minNumCharsValidationError && (React__default["default"].createElement("div", { className: "text-danger fw-bold mt-2" }, minNumCharsValidationError)),
1402
+ customValidationError && (React__default["default"].createElement("div", { className: "text-danger fw-bold mt-2" }, customValidationError)))));
1403
+ }
1262
1404
  /* ------ Custom Modal Portals ------ */
1263
1405
  // Custom modal portals
1264
1406
  const customModalPortals = [];
@@ -1328,7 +1470,7 @@ const AppWrapper = (props) => {
1328
1470
  ? `
1329
1471
  .tooltip-inner {
1330
1472
  background-color: white;
1331
- color: black;
1473
+ color: black !important;
1332
1474
  border: 0.1rem solid black;
1333
1475
  pointer-events: none;
1334
1476
  }
@@ -1341,7 +1483,7 @@ const AppWrapper = (props) => {
1341
1483
  : `
1342
1484
  .tooltip-inner {
1343
1485
  background-color: black;
1344
- color: white;
1486
+ color: white !important;
1345
1487
  border: 0.1rem solid white;
1346
1488
  pointer-events: none;
1347
1489
  }
@@ -1560,24 +1702,31 @@ const RadioButton = (props) => {
1560
1702
  /* -------------------------------- Setup ------------------------------- */
1561
1703
  /*------------------------------------------------------------------------*/
1562
1704
  /* -------------- Props ------------- */
1563
- const { text, onSelected, ariaLabel, title, selected, id, noMarginOnRight, selectedVariant = (isDarkModeOn()
1705
+ const { text, onSelected, ariaLabel, title, selected, id, className, noMarginOnRight, selectedVariant = (isDarkModeOn()
1564
1706
  ? Variant$1.Light
1565
1707
  : Variant$1.Secondary), unselectedVariant = (isDarkModeOn()
1566
1708
  ? Variant$1.Secondary
1567
- : Variant$1.Light), small, } = props;
1709
+ : Variant$1.Light), small, useComplexFormatting, } = props;
1568
1710
  /*------------------------------------------------------------------------*/
1569
1711
  /* ------------------------------- Render ------------------------------- */
1570
1712
  /*------------------------------------------------------------------------*/
1571
1713
  /*----------------------------------------*/
1572
1714
  /* --------------- Main UI -------------- */
1573
1715
  /*----------------------------------------*/
1574
- return (React__default["default"].createElement("button", { type: "button", id: id, title: title, className: `btn btn-${selected ? selectedVariant : unselectedVariant}${selected ? ' selected' : ''}${small ? ' btn-sm' : ''} m-0${noMarginOnRight ? '' : ' me-1'}`, "aria-label": `${ariaLabel}${selected ? ': currently selected' : ''}`, onClick: () => {
1716
+ return (React__default["default"].createElement("button", { type: "button", id: id, title: title, className: `btn btn-${selected ? selectedVariant : unselectedVariant}${selected ? ' selected' : ''}${small ? ' btn-sm' : ''} m-0${noMarginOnRight ? '' : ' me-1'} ${className !== null && className !== void 0 ? className : ''}`, "aria-label": `${ariaLabel}${selected ? ': currently selected' : ''}`, onClick: () => {
1575
1717
  if (!selected) {
1576
1718
  onSelected();
1577
1719
  }
1578
1720
  } },
1579
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: selected ? freeSolidSvgIcons.faDotCircle : freeRegularSvgIcons.faCircle, className: "me-1" }),
1580
- text));
1721
+ React__default["default"].createElement("div", { className: "d-flex flex-row align-items-center" },
1722
+ React__default["default"].createElement("div", { className: "me-1" },
1723
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: selected ? freeSolidSvgIcons.faDotCircle : freeRegularSvgIcons.faCircle })),
1724
+ useComplexFormatting
1725
+ ? (React__default["default"].createElement("pre", { className: "ps-1 text-start text-break", style: {
1726
+ whiteSpace: 'pre-wrap',
1727
+ tabSize: 2,
1728
+ } }, text))
1729
+ : (React__default["default"].createElement("div", { className: "flex-grow-1 text-start text-break" }, text)))));
1581
1730
  };
1582
1731
 
1583
1732
  /**
@@ -1596,7 +1745,7 @@ const CheckboxButton = (props) => {
1596
1745
  ? Variant$1.Light
1597
1746
  : Variant$1.Secondary), uncheckedVariant = (isDarkModeOn()
1598
1747
  ? Variant$1.Secondary
1599
- : Variant$1.Light), small, dashed, } = props;
1748
+ : Variant$1.Light), small, dashed, useComplexFormatting, } = props;
1600
1749
  /*------------------------------------------------------------------------*/
1601
1750
  /* ------------------------------- Render ------------------------------- */
1602
1751
  /*------------------------------------------------------------------------*/
@@ -1617,10 +1766,15 @@ const CheckboxButton = (props) => {
1617
1766
  return (React__default["default"].createElement("button", { type: "button", id: id, title: title, className: `CheckboxButton-status-${checked ? 'checked' : 'unchecked'} ${dashed ? 'CheckboxButton-dashed ' : ''}btn btn-${checked ? checkedVariant : uncheckedVariant}${checked ? ' selected' : ''}${small ? ' btn-sm' : ''} m-0${noMarginOnRight ? '' : ' me-1'} ${className !== null && className !== void 0 ? className : ''}`, "aria-label": `${ariaLabel}${checked ? ': currently checked' : ''}`, onClick: () => {
1618
1767
  onChanged(!checked);
1619
1768
  } },
1620
- React__default["default"].createElement("div", { className: "d-flex" },
1621
- React__default["default"].createElement("div", { className: "align-items-center" },
1622
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: icon, className: "me-1" })),
1623
- React__default["default"].createElement("div", { className: "flex-grow-1" }, text))));
1769
+ React__default["default"].createElement("div", { className: "d-flex align-items-center" },
1770
+ React__default["default"].createElement("div", { className: "me-1" },
1771
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: icon })),
1772
+ useComplexFormatting
1773
+ ? (React__default["default"].createElement("pre", { className: "ps-1 text-start text-break", style: {
1774
+ whiteSpace: 'pre-wrap',
1775
+ tabSize: 2,
1776
+ } }, text))
1777
+ : (React__default["default"].createElement("div", { className: "flex-grow-1 text-start text-break" }, text)))));
1624
1778
  };
1625
1779
 
1626
1780
  /**
@@ -1801,7 +1955,13 @@ const SimpleDateChooser = (props) => {
1801
1955
  }
1802
1956
  const monthName = getMonthName(month).full;
1803
1957
  // Year is start year +1 for each 12 months
1804
- const year = (startYear + (Math.floor(unmoddedMonth / 12)));
1958
+ let yearsToAdd = 0;
1959
+ let monthsOfYearsToAdd = unmoddedMonth;
1960
+ while (monthsOfYearsToAdd > 12) {
1961
+ monthsOfYearsToAdd -= 12;
1962
+ yearsToAdd += 1;
1963
+ }
1964
+ const year = startYear + yearsToAdd;
1805
1965
  // Figure out which days are allowed
1806
1966
  const days = [];
1807
1967
  const numDaysInMonth = (new Date(year, month, 0)).getDate();
@@ -1852,7 +2012,7 @@ const SimpleDateChooser = (props) => {
1852
2012
  });
1853
2013
  }
1854
2014
  });
1855
- return (React__default["default"].createElement("div", { className: "SimpleDateChooser d-inline-block", "aria-label": `date chooser with selected date: ${month} ${day}, ${year}` },
2015
+ return (React__default["default"].createElement("div", { className: "SimpleDateChooser d-inline-block", "aria-label": `date chooser with selected date: ${month}/${day}/${year}` },
1856
2016
  React__default["default"].createElement("select", { "aria-label": `month for ${ariaLabel}`, className: "custom-select d-inline-block mr-1", style: { width: 'auto' }, id: `SimpleDateChooser-${name}-month`, value: `${month}-${year}`, onChange: (e) => {
1857
2017
  const choice = choices[e.target.selectedIndex];
1858
2018
  // Change day, month, and year
@@ -3555,14 +3715,12 @@ var ActionType$6;
3555
3715
  ActionType["UpdateActionErrorFilterState"] = "update-action-error-filter-state";
3556
3716
  // Update the advanced filter state
3557
3717
  ActionType["UpdateAdvancedFilterState"] = "update-advanced-filter-state";
3558
- // Increment the page number
3559
- ActionType["IncrementPageNumber"] = "increment-page-number";
3560
- // Decrement the page number
3561
- ActionType["DecrementPageNumber"] = "decrement-page-number";
3562
3718
  // Set has another page
3563
3719
  ActionType["SetHasAnotherPage"] = "set-has-another-page";
3564
3720
  // Set the number of pages
3565
3721
  ActionType["SetNumPages"] = "set-num-pages";
3722
+ // Set page number
3723
+ ActionType["SetPageNumber"] = "set-page-number";
3566
3724
  })(ActionType$6 || (ActionType$6 = {}));
3567
3725
  /**
3568
3726
  * Reducer that executes actions
@@ -3573,10 +3731,10 @@ var ActionType$6;
3573
3731
  const reducer$7 = (state, action) => {
3574
3732
  switch (action.type) {
3575
3733
  case ActionType$6.StartLoading: {
3576
- return Object.assign(Object.assign({}, state), { loading: true });
3734
+ return Object.assign(Object.assign({}, state), { loading: true, showSpinner: action.filtersChanged });
3577
3735
  }
3578
3736
  case ActionType$6.FinishLoading: {
3579
- return Object.assign(Object.assign({}, state), { loading: false, logs: action.logs });
3737
+ return Object.assign(Object.assign({}, state), { loading: false, showSpinner: false, logs: action.logs });
3580
3738
  }
3581
3739
  case ActionType$6.ToggleFilterDrawer: {
3582
3740
  return Object.assign(Object.assign({}, state), { expandedFilterDrawer: (state.expandedFilterDrawer === action.filterDrawer
@@ -3604,18 +3762,15 @@ const reducer$7 = (state, action) => {
3604
3762
  case ActionType$6.UpdateAdvancedFilterState: {
3605
3763
  return Object.assign(Object.assign({}, state), { advancedFilterState: action.advancedFilterState });
3606
3764
  }
3607
- case ActionType$6.IncrementPageNumber: {
3608
- return Object.assign(Object.assign({}, state), { pageNumber: state.pageNumber + 1 });
3609
- }
3610
- case ActionType$6.DecrementPageNumber: {
3611
- return Object.assign(Object.assign({}, state), { pageNumber: state.pageNumber - 1 });
3612
- }
3613
3765
  case ActionType$6.SetHasAnotherPage: {
3614
3766
  return Object.assign(Object.assign({}, state), { hasAnotherPage: action.hasAnotherPage });
3615
3767
  }
3616
3768
  case ActionType$6.SetNumPages: {
3617
3769
  return Object.assign(Object.assign({}, state), { numPages: action.numPages });
3618
3770
  }
3771
+ case ActionType$6.SetPageNumber: {
3772
+ return Object.assign(Object.assign({}, state), { pageNumber: action.pageNumber });
3773
+ }
3619
3774
  default: {
3620
3775
  return state;
3621
3776
  }
@@ -3733,6 +3888,7 @@ const LogReviewer = (props) => {
3733
3888
  // Initial state
3734
3889
  const initialState = {
3735
3890
  loading: true,
3891
+ showSpinner: true,
3736
3892
  logs: [],
3737
3893
  expandedFilterDrawer: undefined,
3738
3894
  dateFilterState: initDateFilterState,
@@ -3747,7 +3903,7 @@ const LogReviewer = (props) => {
3747
3903
  // Initialize state
3748
3904
  const [state, dispatch] = React.useReducer(reducer$7, initialState);
3749
3905
  // Destructure common state
3750
- const { loading, logs, expandedFilterDrawer, dateFilterState, contextFilterState, tagFilterState, actionErrorFilterState, advancedFilterState, pageNumber, hasAnotherPage, numPages, } = state;
3906
+ const { loading, showSpinner, logs, expandedFilterDrawer, dateFilterState, contextFilterState, tagFilterState, actionErrorFilterState, advancedFilterState, pageNumber, hasAnotherPage, numPages, } = state;
3751
3907
  /*------------------------------------------------------------------------*/
3752
3908
  /* ------------------------- Component Functions ------------------------ */
3753
3909
  /*------------------------------------------------------------------------*/
@@ -3755,8 +3911,11 @@ const LogReviewer = (props) => {
3755
3911
  * Fetch logs from the server based on current filters
3756
3912
  */
3757
3913
  const fetchLogs = (opts) => __awaiter(void 0, void 0, void 0, function* () {
3758
- const { filters, pageNum, countDocuments, } = opts;
3759
- dispatch({ type: ActionType$6.StartLoading });
3914
+ const { filters, pageNum, filtersChanged, } = opts;
3915
+ dispatch({
3916
+ type: ActionType$6.StartLoading,
3917
+ filtersChanged,
3918
+ });
3760
3919
  try {
3761
3920
  // Send filters to the server
3762
3921
  let fetchedLogs = [];
@@ -3767,7 +3926,7 @@ const LogReviewer = (props) => {
3767
3926
  params: {
3768
3927
  pageNumber: pageNum,
3769
3928
  filters,
3770
- countDocuments,
3929
+ countDocuments: filtersChanged,
3771
3930
  },
3772
3931
  });
3773
3932
  fetchedLogs = fetchedLogs.concat(response.items);
@@ -3775,7 +3934,7 @@ const LogReviewer = (props) => {
3775
3934
  type: ActionType$6.SetHasAnotherPage,
3776
3935
  hasAnotherPage: response.hasAnotherPage,
3777
3936
  });
3778
- if (countDocuments && response.numPages) {
3937
+ if (filtersChanged && response.numPages) {
3779
3938
  dispatch({
3780
3939
  type: ActionType$6.SetNumPages,
3781
3940
  numPages: response.numPages,
@@ -3786,12 +3945,34 @@ const LogReviewer = (props) => {
3786
3945
  type: ActionType$6.FinishLoading,
3787
3946
  logs: fetchedLogs,
3788
3947
  });
3948
+ // Update page number
3949
+ dispatch({
3950
+ type: ActionType$6.SetPageNumber,
3951
+ pageNumber: pageNum,
3952
+ });
3789
3953
  }
3790
3954
  catch (err) {
3791
3955
  return showFatalError(err);
3792
3956
  }
3793
3957
  });
3794
3958
  /*------------------------------------------------------------------------*/
3959
+ /* ------------------------- Lifecycle Functions ------------------------ */
3960
+ /*------------------------------------------------------------------------*/
3961
+ // Fetch logs on mount
3962
+ React.useEffect(() => {
3963
+ fetchLogs({
3964
+ filters: {
3965
+ dateFilterState,
3966
+ contextFilterState,
3967
+ tagFilterState,
3968
+ actionErrorFilterState,
3969
+ advancedFilterState,
3970
+ },
3971
+ pageNum: 1,
3972
+ filtersChanged: true,
3973
+ });
3974
+ }, []);
3975
+ /*------------------------------------------------------------------------*/
3795
3976
  /* ------------------------------- Render ------------------------------- */
3796
3977
  /*------------------------------------------------------------------------*/
3797
3978
  /*----------------------------------------*/
@@ -3800,7 +3981,7 @@ const LogReviewer = (props) => {
3800
3981
  // Body that will be filled with the contents of the panel
3801
3982
  let body;
3802
3983
  /* ------------- Loading ------------ */
3803
- if (loading) {
3984
+ if (showSpinner) {
3804
3985
  body = (React__default["default"].createElement("div", { className: "text-center p-5" },
3805
3986
  React__default["default"].createElement(LoadingSpinner, null)));
3806
3987
  }
@@ -3809,7 +3990,7 @@ const LogReviewer = (props) => {
3809
3990
  /* ------------ Pagination -------------- */
3810
3991
  /*----------------------------------------*/
3811
3992
  const paginationControls = logs.length > 0 && (React__default["default"].createElement("div", { className: "text-center mt-3" },
3812
- React__default["default"].createElement("button", { type: "button", className: "btn btn-secondary me-2", disabled: pageNumber <= 1, onClick: () => {
3993
+ React__default["default"].createElement("button", { type: "button", className: "btn btn-secondary me-2", disabled: pageNumber <= 1 || loading, onClick: () => {
3813
3994
  fetchLogs({
3814
3995
  filters: {
3815
3996
  dateFilterState,
@@ -3819,10 +4000,7 @@ const LogReviewer = (props) => {
3819
4000
  advancedFilterState,
3820
4001
  },
3821
4002
  pageNum: pageNumber - 1,
3822
- countDocuments: false,
3823
- });
3824
- dispatch({
3825
- type: ActionType$6.DecrementPageNumber,
4003
+ filtersChanged: false,
3826
4004
  });
3827
4005
  } },
3828
4006
  React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faArrowLeft, className: "me-2" }),
@@ -3835,7 +4013,7 @@ const LogReviewer = (props) => {
3835
4013
  "of",
3836
4014
  ' ',
3837
4015
  numPages),
3838
- React__default["default"].createElement("button", { type: "button", className: "btn btn-secondary ms-2", disabled: !hasAnotherPage, onClick: () => {
4016
+ React__default["default"].createElement("button", { type: "button", className: "btn btn-secondary ms-2", disabled: !hasAnotherPage || loading, onClick: () => {
3839
4017
  fetchLogs({
3840
4018
  filters: {
3841
4019
  dateFilterState,
@@ -3845,10 +4023,7 @@ const LogReviewer = (props) => {
3845
4023
  advancedFilterState,
3846
4024
  },
3847
4025
  pageNum: pageNumber + 1,
3848
- countDocuments: false,
3849
- });
3850
- dispatch({
3851
- type: ActionType$6.IncrementPageNumber,
4026
+ filtersChanged: false,
3852
4027
  });
3853
4028
  } },
3854
4029
  "Next Page",
@@ -3910,7 +4085,7 @@ const LogReviewer = (props) => {
3910
4085
  advancedFilterState: initAdvancedFilterState,
3911
4086
  },
3912
4087
  pageNum: 1,
3913
- countDocuments: true,
4088
+ filtersChanged: true,
3914
4089
  });
3915
4090
  dispatch({
3916
4091
  type: ActionType$6.ResetFilters,
@@ -3940,7 +4115,7 @@ const LogReviewer = (props) => {
3940
4115
  advancedFilterState,
3941
4116
  },
3942
4117
  pageNum: 1,
3943
- countDocuments: true,
4118
+ filtersChanged: true,
3944
4119
  });
3945
4120
  } },
3946
4121
  React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faSearch }),
@@ -4322,14 +4497,16 @@ const LogReviewer = (props) => {
4322
4497
  filterToggles,
4323
4498
  filterDrawer && (React__default["default"].createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
4324
4499
  // Main body
4325
- body = (React__default["default"].createElement(React__default["default"].Fragment, null,
4326
- filters,
4327
- React__default["default"].createElement("div", { className: "mt-2" },
4328
- React__default["default"].createElement(IntelliTable, { title: "Matching Logs:", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns }),
4329
- logs.length === 0 && (React__default["default"].createElement("div", { className: "alert alert-warning text-center mt-2" },
4330
- React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
4331
- React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))),
4332
- paginationControls)));
4500
+ if (!showSpinner) {
4501
+ body = (React__default["default"].createElement(React__default["default"].Fragment, null,
4502
+ filters,
4503
+ React__default["default"].createElement("div", { className: "mt-2" },
4504
+ React__default["default"].createElement(IntelliTable, { title: "Matching Logs:", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns }),
4505
+ logs.length === 0 && (React__default["default"].createElement("div", { className: "alert alert-warning text-center mt-2" },
4506
+ React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
4507
+ React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))),
4508
+ paginationControls)));
4509
+ }
4333
4510
  /* ---------- Wrap in Modal --------- */
4334
4511
  return (React__default["default"].createElement("div", { className: "LogReviewer-outer-container" },
4335
4512
  React__default["default"].createElement("style", null, style$2),
@@ -13671,6 +13848,12 @@ const padZerosLeft = (num, numDigits) => {
13671
13848
  */
13672
13849
  const LOG_REVIEW_STATUS_ROUTE = `${ROUTE_PATH_PREFIX}/logs/access_allowed`;
13673
13850
 
13851
+ /**
13852
+ * Log reviewer page size
13853
+ * @author Yuen Ler Chow
13854
+ */
13855
+ const LOG_REVIEW_PAGE_SIZE = 100;
13856
+
13674
13857
  // Stored copy of caccl functions
13675
13858
  let _cacclGetLaunchInfo;
13676
13859
  // Stored copy of dce-mango log collection
@@ -14019,12 +14202,12 @@ const initServer = (opts) => {
14019
14202
  // Query for logs
14020
14203
  const response = yield _logCollection.findPaged({
14021
14204
  query,
14022
- perPage: 50,
14205
+ perPage: LOG_REVIEW_PAGE_SIZE,
14023
14206
  pageNumber,
14024
14207
  });
14025
14208
  // Count documents if requested
14026
14209
  if (countDocuments) {
14027
- response.numPages = Math.ceil((yield _logCollection.count(query)) / 50);
14210
+ response.numPages = Math.ceil((yield _logCollection.count(query)) / LOG_REVIEW_PAGE_SIZE);
14028
14211
  }
14029
14212
  // Return response
14030
14213
  return response;
@@ -14842,16 +15025,16 @@ const genRouteHandler = (opts) => {
14842
15025
  const { timestamp, year, month, day, hour, minute, } = getTimeInfoInET();
14843
15026
  // Main log info
14844
15027
  const mainLogInfo = {
14845
- id: `${launchInfo ? 'unknown' : launchInfo.userId}-${Date.now()}-${Math.floor(Math.random() * 100000)}-${Math.floor(Math.random() * 100000)}`,
14846
- userFirstName: (launchInfo ? 'unknown' : launchInfo.userFirstName),
14847
- userLastName: (launchInfo ? 'unknown' : launchInfo.userLastName),
14848
- userEmail: (launchInfo ? 'unknown' : launchInfo.userEmail),
14849
- userId: (launchInfo ? 'unknown' : launchInfo.userId),
15028
+ id: `${launchInfo ? launchInfo.userId : 'unknown'}-${Date.now()}-${Math.floor(Math.random() * 100000)}-${Math.floor(Math.random() * 100000)}`,
15029
+ userFirstName: (launchInfo ? launchInfo.userFirstName : 'unknown'),
15030
+ userLastName: (launchInfo ? launchInfo.userLastName : 'unknown'),
15031
+ userEmail: (launchInfo ? launchInfo.userEmail : 'unknown'),
15032
+ userId: (launchInfo ? launchInfo.userId : 'unknown'),
14850
15033
  isLearner: (launchInfo && !!launchInfo.isLearner),
14851
15034
  isAdmin: (launchInfo && !!launchInfo.isAdmin),
14852
15035
  isTTM: (launchInfo && !!launchInfo.isTTM),
14853
- courseId: (launchInfo ? 'unknown' : launchInfo.courseId),
14854
- courseName: (launchInfo ? 'unknown' : launchInfo.contextLabel),
15036
+ courseId: (launchInfo ? launchInfo.courseId : 'unknown'),
15037
+ courseName: (launchInfo ? launchInfo.contextLabel : 'unknown'),
14855
15038
  browser,
14856
15039
  device,
14857
15040
  year,
@@ -15258,6 +15441,11 @@ const initLogCollection = (Collection) => {
15258
15441
  'context',
15259
15442
  'subcontext',
15260
15443
  'tags',
15444
+ 'year',
15445
+ 'month',
15446
+ 'day',
15447
+ 'hour',
15448
+ 'type',
15261
15449
  ],
15262
15450
  });
15263
15451
  };
@@ -16279,6 +16467,7 @@ exports.padDecimalZeros = padDecimalZeros;
16279
16467
  exports.padZerosLeft = padZerosLeft;
16280
16468
  exports.parallelLimit = parallelLimit;
16281
16469
  exports.prefixWithAOrAn = prefixWithAOrAn;
16470
+ exports.prompt = prompt;
16282
16471
  exports.roundToNumDecimals = roundToNumDecimals;
16283
16472
  exports.setClientEventMetadataPopulator = setClientEventMetadataPopulator;
16284
16473
  exports.showFatalError = showFatalError;