Files
redash/client/app/components/items-list/classes/ItemsSource.js
Arik Fraimovich 84d516bfd1 Misc changes to codebase back ported from internal fork (#5129)
* Set corejs version in .babelrc so Jest doesn't complain.

* Rewrite services/routes in TypeScript.

* Add TypeScript definitions for DialogComponent.

* Make image paths more portable

* Add current route context and hook.

* Make EmptyState more flexible by being able to pass in getSteps function.

* Rewrite ItemsList in TypeScript.

* Introduce the possibility to add custom sorters for a column.

* Rearrange props to be friendly to TypeScript.

* Type definitions for NotificationApi.

* Use Databricks query editor components for databricks_internal type of query runner.

* URL Escape password in Alembic configuration.

* Compare types in migrations.
2020-08-25 14:11:38 +03:00

180 lines
5.2 KiB
JavaScript

import { isFunction, identity, map, extend } from "lodash";
import Paginator from "./Paginator";
import Sorter from "./Sorter";
import { PlainListFetcher, PaginatedListFetcher } from "./ItemsFetcher";
export class ItemsSource {
onBeforeUpdate = null;
onAfterUpdate = null;
onError = null;
sortByIteratees = undefined;
getCallbackContext = () => null;
_beforeUpdate() {
if (isFunction(this.onBeforeUpdate)) {
return Promise.resolve(this.onBeforeUpdate(this.getState(), this.getCallbackContext()));
}
return Promise.resolve();
}
_afterUpdate() {
if (isFunction(this.onAfterUpdate)) {
return Promise.resolve(this.onAfterUpdate(this.getState(), this.getCallbackContext()));
}
return Promise.resolve();
}
// changes: object with flags or null (full refresh requested)
_changed(changes) {
const state = {
paginator: this._paginator,
sorter: this._sorter,
searchTerm: this._searchTerm,
selectedTags: this._selectedTags,
};
const customParams = {};
const context = {
...this.getCallbackContext(),
setCustomParams: params => {
extend(customParams, params);
},
};
return this._beforeUpdate().then(() => {
const fetchToken = Math.random()
.toString(36)
.substr(2);
this._currentFetchToken = fetchToken;
return this._fetcher
.fetch(changes, state, context)
.then(({ results, count, allResults }) => {
if (this._currentFetchToken === fetchToken) {
this._pageItems = results;
this._allItems = allResults || null;
this._paginator.setTotalCount(count);
this._params = { ...this._params, ...customParams };
return this._afterUpdate();
}
})
.catch(error => this.handleError(error));
});
}
constructor({
getRequest,
doRequest,
processResults,
isPlainList = false,
sortByIteratees = undefined,
...defaultState
}) {
if (!isFunction(getRequest)) {
getRequest = identity;
}
this._fetcher = isPlainList
? new PlainListFetcher({ getRequest, doRequest, processResults })
: new PaginatedListFetcher({ getRequest, doRequest, processResults });
this.sortByIteratees = sortByIteratees;
this.setState(defaultState);
this._pageItems = [];
this._params = {};
}
getState() {
return {
page: this._paginator.page,
itemsPerPage: this._paginator.itemsPerPage,
orderByField: this._sorter.field,
orderByReverse: this._sorter.reverse,
searchTerm: this._searchTerm,
selectedTags: this._selectedTags,
totalCount: this._paginator.totalCount,
pageItems: this._pageItems,
allItems: this._allItems,
params: this._params,
};
}
setState(state) {
this._paginator = new Paginator(state);
this._sorter = new Sorter(state, this.sortByIteratees);
this._searchTerm = state.searchTerm || "";
this._selectedTags = state.selectedTags || [];
this._savedOrderByField = this._sorter.field;
}
updatePagination = ({ page, itemsPerPage }) => {
const { page: prevPage, itemsPerPage: prevItemsPerPage } = this._paginator;
this._paginator.setItemsPerPage(itemsPerPage);
this._paginator.setPage(page);
this._changed({
pagination: {
page: prevPage !== this._paginator.page, // page changed flag
itemsPerPage: prevItemsPerPage !== this._paginator.itemsPerPage, // items per page changed flags
},
});
};
toggleSorting = orderByField => {
this._sorter.toggleField(orderByField);
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
// provided by the server-side FTS backend instead, unless it was
// requested by the user by actively ordering in search mode
if (searchTerm === "") {
this._sorter.setField(this._savedOrderByField); // restore ordering
} else {
this._sorter.setField(null);
}
this._paginator.setPage(1);
this._changed({ search: true, pagination: { page: true } });
};
updateSelectedTags = selectedTags => {
this._selectedTags = selectedTags;
this._paginator.setPage(1);
this._changed({ tags: true, pagination: { page: true } });
};
update = () => this._changed();
handleError = error => {
if (isFunction(this.onError)) {
this.onError(error);
}
};
}
export class ResourceItemsSource extends ItemsSource {
constructor({ getResource, getItemProcessor, ...rest }) {
getItemProcessor = isFunction(getItemProcessor) ? getItemProcessor : () => null;
super({
...rest,
doRequest: (request, context) => {
const resource = getResource(context)(request);
return resource;
},
processResults: (results, context) => {
let processItem = getItemProcessor(context);
processItem = isFunction(processItem) ? processItem : identity;
return map(results, item => processItem(item, context));
},
});
}
}