Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed table sort mode and table search string resetting upon selecting a new table. #154

Merged
merged 15 commits into from
Dec 17, 2021
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
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@

Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) convention.

## [Unreleased]
## Unreleased
### Added
- Switch filter cards restrictions handling from array to set for performance boost PR #150
### Fixed
- Fix redundant double fetch bug PR #150
- Fixed issue with parsing NaNs and infinities (#147) PR #150
- Fixed broken table sort (#151) PR #154
- Fixed table search string reseting when user selects a new table PR #154
- Fixed flexbox grow issues with safari by adding prefix. PR #142

## [0.1.0] - 2021-03-31
Expand Down
18,887 changes: 18,871 additions & 16 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@types/react-helmet": "^6.1.0",
"@types/react-router": "^5.1.10",
"js-cookie": "^2.2.1",
"json5": "^2.1.3",
"python-shell": "^2.0.3",
"react": "^17.0.1",
"react-dom": "^17.0.1",
Expand Down
8 changes: 4 additions & 4 deletions src/Components/MainTableView/Filter/Filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import './Filter.css'

interface FilterProps {
tableAttributesInfo?: TableAttributesInfo;
setRestrictions: (restrictions: Array<Restriction>) => void;
setRestrictions: (restrictions: Set<Restriction>) => void;
}

interface FilterState {
Expand Down Expand Up @@ -100,7 +100,7 @@ export default class Filter extends React.Component<FilterProps, FilterState> {

const restrictionChangeTimeout = setTimeout(() => {
// Check if any of the restrictions are valid, if so then send them to TableView fetchTuples
let validRestrictions: Array<Restriction> = []
let validRestrictions: Set<Restriction> = new Set();
for (let restriction of this.state.restrictionsBuffer) {
if (restriction.tableAttribute !== undefined && restriction.restrictionType !== undefined && restriction.value !== undefined && restriction.isEnable === true) {

Expand All @@ -113,12 +113,12 @@ export default class Filter extends React.Component<FilterProps, FilterState> {
}

// Valid restriction, thus add it to the list
validRestrictions.push(restriction);
validRestrictions.add(restriction);
}
}

// Call fetch content if there is at lesat one valid restriction
if (validRestrictions.length >= 0) {
if (validRestrictions.size >= 0) {
this.props.setRestrictions(validRestrictions);
}
}, 1000);
Expand Down
2 changes: 1 addition & 1 deletion src/Components/MainTableView/TableContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ interface TableContentProps {
setPageNumber: (pageNumber: number) => void;
setNumberOfTuplesPerPage: (numberOfTuplesPerPage: number) => void;
fetchTableContent: () => void; // Callback function to tell the parent component to update the contentData
setRestrictions: (restrictions: Array<Restriction>) => void;
setRestrictions: (restrictions: Set<Restriction>) => void;
}

interface TableContentState {
Expand Down
21 changes: 13 additions & 8 deletions src/Components/MainTableView/TableView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import JSON5 from 'json5';
import "./TableView.css";

// Component imports
Expand All @@ -12,6 +13,8 @@ import SecondaryTableAttribute from './DataStorageClasses/SecondaryTableAttribut
import TableAttribute from './DataStorageClasses/TableAttribute';
import Restriction from './DataStorageClasses/Restriction';
import BasicLoadingIcon from '../LoadingAnimation/BasicLoadingIcon';
import { isEqualSet } from '../Utils';
import json5 from 'json5';

const NUMBER_OF_TUPLES_PER_PAGE_TIMEOUT: number = 500;

Expand Down Expand Up @@ -41,7 +44,7 @@ interface TableViewState {
tableInfoData: string; // Table description obtain from backend
errorMessage: string; // Error message buffer
isLoading: boolean; // Boolean for loading animation
restrictions: Array<Restriction>; // Storage for the last requested restrction to deal with case such as numberOfTuplesPerPage change
restrictions: Set<Restriction>; // Storage for the last requested restrction to deal with case such as numberOfTuplesPerPage change
}

/**
Expand All @@ -64,7 +67,7 @@ export default class TableView extends React.Component<TableViewProps, TableView
tableInfoData: '',
errorMessage: '',
isLoading: false,
restrictions: []
restrictions: new Set()
}

this.fetchTableAttributeAndContent = this.fetchTableAttributeAndContent.bind(this);
Expand Down Expand Up @@ -102,7 +105,7 @@ export default class TableView extends React.Component<TableViewProps, TableView
* Setter for valid restrictions to apply during table fetching
* @param restrictions Array of vaild Restrictions
*/
setRestrictions(restrictions: Array<Restriction>) {
setRestrictions(restrictions: Set<Restriction>) {
this.setState({restrictions: restrictions});
}

Expand Down Expand Up @@ -131,12 +134,12 @@ export default class TableView extends React.Component<TableViewProps, TableView
if (this.state.currentView === CurrentView.TABLE_CONTENT) {
// User is on TableContent, fetch data related to that view and set tableInfoNeedRefresh to true
this.fetchTableAttributeAndContent();
this.setState({tableContentNeedRefresh: false, tableDefinitionNeedRefresh: true, restrictions: []});
this.setState({tableContentNeedRefresh: false, tableDefinitionNeedRefresh: true, restrictions: new Set()});
}
else if (this.state.currentView === CurrentView.TABLE_INFO) {
// User is on TableInfo, fetch data related to that view nad set tableContentNeedRefresh to true
this.fetchTableDefinition();
this.setState({tableContentNeedRefresh: true, tableDefinitionNeedRefresh: false, restrictions: []});
this.setState({tableContentNeedRefresh: true, tableDefinitionNeedRefresh: false, restrictions: new Set()});
}
}
else if (this.state.currentView !== prevState.currentView) {
Expand All @@ -163,7 +166,7 @@ export default class TableView extends React.Component<TableViewProps, TableView
}, NUMBER_OF_TUPLES_PER_PAGE_TIMEOUT)
this.setState({setNumberOFTuplesPerPageTimeout: setNumberOFTuplesPerPageTimeout});
}
else if (this.state.restrictions !== prevState.restrictions) {
else if (!isEqualSet(this.state.restrictions, prevState.restrictions)) {
this.fetchTableContent();
}
}
Expand Down Expand Up @@ -245,7 +248,7 @@ export default class TableView extends React.Component<TableViewProps, TableView
// Add page param
urlParams.push('page=' + this.state.currentPageNumber);

if (this.state.restrictions.length !== 0) {
if (this.state.restrictions.size !== 0) {
let restrictionsInAPIFormat = []
for (let restriction of this.state.restrictions) {
if (restriction.tableAttribute?.attributeType === TableAttributeType.DATETIME) {
Expand Down Expand Up @@ -287,6 +290,7 @@ export default class TableView extends React.Component<TableViewProps, TableView
headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + this.props.token},
})
.then(result => {
console.log(result);
if (!result.ok) {
result.text()
.then(errorMessage => {
Expand All @@ -296,8 +300,9 @@ export default class TableView extends React.Component<TableViewProps, TableView
this.setState({tableContentData: [], errorMessage: 'Problem fetching table content: ' + error, isLoading: false})
})
}
return result.json();
return result.text();
})
.then(result => JSON5.parse(result))
.then(result => {
// Deal with coverting time back to datajoint format
let tableAttributes: Array<TableAttribute> = this.state.tableAttributesInfo?.primaryAttributes as Array<TableAttribute>;
Expand Down
168 changes: 146 additions & 22 deletions src/Components/SideMenu/TableList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import TableListLoading from '../LoadingAnimation/TableListLoading';
import TableListDict from './TableListDict'

enum TableSortMode {
ATOZ,
ZTOA,
A_TO_Z_BY_TIER,
Z_TO_A_BY_TIER,
}

/**
Expand Down Expand Up @@ -92,11 +92,12 @@ export default class TableList extends React.Component<TableListProps, TableList
tableList: [],
restrictedTableList: [],
searchString: '',
currentTableSortMode: TableSortMode.ATOZ
currentTableSortMode: TableSortMode.A_TO_Z_BY_TIER
}

this.onSearchStringChange = this.onSearchStringChange.bind(this);
this.flipTableOrder = this.flipTableOrder.bind(this);
this.restrictTableListBySeachString = this.restrictTableListBySeachString.bind(this);
this.changeTableSortMode = this.changeTableSortMode.bind(this);
}

/**
Expand Down Expand Up @@ -168,8 +169,123 @@ export default class TableList extends React.Component<TableListProps, TableList
this.parseTableEntry(tableList, this.props.tableListDict.lookup_tables, TableType.LOOKUP, partTableDict);
this.parseTableEntry(tableList, this.props.tableListDict.imported_tables, TableType.IMPORTED, partTableDict);

// Update the state and reset sort mode to ATOZ
this.setState({tableList: tableList, restrictedTableList: tableList, searchString: '', currentTableSortMode: TableSortMode.ATOZ});
console.log(tableList);
tableList = this.sortTableList(tableList, this.state.currentTableSortMode);
console.log(tableList);
// Update the state and reset sort mode to ATOZ
this.setState({tableList: tableList, restrictedTableList: tableList, searchString: ''});
}

/**
* Sort the given the tableList by one one of the avaliable sortMode listed in TableSortMode
* @param tableList The array of ParentTableListEntry to be sorted
* @param sortMode One of the supported TableSortMode entry
* @returns A sorted table list
*/
sortTableList(tableList: Array<ParentTableListEntry>, sortMode: TableSortMode) {
let tableListsByTiers: any = []; // Dict to store an array for each tier of table

// Sort the tableList into tiers first
for (let parentTableListEntry of tableList) {
// Check if the array is initalized
if (tableListsByTiers[parentTableListEntry.tableType] === undefined) {
// Initalized with an empty array
tableListsByTiers[parentTableListEntry.tableType] = [];
}

// Throw the parentTableListEntry into their respective tier arrays
tableListsByTiers[parentTableListEntry.tableType].push(parentTableListEntry);
}

if (sortMode === TableSortMode.A_TO_Z_BY_TIER) {
// Sort by name for each tier from A to Z
for (let tableTierKey of Object.keys(tableListsByTiers)) {
(tableListsByTiers[tableTierKey] as Array<ParentTableListEntry>).sort(function(a: TableListEntry, b: TableListEntry) {
let aLowerCase = a.tableName.toLowerCase();
let bLowerCase = b.tableName.toLowerCase();

if (aLowerCase < bLowerCase) {
return -1;
}
else if (aLowerCase > bLowerCase) {
return 1;
}
else {
return 0;
}
})

// Check if the parentTableListEntry has part table, if so sort it also
for (let parentTableListEntry of tableListsByTiers[tableTierKey] as Array<ParentTableListEntry>) {
if (parentTableListEntry.partTables.length > 0) {
parentTableListEntry.partTables.sort(function(a: TableListEntry, b: TableListEntry) {
let aLowerCase = a.tableName.toLowerCase();
let bLowerCase = b.tableName.toLowerCase();

if (aLowerCase < bLowerCase) {
return -1;
}
else if (aLowerCase > bLowerCase) {
return 1;
}
else {
return 0;
}
})
}
}
}
}
else if (sortMode === TableSortMode.Z_TO_A_BY_TIER) {
// Basically exactly ATOZ but with the sort code flipped
for (let tableTierKey of Object.keys(tableListsByTiers)) {
(tableListsByTiers[tableTierKey] as Array<ParentTableListEntry>).sort(function(a: TableListEntry, b: TableListEntry) {
let aLowerCase = a.tableName.toLowerCase();
let bLowerCase = b.tableName.toLowerCase();

if (aLowerCase < bLowerCase) {
return 1;
}
else if (aLowerCase > bLowerCase) {
return -1;
}
else {
return 0;
}
})

// Check if the parentTableListEntry has part table, if so sort it also
for (let parentTableListEntry of tableListsByTiers[tableTierKey] as Array<ParentTableListEntry>) {
if (parentTableListEntry.partTables.length > 0) {
parentTableListEntry.partTables.sort(function(a: TableListEntry, b: TableListEntry) {
let aLowerCase = a.tableName.toLowerCase();
let bLowerCase = b.tableName.toLowerCase();

if (aLowerCase < bLowerCase) {
return 1;
}
else if (aLowerCase > bLowerCase) {
return -1;
}
else {
return 0;
}
})
}
}
}
}
else {
throw 'Unsupported Table List sort mode';
}

// Rebuild the tableList
let sortedTableList: Array<ParentTableListEntry> = [];
for (let tableTierKey of Object.keys(tableListsByTiers)) {
sortedTableList = sortedTableList.concat(tableListsByTiers[tableTierKey]);
}

return sortedTableList;
}

/**
Expand Down Expand Up @@ -197,26 +313,34 @@ export default class TableList extends React.Component<TableListProps, TableList
* @param event
*/
onSearchStringChange(event: React.ChangeEvent<HTMLInputElement>) {
this.restrictTableListBySeachString(event.target.value);
}

/**
* Apply the restriction string by looping through the table list and copying the entries that match the condition to restrictTableList
* @param searchString String to restrict the table's name by
*/
restrictTableListBySeachString(searchString: string) {
// Filter our the results based on the search string, assuming it is not empty
let restrictedTableList: Array<ParentTableListEntry> = [];

if (event.target.value !== '') {
if (searchString !== '') {
for (let parentTableListEntry of this.state.tableList) {
if (parentTableListEntry.tableName.toLocaleLowerCase().includes(event.target.value.toLocaleLowerCase())) {
if (parentTableListEntry.tableName.toLocaleLowerCase().includes(searchString.toLocaleLowerCase())) {
restrictedTableList.push(parentTableListEntry);
}
else {
for (let partTableListEntry of parentTableListEntry.partTables) {
if (partTableListEntry.tableName.toLocaleLowerCase().includes(event.target.value.toLocaleLowerCase())) {
if (partTableListEntry.tableName.toLocaleLowerCase().includes(searchString.toLocaleLowerCase())) {
restrictedTableList.push(parentTableListEntry);
}
}
}
}
this.setState({searchString: event.target.value, restrictedTableList: restrictedTableList});
this.setState({searchString: searchString, restrictedTableList: restrictedTableList});
}
else {
this.setState({searchString: event.target.value, restrictedTableList: this.state.tableList});
this.setState({searchString: searchString, restrictedTableList: this.state.tableList});
}
}

Expand All @@ -225,16 +349,16 @@ export default class TableList extends React.Component<TableListProps, TableList
* the list that is ascending and decensding with ascending by default, we can take advantage by this by just simply fliping the list when
* the user change between the two sort mode. We also need to change the selected schema index accordingly which is simply just lengtOfArray - currentIndex - 1
*/
flipTableOrder(event: React.ChangeEvent<HTMLSelectElement>) {
var restrictedTableList: Array<ParentTableListEntry> = Object.assign([], this.state.restrictedTableList);
async changeTableSortMode(event: React.ChangeEvent<HTMLSelectElement>) {
let requestedTableSortMode: TableSortMode = parseInt(event.target.value) as TableSortMode;
if (requestedTableSortMode !== this.state.currentTableSortMode) {
let tableList = this.sortTableList(this.state.tableList, requestedTableSortMode);
// Update the tableList
await this.setState({tableList: this.sortTableList(this.state.tableList, requestedTableSortMode), currentTableSortMode: requestedTableSortMode});

// Flip all part tables first
for (let parentTableListEntry of restrictedTableList) {
parentTableListEntry.partTables.reverse();
// Reapply search string restriction
this.restrictTableListBySeachString(this.state.searchString);
}

// Flip all the parent tables
this.setState({restrictedTableList: this.state.restrictedTableList.reverse(), currentTableSortMode: parseInt(event.target.value)});
}

render() {
Expand All @@ -250,9 +374,9 @@ export default class TableList extends React.Component<TableListProps, TableList
<FontAwesomeIcon className="sort-icon" icon={faSortAmountDown} />
<label>Sort<br />Table</label>
</div>
<select className="sort-table-options" onChange={this.flipTableOrder}>
<option value={TableSortMode.ATOZ} selected={this.state.currentTableSortMode === TableSortMode.ATOZ}>Alphabetical (A-Z)</option>
<option value={TableSortMode.ZTOA} selected={this.state.currentTableSortMode === TableSortMode.ZTOA}>Alphabetical (Z-A)</option>
<select className="sort-table-options" onChange={this.changeTableSortMode}>
<option value={TableSortMode.A_TO_Z_BY_TIER} selected={this.state.currentTableSortMode === TableSortMode.A_TO_Z_BY_TIER}>Alphabetical (A-Z)</option>
<option value={TableSortMode.Z_TO_A_BY_TIER} selected={this.state.currentTableSortMode === TableSortMode.Z_TO_A_BY_TIER}>Alphabetical (Z-A)</option>
{/* <option value="tb">Topological (top-bottom)</option> */}
{/* <option value="bt">Topological (bottom-top)</option> */}
</select>
Expand Down
Loading