• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wger-project / flutter / 30901196754

04 Aug 2026 10:34AM UTC coverage: 51.289% (-1.8%) from 53.103%
30901196754

Pull #1301

github

web-flow
Merge 5c77a99bf into e3affdd4f
Pull Request #1301: feat : implemented dynamic colour support

98 of 138 new or added lines in 24 files covered. (71.01%)

1088 existing lines in 87 files now uncovered.

11659 of 22732 relevant lines covered (51.29%)

5.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.45
/lib/powersync/sync_diagnostics.dart
1
/*
2
 * This file is part of wger Workout Manager <https://github.com/wger-project>.
3
 * Copyright (c) 2026 wger Team
4
 *
5
 * wger Workout Manager is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU Affero General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU Affero General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Affero General Public License
16
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18

19
import 'package:logging/logging.dart';
20
import 'package:powersync/powersync.dart'
21
    show CredentialsException, PowerSyncProtocolException, SyncResponseException, SyncStatus;
22
import 'package:wger/core/consts.dart';
23
import 'package:wger/database/powersync/powersync.dart' show builtPowerSyncInstance;
24
import 'package:wger/powersync/connector.dart' show RetryableUploadException;
25

26
final _logger = Logger('sync_diagnostics');
×
27

28
/// Maps an HTTP status code to a short English category label.
29
String _categoriseHttpStatus(int statusCode) {
2✔
30
  if (statusCode == 401 || statusCode == 403) {
3✔
31
    return 'Authentication error';
32
  }
33
  if (statusCode >= 500) {
1✔
34
    return 'Server error';
35
  }
36
  return 'HTTP $statusCode';
1✔
37
}
38

39
/// Classifies a sync error into a short English category label
40
String? categoriseSyncError(Object error) {
3✔
41
  if (error is CredentialsException) {
3✔
42
    return 'Authentication error';
43
  }
44

45
  if (error is PowerSyncProtocolException) {
3✔
46
    return 'Protocol error';
47
  }
48

49
  if (error is SyncResponseException) {
3✔
50
    return _categoriseHttpStatus(error.statusCode);
4✔
51
  }
52
  if (error is RetryableUploadException) {
3✔
53
    return _categoriseHttpStatus(error.statusCode);
2✔
54
  }
55

56
  final typeName = error.runtimeType.toString();
6✔
57
  if (typeName.endsWith('SocketException') ||
3✔
58
      typeName == 'WebSocketChannelException' ||
3✔
59
      typeName == 'ClientException' ||
3✔
60
      typeName == 'HttpException') {
3✔
61
    return 'Connection error';
62
  }
63

64
  return null;
65
}
66

67
/// Reduces [serverUrl] to a category for bug reports, so self-hosted URLs
68
/// stay private. Null when the URL is unknown.
UNCOV
69
String? serverCategory(String? serverUrl) {
×
70
  return switch (serverUrl) {
71
    null => null,
UNCOV
72
    DEFAULT_SERVER_PROD => 'wger.de',
×
UNCOV
73
    DEFAULT_SERVER_TEST => 'dev.wger.de',
×
74
    _ => 'self-hosted',
75
  };
76
}
77

78
/// Renders a compact sync-state summary for bug reports.
79
String formatSyncDiagnostics(
2✔
80
  SyncStatus status, {
81
  required int pendingUploads,
82
  String? server,
83
}) {
84
  final buffer = StringBuffer();
2✔
85
  if (server != null) {
UNCOV
86
    buffer.writeln('server: $server');
×
87
  }
88
  buffer
89
    ..writeln(
4✔
90
      'connected: ${status.connected}, connecting: ${status.connecting}, '
4✔
91
      'downloading: ${status.downloading}, uploading: ${status.uploading}',
4✔
92
    )
93
    ..writeln('last successful sync: ${status.lastSyncedAt?.toUtc().toIso8601String() ?? 'never'}')
6✔
94
    ..writeln('pending uploads: $pendingUploads');
4✔
95
  if (status.downloadProgress case final progress?) {
2✔
96
    buffer.writeln(
×
97
      'download progress: ${progress.downloadedOperations} / ${progress.totalOperations}',
×
98
    );
99
  }
100
  if (status.anyError case final error?) {
2✔
101
    // Clamped: some exception toStrings embed whole response bodies, and
102
    // the report has to fit into a GitHub issue URL.
103
    final text = error.toString();
1✔
104
    final clamped = text.length <= 300 ? text : '${text.substring(0, 300)}…';
4✔
105
    buffer.writeln('error (${categoriseSyncError(error) ?? 'Uncategorised'}): $clamped');
3✔
106
  }
107
  return buffer.toString().trimRight();
4✔
108
}
109

110
/// Snapshot of the current sync state for bug reports, or null when the
111
/// PowerSync database has not been initialised.
112
///
113
/// Never throws: this runs on report paths where the app may already be in
114
/// a broken state (e.g. the DB is closing down), and a missing sync section
115
/// must not prevent the report itself.
116
Future<String?> collectSyncDiagnostics({String? serverUrl}) async {
1✔
117
  final db = builtPowerSyncInstance;
1✔
118
  if (db == null) {
119
    return null;
120
  }
121
  try {
122
    final queue = await db.getUploadQueueStats();
×
123
    return formatSyncDiagnostics(
×
124
      db.currentStatus,
×
125
      pendingUploads: queue.count,
×
126
      server: serverCategory(serverUrl),
×
127
    );
128
  } catch (e, s) {
129
    _logger.warning('Could not collect sync diagnostics', e, s);
×
130
    return null;
131
  }
132
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc