Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inspector widget selection improvements (#3489) #3525

Merged
merged 60 commits into from Jan 4, 2022

Conversation

aednlaxer
Copy link
Contributor

@aednlaxer aednlaxer commented Dec 7, 2021

Original ticket: #3489

Changes in this pull request:

  • add widget search with text selection
  • rename "Details Tree" to "Widget Details Tree"
  • add breadcrumbs navigator to Widget Details Tree

demo

msavela and others added 19 commits November 18, 2021 15:45
…#3489)

* Add a method for finding a path from currently selected row item to the tree root (flutter#3489)

* Add InspectorBreadcrumbNavigator (flutter#3489)

* Set text style explicitly (flutter#3489)

* Notify controller about selected widget on tap (flutter#3489)

* Make currently selected item not clickable (flutter#3489)

* Move breadcrumbs out of scrollable tree container (flutter#3489)

* Tweak padding size and appearance parameters (flutter#3489)

* Scroll to the end of the list so the last breadcrumb is always visible (flutter#3489)

* Simplify constructor (flutter#3489)

* Provide inspector tree controller directly to the tree widget where needed (flutter#3489)

* Provide dummy inspectorTreeController to tests in inspector_tree_test (flutter#3489)

* Ensure inspectorTreeController is provided when InspectorTree is showing a widget detail tree (flutter#3489)

* Test if breadcrumbs widget is displayed  (flutter#3489)

* Remove empty constructor (flutter#3489)

* Use row for showing breadcrumbs (flutter#3489)
Disable Details tree search & Highlight search matches with orange color & breadcrumbs
Copy link
Member

@kenzieschmoll kenzieschmoll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done with first round of comments. Generally, we should be trying to use the search functionality in SearchControllerMixin where possible instead of trying to manage matches and match indexes manually. Happy to discuss further or chat offline for help with hooking the inspector up to the existing search functionality in DevTools.

packages/devtools_app/lib/src/common_widgets.dart Outdated Show resolved Hide resolved
@@ -35,18 +35,24 @@ class DiagnosticsNodeDescription extends StatelessWidget {
const DiagnosticsNodeDescription(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @matt-ragonese in case you are using this file for your custom screen

packages/devtools_app/lib/src/inspector/diagnostics.dart Outdated Show resolved Hide resolved
packages/devtools_app/lib/src/inspector/diagnostics.dart Outdated Show resolved Hide resolved
// Reset search matches
for (final row in cachedRows) {
_statsWidgets++;
setSearchMatch(row.node, false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't need to do this. SearchControllerMixin already resets the isSearchMatch field in _updateMatches

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setSearchMatch does two things: it expands current node and it marks a node (not row) as search match.
InspectorTree uses getRow() to get row data which is different from what's returned from getCachedRow(). _updateMatches() marks a row as search match but this row is a cached row so isSearchMatch is not reflecting the actual status for rows returned by getRow().

I have the following questions regarding this:

  • can cached rows be used in InspectorTree?
  • what would be a good way to expand a node if setSearchMatch() is removed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple questsions:

  1. Why do we need to mark both the row and the node as having a search match? as far as I can tell, everywhere hasSearchMatch is used, we have access to the row, so we could just write row.isSearchMatch instead.
  2. Why are we expanding nodes at all for search?

If I understand correctly, the issue with cached rows vs non-cached rows is that we are setting isSearchMatch on cached rows and not on rows returned by getRow() - is that correct? If so, can we do a comparison where ever we need to call getRow() and need to know the value of isSearchMatch to check if there is a matching cached row with isSearchMatch set to true?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to mark both the row and the node as having a search match?

It was made as a work around because isSearchMatch is set on cached rows but different instances of rows are used when displaying the tree.

Why are we expanding nodes at all for search?

Search is looking for matches in cachedRows. In order to have all rows in cachedRows list all nodes need to be expanded.

If I understand correctly, the issue with cached rows vs non-cached rows is that we are setting isSearchMatch on cached rows and not on rows returned by getRow() - is that correct?

Yes, that's correct.

can we do a comparison where ever we need to call getRow() and need to know the value of isSearchMatch

Yes. We need to know the value of isSearchMatch in _InspectorTreeState but getRows() is used there.

I've made some changes to the code:

  • setSearchMatch() and hasSearchMatch have been removed
  • InspectorTree now uses getCachedRow(). It should be okay to use it since it may still call root?.getRow() under the hood.

There's still need to expand the tree to get all rows cached. I did this by expanding all nodes of the tree in the overridden set search setter (so nodes are expanded before submitting search query). I don't think it's ideal, maybe it's better to find a solution that keeps current state of collapsed and expanded nodes.

_statsSearchOps++;
if (searchValue.toLowerCase().contains(caseInsensitiveSearch)) {
matches.add(row);
setSearchMatch(row.node, true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as above. SearchControllerMixin._updateMatches should already handle this

packages/devtools_app/lib/src/ui/search.dart Outdated Show resolved Hide resolved
final pathToRoot = <InspectorTreeRow>[selectedItem];
InspectorTreeNode nextParentNode = selectedItem.node.parent;
while (nextParentNode != null) {
final rowItem = cachedRows.firstWhere(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, but then wouldn't the path to root be incomplete, meaning the breadcrumb navigator would be incomplete and not show the root node?

// Reset search matches
for (final row in cachedRows) {
_statsWidgets++;
setSearchMatch(row.node, false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple questsions:

  1. Why do we need to mark both the row and the node as having a search match? as far as I can tell, everywhere hasSearchMatch is used, we have access to the row, so we could just write row.isSearchMatch instead.
  2. Why are we expanding nodes at all for search?

If I understand correctly, the issue with cached rows vs non-cached rows is that we are setting isSearchMatch on cached rows and not on rows returned by getRow() - is that correct? If so, can we do a comparison where ever we need to call getRow() and need to know the value of isSearchMatch to check if there is a matching cached row with isSearchMatch set to true?

@override
set search(String value) {
// Expand tree so all rows are available for search
_expandAll(root);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of overriding search to expand the tree. Why don't we create a copy of the tree at the time of tree creation. This copy can be fully expanded from the start, which has the advantages that A) we won't have to modify the expand/collapse state of the original tree, and B) we won't have to pay the cost of expanding the tree each time we perform a search.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a copy leads to a previous issue when actual\cached rows and rows from the copy are not the same instances so isSearchMatch doesn't reflect the actual value. Anyway I tried using a copy of the tree for search and finding a path to the root for breadcrumbs. isSearchMatch was made to show the actual value because getCachedRow() sets the value to the cached row before returning it.

final rowItem = cachedRows.firstWhere(
(element) => element.node == nextParentNode,
orElse: null,
final rowItem = _searchableCachedRows.firstWhere(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have you tried this with a very large app? The worst case scenario of this is that you are trying to get the path from the deepest leaf node to the root. Where N is the depth of the leaf and M is the total number of nodes in the tree, this could be NxM, which could be very slow for a large app.

seems like it would be more performant to either A) make rows aware of their parent rows so that row.parent would yield the next parent, or B) to give Node's access to their associated row, so that row.node.parent.row would yield the next parent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out it's enough to have an instance of InspectorTreeNode for the selected row to find a way to the root. Such instance is available from an instance of InspectorTreeRow and _cachedSelectedRow in particular. This method now uses parent of the selected row.node to find a path to the root. Should be more performant now as it doesn't iterate through the whole tree to find the right item on every iteration of the while loop.

@@ -184,6 +185,10 @@ class InspectorTreeController extends Object
final List<InspectorTreeRow> cachedRows = [];
InspectorTreeRow _cachedSelectedRow;

/// Cached rows of the tree. Similar to [cachedRows] but items are populated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dart-doc nit: there should be a blank new line between the first sentence and the rest.

Also just to clarify, _searchableCachedRows contains every row in the tree, is that correct? not just those that are visible (nodes with expanded parents)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it contains every row in the tree (including collapsed nodes, children of collapsed nodes), it's now reflected in the doc

# Conflicts:
#	packages/devtools_app/lib/src/common_widgets.dart
#	packages/devtools_app/lib/src/network/network_screen.dart
#	packages/devtools_app/test/goldens/timeline_flame_chart_with_selected_frame.png
@aednlaxer
Copy link
Contributor Author

aednlaxer commented Dec 21, 2021

I'm having difficulties figuring out why goldens tests fail. I'm unable to reproduce it or update golden files locally when using Flutter master or stable. As far as I understand, devtools uses specific version of SDK in CI/CD (2.8.0-3.1.pre currently) but using same version locally didn't help. @kenzieschmoll do you have any idea what could be causing these tests to fail?

@aednlaxer
Copy link
Contributor Author

The goldens and other tests seem to be okay now

@kenzieschmoll
Copy link
Member

I just checked this out locally and played with it against a large application to verify the performance is acceptable. It looks fantastic - great work!

Here is one polish item that should be done as a follow up: #3561

@kenzieschmoll kenzieschmoll merged commit 8724666 into flutter:master Jan 4, 2022
@aednlaxer
Copy link
Contributor Author

Thanks for thoroughly reviewing this PR! I'll have a look at the issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants