2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-04-28 05:26:47 +00:00

Consolidated search (#289)

* Update search widget to support consolidated search API endpoint

* Finer control over global search

* Update release notes

* remove unused variable
This commit is contained in:
Oliver 2023-03-24 21:09:38 +11:00 committed by GitHub
parent 9543490c21
commit d7f2c3939b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 129 additions and 30 deletions

View File

@ -6,6 +6,7 @@
- Adds support for proper currency rendering - Adds support for proper currency rendering
- Fix icon for supplier part detail widget - Fix icon for supplier part detail widget
- Support global search API endpoint
### 0.10.1 - February 2023 ### 0.10.1 - February 2023
--- ---

View File

@ -279,6 +279,9 @@ class InvenTreeAPI {
// Company attachments require API v95 or newer // Company attachments require API v95 or newer
bool get supportCompanyAttachments => isConnected() && apiVersion >= 95; bool get supportCompanyAttachments => isConnected() && apiVersion >= 95;
// Consolidated search request API v102 or newer
bool get supportsConsolidatedSearch => isConnected() && apiVersion >= 102;
// Are plugins enabled on the server? // Are plugins enabled on the server?
bool _pluginsEnabled = false; bool _pluginsEnabled = false;

View File

@ -69,27 +69,34 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
Timer? debounceTimer; Timer? debounceTimer;
/*
* Decrement the number of pending / outstanding search queries
*/
void decrementPendingSearches() {
if (nPendingSearches > 0) {
nPendingSearches--;
}
}
/*
* Determine if the search is still running
*/
bool isSearching() { bool isSearching() {
if (searchController.text.isEmpty) { if (searchController.text.isEmpty) {
return false; return false;
} }
return nSearchResults < 5; return nPendingSearches > 0;
} }
int nSearchResults = 0; // Individual search result count (for legacy search API)
int nPendingSearches = 0;
int nPartResults = 0; int nPartResults = 0;
int nCategoryResults = 0; int nCategoryResults = 0;
int nStockResults = 0; int nStockResults = 0;
int nLocationResults = 0; int nLocationResults = 0;
int nSupplierResults = 0; int nSupplierResults = 0;
int nPurchaseOrderResults = 0; int nPurchaseOrderResults = 0;
late FocusNode _focusNode; late FocusNode _focusNode;
@ -114,6 +121,32 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
} }
} }
/*
* Return the 'result count' for a particular query from the results map
* e.g.
* {
* "part": {
* "count": 102,
* }
* }
*/
int getSearchResultCount(Map <String, dynamic> results, String key) {
dynamic result = results[key];
if (result == null || result is! Map) {
return 0;
}
dynamic count = result["count"];
if (count == null || count is! int) {
return 0;
}
return count;
}
/* /*
* Initiate multiple search requests to the server. * Initiate multiple search requests to the server.
* Each request returns at *some point* in the future, * Each request returns at *some point* in the future,
@ -122,7 +155,6 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
* So, each request only causes an update *if* the search term is still the same when it completes * So, each request only causes an update *if* the search term is still the same when it completes
*/ */
Future<void> search(String term) async { Future<void> search(String term) async {
var api = InvenTreeAPI(); var api = InvenTreeAPI();
if (!mounted) { if (!mounted) {
@ -138,21 +170,95 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
nSupplierResults = 0; nSupplierResults = 0;
nPurchaseOrderResults = 0; nPurchaseOrderResults = 0;
nSearchResults = 0; nPendingSearches = 0;
}); });
if (term.isEmpty) { if (term.isEmpty) {
return; return;
} }
// Consolidated search allows us to perform *all* searches in a single query
if (api.supportsConsolidatedSearch) {
Map<String, dynamic> body = {
"limit": 1,
"search": term,
};
// Part search
if (api.checkPermission("part", "view")) {
body["part"] = {};
}
// PartCategory search
if (api.checkPermission("part_category", "view")) {
body["partcategory"] = {};
}
// StockItem search
if (api.checkPermission("stock", "view")) {
body["stockitem"] = {
"in_stock": true,
};
}
// StockLocation search
if (api.checkPermission("stock_location", "view")) {
body["stocklocation"] = {};
}
// PurchaseOrder search
if (api.checkPermission("purchase_order", "view")) {
body["purchaseorder"] = {
"outstanding": true
};
}
if (body.isNotEmpty) {
nPendingSearches++;
api.post(
"search/",
body: body,
expectedStatusCode: 200).then((APIResponse response) {
decrementPendingSearches();
Map<String, dynamic> results = {};
if (response.data is Map<String, dynamic>) {
results = response.data as Map<String, dynamic>;
}
if (mounted) {
setState(() {
nPartResults = getSearchResultCount(results, "part");
nCategoryResults = getSearchResultCount(results, "partcategory");
nStockResults = getSearchResultCount(results, "stockitem");
nLocationResults = getSearchResultCount(results, "stocklocation");
nSupplierResults = 0; //getSearchResultCount(results, "")
nPurchaseOrderResults = getSearchResultCount(results, "purchaseorder");
});
}
});
}
} else {
legacySearch(term);
}
}
/*
* Perform "legacy" search (without consolidated search API endpoint
*/
Future<void> legacySearch(String term) async {
// Search parts // Search parts
if (api.checkPermission("part", "view")) { if (api.checkPermission("part", "view")) {
nPendingSearches++;
InvenTreePart().count(searchQuery: term).then((int n) { InvenTreePart().count(searchQuery: term).then((int n) {
if (term == searchController.text) { if (term == searchController.text) {
if (mounted) { if (mounted) {
decrementPendingSearches();
setState(() { setState(() {
nPartResults = n; nPartResults = n;
nSearchResults++;
}); });
} }
} }
@ -161,12 +267,13 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
// Search part categories // Search part categories
if (api.checkPermission("part_category", "view")) { if (api.checkPermission("part_category", "view")) {
nPendingSearches++;
InvenTreePartCategory().count(searchQuery: term,).then((int n) { InvenTreePartCategory().count(searchQuery: term,).then((int n) {
if (term == searchController.text) { if (term == searchController.text) {
if (mounted) { if (mounted) {
decrementPendingSearches();
setState(() { setState(() {
nCategoryResults = n; nCategoryResults = n;
nSearchResults++;
}); });
} }
} }
@ -175,12 +282,13 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
// Search stock items // Search stock items
if (api.checkPermission("stock", "view")) { if (api.checkPermission("stock", "view")) {
nPendingSearches++;
InvenTreeStockItem().count(searchQuery: term).then((int n) { InvenTreeStockItem().count(searchQuery: term).then((int n) {
if (term == searchController.text) { if (term == searchController.text) {
if (mounted) { if (mounted) {
decrementPendingSearches();
setState(() { setState(() {
nStockResults = n; nStockResults = n;
nSearchResults++;
}); });
} }
} }
@ -189,35 +297,22 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
// Search stock locations // Search stock locations
if (api.checkPermission("stock_location", "view")) { if (api.checkPermission("stock_location", "view")) {
nPendingSearches++;
InvenTreeStockLocation().count(searchQuery: term).then((int n) { InvenTreeStockLocation().count(searchQuery: term).then((int n) {
if (term == searchController.text) { if (term == searchController.text) {
if (mounted) { if (mounted) {
decrementPendingSearches();
setState(() { setState(() {
nLocationResults = n; nLocationResults = n;
nSearchResults++;
}); });
} }
} }
}); });
} }
// TDOO: Re-implement this once display for companies has been fixed
/*
// Search suppliers
InvenTreeCompany().count(searchQuery: term,
filters: {
"is_supplier": "true",
},
).then((int n) {
setState(() {
nSupplierResults = n;
nSearchResults++;
});
});
*/
// Search purchase orders // Search purchase orders
if (api.checkPermission("purchase_order", "view")) { if (api.checkPermission("purchase_order", "view")) {
nPendingSearches++;
InvenTreePurchaseOrder().count( InvenTreePurchaseOrder().count(
searchQuery: term, searchQuery: term,
filters: { filters: {
@ -226,9 +321,9 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
).then((int n) { ).then((int n) {
if (term == searchController.text) { if (term == searchController.text) {
if (mounted) { if (mounted) {
decrementPendingSearches();
setState(() { setState(() {
nPurchaseOrderResults = n; nPurchaseOrderResults = n;
nSearchResults++;
}); });
} }
} }