Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d16a732
feat(synonyms): add new synonym set classes
tharropoulos Aug 25, 2025
137266b
feat(client): register synonym set classes to client object
tharropoulos Aug 25, 2025
b3a1c68
feat(test): add helper method to skip deprecated tests
tharropoulos Aug 25, 2025
a7871ce
feat(test): add util for creating test synonym sets
tharropoulos Aug 25, 2025
4a8a037
feat(test): cleanup synonym sets after tests
tharropoulos Aug 25, 2025
d46c832
test(synonyms): add tests for synonym sets
tharropoulos Aug 25, 2025
7546bec
fix(test): skip old synonym tests on v30 and above
tharropoulos Aug 25, 2025
9f299ed
feat(synonyms): add deprecation logging and lsp info on old synonyms
tharropoulos Aug 25, 2025
5655877
feat(serializer): add custom serializer for analytics rules
tharropoulos Aug 26, 2025
40e0977
feat(analytics): update analytic event class to new api
tharropoulos Aug 26, 2025
9979908
feat(analytics): update analytic rule classes to new api
tharropoulos Aug 26, 2025
57493d2
feat(test): add test helper methods for new analytics
tharropoulos Aug 26, 2025
2a57cab
test(analytics): test new analytics api implementation
tharropoulos Aug 26, 2025
2ce38cb
refactor(test): use `Arrays.asList` instead of `List.of` for java 8 c…
tharropoulos Aug 27, 2025
6c8c0bb
chore(test): only remove existing resources on teardown
tharropoulos Aug 27, 2025
0200744
chore(test): remove old curation tests
tharropoulos Sep 30, 2025
48fd84b
chore(test): remove old curation test helpers
tharropoulos Sep 30, 2025
b9160ee
chore(curation): remove old override classes
tharropoulos Sep 30, 2025
5451b3c
feat(curation): add curation set classes
tharropoulos Sep 30, 2025
9b14d00
test(curation): test curation sets
tharropoulos Sep 30, 2025
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
11 changes: 8 additions & 3 deletions src/main/java/org/typesense/api/AnalyticsEvents.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package org.typesense.api;

import org.typesense.model.AnalyticsEvent;
import org.typesense.model.AnalyticsEventCreateResponse;
import org.typesense.model.AnalyticsEventCreateSchema;

import org.typesense.model.AnalyticsEventsResponse;
import java.util.Map;

public class AnalyticsEvents {
private final ApiCall apiCall;
Expand All @@ -12,7 +13,11 @@ public AnalyticsEvents(ApiCall apiCall) {
this.apiCall = apiCall;
}

public AnalyticsEventCreateResponse create(AnalyticsEventCreateSchema event) throws Exception {
public AnalyticsEventCreateResponse create(AnalyticsEvent event) throws Exception {
return this.apiCall.post(RESOURCE_PATH, event, null, AnalyticsEventCreateResponse.class);
}

public AnalyticsEventsResponse retrieve(Map<String, Object> params) throws Exception {
return this.apiCall.get(RESOURCE_PATH, params, AnalyticsEventsResponse.class);
}
}
29 changes: 21 additions & 8 deletions src/main/java/org/typesense/api/AnalyticsRule.java
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
package org.typesense.api;

import org.typesense.api.utils.URLEncoding;
import org.typesense.model.AnalyticsRuleDeleteResponse;
import org.typesense.model.AnalyticsRuleSchema;
import org.typesense.model.AnalyticsRuleUpdate;

public class AnalyticsRule {
private final ApiCall apiCall;
private final String ruleId;
private final AnalyticsRuleSerializer serializer;

public AnalyticsRule(String ruleId, ApiCall apiCall) {
this.apiCall = apiCall;
this.apiCall = apiCall;
this.ruleId = ruleId;
this.serializer = new AnalyticsRuleSerializer();
}

public AnalyticsRule(String ruleId, ApiCall apiCall, AnalyticsRuleSerializer serializer) {
this.apiCall = apiCall;
this.ruleId = ruleId;
this.serializer = serializer;
}

public org.typesense.model.AnalyticsRule retrieve() throws Exception {
String response = this.apiCall.get(this.getEndpoint(), null, String.class);
return serializer.parseFromJson(response);
}

public AnalyticsRuleSchema retrieve() throws Exception {
return this.apiCall.get(this.getEndpoint(), null, AnalyticsRuleSchema.class);
public org.typesense.model.AnalyticsRule delete() throws Exception {
String response = this.apiCall.delete(this.getEndpoint(), null, String.class);
org.typesense.model.AnalyticsRule result = new org.typesense.model.AnalyticsRule();
result.name(this.ruleId);
return result;
}

public AnalyticsRuleDeleteResponse delete() throws Exception {
return this.apiCall.delete(this.getEndpoint(), null, AnalyticsRuleDeleteResponse.class);
public org.typesense.model.AnalyticsRule update(AnalyticsRuleUpdate rule) throws Exception {
return this.apiCall.put(this.getEndpoint(), rule, null, org.typesense.model.AnalyticsRule.class);
}

private String getEndpoint() {
return AnalyticsRules.RESOURCE_PATH + "/" + URLEncoding.encodeURIComponent(ruleId);
}

}
138 changes: 138 additions & 0 deletions src/main/java/org/typesense/api/AnalyticsRuleSerializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package org.typesense.api;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.DeserializationFeature;
import org.typesense.model.AnalyticsRule;
import org.typesense.model.AnalyticsRuleCreate;
import org.typesense.model.AnalyticsRuleCreateParams;
import java.util.List;
import java.util.ArrayList;

/**
* Serializer for AnalyticsRule objects to avoid Jackson type discriminator issues
*/
public class AnalyticsRuleSerializer {

private final ObjectMapper objectMapper;

public AnalyticsRuleSerializer() {
this.objectMapper = new ObjectMapper();
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
}

/**
* Parse a single AnalyticsRule from JsonNode
*/
public AnalyticsRule parseFromJsonNode(JsonNode node) {
AnalyticsRule rule = new AnalyticsRule();

if (node.has("name")) {
rule.name(node.get("name").asText());
}

if (node.has("type")) {
String typeStr = node.get("type").asText();
if ("counter".equals(typeStr)) {
rule.type(AnalyticsRuleCreate.TypeEnum.COUNTER);
} else if ("popular_queries".equals(typeStr)) {
rule.type(AnalyticsRuleCreate.TypeEnum.POPULAR_QUERIES);
} else if ("nohits_queries".equals(typeStr)) {
rule.type(AnalyticsRuleCreate.TypeEnum.NOHITS_QUERIES);
} else if ("log".equals(typeStr)) {
rule.type(AnalyticsRuleCreate.TypeEnum.LOG);
}
}

if (node.has("collection")) {
rule.collection(node.get("collection").asText());
}

if (node.has("event_type")) {
rule.eventType(node.get("event_type").asText());
}

if (node.has("rule_tag")) {
rule.ruleTag(node.get("rule_tag").asText());
}

if (node.has("params")) {
JsonNode paramsNode = node.get("params");
AnalyticsRuleCreateParams params = new AnalyticsRuleCreateParams();

if (paramsNode.has("counter_field")) {
params.counterField(paramsNode.get("counter_field").asText());
}

if (paramsNode.has("weight")) {
params.weight(paramsNode.get("weight").asInt());
}

if (paramsNode.has("destination_collection")) {
params.destinationCollection(paramsNode.get("destination_collection").asText());
}

if (paramsNode.has("limit")) {
params.limit(paramsNode.get("limit").asInt());
}

if (paramsNode.has("capture_search_requests")) {
params.captureSearchRequests(paramsNode.get("capture_search_requests").asBoolean());
}

if (paramsNode.has("meta_fields")) {
List<String> metaFields = new ArrayList<>();
JsonNode metaFieldsNode = paramsNode.get("meta_fields");
if (metaFieldsNode.isArray()) {
for (JsonNode metaField : metaFieldsNode) {
metaFields.add(metaField.asText());
}
}
params.metaFields(metaFields);
}

if (paramsNode.has("expand_query")) {
params.expandQuery(paramsNode.get("expand_query").asBoolean());
}

rule.params(params);
}

return rule;
}

/**
* Parse AnalyticsRule from JSON string
*/
public AnalyticsRule parseFromJson(String jsonResponse) throws Exception {
JsonNode rootNode = objectMapper.readTree(jsonResponse);
return parseFromJsonNode(rootNode);
}

/**
* Parse a list of AnalyticsRule objects from JSON string
*/
public List<AnalyticsRule> parseListFromJson(String jsonResponse) throws Exception {
JsonNode rootNode = objectMapper.readTree(jsonResponse);
List<AnalyticsRule> rules = new ArrayList<>();

if (rootNode.isArray()) {
for (JsonNode item : rootNode) {
if (item.has("error")) {
String error = item.get("error").asText();
throw new RuntimeException("Analytics rule parsing failed: " + error);
} else {
AnalyticsRule rule = parseFromJsonNode(item);
rules.add(rule);
}
}
} else {
// Single object response
AnalyticsRule rule = parseFromJsonNode(rootNode);
rules.add(rule);
}

return rules;
}
}
75 changes: 64 additions & 11 deletions src/main/java/org/typesense/api/AnalyticsRules.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,83 @@
package org.typesense.api;

import org.typesense.api.utils.URLEncoding;
import org.typesense.model.AnalyticsRuleSchema;
import org.typesense.model.AnalyticsRuleUpsertSchema;
import org.typesense.model.AnalyticsRulesRetrieveSchema;
import org.typesense.model.AnalyticsRule;
import org.typesense.model.AnalyticsRuleCreate;
import java.util.List;

public class AnalyticsRules {

private final ApiCall apiCall;
private final AnalyticsRuleSerializer serializer;
public final static String RESOURCE_PATH = "/analytics/rules";

public AnalyticsRules(ApiCall apiCall) {
this.apiCall = apiCall;
this.serializer = new AnalyticsRuleSerializer();
}

public AnalyticsRules(ApiCall apiCall, AnalyticsRuleSerializer serializer) {
this.apiCall = apiCall;
this.serializer = serializer;
}

public AnalyticsRuleSchema create(AnalyticsRuleSchema rule) throws Exception {
return this.apiCall.post(RESOURCE_PATH, rule, null, AnalyticsRuleSchema.class);
public AnalyticsRulesResponse create(List<AnalyticsRuleCreate> rules) throws Exception {
String response = this.apiCall.post(RESOURCE_PATH, rules, null, String.class);
return parseCreateResponse(response);
}

public AnalyticsRuleSchema upsert(String name, AnalyticsRuleUpsertSchema rule) throws Exception {
return this.apiCall.put(RESOURCE_PATH + "/" + URLEncoding.encodeURIComponent(name), rule, null,
AnalyticsRuleSchema.class);
public List<AnalyticsRule> retrieve() throws Exception {
String response = this.apiCall.get(RESOURCE_PATH, null, String.class);
return parseRetrieveResponse(response);
}

public AnalyticsRulesRetrieveSchema retrieve() throws Exception {
return this.apiCall.get(RESOURCE_PATH, null, AnalyticsRulesRetrieveSchema.class);
/**
* Parse the create response which can be either a single AnalyticsRule or an array
*/
private AnalyticsRulesResponse parseCreateResponse(String jsonResponse) throws Exception {
List<AnalyticsRule> rules = serializer.parseListFromJson(jsonResponse);

for (AnalyticsRule rule : rules) {
if (rule.getName() == null) {
throw new RuntimeException("Analytics rule creation failed: rule name is null");
}
}

return new AnalyticsRulesResponse(rules);
}

/**
* Parse the retrieve response which is always an array of AnalyticsRule objects
*/
private List<AnalyticsRule> parseRetrieveResponse(String jsonResponse) throws Exception {
List<AnalyticsRule> rules = serializer.parseListFromJson(jsonResponse);

return rules;
}

/**
* Response wrapper for analytics rules operations
*/
public static class AnalyticsRulesResponse {
private final List<AnalyticsRule> rules;

public AnalyticsRulesResponse(List<AnalyticsRule> rules) {
this.rules = rules;
}

public List<AnalyticsRule> getRules() {
return rules;
}

public AnalyticsRule getFirstRule() {
return rules.isEmpty() ? null : rules.get(0);
}

public int getCount() {
return rules.size();
}

public boolean isEmpty() {
return rules.isEmpty();
}
}
}
40 changes: 40 additions & 0 deletions src/main/java/org/typesense/api/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ public class Client {
private Stopwords stopwords;
private Map<String, StopwordsSet> individualStopwordsSets;

private SynonymSets synonymSets;
private Map<String, SynonymSet> individualSynonymSets;

private CurationSets curationSets;
private Map<String, CurationSet> individualCurationSets;

public Health health;
public Operations operations;
public Metrics metrics;
Expand All @@ -50,6 +56,10 @@ public Client(Configuration configuration){
this.stemming = new Stemming(this.apiCall);
this.stopwords = new Stopwords(this.apiCall);
this.individualStopwordsSets = new HashMap<>();
this.synonymSets = new SynonymSets(this.apiCall);
this.individualSynonymSets = new HashMap<>();
this.curationSets = new CurationSets(this.apiCall);
this.individualCurationSets = new HashMap<>();
}

public Collection collections(String name){
Expand Down Expand Up @@ -121,4 +131,34 @@ public StopwordsSet stopwords(String stopwordsSetId) {
retVal = this.individualStopwordsSets.get(stopwordsSetId);
return retVal;
}

public SynonymSets synonymSets() {
return this.synonymSets;
}

public SynonymSet synonymSet(String synonymSetName) {
SynonymSet retVal;

if (!this.individualSynonymSets.containsKey(synonymSetName)) {
this.individualSynonymSets.put(synonymSetName, new SynonymSet(synonymSetName, this.apiCall));
}

retVal = this.individualSynonymSets.get(synonymSetName);
return retVal;
}

public CurationSets curationSets() {
return this.curationSets;
}

public CurationSet curationSet(String curationSetName) {
CurationSet retVal;

if (!this.individualCurationSets.containsKey(curationSetName)) {
this.individualCurationSets.put(curationSetName, new CurationSet(curationSetName, this.apiCall));
}

retVal = this.individualCurationSets.get(curationSetName);
return retVal;
}
}
Loading