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
76 changes: 76 additions & 0 deletions src/main/java/lol/pbu/z4j/SearchService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2026 Peanut Butter Unicorn, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package lol.pbu.z4j;

import jakarta.inject.Inject;
import lol.pbu.z4j.client.SearchClient;
import lol.pbu.z4j.model.ExportResponse;
import lol.pbu.z4j.model.Ticket;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
* Service for searching and exporting tickets, orgs, users, and groups.
*
* @author Jonathan-Zollinger
* @since 0.1.3
*/
@Slf4j
public class SearchService {
@Inject
SearchClient searchClient;

/**
* Searches for tickets using the provided query string with a default page size of 100.
*
* @param query The search query string. Note: 'type:ticket' is automatically prepended.
* @return A {@link Flux} emitting the {@link Ticket} results found.
* @see #getTickets(String, int)
*/
public Flux<Ticket> getTickets(String query) {
return getTickets(query, 100);
}


/**
* Searches for tickets using the provided query string and page size.
*
* @param query The search query string. Note: 'type:ticket' is automatically prepended.
* @param pageSize The number of results to return per page.
* @return A {@link Flux} emitting the {@link Ticket} results found.
*/
public Flux<Ticket> getTickets(String query, int pageSize) {
String ticketPreface = "type:ticket";
query = query.replace(ticketPreface + " ", "").replace(ticketPreface, "");
if (query.contains("type:")) {
if (query.contains("_type:")) {
log.debug("verified that the query object does not already contain 'type:ticket': {}", query);
} else {
throw new IllegalArgumentException("Do not include 'type:<obj>' in your query. 'type:ticket' will automatically be added to your query.");
}
}
final String finalQuery = ticketPreface + " " + query;

return searchClient.exportTicket(finalQuery, pageSize, null).expand(page -> {
if (null != page.getMeta() && page.getMeta().getHasMore() && null != page.getMeta().getAfterCursor()) {
log.debug("sending query for {} with cursor {}", finalQuery, page.getMeta().getAfterCursor().substring(0, Math.min(page.getMeta().getAfterCursor().length(), 6)));
return searchClient.exportTicket(finalQuery, pageSize, page.getMeta().getAfterCursor());
}
return Mono.empty();
}).flatMapIterable(ExportResponse::getResults).cast(Ticket.class);
}
}
28 changes: 10 additions & 18 deletions src/main/java/lol/pbu/z4j/client/SearchClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,15 @@
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotNull;
import lol.pbu.z4j.model.SearchExportType;
import lol.pbu.z4j.model.SearchResponse;
import lol.pbu.z4j.model.SortBy;
import lol.pbu.z4j.model.SortOrder;
import lol.pbu.z4j.model.*;
import reactor.core.publisher.Mono;

/**
* <h1>{@summary Perform Searches in Zendesk.}</h1>
* <h1>{@summary Zendesk Search API}</h1>
* <p>Reactive client for performing searches in Zendesk. These methods provide low-level access to the search endpoints.</p>
* <ul>
* <li>Show Search Result Counts with{@link #count}</li>
* <li>Export Search Results with{@link #export}</li>
* <li>Show Search Result Counts with {@link #count(String)}</li>
* <li>Export Search Results with {@link #exportTicket(String, Integer, String)}</li>
* <li>List Search Results with{@link #list}</li>
* </ul>
*
Expand All @@ -57,7 +55,7 @@ public interface SearchClient {

/**
* <h1>{@summary Export Search Results}</h1>
* <p>Exports a set of results. See <a
* <p>Exports a set of Tickets. See <a
* href='https://developer.zendesk.com/api-reference/ticketing/ticket-management/search/#query-syntax'>Query
* syntax</a> for the syntax of the {@code query} parameter.</p> <p>Use this endpoint for search queries that will
* return more than 1000 results. The result set is ordered only by the {@code created_at} attribute.</p> <p>The search
Expand Down Expand Up @@ -89,18 +87,14 @@ public interface SearchClient {
* @param query Returns the search results. See <a href='https://developer.zendesk.com/api-reference/ticketing/ticket-management/search/#query-syntax'>Query syntax</a> for details on the {@code query} parameter. For details on the query syntax, see the <a href='https://support.zendesk.com/hc/en-us/articles/203663226'>Zendesk Support search reference</a>. (required)
* @param pageSize The number of results shown in a page. (required)
* @param pageAfter The cursor token for fetching the next page of results. (required)
* @param filterType The object type returned by the export query. Can be `ticket`, `organization`, `user`, or `group`. (required)
* @param include Sideloads to include in the response. Accepts a comma-separated list of values. The available sideloads depend on the search result types. (optional)
* @return Success response (status code 200)
* or Error response (status code 400)
*/
@Get("/api/v2/search/export")
Mono<@Valid SearchResponse> export(
@Get("/api/v2/search/export?filter[type]=ticket")
Mono<@Valid ExportResponse<Ticket>> exportTicket(
@QueryValue("query") @NotNull String query,
@QueryValue("page[size]") @NotNull @Max(1000) Integer pageSize,
@QueryValue("page[after]") @NotNull String pageAfter,
@QueryValue("filter[type]") @NotNull SearchExportType filterType,
@QueryValue("include") @Nullable String include
@QueryValue("page[size]") @Nullable @Max(1000) Integer pageSize,
@QueryValue("page[after]") @Nullable String pageAfter
);

/**
Expand All @@ -116,7 +110,6 @@ public interface SearchClient {
* @param query Returns the search results. See <a href='https://developer.zendesk.com/api-reference/ticketing/ticket-management/search/#query-syntax'>Query syntax</a> for details on the {@code query} parameter. For details on the query syntax, see the <a href='https://support.zendesk.com/hc/en-us/articles/203663226'>Zendesk Support search reference</a>. (required)
* @param sortBy One of {@code updated_at}, {@code created_at}, {@code priority}, {@code status}, or {@code ticket_type}. Defaults to sorting by relevance (optional)
* @param sortOrder Defaults to descending (optional)
* @param include Sideloads to include in the response. Accepts a comma-separated list of values. The available sideloads depend on the search result types. (optional)
* @param page The page number to retrieve. (optional)
* @param perPage The count of results to include in each page. (optional)
* @return Success response (status code 200)
Expand All @@ -127,7 +120,6 @@ public interface SearchClient {
@QueryValue("query") @NotNull String query,
@QueryValue("sort_by") @Nullable SortBy sortBy,
@QueryValue("sort_order") @Nullable SortOrder sortOrder,
@QueryValue("include") @Nullable String include,
@QueryValue("page") @Nullable Integer page,
@QueryValue("per_page") @Nullable @Max(100) Integer perPage
);
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/lol/pbu/z4j/model/CreateResourceResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2026 Peanut Butter Unicorn, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package lol.pbu.z4j.model;

import io.micronaut.serde.annotation.Serdeable;
import lombok.*;
import lombok.experimental.Accessors;

@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Setter
@Getter
@Serdeable
public class CreateResourceResult extends JobStatus {
private Integer id;
private Integer index;
}
19 changes: 19 additions & 0 deletions src/main/java/lol/pbu/z4j/model/ExportResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package lol.pbu.z4j.model;

import io.micronaut.serde.annotation.Serdeable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.util.List;

@Data
@Accessors(chain = true)
@EqualsAndHashCode
@Serdeable
public class ExportResponse<T> {
private List<T> results;
private String facets;
private Meta meta;
private Links links;
}
7 changes: 7 additions & 0 deletions src/main/java/lol/pbu/z4j/model/Exportable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package lol.pbu.z4j.model;

import io.micronaut.serde.annotation.Serdeable;

@Serdeable
public interface Exportable {
}
20 changes: 20 additions & 0 deletions src/main/java/lol/pbu/z4j/model/FailedResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package lol.pbu.z4j.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.micronaut.serde.annotation.Serdeable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Serdeable
public class FailedResult extends JobStatus {
private String action;
private String details;
private String error;
@JsonProperty("id")
private Integer failedResultID;
private Boolean success;
}
7 changes: 7 additions & 0 deletions src/main/java/lol/pbu/z4j/model/Group.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package lol.pbu.z4j.model;

import io.micronaut.serde.annotation.Serdeable;

@Serdeable
public class Group implements Exportable {
}
24 changes: 24 additions & 0 deletions src/main/java/lol/pbu/z4j/model/JobStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2026 Peanut Butter Unicorn, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package lol.pbu.z4j.model;

import io.micronaut.serde.annotation.Serdeable;
import lombok.EqualsAndHashCode;

@EqualsAndHashCode
@Serdeable
public class JobStatus {
}
35 changes: 35 additions & 0 deletions src/main/java/lol/pbu/z4j/model/JobStatusResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2026 Peanut Butter Unicorn, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package lol.pbu.z4j.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.micronaut.serde.annotation.Serdeable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

@Getter
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@Serdeable
public class JobStatusResponse {

@JsonProperty("job_status")
private JobStatus jobStatus;

}
17 changes: 17 additions & 0 deletions src/main/java/lol/pbu/z4j/model/Links.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package lol.pbu.z4j.model;

import io.micronaut.serde.annotation.Serdeable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.net.URL;

@Data
@Accessors(chain = true)
@EqualsAndHashCode
@Serdeable
public class Links {
private URL prev;
private URL next;
}
12 changes: 12 additions & 0 deletions src/main/java/lol/pbu/z4j/model/LocaleAbbreviation.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ public enum LocaleAbbreviation {
@JsonProperty("en-us")
ENGLISH_UNITED_STATES("en-us"),

@JsonProperty("en-vn")
ENGLISH_VIETNAM("en-vn"),

@JsonProperty("fa-af")
DARI_PERSIAN_AFGHANISTAN("fa-af"),

Expand Down Expand Up @@ -110,6 +113,9 @@ public enum LocaleAbbreviation {
@JsonProperty("pl")
POLISH("pl"),

@JsonProperty("pt")
PORTUGUESE("pt"),

@JsonProperty("ro")
ROMANIAN("ro"),

Expand Down Expand Up @@ -140,8 +146,14 @@ public enum LocaleAbbreviation {
@JsonProperty("uk")
UKRAINIAN("uk"),

@JsonProperty("vi-vn")
VIETNAMESE_VIETNAM("vi-vn"),

@JsonProperty("vi")
VIETNAMESE("vi"),

@JsonProperty("zu-za")
ZULU_SOUTH_AFRICA("zu-za"),
;

public static final Map<String, LocaleAbbreviation> VALUE_MAPPING = Map.copyOf(Arrays.stream(values())
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/lol/pbu/z4j/model/Meta.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package lol.pbu.z4j.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.micronaut.serde.annotation.Serdeable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

@Data
@EqualsAndHashCode
@Accessors(chain = true)
@Serdeable
public class Meta {
@JsonProperty("has_more")
private Boolean hasMore;
@JsonProperty("after_cursor")
private String afterCursor;
@JsonProperty("before_cursor")
private String beforeCursor;
}
7 changes: 7 additions & 0 deletions src/main/java/lol/pbu/z4j/model/Organization.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package lol.pbu.z4j.model;

import io.micronaut.serde.annotation.Serdeable;

@Serdeable
public class Organization implements Exportable {
}
Loading
Loading