diff --git a/frontend/src/actions/index.js b/frontend/src/actions/index.js
index 5f6c1866..5303e15e 100644
--- a/frontend/src/actions/index.js
+++ b/frontend/src/actions/index.js
@@ -253,6 +253,17 @@ export const updateResultSelect = (projectId, resultId, selected) => ({
selected,
});
+// result filter
+
+export const RESULT_FILTER_UPDATE = 'RESULT_FILTER_UPDATE';
+
+export const updateResultFilter = (projectId, filterKey, filterText) => ({
+ type: RESULT_FILTER_UPDATE,
+ projectId,
+ filterKey,
+ filterText,
+});
+
// lines config
export const LINES_CONFIG_LINE_UPDATE = 'LINES_CONFIG_LINE_UPDATE';
diff --git a/frontend/src/components/ExperimentsTable.jsx b/frontend/src/components/ExperimentsTable.jsx
index c0b7103a..979a88ee 100644
--- a/frontend/src/components/ExperimentsTable.jsx
+++ b/frontend/src/components/ExperimentsTable.jsx
@@ -14,6 +14,7 @@ import {
import ResultName from './experiments_table_cell/ResultName';
import ToggleResult from './experiments_table_cell/ToggleResult';
import SubComponent from './experiments_table_cell/SubComponent';
+import ResultFilter from './experiments_table_cell/ResultFilter';
import VisibilityCheckbox from './VisibilityCheckbox';
import TableConfigurator from './TableConfigurator';
@@ -24,12 +25,14 @@ const ExperimentsTable = (props) => {
project,
results,
resultsStatus,
+ resultFilter,
stats,
projectConfig,
globalConfig,
onResultsConfigSelectUpdate,
onResultUpdate,
onResultSelect,
+ onResultFilterUpdate,
onCommandSubmit,
onTableExpandedUpdate,
onTableColumnsVisibilityUpdate,
@@ -90,6 +93,7 @@ const ExperimentsTable = (props) => {
},
className: 'text-center',
sortable: false,
+ filterable: false,
minWidth: 40,
Aggregated: (row) => {
const groupedResultKeys = row.subRows.map((r) => {
@@ -129,6 +133,14 @@ const ExperimentsTable = (props) => {
return null;
},
minWidth: 250,
+ Filter: (
+
+ ),
},
];
if (isGrouped) {
@@ -136,6 +148,14 @@ const ExperimentsTable = (props) => {
Header: '',
id: 'group',
accessor: (p) => getGrandParentDirectoryName(p),
+ Filter: (
+
+ ),
});
}
const groupedKey = isGrouped ? ['group'] : [];
@@ -153,6 +173,7 @@ const ExperimentsTable = (props) => {
style: defaultStyle,
show: !(knownLogKeysConfig[logKey] || {}).hidden,
aggregate: () => '',
+ filterable: false,
}));
const argsList = sortKeys(argKeys, knownArgKeysConfig).map((argKey) => ({
@@ -169,6 +190,7 @@ const ExperimentsTable = (props) => {
style: defaultStyle,
show: !(knownArgKeysConfig[argKey] || {}).hidden,
aggregate: () => '',
+ filterable: false,
}));
const columns = [
@@ -203,6 +225,7 @@ const ExperimentsTable = (props) => {
expanded={expanded}
onExpandedChange={(nextExpanded) => onTableExpandedUpdate(project.id, nextExpanded)}
pageSize={resultList.length}
+ filterable
defaultSortMethod={sortMethod}
defaultSorted={[
{
@@ -254,12 +277,14 @@ ExperimentsTable.propTypes = {
project: uiPropTypes.project.isRequired,
results: uiPropTypes.results.isRequired,
resultsStatus: uiPropTypes.resultsStatus,
+ resultFilter: uiPropTypes.resultFilter,
projectConfig: uiPropTypes.projectConfig.isRequired,
globalConfig: uiPropTypes.globalConfig.isRequired,
stats: uiPropTypes.stats.isRequired,
onResultsConfigSelectUpdate: PropTypes.func.isRequired,
onResultUpdate: PropTypes.func.isRequired,
onResultSelect: PropTypes.func.isRequired,
+ onResultFilterUpdate: PropTypes.func.isRequired,
onCommandSubmit: PropTypes.func.isRequired,
onTableExpandedUpdate: PropTypes.func.isRequired,
onTableColumnsVisibilityUpdate: PropTypes.func.isRequired,
@@ -267,6 +292,7 @@ ExperimentsTable.propTypes = {
ExperimentsTable.defaultProps = {
resultsStatus: {},
+ resultFilter: {},
};
export default ExperimentsTable;
diff --git a/frontend/src/components/experiments_table_cell/ResultFilter.jsx b/frontend/src/components/experiments_table_cell/ResultFilter.jsx
new file mode 100644
index 00000000..cc96c106
--- /dev/null
+++ b/frontend/src/components/experiments_table_cell/ResultFilter.jsx
@@ -0,0 +1,35 @@
+import React, { useCallback } from 'react';
+import PropTypes from 'prop-types';
+
+const ResultFilter = ({ projectId, filterKey, filterText, onResultFilterUpdate }) => {
+ const onChange = useCallback(
+ (e) => {
+ onResultFilterUpdate(projectId, filterKey, e.target.value);
+ },
+ [projectId, filterKey, onResultFilterUpdate]
+ );
+
+ return (
+
+
+
+ );
+};
+
+ResultFilter.propTypes = {
+ projectId: PropTypes.number.isRequired,
+ filterKey: PropTypes.string.isRequired,
+ filterText: PropTypes.string,
+ onResultFilterUpdate: PropTypes.func.isRequired,
+};
+
+ResultFilter.defaultProps = {
+ filterText: '',
+};
+
+export default ResultFilter;
diff --git a/frontend/src/containers/PlotContainer.jsx b/frontend/src/containers/PlotContainer.jsx
index 45dc5de6..cb206287 100644
--- a/frontend/src/containers/PlotContainer.jsx
+++ b/frontend/src/containers/PlotContainer.jsx
@@ -14,6 +14,7 @@ import {
updateAxisScale,
toggleLogKeySelect,
updateResultSelect,
+ updateResultFilter,
updateResultsConfigSelect,
updateXAxisKey,
updateAxisScaleRangeType,
@@ -32,7 +33,12 @@ import LogVisualizer from '../components/LogVisualizer';
import SideBar from '../components/SideBar';
import ResultTypeSelector from '../components/ResultTypeSelector';
import { defaultProjectStatus, defaultProjectConfig } from '../constants';
-import { startPolling, stopPolling } from '../utils';
+import {
+ startPolling,
+ stopPolling,
+ getGrandParentDirectoryName,
+ displayResultNameFull,
+} from '../utils';
class PlotContainer extends React.Component {
componentDidMount() {
@@ -145,12 +151,14 @@ class PlotContainer extends React.Component {
project={project}
results={results}
resultsStatus={projectStatus.resultsStatus}
+ resultFilter={projectStatus.resultFilter}
stats={stats}
projectConfig={projectConfig}
globalConfig={globalConfig}
onResultsConfigSelectUpdate={this.props.updateResultsConfigSelect}
onResultUpdate={this.props.updateResult}
onResultSelect={this.props.updateResultSelect}
+ onResultFilterUpdate={this.props.updateResultFilter}
onCommandSubmit={this.props.createCommand}
onTableExpandedUpdate={this.props.updateTableExpanded}
onTableColumnsVisibilityUpdate={this.handleExperimentsTableColumnsVisibilityUpdate}
@@ -187,6 +195,7 @@ PlotContainer.propTypes = {
updateAxisScale: PropTypes.func.isRequired,
toggleLogKeySelect: PropTypes.func.isRequired,
updateResultSelect: PropTypes.func.isRequired,
+ updateResultFilter: PropTypes.func.isRequired,
updateResultsConfigSelect: PropTypes.func.isRequired,
updateXAxisKey: PropTypes.func.isRequired,
updateAxisScaleRangeType: PropTypes.func.isRequired,
@@ -197,6 +206,33 @@ PlotContainer.propTypes = {
updateTargetResultType: PropTypes.func.isRequired,
};
+const getTargetTextForFilter = (project, result, filterKey) => {
+ switch (filterKey) {
+ case 'group':
+ return getGrandParentDirectoryName(result);
+ case 'name':
+ return displayResultNameFull(project, result);
+ default:
+ return result[filterKey];
+ }
+};
+
+const filterResults = (project, results, resultFilter) => {
+ const filteredResults = Object.keys(results).reduce((pre, resultId) => {
+ const result = results[resultId];
+ const isMatched = Object.keys(resultFilter).every((filterKey) => {
+ const filterText = resultFilter[filterKey];
+ const targetText = getTargetTextForFilter(project, result, filterKey);
+ return !targetText || targetText.includes(filterText);
+ });
+ if (isMatched) {
+ return { ...pre, [resultId]: result };
+ }
+ return pre;
+ }, {});
+ return filteredResults;
+};
+
const mapStateToProps = (state, ownProps) => {
const projectId = Number(ownProps.params.projectId);
const { entities, status, config } = state;
@@ -205,11 +241,15 @@ const mapStateToProps = (state, ownProps) => {
const projectStatus = status.projectsStatus[projectId] || defaultProjectStatus;
const projectConfig = config.projectsConfig[projectId] || defaultProjectConfig;
const globalConfig = config.global;
+ const { resultFilter = {} } = projectStatus;
const { stats } = status;
+
+ const filteredResults = filterResults(project, results, resultFilter);
+
return {
projectId,
project,
- results,
+ results: filteredResults,
projectStatus,
projectConfig,
globalConfig,
@@ -230,6 +270,7 @@ export default connect(
updateAxisScale,
toggleLogKeySelect,
updateResultSelect,
+ updateResultFilter,
updateResultsConfigSelect,
updateXAxisKey,
updateAxisScaleRangeType,
diff --git a/frontend/src/reducers/index.jsx b/frontend/src/reducers/index.jsx
index c898fd70..684e9d45 100644
--- a/frontend/src/reducers/index.jsx
+++ b/frontend/src/reducers/index.jsx
@@ -225,9 +225,24 @@ const resultsStatusReducer = (state = {}, action) => {
return state;
};
+const resultFilterReducer = (state = {}, action) => {
+ switch (action.type) {
+ case ActionTypes.RESULT_FILTER_UPDATE: {
+ const { filterKey, filterText } = action;
+ return {
+ ...state,
+ [filterKey]: filterText,
+ };
+ }
+ default:
+ return state;
+ }
+};
+
const projectStatusReducer = combineReducers({
chartDownloadStatus: chartDownloadStatusReducer,
resultsStatus: resultsStatusReducer,
+ resultFilter: resultFilterReducer,
});
const projectsStatusReducer = (state = {}, action) => {
diff --git a/frontend/src/store/uiPropTypes.js b/frontend/src/store/uiPropTypes.js
index fd30aaf6..417f85ef 100644
--- a/frontend/src/store/uiPropTypes.js
+++ b/frontend/src/store/uiPropTypes.js
@@ -173,6 +173,8 @@ export const resultStatus = PropTypes.shape({
export const resultsStatus = PropTypes.objectOf(resultStatus);
+export const resultFilter = PropTypes.objectOf(PropTypes.string);
+
export const projectStatus = PropTypes.shape({
chartDownloadStatus: PropTypes.oneOf(Object.values(CHART_DOWNLOAD_STATUS)),
resultsStatus,