Skip to content
Draft
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
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
/src/commands/analysis/ @celonis/process-analytics
/src/commands/cpm4/ @celonis/cpm4
/src/commands/data-pipeline/ @Dusan-r @IvanGandacov @EktaCelonis @gorasoCelonis
/src/commands/data-model-migration/ @celonis/studio-platform
/tests/commands/data-model-migration/ @celonis/studio-platform
/src/commands/studio/ @celonis/astro @celonis/studio-platform
/tests/commands/studio/ @celonis/astro @celonis/studio-platform
/package.json @celonis/studio-platform @aocelo @siavash-celonis
Expand Down
10 changes: 8 additions & 2 deletions docs/command-graph.html
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,9 @@
{ id: "export_data_pool", label: "data-pool", group: "command", path: "export data-pool",
description: "Command to export a data pool",
options: ["-p, --profile <profile>", "--id <id>", "--outputToJsonFile", "-h, --help"] },
{ id: "export_data_model", label: "data-model", group: "command", path: "export data-model",
description: "Export a data model with tables, foreign keys, and process configurations",
options: ["-p, --profile <profile>", "--poolId <poolId>", "--dataModelId <dataModelId>", "--outputToJsonFile", "-h, --help"] },

// import
{ id: "import_action_flows", label: "action-flows", group: "command", path: "import action-flows",
Expand Down Expand Up @@ -247,6 +250,9 @@
description: "Command to push a data pool", options: ["-p, --profile <profile>", "-f, --file <file>", "-h, --help"] },
{ id: "push_data_pools", label: "data-pools", group: "command", path: "push data-pools",
description: "Command to push data pools", options: ["-p, --profile <profile>", "-h, --help"] },
{ id: "push_semantic_model", label: "semantic-model", group: "command", path: "push semantic-model",
description: "Convert a data model into semantic entities and push them into a pig package",
options: ["-p, --profile <profile>", "--poolId <poolId>", "--dataModelId <dataModelId>", "--package <packageKey>", "--schema <schema>", "--namespace <namespace>", "-f, --fromFile <file>", "--dryRun", "--outputToJsonFile", "-h, --help"] },
{ id: "push_asset", label: "asset", group: "command", path: "push asset",
description: "Command to push an asset to Studio", options: ["-p, --profile <profile>", "-f, --file <file>", "--package <packageKey>", "-h, --help"] },
{ id: "push_assets", label: "assets", group: "command", path: "push assets",
Expand Down Expand Up @@ -479,15 +485,15 @@

["area_analyze","analyze_action_flows"],

["area_export","export_action_flows"],["area_export","export_data_pool"],
["area_export","export_action_flows"],["area_export","export_data_pool"],["area_export","export_data_model"],

["area_import","import_action_flows"],["area_import","import_data_pools"],

["area_pull","pull_skill"],["area_pull","pull_bookmarks"],["area_pull","pull_data_pool"],
["area_pull","pull_asset"],["area_pull","pull_package"],["area_pull","pull_view_bookmarks"],

["area_push","push_skill"],["area_push","push_bookmarks"],["area_push","push_ctp"],
["area_push","push_data_pool"],["area_push","push_data_pools"],["area_push","push_asset"],
["area_push","push_data_pool"],["area_push","push_data_pools"],["area_push","push_semantic_model"],["area_push","push_asset"],
["area_push","push_assets"],["area_push","push_package"],["area_push","push_packages"],
["area_push","push_widget"],["area_push","push_view_bookmarks"],

Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ content-cli pull package -h
- [Config Commands](user-guide/config-commands.md) -- Batch export/import, variables, nodes, diffs, and dependencies
- [Deployment Commands](user-guide/deployment-commands.md) -- Create, history, active deployments, deployables, and targets
- [Data Pool Commands](user-guide/data-pool-commands.md) -- Export/import data pools and connection management
- [Data Model Migration Commands](user-guide/data-model-migration-commands.md) -- Export data models and push semantic entities to pig packages
- [Action Flow Commands](user-guide/action-flow-commands.md) -- Analyze and export action flows
- **Development**
- [Architecture](internal-architecture.md) -- Internal architecture and inner workings
Expand Down
79 changes: 79 additions & 0 deletions docs/user-guide/data-model-migration-commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Data Model Migration Commands

These commands export a Data Integration data model from cloud-data-integration and convert it into semantic entity nodes in a target OCDM package via the Pacman staging-node API (same path as `config nodes create`).

Supported mappings:

| Data Integration | Semantic entity |
|---|---|
| Table | Object (with data binding) |
| Process configuration (event log) | Event source (with data binding) |
| Classic foreign key | Relationship |
| Data model | Perspective |

**Object links** (`signal-links` in cloud-data-integration) are **not** supported. The converter only reads classic `foreignKeys[]` from the `/transport` export.

## Export Data Model

Downloads the full data model transport (tables, columns, foreign keys, process configurations):

```
content-cli export data-model --poolId <pool-id> --dataModelId <data-model-id> --profile <profile> [--outputToJsonFile]
```

Example:

```
content-cli export data-model --poolId 80a1389d-50c5-4976-ad6e-fb5b7a2b5517 --dataModelId 1b9b368b-e0df-4e74-99e8-59e2febe9687 --profile local --outputToJsonFile
```

## Push Semantic Model

Converts the data model and pushes semantic entity nodes into a target OCDM package:

```
content-cli push semantic-model \
--poolId <pool-id> \
--dataModelId <data-model-id> \
--package <package-key> \
--profile <profile> \
[--schema <lake-schema>] \
[--namespace <namespace>] \
[--fromFile <transport.json>] \
[--dryRun] \
[--outputToJsonFile]
```

Options:

- `--schema`: Physical lake schema used in data bindings. When omitted, the CLI derives `datapipelines_<poolId>_draft` (hyphens in the pool id become underscores).
- `--fromFile`: Skip download and convert a previously exported transport JSON file.
- `--dryRun`: Convert only; print or write the Pacman node payloads without calling the staging-node API.
- `--namespace`: Optional namespace for data bindings (entity references use the package `local` namespace by default).

Push order: objects → event sources → relationships → perspective.

Each entity is created as a Pacman staging node with types `SEMANTIC_OBJECT_TYPE`, `SEMANTIC_EVENT_SOURCE_TYPE`, `SEMANTIC_RELATIONSHIP_TYPE`, and `SEMANTIC_PERSPECTIVE_TYPE`.

After pushing, validate and version the authored nodes with the standard config commands:

```
content-cli config package validate --packageKey <package-key> --nodeKeys <key1> <key2> --layers SCHEMA BUSINESS
content-cli config versions create --packageKey <package-key> --nodeFilterKeys <key1> <key2> --versionBumpOption PATCH --summaryOfChanges "..."
```

Example dry run:

```
content-cli push semantic-model \
--poolId 80a1389d-50c5-4976-ad6e-fb5b7a2b5517 \
--dataModelId 1b9b368b-e0df-4e74-99e8-59e2febe9687 \
--package my-context-model \
--profile local \
--dryRun
```

## Authentication

- **Download** uses the existing `integration.data-pools` OAuth scope (same as other data pool commands).
- **Push** uses the existing `package-manager` OAuth scope via `/pacman/api/core/staging/packages/{packageKey}/nodes`. Ensure the profile has edit access to the target package.
1 change: 1 addition & 0 deletions mkdocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ nav:
- Deployment Commands: './user-guide/deployment-commands.md'
- Asset Registry Commands: './user-guide/asset-registry-commands.md'
- Data Pool Commands: './user-guide/data-pool-commands.md'
- Data Model Migration Commands: './user-guide/data-model-migration-commands.md'
- Action Flow Commands: './user-guide/action-flow-commands.md'
- Development:
- Architecture: './internal-architecture.md'
Expand Down
23 changes: 23 additions & 0 deletions src/commands/data-model-migration/api/data-model-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Context } from "../../../core/command/cli-context";
import { FatalError } from "../../../core/utils/logger";
import { HttpClient } from "../../../core/http/http-client";
import { DataModelTransport } from "../interfaces/data-model-transport.interfaces";

export class DataModelApi {

Check warning on line 6 in src/commands/data-model-migration/api/data-model-api.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Mark this member as `readonly`.

See more on https://sonarcloud.io/project/issues?id=celonis_content-cli&issues=AZ_W0MH_gF4Opm67jk7K&open=AZ_W0MH_gF4Opm67jk7K&pullRequest=403

private httpClient: () => HttpClient;

constructor(context: Context) {
this.httpClient = () => context.httpClient;
}

/** Fetches the full data model transport including columns from cloud-data-integration. */
public async findOneTransport(poolId: string, dataModelId: string, includeColumns = true): Promise<DataModelTransport> {
const query = includeColumns ? "?includeColumns=true" : "";
return this.httpClient()
.get(`/integration/api/pools/${poolId}/data-models/${dataModelId}/transport${query}`)
.catch((error) => {
throw new FatalError(`Data model export failed for pool ${poolId}, data model ${dataModelId}: ${error}`);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** Pacman node type identifiers for context-model semantic assets. */
export const SEMANTIC_NODE_TYPES = {
OBJECT: "SEMANTIC_OBJECT_TYPE",
EVENT_SOURCE: "SEMANTIC_EVENT_SOURCE_TYPE",
RELATIONSHIP: "SEMANTIC_RELATIONSHIP_TYPE",
PERSPECTIVE: "SEMANTIC_PERSPECTIVE_TYPE",
} as const;

/** Asset-registry schema version for semantic entity node types. */
export const SEMANTIC_SCHEMA_VERSION = 1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Context } from "../../core/command/cli-context";
import { DataModelMigrationService } from "./service/data-model-migration.service";

export class DataModelMigrationCommandService {

Check warning on line 4 in src/commands/data-model-migration/data-model-migration-command.service.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Mark this member as `readonly`.

See more on https://sonarcloud.io/project/issues?id=celonis_content-cli&issues=AZ_W0MHvgF4Opm67jk7I&open=AZ_W0MHvgF4Opm67jk7I&pullRequest=403

private migrationService: DataModelMigrationService;

constructor(context: Context) {
this.migrationService = new DataModelMigrationService(context);
}

public async exportDataModel(poolId: string, dataModelId: string, outputToJsonFile: boolean): Promise<void> {
await this.migrationService.exportDataModel(poolId, dataModelId, outputToJsonFile);
}

public async pushSemanticModel(options: {
poolId: string;
dataModelId: string;
packageKey: string;
schema?: string;
namespace?: string;
fromFile?: string;
dryRun?: boolean;
outputToJsonFile?: boolean;
}): Promise<void> {
await this.migrationService.pushSemanticModel(options);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Context } from "../../core/command/cli-context";
import { Configurator } from "../../core/command/module-handler";
import { Command, OptionValues } from "commander";
import { DataModelMigrationCommandService } from "./data-model-migration-command.service";

export class DataModelMigrationCommands {

public register(_context: Context, configurator: Configurator): void {
configurator.command("export")
.command("data-model")
.description("Export a data model with tables, foreign keys, and process configurations")
.requiredOption("--poolId <poolId>", "ID of the data pool")
.requiredOption("--dataModelId <dataModelId>", "ID of the data model")
.option("--outputToJsonFile", "Write the exported data model to a JSON file")
.action(this.exportDataModel);

configurator.command("push")
.command("semantic-model")
.description("Convert a data model into semantic entities and push them into a pig package")
.requiredOption("--package <packageKey>", "Target pig package key")
.option("--poolId <poolId>", "ID of the data pool (required unless --fromFile is set)")
.option("--dataModelId <dataModelId>", "ID of the data model (required unless --fromFile is set)")
.option("--schema <schema>", "Physical lake schema for data bindings (overrides pool-derived default)")
.option("--namespace <namespace>", "Namespace for created semantic entities")
.option("-f, --fromFile <file>", "Use a previously exported data model transport JSON file")
.option("--dryRun", "Convert only; print or write node payloads without pushing to Pacman")
.option("--outputToJsonFile", "With --dryRun, write conversion output to a JSON file")
.action(this.pushSemanticModel);
}

private async exportDataModel(context: Context, _command: Command, options: OptionValues): Promise<void> {
await new DataModelMigrationCommandService(context).exportDataModel(
options.poolId,
options.dataModelId,
!!options.outputToJsonFile
);
}

private async pushSemanticModel(context: Context, _command: Command, options: OptionValues): Promise<void> {
if (!options.fromFile && (!options.poolId || !options.dataModelId)) {
throw new Error("Either --fromFile or both --poolId and --dataModelId are required");
}

await new DataModelMigrationCommandService(context).pushSemanticModel({
poolId: options.poolId,
dataModelId: options.dataModelId,
packageKey: options.package,
schema: options.schema,
namespace: options.namespace,
fromFile: options.fromFile,
dryRun: !!options.dryRun,
outputToJsonFile: !!options.outputToJsonFile,
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { SaveNodeTransport } from "../../configuration-management/interfaces/node.interfaces";

export interface ConversionResult {
objects: SaveNodeTransport[];
eventSources: SaveNodeTransport[];
relationships: SaveNodeTransport[];
perspective: SaveNodeTransport;
}

export interface ConversionOptions {
poolId: string;
bindingSchema: string;
packageKey: string;
namespace?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
export type ColumnType = "INTEGER" | "DATE" | "TIME" | "DATETIME" | "FLOAT" | "BOOLEAN" | "STRING";

export type DataModelType = "CASE_CENTRIC" | "OBJECT_CENTRIC";

export interface DataModelColumnTransport {
name: string;
type: ColumnType;
primaryKey?: boolean;
}

export interface DataModelTableTransport {
id: string;
name: string;
alias?: string;
aliasOrName?: string;
dataModelId?: string;
dataSourceId?: string;
primaryKeys?: string[];
columns?: DataModelColumnTransport[];
}

export interface DataModelForeignKeyColumnTransport {
id?: string;
sourceColumnName: string;
targetColumnName: string;
}

export interface DataModelForeignKeyTransport {
id: string;
dataModelId?: string;
sourceTableId: string;
targetTableId: string;
columns: DataModelForeignKeyColumnTransport[];
}

export interface DataModelConfigurationTransport {
id?: string;
dataModelId?: string;
activityTableId: string;
caseTableId?: string;
caseIdColumn: string;
activityColumn: string;
timestampColumn: string;
endTimestampColumn?: string;
sortingColumn?: string;
costColumn?: string;
userColumn?: string;
defaultConfiguration?: boolean;
}

export interface DataModelTransport {
id: string;
name: string;
poolId?: string;
dataModelType?: DataModelType;
tables: DataModelTableTransport[];
foreignKeys?: DataModelForeignKeyTransport[];
processConfigurations?: DataModelConfigurationTransport[];
}
Loading
Loading