dce-reactkit 3.9.4-beta-prompt.2 → 3.9.4-beta-logreviewer.3

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 (42) hide show
  1. package/dist/cjs/index.js +670 -869
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/AppWrapper.d.ts +0 -24
  4. package/dist/cjs/types/constants/LOG_REVIEW_GET_LOGS_ROUTE.d.ts +6 -0
  5. package/dist/cjs/types/index.d.ts +2 -2
  6. package/dist/cjs/types/types/LogReviewerFilterState/ActionErrorFilterState.d.ts +13 -0
  7. package/dist/cjs/types/types/LogReviewerFilterState/AdvancedFilterState.d.ts +17 -0
  8. package/dist/cjs/types/types/LogReviewerFilterState/ContextFilterState.d.ts +6 -0
  9. package/dist/cjs/types/types/LogReviewerFilterState/DateFilterState.d.ts +13 -0
  10. package/dist/cjs/types/types/LogReviewerFilterState/TagFilterState.d.ts +4 -0
  11. package/dist/cjs/types/types/LogReviewerFilterState/index.d.ts +17 -0
  12. package/dist/cjs/types/types/ModalType.d.ts +0 -1
  13. package/dist/esm/index.js +672 -870
  14. package/dist/esm/index.js.map +1 -1
  15. package/dist/esm/types/components/AppWrapper.d.ts +0 -24
  16. package/dist/esm/types/constants/LOG_REVIEW_GET_LOGS_ROUTE.d.ts +6 -0
  17. package/dist/esm/types/index.d.ts +2 -2
  18. package/dist/esm/types/types/LogReviewerFilterState/ActionErrorFilterState.d.ts +13 -0
  19. package/dist/esm/types/types/LogReviewerFilterState/AdvancedFilterState.d.ts +17 -0
  20. package/dist/esm/types/types/LogReviewerFilterState/ContextFilterState.d.ts +6 -0
  21. package/dist/esm/types/types/LogReviewerFilterState/DateFilterState.d.ts +13 -0
  22. package/dist/esm/types/types/LogReviewerFilterState/TagFilterState.d.ts +4 -0
  23. package/dist/esm/types/types/LogReviewerFilterState/index.d.ts +17 -0
  24. package/dist/esm/types/types/ModalType.d.ts +0 -1
  25. package/dist/index.d.ts +1 -26
  26. package/package.json +1 -1
  27. package/src/components/AppWrapper.tsx +0 -216
  28. package/src/components/LogReviewer.tsx +840 -1232
  29. package/src/components/Modal/index.tsx +0 -3
  30. package/src/constants/LOG_REVIEW_GET_LOGS_ROUTE.ts +9 -0
  31. package/src/index.ts +0 -2
  32. package/src/server/initServer.ts +216 -19
  33. package/src/types/LogReviewerFilterState/ActionErrorFilterState.ts +21 -0
  34. package/src/types/LogReviewerFilterState/AdvancedFilterState.ts +33 -0
  35. package/src/types/LogReviewerFilterState/ContextFilterState.ts +13 -0
  36. package/src/types/LogReviewerFilterState/DateFilterState.ts +23 -0
  37. package/src/types/LogReviewerFilterState/TagFilterState.ts +6 -0
  38. package/src/types/LogReviewerFilterState/index.ts +24 -0
  39. package/src/types/ModalType.tsx +0 -1
  40. package/dist/cjs/types/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.d.ts +0 -6
  41. package/dist/esm/types/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.d.ts +0 -6
  42. package/src/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.ts +0 -9
@@ -49,9 +49,6 @@ const modalTypeToModalButtonTypes: {
49
49
  [ModalType.Okay]: [
50
50
  ModalButtonType.Okay,
51
51
  ],
52
- [ModalType.Cancel]: [
53
- ModalButtonType.Cancel,
54
- ],
55
52
  [ModalType.OkayCancel]: [
56
53
  ModalButtonType.Okay,
57
54
  ModalButtonType.Cancel,
@@ -0,0 +1,9 @@
1
+ import ROUTE_PATH_PREFIX from './ROUTE_PATH_PREFIX';
2
+
3
+ /**
4
+ * Path of the route for getting logs for log review
5
+ * @author Gabe Abrams
6
+ */
7
+ const LOG_REVIEW_GET_LOGS_ROUTE = `/admin${ROUTE_PATH_PREFIX}/logs`;
8
+
9
+ export default LOG_REVIEW_GET_LOGS_ROUTE;
package/src/index.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  // Import components
2
2
  import AppWrapper, {
3
3
  alert,
4
- prompt,
5
4
  confirm,
6
5
  showFatalError,
7
6
  addFatalErrorHandler,
@@ -149,7 +148,6 @@ export {
149
148
  Dropdown,
150
149
  // Global functions
151
150
  alert,
152
- prompt,
153
151
  confirm,
154
152
  showFatalError,
155
153
  // Errors
@@ -2,7 +2,7 @@
2
2
  import ErrorWithCode from '../errors/ErrorWithCode';
3
3
 
4
4
  // Import shared constants
5
- import LOG_REVIEW_ROUTE_PATH_PREFIX from '../constants/LOG_REVIEW_ROUTE_PATH_PREFIX';
5
+ import LOG_REVIEW_ROUTE_PATH_PREFIX from '../constants/LOG_REVIEW_GET_LOGS_ROUTE';
6
6
  import LOG_ROUTE_PATH from '../constants/LOG_ROUTE_PATH';
7
7
  import LOG_REVIEW_STATUS_ROUTE from '../constants/LOG_REVIEW_STATUS_ROUTE';
8
8
 
@@ -10,10 +10,11 @@ import LOG_REVIEW_STATUS_ROUTE from '../constants/LOG_REVIEW_STATUS_ROUTE';
10
10
  import genRouteHandler from '../helpers/genRouteHandler';
11
11
 
12
12
  // Import shared types
13
- import Log from '../types/Log';
14
13
  import LogFunction from '../types/LogFunction';
15
14
  import ParamType from '../types/ParamType';
16
15
  import ReactKitErrorCode from '../types/ReactKitErrorCode';
16
+ import LogReviewerFilterState from '../types/LogReviewerFilterState';
17
+ import LogType from '../types/LogType';
17
18
 
18
19
  // Types
19
20
  type GetLaunchInfoFunction = (req: any) => {
@@ -234,28 +235,36 @@ const initServer = (
234
235
  );
235
236
 
236
237
  /**
237
- * Get all logs for a certain month
238
- * @author Gabe Abrams
239
- * @param {number} year the year to query (e.g. 2022)
240
- * @param {number} month the month to query (e.g. 1 = January)
241
- * @returns {Log[]} list of logs from the given month
242
- */
238
+ * Get filtered logs based on provided filters
239
+ * @author Gabe Abrams, Yuen Ler Chow
240
+ * @param pageNumber the page number to get
241
+ * @param filters the filters to apply to the logs
242
+ * @returns {Log[]} list of logs that match the filters
243
+ */
243
244
  opts.app.get(
244
- `${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/:year/months/:month`,
245
+ `${LOG_REVIEW_ROUTE_PATH_PREFIX}`,
245
246
  genRouteHandler({
246
247
  paramTypes: {
247
- year: ParamType.Int,
248
- month: ParamType.Int,
248
+ pageNumber: ParamType.Int,
249
+ filters: ParamType.JSON,
249
250
  },
250
251
  handler: async ({ params }) => {
251
- // Get user info
252
+ // Get user info
252
253
  const {
253
- year,
254
- month,
254
+ pageNumber,
255
255
  userId,
256
256
  isAdmin,
257
+ filters,
257
258
  } = params;
258
259
 
260
+ const {
261
+ dateFilterState,
262
+ contextFilterState,
263
+ tagFilterState,
264
+ actionErrorFilterState,
265
+ advancedFilterState,
266
+ } = filters as LogReviewerFilterState;
267
+
259
268
  // Validate user
260
269
  const canReview = await canReviewLogs(userId, isAdmin);
261
270
  if (!canReview) {
@@ -265,14 +274,202 @@ const initServer = (
265
274
  );
266
275
  }
267
276
 
277
+ // Build MongoDB query based on filters
278
+ const query: { [k: string]: any } = {};
279
+
280
+ /* -------------- Date Filter ------------- */
281
+
282
+ // Convert start and end dates from the dateFilterState into timestamps
283
+ const { startDate, endDate } = dateFilterState;
284
+ const startTimestamp = new Date(startDate.year, startDate.month - 1, startDate.day).getTime();
285
+ const endTimestamp = new Date(endDate.year, endDate.month - 1, endDate.day + 1).getTime() - 1;
286
+
287
+ // Add a date range condition to the query
288
+ query.timestamp = {
289
+ $gte: startTimestamp,
290
+ $lte: endTimestamp,
291
+ };
292
+
293
+ /* ------------ Context Filter ------------ */
294
+
295
+ // Process context filters to include selected contexts and subcontexts
296
+ const selectedContexts: string[] = [];
297
+ const selectedSubcontexts: string[] = [];
298
+
299
+ // Process each context filter
300
+ Object.keys(contextFilterState).forEach((context) => {
301
+ const value = contextFilterState[context];
302
+ if (typeof value === 'boolean') {
303
+ if (value) {
304
+ selectedContexts.push(context);
305
+ }
306
+ } else {
307
+ // At least one subcontext is selected
308
+ const atLeastOneSubcontextSelected = (
309
+ Object.values(value)
310
+ .some((subcontextValue) => {
311
+ return subcontextValue;
312
+ })
313
+ );
314
+ if (atLeastOneSubcontextSelected) {
315
+ selectedContexts.push(context);
316
+ }
317
+ // Add all selected subcontexts
318
+ Object.keys(value).forEach((subcontext) => {
319
+ if (value[subcontext]) {
320
+ selectedSubcontexts.push(subcontext);
321
+ }
322
+ });
323
+ }
324
+ });
325
+
326
+ // Add context and subcontext conditions to the query if any are selected
327
+ if (selectedContexts.length > 0) {
328
+ query.context = { $in: selectedContexts };
329
+ }
330
+
331
+ if (selectedSubcontexts.length > 0) {
332
+ query.subcontext = { $in: selectedSubcontexts };
333
+ }
334
+
335
+ /* -------------- Tag Filter -------------- */
336
+
337
+ const selectedTags = Object.keys(tagFilterState).filter((tag) => { return tagFilterState[tag]; });
338
+ if (selectedTags.length > 0) {
339
+ query.tags = { $in: selectedTags };
340
+ }
341
+
342
+ /* --------- Action/Error Filter ---------- */
343
+
344
+ if (actionErrorFilterState.type) {
345
+ query.type = actionErrorFilterState.type;
346
+ }
347
+
348
+ if (actionErrorFilterState.type === LogType.Error) {
349
+ if (actionErrorFilterState.errorMessage) {
350
+ // Add error message to the query.
351
+ // $i is used for case-insensitive search, and $regex is used for partial matching
352
+ query.errorMessage = {
353
+ $regex: actionErrorFilterState.errorMessage,
354
+ $options: 'i',
355
+ };
356
+ }
357
+
358
+ if (actionErrorFilterState.errorCode) {
359
+ query.errorCode = {
360
+ $regex: actionErrorFilterState.errorCode,
361
+ $options: 'i',
362
+ };
363
+ }
364
+ }
365
+
366
+ if (actionErrorFilterState.type === LogType.Action) {
367
+ const selectedTargets = (
368
+ Object.keys(actionErrorFilterState.target)
369
+ .filter((target) => {
370
+ return actionErrorFilterState.target[target];
371
+ })
372
+ );
373
+ const selectedActions = (
374
+ Object.keys(actionErrorFilterState.action)
375
+ .filter((action) => {
376
+ return actionErrorFilterState.action[action];
377
+ })
378
+ );
379
+ if (selectedTargets.length > 0) {
380
+ query.target = { $in: selectedTargets };
381
+ }
382
+ if (selectedActions.length > 0) {
383
+ query.action = { $in: selectedActions };
384
+ }
385
+ }
386
+
387
+ /* ------------ Advanced Filter ----------- */
388
+
389
+ if (advancedFilterState.userFirstName) {
390
+ query.userFirstName = {
391
+ $regex: advancedFilterState.userFirstName,
392
+ $options: 'i',
393
+ };
394
+ }
395
+
396
+ if (advancedFilterState.userLastName) {
397
+ query.userLastName = {
398
+ $regex: advancedFilterState.userLastName,
399
+ $options: 'i',
400
+ };
401
+ }
402
+
403
+ if (advancedFilterState.userEmail) {
404
+ query.userEmail = {
405
+ $regex: advancedFilterState.userEmail,
406
+ $options: 'i',
407
+ };
408
+ }
409
+
410
+ if (advancedFilterState.userId) {
411
+ query.userId = Number.parseInt(advancedFilterState.userId, 10);
412
+ }
413
+
414
+ const roles = [];
415
+ if (advancedFilterState.includeLearners) {
416
+ roles.push({ isLearner: true });
417
+ }
418
+ if (advancedFilterState.includeTTMs) {
419
+ roles.push({ isTTM: true });
420
+ }
421
+ if (advancedFilterState.includeAdmins) {
422
+ roles.push({ isAdmin: true });
423
+ }
424
+ // If any roles are selected, add them to the query
425
+ if (roles.length > 0) {
426
+ // The $or operator is used to match any of the roles
427
+ // The $and operator is to ensure that other conditions in the query are met
428
+ query.$and = [{ $or: roles }];
429
+ }
430
+
431
+ if (advancedFilterState.courseId) {
432
+ query.courseId = Number.parseInt(advancedFilterState.courseId, 10);
433
+ }
434
+
435
+ if (advancedFilterState.courseName) {
436
+ query.courseName = {
437
+ $regex: advancedFilterState.courseName,
438
+ $options: 'i',
439
+ };
440
+ }
441
+
442
+ if (advancedFilterState.isMobile !== undefined) {
443
+ query['device.isMobile'] = Boolean(advancedFilterState.isMobile);
444
+ }
445
+
446
+ if (advancedFilterState.source) {
447
+ query.source = advancedFilterState.source;
448
+ }
449
+
450
+ if (advancedFilterState.routePath) {
451
+ query.routePath = {
452
+ $regex: advancedFilterState.routePath,
453
+ $options: 'i',
454
+ };
455
+ }
456
+
457
+ if (advancedFilterState.routeTemplate) {
458
+ query.routeTemplate = {
459
+ $regex: advancedFilterState.routeTemplate,
460
+ $options: 'i',
461
+ };
462
+ }
463
+
268
464
  // Query for logs
269
- const logs: Log[] = await _logCollection.find({
270
- year,
271
- month,
465
+ const response = await _logCollection.findPaged({
466
+ query,
467
+ perPage: 50,
468
+ pageNumber,
272
469
  });
273
470
 
274
- // Return logs
275
- return logs;
471
+ // Return response
472
+ return response;
276
473
  },
277
474
  }),
278
475
  );
@@ -0,0 +1,21 @@
1
+ import LogType from '../LogType';
2
+
3
+ // Action filter state (only relevant for action logs)
4
+ type ActionErrorFilterState = {
5
+ // Required type of log
6
+ type: LogType | undefined, // If undefined, no filter applied
7
+ // Query for error message (only relevant if type is error)
8
+ errorMessage: string, // If empty, no filter applied
9
+ // Query for error code (only relevant if type is error)
10
+ errorCode: string, // If empty, no filter applied
11
+ // Action targets to include (only relevant if type is action)
12
+ target: {
13
+ [k: string]: boolean
14
+ },
15
+ // Action types to include (only relevant if type is action)
16
+ action: {
17
+ [k: string]: boolean
18
+ },
19
+ };
20
+
21
+ export default ActionErrorFilterState;
@@ -0,0 +1,33 @@
1
+ import LogSource from '../LogSource';
2
+
3
+ // Advanced filter state
4
+ type AdvancedFilterState = {
5
+ // Query for user first name (case insensitive)
6
+ userFirstName: string, // If empty, no filter applied
7
+ // Query for user last name (case insensitive)
8
+ userLastName: string, // If empty, no filter applied
9
+ // Query for user email (case insensitive)
10
+ userEmail: string, // If empty, no filter applied
11
+ // Match for userId (numerical)
12
+ userId: string, // If empty, no filter applied
13
+ // If true, include students
14
+ includeLearners: boolean,
15
+ // If true, include ttms
16
+ includeTTMs: boolean,
17
+ // If true, include admins
18
+ includeAdmins: boolean,
19
+ // Match for courseId (numerical)
20
+ courseId: string, // If empty, no filter applied
21
+ // Query for course name (case insensitive)
22
+ courseName: string, // If empty, no filter applied
23
+ // Required isMobile value
24
+ isMobile: (true | false | undefined), // If undefined, no filter applied
25
+ // Required log source value
26
+ source: LogSource | undefined, // If undefined, no filter applied
27
+ // Query for route path (only relevant if source is server)
28
+ routePath: string, // If empty, no filter applied
29
+ // Query for route template (only relevant if source is server)
30
+ routeTemplate: string, // If empty, no filter applied
31
+ };
32
+
33
+ export default AdvancedFilterState;
@@ -0,0 +1,13 @@
1
+ // Context filter state
2
+ type ContextFilterState = {
3
+ [k: string]: (
4
+ // No subcontexts
5
+ | boolean // True if selected
6
+ // Includes subcontexts
7
+ | {
8
+ [k: string]: boolean // True if selected
9
+ }
10
+ )
11
+ };
12
+
13
+ export default ContextFilterState;
@@ -0,0 +1,23 @@
1
+ // Date filter state
2
+ type DateFilterState = {
3
+ // Current start date
4
+ startDate: {
5
+ // Full year
6
+ year: number,
7
+ // 1-indexed month
8
+ month: number,
9
+ // 1-indexed day
10
+ day: number,
11
+ },
12
+ // Current end date
13
+ endDate: {
14
+ // Full year
15
+ year: number,
16
+ // 1-indexed month
17
+ month: number,
18
+ // 1-indexed day
19
+ day: number,
20
+ },
21
+ };
22
+
23
+ export default DateFilterState;
@@ -0,0 +1,6 @@
1
+ // Tag filter state
2
+ type TagFilterState = {
3
+ [k: string]: boolean // tag => true if in the list of tags to show
4
+ };
5
+
6
+ export default TagFilterState;
@@ -0,0 +1,24 @@
1
+ import DateFilterState from './DateFilterState';
2
+ import ContextFilterState from './ContextFilterState';
3
+ import TagFilterState from './TagFilterState';
4
+ import ActionErrorFilterState from './ActionErrorFilterState';
5
+ import AdvancedFilterState from './AdvancedFilterState';
6
+
7
+ /**
8
+ * A bundle of filter state objects for each type of filter
9
+ * @author Gabe Abrams
10
+ */
11
+ type LogReviewerFilterState = {
12
+ // Date filter state
13
+ dateFilterState: DateFilterState,
14
+ // Context filter state
15
+ contextFilterState: ContextFilterState,
16
+ // Tag filter state
17
+ tagFilterState: TagFilterState,
18
+ // Action error filter state
19
+ actionErrorFilterState: ActionErrorFilterState,
20
+ // Advanced filter state
21
+ advancedFilterState: AdvancedFilterState,
22
+ };
23
+
24
+ export default LogReviewerFilterState;
@@ -4,7 +4,6 @@
4
4
  */
5
5
  enum ModalType {
6
6
  Okay = 'okay', // [Okay]
7
- Cancel = 'cancel', // [Cancel]
8
7
  OkayCancel = 'okay-cancel', // [Okay] [Cancel]
9
8
  YesNo = 'yes-no', // [Yes] [No]
10
9
  YesNoCancel = 'yes-no-cancel', // [Yes] [No] [Cancel]
@@ -1,6 +0,0 @@
1
- /**
2
- * Path of the route for storing client-side logs
3
- * @author Gabe Abrams
4
- */
5
- declare const LOG_REVIEW_ROUTE_PATH_PREFIX: string;
6
- export default LOG_REVIEW_ROUTE_PATH_PREFIX;
@@ -1,6 +0,0 @@
1
- /**
2
- * Path of the route for storing client-side logs
3
- * @author Gabe Abrams
4
- */
5
- declare const LOG_REVIEW_ROUTE_PATH_PREFIX: string;
6
- export default LOG_REVIEW_ROUTE_PATH_PREFIX;
@@ -1,9 +0,0 @@
1
- import ROUTE_PATH_PREFIX from './ROUTE_PATH_PREFIX';
2
-
3
- /**
4
- * Path of the route for storing client-side logs
5
- * @author Gabe Abrams
6
- */
7
- const LOG_REVIEW_ROUTE_PATH_PREFIX = `/admin${ROUTE_PATH_PREFIX}/logs`;
8
-
9
- export default LOG_REVIEW_ROUTE_PATH_PREFIX;