mirror of
https://github.com/getredash/redash.git
synced 2025-12-19 17:37:19 -05:00
Fix table item list ordering (#7366)
Fix query list item list sorting - descending order, no triangle mark - ascending order, up triangle mark(▲) - descending order, down triangle mark(▼) - ascending order, no triangle mark - descending order, up triangle mark(▲) - ascending order, down triangle mark(▼) - descending order, no triangle mark "sorting order" have 2-click cycle, but "triangle mark" have 3-click cycle.
This commit is contained in:
@@ -28,6 +28,7 @@ export interface Controller<I, P = any> {
|
||||
orderByField?: string;
|
||||
orderByReverse: boolean;
|
||||
toggleSorting: (orderByField: string) => void;
|
||||
setSorting: (orderByField: string, orderByReverse: boolean) => void;
|
||||
|
||||
// pagination
|
||||
page: number;
|
||||
@@ -139,10 +140,11 @@ export function wrap<I, P = any>(
|
||||
this.props.onError!(error);
|
||||
|
||||
const initialState = this.getState({ ...itemsSource.getState(), isLoaded: false });
|
||||
const { updatePagination, toggleSorting, updateSearch, updateSelectedTags, update, handleError } = itemsSource;
|
||||
const { updatePagination, toggleSorting, setSorting, updateSearch, updateSelectedTags, update, handleError } = itemsSource;
|
||||
this.state = {
|
||||
...initialState,
|
||||
toggleSorting, // eslint-disable-line react/no-unused-state
|
||||
setSorting, // eslint-disable-line react/no-unused-state
|
||||
updateSearch: debounce(updateSearch, 200), // eslint-disable-line react/no-unused-state
|
||||
updateSelectedTags, // eslint-disable-line react/no-unused-state
|
||||
updatePagination, // eslint-disable-line react/no-unused-state
|
||||
|
||||
@@ -39,14 +39,12 @@ export class ItemsSource {
|
||||
const customParams = {};
|
||||
const context = {
|
||||
...this.getCallbackContext(),
|
||||
setCustomParams: params => {
|
||||
setCustomParams: (params) => {
|
||||
extend(customParams, params);
|
||||
},
|
||||
};
|
||||
return this._beforeUpdate().then(() => {
|
||||
const fetchToken = Math.random()
|
||||
.toString(36)
|
||||
.substr(2);
|
||||
const fetchToken = Math.random().toString(36).substr(2);
|
||||
this._currentFetchToken = fetchToken;
|
||||
return this._fetcher
|
||||
.fetch(changes, state, context)
|
||||
@@ -59,7 +57,7 @@ export class ItemsSource {
|
||||
return this._afterUpdate();
|
||||
}
|
||||
})
|
||||
.catch(error => this.handleError(error));
|
||||
.catch((error) => this.handleError(error));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -124,13 +122,20 @@ export class ItemsSource {
|
||||
});
|
||||
};
|
||||
|
||||
toggleSorting = orderByField => {
|
||||
toggleSorting = (orderByField) => {
|
||||
this._sorter.toggleField(orderByField);
|
||||
this._savedOrderByField = this._sorter.field;
|
||||
this._changed({ sorting: true });
|
||||
};
|
||||
|
||||
updateSearch = searchTerm => {
|
||||
setSorting = (orderByField, orderByReverse) => {
|
||||
this._sorter.setField(orderByField);
|
||||
this._sorter.setReverse(orderByReverse);
|
||||
this._savedOrderByField = this._sorter.field;
|
||||
this._changed({ sorting: true });
|
||||
};
|
||||
|
||||
updateSearch = (searchTerm) => {
|
||||
// here we update state directly, but later `fetchData` will update it properly
|
||||
this._searchTerm = searchTerm;
|
||||
// in search mode ignore the ordering and use the ranking order
|
||||
@@ -145,7 +150,7 @@ export class ItemsSource {
|
||||
this._changed({ search: true, pagination: { page: true } });
|
||||
};
|
||||
|
||||
updateSelectedTags = selectedTags => {
|
||||
updateSelectedTags = (selectedTags) => {
|
||||
this._selectedTags = selectedTags;
|
||||
this._paginator.setPage(1);
|
||||
this._changed({ tags: true, pagination: { page: true } });
|
||||
@@ -153,7 +158,7 @@ export class ItemsSource {
|
||||
|
||||
update = () => this._changed();
|
||||
|
||||
handleError = error => {
|
||||
handleError = (error) => {
|
||||
if (isFunction(this.onError)) {
|
||||
this.onError(error);
|
||||
}
|
||||
@@ -172,7 +177,7 @@ export class ResourceItemsSource extends ItemsSource {
|
||||
processResults: (results, context) => {
|
||||
let processItem = getItemProcessor(context);
|
||||
processItem = isFunction(processItem) ? processItem : identity;
|
||||
return map(results, item => processItem(item, context));
|
||||
return map(results, (item) => processItem(item, context));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export const Columns = {
|
||||
date(overrides) {
|
||||
return extend(
|
||||
{
|
||||
render: text => formatDate(text),
|
||||
render: (text) => formatDate(text),
|
||||
},
|
||||
overrides
|
||||
);
|
||||
@@ -52,7 +52,7 @@ export const Columns = {
|
||||
dateTime(overrides) {
|
||||
return extend(
|
||||
{
|
||||
render: text => formatDateTime(text),
|
||||
render: (text) => formatDateTime(text),
|
||||
},
|
||||
overrides
|
||||
);
|
||||
@@ -62,7 +62,7 @@ export const Columns = {
|
||||
{
|
||||
width: "1%",
|
||||
className: "text-nowrap",
|
||||
render: text => durationHumanize(text),
|
||||
render: (text) => durationHumanize(text),
|
||||
},
|
||||
overrides
|
||||
);
|
||||
@@ -70,7 +70,7 @@ export const Columns = {
|
||||
timeAgo(overrides, timeAgoCustomProps = undefined) {
|
||||
return extend(
|
||||
{
|
||||
render: value => <TimeAgo date={value} {...timeAgoCustomProps} />,
|
||||
render: (value) => <TimeAgo date={value} {...timeAgoCustomProps} />,
|
||||
},
|
||||
overrides
|
||||
);
|
||||
@@ -110,6 +110,7 @@ export default class ItemsTable extends React.Component {
|
||||
orderByField: PropTypes.string,
|
||||
orderByReverse: PropTypes.bool,
|
||||
toggleSorting: PropTypes.func,
|
||||
setSorting: PropTypes.func,
|
||||
"data-test": PropTypes.string,
|
||||
rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||
};
|
||||
@@ -127,18 +128,15 @@ export default class ItemsTable extends React.Component {
|
||||
};
|
||||
|
||||
prepareColumns() {
|
||||
const { orderByField, orderByReverse, toggleSorting } = this.props;
|
||||
const { orderByField, orderByReverse } = this.props;
|
||||
const orderByDirection = orderByReverse ? "descend" : "ascend";
|
||||
|
||||
return map(
|
||||
map(
|
||||
filter(this.props.columns, column => (isFunction(column.isAvailable) ? column.isAvailable() : true)),
|
||||
column => extend(column, { orderByField: column.orderByField || column.field })
|
||||
filter(this.props.columns, (column) => (isFunction(column.isAvailable) ? column.isAvailable() : true)),
|
||||
(column) => extend(column, { orderByField: column.orderByField || column.field })
|
||||
),
|
||||
(column, index) => {
|
||||
// Bind click events only to sortable columns
|
||||
const onHeaderCell = column.sorter ? () => ({ onClick: () => toggleSorting(column.orderByField) }) : null;
|
||||
|
||||
// Wrap render function to pass correct arguments
|
||||
const render = isFunction(column.render) ? (text, row) => column.render(text, row.item) : identity;
|
||||
|
||||
@@ -146,14 +144,13 @@ export default class ItemsTable extends React.Component {
|
||||
key: "column" + index,
|
||||
dataIndex: ["item", column.field],
|
||||
defaultSortOrder: column.orderByField === orderByField ? orderByDirection : null,
|
||||
onHeaderCell,
|
||||
render,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getRowKey = record => {
|
||||
getRowKey = (record) => {
|
||||
const { rowKey } = this.props;
|
||||
if (rowKey) {
|
||||
if (isFunction(rowKey)) {
|
||||
@@ -172,22 +169,43 @@ export default class ItemsTable extends React.Component {
|
||||
|
||||
// Bind events only if `onRowClick` specified
|
||||
const onTableRow = isFunction(this.props.onRowClick)
|
||||
? row => ({
|
||||
onClick: event => {
|
||||
? (row) => ({
|
||||
onClick: (event) => {
|
||||
this.props.onRowClick(event, row.item);
|
||||
},
|
||||
})
|
||||
: null;
|
||||
|
||||
const onChange = (pagination, filters, sorter, extra) => {
|
||||
const action = extra?.action;
|
||||
if (action === "sort") {
|
||||
const propsColumn = this.props.columns.find((column) => column.field === sorter.field[1]);
|
||||
if (!propsColumn.sorter) {
|
||||
return;
|
||||
}
|
||||
let orderByField = propsColumn.orderByField;
|
||||
const orderByReverse = sorter.order === "descend";
|
||||
|
||||
if (orderByReverse === undefined) {
|
||||
orderByField = null;
|
||||
}
|
||||
if (this.props.setSorting) {
|
||||
this.props.setSorting(orderByField, orderByReverse);
|
||||
} else {
|
||||
this.props.toggleSorting(orderByField);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const { showHeader } = this.props;
|
||||
if (this.props.loading) {
|
||||
if (isEmpty(tableDataProps.dataSource)) {
|
||||
tableDataProps.columns = tableDataProps.columns.map(column => ({
|
||||
tableDataProps.columns = tableDataProps.columns.map((column) => ({
|
||||
...column,
|
||||
sorter: false,
|
||||
render: () => <Skeleton active paragraph={false} />,
|
||||
}));
|
||||
tableDataProps.dataSource = range(10).map(key => ({ key: `${key}` }));
|
||||
tableDataProps.dataSource = range(10).map((key) => ({ key: `${key}` }));
|
||||
} else {
|
||||
tableDataProps.loading = { indicator: null };
|
||||
}
|
||||
@@ -200,6 +218,7 @@ export default class ItemsTable extends React.Component {
|
||||
rowKey={this.getRowKey}
|
||||
pagination={false}
|
||||
onRow={onTableRow}
|
||||
onChange={onChange}
|
||||
data-test={this.props["data-test"]}
|
||||
{...tableDataProps}
|
||||
/>
|
||||
|
||||
@@ -160,14 +160,15 @@ function QueriesList({ controller }) {
|
||||
orderByField={controller.orderByField}
|
||||
orderByReverse={controller.orderByReverse}
|
||||
toggleSorting={controller.toggleSorting}
|
||||
setSorting={controller.setSorting}
|
||||
/>
|
||||
<Paginator
|
||||
showPageSizeSelect
|
||||
totalCount={controller.totalItemsCount}
|
||||
pageSize={controller.itemsPerPage}
|
||||
onPageSizeChange={itemsPerPage => controller.updatePagination({ itemsPerPage })}
|
||||
onPageSizeChange={(itemsPerPage) => controller.updatePagination({ itemsPerPage })}
|
||||
page={controller.page}
|
||||
onChange={page => controller.updatePagination({ page })}
|
||||
onChange={(page) => controller.updatePagination({ page })}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
@@ -196,7 +197,7 @@ const QueriesListPage = itemsList(
|
||||
}[currentPage];
|
||||
},
|
||||
getItemProcessor() {
|
||||
return item => new Query(item);
|
||||
return (item) => new Query(item);
|
||||
},
|
||||
}),
|
||||
() => new UrlStateStorage({ orderByField: "created_at", orderByReverse: true })
|
||||
@@ -207,7 +208,7 @@ routes.register(
|
||||
routeWithUserSession({
|
||||
path: "/queries",
|
||||
title: "Queries",
|
||||
render: pageProps => <QueriesListPage {...pageProps} currentPage="all" />,
|
||||
render: (pageProps) => <QueriesListPage {...pageProps} currentPage="all" />,
|
||||
})
|
||||
);
|
||||
routes.register(
|
||||
@@ -215,7 +216,7 @@ routes.register(
|
||||
routeWithUserSession({
|
||||
path: "/queries/favorites",
|
||||
title: "Favorite Queries",
|
||||
render: pageProps => <QueriesListPage {...pageProps} currentPage="favorites" />,
|
||||
render: (pageProps) => <QueriesListPage {...pageProps} currentPage="favorites" />,
|
||||
})
|
||||
);
|
||||
routes.register(
|
||||
@@ -223,7 +224,7 @@ routes.register(
|
||||
routeWithUserSession({
|
||||
path: "/queries/archive",
|
||||
title: "Archived Queries",
|
||||
render: pageProps => <QueriesListPage {...pageProps} currentPage="archive" />,
|
||||
render: (pageProps) => <QueriesListPage {...pageProps} currentPage="archive" />,
|
||||
})
|
||||
);
|
||||
routes.register(
|
||||
@@ -231,6 +232,6 @@ routes.register(
|
||||
routeWithUserSession({
|
||||
path: "/queries/my",
|
||||
title: "My Queries",
|
||||
render: pageProps => <QueriesListPage {...pageProps} currentPage="my" />,
|
||||
render: (pageProps) => <QueriesListPage {...pageProps} currentPage="my" />,
|
||||
})
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user