Skip to content

Commit ae2bd0f

Browse files
committed
Saad Stat Tracking #1
1 parent 55ed86a commit ae2bd0f

12 files changed

+460
-19
lines changed

example/pubspec.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ dependencies:
1616
codelessly_sdk:
1717
path: ../
1818

19+
dependency_overrides:
20+
archive: ^3.6.1
21+
win32: ^5.5.3
22+
1923
dev_dependencies:
2024
flutter_test:
2125
sdk: flutter

lib/codelessly_sdk.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export 'src/data/web_data_repository.dart';
1717
export 'src/functions/functions.dart';
1818
export 'src/logging/codelessly_logger.dart';
1919
export 'src/logging/error_handler.dart';
20+
export 'src/logging/stat_tracker.dart';
2021
export 'src/model/asset_model.dart';
2122
export 'src/model/auth_data.dart';
2223
export 'src/model/custom_component.dart';

lib/src/codelessly.dart

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ class Codelessly {
128128
final StreamController<CStatus> _statusStreamController =
129129
StreamController.broadcast()..add(CStatus.empty());
130130

131+
final StatTracker _tracker = FirestoreStatTracker();
132+
133+
/// Tracks statistics of various operations in the SDK.
134+
StatTracker get tracker => _tracker;
135+
131136
/// Returns a stream of status updates for this SDK instance.
132137
Stream<CStatus> get statusStream => _statusStreamController.stream;
133138

@@ -557,11 +562,16 @@ class Codelessly {
557562
cacheManager: _cacheManager!,
558563
authManager: _authManager!,
559564
networkDataRepository: FirebaseDataRepository(
560-
firestore: firebaseFirestore, config: _config!),
561-
localDataRepository:
562-
LocalDataRepository(cacheManager: _cacheManager!),
565+
firestore: firebaseFirestore,
566+
tracker: tracker,
567+
config: _config!,
568+
),
569+
localDataRepository: LocalDataRepository(
570+
cacheManager: _cacheManager!,
571+
),
563572
firebaseFirestore: firebaseFirestore,
564573
errorHandler: errorHandler,
574+
tracker: tracker,
565575
);
566576

567577
// Create the preview data manager.
@@ -572,11 +582,16 @@ class Codelessly {
572582
cacheManager: _cacheManager!,
573583
authManager: _authManager!,
574584
networkDataRepository: FirebaseDataRepository(
575-
firestore: firebaseFirestore, config: _config!),
576-
localDataRepository:
577-
LocalDataRepository(cacheManager: _cacheManager!),
585+
firestore: firebaseFirestore,
586+
config: _config!,
587+
tracker: tracker,
588+
),
589+
localDataRepository: LocalDataRepository(
590+
cacheManager: _cacheManager!,
591+
),
578592
firebaseFirestore: firebaseFirestore,
579593
errorHandler: errorHandler,
594+
tracker: tracker,
580595
);
581596

582597
// Create the template data manager. This is always automatically
@@ -591,11 +606,16 @@ class Codelessly {
591606
cacheManager: _cacheManager!,
592607
authManager: _authManager!,
593608
networkDataRepository: FirebaseDataRepository(
594-
firestore: firebaseFirestore, config: _config!),
595-
localDataRepository:
596-
LocalDataRepository(cacheManager: _cacheManager!),
609+
firestore: firebaseFirestore,
610+
config: _config!,
611+
tracker: tracker,
612+
),
613+
localDataRepository: LocalDataRepository(
614+
cacheManager: _cacheManager!,
615+
),
597616
firebaseFirestore: firebaseFirestore,
598617
errorHandler: errorHandler,
618+
tracker: tracker,
599619
);
600620

601621
_updateStatus(CStatus.loading(CLoadingState.createdManagers));
@@ -620,6 +640,10 @@ class Codelessly {
620640
await _authManager!.init();
621641
_config!.publishSource = _authManager!.getBestPublishSource(_config!);
622642

643+
if (_authManager?.authData?.projectId case String projectId) {
644+
tracker.init(projectId);
645+
}
646+
623647
_updateStatus(CStatus.loading(CLoadingState.initializedAuth));
624648
} else {
625649
log('A slug was provided. Acutely skipping authentication.');
@@ -667,6 +691,10 @@ class Codelessly {
667691
_authManager!.init().then((_) async {
668692
if (_authManager!.authData == null) return;
669693

694+
if (_authManager?.authData?.projectId case String projectId) {
695+
tracker.init(projectId);
696+
}
697+
670698
log('[POST-INIT] Background authentication succeeded. Initializing layout storage.');
671699
await dataManager
672700
.onPublishModelLoaded(_authManager!.authData!.projectId);

lib/src/constants.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ const String publishPath = 'publish';
5353
const String previewPath = 'publish_preview';
5454
const String templatesPath = 'templates';
5555

56+
/// STAT TRACKING
57+
const String statsCollection = 'stats';
58+
const String lostStatsDoc = '_lost';
59+
const String writesField = 'writes';
60+
const String readsField = 'reads';
61+
const String bundleDownloadsField = 'bundle_downloads';
62+
const String fontDownloadsField = 'font_downloads';
63+
const String actionsField = 'actions';
64+
const String cloudActionsField = 'cloud_actions';
65+
5666
/// The template url for the svg icons.
5767
/// {{style}}: The style of the icon. e.g. materialiconsoutlined
5868
/// {{name}}: The name of the icon. e.g. home

lib/src/data/cloud_database.dart

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import 'dart:async';
22

33
import 'package:cloud_firestore/cloud_firestore.dart';
44
import 'package:codelessly_api/codelessly_api.dart';
5-
import 'package:flutter/cupertino.dart';
5+
import 'package:flutter/widgets.dart';
66

77
import '../../codelessly_sdk.dart';
88
import '../utils/constants.dart';
@@ -41,6 +41,8 @@ abstract class CloudDatabase extends ChangeNotifier {
4141
/// This cannot be an empty string.
4242
final String identifier;
4343

44+
/// The source of the publish, this is used to determine the root collection
45+
/// to use for the cloud database.
4446
final PublishSource publishSource;
4547

4648
/// Creates a new instance of with the given [identifier].
@@ -195,12 +197,20 @@ class FirestoreCloudDatabase extends CloudDatabase {
195197
/// Reference to the Firestore instance.
196198
final FirebaseFirestore firestore;
197199

200+
/// Reference to the StatTracker instance, used to track reads and writes.
201+
final StatTracker tracker;
202+
198203
/// Subscriptions to the streams that are being listened to.
199204
final List<StreamSubscription> _subscriptions = [];
200205

201206
/// Creates a [FirestoreCloudDatabase] with the given [identifier] and
202207
/// [firestore] instance.
203-
FirestoreCloudDatabase(super.identifier, this.firestore, super.publishSource);
208+
FirestoreCloudDatabase(
209+
super.identifier,
210+
super.publishSource, {
211+
required this.firestore,
212+
required this.tracker,
213+
});
204214

205215
void log(String message) => logger.log(_label, message);
206216

@@ -213,12 +223,14 @@ class FirestoreCloudDatabase extends CloudDatabase {
213223

214224
// Create project doc if missing.
215225
final snapshot = await rootRef.get();
226+
tracker.trackRead();
216227

217228
// Do nothing if project doc exists.
218229
if (snapshot.exists) return;
219230

220231
// Create project doc if it does not exist.
221232
await rootRef.set({'project': identifier});
233+
tracker.trackWrite();
222234

223235
logger.log(_label, 'Done initializing for $identifier');
224236
}
@@ -295,6 +307,8 @@ class FirestoreCloudDatabase extends CloudDatabase {
295307
if (autoGenerateId) {
296308
// if autoGenerateId is true, then skipCreationIfDocumentExists and docId is ignored.
297309
final document = await rootRef.collection(path).add(value);
310+
tracker.trackWrite();
311+
298312
logger.log(_label, 'Document added: ${document.path}');
299313

300314
// Sanitize data afterwards because we didn't have the docId before.
@@ -313,6 +327,8 @@ class FirestoreCloudDatabase extends CloudDatabase {
313327

314328
// Get snapshot to check if document exists.
315329
final snapshot = await docRef.get();
330+
tracker.trackRead();
331+
316332
if (skipCreationIfDocumentExists && snapshot.exists) {
317333
// if skipCreationIfDocumentExists is true, check if document exists.
318334
// if document exists, then return.
@@ -322,6 +338,8 @@ class FirestoreCloudDatabase extends CloudDatabase {
322338

323339
// Set document.
324340
await docRef.set(value);
341+
tracker.trackWrite();
342+
325343
logger.log(_label, 'Document added: ${docRef.path}/$documentId');
326344
return true;
327345
}
@@ -344,36 +362,50 @@ class FirestoreCloudDatabase extends CloudDatabase {
344362

345363
// TODO: Should we do update instead of set?
346364
await docRef.set(value, SetOptions(merge: true));
365+
tracker.trackWrite();
366+
347367
logger.log(_label, 'Document updated: ${docRef.path}');
348368
return true;
349369
}
350370

351371
@override
352372
Future<bool> removeDocument(String path, String documentId) async {
353373
final docRef = getDocPath(path, documentId);
374+
354375
final snapshot = await docRef.get();
376+
tracker.trackRead();
377+
355378
// TODO: Do we have to check for existence?
356379
if (!snapshot.exists) return false;
380+
357381
await docRef.delete();
382+
tracker.trackWrite();
383+
358384
return true;
359385
}
360386

361387
@override
362388
Future<Map<String, dynamic>> getDocumentData(
363389
String path, String documentId) async {
364390
final docRef = getDocPath(path, documentId);
391+
365392
final snapshot = await docRef.get();
393+
tracker.trackRead();
394+
366395
final data = snapshot.data() ?? {};
367396
return sanitizeCloudDataForUse(data, docId: snapshot.id);
368397
}
369398

370399
@override
371400
Stream<Map<String, dynamic>> streamDocument(String path, String documentId) {
372401
final docRef = getDocPath(path, documentId);
373-
return docRef.snapshots().map((snapshot) =>
374-
snapshot.data()?.let(
375-
(value) => sanitizeCloudDataForUse(value, docId: snapshot.id)) ??
376-
{});
402+
return docRef.snapshots().map((snapshot) {
403+
tracker.trackRead();
404+
405+
return snapshot.data()?.let(
406+
(value) => sanitizeCloudDataForUse(value, docId: snapshot.id)) ??
407+
{};
408+
});
377409
}
378410

379411
@override
@@ -391,6 +423,8 @@ class FirestoreCloudDatabase extends CloudDatabase {
391423
// Listen to the stream and update the variable.
392424
final subscription = stream.listen(
393425
(data) {
426+
tracker.trackRead();
427+
394428
logger.log(_label,
395429
'Document stream update from cloud storage: $path/$documentId');
396430
logger.log(_label,
@@ -466,6 +500,8 @@ class FirestoreCloudDatabase extends CloudDatabase {
466500
// Listen to the stream and update the variable.
467501
final subscription = stream.listen(
468502
(snapshot) {
503+
tracker.trackRead();
504+
469505
final docs = snapshot.docs
470506
.map((doc) => sanitizeCloudDataForUse(doc.data(), docId: doc.id))
471507
.toList();

lib/src/data/data_manager.dart

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,32 @@ class DataManager {
4646
/// The auth manager to use. By default, it is [CodelesslyAuthManager].
4747
final AuthManager authManager;
4848

49+
// TODO(Saad): This is only used to initialize CloudDatabase. We may be able
50+
// to decouple it.
4951
/// The firestore instance to use.
5052
final FirebaseFirestore firebaseFirestore;
5153

5254
/// The error handler to use.
5355
final CodelesslyErrorHandler errorHandler;
5456

57+
// TODO(Saad): This is only used to initialize CloudDatabase. We may be able
58+
// to decouple it.
59+
/// The stat tracker to use, used to track various reads and writes in this
60+
/// data manager.
61+
final StatTracker tracker;
62+
5563
SDKPublishModel? _publishModel;
5664

65+
// TODO(Saad): LocalDatabase is only initialized and never truly accessed by
66+
// the DataManager itself. We can decouple it by moving it to the
67+
// [Codelessly] level. Maybe through a new shared manager for it and
68+
// CloudDatabase.
5769
LocalDatabase? _localDatabase;
5870

71+
// TODO(Saad): CloudDatabase is only initialized and never truly accessed by
72+
// the DataManager itself. We can decouple it by moving it to the
73+
// [Codelessly] level. Maybe through a new shared manager for it and
74+
// LocalDatabase.
5975
CloudDatabase? _cloudDatabase;
6076

6177
/// The local storage instance used by this data manager.
@@ -102,6 +118,7 @@ class DataManager {
102118
required this.localDataRepository,
103119
required this.firebaseFirestore,
104120
required this.errorHandler,
121+
required this.tracker,
105122
SDKPublishModel? publishModel,
106123
}) : _publishModel = publishModel;
107124

@@ -479,12 +496,17 @@ class DataManager {
479496
return HiveLocalDatabase(box, identifier: projectId);
480497
}
481498

499+
// TODO(Saad): We can de-couple [firebaseFirestore] and [tracker] from
500+
// [DataManager] by delegating this initialization to the creator of
501+
// [DataManager], maybe through a callback so that DataManager can decide
502+
// exactly when to initialize this.
482503
Future<CloudDatabase> initializeCloudStorage(
483504
{required String projectId}) async {
484505
final instance = FirestoreCloudDatabase(
485506
projectId,
486-
firebaseFirestore,
487507
config.publishSource,
508+
firestore: firebaseFirestore,
509+
tracker: tracker,
488510
);
489511
// initialize cloud storage.
490512
await instance.init();

0 commit comments

Comments
 (0)