Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion app/lib/shared/search_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,64 @@ class PackageDocument extends Object with _$PackageDocumentSerializerMixin {
_$PackageDocumentFromJson(json);
}

/// How search results should be ordered.
enum SearchOrder {
/// Search score should be a weighted value of [text], [updated],
/// [popularity], [health] and [maintenance], ordered decreasing.
overall,

/// Search score should depend only on text match similarity, ordered
/// decreasing.
text,

/// Search order should be in decreasing last updated time.
updated,

/// Search order should be in decreasing popularity score.
popularity,

/// Search order should be in decreasing health score.
health,
}

/// Returns null if [value] is not a recognized search order.
SearchOrder parseSearchOrder(String value, {SearchOrder defaultsTo}) {
if (value != null) {
switch (value) {
case 'overall':
return SearchOrder.overall;
case 'text':
return SearchOrder.text;
case 'updated':
return SearchOrder.updated;
case 'popularity':
return SearchOrder.popularity;
case 'health':
return SearchOrder.health;
}
}
if (defaultsTo != null) return defaultsTo;
throw new Exception('Unable to parse SearchOrder: $value');
}

String serializeSearchOrder(SearchOrder order) {
if (order == null) return null;
return order.toString().split('.').last;
}

class SearchQuery {
final String text;
final PlatformPredicate platformPredicate;
final String packagePrefix;
final SearchOrder order;
final int offset;
final int limit;

SearchQuery(
this.text, {
this.platformPredicate,
this.packagePrefix,
this.order,
this.offset: 0,
this.limit: 10,
});
Expand All @@ -95,6 +142,10 @@ class SearchQuery {
if (type != null && type.isEmpty) type = null;
String packagePrefix = uri.queryParameters['pkg-prefix'];
if (packagePrefix != null && packagePrefix.isEmpty) packagePrefix = null;
final SearchOrder order = parseSearchOrder(
uri.queryParameters['order'],
defaultsTo: SearchOrder.overall,
);
int offset =
int.parse(uri.queryParameters['offset'] ?? '0', onError: (_) => 0);
int limit = int.parse(uri.queryParameters['limit'] ?? '0',
Expand All @@ -108,6 +159,7 @@ class SearchQuery {
text,
platformPredicate: platform,
packagePrefix: packagePrefix,
order: order,
offset: offset,
limit: limit,
);
Expand All @@ -120,6 +172,9 @@ class SearchQuery {
'offset': offset?.toString(),
'limit': limit?.toString(),
};
if (order != null) {
map['order'] = serializeSearchOrder(order);
}
if (packagePrefix != null) {
map['pkg-prefix'] = packagePrefix;
}
Expand All @@ -131,7 +186,10 @@ class SearchQuery {
final bool hasText = text != null && text.isNotEmpty;
final bool hasPackagePrefix =
packagePrefix != null && packagePrefix.isNotEmpty;
return hasText || hasPackagePrefix;
final bool hasNonTextOrdering = order != null &&
order != SearchOrder.overall &&
order != SearchOrder.text;
return hasText || hasPackagePrefix || hasNonTextOrdering;
}
}

Expand Down
74 changes: 74 additions & 0 deletions app/test/shared/search_service_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:test/test.dart';

import 'package:pub_dartlang_org/shared/search_service.dart';

void main() {
group('SearchOrder enum', () {
test('serialization', () {
for (var value in SearchOrder.values) {
final String serialized = serializeSearchOrder(value);
expect(serialized, isNotEmpty);
final SearchOrder deserialized = parseSearchOrder(serialized);
expect(deserialized, value);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you write a test, then you might as well add a case for parseSearchOrder('foobar', defaultsTo: ...) :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

});

test('defaultsTo', () {
expect(() => parseSearchOrder('foobar'), throwsException);
expect(parseSearchOrder('foobar', defaultsTo: SearchOrder.popularity),
SearchOrder.popularity);
});
});

group('SearchQuery.isValid', () {
test('empty', () {
expect(new SearchQuery(null).isValid, isFalse);
expect(new SearchQuery('').isValid, isFalse);
});

test('contains text', () {
expect(new SearchQuery('text').isValid, isTrue);
});

test('has package prefix', () {
expect(new SearchQuery('', packagePrefix: 'angular_').isValid, isTrue);
});

test('has text-based ordering', () {
expect(new SearchQuery('', order: SearchOrder.overall).isValid, isFalse);
expect(new SearchQuery('', order: SearchOrder.text).isValid, isFalse);

expect(
new SearchQuery('text', order: SearchOrder.overall).isValid, isTrue);
expect(new SearchQuery('text', order: SearchOrder.text).isValid, isTrue);

expect(
new SearchQuery(
'',
packagePrefix: 'angular_',
order: SearchOrder.overall,
)
.isValid,
isTrue);
expect(
new SearchQuery(
'',
packagePrefix: 'angular_',
order: SearchOrder.text,
)
.isValid,
isTrue);
});

test('has non-text-based ordering', () {
expect(new SearchQuery('', order: SearchOrder.updated).isValid, isTrue);
expect(
new SearchQuery('', order: SearchOrder.popularity).isValid, isTrue);
expect(new SearchQuery('', order: SearchOrder.health).isValid, isTrue);
});
});
}