mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-28 13:36:50 +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:
parent
9543490c21
commit
d7f2c3939b
@ -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
|
||||||
---
|
---
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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++;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user