Skip to content

Commit

Permalink
Make filtering easier to access from the logging page (#8427)
Browse files Browse the repository at this point in the history
  • Loading branch information
kenzieschmoll authored Oct 14, 2024
1 parent 46dd33e commit b09d0a0
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -118,41 +118,48 @@ class LoggingController extends DisposableController

/// The toggle filters available for the Logging screen.
@override
List<ToggleFilter<LogData>> createToggleFilters() => [
if (serviceConnection.serviceManager.connectedApp?.isFlutterAppNow ??
true) ...[
ToggleFilter<LogData>(
name: 'Hide verbose Flutter framework logs (initialization, frame '
'times, image sizes)',
includeCallback: (log) => !_verboseFlutterFrameworkLogKinds
.any((kind) => kind.caseInsensitiveEquals(log.kind)),
enabledByDefault: true,
),
ToggleFilter<LogData>(
name: 'Hide verbose Flutter service logs (service extension state '
'changes)',
includeCallback: (log) => !_verboseFlutterServiceLogKinds
.any((kind) => kind.caseInsensitiveEquals(log.kind)),
enabledByDefault: true,
),
],
ToggleFilter<LogData>(
name: 'Hide garbage collection logs',
includeCallback: (log) => !log.kind.caseInsensitiveEquals(_gcLogKind),
enabledByDefault: true,
),
];
List<ToggleFilter<LogData>> createToggleFilters() => toggleFilters;

@visibleForTesting
static final toggleFilters = <ToggleFilter<LogData>>[
if (serviceConnection.serviceManager.connectedApp?.isFlutterAppNow ??
true) ...[
ToggleFilter<LogData>(
name: 'Hide verbose Flutter framework logs (initialization, frame '
'times, image sizes)',
includeCallback: (log) => !_verboseFlutterFrameworkLogKinds
.any((kind) => kind.caseInsensitiveEquals(log.kind)),
enabledByDefault: true,
),
ToggleFilter<LogData>(
name: 'Hide verbose Flutter service logs (service extension state '
'changes)',
includeCallback: (log) => !_verboseFlutterServiceLogKinds
.any((kind) => kind.caseInsensitiveEquals(log.kind)),
enabledByDefault: true,
),
],
ToggleFilter<LogData>(
name: 'Hide garbage collection logs',
includeCallback: (log) => !log.kind.caseInsensitiveEquals(_gcLogKind),
enabledByDefault: true,
),
];

static const kindFilterId = 'logging-kind-filter';

@override
Map<String, QueryFilterArgument<LogData>> createQueryFilterArgs() => {
kindFilterId: QueryFilterArgument<LogData>(
keys: ['kind', 'k'],
dataValueProvider: (log) => log.kind,
substringMatch: true,
),
};
Map<String, QueryFilterArgument<LogData>> createQueryFilterArgs() =>
queryFilterArgs;

@visibleForTesting
static final queryFilterArgs = <String, QueryFilterArgument<LogData>>{
kindFilterId: QueryFilterArgument<LogData>(
keys: ['kind', 'k'],
dataValueProvider: (log) => log.kind,
substringMatch: true,
),
};

final _logStatusController = StreamController<String>.broadcast();

Expand Down
35 changes: 14 additions & 21 deletions packages/devtools_app/lib/src/screens/logging/logging_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,30 +45,23 @@ Example queries:
gaSelection: gac.clear,
minScreenWidthForTextBeforeScaling: loggingMinVerboseWidth,
),
const Spacer(),
const SizedBox(width: denseSpacing),
// TODO(kenz): fix focus issue when state is refreshed
SearchField<LoggingController>(
searchFieldWidth: isScreenWiderThan(context, loggingMinVerboseWidth)
? wideSearchFieldWidth
: defaultSearchFieldWidth,
searchController: controller,
searchFieldEnabled: hasData,
Expanded(
// TODO(kenz): fix focus issue when state is refreshed
child: SearchField<LoggingController>(
searchFieldWidth: isScreenWiderThan(context, loggingMinVerboseWidth)
? wideSearchFieldWidth
: defaultSearchFieldWidth,
searchController: controller,
searchFieldEnabled: hasData,
containerPadding: EdgeInsets.zero,
),
),
const SizedBox(width: denseSpacing),
DevToolsFilterButton(
onPressed: () {
unawaited(
showDialog(
context: context,
builder: (context) => FilterDialog<LogData>(
controller: controller,
queryInstructions: filterQueryInstructions,
),
),
);
},
isFilterActive: controller.isFilterActive,
Expanded(
child: StandaloneFilterField<LogData>(
controller: controller,
),
),
const SizedBox(width: denseSpacing),
CopyToClipboardControl(
Expand Down
46 changes: 29 additions & 17 deletions packages/devtools_app/lib/src/shared/ui/filter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -513,8 +513,36 @@ class _StandaloneFilterFieldState<T> extends State<StandaloneFilterField<T>>
builder: (context, useRegExp, _) {
return DevToolsClearableTextField(
autofocus: true,
labelText: 'Filter',
hintText: 'Filter',
controller: queryTextFieldController,
prefixIcon: Container(
height: inputDecorationElementHeight,
padding: const EdgeInsets.only(
left: densePadding,
right: denseSpacing,
),
child: ValueListenableBuilder<Filter>(
valueListenable: widget.controller.activeFilter,
builder: (context, _, __) {
// TODO(https://github.com/flutter/devtools/issues/8426): support filtering by log level.
return DevToolsFilterButton(
message: 'More filters',
onPressed: () {
unawaited(
showDialog(
context: context,
builder: (context) => FilterDialog(
controller: widget.controller,
includeQueryFilter: false,
),
),
);
},
isFilterActive: widget.controller.isFilterActive,
);
},
),
),
additionalSuffixActions: [
DevToolsToggleButton(
icon: Codicons.regex,
Expand All @@ -540,22 +568,6 @@ class _StandaloneFilterFieldState<T> extends State<StandaloneFilterField<T>>
},
),
),
const SizedBox(width: defaultSpacing),
DevToolsFilterButton(
message: 'Filter Settings',
onPressed: () {
unawaited(
showDialog(
context: context,
builder: (context) => FilterDialog(
controller: widget.controller,
includeQueryFilter: false,
),
),
);
},
isFilterActive: false,
),
],
);
}
Expand Down
61 changes: 40 additions & 21 deletions packages/devtools_app/lib/src/shared/ui/search.dart
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,7 @@ class _SearchFieldState extends State<SearchField>
shouldRequestFocus: widget.shouldRequestFocus,
supportsNavigation: widget.supportsNavigation,
onClose: widget.onClose,
searchFieldHeight: widget.searchFieldHeight,
),
);
}
Expand All @@ -976,14 +977,15 @@ class StatelessSearchField<T extends SearchableDataMixin>
required this.searchFieldEnabled,
required this.shouldRequestFocus,
this.searchFieldKey,
this.label = 'Search',
this.label,
this.decoration,
this.supportsNavigation = false,
this.onClose,
this.onChanged,
this.prefix,
this.suffix,
this.style,
this.searchFieldHeight,
});

final SearchControllerMixin<T> controller;
Expand All @@ -1000,7 +1002,7 @@ class StatelessSearchField<T extends SearchableDataMixin>
final bool supportsNavigation;

/// Label for the search field's input text decoration.
final String label;
final String? label;

/// Optional callback called when the search field suffix close action is
/// triggered.
Expand All @@ -1026,23 +1028,27 @@ class StatelessSearchField<T extends SearchableDataMixin>
/// Optional key for the search text field.
final GlobalKey? searchFieldKey;

final double? searchFieldHeight;

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final textStyle = style ?? theme.regularTextStyle;

void onChanged(String value) {
this.onChanged?.call(value);
controller.search = value;
controller.searchFieldFocusNode?.requestFocus();
}

final searchField = TextField(
key: searchFieldKey,
autofocus: true,
enabled: searchFieldEnabled,
focusNode: controller.searchFieldFocusNode,
controller: controller.searchTextFieldController,
style: textStyle,
onChanged: (value) {
onChanged?.call(value);
controller.search = value;
controller.searchFieldFocusNode?.requestFocus();
},
onChanged: onChanged,
onEditingComplete: () {
controller.searchFieldFocusNode?.requestFocus();
},
Expand All @@ -1054,14 +1060,18 @@ class StatelessSearchField<T extends SearchableDataMixin>
// snapshots will compare with the exact color.
decoration: decoration ??
InputDecoration(
constraints: BoxConstraints(
minHeight: searchFieldHeight ?? defaultTextFieldHeight,
),
contentPadding: const EdgeInsets.symmetric(
horizontal: denseSpacing,
vertical: densePadding,
horizontal: densePadding,
),
border: const OutlineInputBorder(),
hintText: 'Search',
hintStyle: theme.subtleTextStyle,
labelText: label,
labelStyle: theme.subtleTextStyle,
// TODO(kenz): add the search icon to the search field.
prefixIcon: Icon(Icons.search, size: defaultIconSize),
prefix: prefix != null
? Row(
mainAxisSize: MainAxisSize.min,
Expand All @@ -1080,13 +1090,14 @@ class StatelessSearchField<T extends SearchableDataMixin>
)
: null,
suffix: suffix ??
((supportsNavigation || onClose != null)
? _SearchFieldSuffix(
controller: controller,
supportsNavigation: supportsNavigation,
onClose: onClose,
)
: null),
_SearchFieldSuffix(
controller: controller,
supportsNavigation: supportsNavigation,
onClose: () {
onClose?.call();
onChanged('');
},
),
),
);

Expand Down Expand Up @@ -1349,10 +1360,14 @@ class _SearchFieldSuffix extends StatelessWidget {

@override
Widget build(BuildContext context) {
assert(supportsNavigation || onClose != null);
return supportsNavigation
? SearchNavigationControls(controller, onClose: onClose)
: InputDecorationSuffixButton.close(onPressed: onClose);
: InputDecorationSuffixButton.close(
onPressed: () {
controller.searchTextFieldController.clear();
onClose?.call();
},
);
}
}

Expand Down Expand Up @@ -1411,8 +1426,12 @@ class SearchNavigationControls extends StatelessWidget {
icon: Icons.keyboard_arrow_down,
onPressed: numMatches > 1 ? controller.nextMatch : null,
),
if (onClose != null)
InputDecorationSuffixButton.close(onPressed: onClose),
InputDecorationSuffixButton.close(
onPressed: () {
controller.searchTextFieldController.clear();
onClose?.call();
},
),
],
);
},
Expand Down
4 changes: 4 additions & 0 deletions packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ due to lazy loading. - [#8421](https://github.com/flutter/devtools/pull/8421)
severity. [#8419](https://github.com/flutter/devtools/pull/8419)
![Logging metadata display](images/log_metadata.png "Logging metadata display")

* Add a text filter to the top-level logging controls. -
[#8427](https://github.com/flutter/devtools/pull/8427)
![Logging filter](images/log_filter.png "Logging filter")

* Fix a bug where logs would get out of order after midnight. -
[#8420](https://github.com/flutter/devtools/pull/8420)

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 8 additions & 6 deletions packages/devtools_app/test/logging/logging_screen_data_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ void main() {
// details behaves the same for each test.
_fakeLogData = null;

mockLoggingController =
createMockLoggingControllerWithDefaults(data: fakeLogData);

when(fakeServiceConnection.serviceManager.connectedApp!.isFlutterWebAppNow)
.thenReturn(false);
when(fakeServiceConnection.serviceManager.connectedApp!.isProfileBuildNow)
Expand All @@ -53,6 +50,9 @@ void main() {
);
setGlobal(PreferencesController, PreferencesController());
setGlobal(IdeTheme, IdeTheme());

mockLoggingController =
createMockLoggingControllerWithDefaults(data: fakeLogData);
});

testWidgetsWithWindowSize(
Expand Down Expand Up @@ -110,19 +110,21 @@ void main() {
await tester.pumpAndSettle();
},
);

testWidgetsWithWindowSize(
'search field can enter text',
windowSize,
(WidgetTester tester) async {
await pumpLoggingScreen(tester);
verifyNever(mockLoggingController.clear());

final textFieldFinder = find.byType(TextField);
final textFieldFinder = find.descendant(
of: find.byType(SearchField<LoggingController>),
matching: find.byType(TextField),
);
expect(textFieldFinder, findsOneWidget);
final textField = tester.widget(textFieldFinder) as TextField;
expect(textField.enabled, isTrue);
await tester.enterText(find.byType(TextField), 'abc');
await tester.enterText(textFieldFinder, 'abc');
},
);

Expand Down
Loading

0 comments on commit b09d0a0

Please sign in to comment.