From 3aa728d88abb613a74579e870972b6b8731037c6 Mon Sep 17 00:00:00 2001 From: James Maes Date: Thu, 15 Oct 2026 11:43:37 -0500 Subject: [PATCH 1/2] feat(ui-api): extension for QQQ UI API 1.6.0 (operationIds, BasicErrorResponseV1, QQQ 4.1.0-RC.14) Regenerated against UI API 1.6.0: every operation has an operationId and the standard error responses (BasicErrorResponseV1: error, code, message, fieldErrors, traceId); the extension's own error schema is kept but deprecated; hand-written error bodies go through QJavalinUtils so they carry code/message/traceId. Extension MINOR bumped. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 + pom.xml | 2 +- .../dataanalysis/api/DataAnalysisApi.java | 2 +- .../openapi/qqq-v1/data-analysis.yaml | 1110 ++++++++++++++++- .../dataanalysis/DataAnalysisApiTest.java | 14 +- 5 files changed, 1124 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bef1b08..1c3b8ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,8 @@ Following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic ### Changed +- UI API extension data-analysis 1.5.0 (requires QQQ 4.1.0-RC.14, UI API 1.6.0): operationIds, standard error responses with BasicErrorResponseV1, extension tags, contract validation for operationIds and schema references, and HTTP error envelope regression assertions. + - Pin QQQ 4.1.0-RC.7 and replace the temporary help loader with core bundle registration and startup sync. - Boolean pivot/crosstab headers now say Yes / No / (Blank). Pivot measures use human labels such as Count, Average Amount, Sum of Amount, Distinct Email, Median Amount and 90th percentile of Amount. diff --git a/pom.xml b/pom.xml index f50dddf..0087569 100644 --- a/pom.xml +++ b/pom.xml @@ -243,7 +243,7 @@ true - 4.1.0-RC.7 + 4.1.0-RC.14 diff --git a/src/main/java/com/kingsrook/qbits/dataanalysis/api/DataAnalysisApi.java b/src/main/java/com/kingsrook/qbits/dataanalysis/api/DataAnalysisApi.java index 692d5c8..b0b37ba 100644 --- a/src/main/java/com/kingsrook/qbits/dataanalysis/api/DataAnalysisApi.java +++ b/src/main/java/com/kingsrook/qbits/dataanalysis/api/DataAnalysisApi.java @@ -76,7 +76,7 @@ public class DataAnalysisApi *******************************************************************************/ public static MiddlewareSpecExtension extension() { - return (new MiddlewareSpecExtension().withName("data-analysis").withSemanticVersion("1.4.0") + return (new MiddlewareSpecExtension().withName("data-analysis").withSemanticVersion("1.5.0") .withDescription("Profiles, pivots, comparisons, distribution shifts, correlations, scatter samples, crosstabs and anomaly watch previews as the signed-in user.") .withSpec(SavedApi.Run.class).withSpec(SavedApi.ListSaved.class).withSpec(SavedApi.Star.class).withSpec(SavedApi.Unstar.class) .withSpec(SavedApi.Sql.class).withSpec(AnalysisFileSpec.class).withSpec(AnalysisFileSpec.Saved.class) diff --git a/src/main/resources/openapi/qqq-v1/data-analysis.yaml b/src/main/resources/openapi/qqq-v1/data-analysis.yaml index 9ca4750..4c14231 100644 --- a/src/main/resources/openapi/qqq-v1/data-analysis.yaml +++ b/src/main/resources/openapi/qqq-v1/data-analysis.yaml @@ -1,5 +1,44 @@ components: schemas: + BasicErrorResponseV1: + description: "The error body of every UI API operation (core and extensions)." + properties: + error: + description: "The error message (the same as message; kept for clients of\ + \ UI API 1.0 to 1.5)." + type: "string" + code: + description: "A stable, machine-readable code. Well-known values: BAD_REQUEST\ + \ (400), UNAUTHENTICATED (401),\nPERMISSION_DENIED (403), NOT_FOUND (404),\ + \ CONFLICT (409), RATE_LIMITED (429), INTERNAL_ERROR (500),\nERROR (other).\ + \ Extensions may add codes; treat an unknown code by the HTTP status.\ + \ Since 1.6.0." + type: "string" + message: + description: "A message to show the user. Since 1.6.0." + type: "string" + fieldErrors: + description: "Errors about particular request fields, when known. Since\ + \ 1.6.0." + items: + properties: + field: + description: "The field (or parameter) name." + type: "string" + message: + description: "The error about that field." + type: "string" + code: + description: "An optional machine-readable code, e.g. REQUIRED, TOO_LONG,\ + \ PATTERN." + type: "string" + type: "object" + type: "array" + traceId: + description: "Identifies this request in the server logs (also the X-QQQ-Trace-Id\ + \ response header); quote it when reporting a problem. Since 1.6.0." + type: "string" + type: "object" DataAnalysisAxisV1: nullable: true properties: @@ -1727,6 +1766,7 @@ components: type: "string" isAscending: description: "Whether this order-by is ascending or descending" + nullable: true type: "boolean" type: "object" QueryFilter: @@ -1756,16 +1796,49 @@ components: type: "object" securitySchemes: sessionUuidCookie: + description: "The session cookie set by POST /qqq/v1/manageSession (web clients)." in: "cookie" name: "sessionUUID" type: "apiKey" + bearerAuth: + bearerFormat: "JWT or session UUID" + description: "`Authorization: Bearer `, where the token is either\n-\ + \ an OAuth2 access token (a signed JWT) issued by the app's identity provider\ + \ to the app's client,\n or to its native client (`values.nativeClientId`\ + \ in GET /qqq/v1/metaData/authentication), for\n example from the device-authorization\ + \ grant or auth-code + PKCE. It is verified against the\n provider's keys\ + \ (issuer, audience, expiry, signature) and gives the same user and permissions\n\ + \ as signing in on the web. When it expires (401), refresh it at the provider\ + \ and retry; or\n- a session UUID returned by POST /qqq/v1/manageSession,\ + \ which the server keeps refreshed." + scheme: "bearer" + type: "http" + oauth2: + description: "The app's OAuth2 / OpenID Connect provider. The real endpoints\ + \ come from the provider's discovery\ndocument: `{values.baseUrl}.well-known/openid-configuration`,\ + \ with values from\nGET /qqq/v1/metaData/authentication. Native clients use\ + \ `values.nativeClientId` (a public client)\nwith auth-code + PKCE (loopback\ + \ or custom-scheme redirect URI registered at the provider) or the\ndevice-authorization\ + \ grant (`device_authorization_endpoint`, RFC 8628), then send the access\ + \ token\nas `Authorization: Bearer`." + flows: + authorizationCode: + authorizationUrl: "/authorize" + scopes: + email: "Email address" + offline_access: "A refresh token, to renew the access token without signing\ + \ in again" + openid: "OpenID Connect sign-in" + profile: "Name, username and groups (permissions come from the groups)" + tokenUrl: "/token" + type: "oauth2" info: contact: email: "contact@kingsrook.com" description: "Profiles, pivots, comparisons, distribution shifts, correlations,\ \ scatter samples, crosstabs and anomaly watch previews as the signed-in user." title: "QQQ Middleware API extension: data-analysis" - version: "1.4.0" + version: "1.5.0" openapi: "3.0.3" paths: /qqq/v1/qbit/data-analysis/saved/{id}/run: @@ -1774,6 +1847,7 @@ paths: \ saved READ and source READ/Analyze. Drops unavailable fields with a warning\ \ and returns the result typed by kind; inaccessible required axes or predicates\ \ produce no result." + operationId: "postQbitDataAnalysisSavedByIdRun" parameters: - description: "Owner-scoped analysis run or watch id." in: "path" @@ -1788,14 +1862,63 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisSavedResultV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Run a saved analysis" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/saved: get: description: "List core-visible saved analyses with name, kind, source table\ \ label, owner, shared state, this viewer's starred flag and last update." + operationId: "getQbitDataAnalysisSaved" responses: 200: content: @@ -1803,14 +1926,63 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisSavedListV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "List visible saved analyses" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/saved/{id}/star: delete: description: "Remove the signed-in user's star on a visible saved analysis.\ \ Idempotent; uses standard saved delete permission and owner locks." + operationId: "deleteQbitDataAnalysisSavedByIdStar" parameters: - description: "Owner-scoped analysis run or watch id." in: "path" @@ -1825,13 +1997,62 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisStarV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Unstar a saved analysis" + tags: + - "data-analysis" put: description: "Star a visible saved analysis for the signed-in user. Idempotent;\ \ uses standard saved insert permission and the personal star record lock." + operationId: "putQbitDataAnalysisSavedByIdStar" parameters: - description: "Owner-scoped analysis run or watch id." in: "path" @@ -1846,16 +2067,65 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisStarV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Star a saved analysis" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/tables/{table}/sql: post: description: "Execute a typed analysis as the viewer and capture the main RDBMS\ \ aggregate SQL from core QueryStat. Requires installed SQL Workbench and\ \ its access permission. Non-RDBMS backends report unavailable; SQL is for\ \ reading and record security remains enforced at runtime." + operationId: "postQbitDataAnalysisTablesByTableSql" parameters: - description: "QQQ table name to analyze." in: "path" @@ -1876,15 +2146,64 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisSqlV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Read an analysis aggregate SQL statement" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/export: post: description: "Execute a typed analysis as the signed-in viewer and download\ \ its result table as CSV or XLSX. Requires source READ/Analyze and FULL fields;\ \ uses core report formats." + operationId: "postQbitDataAnalysisExport" requestBody: content: application/json: @@ -1901,15 +2220,64 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisFileV1" description: "Analysis result file" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Export an analysis result" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/saved/{id}/export: get: description: "Re-run a visible saved analysis as the viewer, applying current\ \ source permissions and dropping unavailable fields, then download its result\ \ table as CSV or XLSX." + operationId: "getQbitDataAnalysisSavedByIdExport" parameters: - description: "Visible saved analysis id." in: "path" @@ -1936,14 +2304,63 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisFileV1" description: "Analysis result file" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Export a saved analysis" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/tables/{table}/capabilities: get: description: "Discover whether the current user can analyze this table, readable\ \ fields, chart defaults, limits and backend capabilities." + operationId: "getQbitDataAnalysisTablesByTableCapabilities" parameters: - description: "QQQ table name to analyze." in: "path" @@ -1958,15 +2375,64 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisCapabilitiesV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Check analysis availability" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/tables/{table}/column-profile: post: description: "Profile one column of a table under a filter: counts, nulls, distinct\ \ values, top values, numeric statistics and histogram, date patterns; every\ \ number has a drill-through filter unless privacy suppresses it." + operationId: "postQbitDataAnalysisTablesByTableColumnProfile" parameters: - description: "QQQ table name to analyze." in: "path" @@ -1987,14 +2453,63 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisColumnProfileV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Profile one column" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/tables/{table}/distinct-values: post: description: "Page through distinct stored values with display labels, counts\ \ and drill-through filters; search narrows stored values." + operationId: "postQbitDataAnalysisTablesByTableDistinctValues" parameters: - description: "QQQ table name to analyze." in: "path" @@ -2015,15 +2530,64 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisDistinctValuesPageV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "List distinct column values" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/tables/{table}/compare: post: description: "Compare filtered A with filtered B (null means the whole permitted\ \ table). Return means, medians or category shares, B-minus-A deltas, valid\ \ significance tests and count-equivalent drills. Only FULL fields are accepted." + operationId: "postQbitDataAnalysisTablesByTableCompare" parameters: - description: "QQQ table name to analyze." in: "path" @@ -2044,15 +2608,64 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisCompareV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Compare two populations" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/tables/{table}/shift: post: description: "Compare category shares or numeric quartiles across session-timezone\ \ calendar buckets. Compute descriptive PSI between the first and last complete\ \ nonempty buckets using fixed categories or baseline quartile bins." + operationId: "postQbitDataAnalysisTablesByTableShift" parameters: - description: "QQQ table name to analyze." in: "path" @@ -2073,16 +2686,65 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisShiftV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Measure distribution shift over time" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/tables/{table}/correlation: post: description: "Compute a Pearson correlation matrix for 2 to 12 distinct FULL\ \ numeric fields, using paired non-null records. Every cell includes n and\ \ parameters for the scatter endpoint; core pushes down CORR or uses bounded\ \ streaming." + operationId: "postQbitDataAnalysisTablesByTableCorrelation" parameters: - description: "QQQ table name to analyze." in: "path" @@ -2103,16 +2765,65 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisCorrelationV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Find numeric relationships" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/tables/{table}/scatter: post: description: "Return at most 2000 paired numeric points as the user, ordered\ \ by primary key when core query sampling is unavailable, plus a least-squares\ \ line fitted to the sample. Privacy disables individual drills and suppresses\ \ small populations." + operationId: "postQbitDataAnalysisTablesByTableScatter" parameters: - description: "QQQ table name to analyze." in: "path" @@ -2133,16 +2844,65 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisScatterV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Inspect a numeric relationship" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/tables/{table}/crosstab: post: description: "Count combinations of two FULL fields with row and column shares,\ \ up to 20 categories per axis plus Other. Test independence using chi-square\ \ and Cramer V after pooling low expected counts; privacy suppresses small\ \ cells and their drills." + operationId: "postQbitDataAnalysisTablesByTableCrosstab" parameters: - description: "QQQ table name to analyze." in: "path" @@ -2163,15 +2923,64 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisCrosstabV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Cross-tabulate two fields" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/watches/{id}/preview: post: description: "Compute complete time buckets and a detector verdict under the\ \ signed-in user's READ, Analyze and FULL-field permissions. Includes a baseline\ \ band annotation. Does not update the watch or raise alerts." + operationId: "postQbitDataAnalysisWatchesByIdPreview" parameters: - description: "Owner-scoped analysis run or watch id." in: "path" @@ -2186,15 +2995,64 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisWatchPreviewV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Preview an anomaly watch" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/tables/{table}/pivot: post: description: "Group records by up to 3 row and 2 column fields (with date or\ \ numeric buckets) and compute counts, sums, averages, min/max, distinct counts\ \ or percentiles per cell; every cell has a drill-through filter." + operationId: "postQbitDataAnalysisTablesByTablePivot" parameters: - description: "QQQ table name to analyze." in: "path" @@ -2215,14 +3073,63 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisPivotV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Explore a pivot table" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/tables/{table}/table-profile: post: description: "Profile readable fields under a filter for completeness, uniqueness,\ \ constant or empty columns and candidate keys." + operationId: "postQbitDataAnalysisTablesByTableTableProfile" parameters: - description: "QQQ table name to analyze." in: "path" @@ -2243,14 +3150,63 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisTableProfileV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Profile table completeness and keys" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/tables/{table}/duplicates: post: description: "Group matching records by the selected readable fields and return\ \ repeated tuples with counts and drill-through filters." + operationId: "postQbitDataAnalysisTablesByTableDuplicates" parameters: - description: "QQQ table name to analyze." in: "path" @@ -2271,15 +3227,64 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisDuplicatesResultV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Find duplicate groups" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/runs: post: description: "Start an owner-scoped exact column or table profile. Poll the\ \ returned runId until COMPLETE or FAILED; results honor the current session\ \ permissions." + operationId: "postQbitDataAnalysisRuns" requestBody: content: application/json: @@ -2293,15 +3298,64 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisRunStartedV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Start an exact profile run" + tags: + - "data-analysis" /qqq/v1/qbit/data-analysis/runs/{runId}: get: description: "Read status, timestamps and the typed columnProfile or tableProfile\ \ result of an owner-scoped run. Completed results are rechecked against current\ \ permissions." + operationId: "getQbitDataAnalysisRunsByRunId" parameters: - description: "Owner-scoped analysis run or watch id." in: "path" @@ -2316,7 +3370,61 @@ paths: schema: $ref: "#/components/schemas/DataAnalysisRunStatusV1" description: "Successful Response" + 400: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Bad request: a parameter or the body is invalid (code BAD_REQUEST);\ + \ fieldErrors may name the fields." + 401: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not authenticated, or the session or token is invalid or expired\ + \ (code UNAUTHENTICATED). Sign in again, or refresh the token." + 403: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "The user (or the credential's scope) may not do this (code\ + \ PERMISSION_DENIED)." + 404: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Not found, or not visible to this user (code NOT_FOUND)." + 409: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Conflict with the current state, e.g. an edit conflict or\ + \ an invalid transition (code CONFLICT)." + 429: + content: + application/json: + schema: + $ref: "#/components/schemas/BasicErrorResponseV1" + description: "Too many requests (code RATE_LIMITED); retry later." security: - sessionUuidCookie: - "N/A" + - bearerAuth: + - "N/A" + - oauth2: + - "openid" + - "profile" + - "email" summary: "Get an exact profile run" + tags: + - "data-analysis" +servers: +- description: "The QQQ server that serves this document (paths include /qqq/v1)." + url: "/" +tags: +- description: "Operations of the data-analysis extension." + name: "data-analysis" diff --git a/src/test/java/com/kingsrook/qbits/dataanalysis/DataAnalysisApiTest.java b/src/test/java/com/kingsrook/qbits/dataanalysis/DataAnalysisApiTest.java index b528fda..39531a6 100644 --- a/src/test/java/com/kingsrook/qbits/dataanalysis/DataAnalysisApiTest.java +++ b/src/test/java/com/kingsrook/qbits/dataanalysis/DataAnalysisApiTest.java @@ -136,6 +136,13 @@ class DataAnalysisApiTest extends EngineFixture } var response = invoker.invoke(request); assertThat(response.getStatus()).as(response.getBody()).isEqualTo(status); + if(status >= 400) + { + for(String field : List.of("error", "code", "message", "traceId")) + { + assertThat(response.getJson().path(field).asText()).as(field).isNotBlank(); + } + } return (response.getJson()); } @@ -282,7 +289,7 @@ class DataAnalysisApiTest extends EngineFixture } /******************************************************************************* - ** Generated fragment is committed verbatim and has all fourteen operations. + ** Generated fragment matches the committed contract and validates all references. *******************************************************************************/ @Test void contract() throws Exception @@ -296,8 +303,9 @@ class DataAnalysisApiTest extends EngineFixture Files.createDirectories(path.getParent()); Files.writeString(path, yaml); } - assertThat(Files.exists(path)).as("Regenerate with ./mvnw -o test -Dtest=DataAnalysisApiTest#contract -Dopenapi.write=true -Dqqq.snapshot.version=4.1.0-DATAANALYSIS-SNAPSHOT").isTrue(); + assertThat(Files.exists(path)).as("Regenerate with ./mvnw -o test -Dtest=DataAnalysisApiTest#contract -Dopenapi.write=true -Dqqq.snapshot.version=4.1.99-SNAPSHOT").isTrue(); assertThat(Files.readString(path).replace("\r\n", "\n")).as("OpenAPI changed: regenerate with -Dopenapi.write=true").isEqualTo(yaml.replace("\r\n", "\n")); + assertThat(UiApiContract.check(yaml, Files.readString(path), null, 1, "-Dopenapi.write=true")).isEmpty(); } @Test void pivotHttp() throws Exception @@ -364,7 +372,7 @@ class DataAnalysisApiTest extends EngineFixture } relationshipCountDrills(call(HttpMethod.POST, "/tables/{table}/crosstab", Map.of("rowField", "status", "columnField", "customer"), 200)); // All new schemas are discoverable from the same served OpenAPI catalog. - assertThat(com.kingsrook.qbits.dataanalysis.api.DataAnalysisApi.extension().getSemanticVersion()).isEqualTo("1.4.0"); + assertThat(com.kingsrook.qbits.dataanalysis.api.DataAnalysisApi.extension().getSemanticVersion()).isEqualTo("1.5.0"); } /******************************************************************************* -- 2.54.0 From 442eb4398c318d1b3431b0a914be500fd53bd48d Mon Sep 17 00:00:00 2001 From: James Maes Date: Thu, 15 Oct 2026 12:27:05 -0500 Subject: [PATCH 2/2] chore: target QQQ 4.1.0-RC.15 (UI API 1.6.0) Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c3b8ea..50992f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,7 @@ Following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic ### Changed -- UI API extension data-analysis 1.5.0 (requires QQQ 4.1.0-RC.14, UI API 1.6.0): operationIds, standard error responses with BasicErrorResponseV1, extension tags, contract validation for operationIds and schema references, and HTTP error envelope regression assertions. +- UI API extension data-analysis 1.5.0 (requires QQQ 4.1.0-RC.15, UI API 1.6.0): operationIds, standard error responses with BasicErrorResponseV1, extension tags, contract validation for operationIds and schema references, and HTTP error envelope regression assertions. - Pin QQQ 4.1.0-RC.7 and replace the temporary help loader with core bundle registration and startup sync. diff --git a/pom.xml b/pom.xml index 0087569..e3ff7b7 100644 --- a/pom.xml +++ b/pom.xml @@ -243,7 +243,7 @@ true - 4.1.0-RC.14 + 4.1.0-RC.15 -- 2.54.0