diff --git a/gradle.properties b/gradle.properties index 5251aed..ec39fa2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,5 +14,4 @@ systemProp.sonar.tests=src/test/groovy systemProp.sonar.junit.reportPaths=build/test-results/test systemProp.sonar.coverage.jacoco.xmlReportPath=build/reports/jacoco/test/jacocoTestReport.xml systemProp.sonar.java.binaries=build/classes/java/main -systemProp.sonar.java.test.binaries=build/classes/groovy/test - +systemProp.sonar.java.test.binaries=build/classes/groovy/test \ No newline at end of file diff --git a/src/main/resources/z4j.yaml b/src/main/resources/z4j.yaml index d7e3760..ec029e8 100644 --- a/src/main/resources/z4j.yaml +++ b/src/main/resources/z4j.yaml @@ -575,7 +575,7 @@ paths: $ref: '#/components/schemas/Category' responses: "201": - description: OK Response + description: Created response headers: Location: description: The URL of the new created category @@ -782,12 +782,205 @@ paths: responses: "204": description:
No content
+ /api/v2/search: + get: + operationId: List + tags: + - Search + summary: List Search Results + description: |- +Returns the search results. See Query syntax for details on the {@code query} parameter.
+Use the ampersand character (&) to append the {@code sort_by} or {@code sort_order} parameters to the URL.
+For examples, see Searching with Zendesk API.
+This endpoint has its own rate limit. The rate limit counts towards the global API rate limit. See Limits.
+Offset pagination may result in duplicate results when paging. You can also use the + Export Search Results endpoint, which + uses cursor-based pagination and doesn't return duplicate results. See + Using cursor pagination for more information.
+ parameters: + - name: query + in: query + description: Returns the search results. See Query syntax for details on the {@code query} parameter. For details on the query syntax, see the Zendesk Support search reference. + required: true + schema: + type: string + - name: sort_by + in: query + description: One of {@code updated_at}, {@code created_at}, {@code priority}, {@code status}, or {@code ticket_type}. Defaults to sorting by relevance + schema: + $ref: '#/components/schemas/SearchSortBy' + - name: sort_order + in: query + description: Defaults to descending + schema: + $ref: '#/components/schemas/SearchSortOrder' + - $ref: '#/components/parameters/SearchInclude' + responses: + "200": + description: Success response + content: + application/json: + schema: + $ref: '#/components/schemas/SearchResponse' + "400": + description: Error response + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestErrorResponse' + /api/v2/search/count: + get: + operationId: Count + tags: + - Search + summary: Show Search Results Count + description: | + Returns the number of items matching the query rather than returning the items. The search string works the same as a regular search. + +Exports a set of results. See Query syntax for the syntax of the {@code query} parameter.
+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.
+The search only returns results of a single object type. The following object types are supported: ticket, organization, user, or group.
+You must specify the type in the {@code filter[type]} parameter. Searches with type in the query string will result in an error.
+See Pagination.
+Returns a maximum of 1000 records per page. The number of results shown in a page is determined by the {@code page[size]} parameter.
+Note: You may experience a speed reduction or a timeout if you request 1000 results per page and you have many archived tickets in the results. Try reducing the number of results per page. We recommend 100 results per page.
+The cursor specified by the {@code after_cursor} property in a response expires after one hour.
+For more information on cursor-based pagination, see the following articles:
+This API endpoint is rate-limited to 100 requests per minute per account. The limit also counts towards the global API rate limit.
+ parameters: + - name: query + in: query + description: Returns the search results. See Query syntax for details on the {@code query} parameter. For details on the query syntax, see the Zendesk Support search reference. + required: true + schema: + type: string + - name: page[size] + in: query + description: The number of results shown in a page. + required: true + schema: + type: integer + - name: page[after] + in: query + description: The cursor token for fetching the next page of results. + required: true + schema: + type: string + - name: filter[type] + in: query + description: The object type returned by the export query. Can be `ticket`, `organization`, `user`, or `group`. + required: true + schema: + $ref: '#/components/schemas/SearchExportType' + - $ref: '#/components/parameters/SearchInclude' + responses: + "200": + description: Success response + content: + application/json: + schema: + $ref: '#/components/schemas/SearchResponse' + "400": + description: Error response + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestErrorResponse' components: schemas: + SearchSortBy: + type: string + enum: + - updated_at + - created_at + - priority + - status + - ticket_type + x-enum-name: SearchSortBy + x-enum-varnames: + - UPDATED_AT + - CREATED_AT + - PRIORITY + - STATUS + - TICKET_TYPE + SearchSortOrder: + type: string + enum: + - asc + - desc + x-enum-name: SearchSortOrder + x-enum-varnames: + - ASCENDING + - DESCENDING + SearchExportType: + type: string + enum: + - ticket + - organization + - user + - group + x-enum-name: SearchExportType + x-enum-varnames: + - TICKET + - ORGANIZATION + - USER + - GROUP Attachment: type: object description: | -A file represented as an Attachment object
+ A file represented as an Attachment object allOf: - $ref: '#/components/schemas/AttachmentBase' - $ref: '#/components/schemas/AttachmentThumbnails' @@ -801,7 +994,7 @@ components: content_url: type: string description: | -A full URL where the attachment image file can be downloaded. The file may be hosted externally so take care not to inadvertently send Zendesk authentication credentials. See Working with url properties
+ A full URL where the attachment image file can be downloaded. The file may be hosted externally so take care not to inadvertently send Zendesk authentication credentials. See Working with url properties readOnly: true deleted: type: boolean @@ -832,6 +1025,17 @@ components: malware_scan_result: type: string description: 'The result of the malware scan. There is a delay between the time the attachment is uploaded and when the malware scan is completed. Usually the scan is done within a few seconds, but high load conditions can delay the scan results. Possible values: "malware_found", "malware_not_found", "failed_to_scan", "not_scanned"' + enum: + - malware_found + - malware_not_found + - failed_to_scan + - not_scanned + x-enum-name: MalwareScanResult + x-enum-varnames: + - MALWARE_FOUND + - MALWARE_NOT_FOUND + - FAILED_TO_SCAN + - NOT_SCANNED readOnly: true mapped_content_url: type: string @@ -3259,6 +3463,64 @@ components: type: array items: $ref: '#/components/schemas/Vote' + SearchResult: + type: object + properties: + created_at: + type: string + description: When the resource was created + default: + type: boolean + description: Flag to indicate whether this is the default resource + deleted: + type: boolean + description: Flag to indicate whether or not resource has been deleted + description: + type: string + description: The description of the resource + id: + type: integer + description: The ID of the resource + name: + type: string + description: The name of the resource + result_type: + type: string + description: The type of the resource + updated_at: + type: string + description: When the resource was last updated + url: + type: string + description: The url of the resource + SearchResponse: + type: object + properties: + count: + type: integer + description: The number of resources returned by the query corresponding to this page of results in the paginated response + readOnly: true + facets: + type: string + description: The facets corresponding to the search query + nullable: true + readOnly: true + next_page: + type: string + description: URL to the next page of results + nullable: true + readOnly: true + previous_page: + type: string + description: URL to the previous page of results + nullable: true + readOnly: true + results: + type: array + description: May consist of tickets, users, groups, or organizations, as specified by the `result_type` property in each result object + items: + $ref: '#/components/schemas/SearchResult' + readOnly: true parameters: ArticleAttachmentId: name: article_attachment_id @@ -3352,6 +3614,14 @@ components: type: string example: en-us example: en-us + SearchInclude: + name: include + in: query + description: | + Sideloads to include in the response. Accepts a comma-separated list of values. + The available sideloads depend on the search result types. + schema: + type: string SectionId: name: section_id in: path diff --git a/src/test/groovy/lol/pbu/z4j/client/SearchClientSpec.groovy b/src/test/groovy/lol/pbu/z4j/client/SearchClientSpec.groovy new file mode 100644 index 0000000..b0f12e7 --- /dev/null +++ b/src/test/groovy/lol/pbu/z4j/client/SearchClientSpec.groovy @@ -0,0 +1,88 @@ +package lol.pbu.z4j.client + +import io.micronaut.http.client.exceptions.HttpClientResponseException +import lol.pbu.z4j.Z4jSpec +import lol.pbu.z4j.model.SearchExportType +import lol.pbu.z4j.model.SearchSortBy +import lol.pbu.z4j.model.SearchSortOrder +import spock.lang.Shared +import spock.lang.Unroll + +class SearchClientSpec extends Z4jSpec { + @Shared + SearchClient adminSearchClient, agentSearchClient, userSearchClient + + def setupSpec() { + adminSearchClient = adminCtx.getBean(SearchClient.class) + agentSearchClient = agentCtx.getBean(SearchClient.class) + userSearchClient = userCtx.getBean(SearchClient.class) + } + + @Unroll("an #clientName user can run the list method with sortby: #sortBy, sortOrder: #sortOrder and include: #include") + void "can run the list method"(String clientName, SearchClient client, SearchSortBy sortBy, SearchSortOrder sortOrder, String include) { + when: + client.list(faker.bluey().quote(), sortBy, sortOrder, include).block() + + then: + noExceptionThrown() + + where: + [[client, clientName], sortBy, sortOrder, include] << [[[adminSearchClient, "admin"], [agentSearchClient, "agent"]], + [SearchSortBy.values(), null].flatten(), + [SearchSortOrder.values(), null].flatten(), + [null, faker.cat().name()]].combinations() + } + + void "an #clientName user can run the count method"(String clientName, SearchClient client) { + when: + client.count(faker.bluey().quote()).block() + + then: + noExceptionThrown() + + where: + [client, clientName] << [[adminSearchClient, "admin"], [agentSearchClient, "agent"]] + } + + @Unroll("a simple user querying the list method fails with #sortBy, #sortOrder and #include") + void "cannot run searchClient.list()"(SearchClient client, SearchSortBy sortBy, SearchSortOrder sortOrder, String include) { + when: + client.list(faker.bluey().quote(), sortBy, sortOrder, include).block() + + then: + thrown(HttpClientResponseException) + + where: + [client, sortBy, sortOrder, include] << [[userSearchClient], + [SearchSortBy.values(), null].flatten(), + [SearchSortOrder.values(), null].flatten(), + [null, faker.cat().name()]].combinations() + } + + void "a normal user cannot run the count method"(SearchClient client) { + when: + client.count(faker.bluey().quote()).block() + + then: + thrown(HttpClientResponseException) + + where: + client | _ + userSearchClient | _ + } + + void "an #clientName can call export method with pageSize: #pageSize, pageAfter: #pageAfter, filterType: #filterType and include: #include"(String clientName, SearchClient client, int pageSize, String pageAfter, SearchExportType filterType, String include) { + when: + client.export(faker.bluey().quote(), pageSize, pageAfter, filterType, include).block() + + then: + noExceptionThrown() + + where: + [[client, clientName], pageSize, pageAfter, filterType, include] << [[[adminSearchClient, "admin"], [agentSearchClient, "agent"]], + [100], + [faker.internet().uuid()], + SearchExportType.values(), + ["organizations"]].combinations() + } +}