Skip to content
This repository has been archived by the owner on Feb 6, 2023. It is now read-only.

Add simple filtering UI for experiments table #301

Merged
merged 18 commits into from
Oct 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions frontend/src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
26 changes: 26 additions & 0 deletions frontend/src/components/ExperimentsTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -24,12 +25,14 @@ const ExperimentsTable = (props) => {
project,
results,
resultsStatus,
resultFilter,
stats,
projectConfig,
globalConfig,
onResultsConfigSelectUpdate,
onResultUpdate,
onResultSelect,
onResultFilterUpdate,
onCommandSubmit,
onTableExpandedUpdate,
onTableColumnsVisibilityUpdate,
Expand Down Expand Up @@ -90,6 +93,7 @@ const ExperimentsTable = (props) => {
},
className: 'text-center',
sortable: false,
filterable: false,
minWidth: 40,
Aggregated: (row) => {
const groupedResultKeys = row.subRows.map((r) => {
Expand Down Expand Up @@ -129,13 +133,29 @@ const ExperimentsTable = (props) => {
return null;
},
minWidth: 250,
Filter: (
<ResultFilter
projectId={project.id}
filterKey="name"
filterText={resultFilter.name}
onResultFilterUpdate={onResultFilterUpdate}
/>
),
},
];
if (isGrouped) {
nameColumns.unshift({
Header: '',
id: 'group',
accessor: (p) => getGrandParentDirectoryName(p),
Filter: (
<ResultFilter
projectId={project.id}
filterKey="group"
filterText={resultFilter.group}
onResultFilterUpdate={onResultFilterUpdate}
/>
),
});
}
const groupedKey = isGrouped ? ['group'] : [];
Expand All @@ -153,6 +173,7 @@ const ExperimentsTable = (props) => {
style: defaultStyle,
show: !(knownLogKeysConfig[logKey] || {}).hidden,
aggregate: () => '',
filterable: false,
}));

const argsList = sortKeys(argKeys, knownArgKeysConfig).map((argKey) => ({
Expand All @@ -169,6 +190,7 @@ const ExperimentsTable = (props) => {
style: defaultStyle,
show: !(knownArgKeysConfig[argKey] || {}).hidden,
aggregate: () => '',
filterable: false,
}));

const columns = [
Expand Down Expand Up @@ -203,6 +225,7 @@ const ExperimentsTable = (props) => {
expanded={expanded}
onExpandedChange={(nextExpanded) => onTableExpandedUpdate(project.id, nextExpanded)}
pageSize={resultList.length}
filterable
defaultSortMethod={sortMethod}
defaultSorted={[
{
Expand Down Expand Up @@ -254,19 +277,22 @@ 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,
};

ExperimentsTable.defaultProps = {
resultsStatus: {},
resultFilter: {},
};

export default ExperimentsTable;
35 changes: 35 additions & 0 deletions frontend/src/components/experiments_table_cell/ResultFilter.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<input
type="text"
placeholder={`filter ${filterKey}`}
value={filterText}
onChange={onChange}
/>
</div>
);
};

ResultFilter.propTypes = {
projectId: PropTypes.number.isRequired,
filterKey: PropTypes.string.isRequired,
filterText: PropTypes.string,
onResultFilterUpdate: PropTypes.func.isRequired,
};

ResultFilter.defaultProps = {
filterText: '',
};

export default ResultFilter;
45 changes: 43 additions & 2 deletions frontend/src/containers/PlotContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
updateAxisScale,
toggleLogKeySelect,
updateResultSelect,
updateResultFilter,
updateResultsConfigSelect,
updateXAxisKey,
updateAxisScaleRangeType,
Expand All @@ -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() {
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand All @@ -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,
Expand All @@ -230,6 +270,7 @@ export default connect(
updateAxisScale,
toggleLogKeySelect,
updateResultSelect,
updateResultFilter,
updateResultsConfigSelect,
updateXAxisKey,
updateAxisScaleRangeType,
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/reducers/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/store/uiPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down