diff --git a/docs/command-graph.html b/docs/command-graph.html index f8b76d74..2645c540 100644 --- a/docs/command-graph.html +++ b/docs/command-graph.html @@ -269,11 +269,11 @@ // asset-registry { id: "ar_list", label: "list", group: "command", path: "asset-registry list", - description: "List all registered asset types", options: ["-p, --profile ", "--json", "-h, --help"] }, + description: "List all registered asset types", options: ["-p, --profile ", "--flavor ", "--json", "-h, --help"] }, { id: "ar_get", label: "get", group: "command", path: "asset-registry get", description: "Get the descriptor for a specific asset type", options: ["-p, --profile ", "--assetType (e.g., BOARD_V2)", "--json", "-h, --help"] }, { id: "ar_schema", label: "schema", group: "command", path: "asset-registry schema", - description: "Get the JSON schema for an asset type's configuration", options: ["-p, --profile ", "--assetType (e.g., BOARD_V2)", "--json", "-h, --help"] }, + description: "Get the JSON schema for an asset type's configuration", options: ["-p, --profile ", "--assetType (e.g., BOARD_V2)", "--path ", "--json", "-h, --help"] }, { id: "ar_examples", label: "examples", group: "command", path: "asset-registry examples", description: "Get example configurations for an asset type", options: ["-p, --profile ", "--assetType (e.g., BOARD_V2)", "--json", "-h, --help"] }, diff --git a/docs/user-guide/asset-registry-commands.md b/docs/user-guide/asset-registry-commands.md index dec25db0..89f47565 100644 --- a/docs/user-guide/asset-registry-commands.md +++ b/docs/user-guide/asset-registry-commands.md @@ -26,6 +26,17 @@ It is also possible to use the `--json` option for writing the full response to content-cli asset-registry list --json ``` +To list only the asset types that apply to one package flavor, pass `--flavor`: + +``` +content-cli asset-registry list --flavor STUDIO +``` + +Options: + +- `--flavor ` – List only the asset types that apply to this package flavor (`STUDIO`, `CCM` or `OCDM`, case-insensitive). Asset types that do not declare any flavors apply to all of them. +- `--json` – Write the full response to a JSON file in the working directory + ## Get Asset Type Get the full descriptor for a specific asset type, including schema version, service base path, and endpoint paths. @@ -67,9 +78,19 @@ The response is the full JSON Schema (draft-07) for the asset type. Use `--json` content-cli asset-registry schema --assetType BOARD_V2 --json ``` +Large schemas can be read one part at a time with `--path`. Write the path the same way the schema writes its `$ref` values. `#` returns the entry point, the top level of the schema without its definitions, and any `$ref` in a response can be passed as the next `--path`: + +``` +content-cli asset-registry schema --assetType BOARD_V2 --path '#' +content-cli asset-registry schema --assetType BOARD_V2 --path '#/$defs/table' +``` + +Quote the path so the shell does not interpret `#` or `$`. Asset types that do not support partial schemas return the full schema. + Options: - `--assetType ` (required) – The asset type identifier +- `--path ` – Return only this part of the schema, written like a `$ref` inside it (for example, `#/$defs/table`) - `--json` – Write the schema to a JSON file in the working directory ## Validating assets diff --git a/src/commands/asset-registry/asset-registry-api.ts b/src/commands/asset-registry/asset-registry-api.ts index 630c1dbb..71126682 100644 --- a/src/commands/asset-registry/asset-registry-api.ts +++ b/src/commands/asset-registry/asset-registry-api.ts @@ -15,9 +15,9 @@ export class AssetRegistryApi { this.httpClient = () => context.httpClient; } - public async listTypes(): Promise { + public async listTypes(flavor?: string): Promise { return this.httpClient() - .get(AssetRegistryApi.endpointUrl("types")) + .get(AssetRegistryApi.withQuery(AssetRegistryApi.endpointUrl("types"), { flavor })) .catch((e) => handleAssetRegistryApiError("listing asset registry types", e)); } @@ -27,9 +27,9 @@ export class AssetRegistryApi { .catch((e) => handleAssetRegistryApiError(`getting asset type '${assetType}'`, e)); } - public async getSchema(assetType: string): Promise { + public async getSchema(assetType: string, path?: string): Promise { return this.httpClient() - .get(AssetRegistryApi.endpointUrl("schemas", encodeURIComponent(assetType))) + .get(AssetRegistryApi.withQuery(AssetRegistryApi.endpointUrl("schemas", encodeURIComponent(assetType)), { path })) .catch((e) => handleAssetRegistryApiError(`getting schema for asset type '${assetType}'`, e)); } @@ -42,4 +42,13 @@ export class AssetRegistryApi { private static endpointUrl(...segments: string[]): string { return `${AssetRegistryApi.BASE_URL}/${segments.join("/")}`; } + + private static withQuery(url: string, params: Record): string { + const queryParams = new URLSearchParams(); + Object.entries(params) + .filter(([, value]) => value !== undefined && value.trim() !== "") + .forEach(([name, value]) => queryParams.set(name, value)); + const query = queryParams.toString(); + return query ? `${url}?${query}` : url; + } } diff --git a/src/commands/asset-registry/asset-registry.service.ts b/src/commands/asset-registry/asset-registry.service.ts index 7f69fa6f..e2ae3c0d 100644 --- a/src/commands/asset-registry/asset-registry.service.ts +++ b/src/commands/asset-registry/asset-registry.service.ts @@ -15,8 +15,8 @@ export class AssetRegistryService { this.cuiFileService = new CuiFileService(context); } - public async listTypes(jsonResponse: boolean): Promise { - const metadata = await this.api.listTypes(); + public async listTypes(jsonResponse: boolean, flavor?: string): Promise { + const metadata = await this.api.listTypes(flavor); const descriptors = Object.values(metadata.types); if (jsonResponse) { @@ -46,8 +46,8 @@ export class AssetRegistryService { } } - public async getSchema(assetType: string, jsonResponse: boolean): Promise { - const data = await this.api.getSchema(assetType); + public async getSchema(assetType: string, jsonResponse: boolean, path?: string): Promise { + const data = await this.api.getSchema(assetType, path); await this.outputResponse(data, jsonResponse); } diff --git a/src/commands/asset-registry/module.ts b/src/commands/asset-registry/module.ts index 20f8afdf..5d989979 100644 --- a/src/commands/asset-registry/module.ts +++ b/src/commands/asset-registry/module.ts @@ -11,6 +11,7 @@ class Module extends IModule { assetRegistryCommand.command("list") .description("List all registered asset types") + .option("--flavor ", "Lists only the asset types that apply to the given package flavor (e.g., STUDIO)") .option("--json", "Return the response as a JSON file") .action(this.listTypes); @@ -23,6 +24,7 @@ class Module extends IModule { assetRegistryCommand.command("schema") .description("Get the JSON schema for an asset type's configuration") .requiredOption("--assetType ", "The asset type identifier (e.g., BOARD_V2)") + .option("--path ", "Return only this part of the schema, written like a $ref inside it (e.g., '#/$defs/table'); '#' returns the entry point") .option("--json", "Return the response as a JSON file") .action(this.getSchema); @@ -34,7 +36,7 @@ class Module extends IModule { } private async listTypes(context: Context, command: Command, options: OptionValues): Promise { - await new AssetRegistryService(context).listTypes(!!options.json); + await new AssetRegistryService(context).listTypes(!!options.json, options.flavor); } private async getType(context: Context, command: Command, options: OptionValues): Promise { @@ -42,7 +44,7 @@ class Module extends IModule { } private async getSchema(context: Context, command: Command, options: OptionValues): Promise { - await new AssetRegistryService(context).getSchema(options.assetType, !!options.json); + await new AssetRegistryService(context).getSchema(options.assetType, !!options.json, options.path); } private async getExamples(context: Context, command: Command, options: OptionValues): Promise { diff --git a/tests/commands/asset-registry/asset-registry-list.spec.ts b/tests/commands/asset-registry/asset-registry-list.spec.ts index 5d5e28dd..da22696e 100644 --- a/tests/commands/asset-registry/asset-registry-list.spec.ts +++ b/tests/commands/asset-registry/asset-registry-list.spec.ts @@ -90,6 +90,20 @@ describe("Asset registry list", () => { expect(written.types["SEMANTIC_MODEL"].assetType).toBe("SEMANTIC_MODEL"); }); + it("Should list only the asset types of the given flavor", async () => { + const studioOnly: AssetRegistryMetadata = { + types: { + BOARD_V2: metadata.types.BOARD_V2, + }, + }; + mockAxiosGet("https://myTeam.celonis.cloud/pacman/api/core/asset-registry/types?flavor=STUDIO", studioOnly); + + await new AssetRegistryService(testContext).listTypes(false, "STUDIO"); + + expect(loggingTestTransport.logMessages).toHaveLength(1); + expect(loggingTestTransport.logMessages[0].message).toContain("BOARD_V2 - View [DASHBOARDS]"); + }); + it("Should handle empty registry", async () => { mockAxiosGet("https://myTeam.celonis.cloud/pacman/api/core/asset-registry/types", { types: {} }); diff --git a/tests/commands/asset-registry/asset-registry-schema.spec.ts b/tests/commands/asset-registry/asset-registry-schema.spec.ts index 6e65d80d..483cf708 100644 --- a/tests/commands/asset-registry/asset-registry-schema.spec.ts +++ b/tests/commands/asset-registry/asset-registry-schema.spec.ts @@ -36,4 +36,25 @@ describe("Asset registry schema", () => { expect(written.$schema).toBe("http://json-schema.org/draft-07/schema#"); expect(written.title).toBe("Board"); }); + + it("Should request only the given part of the schema when a path is passed", async () => { + const fragment = { type: "object", properties: { columns: { $ref: "#/$defs/column" } } }; + mockAxiosGet( + "https://myTeam.celonis.cloud/pacman/api/core/asset-registry/schemas/BOARD_V2?path=%23%2F%24defs%2Ftable", + fragment + ); + + await new AssetRegistryService(testContext).getSchema("BOARD_V2", false, "#/$defs/table"); + + expect(loggingTestTransport.logMessages).toHaveLength(1); + expect(loggingTestTransport.logMessages[0].message).toContain("#/$defs/column"); + }); + + it("Should request the full schema when the path is blank", async () => { + mockAxiosGet("https://myTeam.celonis.cloud/pacman/api/core/asset-registry/schemas/BOARD_V2", schemaResponse); + + await new AssetRegistryService(testContext).getSchema("BOARD_V2", false, " "); + + expect(loggingTestTransport.logMessages[0].message).toContain("Board"); + }); }); diff --git a/tests/integration/commands/asset-registry.spec.ts b/tests/integration/commands/asset-registry.spec.ts index 5a0d85bf..3b43a17b 100644 --- a/tests/integration/commands/asset-registry.spec.ts +++ b/tests/integration/commands/asset-registry.spec.ts @@ -29,12 +29,19 @@ describe("asset-registry command integration", () => { const result = await runCli(["asset-registry", "schema", "--assetType", "BOARD_V2", "--json"]); expect(result.exitCode).toBe(0); - expect(mockService.getSchema).toHaveBeenCalledWith("BOARD_V2", true); + expect(mockService.getSchema).toHaveBeenCalledWith("BOARD_V2", true, undefined); }); it("defaults --json to false when omitted", async () => { await runCli(["asset-registry", "schema", "--assetType", "BOARD_V2"]); - expect(mockService.getSchema).toHaveBeenCalledWith("BOARD_V2", false); + expect(mockService.getSchema).toHaveBeenCalledWith("BOARD_V2", false, undefined); + }); + + it("passes --path to getSchema", async () => { + const result = await runCli(["asset-registry", "schema", "--assetType", "BOARD_V2", "--path", "#/$defs/table"]); + + expect(result.exitCode).toBe(0); + expect(mockService.getSchema).toHaveBeenCalledWith("BOARD_V2", false, "#/$defs/table"); }); }); @@ -70,12 +77,19 @@ describe("asset-registry command integration", () => { const result = await runCli(["asset-registry", "list", "--json"]); expect(result.exitCode).toBe(0); - expect(mockService.listTypes).toHaveBeenCalledWith(true); + expect(mockService.listTypes).toHaveBeenCalledWith(true, undefined); }); it("calls listTypes without --json", async () => { await runCli(["asset-registry", "list"]); - expect(mockService.listTypes).toHaveBeenCalledWith(false); + expect(mockService.listTypes).toHaveBeenCalledWith(false, undefined); + }); + + it("passes --flavor to listTypes", async () => { + const result = await runCli(["asset-registry", "list", "--flavor", "STUDIO"]); + + expect(result.exitCode).toBe(0); + expect(mockService.listTypes).toHaveBeenCalledWith(false, "STUDIO"); }); });