mirror of
https://github.com/lichess-org/mobile.git
synced 2026-05-26 13:50:52 +00:00
Add search to HTTP logs (#2852)
This commit is contained in:
@@ -9,22 +9,30 @@ part 'http_log_paginator.freezed.dart';
|
||||
const _pageSize = 20;
|
||||
|
||||
/// A provider for [HttpLogPaginator].
|
||||
final httpLogPaginatorProvider = AsyncNotifierProvider.autoDispose<HttpLogPaginator, HttpLogState>(
|
||||
HttpLogPaginator.new,
|
||||
name: 'HttpLogPaginatorProvider',
|
||||
);
|
||||
///
|
||||
/// The family argument is the optional search query string.
|
||||
final httpLogPaginatorProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<HttpLogPaginator, HttpLogState, String?>(
|
||||
HttpLogPaginator.new,
|
||||
name: 'HttpLogPaginatorProvider',
|
||||
);
|
||||
|
||||
/// A Riverpod controller for managing HTTP logs.
|
||||
///
|
||||
/// The `HttpLogController` class is responsible for fetching and managing
|
||||
/// paginated HTTP log entries from the storage. It uses a throttler to limit
|
||||
/// the rate of fetching new pages.
|
||||
class HttpLogPaginator extends AsyncNotifier<HttpLogState> {
|
||||
HttpLogPaginator(this._searchQuery);
|
||||
|
||||
final String? _searchQuery;
|
||||
|
||||
@override
|
||||
Future<HttpLogState> build() async {
|
||||
final storage = await ref.read(httpLogStorageProvider.future);
|
||||
return HttpLogState(
|
||||
data: IList.new([await AsyncValue.guard(() => storage.page(limit: _pageSize))]),
|
||||
data: IList.new([
|
||||
await AsyncValue.guard(() => storage.page(limit: _pageSize, searchQuery: _searchQuery)),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,7 +44,11 @@ class HttpLogPaginator extends AsyncNotifier<HttpLogState> {
|
||||
if (state.hasValue && state.requireValue.hasMore) {
|
||||
final storage = await ref.read(httpLogStorageProvider.future);
|
||||
final asyncPage = await AsyncValue.guard(
|
||||
() => storage.page(limit: _pageSize, cursor: state.requireValue.nextPage),
|
||||
() => storage.page(
|
||||
limit: _pageSize,
|
||||
cursor: state.requireValue.nextPage,
|
||||
searchQuery: _searchQuery,
|
||||
),
|
||||
);
|
||||
state = AsyncValue.data(
|
||||
state.requireValue.copyWith(data: state.requireValue.data.add(asyncPage)),
|
||||
|
||||
@@ -22,12 +22,28 @@ class HttpLogStorage {
|
||||
final Database _db;
|
||||
|
||||
/// Retrieves a paginated list of [HttpLogEntry] entries from the database.
|
||||
Future<HttpLog> page({int? cursor, int limit = 100}) async {
|
||||
///
|
||||
/// [searchQuery] filters entries whose request method, URL, or error message contain the query.
|
||||
Future<HttpLog> page({int? cursor, String? searchQuery, int limit = 100}) async {
|
||||
final whereParts = <String>[];
|
||||
final args = <dynamic>[];
|
||||
|
||||
if (cursor != null) {
|
||||
whereParts.add('id <= ?');
|
||||
args.add(cursor);
|
||||
}
|
||||
if (searchQuery != null && searchQuery.isNotEmpty) {
|
||||
whereParts.add('(requestMethod LIKE ? OR requestUrl LIKE ? OR errorMessage LIKE ?)');
|
||||
final pattern = '%$searchQuery%';
|
||||
args.addAll([pattern, pattern, pattern]);
|
||||
}
|
||||
|
||||
final res = await _db.query(
|
||||
kHttpLogStorageTable,
|
||||
limit: limit + 1,
|
||||
orderBy: 'id DESC',
|
||||
where: cursor != null ? 'id <= $cursor' : null,
|
||||
where: whereParts.isNotEmpty ? whereParts.join(' AND ') : null,
|
||||
whereArgs: args.isNotEmpty ? args : null,
|
||||
);
|
||||
return HttpLog(
|
||||
items: res.take(limit).map(HttpLogEntry.fromJson).toIList(),
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:lichess_mobile/src/styles/styles.dart';
|
||||
import 'package:lichess_mobile/src/utils/navigation.dart';
|
||||
import 'package:lichess_mobile/src/widgets/adaptive_action_sheet.dart';
|
||||
import 'package:lichess_mobile/src/widgets/haptic_refresh_indicator.dart';
|
||||
import 'package:lichess_mobile/src/widgets/platform_search_bar.dart';
|
||||
|
||||
class HttpLogScreen extends ConsumerStatefulWidget {
|
||||
const HttpLogScreen({super.key});
|
||||
@@ -23,6 +24,8 @@ class HttpLogScreen extends ConsumerStatefulWidget {
|
||||
class _HttpLogScreenState extends ConsumerState<HttpLogScreen> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
String? _searchQuery;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -34,26 +37,27 @@ class _HttpLogScreenState extends ConsumerState<HttpLogScreen> {
|
||||
void dispose() {
|
||||
_scrollController.removeListener(_scrollListener);
|
||||
_scrollController.dispose();
|
||||
_searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _scrollListener() {
|
||||
if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 300) {
|
||||
final currentState = ref.read(httpLogPaginatorProvider);
|
||||
final currentState = ref.read(httpLogPaginatorProvider(_searchQuery));
|
||||
if (currentState.hasValue && !currentState.isLoading && currentState.requireValue.hasMore) {
|
||||
ref.read(httpLogPaginatorProvider.notifier).next();
|
||||
ref.read(httpLogPaginatorProvider(_searchQuery).notifier).next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onRefresh() async {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 300));
|
||||
return ref.read(httpLogPaginatorProvider.notifier).refresh();
|
||||
return ref.read(httpLogPaginatorProvider(_searchQuery).notifier).refresh();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final asyncState = ref.watch(httpLogPaginatorProvider);
|
||||
final asyncState = ref.watch(httpLogPaginatorProvider(_searchQuery));
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('HTTP logs'),
|
||||
@@ -68,11 +72,29 @@ class _HttpLogScreenState extends ConsumerState<HttpLogScreen> {
|
||||
context,
|
||||
// TODO localize
|
||||
title: const Text('Delete all logs'),
|
||||
onConfirm: () => ref.read(httpLogPaginatorProvider.notifier).deleteAll(),
|
||||
onConfirm: () =>
|
||||
ref.read(httpLogPaginatorProvider(_searchQuery).notifier).deleteAll(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(60.0),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
child: PlatformSearchBar(
|
||||
controller: _searchController,
|
||||
hintText: 'Search logs...',
|
||||
onChanged: (value) => setState(() {
|
||||
_searchQuery = value.isEmpty ? null : value;
|
||||
}),
|
||||
onClear: () => setState(() {
|
||||
_searchQuery = null;
|
||||
_searchController.clear();
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: _HttpLogList(
|
||||
scrollController: _scrollController,
|
||||
|
||||
Reference in New Issue
Block a user