dce-reactkit 3.2.2-beta.5 → 3.2.2-beta.50
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 +728 -433
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/ButtonInputGroup.d.ts +1 -0
- package/dist/cjs/types/components/Drawer.d.ts +1 -0
- package/dist/cjs/types/components/IntelliTable.d.ts +1 -0
- package/dist/cjs/types/components/ItemPicker/NestableItemList.d.ts +1 -0
- package/dist/cjs/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/cjs/types/types/LogAction.d.ts +16 -16
- package/dist/cjs/types/types/LogBuiltInMetadata.d.ts +1 -1
- package/dist/cjs/types/types/LogMetadataContextMap.d.ts +11 -0
- package/dist/cjs/types/types/LogMetadataTagMap.d.ts +8 -0
- package/dist/cjs/types/types/LogMetadataTargetMap.d.ts +8 -0
- package/dist/cjs/types/types/LogMetadataType.d.ts +6 -12
- package/dist/esm/index.js +729 -434
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/ButtonInputGroup.d.ts +1 -0
- package/dist/esm/types/components/Drawer.d.ts +1 -0
- package/dist/esm/types/components/IntelliTable.d.ts +1 -0
- package/dist/esm/types/components/ItemPicker/NestableItemList.d.ts +1 -0
- package/dist/esm/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/esm/types/types/LogAction.d.ts +16 -16
- package/dist/esm/types/types/LogBuiltInMetadata.d.ts +1 -1
- package/dist/esm/types/types/LogMetadataContextMap.d.ts +11 -0
- package/dist/esm/types/types/LogMetadataTagMap.d.ts +8 -0
- package/dist/esm/types/types/LogMetadataTargetMap.d.ts +8 -0
- package/dist/esm/types/types/LogMetadataType.d.ts +6 -12
- package/dist/index.d.ts +51 -29
- package/package.json +1 -1
- package/src/components/ButtonInputGroup.tsx +5 -2
- package/src/components/CSVDownloadButton.tsx +2 -2
- package/src/components/Drawer.tsx +9 -1
- package/src/components/IntelliTable.tsx +181 -26
- package/src/components/ItemPicker/NestableItemList.tsx +32 -14
- package/src/components/ItemPicker/index.tsx +4 -0
- package/src/components/LogReviewer.tsx +635 -406
- package/src/components/SimpleDateChooser.tsx +27 -18
- package/src/helpers/genCSV.ts +22 -4
- package/src/helpers/genRouteHandler.ts +2 -2
- package/src/helpers/logClientEvent.tsx +1 -1
- package/src/server/initServer.ts +10 -3
- package/src/types/LogAction.ts +16 -16
- package/src/types/LogBuiltInMetadata.ts +5 -5
- package/src/types/LogMetadataContextMap.ts +15 -0
- package/src/types/LogMetadataTagMap.ts +9 -0
- package/src/types/LogMetadataTargetMap.ts +9 -0
- package/src/types/LogMetadataType.ts +8 -15
package/dist/cjs/index.js
CHANGED
|
@@ -519,14 +519,14 @@ const LOG_ROUTE_PATH = `${ROUTE_PATH_PREFIX}/log`;
|
|
|
519
519
|
const LogBuiltInMetadata = {
|
|
520
520
|
// Contexts
|
|
521
521
|
Context: {
|
|
522
|
-
Uncategorized: '
|
|
523
|
-
ServerRenderedErrorPage: '
|
|
524
|
-
ServerEndpointError: '
|
|
525
|
-
ClientFatalError: '
|
|
522
|
+
Uncategorized: 'Uncategorized',
|
|
523
|
+
ServerRenderedErrorPage: 'ServerRenderedErrorPage',
|
|
524
|
+
ServerEndpointError: 'ServerEndpointError',
|
|
525
|
+
ClientFatalError: 'ClientFatalError',
|
|
526
526
|
},
|
|
527
527
|
// Targets
|
|
528
528
|
Target: {
|
|
529
|
-
|
|
529
|
+
NoTarget: 'NoTarget',
|
|
530
530
|
},
|
|
531
531
|
};
|
|
532
532
|
|
|
@@ -682,7 +682,7 @@ const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
682
682
|
? opts.error.stack
|
|
683
683
|
: undefined),
|
|
684
684
|
target: (opts.action
|
|
685
|
-
? ((_g = opts.target) !== null && _g !== void 0 ? _g : LogBuiltInMetadata.Target.
|
|
685
|
+
? ((_g = opts.target) !== null && _g !== void 0 ? _g : LogBuiltInMetadata.Target.NoTarget)
|
|
686
686
|
: undefined),
|
|
687
687
|
action: (opts.action
|
|
688
688
|
? opts.action
|
|
@@ -1188,21 +1188,21 @@ const ButtonInputGroup = (props) => {
|
|
|
1188
1188
|
/*------------------------------------------------------------------------*/
|
|
1189
1189
|
/* -------------- Props ------------- */
|
|
1190
1190
|
// Destructure all props
|
|
1191
|
-
const { label, minLabelWidth, children, } = props;
|
|
1191
|
+
const { label, minLabelWidth, children, className, } = props;
|
|
1192
1192
|
/*------------------------------------------------------------------------*/
|
|
1193
1193
|
/* Render */
|
|
1194
1194
|
/*------------------------------------------------------------------------*/
|
|
1195
1195
|
/*----------------------------------------*/
|
|
1196
1196
|
/* Main UI */
|
|
1197
1197
|
/*----------------------------------------*/
|
|
1198
|
-
return (React__default["default"].createElement("div", { className:
|
|
1198
|
+
return (React__default["default"].createElement("div", { className: `input-group ${className !== null && className !== void 0 ? className : ''}` },
|
|
1199
1199
|
React__default["default"].createElement("div", { className: "input-group-prepend d-flex w-100" },
|
|
1200
1200
|
React__default["default"].createElement("span", { className: "input-group-text", style: {
|
|
1201
1201
|
minWidth: (minLabelWidth !== null && minLabelWidth !== void 0 ? minLabelWidth : undefined),
|
|
1202
1202
|
borderTopRightRadius: 0,
|
|
1203
1203
|
borderBottomRightRadius: 0,
|
|
1204
1204
|
} }, label),
|
|
1205
|
-
React__default["default"].createElement("span", { className: "input-group-text flex-grow-1 rounded-right", style: {
|
|
1205
|
+
React__default["default"].createElement("span", { className: "input-group-text flex-grow-1 rounded-right d-flex flex-wrap", style: {
|
|
1206
1206
|
backgroundColor: 'white',
|
|
1207
1207
|
borderTopLeftRadius: 0,
|
|
1208
1208
|
borderBottomLeftRadius: 0,
|
|
@@ -1347,7 +1347,7 @@ const SimpleDateChooser = (props) => {
|
|
|
1347
1347
|
let startMonth = today.month;
|
|
1348
1348
|
if (chooseFromPast) {
|
|
1349
1349
|
startMonth -= Math.max(0, numMonthsToShow - 1);
|
|
1350
|
-
|
|
1350
|
+
while (startMonth <= 0) {
|
|
1351
1351
|
startMonth += 12;
|
|
1352
1352
|
startYear -= 1;
|
|
1353
1353
|
}
|
|
@@ -1355,22 +1355,35 @@ const SimpleDateChooser = (props) => {
|
|
|
1355
1355
|
for (let i = 0; i < numMonthsToShow; i++) {
|
|
1356
1356
|
// Get month and year info
|
|
1357
1357
|
const unmoddedMonth = (startMonth + i);
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1358
|
+
let month = unmoddedMonth;
|
|
1359
|
+
while (month > 12) {
|
|
1360
|
+
month -= 12;
|
|
1361
|
+
}
|
|
1361
1362
|
const monthName = getMonthName(month).full;
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
: startYear);
|
|
1363
|
+
// Year is start year +1 for each 12 months
|
|
1364
|
+
const year = (startYear + (Math.floor(unmoddedMonth / 12)));
|
|
1365
1365
|
// Figure out which days are allowed
|
|
1366
1366
|
const days = [];
|
|
1367
1367
|
const numDaysInMonth = (new Date(year, month, 0)).getDate();
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1368
|
+
if (chooseFromPast) {
|
|
1369
|
+
// Past selection
|
|
1370
|
+
const numDaysToAdd = ((month === today.month)
|
|
1371
|
+
? today.day // Current month, only add up to today
|
|
1372
|
+
: numDaysInMonth // Past month, add all days
|
|
1373
|
+
);
|
|
1374
|
+
for (let day = 1; day <= numDaysToAdd; day++) {
|
|
1375
|
+
days.push(day);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
else {
|
|
1379
|
+
// Future selection: add all remaining days of the month
|
|
1380
|
+
const firstDay = (month === today.month
|
|
1381
|
+
? today.day // Current month: start at current date
|
|
1382
|
+
: 1 // Future month: start at beginning of month
|
|
1383
|
+
);
|
|
1384
|
+
for (let day = firstDay; day <= numDaysInMonth; day++) {
|
|
1385
|
+
days.push(day);
|
|
1386
|
+
}
|
|
1374
1387
|
}
|
|
1375
1388
|
choices.push({
|
|
1376
1389
|
choiceName: `${monthName} ${year}`,
|
|
@@ -1441,14 +1454,16 @@ const Drawer = (props) => {
|
|
|
1441
1454
|
/*------------------------------------------------------------------------*/
|
|
1442
1455
|
/* -------------- Props ------------- */
|
|
1443
1456
|
// Destructure all props
|
|
1444
|
-
const { children, } = props;
|
|
1457
|
+
const { customBackgroundColor, children, } = props;
|
|
1445
1458
|
/*------------------------------------------------------------------------*/
|
|
1446
1459
|
/* Render */
|
|
1447
1460
|
/*------------------------------------------------------------------------*/
|
|
1448
1461
|
/*----------------------------------------*/
|
|
1449
1462
|
/* Main UI */
|
|
1450
1463
|
/*----------------------------------------*/
|
|
1451
|
-
return (React__default["default"].createElement("div", { className: "Drawer-container"
|
|
1464
|
+
return (React__default["default"].createElement("div", { className: "Drawer-container", style: {
|
|
1465
|
+
backgroundColor: (customBackgroundColor !== null && customBackgroundColor !== void 0 ? customBackgroundColor : undefined),
|
|
1466
|
+
} },
|
|
1452
1467
|
React__default["default"].createElement("style", null, style$4),
|
|
1453
1468
|
children));
|
|
1454
1469
|
};
|
|
@@ -1911,24 +1926,25 @@ const CopiableBox = (props) => {
|
|
|
1911
1926
|
/**
|
|
1912
1927
|
* Reusable nested item picker
|
|
1913
1928
|
* @author Yuen Ler Chow
|
|
1929
|
+
* @author Gabe Abrams
|
|
1914
1930
|
*/
|
|
1915
1931
|
/* ------------- Actions ------------ */
|
|
1916
1932
|
// Types of actions
|
|
1917
1933
|
var ActionType$2;
|
|
1918
1934
|
(function (ActionType) {
|
|
1919
|
-
// Toggle whether
|
|
1920
|
-
ActionType["
|
|
1935
|
+
// Toggle whether a child are being shown
|
|
1936
|
+
ActionType["ToggleChild"] = "toggle-child";
|
|
1921
1937
|
})(ActionType$2 || (ActionType$2 = {}));
|
|
1922
1938
|
/**
|
|
1923
1939
|
* Reducer that executes actions
|
|
1924
|
-
* @author
|
|
1940
|
+
* @author Gabe Abrams
|
|
1925
1941
|
* @param state current state
|
|
1926
1942
|
* @param action action to execute
|
|
1927
1943
|
*/
|
|
1928
1944
|
const reducer$2 = (state, action) => {
|
|
1929
1945
|
switch (action.type) {
|
|
1930
|
-
case ActionType$2.
|
|
1931
|
-
return {
|
|
1946
|
+
case ActionType$2.ToggleChild: {
|
|
1947
|
+
return Object.assign(Object.assign({}, state), { childExpanded: Object.assign(Object.assign({}, state.childExpanded), { [String(action.id)]: !state.childExpanded[String(action.id)] }) });
|
|
1932
1948
|
}
|
|
1933
1949
|
default: {
|
|
1934
1950
|
return state;
|
|
@@ -1946,14 +1962,19 @@ const NestableItemList = (props) => {
|
|
|
1946
1962
|
// Destructure all props
|
|
1947
1963
|
const { items, onChanged, } = props;
|
|
1948
1964
|
/* -------------- State ------------- */
|
|
1965
|
+
// Create initial map of child expanded booleans
|
|
1966
|
+
const initChildExpanded = {};
|
|
1967
|
+
items.forEach((item) => {
|
|
1968
|
+
initChildExpanded[String(item.id)] = false;
|
|
1969
|
+
});
|
|
1949
1970
|
// Initial state
|
|
1950
1971
|
const initialState = {
|
|
1951
|
-
|
|
1972
|
+
childExpanded: initChildExpanded,
|
|
1952
1973
|
};
|
|
1953
1974
|
// Initialize state
|
|
1954
1975
|
const [state, dispatch] = React.useReducer(reducer$2, initialState);
|
|
1955
1976
|
// Destructure common state
|
|
1956
|
-
const {
|
|
1977
|
+
const { childExpanded, } = state;
|
|
1957
1978
|
/*------------------------------------------------------------------------*/
|
|
1958
1979
|
/* Component Functions */
|
|
1959
1980
|
/*------------------------------------------------------------------------*/
|
|
@@ -2041,14 +2062,15 @@ const NestableItemList = (props) => {
|
|
|
2041
2062
|
backgroundColor: 'transparent',
|
|
2042
2063
|
}, type: "button", onClick: () => {
|
|
2043
2064
|
dispatch({
|
|
2044
|
-
type: ActionType$2.
|
|
2065
|
+
type: ActionType$2.ToggleChild,
|
|
2066
|
+
id: item.id,
|
|
2045
2067
|
});
|
|
2046
|
-
}, "aria-label": `${
|
|
2047
|
-
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon:
|
|
2068
|
+
}, "aria-label": `${childExpanded[item.id] ? 'Hide' : 'Show'} items in ${item.name}` },
|
|
2069
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: childExpanded[item.id] ? freeSolidSvgIcons.faChevronDown : freeSolidSvgIcons.faChevronRight })))),
|
|
2048
2070
|
React__default["default"].createElement(CheckboxButton, { className: `NestableItemList-CheckboxButton-${item.id}`, text: item.name, checked: item.isGroup ? allChecked(item.children) : item.checked, dashed: item.isGroup ? !noneChecked(item.children) : false, onChanged: (checked) => {
|
|
2049
2071
|
onChanged(changeChecked(item.id, checked, items));
|
|
2050
2072
|
}, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light }),
|
|
2051
|
-
item.isGroup &&
|
|
2073
|
+
(item.isGroup && childExpanded[item.id]) && (React__default["default"].createElement("div", { className: "NestableItemList-children-container", style: {
|
|
2052
2074
|
paddingLeft: '2.2rem',
|
|
2053
2075
|
} },
|
|
2054
2076
|
React__default["default"].createElement(NestableItemList, { items: item.children, onChanged: (updatedItems) => {
|
|
@@ -2070,7 +2092,7 @@ const ItemPicker = (props) => {
|
|
|
2070
2092
|
/*------------------------------------------------------------------------*/
|
|
2071
2093
|
/* -------------- Props ------------- */
|
|
2072
2094
|
// Destructure all props
|
|
2073
|
-
const { title, items, onChanged, } = props;
|
|
2095
|
+
const { title, items, onChanged, noBottomMargin, } = props;
|
|
2074
2096
|
/*------------------------------------------------------------------------*/
|
|
2075
2097
|
/* Component Functions */
|
|
2076
2098
|
/*------------------------------------------------------------------------*/
|
|
@@ -2080,11 +2102,34 @@ const ItemPicker = (props) => {
|
|
|
2080
2102
|
/*----------------------------------------*/
|
|
2081
2103
|
/* Main UI */
|
|
2082
2104
|
/*----------------------------------------*/
|
|
2083
|
-
return (React__default["default"].createElement(TabBox, { title: title },
|
|
2105
|
+
return (React__default["default"].createElement(TabBox, { title: title, noBottomMargin: noBottomMargin },
|
|
2084
2106
|
React__default["default"].createElement("div", { style: { overflowX: 'auto' } },
|
|
2085
2107
|
React__default["default"].createElement(NestableItemList, { items: items, onChanged: onChanged }))));
|
|
2086
2108
|
};
|
|
2087
2109
|
|
|
2110
|
+
// Import shared helpers
|
|
2111
|
+
/**
|
|
2112
|
+
* Get a human-readable description of a date (all in ET)
|
|
2113
|
+
* @author Gabe Abrams
|
|
2114
|
+
* @param [dateOrTimestamp=today] the date or timestamp for the date to describe
|
|
2115
|
+
* @returns human-readable description of the date
|
|
2116
|
+
*/
|
|
2117
|
+
const getHumanReadableDate = (dateOrTimestamp) => {
|
|
2118
|
+
// Get the date info
|
|
2119
|
+
const { month, day, year, } = getTimeInfoInET(dateOrTimestamp);
|
|
2120
|
+
const currYear = getTimeInfoInET().year;
|
|
2121
|
+
// Get the short month description
|
|
2122
|
+
const monthName = getMonthName(month).short;
|
|
2123
|
+
// Create start of description
|
|
2124
|
+
let description = `${monthName} ${day}${getOrdinal(day)}`;
|
|
2125
|
+
// Add on year if it's different
|
|
2126
|
+
if (year !== currYear) {
|
|
2127
|
+
description += ` ${year}`;
|
|
2128
|
+
}
|
|
2129
|
+
// Return description
|
|
2130
|
+
return description;
|
|
2131
|
+
};
|
|
2132
|
+
|
|
2088
2133
|
/**
|
|
2089
2134
|
* Path of the route for storing client-side logs
|
|
2090
2135
|
* @author Gabe Abrams
|
|
@@ -2124,37 +2169,37 @@ var LogType$1 = LogType;
|
|
|
2124
2169
|
var LogAction;
|
|
2125
2170
|
(function (LogAction) {
|
|
2126
2171
|
// Target was opened by the user (it was not on screen, but now it is)
|
|
2127
|
-
LogAction["Open"] = "
|
|
2172
|
+
LogAction["Open"] = "Open";
|
|
2128
2173
|
// Target was closed by the user (it was on screen, but now it is not)
|
|
2129
|
-
LogAction["Close"] = "
|
|
2174
|
+
LogAction["Close"] = "Close";
|
|
2130
2175
|
// Target was cancelled by the user (it was on closed without saving)
|
|
2131
|
-
LogAction["Cancel"] = "
|
|
2176
|
+
LogAction["Cancel"] = "Cancel";
|
|
2132
2177
|
// Target was expanded by the user (it always remains on screen, but size was changed)
|
|
2133
|
-
LogAction["Expand"] = "
|
|
2178
|
+
LogAction["Expand"] = "Expand";
|
|
2134
2179
|
// Target was collapsed by the user (it always remains on screen, but size was changed)
|
|
2135
|
-
LogAction["Collapse"] = "
|
|
2180
|
+
LogAction["Collapse"] = "Collapse";
|
|
2136
2181
|
// Target was viewed by the user (only for items that are not opened or closed, those must use Open/Close actions)
|
|
2137
|
-
LogAction["View"] = "
|
|
2182
|
+
LogAction["View"] = "View";
|
|
2138
2183
|
// Target interrupted the user (popup, dialog, validation message, etc. appeared without user prompting)
|
|
2139
|
-
LogAction["Interrupt"] = "
|
|
2184
|
+
LogAction["Interrupt"] = "Interrupt";
|
|
2140
2185
|
// Target was created by the user (it did not exist before)
|
|
2141
|
-
LogAction["Create"] = "
|
|
2186
|
+
LogAction["Create"] = "Create";
|
|
2142
2187
|
// Target was edited by the user (it existed and was changed)
|
|
2143
|
-
LogAction["Edit"] = "
|
|
2188
|
+
LogAction["Edit"] = "Edit";
|
|
2144
2189
|
// Target was deleted by the user (it existed and now it doesn't)
|
|
2145
|
-
LogAction["Delete"] = "
|
|
2190
|
+
LogAction["Delete"] = "Delete";
|
|
2146
2191
|
// Target was added by the user (it already existed and was added to another place)
|
|
2147
|
-
LogAction["Add"] = "
|
|
2192
|
+
LogAction["Add"] = "Add";
|
|
2148
2193
|
// Target was removed by the user (it was removed from something but still exists)
|
|
2149
|
-
LogAction["Remove"] = "
|
|
2194
|
+
LogAction["Remove"] = "Remove";
|
|
2150
2195
|
// Target was activated by the user (click, check, tap, keypress, etc.)
|
|
2151
|
-
LogAction["Activate"] = "
|
|
2196
|
+
LogAction["Activate"] = "Activate";
|
|
2152
2197
|
// Target was deactivated by the user (click away, uncheck, tap outside of, tab away, etc.)
|
|
2153
|
-
LogAction["Deactivate"] = "
|
|
2198
|
+
LogAction["Deactivate"] = "Deactivate";
|
|
2154
2199
|
// User showed interest in a target (hover, peek, etc.)
|
|
2155
|
-
LogAction["Peek"] = "
|
|
2200
|
+
LogAction["Peek"] = "Peek";
|
|
2156
2201
|
// Unknown action
|
|
2157
|
-
LogAction["Unknown"] = "
|
|
2202
|
+
LogAction["Unknown"] = "Unknown";
|
|
2158
2203
|
})(LogAction || (LogAction = {}));
|
|
2159
2204
|
var LogAction$1 = LogAction;
|
|
2160
2205
|
|
|
@@ -2196,12 +2241,12 @@ const roundToNumDecimals = (num, numDecimals) => {
|
|
|
2196
2241
|
* @returns escaped cell text
|
|
2197
2242
|
*/
|
|
2198
2243
|
const escapeCellText = (text) => {
|
|
2199
|
-
if (!text.includes(',')) {
|
|
2244
|
+
if (!String(text).includes(',')) {
|
|
2200
2245
|
// No need to escape
|
|
2201
|
-
return text;
|
|
2246
|
+
return String(text);
|
|
2202
2247
|
}
|
|
2203
2248
|
// Perform escape
|
|
2204
|
-
return `"${text.replace(/"/g, '""')}`;
|
|
2249
|
+
return `"${String(text).replace(/"/g, '""')}`;
|
|
2205
2250
|
};
|
|
2206
2251
|
/**
|
|
2207
2252
|
* Generate a CSV file
|
|
@@ -2220,9 +2265,26 @@ const genCSV = (data, columns) => {
|
|
|
2220
2265
|
.join(','));
|
|
2221
2266
|
// Add each row
|
|
2222
2267
|
data.forEach((datum) => {
|
|
2268
|
+
csv += '\n';
|
|
2223
2269
|
csv += (columns
|
|
2224
2270
|
.map((column) => {
|
|
2225
|
-
|
|
2271
|
+
let contents;
|
|
2272
|
+
const cell = datum[column.param];
|
|
2273
|
+
if (typeof cell === 'string'
|
|
2274
|
+
|| typeof cell === 'number') {
|
|
2275
|
+
contents = String(cell);
|
|
2276
|
+
}
|
|
2277
|
+
else if (typeof cell === 'undefined'
|
|
2278
|
+
|| cell === null) {
|
|
2279
|
+
contents = '';
|
|
2280
|
+
}
|
|
2281
|
+
else if (typeof cell === 'object') {
|
|
2282
|
+
contents = JSON.stringify(cell);
|
|
2283
|
+
}
|
|
2284
|
+
else {
|
|
2285
|
+
contents = '';
|
|
2286
|
+
}
|
|
2287
|
+
return escapeCellText(contents);
|
|
2226
2288
|
})
|
|
2227
2289
|
.join(','));
|
|
2228
2290
|
});
|
|
@@ -2251,11 +2313,12 @@ const CSVDownloadButton = (props) => {
|
|
|
2251
2313
|
/* Main UI */
|
|
2252
2314
|
/*----------------------------------------*/
|
|
2253
2315
|
// Render the button
|
|
2254
|
-
return (React__default["default"].createElement("a", { id: id, download: filename, href: `data:application/octet-stream,${csv}`, className: `CSVDownloadButton-button ${className !== null && className !== void 0 ? className : 'btn btn-secondary'}`, "aria-label": (ariaLabel
|
|
2316
|
+
return (React__default["default"].createElement("a", { id: id, download: filename, href: `data:application/octet-stream,${encodeURIComponent(csv)}`, className: `CSVDownloadButton-button ${className !== null && className !== void 0 ? className : 'btn btn-secondary'}`, "aria-label": (ariaLabel
|
|
2255
2317
|
? `Click to download ${filename}`
|
|
2256
2318
|
: ariaLabel), style: style, onClick: onClick },
|
|
2257
2319
|
!children && (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
2258
|
-
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCloudDownloadAlt
|
|
2320
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCloudDownloadAlt }),
|
|
2321
|
+
' ',
|
|
2259
2322
|
"Download CSV")),
|
|
2260
2323
|
children));
|
|
2261
2324
|
};
|
|
@@ -2279,9 +2342,13 @@ var ActionType$1;
|
|
|
2279
2342
|
// Toggle sort column param
|
|
2280
2343
|
ActionType["ToggleSortColumn"] = "toggle-sort-column";
|
|
2281
2344
|
// Toggle the visibility of a column
|
|
2282
|
-
ActionType["
|
|
2345
|
+
ActionType["UpdateColumnVisibility"] = "update-column-visibility";
|
|
2283
2346
|
// Toggle the column visibility customization modal
|
|
2284
2347
|
ActionType["ToggleColVisCusModalVisibility"] = "toggle-col-vis-cus-modal-visibility";
|
|
2348
|
+
// Show all columns
|
|
2349
|
+
ActionType["ShowAllColumns"] = "show-all-columns";
|
|
2350
|
+
// Hide all columns
|
|
2351
|
+
ActionType["HideAllColumns"] = "hide-all-columns";
|
|
2285
2352
|
})(ActionType$1 || (ActionType$1 = {}));
|
|
2286
2353
|
/**
|
|
2287
2354
|
* Reducer that executes actions
|
|
@@ -2303,14 +2370,28 @@ const reducer$1 = (state, action) => {
|
|
|
2303
2370
|
// Stop sorting by column
|
|
2304
2371
|
return Object.assign(Object.assign({}, state), { sortColumnParam: undefined, sortType: SortType.Ascending });
|
|
2305
2372
|
}
|
|
2306
|
-
case ActionType$1.
|
|
2373
|
+
case ActionType$1.UpdateColumnVisibility: {
|
|
2307
2374
|
const { columnVisibilityMap } = state;
|
|
2308
|
-
columnVisibilityMap[action.param] =
|
|
2375
|
+
columnVisibilityMap[action.param] = action.visible;
|
|
2309
2376
|
return Object.assign(Object.assign({}, state), { columnVisibilityMap });
|
|
2310
2377
|
}
|
|
2311
2378
|
case ActionType$1.ToggleColVisCusModalVisibility: {
|
|
2312
2379
|
return Object.assign(Object.assign({}, state), { columnVisibilityCustomizationModalVisible: !state.columnVisibilityCustomizationModalVisible });
|
|
2313
2380
|
}
|
|
2381
|
+
case ActionType$1.ShowAllColumns: {
|
|
2382
|
+
const { columnVisibilityMap } = state;
|
|
2383
|
+
Object.keys(columnVisibilityMap).forEach((param) => {
|
|
2384
|
+
columnVisibilityMap[param] = true;
|
|
2385
|
+
});
|
|
2386
|
+
return Object.assign(Object.assign({}, state), { columnVisibilityMap });
|
|
2387
|
+
}
|
|
2388
|
+
case ActionType$1.HideAllColumns: {
|
|
2389
|
+
const { columnVisibilityMap } = state;
|
|
2390
|
+
Object.keys(columnVisibilityMap).forEach((param) => {
|
|
2391
|
+
columnVisibilityMap[param] = false;
|
|
2392
|
+
});
|
|
2393
|
+
return Object.assign(Object.assign({}, state), { columnVisibilityMap });
|
|
2394
|
+
}
|
|
2314
2395
|
default: {
|
|
2315
2396
|
return state;
|
|
2316
2397
|
}
|
|
@@ -2326,7 +2407,18 @@ const IntelliTable = (props) => {
|
|
|
2326
2407
|
var _a;
|
|
2327
2408
|
/* -------------- Props ------------- */
|
|
2328
2409
|
// Destructure all props
|
|
2329
|
-
const { title, id,
|
|
2410
|
+
const { title, id, columns, } = props;
|
|
2411
|
+
// Get data, show empty row if none
|
|
2412
|
+
const data = ((props.data && props.data.length > 0)
|
|
2413
|
+
? props.data
|
|
2414
|
+
: [{ id: 'empty-row' }]);
|
|
2415
|
+
// Get CSV filename
|
|
2416
|
+
let filename = `${title}.csv`;
|
|
2417
|
+
if (props.csvName) {
|
|
2418
|
+
filename = (props.csvName.endsWith('.csv')
|
|
2419
|
+
? props.csvName
|
|
2420
|
+
: `${props.csvName}.csv`);
|
|
2421
|
+
}
|
|
2330
2422
|
/* -------------- State ------------- */
|
|
2331
2423
|
// Initial state
|
|
2332
2424
|
const initColumnVisibilityMap = {};
|
|
@@ -2343,21 +2435,49 @@ const IntelliTable = (props) => {
|
|
|
2343
2435
|
const [state, dispatch] = React.useReducer(reducer$1, initialState);
|
|
2344
2436
|
// Destructure common state
|
|
2345
2437
|
const { sortColumnParam, sortType, columnVisibilityMap, columnVisibilityCustomizationModalVisible, } = state;
|
|
2438
|
+
/*------------------------------------------------------------------------*/
|
|
2439
|
+
/* Render */
|
|
2440
|
+
/*------------------------------------------------------------------------*/
|
|
2441
|
+
/*----------------------------------------*/
|
|
2442
|
+
/* Modal */
|
|
2443
|
+
/*----------------------------------------*/
|
|
2444
|
+
// Modal that may be defined
|
|
2445
|
+
let modal;
|
|
2346
2446
|
/* ------- Col Vis Customization Modal ------ */
|
|
2347
2447
|
if (columnVisibilityCustomizationModalVisible) {
|
|
2348
2448
|
// Create modal
|
|
2349
|
-
(React__default["default"].createElement(Modal, { type: ModalType$1.Okay, title: "Choose columns to show:", onClose: () => {
|
|
2449
|
+
modal = (React__default["default"].createElement(Modal, { type: ModalType$1.Okay, title: "Choose columns to show:", onClose: () => {
|
|
2450
|
+
const noColumnsSelected = (Object.values(columnVisibilityMap)
|
|
2451
|
+
.every((isSelected) => {
|
|
2452
|
+
return !isSelected;
|
|
2453
|
+
}));
|
|
2454
|
+
if (noColumnsSelected) {
|
|
2455
|
+
return alert$1('Choose at least one column', 'To continue, you have to choose at least one column to show');
|
|
2456
|
+
}
|
|
2350
2457
|
dispatch({
|
|
2351
2458
|
type: ActionType$1.ToggleColVisCusModalVisibility,
|
|
2352
2459
|
});
|
|
2353
|
-
}
|
|
2354
|
-
|
|
2460
|
+
}, okayVariant: Variant$1.Light },
|
|
2461
|
+
columns.map((column) => {
|
|
2462
|
+
return (React__default["default"].createElement(CheckboxButton, { key: column.param, id: `IntelliTable-${id}-toggle-visibility-${column.param}`, className: "mb-2", text: column.title, onChanged: (checked) => {
|
|
2463
|
+
dispatch({
|
|
2464
|
+
type: ActionType$1.UpdateColumnVisibility,
|
|
2465
|
+
param: column.param,
|
|
2466
|
+
visible: checked,
|
|
2467
|
+
});
|
|
2468
|
+
}, checked: columnVisibilityMap[column.param], ariaLabel: `show "${column.title}" column in the ${title} table`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Secondary }));
|
|
2469
|
+
}),
|
|
2470
|
+
React__default["default"].createElement("div", { className: "mt-3" }, "Or you can:"),
|
|
2471
|
+
React__default["default"].createElement("button", { type: "button", id: `IntelliTable-${id}-select-all-columns`, className: "btn btn-secondary me-2", "aria-label": `show all columns in the ${title} table`, onClick: () => {
|
|
2472
|
+
dispatch({
|
|
2473
|
+
type: ActionType$1.ShowAllColumns,
|
|
2474
|
+
});
|
|
2475
|
+
} }, "Select All"),
|
|
2476
|
+
React__default["default"].createElement("button", { type: "button", id: `IntelliTable-${id}-select-none-columns`, className: "btn btn-secondary", "aria-label": `hide all columns in the ${title} table`, onClick: () => {
|
|
2355
2477
|
dispatch({
|
|
2356
|
-
type: ActionType$1.
|
|
2357
|
-
param: column.param,
|
|
2478
|
+
type: ActionType$1.HideAllColumns,
|
|
2358
2479
|
});
|
|
2359
|
-
}
|
|
2360
|
-
})));
|
|
2480
|
+
} }, "Deselect All")));
|
|
2361
2481
|
}
|
|
2362
2482
|
/*----------------------------------------*/
|
|
2363
2483
|
/* Main UI */
|
|
@@ -2371,6 +2491,7 @@ const IntelliTable = (props) => {
|
|
|
2371
2491
|
// Custom info based on current sort type
|
|
2372
2492
|
let sortButtonAriaLabel;
|
|
2373
2493
|
let sortIcon = freeSolidSvgIcons.faSort;
|
|
2494
|
+
let sortingByThisColumn = false;
|
|
2374
2495
|
if (!sortColumnParam) {
|
|
2375
2496
|
// Not being sorted yet
|
|
2376
2497
|
sortButtonAriaLabel = `sort ascending by ${column.title}`;
|
|
@@ -2378,6 +2499,7 @@ const IntelliTable = (props) => {
|
|
|
2378
2499
|
}
|
|
2379
2500
|
else if (column.param === sortColumnParam) {
|
|
2380
2501
|
// Already sorted by this column
|
|
2502
|
+
sortingByThisColumn = true;
|
|
2381
2503
|
if (sortType === SortType.Ascending) {
|
|
2382
2504
|
// Sorted ascending
|
|
2383
2505
|
sortButtonAriaLabel = `sort descending by ${column.title}`;
|
|
@@ -2395,11 +2517,14 @@ const IntelliTable = (props) => {
|
|
|
2395
2517
|
sortIcon = freeSolidSvgIcons.faSort;
|
|
2396
2518
|
}
|
|
2397
2519
|
// Create the cell UI
|
|
2398
|
-
return (React__default["default"].createElement("th", { key: column.param, scope: "col", id: `IntelliTable-${id}-header-${column.param}
|
|
2399
|
-
|
|
2400
|
-
|
|
2520
|
+
return (React__default["default"].createElement("th", { key: column.param, scope: "col", id: `IntelliTable-${id}-header-${column.param}`, className: "text-start", style: {
|
|
2521
|
+
borderRight: '0.05rem solid #555',
|
|
2522
|
+
borderLeft: '0.05rem solid #555',
|
|
2523
|
+
} },
|
|
2524
|
+
React__default["default"].createElement("div", { className: "d-flex align-items-center justify-content-start flex-row h-100" },
|
|
2525
|
+
React__default["default"].createElement("span", { className: "text-nowrap" }, column.title),
|
|
2401
2526
|
React__default["default"].createElement("div", null,
|
|
2402
|
-
React__default["default"].createElement("button", { type: "button", className:
|
|
2527
|
+
React__default["default"].createElement("button", { type: "button", id: `IntelliTable-${id}-sort-by-${column.param}-button`, className: `btn btn-${sortingByThisColumn ? 'light' : 'secondary'} btn-sm ms-1 ps-1 pe-1 pt-0 pb-0`, "aria-label": sortButtonAriaLabel, onClick: () => {
|
|
2403
2528
|
dispatch({
|
|
2404
2529
|
type: ActionType$1.ToggleSortColumn,
|
|
2405
2530
|
param: column.param,
|
|
@@ -2417,8 +2542,28 @@ const IntelliTable = (props) => {
|
|
|
2417
2542
|
const descending = (sortType === SortType.Descending);
|
|
2418
2543
|
if (sortColumnParam) {
|
|
2419
2544
|
sortedData.sort((a, b) => {
|
|
2545
|
+
var _a, _b;
|
|
2420
2546
|
const aVal = a[sortColumnParam];
|
|
2421
2547
|
const bVal = b[sortColumnParam];
|
|
2548
|
+
// Tiebreaker sort by timestamp, most recent first (used if tied)
|
|
2549
|
+
const tiebreaker = (((_a = b.timestamp) !== null && _a !== void 0 ? _a : 0)
|
|
2550
|
+
- ((_b = a.timestamp) !== null && _b !== void 0 ? _b : 0));
|
|
2551
|
+
// Auto-sort undefined and null to end of list
|
|
2552
|
+
if ((aVal === undefined || aVal === null)
|
|
2553
|
+
|| (bVal === undefined || bVal === null)) {
|
|
2554
|
+
// At least one was undefined
|
|
2555
|
+
if ((aVal === undefined || aVal === null)
|
|
2556
|
+
&& (bVal === undefined || bVal === null)) {
|
|
2557
|
+
// Both undefined
|
|
2558
|
+
return tiebreaker;
|
|
2559
|
+
}
|
|
2560
|
+
if (aVal === undefined || aVal === null) {
|
|
2561
|
+
// First was undefined
|
|
2562
|
+
return 1;
|
|
2563
|
+
}
|
|
2564
|
+
// Second was undefined
|
|
2565
|
+
return -1;
|
|
2566
|
+
}
|
|
2422
2567
|
// Sort differently based on the data type
|
|
2423
2568
|
// > Boolean
|
|
2424
2569
|
if (paramType === ParamType$1.Boolean) {
|
|
@@ -2428,7 +2573,7 @@ const IntelliTable = (props) => {
|
|
|
2428
2573
|
if (!aVal && bVal) {
|
|
2429
2574
|
return (descending ? 1 : -1);
|
|
2430
2575
|
}
|
|
2431
|
-
return
|
|
2576
|
+
return tiebreaker;
|
|
2432
2577
|
}
|
|
2433
2578
|
// > Number
|
|
2434
2579
|
if (paramType === ParamType$1.Int
|
|
@@ -2440,12 +2585,12 @@ const IntelliTable = (props) => {
|
|
|
2440
2585
|
// > String
|
|
2441
2586
|
if (paramType === ParamType$1.String) {
|
|
2442
2587
|
if (aVal < bVal) {
|
|
2443
|
-
return (descending ?
|
|
2588
|
+
return (descending ? 1 : -1);
|
|
2444
2589
|
}
|
|
2445
2590
|
if (aVal > bVal) {
|
|
2446
|
-
return (descending ? 1 :
|
|
2591
|
+
return (descending ? -1 : 1);
|
|
2447
2592
|
}
|
|
2448
|
-
return
|
|
2593
|
+
return tiebreaker;
|
|
2449
2594
|
}
|
|
2450
2595
|
// > JSON
|
|
2451
2596
|
if (paramType === ParamType$1.JSON) {
|
|
@@ -2460,7 +2605,7 @@ const IntelliTable = (props) => {
|
|
|
2460
2605
|
: (aSize - bSize));
|
|
2461
2606
|
}
|
|
2462
2607
|
// No sort
|
|
2463
|
-
return
|
|
2608
|
+
return tiebreaker;
|
|
2464
2609
|
});
|
|
2465
2610
|
}
|
|
2466
2611
|
// Table body
|
|
@@ -2485,9 +2630,12 @@ const IntelliTable = (props) => {
|
|
|
2485
2630
|
const noValue = (value === undefined
|
|
2486
2631
|
|| value === null);
|
|
2487
2632
|
visibleValue = (noValue
|
|
2488
|
-
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.
|
|
2489
|
-
: (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faXmarkCircle })));
|
|
2633
|
+
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faMinus }))
|
|
2634
|
+
: (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: fullValue ? freeSolidSvgIcons.faCheckCircle : freeSolidSvgIcons.faXmarkCircle })));
|
|
2490
2635
|
title = (fullValue ? 'True' : 'False');
|
|
2636
|
+
if (noValue) {
|
|
2637
|
+
title = 'Empty Cell';
|
|
2638
|
+
}
|
|
2491
2639
|
}
|
|
2492
2640
|
else if (column.type === ParamType$1.Int) {
|
|
2493
2641
|
fullValue = Number.parseInt(value, 10);
|
|
@@ -2496,6 +2644,9 @@ const IntelliTable = (props) => {
|
|
|
2496
2644
|
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faMinus }))
|
|
2497
2645
|
: fullValue);
|
|
2498
2646
|
title = String(fullValue);
|
|
2647
|
+
if (noValue) {
|
|
2648
|
+
title = 'Empty Cell';
|
|
2649
|
+
}
|
|
2499
2650
|
}
|
|
2500
2651
|
else if (column.type === ParamType$1.Float) {
|
|
2501
2652
|
fullValue = Number.parseFloat(value);
|
|
@@ -2504,14 +2655,22 @@ const IntelliTable = (props) => {
|
|
|
2504
2655
|
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faMinus }))
|
|
2505
2656
|
: roundToNumDecimals(fullValue, 2));
|
|
2506
2657
|
title = String(fullValue);
|
|
2658
|
+
if (noValue) {
|
|
2659
|
+
title = 'Empty Cell';
|
|
2660
|
+
}
|
|
2507
2661
|
}
|
|
2508
2662
|
else if (column.type === ParamType$1.String) {
|
|
2509
2663
|
fullValue = String(value).trim();
|
|
2510
|
-
const noValue = (value
|
|
2664
|
+
const noValue = (value === undefined
|
|
2665
|
+
|| value === null
|
|
2666
|
+
|| String(fullValue).trim().length === 0);
|
|
2511
2667
|
visibleValue = (noValue
|
|
2512
2668
|
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faMinus }))
|
|
2513
2669
|
: fullValue);
|
|
2514
2670
|
title = `"${value}"`;
|
|
2671
|
+
if (noValue) {
|
|
2672
|
+
title = 'Empty Cell';
|
|
2673
|
+
}
|
|
2515
2674
|
}
|
|
2516
2675
|
else if (column.type === ParamType$1.JSON) {
|
|
2517
2676
|
fullValue = JSON.stringify(value);
|
|
@@ -2521,9 +2680,13 @@ const IntelliTable = (props) => {
|
|
|
2521
2680
|
visibleValue = (noValue
|
|
2522
2681
|
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faMinus }))
|
|
2523
2682
|
: fullValue);
|
|
2683
|
+
title = "JSON Object";
|
|
2524
2684
|
}
|
|
2525
2685
|
// Create UI
|
|
2526
|
-
return (React__default["default"].createElement("td", { key: `${datum.id}-${column.param}`, title: title
|
|
2686
|
+
return (React__default["default"].createElement("td", { key: `${datum.id}-${column.param}`, title: title, style: {
|
|
2687
|
+
borderRight: '0.05rem solid #555',
|
|
2688
|
+
borderLeft: '0.05rem solid #555',
|
|
2689
|
+
} }, visibleValue));
|
|
2527
2690
|
}));
|
|
2528
2691
|
// Add cells to a row
|
|
2529
2692
|
return (React__default["default"].createElement("tr", { key: datum.id }, cells));
|
|
@@ -2540,14 +2703,17 @@ const IntelliTable = (props) => {
|
|
|
2540
2703
|
})
|
|
2541
2704
|
.length);
|
|
2542
2705
|
// Build CSV
|
|
2543
|
-
const csv = genCSV(data, columns)
|
|
2706
|
+
const csv = genCSV(data, columns.filter((column) => {
|
|
2707
|
+
return columnVisibilityMap[column.param];
|
|
2708
|
+
}));
|
|
2544
2709
|
// Build main UI
|
|
2545
2710
|
return (React__default["default"].createElement("div", { className: `IntelliTable-container-${id}` },
|
|
2546
|
-
|
|
2711
|
+
modal,
|
|
2712
|
+
React__default["default"].createElement("div", { className: "d-flex align-items-center justify-content-start" },
|
|
2547
2713
|
React__default["default"].createElement("h3", { className: "m-0" }, title),
|
|
2548
2714
|
React__default["default"].createElement("div", { className: "flex-grow-1 text-end" },
|
|
2549
|
-
React__default["default"].createElement(CSVDownloadButton, { "aria-label": `download data as csv for ${title}`, id: `IntelliTable-${id}-download-as-csv`, filename:
|
|
2550
|
-
React__default["default"].createElement("button", { type: "button", className: "btn btn-secondary", "aria-label": `show panel for customizing which columns show in table ${title}`, id: `IntelliTable-${id}-show-column-customization-modal`, onClick: () => {
|
|
2715
|
+
React__default["default"].createElement(CSVDownloadButton, { "aria-label": `download data as csv for ${title}`, id: `IntelliTable-${id}-download-as-csv`, filename: filename, csv: csv }),
|
|
2716
|
+
React__default["default"].createElement("button", { type: "button", className: "btn btn-secondary ms-2", "aria-label": `show panel for customizing which columns show in table ${title}`, id: `IntelliTable-${id}-show-column-customization-modal`, onClick: () => {
|
|
2551
2717
|
dispatch({
|
|
2552
2718
|
type: ActionType$1.ToggleColVisCusModalVisibility,
|
|
2553
2719
|
});
|
|
@@ -2559,7 +2725,7 @@ const IntelliTable = (props) => {
|
|
|
2559
2725
|
numHiddenCols,
|
|
2560
2726
|
' ',
|
|
2561
2727
|
"hidden)"))))),
|
|
2562
|
-
React__default["default"].createElement("div", { className: `IntelliTable-table-${id}`, style: {
|
|
2728
|
+
React__default["default"].createElement("div", { className: `IntelliTable-table-${id} mt-2`, style: {
|
|
2563
2729
|
overflowX: 'auto',
|
|
2564
2730
|
} }, table)));
|
|
2565
2731
|
};
|
|
@@ -2611,6 +2777,11 @@ const style = `
|
|
|
2611
2777
|
border: 0.05rem solid black;
|
|
2612
2778
|
border-radius: 0.5rem;
|
|
2613
2779
|
overflow: hidden;
|
|
2780
|
+
padding: 0.7rem;
|
|
2781
|
+
|
|
2782
|
+
/* Solid background */
|
|
2783
|
+
background-color: white;
|
|
2784
|
+
color: black;
|
|
2614
2785
|
|
|
2615
2786
|
/* Place contents in flex column */
|
|
2616
2787
|
flex-direction: column;
|
|
@@ -2637,8 +2808,218 @@ const style = `
|
|
|
2637
2808
|
/* Vertical scroll */
|
|
2638
2809
|
overflow-y: auto;
|
|
2639
2810
|
}
|
|
2811
|
+
|
|
2812
|
+
.LogReviewer-header-close-button {
|
|
2813
|
+
border: 0 !important;
|
|
2814
|
+
background-color: transparent !important;
|
|
2815
|
+
padding-top: 0 !important;
|
|
2816
|
+
padding-bottom: 0 !important;
|
|
2817
|
+
padding-right: 1em !important;
|
|
2818
|
+
margin: 0 !important;
|
|
2819
|
+
color: #444 !important;
|
|
2820
|
+
|
|
2821
|
+
right: 0 !important;
|
|
2822
|
+
position: absolute !important;
|
|
2823
|
+
}
|
|
2824
|
+
.LogReviewer-header-close-button:hover {
|
|
2825
|
+
border: 0 !important;
|
|
2826
|
+
background-color: transparent !important;
|
|
2827
|
+
padding-top: 0 !important;
|
|
2828
|
+
padding-bottom: 0 !important;
|
|
2829
|
+
margin: 0 !important;
|
|
2830
|
+
color: #000 !important;
|
|
2831
|
+
}
|
|
2640
2832
|
`;
|
|
2641
2833
|
/*------------------------------------------------------------------------*/
|
|
2834
|
+
/* Constants */
|
|
2835
|
+
/*------------------------------------------------------------------------*/
|
|
2836
|
+
const columns = [
|
|
2837
|
+
{
|
|
2838
|
+
title: 'First Name',
|
|
2839
|
+
param: 'userFirstName',
|
|
2840
|
+
type: ParamType$1.String,
|
|
2841
|
+
},
|
|
2842
|
+
{
|
|
2843
|
+
title: 'Last Name',
|
|
2844
|
+
param: 'userLastName',
|
|
2845
|
+
type: ParamType$1.String,
|
|
2846
|
+
},
|
|
2847
|
+
{
|
|
2848
|
+
title: 'Email',
|
|
2849
|
+
param: 'userEmail',
|
|
2850
|
+
type: ParamType$1.String,
|
|
2851
|
+
},
|
|
2852
|
+
{
|
|
2853
|
+
title: 'Canvas Id',
|
|
2854
|
+
param: 'userId',
|
|
2855
|
+
type: ParamType$1.Int,
|
|
2856
|
+
},
|
|
2857
|
+
{
|
|
2858
|
+
title: 'Student?',
|
|
2859
|
+
param: 'isLearner',
|
|
2860
|
+
type: ParamType$1.Boolean,
|
|
2861
|
+
},
|
|
2862
|
+
{
|
|
2863
|
+
title: 'Teaching Staff?',
|
|
2864
|
+
param: 'isTTM',
|
|
2865
|
+
type: ParamType$1.Boolean,
|
|
2866
|
+
startsHidden: true,
|
|
2867
|
+
},
|
|
2868
|
+
{
|
|
2869
|
+
title: 'Admin?',
|
|
2870
|
+
param: 'isAdmin',
|
|
2871
|
+
type: ParamType$1.Boolean,
|
|
2872
|
+
startsHidden: true,
|
|
2873
|
+
},
|
|
2874
|
+
{
|
|
2875
|
+
title: 'Course Canvas Id',
|
|
2876
|
+
param: 'courseId',
|
|
2877
|
+
type: ParamType$1.Int,
|
|
2878
|
+
startsHidden: true,
|
|
2879
|
+
},
|
|
2880
|
+
{
|
|
2881
|
+
title: 'Course Name',
|
|
2882
|
+
param: 'courseName',
|
|
2883
|
+
type: ParamType$1.String,
|
|
2884
|
+
},
|
|
2885
|
+
{
|
|
2886
|
+
title: 'Browser Name',
|
|
2887
|
+
param: 'browser.name',
|
|
2888
|
+
type: ParamType$1.String,
|
|
2889
|
+
startsHidden: true,
|
|
2890
|
+
},
|
|
2891
|
+
{
|
|
2892
|
+
title: 'Browser Version',
|
|
2893
|
+
param: 'browser.version',
|
|
2894
|
+
type: ParamType$1.String,
|
|
2895
|
+
startsHidden: true,
|
|
2896
|
+
},
|
|
2897
|
+
{
|
|
2898
|
+
title: 'OS',
|
|
2899
|
+
param: 'device.os',
|
|
2900
|
+
type: ParamType$1.String,
|
|
2901
|
+
startsHidden: true,
|
|
2902
|
+
},
|
|
2903
|
+
{
|
|
2904
|
+
title: 'Mobile?',
|
|
2905
|
+
param: 'device.isMobile',
|
|
2906
|
+
type: ParamType$1.Boolean,
|
|
2907
|
+
startsHidden: true,
|
|
2908
|
+
},
|
|
2909
|
+
{
|
|
2910
|
+
title: 'Year',
|
|
2911
|
+
param: 'year',
|
|
2912
|
+
type: ParamType$1.Int,
|
|
2913
|
+
},
|
|
2914
|
+
{
|
|
2915
|
+
title: 'Month',
|
|
2916
|
+
param: 'month',
|
|
2917
|
+
type: ParamType$1.Int,
|
|
2918
|
+
},
|
|
2919
|
+
{
|
|
2920
|
+
title: 'Day',
|
|
2921
|
+
param: 'day',
|
|
2922
|
+
type: ParamType$1.Int,
|
|
2923
|
+
},
|
|
2924
|
+
{
|
|
2925
|
+
title: 'Hour',
|
|
2926
|
+
param: 'hour',
|
|
2927
|
+
type: ParamType$1.Int,
|
|
2928
|
+
},
|
|
2929
|
+
{
|
|
2930
|
+
title: 'Minute',
|
|
2931
|
+
param: 'minute',
|
|
2932
|
+
type: ParamType$1.Int,
|
|
2933
|
+
startsHidden: true,
|
|
2934
|
+
},
|
|
2935
|
+
{
|
|
2936
|
+
title: 'Timestamp',
|
|
2937
|
+
param: 'timestamp',
|
|
2938
|
+
type: ParamType$1.Int,
|
|
2939
|
+
startsHidden: true,
|
|
2940
|
+
},
|
|
2941
|
+
{
|
|
2942
|
+
title: 'Context',
|
|
2943
|
+
param: 'context',
|
|
2944
|
+
type: ParamType$1.String,
|
|
2945
|
+
},
|
|
2946
|
+
{
|
|
2947
|
+
title: 'Subcontext',
|
|
2948
|
+
param: 'subcontext',
|
|
2949
|
+
type: ParamType$1.String,
|
|
2950
|
+
},
|
|
2951
|
+
{
|
|
2952
|
+
title: 'Tags',
|
|
2953
|
+
param: 'tags',
|
|
2954
|
+
type: ParamType$1.JSON,
|
|
2955
|
+
startsHidden: true,
|
|
2956
|
+
},
|
|
2957
|
+
{
|
|
2958
|
+
title: 'Log Level',
|
|
2959
|
+
param: 'level',
|
|
2960
|
+
type: ParamType$1.String,
|
|
2961
|
+
startsHidden: true,
|
|
2962
|
+
},
|
|
2963
|
+
{
|
|
2964
|
+
title: 'Metadata',
|
|
2965
|
+
param: 'metadata',
|
|
2966
|
+
type: ParamType$1.JSON,
|
|
2967
|
+
startsHidden: true,
|
|
2968
|
+
},
|
|
2969
|
+
{
|
|
2970
|
+
title: 'Source',
|
|
2971
|
+
param: 'source',
|
|
2972
|
+
type: ParamType$1.String,
|
|
2973
|
+
},
|
|
2974
|
+
{
|
|
2975
|
+
title: 'Server Route Path',
|
|
2976
|
+
param: 'routePath',
|
|
2977
|
+
type: ParamType$1.String,
|
|
2978
|
+
startsHidden: true,
|
|
2979
|
+
},
|
|
2980
|
+
{
|
|
2981
|
+
title: 'Server Route Template',
|
|
2982
|
+
param: 'routeTemplate',
|
|
2983
|
+
type: ParamType$1.String,
|
|
2984
|
+
startsHidden: true,
|
|
2985
|
+
},
|
|
2986
|
+
{
|
|
2987
|
+
title: 'Type',
|
|
2988
|
+
param: 'type',
|
|
2989
|
+
type: ParamType$1.String,
|
|
2990
|
+
},
|
|
2991
|
+
{
|
|
2992
|
+
title: 'Error Message',
|
|
2993
|
+
param: 'errorMessage',
|
|
2994
|
+
type: ParamType$1.String,
|
|
2995
|
+
startsHidden: true,
|
|
2996
|
+
},
|
|
2997
|
+
{
|
|
2998
|
+
title: 'Error Code',
|
|
2999
|
+
param: 'errorCode',
|
|
3000
|
+
type: ParamType$1.String,
|
|
3001
|
+
startsHidden: true,
|
|
3002
|
+
},
|
|
3003
|
+
{
|
|
3004
|
+
title: 'Error Stack',
|
|
3005
|
+
param: 'errorStack',
|
|
3006
|
+
type: ParamType$1.String,
|
|
3007
|
+
startsHidden: true,
|
|
3008
|
+
},
|
|
3009
|
+
{
|
|
3010
|
+
title: 'Action Target',
|
|
3011
|
+
param: 'target',
|
|
3012
|
+
type: ParamType$1.String,
|
|
3013
|
+
startsHidden: true,
|
|
3014
|
+
},
|
|
3015
|
+
{
|
|
3016
|
+
title: 'Action Type',
|
|
3017
|
+
param: 'action',
|
|
3018
|
+
type: ParamType$1.String,
|
|
3019
|
+
startsHidden: true,
|
|
3020
|
+
},
|
|
3021
|
+
];
|
|
3022
|
+
/*------------------------------------------------------------------------*/
|
|
2642
3023
|
/* Static Functions */
|
|
2643
3024
|
/*------------------------------------------------------------------------*/
|
|
2644
3025
|
/**
|
|
@@ -2656,7 +3037,7 @@ const genHumanReadableName = (machineReadableName) => {
|
|
|
2656
3037
|
// Uppercase! Add a space before
|
|
2657
3038
|
humanReadableName += ' ';
|
|
2658
3039
|
}
|
|
2659
|
-
humanReadableName +=
|
|
3040
|
+
humanReadableName += char;
|
|
2660
3041
|
});
|
|
2661
3042
|
// Trim and return
|
|
2662
3043
|
return humanReadableName.trim();
|
|
@@ -2718,7 +3099,19 @@ const reducer = (state, action) => {
|
|
|
2718
3099
|
return Object.assign(Object.assign({}, state), { contextFilterState: action.contextFilterState });
|
|
2719
3100
|
}
|
|
2720
3101
|
case ActionType.UpdateTagFilterState: {
|
|
2721
|
-
|
|
3102
|
+
const { tagFilterState } = action;
|
|
3103
|
+
// Select all if every tag is deselected
|
|
3104
|
+
const numTagsSelected = (Object.values(tagFilterState)
|
|
3105
|
+
.filter((isSelected) => {
|
|
3106
|
+
return isSelected;
|
|
3107
|
+
})
|
|
3108
|
+
.length);
|
|
3109
|
+
if (numTagsSelected === 0) {
|
|
3110
|
+
Object.keys(tagFilterState).forEach((tag) => {
|
|
3111
|
+
tagFilterState[tag] = true;
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
3114
|
+
return Object.assign(Object.assign({}, state), { tagFilterState });
|
|
2722
3115
|
}
|
|
2723
3116
|
case ActionType.UpdateActionErrorFilterState: {
|
|
2724
3117
|
return Object.assign(Object.assign({}, state), { actionErrorFilterState: action.actionErrorFilterState });
|
|
@@ -2738,10 +3131,35 @@ const LogReviewer = (props) => {
|
|
|
2738
3131
|
/*------------------------------------------------------------------------*/
|
|
2739
3132
|
/* Setup */
|
|
2740
3133
|
/*------------------------------------------------------------------------*/
|
|
2741
|
-
var _a, _b, _c, _d, _e;
|
|
3134
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2742
3135
|
/* -------------- Props ------------- */
|
|
2743
3136
|
// Destructure props
|
|
2744
3137
|
const { LogMetadata, onClose, } = props;
|
|
3138
|
+
// Create complete map of contexts
|
|
3139
|
+
const contextMap = {};
|
|
3140
|
+
[
|
|
3141
|
+
((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {}),
|
|
3142
|
+
LogBuiltInMetadata.Context,
|
|
3143
|
+
].forEach((partialContextMap) => {
|
|
3144
|
+
Object.keys(partialContextMap).forEach((context) => {
|
|
3145
|
+
// Add context
|
|
3146
|
+
contextMap[context] = context;
|
|
3147
|
+
// If context has children, add an "uncategorized" subcontext
|
|
3148
|
+
if (typeof contextMap[context] !== 'string') {
|
|
3149
|
+
contextMap[context][LogBuiltInMetadata.Context.Uncategorized] = (LogBuiltInMetadata.Context.Uncategorized);
|
|
3150
|
+
}
|
|
3151
|
+
});
|
|
3152
|
+
});
|
|
3153
|
+
// Create complete map of targets
|
|
3154
|
+
const targetMap = {};
|
|
3155
|
+
[
|
|
3156
|
+
((_b = LogMetadata.Target) !== null && _b !== void 0 ? _b : {}),
|
|
3157
|
+
LogBuiltInMetadata.Target,
|
|
3158
|
+
].forEach((partialTargetMap) => {
|
|
3159
|
+
Object.keys(partialTargetMap).forEach((target) => {
|
|
3160
|
+
targetMap[target] = target;
|
|
3161
|
+
});
|
|
3162
|
+
});
|
|
2745
3163
|
/* -------------- State ------------- */
|
|
2746
3164
|
// Create initial date filter state
|
|
2747
3165
|
const today = getTimeInfoInET();
|
|
@@ -2761,26 +3179,30 @@ const LogReviewer = (props) => {
|
|
|
2761
3179
|
};
|
|
2762
3180
|
// Create initial context filter state
|
|
2763
3181
|
const initContextFilterState = {};
|
|
2764
|
-
Object.keys((
|
|
3182
|
+
Object.keys((_c = LogMetadata.Context) !== null && _c !== void 0 ? _c : {}).forEach((context) => {
|
|
2765
3183
|
var _a, _b;
|
|
2766
3184
|
const contextValue = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
|
|
2767
3185
|
if (typeof contextValue === 'string') {
|
|
2768
|
-
// Case: no subcontexts
|
|
3186
|
+
// Case: no subcontexts, init as checked
|
|
2769
3187
|
initContextFilterState[contextValue] = true;
|
|
2770
3188
|
}
|
|
2771
3189
|
else {
|
|
2772
3190
|
// Case: subcontexts exist
|
|
2773
|
-
initContextFilterState[
|
|
3191
|
+
initContextFilterState[context] = {};
|
|
2774
3192
|
Object.values(((_b = LogMetadata.Context) !== null && _b !== void 0 ? _b : {})[context]).forEach((subcontext) => {
|
|
2775
|
-
|
|
2776
|
-
|
|
3193
|
+
// Skip self ("_")
|
|
3194
|
+
if (subcontext === '_') {
|
|
3195
|
+
return;
|
|
3196
|
+
}
|
|
3197
|
+
// Initialize as checked
|
|
3198
|
+
initContextFilterState[context][subcontext] = true;
|
|
2777
3199
|
});
|
|
2778
3200
|
}
|
|
2779
3201
|
});
|
|
2780
3202
|
// Create initial tag filter state
|
|
2781
3203
|
const initTagFilterState = {};
|
|
2782
|
-
Object.
|
|
2783
|
-
initTagFilterState[
|
|
3204
|
+
Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {}).forEach((tag) => {
|
|
3205
|
+
initTagFilterState[tag] = false;
|
|
2784
3206
|
});
|
|
2785
3207
|
// Create advanced filter state
|
|
2786
3208
|
const initAdvancedFilterState = {
|
|
@@ -2806,12 +3228,16 @@ const LogReviewer = (props) => {
|
|
|
2806
3228
|
target: {},
|
|
2807
3229
|
action: {},
|
|
2808
3230
|
};
|
|
2809
|
-
Object.values((
|
|
3231
|
+
Object.values((_e = LogMetadata.Target) !== null && _e !== void 0 ? _e : {}).forEach((target) => {
|
|
2810
3232
|
initActionErrorFilterState.target[target] = true;
|
|
2811
3233
|
});
|
|
2812
3234
|
Object.values(LogAction$1).forEach((action) => {
|
|
2813
3235
|
initActionErrorFilterState.action[action] = true;
|
|
2814
3236
|
});
|
|
3237
|
+
// Add built-in targets
|
|
3238
|
+
Object.values(LogBuiltInMetadata.Target).forEach((target) => {
|
|
3239
|
+
initActionErrorFilterState.target[target] = true;
|
|
3240
|
+
});
|
|
2815
3241
|
// Initial state
|
|
2816
3242
|
const initialState = {
|
|
2817
3243
|
loading: true,
|
|
@@ -2845,15 +3271,18 @@ const LogReviewer = (props) => {
|
|
|
2845
3271
|
let month = newDateFilterState.startDate.month;
|
|
2846
3272
|
while (
|
|
2847
3273
|
// Earlier year
|
|
2848
|
-
(year
|
|
3274
|
+
(year < newDateFilterState.endDate.year)
|
|
2849
3275
|
// Current year but included month
|
|
2850
3276
|
|| (year === newDateFilterState.endDate.year
|
|
2851
3277
|
&& month <= newDateFilterState.endDate.month)) {
|
|
2852
|
-
// Add to list
|
|
2853
|
-
|
|
2854
|
-
year
|
|
2855
|
-
|
|
2856
|
-
|
|
3278
|
+
// Add to list if not already loaded
|
|
3279
|
+
if (!logMap[year]
|
|
3280
|
+
|| !logMap[year][month]) {
|
|
3281
|
+
toLoad.push({
|
|
3282
|
+
year,
|
|
3283
|
+
month,
|
|
3284
|
+
});
|
|
3285
|
+
}
|
|
2857
3286
|
// Increment
|
|
2858
3287
|
month += 1;
|
|
2859
3288
|
if (month > 12) {
|
|
@@ -2941,10 +3370,10 @@ const LogReviewer = (props) => {
|
|
|
2941
3370
|
/* Filters */
|
|
2942
3371
|
/*----------------------------------------*/
|
|
2943
3372
|
// Filter toggle
|
|
2944
|
-
const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles
|
|
3373
|
+
const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles" },
|
|
2945
3374
|
React__default["default"].createElement("h3", { className: "m-0" }, "Filters:"),
|
|
2946
|
-
React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons" },
|
|
2947
|
-
React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-date-filter-drawer", className: `btn btn-${FilterDrawer.Date === expandedFilterDrawer} me-2`, "aria-label": "toggle date filter drawer", onClick: () => {
|
|
3375
|
+
React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
|
|
3376
|
+
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: () => {
|
|
2948
3377
|
dispatch({
|
|
2949
3378
|
type: ActionType.ToggleFilterDrawer,
|
|
2950
3379
|
filterDrawer: FilterDrawer.Date,
|
|
@@ -2952,7 +3381,7 @@ const LogReviewer = (props) => {
|
|
|
2952
3381
|
} },
|
|
2953
3382
|
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCalendar, className: "me-2" }),
|
|
2954
3383
|
"Date"),
|
|
2955
|
-
React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-context-filter-drawer", className: `btn btn-${FilterDrawer.Context === expandedFilterDrawer} me-2`, "aria-label": "toggle context filter drawer", onClick: () => {
|
|
3384
|
+
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: () => {
|
|
2956
3385
|
dispatch({
|
|
2957
3386
|
type: ActionType.ToggleFilterDrawer,
|
|
2958
3387
|
filterDrawer: FilterDrawer.Context,
|
|
@@ -2960,15 +3389,15 @@ const LogReviewer = (props) => {
|
|
|
2960
3389
|
} },
|
|
2961
3390
|
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "me-2" }),
|
|
2962
3391
|
"Context"),
|
|
2963
|
-
React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-tag-filter-drawer", className: `btn btn-${FilterDrawer.Tag === expandedFilterDrawer} me-2`, "aria-label": "toggle tag filter drawer", onClick: () => {
|
|
3392
|
+
(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: () => {
|
|
2964
3393
|
dispatch({
|
|
2965
3394
|
type: ActionType.ToggleFilterDrawer,
|
|
2966
3395
|
filterDrawer: FilterDrawer.Tag,
|
|
2967
3396
|
});
|
|
2968
3397
|
} },
|
|
2969
3398
|
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTag, className: "me-2" }),
|
|
2970
|
-
"Tag"),
|
|
2971
|
-
React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-action-filter-drawer", className: `btn btn-${FilterDrawer.Action === expandedFilterDrawer} me-2`, "aria-label": "toggle action and error filter drawer", onClick: () => {
|
|
3399
|
+
"Tag")),
|
|
3400
|
+
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: () => {
|
|
2972
3401
|
dispatch({
|
|
2973
3402
|
type: ActionType.ToggleFilterDrawer,
|
|
2974
3403
|
filterDrawer: FilterDrawer.Action,
|
|
@@ -2976,41 +3405,64 @@ const LogReviewer = (props) => {
|
|
|
2976
3405
|
} },
|
|
2977
3406
|
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faHammer, className: "me-2" }),
|
|
2978
3407
|
"Action"),
|
|
2979
|
-
React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-advanced-filter-drawer", className: `btn btn-${FilterDrawer.Advanced === expandedFilterDrawer}`, "aria-label": "toggle advanced filter drawer", onClick: () => {
|
|
3408
|
+
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: () => {
|
|
2980
3409
|
dispatch({
|
|
2981
3410
|
type: ActionType.ToggleFilterDrawer,
|
|
2982
3411
|
filterDrawer: FilterDrawer.Advanced,
|
|
2983
3412
|
});
|
|
2984
3413
|
} },
|
|
2985
3414
|
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faList, className: "me-2" }),
|
|
2986
|
-
"Advanced")
|
|
3415
|
+
"Advanced"),
|
|
3416
|
+
React__default["default"].createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
|
|
3417
|
+
dispatch({
|
|
3418
|
+
type: ActionType.ResetFilters,
|
|
3419
|
+
initActionErrorFilterState,
|
|
3420
|
+
initAdvancedFilterState,
|
|
3421
|
+
initContextFilterState,
|
|
3422
|
+
initDateFilterState,
|
|
3423
|
+
initTagFilterState,
|
|
3424
|
+
});
|
|
3425
|
+
} },
|
|
3426
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes }),
|
|
3427
|
+
' ',
|
|
3428
|
+
"Reset"))));
|
|
2987
3429
|
// Filter drawer
|
|
2988
3430
|
let filterDrawer;
|
|
2989
3431
|
if (expandedFilterDrawer) {
|
|
2990
3432
|
if (expandedFilterDrawer === FilterDrawer.Date) {
|
|
2991
3433
|
filterDrawer = (React__default["default"].createElement(TabBox, { title: "Date" },
|
|
2992
|
-
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, onChange: (month, day, year) => {
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
dateFilterState: Object.assign(Object.assign({}, dateFilterState), { startDate: { month, day, year } }),
|
|
2996
|
-
});
|
|
3434
|
+
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) => {
|
|
3435
|
+
dateFilterState.startDate = { month, day, year };
|
|
3436
|
+
handleDateRangeUpdated(dateFilterState);
|
|
2997
3437
|
} }),
|
|
2998
3438
|
' ',
|
|
2999
3439
|
"to",
|
|
3000
3440
|
' ',
|
|
3001
|
-
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, onChange: (month, day, year) => {
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3441
|
+
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) => {
|
|
3442
|
+
if (year < dateFilterState.startDate.year
|
|
3443
|
+
|| (year === dateFilterState.startDate.year
|
|
3444
|
+
&& month < dateFilterState.startDate.month)
|
|
3445
|
+
|| (year === dateFilterState.startDate.year
|
|
3446
|
+
&& month === dateFilterState.startDate.month
|
|
3447
|
+
&& day < dateFilterState.startDate.day)) {
|
|
3448
|
+
return alert$1('Invalid Start Date', 'The start date cannot be before the end date.');
|
|
3449
|
+
}
|
|
3450
|
+
dateFilterState.endDate = { month, day, year };
|
|
3451
|
+
handleDateRangeUpdated(dateFilterState);
|
|
3006
3452
|
} })));
|
|
3007
3453
|
}
|
|
3008
3454
|
else if (expandedFilterDrawer === FilterDrawer.Context) {
|
|
3009
3455
|
// Create item picker items
|
|
3010
|
-
const
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3456
|
+
const builtInPickableItem = {
|
|
3457
|
+
id: '_',
|
|
3458
|
+
name: 'Auto-logged',
|
|
3459
|
+
isGroup: true,
|
|
3460
|
+
children: [],
|
|
3461
|
+
};
|
|
3462
|
+
const pickableItems = [];
|
|
3463
|
+
Object.keys(contextMap)
|
|
3464
|
+
.forEach((context) => {
|
|
3465
|
+
const value = (contextMap)[context];
|
|
3014
3466
|
if (typeof value === 'string') {
|
|
3015
3467
|
// No subcategories
|
|
3016
3468
|
const item = {
|
|
@@ -3019,38 +3471,64 @@ const LogReviewer = (props) => {
|
|
|
3019
3471
|
isGroup: false,
|
|
3020
3472
|
checked: !!contextFilterState[context],
|
|
3021
3473
|
};
|
|
3022
|
-
|
|
3474
|
+
// Add built-in items to its own folder
|
|
3475
|
+
const isBuiltIn = context in LogBuiltInMetadata.Context;
|
|
3476
|
+
if (isBuiltIn) {
|
|
3477
|
+
// Add to built-in pickable item
|
|
3478
|
+
builtInPickableItem.children.push(item);
|
|
3479
|
+
}
|
|
3480
|
+
else {
|
|
3481
|
+
// Add to pickable items list
|
|
3482
|
+
pickableItems.push(item);
|
|
3483
|
+
}
|
|
3023
3484
|
}
|
|
3024
3485
|
// Has subcategories
|
|
3025
3486
|
const children = (Object.keys(value)
|
|
3487
|
+
// Remove parent name
|
|
3026
3488
|
.filter((subcontext) => {
|
|
3027
3489
|
return subcontext !== '_';
|
|
3028
3490
|
})
|
|
3491
|
+
// Create child pickable items
|
|
3029
3492
|
.map((subcontext) => {
|
|
3030
3493
|
return {
|
|
3031
|
-
id:
|
|
3494
|
+
id: subcontext,
|
|
3032
3495
|
name: genHumanReadableName(subcontext),
|
|
3033
3496
|
isGroup: false,
|
|
3034
|
-
checked:
|
|
3497
|
+
checked: contextFilterState[context][subcontext],
|
|
3035
3498
|
};
|
|
3036
3499
|
}));
|
|
3037
3500
|
const item = {
|
|
3038
3501
|
id: context,
|
|
3039
|
-
name: context,
|
|
3502
|
+
name: genHumanReadableName(context),
|
|
3040
3503
|
isGroup: true,
|
|
3041
3504
|
children,
|
|
3042
3505
|
};
|
|
3043
|
-
|
|
3044
|
-
})
|
|
3506
|
+
pickableItems.push(item);
|
|
3507
|
+
});
|
|
3508
|
+
// Add built-in contexts to end ofl ist
|
|
3509
|
+
pickableItems.push(builtInPickableItem);
|
|
3045
3510
|
// Create filter UI
|
|
3046
3511
|
filterDrawer = (React__default["default"].createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
|
|
3047
3512
|
// Update our state
|
|
3048
3513
|
updatedItems.forEach((pickableItem) => {
|
|
3049
3514
|
if (pickableItem.isGroup) {
|
|
3050
3515
|
// Has subcontexts
|
|
3051
|
-
pickableItem.
|
|
3052
|
-
|
|
3053
|
-
|
|
3516
|
+
if (pickableItem.id === '_') {
|
|
3517
|
+
// Built-in
|
|
3518
|
+
// Treat as if these were top-level contexts
|
|
3519
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
3520
|
+
contextFilterState[subcontextItem.id] = ('checked' in subcontextItem
|
|
3521
|
+
&& subcontextItem.checked);
|
|
3522
|
+
});
|
|
3523
|
+
}
|
|
3524
|
+
else {
|
|
3525
|
+
// Not built-in
|
|
3526
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
3527
|
+
if (!subcontextItem.isGroup) {
|
|
3528
|
+
contextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
|
|
3529
|
+
}
|
|
3530
|
+
});
|
|
3531
|
+
}
|
|
3054
3532
|
}
|
|
3055
3533
|
else {
|
|
3056
3534
|
// No subcontexts
|
|
@@ -3065,13 +3543,19 @@ const LogReviewer = (props) => {
|
|
|
3065
3543
|
}
|
|
3066
3544
|
else if (expandedFilterDrawer === FilterDrawer.Tag) {
|
|
3067
3545
|
// Create filter UI
|
|
3068
|
-
filterDrawer = (React__default["default"].createElement(TabBox, { title: "Tags" },
|
|
3069
|
-
.
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
} }))
|
|
3074
|
-
|
|
3546
|
+
filterDrawer = (React__default["default"].createElement(TabBox, { title: "Tags" },
|
|
3547
|
+
React__default["default"].createElement("div", null, "If any are selected, logs must contain at least one but not necessarily all of the selected tags."),
|
|
3548
|
+
Object.keys((_f = LogMetadata.Tag) !== null && _f !== void 0 ? _f : {})
|
|
3549
|
+
.map((tag, i) => {
|
|
3550
|
+
const description = genHumanReadableName(tag);
|
|
3551
|
+
return (React__default["default"].createElement(CheckboxButton, { id: `LogReviewer-tag-${tag}-checkbox`, text: description, ariaLabel: `require that logs be tagged with "${description}" or any other selected tag`, noMarginOnRight: i === Object.keys(tagFilterState).length - 1, checked: tagFilterState[tag], onChanged: (checked) => {
|
|
3552
|
+
tagFilterState[tag] = checked;
|
|
3553
|
+
dispatch({
|
|
3554
|
+
type: ActionType.UpdateTagFilterState,
|
|
3555
|
+
tagFilterState,
|
|
3556
|
+
});
|
|
3557
|
+
} }));
|
|
3558
|
+
})));
|
|
3075
3559
|
}
|
|
3076
3560
|
else if (expandedFilterDrawer === FilterDrawer.Action) {
|
|
3077
3561
|
// Create filter UI
|
|
@@ -3100,34 +3584,37 @@ const LogReviewer = (props) => {
|
|
|
3100
3584
|
}, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
|
|
3101
3585
|
(actionErrorFilterState.type === undefined
|
|
3102
3586
|
|| actionErrorFilterState.type === LogType$1.Action) && (React__default["default"].createElement(TabBox, { title: "Action Log Details" },
|
|
3103
|
-
React__default["default"].createElement(ButtonInputGroup, { label: "Action" },
|
|
3104
|
-
.
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
actionErrorFilterState,
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3587
|
+
React__default["default"].createElement(ButtonInputGroup, { label: "Action", className: "mb-2" },
|
|
3588
|
+
React__default["default"].createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys(LogAction$1)
|
|
3589
|
+
.map((action) => {
|
|
3590
|
+
const description = genHumanReadableName(action);
|
|
3591
|
+
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) => {
|
|
3592
|
+
actionErrorFilterState.action[action] = checked;
|
|
3593
|
+
console.log(actionErrorFilterState, action, checked);
|
|
3594
|
+
dispatch({
|
|
3595
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
3596
|
+
actionErrorFilterState,
|
|
3597
|
+
});
|
|
3598
|
+
} }));
|
|
3599
|
+
}))),
|
|
3600
|
+
React__default["default"].createElement(ButtonInputGroup, { label: "Target" },
|
|
3601
|
+
React__default["default"].createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys(targetMap)
|
|
3602
|
+
.map((target) => {
|
|
3603
|
+
const description = genHumanReadableName(target);
|
|
3604
|
+
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) => {
|
|
3605
|
+
actionErrorFilterState.target[target] = checked;
|
|
3606
|
+
console.log(actionErrorFilterState, target, checked);
|
|
3607
|
+
dispatch({
|
|
3608
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
3609
|
+
actionErrorFilterState,
|
|
3610
|
+
});
|
|
3611
|
+
} }));
|
|
3612
|
+
}))))),
|
|
3126
3613
|
(actionErrorFilterState.type === undefined
|
|
3127
3614
|
|| actionErrorFilterState.type === LogType$1.Error) && (React__default["default"].createElement(TabBox, { title: "Error Log Details" },
|
|
3128
3615
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3129
3616
|
React__default["default"].createElement("span", { className: "input-group-text" }, "Error Message"),
|
|
3130
|
-
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for error message", value: actionErrorFilterState.errorMessage, onChange: (e) => {
|
|
3617
|
+
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) => {
|
|
3131
3618
|
actionErrorFilterState.errorMessage = e.target.value;
|
|
3132
3619
|
dispatch({
|
|
3133
3620
|
type: ActionType.UpdateActionErrorFilterState,
|
|
@@ -3136,7 +3623,7 @@ const LogReviewer = (props) => {
|
|
|
3136
3623
|
} })),
|
|
3137
3624
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3138
3625
|
React__default["default"].createElement("span", { className: "input-group-text" }, "Error Code"),
|
|
3139
|
-
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for error code", value: actionErrorFilterState.
|
|
3626
|
+
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) => {
|
|
3140
3627
|
actionErrorFilterState.errorCode = ((e.target.value)
|
|
3141
3628
|
.trim()
|
|
3142
3629
|
.toUpperCase());
|
|
@@ -3152,7 +3639,7 @@ const LogReviewer = (props) => {
|
|
|
3152
3639
|
React__default["default"].createElement(TabBox, { title: "User Info" },
|
|
3153
3640
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3154
3641
|
React__default["default"].createElement("span", { className: "input-group-text" }, "User First Name"),
|
|
3155
|
-
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user first name", value: advancedFilterState.userFirstName, onChange: (e) => {
|
|
3642
|
+
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) => {
|
|
3156
3643
|
advancedFilterState.userFirstName = e.target.value;
|
|
3157
3644
|
dispatch({
|
|
3158
3645
|
type: ActionType.UpdateAdvancedFilterState,
|
|
@@ -3161,7 +3648,7 @@ const LogReviewer = (props) => {
|
|
|
3161
3648
|
} })),
|
|
3162
3649
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3163
3650
|
React__default["default"].createElement("span", { className: "input-group-text" }, "User Last Name"),
|
|
3164
|
-
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user last name", value: advancedFilterState.userLastName, onChange: (e) => {
|
|
3651
|
+
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) => {
|
|
3165
3652
|
advancedFilterState.userLastName = e.target.value;
|
|
3166
3653
|
dispatch({
|
|
3167
3654
|
type: ActionType.UpdateAdvancedFilterState,
|
|
@@ -3170,7 +3657,7 @@ const LogReviewer = (props) => {
|
|
|
3170
3657
|
} })),
|
|
3171
3658
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3172
3659
|
React__default["default"].createElement("span", { className: "input-group-text" }, "User Email"),
|
|
3173
|
-
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user email", value: advancedFilterState.userEmail, onChange: (e) => {
|
|
3660
|
+
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) => {
|
|
3174
3661
|
advancedFilterState.userEmail = ((e.target.value)
|
|
3175
3662
|
.trim());
|
|
3176
3663
|
dispatch({
|
|
@@ -3180,7 +3667,7 @@ const LogReviewer = (props) => {
|
|
|
3180
3667
|
} })),
|
|
3181
3668
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3182
3669
|
React__default["default"].createElement("span", { className: "input-group-text" }, "User Canvas Id"),
|
|
3183
|
-
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user canvas id", value: advancedFilterState.userId, onChange: (e) => {
|
|
3670
|
+
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) => {
|
|
3184
3671
|
const { value } = e.target;
|
|
3185
3672
|
// Only update if value contains only numbers
|
|
3186
3673
|
if (/^\d+$/.test(value)) {
|
|
@@ -3217,7 +3704,7 @@ const LogReviewer = (props) => {
|
|
|
3217
3704
|
React__default["default"].createElement(TabBox, { title: "Course Info" },
|
|
3218
3705
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3219
3706
|
React__default["default"].createElement("span", { className: "input-group-text" }, "Course Name"),
|
|
3220
|
-
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course name", value: advancedFilterState.courseName, onChange: (e) => {
|
|
3707
|
+
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) => {
|
|
3221
3708
|
advancedFilterState.courseName = e.target.value;
|
|
3222
3709
|
dispatch({
|
|
3223
3710
|
type: ActionType.UpdateAdvancedFilterState,
|
|
@@ -3226,7 +3713,7 @@ const LogReviewer = (props) => {
|
|
|
3226
3713
|
} })),
|
|
3227
3714
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3228
3715
|
React__default["default"].createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
|
|
3229
|
-
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course canvas id", value: advancedFilterState.courseId, onChange: (e) => {
|
|
3716
|
+
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) => {
|
|
3230
3717
|
const { value } = e.target;
|
|
3231
3718
|
// Only update if value contains only numbers
|
|
3232
3719
|
if (/^\d+$/.test(value)) {
|
|
@@ -3284,37 +3771,38 @@ const LogReviewer = (props) => {
|
|
|
3284
3771
|
advancedFilterState,
|
|
3285
3772
|
});
|
|
3286
3773
|
}, noMarginOnRight: true })),
|
|
3287
|
-
React__default["default"].createElement("div", { className: "
|
|
3288
|
-
React__default["default"].createElement("
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
.
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
React__default["default"].createElement("
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
.
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3774
|
+
advancedFilterState.source !== LogSource$1.Client && (React__default["default"].createElement("div", { className: "mt-2" },
|
|
3775
|
+
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3776
|
+
React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Path"),
|
|
3777
|
+
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) => {
|
|
3778
|
+
advancedFilterState.courseName = ((e.target.value)
|
|
3779
|
+
.trim());
|
|
3780
|
+
dispatch({
|
|
3781
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
3782
|
+
advancedFilterState,
|
|
3783
|
+
});
|
|
3784
|
+
} })),
|
|
3785
|
+
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3786
|
+
React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Template"),
|
|
3787
|
+
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) => {
|
|
3788
|
+
advancedFilterState.courseName = ((e.target.value)
|
|
3789
|
+
.trim());
|
|
3790
|
+
dispatch({
|
|
3791
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
3792
|
+
advancedFilterState,
|
|
3793
|
+
});
|
|
3794
|
+
} })))))));
|
|
3307
3795
|
}
|
|
3308
3796
|
}
|
|
3309
3797
|
// Filters UI
|
|
3310
3798
|
const filters = (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
3311
3799
|
filterToggles,
|
|
3312
|
-
filterDrawer && (React__default["default"].createElement(Drawer,
|
|
3800
|
+
filterDrawer && (React__default["default"].createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
|
|
3313
3801
|
// Actually filter the logs
|
|
3314
3802
|
// > Perform filters
|
|
3315
3803
|
const logs = [];
|
|
3316
3804
|
Object.keys(logMap).forEach((year) => {
|
|
3317
|
-
Object.keys(logMap).forEach((month) => {
|
|
3805
|
+
Object.keys(logMap[year]).forEach((month) => {
|
|
3318
3806
|
logMap[year][month].forEach((log) => {
|
|
3319
3807
|
/* ----------- Date Filter ---------- */
|
|
3320
3808
|
var _a;
|
|
@@ -3350,16 +3838,23 @@ const LogReviewer = (props) => {
|
|
|
3350
3838
|
// Whole context is deselected
|
|
3351
3839
|
contextFilterState[log.context] === false
|
|
3352
3840
|
// None of the subcontexts are selected
|
|
3353
|
-
|| (
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3841
|
+
|| (
|
|
3842
|
+
// Has subcontexts
|
|
3843
|
+
typeof contextFilterState[log.context] !== 'boolean'
|
|
3844
|
+
// None of the subcontexts are selected
|
|
3845
|
+
&& Object.values((_a = contextFilterState[log.context]) !== null && _a !== void 0 ? _a : {})
|
|
3846
|
+
.every((isSelected) => {
|
|
3847
|
+
return !isSelected;
|
|
3848
|
+
}))) {
|
|
3849
|
+
console.log(log.context, contextFilterState);
|
|
3357
3850
|
return;
|
|
3358
3851
|
}
|
|
3359
3852
|
// Subcontext doesn't match
|
|
3360
3853
|
if (
|
|
3361
|
-
// Log
|
|
3362
|
-
log.
|
|
3854
|
+
// Log context is not "uncategorized" (no point in further filters)
|
|
3855
|
+
log.context !== LogBuiltInMetadata.Context.Uncategorized
|
|
3856
|
+
// Log has a subcontext
|
|
3857
|
+
&& log.subcontext
|
|
3363
3858
|
// Context has subcontexts
|
|
3364
3859
|
&& (contextFilterState[log.context]
|
|
3365
3860
|
&& contextFilterState[log.context] !== false
|
|
@@ -3371,12 +3866,16 @@ const LogReviewer = (props) => {
|
|
|
3371
3866
|
/* -------------- Tags -------------- */
|
|
3372
3867
|
// No tags match
|
|
3373
3868
|
if (
|
|
3374
|
-
//
|
|
3375
|
-
|
|
3869
|
+
// At least one tag is required
|
|
3870
|
+
Object.values(tagFilterState)
|
|
3871
|
+
.filter((isSelected) => {
|
|
3872
|
+
return isSelected;
|
|
3873
|
+
})
|
|
3874
|
+
.length > 0
|
|
3376
3875
|
// No tags match
|
|
3377
|
-
&&
|
|
3876
|
+
&& log.tags.every((tag) => {
|
|
3378
3877
|
return !tagFilterState[tag];
|
|
3379
|
-
}))
|
|
3878
|
+
})) {
|
|
3380
3879
|
return;
|
|
3381
3880
|
}
|
|
3382
3881
|
/* ------- Actions and Errors ------- */
|
|
@@ -3510,7 +4009,7 @@ const LogReviewer = (props) => {
|
|
|
3510
4009
|
// Device info exists
|
|
3511
4010
|
&& log.device
|
|
3512
4011
|
// Mobile filter doesn't match
|
|
3513
|
-
&& (advancedFilterState.isMobile
|
|
4012
|
+
&& (advancedFilterState.isMobile !== log.device.isMobile)) {
|
|
3514
4013
|
return;
|
|
3515
4014
|
}
|
|
3516
4015
|
// Log source doesn't match
|
|
@@ -3552,199 +4051,20 @@ const LogReviewer = (props) => {
|
|
|
3552
4051
|
/*----------------------------------------*/
|
|
3553
4052
|
/* Data */
|
|
3554
4053
|
/*----------------------------------------*/
|
|
3555
|
-
//
|
|
3556
|
-
const
|
|
3557
|
-
{
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
},
|
|
3562
|
-
{
|
|
3563
|
-
title: 'Last Name',
|
|
3564
|
-
param: 'userLastName',
|
|
3565
|
-
type: ParamType$1.String,
|
|
3566
|
-
},
|
|
3567
|
-
{
|
|
3568
|
-
title: 'Email',
|
|
3569
|
-
param: 'userEmail',
|
|
3570
|
-
type: ParamType$1.String,
|
|
3571
|
-
},
|
|
3572
|
-
{
|
|
3573
|
-
title: 'Canvas Id',
|
|
3574
|
-
param: 'userId',
|
|
3575
|
-
type: ParamType$1.Int,
|
|
3576
|
-
},
|
|
3577
|
-
{
|
|
3578
|
-
title: 'Student',
|
|
3579
|
-
param: 'isLearner',
|
|
3580
|
-
type: ParamType$1.Boolean,
|
|
3581
|
-
},
|
|
3582
|
-
{
|
|
3583
|
-
title: 'Teaching Staff',
|
|
3584
|
-
param: 'isTTM',
|
|
3585
|
-
type: ParamType$1.Boolean,
|
|
3586
|
-
startsHidden: true,
|
|
3587
|
-
},
|
|
3588
|
-
{
|
|
3589
|
-
title: 'Admin',
|
|
3590
|
-
param: 'isAdmin',
|
|
3591
|
-
type: ParamType$1.Boolean,
|
|
3592
|
-
startsHidden: true,
|
|
3593
|
-
},
|
|
3594
|
-
{
|
|
3595
|
-
title: 'Course Canvas Id',
|
|
3596
|
-
param: 'courseId',
|
|
3597
|
-
type: ParamType$1.Int,
|
|
3598
|
-
startsHidden: true,
|
|
3599
|
-
},
|
|
3600
|
-
{
|
|
3601
|
-
title: 'Course Name',
|
|
3602
|
-
param: 'courseName',
|
|
3603
|
-
type: ParamType$1.String,
|
|
3604
|
-
},
|
|
3605
|
-
{
|
|
3606
|
-
title: 'Browser Name',
|
|
3607
|
-
param: 'browser.name',
|
|
3608
|
-
type: ParamType$1.String,
|
|
3609
|
-
startsHidden: true,
|
|
3610
|
-
},
|
|
3611
|
-
{
|
|
3612
|
-
title: 'Browser Version',
|
|
3613
|
-
param: 'browser.version',
|
|
3614
|
-
type: ParamType$1.String,
|
|
3615
|
-
startsHidden: true,
|
|
3616
|
-
},
|
|
3617
|
-
{
|
|
3618
|
-
title: 'OS',
|
|
3619
|
-
param: 'device.os',
|
|
3620
|
-
type: ParamType$1.String,
|
|
3621
|
-
startsHidden: true,
|
|
3622
|
-
},
|
|
3623
|
-
{
|
|
3624
|
-
title: 'Mobile',
|
|
3625
|
-
param: 'device.isMobile',
|
|
3626
|
-
type: ParamType$1.Boolean,
|
|
3627
|
-
startsHidden: true,
|
|
3628
|
-
},
|
|
3629
|
-
{
|
|
3630
|
-
title: 'Year',
|
|
3631
|
-
param: 'year',
|
|
3632
|
-
type: ParamType$1.Int,
|
|
3633
|
-
},
|
|
3634
|
-
{
|
|
3635
|
-
title: 'Month',
|
|
3636
|
-
param: 'month',
|
|
3637
|
-
type: ParamType$1.Int,
|
|
3638
|
-
},
|
|
3639
|
-
{
|
|
3640
|
-
title: 'Day',
|
|
3641
|
-
param: 'day',
|
|
3642
|
-
type: ParamType$1.Int,
|
|
3643
|
-
},
|
|
3644
|
-
{
|
|
3645
|
-
title: 'Hour',
|
|
3646
|
-
param: 'hour',
|
|
3647
|
-
type: ParamType$1.Int,
|
|
3648
|
-
},
|
|
3649
|
-
{
|
|
3650
|
-
title: 'Minute',
|
|
3651
|
-
param: 'minute',
|
|
3652
|
-
type: ParamType$1.Int,
|
|
3653
|
-
startsHidden: true,
|
|
3654
|
-
},
|
|
3655
|
-
{
|
|
3656
|
-
title: 'Timestamp',
|
|
3657
|
-
param: 'timestamp',
|
|
3658
|
-
type: ParamType$1.Int,
|
|
3659
|
-
startsHidden: true,
|
|
3660
|
-
},
|
|
3661
|
-
{
|
|
3662
|
-
title: 'Context',
|
|
3663
|
-
param: 'context',
|
|
3664
|
-
type: ParamType$1.String,
|
|
3665
|
-
},
|
|
3666
|
-
{
|
|
3667
|
-
title: 'Subcontext',
|
|
3668
|
-
param: 'subcontext',
|
|
3669
|
-
type: ParamType$1.String,
|
|
3670
|
-
},
|
|
3671
|
-
{
|
|
3672
|
-
title: 'Tags',
|
|
3673
|
-
param: 'tags',
|
|
3674
|
-
type: ParamType$1.JSON,
|
|
3675
|
-
startsHidden: true,
|
|
3676
|
-
},
|
|
3677
|
-
{
|
|
3678
|
-
title: 'Log Level',
|
|
3679
|
-
param: 'level',
|
|
3680
|
-
type: ParamType$1.String,
|
|
3681
|
-
startsHidden: true,
|
|
3682
|
-
},
|
|
3683
|
-
{
|
|
3684
|
-
title: 'Metadata',
|
|
3685
|
-
param: 'metadata',
|
|
3686
|
-
type: ParamType$1.JSON,
|
|
3687
|
-
startsHidden: true,
|
|
3688
|
-
},
|
|
3689
|
-
{
|
|
3690
|
-
title: 'Source',
|
|
3691
|
-
param: 'source',
|
|
3692
|
-
type: ParamType$1.String,
|
|
3693
|
-
},
|
|
3694
|
-
{
|
|
3695
|
-
title: 'Server Route Path',
|
|
3696
|
-
param: 'routePath',
|
|
3697
|
-
type: ParamType$1.String,
|
|
3698
|
-
startsHidden: true,
|
|
3699
|
-
},
|
|
3700
|
-
{
|
|
3701
|
-
title: 'Server Route Template',
|
|
3702
|
-
param: 'routeTemplate',
|
|
3703
|
-
type: ParamType$1.String,
|
|
3704
|
-
startsHidden: true,
|
|
3705
|
-
},
|
|
3706
|
-
{
|
|
3707
|
-
title: 'Type',
|
|
3708
|
-
param: 'type',
|
|
3709
|
-
type: ParamType$1.String,
|
|
3710
|
-
},
|
|
3711
|
-
{
|
|
3712
|
-
title: 'Error Message',
|
|
3713
|
-
param: 'errorMessage',
|
|
3714
|
-
type: ParamType$1.String,
|
|
3715
|
-
startsHidden: true,
|
|
3716
|
-
},
|
|
3717
|
-
{
|
|
3718
|
-
title: 'Error Code',
|
|
3719
|
-
param: 'errorCode',
|
|
3720
|
-
type: ParamType$1.String,
|
|
3721
|
-
startsHidden: true,
|
|
3722
|
-
},
|
|
3723
|
-
{
|
|
3724
|
-
title: 'Error Stack',
|
|
3725
|
-
param: 'errorStack',
|
|
3726
|
-
type: ParamType$1.String,
|
|
3727
|
-
startsHidden: true,
|
|
3728
|
-
},
|
|
3729
|
-
{
|
|
3730
|
-
title: 'Action Target',
|
|
3731
|
-
param: 'target',
|
|
3732
|
-
type: ParamType$1.String,
|
|
3733
|
-
startsHidden: true,
|
|
3734
|
-
},
|
|
3735
|
-
{
|
|
3736
|
-
title: 'Action Type',
|
|
3737
|
-
param: 'action',
|
|
3738
|
-
type: ParamType$1.String,
|
|
3739
|
-
startsHidden: true,
|
|
3740
|
-
},
|
|
3741
|
-
];
|
|
4054
|
+
// Nothing to show notice
|
|
4055
|
+
const noLogsNotice = (logs.length === 0
|
|
4056
|
+
? (React__default["default"].createElement("div", { className: "alert alert-warning text-center mt-2" },
|
|
4057
|
+
React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
|
|
4058
|
+
React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet.")))
|
|
4059
|
+
: undefined);
|
|
3742
4060
|
// Create intelliTable
|
|
3743
|
-
const dataTable = (React__default["default"].createElement(IntelliTable, { title: "Matching Logs", id: "logs", data: logs, columns: columns }));
|
|
4061
|
+
const dataTable = (React__default["default"].createElement(IntelliTable, { title: "Matching Logs:", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns }));
|
|
3744
4062
|
// Main body
|
|
3745
4063
|
body = (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
3746
4064
|
filters,
|
|
3747
|
-
|
|
4065
|
+
React__default["default"].createElement("div", { className: "mt-2" },
|
|
4066
|
+
dataTable,
|
|
4067
|
+
noLogsNotice)));
|
|
3748
4068
|
}
|
|
3749
4069
|
/* ---------- Wrap in Modal --------- */
|
|
3750
4070
|
return (React__default["default"].createElement("div", { className: "LogReviewer-outer-container" },
|
|
@@ -3752,14 +4072,10 @@ const LogReviewer = (props) => {
|
|
|
3752
4072
|
React__default["default"].createElement("div", { className: "LogReviewer-inner-container" },
|
|
3753
4073
|
React__default["default"].createElement("div", { className: "LogReviewer-header" },
|
|
3754
4074
|
React__default["default"].createElement("div", { className: "LogReviewer-header-title" },
|
|
3755
|
-
React__default["default"].createElement("
|
|
3756
|
-
React__default["default"].createElement("
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
padding: 0,
|
|
3760
|
-
margin: 0,
|
|
3761
|
-
} },
|
|
3762
|
-
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes }))),
|
|
4075
|
+
React__default["default"].createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
|
|
4076
|
+
React__default["default"].createElement("div", { style: { width: 0 } },
|
|
4077
|
+
React__default["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 },
|
|
4078
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes })))),
|
|
3763
4079
|
React__default["default"].createElement("div", { className: "LogReviewer-contents" }, body))));
|
|
3764
4080
|
};
|
|
3765
4081
|
|
|
@@ -4090,15 +4406,17 @@ const initServer = (opts) => {
|
|
|
4090
4406
|
},
|
|
4091
4407
|
handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
4092
4408
|
// Get user info
|
|
4093
|
-
const { userId, isAdmin } = params;
|
|
4409
|
+
const { year, month, userId, isAdmin, } = params;
|
|
4094
4410
|
// Validate user
|
|
4095
|
-
// isAdmin is already checked because path starts with '/admin'
|
|
4096
4411
|
const canReview = yield canReviewLogs(userId, isAdmin);
|
|
4097
4412
|
if (!canReview) {
|
|
4098
4413
|
throw new ErrorWithCode('You cannot access this resource because you do not have the appropriate permissions.', ReactKitErrorCode$1.NotAllowedToReviewLogs);
|
|
4099
4414
|
}
|
|
4100
4415
|
// Query for logs
|
|
4101
|
-
const logs = yield _logCollection.find({
|
|
4416
|
+
const logs = yield _logCollection.find({
|
|
4417
|
+
year,
|
|
4418
|
+
month,
|
|
4419
|
+
});
|
|
4102
4420
|
// Return logs
|
|
4103
4421
|
return logs;
|
|
4104
4422
|
}),
|
|
@@ -4747,7 +5065,7 @@ const genRouteHandler = (opts) => {
|
|
|
4747
5065
|
isAdmin: !!launchInfo.isAdmin,
|
|
4748
5066
|
isTTM: !!launchInfo.isTTM,
|
|
4749
5067
|
courseId: launchInfo.courseId,
|
|
4750
|
-
courseName: launchInfo.
|
|
5068
|
+
courseName: launchInfo.contextLabel,
|
|
4751
5069
|
browser,
|
|
4752
5070
|
device,
|
|
4753
5071
|
year,
|
|
@@ -4774,7 +5092,7 @@ const genRouteHandler = (opts) => {
|
|
|
4774
5092
|
}
|
|
4775
5093
|
: {
|
|
4776
5094
|
type: LogType$1.Action,
|
|
4777
|
-
target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.
|
|
5095
|
+
target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.NoTarget),
|
|
4778
5096
|
action: ((_o = opts.action) !== null && _o !== void 0 ? _o : LogAction$1.Unknown),
|
|
4779
5097
|
});
|
|
4780
5098
|
// Source-specific info
|
|
@@ -4987,29 +5305,6 @@ const startMinWait = (minWaitMs) => {
|
|
|
4987
5305
|
});
|
|
4988
5306
|
};
|
|
4989
5307
|
|
|
4990
|
-
// Import shared helpers
|
|
4991
|
-
/**
|
|
4992
|
-
* Get a human-readable description of a date (all in ET)
|
|
4993
|
-
* @author Gabe Abrams
|
|
4994
|
-
* @param [dateOrTimestamp=today] the date or timestamp for the date to describe
|
|
4995
|
-
* @returns human-readable description of the date
|
|
4996
|
-
*/
|
|
4997
|
-
const getHumanReadableDate = (dateOrTimestamp) => {
|
|
4998
|
-
// Get the date info
|
|
4999
|
-
const { month, day, year, } = getTimeInfoInET(dateOrTimestamp);
|
|
5000
|
-
const currYear = getTimeInfoInET().year;
|
|
5001
|
-
// Get the short month description
|
|
5002
|
-
const monthName = getMonthName(month).short;
|
|
5003
|
-
// Create start of description
|
|
5004
|
-
let description = `${monthName} ${day}${getOrdinal(day)}`;
|
|
5005
|
-
// Add on year if it's different
|
|
5006
|
-
if (year !== currYear) {
|
|
5007
|
-
description += ` ${year}`;
|
|
5008
|
-
}
|
|
5009
|
-
// Return description
|
|
5010
|
-
return description;
|
|
5011
|
-
};
|
|
5012
|
-
|
|
5013
5308
|
/**
|
|
5014
5309
|
* Get the current part of day (morning, evening, etc.)
|
|
5015
5310
|
* @author Gabe Abrams
|