Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/command-graph.html
Original file line number Diff line number Diff line change
Expand Up @@ -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 <profile>", "--json", "-h, --help"] },
description: "List all registered asset types", options: ["-p, --profile <profile>", "--flavor <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 <profile>", "--assetType <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 <profile>", "--assetType <assetType> (e.g., BOARD_V2)", "--json", "-h, --help"] },
description: "Get the JSON schema for an asset type's configuration", options: ["-p, --profile <profile>", "--assetType <assetType> (e.g., BOARD_V2)", "--path <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 <profile>", "--assetType <assetType> (e.g., BOARD_V2)", "--json", "-h, --help"] },

Expand Down
21 changes: 21 additions & 0 deletions docs/user-guide/asset-registry-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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.
Expand Down Expand Up @@ -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 <assetType>` (required) – The asset type identifier
- `--path <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
Expand Down
17 changes: 13 additions & 4 deletions src/commands/asset-registry/asset-registry-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export class AssetRegistryApi {
this.httpClient = () => context.httpClient;
}

public async listTypes(): Promise<AssetRegistryMetadata> {
public async listTypes(flavor?: string): Promise<AssetRegistryMetadata> {
return this.httpClient()
.get(AssetRegistryApi.endpointUrl("types"))
.get(AssetRegistryApi.withQuery(AssetRegistryApi.endpointUrl("types"), { flavor }))
.catch((e) => handleAssetRegistryApiError("listing asset registry types", e));
}

Expand All @@ -27,9 +27,9 @@ export class AssetRegistryApi {
.catch((e) => handleAssetRegistryApiError(`getting asset type '${assetType}'`, e));
}

public async getSchema(assetType: string): Promise<any> {
public async getSchema(assetType: string, path?: string): Promise<any> {
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));
}

Expand All @@ -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, string | undefined>): 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;
}
}
8 changes: 4 additions & 4 deletions src/commands/asset-registry/asset-registry.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export class AssetRegistryService {
this.cuiFileService = new CuiFileService(context);
}

public async listTypes(jsonResponse: boolean): Promise<void> {
const metadata = await this.api.listTypes();
public async listTypes(jsonResponse: boolean, flavor?: string): Promise<void> {
const metadata = await this.api.listTypes(flavor);
const descriptors = Object.values(metadata.types);

if (jsonResponse) {
Expand Down Expand Up @@ -46,8 +46,8 @@ export class AssetRegistryService {
}
}

public async getSchema(assetType: string, jsonResponse: boolean): Promise<void> {
const data = await this.api.getSchema(assetType);
public async getSchema(assetType: string, jsonResponse: boolean, path?: string): Promise<void> {
const data = await this.api.getSchema(assetType, path);
await this.outputResponse(data, jsonResponse);
}

Expand Down
6 changes: 4 additions & 2 deletions src/commands/asset-registry/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class Module extends IModule {

assetRegistryCommand.command("list")
.description("List all registered asset types")
.option("--flavor <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);

Expand All @@ -23,6 +24,7 @@ class Module extends IModule {
assetRegistryCommand.command("schema")
.description("Get the JSON schema for an asset type's configuration")
.requiredOption("--assetType <assetType>", "The asset type identifier (e.g., BOARD_V2)")
.option("--path <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);

Expand All @@ -34,15 +36,15 @@ class Module extends IModule {
}

private async listTypes(context: Context, command: Command, options: OptionValues): Promise<void> {
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<void> {
await new AssetRegistryService(context).getType(options.assetType, !!options.json);
}

private async getSchema(context: Context, command: Command, options: OptionValues): Promise<void> {
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<void> {
Expand Down
14 changes: 14 additions & 0 deletions tests/commands/asset-registry/asset-registry-list.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {} });

Expand Down
21 changes: 21 additions & 0 deletions tests/commands/asset-registry/asset-registry-schema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});
22 changes: 18 additions & 4 deletions tests/integration/commands/asset-registry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});

Expand Down Expand Up @@ -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");
});
});

Expand Down
Loading