Skip to content

Commit 30e4086

Browse files
fix(cat-voices): discovery recent proposals query (#2586)
* fix: unique documents query * feat: use proposals page api for discovery recent proposals * chore: remove unused method * chore: adjust debounceTime * chore: cleanup code * chore: simplify isNotDraft check * test: proposals dao test
1 parent 108a114 commit 30e4086

File tree

9 files changed

+200
-314
lines changed

9 files changed

+200
-314
lines changed

catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/discovery/discovery_cubit.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,11 @@ class DiscoveryCubit extends Cubit<DiscoveryState> with BlocErrorEmitterMixin {
140140
_logger.fine('Building proposals subscription');
141141

142142
return _proposalService
143-
.watchLatestProposals(limit: _maxRecentProposalsCount)
143+
.watchProposalsPage(
144+
request: const PageRequest(page: 0, size: _maxRecentProposalsCount),
145+
filters: const ProposalsFilters(),
146+
)
147+
.map((event) => event.items)
144148
.distinct(listEquals)
145149
.listen(
146150
_handleProposals,

catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/database/dao/documents_dao.dart

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -336,19 +336,36 @@ class DriftDocumentsDao extends DatabaseAccessor<DriftCatalystDatabase>
336336
]);
337337

338338
if (unique) {
339-
return query.watch().map((list) {
340-
final latestVersions = <String, DocumentEntity>{};
341-
for (final entity in list) {
342-
final id = '${entity.idHi}_${entity.idLo}';
343-
if (!latestVersions.containsKey(id)) {
344-
latestVersions[id] = entity;
345-
}
346-
}
347-
if (limit != null) {
348-
return latestVersions.values.toList().take(limit).toList();
349-
}
350-
return latestVersions.values.toList();
351-
});
339+
final latestDocumentRef = alias(documents, 'latestDocumentRef');
340+
final maxVerHi = latestDocumentRef.verHi.max();
341+
final latestDocumentQuery = selectOnly(latestDocumentRef, distinct: true)
342+
..addColumns([
343+
latestDocumentRef.idHi,
344+
latestDocumentRef.idLo,
345+
maxVerHi,
346+
latestDocumentRef.verLo,
347+
])
348+
..where(latestDocumentRef.type.equalsValue(DocumentType.proposalDocument))
349+
..groupBy([latestDocumentRef.idHi + latestDocumentRef.idLo]);
350+
351+
final verSubquery = Subquery(latestDocumentQuery, 'latestDocumentRef');
352+
353+
final uniqueQuery = query.join([
354+
innerJoin(
355+
verSubquery,
356+
Expression.and([
357+
verSubquery.ref(maxVerHi).equalsExp(documents.verHi),
358+
verSubquery.ref(latestDocumentRef.verLo).equalsExp(documents.verLo),
359+
]),
360+
useColumns: false,
361+
),
362+
]);
363+
364+
if (limit != null) {
365+
uniqueQuery.limit(limit);
366+
}
367+
368+
return uniqueQuery.map((row) => row.readTable(documents)).watch();
352369
}
353370

354371
if (limit != null) {

catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/database/dao/proposals_dao.dart

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import 'package:collection/collection.dart';
1515
import 'package:drift/drift.dart';
1616
import 'package:drift/extensions/json1.dart';
1717
import 'package:equatable/equatable.dart';
18+
import 'package:rxdart/rxdart.dart';
1819

1920
@DriftAccessor(
2021
tables: [
@@ -227,6 +228,21 @@ class DriftProposalsDao extends DatabaseAccessor<DriftCatalystDatabase>
227228
return _transformRefsStreamToCount(stream, author: filters.author);
228229
}
229230

231+
@override
232+
Stream<Page<JoinedProposalEntity>> watchProposalsPage({
233+
required PageRequest request,
234+
required ProposalsFilters filters,
235+
}) async* {
236+
yield await queryProposalsPage(request: request, filters: filters);
237+
238+
yield* connection.streamQueries
239+
.updatesForSync(TableUpdateQuery.onAllTables([documents, documentsFavorites]))
240+
.debounceTime(const Duration(milliseconds: 10))
241+
.asyncMap((event) {
242+
return queryProposalsPage(request: request, filters: filters);
243+
});
244+
}
245+
230246
// TODO(damian-molinski): Make this more specialized per case.
231247
// for example proposals list does not need all versions, just count.
232248
Future<JoinedProposalEntity> _buildJoinedProposal(
@@ -279,7 +295,7 @@ class DriftProposalsDao extends DatabaseAccessor<DriftCatalystDatabase>
279295
return _getProposalsLatestAction().then(
280296
(value) {
281297
return value
282-
.where((element) => element.action.isNotDraft)
298+
.where((element) => !element.action.isDraft)
283299
.map((e) => e.proposalRef.id)
284300
.map(UuidHiLo.from);
285301
},
@@ -418,14 +434,10 @@ class DriftProposalsDao extends DatabaseAccessor<DriftCatalystDatabase>
418434
rawAction is String ? ProposalSubmissionActionDto.fromJson(rawAction) : null;
419435
final action = actionDto?.toModel();
420436

421-
if (action == null) {
422-
return null;
423-
}
424-
425437
return _ProposalActions(
426438
selfRef: selfRef,
427439
proposalRef: proposalRef,
428-
action: action,
440+
action: action ?? ProposalSubmissionAction.draft,
429441
);
430442
})
431443
.get()
@@ -614,6 +626,11 @@ abstract interface class ProposalsDao {
614626
Stream<ProposalsCount> watchCount({
615627
required ProposalsCountFilters filters,
616628
});
629+
630+
Stream<Page<JoinedProposalEntity>> watchProposalsPage({
631+
required PageRequest request,
632+
required ProposalsFilters filters,
633+
});
617634
}
618635

619636
final class _IdsFilter extends Equatable {
@@ -647,11 +664,11 @@ final class _ProposalActions extends Equatable {
647664
}
648665

649666
extension on ProposalSubmissionAction {
667+
bool get isDraft => this == ProposalSubmissionAction.draft;
668+
650669
bool get isFinal => this == ProposalSubmissionAction.aFinal;
651670

652671
bool get isHidden => this == ProposalSubmissionAction.hide;
653-
654-
bool get isNotDraft => isFinal || isHidden;
655672
}
656673

657674
extension on TypedResult {

catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/source/database_documents_data_source.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,16 @@ final class DatabaseDocumentsDataSource
153153
return _database.proposalsDao.watchCount(filters: filters);
154154
}
155155

156+
@override
157+
Stream<Page<ProposalDocumentData>> watchProposalsPage({
158+
required PageRequest request,
159+
required ProposalsFilters filters,
160+
}) {
161+
return _database.proposalsDao
162+
.watchProposalsPage(request: request, filters: filters)
163+
.map((page) => page.map((e) => e.toModel()));
164+
}
165+
156166
@override
157167
Stream<DocumentData?> watchRefToDocumentData({
158168
required DocumentRef refTo,

catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/source/proposal_document_data_local_source.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ abstract interface class ProposalDocumentDataLocalSource {
1919
required ProposalsFilters filters,
2020
});
2121

22+
Stream<Page<ProposalDocumentData>> watchProposalsPage({
23+
required PageRequest request,
24+
required ProposalsFilters filters,
25+
});
26+
2227
Stream<ProposalsCount> watchProposalsCount({
2328
required ProposalsCountFilters filters,
2429
});

catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/proposal/proposal_repository.dart

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ abstract interface class ProposalRepository {
9292
required ProposalsCountFilters filters,
9393
});
9494

95+
Stream<Page<ProposalData>> watchProposalsPage({
96+
required PageRequest request,
97+
required ProposalsFilters filters,
98+
});
99+
95100
Stream<List<ProposalDocument>> watchUserProposals({
96101
required CatalystId authorId,
97102
});
@@ -341,6 +346,16 @@ final class ProposalRepositoryImpl implements ProposalRepository {
341346
return _proposalsLocalSource.watchProposalsCount(filters: filters);
342347
}
343348

349+
@override
350+
Stream<Page<ProposalData>> watchProposalsPage({
351+
required PageRequest request,
352+
required ProposalsFilters filters,
353+
}) {
354+
return _proposalsLocalSource
355+
.watchProposalsPage(request: request, filters: filters)
356+
.map((value) => value.map(_buildProposalData));
357+
}
358+
344359
@override
345360
Stream<List<ProposalDocument>> watchUserProposals({
346361
required CatalystId authorId,
@@ -374,11 +389,8 @@ final class ProposalRepositoryImpl implements ProposalRepository {
374389
if (action == null) {
375390
return null;
376391
}
377-
final proposalAction = ProposalSubmissionActionDocumentDto.fromJson(
378-
action.content.data,
379-
).action.toModel();
380-
381-
return proposalAction;
392+
final dto = ProposalSubmissionActionDocumentDto.fromJson(action.content.data);
393+
return dto.action.toModel();
382394
}
383395

384396
ProposalData _buildProposalData(ProposalDocumentData data) {
@@ -388,8 +400,9 @@ final class ProposalRepositoryImpl implements ProposalRepository {
388400
ProposalSubmissionAction.aFinal => ProposalPublish.submittedProposal,
389401
ProposalSubmissionAction.draft || null => ProposalPublish.publishedDraft,
390402
ProposalSubmissionAction.hide => throw ArgumentError(
391-
'Unsupported ${ProposalSubmissionAction.hide}, Make sure to filter'
392-
' out hidden proposals before this code is reached.',
403+
'Proposal(${data.proposal.metadata.selfRef}) is '
404+
'unsupported ${ProposalSubmissionAction.hide}. Make sure to filter '
405+
'out hidden proposals before this code is reached.',
393406
),
394407
};
395408

catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/database/dao/proposals_dao_test.dart

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,73 @@ void main() {
10891089
},
10901090
onPlatform: driftOnPlatforms,
10911091
);
1092+
1093+
test(
1094+
'hidden proposals are filtered out when pointing to older version',
1095+
() async {
1096+
// Given
1097+
final templateRef = SignedDocumentRef.generateFirstRef();
1098+
final proposalRef = SignedDocumentRef.generateFirstRef();
1099+
final nextProposalRef = proposalRef.nextVersion().toSignedDocumentRef();
1100+
1101+
final templates = [
1102+
_buildProposalTemplate(selfRef: templateRef),
1103+
];
1104+
1105+
final proposals = [
1106+
_buildProposal(
1107+
selfRef: proposalRef,
1108+
template: templateRef,
1109+
),
1110+
_buildProposal(
1111+
selfRef: nextProposalRef,
1112+
template: templateRef,
1113+
),
1114+
];
1115+
1116+
const expectedRefs = <SignedDocumentRef>[];
1117+
1118+
final actions = <DocumentEntityWithMetadata>[
1119+
_buildProposalAction(
1120+
selfRef: _buildRefAt(DateTime(2025, 5, 2)),
1121+
action: ProposalSubmissionActionDto.aFinal,
1122+
proposalRef: proposalRef,
1123+
),
1124+
_buildProposalAction(
1125+
selfRef: _buildRefAt(DateTime(2025, 5, 20)),
1126+
action: ProposalSubmissionActionDto.hide,
1127+
proposalRef: proposalRef,
1128+
),
1129+
];
1130+
final comments = <DocumentEntityWithMetadata>[];
1131+
1132+
const filters = ProposalsFilters();
1133+
1134+
// When
1135+
await database.documentsDao.saveAll([
1136+
...templates,
1137+
...proposals,
1138+
...actions,
1139+
...comments,
1140+
]);
1141+
1142+
// Then
1143+
const request = PageRequest(page: 0, size: 25);
1144+
final page = await database.proposalsDao.queryProposalsPage(
1145+
request: request,
1146+
filters: filters,
1147+
);
1148+
1149+
expect(page.page, 0);
1150+
expect(page.total, expectedRefs.length);
1151+
1152+
final refs = page.items.map((e) => e.proposal.metadata.selfRef).toList();
1153+
1154+
expect(refs, hasLength(expectedRefs.length));
1155+
expect(refs, containsAll(expectedRefs));
1156+
},
1157+
onPlatform: driftOnPlatforms,
1158+
);
10921159
});
10931160
group('queryProposals', () {
10941161
test(

0 commit comments

Comments
 (0)