Skip to content

Commit

Permalink
feat(app) add complex filter
Browse files Browse the repository at this point in the history
  • Loading branch information
helderbetiol committed Jun 19, 2024
1 parent f6d6c3a commit 84fd9af
Show file tree
Hide file tree
Showing 16 changed files with 242 additions and 64 deletions.
35 changes: 31 additions & 4 deletions APP/lib/common/api_backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:ogree_app/models/tag.dart';
import 'package:ogree_app/models/tenant.dart';
import 'package:ogree_app/models/user.dart';
import 'package:universal_html/html.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

import 'definitions.dart';

Expand Down Expand Up @@ -434,7 +435,8 @@ Future<Result<void, Exception>> createObject(
}
}

Future<Result<Map<String, dynamic>, Exception>> fetchObject(String id,
Future<Result<Map<String, dynamic>, Exception>> fetchObject(
String id, AppLocalizations localeMsg,
{String idKey = "id"}) async {
print("API fetch Object");
try {
Expand All @@ -444,7 +446,7 @@ Future<Result<Map<String, dynamic>, Exception>> fetchObject(String id,
Map<String, dynamic> data = json.decode(response.body);
var list = List<Map<String, dynamic>>.from(data["data"]);
if (list.isEmpty) {
return Failure(Exception("No object found for to this request"));
return Failure(Exception(localeMsg.noObjectsFound));
}
return Success(list.first);
} else {
Expand Down Expand Up @@ -474,6 +476,31 @@ Future<Result<Map<String, dynamic>, Exception>> fetchObjectChildren(
}
}

Future<Result<List<Map<String, dynamic>>, Exception>> fetchWithComplexFilter(
String filter, Namespace namespace, AppLocalizations localeMsg) async {
print("API fetch Complex Filter");
try {
Uri url = Uri.parse(
'$apiUrl/api/objects/search?namespace=${namespace.name.toLowerCase()}');
final response = await http.post(url,
body: json.encode(<String, dynamic>{'filter': filter}),
headers: getHeader(token));
if (response.statusCode == 200 || response.statusCode == 201) {
Map<String, dynamic> data = json.decode(response.body);
var list = List<Map<String, dynamic>>.from(data["data"]);
if (list.isEmpty) {
return Failure(Exception(localeMsg.noObjectsFound));
}
return Success(list);
} else {
final Map<String, dynamic> data = json.decode(response.body);
return Failure(Exception(data["message"].toString()));
}
} on Exception catch (e) {
return Failure(e);
}
}

Future<Result<void, Exception>> updateObject(
String objId, String category, Map<String, dynamic> object) async {
print("API update object");
Expand Down Expand Up @@ -541,7 +568,7 @@ Future<Result<void, Exception>> createTemplate(
}

Future<Result<List<String>, Exception>> fetchGroupContent(
String id, category) async {
String id, category, AppLocalizations localeMsg) async {
print("API fetch GR content");
try {
Uri url = Uri.parse('$apiUrl/api/objects?id=$id.*&category=$category');
Expand All @@ -551,7 +578,7 @@ Future<Result<List<String>, Exception>> fetchGroupContent(
var list = List<Map<String, dynamic>>.from(data["data"]);
print(list);
if (list.isEmpty) {
return Failure(Exception("No object found for to this request"));
return Failure(Exception(localeMsg.noObjectsFound));
} else {
List<String> content = [];
for (var item in list) {
Expand Down
5 changes: 5 additions & 0 deletions APP/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,21 @@
"whatNamespace": "Select a namespace:",

"searchById": "Search by ID",
"searchAdvanced": "Advanced search",
"filters": "Filters",
"categoryFilters": "Filter ID by category",
"clearAllFilters": "Clear all",
"noNodeFound": "No node found with ID:",
"nodeFound": "Node found:",
"xNodesFound": "{count, plural, =1{1 node found for:} other{{count} nodes found for:}}",
"expandAll" : "Expand All",
"collapseAll": "Collapse All",
"selectAll": "Select All",
"deselectAll": "Deselect All",
"nodePath": "Node path:",
"toggleSelection": "Toggle selection for this node and all its children",
"expression": "Expression",
"advancedSearchHint": "Compose complex boolean expressions with the operators =, !=, <, <=, >, >=, & and |.\nParenthesis can also be used to separate the expressions.\nExample:",

"addColumnTip": "Add a column",
"yourReport": "Your report",
Expand Down
5 changes: 5 additions & 0 deletions APP/lib/l10n/app_es.arb
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,21 @@
"whatNamespace": "Seleccionar un namespace:",

"searchById": "Buscar por ID",
"searchAdvanced": "Búsqueda avanzada",
"filters": "Filtros",
"categoryFilters": "Filtros de ID por categoría",
"clearAllFilters": "Borrar todo",
"noNodeFound": "No se encontró ningún nodo con ID:",
"xNodesFound": "{count, plural, =1{1 nodo encontrado para:} other{{count} nodos encontrados para:}}",
"nodeFound": "Nodo encontrado:",
"expandAll" : "Expandir Todo",
"collapseAll": "Reducir Todo",
"selectAll": "Seleccionar Todo",
"deselectAll": "Deseleccionar Todo",
"nodePath": "Ruta del nodo:",
"toggleSelection": "Invertir selección de este nodo y todos sus hijos",
"expression": "Expresión",
"advancedSearchHint": "Componga expresiones booleanas complejas con los operadores =, !=, <, <=, >, >=, & y |.\nTambién se pueden utilizar paréntesis para separar las expresiones.\nEjemplo:",

"addColumnTip": "Agregar una columna",
"yourReport": "Su reporte",
Expand Down
13 changes: 13 additions & 0 deletions APP/lib/l10n/app_fr.arb
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,29 @@
"whatNamespace": "Sélectionnez le type de namespace :",

"searchById": "Rechercher par ID",
"searchAdvanced": "Recherche avancée",
"filters": "Filtres",
"categoryFilters": "Filtrer l'ID par categorie",
"clearAllFilters": "Effacer tout",
"noNodeFound": "Pas de noeud trouvé avec l'ID :",
"nodeFound": "Noeud trouvé :",
"xNodesFound": "{count, plural, =1{1 noeud trouvé pour :} other{{count} noeud trouvés pour :}}",
"@xNodesFound": {
"placeholders": {
"count": {
"type": "num",
"format": "compact"
}
}
},
"expandAll" : "Développer tout",
"collapseAll": "Réduire tout",
"selectAll": "Sélectionner tout",
"deselectAll": "Désélectionner tout",
"nodePath": "Chemin du noeud :",
"toggleSelection": "Inverser sélection du noeud et de tous ses enfants",
"expression": "Expression",
"advancedSearchHint": "Composer des expressions booléennes complexes avec les opérateurs =, !=, <, <=, >, >=, & et |.\nLa parenthèse peut également être utilisée pour séparer les expressions.\nExemple :",

"@result": { "description": "Result Page" },
"result": "Résultat",
Expand Down
5 changes: 5 additions & 0 deletions APP/lib/l10n/app_pt.arb
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,21 @@
"whatNamespace": "Selecione um namespace:",

"searchById": "Pesquisar por ID",
"searchAdvanced": "Pesquisa avançada",
"filters": "Filtros",
"categoryFilters": "Filtrar ID por categoria",
"clearAllFilters": "Limpar tudo",
"noNodeFound": "Nenhum nó encontrado com ID:",
"nodeFound": "Nó encontrado:",
"xNodesFound": "{count, plural, =1{1 nó encontrado para:} other{{count} nós encontrados para:}}",
"expandAll" : "Expandir Todos",
"collapseAll": "Recolher Todos",
"selectAll": "Selecionar Todos",
"deselectAll": "Desmarcar Todos",
"nodePath": "Caminho do nó:",
"toggleSelection": "Inverter seleção deste nó e de todos os seus filhos",
"expression": "Expressão",
"advancedSearchHint": "Componha expressões booleanas complexas com os operadores =, !=, <, <=, >, >=, & e |.\nParênteses também podem ser usados para separar as expressões.\nExemplo:",

"addColumnTip": "Adicionar uma coluna",
"yourReport": "Seu relatório",
Expand Down
7 changes: 5 additions & 2 deletions APP/lib/widgets/select_objects/object_popup.dart
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,8 @@ class _ObjectPopupState extends State<ObjectPopup> {
}

Future<List<String>?> getGroupContent(String parentId, targetCategory) async {
var result = await fetchGroupContent(parentId, targetCategory);
var result = await fetchGroupContent(
parentId, targetCategory, AppLocalizations.of(context)!);
switch (result) {
case Success(value: final value):
return value;
Expand All @@ -472,7 +473,8 @@ class _ObjectPopupState extends State<ObjectPopup> {
var errMsg = "";
// Try both id and slug since we dont know the obj's category
for (var keyId in ["id", "slug", "name"]) {
var result = await fetchObject(_objId, idKey: keyId);
var result = await fetchObject(_objId, AppLocalizations.of(context)!,
idKey: keyId);
switch (result) {
case Success(value: final value):
if (widget.namespace == Namespace.Logical) {
Expand Down Expand Up @@ -581,6 +583,7 @@ class _ObjectPopupState extends State<ObjectPopup> {
return TextFormField(
controller: textEditingController,
focusNode: focusNode,
style: const TextStyle(fontSize: 14),
decoration: GetFormInputDecoration(
false, "$starSymbol${AppLocalizations.of(context)!.domain}",
icon: Icons.edit),
Expand Down
17 changes: 10 additions & 7 deletions APP/lib/widgets/select_objects/select_objects.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ class _SelectObjectsState extends State<SelectObjects> {
])
: _ResponsiveBody(
namespace: widget.namespace,
noFilters: widget.namespace != Namespace.Physical,
controller: appController,
callback: () => setState(() {
widget.load = true;
Expand Down Expand Up @@ -107,14 +106,12 @@ class _Unfocus extends StatelessWidget {

class _ResponsiveBody extends StatelessWidget {
final Namespace namespace;
final bool noFilters;
final TreeAppController controller;
final Function() callback;
const _ResponsiveBody(
{Key? key,
required this.namespace,
required this.controller,
this.noFilters = false,
required this.callback})
: super(key: key);

Expand All @@ -135,6 +132,7 @@ class _ResponsiveBody extends StatelessWidget {
context,
SettingsViewPopup(
controller: controller,
namespace: namespace,
),
isDismissible: true),
icon: const Icon(Icons.filter_alt_outlined),
Expand Down Expand Up @@ -163,7 +161,7 @@ class _ResponsiveBody extends StatelessWidget {
color: Colors.black26,
),
Expanded(
child: SettingsView(isTenantMode: false, noFilters: noFilters)),
child: SettingsView(isTenantMode: false, namespace: namespace)),
],
),
);
Expand Down Expand Up @@ -203,8 +201,10 @@ addObjectButton(

class SettingsViewPopup extends StatelessWidget {
final TreeAppController controller;
final Namespace namespace;

const SettingsViewPopup({super.key, required this.controller});
const SettingsViewPopup(
{super.key, required this.controller, required this.namespace});

@override
Widget build(BuildContext context) {
Expand All @@ -226,9 +226,12 @@ class SettingsViewPopup extends StatelessWidget {
padding: EdgeInsets.zero,
shrinkWrap: true,
children: [
const SizedBox(
SizedBox(
height: 420,
child: SettingsView(isTenantMode: false),
child: SettingsView(
isTenantMode: false,
namespace: namespace,
),
),
const SizedBox(height: 10),
TextButton.icon(
Expand Down
126 changes: 126 additions & 0 deletions APP/lib/widgets/select_objects/settings_view/_advanced_find_field.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
part of 'settings_view.dart';

class _AdvancedFindField extends StatefulWidget {
final Namespace namespace;
const _AdvancedFindField({required this.namespace});

@override
_AdvancedFindFieldState createState() => _AdvancedFindFieldState();
}

class _AdvancedFindFieldState extends State<_AdvancedFindField> {
late final controller = TextEditingController();

@override
void dispose() {
controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
final localeMsg = AppLocalizations.of(context)!;

return TextField(
controller: controller,
autofocus: false,
style: const TextStyle(fontSize: 14),
decoration: GetFormInputDecoration(
false,
localeMsg.expression,
hint: "name=bladeA&category=device",
iconWidget: Padding(
padding: const EdgeInsets.only(right: 12, left: 12.0),
child: Tooltip(
message:
"${localeMsg.advancedSearchHint} (category=device & name=ibm*) | tag=blade-hp",
verticalOffset: 13,
decoration: const BoxDecoration(
color: Colors.blueAccent,
borderRadius: BorderRadius.all(Radius.circular(12)),
),
textStyle: const TextStyle(
fontSize: 13,
color: Colors.white,
),
padding: const EdgeInsets.all(13),
child: const Icon(Icons.info_outline_rounded,
color: Colors.blueAccent),
),
),
),
onSubmitted: (_) => submitted(),
);
}

Future<void> submitted() async {
final searchExpression = controller.text.trim();
final appController = TreeAppController.of(context);
final localeMsg = AppLocalizations.of(context)!;
final messenger = ScaffoldMessenger.of(context);
List<TreeNode> nodes;

var result = await fetchWithComplexFilter(
searchExpression, widget.namespace, localeMsg);
switch (result) {
case Success(value: final foundObjs):
print(foundObjs);
nodes = getTreeNodesFromObjects(foundObjs, appController);
case Failure(exception: final exception):
showSnackBar(messenger, exception.toString(), isError: true);
return;
}

if (nodes.isEmpty) {
showSnackBar(
messenger,
'${localeMsg.noNodeFound} $searchExpression',
duration: const Duration(seconds: 3),
);
} else {
showSnackBar(
messenger,
'${localeMsg.xNodesFound(nodes.length)} $searchExpression',
isSuccess: true,
);
// Expand only until found nodes and scroll to first one
if (!appController.treeController.areAllRootsCollapsed) {
appController.treeController.collapseAll();
}
for (var node in nodes) {
appController.treeController.expandAncestors(node);
appController.scrollTo(node);
appController.selectNode(node.id);
}
appController.scrollTo(nodes.first);
}
}

List<TreeNode> getTreeNodesFromObjects(
List<Map<String, dynamic>> foundObjs, TreeAppController appController) {
List<TreeNode> nodes = [];
for (var obj in foundObjs) {
var id = obj["id"] as String;
// search for this obj on root node or in its children
for (var root in appController.treeController.roots) {
TreeNode? node;
if (root.id.toLowerCase().contains(id.toLowerCase())) {
node = root;
} else {
node = root.nullableDescendants.firstWhere(
(descendant) => descendant == null
? false
: descendant.id.toLowerCase().contains(id.toLowerCase()),
orElse: () => null,
);
}
//found it
if (node != null) {
nodes.add(node);
break;
}
}
}
return nodes;
}
}
Loading

0 comments on commit 84fd9af

Please sign in to comment.