dce-reactkit 3.2.2-beta.5 → 3.2.2-beta.51
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 +741 -446
- 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 +742 -447
- 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 +28 -23
- 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,
|
|
@@ -1307,18 +1307,6 @@ const getTimeInfoInET = (dateOrTimestamp) => {
|
|
|
1307
1307
|
};
|
|
1308
1308
|
};
|
|
1309
1309
|
|
|
1310
|
-
/**
|
|
1311
|
-
* Force a number to stay within specific bounds
|
|
1312
|
-
* @author Gabe Abrams
|
|
1313
|
-
* @param num the number to move into the bounds
|
|
1314
|
-
* @param min the minimum number in the bound
|
|
1315
|
-
* @param max the maximum number in the bound
|
|
1316
|
-
* @returns bounded number
|
|
1317
|
-
*/
|
|
1318
|
-
const forceNumIntoBounds = (num, min, max) => {
|
|
1319
|
-
return Math.max(min, Math.min(max, num));
|
|
1320
|
-
};
|
|
1321
|
-
|
|
1322
1310
|
/**
|
|
1323
1311
|
* A very simple, lightweight date chooser
|
|
1324
1312
|
* @author Gabe Abrams
|
|
@@ -1333,7 +1321,7 @@ const SimpleDateChooser = (props) => {
|
|
|
1333
1321
|
var _a;
|
|
1334
1322
|
/* -------------- Props ------------- */
|
|
1335
1323
|
const { ariaLabel, name, month, day, year, onChange, chooseFromPast, } = props;
|
|
1336
|
-
|
|
1324
|
+
const numMonthsToShow = ((_a = props.numMonthsToShow) !== null && _a !== void 0 ? _a : 6);
|
|
1337
1325
|
/*------------------------------------------------------------------------*/
|
|
1338
1326
|
/* Render */
|
|
1339
1327
|
/*------------------------------------------------------------------------*/
|
|
@@ -1347,7 +1335,7 @@ const SimpleDateChooser = (props) => {
|
|
|
1347
1335
|
let startMonth = today.month;
|
|
1348
1336
|
if (chooseFromPast) {
|
|
1349
1337
|
startMonth -= Math.max(0, numMonthsToShow - 1);
|
|
1350
|
-
|
|
1338
|
+
while (startMonth <= 0) {
|
|
1351
1339
|
startMonth += 12;
|
|
1352
1340
|
startYear -= 1;
|
|
1353
1341
|
}
|
|
@@ -1355,22 +1343,35 @@ const SimpleDateChooser = (props) => {
|
|
|
1355
1343
|
for (let i = 0; i < numMonthsToShow; i++) {
|
|
1356
1344
|
// Get month and year info
|
|
1357
1345
|
const unmoddedMonth = (startMonth + i);
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1346
|
+
let month = unmoddedMonth;
|
|
1347
|
+
while (month > 12) {
|
|
1348
|
+
month -= 12;
|
|
1349
|
+
}
|
|
1361
1350
|
const monthName = getMonthName(month).full;
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
: startYear);
|
|
1351
|
+
// Year is start year +1 for each 12 months
|
|
1352
|
+
const year = (startYear + (Math.floor(unmoddedMonth / 12)));
|
|
1365
1353
|
// Figure out which days are allowed
|
|
1366
1354
|
const days = [];
|
|
1367
1355
|
const numDaysInMonth = (new Date(year, month, 0)).getDate();
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1356
|
+
if (chooseFromPast) {
|
|
1357
|
+
// Past selection
|
|
1358
|
+
const numDaysToAdd = ((month === today.month)
|
|
1359
|
+
? today.day // Current month, only add up to today
|
|
1360
|
+
: numDaysInMonth // Past month, add all days
|
|
1361
|
+
);
|
|
1362
|
+
for (let day = 1; day <= numDaysToAdd; day++) {
|
|
1363
|
+
days.push(day);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
else {
|
|
1367
|
+
// Future selection: add all remaining days of the month
|
|
1368
|
+
const firstDay = (month === today.month
|
|
1369
|
+
? today.day // Current month: start at current date
|
|
1370
|
+
: 1 // Future month: start at beginning of month
|
|
1371
|
+
);
|
|
1372
|
+
for (let day = firstDay; day <= numDaysInMonth; day++) {
|
|
1373
|
+
days.push(day);
|
|
1374
|
+
}
|
|
1374
1375
|
}
|
|
1375
1376
|
choices.push({
|
|
1376
1377
|
choiceName: `${monthName} ${year}`,
|
|
@@ -1441,14 +1442,16 @@ const Drawer = (props) => {
|
|
|
1441
1442
|
/*------------------------------------------------------------------------*/
|
|
1442
1443
|
/* -------------- Props ------------- */
|
|
1443
1444
|
// Destructure all props
|
|
1444
|
-
const { children, } = props;
|
|
1445
|
+
const { customBackgroundColor, children, } = props;
|
|
1445
1446
|
/*------------------------------------------------------------------------*/
|
|
1446
1447
|
/* Render */
|
|
1447
1448
|
/*------------------------------------------------------------------------*/
|
|
1448
1449
|
/*----------------------------------------*/
|
|
1449
1450
|
/* Main UI */
|
|
1450
1451
|
/*----------------------------------------*/
|
|
1451
|
-
return (React__default["default"].createElement("div", { className: "Drawer-container"
|
|
1452
|
+
return (React__default["default"].createElement("div", { className: "Drawer-container", style: {
|
|
1453
|
+
backgroundColor: (customBackgroundColor !== null && customBackgroundColor !== void 0 ? customBackgroundColor : undefined),
|
|
1454
|
+
} },
|
|
1452
1455
|
React__default["default"].createElement("style", null, style$4),
|
|
1453
1456
|
children));
|
|
1454
1457
|
};
|
|
@@ -1911,24 +1914,25 @@ const CopiableBox = (props) => {
|
|
|
1911
1914
|
/**
|
|
1912
1915
|
* Reusable nested item picker
|
|
1913
1916
|
* @author Yuen Ler Chow
|
|
1917
|
+
* @author Gabe Abrams
|
|
1914
1918
|
*/
|
|
1915
1919
|
/* ------------- Actions ------------ */
|
|
1916
1920
|
// Types of actions
|
|
1917
1921
|
var ActionType$2;
|
|
1918
1922
|
(function (ActionType) {
|
|
1919
|
-
// Toggle whether
|
|
1920
|
-
ActionType["
|
|
1923
|
+
// Toggle whether a child are being shown
|
|
1924
|
+
ActionType["ToggleChild"] = "toggle-child";
|
|
1921
1925
|
})(ActionType$2 || (ActionType$2 = {}));
|
|
1922
1926
|
/**
|
|
1923
1927
|
* Reducer that executes actions
|
|
1924
|
-
* @author
|
|
1928
|
+
* @author Gabe Abrams
|
|
1925
1929
|
* @param state current state
|
|
1926
1930
|
* @param action action to execute
|
|
1927
1931
|
*/
|
|
1928
1932
|
const reducer$2 = (state, action) => {
|
|
1929
1933
|
switch (action.type) {
|
|
1930
|
-
case ActionType$2.
|
|
1931
|
-
return {
|
|
1934
|
+
case ActionType$2.ToggleChild: {
|
|
1935
|
+
return Object.assign(Object.assign({}, state), { childExpanded: Object.assign(Object.assign({}, state.childExpanded), { [String(action.id)]: !state.childExpanded[String(action.id)] }) });
|
|
1932
1936
|
}
|
|
1933
1937
|
default: {
|
|
1934
1938
|
return state;
|
|
@@ -1946,14 +1950,19 @@ const NestableItemList = (props) => {
|
|
|
1946
1950
|
// Destructure all props
|
|
1947
1951
|
const { items, onChanged, } = props;
|
|
1948
1952
|
/* -------------- State ------------- */
|
|
1953
|
+
// Create initial map of child expanded booleans
|
|
1954
|
+
const initChildExpanded = {};
|
|
1955
|
+
items.forEach((item) => {
|
|
1956
|
+
initChildExpanded[String(item.id)] = false;
|
|
1957
|
+
});
|
|
1949
1958
|
// Initial state
|
|
1950
1959
|
const initialState = {
|
|
1951
|
-
|
|
1960
|
+
childExpanded: initChildExpanded,
|
|
1952
1961
|
};
|
|
1953
1962
|
// Initialize state
|
|
1954
1963
|
const [state, dispatch] = React.useReducer(reducer$2, initialState);
|
|
1955
1964
|
// Destructure common state
|
|
1956
|
-
const {
|
|
1965
|
+
const { childExpanded, } = state;
|
|
1957
1966
|
/*------------------------------------------------------------------------*/
|
|
1958
1967
|
/* Component Functions */
|
|
1959
1968
|
/*------------------------------------------------------------------------*/
|
|
@@ -2041,14 +2050,15 @@ const NestableItemList = (props) => {
|
|
|
2041
2050
|
backgroundColor: 'transparent',
|
|
2042
2051
|
}, type: "button", onClick: () => {
|
|
2043
2052
|
dispatch({
|
|
2044
|
-
type: ActionType$2.
|
|
2053
|
+
type: ActionType$2.ToggleChild,
|
|
2054
|
+
id: item.id,
|
|
2045
2055
|
});
|
|
2046
|
-
}, "aria-label": `${
|
|
2047
|
-
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon:
|
|
2056
|
+
}, "aria-label": `${childExpanded[item.id] ? 'Hide' : 'Show'} items in ${item.name}` },
|
|
2057
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: childExpanded[item.id] ? freeSolidSvgIcons.faChevronDown : freeSolidSvgIcons.faChevronRight })))),
|
|
2048
2058
|
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
2059
|
onChanged(changeChecked(item.id, checked, items));
|
|
2050
2060
|
}, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light }),
|
|
2051
|
-
item.isGroup &&
|
|
2061
|
+
(item.isGroup && childExpanded[item.id]) && (React__default["default"].createElement("div", { className: "NestableItemList-children-container", style: {
|
|
2052
2062
|
paddingLeft: '2.2rem',
|
|
2053
2063
|
} },
|
|
2054
2064
|
React__default["default"].createElement(NestableItemList, { items: item.children, onChanged: (updatedItems) => {
|
|
@@ -2070,7 +2080,7 @@ const ItemPicker = (props) => {
|
|
|
2070
2080
|
/*------------------------------------------------------------------------*/
|
|
2071
2081
|
/* -------------- Props ------------- */
|
|
2072
2082
|
// Destructure all props
|
|
2073
|
-
const { title, items, onChanged, } = props;
|
|
2083
|
+
const { title, items, onChanged, noBottomMargin, } = props;
|
|
2074
2084
|
/*------------------------------------------------------------------------*/
|
|
2075
2085
|
/* Component Functions */
|
|
2076
2086
|
/*------------------------------------------------------------------------*/
|
|
@@ -2080,11 +2090,34 @@ const ItemPicker = (props) => {
|
|
|
2080
2090
|
/*----------------------------------------*/
|
|
2081
2091
|
/* Main UI */
|
|
2082
2092
|
/*----------------------------------------*/
|
|
2083
|
-
return (React__default["default"].createElement(TabBox, { title: title },
|
|
2093
|
+
return (React__default["default"].createElement(TabBox, { title: title, noBottomMargin: noBottomMargin },
|
|
2084
2094
|
React__default["default"].createElement("div", { style: { overflowX: 'auto' } },
|
|
2085
2095
|
React__default["default"].createElement(NestableItemList, { items: items, onChanged: onChanged }))));
|
|
2086
2096
|
};
|
|
2087
2097
|
|
|
2098
|
+
// Import shared helpers
|
|
2099
|
+
/**
|
|
2100
|
+
* Get a human-readable description of a date (all in ET)
|
|
2101
|
+
* @author Gabe Abrams
|
|
2102
|
+
* @param [dateOrTimestamp=today] the date or timestamp for the date to describe
|
|
2103
|
+
* @returns human-readable description of the date
|
|
2104
|
+
*/
|
|
2105
|
+
const getHumanReadableDate = (dateOrTimestamp) => {
|
|
2106
|
+
// Get the date info
|
|
2107
|
+
const { month, day, year, } = getTimeInfoInET(dateOrTimestamp);
|
|
2108
|
+
const currYear = getTimeInfoInET().year;
|
|
2109
|
+
// Get the short month description
|
|
2110
|
+
const monthName = getMonthName(month).short;
|
|
2111
|
+
// Create start of description
|
|
2112
|
+
let description = `${monthName} ${day}${getOrdinal(day)}`;
|
|
2113
|
+
// Add on year if it's different
|
|
2114
|
+
if (year !== currYear) {
|
|
2115
|
+
description += ` ${year}`;
|
|
2116
|
+
}
|
|
2117
|
+
// Return description
|
|
2118
|
+
return description;
|
|
2119
|
+
};
|
|
2120
|
+
|
|
2088
2121
|
/**
|
|
2089
2122
|
* Path of the route for storing client-side logs
|
|
2090
2123
|
* @author Gabe Abrams
|
|
@@ -2124,37 +2157,37 @@ var LogType$1 = LogType;
|
|
|
2124
2157
|
var LogAction;
|
|
2125
2158
|
(function (LogAction) {
|
|
2126
2159
|
// Target was opened by the user (it was not on screen, but now it is)
|
|
2127
|
-
LogAction["Open"] = "
|
|
2160
|
+
LogAction["Open"] = "Open";
|
|
2128
2161
|
// Target was closed by the user (it was on screen, but now it is not)
|
|
2129
|
-
LogAction["Close"] = "
|
|
2162
|
+
LogAction["Close"] = "Close";
|
|
2130
2163
|
// Target was cancelled by the user (it was on closed without saving)
|
|
2131
|
-
LogAction["Cancel"] = "
|
|
2164
|
+
LogAction["Cancel"] = "Cancel";
|
|
2132
2165
|
// Target was expanded by the user (it always remains on screen, but size was changed)
|
|
2133
|
-
LogAction["Expand"] = "
|
|
2166
|
+
LogAction["Expand"] = "Expand";
|
|
2134
2167
|
// Target was collapsed by the user (it always remains on screen, but size was changed)
|
|
2135
|
-
LogAction["Collapse"] = "
|
|
2168
|
+
LogAction["Collapse"] = "Collapse";
|
|
2136
2169
|
// Target was viewed by the user (only for items that are not opened or closed, those must use Open/Close actions)
|
|
2137
|
-
LogAction["View"] = "
|
|
2170
|
+
LogAction["View"] = "View";
|
|
2138
2171
|
// Target interrupted the user (popup, dialog, validation message, etc. appeared without user prompting)
|
|
2139
|
-
LogAction["Interrupt"] = "
|
|
2172
|
+
LogAction["Interrupt"] = "Interrupt";
|
|
2140
2173
|
// Target was created by the user (it did not exist before)
|
|
2141
|
-
LogAction["Create"] = "
|
|
2174
|
+
LogAction["Create"] = "Create";
|
|
2142
2175
|
// Target was edited by the user (it existed and was changed)
|
|
2143
|
-
LogAction["Edit"] = "
|
|
2176
|
+
LogAction["Edit"] = "Edit";
|
|
2144
2177
|
// Target was deleted by the user (it existed and now it doesn't)
|
|
2145
|
-
LogAction["Delete"] = "
|
|
2178
|
+
LogAction["Delete"] = "Delete";
|
|
2146
2179
|
// Target was added by the user (it already existed and was added to another place)
|
|
2147
|
-
LogAction["Add"] = "
|
|
2180
|
+
LogAction["Add"] = "Add";
|
|
2148
2181
|
// Target was removed by the user (it was removed from something but still exists)
|
|
2149
|
-
LogAction["Remove"] = "
|
|
2182
|
+
LogAction["Remove"] = "Remove";
|
|
2150
2183
|
// Target was activated by the user (click, check, tap, keypress, etc.)
|
|
2151
|
-
LogAction["Activate"] = "
|
|
2184
|
+
LogAction["Activate"] = "Activate";
|
|
2152
2185
|
// Target was deactivated by the user (click away, uncheck, tap outside of, tab away, etc.)
|
|
2153
|
-
LogAction["Deactivate"] = "
|
|
2186
|
+
LogAction["Deactivate"] = "Deactivate";
|
|
2154
2187
|
// User showed interest in a target (hover, peek, etc.)
|
|
2155
|
-
LogAction["Peek"] = "
|
|
2188
|
+
LogAction["Peek"] = "Peek";
|
|
2156
2189
|
// Unknown action
|
|
2157
|
-
LogAction["Unknown"] = "
|
|
2190
|
+
LogAction["Unknown"] = "Unknown";
|
|
2158
2191
|
})(LogAction || (LogAction = {}));
|
|
2159
2192
|
var LogAction$1 = LogAction;
|
|
2160
2193
|
|
|
@@ -2196,12 +2229,12 @@ const roundToNumDecimals = (num, numDecimals) => {
|
|
|
2196
2229
|
* @returns escaped cell text
|
|
2197
2230
|
*/
|
|
2198
2231
|
const escapeCellText = (text) => {
|
|
2199
|
-
if (!text.includes(',')) {
|
|
2232
|
+
if (!String(text).includes(',')) {
|
|
2200
2233
|
// No need to escape
|
|
2201
|
-
return text;
|
|
2234
|
+
return String(text);
|
|
2202
2235
|
}
|
|
2203
2236
|
// Perform escape
|
|
2204
|
-
return `"${text.replace(/"/g, '""')}`;
|
|
2237
|
+
return `"${String(text).replace(/"/g, '""')}`;
|
|
2205
2238
|
};
|
|
2206
2239
|
/**
|
|
2207
2240
|
* Generate a CSV file
|
|
@@ -2220,9 +2253,26 @@ const genCSV = (data, columns) => {
|
|
|
2220
2253
|
.join(','));
|
|
2221
2254
|
// Add each row
|
|
2222
2255
|
data.forEach((datum) => {
|
|
2256
|
+
csv += '\n';
|
|
2223
2257
|
csv += (columns
|
|
2224
2258
|
.map((column) => {
|
|
2225
|
-
|
|
2259
|
+
let contents;
|
|
2260
|
+
const cell = datum[column.param];
|
|
2261
|
+
if (typeof cell === 'string'
|
|
2262
|
+
|| typeof cell === 'number') {
|
|
2263
|
+
contents = String(cell);
|
|
2264
|
+
}
|
|
2265
|
+
else if (typeof cell === 'undefined'
|
|
2266
|
+
|| cell === null) {
|
|
2267
|
+
contents = '';
|
|
2268
|
+
}
|
|
2269
|
+
else if (typeof cell === 'object') {
|
|
2270
|
+
contents = JSON.stringify(cell);
|
|
2271
|
+
}
|
|
2272
|
+
else {
|
|
2273
|
+
contents = '';
|
|
2274
|
+
}
|
|
2275
|
+
return escapeCellText(contents);
|
|
2226
2276
|
})
|
|
2227
2277
|
.join(','));
|
|
2228
2278
|
});
|
|
@@ -2251,11 +2301,12 @@ const CSVDownloadButton = (props) => {
|
|
|
2251
2301
|
/* Main UI */
|
|
2252
2302
|
/*----------------------------------------*/
|
|
2253
2303
|
// 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
|
|
2304
|
+
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
2305
|
? `Click to download ${filename}`
|
|
2256
2306
|
: ariaLabel), style: style, onClick: onClick },
|
|
2257
2307
|
!children && (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
2258
|
-
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCloudDownloadAlt
|
|
2308
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCloudDownloadAlt }),
|
|
2309
|
+
' ',
|
|
2259
2310
|
"Download CSV")),
|
|
2260
2311
|
children));
|
|
2261
2312
|
};
|
|
@@ -2279,9 +2330,13 @@ var ActionType$1;
|
|
|
2279
2330
|
// Toggle sort column param
|
|
2280
2331
|
ActionType["ToggleSortColumn"] = "toggle-sort-column";
|
|
2281
2332
|
// Toggle the visibility of a column
|
|
2282
|
-
ActionType["
|
|
2333
|
+
ActionType["UpdateColumnVisibility"] = "update-column-visibility";
|
|
2283
2334
|
// Toggle the column visibility customization modal
|
|
2284
2335
|
ActionType["ToggleColVisCusModalVisibility"] = "toggle-col-vis-cus-modal-visibility";
|
|
2336
|
+
// Show all columns
|
|
2337
|
+
ActionType["ShowAllColumns"] = "show-all-columns";
|
|
2338
|
+
// Hide all columns
|
|
2339
|
+
ActionType["HideAllColumns"] = "hide-all-columns";
|
|
2285
2340
|
})(ActionType$1 || (ActionType$1 = {}));
|
|
2286
2341
|
/**
|
|
2287
2342
|
* Reducer that executes actions
|
|
@@ -2303,14 +2358,28 @@ const reducer$1 = (state, action) => {
|
|
|
2303
2358
|
// Stop sorting by column
|
|
2304
2359
|
return Object.assign(Object.assign({}, state), { sortColumnParam: undefined, sortType: SortType.Ascending });
|
|
2305
2360
|
}
|
|
2306
|
-
case ActionType$1.
|
|
2361
|
+
case ActionType$1.UpdateColumnVisibility: {
|
|
2307
2362
|
const { columnVisibilityMap } = state;
|
|
2308
|
-
columnVisibilityMap[action.param] =
|
|
2363
|
+
columnVisibilityMap[action.param] = action.visible;
|
|
2309
2364
|
return Object.assign(Object.assign({}, state), { columnVisibilityMap });
|
|
2310
2365
|
}
|
|
2311
2366
|
case ActionType$1.ToggleColVisCusModalVisibility: {
|
|
2312
2367
|
return Object.assign(Object.assign({}, state), { columnVisibilityCustomizationModalVisible: !state.columnVisibilityCustomizationModalVisible });
|
|
2313
2368
|
}
|
|
2369
|
+
case ActionType$1.ShowAllColumns: {
|
|
2370
|
+
const { columnVisibilityMap } = state;
|
|
2371
|
+
Object.keys(columnVisibilityMap).forEach((param) => {
|
|
2372
|
+
columnVisibilityMap[param] = true;
|
|
2373
|
+
});
|
|
2374
|
+
return Object.assign(Object.assign({}, state), { columnVisibilityMap });
|
|
2375
|
+
}
|
|
2376
|
+
case ActionType$1.HideAllColumns: {
|
|
2377
|
+
const { columnVisibilityMap } = state;
|
|
2378
|
+
Object.keys(columnVisibilityMap).forEach((param) => {
|
|
2379
|
+
columnVisibilityMap[param] = false;
|
|
2380
|
+
});
|
|
2381
|
+
return Object.assign(Object.assign({}, state), { columnVisibilityMap });
|
|
2382
|
+
}
|
|
2314
2383
|
default: {
|
|
2315
2384
|
return state;
|
|
2316
2385
|
}
|
|
@@ -2326,7 +2395,18 @@ const IntelliTable = (props) => {
|
|
|
2326
2395
|
var _a;
|
|
2327
2396
|
/* -------------- Props ------------- */
|
|
2328
2397
|
// Destructure all props
|
|
2329
|
-
const { title, id,
|
|
2398
|
+
const { title, id, columns, } = props;
|
|
2399
|
+
// Get data, show empty row if none
|
|
2400
|
+
const data = ((props.data && props.data.length > 0)
|
|
2401
|
+
? props.data
|
|
2402
|
+
: [{ id: 'empty-row' }]);
|
|
2403
|
+
// Get CSV filename
|
|
2404
|
+
let filename = `${title}.csv`;
|
|
2405
|
+
if (props.csvName) {
|
|
2406
|
+
filename = (props.csvName.endsWith('.csv')
|
|
2407
|
+
? props.csvName
|
|
2408
|
+
: `${props.csvName}.csv`);
|
|
2409
|
+
}
|
|
2330
2410
|
/* -------------- State ------------- */
|
|
2331
2411
|
// Initial state
|
|
2332
2412
|
const initColumnVisibilityMap = {};
|
|
@@ -2343,21 +2423,49 @@ const IntelliTable = (props) => {
|
|
|
2343
2423
|
const [state, dispatch] = React.useReducer(reducer$1, initialState);
|
|
2344
2424
|
// Destructure common state
|
|
2345
2425
|
const { sortColumnParam, sortType, columnVisibilityMap, columnVisibilityCustomizationModalVisible, } = state;
|
|
2426
|
+
/*------------------------------------------------------------------------*/
|
|
2427
|
+
/* Render */
|
|
2428
|
+
/*------------------------------------------------------------------------*/
|
|
2429
|
+
/*----------------------------------------*/
|
|
2430
|
+
/* Modal */
|
|
2431
|
+
/*----------------------------------------*/
|
|
2432
|
+
// Modal that may be defined
|
|
2433
|
+
let modal;
|
|
2346
2434
|
/* ------- Col Vis Customization Modal ------ */
|
|
2347
2435
|
if (columnVisibilityCustomizationModalVisible) {
|
|
2348
2436
|
// Create modal
|
|
2349
|
-
(React__default["default"].createElement(Modal, { type: ModalType$1.Okay, title: "Choose columns to show:", onClose: () => {
|
|
2437
|
+
modal = (React__default["default"].createElement(Modal, { type: ModalType$1.Okay, title: "Choose columns to show:", onClose: () => {
|
|
2438
|
+
const noColumnsSelected = (Object.values(columnVisibilityMap)
|
|
2439
|
+
.every((isSelected) => {
|
|
2440
|
+
return !isSelected;
|
|
2441
|
+
}));
|
|
2442
|
+
if (noColumnsSelected) {
|
|
2443
|
+
return alert$1('Choose at least one column', 'To continue, you have to choose at least one column to show');
|
|
2444
|
+
}
|
|
2350
2445
|
dispatch({
|
|
2351
2446
|
type: ActionType$1.ToggleColVisCusModalVisibility,
|
|
2352
2447
|
});
|
|
2353
|
-
}
|
|
2354
|
-
|
|
2448
|
+
}, okayVariant: Variant$1.Light },
|
|
2449
|
+
columns.map((column) => {
|
|
2450
|
+
return (React__default["default"].createElement(CheckboxButton, { key: column.param, id: `IntelliTable-${id}-toggle-visibility-${column.param}`, className: "mb-2", text: column.title, onChanged: (checked) => {
|
|
2451
|
+
dispatch({
|
|
2452
|
+
type: ActionType$1.UpdateColumnVisibility,
|
|
2453
|
+
param: column.param,
|
|
2454
|
+
visible: checked,
|
|
2455
|
+
});
|
|
2456
|
+
}, checked: columnVisibilityMap[column.param], ariaLabel: `show "${column.title}" column in the ${title} table`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Secondary }));
|
|
2457
|
+
}),
|
|
2458
|
+
React__default["default"].createElement("div", { className: "mt-3" }, "Or you can:"),
|
|
2459
|
+
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: () => {
|
|
2355
2460
|
dispatch({
|
|
2356
|
-
type: ActionType$1.
|
|
2357
|
-
param: column.param,
|
|
2461
|
+
type: ActionType$1.ShowAllColumns,
|
|
2358
2462
|
});
|
|
2359
|
-
}
|
|
2360
|
-
|
|
2463
|
+
} }, "Select All"),
|
|
2464
|
+
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: () => {
|
|
2465
|
+
dispatch({
|
|
2466
|
+
type: ActionType$1.HideAllColumns,
|
|
2467
|
+
});
|
|
2468
|
+
} }, "Deselect All")));
|
|
2361
2469
|
}
|
|
2362
2470
|
/*----------------------------------------*/
|
|
2363
2471
|
/* Main UI */
|
|
@@ -2371,6 +2479,7 @@ const IntelliTable = (props) => {
|
|
|
2371
2479
|
// Custom info based on current sort type
|
|
2372
2480
|
let sortButtonAriaLabel;
|
|
2373
2481
|
let sortIcon = freeSolidSvgIcons.faSort;
|
|
2482
|
+
let sortingByThisColumn = false;
|
|
2374
2483
|
if (!sortColumnParam) {
|
|
2375
2484
|
// Not being sorted yet
|
|
2376
2485
|
sortButtonAriaLabel = `sort ascending by ${column.title}`;
|
|
@@ -2378,6 +2487,7 @@ const IntelliTable = (props) => {
|
|
|
2378
2487
|
}
|
|
2379
2488
|
else if (column.param === sortColumnParam) {
|
|
2380
2489
|
// Already sorted by this column
|
|
2490
|
+
sortingByThisColumn = true;
|
|
2381
2491
|
if (sortType === SortType.Ascending) {
|
|
2382
2492
|
// Sorted ascending
|
|
2383
2493
|
sortButtonAriaLabel = `sort descending by ${column.title}`;
|
|
@@ -2395,11 +2505,14 @@ const IntelliTable = (props) => {
|
|
|
2395
2505
|
sortIcon = freeSolidSvgIcons.faSort;
|
|
2396
2506
|
}
|
|
2397
2507
|
// Create the cell UI
|
|
2398
|
-
return (React__default["default"].createElement("th", { key: column.param, scope: "col", id: `IntelliTable-${id}-header-${column.param}
|
|
2399
|
-
|
|
2400
|
-
|
|
2508
|
+
return (React__default["default"].createElement("th", { key: column.param, scope: "col", id: `IntelliTable-${id}-header-${column.param}`, className: "text-start", style: {
|
|
2509
|
+
borderRight: '0.05rem solid #555',
|
|
2510
|
+
borderLeft: '0.05rem solid #555',
|
|
2511
|
+
} },
|
|
2512
|
+
React__default["default"].createElement("div", { className: "d-flex align-items-center justify-content-start flex-row h-100" },
|
|
2513
|
+
React__default["default"].createElement("span", { className: "text-nowrap" }, column.title),
|
|
2401
2514
|
React__default["default"].createElement("div", null,
|
|
2402
|
-
React__default["default"].createElement("button", { type: "button", className:
|
|
2515
|
+
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
2516
|
dispatch({
|
|
2404
2517
|
type: ActionType$1.ToggleSortColumn,
|
|
2405
2518
|
param: column.param,
|
|
@@ -2417,8 +2530,28 @@ const IntelliTable = (props) => {
|
|
|
2417
2530
|
const descending = (sortType === SortType.Descending);
|
|
2418
2531
|
if (sortColumnParam) {
|
|
2419
2532
|
sortedData.sort((a, b) => {
|
|
2533
|
+
var _a, _b;
|
|
2420
2534
|
const aVal = a[sortColumnParam];
|
|
2421
2535
|
const bVal = b[sortColumnParam];
|
|
2536
|
+
// Tiebreaker sort by timestamp, most recent first (used if tied)
|
|
2537
|
+
const tiebreaker = (((_a = b.timestamp) !== null && _a !== void 0 ? _a : 0)
|
|
2538
|
+
- ((_b = a.timestamp) !== null && _b !== void 0 ? _b : 0));
|
|
2539
|
+
// Auto-sort undefined and null to end of list
|
|
2540
|
+
if ((aVal === undefined || aVal === null)
|
|
2541
|
+
|| (bVal === undefined || bVal === null)) {
|
|
2542
|
+
// At least one was undefined
|
|
2543
|
+
if ((aVal === undefined || aVal === null)
|
|
2544
|
+
&& (bVal === undefined || bVal === null)) {
|
|
2545
|
+
// Both undefined
|
|
2546
|
+
return tiebreaker;
|
|
2547
|
+
}
|
|
2548
|
+
if (aVal === undefined || aVal === null) {
|
|
2549
|
+
// First was undefined
|
|
2550
|
+
return 1;
|
|
2551
|
+
}
|
|
2552
|
+
// Second was undefined
|
|
2553
|
+
return -1;
|
|
2554
|
+
}
|
|
2422
2555
|
// Sort differently based on the data type
|
|
2423
2556
|
// > Boolean
|
|
2424
2557
|
if (paramType === ParamType$1.Boolean) {
|
|
@@ -2428,7 +2561,7 @@ const IntelliTable = (props) => {
|
|
|
2428
2561
|
if (!aVal && bVal) {
|
|
2429
2562
|
return (descending ? 1 : -1);
|
|
2430
2563
|
}
|
|
2431
|
-
return
|
|
2564
|
+
return tiebreaker;
|
|
2432
2565
|
}
|
|
2433
2566
|
// > Number
|
|
2434
2567
|
if (paramType === ParamType$1.Int
|
|
@@ -2440,12 +2573,12 @@ const IntelliTable = (props) => {
|
|
|
2440
2573
|
// > String
|
|
2441
2574
|
if (paramType === ParamType$1.String) {
|
|
2442
2575
|
if (aVal < bVal) {
|
|
2443
|
-
return (descending ?
|
|
2576
|
+
return (descending ? 1 : -1);
|
|
2444
2577
|
}
|
|
2445
2578
|
if (aVal > bVal) {
|
|
2446
|
-
return (descending ? 1 :
|
|
2579
|
+
return (descending ? -1 : 1);
|
|
2447
2580
|
}
|
|
2448
|
-
return
|
|
2581
|
+
return tiebreaker;
|
|
2449
2582
|
}
|
|
2450
2583
|
// > JSON
|
|
2451
2584
|
if (paramType === ParamType$1.JSON) {
|
|
@@ -2460,7 +2593,7 @@ const IntelliTable = (props) => {
|
|
|
2460
2593
|
: (aSize - bSize));
|
|
2461
2594
|
}
|
|
2462
2595
|
// No sort
|
|
2463
|
-
return
|
|
2596
|
+
return tiebreaker;
|
|
2464
2597
|
});
|
|
2465
2598
|
}
|
|
2466
2599
|
// Table body
|
|
@@ -2485,9 +2618,12 @@ const IntelliTable = (props) => {
|
|
|
2485
2618
|
const noValue = (value === undefined
|
|
2486
2619
|
|| value === null);
|
|
2487
2620
|
visibleValue = (noValue
|
|
2488
|
-
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.
|
|
2489
|
-
: (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faXmarkCircle })));
|
|
2621
|
+
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faMinus }))
|
|
2622
|
+
: (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: fullValue ? freeSolidSvgIcons.faCheckCircle : freeSolidSvgIcons.faXmarkCircle })));
|
|
2490
2623
|
title = (fullValue ? 'True' : 'False');
|
|
2624
|
+
if (noValue) {
|
|
2625
|
+
title = 'Empty Cell';
|
|
2626
|
+
}
|
|
2491
2627
|
}
|
|
2492
2628
|
else if (column.type === ParamType$1.Int) {
|
|
2493
2629
|
fullValue = Number.parseInt(value, 10);
|
|
@@ -2496,6 +2632,9 @@ const IntelliTable = (props) => {
|
|
|
2496
2632
|
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faMinus }))
|
|
2497
2633
|
: fullValue);
|
|
2498
2634
|
title = String(fullValue);
|
|
2635
|
+
if (noValue) {
|
|
2636
|
+
title = 'Empty Cell';
|
|
2637
|
+
}
|
|
2499
2638
|
}
|
|
2500
2639
|
else if (column.type === ParamType$1.Float) {
|
|
2501
2640
|
fullValue = Number.parseFloat(value);
|
|
@@ -2504,14 +2643,22 @@ const IntelliTable = (props) => {
|
|
|
2504
2643
|
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faMinus }))
|
|
2505
2644
|
: roundToNumDecimals(fullValue, 2));
|
|
2506
2645
|
title = String(fullValue);
|
|
2646
|
+
if (noValue) {
|
|
2647
|
+
title = 'Empty Cell';
|
|
2648
|
+
}
|
|
2507
2649
|
}
|
|
2508
2650
|
else if (column.type === ParamType$1.String) {
|
|
2509
2651
|
fullValue = String(value).trim();
|
|
2510
|
-
const noValue = (value
|
|
2652
|
+
const noValue = (value === undefined
|
|
2653
|
+
|| value === null
|
|
2654
|
+
|| String(fullValue).trim().length === 0);
|
|
2511
2655
|
visibleValue = (noValue
|
|
2512
2656
|
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faMinus }))
|
|
2513
2657
|
: fullValue);
|
|
2514
2658
|
title = `"${value}"`;
|
|
2659
|
+
if (noValue) {
|
|
2660
|
+
title = 'Empty Cell';
|
|
2661
|
+
}
|
|
2515
2662
|
}
|
|
2516
2663
|
else if (column.type === ParamType$1.JSON) {
|
|
2517
2664
|
fullValue = JSON.stringify(value);
|
|
@@ -2521,9 +2668,13 @@ const IntelliTable = (props) => {
|
|
|
2521
2668
|
visibleValue = (noValue
|
|
2522
2669
|
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faMinus }))
|
|
2523
2670
|
: fullValue);
|
|
2671
|
+
title = "JSON Object";
|
|
2524
2672
|
}
|
|
2525
2673
|
// Create UI
|
|
2526
|
-
return (React__default["default"].createElement("td", { key: `${datum.id}-${column.param}`, title: title
|
|
2674
|
+
return (React__default["default"].createElement("td", { key: `${datum.id}-${column.param}`, title: title, style: {
|
|
2675
|
+
borderRight: '0.05rem solid #555',
|
|
2676
|
+
borderLeft: '0.05rem solid #555',
|
|
2677
|
+
} }, visibleValue));
|
|
2527
2678
|
}));
|
|
2528
2679
|
// Add cells to a row
|
|
2529
2680
|
return (React__default["default"].createElement("tr", { key: datum.id }, cells));
|
|
@@ -2540,14 +2691,17 @@ const IntelliTable = (props) => {
|
|
|
2540
2691
|
})
|
|
2541
2692
|
.length);
|
|
2542
2693
|
// Build CSV
|
|
2543
|
-
const csv = genCSV(data, columns)
|
|
2694
|
+
const csv = genCSV(data, columns.filter((column) => {
|
|
2695
|
+
return columnVisibilityMap[column.param];
|
|
2696
|
+
}));
|
|
2544
2697
|
// Build main UI
|
|
2545
2698
|
return (React__default["default"].createElement("div", { className: `IntelliTable-container-${id}` },
|
|
2546
|
-
|
|
2699
|
+
modal,
|
|
2700
|
+
React__default["default"].createElement("div", { className: "d-flex align-items-center justify-content-start" },
|
|
2547
2701
|
React__default["default"].createElement("h3", { className: "m-0" }, title),
|
|
2548
2702
|
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: () => {
|
|
2703
|
+
React__default["default"].createElement(CSVDownloadButton, { "aria-label": `download data as csv for ${title}`, id: `IntelliTable-${id}-download-as-csv`, filename: filename, csv: csv }),
|
|
2704
|
+
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
2705
|
dispatch({
|
|
2552
2706
|
type: ActionType$1.ToggleColVisCusModalVisibility,
|
|
2553
2707
|
});
|
|
@@ -2559,7 +2713,7 @@ const IntelliTable = (props) => {
|
|
|
2559
2713
|
numHiddenCols,
|
|
2560
2714
|
' ',
|
|
2561
2715
|
"hidden)"))))),
|
|
2562
|
-
React__default["default"].createElement("div", { className: `IntelliTable-table-${id}`, style: {
|
|
2716
|
+
React__default["default"].createElement("div", { className: `IntelliTable-table-${id} mt-2`, style: {
|
|
2563
2717
|
overflowX: 'auto',
|
|
2564
2718
|
} }, table)));
|
|
2565
2719
|
};
|
|
@@ -2611,6 +2765,11 @@ const style = `
|
|
|
2611
2765
|
border: 0.05rem solid black;
|
|
2612
2766
|
border-radius: 0.5rem;
|
|
2613
2767
|
overflow: hidden;
|
|
2768
|
+
padding: 0.7rem;
|
|
2769
|
+
|
|
2770
|
+
/* Solid background */
|
|
2771
|
+
background-color: white;
|
|
2772
|
+
color: black;
|
|
2614
2773
|
|
|
2615
2774
|
/* Place contents in flex column */
|
|
2616
2775
|
flex-direction: column;
|
|
@@ -2637,8 +2796,218 @@ const style = `
|
|
|
2637
2796
|
/* Vertical scroll */
|
|
2638
2797
|
overflow-y: auto;
|
|
2639
2798
|
}
|
|
2799
|
+
|
|
2800
|
+
.LogReviewer-header-close-button {
|
|
2801
|
+
border: 0 !important;
|
|
2802
|
+
background-color: transparent !important;
|
|
2803
|
+
padding-top: 0 !important;
|
|
2804
|
+
padding-bottom: 0 !important;
|
|
2805
|
+
padding-right: 1em !important;
|
|
2806
|
+
margin: 0 !important;
|
|
2807
|
+
color: #444 !important;
|
|
2808
|
+
|
|
2809
|
+
right: 0 !important;
|
|
2810
|
+
position: absolute !important;
|
|
2811
|
+
}
|
|
2812
|
+
.LogReviewer-header-close-button:hover {
|
|
2813
|
+
border: 0 !important;
|
|
2814
|
+
background-color: transparent !important;
|
|
2815
|
+
padding-top: 0 !important;
|
|
2816
|
+
padding-bottom: 0 !important;
|
|
2817
|
+
margin: 0 !important;
|
|
2818
|
+
color: #000 !important;
|
|
2819
|
+
}
|
|
2640
2820
|
`;
|
|
2641
2821
|
/*------------------------------------------------------------------------*/
|
|
2822
|
+
/* Constants */
|
|
2823
|
+
/*------------------------------------------------------------------------*/
|
|
2824
|
+
const columns = [
|
|
2825
|
+
{
|
|
2826
|
+
title: 'First Name',
|
|
2827
|
+
param: 'userFirstName',
|
|
2828
|
+
type: ParamType$1.String,
|
|
2829
|
+
},
|
|
2830
|
+
{
|
|
2831
|
+
title: 'Last Name',
|
|
2832
|
+
param: 'userLastName',
|
|
2833
|
+
type: ParamType$1.String,
|
|
2834
|
+
},
|
|
2835
|
+
{
|
|
2836
|
+
title: 'Email',
|
|
2837
|
+
param: 'userEmail',
|
|
2838
|
+
type: ParamType$1.String,
|
|
2839
|
+
},
|
|
2840
|
+
{
|
|
2841
|
+
title: 'Canvas Id',
|
|
2842
|
+
param: 'userId',
|
|
2843
|
+
type: ParamType$1.Int,
|
|
2844
|
+
},
|
|
2845
|
+
{
|
|
2846
|
+
title: 'Student?',
|
|
2847
|
+
param: 'isLearner',
|
|
2848
|
+
type: ParamType$1.Boolean,
|
|
2849
|
+
},
|
|
2850
|
+
{
|
|
2851
|
+
title: 'Teaching Staff?',
|
|
2852
|
+
param: 'isTTM',
|
|
2853
|
+
type: ParamType$1.Boolean,
|
|
2854
|
+
startsHidden: true,
|
|
2855
|
+
},
|
|
2856
|
+
{
|
|
2857
|
+
title: 'Admin?',
|
|
2858
|
+
param: 'isAdmin',
|
|
2859
|
+
type: ParamType$1.Boolean,
|
|
2860
|
+
startsHidden: true,
|
|
2861
|
+
},
|
|
2862
|
+
{
|
|
2863
|
+
title: 'Course Canvas Id',
|
|
2864
|
+
param: 'courseId',
|
|
2865
|
+
type: ParamType$1.Int,
|
|
2866
|
+
startsHidden: true,
|
|
2867
|
+
},
|
|
2868
|
+
{
|
|
2869
|
+
title: 'Course Name',
|
|
2870
|
+
param: 'courseName',
|
|
2871
|
+
type: ParamType$1.String,
|
|
2872
|
+
},
|
|
2873
|
+
{
|
|
2874
|
+
title: 'Browser Name',
|
|
2875
|
+
param: 'browser.name',
|
|
2876
|
+
type: ParamType$1.String,
|
|
2877
|
+
startsHidden: true,
|
|
2878
|
+
},
|
|
2879
|
+
{
|
|
2880
|
+
title: 'Browser Version',
|
|
2881
|
+
param: 'browser.version',
|
|
2882
|
+
type: ParamType$1.String,
|
|
2883
|
+
startsHidden: true,
|
|
2884
|
+
},
|
|
2885
|
+
{
|
|
2886
|
+
title: 'OS',
|
|
2887
|
+
param: 'device.os',
|
|
2888
|
+
type: ParamType$1.String,
|
|
2889
|
+
startsHidden: true,
|
|
2890
|
+
},
|
|
2891
|
+
{
|
|
2892
|
+
title: 'Mobile?',
|
|
2893
|
+
param: 'device.isMobile',
|
|
2894
|
+
type: ParamType$1.Boolean,
|
|
2895
|
+
startsHidden: true,
|
|
2896
|
+
},
|
|
2897
|
+
{
|
|
2898
|
+
title: 'Year',
|
|
2899
|
+
param: 'year',
|
|
2900
|
+
type: ParamType$1.Int,
|
|
2901
|
+
},
|
|
2902
|
+
{
|
|
2903
|
+
title: 'Month',
|
|
2904
|
+
param: 'month',
|
|
2905
|
+
type: ParamType$1.Int,
|
|
2906
|
+
},
|
|
2907
|
+
{
|
|
2908
|
+
title: 'Day',
|
|
2909
|
+
param: 'day',
|
|
2910
|
+
type: ParamType$1.Int,
|
|
2911
|
+
},
|
|
2912
|
+
{
|
|
2913
|
+
title: 'Hour',
|
|
2914
|
+
param: 'hour',
|
|
2915
|
+
type: ParamType$1.Int,
|
|
2916
|
+
},
|
|
2917
|
+
{
|
|
2918
|
+
title: 'Minute',
|
|
2919
|
+
param: 'minute',
|
|
2920
|
+
type: ParamType$1.Int,
|
|
2921
|
+
startsHidden: true,
|
|
2922
|
+
},
|
|
2923
|
+
{
|
|
2924
|
+
title: 'Timestamp',
|
|
2925
|
+
param: 'timestamp',
|
|
2926
|
+
type: ParamType$1.Int,
|
|
2927
|
+
startsHidden: true,
|
|
2928
|
+
},
|
|
2929
|
+
{
|
|
2930
|
+
title: 'Context',
|
|
2931
|
+
param: 'context',
|
|
2932
|
+
type: ParamType$1.String,
|
|
2933
|
+
},
|
|
2934
|
+
{
|
|
2935
|
+
title: 'Subcontext',
|
|
2936
|
+
param: 'subcontext',
|
|
2937
|
+
type: ParamType$1.String,
|
|
2938
|
+
},
|
|
2939
|
+
{
|
|
2940
|
+
title: 'Tags',
|
|
2941
|
+
param: 'tags',
|
|
2942
|
+
type: ParamType$1.JSON,
|
|
2943
|
+
startsHidden: true,
|
|
2944
|
+
},
|
|
2945
|
+
{
|
|
2946
|
+
title: 'Log Level',
|
|
2947
|
+
param: 'level',
|
|
2948
|
+
type: ParamType$1.String,
|
|
2949
|
+
startsHidden: true,
|
|
2950
|
+
},
|
|
2951
|
+
{
|
|
2952
|
+
title: 'Metadata',
|
|
2953
|
+
param: 'metadata',
|
|
2954
|
+
type: ParamType$1.JSON,
|
|
2955
|
+
startsHidden: true,
|
|
2956
|
+
},
|
|
2957
|
+
{
|
|
2958
|
+
title: 'Source',
|
|
2959
|
+
param: 'source',
|
|
2960
|
+
type: ParamType$1.String,
|
|
2961
|
+
},
|
|
2962
|
+
{
|
|
2963
|
+
title: 'Server Route Path',
|
|
2964
|
+
param: 'routePath',
|
|
2965
|
+
type: ParamType$1.String,
|
|
2966
|
+
startsHidden: true,
|
|
2967
|
+
},
|
|
2968
|
+
{
|
|
2969
|
+
title: 'Server Route Template',
|
|
2970
|
+
param: 'routeTemplate',
|
|
2971
|
+
type: ParamType$1.String,
|
|
2972
|
+
startsHidden: true,
|
|
2973
|
+
},
|
|
2974
|
+
{
|
|
2975
|
+
title: 'Type',
|
|
2976
|
+
param: 'type',
|
|
2977
|
+
type: ParamType$1.String,
|
|
2978
|
+
},
|
|
2979
|
+
{
|
|
2980
|
+
title: 'Error Message',
|
|
2981
|
+
param: 'errorMessage',
|
|
2982
|
+
type: ParamType$1.String,
|
|
2983
|
+
startsHidden: true,
|
|
2984
|
+
},
|
|
2985
|
+
{
|
|
2986
|
+
title: 'Error Code',
|
|
2987
|
+
param: 'errorCode',
|
|
2988
|
+
type: ParamType$1.String,
|
|
2989
|
+
startsHidden: true,
|
|
2990
|
+
},
|
|
2991
|
+
{
|
|
2992
|
+
title: 'Error Stack',
|
|
2993
|
+
param: 'errorStack',
|
|
2994
|
+
type: ParamType$1.String,
|
|
2995
|
+
startsHidden: true,
|
|
2996
|
+
},
|
|
2997
|
+
{
|
|
2998
|
+
title: 'Action Target',
|
|
2999
|
+
param: 'target',
|
|
3000
|
+
type: ParamType$1.String,
|
|
3001
|
+
startsHidden: true,
|
|
3002
|
+
},
|
|
3003
|
+
{
|
|
3004
|
+
title: 'Action Type',
|
|
3005
|
+
param: 'action',
|
|
3006
|
+
type: ParamType$1.String,
|
|
3007
|
+
startsHidden: true,
|
|
3008
|
+
},
|
|
3009
|
+
];
|
|
3010
|
+
/*------------------------------------------------------------------------*/
|
|
2642
3011
|
/* Static Functions */
|
|
2643
3012
|
/*------------------------------------------------------------------------*/
|
|
2644
3013
|
/**
|
|
@@ -2656,7 +3025,7 @@ const genHumanReadableName = (machineReadableName) => {
|
|
|
2656
3025
|
// Uppercase! Add a space before
|
|
2657
3026
|
humanReadableName += ' ';
|
|
2658
3027
|
}
|
|
2659
|
-
humanReadableName +=
|
|
3028
|
+
humanReadableName += char;
|
|
2660
3029
|
});
|
|
2661
3030
|
// Trim and return
|
|
2662
3031
|
return humanReadableName.trim();
|
|
@@ -2718,7 +3087,19 @@ const reducer = (state, action) => {
|
|
|
2718
3087
|
return Object.assign(Object.assign({}, state), { contextFilterState: action.contextFilterState });
|
|
2719
3088
|
}
|
|
2720
3089
|
case ActionType.UpdateTagFilterState: {
|
|
2721
|
-
|
|
3090
|
+
const { tagFilterState } = action;
|
|
3091
|
+
// Select all if every tag is deselected
|
|
3092
|
+
const numTagsSelected = (Object.values(tagFilterState)
|
|
3093
|
+
.filter((isSelected) => {
|
|
3094
|
+
return isSelected;
|
|
3095
|
+
})
|
|
3096
|
+
.length);
|
|
3097
|
+
if (numTagsSelected === 0) {
|
|
3098
|
+
Object.keys(tagFilterState).forEach((tag) => {
|
|
3099
|
+
tagFilterState[tag] = true;
|
|
3100
|
+
});
|
|
3101
|
+
}
|
|
3102
|
+
return Object.assign(Object.assign({}, state), { tagFilterState });
|
|
2722
3103
|
}
|
|
2723
3104
|
case ActionType.UpdateActionErrorFilterState: {
|
|
2724
3105
|
return Object.assign(Object.assign({}, state), { actionErrorFilterState: action.actionErrorFilterState });
|
|
@@ -2738,10 +3119,35 @@ const LogReviewer = (props) => {
|
|
|
2738
3119
|
/*------------------------------------------------------------------------*/
|
|
2739
3120
|
/* Setup */
|
|
2740
3121
|
/*------------------------------------------------------------------------*/
|
|
2741
|
-
var _a, _b, _c, _d, _e;
|
|
3122
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2742
3123
|
/* -------------- Props ------------- */
|
|
2743
3124
|
// Destructure props
|
|
2744
3125
|
const { LogMetadata, onClose, } = props;
|
|
3126
|
+
// Create complete map of contexts
|
|
3127
|
+
const contextMap = {};
|
|
3128
|
+
[
|
|
3129
|
+
((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {}),
|
|
3130
|
+
LogBuiltInMetadata.Context,
|
|
3131
|
+
].forEach((partialContextMap) => {
|
|
3132
|
+
Object.keys(partialContextMap).forEach((context) => {
|
|
3133
|
+
// Add context
|
|
3134
|
+
contextMap[context] = context;
|
|
3135
|
+
// If context has children, add an "uncategorized" subcontext
|
|
3136
|
+
if (typeof contextMap[context] !== 'string') {
|
|
3137
|
+
contextMap[context][LogBuiltInMetadata.Context.Uncategorized] = (LogBuiltInMetadata.Context.Uncategorized);
|
|
3138
|
+
}
|
|
3139
|
+
});
|
|
3140
|
+
});
|
|
3141
|
+
// Create complete map of targets
|
|
3142
|
+
const targetMap = {};
|
|
3143
|
+
[
|
|
3144
|
+
((_b = LogMetadata.Target) !== null && _b !== void 0 ? _b : {}),
|
|
3145
|
+
LogBuiltInMetadata.Target,
|
|
3146
|
+
].forEach((partialTargetMap) => {
|
|
3147
|
+
Object.keys(partialTargetMap).forEach((target) => {
|
|
3148
|
+
targetMap[target] = target;
|
|
3149
|
+
});
|
|
3150
|
+
});
|
|
2745
3151
|
/* -------------- State ------------- */
|
|
2746
3152
|
// Create initial date filter state
|
|
2747
3153
|
const today = getTimeInfoInET();
|
|
@@ -2761,26 +3167,30 @@ const LogReviewer = (props) => {
|
|
|
2761
3167
|
};
|
|
2762
3168
|
// Create initial context filter state
|
|
2763
3169
|
const initContextFilterState = {};
|
|
2764
|
-
Object.keys((
|
|
3170
|
+
Object.keys((_c = LogMetadata.Context) !== null && _c !== void 0 ? _c : {}).forEach((context) => {
|
|
2765
3171
|
var _a, _b;
|
|
2766
3172
|
const contextValue = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
|
|
2767
3173
|
if (typeof contextValue === 'string') {
|
|
2768
|
-
// Case: no subcontexts
|
|
3174
|
+
// Case: no subcontexts, init as checked
|
|
2769
3175
|
initContextFilterState[contextValue] = true;
|
|
2770
3176
|
}
|
|
2771
3177
|
else {
|
|
2772
3178
|
// Case: subcontexts exist
|
|
2773
|
-
initContextFilterState[
|
|
3179
|
+
initContextFilterState[context] = {};
|
|
2774
3180
|
Object.values(((_b = LogMetadata.Context) !== null && _b !== void 0 ? _b : {})[context]).forEach((subcontext) => {
|
|
2775
|
-
|
|
2776
|
-
|
|
3181
|
+
// Skip self ("_")
|
|
3182
|
+
if (subcontext === '_') {
|
|
3183
|
+
return;
|
|
3184
|
+
}
|
|
3185
|
+
// Initialize as checked
|
|
3186
|
+
initContextFilterState[context][subcontext] = true;
|
|
2777
3187
|
});
|
|
2778
3188
|
}
|
|
2779
3189
|
});
|
|
2780
3190
|
// Create initial tag filter state
|
|
2781
3191
|
const initTagFilterState = {};
|
|
2782
|
-
Object.
|
|
2783
|
-
initTagFilterState[
|
|
3192
|
+
Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {}).forEach((tag) => {
|
|
3193
|
+
initTagFilterState[tag] = false;
|
|
2784
3194
|
});
|
|
2785
3195
|
// Create advanced filter state
|
|
2786
3196
|
const initAdvancedFilterState = {
|
|
@@ -2806,12 +3216,16 @@ const LogReviewer = (props) => {
|
|
|
2806
3216
|
target: {},
|
|
2807
3217
|
action: {},
|
|
2808
3218
|
};
|
|
2809
|
-
Object.values((
|
|
3219
|
+
Object.values((_e = LogMetadata.Target) !== null && _e !== void 0 ? _e : {}).forEach((target) => {
|
|
2810
3220
|
initActionErrorFilterState.target[target] = true;
|
|
2811
3221
|
});
|
|
2812
3222
|
Object.values(LogAction$1).forEach((action) => {
|
|
2813
3223
|
initActionErrorFilterState.action[action] = true;
|
|
2814
3224
|
});
|
|
3225
|
+
// Add built-in targets
|
|
3226
|
+
Object.values(LogBuiltInMetadata.Target).forEach((target) => {
|
|
3227
|
+
initActionErrorFilterState.target[target] = true;
|
|
3228
|
+
});
|
|
2815
3229
|
// Initial state
|
|
2816
3230
|
const initialState = {
|
|
2817
3231
|
loading: true,
|
|
@@ -2845,15 +3259,18 @@ const LogReviewer = (props) => {
|
|
|
2845
3259
|
let month = newDateFilterState.startDate.month;
|
|
2846
3260
|
while (
|
|
2847
3261
|
// Earlier year
|
|
2848
|
-
(year
|
|
3262
|
+
(year < newDateFilterState.endDate.year)
|
|
2849
3263
|
// Current year but included month
|
|
2850
3264
|
|| (year === newDateFilterState.endDate.year
|
|
2851
3265
|
&& month <= newDateFilterState.endDate.month)) {
|
|
2852
|
-
// Add to list
|
|
2853
|
-
|
|
2854
|
-
year
|
|
2855
|
-
|
|
2856
|
-
|
|
3266
|
+
// Add to list if not already loaded
|
|
3267
|
+
if (!logMap[year]
|
|
3268
|
+
|| !logMap[year][month]) {
|
|
3269
|
+
toLoad.push({
|
|
3270
|
+
year,
|
|
3271
|
+
month,
|
|
3272
|
+
});
|
|
3273
|
+
}
|
|
2857
3274
|
// Increment
|
|
2858
3275
|
month += 1;
|
|
2859
3276
|
if (month > 12) {
|
|
@@ -2941,10 +3358,10 @@ const LogReviewer = (props) => {
|
|
|
2941
3358
|
/* Filters */
|
|
2942
3359
|
/*----------------------------------------*/
|
|
2943
3360
|
// Filter toggle
|
|
2944
|
-
const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles
|
|
3361
|
+
const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles" },
|
|
2945
3362
|
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: () => {
|
|
3363
|
+
React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
|
|
3364
|
+
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
3365
|
dispatch({
|
|
2949
3366
|
type: ActionType.ToggleFilterDrawer,
|
|
2950
3367
|
filterDrawer: FilterDrawer.Date,
|
|
@@ -2952,7 +3369,7 @@ const LogReviewer = (props) => {
|
|
|
2952
3369
|
} },
|
|
2953
3370
|
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCalendar, className: "me-2" }),
|
|
2954
3371
|
"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: () => {
|
|
3372
|
+
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
3373
|
dispatch({
|
|
2957
3374
|
type: ActionType.ToggleFilterDrawer,
|
|
2958
3375
|
filterDrawer: FilterDrawer.Context,
|
|
@@ -2960,15 +3377,15 @@ const LogReviewer = (props) => {
|
|
|
2960
3377
|
} },
|
|
2961
3378
|
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "me-2" }),
|
|
2962
3379
|
"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: () => {
|
|
3380
|
+
(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
3381
|
dispatch({
|
|
2965
3382
|
type: ActionType.ToggleFilterDrawer,
|
|
2966
3383
|
filterDrawer: FilterDrawer.Tag,
|
|
2967
3384
|
});
|
|
2968
3385
|
} },
|
|
2969
3386
|
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: () => {
|
|
3387
|
+
"Tag")),
|
|
3388
|
+
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
3389
|
dispatch({
|
|
2973
3390
|
type: ActionType.ToggleFilterDrawer,
|
|
2974
3391
|
filterDrawer: FilterDrawer.Action,
|
|
@@ -2976,41 +3393,64 @@ const LogReviewer = (props) => {
|
|
|
2976
3393
|
} },
|
|
2977
3394
|
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faHammer, className: "me-2" }),
|
|
2978
3395
|
"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: () => {
|
|
3396
|
+
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
3397
|
dispatch({
|
|
2981
3398
|
type: ActionType.ToggleFilterDrawer,
|
|
2982
3399
|
filterDrawer: FilterDrawer.Advanced,
|
|
2983
3400
|
});
|
|
2984
3401
|
} },
|
|
2985
3402
|
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faList, className: "me-2" }),
|
|
2986
|
-
"Advanced")
|
|
3403
|
+
"Advanced"),
|
|
3404
|
+
React__default["default"].createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
|
|
3405
|
+
dispatch({
|
|
3406
|
+
type: ActionType.ResetFilters,
|
|
3407
|
+
initActionErrorFilterState,
|
|
3408
|
+
initAdvancedFilterState,
|
|
3409
|
+
initContextFilterState,
|
|
3410
|
+
initDateFilterState,
|
|
3411
|
+
initTagFilterState,
|
|
3412
|
+
});
|
|
3413
|
+
} },
|
|
3414
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes }),
|
|
3415
|
+
' ',
|
|
3416
|
+
"Reset"))));
|
|
2987
3417
|
// Filter drawer
|
|
2988
3418
|
let filterDrawer;
|
|
2989
3419
|
if (expandedFilterDrawer) {
|
|
2990
3420
|
if (expandedFilterDrawer === FilterDrawer.Date) {
|
|
2991
3421
|
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
|
-
});
|
|
3422
|
+
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) => {
|
|
3423
|
+
dateFilterState.startDate = { month, day, year };
|
|
3424
|
+
handleDateRangeUpdated(dateFilterState);
|
|
2997
3425
|
} }),
|
|
2998
3426
|
' ',
|
|
2999
3427
|
"to",
|
|
3000
3428
|
' ',
|
|
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
|
-
|
|
3429
|
+
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) => {
|
|
3430
|
+
if (year < dateFilterState.startDate.year
|
|
3431
|
+
|| (year === dateFilterState.startDate.year
|
|
3432
|
+
&& month < dateFilterState.startDate.month)
|
|
3433
|
+
|| (year === dateFilterState.startDate.year
|
|
3434
|
+
&& month === dateFilterState.startDate.month
|
|
3435
|
+
&& day < dateFilterState.startDate.day)) {
|
|
3436
|
+
return alert$1('Invalid Start Date', 'The start date cannot be before the end date.');
|
|
3437
|
+
}
|
|
3438
|
+
dateFilterState.endDate = { month, day, year };
|
|
3439
|
+
handleDateRangeUpdated(dateFilterState);
|
|
3006
3440
|
} })));
|
|
3007
3441
|
}
|
|
3008
3442
|
else if (expandedFilterDrawer === FilterDrawer.Context) {
|
|
3009
3443
|
// Create item picker items
|
|
3010
|
-
const
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3444
|
+
const builtInPickableItem = {
|
|
3445
|
+
id: '_',
|
|
3446
|
+
name: 'Auto-logged',
|
|
3447
|
+
isGroup: true,
|
|
3448
|
+
children: [],
|
|
3449
|
+
};
|
|
3450
|
+
const pickableItems = [];
|
|
3451
|
+
Object.keys(contextMap)
|
|
3452
|
+
.forEach((context) => {
|
|
3453
|
+
const value = (contextMap)[context];
|
|
3014
3454
|
if (typeof value === 'string') {
|
|
3015
3455
|
// No subcategories
|
|
3016
3456
|
const item = {
|
|
@@ -3019,38 +3459,64 @@ const LogReviewer = (props) => {
|
|
|
3019
3459
|
isGroup: false,
|
|
3020
3460
|
checked: !!contextFilterState[context],
|
|
3021
3461
|
};
|
|
3022
|
-
|
|
3462
|
+
// Add built-in items to its own folder
|
|
3463
|
+
const isBuiltIn = context in LogBuiltInMetadata.Context;
|
|
3464
|
+
if (isBuiltIn) {
|
|
3465
|
+
// Add to built-in pickable item
|
|
3466
|
+
builtInPickableItem.children.push(item);
|
|
3467
|
+
}
|
|
3468
|
+
else {
|
|
3469
|
+
// Add to pickable items list
|
|
3470
|
+
pickableItems.push(item);
|
|
3471
|
+
}
|
|
3023
3472
|
}
|
|
3024
3473
|
// Has subcategories
|
|
3025
3474
|
const children = (Object.keys(value)
|
|
3475
|
+
// Remove parent name
|
|
3026
3476
|
.filter((subcontext) => {
|
|
3027
3477
|
return subcontext !== '_';
|
|
3028
3478
|
})
|
|
3479
|
+
// Create child pickable items
|
|
3029
3480
|
.map((subcontext) => {
|
|
3030
3481
|
return {
|
|
3031
|
-
id:
|
|
3482
|
+
id: subcontext,
|
|
3032
3483
|
name: genHumanReadableName(subcontext),
|
|
3033
3484
|
isGroup: false,
|
|
3034
|
-
checked:
|
|
3485
|
+
checked: contextFilterState[context][subcontext],
|
|
3035
3486
|
};
|
|
3036
3487
|
}));
|
|
3037
3488
|
const item = {
|
|
3038
3489
|
id: context,
|
|
3039
|
-
name: context,
|
|
3490
|
+
name: genHumanReadableName(context),
|
|
3040
3491
|
isGroup: true,
|
|
3041
3492
|
children,
|
|
3042
3493
|
};
|
|
3043
|
-
|
|
3044
|
-
})
|
|
3494
|
+
pickableItems.push(item);
|
|
3495
|
+
});
|
|
3496
|
+
// Add built-in contexts to end ofl ist
|
|
3497
|
+
pickableItems.push(builtInPickableItem);
|
|
3045
3498
|
// Create filter UI
|
|
3046
3499
|
filterDrawer = (React__default["default"].createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
|
|
3047
3500
|
// Update our state
|
|
3048
3501
|
updatedItems.forEach((pickableItem) => {
|
|
3049
3502
|
if (pickableItem.isGroup) {
|
|
3050
3503
|
// Has subcontexts
|
|
3051
|
-
pickableItem.
|
|
3052
|
-
|
|
3053
|
-
|
|
3504
|
+
if (pickableItem.id === '_') {
|
|
3505
|
+
// Built-in
|
|
3506
|
+
// Treat as if these were top-level contexts
|
|
3507
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
3508
|
+
contextFilterState[subcontextItem.id] = ('checked' in subcontextItem
|
|
3509
|
+
&& subcontextItem.checked);
|
|
3510
|
+
});
|
|
3511
|
+
}
|
|
3512
|
+
else {
|
|
3513
|
+
// Not built-in
|
|
3514
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
3515
|
+
if (!subcontextItem.isGroup) {
|
|
3516
|
+
contextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
|
|
3517
|
+
}
|
|
3518
|
+
});
|
|
3519
|
+
}
|
|
3054
3520
|
}
|
|
3055
3521
|
else {
|
|
3056
3522
|
// No subcontexts
|
|
@@ -3065,13 +3531,19 @@ const LogReviewer = (props) => {
|
|
|
3065
3531
|
}
|
|
3066
3532
|
else if (expandedFilterDrawer === FilterDrawer.Tag) {
|
|
3067
3533
|
// Create filter UI
|
|
3068
|
-
filterDrawer = (React__default["default"].createElement(TabBox, { title: "Tags" },
|
|
3069
|
-
.
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
} }))
|
|
3074
|
-
|
|
3534
|
+
filterDrawer = (React__default["default"].createElement(TabBox, { title: "Tags" },
|
|
3535
|
+
React__default["default"].createElement("div", null, "If any are selected, logs must contain at least one but not necessarily all of the selected tags."),
|
|
3536
|
+
Object.keys((_f = LogMetadata.Tag) !== null && _f !== void 0 ? _f : {})
|
|
3537
|
+
.map((tag, i) => {
|
|
3538
|
+
const description = genHumanReadableName(tag);
|
|
3539
|
+
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) => {
|
|
3540
|
+
tagFilterState[tag] = checked;
|
|
3541
|
+
dispatch({
|
|
3542
|
+
type: ActionType.UpdateTagFilterState,
|
|
3543
|
+
tagFilterState,
|
|
3544
|
+
});
|
|
3545
|
+
} }));
|
|
3546
|
+
})));
|
|
3075
3547
|
}
|
|
3076
3548
|
else if (expandedFilterDrawer === FilterDrawer.Action) {
|
|
3077
3549
|
// Create filter UI
|
|
@@ -3100,34 +3572,37 @@ const LogReviewer = (props) => {
|
|
|
3100
3572
|
}, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
|
|
3101
3573
|
(actionErrorFilterState.type === undefined
|
|
3102
3574
|
|| 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
|
-
|
|
3575
|
+
React__default["default"].createElement(ButtonInputGroup, { label: "Action", className: "mb-2" },
|
|
3576
|
+
React__default["default"].createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys(LogAction$1)
|
|
3577
|
+
.map((action) => {
|
|
3578
|
+
const description = genHumanReadableName(action);
|
|
3579
|
+
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) => {
|
|
3580
|
+
actionErrorFilterState.action[action] = checked;
|
|
3581
|
+
console.log(actionErrorFilterState, action, checked);
|
|
3582
|
+
dispatch({
|
|
3583
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
3584
|
+
actionErrorFilterState,
|
|
3585
|
+
});
|
|
3586
|
+
} }));
|
|
3587
|
+
}))),
|
|
3588
|
+
React__default["default"].createElement(ButtonInputGroup, { label: "Target" },
|
|
3589
|
+
React__default["default"].createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys(targetMap)
|
|
3590
|
+
.map((target) => {
|
|
3591
|
+
const description = genHumanReadableName(target);
|
|
3592
|
+
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) => {
|
|
3593
|
+
actionErrorFilterState.target[target] = checked;
|
|
3594
|
+
console.log(actionErrorFilterState, target, checked);
|
|
3595
|
+
dispatch({
|
|
3596
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
3597
|
+
actionErrorFilterState,
|
|
3598
|
+
});
|
|
3599
|
+
} }));
|
|
3600
|
+
}))))),
|
|
3126
3601
|
(actionErrorFilterState.type === undefined
|
|
3127
3602
|
|| actionErrorFilterState.type === LogType$1.Error) && (React__default["default"].createElement(TabBox, { title: "Error Log Details" },
|
|
3128
3603
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3129
3604
|
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) => {
|
|
3605
|
+
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
3606
|
actionErrorFilterState.errorMessage = e.target.value;
|
|
3132
3607
|
dispatch({
|
|
3133
3608
|
type: ActionType.UpdateActionErrorFilterState,
|
|
@@ -3136,7 +3611,7 @@ const LogReviewer = (props) => {
|
|
|
3136
3611
|
} })),
|
|
3137
3612
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3138
3613
|
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.
|
|
3614
|
+
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
3615
|
actionErrorFilterState.errorCode = ((e.target.value)
|
|
3141
3616
|
.trim()
|
|
3142
3617
|
.toUpperCase());
|
|
@@ -3152,7 +3627,7 @@ const LogReviewer = (props) => {
|
|
|
3152
3627
|
React__default["default"].createElement(TabBox, { title: "User Info" },
|
|
3153
3628
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3154
3629
|
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) => {
|
|
3630
|
+
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
3631
|
advancedFilterState.userFirstName = e.target.value;
|
|
3157
3632
|
dispatch({
|
|
3158
3633
|
type: ActionType.UpdateAdvancedFilterState,
|
|
@@ -3161,7 +3636,7 @@ const LogReviewer = (props) => {
|
|
|
3161
3636
|
} })),
|
|
3162
3637
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3163
3638
|
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) => {
|
|
3639
|
+
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
3640
|
advancedFilterState.userLastName = e.target.value;
|
|
3166
3641
|
dispatch({
|
|
3167
3642
|
type: ActionType.UpdateAdvancedFilterState,
|
|
@@ -3170,7 +3645,7 @@ const LogReviewer = (props) => {
|
|
|
3170
3645
|
} })),
|
|
3171
3646
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3172
3647
|
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) => {
|
|
3648
|
+
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
3649
|
advancedFilterState.userEmail = ((e.target.value)
|
|
3175
3650
|
.trim());
|
|
3176
3651
|
dispatch({
|
|
@@ -3180,7 +3655,7 @@ const LogReviewer = (props) => {
|
|
|
3180
3655
|
} })),
|
|
3181
3656
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3182
3657
|
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) => {
|
|
3658
|
+
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
3659
|
const { value } = e.target;
|
|
3185
3660
|
// Only update if value contains only numbers
|
|
3186
3661
|
if (/^\d+$/.test(value)) {
|
|
@@ -3217,7 +3692,7 @@ const LogReviewer = (props) => {
|
|
|
3217
3692
|
React__default["default"].createElement(TabBox, { title: "Course Info" },
|
|
3218
3693
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3219
3694
|
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) => {
|
|
3695
|
+
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
3696
|
advancedFilterState.courseName = e.target.value;
|
|
3222
3697
|
dispatch({
|
|
3223
3698
|
type: ActionType.UpdateAdvancedFilterState,
|
|
@@ -3226,7 +3701,7 @@ const LogReviewer = (props) => {
|
|
|
3226
3701
|
} })),
|
|
3227
3702
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3228
3703
|
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) => {
|
|
3704
|
+
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
3705
|
const { value } = e.target;
|
|
3231
3706
|
// Only update if value contains only numbers
|
|
3232
3707
|
if (/^\d+$/.test(value)) {
|
|
@@ -3284,37 +3759,38 @@ const LogReviewer = (props) => {
|
|
|
3284
3759
|
advancedFilterState,
|
|
3285
3760
|
});
|
|
3286
3761
|
}, 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
|
-
|
|
3762
|
+
advancedFilterState.source !== LogSource$1.Client && (React__default["default"].createElement("div", { className: "mt-2" },
|
|
3763
|
+
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3764
|
+
React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Path"),
|
|
3765
|
+
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) => {
|
|
3766
|
+
advancedFilterState.courseName = ((e.target.value)
|
|
3767
|
+
.trim());
|
|
3768
|
+
dispatch({
|
|
3769
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
3770
|
+
advancedFilterState,
|
|
3771
|
+
});
|
|
3772
|
+
} })),
|
|
3773
|
+
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3774
|
+
React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Template"),
|
|
3775
|
+
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) => {
|
|
3776
|
+
advancedFilterState.courseName = ((e.target.value)
|
|
3777
|
+
.trim());
|
|
3778
|
+
dispatch({
|
|
3779
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
3780
|
+
advancedFilterState,
|
|
3781
|
+
});
|
|
3782
|
+
} })))))));
|
|
3307
3783
|
}
|
|
3308
3784
|
}
|
|
3309
3785
|
// Filters UI
|
|
3310
3786
|
const filters = (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
3311
3787
|
filterToggles,
|
|
3312
|
-
filterDrawer && (React__default["default"].createElement(Drawer,
|
|
3788
|
+
filterDrawer && (React__default["default"].createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
|
|
3313
3789
|
// Actually filter the logs
|
|
3314
3790
|
// > Perform filters
|
|
3315
3791
|
const logs = [];
|
|
3316
3792
|
Object.keys(logMap).forEach((year) => {
|
|
3317
|
-
Object.keys(logMap).forEach((month) => {
|
|
3793
|
+
Object.keys(logMap[year]).forEach((month) => {
|
|
3318
3794
|
logMap[year][month].forEach((log) => {
|
|
3319
3795
|
/* ----------- Date Filter ---------- */
|
|
3320
3796
|
var _a;
|
|
@@ -3350,16 +3826,23 @@ const LogReviewer = (props) => {
|
|
|
3350
3826
|
// Whole context is deselected
|
|
3351
3827
|
contextFilterState[log.context] === false
|
|
3352
3828
|
// None of the subcontexts are selected
|
|
3353
|
-
|| (
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3829
|
+
|| (
|
|
3830
|
+
// Has subcontexts
|
|
3831
|
+
typeof contextFilterState[log.context] !== 'boolean'
|
|
3832
|
+
// None of the subcontexts are selected
|
|
3833
|
+
&& Object.values((_a = contextFilterState[log.context]) !== null && _a !== void 0 ? _a : {})
|
|
3834
|
+
.every((isSelected) => {
|
|
3835
|
+
return !isSelected;
|
|
3836
|
+
}))) {
|
|
3837
|
+
console.log(log.context, contextFilterState);
|
|
3357
3838
|
return;
|
|
3358
3839
|
}
|
|
3359
3840
|
// Subcontext doesn't match
|
|
3360
3841
|
if (
|
|
3361
|
-
// Log
|
|
3362
|
-
log.
|
|
3842
|
+
// Log context is not "uncategorized" (no point in further filters)
|
|
3843
|
+
log.context !== LogBuiltInMetadata.Context.Uncategorized
|
|
3844
|
+
// Log has a subcontext
|
|
3845
|
+
&& log.subcontext
|
|
3363
3846
|
// Context has subcontexts
|
|
3364
3847
|
&& (contextFilterState[log.context]
|
|
3365
3848
|
&& contextFilterState[log.context] !== false
|
|
@@ -3371,12 +3854,16 @@ const LogReviewer = (props) => {
|
|
|
3371
3854
|
/* -------------- Tags -------------- */
|
|
3372
3855
|
// No tags match
|
|
3373
3856
|
if (
|
|
3374
|
-
//
|
|
3375
|
-
|
|
3857
|
+
// At least one tag is required
|
|
3858
|
+
Object.values(tagFilterState)
|
|
3859
|
+
.filter((isSelected) => {
|
|
3860
|
+
return isSelected;
|
|
3861
|
+
})
|
|
3862
|
+
.length > 0
|
|
3376
3863
|
// No tags match
|
|
3377
|
-
&&
|
|
3864
|
+
&& log.tags.every((tag) => {
|
|
3378
3865
|
return !tagFilterState[tag];
|
|
3379
|
-
}))
|
|
3866
|
+
})) {
|
|
3380
3867
|
return;
|
|
3381
3868
|
}
|
|
3382
3869
|
/* ------- Actions and Errors ------- */
|
|
@@ -3510,7 +3997,7 @@ const LogReviewer = (props) => {
|
|
|
3510
3997
|
// Device info exists
|
|
3511
3998
|
&& log.device
|
|
3512
3999
|
// Mobile filter doesn't match
|
|
3513
|
-
&& (advancedFilterState.isMobile
|
|
4000
|
+
&& (advancedFilterState.isMobile !== log.device.isMobile)) {
|
|
3514
4001
|
return;
|
|
3515
4002
|
}
|
|
3516
4003
|
// Log source doesn't match
|
|
@@ -3552,199 +4039,20 @@ const LogReviewer = (props) => {
|
|
|
3552
4039
|
/*----------------------------------------*/
|
|
3553
4040
|
/* Data */
|
|
3554
4041
|
/*----------------------------------------*/
|
|
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
|
-
];
|
|
4042
|
+
// Nothing to show notice
|
|
4043
|
+
const noLogsNotice = (logs.length === 0
|
|
4044
|
+
? (React__default["default"].createElement("div", { className: "alert alert-warning text-center mt-2" },
|
|
4045
|
+
React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
|
|
4046
|
+
React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet.")))
|
|
4047
|
+
: undefined);
|
|
3742
4048
|
// Create intelliTable
|
|
3743
|
-
const dataTable = (React__default["default"].createElement(IntelliTable, { title: "Matching Logs", id: "logs", data: logs, columns: columns }));
|
|
4049
|
+
const dataTable = (React__default["default"].createElement(IntelliTable, { title: "Matching Logs:", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns }));
|
|
3744
4050
|
// Main body
|
|
3745
4051
|
body = (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
3746
4052
|
filters,
|
|
3747
|
-
|
|
4053
|
+
React__default["default"].createElement("div", { className: "mt-2" },
|
|
4054
|
+
dataTable,
|
|
4055
|
+
noLogsNotice)));
|
|
3748
4056
|
}
|
|
3749
4057
|
/* ---------- Wrap in Modal --------- */
|
|
3750
4058
|
return (React__default["default"].createElement("div", { className: "LogReviewer-outer-container" },
|
|
@@ -3752,14 +4060,10 @@ const LogReviewer = (props) => {
|
|
|
3752
4060
|
React__default["default"].createElement("div", { className: "LogReviewer-inner-container" },
|
|
3753
4061
|
React__default["default"].createElement("div", { className: "LogReviewer-header" },
|
|
3754
4062
|
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 }))),
|
|
4063
|
+
React__default["default"].createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
|
|
4064
|
+
React__default["default"].createElement("div", { style: { width: 0 } },
|
|
4065
|
+
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 },
|
|
4066
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes })))),
|
|
3763
4067
|
React__default["default"].createElement("div", { className: "LogReviewer-contents" }, body))));
|
|
3764
4068
|
};
|
|
3765
4069
|
|
|
@@ -3855,6 +4159,18 @@ const floorToNumDecimals = (num, numDecimals) => {
|
|
|
3855
4159
|
return (Math.floor(num * rounder) / rounder);
|
|
3856
4160
|
};
|
|
3857
4161
|
|
|
4162
|
+
/**
|
|
4163
|
+
* Force a number to stay within specific bounds
|
|
4164
|
+
* @author Gabe Abrams
|
|
4165
|
+
* @param num the number to move into the bounds
|
|
4166
|
+
* @param min the minimum number in the bound
|
|
4167
|
+
* @param max the maximum number in the bound
|
|
4168
|
+
* @returns bounded number
|
|
4169
|
+
*/
|
|
4170
|
+
const forceNumIntoBounds = (num, min, max) => {
|
|
4171
|
+
return Math.max(min, Math.min(max, num));
|
|
4172
|
+
};
|
|
4173
|
+
|
|
3858
4174
|
/**
|
|
3859
4175
|
* Pad a number's decimal with zeros on the right
|
|
3860
4176
|
* (e.g. 5.2 becomes 5.20 with 2 digit padding)
|
|
@@ -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
|