diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 7d9b009..78e7f27 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.10.0" + ".": "0.11.0" } diff --git a/.stats.yml b/.stats.yml index ba28b2d..8d39d01 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 28 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/formbricks/hub-eea9fcd4320ce8aaa8745747908c06bdd77e8b4052d9995dde1ecca8168283a8.yml -openapi_spec_hash: 3ee9cb170c7c23af4d2ca746bd1d26dc -config_hash: a2271788ad3aa0e64181e7131d48213f +configured_endpoints: 29 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/formbricks/hub-415f62eebdb4b52442a1a9463944d9038ffff980718442efde39aae9ac43073d.yml +openapi_spec_hash: f6526fad0afa1782ecec5e52610fbcc9 +config_hash: 0b48961f5f0b4dca3a9a4342cd42bd51 diff --git a/CHANGELOG.md b/CHANGELOG.md index f049c0a..56d199f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 0.11.0 (2026-07-31) + +Full Changelog: [v0.10.0...v0.11.0](https://github.com/formbricks/hub-typescript/compare/v0.10.0...v0.11.0) + +### Features + +* expose per-tenant enrichment status and backlog metric (ENG-1670) ([6c72606](https://github.com/formbricks/hub-typescript/commit/6c72606ec41b69a44d9d3e9197dc357d123a1ff2)) + + +### Bug Fixes + +* **taxonomy:** 404 node records on a tenant mismatch (ENG-1887) ([bd87201](https://github.com/formbricks/hub-typescript/commit/bd87201a5af334044acad1a9427820a07148e00d)) + ## 0.10.0 (2026-07-18) Full Changelog: [v0.9.0...v0.10.0](https://github.com/formbricks/hub-typescript/compare/v0.9.0...v0.10.0) diff --git a/api.md b/api.md index 40537dc..ee5731a 100644 --- a/api.md +++ b/api.md @@ -128,3 +128,14 @@ Methods: - client.taxonomy.nodes.listRecords(nodeID, { ...params }) -> NodeListRecordsResponse - client.taxonomy.nodes.rename(nodeID, { ...params }) -> Node - client.taxonomy.nodes.softRemove(nodeID, { ...params }) -> Node + +# EnrichmentStatus + +Types: + +- TypeStatus +- EnrichmentStatusRetrieveResponse + +Methods: + +- client.enrichmentStatus.retrieve({ ...params }) -> EnrichmentStatusRetrieveResponse diff --git a/package.json b/package.json index b1b485f..1dccdbd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@formbricks/hub", - "version": "0.10.0", + "version": "0.11.0", "description": "The official TypeScript library for the Formbricks Hub API", "author": "Formbricks Hub ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/manifest.json b/packages/mcp-server/manifest.json index 2274829..4031146 100644 --- a/packages/mcp-server/manifest.json +++ b/packages/mcp-server/manifest.json @@ -1,7 +1,7 @@ { "dxt_version": "0.2", "name": "@formbricks/hub-mcp", - "version": "0.10.0", + "version": "0.11.0", "description": "The official MCP Server for the Formbricks Hub API", "author": { "name": "Formbricks Hub", diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index c3bea6b..55b5d36 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "@formbricks/hub-mcp", - "version": "0.10.0", + "version": "0.11.0", "description": "The official MCP Server for the Formbricks Hub API", "author": "Formbricks Hub ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts index 9b3ec7b..dcfd713 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -136,6 +136,7 @@ const fuse = new Fuse( 'client.taxonomy.nodes.listRecords', 'client.taxonomy.nodes.rename', 'client.taxonomy.nodes.softRemove', + 'client.enrichmentStatus.retrieve', ], { threshold: 1, shouldSort: true }, ); diff --git a/packages/mcp-server/src/local-docs-search.ts b/packages/mcp-server/src/local-docs-search.ts index d2b1dd2..cbacdc9 100644 --- a/packages/mcp-server/src/local-docs-search.ts +++ b/packages/mcp-server/src/local-docs-search.ts @@ -839,14 +839,14 @@ const EMBEDDED_METHODS: MethodEntry[] = [ httpMethod: 'get', summary: 'List feedback records for a taxonomy node', description: - 'Returns the feedback records assigned to a node and all of its (visible) descendant nodes, via the\nclusters those nodes reference. Tenant-scoped. The `limit` in the response reflects the applied cap.\n', + 'Returns the feedback records assigned to a node and all of its (visible) descendant nodes, via the\nclusters those nodes reference. Tenant-scoped; returns 404 if the node does not belong to the tenant\nor has been removed. An empty `data` therefore means the node genuinely holds no records. The `limit`\nin the response reflects the applied cap.\n', stainlessPath: '(resource) taxonomy.nodes > (method) list_records', qualified: 'client.taxonomy.nodes.listRecords', params: ['node_id: string;', 'tenant_id: string;', 'limit?: number;'], response: "{ data: { id: string; collected_at: string; created_at: string; field_id: string; field_type: 'text' | 'categorical' | 'nps' | 'csat' | 'ces' | 'rating' | 'number' | 'boolean' | 'date'; source_type: string; submission_id: string; tenant_id: string; updated_at: string; emotions?: 'joy' | 'anger' | 'sadness' | 'fear' | 'surprise' | 'disgust'[]; field_group_id?: string; field_group_label?: string; field_label?: string; language?: string; metadata?: object; sentiment?: 'very_negative' | 'negative' | 'neutral' | 'positive' | 'very_positive' | 'mixed'; sentiment_score?: number; source_id?: string; source_name?: string; translation_lang_key?: string; user_id?: string; value_boolean?: boolean; value_date?: string; value_id?: string; value_number?: number; value_text?: string; value_text_translated?: string; }[]; limit: number; }", markdown: - "## list_records\n\n`client.taxonomy.nodes.listRecords(node_id: string, tenant_id: string, limit?: number): { data: feedback_record_data[]; limit: number; }`\n\n**get** `/v1/taxonomy/nodes/{node_id}/records`\n\nReturns the feedback records assigned to a node and all of its (visible) descendant nodes, via the\nclusters those nodes reference. Tenant-scoped. The `limit` in the response reflects the applied cap.\n\n\n### Parameters\n\n- `node_id: string`\n\n- `tenant_id: string`\n Tenant that owns the node.\n\n- `limit?: number`\n Maximum number of feedback records to return.\n\n### Returns\n\n- `{ data: { id: string; collected_at: string; created_at: string; field_id: string; field_type: 'text' | 'categorical' | 'nps' | 'csat' | 'ces' | 'rating' | 'number' | 'boolean' | 'date'; source_type: string; submission_id: string; tenant_id: string; updated_at: string; emotions?: 'joy' | 'anger' | 'sadness' | 'fear' | 'surprise' | 'disgust'[]; field_group_id?: string; field_group_label?: string; field_label?: string; language?: string; metadata?: object; sentiment?: 'very_negative' | 'negative' | 'neutral' | 'positive' | 'very_positive' | 'mixed'; sentiment_score?: number; source_id?: string; source_name?: string; translation_lang_key?: string; user_id?: string; value_boolean?: boolean; value_date?: string; value_id?: string; value_number?: number; value_text?: string; value_text_translated?: string; }[]; limit: number; }`\n\n - `data: { id: string; collected_at: string; created_at: string; field_id: string; field_type: 'text' | 'categorical' | 'nps' | 'csat' | 'ces' | 'rating' | 'number' | 'boolean' | 'date'; source_type: string; submission_id: string; tenant_id: string; updated_at: string; emotions?: 'joy' | 'anger' | 'sadness' | 'fear' | 'surprise' | 'disgust'[]; field_group_id?: string; field_group_label?: string; field_label?: string; language?: string; metadata?: object; sentiment?: 'very_negative' | 'negative' | 'neutral' | 'positive' | 'very_positive' | 'mixed'; sentiment_score?: number; source_id?: string; source_name?: string; translation_lang_key?: string; user_id?: string; value_boolean?: boolean; value_date?: string; value_id?: string; value_number?: number; value_text?: string; value_text_translated?: string; }[]`\n - `limit: number`\n\n### Example\n\n```typescript\nimport FormbricksHub from '@formbricks/hub';\n\nconst client = new FormbricksHub();\n\nconst response = await client.taxonomy.nodes.listRecords('019f177f-9abe-78cd-8008-f40b58e3147d', { tenant_id: 'org-123' });\n\nconsole.log(response);\n```", + "## list_records\n\n`client.taxonomy.nodes.listRecords(node_id: string, tenant_id: string, limit?: number): { data: feedback_record_data[]; limit: number; }`\n\n**get** `/v1/taxonomy/nodes/{node_id}/records`\n\nReturns the feedback records assigned to a node and all of its (visible) descendant nodes, via the\nclusters those nodes reference. Tenant-scoped; returns 404 if the node does not belong to the tenant\nor has been removed. An empty `data` therefore means the node genuinely holds no records. The `limit`\nin the response reflects the applied cap.\n\n\n### Parameters\n\n- `node_id: string`\n\n- `tenant_id: string`\n Tenant that owns the node.\n\n- `limit?: number`\n Maximum number of feedback records to return.\n\n### Returns\n\n- `{ data: { id: string; collected_at: string; created_at: string; field_id: string; field_type: 'text' | 'categorical' | 'nps' | 'csat' | 'ces' | 'rating' | 'number' | 'boolean' | 'date'; source_type: string; submission_id: string; tenant_id: string; updated_at: string; emotions?: 'joy' | 'anger' | 'sadness' | 'fear' | 'surprise' | 'disgust'[]; field_group_id?: string; field_group_label?: string; field_label?: string; language?: string; metadata?: object; sentiment?: 'very_negative' | 'negative' | 'neutral' | 'positive' | 'very_positive' | 'mixed'; sentiment_score?: number; source_id?: string; source_name?: string; translation_lang_key?: string; user_id?: string; value_boolean?: boolean; value_date?: string; value_id?: string; value_number?: number; value_text?: string; value_text_translated?: string; }[]; limit: number; }`\n\n - `data: { id: string; collected_at: string; created_at: string; field_id: string; field_type: 'text' | 'categorical' | 'nps' | 'csat' | 'ces' | 'rating' | 'number' | 'boolean' | 'date'; source_type: string; submission_id: string; tenant_id: string; updated_at: string; emotions?: 'joy' | 'anger' | 'sadness' | 'fear' | 'surprise' | 'disgust'[]; field_group_id?: string; field_group_label?: string; field_label?: string; language?: string; metadata?: object; sentiment?: 'very_negative' | 'negative' | 'neutral' | 'positive' | 'very_positive' | 'mixed'; sentiment_score?: number; source_id?: string; source_name?: string; translation_lang_key?: string; user_id?: string; value_boolean?: boolean; value_date?: string; value_id?: string; value_number?: number; value_text?: string; value_text_translated?: string; }[]`\n - `limit: number`\n\n### Example\n\n```typescript\nimport FormbricksHub from '@formbricks/hub';\n\nconst client = new FormbricksHub();\n\nconst response = await client.taxonomy.nodes.listRecords('019f177f-9abe-78cd-8008-f40b58e3147d', { tenant_id: 'org-123' });\n\nconsole.log(response);\n```", perLanguage: { typescript: { method: 'client.taxonomy.nodes.listRecords', @@ -859,6 +859,32 @@ const EMBEDDED_METHODS: MethodEntry[] = [ }, }, }, + { + name: 'retrieve', + endpoint: '/v1/enrichment-status', + httpMethod: 'get', + summary: 'Get tenant enrichment status', + description: + 'Returns a tenant\'s enrichment progress across the record-level enrichments\n(translation, sentiment, emotions). For each, `enabled` reports whether the enrichment\nis active for the tenant (deployment-configured and switched on / with a resolvable\ntarget language), and `eligible`/`done` are directory-level counts of feedback records\nthat qualify and that have been enriched — the UI derives "in progress" as\n`eligible - done`. When an enrichment is not enabled its counts are zero. The response\ncontains counts only (no record identifiers or content).\n', + stainlessPath: '(resource) enrichment_status > (method) retrieve', + qualified: 'client.enrichmentStatus.retrieve', + params: ['tenant_id: string;'], + response: + '{ emotions: { done: number; eligible: number; enabled: boolean; }; sentiment: { done: number; eligible: number; enabled: boolean; }; tenant_id: string; translation: { done: number; eligible: number; enabled: boolean; }; }', + markdown: + "## retrieve\n\n`client.enrichmentStatus.retrieve(tenant_id: string): { emotions: type_status; sentiment: type_status; tenant_id: string; translation: type_status; }`\n\n**get** `/v1/enrichment-status`\n\nReturns a tenant's enrichment progress across the record-level enrichments\n(translation, sentiment, emotions). For each, `enabled` reports whether the enrichment\nis active for the tenant (deployment-configured and switched on / with a resolvable\ntarget language), and `eligible`/`done` are directory-level counts of feedback records\nthat qualify and that have been enriched — the UI derives \"in progress\" as\n`eligible - done`. When an enrichment is not enabled its counts are zero. The response\ncontains counts only (no record identifiers or content).\n\n\n### Parameters\n\n- `tenant_id: string`\n Tenant whose enrichment status should be returned.\n\n### Returns\n\n- `{ emotions: { done: number; eligible: number; enabled: boolean; }; sentiment: { done: number; eligible: number; enabled: boolean; }; tenant_id: string; translation: { done: number; eligible: number; enabled: boolean; }; }`\n A tenant's enrichment progress across the record-level enrichments. Counts are directory-level totals.\n\n - `emotions: { done: number; eligible: number; enabled: boolean; }`\n - `sentiment: { done: number; eligible: number; enabled: boolean; }`\n - `tenant_id: string`\n - `translation: { done: number; eligible: number; enabled: boolean; }`\n\n### Example\n\n```typescript\nimport FormbricksHub from '@formbricks/hub';\n\nconst client = new FormbricksHub();\n\nconst enrichmentStatus = await client.enrichmentStatus.retrieve({ tenant_id: 'org-123' });\n\nconsole.log(enrichmentStatus);\n```", + perLanguage: { + typescript: { + method: 'client.enrichmentStatus.retrieve', + example: + "import FormbricksHub from '@formbricks/hub';\n\nconst client = new FormbricksHub({\n apiKey: process.env['HUB_API_KEY'], // This is the default and can be omitted\n});\n\nconst enrichmentStatus = await client.enrichmentStatus.retrieve({ tenant_id: 'org-123' });\n\nconsole.log(enrichmentStatus.tenant_id);", + }, + http: { + example: + 'curl http://localhost:8080/v1/enrichment-status \\\n -H "Authorization: Bearer $HUB_API_KEY"', + }, + }, + }, ]; const EMBEDDED_READMES: { language: string; content: string }[] = [ diff --git a/packages/mcp-server/src/methods.ts b/packages/mcp-server/src/methods.ts index 14ceda7..aa24431 100644 --- a/packages/mcp-server/src/methods.ts +++ b/packages/mcp-server/src/methods.ts @@ -178,6 +178,12 @@ export const sdkMethods: SdkMethod[] = [ httpMethod: 'delete', httpPath: '/v1/taxonomy/nodes/{node_id}', }, + { + clientCallName: 'client.enrichmentStatus.retrieve', + fullyQualifiedName: 'enrichmentStatus.retrieve', + httpMethod: 'get', + httpPath: '/v1/enrichment-status', + }, ]; function allowedMethodsForCodeTool(options: McpOptions | undefined): SdkMethod[] | undefined { diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index 0d9a926..26d8307 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -28,7 +28,7 @@ export const newMcpServer = async ({ new McpServer( { name: 'formbricks_hub_api', - version: '0.10.0', + version: '0.11.0', }, { instructions: await getInstructions({ stainlessApiKey, customInstructionsPath }), diff --git a/src/client.ts b/src/client.ts index bcf1975..8f95de5 100644 --- a/src/client.ts +++ b/src/client.ts @@ -17,6 +17,12 @@ import * as Errors from './core/error'; import * as Uploads from './core/uploads'; import * as API from './resources/index'; import { APIPromise } from './core/api-promise'; +import { + EnrichmentStatus, + EnrichmentStatusRetrieveParams, + EnrichmentStatusRetrieveResponse, + TypeStatus, +} from './resources/enrichment-status'; import { Health, HealthCheckResponse } from './resources/health'; import { WebhookCreateParams, @@ -790,6 +796,10 @@ export class FormbricksHub { * Automatic topic/subtopic taxonomy generation, run history, tree browsing, and node edits */ taxonomy: API.Taxonomy = new API.Taxonomy(this); + /** + * Tenant-scoped enrichment progress (translation, sentiment, emotions) + */ + enrichmentStatus: API.EnrichmentStatus = new API.EnrichmentStatus(this); } FormbricksHub.Health = Health; @@ -797,6 +807,7 @@ FormbricksHub.FeedbackRecords = FeedbackRecords; FormbricksHub.Webhooks = Webhooks; FormbricksHub.Tenants = Tenants; FormbricksHub.Taxonomy = Taxonomy; +FormbricksHub.EnrichmentStatus = EnrichmentStatus; export declare namespace FormbricksHub { export type RequestOptions = Opts.RequestOptions; @@ -838,4 +849,11 @@ export declare namespace FormbricksHub { type TaxonomyListFieldsResponse as TaxonomyListFieldsResponse, type TaxonomyListFieldsParams as TaxonomyListFieldsParams, }; + + export { + EnrichmentStatus as EnrichmentStatus, + type TypeStatus as TypeStatus, + type EnrichmentStatusRetrieveResponse as EnrichmentStatusRetrieveResponse, + type EnrichmentStatusRetrieveParams as EnrichmentStatusRetrieveParams, + }; } diff --git a/src/resources/enrichment-status.ts b/src/resources/enrichment-status.ts new file mode 100644 index 0000000..a8fb07d --- /dev/null +++ b/src/resources/enrichment-status.ts @@ -0,0 +1,90 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; + +/** + * Tenant-scoped enrichment progress (translation, sentiment, emotions) + */ +export class EnrichmentStatus extends APIResource { + /** + * Returns a tenant's enrichment progress across the record-level enrichments + * (translation, sentiment, emotions). For each, `enabled` reports whether the + * enrichment is active for the tenant (deployment-configured and switched on / + * with a resolvable target language), and `eligible`/`done` are directory-level + * counts of feedback records that qualify and that have been enriched — the UI + * derives "in progress" as `eligible - done`. When an enrichment is not enabled + * its counts are zero. The response contains counts only (no record identifiers or + * content). + */ + retrieve( + query: EnrichmentStatusRetrieveParams, + options?: RequestOptions, + ): APIPromise { + return this._client.get('/v1/enrichment-status', { query, ...options }); + } +} + +/** + * One enrichment's progress for a tenant. When `enabled` is false, `eligible` and + * `done` are zero. + */ +export interface TypeStatus { + /** + * Eligible records that have been enriched. + */ + done: number; + + /** + * Feedback records that qualify for this enrichment. + */ + eligible: number; + + /** + * Whether the enrichment is active for the tenant (deployment-configured and + * switched on / with a resolvable target language). + */ + enabled: boolean; +} + +/** + * A tenant's enrichment progress across the record-level enrichments. Counts are + * directory-level totals. + */ +export interface EnrichmentStatusRetrieveResponse { + /** + * One enrichment's progress for a tenant. When `enabled` is false, `eligible` and + * `done` are zero. + */ + emotions: TypeStatus; + + /** + * One enrichment's progress for a tenant. When `enabled` is false, `eligible` and + * `done` are zero. + */ + sentiment: TypeStatus; + + tenant_id: string; + + /** + * One enrichment's progress for a tenant. When `enabled` is false, `eligible` and + * `done` are zero. + */ + translation: TypeStatus; +} + +export interface EnrichmentStatusRetrieveParams { + /** + * Tenant whose enrichment status should be returned. + */ + tenant_id: string; +} + +export declare namespace EnrichmentStatus { + export { + type TypeStatus as TypeStatus, + type EnrichmentStatusRetrieveResponse as EnrichmentStatusRetrieveResponse, + type EnrichmentStatusRetrieveParams as EnrichmentStatusRetrieveParams, + }; +} diff --git a/src/resources/index.ts b/src/resources/index.ts index 50e1854..fc2add8 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -1,5 +1,11 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +export { + EnrichmentStatus, + type TypeStatus, + type EnrichmentStatusRetrieveResponse, + type EnrichmentStatusRetrieveParams, +} from './enrichment-status'; export { FeedbackRecords, type FeedbackRecordData, diff --git a/src/resources/taxonomy/nodes.ts b/src/resources/taxonomy/nodes.ts index efb8322..9aca787 100644 --- a/src/resources/taxonomy/nodes.ts +++ b/src/resources/taxonomy/nodes.ts @@ -13,8 +13,10 @@ import { path } from '../../internal/utils/path'; export class Nodes extends APIResource { /** * Returns the feedback records assigned to a node and all of its (visible) - * descendant nodes, via the clusters those nodes reference. Tenant-scoped. The - * `limit` in the response reflects the applied cap. + * descendant nodes, via the clusters those nodes reference. Tenant-scoped; returns + * 404 if the node does not belong to the tenant or has been removed. An empty + * `data` therefore means the node genuinely holds no records. The `limit` in the + * response reflects the applied cap. * * @example * ```ts diff --git a/src/version.ts b/src/version.ts index c2e5b96..9085e9d 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.10.0'; // x-release-please-version +export const VERSION = '0.11.0'; // x-release-please-version diff --git a/tests/api-resources/enrichment-status.test.ts b/tests/api-resources/enrichment-status.test.ts new file mode 100644 index 0000000..b87c9aa --- /dev/null +++ b/tests/api-resources/enrichment-status.test.ts @@ -0,0 +1,27 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import FormbricksHub from '@formbricks/hub'; + +const client = new FormbricksHub({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource enrichmentStatus', () => { + // Mock server tests are disabled + test.skip('retrieve: only required params', async () => { + const responsePromise = client.enrichmentStatus.retrieve({ tenant_id: 'org-123' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Mock server tests are disabled + test.skip('retrieve: required and optional params', async () => { + const response = await client.enrichmentStatus.retrieve({ tenant_id: 'org-123' }); + }); +});