dce-reactkit 3.9.4-beta-logreviewer.1 → 3.9.4-beta-logreviewer.2

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.
package/dist/cjs/index.js CHANGED
@@ -3537,7 +3537,7 @@ var ActionType$6;
3537
3537
  (function (ActionType) {
3538
3538
  // Show the loading bar
3539
3539
  ActionType["StartLoading"] = "start-loading";
3540
- // Finish loading one or more months of logs
3540
+ // Finish loading logs
3541
3541
  ActionType["FinishLoading"] = "finish-loading";
3542
3542
  // Reset filters to initial values
3543
3543
  ActionType["ResetFilters"] = "reset-filters";
@@ -3555,6 +3555,12 @@ var ActionType$6;
3555
3555
  ActionType["UpdateActionErrorFilterState"] = "update-action-error-filter-state";
3556
3556
  // Update the advanced filter state
3557
3557
  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
+ // Set has another page
3563
+ ActionType["SetHasAnotherPage"] = "set-has-another-page";
3558
3564
  })(ActionType$6 || (ActionType$6 = {}));
3559
3565
  /**
3560
3566
  * Reducer that executes actions
@@ -3568,7 +3574,7 @@ const reducer$7 = (state, action) => {
3568
3574
  return Object.assign(Object.assign({}, state), { loading: true });
3569
3575
  }
3570
3576
  case ActionType$6.FinishLoading: {
3571
- return Object.assign(Object.assign({}, state), { loading: false, logMap: action.logMap });
3577
+ return Object.assign(Object.assign({}, state), { loading: false, logs: action.logs });
3572
3578
  }
3573
3579
  case ActionType$6.ToggleFilterDrawer: {
3574
3580
  return Object.assign(Object.assign({}, state), { expandedFilterDrawer: (state.expandedFilterDrawer === action.filterDrawer
@@ -3596,6 +3602,12 @@ const reducer$7 = (state, action) => {
3596
3602
  case ActionType$6.UpdateAdvancedFilterState: {
3597
3603
  return Object.assign(Object.assign({}, state), { advancedFilterState: action.advancedFilterState });
3598
3604
  }
3605
+ case ActionType$6.IncrementPageNumber: {
3606
+ return Object.assign(Object.assign({}, state), { pageNumber: state.pageNumber + 1 });
3607
+ }
3608
+ case ActionType$6.DecrementPageNumber: {
3609
+ return Object.assign(Object.assign({}, state), { pageNumber: state.pageNumber - 1 });
3610
+ }
3599
3611
  default: {
3600
3612
  return state;
3601
3613
  }
@@ -3713,126 +3725,78 @@ const LogReviewer = (props) => {
3713
3725
  // Initial state
3714
3726
  const initialState = {
3715
3727
  loading: true,
3716
- logMap: {},
3728
+ logs: [],
3717
3729
  expandedFilterDrawer: undefined,
3718
3730
  dateFilterState: initDateFilterState,
3719
3731
  contextFilterState: initContextFilterState,
3720
3732
  tagFilterState: initTagFilterState,
3721
3733
  actionErrorFilterState: initActionErrorFilterState,
3722
3734
  advancedFilterState: initAdvancedFilterState,
3735
+ pageNumber: 1,
3736
+ hasAnotherPage: false,
3723
3737
  };
3724
3738
  // Initialize state
3725
3739
  const [state, dispatch] = React.useReducer(reducer$7, initialState);
3726
3740
  // Destructure common state
3727
- const { loading, logMap, expandedFilterDrawer, dateFilterState, contextFilterState, tagFilterState, actionErrorFilterState, advancedFilterState, } = state;
3741
+ const { loading, logs, expandedFilterDrawer, dateFilterState, contextFilterState, tagFilterState, actionErrorFilterState, advancedFilterState, pageNumber, hasAnotherPage, } = state;
3728
3742
  /*------------------------------------------------------------------------*/
3729
3743
  /* ------------------------- Component Functions ------------------------ */
3730
3744
  /*------------------------------------------------------------------------*/
3731
3745
  /**
3732
- * Get the list of year/month combos that need to be loaded given a new
3733
- * start or end date and the existing logMap
3734
- * @author Gabe Abrams
3735
- * @param newDateFilterState the new date filter state
3736
- * @returns list of year/month combos that need to be loaded
3737
- */
3738
- const listMonthsToLoad = (newDateFilterState) => {
3739
- // List of year/month combos that need to be loaded
3740
- const toLoad = [];
3741
- // Loop through dates
3742
- let { year, month } = newDateFilterState.startDate;
3743
- while (
3744
- // Earlier year
3745
- (year < newDateFilterState.endDate.year)
3746
- // Current year but included month
3747
- || (year === newDateFilterState.endDate.year
3748
- && month <= newDateFilterState.endDate.month)) {
3749
- // Add to list if not already loaded
3750
- if (!logMap[year]
3751
- || !logMap[year][month]) {
3752
- toLoad.push({
3753
- year,
3754
- month,
3755
- });
3756
- }
3757
- // Increment
3758
- month += 1;
3759
- if (month > 12) {
3760
- month -= 12;
3761
- year += 1;
3762
- }
3763
- }
3764
- // Return
3765
- return toLoad;
3766
- };
3767
- /**
3768
- * Handle updated start/end dates (updates state, loads if necessary)
3769
- * @author Gabe Abrams
3770
- * @param newDateFilterState the new date filter state
3746
+ * Fetch logs from the server based on current filters
3771
3747
  */
3772
- const handleDateRangeUpdated = (newDateFilterState) => __awaiter(void 0, void 0, void 0, function* () {
3773
- // Update state
3774
- dispatch({
3775
- type: ActionType$6.UpdateDateFilterState,
3776
- dateFilterState: newDateFilterState,
3777
- });
3778
- // Check which year/month combos we need to load
3779
- const toLoad = listMonthsToLoad(newDateFilterState);
3780
- // If nothing to load, finished
3781
- if (toLoad.length === 0) {
3782
- return;
3783
- }
3784
- // Start loading
3785
- dispatch({
3786
- type: ActionType$6.StartLoading,
3787
- });
3788
- // Load required months
3748
+ const fetchLogs = () => __awaiter(void 0, void 0, void 0, function* () {
3749
+ dispatch({ type: ActionType$6.StartLoading });
3789
3750
  try {
3790
- for (let i = 0; i < toLoad.length; i++) {
3791
- // Destructure
3792
- const { year, month } = toLoad[i];
3793
- // Load
3794
- let logs = [];
3795
- let pageNumber = 1;
3796
- let hasAnotherPage = true;
3797
- while (hasAnotherPage) {
3798
- const response = yield visitServerEndpoint({
3799
- path: `${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/${year}/months/${month}`,
3800
- method: 'GET',
3801
- params: {
3802
- pageNumber,
3803
- },
3804
- });
3805
- logs = logs.concat(response.items);
3806
- hasAnotherPage = response.hasAnotherPage;
3807
- pageNumber += 1;
3808
- }
3809
- // Add to map
3810
- if (!logMap[year]) {
3811
- logMap[year] = {};
3812
- }
3813
- logMap[year][month] = logs;
3814
- }
3751
+ // Prepare filter parameters
3752
+ const filters = {
3753
+ startDate: dateFilterState.startDate,
3754
+ endDate: dateFilterState.endDate,
3755
+ contextFilterState,
3756
+ tagFilterState,
3757
+ actionErrorFilterState,
3758
+ advancedFilterState,
3759
+ };
3760
+ // Send filters to the server
3761
+ let fetchedLogs = [];
3762
+ const response = yield visitServerEndpoint({
3763
+ path: `${LOG_REVIEW_ROUTE_PATH_PREFIX}/logs`,
3764
+ method: 'GET',
3765
+ params: {
3766
+ pageNumber,
3767
+ filters,
3768
+ },
3769
+ });
3770
+ fetchedLogs = fetchedLogs.concat(response.items);
3771
+ dispatch({
3772
+ type: ActionType$6.SetHasAnotherPage,
3773
+ hasAnotherPage: response.hasAnotherPage,
3774
+ });
3775
+ // Update logs in state
3776
+ dispatch({
3777
+ type: ActionType$6.FinishLoading,
3778
+ logs: fetchedLogs,
3779
+ });
3815
3780
  }
3816
3781
  catch (err) {
3817
3782
  return showFatalError(err);
3818
3783
  }
3819
- // Finish loading
3820
- dispatch({
3821
- type: ActionType$6.FinishLoading,
3822
- logMap,
3823
- });
3824
3784
  });
3825
3785
  /*------------------------------------------------------------------------*/
3826
3786
  /* ------------------------- Lifecycle Functions ------------------------ */
3827
3787
  /*------------------------------------------------------------------------*/
3828
3788
  /**
3829
- * Mount
3830
- * @author Gabe Abrams
3789
+ * Fetch logs whenever filters change
3831
3790
  */
3832
3791
  React.useEffect(() => {
3833
- // Perform initial load
3834
- handleDateRangeUpdated(dateFilterState);
3835
- }, []);
3792
+ fetchLogs();
3793
+ }, [
3794
+ dateFilterState,
3795
+ contextFilterState,
3796
+ tagFilterState,
3797
+ actionErrorFilterState,
3798
+ advancedFilterState,
3799
+ ]);
3836
3800
  /*------------------------------------------------------------------------*/
3837
3801
  /* ------------------------------- Render ------------------------------- */
3838
3802
  /*------------------------------------------------------------------------*/
@@ -3847,703 +3811,472 @@ const LogReviewer = (props) => {
3847
3811
  React__default["default"].createElement(LoadingSpinner, null)));
3848
3812
  }
3849
3813
  /* ------------ Review UI ----------- */
3850
- if (!loading) {
3851
- /*----------------------------------------*/
3852
- /* --------------- Filters -------------- */
3853
- /*----------------------------------------*/
3854
- // Filter toggle
3855
- const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles" },
3856
- React__default["default"].createElement("h3", { className: "m-0" }, "Filters:"),
3857
- React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
3858
- React__default["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: () => {
3859
- dispatch({
3860
- type: ActionType$6.ToggleFilterDrawer,
3861
- filterDrawer: FilterDrawer.Date,
3862
- });
3863
- } },
3864
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCalendar, className: "me-2" }),
3865
- "Date"),
3866
- React__default["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: () => {
3867
- dispatch({
3868
- type: ActionType$6.ToggleFilterDrawer,
3869
- filterDrawer: FilterDrawer.Context,
3870
- });
3871
- } },
3872
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "me-2" }),
3873
- "Context"),
3874
- (LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (React__default["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: () => {
3875
- dispatch({
3876
- type: ActionType$6.ToggleFilterDrawer,
3877
- filterDrawer: FilterDrawer.Tag,
3878
- });
3879
- } },
3880
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTag, className: "me-2" }),
3881
- "Tag")),
3882
- React__default["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: () => {
3883
- dispatch({
3884
- type: ActionType$6.ToggleFilterDrawer,
3885
- filterDrawer: FilterDrawer.Action,
3886
- });
3887
- } },
3888
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faHammer, className: "me-2" }),
3889
- "Action"),
3890
- React__default["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: () => {
3814
+ /*----------------------------------------*/
3815
+ /* ------------ Pagination -------------- */
3816
+ /*----------------------------------------*/
3817
+ const paginationControls = logs.length > 0 && (React__default["default"].createElement("div", { className: "text-center mt-3" },
3818
+ React__default["default"].createElement("button", { type: "button", className: "btn btn-secondary me-2", disabled: pageNumber <= 1, onClick: () => {
3819
+ dispatch({
3820
+ type: ActionType$6.DecrementPageNumber,
3821
+ });
3822
+ } },
3823
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faArrowLeft, className: "me-2" }),
3824
+ "Previous Page"),
3825
+ React__default["default"].createElement("span", { className: "mx-3" },
3826
+ "Page",
3827
+ ' ',
3828
+ pageNumber),
3829
+ React__default["default"].createElement("button", { type: "button", className: "btn btn-secondary ms-2", disabled: !hasAnotherPage, onClick: () => {
3830
+ dispatch({
3831
+ type: ActionType$6.IncrementPageNumber,
3832
+ });
3833
+ } },
3834
+ "Next Page",
3835
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faArrowRight, className: "ms-2" }))));
3836
+ /*----------------------------------------*/
3837
+ /* --------------- Filters -------------- */
3838
+ /*----------------------------------------*/
3839
+ // Filter toggle
3840
+ const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles" },
3841
+ React__default["default"].createElement("h3", { className: "m-0" }, "Filters:"),
3842
+ React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
3843
+ React__default["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: () => {
3844
+ dispatch({
3845
+ type: ActionType$6.ToggleFilterDrawer,
3846
+ filterDrawer: FilterDrawer.Date,
3847
+ });
3848
+ } },
3849
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCalendar, className: "me-2" }),
3850
+ "Date"),
3851
+ React__default["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: () => {
3852
+ dispatch({
3853
+ type: ActionType$6.ToggleFilterDrawer,
3854
+ filterDrawer: FilterDrawer.Context,
3855
+ });
3856
+ } },
3857
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "me-2" }),
3858
+ "Context"),
3859
+ (LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (React__default["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: () => {
3860
+ dispatch({
3861
+ type: ActionType$6.ToggleFilterDrawer,
3862
+ filterDrawer: FilterDrawer.Tag,
3863
+ });
3864
+ } },
3865
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTag, className: "me-2" }),
3866
+ "Tag")),
3867
+ React__default["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: () => {
3868
+ dispatch({
3869
+ type: ActionType$6.ToggleFilterDrawer,
3870
+ filterDrawer: FilterDrawer.Action,
3871
+ });
3872
+ } },
3873
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faHammer, className: "me-2" }),
3874
+ "Action"),
3875
+ React__default["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: () => {
3876
+ dispatch({
3877
+ type: ActionType$6.ToggleFilterDrawer,
3878
+ filterDrawer: FilterDrawer.Advanced,
3879
+ });
3880
+ } },
3881
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faList, className: "me-2" }),
3882
+ "Advanced"),
3883
+ React__default["default"].createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
3884
+ dispatch({
3885
+ type: ActionType$6.ResetFilters,
3886
+ initActionErrorFilterState,
3887
+ initAdvancedFilterState,
3888
+ initContextFilterState,
3889
+ initDateFilterState,
3890
+ initTagFilterState,
3891
+ });
3892
+ } },
3893
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes }),
3894
+ ' ',
3895
+ "Reset"))));
3896
+ // Filter drawer
3897
+ let filterDrawer;
3898
+ if (expandedFilterDrawer) {
3899
+ if (expandedFilterDrawer === FilterDrawer.Date) {
3900
+ filterDrawer = (React__default["default"].createElement(TabBox, { title: "Date" },
3901
+ React__default["default"].createElement(SimpleDateChooser, { ariaLabel: "filter start date", name: "filter-start-date", year: dateFilterState.startDate.year, month: dateFilterState.startDate.month, day: dateFilterState.startDate.day, chooseFromPast: true, numMonthsToShow: 36, onChange: (month, day, year) => {
3902
+ dateFilterState.startDate = { month, day, year };
3891
3903
  dispatch({
3892
- type: ActionType$6.ToggleFilterDrawer,
3893
- filterDrawer: FilterDrawer.Advanced,
3904
+ type: ActionType$6.UpdateDateFilterState,
3905
+ dateFilterState,
3894
3906
  });
3895
- } },
3896
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faList, className: "me-2" }),
3897
- "Advanced"),
3898
- React__default["default"].createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
3907
+ } }),
3908
+ ' ',
3909
+ "to",
3910
+ ' ',
3911
+ React__default["default"].createElement(SimpleDateChooser, { ariaLabel: "filter end date", name: "filter-end-date", year: dateFilterState.endDate.year, month: dateFilterState.endDate.month, day: dateFilterState.endDate.day, chooseFromPast: true, numMonthsToShow: 12, onChange: (month, day, year) => {
3912
+ if (year < dateFilterState.startDate.year
3913
+ || (year === dateFilterState.startDate.year
3914
+ && month < dateFilterState.startDate.month)
3915
+ || (year === dateFilterState.startDate.year
3916
+ && month === dateFilterState.startDate.month
3917
+ && day < dateFilterState.startDate.day)) {
3918
+ return alert('Invalid Start Date', 'The start date cannot be before the end date.');
3919
+ }
3920
+ dateFilterState.endDate = { month, day, year };
3899
3921
  dispatch({
3900
- type: ActionType$6.ResetFilters,
3901
- initActionErrorFilterState,
3902
- initAdvancedFilterState,
3903
- initContextFilterState,
3904
- initDateFilterState,
3905
- initTagFilterState,
3922
+ type: ActionType$6.UpdateDateFilterState,
3923
+ dateFilterState,
3906
3924
  });
3907
- } },
3908
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes }),
3909
- ' ',
3910
- "Reset"))));
3911
- // Filter drawer
3912
- let filterDrawer;
3913
- if (expandedFilterDrawer) {
3914
- if (expandedFilterDrawer === FilterDrawer.Date) {
3915
- filterDrawer = (React__default["default"].createElement(TabBox, { title: "Date" },
3916
- React__default["default"].createElement(SimpleDateChooser, { ariaLabel: "filter start date", name: "filter-start-date", year: dateFilterState.startDate.year, month: dateFilterState.startDate.month, day: dateFilterState.startDate.day, chooseFromPast: true, numMonthsToShow: 36, onChange: (month, day, year) => {
3917
- dateFilterState.startDate = { month, day, year };
3918
- handleDateRangeUpdated(dateFilterState);
3919
- } }),
3920
- ' ',
3921
- "to",
3922
- ' ',
3923
- React__default["default"].createElement(SimpleDateChooser, { ariaLabel: "filter end date", name: "filter-end-date", year: dateFilterState.endDate.year, month: dateFilterState.endDate.month, day: dateFilterState.endDate.day, chooseFromPast: true, numMonthsToShow: 12, onChange: (month, day, year) => {
3924
- if (year < dateFilterState.startDate.year
3925
- || (year === dateFilterState.startDate.year
3926
- && month < dateFilterState.startDate.month)
3927
- || (year === dateFilterState.startDate.year
3928
- && month === dateFilterState.startDate.month
3929
- && day < dateFilterState.startDate.day)) {
3930
- return alert('Invalid Start Date', 'The start date cannot be before the end date.');
3931
- }
3932
- dateFilterState.endDate = { month, day, year };
3933
- handleDateRangeUpdated(dateFilterState);
3934
- } })));
3935
- }
3936
- else if (expandedFilterDrawer === FilterDrawer.Context) {
3937
- // Create item picker items
3938
- const builtInPickableItem = {
3939
- id: 'built-in-contexts',
3940
- name: 'Auto-logged',
3941
- isGroup: true,
3942
- children: [],
3943
- };
3944
- const pickableItems = [];
3945
- Object.keys(contextMap)
3946
- .forEach((context) => {
3947
- const value = contextMap[context];
3948
- if (typeof value === 'string') {
3949
- // No subcategories
3950
- const item = {
3951
- id: context,
3952
- name: genHumanReadableName(context),
3953
- isGroup: false,
3954
- checked: !!contextFilterState[context],
3955
- };
3956
- // Add built-in items to its own folder
3957
- const isBuiltIn = context in LogBuiltInMetadata.Context;
3958
- if (isBuiltIn) {
3959
- // Add to built-in pickable item
3960
- builtInPickableItem.children.push(item);
3961
- }
3962
- else {
3963
- // Add to pickable items list
3964
- pickableItems.push(item);
3965
- }
3966
- return;
3967
- }
3968
- // Has subcategories
3969
- const children = (Object.keys(value)
3970
- // Remove parent name
3971
- .filter((subcontext) => {
3972
- return subcontext !== '_';
3973
- })
3974
- // Create child pickable items
3975
- .map((subcontext) => {
3976
- return {
3977
- id: subcontext,
3978
- name: genHumanReadableName(subcontext),
3979
- isGroup: false,
3980
- checked: contextFilterState[context][subcontext],
3981
- };
3982
- }));
3925
+ } })));
3926
+ }
3927
+ else if (expandedFilterDrawer === FilterDrawer.Context) {
3928
+ // Create item picker items
3929
+ const builtInPickableItem = {
3930
+ id: 'built-in-contexts',
3931
+ name: 'Auto-logged',
3932
+ isGroup: true,
3933
+ children: [],
3934
+ };
3935
+ const pickableItems = [];
3936
+ Object.keys(contextMap)
3937
+ .forEach((context) => {
3938
+ const value = contextMap[context];
3939
+ if (typeof value === 'string') {
3940
+ // No subcategories
3983
3941
  const item = {
3984
3942
  id: context,
3985
3943
  name: genHumanReadableName(context),
3986
- isGroup: true,
3987
- children,
3944
+ isGroup: false,
3945
+ checked: !!contextFilterState[context],
3988
3946
  };
3989
- pickableItems.push(item);
3990
- });
3991
- // Add built-in contexts to end ofl ist
3992
- pickableItems.push(builtInPickableItem);
3993
- // Create filter UI
3994
- filterDrawer = (React__default["default"].createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
3995
- // Update our state
3996
- updatedItems.forEach((pickableItem) => {
3997
- if (pickableItem.isGroup) {
3998
- // Has subcontexts
3999
- if (pickableItem.id === 'built-in-contexts') {
4000
- // Built-in
4001
- // Treat as if these were top-level contexts
4002
- pickableItem.children.forEach((subcontextItem) => {
4003
- contextFilterState[subcontextItem.id] = ('checked' in subcontextItem
4004
- && subcontextItem.checked);
4005
- });
4006
- }
4007
- else {
4008
- // Not built-in
4009
- pickableItem.children.forEach((subcontextItem) => {
4010
- if (!subcontextItem.isGroup) {
4011
- contextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
4012
- }
4013
- });
4014
- }
3947
+ // Add built-in items to its own folder
3948
+ const isBuiltIn = context in LogBuiltInMetadata.Context;
3949
+ if (isBuiltIn) {
3950
+ // Add to built-in pickable item
3951
+ builtInPickableItem.children.push(item);
3952
+ }
3953
+ else {
3954
+ // Add to pickable items list
3955
+ pickableItems.push(item);
3956
+ }
3957
+ return;
3958
+ }
3959
+ // Has subcategories
3960
+ const children = (Object.keys(value)
3961
+ // Remove parent name
3962
+ .filter((subcontext) => {
3963
+ return subcontext !== '_';
3964
+ })
3965
+ // Create child pickable items
3966
+ .map((subcontext) => {
3967
+ return {
3968
+ id: subcontext,
3969
+ name: genHumanReadableName(subcontext),
3970
+ isGroup: false,
3971
+ checked: contextFilterState[context][subcontext],
3972
+ };
3973
+ }));
3974
+ const item = {
3975
+ id: context,
3976
+ name: genHumanReadableName(context),
3977
+ isGroup: true,
3978
+ children,
3979
+ };
3980
+ pickableItems.push(item);
3981
+ });
3982
+ // Add built-in contexts to end ofl ist
3983
+ pickableItems.push(builtInPickableItem);
3984
+ // Create filter UI
3985
+ filterDrawer = (React__default["default"].createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
3986
+ // Update our state
3987
+ updatedItems.forEach((pickableItem) => {
3988
+ if (pickableItem.isGroup) {
3989
+ // Has subcontexts
3990
+ if (pickableItem.id === 'built-in-contexts') {
3991
+ // Built-in
3992
+ // Treat as if these were top-level contexts
3993
+ pickableItem.children.forEach((subcontextItem) => {
3994
+ contextFilterState[subcontextItem.id] = ('checked' in subcontextItem
3995
+ && subcontextItem.checked);
3996
+ });
4015
3997
  }
4016
3998
  else {
4017
- // No subcontexts
4018
- contextFilterState[pickableItem.id] = (pickableItem.checked);
3999
+ // Not built-in
4000
+ pickableItem.children.forEach((subcontextItem) => {
4001
+ if (!subcontextItem.isGroup) {
4002
+ contextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
4003
+ }
4004
+ });
4019
4005
  }
4020
- });
4021
- dispatch({
4022
- type: ActionType$6.UpdateContextFilterState,
4023
- contextFilterState,
4024
- });
4025
- } }));
4026
- }
4027
- else if (expandedFilterDrawer === FilterDrawer.Tag) {
4028
- // Create filter UI
4029
- filterDrawer = (React__default["default"].createElement(TabBox, { title: "Tags" },
4030
- React__default["default"].createElement("div", null, "If any tags are selected, logs must contain at least one (but not necessarily all) of the selected tags."),
4031
- React__default["default"].createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {})
4032
- .map((tag) => {
4033
- const description = genHumanReadableName(tag);
4034
- return (React__default["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) => {
4035
- tagFilterState[tag] = checked;
4006
+ }
4007
+ else {
4008
+ // No subcontexts
4009
+ contextFilterState[pickableItem.id] = (pickableItem.checked);
4010
+ }
4011
+ });
4012
+ dispatch({
4013
+ type: ActionType$6.UpdateContextFilterState,
4014
+ contextFilterState,
4015
+ });
4016
+ } }));
4017
+ }
4018
+ else if (expandedFilterDrawer === FilterDrawer.Tag) {
4019
+ // Create filter UI
4020
+ filterDrawer = (React__default["default"].createElement(TabBox, { title: "Tags" },
4021
+ React__default["default"].createElement("div", null, "If any tags are selected, logs must contain at least one (but not necessarily all) of the selected tags."),
4022
+ React__default["default"].createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {})
4023
+ .map((tag) => {
4024
+ const description = genHumanReadableName(tag);
4025
+ return (React__default["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) => {
4026
+ tagFilterState[tag] = checked;
4027
+ dispatch({
4028
+ type: ActionType$6.UpdateTagFilterState,
4029
+ tagFilterState,
4030
+ });
4031
+ } }));
4032
+ }))));
4033
+ }
4034
+ else if (expandedFilterDrawer === FilterDrawer.Action) {
4035
+ // Create filter UI
4036
+ filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
4037
+ React__default["default"].createElement(TabBox, { title: "Log Type" },
4038
+ React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-all", text: "All Logs", onSelected: () => {
4039
+ actionErrorFilterState.type = undefined;
4040
+ dispatch({
4041
+ type: ActionType$6.UpdateActionErrorFilterState,
4042
+ actionErrorFilterState,
4043
+ });
4044
+ }, ariaLabel: "show logs of all types", selected: actionErrorFilterState.type === undefined }),
4045
+ React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-action-only", text: "Action Logs Only", onSelected: () => {
4046
+ actionErrorFilterState.type = LogType$1.Action;
4047
+ dispatch({
4048
+ type: ActionType$6.UpdateActionErrorFilterState,
4049
+ actionErrorFilterState,
4050
+ });
4051
+ }, ariaLabel: "only show action logs", selected: actionErrorFilterState.type === LogType$1.Action }),
4052
+ React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-error-only", text: "Action Error Only", onSelected: () => {
4053
+ actionErrorFilterState.type = LogType$1.Error;
4054
+ dispatch({
4055
+ type: ActionType$6.UpdateActionErrorFilterState,
4056
+ actionErrorFilterState,
4057
+ });
4058
+ }, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
4059
+ (actionErrorFilterState.type === undefined
4060
+ || actionErrorFilterState.type === LogType$1.Action) && (React__default["default"].createElement(TabBox, { title: "Action Log Details" },
4061
+ React__default["default"].createElement(ButtonInputGroup, { label: "Action", className: "mb-2", wrapButtonsAndAddGaps: true }, Object.keys(LogAction$1)
4062
+ .map((action) => {
4063
+ const description = genHumanReadableName(action);
4064
+ return (React__default["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) => {
4065
+ actionErrorFilterState.action[action] = checked;
4036
4066
  dispatch({
4037
- type: ActionType$6.UpdateTagFilterState,
4038
- tagFilterState,
4067
+ type: ActionType$6.UpdateActionErrorFilterState,
4068
+ actionErrorFilterState,
4039
4069
  });
4040
4070
  } }));
4041
- }))));
4042
- }
4043
- else if (expandedFilterDrawer === FilterDrawer.Action) {
4044
- // Create filter UI
4045
- filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
4046
- React__default["default"].createElement(TabBox, { title: "Log Type" },
4047
- React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-all", text: "All Logs", onSelected: () => {
4048
- actionErrorFilterState.type = undefined;
4071
+ })),
4072
+ React__default["default"].createElement(ButtonInputGroup, { label: "Target", wrapButtonsAndAddGaps: true }, Object.keys(targetMap)
4073
+ .map((target) => {
4074
+ const description = genHumanReadableName(target);
4075
+ return (React__default["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) => {
4076
+ actionErrorFilterState.target[target] = checked;
4049
4077
  dispatch({
4050
4078
  type: ActionType$6.UpdateActionErrorFilterState,
4051
4079
  actionErrorFilterState,
4052
4080
  });
4053
- }, ariaLabel: "show logs of all types", selected: actionErrorFilterState.type === undefined }),
4054
- React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-action-only", text: "Action Logs Only", onSelected: () => {
4055
- actionErrorFilterState.type = LogType$1.Action;
4081
+ } }));
4082
+ })))),
4083
+ (actionErrorFilterState.type === undefined
4084
+ || actionErrorFilterState.type === LogType$1.Error) && (React__default["default"].createElement(TabBox, { title: "Error Log Details" },
4085
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
4086
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Error Message"),
4087
+ React__default["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) => {
4088
+ actionErrorFilterState.errorMessage = e.target.value;
4056
4089
  dispatch({
4057
4090
  type: ActionType$6.UpdateActionErrorFilterState,
4058
4091
  actionErrorFilterState,
4059
4092
  });
4060
- }, ariaLabel: "only show action logs", selected: actionErrorFilterState.type === LogType$1.Action }),
4061
- React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-error-only", text: "Action Error Only", onSelected: () => {
4062
- actionErrorFilterState.type = LogType$1.Error;
4093
+ } })),
4094
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
4095
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Error Code"),
4096
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for error code", value: actionErrorFilterState.errorCode, placeholder: "e.g. GC22", onChange: (e) => {
4097
+ actionErrorFilterState.errorCode = ((e.target.value)
4098
+ .trim()
4099
+ .toUpperCase());
4063
4100
  dispatch({
4064
4101
  type: ActionType$6.UpdateActionErrorFilterState,
4065
4102
  actionErrorFilterState,
4066
4103
  });
4067
- }, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
4068
- (actionErrorFilterState.type === undefined
4069
- || actionErrorFilterState.type === LogType$1.Action) && (React__default["default"].createElement(TabBox, { title: "Action Log Details" },
4070
- React__default["default"].createElement(ButtonInputGroup, { label: "Action", className: "mb-2", wrapButtonsAndAddGaps: true }, Object.keys(LogAction$1)
4071
- .map((action) => {
4072
- const description = genHumanReadableName(action);
4073
- return (React__default["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) => {
4074
- actionErrorFilterState.action[action] = checked;
4075
- dispatch({
4076
- type: ActionType$6.UpdateActionErrorFilterState,
4077
- actionErrorFilterState,
4078
- });
4079
- } }));
4080
- })),
4081
- React__default["default"].createElement(ButtonInputGroup, { label: "Target", wrapButtonsAndAddGaps: true }, Object.keys(targetMap)
4082
- .map((target) => {
4083
- const description = genHumanReadableName(target);
4084
- return (React__default["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) => {
4085
- actionErrorFilterState.target[target] = checked;
4086
- dispatch({
4087
- type: ActionType$6.UpdateActionErrorFilterState,
4088
- actionErrorFilterState,
4089
- });
4090
- } }));
4091
- })))),
4092
- (actionErrorFilterState.type === undefined
4093
- || actionErrorFilterState.type === LogType$1.Error) && (React__default["default"].createElement(TabBox, { title: "Error Log Details" },
4094
- React__default["default"].createElement("div", { className: "input-group mb-2" },
4095
- React__default["default"].createElement("span", { className: "input-group-text" }, "Error Message"),
4096
- React__default["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) => {
4097
- actionErrorFilterState.errorMessage = e.target.value;
4098
- dispatch({
4099
- type: ActionType$6.UpdateActionErrorFilterState,
4100
- actionErrorFilterState,
4101
- });
4102
- } })),
4103
- React__default["default"].createElement("div", { className: "input-group mb-2" },
4104
- React__default["default"].createElement("span", { className: "input-group-text" }, "Error Code"),
4105
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for error code", value: actionErrorFilterState.errorCode, placeholder: "e.g. GC22", onChange: (e) => {
4106
- actionErrorFilterState.errorCode = ((e.target.value)
4107
- .trim()
4108
- .toUpperCase());
4109
- dispatch({
4110
- type: ActionType$6.UpdateActionErrorFilterState,
4111
- actionErrorFilterState,
4112
- });
4113
- } }))))));
4114
- }
4115
- else if (expandedFilterDrawer === FilterDrawer.Advanced) {
4116
- // Create advanced filter ui
4117
- filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
4118
- React__default["default"].createElement(TabBox, { title: "User" },
4119
- React__default["default"].createElement("div", { className: "input-group mb-2" },
4120
- React__default["default"].createElement("span", { className: "input-group-text" }, "User First Name"),
4121
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user first name", value: advancedFilterState.userFirstName, placeholder: "e.g. Divardo", onChange: (e) => {
4122
- advancedFilterState.userFirstName = e.target.value;
4123
- dispatch({
4124
- type: ActionType$6.UpdateAdvancedFilterState,
4125
- advancedFilterState,
4126
- });
4127
- } })),
4128
- React__default["default"].createElement("div", { className: "input-group mb-2" },
4129
- React__default["default"].createElement("span", { className: "input-group-text" }, "User Last Name"),
4130
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user last name", value: advancedFilterState.userLastName, placeholder: "e.g. Calicci", onChange: (e) => {
4131
- advancedFilterState.userLastName = e.target.value;
4132
- dispatch({
4133
- type: ActionType$6.UpdateAdvancedFilterState,
4134
- advancedFilterState,
4135
- });
4136
- } })),
4137
- React__default["default"].createElement("div", { className: "input-group mb-2" },
4138
- React__default["default"].createElement("span", { className: "input-group-text" }, "User Email"),
4139
- React__default["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) => {
4140
- advancedFilterState.userEmail = ((e.target.value)
4104
+ } }))))));
4105
+ }
4106
+ else if (expandedFilterDrawer === FilterDrawer.Advanced) {
4107
+ // Create advanced filter ui
4108
+ filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
4109
+ React__default["default"].createElement(TabBox, { title: "User" },
4110
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
4111
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User First Name"),
4112
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user first name", value: advancedFilterState.userFirstName, placeholder: "e.g. Divardo", onChange: (e) => {
4113
+ advancedFilterState.userFirstName = e.target.value;
4114
+ dispatch({
4115
+ type: ActionType$6.UpdateAdvancedFilterState,
4116
+ advancedFilterState,
4117
+ });
4118
+ } })),
4119
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
4120
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User Last Name"),
4121
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user last name", value: advancedFilterState.userLastName, placeholder: "e.g. Calicci", onChange: (e) => {
4122
+ advancedFilterState.userLastName = e.target.value;
4123
+ dispatch({
4124
+ type: ActionType$6.UpdateAdvancedFilterState,
4125
+ advancedFilterState,
4126
+ });
4127
+ } })),
4128
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
4129
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User Email"),
4130
+ React__default["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) => {
4131
+ advancedFilterState.userEmail = ((e.target.value)
4132
+ .trim());
4133
+ dispatch({
4134
+ type: ActionType$6.UpdateAdvancedFilterState,
4135
+ advancedFilterState,
4136
+ });
4137
+ } })),
4138
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
4139
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User Canvas Id"),
4140
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user canvas id", value: advancedFilterState.userId, placeholder: "e.g. 104985", onChange: (e) => {
4141
+ const { value } = e.target;
4142
+ // Only update if value contains only numbers
4143
+ if (/^\d+$/.test(value)) {
4144
+ advancedFilterState.userId = ((e.target.value)
4141
4145
  .trim());
4142
- dispatch({
4143
- type: ActionType$6.UpdateAdvancedFilterState,
4144
- advancedFilterState,
4145
- });
4146
- } })),
4147
- React__default["default"].createElement("div", { className: "input-group mb-2" },
4148
- React__default["default"].createElement("span", { className: "input-group-text" }, "User Canvas Id"),
4149
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user canvas id", value: advancedFilterState.userId, placeholder: "e.g. 104985", onChange: (e) => {
4150
- const { value } = e.target;
4151
- // Only update if value contains only numbers
4152
- if (/^\d+$/.test(value)) {
4153
- advancedFilterState.userId = ((e.target.value)
4154
- .trim());
4155
- }
4156
- dispatch({
4157
- type: ActionType$6.UpdateAdvancedFilterState,
4158
- advancedFilterState,
4159
- });
4160
- } })),
4161
- React__default["default"].createElement(ButtonInputGroup, { label: "Role" },
4162
- React__default["default"].createElement(CheckboxButton, { text: "Students", onChanged: (checked) => {
4163
- advancedFilterState.includeLearners = checked;
4164
- dispatch({
4165
- type: ActionType$6.UpdateAdvancedFilterState,
4166
- advancedFilterState,
4167
- });
4168
- }, checked: advancedFilterState.includeLearners, ariaLabel: "show logs from students" }),
4169
- React__default["default"].createElement(CheckboxButton, { text: "Teaching Team Members", onChanged: (checked) => {
4170
- advancedFilterState.includeTTMs = checked;
4171
- dispatch({
4172
- type: ActionType$6.UpdateAdvancedFilterState,
4173
- advancedFilterState,
4174
- });
4175
- }, checked: advancedFilterState.includeTTMs, ariaLabel: "show logs from teaching team members" }),
4176
- React__default["default"].createElement(CheckboxButton, { text: "Admins", onChanged: (checked) => {
4177
- advancedFilterState.includeAdmins = checked;
4178
- dispatch({
4179
- type: ActionType$6.UpdateAdvancedFilterState,
4180
- advancedFilterState,
4181
- });
4182
- }, checked: advancedFilterState.includeAdmins, ariaLabel: "show logs from admins" }))),
4183
- React__default["default"].createElement(TabBox, { title: "Course" },
4146
+ }
4147
+ dispatch({
4148
+ type: ActionType$6.UpdateAdvancedFilterState,
4149
+ advancedFilterState,
4150
+ });
4151
+ } })),
4152
+ React__default["default"].createElement(ButtonInputGroup, { label: "Role" },
4153
+ React__default["default"].createElement(CheckboxButton, { text: "Students", onChanged: (checked) => {
4154
+ advancedFilterState.includeLearners = checked;
4155
+ dispatch({
4156
+ type: ActionType$6.UpdateAdvancedFilterState,
4157
+ advancedFilterState,
4158
+ });
4159
+ }, checked: advancedFilterState.includeLearners, ariaLabel: "show logs from students" }),
4160
+ React__default["default"].createElement(CheckboxButton, { text: "Teaching Team Members", onChanged: (checked) => {
4161
+ advancedFilterState.includeTTMs = checked;
4162
+ dispatch({
4163
+ type: ActionType$6.UpdateAdvancedFilterState,
4164
+ advancedFilterState,
4165
+ });
4166
+ }, checked: advancedFilterState.includeTTMs, ariaLabel: "show logs from teaching team members" }),
4167
+ React__default["default"].createElement(CheckboxButton, { text: "Admins", onChanged: (checked) => {
4168
+ advancedFilterState.includeAdmins = checked;
4169
+ dispatch({
4170
+ type: ActionType$6.UpdateAdvancedFilterState,
4171
+ advancedFilterState,
4172
+ });
4173
+ }, checked: advancedFilterState.includeAdmins, ariaLabel: "show logs from admins" }))),
4174
+ React__default["default"].createElement(TabBox, { title: "Course" },
4175
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
4176
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Course Name"),
4177
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course name", value: advancedFilterState.courseName, placeholder: "e.g. GLC 200", onChange: (e) => {
4178
+ advancedFilterState.courseName = e.target.value;
4179
+ dispatch({
4180
+ type: ActionType$6.UpdateAdvancedFilterState,
4181
+ advancedFilterState,
4182
+ });
4183
+ } })),
4184
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
4185
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
4186
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course canvas id", value: advancedFilterState.courseId, placeholder: "e.g. 15948", onChange: (e) => {
4187
+ const { value } = e.target;
4188
+ // Only update if value contains only numbers
4189
+ if (/^\d+$/.test(value)) {
4190
+ advancedFilterState.courseId = ((e.target.value)
4191
+ .trim());
4192
+ }
4193
+ dispatch({
4194
+ type: ActionType$6.UpdateAdvancedFilterState,
4195
+ advancedFilterState,
4196
+ });
4197
+ } }))),
4198
+ React__default["default"].createElement(TabBox, { title: "Device" },
4199
+ React__default["default"].createElement(ButtonInputGroup, { label: "Device Type" },
4200
+ React__default["default"].createElement(RadioButton, { text: "All Devices", ariaLabel: "show logs from all devices", selected: advancedFilterState.isMobile === undefined, onSelected: () => {
4201
+ advancedFilterState.isMobile = undefined;
4202
+ dispatch({
4203
+ type: ActionType$6.UpdateAdvancedFilterState,
4204
+ advancedFilterState,
4205
+ });
4206
+ } }),
4207
+ React__default["default"].createElement(RadioButton, { text: "Mobile Only", ariaLabel: "show logs from mobile devices", selected: advancedFilterState.isMobile === true, onSelected: () => {
4208
+ advancedFilterState.isMobile = true;
4209
+ dispatch({
4210
+ type: ActionType$6.UpdateAdvancedFilterState,
4211
+ advancedFilterState,
4212
+ });
4213
+ } }),
4214
+ React__default["default"].createElement(RadioButton, { text: "Desktop Only", ariaLabel: "show logs from desktop devices", selected: advancedFilterState.isMobile === false, onSelected: () => {
4215
+ advancedFilterState.isMobile = false;
4216
+ dispatch({
4217
+ type: ActionType$6.UpdateAdvancedFilterState,
4218
+ advancedFilterState,
4219
+ });
4220
+ }, noMarginOnRight: true }))),
4221
+ React__default["default"].createElement(TabBox, { title: "Source" },
4222
+ React__default["default"].createElement(ButtonInputGroup, { label: "Source Type" },
4223
+ React__default["default"].createElement(RadioButton, { text: "Both", ariaLabel: "show logs from all sources", selected: advancedFilterState.source === undefined, onSelected: () => {
4224
+ advancedFilterState.source = undefined;
4225
+ dispatch({
4226
+ type: ActionType$6.UpdateAdvancedFilterState,
4227
+ advancedFilterState,
4228
+ });
4229
+ } }),
4230
+ React__default["default"].createElement(RadioButton, { text: "Client Only", ariaLabel: "show logs from client source", selected: advancedFilterState.source === LogSource$1.Client, onSelected: () => {
4231
+ advancedFilterState.source = LogSource$1.Client;
4232
+ dispatch({
4233
+ type: ActionType$6.UpdateAdvancedFilterState,
4234
+ advancedFilterState,
4235
+ });
4236
+ } }),
4237
+ React__default["default"].createElement(RadioButton, { text: "Server Only", ariaLabel: "show logs from server source", selected: advancedFilterState.source === LogSource$1.Server, onSelected: () => {
4238
+ advancedFilterState.source = LogSource$1.Server;
4239
+ dispatch({
4240
+ type: ActionType$6.UpdateAdvancedFilterState,
4241
+ advancedFilterState,
4242
+ });
4243
+ }, noMarginOnRight: true })),
4244
+ advancedFilterState.source !== LogSource$1.Client && (React__default["default"].createElement("div", { className: "mt-2" },
4184
4245
  React__default["default"].createElement("div", { className: "input-group mb-2" },
4185
- React__default["default"].createElement("span", { className: "input-group-text" }, "Course Name"),
4186
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course name", value: advancedFilterState.courseName, placeholder: "e.g. GLC 200", onChange: (e) => {
4187
- advancedFilterState.courseName = e.target.value;
4246
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Path"),
4247
+ React__default["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) => {
4248
+ advancedFilterState.courseName = ((e.target.value)
4249
+ .trim());
4188
4250
  dispatch({
4189
4251
  type: ActionType$6.UpdateAdvancedFilterState,
4190
4252
  advancedFilterState,
4191
4253
  });
4192
4254
  } })),
4193
4255
  React__default["default"].createElement("div", { className: "input-group mb-2" },
4194
- React__default["default"].createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
4195
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course canvas id", value: advancedFilterState.courseId, placeholder: "e.g. 15948", onChange: (e) => {
4196
- const { value } = e.target;
4197
- // Only update if value contains only numbers
4198
- if (/^\d+$/.test(value)) {
4199
- advancedFilterState.courseId = ((e.target.value)
4200
- .trim());
4201
- }
4202
- dispatch({
4203
- type: ActionType$6.UpdateAdvancedFilterState,
4204
- advancedFilterState,
4205
- });
4206
- } }))),
4207
- React__default["default"].createElement(TabBox, { title: "Device" },
4208
- React__default["default"].createElement(ButtonInputGroup, { label: "Device Type" },
4209
- React__default["default"].createElement(RadioButton, { text: "All Devices", ariaLabel: "show logs from all devices", selected: advancedFilterState.isMobile === undefined, onSelected: () => {
4210
- advancedFilterState.isMobile = undefined;
4211
- dispatch({
4212
- type: ActionType$6.UpdateAdvancedFilterState,
4213
- advancedFilterState,
4214
- });
4215
- } }),
4216
- React__default["default"].createElement(RadioButton, { text: "Mobile Only", ariaLabel: "show logs from mobile devices", selected: advancedFilterState.isMobile === true, onSelected: () => {
4217
- advancedFilterState.isMobile = true;
4218
- dispatch({
4219
- type: ActionType$6.UpdateAdvancedFilterState,
4220
- advancedFilterState,
4221
- });
4222
- } }),
4223
- React__default["default"].createElement(RadioButton, { text: "Desktop Only", ariaLabel: "show logs from desktop devices", selected: advancedFilterState.isMobile === false, onSelected: () => {
4224
- advancedFilterState.isMobile = false;
4225
- dispatch({
4226
- type: ActionType$6.UpdateAdvancedFilterState,
4227
- advancedFilterState,
4228
- });
4229
- }, noMarginOnRight: true }))),
4230
- React__default["default"].createElement(TabBox, { title: "Source" },
4231
- React__default["default"].createElement(ButtonInputGroup, { label: "Source Type" },
4232
- React__default["default"].createElement(RadioButton, { text: "Both", ariaLabel: "show logs from all sources", selected: advancedFilterState.source === undefined, onSelected: () => {
4233
- advancedFilterState.source = undefined;
4234
- dispatch({
4235
- type: ActionType$6.UpdateAdvancedFilterState,
4236
- advancedFilterState,
4237
- });
4238
- } }),
4239
- React__default["default"].createElement(RadioButton, { text: "Client Only", ariaLabel: "show logs from client source", selected: advancedFilterState.source === LogSource$1.Client, onSelected: () => {
4240
- advancedFilterState.source = LogSource$1.Client;
4241
- dispatch({
4242
- type: ActionType$6.UpdateAdvancedFilterState,
4243
- advancedFilterState,
4244
- });
4245
- } }),
4246
- React__default["default"].createElement(RadioButton, { text: "Server Only", ariaLabel: "show logs from server source", selected: advancedFilterState.source === LogSource$1.Server, onSelected: () => {
4247
- advancedFilterState.source = LogSource$1.Server;
4256
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Template"),
4257
+ React__default["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) => {
4258
+ advancedFilterState.courseName = ((e.target.value)
4259
+ .trim());
4248
4260
  dispatch({
4249
4261
  type: ActionType$6.UpdateAdvancedFilterState,
4250
4262
  advancedFilterState,
4251
4263
  });
4252
- }, noMarginOnRight: true })),
4253
- advancedFilterState.source !== LogSource$1.Client && (React__default["default"].createElement("div", { className: "mt-2" },
4254
- React__default["default"].createElement("div", { className: "input-group mb-2" },
4255
- React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Path"),
4256
- React__default["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) => {
4257
- advancedFilterState.courseName = ((e.target.value)
4258
- .trim());
4259
- dispatch({
4260
- type: ActionType$6.UpdateAdvancedFilterState,
4261
- advancedFilterState,
4262
- });
4263
- } })),
4264
- React__default["default"].createElement("div", { className: "input-group mb-2" },
4265
- React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Template"),
4266
- React__default["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) => {
4267
- advancedFilterState.courseName = ((e.target.value)
4268
- .trim());
4269
- dispatch({
4270
- type: ActionType$6.UpdateAdvancedFilterState,
4271
- advancedFilterState,
4272
- });
4273
- } })))))));
4274
- }
4275
- }
4276
- // Filters UI
4277
- const filters = (React__default["default"].createElement(React__default["default"].Fragment, null,
4278
- filterToggles,
4279
- filterDrawer && (React__default["default"].createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
4280
- // Actually filter the logs
4281
- // > Perform filters
4282
- const logs = [];
4283
- Object.keys(logMap).forEach((year) => {
4284
- Object.keys(logMap[year]).forEach((month) => {
4285
- logMap[year][month].forEach((log) => {
4286
- /* ----------- Date Filter ---------- */
4287
- var _a;
4288
- // Before start date
4289
- if (
4290
- // Previous year
4291
- log.year < dateFilterState.startDate.year
4292
- // Same year, earlier month
4293
- || ((log.year === dateFilterState.startDate.year)
4294
- && (log.month < dateFilterState.startDate.month))
4295
- // Same year, same month, earlier day
4296
- || ((log.year === dateFilterState.startDate.year)
4297
- && (log.month === dateFilterState.startDate.month)
4298
- && (log.day < dateFilterState.startDate.day))) {
4299
- return;
4300
- }
4301
- // After end date
4302
- if (
4303
- // Later year
4304
- log.year > dateFilterState.endDate.year
4305
- // Same year, later month
4306
- || ((log.year === dateFilterState.endDate.year)
4307
- && (log.month > dateFilterState.endDate.month))
4308
- // Same year, same month, later day
4309
- || ((log.year === dateFilterState.endDate.year)
4310
- && (log.month === dateFilterState.endDate.month)
4311
- && (log.day > dateFilterState.endDate.day))) {
4312
- return;
4313
- }
4314
- /* --------- Context Filter --------- */
4315
- // Context doesn't match
4316
- if (
4317
- // Whole context is deselected
4318
- contextFilterState[log.context] === false
4319
- // None of the subcontexts are selected
4320
- || (
4321
- // Has subcontexts
4322
- typeof contextFilterState[log.context] !== 'boolean'
4323
- // None of the subcontexts are selected
4324
- && Object.values((_a = contextFilterState[log.context]) !== null && _a !== void 0 ? _a : {})
4325
- .every((isSelected) => {
4326
- return !isSelected;
4327
- }))) {
4328
- return;
4329
- }
4330
- // Subcontext doesn't match
4331
- if (
4332
- // Log context is not "uncategorized" (no point in further filters)
4333
- log.context !== LogBuiltInMetadata.Context.Uncategorized
4334
- // Log has a subcontext
4335
- && log.subcontext
4336
- // Context has subcontexts
4337
- && (contextFilterState[log.context]
4338
- && contextFilterState[log.context] !== false
4339
- && contextFilterState[log.context] !== true)
4340
- // Subcontext is not selected
4341
- && !contextFilterState[log.context][log.subcontext]) {
4342
- return;
4343
- }
4344
- /* -------------- Tags -------------- */
4345
- // No tags match
4346
- if (
4347
- // At least one tag is required
4348
- Object.values(tagFilterState)
4349
- .filter((isSelected) => {
4350
- return isSelected;
4351
- })
4352
- .length > 0
4353
- // No tags match
4354
- && log.tags.every((tag) => {
4355
- return !tagFilterState[tag];
4356
- })) {
4357
- return;
4358
- }
4359
- /* ------- Actions and Errors ------- */
4360
- // Log type doesn't match
4361
- if (
4362
- // Filter won't allow all types
4363
- actionErrorFilterState.type !== undefined
4364
- // Log type doesn't match
4365
- && actionErrorFilterState.type !== log.type) {
4366
- return;
4367
- }
4368
- // Filter errors
4369
- if (log.type === LogType$1.Error) {
4370
- // Message doesn't match
4371
- if (
4372
- // Message exists
4373
- log.errorMessage
4374
- // Message filter exists
4375
- && actionErrorFilterState.errorMessage.trim().length > 0
4376
- // Message doesn't match
4377
- && log.errorMessage.toLowerCase().includes(actionErrorFilterState.errorMessage.trim().toLowerCase())) {
4378
- return;
4379
- }
4380
- // Code doesn't match
4381
- if (
4382
- // Code exists
4383
- log.errorCode
4384
- // Code filter exists
4385
- && actionErrorFilterState.errorCode.trim().length > 0
4386
- // Code doesn't match
4387
- && log.errorCode.toUpperCase().includes(actionErrorFilterState.errorCode.trim().toUpperCase())) {
4388
- return;
4389
- }
4390
- }
4391
- // Filter actions
4392
- if (log.type === LogType$1.Action) {
4393
- // Target isn't selected
4394
- if (
4395
- // Target exists
4396
- log.target
4397
- // Target isn't selected
4398
- && !actionErrorFilterState.target[log.target]) {
4399
- return;
4400
- }
4401
- // Action
4402
- if (
4403
- // Action exists
4404
- log.action
4405
- // Action isn't selected
4406
- && !actionErrorFilterState.action[log.action]) {
4407
- return;
4408
- }
4409
- }
4410
- /* --------- Advanced Filter -------- */
4411
- // First name doesn't match
4412
- if (
4413
- // First name exists
4414
- log.userFirstName
4415
- // First name query doesn't match
4416
- && !log.userFirstName.toLowerCase().includes(advancedFilterState.userFirstName.toLowerCase().trim())) {
4417
- return;
4418
- }
4419
- // Last name doesn't match
4420
- if (
4421
- // Last name exists
4422
- log.userLastName
4423
- // Last name query doesn't match
4424
- && !log.userLastName.toLowerCase().includes(advancedFilterState.userLastName.toLowerCase().trim())) {
4425
- return;
4426
- }
4427
- // Email doesn't match
4428
- if (
4429
- // Email exists
4430
- log.userEmail
4431
- // Email query doesn't match
4432
- && !log.userEmail.toLowerCase().includes(advancedFilterState.userEmail.toLowerCase().trim())) {
4433
- return;
4434
- }
4435
- // User id doesn't match
4436
- if (
4437
- // User id exists
4438
- log.userId
4439
- // User id doesn't match
4440
- && !String(log.userId).includes(advancedFilterState.userId.trim())) {
4441
- return;
4442
- }
4443
- // Learner not allowed
4444
- if (
4445
- // User is a learner
4446
- log.isLearner
4447
- // Learners aren't included
4448
- && !advancedFilterState.includeLearners) {
4449
- return;
4450
- }
4451
- // TTM not allowed
4452
- if (
4453
- // User is a ttm
4454
- log.isTTM
4455
- // TTMs aren't included
4456
- && !advancedFilterState.includeTTMs) {
4457
- return;
4458
- }
4459
- // Admin not allowed
4460
- if (
4461
- // User is an admin
4462
- log.isAdmin
4463
- // Admins aren't included
4464
- && !advancedFilterState.includeAdmins) {
4465
- return;
4466
- }
4467
- // Course Id doesn't match
4468
- if (
4469
- // Course Id exists
4470
- log.courseId
4471
- // Course Id doesn't match
4472
- && !String(log.courseId).includes(advancedFilterState.courseId.trim())) {
4473
- return;
4474
- }
4475
- // Course name doesn't match
4476
- if (
4477
- // Course name exists
4478
- log.courseName
4479
- // Course name doesn't match
4480
- && !String(log.courseName).includes(advancedFilterState.courseName.trim())) {
4481
- return;
4482
- }
4483
- // Mobile filter doesn't match
4484
- if (
4485
- // Mobile filter exists
4486
- advancedFilterState.isMobile !== undefined
4487
- // Device info exists
4488
- && log.device
4489
- // Mobile filter doesn't match
4490
- && (advancedFilterState.isMobile !== log.device.isMobile)) {
4491
- return;
4492
- }
4493
- // Log source doesn't match
4494
- if (
4495
- // Source filter exists
4496
- advancedFilterState.source !== undefined
4497
- // Source info exists
4498
- && log.source
4499
- // Source filter doesn't match
4500
- && (advancedFilterState.source !== log.source)) {
4501
- return;
4502
- }
4503
- // Route path doesn't match (Only for server source)
4504
- if (
4505
- // Source is server
4506
- (log.source === LogSource$1.Server)
4507
- // Route path is being filtered
4508
- && (advancedFilterState.routePath.trim().length)
4509
- // Route path doesn't match
4510
- && !(log.routePath.includes(advancedFilterState.routePath.trim()))) {
4511
- return;
4512
- }
4513
- // Route template doesn't match (Only for server source)
4514
- if (
4515
- // Source is server
4516
- (log.source === LogSource$1.Server)
4517
- // Route template is being filtered
4518
- && (advancedFilterState.routeTemplate.trim().length)
4519
- // Route template doesn't match
4520
- && !(log.routeTemplate.includes(advancedFilterState.routeTemplate.trim()))) {
4521
- return;
4522
- }
4523
- /* -------------- Done -------------- */
4524
- // Made it past all filters. Add to the list
4525
- logs.push(log);
4526
- });
4527
- });
4528
- });
4529
- /*----------------------------------------*/
4530
- /* ---------------- Data ---------------- */
4531
- /*----------------------------------------*/
4532
- // Nothing to show notice
4533
- const noLogsNotice = (logs.length === 0
4534
- ? (React__default["default"].createElement("div", { className: "alert alert-warning text-center mt-2" },
4264
+ } })))))));
4265
+ }
4266
+ }
4267
+ // Filters UI
4268
+ const filters = (React__default["default"].createElement(React__default["default"].Fragment, null,
4269
+ filterToggles,
4270
+ filterDrawer && (React__default["default"].createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
4271
+ // Main body
4272
+ body = (React__default["default"].createElement(React__default["default"].Fragment, null,
4273
+ filters,
4274
+ React__default["default"].createElement("div", { className: "mt-2" },
4275
+ React__default["default"].createElement(IntelliTable, { title: "Matching Logs:", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns }),
4276
+ logs.length === 0 && (React__default["default"].createElement("div", { className: "alert alert-warning text-center mt-2" },
4535
4277
  React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
4536
- React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet.")))
4537
- : undefined);
4538
- // Create intelliTable
4539
- const dataTable = (React__default["default"].createElement(IntelliTable, { title: "Matching Logs:", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns }));
4540
- // Main body
4541
- body = (React__default["default"].createElement(React__default["default"].Fragment, null,
4542
- filters,
4543
- React__default["default"].createElement("div", { className: "mt-2" },
4544
- dataTable,
4545
- noLogsNotice)));
4546
- }
4278
+ React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))),
4279
+ paginationControls)));
4547
4280
  /* ---------- Wrap in Modal --------- */
4548
4281
  return (React__default["default"].createElement("div", { className: "LogReviewer-outer-container" },
4549
4282
  React__default["default"].createElement("style", null, style$2),
@@ -14055,33 +13788,134 @@ const initServer = (opts) => {
14055
13788
  }),
14056
13789
  }));
14057
13790
  /**
14058
- * Get all logs for a certain month
14059
- * @author Gabe Abrams
14060
- * @param {number} year the year to query (e.g. 2022)
14061
- * @param {number} month the month to query (e.g. 1 = January)
14062
- * @returns {Log[]} list of logs from the given month
14063
- */
14064
- opts.app.get(`${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/:year/months/:month`, genRouteHandler({
13791
+ * Get filtered logs based on provided filters
13792
+ * @author Gabe Abrams, Yuen Ler Chow
13793
+ * @returns {Log[]} list of logs that match the filters
13794
+ */
13795
+ opts.app.get(`${LOG_REVIEW_ROUTE_PATH_PREFIX}/logs`, genRouteHandler({
14065
13796
  paramTypes: {
14066
- year: ParamType$1.Int,
14067
- month: ParamType$1.Int,
14068
13797
  pageNumber: ParamType$1.Int,
13798
+ filters: ParamType$1.JSON,
14069
13799
  },
14070
13800
  handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
14071
13801
  // Get user info
14072
- const { year, month, pageNumber, userId, isAdmin, } = params;
13802
+ const { pageNumber, userId, isAdmin, filters, } = params;
14073
13803
  // Validate user
14074
13804
  const canReview = yield canReviewLogs(userId, isAdmin);
14075
13805
  if (!canReview) {
14076
13806
  throw new ErrorWithCode('You cannot access this resource because you do not have the appropriate permissions.', ReactKitErrorCode$1.NotAllowedToReviewLogs);
14077
13807
  }
13808
+ // Build MongoDB query based on filters
13809
+ const query = {};
13810
+ /* -------------- Date Filter ------------- */
13811
+ const { startDate, endDate } = filters.dateFilterState;
13812
+ const startTimestamp = new Date(startDate.year, startDate.month - 1, startDate.day).getTime();
13813
+ const endTimestamp = new Date(endDate.year, endDate.month - 1, endDate.day + 1).getTime() - 1;
13814
+ query.timestamp = {
13815
+ $gte: startTimestamp,
13816
+ $lte: endTimestamp,
13817
+ };
13818
+ /* ------------ Context Filter ------------ */
13819
+ const { contextFilterState } = filters;
13820
+ const contextConditions = [];
13821
+ Object.keys(contextFilterState).forEach((context) => {
13822
+ const value = contextFilterState[context];
13823
+ if (typeof value === 'boolean') {
13824
+ if (value) {
13825
+ // The entire context is selected
13826
+ contextConditions.push({ context });
13827
+ }
13828
+ }
13829
+ else {
13830
+ // The context has subcontexts
13831
+ const subcontexts = Object.keys(value).filter((subcontext) => { return value[subcontext]; });
13832
+ if (subcontexts.length > 0) {
13833
+ contextConditions.push({
13834
+ context,
13835
+ subcontext: { $in: subcontexts },
13836
+ });
13837
+ }
13838
+ }
13839
+ });
13840
+ if (contextConditions.length > 0) {
13841
+ query.$or = contextConditions;
13842
+ }
13843
+ /* -------------- Tag Filter -------------- */
13844
+ const { tagFilterState } = filters;
13845
+ const selectedTags = Object.keys(tagFilterState).filter((tag) => { return tagFilterState[tag]; });
13846
+ if (selectedTags.length > 0) {
13847
+ query.tags = { $in: selectedTags };
13848
+ }
13849
+ /* --------- Action/Error Filter ---------- */
13850
+ const { actionErrorFilterState } = filters;
13851
+ if (actionErrorFilterState.type) {
13852
+ query.type = actionErrorFilterState.type;
13853
+ }
13854
+ if (actionErrorFilterState.type === 'error' && actionErrorFilterState.errorMessage) {
13855
+ query.errorMessage = { $regex: actionErrorFilterState.errorMessage, $options: 'i' };
13856
+ }
13857
+ if (actionErrorFilterState.type === 'error' && actionErrorFilterState.errorCode) {
13858
+ query.errorCode = { $regex: actionErrorFilterState.errorCode, $options: 'i' };
13859
+ }
13860
+ if (actionErrorFilterState.type === 'action') {
13861
+ const selectedTargets = Object.keys(actionErrorFilterState.target).filter((target) => { return actionErrorFilterState.target[target]; });
13862
+ const selectedActions = Object.keys(actionErrorFilterState.action).filter((action) => { return actionErrorFilterState.action[action]; });
13863
+ if (selectedTargets.length > 0) {
13864
+ query.target = { $in: selectedTargets };
13865
+ }
13866
+ if (selectedActions.length > 0) {
13867
+ query.action = { $in: selectedActions };
13868
+ }
13869
+ }
13870
+ /* ------------ Advanced Filter ----------- */
13871
+ const { advancedFilterState } = filters;
13872
+ if (advancedFilterState.userFirstName) {
13873
+ query.userFirstName = { $regex: advancedFilterState.userFirstName, $options: 'i' };
13874
+ }
13875
+ if (advancedFilterState.userLastName) {
13876
+ query.userLastName = { $regex: advancedFilterState.userLastName, $options: 'i' };
13877
+ }
13878
+ if (advancedFilterState.userEmail) {
13879
+ query.userEmail = { $regex: advancedFilterState.userEmail, $options: 'i' };
13880
+ }
13881
+ if (advancedFilterState.userId) {
13882
+ query.userId = parseInt(advancedFilterState.userId, 10);
13883
+ }
13884
+ const roles = [];
13885
+ if (advancedFilterState.includeLearners) {
13886
+ roles.push({ isLearner: true });
13887
+ }
13888
+ if (advancedFilterState.includeTTMs) {
13889
+ roles.push({ isTTM: true });
13890
+ }
13891
+ if (advancedFilterState.includeAdmins) {
13892
+ roles.push({ isAdmin: true });
13893
+ }
13894
+ if (roles.length > 0) {
13895
+ query.$and = [{ $or: roles }];
13896
+ }
13897
+ if (advancedFilterState.courseId) {
13898
+ query.courseId = parseInt(advancedFilterState.courseId, 10);
13899
+ }
13900
+ if (advancedFilterState.courseName) {
13901
+ query.courseName = { $regex: advancedFilterState.courseName, $options: 'i' };
13902
+ }
13903
+ if (advancedFilterState.isMobile !== undefined) {
13904
+ query['device.isMobile'] = advancedFilterState.isMobile;
13905
+ }
13906
+ if (advancedFilterState.source) {
13907
+ query.source = advancedFilterState.source;
13908
+ }
13909
+ if (advancedFilterState.routePath) {
13910
+ query.routePath = { $regex: advancedFilterState.routePath, $options: 'i' };
13911
+ }
13912
+ if (advancedFilterState.routeTemplate) {
13913
+ query.routeTemplate = { $regex: advancedFilterState.routeTemplate, $options: 'i' };
13914
+ }
14078
13915
  // Query for logs
14079
13916
  const response = yield _logCollection.findPaged({
14080
- query: {
14081
- year,
14082
- month,
14083
- },
14084
- perPage: 1000,
13917
+ query,
13918
+ perPage: 50,
14085
13919
  pageNumber,
14086
13920
  });
14087
13921
  // Return response