-
Notifications
You must be signed in to change notification settings - Fork 337
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
Add filtering to the Network profiler #2340
Changes from 1 commit
b8bf962
4a701c2
13034a5
7508a88
174b900
cb9d6b6
ebf2472
ad315db
9775663
0360338
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -537,6 +537,62 @@ class ExportButton extends StatelessWidget { | |
} | ||
} | ||
|
||
class FilterButton extends StatelessWidget { | ||
const FilterButton({ | ||
Key key, | ||
@required this.onPressed, | ||
@required this.isFilterActive, | ||
}) : super(key: key); | ||
|
||
final VoidCallback onPressed; | ||
|
||
final bool isFilterActive; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final colorScheme = Theme.of(context).colorScheme; | ||
return RoundedOutlinedBorder( | ||
child: SizedBox( | ||
height: defaultButtonHeight, | ||
child: Tooltip( | ||
message: 'Filter', | ||
child: FlatButton( | ||
key: key, | ||
onPressed: onPressed, | ||
color: isFilterActive | ||
? colorScheme.toggleButtonBackgroundColor | ||
: Colors.transparent, | ||
child: createIcon( | ||
Icons.filter_list, | ||
color: isFilterActive | ||
? colorScheme.toggleButtonForegroundColor | ||
: null, | ||
), | ||
), | ||
), | ||
), | ||
); | ||
} | ||
} | ||
|
||
Widget clearTextFieldButton(VoidCallback onPressed) { | ||
return textFieldSuffixButton(Icons.clear, onPressed); | ||
} | ||
|
||
Widget textFieldSuffixButton(IconData icon, VoidCallback onPressed) { | ||
return Container( | ||
padding: const EdgeInsets.symmetric(horizontal: densePadding), | ||
width: 24.0, | ||
child: IconButton( | ||
padding: const EdgeInsets.all(0.0), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: would placing the padding in the icon button look different than in the container? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the explicit 0 padding is to override the default padding of 4.0 in the IconButton, otherwise the size constraint of 24.0 is not respected. |
||
onPressed: onPressed, | ||
iconSize: defaultIconSize, | ||
splashRadius: defaultIconSize, | ||
icon: Icon(icon), | ||
), | ||
); | ||
} | ||
|
||
class OutlineDecoration extends StatelessWidget { | ||
const OutlineDecoration({Key key, this.child}) : super(key: key); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,8 @@ class NetworkController with SearchControllerMixin<NetworkRequest> { | |
_networkService = NetworkService(this); | ||
} | ||
|
||
static NetworkFilter defaultFilter = NetworkFilter(); | ||
|
||
/// Notifies that new Network requests have been processed. | ||
ValueListenable<NetworkRequests> get requests => _requests; | ||
|
||
|
@@ -30,6 +32,15 @@ class NetworkController with SearchControllerMixin<NetworkRequest> { | |
|
||
final _selectedRequest = ValueNotifier<NetworkRequest>(null); | ||
|
||
ValueListenable<List<NetworkRequest>> get filteredRequests => | ||
_filteredRequests; | ||
|
||
final _filteredRequests = ValueNotifier<List<NetworkRequest>>([]); | ||
|
||
ValueListenable<NetworkFilter> get activeFilter => _activeFilter; | ||
|
||
final _activeFilter = ValueNotifier<NetworkFilter>(defaultFilter); | ||
|
||
/// Notifies that the timeline is currently being recorded. | ||
ValueListenable<bool> get recordingNotifier => _recordingNotifier; | ||
final _recordingNotifier = ValueNotifier<bool>(false); | ||
|
@@ -169,6 +180,7 @@ class NetworkController with SearchControllerMixin<NetworkRequest> { | |
invalidRequests: [], | ||
outstandingRequestsMap: Map.from(requests.value.outstandingHttpRequests), | ||
); | ||
filterData(_activeFilter.value); | ||
refreshSearchMatches(); | ||
} | ||
|
||
|
@@ -255,6 +267,7 @@ class NetworkController with SearchControllerMixin<NetworkRequest> { | |
Future<void> clear() async { | ||
await _networkService.clearData(); | ||
_requests.value = NetworkRequests(); | ||
_filteredRequests.value = []; | ||
refreshSearchMatches(); | ||
_selectedRequest.value = null; | ||
} | ||
|
@@ -268,12 +281,83 @@ class NetworkController with SearchControllerMixin<NetworkRequest> { | |
// TODO(kenz): support intelligent search queries like t:http (type = http) | ||
// or m:GET (method = GET). | ||
|
||
final currentRequests = _requests.value.requests; | ||
final currentRequests = _filteredRequests.value; | ||
for (final request in currentRequests) { | ||
if (request.uri.toLowerCase().contains(caseInsensitiveSearch)) { | ||
matches.add(request); | ||
} | ||
} | ||
return matches; | ||
} | ||
|
||
void filterData(NetworkFilter filter) { | ||
if (filter == defaultFilter) { | ||
_filteredRequests.value = List.from(_requests.value.requests); | ||
} | ||
_filteredRequests.value = | ||
_requests.value.requests.where((NetworkRequest r) { | ||
if (!filter.showHttp && r is HttpRequestData) { | ||
return false; | ||
} | ||
if (!filter.showWebSocket && r is WebSocket) { | ||
return false; | ||
} | ||
if (filter.method != null && | ||
r.method.toLowerCase() != filter.method.toLowerCase()) { | ||
return false; | ||
} | ||
if (filter.uriSubstring != null && | ||
!r.uri.toLowerCase().contains(filter.uriSubstring.toLowerCase())) { | ||
return false; | ||
} | ||
if (filter.status != null && | ||
r.status.toLowerCase() != filter.status.toLowerCase()) { | ||
return false; | ||
} | ||
if (filter.type != null && | ||
r.type.toLowerCase() != filter.type.toLowerCase()) { | ||
return false; | ||
} | ||
return true; | ||
}).toList(); | ||
_activeFilter.value = filter; | ||
} | ||
|
||
void resetFilters() { | ||
_activeFilter.value = defaultFilter; | ||
} | ||
} | ||
|
||
class NetworkFilter { | ||
NetworkFilter({ | ||
this.method, | ||
this.uriSubstring, | ||
this.status, | ||
this.type, | ||
this.showHttp = true, | ||
this.showWebSocket = true, | ||
}); | ||
|
||
static NetworkFilter from(NetworkFilter filter) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: make a factory constructor even though that won't change anything. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
return NetworkFilter( | ||
method: filter.method, | ||
uriSubstring: filter.uriSubstring, | ||
status: filter.status, | ||
type: filter.type, | ||
showHttp: filter.showHttp, | ||
showWebSocket: filter.showWebSocket, | ||
); | ||
} | ||
|
||
String method; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can these all be final? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no because they need to be modified when adjusting the filter in the dialog. We could make a |
||
|
||
String uriSubstring; | ||
|
||
String status; | ||
|
||
String type; | ||
|
||
bool showHttp; | ||
|
||
bool showWebSocket; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this a "suffix" button?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's for the
suffix
parameter of an InputDecoration. Maybe I should rename the method to inputDecorationSuffixButton