Skip to content

Commit

Permalink
add filtering on report msg (#2256)
Browse files Browse the repository at this point in the history
Signed-off-by: Abdelsalem <abdelsalem.hedhili@rte-france.com>
  • Loading branch information
AbdelHedhili authored Sep 27, 2024
1 parent e924f22 commit 73b24e6
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 94 deletions.
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"reconnecting-websocket": "^4.4.0",
"redux": "^5.0.1",
"typeface-roboto": "^1.1.13",
"use-debounce": "^10.0.3",
"uuid": "^9.0.1",
"yup": "^1.4.0"
},
Expand Down
35 changes: 32 additions & 3 deletions src/components/report-viewer/log-table.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import { styled } from '@mui/system';
import { MuiVirtualizedTable } from '@gridsuite/commons-ui';
import { useTheme } from '@mui/material/styles';
import { FilterButton } from './filter-button';
import { TextFilterButton } from './text-filter-button.tsx';
import { useDispatch, useSelector } from 'react-redux';
import { useDebounce } from 'use-debounce';
import { setReportFilters } from '../../redux/actions';

// WARNING this file has been copied from commons-ui, and updated here. Putting it back to commons-ui has to be discussed.

Expand All @@ -35,13 +39,37 @@ const styles = {

const VirtualizedTable = styled(MuiVirtualizedTable)(styles);

const LogTable = ({ logs, onRowClick, selectedSeverity, setSelectedSeverity }) => {
const LogTable = ({ logs, onRowClick }) => {
const intl = useIntl();

const theme = useTheme();

const dispatch = useDispatch();

const [selectedRowIndex, setSelectedRowIndex] = useState(-1);

const severityFilter = useSelector((state) => state.reportSeverityFilter);

const selectedReportId = useSelector((state) => state.reportSelectedReportId);

//messageFilter is only used for display, debouncedMessageFilter is used for triggering fetch
const [messageFilter, setMessageFilter] = useState('');

const [debouncedMessageFilter] = useDebounce(messageFilter, 500);

useEffect(() => {
dispatch(setReportFilters(undefined, debouncedMessageFilter, undefined));
}, [debouncedMessageFilter, dispatch]);

//We reset the displayed message filter when we switch reports
useEffect(() => {
setMessageFilter('');
}, [selectedReportId]);

const setSeverityFilter = (selectedSeverity) => {
dispatch(setReportFilters(undefined, undefined, selectedSeverity));
};

const severityCellRender = (cellData) => {
return (
<TableCell
Expand All @@ -67,12 +95,13 @@ const LogTable = ({ logs, onRowClick, selectedSeverity, setSelectedSeverity }) =
maxWidth: SEVERITY_COLUMN_FIXED_WIDTH,
minWidth: SEVERITY_COLUMN_FIXED_WIDTH,
cellRenderer: severityCellRender,
extra: <FilterButton selectedItems={selectedSeverity} setSelectedItems={setSelectedSeverity} />,
extra: <FilterButton selectedItems={severityFilter} setSelectedItems={setSeverityFilter} />,
},
{
label: intl.formatMessage({ id: 'report_viewer/message' }).toUpperCase(),
id: 'message',
dataKey: 'message',
extra: <TextFilterButton filterText={messageFilter} setFilterText={setMessageFilter} />,
},
];

Expand All @@ -89,8 +118,8 @@ const LogTable = ({ logs, onRowClick, selectedSeverity, setSelectedSeverity }) =
return {
severity: log.severity.name,
message: log.message,
backgroundColor: log.severity.colorName,
parentId: log.parentId,
backgroundColor: log.severity.colorName,
};
});
};
Expand Down
83 changes: 40 additions & 43 deletions src/components/report-viewer/report-viewer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import ReportTree from './report-tree';
import ReportItem from './report-item';
import { getDefaultSeverityFilter } from '../../utils/report-severity.utils';
import { useReportFetcher } from '../../hooks/use-report-fetcher';
import { mapReportLog } from '../../utils/report-log.mapper';
import { mapReportsTree } from '../../utils/report-tree.mapper';
import { useDispatch, useSelector } from 'react-redux';
import { setReportFilters } from '../../redux/actions';

// WARNING this file has been copied from commons-ui, and updated here. Putting it back to commons-ui has to be discussed.

Expand All @@ -25,13 +26,17 @@ const styles = {
};

export default function ReportViewer({ report, reportType }) {
const [selectedReportId, setSelectedReportId] = useState(null);
const dispatch = useDispatch();

const [expandedTreeReports, setExpandedTreeReports] = useState([]);
const [logs, setLogs] = useState(null);
const [highlightedReportId, setHighlightedReportId] = useState();
const [severityFilter, setSeverityFilter] = useState(getDefaultSeverityFilter());
const [reportVerticalPositionFromTop, setReportVerticalPositionFromTop] = useState(undefined);
const [isLogLoading, , fetchLogs] = useReportFetcher(reportType);
const [isLogLoading, , fetchReportLogs] = useReportFetcher(reportType);

const selectedReportId = useSelector((state) => state.reportSelectedReportId);
const severityFilter = useSelector((state) => state.reportSeverityFilter);
const messageFilter = useSelector((state) => state.reportMessageFilter);

const reportTreeData = useRef({});
const treeView = useRef(null);
Expand All @@ -56,59 +61,56 @@ export default function ReportViewer({ report, reportType }) {
}, []);

const refreshLogsOnSelectedReport = useCallback(
(reportId, severityFilter) => {
let severityList = [];
for (const [severity, selected] of Object.entries(severityFilter)) {
if (selected) {
severityList.push(severity);
(selectedReportId, severityFilter, messageFilter) => {
//we need to do this check because selectedReportId can be outdated when switching between the different place we have this component
if (reportTreeData.current[selectedReportId] != null) {
let severityList = [];
for (const [severity, selected] of Object.entries(severityFilter)) {
if (selected) {
severityList.push(severity);
}
}
}

if (severityList.length === 0) {
// no severity => there is no log to fetch, no need to request the back-end
setSelectedReportId(reportId);
setLogs([]);
setHighlightedReportId(null);
return;
}

fetchLogs(reportId, severityList, reportTreeData.current[reportId].type).then((logs) => {
if (logs !== undefined) {
setLogs(logs);
setSelectedReportId(reportId);
if (severityList.length === 0) {
setLogs([]);
setHighlightedReportId(null);
return;
}
});
fetchReportLogs(
selectedReportId,
severityList,
reportTreeData.current[selectedReportId].type,
messageFilter
).then((reportLogs) => {
setLogs(reportLogs);
});
}
},
[fetchLogs]
[fetchReportLogs]
);

useEffect(() => {
refreshLogsOnSelectedReport(selectedReportId, severityFilter, messageFilter);
}, [messageFilter, severityFilter, selectedReportId, refreshLogsOnSelectedReport]);

useEffect(() => {
const reportTree = mapReportsTree(report);
treeView.current = initializeTreeDataAndComponent(reportTree);
setSelectedReportId(report.id);
setExpandedTreeReports([report.id]);
setLogs(mapReportLog(report, reportTree.severities));
setSeverityFilter(getDefaultSeverityFilter(reportTree.severities));
}, [report, initializeTreeDataAndComponent, refreshLogsOnSelectedReport]);
dispatch(setReportFilters(report.id, '', getDefaultSeverityFilter(reportTree.severities)));
}, [report, initializeTreeDataAndComponent, dispatch]);

const handleReportVerticalPositionFromTop = useCallback((node) => {
setReportVerticalPositionFromTop(node?.getBoundingClientRect()?.top);
}, []);

const handleSelectNode = (_, reportId) => {
if (selectedReportId !== reportId) {
const updatedSeverityFilter = getDefaultSeverityFilter(reportTreeData.current[reportId].severities);
setSeverityFilter(updatedSeverityFilter);
refreshLogsOnSelectedReport(reportId, updatedSeverityFilter);
dispatch(
setReportFilters(reportId, '', getDefaultSeverityFilter(reportTreeData.current[reportId].severities))
);
}
};

const onSeverityChange = (newSeverityFilter) => {
refreshLogsOnSelectedReport(selectedReportId, newSeverityFilter);
setSeverityFilter(newSeverityFilter);
};

// The MUI TreeView/TreeItems use useMemo on our items, so it's important to avoid changing the context
const isHighlighted = useMemo(
() => ({
Expand Down Expand Up @@ -163,12 +165,7 @@ export default function ReportViewer({ report, reportType }) {
</ReportTreeViewContext.Provider>
<Grid item xs={12} sm={9} sx={{ height: '100%' }}>
<WaitingLoader loading={isLogLoading} message={'loadingReport'}>
<LogTable
logs={logs}
onRowClick={onLogRowClick}
selectedSeverity={severityFilter}
setSelectedSeverity={onSeverityChange}
/>
<LogTable logs={logs} onRowClick={onLogRowClick} severityFilter={severityFilter} />
</WaitingLoader>
</Grid>
</Grid>
Expand Down
90 changes: 90 additions & 0 deletions src/components/report-viewer/text-filter-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* Copyright (c) 2024, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import React, { ChangeEvent, FunctionComponent, useState } from 'react';
import { Box, IconButton, TextField } from '@mui/material';
import FilterAltIcon from '@mui/icons-material/FilterAlt';
import { useIntl } from 'react-intl';
import Menu from '@mui/material/Menu';

const styles = {
container: {
position: 'relative',
},
icon: {
width: '0.7em',
height: '0.7em',
},
notificationDot: {
height: '6px',
width: '6px',
backgroundColor: '#cc70a0',
borderRadius: '50%',
position: 'absolute',
top: '5px',
left: '23px',
},
};

/**
* FilterButton wraps a MultiSelectList with a button which has a visual indication to indicate when the user alters the default state of the list
* @param {String} filterText - Value of the filter
* @param {Function} setFilterText - Setter needed to update the filter
*/

interface TextFilterButtonProps {
filterText: string;
setFilterText: (text: string) => void;
}

export const TextFilterButton: FunctionComponent<TextFilterButtonProps> = ({ filterText, setFilterText }) => {
const intl = useIntl();

const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);

const handleClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
setAnchorEl(event.currentTarget);
};

const handleClose = () => {
setAnchorEl(null);
};

const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
setFilterText(event.target.value);
};

const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === 'Enter') {
event.preventDefault();
handleClose();
}
};

return (
<Box sx={styles.container}>
<IconButton onClick={handleClick}>
<FilterAltIcon sx={styles.icon} />
{filterText && <Box sx={styles.notificationDot} />}
</IconButton>
<Menu open={Boolean(anchorEl)} onClose={handleClose} anchorEl={anchorEl}>
<TextField
size={'small'}
fullWidth
value={filterText || ''}
onChange={handleChange}
onKeyDown={handleKeyDown}
placeholder={intl.formatMessage({
id: 'filter.filterOoo',
})}
inputProps={{
type: 'text',
}}
/>
</Menu>
</Box>
);
};
Loading

0 comments on commit 73b24e6

Please sign in to comment.