Skip to content

Commit

Permalink
[1.0.2] Added support for Facet Values & Insights
Browse files Browse the repository at this point in the history
  • Loading branch information
nhathiwala committed Oct 22, 2021
1 parent e0c3db2 commit 9e60161
Show file tree
Hide file tree
Showing 22 changed files with 1,616 additions and 1,268 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/dart.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: Publish package to pub.dev
name: CI
on:
push:
branches: [ master ]
Expand Down
Empty file added .travis.yml
Empty file.
6 changes: 6 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
"request": "launch",
"type": "dart",
"program": "${workspaceRoot}/test/algolia_test.dart"
},
{
"name": "Dart Tests",
"request": "launch",
"type": "dart",
"program": "${workspaceRoot}/test/algolia_test.dart"
}
]
}
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
## [1.0.2] - Added support for Facet Values & Insights

- [Added] Facet values `AlgoliaFacetValueSnapshot`:
Now you can get list of all facet value to implement advance filtering options.
- [Added] Insights implementation.

```dart
// Create an Event
AlgoliaEvent event = AlgoliaEvent(
eventType: AlgoliaEventType.view,
eventName: 'View contact',
index: 'contacts',
userToken: 'userId123',
);
// Push Event
await algolia.instance.pushEvents([event]);
```

- [Added] `queryId` to query snapshot when click analytics is enabled.
- [Bug] Fixed null error in query snapshot.
- [Improved] Improved concurrency of snapshot interface by making constructor base multiple mapped value to a getter parameters.


## [1.0.1] - Bug-fixes with improved debugging stability

- [Bug] faulty assert resolved for checking empty values [#40](https://github.com/knoxpo/dart_algolia/issues/40)
Expand Down
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

**[UNOFFICIAL]** Algolia is a pure dart SDK, wrapped around Algolia REST API for easy implementation for your Flutter or Dart projects.

[![pub package](https://img.shields.io/pub/v/algolia.svg)](https://pub.dartlang.org/packages/algolia) [![.github/workflows/dart.yml](https://github.com/knoxpo/dart_algolia/actions/workflows/dart.yml/badge.svg)](https://github.com/knoxpo/dart_algolia/actions/workflows/dart.yml)
[![pub package](https://img.shields.io/pub/v/algolia.svg)](https://pub.dartlang.org/packages/algolia) [![.github/workflows/dart.yml](https://github.com/knoxpo/dart_algolia/actions/workflows/dart.yml/badge.svg)](https://github.com/knoxpo/dart_algolia/actions/workflows/dart.yml) [![Build Status](https://travis-ci.com/knoxpo/dart_algolia.svg?branch=master)](https://travis-ci.com/knoxpo/dart_algolia)

[Pub](https://pub.dartlang.org/packages/algolia) - [API Docs](https://pub.dartlang.org/documentation/algolia/latest/) - [GitHub](https://github.com/knoxpo/dart_algolia)

Expand All @@ -25,6 +25,7 @@
- Copy Index
- Move Index
- Index Settings
- Push Insights Events

## Version compatibility

Expand Down Expand Up @@ -103,9 +104,25 @@ There is a pretty sweet example project in the `example` folder. Check it out. O
// Checking if has [AlgoliaTask]
print('\n\n');
print(setSettings.data);
// Pushing Event
AlgoliaEvent event = AlgoliaEvent(
eventType: AlgoliaEventType.view,
eventName: 'View contact',
index: 'contacts',
userToken: 'user123',
);
await algolia.instance.pushEvents([event]);
}
```

## Insights

The Insights API lets you push a collection of events related to how your product is being used. Sending those events is a required step for using several Algolia features like Click analytics, A/B Testing, Personalization and Dynamic Re-Ranking.

- `.pushEvents(List<AlgoliaEvent> events)`


## Search Parameters

Here is the list of parameters you can use with the search method (search scope).
Expand Down
2 changes: 1 addition & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
include: package:pedantic/analysis_options.1.11.0.yaml
include: package:lints/recommended.yaml
21 changes: 6 additions & 15 deletions lib/algolia.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,16 @@ import 'package:http/http.dart' as http;
import 'package:uuid/uuid.dart';

part 'src/algolia.dart';

part 'src/batch.dart';
part 'src/error.dart';
part 'src/event_snapshot.dart';
part 'src/facet_value_snapshot.dart';
part 'src/index_reference.dart';

part 'src/index_settings.dart';

part 'src/index_snapshot.dart';

part 'src/object_reference.dart';
part 'src/object_snapshot.dart';
part 'src/query.dart';

part 'src/query_snapshot.dart';

part 'src/object_snapshot.dart';

part 'src/object_reference.dart';

part 'src/batch.dart';

part 'src/task.dart';

part 'src/error.dart';

part 'src/util/json_encode.dart';
44 changes: 40 additions & 4 deletions lib/src/algolia.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ class Algolia {
Algolia get instance => Algolia._(
applicationId: applicationId,
apiKey: _apiKey,
extraHeaders: _header,
extraHeaders: _headers,
);

String get _host => 'https://$applicationId-dsn.algolia.net/1/';
String get _insightsHost => 'https://insights.algolia.io/1/';

Map<String, String> get _header {
Map<String, String> get _headers {
var map = <String, String>{
'X-Algolia-Application-Id': applicationId,
'X-Algolia-API-Key': _apiKey,
Expand All @@ -43,7 +44,7 @@ class Algolia {
return Algolia._(
applicationId: applicationId,
apiKey: _apiKey,
extraHeaders: _header,
extraHeaders: _headers,
);
}

Expand All @@ -58,7 +59,7 @@ class Algolia {
var _url = '${_host}indexes';
var response = await http.get(
Uri.parse(_url),
headers: _header,
headers: _headers,
);
Map<String, dynamic> body = json.decode(response.body);

Expand All @@ -68,4 +69,39 @@ class Algolia {

return AlgoliaIndexesSnapshot._(this, body);
}

/// Pushes an array of events to the Insights API.
///
/// An event is
/// - an action: `eventName`
/// - performed in a context: `eventType`
/// - at some point in time provided: `timestamp`
/// - by an end user: `userToken`
/// - on something: `index`
///
///
/// **Notes:**
/// - The number of events that can be sent at the same time is limited to 1000.
/// - To be accepted, all events sent must be valid.
/// - When an event is tied to an Algolia search, it must also provide a `queryId`. If that event is a click, their absolute `positions` should also be passed.
/// - We consider that an index provides access to 2 resources: objects and filters. An event can only interact with a single resource type,
/// but not necessarily on a single item. As such an event will accept an array of `objectIds` or `filters`.
///
/// Source: [Learn more](https://www.algolia.com/doc/rest-api/insights).
Future<void> pushEvents(List<AlgoliaEvent> events) async {
if (events.isEmpty) return;
final url = '${_insightsHost}events';
final eventList = events.map((e) => e.toMap()).toList();
final response = await http.post(
Uri.parse(url),
headers: _headers,
body: utf8.encode(json.encode({'events': eventList})),
encoding: Encoding.getByName('utf-8'),
);
Map<String, dynamic> body = json.decode(response.body);

if (!(response.statusCode >= 200 && response.statusCode < 300)) {
throw AlgoliaError._(body, response.statusCode);
}
}
}
2 changes: 1 addition & 1 deletion lib/src/batch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class AlgoliaBatch {

var response = await http.post(
Uri.parse(url),
headers: algolia._header,
headers: algolia._headers,
body: utf8.encode(json
.encode({'requests': actions}, toEncodable: jsonEncodeHelper)),
encoding: Encoding.getByName('utf-8'),
Expand Down
186 changes: 186 additions & 0 deletions lib/src/event_snapshot.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
part of algolia;

enum AlgoliaEventType {
click,
conversion,
view,
}

extension AlgoliaEventTypeExtention on AlgoliaEventType {
String toMap() {
return toString().split('.').last;
}
}

class AlgoliaEvent {
AlgoliaEvent({
required this.eventType,
required this.eventName,
required this.index,
required this.userToken,
this.queryId,
this.objectIds,
this.timestamp,
this.filters,
this.positions,
}) : assert(
eventName.isNotEmpty && eventName.length <= 64,
'eventName - Format: any ASCII character, '
'except control points with a length between 1 and 64.',
),
assert(
RegExp(r'[a-zA-Z0-9_-]{1,64}').hasMatch(eventName),
'eventName - Format: [a-zA-Z0-9_-]{1,64}',
),
assert(
(objectIds?.isEmpty ?? true) || (filters?.isEmpty ?? true),
'An event can’t have both objectIds and filters at the same time.',
),
assert(
!((queryId?.isNotEmpty ?? false) &&
eventType == AlgoliaEventType.click) ||
((positions?.isNotEmpty ?? false) &&
(objectIds?.isNotEmpty ?? false) &&
positions?.length == objectIds?.length),
'positions and objectIds field is required if a queryId is provided. '
'One position must be provided for each objectId. '
'Only include this parameter when sending click events.',
);

/// An eventType can be a `click`, a `conversion`, or a `view`.
final AlgoliaEventType eventType;

/// A user-defined string used to categorize events.
///
/// **Format:**
/// any ASCII character, except control points with a length between 1 and 64.
final String eventName;

/// Name of the targeted index.
///
/// **Format:**
/// same as the index name used by the search engine.
final String index;

/// Name of the targeted index.
///
/// **Format:**
/// same as the index name used by the search engine.
final String userToken;

/// Time of the event expressed in milliseconds since the Unix epoch. Default: now()
final DateTime? timestamp;

/// Algolia `queryId`. This is required when an event is tied to a search.
///
/// It can be found in the Search API results response.
final String? queryId;

/// An array of index `objectId`. Limited to 20 objects.
///
/// An event can’t have both `objectIds` and `filters` at the same time.
final List<String>? objectIds;

/// An array of filters. Limited to 10 filters.
///
/// **Format:**
/// ${attribute}:${value}, like brand:apple.
///
/// An event can’t have both `objectIds` and `filters` at the same time.
final List<String>? filters;

/// Position of the click in the list of Algolia search results.
///
/// This field is
/// **required**
/// if a `queryId` is provided. One position must be provided for each
/// `objectId`.
///
/// The position value must be absolute, not relative, to the result page.
/// For example, when clicking on the third record of the second results page,
/// assuming 10 results per page, the position should be 13. Since page starts
/// at 0, you can use the following formula:
/// ```$positionOnPage + $page * hitsPerPage```
///
/// Note: For InstantSearch implementations, this position is available
/// through the hit.__position attribute
///
/// **Only**
/// include this parameter when sending click events.
final List<int>? positions;
Map<String, dynamic> toMap() {
var body = <String, dynamic>{
'eventType': eventType.toMap(),
'eventName': eventName,
'index': index,
'userToken': userToken,
};
if (timestamp != null) {
body['timestamp'] = timestamp!.millisecondsSinceEpoch;
}
if (queryId != null) body['queryId'] = queryId;
if (objectIds != null) body['objectIds'] = objectIds;
if (filters != null) body['filters'] = filters;
if (positions != null) body['positions'] = positions;
return body;
}
AlgoliaEvent copyWith({
AlgoliaEventType? eventType,
String? eventName,
String? index,
String? userToken,
DateTime? timestamp,
String? queryId,
List<String>? objectIds,
List<String>? filters,
List<int>? positions,
}) {
return AlgoliaEvent(
eventType: eventType ?? this.eventType,
eventName: eventName ?? this.eventName,
index: index ?? this.index,
userToken: userToken ?? this.userToken,
timestamp: timestamp ?? this.timestamp,
queryId: queryId ?? this.queryId,
objectIds: objectIds ?? this.objectIds,
filters: filters ?? this.filters,
positions: positions ?? this.positions,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is AlgoliaEvent &&
other.eventType == eventType &&
other.eventName == eventName &&
other.index == index &&
other.userToken == userToken &&
other.timestamp == timestamp &&
other.queryId == queryId &&
other.objectIds.hashCode == objectIds.hashCode &&
other.filters.hashCode == filters.hashCode &&
other.positions.hashCode == positions.hashCode;
}
@override
int get hashCode {
return eventType.hashCode ^
eventName.hashCode ^
index.hashCode ^
userToken.hashCode ^
timestamp.hashCode ^
queryId.hashCode ^
objectIds.hashCode ^
filters.hashCode ^
positions.hashCode;
}
@override
String toString() {
return 'AlgoliaEvent(eventType: $eventType, eventName: $eventName, index: $index, userToken: $userToken, timestamp: $timestamp, queryId: $queryId, objectIds: $objectIds, filters: $filters, positions: $positions)';
}
}

0 comments on commit 9e60161

Please sign in to comment.