Skip to content

Commit

Permalink
STSMACOM-324 Refactor SearchAndSort away from deprecated lifecycle. (#…
Browse files Browse the repository at this point in the history
…1281)

* refactor cWRP

* resolve test issues

* track browser query changes to control component updates

* remove constructor logic

* update search in derived state

* cleanup code smells

* lint

* add tests for query/search form sync

* test single pane result view, implement local pane interactor in SAS tests

* ignore local environment files

* remove local environment files

* remove/ignore correct local dev files

* replace cli config

* clean up SASTestHarness

* add comment in SASTestHarness

* clean up SASTestHarness imports

* remove package-lock

* lint network test files

Co-authored-by: Zak Burke <[email protected]>
  • Loading branch information
JohnC-80 and zburke authored Sep 12, 2022
1 parent 9b5e0f4 commit e918a62
Show file tree
Hide file tree
Showing 11 changed files with 395 additions and 151 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ node_modules
.DS_Store
yarn-error.log
/artifacts
.vscode
.stripesclirc.json

181 changes: 97 additions & 84 deletions lib/SearchAndSort/SearchAndSort.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ const NO_PERMISSION_NODE = (
</div>
);

const queryParam = (name, props) => {
const {
parentResources: { query },
nsParams,
} = props;
const nsKey = getNsKey(name, nsParams);

return get(query, nsKey);
};

class SearchAndSort extends React.Component {
static propTypes = {
actionMenu: PropTypes.func, // parameter properties provided by caller
Expand Down Expand Up @@ -278,7 +288,7 @@ class SearchAndSort extends React.Component {
const {
viewRecordComponent,
stripes,
} = this.props;
} = props;

// Use local storage to remember whether the filter pane has been toggled off or not.
this.filterPaneVisibilityKey = `${props.namespace}/filterPaneVisibility`;
Expand All @@ -289,6 +299,9 @@ class SearchAndSort extends React.Component {
filterPaneIsVisible: storedFilterPaneState === null ? true : !!storedFilterPaneState,
locallyChangedSearchTerm: '',
locallyChangedQueryIndex: '',
// eslint didn't learn about gDSFP 7.26.x; see ESCONF-21.
// eslint-disable-next-line react/no-unused-state
previousQueryString: '',
};

this.handleFilterChange = handleFilterChange.bind(this);
Expand All @@ -300,7 +313,7 @@ class SearchAndSort extends React.Component {
};

this.SRStatus = null;
this.lastNonNullReaultCount = undefined;
this.lastNonNullResultCount = undefined;
this.initialQuery = queryString.parse(this.initialSearch);
this.initialFilters = this.initialQuery.filters;
this.initialSort = this.initialQuery.sort;
Expand All @@ -310,73 +323,92 @@ class SearchAndSort extends React.Component {
this.log = logger.log.bind(logger);
}

componentDidMount() {
this.updateLocallyChangedSearchTerm();
}

UNSAFE_componentWillReceiveProps(nextProps) {
// State is an interstitial holder of data of props when the ui needs to be synced accordingly,
// but only certain user interactions will sync the changes with the internal state, internal state
// from the query will only need to be updated when the browsers' query string changes.
// Ex: the `query` parameter in of window.location - the ui should sync to any query parameter change that happens,
// but the changes to local state/the search field will not reach the window.location until the search is submitted,
// so we derive state from props...
static getDerivedStateFromProps = (props, state) => {
const {
stripes: { logger },
finishedResourceName,
location: {
search: currentSearch,
search
},
syncQueryWithUrl,
} = this.props;
const oldState = makeConnectedSource(this.props, logger);
const newState = makeConnectedSource(nextProps, logger);
const {
location: {
search,
},
} = nextProps;
const { qindex, segment: nextSegment } = queryString.parse(search);
const { segment: currSegment } = queryString.parse(currentSearch);
const oldQuery = this.queryParam('query', this.props);
const newQuery = this.queryParam('query', nextProps);
} = props;

// If the nominated mutation has finished, select the newly created record
const oldStateForFinalSource = makeConnectedSource(this.props, logger, finishedResourceName);
const newStateForFinalSource = makeConnectedSource(nextProps, logger, finishedResourceName);
const nextState = {};

if (oldStateForFinalSource.records()) {
const finishedResourceNextSM = newStateForFinalSource.successfulMutations();
if (search !== state.previousQueryString) {
nextState.previousQueryString = search;
const parsedQuery = queryString.parse(search);

if (finishedResourceNextSM.length > oldStateForFinalSource.successfulMutations().length) {
const sm = newState.successfulMutations();
// if update the local state of the search term to match the window location's query string.
if (parsedQuery?.query) {
nextState.locallyChangedSearchTerm = parsedQuery.query;
} else {
nextState.locallyChangedSearchTerm = '';
}

if (sm[0]) this.onSelectRow(undefined, { id: sm[0].record.id });
// sync qindex...
// if no 'qindex' parameter is present in window.location, reset the local qIndex:
if (parsedQuery?.qindex) {
nextState.locallyChangedQueryIndex = parsedQuery.qindex;
} else {
nextState.locallyChangedQueryIndex = '';
}
}

// If a search that was pending is now complete, notify the screen-reader
if (oldState.pending() && !newState.pending()) {
this.log('event', 'new search-result');
const count = newState.totalCount();
if (Object.keys(nextState).length > 0) return nextState;
return null;
}

this.SRStatus.sendMessage(
<FormattedMessage id="stripes-smart-components.searchReturnedResults" values={{ count }} />
);
}
componentDidMount() {
this.updateLocallyChangedSearchTerm();
}

componentDidUpdate(prevProps) {
const {
showSingleResult,
finishedResourceName,
stripes: { logger },
} = this.props;

const previousState = makeConnectedSource(prevProps, logger);
const currentState = makeConnectedSource(this.props, logger);

// if the results list is winnowed down to a single record, display the record.
// If there's a single hit, open the detail pane
if (nextProps.showSingleResult &&
newState.totalCount() === 1 &&
(this.lastNonNullReaultCount > 1 || (oldState.records()[0] !== newState.records()[0]))) {
this.onSelectRow(null, newState.records()[0]);
// If there's a single hit, open the detail pane as side-effect.
const totalCountResult = currentState.totalCount();
if (showSingleResult &&
totalCountResult === 1 &&
(this.lastNonNullResultCount > 1 || (previousState.records()[0] !== currentState.records()[0]))) {
this.onSelectRow(null, currentState.records()[0]);
}

if (newState.totalCount() !== null) {
this.lastNonNullReaultCount = newState.totalCount();
}
this.lastNonNullResultCount = totalCountResult ?? this.lastNonNullResultCount;

// If a search that was pending is now complete, notify the screen-reader as side-effect.
if (previousState.pending() && !currentState.pending()) {
this.log('event', 'new search-result');

if (currSegment !== nextSegment) {
this.resetLocalQIndex(qindex);
this.SRStatus.sendMessage(
<FormattedMessage id="stripes-smart-components.searchReturnedResults" values={{ totalCountResult }} />
);
}

if (syncQueryWithUrl && oldQuery !== newQuery) {
this.setState({ locallyChangedSearchTerm: newQuery });
// If the nominated mutation has finished, select the newly created record as side-effect.
const oldStateForFinalSource = makeConnectedSource(prevProps, logger, finishedResourceName);
const newStateForFinalSource = makeConnectedSource(this.props, logger, finishedResourceName);

if (oldStateForFinalSource.records()) {
const finishedResourceNextSM = newStateForFinalSource.successfulMutations();

if (finishedResourceNextSM.length > oldStateForFinalSource.successfulMutations().length) {
if (currentState.successfulMutations()[0]) {
this.onSelectRow(undefined, { id: currentState.successfulMutations()[0].record.id });
}
}
}
}

Expand Down Expand Up @@ -404,21 +436,13 @@ class SearchAndSort extends React.Component {
return type === 'plugin';
}

// Reset local qindex if the qindex is not present in the url.
// This happens when the search segment changes.
resetLocalQIndex(qindex) {
if (this.state.locallyChangedQueryIndex && !qindex) {
this.setState({ locallyChangedQueryIndex: '' });
}
}

updateLocallyChangedSearchTerm() {
const {
location: {
search,
},
} = this.props;
const resQuery = this.queryParam('query');
const resQuery = queryParam('query', this.props);
const { query } = queryString.parse(search);

// update locallyChangedSearchTerm only
Expand Down Expand Up @@ -554,11 +578,11 @@ class SearchAndSort extends React.Component {
this.props.filterChangeCallback(newFilters);
};

resetLocallyChangedQuery = () => {
resetLocallyChangedQuery = (cb = noop) => {
this.setState({
locallyChangedSearchTerm: '',
locallyChangedQueryIndex: '',
});
}, cb);
}

onClearSearchAndFilters = () => {
Expand All @@ -567,15 +591,14 @@ class SearchAndSort extends React.Component {
} = this.props;
this.log('action', 'cleared search and filters');

this.resetLocallyChangedQuery();
this.transitionToParams({
filters: this.initialFilters || '',
sort: this.initialSort || '',
query: '',
qindex: '',
...extraParamsToReset,
});

this.resetLocallyChangedQuery();
this.props.filterChangeCallback({});
this.props.onResetAll();
};
Expand Down Expand Up @@ -682,7 +705,7 @@ class SearchAndSort extends React.Component {

if (sortableColumns && !includes(sortableColumns, newOrder)) return;

const oldOrder = this.queryParam('sort');
const oldOrder = queryParam('sort', this.props);
const orders = oldOrder ? oldOrder.split(',') : [];
const mainSort = orders[0];
const isSameColumn = mainSort && newOrder === mainSort.replace(/^-/, '');
Expand Down Expand Up @@ -742,7 +765,7 @@ class SearchAndSort extends React.Component {
aria-rowindex={rowIndex + 2}
key={`row-${rowIndex}`}
>
{ this.props.viewRecordPathById
{this.props.viewRecordPathById
?
<Link
to={this.props.viewRecordPathById(rowData.id)}
Expand Down Expand Up @@ -822,24 +845,14 @@ class SearchAndSort extends React.Component {
this.transitionToParams(queryParams);
}, 350);

queryParam(name, props = this.props) {
const {
parentResources: { query },
nsParams,
} = props;
const nsKey = getNsKey(name, nsParams);

return get(query, nsKey);
}

toggleFilterPane = () => {
this.setState(prevState => ({ filterPaneIsVisible: !prevState.filterPaneIsVisible }), () => {
window.localStorage.setItem(this.filterPaneVisibilityKey, this.state.filterPaneIsVisible);
});
};

toggleHelperApp = (curHelper) => {
const prevHelper = this.queryParam('helper');
const prevHelper = queryParam('helper', this.props);
const helper = prevHelper === curHelper ? null : curHelper;

this.transitionToParams({ helper });
Expand All @@ -855,9 +868,9 @@ class SearchAndSort extends React.Component {

isResetButtonDisabled() {
const initialFilters = this.initialFilters || '';
const currentFilters = this.queryParam('filters') || '';
const currentFilters = queryParam('filters', this.props) || '';
const noFiltersChoosen = currentFilters === initialFilters;
const currentQuery = this.queryParam('query');
const currentQuery = queryParam('query', this.props);
const searchTermFieldIsEmpty = isEmpty(this.state.locallyChangedSearchTerm || currentQuery);

return noFiltersChoosen && searchTermFieldIsEmpty;
Expand Down Expand Up @@ -889,7 +902,7 @@ class SearchAndSort extends React.Component {
} = this.props;

const moduleName = this.getModuleName();
const helper = this.queryParam('helper');
const helper = queryParam('helper', this.props);
const HelperAppComponent = this.getHelperComponent(helper);

if (!HelperAppComponent) {
Expand Down Expand Up @@ -964,7 +977,7 @@ class SearchAndSort extends React.Component {
return null;
}

const filters = filterState(this.queryParam('filters'));
const filters = filterState(queryParam('filters', this.props));
const filterCount = Object.keys(filters).length;

return (
Expand Down Expand Up @@ -1087,8 +1100,8 @@ class SearchAndSort extends React.Component {
locallyChangedQueryIndex,
} = this.state;

const filters = filterState(this.queryParam('filters'));
const query = this.queryParam('query') || '';
const filters = filterState(queryParam('filters', this.props));
const query = queryParam('query', this.props) || '';
const searchTerm = locallyChangedSearchTerm || query;
const queryIndex = locallyChangedQueryIndex || selectedIndex;

Expand Down Expand Up @@ -1178,8 +1191,8 @@ class SearchAndSort extends React.Component {
const moduleName = this.getModuleName();
const records = source.records();
const count = source.totalCount();
const query = this.queryParam('query') || '';
const sortOrder = this.queryParam('sort') || '';
const query = queryParam('query', this.props) || '';
const sortOrder = queryParam('sort', this.props) || '';
const message = <NoResultsMessage
source={source}
searchTerm={query}
Expand Down
Loading

0 comments on commit e918a62

Please sign in to comment.