dce-reactkit 4.0.1 → 4.1.0

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 (37) hide show
  1. package/dist/cjs/index.js +662 -771
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/Pagination.d.ts +13 -0
  4. package/dist/cjs/types/constants/LOG_REVIEW_GET_LOGS_ROUTE.d.ts +6 -0
  5. package/dist/cjs/types/helpers/cloneDeep.d.ts +8 -0
  6. package/dist/cjs/types/index.d.ts +4 -1
  7. package/dist/cjs/types/types/LogReviewerFilterState/ActionErrorFilterState.d.ts +17 -0
  8. package/dist/cjs/types/types/LogReviewerFilterState/AdvancedFilterState.d.ts +21 -0
  9. package/dist/cjs/types/types/LogReviewerFilterState/ContextFilterState.d.ts +10 -0
  10. package/dist/cjs/types/types/LogReviewerFilterState/DateFilterState.d.ts +17 -0
  11. package/dist/cjs/types/types/LogReviewerFilterState/TagFilterState.d.ts +8 -0
  12. package/dist/cjs/types/types/LogReviewerFilterState/index.d.ts +17 -0
  13. package/dist/esm/index.js +661 -773
  14. package/dist/esm/index.js.map +1 -1
  15. package/dist/esm/types/components/Pagination.d.ts +13 -0
  16. package/dist/esm/types/constants/LOG_REVIEW_GET_LOGS_ROUTE.d.ts +6 -0
  17. package/dist/esm/types/helpers/cloneDeep.d.ts +8 -0
  18. package/dist/esm/types/index.d.ts +4 -1
  19. package/dist/esm/types/types/LogReviewerFilterState/ActionErrorFilterState.d.ts +17 -0
  20. package/dist/esm/types/types/LogReviewerFilterState/AdvancedFilterState.d.ts +21 -0
  21. package/dist/esm/types/types/LogReviewerFilterState/ContextFilterState.d.ts +10 -0
  22. package/dist/esm/types/types/LogReviewerFilterState/DateFilterState.d.ts +17 -0
  23. package/dist/esm/types/types/LogReviewerFilterState/TagFilterState.d.ts +8 -0
  24. package/dist/esm/types/types/LogReviewerFilterState/index.d.ts +17 -0
  25. package/dist/index.d.ts +98 -1
  26. package/package.json +2 -1
  27. package/src/components/LogReviewer.tsx +1133 -1307
  28. package/src/components/Pagination.tsx +172 -0
  29. package/src/constants/LOG_REVIEW_GET_LOGS_ROUTE.ts +9 -0
  30. package/src/helpers/cloneDeep.ts +11 -0
  31. package/src/index.ts +6 -0
  32. package/src/types/LogReviewerFilterState/ActionErrorFilterState.ts +24 -0
  33. package/src/types/LogReviewerFilterState/AdvancedFilterState.ts +36 -0
  34. package/src/types/LogReviewerFilterState/ContextFilterState.ts +16 -0
  35. package/src/types/LogReviewerFilterState/DateFilterState.ts +26 -0
  36. package/src/types/LogReviewerFilterState/TagFilterState.ts +9 -0
  37. package/src/types/LogReviewerFilterState/index.ts +24 -0
package/dist/esm/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { useState, useRef, useEffect, useReducer, forwardRef, useContext, useLayoutEffect, createContext, useMemo, useCallback, Component, Fragment } from 'react';
3
- import { faExclamationTriangle, faCircle, faHourglassEnd, faDotCircle, faCheckSquare, faHourglass, faClipboard, faChevronDown, faChevronRight, faCloudDownloadAlt, faMinus, faCheckCircle, faXmarkCircle, faSort, faSortDown, faSortUp, faCalendar, faTag, faHammer, faList, faTimes, faSave, faTrash, faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
3
+ import { faExclamationTriangle, faCircle, faHourglassEnd, faDotCircle, faCheckSquare, faHourglass, faClipboard, faChevronDown, faChevronRight, faCloudDownloadAlt, faMinus, faCheckCircle, faXmarkCircle, faSort, faSortDown, faSortUp, faArrowLeft, faArrowRight, faCalendar, faTag, faHammer, faList, faTimes, faSearch, faSave, faTrash, faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
4
4
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5
5
  import { faCircle as faCircle$1, faSquareMinus, faSquare } from '@fortawesome/free-regular-svg-icons';
6
+ import clone from 'nanoclone';
6
7
 
7
8
  /*! *****************************************************************************
8
9
  Copyright (c) Microsoft Corporation.
@@ -3624,10 +3625,10 @@ const getHumanReadableDate = (dateOrTimestamp) => {
3624
3625
  };
3625
3626
 
3626
3627
  /**
3627
- * Path of the route for storing client-side logs
3628
+ * Path of the route for getting logs for log review
3628
3629
  * @author Gabe Abrams
3629
3630
  */
3630
- const LOG_REVIEW_ROUTE_PATH_PREFIX = `/admin${ROUTE_PATH_PREFIX}/logs`;
3631
+ const LOG_REVIEW_GET_LOGS_ROUTE = `/admin${ROUTE_PATH_PREFIX}/logs`;
3631
3632
 
3632
3633
  /**
3633
3634
  * Source of a log event
@@ -4232,6 +4233,71 @@ const IntelliTable = (props) => {
4232
4233
  } }, table)));
4233
4234
  };
4234
4235
 
4236
+ // Import React
4237
+ /*------------------------------------------------------------------------*/
4238
+ /* ------------------------------ Component ----------------------------- */
4239
+ /*------------------------------------------------------------------------*/
4240
+ const Pagination = (props) => {
4241
+ /*------------------------------------------------------------------------*/
4242
+ /* -------------------------------- Setup ------------------------------- */
4243
+ /*------------------------------------------------------------------------*/
4244
+ /* -------------- Props ------------- */
4245
+ // Destructure props
4246
+ const { currentPage, numPages, loading = false, onPageChanged, } = props;
4247
+ /*------------------------------------------------------------------------*/
4248
+ /* ------------------------------- Render ------------------------------- */
4249
+ /*------------------------------------------------------------------------*/
4250
+ // Compute pages to display
4251
+ const pages = [];
4252
+ const delta = 2; // how many pages to show on either side of current
4253
+ let start = Math.max(1, currentPage - delta);
4254
+ let end = Math.min(numPages, currentPage + delta);
4255
+ // If we are too close to the beginning or end, shift the window.
4256
+ if (currentPage - delta < 1) {
4257
+ end = Math.min(numPages, end + (1 - (currentPage - delta)));
4258
+ }
4259
+ if (currentPage + delta > numPages) {
4260
+ start = Math.max(1, start - ((currentPage + delta) - numPages));
4261
+ }
4262
+ for (let i = start; i <= end; i++) {
4263
+ pages.push(i);
4264
+ }
4265
+ // Render
4266
+ return (React__default.createElement("nav", { "aria-label": "Page navigation for logs", className: "mt-3" },
4267
+ React__default.createElement("ul", { className: "pagination justify-content-center" },
4268
+ React__default.createElement("li", { className: `page-item ${(currentPage <= 1 || loading) ? 'disabled' : ''}` },
4269
+ React__default.createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(currentPage - 1); }, disabled: currentPage <= 1 || loading, "aria-label": "Go to previous page" },
4270
+ React__default.createElement(FontAwesomeIcon, { icon: faArrowLeft }),
4271
+ ' ',
4272
+ "Prev")),
4273
+ (currentPage > 3 && pages[0] !== 1) && (React__default.createElement("li", { className: "page-item" },
4274
+ React__default.createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(1); }, disabled: loading, "aria-label": "Go to page 1" }, "1"))),
4275
+ currentPage > 4 && (React__default.createElement("li", { className: "page-item disabled" },
4276
+ React__default.createElement("span", { className: "page-link" }, "..."))),
4277
+ pages.map((pageNum) => {
4278
+ return (React__default.createElement("li", { key: pageNum, className: `page-item ${pageNum === currentPage ? 'active' : ''}` },
4279
+ React__default.createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(pageNum); }, disabled: loading, "aria-label": `Go to page ${pageNum}` }, pageNum)));
4280
+ }),
4281
+ currentPage < numPages - 3 && (React__default.createElement("li", { className: "page-item disabled" },
4282
+ React__default.createElement("span", { className: "page-link" }, "..."))),
4283
+ (currentPage < numPages - 2
4284
+ && pages[pages.length - 1] !== numPages) && (React__default.createElement("li", { className: "page-item" },
4285
+ React__default.createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(numPages); }, disabled: loading, "aria-label": "Go to last page" }, numPages))),
4286
+ React__default.createElement("li", { className: `page-item ${(currentPage >= numPages || loading) ? 'disabled' : ''}` },
4287
+ React__default.createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(currentPage + 1); }, disabled: currentPage >= numPages || loading, "aria-label": "Go to next page" },
4288
+ "Next",
4289
+ ' ',
4290
+ React__default.createElement(FontAwesomeIcon, { icon: faArrowRight }))))));
4291
+ };
4292
+
4293
+ /**
4294
+ * Deeply clones an object
4295
+ * @author Yuen Ler Chow
4296
+ * @param obj the object to clone
4297
+ * @returns a deep clone of the object
4298
+ */
4299
+ const cloneDeep = clone;
4300
+
4235
4301
  /**
4236
4302
  * Log reviewer panel that allows users (must be approved admins) to
4237
4303
  * review logs written by dce-reactkit
@@ -4587,7 +4653,7 @@ var ActionType$6;
4587
4653
  (function (ActionType) {
4588
4654
  // Show the loading bar
4589
4655
  ActionType["StartLoading"] = "start-loading";
4590
- // Finish loading one or more months of logs
4656
+ // Finish loading logs
4591
4657
  ActionType["FinishLoading"] = "finish-loading";
4592
4658
  // Reset filters to initial values
4593
4659
  ActionType["ResetFilters"] = "reset-filters";
@@ -4605,6 +4671,14 @@ var ActionType$6;
4605
4671
  ActionType["UpdateActionErrorFilterState"] = "update-action-error-filter-state";
4606
4672
  // Update the advanced filter state
4607
4673
  ActionType["UpdateAdvancedFilterState"] = "update-advanced-filter-state";
4674
+ // Set has another page
4675
+ ActionType["SetHasAnotherPage"] = "set-has-another-page";
4676
+ // Set the number of pages
4677
+ ActionType["SetNumPages"] = "set-num-pages";
4678
+ // Set page number
4679
+ ActionType["SetPageNumber"] = "set-page-number";
4680
+ // Reset user made filter change indicator
4681
+ ActionType["ResetUserMadeFilterChange"] = "reset-user-made-filter-change";
4608
4682
  })(ActionType$6 || (ActionType$6 = {}));
4609
4683
  /**
4610
4684
  * Reducer that executes actions
@@ -4618,7 +4692,7 @@ const reducer$7 = (state, action) => {
4618
4692
  return Object.assign(Object.assign({}, state), { loading: true });
4619
4693
  }
4620
4694
  case ActionType$6.FinishLoading: {
4621
- return Object.assign(Object.assign({}, state), { loading: false, logMap: action.logMap });
4695
+ return Object.assign(Object.assign({}, state), { loading: false, logs: action.logs });
4622
4696
  }
4623
4697
  case ActionType$6.ToggleFilterDrawer: {
4624
4698
  return Object.assign(Object.assign({}, state), { expandedFilterDrawer: (state.expandedFilterDrawer === action.filterDrawer
@@ -4629,22 +4703,34 @@ const reducer$7 = (state, action) => {
4629
4703
  return Object.assign(Object.assign({}, state), { expandedFilterDrawer: undefined });
4630
4704
  }
4631
4705
  case ActionType$6.ResetFilters: {
4632
- return Object.assign(Object.assign({}, state), { dateFilterState: action.initDateFilterState, contextFilterState: action.initContextFilterState, tagFilterState: action.initTagFilterState, actionErrorFilterState: action.initActionErrorFilterState, advancedFilterState: action.initAdvancedFilterState });
4706
+ return Object.assign(Object.assign({}, state), { pendingDateFilterState: action.initDateFilterState, pendingContextFilterState: action.initContextFilterState, pendingTagFilterState: action.initTagFilterState, pendingActionErrorFilterState: action.initActionErrorFilterState, pendingAdvancedFilterState: action.initAdvancedFilterState, pageNumber: 1 });
4633
4707
  }
4634
4708
  case ActionType$6.UpdateDateFilterState: {
4635
- return Object.assign(Object.assign({}, state), { dateFilterState: action.dateFilterState });
4709
+ return Object.assign(Object.assign({}, state), { pendingDateFilterState: action.dateFilterState, userMadeFilterChange: true });
4636
4710
  }
4637
4711
  case ActionType$6.UpdateContextFilterState: {
4638
- return Object.assign(Object.assign({}, state), { contextFilterState: action.contextFilterState });
4712
+ return Object.assign(Object.assign({}, state), { pendingContextFilterState: action.contextFilterState, userMadeFilterChange: true });
4639
4713
  }
4640
4714
  case ActionType$6.UpdateTagFilterState: {
4641
- return Object.assign(Object.assign({}, state), { tagFilterState: action.tagFilterState });
4715
+ return Object.assign(Object.assign({}, state), { pendingTagFilterState: action.tagFilterState, userMadeFilterChange: true });
4642
4716
  }
4643
4717
  case ActionType$6.UpdateActionErrorFilterState: {
4644
- return Object.assign(Object.assign({}, state), { actionErrorFilterState: action.actionErrorFilterState });
4718
+ return Object.assign(Object.assign({}, state), { pendingActionErrorFilterState: action.actionErrorFilterState, userMadeFilterChange: true });
4645
4719
  }
4646
4720
  case ActionType$6.UpdateAdvancedFilterState: {
4647
- return Object.assign(Object.assign({}, state), { advancedFilterState: action.advancedFilterState });
4721
+ return Object.assign(Object.assign({}, state), { pendingAdvancedFilterState: action.advancedFilterState, userMadeFilterChange: true });
4722
+ }
4723
+ case ActionType$6.SetHasAnotherPage: {
4724
+ return Object.assign(Object.assign({}, state), { hasAnotherPage: action.hasAnotherPage });
4725
+ }
4726
+ case ActionType$6.SetNumPages: {
4727
+ return Object.assign(Object.assign({}, state), { numPages: action.numPages });
4728
+ }
4729
+ case ActionType$6.SetPageNumber: {
4730
+ return Object.assign(Object.assign({}, state), { pageNumber: action.pageNumber });
4731
+ }
4732
+ case ActionType$6.ResetUserMadeFilterChange: {
4733
+ return Object.assign(Object.assign({}, state), { userMadeFilterChange: false });
4648
4734
  }
4649
4735
  default: {
4650
4736
  return state;
@@ -4715,7 +4801,7 @@ const LogReviewer = (props) => {
4715
4801
  else {
4716
4802
  // Case: subcontexts exist
4717
4803
  initContextFilterState[context] = {};
4718
- Object.values(contextMap[context]).forEach((subcontext) => {
4804
+ Object.keys(contextMap[context]).forEach((subcontext) => {
4719
4805
  // Skip self ("_")
4720
4806
  if (subcontext === '_') {
4721
4807
  return;
@@ -4763,125 +4849,105 @@ const LogReviewer = (props) => {
4763
4849
  // Initial state
4764
4850
  const initialState = {
4765
4851
  loading: true,
4766
- logMap: {},
4852
+ logs: [],
4767
4853
  expandedFilterDrawer: undefined,
4768
- dateFilterState: initDateFilterState,
4769
- contextFilterState: initContextFilterState,
4770
- tagFilterState: initTagFilterState,
4771
- actionErrorFilterState: initActionErrorFilterState,
4772
- advancedFilterState: initAdvancedFilterState,
4854
+ pendingDateFilterState: initDateFilterState,
4855
+ pendingContextFilterState: initContextFilterState,
4856
+ pendingTagFilterState: initTagFilterState,
4857
+ pendingActionErrorFilterState: initActionErrorFilterState,
4858
+ pendingAdvancedFilterState: initAdvancedFilterState,
4859
+ pageNumber: 1,
4860
+ hasAnotherPage: false,
4861
+ numPages: 1,
4862
+ userMadeFilterChange: false,
4773
4863
  };
4774
4864
  // Initialize state
4775
4865
  const [state, dispatch] = useReducer(reducer$7, initialState);
4776
4866
  // Destructure common state
4777
- const { loading, logMap, expandedFilterDrawer, dateFilterState, contextFilterState, tagFilterState, actionErrorFilterState, advancedFilterState, } = state;
4867
+ const { loading, logs, expandedFilterDrawer, pendingDateFilterState, pendingContextFilterState, pendingTagFilterState, pendingActionErrorFilterState, pendingAdvancedFilterState, pageNumber, numPages, userMadeFilterChange, } = state;
4868
+ /* -------------- Refs -------------- */
4869
+ // Initialize refs
4870
+ const activeFiltersRef = useRef({
4871
+ dateFilterState: JSON.parse(JSON.stringify(pendingDateFilterState)),
4872
+ contextFilterState: JSON.parse(JSON.stringify(pendingContextFilterState)),
4873
+ tagFilterState: JSON.parse(JSON.stringify(pendingTagFilterState)),
4874
+ actionErrorFilterState: JSON.parse(JSON.stringify(pendingActionErrorFilterState)),
4875
+ advancedFilterState: JSON.parse(JSON.stringify(pendingAdvancedFilterState)),
4876
+ });
4778
4877
  /*------------------------------------------------------------------------*/
4779
4878
  /* ------------------------- Component Functions ------------------------ */
4780
4879
  /*------------------------------------------------------------------------*/
4781
4880
  /**
4782
- * Get the list of year/month combos that need to be loaded given a new
4783
- * start or end date and the existing logMap
4784
- * @author Gabe Abrams
4785
- * @param newDateFilterState the new date filter state
4786
- * @returns list of year/month combos that need to be loaded
4787
- */
4788
- const listMonthsToLoad = (newDateFilterState) => {
4789
- // List of year/month combos that need to be loaded
4790
- const toLoad = [];
4791
- // Loop through dates
4792
- let { year, month } = newDateFilterState.startDate;
4793
- while (
4794
- // Earlier year
4795
- (year < newDateFilterState.endDate.year)
4796
- // Current year but included month
4797
- || (year === newDateFilterState.endDate.year
4798
- && month <= newDateFilterState.endDate.month)) {
4799
- // Add to list if not already loaded
4800
- if (!logMap[year]
4801
- || !logMap[year][month]) {
4802
- toLoad.push({
4803
- year,
4804
- month,
4805
- });
4806
- }
4807
- // Increment
4808
- month += 1;
4809
- if (month > 12) {
4810
- month -= 12;
4811
- year += 1;
4812
- }
4813
- }
4814
- // Return
4815
- return toLoad;
4816
- };
4817
- /**
4818
- * Handle updated start/end dates (updates state, loads if necessary)
4819
- * @author Gabe Abrams
4820
- * @param newDateFilterState the new date filter state
4881
+ * Fetch logs from the server based on current filters
4882
+ * @author Yuen Ler Chow
4883
+ * @param opts object containing all arguments
4884
+ * @param opts.filters the filters to apply
4885
+ * @param opts.pageNum the page number to fetch
4886
+ * @param opts.filtersChanged if true, the filters have changed
4821
4887
  */
4822
- const handleDateRangeUpdated = (newDateFilterState) => __awaiter(void 0, void 0, void 0, function* () {
4823
- // Update state
4824
- dispatch({
4825
- type: ActionType$6.UpdateDateFilterState,
4826
- dateFilterState: newDateFilterState,
4827
- });
4828
- // Check which year/month combos we need to load
4829
- const toLoad = listMonthsToLoad(newDateFilterState);
4830
- // If nothing to load, finished
4831
- if (toLoad.length === 0) {
4832
- return;
4833
- }
4834
- // Start loading
4888
+ const fetchLogs = (opts) => __awaiter(void 0, void 0, void 0, function* () {
4889
+ const { filters, pageNum, filtersChanged, } = opts;
4835
4890
  dispatch({
4836
4891
  type: ActionType$6.StartLoading,
4837
4892
  });
4838
- // Load required months
4839
4893
  try {
4840
- for (let i = 0; i < toLoad.length; i++) {
4841
- // Destructure
4842
- const { year, month } = toLoad[i];
4843
- // Load
4844
- let logs = [];
4845
- let pageNumber = 1;
4846
- let hasAnotherPage = true;
4847
- while (hasAnotherPage) {
4848
- const response = yield visitServerEndpoint({
4849
- path: `${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/${year}/months/${month}`,
4850
- method: 'GET',
4851
- params: {
4852
- pageNumber,
4853
- },
4854
- });
4855
- logs = logs.concat(response.items);
4856
- hasAnotherPage = response.hasAnotherPage;
4857
- pageNumber += 1;
4858
- }
4859
- // Add to map
4860
- if (!logMap[year]) {
4861
- logMap[year] = {};
4862
- }
4863
- logMap[year][month] = logs;
4894
+ // Send filters to the server
4895
+ let fetchedLogs = [];
4896
+ // Get logs from server
4897
+ const response = yield visitServerEndpoint({
4898
+ path: LOG_REVIEW_GET_LOGS_ROUTE,
4899
+ method: 'GET',
4900
+ params: {
4901
+ pageNumber: pageNum,
4902
+ filters,
4903
+ countDocuments: filtersChanged,
4904
+ },
4905
+ });
4906
+ fetchedLogs = fetchedLogs.concat(response.items);
4907
+ dispatch({
4908
+ type: ActionType$6.SetHasAnotherPage,
4909
+ hasAnotherPage: response.hasAnotherPage,
4910
+ });
4911
+ if (filtersChanged && response.numPages !== undefined) {
4912
+ dispatch({
4913
+ type: ActionType$6.SetNumPages,
4914
+ numPages: response.numPages,
4915
+ });
4864
4916
  }
4917
+ // Update logs in state
4918
+ dispatch({
4919
+ type: ActionType$6.FinishLoading,
4920
+ logs: fetchedLogs,
4921
+ });
4922
+ // Update page number
4923
+ dispatch({
4924
+ type: ActionType$6.SetPageNumber,
4925
+ pageNumber: pageNum,
4926
+ });
4865
4927
  }
4866
4928
  catch (err) {
4867
4929
  return showFatalError(err);
4868
4930
  }
4869
- // Finish loading
4870
- dispatch({
4871
- type: ActionType$6.FinishLoading,
4872
- logMap,
4873
- });
4874
4931
  });
4875
4932
  /*------------------------------------------------------------------------*/
4876
4933
  /* ------------------------- Lifecycle Functions ------------------------ */
4877
4934
  /*------------------------------------------------------------------------*/
4878
4935
  /**
4879
- * Mount
4880
- * @author Gabe Abrams
4936
+ * Fetch logs on mount
4937
+ * @author Yuen Ler Chow
4881
4938
  */
4882
4939
  useEffect(() => {
4883
- // Perform initial load
4884
- handleDateRangeUpdated(dateFilterState);
4940
+ fetchLogs({
4941
+ filters: {
4942
+ dateFilterState: pendingDateFilterState,
4943
+ contextFilterState: pendingContextFilterState,
4944
+ tagFilterState: pendingTagFilterState,
4945
+ actionErrorFilterState: pendingActionErrorFilterState,
4946
+ advancedFilterState: pendingAdvancedFilterState,
4947
+ },
4948
+ pageNum: 1,
4949
+ filtersChanged: true,
4950
+ });
4885
4951
  }, []);
4886
4952
  /*------------------------------------------------------------------------*/
4887
4953
  /* ------------------------------- Render ------------------------------- */
@@ -4889,718 +4955,534 @@ const LogReviewer = (props) => {
4889
4955
  /*----------------------------------------*/
4890
4956
  /* --------------- Main UI -------------- */
4891
4957
  /*----------------------------------------*/
4892
- // Body that will be filled with the contents of the panel
4893
- let body;
4894
- /* ------------- Loading ------------ */
4895
- if (loading) {
4896
- body = (React__default.createElement("div", { className: "text-center p-5" },
4897
- React__default.createElement(LoadingSpinner, null)));
4898
- }
4899
- /* ------------ Review UI ----------- */
4900
- if (!loading) {
4901
- /*----------------------------------------*/
4902
- /* --------------- Filters -------------- */
4903
- /*----------------------------------------*/
4904
- // Filter toggle
4905
- const filterToggles = (React__default.createElement("div", { className: "LogReviewer-filter-toggles" },
4906
- React__default.createElement("h3", { className: "m-0" }, "Filters:"),
4907
- React__default.createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
4908
- React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-date-filter-drawer", className: `btn btn-${FilterDrawer.Date === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle date filter drawer", onClick: () => {
4909
- dispatch({
4910
- type: ActionType$6.ToggleFilterDrawer,
4911
- filterDrawer: FilterDrawer.Date,
4912
- });
4913
- } },
4914
- React__default.createElement(FontAwesomeIcon, { icon: faCalendar, className: "me-2" }),
4915
- "Date"),
4916
- React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-context-filter-drawer", className: `btn btn-${FilterDrawer.Context === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle context filter drawer", onClick: () => {
4917
- dispatch({
4918
- type: ActionType$6.ToggleFilterDrawer,
4919
- filterDrawer: FilterDrawer.Context,
4920
- });
4921
- } },
4922
- React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "me-2" }),
4923
- "Context"),
4924
- (LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-tag-filter-drawer", className: `btn btn-${FilterDrawer.Tag === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle tag filter drawer", onClick: () => {
4925
- dispatch({
4926
- type: ActionType$6.ToggleFilterDrawer,
4927
- filterDrawer: FilterDrawer.Tag,
4928
- });
4929
- } },
4930
- React__default.createElement(FontAwesomeIcon, { icon: faTag, className: "me-2" }),
4931
- "Tag")),
4932
- React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-action-filter-drawer", className: `btn btn-${FilterDrawer.Action === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle action and error filter drawer", onClick: () => {
4933
- dispatch({
4934
- type: ActionType$6.ToggleFilterDrawer,
4935
- filterDrawer: FilterDrawer.Action,
4936
- });
4937
- } },
4938
- React__default.createElement(FontAwesomeIcon, { icon: faHammer, className: "me-2" }),
4939
- "Action"),
4940
- React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-advanced-filter-drawer", className: `btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle advanced filter drawer", onClick: () => {
4958
+ /*----------------------------------------*/
4959
+ /* ------------ Pagination -------------- */
4960
+ /*----------------------------------------*/
4961
+ const paginationControls = logs.length > 0 && (React__default.createElement(Pagination, { currentPage: pageNumber, numPages: numPages, loading: loading, onPageChanged: (targetPage) => {
4962
+ const { current: activeFilters } = activeFiltersRef;
4963
+ fetchLogs({
4964
+ filters: {
4965
+ dateFilterState: activeFilters.dateFilterState,
4966
+ contextFilterState: activeFilters.contextFilterState,
4967
+ tagFilterState: activeFilters.tagFilterState,
4968
+ actionErrorFilterState: activeFilters.actionErrorFilterState,
4969
+ advancedFilterState: activeFilters.advancedFilterState,
4970
+ },
4971
+ pageNum: targetPage,
4972
+ filtersChanged: false,
4973
+ });
4974
+ } }));
4975
+ /*----------------------------------------*/
4976
+ /* --------------- Filters -------------- */
4977
+ /*----------------------------------------*/
4978
+ // Filter toggle
4979
+ const filterToggles = (React__default.createElement("div", { className: "LogReviewer-filter-toggles" },
4980
+ React__default.createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
4981
+ React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-date-filter-drawer", className: `btn btn-${FilterDrawer.Date === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle date filter drawer", onClick: () => {
4982
+ dispatch({
4983
+ type: ActionType$6.ToggleFilterDrawer,
4984
+ filterDrawer: FilterDrawer.Date,
4985
+ });
4986
+ } },
4987
+ React__default.createElement(FontAwesomeIcon, { icon: faCalendar, className: "me-2" }),
4988
+ "Date"),
4989
+ React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-context-filter-drawer", className: `btn btn-${FilterDrawer.Context === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle context filter drawer", onClick: () => {
4990
+ dispatch({
4991
+ type: ActionType$6.ToggleFilterDrawer,
4992
+ filterDrawer: FilterDrawer.Context,
4993
+ });
4994
+ } },
4995
+ React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "me-2" }),
4996
+ "Context"),
4997
+ (LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-tag-filter-drawer", className: `btn btn-${FilterDrawer.Tag === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle tag filter drawer", onClick: () => {
4998
+ dispatch({
4999
+ type: ActionType$6.ToggleFilterDrawer,
5000
+ filterDrawer: FilterDrawer.Tag,
5001
+ });
5002
+ } },
5003
+ React__default.createElement(FontAwesomeIcon, { icon: faTag, className: "me-2" }),
5004
+ "Tag")),
5005
+ React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-action-filter-drawer", className: `btn btn-${FilterDrawer.Action === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle action and error filter drawer", onClick: () => {
5006
+ dispatch({
5007
+ type: ActionType$6.ToggleFilterDrawer,
5008
+ filterDrawer: FilterDrawer.Action,
5009
+ });
5010
+ } },
5011
+ React__default.createElement(FontAwesomeIcon, { icon: faHammer, className: "me-2" }),
5012
+ "Action"),
5013
+ React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-advanced-filter-drawer", className: `btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle advanced filter drawer", onClick: () => {
5014
+ dispatch({
5015
+ type: ActionType$6.ToggleFilterDrawer,
5016
+ filterDrawer: FilterDrawer.Advanced,
5017
+ });
5018
+ } },
5019
+ React__default.createElement(FontAwesomeIcon, { icon: faList, className: "me-2" }),
5020
+ "Advanced"),
5021
+ React__default.createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
5022
+ // Save active filters
5023
+ // Deep clone the initial filter states to avoid any reference issues
5024
+ // if the filter states are modified later
5025
+ activeFiltersRef.current = cloneDeep({
5026
+ dateFilterState: initDateFilterState,
5027
+ contextFilterState: initContextFilterState,
5028
+ tagFilterState: initTagFilterState,
5029
+ actionErrorFilterState: initActionErrorFilterState,
5030
+ advancedFilterState: initAdvancedFilterState,
5031
+ });
5032
+ fetchLogs({
5033
+ filters: {
5034
+ dateFilterState: initDateFilterState,
5035
+ contextFilterState: initContextFilterState,
5036
+ tagFilterState: initTagFilterState,
5037
+ actionErrorFilterState: initActionErrorFilterState,
5038
+ advancedFilterState: initAdvancedFilterState,
5039
+ },
5040
+ pageNum: 1,
5041
+ filtersChanged: true,
5042
+ });
5043
+ dispatch({
5044
+ type: ActionType$6.ResetFilters,
5045
+ initActionErrorFilterState,
5046
+ initAdvancedFilterState,
5047
+ initContextFilterState,
5048
+ initDateFilterState,
5049
+ initTagFilterState,
5050
+ });
5051
+ dispatch({
5052
+ type: ActionType$6.HideFilterDrawer,
5053
+ });
5054
+ } },
5055
+ React__default.createElement(FontAwesomeIcon, { icon: faTimes }),
5056
+ ' ',
5057
+ "Reset"),
5058
+ userMadeFilterChange && (React__default.createElement("button", { type: "button", id: "LogReviewer-submit-filters-button", className: "btn btn-primary ms-2", "aria-label": "submit filters", onClick: () => {
5059
+ dispatch({
5060
+ type: ActionType$6.HideFilterDrawer,
5061
+ });
5062
+ // Reset user made filter change indicator
5063
+ dispatch({
5064
+ type: ActionType$6.ResetUserMadeFilterChange,
5065
+ });
5066
+ // Save active filters
5067
+ // Deep clone the pending filter states to avoid any reference issues
5068
+ // if the filter states are modified later
5069
+ activeFiltersRef.current = cloneDeep({
5070
+ dateFilterState: pendingDateFilterState,
5071
+ contextFilterState: pendingContextFilterState,
5072
+ tagFilterState: pendingTagFilterState,
5073
+ actionErrorFilterState: pendingActionErrorFilterState,
5074
+ advancedFilterState: pendingAdvancedFilterState,
5075
+ });
5076
+ fetchLogs({
5077
+ filters: {
5078
+ dateFilterState: pendingDateFilterState,
5079
+ contextFilterState: pendingContextFilterState,
5080
+ tagFilterState: pendingTagFilterState,
5081
+ actionErrorFilterState: pendingActionErrorFilterState,
5082
+ advancedFilterState: pendingAdvancedFilterState,
5083
+ },
5084
+ pageNum: 1,
5085
+ filtersChanged: true,
5086
+ });
5087
+ } },
5088
+ React__default.createElement(FontAwesomeIcon, { icon: faSearch }),
5089
+ ' ',
5090
+ "Apply Filters")))));
5091
+ // Filter drawer
5092
+ let filterDrawer;
5093
+ if (expandedFilterDrawer) {
5094
+ if (expandedFilterDrawer === FilterDrawer.Date) {
5095
+ filterDrawer = (React__default.createElement(TabBox, { title: "Date" },
5096
+ React__default.createElement(SimpleDateChooser, { ariaLabel: "filter start date", name: "filter-start-date", year: pendingDateFilterState.startDate.year, month: pendingDateFilterState.startDate.month, day: pendingDateFilterState.startDate.day, dontAllowFuture: true, numMonthsToShow: 36, onChange: (month, day, year) => {
5097
+ const newDateFilterState = Object.assign(Object.assign({}, pendingDateFilterState), { startDate: { month, day, year } });
4941
5098
  dispatch({
4942
- type: ActionType$6.ToggleFilterDrawer,
4943
- filterDrawer: FilterDrawer.Advanced,
5099
+ type: ActionType$6.UpdateDateFilterState,
5100
+ dateFilterState: newDateFilterState,
4944
5101
  });
4945
- } },
4946
- React__default.createElement(FontAwesomeIcon, { icon: faList, className: "me-2" }),
4947
- "Advanced"),
4948
- React__default.createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
5102
+ } }),
5103
+ ' ',
5104
+ "to",
5105
+ ' ',
5106
+ React__default.createElement(SimpleDateChooser, { ariaLabel: "filter end date", name: "filter-end-date", year: pendingDateFilterState.endDate.year, month: pendingDateFilterState.endDate.month, day: pendingDateFilterState.endDate.day, dontAllowFuture: true, numMonthsToShow: 12, onChange: (month, day, year) => {
5107
+ if (year < pendingDateFilterState.startDate.year
5108
+ || (year === pendingDateFilterState.startDate.year
5109
+ && month < pendingDateFilterState.startDate.month)
5110
+ || (year === pendingDateFilterState.startDate.year
5111
+ && month === pendingDateFilterState.startDate.month
5112
+ && day < pendingDateFilterState.startDate.day)) {
5113
+ return alert('Invalid End Date', 'The end date cannot be before the start date.');
5114
+ }
5115
+ const newDateFilterState = Object.assign(Object.assign({}, pendingDateFilterState), { endDate: { month, day, year } });
4949
5116
  dispatch({
4950
- type: ActionType$6.ResetFilters,
4951
- initActionErrorFilterState,
4952
- initAdvancedFilterState,
4953
- initContextFilterState,
4954
- initDateFilterState,
4955
- initTagFilterState,
5117
+ type: ActionType$6.UpdateDateFilterState,
5118
+ dateFilterState: newDateFilterState,
4956
5119
  });
4957
- } },
4958
- React__default.createElement(FontAwesomeIcon, { icon: faTimes }),
4959
- ' ',
4960
- "Reset"))));
4961
- // Filter drawer
4962
- let filterDrawer;
4963
- if (expandedFilterDrawer) {
4964
- if (expandedFilterDrawer === FilterDrawer.Date) {
4965
- filterDrawer = (React__default.createElement(TabBox, { title: "Date" },
4966
- React__default.createElement(SimpleDateChooser, { ariaLabel: "filter start date", name: "filter-start-date", year: dateFilterState.startDate.year, month: dateFilterState.startDate.month, day: dateFilterState.startDate.day, dontAllowFuture: true, numMonthsToShow: 36, onChange: (month, day, year) => {
4967
- dateFilterState.startDate = { month, day, year };
4968
- handleDateRangeUpdated(dateFilterState);
4969
- } }),
4970
- ' ',
4971
- "to",
4972
- ' ',
4973
- React__default.createElement(SimpleDateChooser, { ariaLabel: "filter end date", name: "filter-end-date", year: dateFilterState.endDate.year, month: dateFilterState.endDate.month, day: dateFilterState.endDate.day, dontAllowFuture: true, numMonthsToShow: 12, onChange: (month, day, year) => {
4974
- if (year < dateFilterState.startDate.year
4975
- || (year === dateFilterState.startDate.year
4976
- && month < dateFilterState.startDate.month)
4977
- || (year === dateFilterState.startDate.year
4978
- && month === dateFilterState.startDate.month
4979
- && day < dateFilterState.startDate.day)) {
4980
- return alert('Invalid Start Date', 'The start date cannot be before the end date.');
4981
- }
4982
- dateFilterState.endDate = { month, day, year };
4983
- handleDateRangeUpdated(dateFilterState);
4984
- } })));
4985
- }
4986
- else if (expandedFilterDrawer === FilterDrawer.Context) {
4987
- // Create item picker items
4988
- const builtInPickableItem = {
4989
- id: 'built-in-contexts',
4990
- name: 'Auto-logged',
4991
- isGroup: true,
4992
- children: [],
4993
- };
4994
- const pickableItems = [];
4995
- Object.keys(contextMap)
4996
- .forEach((context) => {
4997
- const value = contextMap[context];
4998
- if (typeof value === 'string') {
4999
- // No subcategories
5000
- const item = {
5001
- id: context,
5002
- name: genHumanReadableName(context),
5003
- isGroup: false,
5004
- checked: !!contextFilterState[context],
5005
- };
5006
- // Add built-in items to its own folder
5007
- const isBuiltIn = context in LogBuiltInMetadata.Context;
5008
- if (isBuiltIn) {
5009
- // Add to built-in pickable item
5010
- builtInPickableItem.children.push(item);
5011
- }
5012
- else {
5013
- // Add to pickable items list
5014
- pickableItems.push(item);
5015
- }
5016
- return;
5017
- }
5018
- // Has subcategories
5019
- const children = (Object.keys(value)
5020
- // Remove parent name
5021
- .filter((subcontext) => {
5022
- return subcontext !== '_';
5023
- })
5024
- // Create child pickable items
5025
- .map((subcontext) => {
5026
- return {
5027
- id: subcontext,
5028
- name: genHumanReadableName(subcontext),
5029
- isGroup: false,
5030
- checked: contextFilterState[context][subcontext],
5031
- };
5032
- }));
5120
+ } })));
5121
+ }
5122
+ else if (expandedFilterDrawer === FilterDrawer.Context) {
5123
+ // Create item picker items
5124
+ const builtInPickableItem = {
5125
+ id: 'built-in-contexts',
5126
+ name: 'Auto-logged',
5127
+ isGroup: true,
5128
+ children: [],
5129
+ };
5130
+ const pickableItems = [];
5131
+ Object.keys(contextMap)
5132
+ .forEach((context) => {
5133
+ const value = contextMap[context];
5134
+ if (typeof value === 'string') {
5135
+ // No subcategories
5033
5136
  const item = {
5034
5137
  id: context,
5035
5138
  name: genHumanReadableName(context),
5036
- isGroup: true,
5037
- children,
5139
+ isGroup: false,
5140
+ checked: !!pendingContextFilterState[context],
5038
5141
  };
5039
- pickableItems.push(item);
5040
- });
5041
- // Add built-in contexts to end ofl ist
5042
- pickableItems.push(builtInPickableItem);
5043
- // Create filter UI
5044
- filterDrawer = (React__default.createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
5045
- // Update our state
5046
- updatedItems.forEach((pickableItem) => {
5047
- if (pickableItem.isGroup) {
5048
- // Has subcontexts
5049
- if (pickableItem.id === 'built-in-contexts') {
5050
- // Built-in
5051
- // Treat as if these were top-level contexts
5052
- pickableItem.children.forEach((subcontextItem) => {
5053
- contextFilterState[subcontextItem.id] = ('checked' in subcontextItem
5054
- && subcontextItem.checked);
5055
- });
5056
- }
5057
- else {
5058
- // Not built-in
5059
- pickableItem.children.forEach((subcontextItem) => {
5060
- if (!subcontextItem.isGroup) {
5061
- contextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
5062
- }
5063
- });
5064
- }
5142
+ // Add built-in items to its own folder
5143
+ const isBuiltIn = context in LogBuiltInMetadata.Context;
5144
+ if (isBuiltIn) {
5145
+ // Add to built-in pickable item
5146
+ builtInPickableItem.children.push(item);
5147
+ }
5148
+ else {
5149
+ // Add to pickable items list
5150
+ pickableItems.push(item);
5151
+ }
5152
+ return;
5153
+ }
5154
+ // Has subcategories
5155
+ const children = (Object.keys(value)
5156
+ // Remove parent name
5157
+ .filter((subcontext) => {
5158
+ return (subcontext !== '_');
5159
+ })
5160
+ // Create child pickable items
5161
+ .map((subcontext) => {
5162
+ return {
5163
+ id: subcontext,
5164
+ name: genHumanReadableName(subcontext),
5165
+ isGroup: false,
5166
+ checked: pendingContextFilterState[context][subcontext],
5167
+ };
5168
+ }));
5169
+ const item = {
5170
+ id: context,
5171
+ name: genHumanReadableName(context),
5172
+ isGroup: true,
5173
+ children,
5174
+ };
5175
+ pickableItems.push(item);
5176
+ });
5177
+ // Add built-in contexts to end of list
5178
+ pickableItems.push(builtInPickableItem);
5179
+ // Create filter UI
5180
+ filterDrawer = (React__default.createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
5181
+ // Update our state
5182
+ const newContextFilterState = Object.assign({}, pendingContextFilterState);
5183
+ updatedItems.forEach((pickableItem) => {
5184
+ if (pickableItem.isGroup) {
5185
+ // Has subcontexts
5186
+ if (pickableItem.id === 'built-in-contexts') {
5187
+ // Built-in
5188
+ // Treat as if these were top-level contexts
5189
+ pickableItem.children.forEach((subcontextItem) => {
5190
+ newContextFilterState[subcontextItem.id] = ('checked' in subcontextItem
5191
+ && subcontextItem.checked);
5192
+ });
5065
5193
  }
5066
5194
  else {
5067
- // No subcontexts
5068
- contextFilterState[pickableItem.id] = (pickableItem.checked);
5195
+ // Not built-in
5196
+ pickableItem.children.forEach((subcontextItem) => {
5197
+ if (!subcontextItem.isGroup) {
5198
+ newContextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
5199
+ }
5200
+ });
5069
5201
  }
5070
- });
5071
- dispatch({
5072
- type: ActionType$6.UpdateContextFilterState,
5073
- contextFilterState,
5074
- });
5075
- } }));
5076
- }
5077
- else if (expandedFilterDrawer === FilterDrawer.Tag) {
5078
- // Create filter UI
5079
- filterDrawer = (React__default.createElement(TabBox, { title: "Tags" },
5080
- React__default.createElement("div", null, "If any tags are selected, logs must contain at least one (but not necessarily all) of the selected tags."),
5081
- React__default.createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {})
5082
- .map((tag) => {
5083
- const description = genHumanReadableName(tag);
5084
- return (React__default.createElement(CheckboxButton, { key: tag, id: `LogReviewer-tag-${tag}-checkbox`, text: description, ariaLabel: `require that logs be tagged with "${description}" or any other selected tag`, checked: tagFilterState[tag], onChanged: (checked) => {
5085
- tagFilterState[tag] = checked;
5202
+ }
5203
+ else {
5204
+ // No subcontexts
5205
+ newContextFilterState[pickableItem.id] = (pickableItem.checked);
5206
+ }
5207
+ });
5208
+ // Update state
5209
+ dispatch({
5210
+ type: ActionType$6.UpdateContextFilterState,
5211
+ contextFilterState: newContextFilterState,
5212
+ });
5213
+ } }));
5214
+ }
5215
+ else if (expandedFilterDrawer === FilterDrawer.Tag) {
5216
+ // Create filter UI
5217
+ filterDrawer = (React__default.createElement(TabBox, { title: "Tags" },
5218
+ React__default.createElement("div", null, "If any tags are selected, logs must contain at least one (but not necessarily all) of the selected tags."),
5219
+ React__default.createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {})
5220
+ .map((tag) => {
5221
+ const description = genHumanReadableName(tag);
5222
+ return (React__default.createElement(CheckboxButton, { key: tag, id: `LogReviewer-tag-${tag}-checkbox`, text: description, ariaLabel: `require that logs be tagged with "${description}" or any other selected tag`, checked: pendingTagFilterState[tag], onChanged: (checked) => {
5223
+ const newTagFilterState = Object.assign(Object.assign({}, pendingTagFilterState), { [tag]: checked });
5224
+ dispatch({
5225
+ type: ActionType$6.UpdateTagFilterState,
5226
+ tagFilterState: newTagFilterState,
5227
+ });
5228
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5229
+ }))));
5230
+ }
5231
+ else if (expandedFilterDrawer === FilterDrawer.Action) {
5232
+ // Create filter UI
5233
+ filterDrawer = (React__default.createElement(React__default.Fragment, null,
5234
+ React__default.createElement(TabBox, { title: "Log Type" },
5235
+ React__default.createElement(RadioButton, { id: "LogReviewer-type-all", text: "All Logs", onSelected: () => {
5236
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: undefined });
5237
+ dispatch({
5238
+ type: ActionType$6.UpdateActionErrorFilterState,
5239
+ actionErrorFilterState: newActionErrorFilterState,
5240
+ });
5241
+ }, ariaLabel: "show logs of all types", selected: pendingActionErrorFilterState.type === undefined, unselectedVariant: Variant$1.Light }),
5242
+ React__default.createElement(RadioButton, { id: "LogReviewer-type-action-only", text: "Action Logs Only", onSelected: () => {
5243
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: LogType$1.Action });
5244
+ dispatch({
5245
+ type: ActionType$6.UpdateActionErrorFilterState,
5246
+ actionErrorFilterState: newActionErrorFilterState,
5247
+ });
5248
+ }, ariaLabel: "only show action logs", selected: pendingActionErrorFilterState.type === LogType$1.Action, unselectedVariant: Variant$1.Light }),
5249
+ React__default.createElement(RadioButton, { id: "LogReviewer-type-error-only", text: "Action Error Only", onSelected: () => {
5250
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: LogType$1.Error });
5251
+ dispatch({
5252
+ type: ActionType$6.UpdateActionErrorFilterState,
5253
+ actionErrorFilterState: newActionErrorFilterState,
5254
+ });
5255
+ }, ariaLabel: "only show error logs", selected: pendingActionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light })),
5256
+ (pendingActionErrorFilterState.type === undefined
5257
+ || pendingActionErrorFilterState.type === LogType$1.Action) && (React__default.createElement(TabBox, { title: "Action Log Details" },
5258
+ React__default.createElement(ButtonInputGroup, { label: "Action", className: "mb-2", wrapButtonsAndAddGaps: true }, Object.keys(LogAction$1)
5259
+ .map((action) => {
5260
+ const description = genHumanReadableName(action);
5261
+ return (React__default.createElement(CheckboxButton, { key: action, id: `LogReviewer-action-${action}-checkbox`, text: description, ariaLabel: `include logs with action type "${description}" in results`, noMarginOnRight: true, checked: pendingActionErrorFilterState.action[action], onChanged: (checked) => {
5262
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { action: Object.assign(Object.assign({}, pendingActionErrorFilterState.action), { [action]: checked }) });
5086
5263
  dispatch({
5087
- type: ActionType$6.UpdateTagFilterState,
5088
- tagFilterState,
5264
+ type: ActionType$6.UpdateActionErrorFilterState,
5265
+ actionErrorFilterState: newActionErrorFilterState,
5089
5266
  });
5090
- } }));
5091
- }))));
5092
- }
5093
- else if (expandedFilterDrawer === FilterDrawer.Action) {
5094
- // Create filter UI
5095
- filterDrawer = (React__default.createElement(React__default.Fragment, null,
5096
- React__default.createElement(TabBox, { title: "Log Type" },
5097
- React__default.createElement(RadioButton, { id: "LogReviewer-type-all", text: "All Logs", onSelected: () => {
5098
- actionErrorFilterState.type = undefined;
5267
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5268
+ })),
5269
+ React__default.createElement(ButtonInputGroup, { label: "Target", wrapButtonsAndAddGaps: true }, Object.keys(targetMap)
5270
+ .map((target) => {
5271
+ const description = genHumanReadableName(target);
5272
+ return (React__default.createElement(CheckboxButton, { key: target, id: `LogReviewer-target-${target}-checkbox`, text: description, ariaLabel: `include logs with target "${description}" in results`, checked: pendingActionErrorFilterState.target[target], noMarginOnRight: true, onChanged: (checked) => {
5273
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { target: Object.assign(Object.assign({}, pendingActionErrorFilterState.target), { [target]: checked }) });
5099
5274
  dispatch({
5100
5275
  type: ActionType$6.UpdateActionErrorFilterState,
5101
- actionErrorFilterState,
5276
+ actionErrorFilterState: newActionErrorFilterState,
5102
5277
  });
5103
- }, ariaLabel: "show logs of all types", selected: actionErrorFilterState.type === undefined }),
5104
- React__default.createElement(RadioButton, { id: "LogReviewer-type-action-only", text: "Action Logs Only", onSelected: () => {
5105
- actionErrorFilterState.type = LogType$1.Action;
5278
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5279
+ })))),
5280
+ (pendingActionErrorFilterState.type === undefined
5281
+ || pendingActionErrorFilterState.type === LogType$1.Error) && (React__default.createElement(TabBox, { title: "Error Log Details" },
5282
+ React__default.createElement("div", { className: "input-group mb-2" },
5283
+ React__default.createElement("span", { className: "input-group-text" }, "Error Message"),
5284
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for error message", value: pendingActionErrorFilterState.errorMessage, placeholder: "e.g. undefined is not a function", onChange: (e) => {
5285
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { errorMessage: e.target.value });
5106
5286
  dispatch({
5107
5287
  type: ActionType$6.UpdateActionErrorFilterState,
5108
- actionErrorFilterState,
5288
+ actionErrorFilterState: newActionErrorFilterState,
5109
5289
  });
5110
- }, ariaLabel: "only show action logs", selected: actionErrorFilterState.type === LogType$1.Action }),
5111
- React__default.createElement(RadioButton, { id: "LogReviewer-type-error-only", text: "Action Error Only", onSelected: () => {
5112
- actionErrorFilterState.type = LogType$1.Error;
5290
+ } })),
5291
+ React__default.createElement("div", { className: "input-group mb-2" },
5292
+ React__default.createElement("span", { className: "input-group-text" }, "Error Code"),
5293
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for error code", value: pendingActionErrorFilterState.errorCode, placeholder: "e.g. GC22", onChange: (e) => {
5294
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { errorCode: ((e.target.value)
5295
+ .trim()
5296
+ .toUpperCase()) });
5113
5297
  dispatch({
5114
5298
  type: ActionType$6.UpdateActionErrorFilterState,
5115
- actionErrorFilterState,
5299
+ actionErrorFilterState: newActionErrorFilterState,
5116
5300
  });
5117
- }, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
5118
- (actionErrorFilterState.type === undefined
5119
- || actionErrorFilterState.type === LogType$1.Action) && (React__default.createElement(TabBox, { title: "Action Log Details" },
5120
- React__default.createElement(ButtonInputGroup, { label: "Action", className: "mb-2", wrapButtonsAndAddGaps: true }, Object.keys(LogAction$1)
5121
- .map((action) => {
5122
- const description = genHumanReadableName(action);
5123
- return (React__default.createElement(CheckboxButton, { key: action, id: `LogReviewer-action-${action}-checkbox`, text: description, ariaLabel: `include logs with action type "${description}" in results`, noMarginOnRight: true, checked: actionErrorFilterState.action[action], onChanged: (checked) => {
5124
- actionErrorFilterState.action[action] = checked;
5125
- dispatch({
5126
- type: ActionType$6.UpdateActionErrorFilterState,
5127
- actionErrorFilterState,
5128
- });
5129
- } }));
5130
- })),
5131
- React__default.createElement(ButtonInputGroup, { label: "Target", wrapButtonsAndAddGaps: true }, Object.keys(targetMap)
5132
- .map((target) => {
5133
- const description = genHumanReadableName(target);
5134
- return (React__default.createElement(CheckboxButton, { key: target, id: `LogReviewer-target-${target}-checkbox`, text: description, ariaLabel: `include logs with target "${description}" in results`, checked: actionErrorFilterState.target[target], noMarginOnRight: true, onChanged: (checked) => {
5135
- actionErrorFilterState.target[target] = checked;
5136
- dispatch({
5137
- type: ActionType$6.UpdateActionErrorFilterState,
5138
- actionErrorFilterState,
5139
- });
5140
- } }));
5141
- })))),
5142
- (actionErrorFilterState.type === undefined
5143
- || actionErrorFilterState.type === LogType$1.Error) && (React__default.createElement(TabBox, { title: "Error Log Details" },
5144
- React__default.createElement("div", { className: "input-group mb-2" },
5145
- React__default.createElement("span", { className: "input-group-text" }, "Error Message"),
5146
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for error message", value: actionErrorFilterState.errorMessage, placeholder: "e.g. undefined is not a function", onChange: (e) => {
5147
- actionErrorFilterState.errorMessage = e.target.value;
5148
- dispatch({
5149
- type: ActionType$6.UpdateActionErrorFilterState,
5150
- actionErrorFilterState,
5151
- });
5152
- } })),
5153
- React__default.createElement("div", { className: "input-group mb-2" },
5154
- React__default.createElement("span", { className: "input-group-text" }, "Error Code"),
5155
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for error code", value: actionErrorFilterState.errorCode, placeholder: "e.g. GC22", onChange: (e) => {
5156
- actionErrorFilterState.errorCode = ((e.target.value)
5157
- .trim()
5158
- .toUpperCase());
5159
- dispatch({
5160
- type: ActionType$6.UpdateActionErrorFilterState,
5161
- actionErrorFilterState,
5162
- });
5163
- } }))))));
5164
- }
5165
- else if (expandedFilterDrawer === FilterDrawer.Advanced) {
5166
- // Create advanced filter ui
5167
- filterDrawer = (React__default.createElement(React__default.Fragment, null,
5168
- React__default.createElement(TabBox, { title: "User" },
5169
- React__default.createElement("div", { className: "input-group mb-2" },
5170
- React__default.createElement("span", { className: "input-group-text" }, "User First Name"),
5171
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user first name", value: advancedFilterState.userFirstName, placeholder: "e.g. Divardo", onChange: (e) => {
5172
- advancedFilterState.userFirstName = e.target.value;
5173
- dispatch({
5174
- type: ActionType$6.UpdateAdvancedFilterState,
5175
- advancedFilterState,
5176
- });
5177
- } })),
5178
- React__default.createElement("div", { className: "input-group mb-2" },
5179
- React__default.createElement("span", { className: "input-group-text" }, "User Last Name"),
5180
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user last name", value: advancedFilterState.userLastName, placeholder: "e.g. Calicci", onChange: (e) => {
5181
- advancedFilterState.userLastName = e.target.value;
5182
- dispatch({
5183
- type: ActionType$6.UpdateAdvancedFilterState,
5184
- advancedFilterState,
5185
- });
5186
- } })),
5187
- React__default.createElement("div", { className: "input-group mb-2" },
5188
- React__default.createElement("span", { className: "input-group-text" }, "User Email"),
5189
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user email", value: advancedFilterState.userEmail, placeholder: "e.g. calicci@fas.harvard.edu", onChange: (e) => {
5190
- advancedFilterState.userEmail = ((e.target.value)
5191
- .trim());
5192
- dispatch({
5193
- type: ActionType$6.UpdateAdvancedFilterState,
5194
- advancedFilterState,
5195
- });
5196
- } })),
5197
- React__default.createElement("div", { className: "input-group mb-2" },
5198
- React__default.createElement("span", { className: "input-group-text" }, "User Canvas Id"),
5199
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user canvas id", value: advancedFilterState.userId, placeholder: "e.g. 104985", onChange: (e) => {
5200
- const { value } = e.target;
5201
- // Only update if value contains only numbers
5202
- if (/^\d+$/.test(value)) {
5203
- advancedFilterState.userId = ((e.target.value)
5204
- .trim());
5205
- }
5206
- dispatch({
5207
- type: ActionType$6.UpdateAdvancedFilterState,
5208
- advancedFilterState,
5209
- });
5210
- } })),
5211
- React__default.createElement(ButtonInputGroup, { label: "Role" },
5212
- React__default.createElement(CheckboxButton, { text: "Students", onChanged: (checked) => {
5213
- advancedFilterState.includeLearners = checked;
5214
- dispatch({
5215
- type: ActionType$6.UpdateAdvancedFilterState,
5216
- advancedFilterState,
5217
- });
5218
- }, checked: advancedFilterState.includeLearners, ariaLabel: "show logs from students" }),
5219
- React__default.createElement(CheckboxButton, { text: "Teaching Team Members", onChanged: (checked) => {
5220
- advancedFilterState.includeTTMs = checked;
5301
+ } }))))));
5302
+ }
5303
+ else if (expandedFilterDrawer === FilterDrawer.Advanced) {
5304
+ // Create advanced filter ui
5305
+ filterDrawer = (React__default.createElement(React__default.Fragment, null,
5306
+ React__default.createElement(TabBox, { title: "User" },
5307
+ React__default.createElement("div", { className: "input-group mb-2" },
5308
+ React__default.createElement("span", { className: "input-group-text" }, "User First Name"),
5309
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user first name", value: pendingAdvancedFilterState.userFirstName, placeholder: "e.g. Divardo", onChange: (e) => {
5310
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userFirstName: e.target.value });
5311
+ dispatch({
5312
+ type: ActionType$6.UpdateAdvancedFilterState,
5313
+ advancedFilterState: newAdvancedFilterState,
5314
+ });
5315
+ } })),
5316
+ React__default.createElement("div", { className: "input-group mb-2" },
5317
+ React__default.createElement("span", { className: "input-group-text" }, "User Last Name"),
5318
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user last name", value: pendingAdvancedFilterState.userLastName, placeholder: "e.g. Calicci", onChange: (e) => {
5319
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userLastName: e.target.value });
5320
+ dispatch({
5321
+ type: ActionType$6.UpdateAdvancedFilterState,
5322
+ advancedFilterState: newAdvancedFilterState,
5323
+ });
5324
+ } })),
5325
+ React__default.createElement("div", { className: "input-group mb-2" },
5326
+ React__default.createElement("span", { className: "input-group-text" }, "User Email"),
5327
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user email", value: pendingAdvancedFilterState.userEmail, placeholder: "e.g. calicci@fas.harvard.edu", onChange: (e) => {
5328
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userEmail: ((e.target.value)
5329
+ .trim()) });
5330
+ dispatch({
5331
+ type: ActionType$6.UpdateAdvancedFilterState,
5332
+ advancedFilterState: newAdvancedFilterState,
5333
+ });
5334
+ } })),
5335
+ React__default.createElement("div", { className: "input-group mb-2" },
5336
+ React__default.createElement("span", { className: "input-group-text" }, "User Canvas Id"),
5337
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user canvas id", value: pendingAdvancedFilterState.userId, placeholder: "e.g. 104985", onChange: (e) => {
5338
+ const { value } = e.target;
5339
+ // Only update if value contains only numbers
5340
+ if (/^\d+$/.test(value) || value === '') {
5341
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userId: ((e.target.value)
5342
+ .trim()) });
5221
5343
  dispatch({
5222
5344
  type: ActionType$6.UpdateAdvancedFilterState,
5223
- advancedFilterState,
5345
+ advancedFilterState: newAdvancedFilterState,
5224
5346
  });
5225
- }, checked: advancedFilterState.includeTTMs, ariaLabel: "show logs from teaching team members" }),
5226
- React__default.createElement(CheckboxButton, { text: "Admins", onChanged: (checked) => {
5227
- advancedFilterState.includeAdmins = checked;
5347
+ }
5348
+ } })),
5349
+ React__default.createElement(ButtonInputGroup, { label: "Role" },
5350
+ React__default.createElement(CheckboxButton, { text: "Students", onChanged: (checked) => {
5351
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeLearners: checked });
5352
+ dispatch({
5353
+ type: ActionType$6.UpdateAdvancedFilterState,
5354
+ advancedFilterState: newAdvancedFilterState,
5355
+ });
5356
+ }, checked: pendingAdvancedFilterState.includeLearners, ariaLabel: "show logs from students", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }),
5357
+ React__default.createElement(CheckboxButton, { text: "Teaching Team Members", onChanged: (checked) => {
5358
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeTTMs: checked });
5359
+ dispatch({
5360
+ type: ActionType$6.UpdateAdvancedFilterState,
5361
+ advancedFilterState: newAdvancedFilterState,
5362
+ });
5363
+ }, checked: pendingAdvancedFilterState.includeTTMs, ariaLabel: "show logs from teaching team members", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }),
5364
+ React__default.createElement(CheckboxButton, { text: "Admins", onChanged: (checked) => {
5365
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeAdmins: checked });
5366
+ dispatch({
5367
+ type: ActionType$6.UpdateAdvancedFilterState,
5368
+ advancedFilterState: newAdvancedFilterState,
5369
+ });
5370
+ }, checked: pendingAdvancedFilterState.includeAdmins, ariaLabel: "show logs from admins", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }))),
5371
+ React__default.createElement(TabBox, { title: "Course" },
5372
+ React__default.createElement("div", { className: "input-group mb-2" },
5373
+ React__default.createElement("span", { className: "input-group-text" }, "Course Name"),
5374
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for course name", value: pendingAdvancedFilterState.courseName, placeholder: "e.g. GLC 200", onChange: (e) => {
5375
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { courseName: e.target.value });
5376
+ dispatch({
5377
+ type: ActionType$6.UpdateAdvancedFilterState,
5378
+ advancedFilterState: newAdvancedFilterState,
5379
+ });
5380
+ } })),
5381
+ React__default.createElement("div", { className: "input-group mb-2" },
5382
+ React__default.createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
5383
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for course canvas id", value: pendingAdvancedFilterState.courseId, placeholder: "e.g. 15948", onChange: (e) => {
5384
+ const { value } = e.target;
5385
+ // Only update if value contains only numbers
5386
+ if (/^\d+$/.test(value) || value === '') {
5387
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { courseId: ((e.target.value)
5388
+ .trim()) });
5228
5389
  dispatch({
5229
5390
  type: ActionType$6.UpdateAdvancedFilterState,
5230
- advancedFilterState,
5391
+ advancedFilterState: newAdvancedFilterState,
5231
5392
  });
5232
- }, checked: advancedFilterState.includeAdmins, ariaLabel: "show logs from admins" }))),
5233
- React__default.createElement(TabBox, { title: "Course" },
5393
+ }
5394
+ } }))),
5395
+ React__default.createElement(TabBox, { title: "Device" },
5396
+ React__default.createElement(ButtonInputGroup, { label: "Device Type" },
5397
+ React__default.createElement(RadioButton, { text: "All Devices", ariaLabel: "show logs from all devices", selected: pendingAdvancedFilterState.isMobile === undefined, onSelected: () => {
5398
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: undefined });
5399
+ dispatch({
5400
+ type: ActionType$6.UpdateAdvancedFilterState,
5401
+ advancedFilterState: newAdvancedFilterState,
5402
+ });
5403
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5404
+ React__default.createElement(RadioButton, { text: "Mobile Only", ariaLabel: "show logs from mobile devices", selected: pendingAdvancedFilterState.isMobile === true, onSelected: () => {
5405
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: true });
5406
+ dispatch({
5407
+ type: ActionType$6.UpdateAdvancedFilterState,
5408
+ advancedFilterState: newAdvancedFilterState,
5409
+ });
5410
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5411
+ React__default.createElement(RadioButton, { text: "Desktop Only", ariaLabel: "show logs from desktop devices", selected: pendingAdvancedFilterState.isMobile === false, onSelected: () => {
5412
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: false });
5413
+ dispatch({
5414
+ type: ActionType$6.UpdateAdvancedFilterState,
5415
+ advancedFilterState: newAdvancedFilterState,
5416
+ });
5417
+ }, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }))),
5418
+ React__default.createElement(TabBox, { title: "Source" },
5419
+ React__default.createElement(ButtonInputGroup, { label: "Source Type" },
5420
+ React__default.createElement(RadioButton, { text: "Both", ariaLabel: "show logs from all sources", selected: pendingAdvancedFilterState.source === undefined, onSelected: () => {
5421
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: undefined });
5422
+ dispatch({
5423
+ type: ActionType$6.UpdateAdvancedFilterState,
5424
+ advancedFilterState: newAdvancedFilterState,
5425
+ });
5426
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5427
+ React__default.createElement(RadioButton, { text: "Client Only", ariaLabel: "show logs from client source", selected: pendingAdvancedFilterState.source === LogSource$1.Client, onSelected: () => {
5428
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: LogSource$1.Client });
5429
+ dispatch({
5430
+ type: ActionType$6.UpdateAdvancedFilterState,
5431
+ advancedFilterState: newAdvancedFilterState,
5432
+ });
5433
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5434
+ React__default.createElement(RadioButton, { text: "Server Only", ariaLabel: "show logs from server source", selected: pendingAdvancedFilterState.source === LogSource$1.Server, onSelected: () => {
5435
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: LogSource$1.Server });
5436
+ dispatch({
5437
+ type: ActionType$6.UpdateAdvancedFilterState,
5438
+ advancedFilterState: newAdvancedFilterState,
5439
+ });
5440
+ }, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light })),
5441
+ pendingAdvancedFilterState.source !== LogSource$1.Client && (React__default.createElement("div", { className: "mt-2" },
5234
5442
  React__default.createElement("div", { className: "input-group mb-2" },
5235
- React__default.createElement("span", { className: "input-group-text" }, "Course Name"),
5236
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for course name", value: advancedFilterState.courseName, placeholder: "e.g. GLC 200", onChange: (e) => {
5237
- advancedFilterState.courseName = e.target.value;
5443
+ React__default.createElement("span", { className: "input-group-text" }, "Server Route Path"),
5444
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route path", value: pendingAdvancedFilterState.routePath, placeholder: "e.g. /api/ttm/courses/12345", onChange: (e) => {
5445
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { routePath: ((e.target.value)
5446
+ .trim()) });
5238
5447
  dispatch({
5239
5448
  type: ActionType$6.UpdateAdvancedFilterState,
5240
- advancedFilterState,
5449
+ advancedFilterState: newAdvancedFilterState,
5241
5450
  });
5242
5451
  } })),
5243
5452
  React__default.createElement("div", { className: "input-group mb-2" },
5244
- React__default.createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
5245
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for course canvas id", value: advancedFilterState.courseId, placeholder: "e.g. 15948", onChange: (e) => {
5246
- const { value } = e.target;
5247
- // Only update if value contains only numbers
5248
- if (/^\d+$/.test(value)) {
5249
- advancedFilterState.courseId = ((e.target.value)
5250
- .trim());
5251
- }
5252
- dispatch({
5253
- type: ActionType$6.UpdateAdvancedFilterState,
5254
- advancedFilterState,
5255
- });
5256
- } }))),
5257
- React__default.createElement(TabBox, { title: "Device" },
5258
- React__default.createElement(ButtonInputGroup, { label: "Device Type" },
5259
- React__default.createElement(RadioButton, { text: "All Devices", ariaLabel: "show logs from all devices", selected: advancedFilterState.isMobile === undefined, onSelected: () => {
5260
- advancedFilterState.isMobile = undefined;
5261
- dispatch({
5262
- type: ActionType$6.UpdateAdvancedFilterState,
5263
- advancedFilterState,
5264
- });
5265
- } }),
5266
- React__default.createElement(RadioButton, { text: "Mobile Only", ariaLabel: "show logs from mobile devices", selected: advancedFilterState.isMobile === true, onSelected: () => {
5267
- advancedFilterState.isMobile = true;
5268
- dispatch({
5269
- type: ActionType$6.UpdateAdvancedFilterState,
5270
- advancedFilterState,
5271
- });
5272
- } }),
5273
- React__default.createElement(RadioButton, { text: "Desktop Only", ariaLabel: "show logs from desktop devices", selected: advancedFilterState.isMobile === false, onSelected: () => {
5274
- advancedFilterState.isMobile = false;
5275
- dispatch({
5276
- type: ActionType$6.UpdateAdvancedFilterState,
5277
- advancedFilterState,
5278
- });
5279
- }, noMarginOnRight: true }))),
5280
- React__default.createElement(TabBox, { title: "Source" },
5281
- React__default.createElement(ButtonInputGroup, { label: "Source Type" },
5282
- React__default.createElement(RadioButton, { text: "Both", ariaLabel: "show logs from all sources", selected: advancedFilterState.source === undefined, onSelected: () => {
5283
- advancedFilterState.source = undefined;
5284
- dispatch({
5285
- type: ActionType$6.UpdateAdvancedFilterState,
5286
- advancedFilterState,
5287
- });
5288
- } }),
5289
- React__default.createElement(RadioButton, { text: "Client Only", ariaLabel: "show logs from client source", selected: advancedFilterState.source === LogSource$1.Client, onSelected: () => {
5290
- advancedFilterState.source = LogSource$1.Client;
5291
- dispatch({
5292
- type: ActionType$6.UpdateAdvancedFilterState,
5293
- advancedFilterState,
5294
- });
5295
- } }),
5296
- React__default.createElement(RadioButton, { text: "Server Only", ariaLabel: "show logs from server source", selected: advancedFilterState.source === LogSource$1.Server, onSelected: () => {
5297
- advancedFilterState.source = LogSource$1.Server;
5453
+ React__default.createElement("span", { className: "input-group-text" }, "Server Route Template"),
5454
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route template", value: pendingAdvancedFilterState.routeTemplate, placeholder: "e.g. /api/ttm/courses/:courseId", onChange: (e) => {
5455
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { routeTemplate: ((e.target.value)
5456
+ .trim()) });
5298
5457
  dispatch({
5299
5458
  type: ActionType$6.UpdateAdvancedFilterState,
5300
- advancedFilterState,
5459
+ advancedFilterState: newAdvancedFilterState,
5301
5460
  });
5302
- }, noMarginOnRight: true })),
5303
- advancedFilterState.source !== LogSource$1.Client && (React__default.createElement("div", { className: "mt-2" },
5304
- React__default.createElement("div", { className: "input-group mb-2" },
5305
- React__default.createElement("span", { className: "input-group-text" }, "Server Route Path"),
5306
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route path", value: advancedFilterState.routePath, placeholder: "e.g. /api/ttm/courses/12345", onChange: (e) => {
5307
- advancedFilterState.courseName = ((e.target.value)
5308
- .trim());
5309
- dispatch({
5310
- type: ActionType$6.UpdateAdvancedFilterState,
5311
- advancedFilterState,
5312
- });
5313
- } })),
5314
- React__default.createElement("div", { className: "input-group mb-2" },
5315
- React__default.createElement("span", { className: "input-group-text" }, "Server Route Template"),
5316
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route template", value: advancedFilterState.routeTemplate, placeholder: "e.g. /api/ttm/courses/:courseId", onChange: (e) => {
5317
- advancedFilterState.courseName = ((e.target.value)
5318
- .trim());
5319
- dispatch({
5320
- type: ActionType$6.UpdateAdvancedFilterState,
5321
- advancedFilterState,
5322
- });
5323
- } })))))));
5324
- }
5461
+ } })))))));
5325
5462
  }
5326
- // Filters UI
5327
- const filters = (React__default.createElement(React__default.Fragment, null,
5328
- filterToggles,
5329
- filterDrawer && (React__default.createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
5330
- // Actually filter the logs
5331
- // > Perform filters
5332
- const logs = [];
5333
- Object.keys(logMap).forEach((year) => {
5334
- Object.keys(logMap[year]).forEach((month) => {
5335
- logMap[year][month].forEach((log) => {
5336
- /* ----------- Date Filter ---------- */
5337
- var _a;
5338
- // Before start date
5339
- if (
5340
- // Previous year
5341
- log.year < dateFilterState.startDate.year
5342
- // Same year, earlier month
5343
- || ((log.year === dateFilterState.startDate.year)
5344
- && (log.month < dateFilterState.startDate.month))
5345
- // Same year, same month, earlier day
5346
- || ((log.year === dateFilterState.startDate.year)
5347
- && (log.month === dateFilterState.startDate.month)
5348
- && (log.day < dateFilterState.startDate.day))) {
5349
- return;
5350
- }
5351
- // After end date
5352
- if (
5353
- // Later year
5354
- log.year > dateFilterState.endDate.year
5355
- // Same year, later month
5356
- || ((log.year === dateFilterState.endDate.year)
5357
- && (log.month > dateFilterState.endDate.month))
5358
- // Same year, same month, later day
5359
- || ((log.year === dateFilterState.endDate.year)
5360
- && (log.month === dateFilterState.endDate.month)
5361
- && (log.day > dateFilterState.endDate.day))) {
5362
- return;
5363
- }
5364
- /* --------- Context Filter --------- */
5365
- // Context doesn't match
5366
- if (
5367
- // Whole context is deselected
5368
- contextFilterState[log.context] === false
5369
- // None of the subcontexts are selected
5370
- || (
5371
- // Has subcontexts
5372
- typeof contextFilterState[log.context] !== 'boolean'
5373
- // None of the subcontexts are selected
5374
- && Object.values((_a = contextFilterState[log.context]) !== null && _a !== void 0 ? _a : {})
5375
- .every((isSelected) => {
5376
- return !isSelected;
5377
- }))) {
5378
- return;
5379
- }
5380
- // Subcontext doesn't match
5381
- if (
5382
- // Log context is not "uncategorized" (no point in further filters)
5383
- log.context !== LogBuiltInMetadata.Context.Uncategorized
5384
- // Log has a subcontext
5385
- && log.subcontext
5386
- // Context has subcontexts
5387
- && (contextFilterState[log.context]
5388
- && contextFilterState[log.context] !== false
5389
- && contextFilterState[log.context] !== true)
5390
- // Subcontext is not selected
5391
- && !contextFilterState[log.context][log.subcontext]) {
5392
- return;
5393
- }
5394
- /* -------------- Tags -------------- */
5395
- // No tags match
5396
- if (
5397
- // At least one tag is required
5398
- Object.values(tagFilterState)
5399
- .filter((isSelected) => {
5400
- return isSelected;
5401
- })
5402
- .length > 0
5403
- // No tags match
5404
- && log.tags.every((tag) => {
5405
- return !tagFilterState[tag];
5406
- })) {
5407
- return;
5408
- }
5409
- /* ------- Actions and Errors ------- */
5410
- // Log type doesn't match
5411
- if (
5412
- // Filter won't allow all types
5413
- actionErrorFilterState.type !== undefined
5414
- // Log type doesn't match
5415
- && actionErrorFilterState.type !== log.type) {
5416
- return;
5417
- }
5418
- // Filter errors
5419
- if (log.type === LogType$1.Error) {
5420
- // Message doesn't match
5421
- if (
5422
- // Message exists
5423
- log.errorMessage
5424
- // Message filter exists
5425
- && actionErrorFilterState.errorMessage.trim().length > 0
5426
- // Message doesn't match
5427
- && log.errorMessage.toLowerCase().includes(actionErrorFilterState.errorMessage.trim().toLowerCase())) {
5428
- return;
5429
- }
5430
- // Code doesn't match
5431
- if (
5432
- // Code exists
5433
- log.errorCode
5434
- // Code filter exists
5435
- && actionErrorFilterState.errorCode.trim().length > 0
5436
- // Code doesn't match
5437
- && log.errorCode.toUpperCase().includes(actionErrorFilterState.errorCode.trim().toUpperCase())) {
5438
- return;
5439
- }
5440
- }
5441
- // Filter actions
5442
- if (log.type === LogType$1.Action) {
5443
- // Target isn't selected
5444
- if (
5445
- // Target exists
5446
- log.target
5447
- // Target isn't selected
5448
- && !actionErrorFilterState.target[log.target]) {
5449
- return;
5450
- }
5451
- // Action
5452
- if (
5453
- // Action exists
5454
- log.action
5455
- // Action isn't selected
5456
- && !actionErrorFilterState.action[log.action]) {
5457
- return;
5458
- }
5459
- }
5460
- /* --------- Advanced Filter -------- */
5461
- // First name doesn't match
5462
- if (
5463
- // First name exists
5464
- log.userFirstName
5465
- // First name query doesn't match
5466
- && !log.userFirstName.toLowerCase().includes(advancedFilterState.userFirstName.toLowerCase().trim())) {
5467
- return;
5468
- }
5469
- // Last name doesn't match
5470
- if (
5471
- // Last name exists
5472
- log.userLastName
5473
- // Last name query doesn't match
5474
- && !log.userLastName.toLowerCase().includes(advancedFilterState.userLastName.toLowerCase().trim())) {
5475
- return;
5476
- }
5477
- // Email doesn't match
5478
- if (
5479
- // Email exists
5480
- log.userEmail
5481
- // Email query doesn't match
5482
- && !log.userEmail.toLowerCase().includes(advancedFilterState.userEmail.toLowerCase().trim())) {
5483
- return;
5484
- }
5485
- // User id doesn't match
5486
- if (
5487
- // User id exists
5488
- log.userId
5489
- // User id doesn't match
5490
- && !String(log.userId).includes(advancedFilterState.userId.trim())) {
5491
- return;
5492
- }
5493
- // Learner not allowed
5494
- if (
5495
- // User is a learner
5496
- log.isLearner
5497
- // Learners aren't included
5498
- && !advancedFilterState.includeLearners) {
5499
- return;
5500
- }
5501
- // TTM not allowed
5502
- if (
5503
- // User is a ttm
5504
- log.isTTM
5505
- // TTMs aren't included
5506
- && !advancedFilterState.includeTTMs) {
5507
- return;
5508
- }
5509
- // Admin not allowed
5510
- if (
5511
- // User is an admin
5512
- log.isAdmin
5513
- // Admins aren't included
5514
- && !advancedFilterState.includeAdmins) {
5515
- return;
5516
- }
5517
- // Course Id doesn't match
5518
- if (
5519
- // Course Id exists
5520
- log.courseId
5521
- // Course Id doesn't match
5522
- && !String(log.courseId).includes(advancedFilterState.courseId.trim())) {
5523
- return;
5524
- }
5525
- // Course name doesn't match
5526
- if (
5527
- // Course name exists
5528
- log.courseName
5529
- // Course name doesn't match
5530
- && !String(log.courseName).includes(advancedFilterState.courseName.trim())) {
5531
- return;
5532
- }
5533
- // Mobile filter doesn't match
5534
- if (
5535
- // Mobile filter exists
5536
- advancedFilterState.isMobile !== undefined
5537
- // Device info exists
5538
- && log.device
5539
- // Mobile filter doesn't match
5540
- && (advancedFilterState.isMobile !== log.device.isMobile)) {
5541
- return;
5542
- }
5543
- // Log source doesn't match
5544
- if (
5545
- // Source filter exists
5546
- advancedFilterState.source !== undefined
5547
- // Source info exists
5548
- && log.source
5549
- // Source filter doesn't match
5550
- && (advancedFilterState.source !== log.source)) {
5551
- return;
5552
- }
5553
- // Route path doesn't match (Only for server source)
5554
- if (
5555
- // Source is server
5556
- (log.source === LogSource$1.Server)
5557
- // Route path is being filtered
5558
- && (advancedFilterState.routePath.trim().length)
5559
- // Route path doesn't match
5560
- && !(log.routePath.includes(advancedFilterState.routePath.trim()))) {
5561
- return;
5562
- }
5563
- // Route template doesn't match (Only for server source)
5564
- if (
5565
- // Source is server
5566
- (log.source === LogSource$1.Server)
5567
- // Route template is being filtered
5568
- && (advancedFilterState.routeTemplate.trim().length)
5569
- // Route template doesn't match
5570
- && !(log.routeTemplate.includes(advancedFilterState.routeTemplate.trim()))) {
5571
- return;
5572
- }
5573
- /* -------------- Done -------------- */
5574
- // Made it past all filters. Add to the list
5575
- logs.push(log);
5576
- });
5577
- });
5578
- });
5579
- /*----------------------------------------*/
5580
- /* ---------------- Data ---------------- */
5581
- /*----------------------------------------*/
5582
- // Nothing to show notice
5583
- const noLogsNotice = (logs.length === 0
5584
- ? (React__default.createElement("div", { className: "alert alert-warning text-center mt-2" },
5585
- React__default.createElement("h4", { className: "m-1" }, "No Logs to Show"),
5586
- React__default.createElement("div", null, "Either your filters are too strict or no matching logs have been created yet.")))
5587
- : undefined);
5588
- // Create intelliTable
5589
- const dataTable = (React__default.createElement(IntelliTable, { title: "Matching Logs:", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns }));
5590
- // Main body
5591
- body = (React__default.createElement(React__default.Fragment, null,
5592
- filters,
5593
- React__default.createElement("div", { className: "mt-2" },
5594
- dataTable,
5595
- noLogsNotice)));
5596
5463
  }
5464
+ // Filters UI
5465
+ const filters = (React__default.createElement(React__default.Fragment, null,
5466
+ filterToggles,
5467
+ filterDrawer && (React__default.createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
5468
+ // Body that will be filled with the contents of the panel
5469
+ const body = (React__default.createElement(React__default.Fragment, null,
5470
+ loading && (React__default.createElement("div", { className: "text-center p-5" },
5471
+ React__default.createElement(LoadingSpinner, null))),
5472
+ !loading && (React__default.createElement("div", null, filters)),
5473
+ !loading && logs.length === 0 && (React__default.createElement("div", { className: "alert alert-warning text-center mt-2" },
5474
+ React__default.createElement("h4", { className: "m-1" }, "No Logs to Show"),
5475
+ React__default.createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))),
5476
+ React__default.createElement("div", { className: loading || logs.length === 0 ? 'd-none' : undefined },
5477
+ React__default.createElement(IntelliTable, { title: "Matching Logs", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns })),
5478
+ !loading && logs.length > 0 && paginationControls));
5597
5479
  /* ---------- Wrap in Modal --------- */
5598
5480
  return (React__default.createElement("div", { className: "LogReviewer-outer-container" },
5599
5481
  React__default.createElement("style", null, style$2),
5600
5482
  React__default.createElement("div", { className: "LogReviewer-inner-container" },
5601
5483
  React__default.createElement("div", { className: "LogReviewer-header" },
5602
5484
  React__default.createElement("div", { className: "LogReviewer-header-title" },
5603
- React__default.createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
5485
+ React__default.createElement("h3", { className: "text-center" }, "Log Review Dashboard")),
5604
5486
  React__default.createElement("div", { style: { width: 0 } },
5605
5487
  React__default.createElement("button", { type: "button", className: "LogReviewer-header-close-button btn btn-dark btn-lg pe-0", "aria-label": "close log reviewer panel", onClick: onClose },
5606
5488
  React__default.createElement(FontAwesomeIcon, { icon: faTimes })))),
@@ -15276,6 +15158,12 @@ const HOUR_IN_MS = 3600000;
15276
15158
  */
15277
15159
  const DAY_IN_MS = 86400000;
15278
15160
 
15161
+ /**
15162
+ * Path of the route for storing client-side logs
15163
+ * @author Gabe Abrams
15164
+ */
15165
+ const LOG_REVIEW_ROUTE_PATH_PREFIX = `/admin${ROUTE_PATH_PREFIX}/logs`;
15166
+
15279
15167
  /**
15280
15168
  * Route for checking the status of the current user's
15281
15169
  * access to log review
@@ -16320,5 +16208,5 @@ var DayOfWeek;
16320
16208
  })(DayOfWeek || (DayOfWeek = {}));
16321
16209
  var DayOfWeek$1 = DayOfWeek;
16322
16210
 
16323
- export { AppWrapper, AutoscrollToBottomContainer, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DAY_IN_MS, DBEntryFieldType$1 as DBEntryFieldType, DBEntryManagerPanel, DayOfWeek$1 as DayOfWeek, Drawer, Dropdown, DropdownItemType$1 as DropdownItemType, DynamicWord, ErrorBox, ErrorWithCode, HOUR_IN_MS, IntelliTable, ItemPicker, LOG_REVIEW_ROUTE_PATH_PREFIX, LOG_REVIEW_STATUS_ROUTE, LOG_ROUTE_PATH, LoadingSpinner, LogAction$1 as LogAction, LogBuiltInMetadata, LogLevel$1 as LogLevel, LogReviewer, LogSource$1 as LogSource, LogType$1 as LogType, MINUTE_IN_MS, Modal, ModalButtonType$1 as ModalButtonType, ModalSize$1 as ModalSize, ModalType$1 as ModalType, MultiSwitch, ParamType$1 as ParamType, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode$1 as ReactKitErrorCode, SimpleDateChooser, SimpleTimeChooser, TabBox, ToggleSwitch, Tooltip, Variant$1 as Variant, abbreviate, addFatalErrorHandler, alert, avg, canReviewLogs, capitalize, ceilToNumDecimals, combineClassNames, compareArraysByProp, confirm, everyAsync, extractProp, filterAsync, floorToNumDecimals, forEachAsync, forceNumIntoBounds, genCSV, genCommaList, getHumanReadableDate, getLocalTimeInfo, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, getWordCount, idify, initClient, isMobileOrTablet, leaveToURL, logClientEvent, makeLinksClickable, mapAsync, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, prefixWithAOrAn, prompt, roundToNumDecimals, setClientEventMetadataPopulator, showFatalError, shuffleArray, someAsync, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, useForceRender, validateEmail, validatePhoneNumber, validateString, visitServerEndpoint, waitMs };
16211
+ export { AppWrapper, AutoscrollToBottomContainer, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DAY_IN_MS, DBEntryFieldType$1 as DBEntryFieldType, DBEntryManagerPanel, DayOfWeek$1 as DayOfWeek, Drawer, Dropdown, DropdownItemType$1 as DropdownItemType, DynamicWord, ErrorBox, ErrorWithCode, HOUR_IN_MS, IntelliTable, ItemPicker, LOG_REVIEW_GET_LOGS_ROUTE, LOG_REVIEW_ROUTE_PATH_PREFIX, LOG_REVIEW_STATUS_ROUTE, LOG_ROUTE_PATH, LoadingSpinner, LogAction$1 as LogAction, LogBuiltInMetadata, LogLevel$1 as LogLevel, LogReviewer, LogSource$1 as LogSource, LogType$1 as LogType, MINUTE_IN_MS, Modal, ModalButtonType$1 as ModalButtonType, ModalSize$1 as ModalSize, ModalType$1 as ModalType, MultiSwitch, ParamType$1 as ParamType, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode$1 as ReactKitErrorCode, SimpleDateChooser, SimpleTimeChooser, TabBox, ToggleSwitch, Tooltip, Variant$1 as Variant, abbreviate, addFatalErrorHandler, alert, avg, canReviewLogs, capitalize, ceilToNumDecimals, cloneDeep, combineClassNames, compareArraysByProp, confirm, everyAsync, extractProp, filterAsync, floorToNumDecimals, forEachAsync, forceNumIntoBounds, genCSV, genCommaList, getHumanReadableDate, getLocalTimeInfo, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, getWordCount, idify, initClient, isMobileOrTablet, leaveToURL, logClientEvent, makeLinksClickable, mapAsync, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, prefixWithAOrAn, prompt, roundToNumDecimals, setClientEventMetadataPopulator, showFatalError, shuffleArray, someAsync, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, useForceRender, validateEmail, validatePhoneNumber, validateString, visitServerEndpoint, waitMs };
16324
16212
  //# sourceMappingURL=index.js.map