Skip to content

Commit 4bb31f0

Browse files
fix: customdomain and other refactors
1 parent 90c55e3 commit 4bb31f0

8 files changed

Lines changed: 126 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111

12-
- [Swagger] Refactored `publish_portal_product` to build published URLs dynamically from `SWAGGER_PORTAL_BASE_PATH`, support preview and section/table-of-contents paths, and return the resolved `liveUrl` or `previewUrl` in the publish response.
12+
- [Swagger] Refactored `publish_portal_product` to build published URLs dynamically from `SWAGGER_PORTAL_BASE_PATH`, support preview and section/table-of-contents paths, and return the resolved `liveUrl` or `previewUrl` plus product, portal, and table-of-contents metadata in the publish response. If `portal.customDomain` is present, the client now uses it as the full host without appending the portal UI suffix.
1313

1414
### Added
1515

docs/products/SmartBear MCP Server/swagger-portal-integration.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,14 @@ The Swagger Portal client provides comprehensive portal and product management c
116116
#### `publish_portal_product`
117117

118118
- Purpose: Publish a product's content to make it live or as preview. This endpoint publishes the current content of a product, making it visible to portal visitors. Use preview mode to test before going live.
119-
- Returns: Publication status information, including the generated `liveUrl` for live publication or `previewUrl` for preview publication when the product can be resolved.
119+
- Returns: Publication status information with the following structure:
120+
- `success` (boolean): Publication success status
121+
- `preview` (boolean): Whether this is a preview or live publication
122+
- `liveUrl` (string, conditional): Generated live URL (only when preview is false)
123+
- `previewUrl` (string, conditional): Generated preview URL (only when preview is true)
124+
- `product` (object): Product metadata with fields: `id`, `name`, `slug`
125+
- `portal` (object): Portal metadata with fields: `id`, `name`, `subdomain`, `customDomain`
126+
- `tableOfContentsItem` (object | null): Table of contents metadata with fields: `id`, `slug`, `title`, `order`, `parentId` (null when tableOfContentsId is not provided or not found)
120127
- Use case: Make product content visible to portal visitors, either as live content or preview for testing.
121128
- Parameters:
122129

@@ -126,7 +133,11 @@ The Swagger Portal client provides comprehensive portal and product management c
126133
| `preview` | Whether to publish as preview (true) or live (false). Preview allows testing before going live. Defaults to false (live publication) | boolean | No |
127134
| `tableOfContentsId` | Optional table of contents UUID, or identifier in the format 'portal-subdomain:product-slug:section-slug:table-of-contents-slug' used to resolve a specific live URL path | string | No |
128135

129-
The publish response now includes `liveUrl` for live publication and `previewUrl` for preview publication. The URL is built dynamically from the configured `portalBasePath`, so it works across dev2, int, and production environments. When `tableOfContentsId` resolves, the returned URL includes the section and table-of-contents path.
136+
**Response Details:**
137+
- When `portal.customDomain` is present, it is used as the full host in the generated URL without appending the portal UI suffix
138+
- When `customDomain` is not present, the URL is built using `portal.subdomain` + the portal UI suffix (`.portal.swaggerhub.com`)
139+
- When `tableOfContentsId` resolves successfully, the returned URL includes the section and table-of-contents path in the format: `/{productSlug}/{sectionSlug}/{tocSlug}`
140+
- The response includes only essential fields from product, portal, and tableOfContentsItem following React/TypeScript best practices using Pick<> utility types
130141

131142
### Product Sections Management
132143

src/swagger/client/api.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -381,15 +381,12 @@ export class SwaggerAPI {
381381
}),
382382
]);
383383

384-
// Fetch portal details after portalId ID is available
385384
const portalDetails = await this.getPortal(
386385
String(productDetails.portalId ?? ""),
387386
);
388387

389-
// Get the first section (primary section)
390388
const targetSection = sections.items[0] ?? null;
391389

392-
// Resolve the target table of contents item if tableOfContentsId is provided
393390
const targetTocItem =
394391
tableOfContentsId && targetSection
395392
? findTableOfContentsItem(
@@ -398,18 +395,15 @@ export class SwaggerAPI {
398395
)
399396
: null;
400397

401-
// Build live URL using environment-specific domain
402-
const host = portalDetails.customDomain ?? portalDetails.subdomain;
403398
const publicationUrl = buildPortalLiveUrl(
404399
this.config,
405-
host,
400+
portalDetails,
406401
productDetails.slug,
407402
targetSection,
408403
targetTocItem,
409404
preview,
410405
);
411406

412-
// Publish the product to the portal
413407
const response = await fetch(
414408
`${this.config.portalBasePath}/products/${productId}/published-content?preview=${preview}`,
415409
{
@@ -424,9 +418,28 @@ export class SwaggerAPI {
424418

425419
return {
426420
...result,
421+
preview,
427422
...(preview
428423
? { previewUrl: publicationUrl }
429424
: { liveUrl: publicationUrl }),
425+
product: {
426+
id: productDetails.id,
427+
name: productDetails.name,
428+
slug: productDetails.slug,
429+
},
430+
portal: {
431+
id: portalDetails.id,
432+
name: portalDetails.name,
433+
subdomain: portalDetails.subdomain,
434+
customDomain: portalDetails.customDomain,
435+
},
436+
tableOfContentsItem: targetTocItem ? {
437+
id: targetTocItem.id,
438+
slug: targetTocItem.slug,
439+
title: targetTocItem.title,
440+
order: targetTocItem.order,
441+
parentId: targetTocItem.parentId,
442+
} : null,
430443
} as PublishPortalProductResponse;
431444
}
432445

src/swagger/client/portal-types.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const GetProductSectionsArgsSchema = z.object({
3939
.number()
4040
.optional()
4141
.describe(
42-
"Number of items per page for pagination - controls how many results are returned per page (default is 20)",
42+
"Number of items per page for pagination - controls how many results are returned per page (default is 10)",
4343
),
4444
});
4545

@@ -339,7 +339,7 @@ export const PublishProductArgsSchema = ProductArgsSchema.extend({
339339
.string()
340340
.optional()
341341
.describe(
342-
"The table of contents UUID, or identifier in the format 'portal-subdomain:product-slug:section-slug:table-of-contents-slug'",
342+
"Optional table of contents UUID, or identifier in the format 'portal-subdomain:product-slug:section-slug:table-of-contents-slug'. When provided, publishPortalProduct uses it to resolve the published URL path for the returned preview/live link.",
343343
),
344344
preview: z
345345
.boolean()
@@ -431,8 +431,12 @@ export type CreateTableOfContentsBody = Omit<
431431
export type UpdateDocumentBody = Omit<UpdateDocumentArgs, "documentId">;
432432

433433
export type PublishPortalProductResponse = SuccessResponse & {
434+
preview: boolean;
434435
liveUrl?: string;
435436
previewUrl?: string;
437+
product?: Pick<Product, "id" | "name" | "slug">;
438+
portal?: Pick<Portal, "id" | "name" | "subdomain" | "customDomain">;
439+
tableOfContentsItem?: Pick<TableOfContentsItem, "id" | "slug" | "title" | "order" | "parentId"> | null;
436440
};
437441

438442
// Response types for better type safety

src/swagger/client/tools.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export const TOOLS: SwaggerToolParams[] = [
107107
title: "Publish Portal Product",
108108
toolset: "Products",
109109
summary:
110-
"Publish a product's content to make it live or as preview. This endpoint publishes the current content of a product, making it visible to portal visitors. Use preview mode to test before going live.",
110+
"Publish a product's content to make it live or as preview. This endpoint publishes the current content of a product, making it visible to portal visitors. Use preview mode to test before going live. Optionally provide tableOfContentsId to form liveUrl or previewUrl for specific table-of-contents item or specific page url. Returns a PublishPortalProductResponse containing: success status, preview boolean, liveUrl or previewUrl (depending on preview mode), and narrowed metadata objects for product (id, name, slug), portal (id, name, subdomain, customDomain), and tableOfContentsItem (id, slug, title, order, parentId) when resolved.",
111111
inputSchema: PublishProductArgsSchema,
112112
handler: "publishPortalProduct",
113113
},

src/swagger/client/utils.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import type { SwaggerConfiguration } from "./configuration";
33
import type { TableOfContentsItem } from "./portal-types";
44

55
type UrlSegmentSource = { slug?: string } | null;
6+
type PortalHostSource = {
7+
customDomain?: string | null;
8+
subdomain?: string | null;
9+
} | null;
610

711
/**
812
* Recursively search for a table of contents item by ID.
@@ -30,17 +34,19 @@ export function findTableOfContentsItem(
3034
*/
3135
export function buildPortalLiveUrl(
3236
config: SwaggerConfiguration,
33-
host: string | undefined,
37+
portal: PortalHostSource,
3438
productSlug: string | undefined,
3539
section: UrlSegmentSource,
3640
tocItem: UrlSegmentSource,
3741
preview: boolean = false,
3842
): string {
43+
const host = portal?.customDomain ?? portal?.subdomain;
44+
const portalUiDomain = portal?.customDomain ? "" : config.getPortalUiDomainSuffix();
45+
3946
if (!host || !productSlug) {
4047
return "";
4148
}
4249

43-
const portalUiDomain = config.getPortalUiDomainSuffix();
4450
const baseUrl = `https://${host}${portalUiDomain}/${productSlug}`;
4551
const previewSuffix = preview ? "?preview=product" : "";
4652

src/tests/unit/common/__snapshots__/server-definitions.test.ts.snap

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10440,7 +10440,7 @@ None",
1044010440
- productId (string) *required*: Product UUID or identifier in the format 'portal-subdomain:product-slug' - unique identifier for the product
1044110441
- embed (array): List of related entities to embed in the response - e.g., ['tableOfContents', 'tableOfContents.swaggerhubApi'] to include table of contents and SwaggerHub API details
1044210442
- page (number): Page number for paginated results - specifies which page of results to retrieve (default is 1)
10443-
- size (number): Number of items per page for pagination - controls how many results are returned per page (default is 20)",
10443+
- size (number): Number of items per page for pagination - controls how many results are returned per page (default is 10)",
1044410444
"inputSchema": [
1044510445
"embed",
1044610446
"page",
@@ -10519,16 +10519,18 @@ None",
1051910519
"readOnlyHint": true,
1052010520
"title": "Swagger: Publish Portal Product",
1052110521
},
10522-
"description": "Publish a product's content to make it live or as preview. This endpoint publishes the current content of a product, making it visible to portal visitors. Use preview mode to test before going live.
10522+
"description": "Publish a product's content to make it live or as preview. This endpoint publishes the current content of a product, making it visible to portal visitors. Use preview mode to test before going live. Optionally provide tableOfContentsId to form liveUrl or previewUrl for specific table-of-contents item or specific page url. Returns a PublishPortalProductResponse containing: success status, preview boolean, liveUrl or previewUrl (depending on preview mode), and narrowed metadata objects for product (id, name, slug), portal (id, name, subdomain, customDomain), and tableOfContentsItem (id, slug, title, order, parentId) when resolved.
1052310523

1052410524
**Toolset:** Products
1052510525

1052610526
**Parameters:**
1052710527
- productId (string) *required*: Product UUID or identifier in the format 'portal-subdomain:product-slug' - unique identifier for the product
10528+
- tableOfContentsId (string): Optional table of contents UUID, or identifier in the format 'portal-subdomain:product-slug:section-slug:table-of-contents-slug'. When provided, publishPortalProduct uses it to resolve the published URL path for the returned preview/live link.
1052810529
- preview (boolean): Whether to publish as preview (true) or live (false). Preview allows testing before going live. Defaults to false (live publication) (default: false)",
1052910530
"inputSchema": [
1053010531
"preview",
1053110532
"productId",
10533+
"tableOfContentsId",
1053210534
],
1053310535
"outputSchema": undefined,
1053410536
"title": "Swagger: Publish Portal Product",

src/tests/unit/swagger/api.test.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,12 @@ describe("SwaggerAPI", () => {
267267
subdomain: "testportal",
268268
};
269269

270+
const customDomainPortalResponse = {
271+
id: portalId,
272+
name: "Test Portal",
273+
customDomain: "testCustomDomain.portal-testing.com",
274+
};
275+
270276
const sectionsResponse = {
271277
page: {
272278
number: 0,
@@ -353,8 +359,27 @@ describe("SwaggerAPI", () => {
353359

354360
expect(result).toEqual({
355361
success: true,
362+
preview: false,
356363
liveUrl:
357364
`https://testportal.portal.swaggerhub.com/${productSlug}/docs/getting-started`,
365+
product: {
366+
id: productResponse.id,
367+
name: productResponse.name,
368+
slug: productResponse.slug,
369+
},
370+
portal: {
371+
id: portalResponse.id,
372+
name: portalResponse.name,
373+
subdomain: portalResponse.subdomain,
374+
customDomain: undefined,
375+
},
376+
tableOfContentsItem: {
377+
id: sectionsResponse.items[0].tableOfContents[0].id,
378+
slug: sectionsResponse.items[0].tableOfContents[0].slug,
379+
title: sectionsResponse.items[0].tableOfContents[0].title,
380+
order: sectionsResponse.items[0].tableOfContents[0].order,
381+
parentId: sectionsResponse.items[0].tableOfContents[0].parentId,
382+
},
358383
});
359384
});
360385

@@ -378,8 +403,56 @@ describe("SwaggerAPI", () => {
378403

379404
expect(result).toEqual({
380405
success: true,
406+
preview: true,
381407
previewUrl:
382408
`https://testportal.portal.swaggerhub.com/${productSlug}?preview=product`,
409+
product: {
410+
id: productResponse.id,
411+
name: productResponse.name,
412+
slug: productResponse.slug,
413+
},
414+
portal: {
415+
id: portalResponse.id,
416+
name: portalResponse.name,
417+
subdomain: portalResponse.subdomain,
418+
customDomain: undefined,
419+
},
420+
tableOfContentsItem: null,
421+
});
422+
});
423+
424+
it("should use customDomain as the full host when present", async () => {
425+
fetchMock
426+
.mockResponseOnce(JSON.stringify(productResponse))
427+
.mockResponseOnce(JSON.stringify(sectionsResponse))
428+
.mockResponseOnce(JSON.stringify(customDomainPortalResponse))
429+
.mockResponseOnce(JSON.stringify(publishResponse));
430+
431+
const result = await api.publishPortalProduct(productId, true, tocId);
432+
433+
expect(result).toEqual({
434+
success: true,
435+
preview: true,
436+
previewUrl:
437+
"https://testCustomDomain.portal-testing.com/test-product/docs/getting-started?preview=product",
438+
product: {
439+
id: productResponse.id,
440+
name: productResponse.name,
441+
slug: productResponse.slug,
442+
},
443+
portal: {
444+
id: customDomainPortalResponse.id,
445+
name: customDomainPortalResponse.name,
446+
subdomain: undefined,
447+
customDomain: customDomainPortalResponse.customDomain,
448+
},
449+
tableOfContentsItem: {
450+
id: sectionsResponse.items[0].tableOfContents[0].id,
451+
slug: sectionsResponse.items[0].tableOfContents[0].slug,
452+
title: sectionsResponse.items[0].tableOfContents[0].title,
453+
order: sectionsResponse.items[0].tableOfContents[0].order,
454+
parentId: sectionsResponse.items[0].tableOfContents[0].parentId,
455+
},
383456
});
384457
});
385458
});

0 commit comments

Comments
 (0)