Skip to content

Data and environments ​

This page documents the four Administration screens that move data or definitions: Data import (CSV into an entity), Tenant migration (move a tenant to another region), Environment promotion (copy an application or module from one tenant to another) and Test / QA cases (versioned test definitions that can run HTTP checks). For the permission summary see Administration and DevOps overview. For routes that are not on these screens (plugins, AI bundles, integrations, Engagement) see Command line - move definitions between environments.

Data import ​

Data import loads rows from a CSV file into any active entity defined in the Entity Designer. You map CSV columns to entity fields, preview, then run. Every row is created through the same Entity Engine path as a manual create, so field constraints and declarative rules apply. Administrators and implementers use it for initial loads. See Entities for the entities themselves.

Where to find it: Workspace > Administration > Data import. Page key data-import; the screen title reads "Data Import".

Key concepts ​

TermMeaning
Target entityAn entity with status active. The select shows its label, or its name when it has no label.
Target fieldAn importable field of the entity: active, not a system field, not read-only. A field marked required shows an asterisk in the mapping select.
MappingFor each CSV column, the target field it fills, or "- skip this column -".
Suggested mappingComputed on preview by matching each column header to a field name or label, ignoring case, spaces, underscores and hyphens.

Fields and options ​

Source panel:

FieldType or valuesDefaultRequiredDescription
Select target entitySelect of active entitiesnoneYesChanging it clears the preview and result
Choose CSV fileFile picker, .csvnoneYesShows the file name once chosen. Maximum 10 MB
PreviewButtonn/an/aEnabled when both entity and file are set. Shows a spinner while loading

Mapping panel (after Preview). Title: "Column mapping - N data row(s), M of K columns mapped".

ColumnDescription
Source columnThe CSV header
Target fieldSelect of target fields (label, with * when required) or "- skip this column -"
Sample valuesThe values of that column in the first rows, comma separated (hidden on narrow screens)

Result panel ("Import result"): chips "N succeeded" and, when any failed, "N failed"; a grid of failed rows with Row and Error (search "Search errors"). When all rows succeeded the panel states "All rows imported successfully."

Actions ​

ActionEffectAPI
PreviewParses the header and counts data rows; returns up to 5 sample rows, the target fields and the suggested mappingPOST /api/v1/entities/{name}/import/preview (multipart file)
Run ImportImports every data row using the current mapping. Disabled until at least one column is mappedPOST /api/v1/entities/{name}/import/execute (multipart file and mapping)

mapping is a JSON array of { "sourceColumn": "...", "targetField": "..." | null }.

Procedures ​

Import customers from customers.csv:

text
Customer Name,E-mail,City,Credit Limit
Acme Ltd,billing@acme.example,Pune,50000
Globex,ap@globex.example,Leeds,
  1. Open Data import, select the entity Customer, choose customers.csv, select Preview.
  2. The panel reads "Column mapping - 2 data row(s), 4 of 4 columns mapped" because the headers match field labels. Change "E-mail" to the field email if it was left unmapped.
  3. Select Run Import. The result shows "2 succeeded".
  4. If a row fails, for example a missing required field, the grid lists Row 3 with the validation message from the entity.

Statuses and lifecycle ​

Each row ends as success or failure; there is no persisted import job. Rows already created are not rolled back when a later row fails.

Permissions ​

The import controller itself performs no permission check. Each row is created by the Entity Engine as the acting user, so entity-level rules, role-field rules and BEFORE_CREATE / AFTER_CREATE rules apply and may reject rows.

API and CLI ​

bash
erp api get /api/v1/entities

The multipart import calls are made from Studio or an HTTP client; erp api sends JSON bodies only.

Limits and behavior ​

  • CSV only. The reader is deliberately simple: UTF-8, first line is the header, one record per line, cells split on every comma, surrounding double quotes removed. A quoted cell that contains a comma is split incorrectly. Blank lines are skipped. XLSX is not supported.
  • Maximum file size 10 MB.
  • Blank cells are not sent, so field defaults apply. All values are sent as text and converted by the Entity Engine.
  • Row numbers in errors match a spreadsheet: the header is row 1 and the first data row is row 2.
  • There is no de-duplication or update: every row calls create. Importing the same file twice creates duplicate records unless the entity has a unique constraint.
  • The server does not check that a mapped field exists on the entity; an unknown field name is passed to the Entity Engine, which decides whether the row is rejected. The request itself is rejected only when no column is mapped.

Errors and troubleshooting ​

Message or symptomCauseFix
"Pick a target entity and a CSV file first."Preview pressed without both inputsChoose both
file is requiredEmpty fileChoose a file with content
File too large (max 10MB)Over the limitSplit the file
CSV file is emptyNo header lineAdd a header row
At least one column must be mapped to a target fieldEverything skippedMap at least one column
No such entity: xEntity renamed or deleted since the page was openedReload and reselect
"mapping" must be a valid JSON array of {sourceColumn, targetField}Malformed mapping in an API callCorrect the JSON
Row error text from the entityValidation or rule failure for that rowFix the value in the CSV and import only the failed rows again

Tenant migration ​

Tenant migration moves the current tenant to a different region. It takes a real backup, restores it into a new database for the destination region, verifies row counts, then repoints routing. Background job scheduling for the tenant is paused while the migration runs. Platform operators and tenant owners use it, normally during a planned window.

Where to find it: Workspace > Administration > Tenant migration. Page key tenant-migration.

Key concepts ​

TermMeaning
RegionA deployment region registered in the region registry (GET /api/v1/authoring/regions) with a code, a name, an Active flag and an Is-primary flag.
Home regionThe region that currently hosts the tenant's data.
Assignment statusactive (normal), migrating (a migration is running), suspended (a cutover failed after a verified restore).
RunOne migration attempt, identified by a UUID; its steps are recorded with the run.
Data residencyThe tenant's declared residency constraint; a target region that violates it is rejected before anything is touched.

Fields and options ​

Section Current:

ElementDescription
Home regionThe code of the home region
StatusChip: green active, amber migrating, red otherwise
Database identifierThe database name of the tenant, or "(default)"

Section Migrate to a different region:

FieldType or valuesDefaultRequiredDescription
Target regionSelect of active regions other than the home region, shown as "Name (code)" with " - primary" for the primary regionnoneYes"No other active region available" when there is none. Disabled during a migration
Start MigrationButtonn/an/aShows "Migrating..." and a spinner while the synchronous call runs

Section Migration history (title adds " - run" and the first 8 characters of the run ID): one row per step with a chip and the detail text, a timestamp and the backup record number when present. Step chips:

StepChip labelMeaning
startedStartedMigration requested
backed_upBacked upA real backup of the source was taken (the detail names the backup record)
restoredRestored into destinationThe dump was restored into the new database tenant_<id>_<region>_db
verifiedVerified (row counts matched)Per-table row counts match the baseline
cutoverCutover (routing repointed)the tenant's database and region now point to the destination
completedCompletedMigration finished
failedFailedThe run stopped; the detail explains why

A Refresh button reloads the history. The text "No migration has been attempted for this tenant yet." shows when the history is empty. When the assignment is suspended a warning states that a previous migration's cutover step failed after a verified restore and that manual intervention is required.

Actions ​

ActionEffectAPI
Start MigrationRuns the whole migration and returns the final outcome. Success shows the result message; failure shows "Migration did not complete: message"POST /api/v1/authoring/tenant-region/{tenantId}/migrate with {"targetRegion":"sg-east"}
Refresh / loadLoads regions, assignment and historyGET /api/v1/authoring/regions, GET /api/v1/authoring/tenant-region/tenant, GET /api/v1/authoring/tenant-region/{tenantId}/migration-status

Procedures ​

Move tenant 7 from in-east to sg-east:

  1. Announce a maintenance window. Take and verify a backup first in Backup.
  2. Open Tenant migration. Current shows Home region in-east, Status active.
  3. Choose Target region "Singapore East (sg-east)" and select Start Migration.
  4. When the call returns, the history shows Started, Backed up, Restored into destination, Verified, Cutover and Completed. Current shows Home region sg-east.
  5. Check Jobs and Monitoring to confirm scheduled work resumed.

Statuses and lifecycle ​

Assignment statusMeaningTransitions
activeNormalmigrating when a run starts
migratingA run is in progress; the job scheduler skips the tenant; another run is refusedactive on success or on a backup or verification failure (original assignment restored); suspended if the cutover write fails
suspendedCutover failed after a verified restoreManual intervention by an operator

Run order: refuse invalid requests, append started, claim the region-ownership lease (30 minutes), mark the tenant migrating, back up, restore into the new database, verify, grant the read-only replica role on the new database, repoint the tenant's database and region, evict cached connections, mark active, append completed, release the lease.

Permissions ​

region:tenant-migration / execute. Without it the endpoint answers 403 with an empty body and the screen shows the HTTP error. The reads of regions, assignment and status are not permission-checked by the tenant-region controller.

API and CLI ​

bash
erp api get /api/v1/authoring/regions
erp api get /api/v1/authoring/tenant-region/tenant
erp api post /api/v1/authoring/tenant-region/7/migrate --body '{"targetRegion":"sg-east"}'
erp api get /api/v1/authoring/tenant-region/7/migration-status

Result body:

json
{
  "success": true,
  "tenantId": 7,
  "runId": "5b0b1c8e-4c0a-4a4e-9e11-8e5c1d3a9f10",
  "sourceRegion": "in-east",
  "targetRegion": "sg-east",
  "finalStatus": "active",
  "backupRecordId": 42,
  "message": "migration in-east -> sg-east completed for tenant 7"
}

A failed migration returns HTTP 422 with the same shape and success: false; a rejected request returns 400 with {"error": "..."}.

Limits and behavior ​

  • The call is synchronous: the HTTP request stays open for the full backup, restore and verification time.
  • The destination database is created on the same database host and port as the source's registered location, named tenant_<id>_<region code with dashes replaced by underscores>_db. The source database is not modified or dropped.
  • Verification compares per-table row counts with the baseline captured at backup time. Tables with vector columns are excluded from the comparison and noted in the result.
  • On a backup failure, a verification mismatch or a failed restore, the tenant is returned to active in its original region and no routing change is made.
  • The read-only replica role is granted select on the new database; if that grant fails a warning is logged and replica-served reads may fail until it is granted manually.
  • The migration needs the backup engine in the same process. A deployment that has backups switched off refuses it.

Errors and troubleshooting ​

Message or symptomCauseFix
"Choose a target region first."Start pressed without a selectionSelect a region
targetRegion is requiredAPI call without a body fieldSupply targetRegion
target region 'x' is not a known, active regionUnknown or inactive regionChoose from the list
tenant N has no region assignment yet - assign a home region firstNo assignmentOperator assigns a home region
tenant N already has a migration in progressAssignment is migratingWait; if stuck, operator investigates
tenant N is already in region 'x'Target equals homeChoose another region
target region 'x' violates tenant N's declared data residency: reasonResidency ruleChoose a compliant region
could not acquire the region-ownership lease for tenant N - currently held by 'node' until timeAnother node holds the leaseRetry after the lease time
backup failed for tenant N (backup id=B) - source untouched, rolling back to activeBackup failureSee Backup
restore verification FAILED for tenant N: ...Row counts differDo not retry blindly; contact the operator
cutover step failed AFTER a successfully verified restore into 'db' - tenant left SUSPENDED, manual intervention requiredRouting write failedOperator repoints routing or restores the assignment
cross-region tenant migration needs the backup service on this deploymentBackup slice not co-hostedRun from a deployment that hosts it

Environment promotion ​

Environment promotion copies an Application or a Module, with all the artifacts it groups, from one tenant (typically a sandbox) into another tenant (typically production). You preview a diff first; nothing is written until you select Promote. Implementers and release managers use it. Rollback is not a button here: every promoted artifact arrives through the target tenant's normal draft and version history.

Where to find it: Workspace > Administration > Environment promotion. Page key environment-promotion.

Key concepts ​

TermMeaning
Source tenantThe tenant the definitions are exported from.
Target tenantThe tenant they are imported into.
OwnerThe Application or Module being promoted, identified by type and name.
PackageThe encrypted application package (.erpapp) engine used internally. It carries the authored definitions only, never records or runtime data. Types included: pages, forms, blocks (widgets), themes, dashboards, rules, actions, entities, websites, website pages, menus, CMS collections, portals.
SandboxA tenant flagged as part of the environment chain, with an optional environment label and a default promotion target.
Diff statusnew (absent in the target), changed (differs from the target), unchanged.

Fields and options ​

FieldType or valuesDefaultRequiredDescription
Source tenant idText (numeric)emptyYesTenant to read from
Target tenant idText (numeric)emptyYesTenant to write to; also selects which promotion history is shown
Owner typeSelect: Application, ModuleApplicationYesWire values application and module
Owner nameTextemptyYesExact name of the Application or Module in the source tenant
Known environment-chain tenantsChips "Name (#id, label)"listed when sandboxes existn/aSelecting a chip sets the Source tenant id

Diff panel (title: owner type and name, with a status chip for the owner itself):

ColumnDescription
TypeArtifact type
NameArtifact name
StatusChip new, changed or unchanged; the suffix "(existence only)" means the comparison could only confirm existence, not content

Empty diff text: "No child artifacts (the owner's own definition is the only content to promote)."

History panel "Promotion history into tenant N": columns When, From tenant (#id), Owner (type and name), By; newest first.

Actions ​

ActionEffectAPI
Preview DiffExports the owner from the source tenant and compares with the target, writing nothingGET /api/v1/authoring/environment-promotion/diff?sourceTenantId=&targetTenantId=&ownerType=&ownerName=
PromoteImports the owner into the target tenant, then records a log row and refreshes the history. Result banner: Promoted "name" - N created, N new version(s), N skipped (entity collisions).POST /api/v1/authoring/environment-promotion/promote with sourceTenantId, targetTenantId, ownerType, ownerName
Refresh (history)Reloads the historyGET /api/v1/authoring/environment-promotion/history?targetTenantId=
Configure sandbox (API only)Flags a tenant as a sandbox with a label and a default promotion targetPOST /tenants/{id}/sandbox with {"isSandbox":true,"environmentLabel":"UAT","promotionTargetTenantId":3}; list with GET /tenants/sandboxes

Procedures ​

Promote the Application Field Service from UAT (tenant 4) to production (tenant 3):

  1. Open Environment promotion. Select the chip "UAT (#4, UAT)" or type 4 into Source tenant id.
  2. Enter Target tenant id 3, Owner type Application, Owner name Field Service, and select Preview Diff.
  3. Review the table, for example Page work-order-list changed, Form work-order new, Entity WorkOrder unchanged. Confirm the owner chip.
  4. Select Promote. The banner reads Promoted "Field Service" - 5 created, 3 new version(s), 1 skipped (entity collisions). and the history gains a row.
  5. In tenant 3, review the new drafts and publish them. To undo one, open its designer and use Restore this version on the earlier version.

Statuses and lifecycle ​

Promoted artifacts arrive as drafts or new draft versions in the target tenant and follow the normal lifecycle in Versioning and release management. A name collision in the target creates a new draft version next to the untouched previous one. A colliding entity is skipped (reported as "skipped") rather than altered.

Permissions ​

For the diff and promote calls the actor needs AUTHOR on the owner type in the source tenant and PUBLISH on the owner type in the target tenant. Viewing history needs PUBLISH on application in the target tenant. Otherwise the request is denied with Actor "x" lacks AUTHOR on application for tenant T.

API and CLI ​

bash
erp api get "/api/v1/authoring/environment-promotion/diff?sourceTenantId=4&targetTenantId=3&ownerType=application&ownerName=Field%20Service"
erp api post /api/v1/authoring/environment-promotion/promote --body '{"sourceTenantId":4,"targetTenantId":3,"ownerType":"application","ownerName":"Field Service"}'
erp api get "/api/v1/authoring/environment-promotion/history?targetTenantId=3"

Promote response (created, newVersion, skippedExisting are lists of {type, name, version}):

json
{
  "ownerCreated": false,
  "created": [ { "type": "form", "name": "work-order", "version": 1 } ],
  "newVersion": [ { "type": "page", "name": "work-order-list", "version": 4 } ],
  "skippedExisting": [ { "type": "entity", "name": "WorkOrder", "version": 1 } ]
}

Limits and behavior ​

  • Cross-tenant by design: the export runs in the source tenant's context and the import in the target's.
  • The package is a blueprint of authored definitions. Records, sessions and execution history are never copied.
  • For types other than those compared by content, the diff is existence-only; such rows are marked "(existence only)".
  • Each promote writes one log row (environment_promotion_log): source and target tenant, owner, import result, actor, time.
  • The screen requires all four inputs: "Source tenant, target tenant, and owner name are all required."

Errors and troubleshooting ​

Message or symptomCauseFix
"Source tenant, target tenant, and owner name are all required."Missing inputFill all four
ownerType must be "application" or "module", got: xAPI call with another typeUse application or module
Failed to prepare promotion diff / Failed to promote (HTTP 500)Package encryption or decryption errorRetry; check the server log with erp logs tail
Actor "x" lacks PUBLISH on application for tenant TMissing target permissionGrant publish on the target tenant
An artifact is missing after promotionIt is not grouped under the Application or ModuleAssign it to the owner in the source tenant and promote again

Test / QA cases ​

Test / QA cases stores versioned test case definitions and runs the HTTP kind for real. A test case has a target type, a target reference and an ordered list of steps with expected results. Cases of type api execute a real HTTP request per step and check status and body; other types are structured, versioned documentation of a test procedure and record a run as not executable. QA engineers and implementers use it. For the broader testing picture see Testing.

Where to find it: Workspace > Administration > Test / QA cases. Page key test-cases.

Key concepts ​

TermMeaning
Test caseAn artifact (test_case_definition) with the lifecycle draft, published, deprecated, archived.
Target typeapi, page, workflow or manual. Only api is executed.
StepOne action with parameters and expected results.
RunOne execution, stored in test_run with outcome, step results, the test case version, the user and start time.
Outcomepassed, failed, error, not_executable.

Fields and options ​

List panel "Test cases": each row shows the name and "vN - status". New opens the editor.

Editor:

FieldType or valuesDefaultRequiredDescription
NameText (create only)emptyYes"Name is required" otherwise
Definition JSONMulti-line JSONTemplate with targetType api, empty targetRef and one GET stepYesMust parse ("Definition must be valid JSON")
Chips"vN - status" and the target typen/an/aCurrent state

Definition schema:

PropertyTypeDescription
targetTypeapi, page, workflow, manualAnything other than api is not executed. A missing value is treated as manual
targetRefStringFree reference to the thing under test (URL, page or workflow name)
steps[].actionStringFor api: the HTTP method (GET, POST, PUT, DELETE, PATCH; default GET). For other types: free text
steps[].params.urlStringRequired for api steps; must be an absolute URL reachable from the server
steps[].params.headersObject of stringsOptional request headers
steps[].params.bodyJSONOptional; sent as application/json
steps[].expected.statusIntegerOptional expected HTTP status
steps[].expected.bodyContainsStringOptional text that the response body must contain

Example:

json
{
  "targetType": "api",
  "targetRef": "Order API health and create",
  "steps": [
    {
      "action": "GET",
      "params": { "url": "https://erp.example.com/api/v1/health" },
      "expected": { "status": 200, "bodyContains": "UP" }
    },
    {
      "action": "POST",
      "params": {
        "url": "https://erp.example.com/api/v1/entities/SalesOrder/records",
        "headers": { "Authorization": "Bearer TOKEN", "X-Tenant-Id": "7" },
        "body": { "customer": "Acme Ltd", "total": 1200 }
      },
      "expected": { "status": 200 }
    }
  ]
}

Run result panel ("Run result" with an outcome chip, green for passed, red for failed or error): one line per step, "Step N (action): mark detail". Run history panel lists up to the stored runs with outcome chip, start time, "vN" and the user.

Actions ​

ActionShown whenEffectAPI
CreateNewCreates a draftPOST /api/v1/authoring/test-cases
Save draftExistingSaves with the revision shownPUT /api/v1/authoring/test-cases/{id}/draft
PublishStatus draftPublishes the versionPOST /api/v1/authoring/test-cases/{id}/publish
RunExistingExecutes and stores a run; refreshes historyPOST /api/v1/test-cases/{id}/runs
(API only)Run historyGET /api/v1/test-cases/{id}/runs
(API only)New version, clone, deprecate, archive, delete, historyPOST .../{id}/new-version, /clone, /deprecate, /archive, DELETE .../{id}, GET .../name/{name}/history

The screen is a lightweight list-and-editor; it does not provide the full designer toolbar (publish, clone and history for all versions are reachable through the API and CLI).

Procedures ​

Check an endpoint after a deployment:

  1. Select New, name it orders-smoke.
  2. Paste the example definition above, adjusting the URL, and select Create.
  3. Select Run. The result shows Step 1 (GET): ... HTTP 200 and the outcome chip passed.
  4. Select Publish to freeze version 1. A later change requires a new version.

Statuses and lifecycle ​

Artifact status: draft to published, then deprecated or archived. A published version is immutable: saving it fails with test_case definition N is PUBLISHED - published definitions are immutable; create a new version instead. A concurrent edit fails with Draft N was saved by someone else: expected revision R but the row is at C.

Run outcome:

OutcomeMeaning
passedEvery step passed
failedAt least one step did not match its expectations and none raised a request error
errorAt least one step raised a request error (the call itself failed)
not_executableThe target type is not api; the run is recorded with no steps

Step details: HTTP 200; HTTP 500 - expected status 200; HTTP 200 - body did not contain "UP"; step has no params.url; request failed: message.

Permissions ​

AUTHOR on test_case to create, edit, clone, create versions and run; PUBLISH to publish, deprecate, archive and delete. Reading the list, definitions and run history is open.

API and CLI ​

bash
erp artifact list --type test-cases
erp artifact create --type test-cases --name orders-smoke --file orders-smoke.json
erp artifact publish --type test-cases --id 18
erp api post /api/v1/test-cases/18/runs
erp api get /api/v1/test-cases/18/runs

The same artifact operations are available as erp plugin op artifact-list --type test-cases and the related artifact-* operations.

Limits and behavior ​

  • Requests are sent by the server, so the URL must be reachable from it. No authentication is added unless you set it in params.headers; do not store long-lived secrets in a definition, which is readable by anyone who can read test cases.
  • The HTTP client treats an HTTP status of 400 or above as a request failure. The step is then recorded as request failed: ... and the run outcome is error, so an expectation of an error status (for example expected.status 404) cannot pass.
  • No timeout is configured on the HTTP client.
  • The definition is stored as written; structure is not validated on save.
  • Only api cases run; there is no browser automation for page or workflow cases.

Errors and troubleshooting ​

Message or symptomCauseFix
"Definition must be valid JSON"Syntax errorFix the JSON
"Name is required"Empty name on createEnter a name
step has no params.urlurl missingAdd params.url
request failed: ...Network failure, DNS, TLS or HTTP error statusCheck the URL from the server; expect error for 4xx and 5xx
not_executabletargetType is not apiUse api, or treat the case as documentation