From 0a733a269d11e1b595e198d2e76b230a8f895779 Mon Sep 17 00:00:00 2001 From: Kashish Mittal <113269381+04kash@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:58:36 -0500 Subject: [PATCH 01/13] chore(ci): add verify changesets step (#65) * chore(ci): add verify changesets step Signed-off-by: Kashish Mittal * exclude noop from verify changesets Signed-off-by: Kashish Mittal * add fetch-depth: 0 Signed-off-by: Kashish Mittal --------- Signed-off-by: Kashish Mittal --- .github/workflows/ci.yml | 6 ++++++ workspaces/bulk-import/packages/backend/package.json | 2 +- workspaces/bulk-import/yarn.lock | 10 ++-------- workspaces/homepage/packages/backend/package.json | 2 +- workspaces/homepage/yarn.lock | 10 ++-------- workspaces/lightspeed/.changeset/config.json | 6 +++++- workspaces/lightspeed/packages/backend/package.json | 2 +- workspaces/lightspeed/yarn.lock | 10 ++-------- workspaces/marketplace/packages/backend/package.json | 2 +- workspaces/marketplace/yarn.lock | 10 ++-------- .../packages/backend/package.json | 2 +- workspaces/openshift-image-registry/yarn.lock | 10 ++-------- workspaces/theme/packages/backend/package.json | 2 +- workspaces/theme/yarn.lock | 10 ++-------- 14 files changed, 29 insertions(+), 55 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31743ca9..3f4ab38e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,6 +70,7 @@ jobs: uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4 with: ref: ${{ github.event.workflow_run.head_branch }} + fetch-depth: 0 - name: Set up Node ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4 @@ -90,6 +91,11 @@ jobs: - name: type checking and declarations run: yarn tsc:full + - name: Verify changesets + if: ${{ !(github.actor == 'rhdh-bot' || github.actor == 'github-actions[bot]') && matrix.workspace != 'repo-tools' && matrix.workspace != 'noop' }} + run: | + yarn changeset status --since=${{ github.event.pull_request.base.sha }} + - name: prettier run: yarn prettier:check diff --git a/workspaces/bulk-import/packages/backend/package.json b/workspaces/bulk-import/packages/backend/package.json index ccf4416f..e413aa45 100644 --- a/workspaces/bulk-import/packages/backend/package.json +++ b/workspaces/bulk-import/packages/backend/package.json @@ -44,7 +44,7 @@ "@backstage/plugin-search-backend-node": "^1.3.4", "@backstage/plugin-techdocs-backend": "^1.11.1", "@red-hat-developer-hub/backstage-plugin-bulk-import-backend": "workspace:^", - "app": "link:../app", + "app": "workspace:*", "better-sqlite3": "^9.0.0", "node-gyp": "^10.0.0", "pg": "^8.11.3", diff --git a/workspaces/bulk-import/yarn.lock b/workspaces/bulk-import/yarn.lock index 9f8ac1a0..367fef57 100644 --- a/workspaces/bulk-import/yarn.lock +++ b/workspaces/bulk-import/yarn.lock @@ -15333,13 +15333,7 @@ __metadata: languageName: node linkType: hard -"app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend": - version: 0.0.0-use.local - resolution: "app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend" - languageName: node - linkType: soft - -"app@workspace:packages/app": +"app@workspace:*, app@workspace:packages/app": version: 0.0.0-use.local resolution: "app@workspace:packages/app" dependencies: @@ -16108,7 +16102,7 @@ __metadata: "@types/express": ^4.17.6 "@types/express-serve-static-core": ^4.17.5 "@types/luxon": ^2.0.4 - app: "link:../app" + app: "workspace:*" better-sqlite3: ^9.0.0 node-gyp: ^10.0.0 pg: ^8.11.3 diff --git a/workspaces/homepage/packages/backend/package.json b/workspaces/homepage/packages/backend/package.json index 3f637e03..bbf8bb65 100644 --- a/workspaces/homepage/packages/backend/package.json +++ b/workspaces/homepage/packages/backend/package.json @@ -44,7 +44,7 @@ "@backstage/plugin-search-backend-module-techdocs": "^0.3.0", "@backstage/plugin-search-backend-node": "^1.3.3", "@backstage/plugin-techdocs-backend": "^1.11.0", - "app": "link:../app", + "app": "workspace:*", "better-sqlite3": "^9.0.0", "node-gyp": "^10.0.0", "pg": "^8.11.3" diff --git a/workspaces/homepage/yarn.lock b/workspaces/homepage/yarn.lock index 999fbe65..1fb133c4 100644 --- a/workspaces/homepage/yarn.lock +++ b/workspaces/homepage/yarn.lock @@ -14347,13 +14347,7 @@ __metadata: languageName: node linkType: hard -"app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend": - version: 0.0.0-use.local - resolution: "app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend" - languageName: node - linkType: soft - -"app@workspace:packages/app": +"app@workspace:*, app@workspace:packages/app": version: 0.0.0-use.local resolution: "app@workspace:packages/app" dependencies: @@ -15057,7 +15051,7 @@ __metadata: "@backstage/plugin-search-backend-module-techdocs": ^0.3.0 "@backstage/plugin-search-backend-node": ^1.3.3 "@backstage/plugin-techdocs-backend": ^1.11.0 - app: "link:../app" + app: "workspace:*" better-sqlite3: ^9.0.0 node-gyp: ^10.0.0 pg: ^8.11.3 diff --git a/workspaces/lightspeed/.changeset/config.json b/workspaces/lightspeed/.changeset/config.json index 4d034bb9..8208df00 100644 --- a/workspaces/lightspeed/.changeset/config.json +++ b/workspaces/lightspeed/.changeset/config.json @@ -6,5 +6,9 @@ "linked": [], "access": "public", "baseBranch": "main", - "updateInternalDependencies": "patch" + "updateInternalDependencies": "patch", + "privatePackages": { + "tag": false, + "version": false + } } diff --git a/workspaces/lightspeed/packages/backend/package.json b/workspaces/lightspeed/packages/backend/package.json index 2bf5becb..7c0c3445 100644 --- a/workspaces/lightspeed/packages/backend/package.json +++ b/workspaces/lightspeed/packages/backend/package.json @@ -45,7 +45,7 @@ "@backstage/plugin-search-backend-node": "^1.3.5", "@backstage/plugin-techdocs-backend": "^1.11.2", "@red-hat-developer-hub/backstage-plugin-lightspeed-backend": "workspace:^", - "app": "link:../app", + "app": "workspace:*", "better-sqlite3": "^9.0.0", "node-gyp": "^10.0.0", "pg": "^8.11.3" diff --git a/workspaces/lightspeed/yarn.lock b/workspaces/lightspeed/yarn.lock index 9f843fcf..5edd4555 100644 --- a/workspaces/lightspeed/yarn.lock +++ b/workspaces/lightspeed/yarn.lock @@ -15287,13 +15287,7 @@ __metadata: languageName: node linkType: hard -"app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend": - version: 0.0.0-use.local - resolution: "app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend" - languageName: node - linkType: soft - -"app@workspace:packages/app": +"app@workspace:*, app@workspace:packages/app": version: 0.0.0-use.local resolution: "app@workspace:packages/app" dependencies: @@ -16014,7 +16008,7 @@ __metadata: "@backstage/plugin-search-backend-node": ^1.3.5 "@backstage/plugin-techdocs-backend": ^1.11.2 "@red-hat-developer-hub/backstage-plugin-lightspeed-backend": "workspace:^" - app: "link:../app" + app: "workspace:*" better-sqlite3: ^9.0.0 node-gyp: ^10.0.0 pg: ^8.11.3 diff --git a/workspaces/marketplace/packages/backend/package.json b/workspaces/marketplace/packages/backend/package.json index 576d9434..f5c3de40 100644 --- a/workspaces/marketplace/packages/backend/package.json +++ b/workspaces/marketplace/packages/backend/package.json @@ -45,7 +45,7 @@ "@backstage/plugin-search-backend-node": "^1.3.3", "@backstage/plugin-techdocs-backend": "^1.11.0", "@red-hat-developer-hub/backstage-plugin-marketplace-backend": "workspace:^", - "app": "link:../app", + "app": "workspace:*", "better-sqlite3": "^9.0.0", "node-gyp": "^10.0.0", "pg": "^8.11.3" diff --git a/workspaces/marketplace/yarn.lock b/workspaces/marketplace/yarn.lock index a8391e4c..dff8d173 100644 --- a/workspaces/marketplace/yarn.lock +++ b/workspaces/marketplace/yarn.lock @@ -14356,13 +14356,7 @@ __metadata: languageName: node linkType: hard -"app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend": - version: 0.0.0-use.local - resolution: "app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend" - languageName: node - linkType: soft - -"app@workspace:packages/app": +"app@workspace:*, app@workspace:packages/app": version: 0.0.0-use.local resolution: "app@workspace:packages/app" dependencies: @@ -15068,7 +15062,7 @@ __metadata: "@backstage/plugin-search-backend-node": ^1.3.3 "@backstage/plugin-techdocs-backend": ^1.11.0 "@red-hat-developer-hub/backstage-plugin-marketplace-backend": "workspace:^" - app: "link:../app" + app: "workspace:*" better-sqlite3: ^9.0.0 node-gyp: ^10.0.0 pg: ^8.11.3 diff --git a/workspaces/openshift-image-registry/packages/backend/package.json b/workspaces/openshift-image-registry/packages/backend/package.json index 70af9ab9..d0f67507 100644 --- a/workspaces/openshift-image-registry/packages/backend/package.json +++ b/workspaces/openshift-image-registry/packages/backend/package.json @@ -43,7 +43,7 @@ "@backstage/plugin-search-backend-module-techdocs": "^0.3.1", "@backstage/plugin-search-backend-node": "^1.3.4", "@backstage/plugin-techdocs-backend": "^1.11.1", - "app": "link:../app", + "app": "workspace:*", "better-sqlite3": "^9.0.0", "node-gyp": "^10.0.0", "pg": "^8.11.3", diff --git a/workspaces/openshift-image-registry/yarn.lock b/workspaces/openshift-image-registry/yarn.lock index 284df957..454c0c15 100644 --- a/workspaces/openshift-image-registry/yarn.lock +++ b/workspaces/openshift-image-registry/yarn.lock @@ -15065,13 +15065,7 @@ __metadata: languageName: node linkType: hard -"app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend": - version: 0.0.0-use.local - resolution: "app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend" - languageName: node - linkType: soft - -"app@workspace:packages/app": +"app@workspace:*, app@workspace:packages/app": version: 0.0.0-use.local resolution: "app@workspace:packages/app" dependencies: @@ -15781,7 +15775,7 @@ __metadata: "@types/express": ^4.17.6 "@types/express-serve-static-core": ^4.17.5 "@types/luxon": ^2.0.4 - app: "link:../app" + app: "workspace:*" better-sqlite3: ^9.0.0 node-gyp: ^10.0.0 pg: ^8.11.3 diff --git a/workspaces/theme/packages/backend/package.json b/workspaces/theme/packages/backend/package.json index aed14daf..774b3d59 100644 --- a/workspaces/theme/packages/backend/package.json +++ b/workspaces/theme/packages/backend/package.json @@ -44,7 +44,7 @@ "@backstage/plugin-search-backend-module-techdocs": "^0.3.0", "@backstage/plugin-search-backend-node": "^1.3.3", "@backstage/plugin-techdocs-backend": "^1.11.0", - "app": "link:../app", + "app": "workspace:*", "better-sqlite3": "^9.0.0", "node-gyp": "^10.0.0", "pg": "^8.11.3" diff --git a/workspaces/theme/yarn.lock b/workspaces/theme/yarn.lock index 6e040304..5ac9506b 100644 --- a/workspaces/theme/yarn.lock +++ b/workspaces/theme/yarn.lock @@ -14133,13 +14133,7 @@ __metadata: languageName: node linkType: hard -"app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend": - version: 0.0.0-use.local - resolution: "app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend" - languageName: node - linkType: soft - -"app@workspace:packages/app": +"app@workspace:*, app@workspace:packages/app": version: 0.0.0-use.local resolution: "app@workspace:packages/app" dependencies: @@ -14843,7 +14837,7 @@ __metadata: "@backstage/plugin-search-backend-module-techdocs": ^0.3.0 "@backstage/plugin-search-backend-node": ^1.3.3 "@backstage/plugin-techdocs-backend": ^1.11.0 - app: "link:../app" + app: "workspace:*" better-sqlite3: ^9.0.0 node-gyp: ^10.0.0 pg: ^8.11.3 From 373c36e9b1ce3bf08b978e51a3b7b21edc4bf5cc Mon Sep 17 00:00:00 2001 From: Kashish Mittal <113269381+04kash@users.noreply.github.com> Date: Fri, 22 Nov 2024 16:12:56 -0500 Subject: [PATCH 02/13] Revert "chore(ci): add verify changesets step (#65)" (#69) This reverts commit 0a733a269d11e1b595e198d2e76b230a8f895779. --- .github/workflows/ci.yml | 6 ------ workspaces/bulk-import/packages/backend/package.json | 2 +- workspaces/bulk-import/yarn.lock | 10 ++++++++-- workspaces/homepage/packages/backend/package.json | 2 +- workspaces/homepage/yarn.lock | 10 ++++++++-- workspaces/lightspeed/.changeset/config.json | 6 +----- workspaces/lightspeed/packages/backend/package.json | 2 +- workspaces/lightspeed/yarn.lock | 10 ++++++++-- workspaces/marketplace/packages/backend/package.json | 2 +- workspaces/marketplace/yarn.lock | 10 ++++++++-- .../packages/backend/package.json | 2 +- workspaces/openshift-image-registry/yarn.lock | 10 ++++++++-- workspaces/theme/packages/backend/package.json | 2 +- workspaces/theme/yarn.lock | 10 ++++++++-- 14 files changed, 55 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f4ab38e..31743ca9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,7 +70,6 @@ jobs: uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4 with: ref: ${{ github.event.workflow_run.head_branch }} - fetch-depth: 0 - name: Set up Node ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4 @@ -91,11 +90,6 @@ jobs: - name: type checking and declarations run: yarn tsc:full - - name: Verify changesets - if: ${{ !(github.actor == 'rhdh-bot' || github.actor == 'github-actions[bot]') && matrix.workspace != 'repo-tools' && matrix.workspace != 'noop' }} - run: | - yarn changeset status --since=${{ github.event.pull_request.base.sha }} - - name: prettier run: yarn prettier:check diff --git a/workspaces/bulk-import/packages/backend/package.json b/workspaces/bulk-import/packages/backend/package.json index e413aa45..ccf4416f 100644 --- a/workspaces/bulk-import/packages/backend/package.json +++ b/workspaces/bulk-import/packages/backend/package.json @@ -44,7 +44,7 @@ "@backstage/plugin-search-backend-node": "^1.3.4", "@backstage/plugin-techdocs-backend": "^1.11.1", "@red-hat-developer-hub/backstage-plugin-bulk-import-backend": "workspace:^", - "app": "workspace:*", + "app": "link:../app", "better-sqlite3": "^9.0.0", "node-gyp": "^10.0.0", "pg": "^8.11.3", diff --git a/workspaces/bulk-import/yarn.lock b/workspaces/bulk-import/yarn.lock index 367fef57..9f8ac1a0 100644 --- a/workspaces/bulk-import/yarn.lock +++ b/workspaces/bulk-import/yarn.lock @@ -15333,7 +15333,13 @@ __metadata: languageName: node linkType: hard -"app@workspace:*, app@workspace:packages/app": +"app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend": + version: 0.0.0-use.local + resolution: "app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend" + languageName: node + linkType: soft + +"app@workspace:packages/app": version: 0.0.0-use.local resolution: "app@workspace:packages/app" dependencies: @@ -16102,7 +16108,7 @@ __metadata: "@types/express": ^4.17.6 "@types/express-serve-static-core": ^4.17.5 "@types/luxon": ^2.0.4 - app: "workspace:*" + app: "link:../app" better-sqlite3: ^9.0.0 node-gyp: ^10.0.0 pg: ^8.11.3 diff --git a/workspaces/homepage/packages/backend/package.json b/workspaces/homepage/packages/backend/package.json index bbf8bb65..3f637e03 100644 --- a/workspaces/homepage/packages/backend/package.json +++ b/workspaces/homepage/packages/backend/package.json @@ -44,7 +44,7 @@ "@backstage/plugin-search-backend-module-techdocs": "^0.3.0", "@backstage/plugin-search-backend-node": "^1.3.3", "@backstage/plugin-techdocs-backend": "^1.11.0", - "app": "workspace:*", + "app": "link:../app", "better-sqlite3": "^9.0.0", "node-gyp": "^10.0.0", "pg": "^8.11.3" diff --git a/workspaces/homepage/yarn.lock b/workspaces/homepage/yarn.lock index 1fb133c4..999fbe65 100644 --- a/workspaces/homepage/yarn.lock +++ b/workspaces/homepage/yarn.lock @@ -14347,7 +14347,13 @@ __metadata: languageName: node linkType: hard -"app@workspace:*, app@workspace:packages/app": +"app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend": + version: 0.0.0-use.local + resolution: "app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend" + languageName: node + linkType: soft + +"app@workspace:packages/app": version: 0.0.0-use.local resolution: "app@workspace:packages/app" dependencies: @@ -15051,7 +15057,7 @@ __metadata: "@backstage/plugin-search-backend-module-techdocs": ^0.3.0 "@backstage/plugin-search-backend-node": ^1.3.3 "@backstage/plugin-techdocs-backend": ^1.11.0 - app: "workspace:*" + app: "link:../app" better-sqlite3: ^9.0.0 node-gyp: ^10.0.0 pg: ^8.11.3 diff --git a/workspaces/lightspeed/.changeset/config.json b/workspaces/lightspeed/.changeset/config.json index 8208df00..4d034bb9 100644 --- a/workspaces/lightspeed/.changeset/config.json +++ b/workspaces/lightspeed/.changeset/config.json @@ -6,9 +6,5 @@ "linked": [], "access": "public", "baseBranch": "main", - "updateInternalDependencies": "patch", - "privatePackages": { - "tag": false, - "version": false - } + "updateInternalDependencies": "patch" } diff --git a/workspaces/lightspeed/packages/backend/package.json b/workspaces/lightspeed/packages/backend/package.json index 7c0c3445..2bf5becb 100644 --- a/workspaces/lightspeed/packages/backend/package.json +++ b/workspaces/lightspeed/packages/backend/package.json @@ -45,7 +45,7 @@ "@backstage/plugin-search-backend-node": "^1.3.5", "@backstage/plugin-techdocs-backend": "^1.11.2", "@red-hat-developer-hub/backstage-plugin-lightspeed-backend": "workspace:^", - "app": "workspace:*", + "app": "link:../app", "better-sqlite3": "^9.0.0", "node-gyp": "^10.0.0", "pg": "^8.11.3" diff --git a/workspaces/lightspeed/yarn.lock b/workspaces/lightspeed/yarn.lock index 5edd4555..9f843fcf 100644 --- a/workspaces/lightspeed/yarn.lock +++ b/workspaces/lightspeed/yarn.lock @@ -15287,7 +15287,13 @@ __metadata: languageName: node linkType: hard -"app@workspace:*, app@workspace:packages/app": +"app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend": + version: 0.0.0-use.local + resolution: "app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend" + languageName: node + linkType: soft + +"app@workspace:packages/app": version: 0.0.0-use.local resolution: "app@workspace:packages/app" dependencies: @@ -16008,7 +16014,7 @@ __metadata: "@backstage/plugin-search-backend-node": ^1.3.5 "@backstage/plugin-techdocs-backend": ^1.11.2 "@red-hat-developer-hub/backstage-plugin-lightspeed-backend": "workspace:^" - app: "workspace:*" + app: "link:../app" better-sqlite3: ^9.0.0 node-gyp: ^10.0.0 pg: ^8.11.3 diff --git a/workspaces/marketplace/packages/backend/package.json b/workspaces/marketplace/packages/backend/package.json index f5c3de40..576d9434 100644 --- a/workspaces/marketplace/packages/backend/package.json +++ b/workspaces/marketplace/packages/backend/package.json @@ -45,7 +45,7 @@ "@backstage/plugin-search-backend-node": "^1.3.3", "@backstage/plugin-techdocs-backend": "^1.11.0", "@red-hat-developer-hub/backstage-plugin-marketplace-backend": "workspace:^", - "app": "workspace:*", + "app": "link:../app", "better-sqlite3": "^9.0.0", "node-gyp": "^10.0.0", "pg": "^8.11.3" diff --git a/workspaces/marketplace/yarn.lock b/workspaces/marketplace/yarn.lock index dff8d173..a8391e4c 100644 --- a/workspaces/marketplace/yarn.lock +++ b/workspaces/marketplace/yarn.lock @@ -14356,7 +14356,13 @@ __metadata: languageName: node linkType: hard -"app@workspace:*, app@workspace:packages/app": +"app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend": + version: 0.0.0-use.local + resolution: "app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend" + languageName: node + linkType: soft + +"app@workspace:packages/app": version: 0.0.0-use.local resolution: "app@workspace:packages/app" dependencies: @@ -15062,7 +15068,7 @@ __metadata: "@backstage/plugin-search-backend-node": ^1.3.3 "@backstage/plugin-techdocs-backend": ^1.11.0 "@red-hat-developer-hub/backstage-plugin-marketplace-backend": "workspace:^" - app: "workspace:*" + app: "link:../app" better-sqlite3: ^9.0.0 node-gyp: ^10.0.0 pg: ^8.11.3 diff --git a/workspaces/openshift-image-registry/packages/backend/package.json b/workspaces/openshift-image-registry/packages/backend/package.json index d0f67507..70af9ab9 100644 --- a/workspaces/openshift-image-registry/packages/backend/package.json +++ b/workspaces/openshift-image-registry/packages/backend/package.json @@ -43,7 +43,7 @@ "@backstage/plugin-search-backend-module-techdocs": "^0.3.1", "@backstage/plugin-search-backend-node": "^1.3.4", "@backstage/plugin-techdocs-backend": "^1.11.1", - "app": "workspace:*", + "app": "link:../app", "better-sqlite3": "^9.0.0", "node-gyp": "^10.0.0", "pg": "^8.11.3", diff --git a/workspaces/openshift-image-registry/yarn.lock b/workspaces/openshift-image-registry/yarn.lock index 454c0c15..284df957 100644 --- a/workspaces/openshift-image-registry/yarn.lock +++ b/workspaces/openshift-image-registry/yarn.lock @@ -15065,7 +15065,13 @@ __metadata: languageName: node linkType: hard -"app@workspace:*, app@workspace:packages/app": +"app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend": + version: 0.0.0-use.local + resolution: "app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend" + languageName: node + linkType: soft + +"app@workspace:packages/app": version: 0.0.0-use.local resolution: "app@workspace:packages/app" dependencies: @@ -15775,7 +15781,7 @@ __metadata: "@types/express": ^4.17.6 "@types/express-serve-static-core": ^4.17.5 "@types/luxon": ^2.0.4 - app: "workspace:*" + app: "link:../app" better-sqlite3: ^9.0.0 node-gyp: ^10.0.0 pg: ^8.11.3 diff --git a/workspaces/theme/packages/backend/package.json b/workspaces/theme/packages/backend/package.json index 774b3d59..aed14daf 100644 --- a/workspaces/theme/packages/backend/package.json +++ b/workspaces/theme/packages/backend/package.json @@ -44,7 +44,7 @@ "@backstage/plugin-search-backend-module-techdocs": "^0.3.0", "@backstage/plugin-search-backend-node": "^1.3.3", "@backstage/plugin-techdocs-backend": "^1.11.0", - "app": "workspace:*", + "app": "link:../app", "better-sqlite3": "^9.0.0", "node-gyp": "^10.0.0", "pg": "^8.11.3" diff --git a/workspaces/theme/yarn.lock b/workspaces/theme/yarn.lock index 5ac9506b..6e040304 100644 --- a/workspaces/theme/yarn.lock +++ b/workspaces/theme/yarn.lock @@ -14133,7 +14133,13 @@ __metadata: languageName: node linkType: hard -"app@workspace:*, app@workspace:packages/app": +"app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend": + version: 0.0.0-use.local + resolution: "app@link:../app::locator=backend%40workspace%3Apackages%2Fbackend" + languageName: node + linkType: soft + +"app@workspace:packages/app": version: 0.0.0-use.local resolution: "app@workspace:packages/app" dependencies: @@ -14837,7 +14843,7 @@ __metadata: "@backstage/plugin-search-backend-module-techdocs": ^0.3.0 "@backstage/plugin-search-backend-node": ^1.3.3 "@backstage/plugin-techdocs-backend": ^1.11.0 - app: "workspace:*" + app: "link:../app" better-sqlite3: ^9.0.0 node-gyp: ^10.0.0 pg: ^8.11.3 From 54daa8cd8f663220f3707ced9ac0dc75fd11f492 Mon Sep 17 00:00:00 2001 From: Bat-Zion Rotman Date: Mon, 25 Nov 2024 13:32:25 +0200 Subject: [PATCH 03/13] plugin(orchestrator): initial migration of orchestrator plugin (#57) * migrate orchestrator plugin * add packages * fix README link * fixed yarn.lock and removed unused story * fixed prettier issues * fix API reports * fixed dependencies and title in app-config * updated CODEOWNERS * remove prebuild it doesn't seem to work * include fixes generated by backstage-cli repo fix * remove bulk import changelog --- .github/CODEOWNERS | 3 +- workspaces/orchestrator/.changeset/README.md | 8 + .../orchestrator/.changeset/config.json | 14 + .../.changeset/migrate-1732030041098.md | 10 + workspaces/orchestrator/.dockerignore | 8 + workspaces/orchestrator/.eslintignore | 5 + workspaces/orchestrator/.eslintrc.js | 1 + workspaces/orchestrator/.gitignore | 54 + workspaces/orchestrator/.prettierignore | 6 + workspaces/orchestrator/README.md | 16 + workspaces/orchestrator/app-config.yaml | 95 + workspaces/orchestrator/backstage.json | 1 + workspaces/orchestrator/catalog-info.yaml | 13 + workspaces/orchestrator/package.json | 64 + workspaces/orchestrator/packages/README.md | 3 + .../orchestrator/packages/app/.eslintignore | 1 + .../orchestrator/packages/app/.eslintrc.js | 16 + .../packages/app/e2e-tests/app.test.ts | 27 + .../orchestrator/packages/app/package.json | 83 + .../app/public/android-chrome-192x192.png | Bin 0 -> 13599 bytes .../packages/app/public/apple-touch-icon.png | Bin 0 -> 12619 bytes .../packages/app/public/favicon-16x16.png | Bin 0 -> 883 bytes .../packages/app/public/favicon-32x32.png | Bin 0 -> 1686 bytes .../packages/app/public/favicon.ico | Bin 0 -> 15086 bytes .../packages/app/public/index.html | 60 + .../packages/app/public/manifest.json | 15 + .../packages/app/public/robots.txt | 2 + .../packages/app/public/safari-pinned-tab.svg | 1 + .../packages/app/src/App.test.tsx | 44 + .../orchestrator/packages/app/src/App.tsx | 142 + .../orchestrator/packages/app/src/apis.ts | 34 + .../app/src/components/Root/LogoFull.tsx | 45 + .../app/src/components/Root/LogoIcon.tsx | 46 + .../packages/app/src/components/Root/Root.tsx | 123 + .../packages/app/src/components/Root/index.ts | 16 + .../app/src/components/catalog/EntityPage.tsx | 406 + .../app/src/components/search/SearchPage.tsx | 142 + .../orchestrator/packages/app/src/index.tsx | 21 + .../packages/app/src/setupTests.ts | 16 + .../packages/backend/.eslintrc.js | 16 + .../orchestrator/packages/backend/Dockerfile | 66 + .../orchestrator/packages/backend/README.md | 59 + .../packages/backend/package.json | 62 + .../packages/backend/src/index.ts | 64 + workspaces/orchestrator/plugins/README.md | 9 + .../orchestrator-backend/.eslintignore | 3 + .../plugins/orchestrator-backend/.eslintrc.js | 11 + .../plugins/orchestrator-backend/.gitignore | 1 + .../orchestrator-backend/.lintstagedrc.json | 4 + .../orchestrator-backend/.prettierignore | 13 + .../orchestrator-backend/.prettierrc.js | 20 + .../plugins/orchestrator-backend/CHANGELOG.md | 732 + .../plugins/orchestrator-backend/README.md | 5 + .../mockComposedGreetingWorfklow.ts | 144 + .../__fixtures__/mockGreetingWorkflowData.ts | 129 + .../mockSpringBootWorkflowData.ts | 595 + .../orchestrator-backend/app-config.yaml | 3 + .../orchestrator-backend/catalog-info.yaml | 25 + .../plugins/orchestrator-backend/dev/index.ts | 24 + .../plugins/orchestrator-backend/package.json | 112 + .../orchestrator-backend/report.api.md | 15 + .../src/OrchestratorPlugin.ts | 75 + .../src/helpers/errorBuilder.ts | 49 + .../src/helpers/filterBuilder.ts | 292 + .../src/helpers/filterBuilders.test.ts | 568 + .../src/helpers/queryBuilder.test.ts | 103 + .../src/helpers/queryBuilder.ts | 69 + .../plugins/orchestrator-backend/src/index.ts | 16 + .../orchestrator-backend/src/plugin.ts | 63 + .../src/routerWrapper/index.ts | 74 + .../src/service/DataIndexService.test.ts | 807 + .../src/service/DataIndexService.ts | 566 + .../src/service/DataInputSchemaService.ts | 26 + .../src/service/DevModeService.ts | 217 + .../src/service/GitService.ts | 108 + .../src/service/GitWrapper/git.ts | 355 + .../src/service/GitWrapper/index.ts | 18 + .../src/service/Helper.test.ts | 64 + .../src/service/Helper.ts | 91 + .../src/service/OrchestratorService.test.ts | 671 + .../src/service/OrchestratorService.ts | 229 + .../src/service/ScaffolderService.ts | 133 + .../src/service/SonataFlowService.test.ts | 370 + .../src/service/SonataFlowService.ts | 276 + .../src/service/WorkflowCacheService.ts | 127 + .../mockProcessDefinitionArgumentsData.ts | 120 + .../mockProcessInstanceArgumentsData.ts | 369 + .../service/api/mapping/V2Mappings.test.ts | 174 + .../src/service/api/mapping/V2Mappings.ts | 209 + .../__fixtures__/assessedProcessInstance.json | 143 + .../src/service/api/test-utils.ts | 158 + .../src/service/api/v2.test.ts | 577 + .../src/service/api/v2.ts | 277 + .../src/service/constants.ts | 19 + .../src/service/router.ts | 841 + .../src/types/pagination.test.ts | 74 + .../src/types/pagination.ts | 90 + .../orchestrator-backend/tsconfig.json | 9 + .../plugins/orchestrator-backend/turbo.json | 8 + .../plugins/orchestrator-common/.eslintignore | 7 + .../plugins/orchestrator-common/.eslintrc.js | 3 + .../plugins/orchestrator-common/.gitignore | 1 + .../orchestrator-common/.lintstagedrc.json | 4 + .../orchestrator-common/.prettierignore | 13 + .../orchestrator-common/.prettierrc.js | 20 + .../plugins/orchestrator-common/CHANGELOG.md | 222 + .../plugins/orchestrator-common/README.md | 5 + .../orchestrator-common/catalog-info.yaml | 25 + .../plugins/orchestrator-common/config.d.ts | 81 + .../orchestrator-common/openapitools.json | 7 + .../plugins/orchestrator-common/package.json | 84 + .../plugins/orchestrator-common/report.api.md | 2117 + .../orchestrator-common/scripts/openapi.sh | 82 + .../orchestrator-common/src/QueryParams.ts | 20 + .../orchestrator-common/src/constants.ts | 25 + .../src/generated/.METADATA.sha1 | 1 + .../src/generated/api/definition.ts | 5 + .../src/generated/client/.gitignore | 4 + .../src/generated/client/.npmignore | 1 + .../client/.openapi-generator-ignore | 23 + .../generated/client/.openapi-generator/FILES | 9 + .../client/.openapi-generator/VERSION | 1 + .../src/generated/client/api.ts | 1698 + .../src/generated/client/base.ts | 86 + .../src/generated/client/common.ts | 150 + .../src/generated/client/configuration.ts | 110 + .../src/generated/client/git_push.sh | 57 + .../src/generated/client/index.ts | 18 + .../docs/html/.openapi-generator-ignore | 23 + .../docs/html/.openapi-generator/FILES | 2 + .../docs/html/.openapi-generator/VERSION | 1 + .../src/generated/docs/html/index.html | 8028 ++++ .../docs/markdown/.openapi-generator-ignore | 23 + .../docs/markdown/.openapi-generator/FILES | 34 + .../docs/markdown/.openapi-generator/VERSION | 1 + .../docs/markdown/Apis/DefaultApi.md | 318 + .../Models/AssessedProcessInstanceDTO.md | 10 + .../docs/markdown/Models/ErrorResponse.md | 10 + .../Models/ExecuteWorkflowRequestDTO.md | 9 + .../Models/ExecuteWorkflowResponseDTO.md | 9 + .../docs/markdown/Models/FieldFilter.md | 11 + .../docs/markdown/Models/FieldFilter_value.md | 8 + .../generated/docs/markdown/Models/Filter.md | 12 + .../markdown/Models/GetInstancesRequest.md | 10 + .../Models/GetOverviewsRequestParams.md | 10 + .../markdown/Models/InputSchemaResponseDTO.md | 10 + .../docs/markdown/Models/LogicalFilter.md | 10 + .../docs/markdown/Models/NodeInstanceDTO.md | 16 + .../docs/markdown/Models/PaginationInfoDTO.md | 13 + .../markdown/Models/ProcessInstanceDTO.md | 23 + .../Models/ProcessInstanceErrorDTO.md | 11 + .../Models/ProcessInstanceListResultDTO.md | 10 + .../Models/ProcessInstanceStatusDTO.md | 8 + .../docs/markdown/Models/SearchRequest.md | 10 + .../markdown/Models/WorkflowCategoryDTO.md | 8 + .../docs/markdown/Models/WorkflowDTO.md | 14 + .../docs/markdown/Models/WorkflowDataDTO.md | 9 + .../docs/markdown/Models/WorkflowFormatDTO.md | 8 + .../markdown/Models/WorkflowListResultDTO.md | 10 + .../markdown/Models/WorkflowOverviewDTO.md | 17 + .../Models/WorkflowOverviewListResultDTO.md | 10 + .../markdown/Models/WorkflowProgressDTO.md | 18 + .../docs/markdown/Models/WorkflowResultDTO.md | 12 + .../WorkflowResultDTO_nextWorkflows_inner.md | 10 + .../Models/WorkflowResultDTO_outputs_inner.md | 11 + .../WorkflowResultDTO_outputs_inner_value.md | 8 + .../markdown/Models/WorkflowRunStatusDTO.md | 10 + .../src/generated/docs/markdown/README.md | 62 + .../plugins/orchestrator-common/src/index.ts | 24 + .../plugins/orchestrator-common/src/models.ts | 134 + .../src/openapi/openapi.yaml | 727 + .../orchestrator-common/src/permissions.ts | 55 + .../plugins/orchestrator-common/src/types.ts | 142 + .../src/utils/StringUtils.ts | 24 + .../orchestrator-common/src/workflow.test.ts | 28 + .../orchestrator-common/src/workflow.ts | 121 + .../plugins/orchestrator-common/tsconfig.json | 9 + .../plugins/orchestrator-common/turbo.json | 8 + .../orchestrator-form-api/.eslintignore | 3 + .../orchestrator-form-api/.eslintrc.js | 1 + .../orchestrator-form-api/.lintstagedrc.json | 4 + .../orchestrator-form-api/.prettierignore | 12 + .../orchestrator-form-api/.prettierrc.js | 20 + .../orchestrator-form-api/.versionhistory.md | 1 + .../orchestrator-form-api/CHANGELOG.md | 33 + .../plugins/orchestrator-form-api/README.md | 5 + .../orchestrator-form-api/package.json | 61 + .../orchestrator-form-api/report.api.md | 34 + .../plugins/orchestrator-form-api/src/api.ts | 87 + .../orchestrator-form-api/src/index.ts | 19 + .../orchestrator-form-api/tsconfig.json | 9 + .../plugins/orchestrator-form-api/turbo.json | 8 + .../orchestrator-form-react/.eslintignore | 3 + .../orchestrator-form-react/.eslintrc.js | 1 + .../.lintstagedrc.json | 4 + .../orchestrator-form-react/.prettierignore | 12 + .../orchestrator-form-react/.prettierrc.js | 20 + .../.versionhistory.md | 1 + .../orchestrator-form-react/CHANGELOG.md | 131 + .../plugins/orchestrator-form-react/README.md | 5 + .../orchestrator-form-react/package.json | 75 + .../orchestrator-form-react/report.api.md | 31 + .../src/DefaultFormApi.tsx | 33 + .../src/components/OrchestratorForm.tsx | 157 + .../components/OrchestratorFormStepper.tsx | 124 + .../components/OrchestratorFormWrapper.tsx | 168 + .../src/components/ReviewStep.tsx | 75 + .../src/components/StepperObjectField.tsx | 84 + .../src/components/SubmitButton.tsx | 59 + .../src/components/index.ts | 19 + .../src/components/useStyles.ts | 15 + .../orchestrator-form-react/src/index.ts | 26 + .../src/utils/StepperContext.tsx | 61 + .../src/utils/generateReviewTableData.test.ts | 169 + .../src/utils/generateReviewTableData.ts | 76 + .../src/utils/generateUiSchema.test.ts | 531 + .../src/utils/generateUiSchema.ts | 236 + .../src/utils/useValidator.ts | 102 + .../orchestrator-form-react/tsconfig.json | 9 + .../orchestrator-form-react/turbo.json | 8 + .../.eslintignore | 3 + .../.eslintrc.js | 1 + .../.lintstagedrc.json | 4 + .../.prettierignore | 12 + .../.prettierrc.js | 20 + .../README.md | 32 + .../catalog-info.yaml | 25 + .../package.json | 87 + .../scripts/build.sh | 7 + .../scripts/postbuild.js | 129 + .../src/index.ejs | 25 + .../src/init/SwfEditorEnvelopeCombined.ts | 44 + .../src/init/SwfEditorEnvelopeDiagram.ts | 46 + .../src/init/SwfEditorEnvelopeText.ts | 42 + .../tsconfig.json | 11 + .../webpack.config.js | 181 + .../plugins/orchestrator/.eslintignore | 3 + .../plugins/orchestrator/.eslintrc.js | 1 + .../plugins/orchestrator/.lintstagedrc.json | 4 + .../plugins/orchestrator/.prettierignore | 12 + .../plugins/orchestrator/.prettierrc.js | 20 + .../plugins/orchestrator/CHANGELOG.md | 812 + .../plugins/orchestrator/README.md | 269 + .../plugins/orchestrator/app-config.yaml | 14 + .../plugins/orchestrator/catalog-info.yaml | 51 + .../plugins/orchestrator/dev/index.tsx | 32 + .../plugins/orchestrator/docs/Permissions.md | 47 + .../orchestrator/docs/executePageNext.png | Bin 0 -> 138885 bytes .../orchestrator/docs/executePageRun.png | Bin 0 -> 135197 bytes .../orchestrator/docs/extensibleForm.md | 136 + .../orchestrator/docs/orchestratorIcon.png | Bin 0 -> 136688 bytes .../plugins/orchestrator/docs/quickstart.md | 35 + .../plugins/orchestrator/docs/rbac-policy.csv | 11 + .../orchestrator/docs/workflowCompleted.png | Bin 0 -> 248260 bytes .../orchestrator/docs/workflowsPage.png | Bin 0 -> 114122 bytes .../plugins/orchestrator/package.json | 122 + .../plugins/orchestrator/report.api.md | 28 + .../src/api/OrchestratorClient.test.ts | 627 + .../src/api/OrchestratorClient.ts | 243 + .../plugins/orchestrator/src/api/api.ts | 67 + .../plugins/orchestrator/src/api/index.ts | 18 + .../src/components/BaseOrchestratorPage.tsx | 47 + .../ExecuteWorkflowPage.tsx | 151 + .../ExecuteWorkflowPage/JsonTextAreaForm.tsx | 83 + .../src/components/InfoDialog.tsx | 77 + .../src/components/OrchestratorIcon.tsx | 32 + .../src/components/OrchestratorPage.tsx | 41 + .../orchestrator/src/components/Paragraph.tsx | 33 + .../orchestrator/src/components/Router.tsx | 47 + .../orchestrator/src/components/Selector.tsx | 84 + .../WorkflowDefinitionDetailsCard.tsx | 120 + .../WorkflowDefinitionViewerPage.tsx | 120 + .../WorkflowDefinitionViewerPage/index.ts | 16 + .../components/WorkflowDescriptionModal.tsx | 141 + .../src/components/WorkflowDialog.tsx | 90 + .../WorkflowEditor/WorkflowEditor.tsx | 323 + .../channel/WorkflowEditorLanguageService.ts | 87 + ...flowEditorLanguageServiceChannelApiImpl.ts | 50 + .../src/components/WorkflowEditor/index.ts | 17 + .../src/components/WorkflowInstancePage.tsx | 274 + .../WorkflowInstancePageContent.tsx | 176 + .../WorkflowInstanceStatusIndicator.tsx | 58 + .../src/components/WorkflowProgress.tsx | 52 + .../src/components/WorkflowProgressNode.tsx | 112 + .../components/WorkflowProgressNodeModel.ts | 68 + .../src/components/WorkflowResult.tsx | 315 + .../src/components/WorkflowRunDetail.ts | 31 + .../src/components/WorkflowRunDetails.tsx | 125 + .../src/components/WorkflowRunsTabContent.tsx | 158 + .../components/WorkflowVariablesViewer.tsx | 44 + .../src/components/WorkflowsTabContent.tsx | 60 + .../src/components/WorkflowsTable.tsx | 162 + .../components/ui/OverrideBackstageTable.tsx | 49 + .../plugins/orchestrator/src/constants.ts | 20 + .../src/dataFormatters/DataFormatter.ts | 20 + .../WorkflowOverviewFormatter.test.ts | 73 + .../WorkflowOverviewFormatter.ts | 64 + .../orchestrator/src/hooks/usePolling.test.ts | 197 + .../orchestrator/src/hooks/usePolling.ts | 83 + .../hooks/useWorkflowInstanceStatusColors.ts | 49 + .../plugins/orchestrator/src/index.ts | 17 + .../plugins/orchestrator/src/plugin.ts | 57 + .../plugins/orchestrator/src/routes.ts | 44 + .../orchestrator/src/utils/ErrorUtils.ts | 26 + .../src/utils/NodeInstanceUtils.test.ts | 225 + .../src/utils/NodeInstanceUtils.ts | 56 + .../orchestrator/src/utils/TypeGuards.ts | 24 + .../orchestrator/src/utils/UrlUtils.test.ts | 47 + .../orchestrator/src/utils/UrlUtils.ts | 33 + .../plugins/orchestrator/tsconfig.json | 9 + .../plugins/orchestrator/turbo.json | 8 + workspaces/orchestrator/tsconfig.json | 18 + workspaces/orchestrator/yarn.lock | 36727 ++++++++++++++++ 313 files changed, 73868 insertions(+), 1 deletion(-) create mode 100644 workspaces/orchestrator/.changeset/README.md create mode 100644 workspaces/orchestrator/.changeset/config.json create mode 100644 workspaces/orchestrator/.changeset/migrate-1732030041098.md create mode 100644 workspaces/orchestrator/.dockerignore create mode 100644 workspaces/orchestrator/.eslintignore create mode 100644 workspaces/orchestrator/.eslintrc.js create mode 100644 workspaces/orchestrator/.gitignore create mode 100644 workspaces/orchestrator/.prettierignore create mode 100644 workspaces/orchestrator/README.md create mode 100644 workspaces/orchestrator/app-config.yaml create mode 100644 workspaces/orchestrator/backstage.json create mode 100644 workspaces/orchestrator/catalog-info.yaml create mode 100644 workspaces/orchestrator/package.json create mode 100644 workspaces/orchestrator/packages/README.md create mode 100644 workspaces/orchestrator/packages/app/.eslintignore create mode 100644 workspaces/orchestrator/packages/app/.eslintrc.js create mode 100644 workspaces/orchestrator/packages/app/e2e-tests/app.test.ts create mode 100644 workspaces/orchestrator/packages/app/package.json create mode 100644 workspaces/orchestrator/packages/app/public/android-chrome-192x192.png create mode 100644 workspaces/orchestrator/packages/app/public/apple-touch-icon.png create mode 100644 workspaces/orchestrator/packages/app/public/favicon-16x16.png create mode 100644 workspaces/orchestrator/packages/app/public/favicon-32x32.png create mode 100644 workspaces/orchestrator/packages/app/public/favicon.ico create mode 100644 workspaces/orchestrator/packages/app/public/index.html create mode 100644 workspaces/orchestrator/packages/app/public/manifest.json create mode 100644 workspaces/orchestrator/packages/app/public/robots.txt create mode 100644 workspaces/orchestrator/packages/app/public/safari-pinned-tab.svg create mode 100644 workspaces/orchestrator/packages/app/src/App.test.tsx create mode 100644 workspaces/orchestrator/packages/app/src/App.tsx create mode 100644 workspaces/orchestrator/packages/app/src/apis.ts create mode 100644 workspaces/orchestrator/packages/app/src/components/Root/LogoFull.tsx create mode 100644 workspaces/orchestrator/packages/app/src/components/Root/LogoIcon.tsx create mode 100644 workspaces/orchestrator/packages/app/src/components/Root/Root.tsx create mode 100644 workspaces/orchestrator/packages/app/src/components/Root/index.ts create mode 100644 workspaces/orchestrator/packages/app/src/components/catalog/EntityPage.tsx create mode 100644 workspaces/orchestrator/packages/app/src/components/search/SearchPage.tsx create mode 100644 workspaces/orchestrator/packages/app/src/index.tsx create mode 100644 workspaces/orchestrator/packages/app/src/setupTests.ts create mode 100644 workspaces/orchestrator/packages/backend/.eslintrc.js create mode 100644 workspaces/orchestrator/packages/backend/Dockerfile create mode 100644 workspaces/orchestrator/packages/backend/README.md create mode 100644 workspaces/orchestrator/packages/backend/package.json create mode 100644 workspaces/orchestrator/packages/backend/src/index.ts create mode 100644 workspaces/orchestrator/plugins/README.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/.eslintignore create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/.eslintrc.js create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/.gitignore create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/.lintstagedrc.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/.prettierignore create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/.prettierrc.js create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/CHANGELOG.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/README.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/__fixtures__/mockComposedGreetingWorfklow.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/__fixtures__/mockGreetingWorkflowData.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/__fixtures__/mockSpringBootWorkflowData.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/app-config.yaml create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/catalog-info.yaml create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/dev/index.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/package.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/report.api.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/OrchestratorPlugin.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/errorBuilder.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/filterBuilder.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/filterBuilders.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/queryBuilder.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/queryBuilder.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/index.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/plugin.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/routerWrapper/index.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/DataIndexService.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/DataIndexService.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/DataInputSchemaService.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/DevModeService.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/GitService.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/GitWrapper/git.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/GitWrapper/index.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/Helper.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/Helper.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/OrchestratorService.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/OrchestratorService.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/ScaffolderService.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/SonataFlowService.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/SonataFlowService.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/WorkflowCacheService.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/__fixtures__/mockProcessDefinitionArgumentsData.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/__fixtures__/mockProcessInstanceArgumentsData.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/mapping/V2Mappings.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/mapping/V2Mappings.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/mapping/__fixtures__/assessedProcessInstance.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/test-utils.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/v2.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/v2.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/constants.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/service/router.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/types/pagination.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/types/pagination.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/tsconfig.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/turbo.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/.eslintignore create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/.eslintrc.js create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/.gitignore create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/.lintstagedrc.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/.prettierignore create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/.prettierrc.js create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/CHANGELOG.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/README.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/catalog-info.yaml create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/config.d.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/openapitools.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/package.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/report.api.md create mode 100755 workspaces/orchestrator/plugins/orchestrator-common/scripts/openapi.sh create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/QueryParams.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/constants.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/.METADATA.sha1 create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/api/definition.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.gitignore create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.npmignore create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.openapi-generator-ignore create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.openapi-generator/FILES create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.openapi-generator/VERSION create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/api.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/base.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/common.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/configuration.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/git_push.sh create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/index.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/html/.openapi-generator-ignore create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/html/.openapi-generator/FILES create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/html/.openapi-generator/VERSION create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/html/index.html create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/.openapi-generator-ignore create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/.openapi-generator/FILES create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/.openapi-generator/VERSION create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Apis/DefaultApi.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/AssessedProcessInstanceDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ErrorResponse.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ExecuteWorkflowRequestDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ExecuteWorkflowResponseDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/FieldFilter.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/FieldFilter_value.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/Filter.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/GetInstancesRequest.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/GetOverviewsRequestParams.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/InputSchemaResponseDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/LogicalFilter.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/NodeInstanceDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/PaginationInfoDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ProcessInstanceDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ProcessInstanceErrorDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ProcessInstanceListResultDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ProcessInstanceStatusDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/SearchRequest.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowCategoryDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowDataDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowFormatDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowListResultDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowOverviewDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowOverviewListResultDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowProgressDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowResultDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowResultDTO_nextWorkflows_inner.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowResultDTO_outputs_inner.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowResultDTO_outputs_inner_value.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowRunStatusDTO.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/README.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/index.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/models.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/openapi/openapi.yaml create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/permissions.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/types.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/utils/StringUtils.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/workflow.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/src/workflow.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/tsconfig.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-common/turbo.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-api/.eslintignore create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-api/.eslintrc.js create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-api/.lintstagedrc.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-api/.prettierignore create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-api/.prettierrc.js create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-api/.versionhistory.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-api/CHANGELOG.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-api/README.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-api/package.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-api/report.api.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-api/src/api.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-api/src/index.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-api/tsconfig.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-api/turbo.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/.eslintignore create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/.eslintrc.js create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/.lintstagedrc.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/.prettierignore create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/.prettierrc.js create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/.versionhistory.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/CHANGELOG.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/README.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/package.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/report.api.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/src/DefaultFormApi.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorForm.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorFormStepper.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorFormWrapper.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/src/components/ReviewStep.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/src/components/StepperObjectField.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/src/components/SubmitButton.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/src/components/index.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/src/components/useStyles.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/src/index.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/StepperContext.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateReviewTableData.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateReviewTableData.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateUiSchema.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateUiSchema.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/useValidator.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/tsconfig.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-form-react/turbo.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.eslintignore create mode 100644 workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.eslintrc.js create mode 100644 workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.lintstagedrc.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.prettierignore create mode 100644 workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.prettierrc.js create mode 100644 workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/README.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/catalog-info.yaml create mode 100644 workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/package.json create mode 100755 workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/scripts/build.sh create mode 100755 workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/scripts/postbuild.js create mode 100644 workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/src/index.ejs create mode 100644 workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/src/init/SwfEditorEnvelopeCombined.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/src/init/SwfEditorEnvelopeDiagram.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/src/init/SwfEditorEnvelopeText.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/tsconfig.json create mode 100644 workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/webpack.config.js create mode 100644 workspaces/orchestrator/plugins/orchestrator/.eslintignore create mode 100644 workspaces/orchestrator/plugins/orchestrator/.eslintrc.js create mode 100644 workspaces/orchestrator/plugins/orchestrator/.lintstagedrc.json create mode 100644 workspaces/orchestrator/plugins/orchestrator/.prettierignore create mode 100644 workspaces/orchestrator/plugins/orchestrator/.prettierrc.js create mode 100644 workspaces/orchestrator/plugins/orchestrator/CHANGELOG.md create mode 100644 workspaces/orchestrator/plugins/orchestrator/README.md create mode 100644 workspaces/orchestrator/plugins/orchestrator/app-config.yaml create mode 100644 workspaces/orchestrator/plugins/orchestrator/catalog-info.yaml create mode 100644 workspaces/orchestrator/plugins/orchestrator/dev/index.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/docs/Permissions.md create mode 100644 workspaces/orchestrator/plugins/orchestrator/docs/executePageNext.png create mode 100644 workspaces/orchestrator/plugins/orchestrator/docs/executePageRun.png create mode 100644 workspaces/orchestrator/plugins/orchestrator/docs/extensibleForm.md create mode 100644 workspaces/orchestrator/plugins/orchestrator/docs/orchestratorIcon.png create mode 100644 workspaces/orchestrator/plugins/orchestrator/docs/quickstart.md create mode 100644 workspaces/orchestrator/plugins/orchestrator/docs/rbac-policy.csv create mode 100644 workspaces/orchestrator/plugins/orchestrator/docs/workflowCompleted.png create mode 100644 workspaces/orchestrator/plugins/orchestrator/docs/workflowsPage.png create mode 100644 workspaces/orchestrator/plugins/orchestrator/package.json create mode 100644 workspaces/orchestrator/plugins/orchestrator/report.api.md create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/api/OrchestratorClient.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/api/OrchestratorClient.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/api/api.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/api/index.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/BaseOrchestratorPage.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/ExecuteWorkflowPage.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/JsonTextAreaForm.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/InfoDialog.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/OrchestratorIcon.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/OrchestratorPage.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/Paragraph.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/Router.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/Selector.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowDefinitionViewerPage/WorkflowDefinitionDetailsCard.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowDefinitionViewerPage/WorkflowDefinitionViewerPage.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowDefinitionViewerPage/index.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowDescriptionModal.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowDialog.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowEditor/WorkflowEditor.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowEditor/channel/WorkflowEditorLanguageService.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowEditor/channel/WorkflowEditorLanguageServiceChannelApiImpl.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowEditor/index.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowInstancePage.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowInstancePageContent.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowInstanceStatusIndicator.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowProgress.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowProgressNode.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowProgressNodeModel.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowResult.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowRunDetail.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowRunDetails.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowRunsTabContent.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowVariablesViewer.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowsTabContent.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/WorkflowsTable.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/components/ui/OverrideBackstageTable.tsx create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/constants.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/dataFormatters/DataFormatter.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/dataFormatters/WorkflowOverviewFormatter.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/dataFormatters/WorkflowOverviewFormatter.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/hooks/usePolling.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/hooks/usePolling.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/hooks/useWorkflowInstanceStatusColors.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/index.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/plugin.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/routes.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/utils/ErrorUtils.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/utils/NodeInstanceUtils.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/utils/NodeInstanceUtils.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/utils/TypeGuards.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/utils/UrlUtils.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/src/utils/UrlUtils.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator/tsconfig.json create mode 100644 workspaces/orchestrator/plugins/orchestrator/turbo.json create mode 100644 workspaces/orchestrator/tsconfig.json create mode 100644 workspaces/orchestrator/yarn.lock diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 50109dda..7218bf97 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -10,4 +10,5 @@ /workspaces/marketplace @christoph-jerolimov @debsmita1 @divyanshiGupta /workspaces/openshift-image-registry @christoph-jerolimov @debsmita1 @divyanshiGupta /workspaces/theme @christoph-jerolimov @ciiay @debsmita1 -/workspaces/lightspeed @karthikjeeyar @yangcao77 @rohitkrai03 \ No newline at end of file +/workspaces/lightspeed @karthikjeeyar @yangcao77 @rohitkrai03 +/workspaces/orchestrator @batzionb @mareklibra @gciavarrini \ No newline at end of file diff --git a/workspaces/orchestrator/.changeset/README.md b/workspaces/orchestrator/.changeset/README.md new file mode 100644 index 00000000..e5b6d8d6 --- /dev/null +++ b/workspaces/orchestrator/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/workspaces/orchestrator/.changeset/config.json b/workspaces/orchestrator/.changeset/config.json new file mode 100644 index 00000000..8208df00 --- /dev/null +++ b/workspaces/orchestrator/.changeset/config.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "privatePackages": { + "tag": false, + "version": false + } +} diff --git a/workspaces/orchestrator/.changeset/migrate-1732030041098.md b/workspaces/orchestrator/.changeset/migrate-1732030041098.md new file mode 100644 index 00000000..bc910dd8 --- /dev/null +++ b/workspaces/orchestrator/.changeset/migrate-1732030041098.md @@ -0,0 +1,10 @@ +--- +'@red-hat-developer-hub/backstage-plugin-orchestrator': patch +'@red-hat-developer-hub/backstage-plugin-orchestrator-backend': patch +'@red-hat-developer-hub/backstage-plugin-orchestrator-common': patch +'@red-hat-developer-hub/backstage-plugin-orchestrator-form-api': patch +'@red-hat-developer-hub/backstage-plugin-orchestrator-form-react': patch +'@red-hat-developer-hub/backstage-plugin-orchestrator-swf-editor-envelope': patch +--- + +Migrated from [janus-idp/backstage-plugins](https://github.com/janus-idp/backstage-plugins). diff --git a/workspaces/orchestrator/.dockerignore b/workspaces/orchestrator/.dockerignore new file mode 100644 index 00000000..05edb626 --- /dev/null +++ b/workspaces/orchestrator/.dockerignore @@ -0,0 +1,8 @@ +.git +.yarn/cache +.yarn/install-state.gz +node_modules +packages/*/src +packages/*/node_modules +plugins +*.local.yaml diff --git a/workspaces/orchestrator/.eslintignore b/workspaces/orchestrator/.eslintignore new file mode 100644 index 00000000..fc5d0d74 --- /dev/null +++ b/workspaces/orchestrator/.eslintignore @@ -0,0 +1,5 @@ +playwright.config.ts +dist-dynamic +dist-scalprum +!.eslintrc.js +!.prettierrc.js \ No newline at end of file diff --git a/workspaces/orchestrator/.eslintrc.js b/workspaces/orchestrator/.eslintrc.js new file mode 100644 index 00000000..59b86f84 --- /dev/null +++ b/workspaces/orchestrator/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('../../.eslintrc.cjs'); diff --git a/workspaces/orchestrator/.gitignore b/workspaces/orchestrator/.gitignore new file mode 100644 index 00000000..fbf81390 --- /dev/null +++ b/workspaces/orchestrator/.gitignore @@ -0,0 +1,54 @@ +# macOS +.DS_Store + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Coverage directory generated when running tests with coverage +coverage + +# Dependencies +node_modules/ + +# Yarn 3 files +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Node version directives +.nvmrc + +# dotenv environment variables file +.env +.env.test + +# Build output +dist +dist-types + +# Temporary change files created by Vim +*.swp + +# MkDocs build output +site + +# Local configuration files +*.local.yaml + +# Sensitive credentials +*-credentials.yaml + +# vscode database functionality support files +*.session.sql + +# E2E test reports +e2e-test-report/ diff --git a/workspaces/orchestrator/.prettierignore b/workspaces/orchestrator/.prettierignore new file mode 100644 index 00000000..326dea77 --- /dev/null +++ b/workspaces/orchestrator/.prettierignore @@ -0,0 +1,6 @@ +dist +dist-types +coverage +.vscode +.eslintrc.js +generated diff --git a/workspaces/orchestrator/README.md b/workspaces/orchestrator/README.md new file mode 100644 index 00000000..94044a3f --- /dev/null +++ b/workspaces/orchestrator/README.md @@ -0,0 +1,16 @@ +# [Backstage](https://backstage.io) + +This is your newly scaffolded Backstage App, Good Luck! + +To start the app, run: + +```sh +yarn install +yarn dev +``` + +To generate knip reports for this app, run: + +```sh +yarn backstage-repo-tools knip-reports +``` diff --git a/workspaces/orchestrator/app-config.yaml b/workspaces/orchestrator/app-config.yaml new file mode 100644 index 00000000..c05d79ad --- /dev/null +++ b/workspaces/orchestrator/app-config.yaml @@ -0,0 +1,95 @@ +app: + title: RHDH Plugins + baseUrl: http://localhost:3000 + +organization: + name: Red Hat + +backend: + # Used for enabling authentication, secret is shared by all backend plugins + # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format + # auth: + # keys: + # - secret: ${BACKEND_SECRET} + baseUrl: http://localhost:7007 + listen: + port: 7007 + # Uncomment the following host directive to bind to specific interfaces + # host: 127.0.0.1 + csp: + frame-ancestors: ['http://localhost:3000', 'http://localhost:7007'] + script-src: ["'self'", "'unsafe-inline'", "'unsafe-eval'"] + script-src-elem: ["'self'", "'unsafe-inline'", "'unsafe-eval'"] + connect-src: ["'self'", 'http:', 'https:', 'data:'] + # Content-Security-Policy directives follow the Helmet format: https://helmetjs.github.io/#reference + # Default Helmet Content-Security-Policy values can be removed by setting the key to false + cors: + origin: http://localhost:3000 + methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true + # This is for local development only, it is not recommended to use this in production + # The production database configuration is stored in app-config.production.yaml + database: + client: better-sqlite3 + connection: ':memory:' + cache: + store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir + +integrations: {} + +proxy: + '/quay/api': + target: 'https://quay.io' + headers: + X-Requested-With: 'XMLHttpRequest' + # Uncomment the following line to access a private Quay Repository using a token + # Authorization: 'Bearer ' + changeOrigin: true + # Change to "false" in case of using self hosted quay instance with a self-signed certificate + secure: true + +quay: + uiUrl: 'https://quay.io' + +techdocs: + builder: 'local' # Alternatives - 'external' + generator: + runIn: 'docker' # Alternatives - 'local' + publisher: + type: 'local' # Alternatives - 'googleGcs' or 'awsS3'. Read documentation for using alternatives. + +auth: + # see https://backstage.io/docs/auth/ to learn about auth providers + providers: { guest: {} } + +scaffolder: + {} + # see https://backstage.io/docs/features/software-templates/configuration for software template options + +catalog: + import: + entityFilename: catalog-info.yaml + pullRequestBranchName: backstage-integration + rules: + - allow: [Component, System, Group, Resource, Location, Template, API] + locations: + - type: url + target: https://github.com/janus-idp/backstage-showcase/blob/main/catalog-entities/all.yaml + + - type: url + target: https://github.com/redhat-developer/red-hat-developer-hub-software-templates/blob/main/templates.yaml + +dynamicPlugins: + frontend: {} +orchestrator: + sonataFlowService: + baseUrl: http://localhost + port: 8899 + autoStart: true + workflowsSource: + gitRepositoryUrl: https://github.com/parodos-dev/backstage-orchestrator-workflows + localPath: /tmp/orchestrator/repository + dataIndexService: + url: http://localhost:8899 diff --git a/workspaces/orchestrator/backstage.json b/workspaces/orchestrator/backstage.json new file mode 100644 index 00000000..d12c3635 --- /dev/null +++ b/workspaces/orchestrator/backstage.json @@ -0,0 +1 @@ +{ "version": "1.33.0" } diff --git a/workspaces/orchestrator/catalog-info.yaml b/workspaces/orchestrator/catalog-info.yaml new file mode 100644 index 00000000..ee676927 --- /dev/null +++ b/workspaces/orchestrator/catalog-info.yaml @@ -0,0 +1,13 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: orchestrator + description: An example of a Backstage application. + # Example for optional annotations + # annotations: + # github.com/project-slug: backstage/backstage + # backstage.io/techdocs-ref: dir:. +spec: + type: website + owner: john@example.com + lifecycle: experimental diff --git a/workspaces/orchestrator/package.json b/workspaces/orchestrator/package.json new file mode 100644 index 00000000..b0d0b3c5 --- /dev/null +++ b/workspaces/orchestrator/package.json @@ -0,0 +1,64 @@ +{ + "name": "@internal/orchestrator", + "version": "1.0.0", + "private": true, + "engines": { + "node": "18 || 20" + }, + "scripts": { + "tsc": "tsc", + "dev": "yarn workspaces foreach -A --include backend --include app --parallel -v -i run start", + "tsc:full": "tsc --skipLibCheck true --incremental false", + "build:all": "backstage-cli repo build --all", + "build:api-reports": "yarn build:api-reports:only", + "build:api-reports:only": "backstage-repo-tools api-reports --allow-all-warnings -o ae-wrong-input-file-type --validate-release-tags --exclude 'plugins/orchestrator-swf-editor-envelope'", + "clean": "backstage-cli repo clean", + "test": "backstage-cli repo test", + "test:all": "backstage-cli repo test --coverage", + "fix": "backstage-cli repo fix", + "lint": "backstage-cli repo lint --since origin/main", + "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", + "new": "backstage-cli new --scope @red-hat-developer-hub", + "postinstall": "cd ../../ && yarn install" + }, + "workspaces": { + "packages": [ + "packages/*", + "plugins/*" + ] + }, + "repository": { + "type": "git", + "url": "https://github.com/redhat-developer/rhdh-plugins", + "directory": "workspaces/orchestrator" + }, + "devDependencies": { + "@backstage/cli": "^0.29.0", + "@backstage/e2e-test-utils": "^0.1.1", + "@backstage/repo-tools": "^0.10.0", + "@changesets/cli": "^2.27.1", + "@ianvs/prettier-plugin-sort-imports": "^4.4.0", + "@spotify/prettier-config": "^12.0.0", + "knip": "^5.27.4", + "node-gyp": "^9.0.0", + "prettier": "^2.3.2", + "typescript": "~5.3.0" + }, + "resolutions": { + "@types/react": "^18", + "@types/react-dom": "^18", + "react": "^18", + "react-dom": "^18" + }, + "prettier": "@spotify/prettier-config", + "lint-staged": { + "*.{js,jsx,ts,tsx,mjs,cjs}": [ + "eslint --fix", + "prettier --write" + ], + "*.{json,md}": [ + "prettier --write" + ] + } +} diff --git a/workspaces/orchestrator/packages/README.md b/workspaces/orchestrator/packages/README.md new file mode 100644 index 00000000..c119f3f6 --- /dev/null +++ b/workspaces/orchestrator/packages/README.md @@ -0,0 +1,3 @@ +This is where your own applications and centrally managed libraries live, each in a separate folder of its own. + +From the start there's an app folder (for the frontend) and a backend folder (for the Node backend), but you can also add more modules in here that house your core additions and adaptations, such as themes, common React component libraries, utilities, and similar. diff --git a/workspaces/orchestrator/packages/app/.eslintignore b/workspaces/orchestrator/packages/app/.eslintignore new file mode 100644 index 00000000..a48cf0de --- /dev/null +++ b/workspaces/orchestrator/packages/app/.eslintignore @@ -0,0 +1 @@ +public diff --git a/workspaces/orchestrator/packages/app/.eslintrc.js b/workspaces/orchestrator/packages/app/.eslintrc.js new file mode 100644 index 00000000..8bd689af --- /dev/null +++ b/workspaces/orchestrator/packages/app/.eslintrc.js @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/workspaces/orchestrator/packages/app/e2e-tests/app.test.ts b/workspaces/orchestrator/packages/app/e2e-tests/app.test.ts new file mode 100644 index 00000000..00dae7ef --- /dev/null +++ b/workspaces/orchestrator/packages/app/e2e-tests/app.test.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect, test } from '@playwright/test'; + +test('App should render the welcome page', async ({ page }) => { + await page.goto('/'); + + const enterButton = page.getByRole('button', { name: 'Enter' }); + await expect(enterButton).toBeVisible(); + await enterButton.click(); + + await expect(page.getByText('My Company Catalog')).toBeVisible(); +}); diff --git a/workspaces/orchestrator/packages/app/package.json b/workspaces/orchestrator/packages/app/package.json new file mode 100644 index 00000000..d24a3762 --- /dev/null +++ b/workspaces/orchestrator/packages/app/package.json @@ -0,0 +1,83 @@ +{ + "name": "app", + "version": "0.0.3", + "private": true, + "bundled": true, + "backstage": { + "role": "frontend" + }, + "repository": { + "type": "git", + "url": "https://github.com/redhat-developer/rhdh-plugins", + "directory": "workspaces/orchestrator/packages/app" + }, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "test": "backstage-cli package test", + "lint": "backstage-cli package lint" + }, + "dependencies": { + "@backstage/app-defaults": "^1.5.12", + "@backstage/catalog-model": "^1.7.0", + "@backstage/cli": "^0.28.0", + "@backstage/core-app-api": "^1.15.1", + "@backstage/core-components": "^0.15.1", + "@backstage/core-plugin-api": "^1.10.0", + "@backstage/integration-react": "^1.2.0", + "@backstage/plugin-api-docs": "^0.11.11", + "@backstage/plugin-catalog": "^1.24.0", + "@backstage/plugin-catalog-common": "^1.1.0", + "@backstage/plugin-catalog-graph": "^0.4.11", + "@backstage/plugin-catalog-import": "^0.12.5", + "@backstage/plugin-catalog-react": "^1.14.0", + "@backstage/plugin-org": "^0.6.31", + "@backstage/plugin-permission-react": "^0.4.27", + "@backstage/plugin-scaffolder": "^1.26.1", + "@backstage/plugin-search": "^1.4.18", + "@backstage/plugin-search-react": "^1.8.1", + "@backstage/plugin-techdocs": "^1.11.0", + "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.16", + "@backstage/plugin-techdocs-react": "^1.2.9", + "@backstage/plugin-user-settings": "^0.8.14", + "@backstage/theme": "^0.6.0", + "@mui/icons-material": "5.15.17", + "@mui/lab": "5.0.0-alpha.173", + "@mui/material": "5.15.17", + "@mui/styles": "5.16.7", + "@red-hat-developer-hub/backstage-plugin-orchestrator": "workspace:^", + "@redhat-developer/red-hat-developer-hub-theme": "^0.4.0", + "history": "^5.0.0", + "react": "^18.0.2", + "react-dom": "^18.0.2", + "react-router": "^6.3.0", + "react-router-dom": "^6.3.0", + "react-use": "^17.2.4" + }, + "devDependencies": { + "@backstage/test-utils": "^1.7.0", + "@playwright/test": "^1.32.3", + "@testing-library/dom": "^9.0.0", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.0.0", + "@types/react-dom": "*", + "cross-env": "^7.0.0" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "files": [ + "dist" + ] +} diff --git a/workspaces/orchestrator/packages/app/public/android-chrome-192x192.png b/workspaces/orchestrator/packages/app/public/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..eec0ae25b971cae8eb0033c9af7e0f676d1df663 GIT binary patch literal 13599 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 EANz~b2mk;8 literal 0 HcmV?d00001 diff --git a/workspaces/orchestrator/packages/app/public/apple-touch-icon.png b/workspaces/orchestrator/packages/app/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3158830ac778a62ff8f08da0e9eeee6e8ada8bfc GIT binary patch literal 12619 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 VSn|oqbSlsa22WQ%mvv4FO#qym7y1AI literal 0 HcmV?d00001 diff --git a/workspaces/orchestrator/packages/app/public/favicon-32x32.png b/workspaces/orchestrator/packages/app/public/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..c0915ece75949f3d917134f55193949927edc633 GIT binary patch literal 1686 zcmd5*YdF&j82@WYD9ojqT$Wo{SSXitp^eRqxy{@++t}P`atlKxQX0)YMm6_KR%p4F z$}J+6LMeH2OXWDHjylI#AJ2#L{k+fbeSgn;`91HK_szmP+3%H7kpcicc}5$z1N!Px-7om}akWbt233fdJ10ExN)z&Z~ATcQ$c2>@ad0I)&=0IMPZ zfJBuET&w|L$8)@+J4!Sl|Nk(stgJlvmnlJ&;dZvl8tO{Q%IJ*5_~q$1+xa={mdc0(T`t!w?RZl>^IxA*%bGu3bF$<*nzTrF> zoQ&9oiO|s=ORAp|1maSVle9kTR(cJ300u{&4iq#dewwjMjZ;umbia|8`fV}#)u6uZ z2{{=Vn6Y8_#3SbXb1ic-&s#nMteerJ5NT@9WJFf_0Z{rXvToRrSOV|5nT6}g)=)7clCVKtHJ1*VPs-V;@XTM)*hF8A>dvO zHk*Y?X8JaA;})mFp7;7TRRuk~8$R*Snh{9txf3X8a4%y!TuP;MHGAGHB;T*cTs()( zPBU^uyOrcpI-AkwlQc~ccBy9xRRz@U7IH^D@piFSO__BF#e(AN-^z2yOmV!NrfGJV z)K=@)T;PiA16X29aqmP@px{GT+&pJ*OCU>i#GE8dw3RuCdu^ zY|d<%y|L(_@zJ9#_m z=Lc$*iilj{bjSDeNH|lRP>yLE)Yq?l!XVsP?uHhR(xnf>5p}vuAuF-j7*tA0oTAH zhFP&ZPMN7z8!K6i|K8u7|AYJvRll0p!ov{KSr|$njU-`o&7fVZN5@WQ|g`Mlirnap=oY~b#iCdHeScp|7sct zd}FL!1NSuuCnVle?ygaP975V!)pzN#*|A{{1* z$fS~)G%HFBO+IIv4>f5HNGY_6SF`0>Gf0kWIFv^nU=X CgrE5U literal 0 HcmV?d00001 diff --git a/workspaces/orchestrator/packages/app/public/favicon.ico b/workspaces/orchestrator/packages/app/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5e45e5dfbde6f39603d5be60d933c1af14dffb1e GIT binary patch literal 15086 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 D+pS%} literal 0 HcmV?d00001 diff --git a/workspaces/orchestrator/packages/app/public/index.html b/workspaces/orchestrator/packages/app/public/index.html new file mode 100644 index 00000000..18da7c47 --- /dev/null +++ b/workspaces/orchestrator/packages/app/public/index.html @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + <%= config.getOptionalString('app.title') ?? 'Backstage' %> + + + +
+ + + diff --git a/workspaces/orchestrator/packages/app/public/manifest.json b/workspaces/orchestrator/packages/app/public/manifest.json new file mode 100644 index 00000000..4a7c1b4e --- /dev/null +++ b/workspaces/orchestrator/packages/app/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "Backstage", + "name": "Backstage", + "icons": [ + { + "src": "favicon.ico", + "sizes": "48x48", + "type": "image/png" + } + ], + "start_url": "./index.html", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/workspaces/orchestrator/packages/app/public/robots.txt b/workspaces/orchestrator/packages/app/public/robots.txt new file mode 100644 index 00000000..01b0f9a1 --- /dev/null +++ b/workspaces/orchestrator/packages/app/public/robots.txt @@ -0,0 +1,2 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * diff --git a/workspaces/orchestrator/packages/app/public/safari-pinned-tab.svg b/workspaces/orchestrator/packages/app/public/safari-pinned-tab.svg new file mode 100644 index 00000000..0f500b30 --- /dev/null +++ b/workspaces/orchestrator/packages/app/public/safari-pinned-tab.svg @@ -0,0 +1 @@ +Created by potrace 1.11, written by Peter Selinger 2001-2013 \ No newline at end of file diff --git a/workspaces/orchestrator/packages/app/src/App.test.tsx b/workspaces/orchestrator/packages/app/src/App.test.tsx new file mode 100644 index 00000000..19a8d6cb --- /dev/null +++ b/workspaces/orchestrator/packages/app/src/App.test.tsx @@ -0,0 +1,44 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { render, waitFor } from '@testing-library/react'; +import React from 'react'; +import App from './App'; + +describe('App', () => { + it('should render', async () => { + process.env = { + NODE_ENV: 'test', + APP_CONFIG: [ + { + data: { + app: { title: 'Test' }, + backend: { baseUrl: 'http://localhost:7007' }, + techdocs: { + storageUrl: 'http://localhost:7007/api/techdocs/static/docs', + }, + }, + context: 'test', + }, + ] as any, + }; + + const rendered = render(); + + await waitFor(() => { + expect(rendered.baseElement).toBeInTheDocument(); + }); + }); +}); diff --git a/workspaces/orchestrator/packages/app/src/App.tsx b/workspaces/orchestrator/packages/app/src/App.tsx new file mode 100644 index 00000000..365412bf --- /dev/null +++ b/workspaces/orchestrator/packages/app/src/App.tsx @@ -0,0 +1,142 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createApp } from '@backstage/app-defaults'; +import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; +import { + AlertDisplay, + OAuthRequestDialog, + SignInPage, +} from '@backstage/core-components'; +import { githubAuthApiRef } from '@backstage/core-plugin-api'; +import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +import { + CatalogEntityPage, + CatalogIndexPage, + catalogPlugin, +} from '@backstage/plugin-catalog'; +import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; +import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +import { + CatalogImportPage, + catalogImportPlugin, +} from '@backstage/plugin-catalog-import'; +import { orgPlugin } from '@backstage/plugin-org'; +import { RequirePermission } from '@backstage/plugin-permission-react'; +import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; +import { SearchPage } from '@backstage/plugin-search'; +import { + TechDocsIndexPage, + techdocsPlugin, + TechDocsReaderPage, +} from '@backstage/plugin-techdocs'; +import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; +import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; +import { UserSettingsPage } from '@backstage/plugin-user-settings'; +import { OrchestratorPage } from '@red-hat-developer-hub/backstage-plugin-orchestrator'; +import { getThemes } from '@redhat-developer/red-hat-developer-hub-theme'; +import React from 'react'; +import { Navigate, Route } from 'react-router-dom'; +import { apis } from './apis'; +import { entityPage } from './components/catalog/EntityPage'; +import { Root } from './components/Root'; +import { searchPage } from './components/search/SearchPage'; + +const app = createApp({ + apis, + bindRoutes({ bind }) { + bind(catalogPlugin.externalRoutes, { + createComponent: scaffolderPlugin.routes.root, + viewTechDoc: techdocsPlugin.routes.docRoot, + createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { + registerApi: catalogImportPlugin.routes.importPage, + }); + bind(scaffolderPlugin.externalRoutes, { + registerComponent: catalogImportPlugin.routes.importPage, + viewTechDoc: techdocsPlugin.routes.docRoot, + }); + bind(orgPlugin.externalRoutes, { + catalogIndex: catalogPlugin.routes.catalogIndex, + }); + }, + components: { + SignInPage: props => ( + + ), + }, + themes: getThemes(), +}); + +const routes = ( + + } /> + } /> + } + > + {entityPage} + + } /> + } + > + + + + + } /> + } /> + + + + } + /> + }> + {searchPage} + + } /> + } /> + } /> + +); + +export default app.createRoot( + <> + + + + {routes} + + , +); diff --git a/workspaces/orchestrator/packages/app/src/apis.ts b/workspaces/orchestrator/packages/app/src/apis.ts new file mode 100644 index 00000000..3b1db0b5 --- /dev/null +++ b/workspaces/orchestrator/packages/app/src/apis.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + AnyApiFactory, + configApiRef, + createApiFactory, +} from '@backstage/core-plugin-api'; +import { + ScmAuth, + ScmIntegrationsApi, + scmIntegrationsApiRef, +} from '@backstage/integration-react'; + +export const apis: AnyApiFactory[] = [ + createApiFactory({ + api: scmIntegrationsApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), + }), + ScmAuth.createDefaultApiFactory(), +]; diff --git a/workspaces/orchestrator/packages/app/src/components/Root/LogoFull.tsx b/workspaces/orchestrator/packages/app/src/components/Root/LogoFull.tsx new file mode 100644 index 00000000..1043a909 --- /dev/null +++ b/workspaces/orchestrator/packages/app/src/components/Root/LogoFull.tsx @@ -0,0 +1,45 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { makeStyles } from '@mui/styles'; +import React from 'react'; + +const useStyles = makeStyles({ + svg: { + width: 'auto', + height: 30, + }, + path: { + fill: '#7df3e1', + }, +}); +const LogoFull = () => { + const classes = useStyles(); + + return ( + + + + ); +}; + +export default LogoFull; diff --git a/workspaces/orchestrator/packages/app/src/components/Root/LogoIcon.tsx b/workspaces/orchestrator/packages/app/src/components/Root/LogoIcon.tsx new file mode 100644 index 00000000..25118221 --- /dev/null +++ b/workspaces/orchestrator/packages/app/src/components/Root/LogoIcon.tsx @@ -0,0 +1,46 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { makeStyles } from '@mui/styles'; +import React from 'react'; + +const useStyles = makeStyles({ + svg: { + width: 'auto', + height: 28, + }, + path: { + fill: '#7df3e1', + }, +}); + +const LogoIcon = () => { + const classes = useStyles(); + + return ( + + + + ); +}; + +export default LogoIcon; diff --git a/workspaces/orchestrator/packages/app/src/components/Root/Root.tsx b/workspaces/orchestrator/packages/app/src/components/Root/Root.tsx new file mode 100644 index 00000000..adf17643 --- /dev/null +++ b/workspaces/orchestrator/packages/app/src/components/Root/Root.tsx @@ -0,0 +1,123 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + Link, + Sidebar, + sidebarConfig, + SidebarDivider, + SidebarGroup, + SidebarItem, + SidebarPage, + SidebarScrollWrapper, + SidebarSpace, + useSidebarOpenState, +} from '@backstage/core-components'; +import { MyGroupsSidebarItem } from '@backstage/plugin-org'; +import { SidebarSearchModal } from '@backstage/plugin-search'; +import { + Settings as SidebarSettings, + UserSettingsSignInAvatar, +} from '@backstage/plugin-user-settings'; +import CreateComponentIcon from '@mui/icons-material/AddCircleOutline'; +import ExtensionIcon from '@mui/icons-material/Extension'; +import HomeIcon from '@mui/icons-material/Home'; +import LibraryBooks from '@mui/icons-material/LibraryBooks'; +import MenuIcon from '@mui/icons-material/Menu'; +import GroupIcon from '@mui/icons-material/People'; +import SearchIcon from '@mui/icons-material/Search'; +import { makeStyles } from '@mui/styles'; +import { OrchestratorIcon } from '@red-hat-developer-hub/backstage-plugin-orchestrator'; +import React, { PropsWithChildren } from 'react'; +import LogoFull from './LogoFull'; +import LogoIcon from './LogoIcon'; + +const useSidebarLogoStyles = makeStyles({ + root: { + width: sidebarConfig.drawerWidthClosed, + height: 3 * sidebarConfig.logoHeight, + display: 'flex', + flexFlow: 'row nowrap', + alignItems: 'center', + marginBottom: -14, + }, + link: { + width: sidebarConfig.drawerWidthClosed, + marginLeft: 24, + }, +}); + +const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); + const { isOpen } = useSidebarOpenState(); + + return ( +
+ + {isOpen ? : } + +
+ ); +}; + +export const Root = ({ children }: PropsWithChildren<{}>) => { + return ( + + + + } to="/search"> + + + + }> + {/* Global nav, not org-specific */} + + + + + + + {/* End global nav */} + + + + + + + + } + to="/settings" + > + + + + {children} + + ); +}; diff --git a/workspaces/orchestrator/packages/app/src/components/Root/index.ts b/workspaces/orchestrator/packages/app/src/components/Root/index.ts new file mode 100644 index 00000000..6e933a21 --- /dev/null +++ b/workspaces/orchestrator/packages/app/src/components/Root/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { Root } from './Root'; diff --git a/workspaces/orchestrator/packages/app/src/components/catalog/EntityPage.tsx b/workspaces/orchestrator/packages/app/src/components/catalog/EntityPage.tsx new file mode 100644 index 00000000..e58be81d --- /dev/null +++ b/workspaces/orchestrator/packages/app/src/components/catalog/EntityPage.tsx @@ -0,0 +1,406 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + RELATION_API_CONSUMED_BY, + RELATION_API_PROVIDED_BY, + RELATION_CONSUMES_API, + RELATION_DEPENDENCY_OF, + RELATION_DEPENDS_ON, + RELATION_HAS_PART, + RELATION_PART_OF, + RELATION_PROVIDES_API, +} from '@backstage/catalog-model'; +import { EmptyState } from '@backstage/core-components'; +import { + EntityApiDefinitionCard, + EntityConsumedApisCard, + EntityConsumingComponentsCard, + EntityHasApisCard, + EntityProvidedApisCard, + EntityProvidingComponentsCard, +} from '@backstage/plugin-api-docs'; +import { + EntityAboutCard, + EntityDependsOnComponentsCard, + EntityDependsOnResourcesCard, + EntityHasComponentsCard, + EntityHasResourcesCard, + EntityHasSubcomponentsCard, + EntityHasSystemsCard, + EntityLayout, + EntityLinksCard, + EntityOrphanWarning, + EntityProcessingErrorsPanel, + EntityRelationWarning, + EntitySwitch, + hasCatalogProcessingErrors, + hasRelationWarnings, + isComponentType, + isKind, + isOrphan, +} from '@backstage/plugin-catalog'; +import { + Direction, + EntityCatalogGraphCard, +} from '@backstage/plugin-catalog-graph'; +import { + EntityGroupProfileCard, + EntityMembersListCard, + EntityOwnershipCard, + EntityUserProfileCard, +} from '@backstage/plugin-org'; +import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; +import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; +import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; +import Button from '@mui/material/Button'; +import Grid from '@mui/material/Grid'; +import React from 'react'; + +const techdocsContent = ( + + + + + +); + +const cicdContent = ( + // This is an example of how you can implement your company's logic in entity page. + // You can for example enforce that all components of type 'service' should use GitHubActions + + {/* + Here you can add support for different CI/CD services, for example + using @backstage-community/plugin-github-actions as follows: + + + + */} + + + + Read more + + } + /> + + +); + +const entityWarningContent = ( + <> + + + + + + + + + + + + + + + + + + + + + + + + +); + +const overviewContent = ( + + {entityWarningContent} + + + + + + + + + + + + + + +); + +const serviceEntityPage = ( + + + {overviewContent} + + + + {cicdContent} + + + + + + + + + + + + + + + + + + + + + + + + + + {techdocsContent} + + +); + +const websiteEntityPage = ( + + + {overviewContent} + + + + {cicdContent} + + + + + + + + + + + + + + + {techdocsContent} + + +); + +/** + * NOTE: This page is designed to work on small screens such as mobile devices. + * This is based on Material UI Grid. If breakpoints are used, each grid item must set the `xs` prop to a column size or to `true`, + * since this does not default. If no breakpoints are used, the items will equitably share the available space. + * https://material-ui.com/components/grid/#basic-grid. + */ + +const defaultEntityPage = ( + + + {overviewContent} + + + + {techdocsContent} + + +); + +const componentPage = ( + + + {serviceEntityPage} + + + + {websiteEntityPage} + + + {defaultEntityPage} + +); + +const apiPage = ( + + + + {entityWarningContent} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +const userPage = ( + + + + {entityWarningContent} + + + + + + + + + +); + +const groupPage = ( + + + + {entityWarningContent} + + + + + + + + + + + + + + + +); + +const systemPage = ( + + + + {entityWarningContent} + + + + + + + + + + + + + + + + + + + + + + + + +); + +const domainPage = ( + + + + {entityWarningContent} + + + + + + + + + + + + +); + +export const entityPage = ( + + + + + + + + + {defaultEntityPage} + +); diff --git a/workspaces/orchestrator/packages/app/src/components/search/SearchPage.tsx b/workspaces/orchestrator/packages/app/src/components/search/SearchPage.tsx new file mode 100644 index 00000000..d02d5c4d --- /dev/null +++ b/workspaces/orchestrator/packages/app/src/components/search/SearchPage.tsx @@ -0,0 +1,142 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + CatalogIcon, + Content, + DocsIcon, + Header, + Page, +} from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; +import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; +import { + CATALOG_FILTER_EXISTS, + catalogApiRef, +} from '@backstage/plugin-catalog-react'; +import { SearchType } from '@backstage/plugin-search'; +import { + SearchBar, + SearchFilter, + SearchPagination, + SearchResult, + useSearch, +} from '@backstage/plugin-search-react'; +import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; +import Grid from '@mui/material/Grid'; +import Paper from '@mui/material/Paper'; +import { Theme } from '@mui/material/styles'; +import { makeStyles } from '@mui/styles'; +import React from 'react'; + +const useStyles = makeStyles((theme: Theme) => ({ + filter: { + '& + &': { + marginTop: theme.spacing(2.5), + }, + }, +})); + +const SearchPage = () => { + const classes = useStyles(); + const { types } = useSearch(); + const catalogApi = useApi(catalogApiRef); + + return ( + +
+ + + + + + + + + , + }, + { + value: 'techdocs', + name: 'Documentation', + icon: , + }, + ]} + /> + + {types.includes('techdocs') && ( + { + // Return a list of entities which are documented. + const { items } = await catalogApi.getEntities({ + fields: ['metadata.name'], + filter: { + 'metadata.annotations.backstage.io/techdocs-ref': + CATALOG_FILTER_EXISTS, + }, + }); + + const names = items.map(entity => entity.metadata.name); + names.sort(); + return names; + }} + /> + )} + + + + + + + + } /> + } /> + + + + + + ); +}; + +export const searchPage = ; diff --git a/workspaces/orchestrator/packages/app/src/index.tsx b/workspaces/orchestrator/packages/app/src/index.tsx new file mode 100644 index 00000000..23389912 --- /dev/null +++ b/workspaces/orchestrator/packages/app/src/index.tsx @@ -0,0 +1,21 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import '@backstage/cli/asset-types'; +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +ReactDOM.createRoot(document.getElementById('root')!).render(); diff --git a/workspaces/orchestrator/packages/app/src/setupTests.ts b/workspaces/orchestrator/packages/app/src/setupTests.ts new file mode 100644 index 00000000..658016ff --- /dev/null +++ b/workspaces/orchestrator/packages/app/src/setupTests.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import '@testing-library/jest-dom'; diff --git a/workspaces/orchestrator/packages/backend/.eslintrc.js b/workspaces/orchestrator/packages/backend/.eslintrc.js new file mode 100644 index 00000000..8bd689af --- /dev/null +++ b/workspaces/orchestrator/packages/backend/.eslintrc.js @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/workspaces/orchestrator/packages/backend/Dockerfile b/workspaces/orchestrator/packages/backend/Dockerfile new file mode 100644 index 00000000..05ad5dff --- /dev/null +++ b/workspaces/orchestrator/packages/backend/Dockerfile @@ -0,0 +1,66 @@ +# This dockerfile builds an image for the backend package. +# It should be executed with the root of the repo as docker context. +# +# Before building this image, be sure to have run the following commands in the repo root: +# +# yarn install --immutable +# yarn tsc +# yarn build:backend +# +# Once the commands have been run, you can build the image using `yarn build-image` + +FROM node:20-bookworm-slim + +# Set Python interpreter for `node-gyp` to use +ENV PYTHON=/usr/bin/python3 + +# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ + apt-get install -y --no-install-recommends python3 g++ build-essential && \ + rm -rf /var/lib/apt/lists/* + +# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, +# in which case you should also move better-sqlite3 to "devDependencies" in package.json. +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ + apt-get install -y --no-install-recommends libsqlite3-dev && \ + rm -rf /var/lib/apt/lists/* + +# From here on we use the least-privileged `node` user to run the backend. +USER node + +# This should create the app dir as `node`. +# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. +# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. +WORKDIR /app + +# Copy files needed by Yarn +COPY --chown=node:node .yarn ./.yarn +COPY --chown=node:node .yarnrc.yml ./ + +# This switches many Node.js dependencies to production mode. +ENV NODE_ENV=production + +# This disables node snapshot for Node 20 to work with the Scaffolder +ENV NODE_OPTIONS "--no-node-snapshot" + +# Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +# The skeleton contains the package.json of each package in the monorepo, +# and along with yarn.lock and the root package.json, that's enough to run yarn install. +COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ +RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn workspaces focus --all --production && rm -rf "$(yarn cache clean)" + +# This will include the examples, if you don't need these simply remove this line +COPY --chown=node:node examples ./examples + +# Then copy the rest of the backend bundle, along with any other files we might want. +COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./ +RUN tar xzf bundle.tar.gz && rm bundle.tar.gz + +CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"] \ No newline at end of file diff --git a/workspaces/orchestrator/packages/backend/README.md b/workspaces/orchestrator/packages/backend/README.md new file mode 100644 index 00000000..3607b0a0 --- /dev/null +++ b/workspaces/orchestrator/packages/backend/README.md @@ -0,0 +1,59 @@ +# example-backend + +This package is an EXAMPLE of a Backstage backend. + +The main purpose of this package is to provide a test bed for Backstage plugins +that have a backend part. Feel free to experiment locally or within your fork by +adding dependencies and routes to this backend, to try things out. + +Our goal is to eventually amend the create-app flow of the CLI, such that a +production ready version of a backend skeleton is made alongside the frontend +app. Until then, feel free to experiment here! + +## Development + +To run the example backend, first go to the project root and run + +```bash +yarn install +``` + +You should only need to do this once. + +After that, go to the `packages/backend` directory and run + +```bash +yarn start +``` + +If you want to override any configuration locally, for example adding any secrets, +you can do so in `app-config.local.yaml`. + +The backend starts up on port 7007 per default. + +## Populating The Catalog + +If you want to use the catalog functionality, you need to add so called +locations to the backend. These are places where the backend can find some +entity descriptor data to consume and serve. For more information, see +[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +To get started quickly, this template already includes some statically configured example locations +in `app-config.yaml` under `catalog.locations`. You can remove and replace these locations as you +like, and also override them for local development in `app-config.local.yaml`. + +## Authentication + +We chose [Passport](http://www.passportjs.org/) as authentication platform due +to its comprehensive set of supported authentication +[strategies](http://www.passportjs.org/packages/). + +Read more about the +[auth-backend](https://github.com/backstage/backstage/blob/master/plugins/auth-backend/README.md) +and +[how to add a new provider](https://github.com/backstage/backstage/blob/master/docs/auth/add-auth-provider.md) + +## Documentation + +- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +- [Backstage Documentation](https://backstage.io/docs) diff --git a/workspaces/orchestrator/packages/backend/package.json b/workspaces/orchestrator/packages/backend/package.json new file mode 100644 index 00000000..7b61223a --- /dev/null +++ b/workspaces/orchestrator/packages/backend/package.json @@ -0,0 +1,62 @@ +{ + "name": "backend", + "version": "0.0.0", + "main": "dist/index.cjs.js", + "types": "src/index.ts", + "private": true, + "backstage": { + "role": "backend" + }, + "repository": { + "type": "git", + "url": "https://github.com/redhat-developer/rhdh-plugins", + "directory": "workspaces/orchestrator/packages/backend" + }, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { + "@backstage/backend-defaults": "^0.5.2", + "@backstage/config": "^1.2.0", + "@backstage/plugin-app-backend": "^0.3.76", + "@backstage/plugin-auth-backend": "^0.23.1", + "@backstage/plugin-auth-backend-module-github-provider": "^0.2.1", + "@backstage/plugin-auth-backend-module-guest-provider": "^0.2.1", + "@backstage/plugin-auth-node": "^0.5.3", + "@backstage/plugin-catalog-backend": "^1.27.1", + "@backstage/plugin-catalog-backend-module-logs": "^0.1.3", + "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.2.1", + "@backstage/plugin-permission-backend": "^0.5.50", + "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.2.1", + "@backstage/plugin-permission-common": "^0.8.1", + "@backstage/plugin-permission-node": "^0.8.4", + "@backstage/plugin-proxy-backend": "^0.5.7", + "@backstage/plugin-scaffolder-backend": "^1.26.2", + "@backstage/plugin-search-backend": "^1.6.1", + "@backstage/plugin-search-backend-module-catalog": "^0.2.4", + "@backstage/plugin-search-backend-module-pg": "^0.5.37", + "@backstage/plugin-search-backend-module-techdocs": "^0.3.1", + "@backstage/plugin-search-backend-node": "^1.3.4", + "@backstage/plugin-techdocs-backend": "^1.11.1", + "@red-hat-developer-hub/backstage-plugin-orchestrator-backend": "workspace:^", + "app": "link:../app", + "better-sqlite3": "^9.0.0", + "node-gyp": "^10.0.0", + "pg": "^8.11.3", + "winston": "^3.2.1" + }, + "devDependencies": { + "@backstage/cli": "^0.28.0", + "@types/express": "^4.17.6", + "@types/express-serve-static-core": "^4.17.5", + "@types/luxon": "^2.0.4" + }, + "files": [ + "dist" + ] +} diff --git a/workspaces/orchestrator/packages/backend/src/index.ts b/workspaces/orchestrator/packages/backend/src/index.ts new file mode 100644 index 00000000..7fe9e430 --- /dev/null +++ b/workspaces/orchestrator/packages/backend/src/index.ts @@ -0,0 +1,64 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); + +backend.add(import('@backstage/plugin-app-backend/alpha')); +backend.add(import('@backstage/plugin-proxy-backend/alpha')); +backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); +backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +// auth plugin +backend.add(import('@backstage/plugin-auth-backend')); +// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin +backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); +// See https://backstage.io/docs/auth/guest/provider +backend.add(import('@backstage/plugin-auth-backend-module-github-provider')); + +// catalog plugin +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +backend.add( + import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), +); + +// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors +backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +// permission plugin +backend.add(import('@backstage/plugin-permission-backend/alpha')); + +// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy +backend.add( + import('@backstage/plugin-permission-backend-module-allow-all-policy'), +); + +// search plugin +backend.add(import('@backstage/plugin-search-backend/alpha')); + +// search engine +// See https://backstage.io/docs/features/search/search-engines +backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +// search collators +backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); +backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +backend.add( + import('@red-hat-developer-hub/backstage-plugin-orchestrator-backend'), +); + +backend.start(); diff --git a/workspaces/orchestrator/plugins/README.md b/workspaces/orchestrator/plugins/README.md new file mode 100644 index 00000000..d7865fdb --- /dev/null +++ b/workspaces/orchestrator/plugins/README.md @@ -0,0 +1,9 @@ +# The Plugins Folder + +This is where your own plugins and their associated modules live, each in a +separate folder of its own. + +If you want to create a new plugin here, go to your project root directory, run +the command `yarn new`, and follow the on-screen instructions. + +You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/.eslintignore b/workspaces/orchestrator/plugins/orchestrator-backend/.eslintignore new file mode 100644 index 00000000..b19336e4 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/.eslintignore @@ -0,0 +1,3 @@ +playwright.config.ts +dist/ +dist-types/ \ No newline at end of file diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/.eslintrc.js b/workspaces/orchestrator/plugins/orchestrator-backend/.eslintrc.js new file mode 100644 index 00000000..bdff83a0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/.eslintrc.js @@ -0,0 +1,11 @@ +const backstageEslintConfig = require('@backstage/cli/config/eslint-factory')( + __dirname, +); + +module.exports = { + ...backstageEslintConfig, + ignorePatterns: [ + ...backstageEslintConfig.ignorePatterns, + 'static/generated/**', + ], +}; diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/.gitignore b/workspaces/orchestrator/plugins/orchestrator-backend/.gitignore new file mode 100644 index 00000000..7b4d4ba2 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/.gitignore @@ -0,0 +1 @@ +static diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/.lintstagedrc.json b/workspaces/orchestrator/plugins/orchestrator-backend/.lintstagedrc.json new file mode 100644 index 00000000..d73476f0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/.lintstagedrc.json @@ -0,0 +1,4 @@ +{ + "*": "prettier --ignore-unknown --no-warn-ignored --write", + "*.{js,jsx,ts,tsx,mjs,cjs}": "backstage-cli package lint --fix" +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/.prettierignore b/workspaces/orchestrator/plugins/orchestrator-backend/.prettierignore new file mode 100644 index 00000000..45c54605 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/.prettierignore @@ -0,0 +1,13 @@ +dist +dist-types +coverage +.vscode +CHANGELOG.md +generated +templates +*.hbs +renovate.json +dist-dynamic +dist-scalprum +playwright-report +static diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/.prettierrc.js b/workspaces/orchestrator/plugins/orchestrator-backend/.prettierrc.js new file mode 100644 index 00000000..5f81a8a0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/.prettierrc.js @@ -0,0 +1,20 @@ +// @ts-check + +/** @type {import("@ianvs/prettier-plugin-sort-imports").PrettierConfig} */ +module.exports = { + ...require('@spotify/prettier-config'), + plugins: ['@ianvs/prettier-plugin-sort-imports'], + importOrder: [ + '^react(.*)$', + '', + '^@backstage/(.*)$', + '', + '', + '', + '^@red-hat-developer-hub/(.*)$', + '', + '', + '', + '^[.]', + ], +}; diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/CHANGELOG.md b/workspaces/orchestrator/plugins/orchestrator-backend/CHANGELOG.md new file mode 100644 index 00000000..e623d555 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/CHANGELOG.md @@ -0,0 +1,732 @@ +### Dependencies + +## 4.1.0 + +### Minor Changes + +- 25f1787: Add enum filters to orchestrator plugin +- 603a162: make error handling consistent in backend and UI + +### Patch Changes + +- Updated dependencies [25f1787] + - @red-hat-developer-hub/backstage-plugin-orchestrator-common@1.24.0 + +## 4.0.1 + +### Patch Changes + +- 0e6bfd3: feat: update Backstage to the latest version + + Update to Backstage 1.32.5 + +- Updated dependencies [0e6bfd3] + - @red-hat-developer-hub/backstage-plugin-orchestrator-common@1.23.1 + - @janus-idp/backstage-plugin-audit-log-node@1.7.1 + - @janus-idp/backstage-plugin-rbac-common@1.12.1 + +## 4.0.0 + +### Minor Changes + +- 8244f28: chore(deps): update to backstage 1.32 + +### Patch Changes + +- Updated dependencies [8244f28] + - @red-hat-developer-hub/backstage-plugin-orchestrator-common@1.23.0 + - @janus-idp/backstage-plugin-audit-log-node@1.7.0 + - @janus-idp/backstage-plugin-rbac-common@1.12.0 + +## 3.0.1 + +### Patch Changes + +- 7342e9b: chore: remove @janus-idp/cli dep and relink local packages + + This update removes `@janus-idp/cli` from all plugins, as it’s no longer necessary. Additionally, packages are now correctly linked with a specified version. + +## 3.0.0 + +### Minor Changes + +- d9551ae: feat(deps): update to backstage 1.31 + +### Patch Changes + +- d9551ae: Change local package references to a `*` +- d9551ae: pin the @janus-idp/cli package +- d9551ae: upgrade to yarn v3 +- d9551ae: Change the export-dynamic script to no longer use any flags and remove the tracking of the dist-dynamic folder +- Updated dependencies [d9551ae] +- Updated dependencies [d9551ae] +- Updated dependencies [d9551ae] + - @red-hat-developer-hub/backstage-plugin-orchestrator-common@1.22.0 + - @janus-idp/backstage-plugin-rbac-common@1.11.0 + - @janus-idp/backstage-plugin-audit-log-node@1.6.0 + +* **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.21.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.20.0 +- **@janus-idp/cli:** upgraded to 1.15.2 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.19.0 + +### Dependencies + +- **@janus-idp/backstage-plugin-audit-log-node:** upgraded to 1.5.1 + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.15.1 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.18.2 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.18.1 + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.15.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.18.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.17.3 + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.14.0 +- **@janus-idp/backstage-plugin-audit-log-node:** upgraded to 1.5.0 +- **@janus-idp/backstage-plugin-rbac-common:** upgraded to 1.10.0 + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.13.2 + +### Dependencies + +- **@janus-idp/backstage-plugin-audit-log-node:** upgraded to 1.4.1 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.17.2 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.17.1 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.17.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.16.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.15.2 + +### Dependencies + +- **@janus-idp/backstage-plugin-rbac-common:** upgraded to 1.9.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.15.1 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.15.0 + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.13.1 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.14.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.17.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.17.2...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.17.3) (2024-08-06) + +### Dependencies + +- **@janus-idp/backstage-plugin-rbac-common:** upgraded to 1.8.2 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.17.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.17.1...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.17.2) (2024-08-05) + +### Dependencies + +- **@janus-idp/backstage-plugin-rbac-common:** upgraded to 1.8.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.17.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.17.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.17.1) (2024-08-02) + +### Bug Fixes + +- **orchestrator:** remove default pagination on v2 endpoints ([#1983](https://github.com/janus-idp/backstage-plugins/issues/1983)) ([5e30274](https://github.com/janus-idp/backstage-plugins/commit/5e302748a25cbad127122407e5258576054eac3d)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.13.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.17.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.16.1...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.17.0) (2024-07-26) + +### Features + +- **deps:** update to backstage 1.29 ([#1900](https://github.com/janus-idp/backstage-plugins/issues/1900)) ([f53677f](https://github.com/janus-idp/backstage-plugins/commit/f53677fb02d6df43a9de98c43a9f101a6db76802)) +- **orchestrator:** use v2 endpoints to retrieve instances ([#1956](https://github.com/janus-idp/backstage-plugins/issues/1956)) ([537502b](https://github.com/janus-idp/backstage-plugins/commit/537502b9d2ac13f2fb3f79188422d2c6e97f41fb)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.13.0 +- **@janus-idp/backstage-plugin-rbac-common:** upgraded to 1.8.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.16.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.16.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.16.1) (2024-07-24) + +### Bug Fixes + +- **deps:** rollback unreleased plugins ([#1951](https://github.com/janus-idp/backstage-plugins/issues/1951)) ([8b77969](https://github.com/janus-idp/backstage-plugins/commit/8b779694f02f8125587296305276b84cdfeeaebe)) + +### Dependencies + +- **@janus-idp/backstage-plugin-rbac-common:** upgraded to 1.7.2 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.16.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.15.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.16.0) (2024-07-24) + +### Features + +- **deps:** update to backstage 1.28 ([#1891](https://github.com/janus-idp/backstage-plugins/issues/1891)) ([1ba1108](https://github.com/janus-idp/backstage-plugins/commit/1ba11088e0de60e90d138944267b83600dc446e5)) +- **orchestrator:** use v2 endpoints to retrieve workflow overviews ([#1892](https://github.com/janus-idp/backstage-plugins/issues/1892)) ([cca1e53](https://github.com/janus-idp/backstage-plugins/commit/cca1e53bc6b3019b1c544f2f62bed8723ebf6130)) + +### Bug Fixes + +- **orchestrator:** resolve broken dynamic plugin publish ([#1906](https://github.com/janus-idp/backstage-plugins/issues/1906)) ([5f99043](https://github.com/janus-idp/backstage-plugins/commit/5f990438ebebf8b23c0c8706852753ad0812c55a)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.12.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.15.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.14.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.15.0) (2024-07-12) + +### Features + +- **orchestrator:** fix version ([#1886](https://github.com/janus-idp/backstage-plugins/issues/1886)) ([65c5917](https://github.com/janus-idp/backstage-plugins/commit/65c5917b8fc066a869d1a8e76d5e7b6cb4c8327c)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.11.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.14.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.13.1...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.14.0) (2024-07-11) + +### Features + +- **orchestrator:** change openapi client generator ([#1864](https://github.com/janus-idp/backstage-plugins/issues/1864)) ([d6a4f4c](https://github.com/janus-idp/backstage-plugins/commit/d6a4f4ccfedfd55356305131029fd3d8ca0ab9c5)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.11.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.13.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.13.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.13.1) (2024-07-01) + +### Bug Fixes + +- **rbac:** update rbac common to fix compilation ([#1858](https://github.com/janus-idp/backstage-plugins/issues/1858)) ([48f142b](https://github.com/janus-idp/backstage-plugins/commit/48f142b447f0d1677ba3f16b2a3c8972b22d0588)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.13.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.12.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.13.0) (2024-06-28) + +### Features + +- **orchestrator:** fix build failure from [#1833](https://github.com/janus-idp/backstage-plugins/issues/1833) ([#1850](https://github.com/janus-idp/backstage-plugins/issues/1850)) ([c0c73e6](https://github.com/janus-idp/backstage-plugins/commit/c0c73e638f66c03dae565614b8186938b38d7032)) +- **orchestrator:** remove unneeded orchestrator jira integration and endpoint ([#1833](https://github.com/janus-idp/backstage-plugins/issues/1833)) ([d2a76fd](https://github.com/janus-idp/backstage-plugins/commit/d2a76fd3db028f9774c821759bee5f38b7131c94)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.10.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.12.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.11.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.12.0) (2024-06-26) + +### Features + +- **orchestrator:** disable buttons based on permissions ([#1818](https://github.com/janus-idp/backstage-plugins/issues/1818)) ([36504b0](https://github.com/janus-idp/backstage-plugins/commit/36504b05d96dbbf0b2395dc6e5c155c21fa73bcd)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.11.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.10.1...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.11.0) (2024-06-25) + +### Features + +- **orchestrator:** add auditLog and reorganize endpoints declaration ([#1820](https://github.com/janus-idp/backstage-plugins/issues/1820)) ([00d9216](https://github.com/janus-idp/backstage-plugins/commit/00d9216ba76c13fac86933a8605102d6e1768929)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.10.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.10.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.10.1) (2024-06-19) + +### Bug Fixes + +- **orchestrator:** change log level of cache messages to be debug ([#1824](https://github.com/janus-idp/backstage-plugins/issues/1824)) ([4224612](https://github.com/janus-idp/backstage-plugins/commit/422461224e31b419cd8394e2432af71ed10a986e)) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.11.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.10.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.8...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.10.0) (2024-06-13) + +### Features + +- **deps:** update to backstage 1.27 ([#1683](https://github.com/janus-idp/backstage-plugins/issues/1683)) ([a14869c](https://github.com/janus-idp/backstage-plugins/commit/a14869c3f4177049cb8d6552b36c3ffd17e7997d)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.9.0 +- **@janus-idp/cli:** upgraded to 1.11.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.9.8](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.7...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.8) (2024-06-13) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.10.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.9.7](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.6...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.7) (2024-06-11) + +### Bug Fixes + +- **orchestrator:** fix error handling in case data index failed to start ([#1804](https://github.com/janus-idp/backstage-plugins/issues/1804)) ([27affb7](https://github.com/janus-idp/backstage-plugins/commit/27affb7815e02127721fd854f7903dca3525dede)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.9.6](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.5...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.6) (2024-06-05) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.10.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.9.5](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.4...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.5) (2024-06-04) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.8.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.9.4](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.3...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.4) (2024-06-03) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.9.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.9.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.2...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.3) (2024-05-29) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.10 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.9.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.1...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.2) (2024-05-29) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.9 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.9.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.1) (2024-05-28) + +### Bug Fixes + +- **orchestrator:** fixed broken workflow viewer ([#1717](https://github.com/janus-idp/backstage-plugins/issues/1717)) ([19cc79b](https://github.com/janus-idp/backstage-plugins/commit/19cc79bb9c1422556ddb9f85a2ac323186808321)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.9.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.8.7...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.9.0) (2024-05-22) + +### Features + +- **orchestrator:** add permissions to orchestrator plugin ([#1599](https://github.com/janus-idp/backstage-plugins/issues/1599)) ([d0a4531](https://github.com/janus-idp/backstage-plugins/commit/d0a453181e177eb1da7b1e231253b76a2d9356a8)) + +### Bug Fixes + +- **orchestrator:** fix the common package reference version ([#1704](https://github.com/janus-idp/backstage-plugins/issues/1704)) ([942b2a3](https://github.com/janus-idp/backstage-plugins/commit/942b2a3b6eb29c0fe88f9c98dea581309d02fded)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.8.7](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.8.6...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.8.7) (2024-05-21) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.8.6](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.8.5...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.8.6) (2024-05-20) + +### Bug Fixes + +- **orchestrator:** fixes many security-related issues ([#1681](https://github.com/janus-idp/backstage-plugins/issues/1681)) ([3e801c8](https://github.com/janus-idp/backstage-plugins/commit/3e801c84015f925bdecd226a161ef81a5fc69432)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.8.5](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.8.4...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.8.5) (2024-05-16) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.7 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.8.4](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.8.3...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.8.4) (2024-05-15) + +### Documentation + +- **orchestrator:** removes instructions related to the editor ([#1664](https://github.com/janus-idp/backstage-plugins/issues/1664)) ([10a75b2](https://github.com/janus-idp/backstage-plugins/commit/10a75b2706c72751bd774d6fae4332bbc527dc2b)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.7.2 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.8.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.8.2...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.8.3) (2024-05-15) + +### Bug Fixes + +- **orchestrator:** export the `OrchestratorPlugin` accordingly ([#1644](https://github.com/janus-idp/backstage-plugins/issues/1644)) ([4a9d1f8](https://github.com/janus-idp/backstage-plugins/commit/4a9d1f821a30437e73631fac98b1aabc65473fba)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.8.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.8.1...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.8.2) (2024-05-09) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.7.1 +- **@janus-idp/cli:** upgraded to 1.8.6 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.8.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.8.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.8.1) (2024-05-09) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.7.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.8.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.7.4...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.8.0) (2024-05-06) + +### Features + +- **orchestrator:** make the internal sonata podman compatible ([#1612](https://github.com/janus-idp/backstage-plugins/issues/1612)) ([e4e528e](https://github.com/janus-idp/backstage-plugins/commit/e4e528e2c10536d029ffec11953f3a1d0309b0c5)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.7.4](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.7.3...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.7.4) (2024-05-02) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.5 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.7.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.7.2...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.7.3) (2024-05-02) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.4 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.7.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.7.1...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.7.2) (2024-04-30) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.3 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.7.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.7.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.7.1) (2024-04-30) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.2 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.7.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.8...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.7.0) (2024-04-25) + +### Features + +- **orchestrator:** add endpoint to retrigger workflow in error state ([#1343](https://github.com/janus-idp/backstage-plugins/issues/1343)) ([328d23a](https://github.com/janus-idp/backstage-plugins/commit/328d23a7992da125becc8d7775a4ebd68165f243)) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.6.8](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.7...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.8) (2024-04-18) + +### Bug Fixes + +- **orchestrator:** allows serving the editor envelope in disconnected environments ([#1450](https://github.com/janus-idp/backstage-plugins/issues/1450)) ([1e778d8](https://github.com/janus-idp/backstage-plugins/commit/1e778d88336dfec79d48ece4fd8d2a035133b70e)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.6.4 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.6.7](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.6...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.7) (2024-04-15) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.6.6](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.5...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.6) (2024-04-09) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.7.10 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.6.5](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.4...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.5) (2024-04-09) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.7.9 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.6.4](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.3...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.4) (2024-04-05) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.6.3 +- **@janus-idp/cli:** upgraded to 1.7.8 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.6.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.2...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.3) (2024-04-04) + +### Bug Fixes + +- **orchestrator:** add lastRunId to overview endpoints ([#1449](https://github.com/janus-idp/backstage-plugins/issues/1449)) ([cce56f7](https://github.com/janus-idp/backstage-plugins/commit/cce56f7de3acc41ecd30b1b9962d7817be69de7d)) +- **orchestrator:** only inputs inherited from the assessment workflow should be disabled ([#1436](https://github.com/janus-idp/backstage-plugins/issues/1436)) ([32d9bdf](https://github.com/janus-idp/backstage-plugins/commit/32d9bdfc38c07c4e60f0ce7670fc3813ad0d92c3)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.6.2 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.6.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.1...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.2) (2024-04-02) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.7.7 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.6.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.1) (2024-03-29) + +### Bug Fixes + +- **orchestrator:** fixes v2/instances endpoint ([#1414](https://github.com/janus-idp/backstage-plugins/issues/1414)) ([88b49df](https://github.com/janus-idp/backstage-plugins/commit/88b49df35cf10e231ba69c239e873cb10e7cc25b)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.6.1 +- **@janus-idp/cli:** upgraded to 1.7.6 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.6.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.5.3...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.6.0) (2024-03-14) + +### Features + +- **orchestrator:** verify availability and cache workflow definition IDs ([#1309](https://github.com/janus-idp/backstage-plugins/issues/1309)) ([4d322f1](https://github.com/janus-idp/backstage-plugins/commit/4d322f1fc5b6f8b1afedf40cfe1b24b2edae2ac1)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.6.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.5.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.5.2...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.5.3) (2024-03-12) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.5.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.5.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.5.1...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.5.2) (2024-03-11) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.5.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.5.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.5.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.5.1) (2024-03-11) + +### Other changes + +- **orchestrator:** add unit tests for v2 endpoints ([#1300](https://github.com/janus-idp/backstage-plugins/issues/1300)) ([9a13138](https://github.com/janus-idp/backstage-plugins/commit/9a13138c61d3cc7331f739da80f020bb68dd61e5)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.4.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.5.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.12...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.5.0) (2024-03-07) + +### Features + +- **orchestrator:** support pagination for /instances and /overview ([#1313](https://github.com/janus-idp/backstage-plugins/issues/1313)) ([79d5988](https://github.com/janus-idp/backstage-plugins/commit/79d598816f16c8346b6868bff4cc30d695cad518)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.4.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.4.12](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.11...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.12) (2024-03-04) + +### Bug Fixes + +- **orchestrator:** increase the number of attempts to fetch the instance after execution ([#1301](https://github.com/janus-idp/backstage-plugins/issues/1301)) ([77dcce3](https://github.com/janus-idp/backstage-plugins/commit/77dcce3adceaf12b583bda5e74be69a5cc273ba1)) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.7.5 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.4.11](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.10...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.11) (2024-03-03) + +### Bug Fixes + +- **orchestrator:** stop fetching workflow URI ([#1297](https://github.com/janus-idp/backstage-plugins/issues/1297)) ([2456a28](https://github.com/janus-idp/backstage-plugins/commit/2456a287dbff955a0916b9600e89a39511cd537a)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.3.7 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.4.10](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.9...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.10) (2024-02-29) + +### Bug Fixes + +- **orchestrator:** refactor 500 response to use ErrorResponse object ([#1290](https://github.com/janus-idp/backstage-plugins/issues/1290)) ([2580f3d](https://github.com/janus-idp/backstage-plugins/commit/2580f3d38cecf78334964666eb7c127c21b00924)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.3.6 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.4.9](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.8...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.9) (2024-02-28) + +### Bug Fixes + +- **orchestrator:** clean up the plugin code ([#1292](https://github.com/janus-idp/backstage-plugins/issues/1292)) ([ad27fb8](https://github.com/janus-idp/backstage-plugins/commit/ad27fb8e98913a6b80feb38ff58a7864e1953a7e)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.3.5 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.4.8](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.7...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.8) (2024-02-28) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.3.4 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.4.7](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.6...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.7) (2024-02-28) + +### Bug Fixes + +- **orchestrator:** handle nullable start/state properties of process instance ([#1277](https://github.com/janus-idp/backstage-plugins/issues/1277)) ([d8a43a5](https://github.com/janus-idp/backstage-plugins/commit/d8a43a5a164f83fc90d037ae3d7a355f5de543e0)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.3.3 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.4.6](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.5...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.6) (2024-02-27) + +### Bug Fixes + +- **orchestrator:** workflowId parameter wrongly parsed in getWorkflowOverviewById (v2) ([#1283](https://github.com/janus-idp/backstage-plugins/issues/1283)) ([2cd70d0](https://github.com/janus-idp/backstage-plugins/commit/2cd70d048d707a3b117c5273a1d8bc9fdc03fff7)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.3.2 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.4.5](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.4...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.5) (2024-02-27) + +### Bug Fixes + +- **orchestrator:** warn "unknown format X ignored in schema at path Y" ([#1270](https://github.com/janus-idp/backstage-plugins/issues/1270)) ([de3c734](https://github.com/janus-idp/backstage-plugins/commit/de3c734299189b753d924c87aa9b5c9b5f94683c)), closes [/github.com/janus-idp/backstage-plugins/blob/903c7f37a1cf138ac96ef3f631f951866c2014fa/plugins/notifications-backend/src/service/router.ts#L45-L52](https://github.com/janus-idp//github.com/janus-idp/backstage-plugins/blob/903c7f37a1cf138ac96ef3f631f951866c2014fa/plugins/notifications-backend/src/service/router.ts/issues/L45-L52) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.7.4 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.4.4](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.3...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.4) (2024-02-26) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.7.3 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.4.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.2...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.3) (2024-02-23) + +### Bug Fixes + +- **orchestrator:** handle api endpoint failure ([#1254](https://github.com/janus-idp/backstage-plugins/issues/1254)) ([503de1b](https://github.com/janus-idp/backstage-plugins/commit/503de1b028e134cafb5a04045068768f30519409)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.4.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.1...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.2) (2024-02-22) + +### Bug Fixes + +- **orchestrator:** improvements to backend services ([#1252](https://github.com/janus-idp/backstage-plugins/issues/1252)) ([af8e072](https://github.com/janus-idp/backstage-plugins/commit/af8e072f35bc033f5111207c87711c9c0f9ff386)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.4.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.1) (2024-02-21) + +### Bug Fixes + +- **orchestrator:** implementation of getWorkflowById (v2) ([#1233](https://github.com/janus-idp/backstage-plugins/issues/1233)) ([f9f9008](https://github.com/janus-idp/backstage-plugins/commit/f9f9008d29f244c2ae6d688d3e2dc9b65b705e5b)) +- **orchestrator:** minor improvements and fixes ([#1242](https://github.com/janus-idp/backstage-plugins/issues/1242)) ([c9ec4cb](https://github.com/janus-idp/backstage-plugins/commit/c9ec4cbe1847268e8068edc69c7937c5e133c315)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.3.1 +- **@janus-idp/cli:** upgraded to 1.7.2 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.4.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.3.1...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.4.0) (2024-02-20) + +### Features + +- **orchestrator:** add OpenAPI v2 implementations ([#1182](https://github.com/janus-idp/backstage-plugins/issues/1182)) ([43ac2f3](https://github.com/janus-idp/backstage-plugins/commit/43ac2f3f492b5c977142a3cfd9868d5e193ceb02)) + +### Bug Fixes + +- **orchestrator:** decommission the ProcessInstance.lastUpdate field ([#1230](https://github.com/janus-idp/backstage-plugins/issues/1230)) ([9724e27](https://github.com/janus-idp/backstage-plugins/commit/9724e27eaa84fe73d7724f28c86409681b7f79f8)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.3.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.3.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.3.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.3.1) (2024-02-16) + +### Bug Fixes + +- **orchestrator:** resolve mismatch between execution data and composed schema ([#1217](https://github.com/janus-idp/backstage-plugins/issues/1217)) ([af85114](https://github.com/janus-idp/backstage-plugins/commit/af851148935e1ed083709cac145520d7551de737)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.2.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.3.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.2.2...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.3.0) (2024-02-16) + +### Features + +- **orchestrator:** add OpenAPI support ([#1123](https://github.com/janus-idp/backstage-plugins/issues/1123)) ([bd88e23](https://github.com/janus-idp/backstage-plugins/commit/bd88e2304c93761ce6754985074f004a5a3c8c4b)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.2.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.2.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.2.1...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.2.2) (2024-02-13) + +### Bug Fixes + +- **orchestrator:** filter out `null` values from action input ([#1199](https://github.com/janus-idp/backstage-plugins/issues/1199)) ([55c3927](https://github.com/janus-idp/backstage-plugins/commit/55c3927fb5211e1ec78719fd38740eb29e481962)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.2.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.2.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.2.1) (2024-02-05) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.7.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.2.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.1.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.2.0) (2024-02-02) + +### Features + +- **orchestrator:** add the ability to rerun workflows in a new instance ([#1141](https://github.com/janus-idp/backstage-plugins/issues/1141)) ([fe326df](https://github.com/janus-idp/backstage-plugins/commit/fe326df569caa5a9e7b7ec809c1c371a2a936010)) + +### Bug Fixes + +- add missing alpha dynamic plugin entry points ([#1161](https://github.com/janus-idp/backstage-plugins/issues/1161)) ([36e9d91](https://github.com/janus-idp/backstage-plugins/commit/36e9d910b8f534fd9db2f8210c9aa7a24560f01d)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.1.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.1.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.0.2...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.1.0) (2024-01-30) + +### Features + +- add new backend system support for existing backend plugins that have not been migrated over yet ([#1132](https://github.com/janus-idp/backstage-plugins/issues/1132)) ([06e16fd](https://github.com/janus-idp/backstage-plugins/commit/06e16fdcf64257dd08297cb727445d9a8a23c522)) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.7.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.0.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.0.1...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.0.2) (2024-01-25) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.6.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend [1.0.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.0.0...@red-hat-developer-hub/backstage-plugin-orchestrator-backend@1.0.1) (2024-01-18) + +### Bug Fixes + +- **orchestrator:** regenerate `orchestrator-backend/dist-dynamic/package.json` ([#1083](https://github.com/janus-idp/backstage-plugins/issues/1083)) ([8a8051c](https://github.com/janus-idp/backstage-plugins/commit/8a8051c5eded7bdd3e05d1532e8354709aaccb8b)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-backend 1.0.0 (2024-01-17) + +### Features + +- **orchestrator:** add orchestrator plugin ([#783](https://github.com/janus-idp/backstage-plugins/issues/783)) ([cf5fe74](https://github.com/janus-idp/backstage-plugins/commit/cf5fe74db6992d9f51f5073bbcf20c8c346357a1)), closes [#28](https://github.com/janus-idp/backstage-plugins/issues/28) [#38](https://github.com/janus-idp/backstage-plugins/issues/38) [#35](https://github.com/janus-idp/backstage-plugins/issues/35) [#21](https://github.com/janus-idp/backstage-plugins/issues/21) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.0.0 diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/README.md b/workspaces/orchestrator/plugins/orchestrator-backend/README.md new file mode 100644 index 00000000..ccdd71d6 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/README.md @@ -0,0 +1,5 @@ +# Orchestrator Backend Plugin for Backstage + +Welcome to the backend package for the Orchestrator plugin! + +For more information about the Orchestrator plugin, see the [Orchestrator Plugin documentation](https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/orchestrator/plugins/orchestrator) on GitHub. diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/__fixtures__/mockComposedGreetingWorfklow.ts b/workspaces/orchestrator/plugins/orchestrator-backend/__fixtures__/mockComposedGreetingWorfklow.ts new file mode 100644 index 00000000..61fc03ba --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/__fixtures__/mockComposedGreetingWorfklow.ts @@ -0,0 +1,144 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { JsonObject } from '@backstage/types'; + +import { JSONSchema7 } from 'json-schema'; + +import { WorkflowDefinition } from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +const schema = { + $id: 'classpath:/schemas/yamlgreet__main-schema.json', + title: 'Data Input Schema', + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + properties: { + language: { + type: 'object', + properties: { + language: { + title: 'Language', + description: 'Language to greet', + type: 'string', + enum: ['English', 'Spanish'], + default: 'English', + }, + }, + title: 'Language', + }, + name: { + type: 'object', + properties: { + name: { + title: 'Name', + description: 'Name of the person', + type: 'string', + default: 'John Doe', + }, + }, + }, + }, + required: ['name'], +} as JSONSchema7; + +const workflowDefinition = { + id: 'yamlgreet', + version: '1.0', + specVersion: '0.8', + name: 'Greeting workflow', + description: 'YAML based greeting workflow', + dataInputSchema: 'schemas/yamlgreet__main-schema.json', + start: 'ChooseOnLanguage', + functions: [ + { + name: 'greetFunction', + type: 'custom', + operation: 'sysout', + }, + ], + states: [ + { + name: 'ChooseOnLanguage', + type: 'switch', + dataConditions: [ + { + condition: '${ .language.language == "English" }', + transition: 'GreetInEnglish', + }, + { + condition: '${ .language.language == "Spanish" }', + transition: 'GreetInSpanish', + }, + ], + defaultCondition: { + transition: 'GreetInEnglish', + }, + }, + { + name: 'GreetInEnglish', + type: 'inject', + data: { + greeting: 'Hello from YAML Workflow, ', + }, + transition: 'GreetPerson', + }, + { + name: 'GreetInSpanish', + type: 'inject', + data: { + greeting: 'Saludos desde YAML Workflow, ', + }, + transition: 'GreetPerson', + }, + { + name: 'GreetPerson', + type: 'operation', + actions: [ + { + name: 'greetAction', + functionRef: { + refName: 'greetFunction', + arguments: { + message: '.greeting+.name.name', + }, + }, + }, + ], + end: { + terminate: true, + }, + }, + ], +} as WorkflowDefinition; + +const variables = { + workflowdata: { + name: { + name: 'John Doe', + }, + language: { + language: 'Spanish', + }, + greeting: 'hello', + }, +}; + +const mockData: { + schema: JSONSchema7; + workflowDefinition: WorkflowDefinition; + variables: JsonObject; +} = { schema, workflowDefinition, variables }; + +export default mockData; diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/__fixtures__/mockGreetingWorkflowData.ts b/workspaces/orchestrator/plugins/orchestrator-backend/__fixtures__/mockGreetingWorkflowData.ts new file mode 100644 index 00000000..c6750d37 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/__fixtures__/mockGreetingWorkflowData.ts @@ -0,0 +1,129 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { JsonObject } from '@backstage/types'; + +import { JSONSchema7 } from 'json-schema'; + +import { WorkflowDefinition } from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +const schema = { + $id: 'classpath:/schemas/yamlgreet__main-schema.json', + title: 'Data Input Schema', + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + properties: { + language: { + title: 'Language', + description: 'Language to greet', + type: 'string', + enum: ['English', 'Spanish'], + default: 'English', + }, + name: { + title: 'Name', + description: 'Name of the person', + type: 'string', + default: 'John Doe', + }, + }, + required: ['name'], +} as JSONSchema7; + +const workflowDefinition = { + id: 'yamlgreet', + version: '1.0', + specVersion: '0.8', + name: 'Greeting workflow', + description: 'YAML based greeting workflow', + dataInputSchema: 'schemas/yamlgreet__main-schema.json', + start: 'ChooseOnLanguage', + functions: [ + { + name: 'greetFunction', + type: 'custom', + operation: 'sysout', + }, + ], + states: [ + { + name: 'ChooseOnLanguage', + type: 'switch', + dataConditions: [ + { + condition: '${ .language == "English" }', + transition: 'GreetInEnglish', + }, + { + condition: '${ .language == "Spanish" }', + transition: 'GreetInSpanish', + }, + ], + defaultCondition: { + transition: 'GreetInEnglish', + }, + }, + { + name: 'GreetInEnglish', + type: 'inject', + data: { + greeting: 'Hello from YAML Workflow, ', + }, + transition: 'GreetPerson', + }, + { + name: 'GreetInSpanish', + type: 'inject', + data: { + greeting: 'Saludos desde YAML Workflow, ', + }, + transition: 'GreetPerson', + }, + { + name: 'GreetPerson', + type: 'operation', + actions: [ + { + name: 'greetAction', + functionRef: { + refName: 'greetFunction', + arguments: { + message: '.greeting+.name', + }, + }, + }, + ], + end: { + terminate: true, + }, + }, + ], +} as WorkflowDefinition; + +const variables = { + workflowdata: { + name: 'John Doe', + greeting: 'Saludos desde YAML Workflow, ', + language: 'Spanish', + }, +}; + +const mockData: { + schema: JSONSchema7; + workflowDefinition: WorkflowDefinition; + variables: JsonObject; +} = { schema, workflowDefinition, variables }; + +export default mockData; diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/__fixtures__/mockSpringBootWorkflowData.ts b/workspaces/orchestrator/plugins/orchestrator-backend/__fixtures__/mockSpringBootWorkflowData.ts new file mode 100644 index 00000000..d9fb13f1 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/__fixtures__/mockSpringBootWorkflowData.ts @@ -0,0 +1,595 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { JSONSchema7 } from 'json-schema'; + +import { WorkflowDefinition } from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +const schema = { + $id: 'classpath:/schemas/spring-boot-backend__main-schema.json', + title: 'Data input schema', + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + properties: { + newComponent: { + $ref: '#/$defs/Provide information about the new component_0', + type: 'object', + }, + javaMetadata: { + $ref: '#/$defs/Provide information about the Java metadata_1', + type: 'object', + }, + ciMethod: { + $ref: '#/$defs/Provide information about the CI method_2', + type: 'object', + }, + }, + $defs: { + 'Provide information about the CI method_2': { + $id: 'classpath:/schemas/spring-boot-backend__ref-schema__CI_Method.json', + title: 'Provide information about the CI method', + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + properties: { + ci: { + title: 'CI Method', + type: 'string', + default: 'github', + oneOf: [ + { + const: 'github', + title: 'GitHub Action', + }, + { + const: 'tekton', + title: 'Tekton', + }, + ], + }, + }, + allOf: [ + { + if: { + properties: { + ci: { + const: 'github', + }, + }, + }, + }, + { + if: { + properties: { + ci: { + const: 'tekton', + }, + }, + }, + then: { + properties: { + imageRepository: { + title: 'Image Registry', + description: 'The registry to use', + type: 'string', + default: 'quay.io', + oneOf: [ + { + const: 'quay.io', + title: 'Quay', + }, + { + const: 'image-registry.openshift-image-registry.svc:5000', + title: 'Internal OpenShift Registry', + }, + ], + }, + imageUrl: { + title: 'Image URL', + description: + 'The Quay.io or OpenShift Image URL //', + type: 'string', + }, + namespace: { + title: 'Namespace', + description: 'The namespace for deploying resources', + type: 'string', + }, + }, + required: ['namespace', 'imageUrl', 'imageRepository'], + }, + }, + ], + }, + 'Provide information about the Java metadata_1': { + $id: 'classpath:/schemas/spring-boot-backend__ref-schema__Java_Metadata.json', + title: 'Provide information about the Java metadata', + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + properties: { + groupId: { + title: 'Group ID', + description: 'Maven Group ID eg (io.janus)', + type: 'string', + default: 'io.janus', + }, + artifactId: { + title: 'Artifact ID', + description: 'Maven Artifact ID', + type: 'string', + default: 'spring-boot-app', + }, + javaPackageName: { + title: 'Java Package Namespace', + description: + 'Name for the Java Package (ensure to use the / character as this is used for folder structure) should match Group ID and Artifact ID', + type: 'string', + default: 'io/janus/spring-boot-app', + }, + version: { + title: 'Version', + description: 'Maven Artifact Version', + type: 'string', + default: '1.0.0-SNAPSHOT', + }, + }, + required: ['groupId', 'artifactId', 'javaPackageName', 'version'], + }, + 'Provide information about the new component_0': { + $id: 'classpath:/schemas/spring-boot-backend__ref-schema__New_Component.json', + title: 'Provide information about the new component', + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + properties: { + orgName: { + title: 'Organization Name', + description: 'Organization name', + type: 'string', + }, + repoName: { + title: 'Repository Name', + description: 'Repository name', + type: 'string', + }, + description: { + title: 'Description', + description: 'Help others understand what this component is for', + type: 'string', + }, + owner: { + title: 'Owner', + description: 'An entity from the catalog', + type: 'string', + }, + system: { + title: 'System', + description: 'An entity from the catalog', + type: 'string', + }, + port: { + title: 'Port', + description: 'Override the port exposed for the application', + type: 'number', + default: 8080, + }, + }, + required: ['orgName', 'repoName', 'owner', 'system', 'port'], + }, + }, +} as JSONSchema7; + +const workflowDefinition = { + id: 'spring-boot-backend', + version: '1.0', + specVersion: '0.8', + name: 'Spring Boot Backend application', + description: + 'Create a starter Spring Boot backend application with a CI pipeline', + dataInputSchema: 'schemas/spring-boot-backend__main-schema.json', + functions: [ + { + name: 'runActionFetchTemplate', + operation: 'specs/actions-openapi.json#fetch:template', + }, + { + name: 'runActionPublishGithub', + operation: 'specs/actions-openapi.json#publish:github', + }, + { + name: 'runActionCatalogRegister', + operation: 'specs/actions-openapi.json#catalog:register', + }, + { + name: 'fs:delete', + operation: 'specs/actions-openapi.json#fs:delete', + }, + { + name: 'sysout', + type: 'custom', + operation: 'sysout', + }, + ], + errors: [ + { + name: 'Error on Action', + code: 'java.lang.RuntimeException', + }, + ], + start: 'Generating the Source Code Component', + states: [ + { + name: 'Generating the Source Code Component', + type: 'operation', + actionMode: 'sequential', + actions: [ + { + name: 'Fetch Template Action - Source Code', + functionRef: { + refName: 'runActionFetchTemplate', + arguments: { + url: 'https://github.com/janus-idp/software-templates/tree/main/templates/github/spring-boot-backend/skeleton', + values: { + orgName: '.newComponent.orgName', + repoName: '.newComponent.repoName', + owner: '.newComponent.owner', + system: '.newComponent.system', + applicationType: 'api', + description: '.newComponent.description', + namespace: '.ciMethod.namespace', + port: '.newComponent.port', + ci: '.ciMethod.ci', + sourceControl: 'github.com', + groupId: '.javaMetadata.groupId', + artifactId: '.javaMetadata.artifactId', + javaPackageName: '.javaMetadata.javaPackageName', + version: '.javaMetadata.version', + }, + }, + }, + actionDataFilter: { + toStateData: '.actionFetchTemplateSourceCodeResult', + }, + }, + ], + onErrors: [ + { + errorRef: 'Error on Action', + transition: 'Handle Error', + }, + ], + compensatedBy: 'Clear File System - Source Code', + transition: 'Generating the CI Component', + }, + { + name: 'Generating the CI Component', + type: 'switch', + dataConditions: [ + { + condition: '${ .ciMethod.ci == "github" }', + transition: 'Generating the CI Component - GitHub', + }, + { + condition: '${ .ciMethod.ci == "tekton" }', + transition: 'Generating the CI Component - Tekton', + }, + ], + defaultCondition: { + transition: 'Generating the CI Component - GitHub', + }, + }, + { + name: 'Generating the CI Component - GitHub', + type: 'operation', + actionMode: 'sequential', + actions: [ + { + name: 'Run Template Fetch Action - CI - GitHub', + functionRef: { + refName: 'runActionFetchTemplate', + arguments: { + url: 'https://github.com/janus-idp/software-templates/tree/main/skeletons/github-actions', + copyWithoutTemplating: ['".github/workflows/"'], + values: { + orgName: '.newComponent.orgName', + repoName: '.newComponent.repoName', + owner: '.newComponent.owner', + system: '.newComponent.system', + applicationType: 'api', + description: '.newComponent.description', + namespace: '.ciMethod.namespace', + port: '.newComponent.port', + ci: '.ciMethod.ci', + sourceControl: 'github.com', + groupId: '.javaMetadata.groupId', + artifactId: '.javaMetadata.artifactId', + javaPackageName: '.javaMetadata.javaPackageName', + version: '.javaMetadata.version', + }, + }, + }, + actionDataFilter: { + toStateData: '.actionTemplateFetchCIResult', + }, + }, + ], + onErrors: [ + { + errorRef: 'Error on Action', + transition: 'Handle Error', + }, + ], + compensatedBy: 'Clear File System - CI', + transition: 'Generating the Catalog Info Component', + }, + { + name: 'Generating the CI Component - Tekton', + type: 'operation', + actionMode: 'sequential', + actions: [ + { + name: 'Run Template Fetch Action - CI - Tekton', + functionRef: { + refName: 'runActionFetchTemplate', + arguments: { + url: 'https://github.com/janus-idp/software-templates/tree/main/skeletons/tekton', + copyWithoutTemplating: ['".github/workflows/"'], + values: { + orgName: '.newComponent.orgName', + repoName: '.newComponent.repoName', + owner: '.newComponent.owner', + system: '.newComponent.system', + applicationType: 'api', + description: '.newComponent.description', + namespace: '.ciMethod.namespace', + imageUrl: '.imageUrl', + imageRepository: '.imageRepository', + imageBuilder: 's2i-go', + port: '.newComponent.port', + ci: '.ciMethod.ci', + sourceControl: 'github.com', + groupId: '.javaMetadata.groupId', + artifactId: '.javaMetadata.artifactId', + javaPackageName: '.javaMetadata.javaPackageName', + version: '.javaMetadata.version', + }, + }, + }, + actionDataFilter: { + toStateData: '.actionTemplateFetchCIResult', + }, + }, + ], + onErrors: [ + { + errorRef: 'Error on Action', + transition: 'Handle Error', + }, + ], + compensatedBy: 'Clear File System - CI', + transition: 'Generating the Catalog Info Component', + }, + { + name: 'Generating the Catalog Info Component', + type: 'operation', + actions: [ + { + name: 'Fetch Template Action - Catalog Info', + functionRef: { + refName: 'runActionFetchTemplate', + arguments: { + url: 'https://github.com/janus-idp/software-templates/tree/main/skeletons/catalog-info', + values: { + orgName: '.newComponent.orgName', + repoName: '.newComponent.repoName', + owner: '.newComponent.owner', + system: '.newComponent.system', + applicationType: 'api', + description: '.newComponent.description', + namespace: '.ciMethod.namespace', + imageUrl: '.ciMethod.imageUrl', + imageRepository: '.ciMethod.imageRepository', + imageBuilder: 's2i-go', + port: '.newComponent.port', + ci: '.ciMethod.ci', + sourceControl: 'github.com', + groupId: '.javaMetadata.groupId', + artifactId: '.javaMetadata.artifactId', + javaPackageName: '.javaMetadata.javaPackageName', + version: '.javaMetadata.version', + }, + }, + }, + actionDataFilter: { + toStateData: '.actionFetchTemplateCatalogInfoResult', + }, + }, + ], + onErrors: [ + { + errorRef: 'Error on Action', + transition: 'Handle Error', + }, + ], + compensatedBy: 'Clear File System - Catalog', + transition: 'Publishing to the Source Code Repository', + }, + { + name: 'Publishing to the Source Code Repository', + type: 'operation', + actionMode: 'sequential', + actions: [ + { + name: 'Publish Github', + functionRef: { + refName: 'runActionPublishGithub', + arguments: { + allowedHosts: ['"github.com"'], + description: 'Workflow Action', + repoUrl: + '"github.com?owner=" + .newComponent.orgName + "&repo=" + .newComponent.repoName', + defaultBranch: 'main', + gitCommitMessage: 'Initial commit', + allowAutoMerge: true, + allowRebaseMerge: true, + }, + }, + actionDataFilter: { + toStateData: '.actionPublishResult', + }, + }, + ], + onErrors: [ + { + errorRef: 'Error on Action', + transition: 'Handle Error', + }, + ], + compensatedBy: 'Remove Source Code Repository', + transition: 'Registering the Catalog Info Component', + }, + { + name: 'Registering the Catalog Info Component', + type: 'operation', + actionMode: 'sequential', + actions: [ + { + name: 'Catalog Register Action', + functionRef: { + refName: 'runActionCatalogRegister', + arguments: { + repoContentsUrl: '.actionPublishResult.repoContentsUrl', + catalogInfoPath: '"/catalog-info.yaml"', + }, + }, + actionDataFilter: { + toStateData: '.actionCatalogRegisterResult', + }, + }, + ], + onErrors: [ + { + errorRef: 'Error on Action', + transition: 'Handle Error', + }, + ], + compensatedBy: 'Remove Catalog Info Component', + end: true, + }, + { + name: 'Handle Error', + type: 'operation', + actions: [ + { + name: 'Error Action', + functionRef: { + refName: 'sysout', + arguments: { + message: 'Error on workflow, triggering compensations', + }, + }, + }, + ], + end: { + compensate: true, + }, + }, + { + name: 'Clear File System - Source Code', + type: 'operation', + usedForCompensation: true, + actions: [ + { + name: 'Clear FS Action', + functionRef: { + refName: 'fs:delete', + arguments: { + files: ['./'], + }, + }, + }, + ], + }, + { + name: 'Clear File System - CI', + type: 'operation', + usedForCompensation: true, + actions: [ + { + name: 'Clear FS Action', + functionRef: { + refName: 'fs:delete', + arguments: { + files: ['./'], + }, + }, + }, + ], + }, + { + name: 'Clear File System - Catalog', + type: 'operation', + usedForCompensation: true, + actions: [ + { + name: 'Clear FS Action', + functionRef: { + refName: 'fs:delete', + arguments: { + files: ['./'], + }, + }, + }, + ], + }, + { + name: 'Remove Source Code Repository', + type: 'operation', + usedForCompensation: true, + actions: [ + { + name: 'Remove Source Code Repository', + functionRef: { + refName: 'sysout', + arguments: { + message: 'Remove Source Code Repository', + }, + }, + }, + ], + }, + { + name: 'Remove Catalog Info Component', + type: 'operation', + usedForCompensation: true, + actions: [ + { + name: 'Remove Catalog Info Component', + functionRef: { + refName: 'sysout', + arguments: { + message: 'Remove Catalog Info Component', + }, + }, + }, + ], + }, + ], +} as WorkflowDefinition; + +const mockData: { + schema: JSONSchema7; + workflowDefinition: WorkflowDefinition; +} = { schema, workflowDefinition }; + +export default mockData; diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/app-config.yaml b/workspaces/orchestrator/plugins/orchestrator-backend/app-config.yaml new file mode 100644 index 00000000..a3a8b8be --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/app-config.yaml @@ -0,0 +1,3 @@ +orchestrator: + dataIndexService: + url: http://sonataflow-platform-data-index-service.sonataflow-infra diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/catalog-info.yaml b/workspaces/orchestrator/plugins/orchestrator-backend/catalog-info.yaml new file mode 100644 index 00000000..25fa8de8 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/catalog-info.yaml @@ -0,0 +1,25 @@ +# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: red-hat-developer-hub-orchestrator-backend + title: '@red-hat-developer-hub/backstage-plugin-orchestrator-backend' + description: Orchestrator Backend Plugin for Backstage + annotations: + backstage.io/source-location: url:https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/orchestrator/plugins/orchestrator-backend + backstage.io/view-url: https://github.com/redhat-developer/rhdh-plugins/blob/main/workspaces/orchestrator/plugins/orchestrator-backend/catalog-info.yaml + backstage.io/edit-url: https://github.com/redhat-developer/rhdh-plugins/edit/main/workspaces/orchestrator/plugins/orchestrator-backend/catalog-info.yaml + github.com/project-slug: red-hat-developer-hub/backstage-plugins + github.com/team-slug: red-hat-developer-hub/orchestrator-codeowners + sonarqube.org/project-key: red_hat_developer_hub_plugins + links: + - url: https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/orchestrator/plugins/orchestrator-backend + title: GitHub Source + icon: source + type: source +spec: + type: backstage-backend-plugin + lifecycle: production + owner: orchestrator-team + system: rhdh + subcomponentOf: red-hat-developer-hub-orchestrator diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/dev/index.ts b/workspaces/orchestrator/plugins/orchestrator-backend/dev/index.ts new file mode 100644 index 00000000..ba4666a0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/dev/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createBackend } from '@backstage/backend-defaults'; + +import { orchestratorPlugin } from '../src/plugin'; + +const backend = createBackend(); + +backend.add(orchestratorPlugin); + +backend.start(); diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/package.json b/workspaces/orchestrator/plugins/orchestrator-backend/package.json new file mode 100644 index 00000000..b139bfb8 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/package.json @@ -0,0 +1,112 @@ +{ + "name": "@red-hat-developer-hub/backstage-plugin-orchestrator-backend", + "version": "4.1.0", + "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "publishConfig": { + "access": "public" + }, + "backstage": { + "role": "backend-plugin", + "supported-versions": "1.32.5", + "pluginId": "orchestrator", + "pluginPackages": [ + "@red-hat-developer-hub/backstage-plugin-orchestrator", + "@red-hat-developer-hub/backstage-plugin-orchestrator-backend", + "@red-hat-developer-hub/backstage-plugin-orchestrator-common" + ] + }, + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "package.json": [ + "package.json" + ] + } + }, + "homepage": "https://red.ht/rhdh", + "repository": { + "type": "git", + "url": "https://github.com/redhat-developer/rhdh-plugins", + "directory": "workspaces/orchestrator/plugins/orchestrator-backend" + }, + "bugs": "https://github.com/redhat-developer/rhdh-plugins/issues", + "keywords": [ + "support:tech-preview", + "lifecycle:active", + "backstage", + "plugin", + "orchestrator", + "workflows" + ], + "files": [ + "app-config.yaml", + "dist", + "dist-dynamic/*.*", + "dist-dynamic/dist/**", + "static" + ], + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "tsc": "tsc", + "prettier:check": "prettier --ignore-unknown --check .", + "prettier:fix": "prettier --ignore-unknown --write .", + "lint:check": "backstage-cli package lint", + "lint:fix": "backstage-cli package lint --fix", + "test": "backstage-cli package test --passWithNoTests --coverage", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@backstage/backend-common": "^0.25.0", + "@backstage/backend-defaults": "^0.5.2", + "@backstage/backend-plugin-api": "^1.0.1", + "@backstage/backend-tasks": "^0.6.1", + "@backstage/catalog-client": "^1.7.1", + "@backstage/errors": "^1.2.4", + "@backstage/integration": "^1.15.1", + "@backstage/plugin-catalog-node": "^1.13.1", + "@backstage/plugin-permission-common": "^0.8.1", + "@backstage/plugin-permission-node": "^0.8.4", + "@backstage/plugin-scaffolder-backend": "^1.26.2", + "@backstage/plugin-scaffolder-node": "^0.5.0", + "@red-hat-developer-hub/backstage-plugin-orchestrator-common": "workspace:^", + "@urql/core": "^4.1.4", + "ajv-formats": "^2.1.1", + "cloudevents": "^8.0.0", + "express": "^4.18.2", + "express-promise-router": "^4.1.1", + "fs-extra": "^10.1.0", + "isomorphic-git": "^1.23.0", + "json-schema": "^0.4.0", + "moment": "^2.29.4", + "openapi-backend": "^5.10.5", + "yn": "^5.0.0" + }, + "devDependencies": { + "@backstage-community/plugin-rbac-common": "^1.12.1", + "@backstage/backend-test-utils": "1.0.2", + "@backstage/cli": "0.28.2", + "@janus-idp/backstage-plugin-audit-log-node": "^1.7.1", + "@types/express": "4.17.21", + "@types/fs-extra": "11.0.4", + "@types/json-schema": "7.0.15", + "prettier": "3.3.3" + }, + "peerDependencies": { + "@backstage-community/plugin-rbac-common": "^1.12.1", + "@janus-idp/backstage-plugin-audit-log-node": "^1.7.1" + }, + "maintainers": [ + "@mlibra", + "@batzionb", + "@gciavarrini" + ], + "author": "The Backstage Community" +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/report.api.md b/workspaces/orchestrator/plugins/orchestrator-backend/report.api.md new file mode 100644 index 00000000..d7788cf2 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/report.api.md @@ -0,0 +1,15 @@ +## API Report File for "@red-hat-developer-hub/backstage-plugin-orchestrator-backend" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { BackendFeature } from '@backstage/backend-plugin-api'; + +// @public +const orchestratorPlugin: BackendFeature; +export default orchestratorPlugin; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/OrchestratorPlugin.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/OrchestratorPlugin.ts new file mode 100644 index 00000000..1f2e1540 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/OrchestratorPlugin.ts @@ -0,0 +1,75 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; + +import { createRouter } from './routerWrapper'; + +export const orchestratorPlugin = createBackendPlugin({ + pluginId: 'orchestrator', + register(env) { + env.registerInit({ + deps: { + logger: coreServices.logger, + config: coreServices.rootConfig, + discovery: coreServices.discovery, + httpRouter: coreServices.httpRouter, + urlReader: coreServices.urlReader, + scheduler: coreServices.scheduler, + permissions: coreServices.permissions, + httpAuth: coreServices.httpAuth, + auth: coreServices.auth, + catalogApi: catalogServiceRef, + }, + async init({ + logger, + config, + discovery, + httpRouter, + catalogApi, + urlReader, + scheduler, + permissions, + httpAuth, + auth, + }) { + const router = await createRouter({ + config: config, + logger, + discovery: discovery, + catalogApi: catalogApi, + urlReader: urlReader, + scheduler: scheduler, + permissions: permissions, + httpAuth: httpAuth, + auth: auth, + }); + httpRouter.use(router); + httpRouter.addAuthPolicy({ + path: '/static/generated/envelope', + allow: 'unauthenticated', + }); + httpRouter.addAuthPolicy({ + path: '/health', + allow: 'unauthenticated', + }); + }, + }); + }, +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/errorBuilder.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/errorBuilder.ts new file mode 100644 index 00000000..fd48657d --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/errorBuilder.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const NO_DATA_INDEX_URL = 'NO_DATA_INDEX_URL'; +export const NO_BACKEND_EXEC_CTX = 'NO_BACKEND_EXEC_CTX'; +export const NO_CLIENT_PROVIDED = 'NO_CLIENT_PROVIDED'; +export const NO_LOGGER = 'NO_LOGGER'; +export const SWF_BACKEND_NOT_INITED = 'SWF_BACKEND_NOT_INITED'; + +export class ErrorBuilder { + public static NewBackendError(name: string, message: string): Error { + const e = new Error(message); + e.name = name; + return e; + } + + public static GET_NO_DATA_INDEX_URL_ERR(): Error { + return this.NewBackendError( + NO_DATA_INDEX_URL, + 'No data index url specified or found', + ); + } + + public static GET_NO_CLIENT_PROVIDED_ERR(): Error { + return this.NewBackendError( + NO_CLIENT_PROVIDED, + 'No or null graphql client', + ); + } + + public static GET_SWF_BACKEND_NOT_INITED(): Error { + return this.NewBackendError( + SWF_BACKEND_NOT_INITED, + 'The SonataFlow backend is not initialized, call initialize() method before trying to get the workflows.', + ); + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/filterBuilder.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/filterBuilder.ts new file mode 100644 index 00000000..7d94e1a8 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/filterBuilder.ts @@ -0,0 +1,292 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + FieldFilter, + FieldFilterOperatorEnum, + Filter, + IntrospectionField, + LogicalFilter, + ProcessInstanceStatusDTO, + TypeName, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import { getProcessInstanceStateFromStatusDTOString } from '../service/api/mapping/V2Mappings'; + +type ProcessType = 'ProcessDefinition' | 'ProcessInstance'; + +function isLogicalFilter(filter: Filter): filter is LogicalFilter { + return (filter as LogicalFilter).filters !== undefined; +} + +function handleLogicalFilter( + introspection: IntrospectionField[], + type: ProcessType, + filter: LogicalFilter, +): string { + if (!filter.operator) return ''; + + const subClauses = filter.filters.map(f => + buildFilterCondition(introspection, type, f), + ); + + return `${filter.operator.toLowerCase()}: {${subClauses.join(', ')}}`; +} + +function handleBetweenOperator(filter: FieldFilter): string { + if (!Array.isArray(filter.value) || filter.value.length !== 2) { + throw new Error('Between operator requires an array of two elements'); + } + return `${filter.field}: {${getGraphQLOperator( + FieldFilterOperatorEnum.Between, + )}: {from: "${filter.value[0]}", to: "${filter.value[1]}"}}`; +} + +function handleIsNullOperator(filter: FieldFilter): string { + return `${filter.field}: {${getGraphQLOperator( + FieldFilterOperatorEnum.IsNull, + )}: ${convertToBoolean(filter.value)}}`; +} + +function isEnumFilter( + fieldName: string, + type: 'ProcessDefinition' | 'ProcessInstance', +): boolean { + if (type === 'ProcessInstance') { + if (fieldName === 'state') { + return true; + } + } + return false; +} + +function convertEnumValue( + fieldName: string, + fieldValue: string, + type: 'ProcessDefinition' | 'ProcessInstance', +): string { + if (type === 'ProcessInstance') { + if (fieldName === 'state') { + const state = (ProcessInstanceStatusDTO as any)[ + fieldValue as keyof typeof ProcessInstanceStatusDTO + ]; + + if (!state) { + throw new Error( + `status ${fieldValue} is not a valid value of ProcessInstanceStatusDTO`, + ); + } + return getProcessInstanceStateFromStatusDTOString(state).valueOf(); + } + } + throw new Error( + `Unsupported enum ${fieldName}: can't convert value ${fieldValue}`, + ); +} + +function isValidEnumOperator(operator: FieldFilterOperatorEnum): boolean { + return ( + operator === FieldFilterOperatorEnum.In || + operator === FieldFilterOperatorEnum.Eq + ); +} + +function handleBinaryOperator( + binaryFilter: FieldFilter, + fieldDef: IntrospectionField, + type: 'ProcessDefinition' | 'ProcessInstance', +): string { + if (isEnumFilter(binaryFilter.field, type)) { + if (!isValidEnumOperator(binaryFilter.operator)) { + throw new Error( + `Invalid operator ${binaryFilter.operator} for enum field ${binaryFilter.field} filter`, + ); + } + binaryFilter.value = convertEnumValue( + binaryFilter.field, + binaryFilter.value, + type, + ); + } + const formattedValue = Array.isArray(binaryFilter.value) + ? `[${binaryFilter.value + .map(v => formatValue(binaryFilter.field, v, fieldDef, type)) + .join(', ')}]` + : formatValue(binaryFilter.field, binaryFilter.value, fieldDef, type); + return `${binaryFilter.field}: {${getGraphQLOperator( + binaryFilter.operator, + )}: ${formattedValue}}`; +} + +export function buildFilterCondition( + introspection: IntrospectionField[], + type: ProcessType, + filters?: Filter, +): string { + if (!filters) { + return ''; + } + + if (isLogicalFilter(filters)) { + return handleLogicalFilter(introspection, type, filters); + } + + if (!isOperatorSupported(filters.operator)) { + throw new Error(`Unsopported operator ${filters.operator}`); + } + + const fieldDef = introspection.find(f => f.name === filters.field); + if (!fieldDef) { + throw new Error(`Can't find field "${filters.field}" definition`); + } + + if (!isOperatorAllowedForField(filters.operator, fieldDef)) { + throw new Error(`Unsupported field type ${fieldDef.type.name}`); + } + + switch (filters.operator) { + case FieldFilterOperatorEnum.IsNull: + return handleIsNullOperator(filters); + case FieldFilterOperatorEnum.Between: + return handleBetweenOperator(filters); + case FieldFilterOperatorEnum.Eq: + case FieldFilterOperatorEnum.Like: + case FieldFilterOperatorEnum.In: + case FieldFilterOperatorEnum.Gt: + case FieldFilterOperatorEnum.Gte: + case FieldFilterOperatorEnum.Lt: + case FieldFilterOperatorEnum.Lte: + return handleBinaryOperator(filters, fieldDef, type); + + default: + throw new Error(`Can't build filter condition`); + } +} + +function isOperatorSupported(operator: FieldFilterOperatorEnum): boolean { + return ( + operator === FieldFilterOperatorEnum.Eq || + operator === FieldFilterOperatorEnum.Like || + operator === FieldFilterOperatorEnum.In || + operator === FieldFilterOperatorEnum.IsNull || + operator === FieldFilterOperatorEnum.Gt || + operator === FieldFilterOperatorEnum.Gte || + operator === FieldFilterOperatorEnum.Lt || + operator === FieldFilterOperatorEnum.Lte || + operator === FieldFilterOperatorEnum.Between + ); +} + +function isFieldFilterSupported(fieldDef: IntrospectionField): boolean { + return fieldDef?.type.name === TypeName.String; +} + +function isOperatorAllowedForField( + operator: FieldFilterOperatorEnum, + fieldDef: IntrospectionField, +): boolean { + const allowedOperators: Record = { + [TypeName.String]: [ + FieldFilterOperatorEnum.In, + FieldFilterOperatorEnum.Like, + FieldFilterOperatorEnum.IsNull, + FieldFilterOperatorEnum.Eq, + ], + [TypeName.Id]: [ + FieldFilterOperatorEnum.In, + FieldFilterOperatorEnum.IsNull, + FieldFilterOperatorEnum.Eq, + ], + [TypeName.Date]: [ + FieldFilterOperatorEnum.IsNull, + FieldFilterOperatorEnum.Eq, + FieldFilterOperatorEnum.Gt, + FieldFilterOperatorEnum.Gte, + FieldFilterOperatorEnum.Lt, + FieldFilterOperatorEnum.Lte, + FieldFilterOperatorEnum.Between, + ], + [TypeName.StringArray]: [], + }; + const allowedForType = allowedOperators[fieldDef.type.name]; + return allowedForType ? allowedForType.includes(operator) : false; +} + +function convertToBoolean(value: any): boolean { + if (typeof value === 'boolean') { + return value; + } + if (typeof value === 'string') { + return value.toLowerCase() === 'true'; + } + if (typeof value === 'number') { + return value === 1; + } + return false; // Default to false for unsupported types +} + +function formatValue( + fieldName: string, + fieldValue: any, + fieldDef: IntrospectionField, + type: ProcessType, +): string { + if (!isFieldFilterSupported) { + throw new Error(`Unsupported field type ${fieldDef.type.name}`); + } + + if (isEnumFilter(fieldName, type)) { + return `${fieldValue}`; + } + if ( + fieldDef.type.name === TypeName.String || + fieldDef.type.name === TypeName.Id || + fieldDef.type.name === TypeName.Date + ) { + return `"${fieldValue}"`; + } + throw new Error( + `Failed to format value for ${fieldName} ${fieldValue} with type ${fieldDef.type.name}`, + ); +} + +function getGraphQLOperator(operator: FieldFilterOperatorEnum): string { + switch (operator) { + case 'EQ': + return 'equal'; + case 'LIKE': + return 'like'; + case 'IN': + return 'in'; + case 'IS_NULL': + return 'isNull'; + case 'GT': + return 'greaterThan'; + case 'GTE': + return 'greaterThanEqual'; + case 'LT': + return 'lessThan'; + case 'LTE': + return 'lessThanEqual'; + // case 'CONTAINS': + // return "contains" + // case 'CONTAINS_ALL': + // case 'CONTAINS_ANY': + case 'BETWEEN': + return 'between'; + default: + throw new Error(`Operation "${operator}" not supported`); + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/filterBuilders.test.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/filterBuilders.test.ts new file mode 100644 index 00000000..8b074f65 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/filterBuilders.test.ts @@ -0,0 +1,568 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + FieldFilterOperatorEnum, + Filter, + IntrospectionField, + ProcessInstanceState, + ProcessInstanceStatusDTO, + TypeKind, + TypeName, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import { buildFilterCondition } from './filterBuilder'; + +describe('column filters', () => { + const createIntrospectionField = ( + name: string, + type: TypeName, + ): IntrospectionField => ({ + name, + type: { + name: type, + kind: TypeKind.InputObject, + ofType: null, + }, + }); + + const createFieldFilter = ( + field: string, + operator: FieldFilterOperatorEnum, + value: any, + ): Filter => ({ + field, + operator, + value, + }); + + type FilterTestCase = { + name: string; + introspectionFields: IntrospectionField[]; + filter: Filter | undefined; + expectedResult: string; + }; + describe('empty filter testcases', () => { + const emptyFilterTestCases: FilterTestCase[] = [ + { + name: 'returns empty string when filters are null or undefined', + introspectionFields: [], + filter: undefined, + expectedResult: '', + }, + ]; + emptyFilterTestCases.forEach( + ({ name, introspectionFields, filter, expectedResult }) => { + it(`${name}`, () => { + const result = buildFilterCondition( + introspectionFields, + 'ProcessInstance', + filter, + ); + expect(result).toBe(expectedResult); + }); + }, + ); + }); + describe('stringArgument testcases', () => { + const stringTestCases: FilterTestCase[] = [ + { + name: 'returns correct filter for single string field with equal operator', + introspectionFields: [ + createIntrospectionField('name', TypeName.String), + ], + filter: createFieldFilter( + 'name', + FieldFilterOperatorEnum.Eq, + 'Hello World Workflow', + ), + expectedResult: 'name: {equal: "Hello World Workflow"}', + }, + { + name: 'returns correct filter for single string field with like operator', + introspectionFields: [ + createIntrospectionField('name', TypeName.String), + ], + filter: createFieldFilter( + 'name', + FieldFilterOperatorEnum.Like, + 'Hello%', + ), + expectedResult: 'name: {like: "Hello%"}', + }, + { + name: 'returns correct filter for string field with isNull operator (true)', + introspectionFields: [ + createIntrospectionField('name', TypeName.String), + ], + filter: createFieldFilter('name', FieldFilterOperatorEnum.IsNull, true), + expectedResult: 'name: {isNull: true}', + }, + { + name: 'returns correct filter for string field with isNull operator (false)', + introspectionFields: [ + createIntrospectionField('name', TypeName.String), + ], + filter: createFieldFilter( + 'name', + FieldFilterOperatorEnum.IsNull, + false, + ), + expectedResult: 'name: {isNull: false}', + }, + { + name: 'returns correct filter for string field with isNull operator ("true" as string)', + introspectionFields: [ + createIntrospectionField('name', TypeName.String), + ], + filter: createFieldFilter( + 'name', + FieldFilterOperatorEnum.IsNull, + 'True', + ), + expectedResult: 'name: {isNull: true}', + }, + { + name: 'returns correct filter for string field with isNull operator ("false" as string)', + introspectionFields: [ + createIntrospectionField('name', TypeName.String), + ], + filter: createFieldFilter( + 'name', + FieldFilterOperatorEnum.IsNull, + 'FALSE', + ), + expectedResult: 'name: {isNull: false}', + }, + { + name: 'returns correct filter for string field with in operator (single value)', + introspectionFields: [ + createIntrospectionField('name', TypeName.String), + ], + filter: createFieldFilter('name', FieldFilterOperatorEnum.In, [ + 'Test String', + ]), + expectedResult: 'name: {in: ["Test String"]}', + }, + { + name: 'returns correct filter for string field with in operator (multiple values)', + introspectionFields: [ + createIntrospectionField('name', TypeName.String), + ], + filter: createFieldFilter('name', FieldFilterOperatorEnum.In, [ + 'Test String 1', + 'Test String 2', + 'Test String 3', + ]), + expectedResult: + 'name: {in: ["Test String 1", "Test String 2", "Test String 3"]}', + }, + { + name: 'returns correct OR filter for two string fields with equal operator', + introspectionFields: [ + createIntrospectionField('name', TypeName.String), + createIntrospectionField('processName', TypeName.String), + ], + filter: { + operator: 'OR', + filters: [ + createFieldFilter( + 'name', + FieldFilterOperatorEnum.Eq, + 'Hello World Workflow', + ), + createFieldFilter( + 'processName', + FieldFilterOperatorEnum.Eq, + 'Greeting workflow', + ), + ], + }, + expectedResult: + 'or: {name: {equal: "Hello World Workflow"}, processName: {equal: "Greeting workflow"}}', + }, + { + name: 'returns correct filter for string field with like and isNull operators', + introspectionFields: [ + createIntrospectionField('description', TypeName.String), + ], + filter: { + operator: 'OR', + filters: [ + createFieldFilter( + 'description', + FieldFilterOperatorEnum.Like, + '%Test%', + ), + createFieldFilter( + 'description', + FieldFilterOperatorEnum.IsNull, + true, + ), + ], + }, + expectedResult: + 'or: {description: {like: "%Test%"}, description: {isNull: true}}', + }, + { + name: 'returns correct filter for string field with in, like, equal, and isNull operators', + introspectionFields: [ + createIntrospectionField('name', TypeName.String), + ], + filter: { + operator: 'OR', + filters: [ + createFieldFilter('name', FieldFilterOperatorEnum.In, [ + 'Test String 1', + 'Test String 2', + ]), + createFieldFilter('name', FieldFilterOperatorEnum.Like, '%Test%'), + createFieldFilter( + 'name', + FieldFilterOperatorEnum.Eq, + 'Exact Match', + ), + createFieldFilter('name', FieldFilterOperatorEnum.IsNull, false), + ], + }, + expectedResult: + 'or: {name: {in: ["Test String 1", "Test String 2"]}, name: {like: "%Test%"}, name: {equal: "Exact Match"}, name: {isNull: false}}', + }, + { + name: 'returns correct filter for string field with in, like, equal, and isNull operators', + introspectionFields: [ + createIntrospectionField('name', TypeName.String), + ], + filter: { + operator: 'AND', + filters: [ + createFieldFilter('name', FieldFilterOperatorEnum.In, [ + 'Test String 1', + 'Test String 2', + ]), + createFieldFilter('name', FieldFilterOperatorEnum.Like, '%Test%'), + createFieldFilter( + 'name', + FieldFilterOperatorEnum.Eq, + 'Exact Match', + ), + createFieldFilter('name', FieldFilterOperatorEnum.IsNull, false), + ], + }, + expectedResult: + 'and: {name: {in: ["Test String 1", "Test String 2"]}, name: {like: "%Test%"}, name: {equal: "Exact Match"}, name: {isNull: false}}', + }, + ]; + stringTestCases.forEach( + ({ name, introspectionFields, filter, expectedResult }) => { + it(`${name}`, () => { + const result = buildFilterCondition( + introspectionFields, + 'ProcessInstance', + filter, + ); + expect(result).toBe(expectedResult); + }); + }, + ); + }); + describe('idArgument testcases', () => { + const idTestCases: FilterTestCase[] = [ + { + name: 'returns correct filter for single id field with equal operator', + introspectionFields: [createIntrospectionField('id', TypeName.Id)], + filter: createFieldFilter('id', FieldFilterOperatorEnum.Eq, 'idA'), + expectedResult: 'id: {equal: "idA"}', + }, + { + name: 'returns correct filter for single id field with isNull operator (false as boolean)', + introspectionFields: [createIntrospectionField('id', TypeName.Id)], + filter: createFieldFilter('id', FieldFilterOperatorEnum.IsNull, false), + expectedResult: 'id: {isNull: false}', + }, + { + name: 'returns correct filter for single id field with isNull operator (false as string)', + introspectionFields: [createIntrospectionField('id', TypeName.Id)], + filter: createFieldFilter( + 'id', + FieldFilterOperatorEnum.IsNull, + 'false', + ), + expectedResult: 'id: {isNull: false}', + }, + { + name: 'returns correct filter for single id field with IN operator', + introspectionFields: [createIntrospectionField('id', TypeName.Id)], + filter: createFieldFilter('id', FieldFilterOperatorEnum.In, [ + 'idA', + 'idB', + 'idC', + ]), + expectedResult: 'id: {in: ["idA", "idB", "idC"]}', + }, + { + name: 'returns correct OR filter for multiple id fields with equal, isNull, and IN operators', + introspectionFields: [ + createIntrospectionField('processId', TypeName.Id), + createIntrospectionField('id', TypeName.Id), + ], + filter: { + operator: 'OR', + filters: [ + createFieldFilter('id', FieldFilterOperatorEnum.Eq, 'idA'), + createFieldFilter( + 'processId', + FieldFilterOperatorEnum.IsNull, + 'True', + ), + createFieldFilter('id', 'IN', ['idA', 'idB', 'idC']), + ], + }, + expectedResult: + 'or: {id: {equal: "idA"}, processId: {isNull: true}, id: {in: ["idA", "idB", "idC"]}}', + }, + { + name: 'returns correct AND filter for multiple id fields with equal, isNull, and IN operators', + introspectionFields: [ + createIntrospectionField('processId', TypeName.Id), + createIntrospectionField('id', TypeName.Id), + ], + filter: { + operator: 'AND', + filters: [ + createFieldFilter('id', FieldFilterOperatorEnum.Eq, 'idA'), + createFieldFilter( + 'processId', + FieldFilterOperatorEnum.IsNull, + 'True', + ), + createFieldFilter('id', 'IN', ['idA', 'idB', 'idC']), + ], + }, + expectedResult: + 'and: {id: {equal: "idA"}, processId: {isNull: true}, id: {in: ["idA", "idB", "idC"]}}', + }, + ]; + + idTestCases.forEach( + ({ name, introspectionFields, filter, expectedResult }) => { + it(`${name}`, () => { + const result = buildFilterCondition( + introspectionFields, + 'ProcessInstance', + filter, + ); + expect(result).toBe(expectedResult); + }); + }, + ); + }); + describe('dateArgument testcases', () => { + const testDate1 = '2024-10-10T09:54:40.759Z'; + const testDate2 = '2025-10-10T09:54:40.759Z'; + + const idTestCases: FilterTestCase[] = [ + { + name: 'returns correct filter for single date field with equal operator', + introspectionFields: [createIntrospectionField('start', TypeName.Date)], + filter: createFieldFilter( + 'start', + FieldFilterOperatorEnum.Eq, + testDate1, + ), + expectedResult: `start: {equal: "${testDate1}"}`, + }, + { + name: 'returns correct filter for single date field with isNull operator (false as boolean)', + introspectionFields: [createIntrospectionField('start', TypeName.Date)], + filter: createFieldFilter( + 'start', + FieldFilterOperatorEnum.IsNull, + false, + ), + expectedResult: 'start: {isNull: false}', + }, + { + name: 'returns correct filter for single date field with isNull operator (false as string)', + introspectionFields: [createIntrospectionField('start', TypeName.Date)], + filter: createFieldFilter( + 'start', + FieldFilterOperatorEnum.IsNull, + 'false', + ), + expectedResult: 'start: {isNull: false}', + }, + { + name: 'returns correct filter for single date field with GT operator', + introspectionFields: [createIntrospectionField('start', TypeName.Date)], + filter: createFieldFilter( + 'start', + FieldFilterOperatorEnum.Gt, + testDate1, + ), + expectedResult: `start: {greaterThan: "${testDate1}"}`, + }, + { + name: 'returns correct filter for single date field with GTE operator', + introspectionFields: [createIntrospectionField('start', TypeName.Date)], + filter: createFieldFilter( + 'start', + FieldFilterOperatorEnum.Gte, + testDate1, + ), + expectedResult: `start: {greaterThanEqual: "${testDate1}"}`, + }, + { + name: 'returns correct filter for single date field with LT operator', + introspectionFields: [createIntrospectionField('start', TypeName.Date)], + filter: createFieldFilter( + 'start', + FieldFilterOperatorEnum.Lt, + testDate1, + ), + expectedResult: `start: {lessThan: "${testDate1}"}`, + }, + { + name: 'returns correct filter for single date field with LTE operator', + introspectionFields: [createIntrospectionField('start', TypeName.Date)], + filter: createFieldFilter( + 'start', + FieldFilterOperatorEnum.Lte, + testDate1, + ), + expectedResult: `start: {lessThanEqual: "${testDate1}"}`, + }, + { + name: 'returns correct filter for single date field with BETWEEN operator', + introspectionFields: [createIntrospectionField('start', TypeName.Date)], + filter: createFieldFilter('start', FieldFilterOperatorEnum.Between, [ + testDate1, + testDate2, + ]), + expectedResult: `start: {between: {from: "${testDate1}", to: "${testDate2}"}}`, + }, + { + name: 'returns correct OR filter for multiple id fields with equal, isNull, and GT operators', + introspectionFields: [ + createIntrospectionField('start', TypeName.Date), + createIntrospectionField('end', TypeName.Date), + ], + filter: { + operator: 'OR', + filters: [ + createFieldFilter('start', FieldFilterOperatorEnum.Eq, testDate1), + createFieldFilter('end', FieldFilterOperatorEnum.IsNull, 'False'), + createFieldFilter('end', FieldFilterOperatorEnum.Gt, testDate1), + ], + }, + expectedResult: `or: {start: {equal: "${testDate1}"}, end: {isNull: false}, end: {greaterThan: "${testDate1}"}}`, + }, + { + name: 'returns correct OR filter for multiple id fields with equal, isNull, and GTE operators', + introspectionFields: [ + createIntrospectionField('start', TypeName.Date), + createIntrospectionField('end', TypeName.Date), + ], + filter: { + operator: 'OR', + filters: [ + createFieldFilter('start', FieldFilterOperatorEnum.Eq, testDate1), + createFieldFilter('end', FieldFilterOperatorEnum.IsNull, 'False'), + createFieldFilter('end', FieldFilterOperatorEnum.Gte, testDate1), + ], + }, + expectedResult: `or: {start: {equal: "${testDate1}"}, end: {isNull: false}, end: {greaterThanEqual: "${testDate1}"}}`, + }, + { + name: 'returns correct AND filter for multiple id fields with equal, isNull, and LTE operators', + introspectionFields: [ + createIntrospectionField('start', TypeName.Date), + createIntrospectionField('end', TypeName.Date), + ], + filter: { + operator: 'AND', + filters: [ + createFieldFilter('start', FieldFilterOperatorEnum.Eq, testDate1), + createFieldFilter('end', FieldFilterOperatorEnum.IsNull, 'False'), + createFieldFilter('end', FieldFilterOperatorEnum.Lte, testDate1), + ], + }, + expectedResult: `and: {start: {equal: "${testDate1}"}, end: {isNull: false}, end: {lessThanEqual: "${testDate1}"}}`, + }, + { + name: 'returns correct AND filter for multiple id fields with equal, isNull, LTE, and between operators', + introspectionFields: [ + createIntrospectionField('start', TypeName.Date), + createIntrospectionField('end', TypeName.Date), + ], + filter: { + operator: 'AND', + filters: [ + createFieldFilter('start', FieldFilterOperatorEnum.Eq, testDate1), + createFieldFilter('end', FieldFilterOperatorEnum.IsNull, 'False'), + createFieldFilter('end', FieldFilterOperatorEnum.Lte, testDate1), + createFieldFilter('start', FieldFilterOperatorEnum.Between, [ + testDate1, + testDate2, + ]), + ], + }, + expectedResult: `and: {start: {equal: "${testDate1}"}, end: {isNull: false}, end: {lessThanEqual: "${testDate1}"}, start: {between: {from: "${testDate1}", to: "${testDate2}"}}}`, + }, + ]; + + idTestCases.forEach( + ({ name, introspectionFields, filter, expectedResult }) => { + it(`${name}`, () => { + const result = buildFilterCondition( + introspectionFields, + 'ProcessInstance', + filter, + ); + expect(result).toBe(expectedResult); + }); + }, + ); + }); + describe('enumArgument testcases', () => { + const idTestCases: FilterTestCase[] = [ + { + name: 'returns correct filter for state enum field with equal operator', + introspectionFields: [ + createIntrospectionField('state', TypeName.String), + ], + filter: createFieldFilter( + 'state', + FieldFilterOperatorEnum.Eq, + ProcessInstanceStatusDTO.Completed, + ), + expectedResult: `state: {equal: ${ProcessInstanceState.Completed}}`, + }, + ]; + + idTestCases.forEach( + ({ name, introspectionFields, filter, expectedResult }) => { + it(`${name}`, () => { + const result = buildFilterCondition( + introspectionFields, + 'ProcessInstance', + filter, + ); + expect(result).toBe(expectedResult); + }); + }, + ); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/queryBuilder.test.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/queryBuilder.test.ts new file mode 100644 index 00000000..ab77d65a --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/queryBuilder.test.ts @@ -0,0 +1,103 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Pagination } from '../types/pagination'; +import { buildGraphQlQuery } from './queryBuilder'; + +describe('buildGraphQlQuery', () => { + const defaultTestParams = { + queryBody: 'id status', + type: 'ProcessInstances' as + | 'ProcessDefinitions' + | 'ProcessInstances' + | 'Jobs', + pagination: { + offset: 0, + limit: 10, + order: 'asc', + sortField: 'name', + } as Pagination | undefined, + whereClause: 'version: "1.0"', + }; + + const getPaginationString = (pagination: Pagination | undefined) => { + const paginationOrder = pagination?.order + ? pagination.order.toUpperCase() + : 'ASC'; + if (pagination) { + return `orderBy: {${pagination.sortField}: ${paginationOrder}}, pagination: {limit: ${pagination.limit}, offset: ${pagination.offset}})`; + } + return undefined; + }; + + type TestCase = { + name: string; + params: typeof defaultTestParams; + expectedResult: string; + }; + + const testCases: TestCase[] = [ + { + name: 'should build a basic query without where clause and pagination', + params: { + type: defaultTestParams.type, + queryBody: defaultTestParams.queryBody, + whereClause: '', + pagination: {}, + }, + expectedResult: `{${defaultTestParams.type} {${defaultTestParams.queryBody} } }`, + }, + { + name: 'should build a query with a where clause', + params: { + type: defaultTestParams.type, + queryBody: defaultTestParams.queryBody, + whereClause: defaultTestParams.whereClause, + pagination: {}, + }, + expectedResult: `{${defaultTestParams.type} (where: {${defaultTestParams.whereClause}}) {${defaultTestParams.queryBody} } }`, + }, + { + name: 'should build a query with pagination', + params: { + type: defaultTestParams.type, + queryBody: defaultTestParams.queryBody, + whereClause: '', + pagination: defaultTestParams.pagination, + }, + expectedResult: `{${defaultTestParams.type} (${getPaginationString( + defaultTestParams.pagination, + )} {${defaultTestParams.queryBody} } }`, + }, + { + name: 'should build a query with both where clause and pagination', + params: { + ...defaultTestParams, + }, + expectedResult: `{${defaultTestParams.type} (where: {${ + defaultTestParams.whereClause + }}, ${getPaginationString(defaultTestParams.pagination)} {${ + defaultTestParams.queryBody + } } }`, + }, + ]; + + testCases.forEach(({ name, params, expectedResult }) => { + it(`${name}`, () => { + const result = buildGraphQlQuery(params); + expect(result).toBe(expectedResult); + }); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/queryBuilder.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/queryBuilder.ts new file mode 100644 index 00000000..e33d20db --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/helpers/queryBuilder.ts @@ -0,0 +1,69 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Pagination } from '../types/pagination'; + +export function buildGraphQlQuery(args: { + type: 'ProcessDefinitions' | 'ProcessInstances' | 'Jobs'; + queryBody: string; + whereClause?: string; + pagination?: Pagination; +}): string { + let query = `{${args.type}`; + + const whereClause = buildWhereClause(args.whereClause); + const paginationClause = buildPaginationClause(args.pagination); + + if (whereClause || paginationClause) { + query += ' ('; + query += [whereClause, paginationClause].filter(Boolean).join(', '); + query += ') '; + } + + query += ` {${args.queryBody} } }`; + + return query.replace(/\s+/g, ' ').trim(); +} + +function buildWhereClause(whereClause?: string): string { + return whereClause ? `where: {${whereClause}}` : ''; +} + +function buildPaginationClause(pagination?: Pagination): string { + if (!pagination) return ''; + + const parts = []; + + if (pagination.sortField !== undefined) { + parts.push( + `orderBy: {${pagination.sortField}: ${ + pagination.order !== undefined ? pagination.order?.toUpperCase() : 'ASC' + }}`, + ); + } + + const paginationParts = []; + if (pagination.limit !== undefined) { + paginationParts.push(`limit: ${pagination.limit}`); + } + if (pagination.offset !== undefined) { + paginationParts.push(`offset: ${pagination.offset}`); + } + if (paginationParts.length) { + parts.push(`pagination: {${paginationParts.join(', ')}}`); + } + + return parts.join(', '); +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/index.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/index.ts new file mode 100644 index 00000000..70217b20 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { orchestratorPlugin as default } from './plugin'; diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/plugin.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/plugin.ts new file mode 100644 index 00000000..0e1c2314 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/plugin.ts @@ -0,0 +1,63 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; + +import { createRouter } from './routerWrapper'; + +/** + * @public + * Orchestrator Backend Plugin + */ +export const orchestratorPlugin = createBackendPlugin({ + pluginId: 'orchestrator', + register(env) { + env.registerInit({ + deps: { + logger: coreServices.logger, + config: coreServices.rootConfig, + discovery: coreServices.discovery, + catalogApi: catalogServiceRef, + urlReader: coreServices.urlReader, + permissions: coreServices.permissions, + scheduler: coreServices.scheduler, + auth: coreServices.auth, + httpAuth: coreServices.httpAuth, + http: coreServices.httpRouter, + }, + async init(props) { + const { http } = props; + const router = await createRouter(props); + http.use(router); + http.addAuthPolicy({ + path: '/health', + allow: 'unauthenticated', + }); + http.addAuthPolicy({ + path: '/static', + allow: 'unauthenticated', + }); + http.addAuthPolicy({ + path: '/docs', + allow: 'unauthenticated', + }); + }, + }); + }, +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/routerWrapper/index.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/routerWrapper/index.ts new file mode 100644 index 00000000..a98bf2e6 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/routerWrapper/index.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { + AuthService, + DiscoveryService, + HttpAuthService, + LoggerService, + PermissionsService, + SchedulerService, + UrlReaderService, +} from '@backstage/backend-plugin-api'; +import type { CatalogApi } from '@backstage/catalog-client'; +import type { Config } from '@backstage/config'; + +import express from 'express'; + +import { DevModeService } from '../service/DevModeService'; +import { createBackendRouter } from '../service/router'; + +export interface RouterOptions { + config: Config; + logger: LoggerService; + discovery: DiscoveryService; + catalogApi: CatalogApi; + urlReader: UrlReaderService; + scheduler: SchedulerService; + permissions: PermissionsService; + httpAuth: HttpAuthService; + auth: AuthService; +} + +export async function createRouter( + args: RouterOptions, +): Promise { + const autoStartDevMode = + args.config.getOptionalBoolean( + 'orchestrator.sonataFlowService.autoStart', + ) ?? false; + + if (autoStartDevMode) { + const devModeService = new DevModeService(args.config, args.logger); + + const isSonataFlowUp = await devModeService.launchDevMode(); + + if (!isSonataFlowUp) { + args.logger.error('SonataFlow is not up. Check your configuration.'); + } + } + + return await createBackendRouter({ + config: args.config, + logger: args.logger, + discovery: args.discovery, + catalogApi: args.catalogApi, + urlReader: args.urlReader, + scheduler: args.scheduler, + permissions: args.permissions, + httpAuth: args.httpAuth, + auth: args.auth, + }); +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/DataIndexService.test.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/DataIndexService.test.ts new file mode 100644 index 00000000..ffffd571 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/DataIndexService.test.ts @@ -0,0 +1,807 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerService } from '@backstage/backend-plugin-api'; + +import { Client, OperationResult } from '@urql/core'; + +import { + FieldFilter, + FieldFilterOperatorEnum, + LogicalFilter, + NodeInstance, + ProcessInstance, + TypeKind, + TypeName, + WorkflowInfo, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import * as buildGrahQLFilterUtils from '../helpers/filterBuilder'; +import * as buildGrahQLQueryUtils from '../helpers/queryBuilder'; +import { Pagination } from '../types/pagination'; +import { + mockProcessDefinitionArguments, + mockProcessDefinitionIntrospection, +} from './__fixtures__/mockProcessDefinitionArgumentsData'; +import { + mockProcessInstanceArguments, + mockProcessInstanceIntrospection, +} from './__fixtures__/mockProcessInstanceArgumentsData'; +import { DataIndexService } from './DataIndexService'; + +jest.mock('../helpers/queryBuilder', () => { + return { + __esModule: true, + ...jest.requireActual('../helpers/queryBuilder'), + }; +}); + +jest.mock('../helpers/filterBuilder', () => { + return { + __esModule: true, + ...jest.requireActual('../helpers/filterBuilder'), + }; +}); + +jest.mock('@urql/core', () => { + return { + Client: jest.fn().mockImplementation(() => ({ + query: jest.fn(), + })), + }; +}); + +const mockOperationResult = (data: T, error?: any): OperationResult => ({ + data, + error, + operation: {} as any, + extensions: {}, + hasNext: false, + stale: false, +}); + +const mockWfInfos: WorkflowInfo[] = [ + { + id: '9fa2a881-c932-468d-83a9-687b9f1e62a7', + nodes: [createNodeObject('A'), createNodeObject('B')], + }, +]; + +const createQueryArgs = ( + type: 'ProcessDefinitions' | 'ProcessInstances' | 'Jobs', + queryBody: string, + whereClause?: string, + pagination?: Pagination, +) => ({ + type, + queryBody, + whereClause, + pagination, +}); + +describe('initInputArgs', () => { + type MockableClient = Pick; + const createMockClient = (): jest.Mocked => ({ + query: jest.fn(), + }); + + let loggerMock: LoggerService; + let dataIndexService: DataIndexService; + let mockClient: jest.Mocked; + + beforeEach(() => { + jest.resetAllMocks(); + jest.clearAllMocks(); + // Create a new mock client for each test + mockClient = createMockClient(); + (Client as jest.MockedClass).mockImplementation( + () => mockClient as unknown as Client, + ); + + loggerMock = { + info: jest.fn(), + debug: jest.fn(), + error: jest.fn(), + warn: jest.fn(), + child: jest.fn(), + }; + mockClient.query.mockResolvedValueOnce( + mockOperationResult(mockProcessDefinitionArguments), + ); + dataIndexService = new DataIndexService('fakeUrl', loggerMock); + }); + + it('ProcessDefinition', async () => { + const processDefinitionArguments = + await dataIndexService.initInputProcessDefinitionArgs(); + + expect(mockClient.query).toHaveBeenCalledTimes(1); + expect(mockClient.query).toHaveBeenCalledWith( + dataIndexService.graphQLArgumentQuery('ProcessDefinition'), + {}, + ); + + expect(processDefinitionArguments).toBeDefined(); + expect( + processDefinitionArguments.every( + val => !['and', 'or', 'not'].includes(val.name), + ), + ).toBe(true); + expect(processDefinitionArguments).toHaveLength(3); + expect( + processDefinitionArguments.some( + obj => + obj.name === 'id' && + obj.type.kind === TypeKind.InputObject && + obj.type.name === TypeName.String, + ), + ).toBe(true); + expect( + processDefinitionArguments.some( + obj => + obj.name === 'name' && + obj.type.kind === TypeKind.InputObject && + obj.type.name === TypeName.String, + ), + ).toBe(true); + expect( + processDefinitionArguments.some( + obj => + obj.name === 'version' && + obj.type.kind === TypeKind.InputObject && + obj.type.name === TypeName.String, + ), + ).toBe(true); + }); +}); + +describe('fetchWorkflowInfos', () => { + let loggerMock: LoggerService; + let buildFilterConditionSpy: any; + let buildGraphQlQuerySpy: jest.SpyInstance; + let dataIndexService: DataIndexService; + let mockClient: jest.Mocked; + + const definitionIds = ['id1', 'id2']; + const queryBody = 'id, name, version, type, endpoint, serviceUrl, source'; + const pagination = { limit: 10, offset: 0, order: 'ASC', sortField: 'name' }; + + const filterString = + 'or: {name: {equal: "Hello World Workflow"}, id: {equal: "yamlgreet"}}'; + + const helloWorldFilter = { + field: 'name', + operator: FieldFilterOperatorEnum.Eq, + value: 'Hello World Workflow', + }; + const greetingFilter = { + field: 'id', + operator: FieldFilterOperatorEnum.Eq, + value: 'yamlgreet', + }; + + const logicalFilter: LogicalFilter = { + operator: 'OR', + filters: [helloWorldFilter, greetingFilter], + }; + + beforeEach(() => { + jest.clearAllMocks(); + + mockClient = { + query: jest.fn(), + } as any; + + (Client as jest.Mock).mockImplementation(() => mockClient); + + loggerMock = { + info: jest.fn(), + debug: jest.fn(), + error: jest.fn(), + warn: jest.fn(), + child: jest.fn(), + }; + + dataIndexService = new DataIndexService('fakeUrl', loggerMock); + + // Set up spies on the graphql utility functions + buildGraphQlQuerySpy = jest.spyOn( + buildGrahQLQueryUtils, + 'buildGraphQlQuery', + ); + buildFilterConditionSpy = jest.spyOn( + buildGrahQLFilterUtils, + 'buildFilterCondition', + ); + }); + it('should fetch workflow infos with no parameters', async () => { + // Given + const mockQueryResult = { + ProcessDefinitions: mockWfInfos, + }; + mockClient.query.mockResolvedValueOnce( + mockOperationResult(mockQueryResult), + ); + + const expectedQueryArgs = createQueryArgs('ProcessDefinitions', queryBody); + // When + const result = await dataIndexService.fetchWorkflowInfos({}); + // Then + expect(result).toBeDefined(); + expect(result).toBe(mockQueryResult.ProcessDefinitions); + expect(buildFilterConditionSpy).not.toHaveBeenCalled(); + expect(buildGraphQlQuerySpy).toHaveBeenCalledTimes(1); + expect(buildGraphQlQuerySpy).toHaveBeenCalledWith({ + type: 'ProcessDefinitions', + queryBody, + }); + expect(mockClient.query).toHaveBeenCalled(); + expect(mockClient.query).toHaveBeenCalledWith( + buildGrahQLQueryUtils.buildGraphQlQuery(expectedQueryArgs), + {}, + ); + }); + + it('should fetch workflow infos with definitionIds', async () => { + // Given + const whereClause = `id: {in: ${JSON.stringify(definitionIds)}}`; + const mockQueryResult = { + ProcessDefinitions: mockWfInfos, + }; + mockClient.query.mockResolvedValueOnce( + mockOperationResult(mockQueryResult), + ); + + const expectedQueryArgs = createQueryArgs( + 'ProcessDefinitions', + queryBody, + whereClause, + ); + // When + const result = await dataIndexService.fetchWorkflowInfos({ + definitionIds, + }); + + // Then + expect(result).toBeDefined(); + expect(result).toBe(mockQueryResult.ProcessDefinitions); + expect(buildGraphQlQuerySpy).toHaveBeenCalledTimes(1); + expect(buildGraphQlQuerySpy).toHaveBeenCalledWith({ + type: 'ProcessDefinitions', + queryBody, + whereClause, + }); + expect(buildFilterConditionSpy).not.toHaveBeenCalled(); + expect(mockClient.query).toHaveBeenCalled(); + expect(mockClient.query).toHaveBeenCalledWith( + buildGrahQLQueryUtils.buildGraphQlQuery(expectedQueryArgs), + {}, + ); + }); + + it('should fetch workflow infos with definitionIds and pagination', async () => { + // Given + const mockQueryResult = { + ProcessDefinitions: mockWfInfos, + }; + mockClient.query.mockResolvedValueOnce( + mockOperationResult(mockQueryResult), + ); + + const expectedQueryArgs = createQueryArgs( + 'ProcessDefinitions', + queryBody, + `id: {in: ${JSON.stringify(definitionIds)}}`, + pagination, + ); + // When + const result = await dataIndexService.fetchWorkflowInfos({ + definitionIds, + pagination, + }); + + // Then + expect(result).toBeDefined(); + expect(result).toBe(mockQueryResult.ProcessDefinitions); + expect(buildGraphQlQuerySpy).toHaveBeenCalledTimes(1); + expect(buildGraphQlQuerySpy).toHaveBeenCalledWith({ + type: 'ProcessDefinitions', + queryBody, + whereClause: `id: {in: ${JSON.stringify(definitionIds)}}`, + pagination, + }); + expect(mockClient.query).toHaveBeenCalledTimes(1); + expect(mockClient.query).toHaveBeenCalledWith( + buildGrahQLQueryUtils.buildGraphQlQuery(expectedQueryArgs), + {}, + ); + expect(buildFilterConditionSpy).not.toHaveBeenCalled(); + }); + + it('should fetch workflow infos with only filter', async () => { + // Given + const mockQueryResult = { + ProcessDefinitions: mockWfInfos, + }; + mockClient.query + .mockResolvedValueOnce( + mockOperationResult(mockProcessDefinitionArguments), + ) + .mockResolvedValueOnce(mockOperationResult(mockQueryResult)); + + const expectedQueryArgs = createQueryArgs( + 'ProcessDefinitions', + queryBody, + filterString, + ); + + // When + const result = await dataIndexService.fetchWorkflowInfos({ + filter: logicalFilter, + }); + + // Then + expect(result).toBeDefined(); + expect(result).toBe(mockQueryResult.ProcessDefinitions); + + expect(buildGraphQlQuerySpy).toHaveBeenCalledTimes(1); + expect(buildGraphQlQuerySpy).toHaveBeenCalledWith({ + type: 'ProcessDefinitions', + queryBody, + whereClause: filterString, + }); + expect(buildFilterConditionSpy).toHaveBeenCalledTimes(1); + expect(buildFilterConditionSpy).toHaveBeenCalledWith( + mockProcessDefinitionIntrospection, + 'ProcessDefinition', + logicalFilter, + ); + expect(mockClient.query).toHaveBeenCalledTimes(2); + expect(mockClient.query).toHaveBeenCalledWith( + buildGrahQLQueryUtils.buildGraphQlQuery(expectedQueryArgs), + {}, + ); + }); + + it('should fetch workflow infos with definitionIds and filter', async () => { + // Given + const whereClause = `and: [{id: {in: ${JSON.stringify( + definitionIds, + )}}}, {${filterString}}]`; + // Given + const mockQueryResult = { + ProcessDefinitions: mockWfInfos, + }; + mockClient.query + .mockResolvedValueOnce( + mockOperationResult(mockProcessDefinitionArguments), + ) + .mockResolvedValueOnce(mockOperationResult(mockQueryResult)); + + const expectedQueryArgs = createQueryArgs( + 'ProcessDefinitions', + queryBody, + whereClause, + ); + + // When + const result = await dataIndexService.fetchWorkflowInfos({ + definitionIds, + filter: logicalFilter, + }); + + // Then + + expect(buildGraphQlQuerySpy).toHaveBeenCalledTimes(1); + expect(buildGraphQlQuerySpy).toHaveBeenCalledWith({ + type: 'ProcessDefinitions', + queryBody: 'id, name, version, type, endpoint, serviceUrl, source', + whereClause, + }); + expect(buildFilterConditionSpy).toHaveBeenCalledTimes(1); + expect(buildFilterConditionSpy).toHaveBeenCalledWith( + mockProcessDefinitionIntrospection, + 'ProcessDefinition', + logicalFilter, + ); + expect(mockClient.query).toHaveBeenCalledTimes(2); + expect(mockClient.query).toHaveBeenCalledWith( + buildGrahQLQueryUtils.buildGraphQlQuery(expectedQueryArgs), + {}, + ); + expect(result).toBeDefined(); + expect(result).toBe(mockQueryResult.ProcessDefinitions); + }); + + it('should fetch workflow infos with definitionIds, pagination, and filter', async () => { + // Given + const whereClause = `and: [{id: {in: ${JSON.stringify( + definitionIds, + )}}}, {${filterString}}]`; + // Given + const mockQueryResult = { + ProcessDefinitions: mockWfInfos, + }; + mockClient.query + .mockResolvedValueOnce( + mockOperationResult(mockProcessDefinitionArguments), + ) + .mockResolvedValueOnce(mockOperationResult(mockQueryResult)); + + const expectedQueryArgs = createQueryArgs( + 'ProcessDefinitions', + queryBody, + whereClause, + pagination, + ); + // When + const result = await dataIndexService.fetchWorkflowInfos({ + definitionIds, + pagination, + filter: logicalFilter, + }); + + // Then + + expect(mockClient.query).toHaveBeenCalledTimes(2); + expect(mockClient.query).toHaveBeenCalledWith( + buildGrahQLQueryUtils.buildGraphQlQuery(expectedQueryArgs), + {}, + ); + expect(buildGraphQlQuerySpy).toHaveBeenCalledTimes(2); + expect(buildGraphQlQuerySpy).toHaveBeenCalledWith({ + type: 'ProcessDefinitions', + queryBody, + whereClause, + pagination, + }); + expect(buildFilterConditionSpy).toHaveBeenCalledTimes(1); + expect(buildFilterConditionSpy).toHaveBeenCalledWith( + mockProcessDefinitionIntrospection, + 'ProcessDefinition', + logicalFilter, + ); + expect(result).toBeDefined(); + expect(result).toBe(mockQueryResult.ProcessDefinitions); + }); +}); +describe('fetchInstances', () => { + let loggerMock: LoggerService; + let buildFilterConditionSpy: any; + let buildGraphQlQuerySpy: any; + let mockClient: jest.Mocked; + + let dataIndexService: DataIndexService; + + const definitionIds = ['id', 'name']; + const pagination = { limit: 10, offset: 0, order: 'ASC', sortField: 'name' }; + + const processIdNotNullCondition = 'processId: {isNull: false}'; + const processIdDefinitions = `processId: {in: ${JSON.stringify( + definitionIds, + )}`; + const queryBody = + 'id, processName, processId, businessKey, state, start, end, nodes { id }, variables, parentProcessInstance {id, processName, businessKey}'; + + const mockProcessInstances: ProcessInstance[] = [ + { + id: 'id', + processId: 'processId1', + endpoint: 'endpoint1', + nodes: [createNodeObject('A'), createNodeObject('B')], + }, + { + id: 'name', + processId: 'processId2', + endpoint: 'endpoint2', + nodes: [createNodeObject('C'), createNodeObject('D')], + }, + ]; + + const filterString = + 'or: {processId: {equal: "processId1"}, processName: {like: "processName%"}}'; + + const procName1Filter: FieldFilter = { + field: 'processName', + operator: FieldFilterOperatorEnum.Like, + value: 'processName%', + }; + const procId1Filter: FieldFilter = { + field: 'processId', + operator: FieldFilterOperatorEnum.Eq, + value: 'processId1', + }; + + const logicalFilter: LogicalFilter = { + operator: 'OR', + filters: [procId1Filter, procName1Filter], + }; + const mockQueryResult = { ProcessInstances: mockProcessInstances }; + + beforeEach(() => { + mockClient = { + query: jest.fn(), + } as any; + + (Client as jest.Mock).mockImplementation(() => mockClient); + + const wfInfo: WorkflowInfo = { + id: 'wfinfo1', + source: 'workflow info source', + }; + + loggerMock = { + info: jest.fn(), + debug: jest.fn(), + error: jest.fn(), + warn: jest.fn(), + child: jest.fn(), + }; + dataIndexService = new DataIndexService('fakeUrl', loggerMock); + // Create a spy for method1 + jest.spyOn(dataIndexService, 'fetchWorkflowInfo').mockResolvedValue(wfInfo); + // Set up spies on the graphql utility functions + buildGraphQlQuerySpy = jest.spyOn( + buildGrahQLQueryUtils, + 'buildGraphQlQuery', + ); + buildFilterConditionSpy = jest.spyOn( + buildGrahQLFilterUtils, + 'buildFilterCondition', + ); + + // Clear mocks before each test + jest.clearAllMocks(); + }); + it('should fetch instances with no parameters', async () => { + // Given + const whereClause = processIdNotNullCondition; + mockClient.query.mockResolvedValueOnce( + mockOperationResult(mockQueryResult), + ); + + const expectedQueryArgs = createQueryArgs( + 'ProcessInstances', + queryBody, + whereClause, + ); + + // When + const result = await dataIndexService.fetchInstances({}); + + // Then + expect(result).toBeDefined(); + expect(result).toStrictEqual(mockQueryResult.ProcessInstances); + + expect(buildGraphQlQuerySpy).toHaveBeenCalledTimes(1); + expect(buildGraphQlQuerySpy).toHaveBeenCalledWith({ + type: 'ProcessInstances', + queryBody, + whereClause, + }); + expect(buildFilterConditionSpy).not.toHaveBeenCalled(); + expect(mockClient.query).toHaveBeenCalled(); + expect(mockClient.query).toHaveBeenCalledWith( + buildGrahQLQueryUtils.buildGraphQlQuery(expectedQueryArgs), + {}, + ); + }); + + it('should fetch instances with definitionIds', async () => { + // Given + const whereClause = `and: [{${processIdNotNullCondition}}, {${processIdDefinitions}}}]`; + + mockClient.query.mockResolvedValueOnce( + mockOperationResult(mockQueryResult), + ); + + const expectedQueryArgs = createQueryArgs( + 'ProcessInstances', + queryBody, + whereClause, + ); + // When + const result = await dataIndexService.fetchInstances({ + definitionIds, + }); + + // Then + expect(buildGraphQlQuerySpy).toHaveBeenCalledTimes(1); + expect(buildGraphQlQuerySpy).toHaveBeenCalledWith({ + type: 'ProcessInstances', + queryBody, + whereClause, + pagination: undefined, + }); + expect(buildFilterConditionSpy).not.toHaveBeenCalled(); + expect(mockClient.query).toHaveBeenCalled(); + expect(mockClient.query).toHaveBeenCalledWith( + buildGrahQLQueryUtils.buildGraphQlQuery(expectedQueryArgs), + {}, + ); + expect(result).toBeDefined(); + expect(result).toStrictEqual(mockQueryResult.ProcessInstances); + }); + + it('should fetch instances with definitionIds and pagination', async () => { + // Given + const whereClause = `and: [{${processIdNotNullCondition}}, {${processIdDefinitions}}}]`; + mockClient.query.mockResolvedValueOnce( + mockOperationResult(mockQueryResult), + ); + + const expectedQueryArgs = createQueryArgs( + 'ProcessInstances', + queryBody, + whereClause, + pagination, + ); + // When + const result = await dataIndexService.fetchInstances({ + definitionIds, + + pagination, + }); + + // Then + expect(result).toBeDefined(); + expect(result).toStrictEqual(mockQueryResult.ProcessInstances); + + expect(buildGraphQlQuerySpy).toHaveBeenCalledTimes(1); + expect(buildGraphQlQuerySpy).toHaveBeenCalledWith({ + type: 'ProcessInstances', + queryBody, + whereClause, + pagination, + }); + expect(buildFilterConditionSpy).not.toHaveBeenCalled(); + expect(mockClient.query).toHaveBeenCalledTimes(1); + expect(mockClient.query).toHaveBeenCalledWith( + buildGrahQLQueryUtils.buildGraphQlQuery(expectedQueryArgs), + {}, + ); + }); + + it('should fetch instances with only filter', async () => { + // Given + const whereClause = `and: [{${processIdNotNullCondition}}, {${filterString}}]`; + mockClient.query + .mockResolvedValueOnce(mockOperationResult(mockProcessInstanceArguments)) + .mockResolvedValueOnce(mockOperationResult(mockQueryResult)); + + const expectedQueryArgs = createQueryArgs( + 'ProcessInstances', + queryBody, + whereClause, + ); + // When + const result = await dataIndexService.fetchInstances({ + filter: logicalFilter, + }); + + // Then + expect(result).toBeDefined(); + expect(result).toStrictEqual(mockQueryResult.ProcessInstances); + expect(buildGraphQlQuerySpy).toHaveBeenCalledTimes(1); + expect(buildGraphQlQuerySpy).toHaveBeenCalledWith({ + type: 'ProcessInstances', + queryBody, + whereClause, + }); + expect(buildFilterConditionSpy).toHaveBeenCalledTimes(1); + expect(buildFilterConditionSpy).toHaveBeenCalledWith( + mockProcessInstanceIntrospection, + 'ProcessInstance', + logicalFilter, + ); + expect(mockClient.query).toHaveBeenCalledTimes(2); + expect(mockClient.query).toHaveBeenCalledWith( + buildGrahQLQueryUtils.buildGraphQlQuery(expectedQueryArgs), + {}, + ); + }); + + it('should fetch instances with definitionIds and filter', async () => { + // Given + const whereClause = `and: [{${processIdNotNullCondition}}, {${processIdDefinitions}}}, {${filterString}}]`; + mockClient.query + .mockResolvedValueOnce(mockOperationResult(mockProcessInstanceArguments)) + .mockResolvedValueOnce(mockOperationResult(mockQueryResult)); + const expectedQueryArgs = createQueryArgs( + 'ProcessInstances', + queryBody, + whereClause, + ); + // When + const result = await dataIndexService.fetchInstances({ + definitionIds, + filter: logicalFilter, + }); + + // Then + expect(buildGraphQlQuerySpy).toHaveBeenCalledTimes(1); + expect(buildGraphQlQuerySpy).toHaveBeenCalledWith({ + type: 'ProcessInstances', + queryBody, + whereClause, + }); + expect(buildFilterConditionSpy).toHaveBeenCalledTimes(1); + expect(buildFilterConditionSpy).toHaveBeenCalledWith( + mockProcessInstanceIntrospection, + 'ProcessInstance', + logicalFilter, + ); + expect(mockClient.query).toHaveBeenCalledTimes(2); + expect(mockClient.query).toHaveBeenCalledWith( + buildGrahQLQueryUtils.buildGraphQlQuery(expectedQueryArgs), + {}, + ); + expect(result).toBeDefined(); + expect(result).toStrictEqual(mockQueryResult.ProcessInstances); + }); + + it('should fetch instances with definitionIds, pagination, and filter', async () => { + // Given + const whereClause = `and: [{${processIdNotNullCondition}}, {${processIdDefinitions}}}, {${filterString}}]`; + mockClient.query + .mockResolvedValueOnce(mockOperationResult(mockProcessInstanceArguments)) + .mockResolvedValueOnce(mockOperationResult(mockQueryResult)); + const expectedQueryArgs = createQueryArgs( + 'ProcessInstances', + queryBody, + whereClause, + pagination, + ); + // When + const result = await dataIndexService.fetchInstances({ + definitionIds, + pagination, + filter: logicalFilter, + }); + + // Then + expect(buildGraphQlQuerySpy).toHaveBeenCalledTimes(1); + expect(buildGraphQlQuerySpy).toHaveBeenCalledWith({ + type: 'ProcessInstances', + queryBody, + whereClause, + pagination, + }); + expect(buildFilterConditionSpy).toHaveBeenCalledTimes(1); + expect(buildFilterConditionSpy).toHaveBeenCalledWith( + mockProcessInstanceIntrospection, + 'ProcessInstance', + logicalFilter, + ); + expect(mockClient.query).toHaveBeenCalledTimes(2); + expect(mockClient.query).toHaveBeenCalledWith( + buildGrahQLQueryUtils.buildGraphQlQuery(expectedQueryArgs), + {}, + ); + expect(result).toBeDefined(); + expect(result).toStrictEqual(mockQueryResult.ProcessInstances); + }); +}); + +function createNodeObject(suffix: string): NodeInstance { + return { + id: `node${suffix}`, + name: `node${suffix}`, + enter: new Date('2024-08-01T14:30:00').toISOString(), + type: 'NodeType', + definitionId: `definitionId${suffix}`, + nodeId: `nodeId${suffix}`, + }; +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/DataIndexService.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/DataIndexService.ts new file mode 100644 index 00000000..37949e55 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/DataIndexService.ts @@ -0,0 +1,566 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerService } from '@backstage/backend-plugin-api'; + +import { Client, fetchExchange, gql } from '@urql/core'; + +import { + Filter, + fromWorkflowSource, + getWorkflowCategory, + IntrospectionField, + parseWorkflowVariables, + ProcessInstance, + WorkflowDefinition, + WorkflowInfo, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import { ErrorBuilder } from '../helpers/errorBuilder'; +import { buildFilterCondition } from '../helpers/filterBuilder'; +import { buildGraphQlQuery } from '../helpers/queryBuilder'; +import { Pagination } from '../types/pagination'; +import { FETCH_PROCESS_INSTANCES_SORT_FIELD } from './constants'; + +export class DataIndexService { + private readonly client: Client; + public processDefinitionArguments: IntrospectionField[] = []; + public processInstanceArguments: IntrospectionField[] = []; + + public constructor( + private readonly dataIndexUrl: string, + private readonly logger: LoggerService, + ) { + if (!dataIndexUrl.length) { + throw ErrorBuilder.GET_NO_DATA_INDEX_URL_ERR(); + } + + this.client = this.getNewGraphQLClient(); + } + + private getNewGraphQLClient(): Client { + const diURL = `${this.dataIndexUrl}/graphql`; + return new Client({ + url: diURL, + exchanges: [fetchExchange], + }); + } + + public async initInputProcessDefinitionArgs(): Promise { + if (this.processDefinitionArguments.length === 0) { + this.processDefinitionArguments = await this.inspectInputArgument( + 'ProcessDefinition', + ); + } + return this.processDefinitionArguments; // For testing purposes + } + + public graphQLArgumentQuery(type: string): string { + return `query ${type}Argument { + __type(name: "${type}Argument") { + kind + name + inputFields { + name + type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + }`; + } + + public async inspectInputArgument( + type: string, + ): Promise { + const result = await this.client.query(this.graphQLArgumentQuery(type), {}); + + this.logger.debug(`Introspection query result: ${JSON.stringify(result)}`); + + if (result?.error) { + this.logger.error(`Error executing introspection query ${result.error}`); + throw result.error; + } + + const pairs: IntrospectionField[] = []; + if (result?.data?.__type?.inputFields) { + for (const field of result.data.__type.inputFields) { + if ( + field.name !== 'and' && + field.name !== 'or' && + field.name !== 'not' + ) { + pairs.push({ + name: field.name, + type: { + name: field.type.name, + kind: field.type.kind, + ofType: field.type.ofType, + }, + }); + } + } + } + return pairs; + } + + public async abortWorkflowInstance(instanceId: string): Promise { + this.logger.info(`Aborting workflow instance ${instanceId}`); + const ProcessInstanceAbortMutationDocument = gql` + mutation ProcessInstanceAbortMutation($id: String) { + ProcessInstanceAbort(id: $id) + } + `; + + const result = await this.client.mutation( + ProcessInstanceAbortMutationDocument, + { id: instanceId }, + ); + + this.logger.debug( + `Abort workflow instance result: ${JSON.stringify(result)}`, + ); + + if (result.error) { + throw new Error( + `Error aborting workflow instance ${instanceId}: ${result.error}`, + ); + } + this.logger.debug(`Successfully aborted workflow instance ${instanceId}`); + } + + public async fetchWorkflowInfo( + definitionId: string, + ): Promise { + const graphQlQuery = `{ ProcessDefinitions ( where: {id: {equal: "${definitionId}" } } ) { id, name, version, type, endpoint, serviceUrl, source } }`; + + const result = await this.client.query(graphQlQuery, {}); + + this.logger.debug( + `Get workflow definition result: ${JSON.stringify(result)}`, + ); + + if (result.error) { + this.logger.error(`Error fetching workflow definition ${result.error}`); + throw result.error; + } + + const processDefinitions = result.data.ProcessDefinitions as WorkflowInfo[]; + + if (processDefinitions.length === 0) { + this.logger.info(`No workflow definition found for ${definitionId}`); + return undefined; + } + + return processDefinitions[0]; + } + + public async fetchWorkflowServiceUrls(): Promise> { + const graphQlQuery = `{ ProcessDefinitions { id, serviceUrl } }`; + + const result = await this.client.query(graphQlQuery, {}); + + this.logger.debug( + `Get workflow service urls result: ${JSON.stringify(result)}`, + ); + + if (result.error) { + this.logger.error(`Error fetching workflow service urls ${result.error}`); + throw result.error; + } + + const processDefinitions = result.data.ProcessDefinitions as WorkflowInfo[]; + return processDefinitions + .filter(definition => definition.serviceUrl) + .map(definition => ({ [definition.id]: definition.serviceUrl! })) + .reduce((acc, curr) => ({ ...acc, ...curr }), {}); + } + + public async fetchWorkflowInfos(args: { + definitionIds?: string[]; + pagination?: Pagination; + filter?: Filter; + }): Promise { + this.logger.info(`fetchWorkflowInfos() called: ${this.dataIndexUrl}`); + const { definitionIds, pagination, filter } = args; + + const definitionIdsCondition = + definitionIds !== undefined && definitionIds.length > 0 + ? `id: {in: ${JSON.stringify(definitionIds)}}` + : undefined; + + const filterCondition = filter + ? buildFilterCondition( + await this.initInputProcessDefinitionArgs(), + 'ProcessDefinition', + filter, + ) + : undefined; + + let whereClause: string | undefined; + if (definitionIds && filter) { + whereClause = `and: [{${definitionIdsCondition}}, {${filterCondition}}]`; + } else if (definitionIdsCondition || filterCondition) { + whereClause = definitionIdsCondition ?? filterCondition; + } else { + whereClause = undefined; + } + + const graphQlQuery = buildGraphQlQuery({ + type: 'ProcessDefinitions', + queryBody: 'id, name, version, type, endpoint, serviceUrl, source', + whereClause, + pagination, + }); + this.logger.debug(`GraphQL query: ${graphQlQuery}`); + const result = await this.client.query(graphQlQuery, {}); + this.logger.debug( + `Get workflow definitions result: ${JSON.stringify(result)}`, + ); + + if (result.error) { + this.logger.error( + `Error fetching data index swf results ${result.error}`, + ); + throw result.error; + } + + return result.data.ProcessDefinitions; + } + + public async fetchInstances(args: { + definitionIds?: string[]; + pagination?: Pagination; + filter?: Filter; + }): Promise { + const { pagination, definitionIds, filter } = args; + if (pagination) pagination.sortField ??= FETCH_PROCESS_INSTANCES_SORT_FIELD; + + const processIdNotNullCondition = 'processId: {isNull: false}'; + const definitionIdsCondition = definitionIds + ? `processId: {in: ${JSON.stringify(definitionIds)}}` + : undefined; + const type = 'ProcessInstance'; + const filterCondition = filter + ? buildFilterCondition( + await this.inspectInputArgument(type), + type, + filter, + ) + : ''; + + let whereClause = ''; + const conditions = []; + + if (processIdNotNullCondition) { + conditions.push(`{${processIdNotNullCondition}}`); + } + + if (definitionIdsCondition) { + conditions.push(`{${definitionIdsCondition}}`); + } + + if (filter) { + conditions.push(`{${filterCondition}}`); + } + + if (conditions.length === 0) { + whereClause = processIdNotNullCondition; + } else if (conditions.length === 1) { + whereClause = conditions[0].slice(1, -1); // Remove the outer braces + } else if (conditions.length > 1) { + whereClause = `and: [${conditions.join(', ')}]`; + } + + const graphQlQuery = buildGraphQlQuery({ + type: 'ProcessInstances', + queryBody: + 'id, processName, processId, businessKey, state, start, end, nodes { id }, variables, parentProcessInstance {id, processName, businessKey}', + whereClause, + pagination, + }); + + this.logger.debug(`GraphQL query: ${graphQlQuery}`); + + const result = await this.client.query(graphQlQuery, {}); + + this.logger.debug( + `Fetch process instances result: ${JSON.stringify(result)}`, + ); + + const processInstancesSrc = result.data + .ProcessInstances as ProcessInstance[]; + + const processInstances = await Promise.all( + processInstancesSrc.map(async instance => { + return await this.getWorkflowDefinitionFromInstance(instance); + }), + ); + return processInstances; + } + + public async fetchInstancesTotalCount( + definitionIds?: string[], + filter?: Filter, + ): Promise { + const definitionIdsCondition = definitionIds + ? `processId: {in: ${JSON.stringify(definitionIds)}}` + : undefined; + this.initInputProcessDefinitionArgs(); + const filterCondition = filter + ? buildFilterCondition( + await this.inspectInputArgument('ProcessInstance'), + 'ProcessInstance', + filter, + ) + : ''; + + let whereClause: string | undefined; + if (definitionIds && filter) { + whereClause = `and: [{${definitionIdsCondition}}, {${filterCondition}}]`; + } else if (definitionIdsCondition || filterCondition) { + whereClause = definitionIdsCondition ?? filterCondition; + } + + const graphQlQuery = buildGraphQlQuery({ + type: 'ProcessInstances', + queryBody: 'id', + whereClause, + }); + this.logger.debug(`GraphQL query: ${graphQlQuery}`); + + const result = await this.client.query(graphQlQuery, {}); + + if (result.error) { + this.logger.error( + `Error when fetching instances total count: ${result.error}`, + ); + throw result.error; + } + + const idArr = result.data.ProcessInstances as ProcessInstance[]; + + return idArr.length; + } + + private async getWorkflowDefinitionFromInstance(instance: ProcessInstance) { + const workflowInfo = await this.fetchWorkflowInfo(instance.processId); + if (!workflowInfo?.source) { + throw new Error( + `Workflow defintion is required to fetch instance ${instance.id}`, + ); + } + const workflowDefinitionSrc: WorkflowDefinition = fromWorkflowSource( + workflowInfo.source, + ); + if (workflowInfo) { + instance.category = getWorkflowCategory(workflowDefinitionSrc); + instance.description = workflowInfo.description; + } + return instance; + } + + public async fetchWorkflowSource( + definitionId: string, + ): Promise { + const graphQlQuery = `{ ProcessDefinitions ( where: {id: {equal: "${definitionId}" } } ) { id, source } }`; + + const result = await this.client.query(graphQlQuery, {}); + + this.logger.debug( + `Fetch workflow source result: ${JSON.stringify(result)}`, + ); + + if (result.error) { + this.logger.error(`Error when fetching workflow source: ${result.error}`); + return undefined; + } + + const processDefinitions = result.data.ProcessDefinitions as WorkflowInfo[]; + + if (processDefinitions.length === 0) { + this.logger.info(`No workflow source found for ${definitionId}`); + return undefined; + } + + return processDefinitions[0].source; + } + + public async fetchInstancesByDefinitionId(args: { + definitionId: string; + limit: number; + offset: number; + }): Promise { + const graphQlQuery = `{ ProcessInstances(where: {processId: {equal: "${args.definitionId}" } }, pagination: {limit: ${args.limit}, offset: ${args.offset}}) { id, processName, state, start, end } }`; + + const result = await this.client.query(graphQlQuery, {}); + + this.logger.debug( + `Fetch workflow instances result: ${JSON.stringify(result)}`, + ); + + if (result.error) { + this.logger.error( + `Error when fetching workflow instances: ${result.error}`, + ); + throw result.error; + } + + return result.data.ProcessInstances; + } + + public async fetchInstanceVariables( + instanceId: string, + ): Promise { + const graphQlQuery = `{ ProcessInstances (where: { id: {equal: "${instanceId}" } } ) { variables } }`; + + const result = await this.client.query(graphQlQuery, {}); + + this.logger.debug( + `Fetch process instance variables result: ${JSON.stringify(result)}`, + ); + + if (result.error) { + this.logger.error( + `Error when fetching process instance variables: ${result.error}`, + ); + throw result.error; + } + + const processInstances = result.data.ProcessInstances as ProcessInstance[]; + + if (processInstances.length === 0) { + return undefined; + } + + return parseWorkflowVariables(processInstances[0].variables as object); + } + + public async fetchDefinitionIdByInstanceId( + instanceId: string, + ): Promise { + const graphQlQuery = `{ ProcessInstances (where: { id: {equal: "${instanceId}" } } ) { processId } }`; + + const result = await this.client.query(graphQlQuery, {}); + + this.logger.debug( + `Fetch process id from instance result: ${JSON.stringify(result)}`, + ); + + if (result.error) { + this.logger.error( + `Error when fetching process id from instance: ${result.error}`, + ); + throw result.error; + } + + const processInstances = result.data.ProcessInstances as ProcessInstance[]; + + if (processInstances.length === 0) { + return undefined; + } + + return processInstances[0].processId; + } + + public async fetchInstance( + instanceId: string, + ): Promise { + const FindProcessInstanceQuery = gql` + query FindProcessInstanceQuery($instanceId: String!) { + ProcessInstances(where: { id: { equal: $instanceId } }) { + id + processName + processId + serviceUrl + businessKey + state + start + end + nodes { + id + nodeId + definitionId + type + name + enter + exit + } + variables + parentProcessInstance { + id + processName + businessKey + } + error { + nodeDefinitionId + message + } + } + } + `; + + const result = await this.client.query(FindProcessInstanceQuery, { + instanceId, + }); + + this.logger.debug( + `Fetch process instance result: ${JSON.stringify(result)}`, + ); + + if (result.error) { + this.logger.error( + `Error when fetching process instances: ${result.error}`, + ); + throw result.error; + } + + const processInstances = result.data.ProcessInstances as ProcessInstance[]; + + if (processInstances.length === 0) { + return undefined; + } + + const instance = processInstances[0]; + + const workflowInfo = await this.fetchWorkflowInfo(instance.processId); + if (!workflowInfo?.source) { + throw new Error( + `Workflow defintion is required to fetch instance ${instance.id}`, + ); + } + const workflowDefinitionSrc: WorkflowDefinition = fromWorkflowSource( + workflowInfo.source, + ); + if (workflowInfo) { + instance.category = getWorkflowCategory(workflowDefinitionSrc); + instance.description = workflowDefinitionSrc.description; + } + return instance; + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/DataInputSchemaService.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/DataInputSchemaService.ts new file mode 100644 index 00000000..ef438e97 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/DataInputSchemaService.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { JsonObject } from '@backstage/types'; + +import { WORKFLOW_DATA_KEY } from './constants'; + +export class DataInputSchemaService { + public extractWorkflowData(variables?: object): JsonObject | undefined { + return variables && WORKFLOW_DATA_KEY in variables + ? (variables[WORKFLOW_DATA_KEY] as JsonObject) + : undefined; + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/DevModeService.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/DevModeService.ts new file mode 100644 index 00000000..05a9d82d --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/DevModeService.ts @@ -0,0 +1,217 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { LoggerService } from '@backstage/backend-plugin-api'; +import type { Config } from '@backstage/config'; + +import fs from 'fs-extra'; + +import { + DEFAULT_SONATAFLOW_BASE_URL, + DEFAULT_SONATAFLOW_CONTAINER_IMAGE, + DEFAULT_SONATAFLOW_PERSISTENCE_PATH, + DEFAULT_WORKFLOWS_PATH, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import { spawn } from 'child_process'; +import { join, resolve } from 'path'; + +import { GitService } from './GitService'; +import { executeWithRetry } from './Helper'; + +const SONATA_FLOW_RESOURCES_PATH = + '/home/kogito/serverless-workflow-project/src/main/resources'; + +interface LauncherCommand { + command: string; + args: string[]; +} + +interface DevModeConnectionConfig { + host: string; + port?: number; + containerImage: string; + resourcesPath: string; + persistencePath: string; + repoUrl?: string; +} + +export class DevModeService { + private readonly connection: DevModeConnectionConfig; + private readonly gitService; + + constructor(config: Config, private readonly logger: LoggerService) { + this.connection = this.extractConnectionConfig(config); + this.gitService = new GitService(logger, config); + } + + public get devModeUrl(): string { + if (!this.connection.port) { + return this.connection.host; + } + return `${this.connection.host}:${this.connection.port}`; + } + + public async launchDevMode(): Promise { + await this.loadDevWorkflows(); + + const isAlreadyUp = await this.isSonataFlowUp(false, this.devModeUrl); + if (isAlreadyUp) { + return true; + } + + this.launchSonataFlow(); + + return await this.isSonataFlowUp(true, this.devModeUrl); + } + + private async isSonataFlowUp( + withRetry: boolean, + endpoint: string, + ): Promise { + const healthUrl = `${endpoint}/q/health`; + this.logger.info(`Checking SonataFlow health at: ${healthUrl}`); + + try { + const response = await executeWithRetry( + () => fetch(healthUrl), + withRetry ? 15 : 1, + ); + if (response.ok) { + this.logger.info('SonataFlow is up and running'); + return true; + } + } catch (e) { + this.logger.error(`Error when checking SonataFlow health: ${e}`); + } + return false; + } + + private launchSonataFlow(): void { + const launcherCmd = this.createLauncherCommand(); + + this.logger.info( + `Auto starting SonataFlow through: ${ + launcherCmd.command + } ${launcherCmd.args.join(' ')}`, + ); + + const process = spawn(launcherCmd.command, launcherCmd.args, { + shell: false, + }); + + process.on('close', code => { + this.logger.info(`SonataFlow process exited with code ${code}`); + }); + + process.on('exit', code => { + this.logger.info(`SonataFlow process exited with code ${code}`); + }); + + process.on('error', error => { + this.logger.error(`SonataFlow process error: ${error}`); + }); + } + + private createLauncherCommand(): LauncherCommand { + const resourcesAbsPath = resolve( + join(this.connection.resourcesPath, DEFAULT_WORKFLOWS_PATH), + ); + + const launcherArgs = [ + 'run', + '--name', + 'backstage-internal-sonataflow', + '--add-host', + 'host.docker.internal:host-gateway', + ]; + + launcherArgs.push('-e', `QUARKUS_HTTP_PORT=${this.connection.port}`); + + launcherArgs.push('-p', `${this.connection.port}:${this.connection.port}`); + launcherArgs.push('-e', `KOGITO_SERVICE_URL=${this.devModeUrl}`); + launcherArgs.push( + '-v', + `${resourcesAbsPath}:${SONATA_FLOW_RESOURCES_PATH}:Z`, + ); + launcherArgs.push('-e', 'KOGITO.CODEGEN.PROCESS.FAILONERROR=false'); + launcherArgs.push( + '-e', + `QUARKUS_EMBEDDED_POSTGRESQL_DATA_DIR=${this.connection.persistencePath}`, + ); + + launcherArgs.push(this.connection.containerImage); + + return { + command: 'docker', + args: launcherArgs, + }; + } + + private extractConnectionConfig(config: Config): DevModeConnectionConfig { + const host = + config.getOptionalString('orchestrator.sonataFlowService.baseUrl') ?? + DEFAULT_SONATAFLOW_BASE_URL; + const port = config.getOptionalNumber( + 'orchestrator.sonataFlowService.port', + ); + + const resourcesPath = + config.getOptionalString( + 'orchestrator.sonataFlowService.workflowsSource.localPath', + ) ?? ''; + + const containerImage = + config.getOptionalString('orchestrator.sonataFlowService.container') ?? + DEFAULT_SONATAFLOW_CONTAINER_IMAGE; + + const persistencePath = + config.getOptionalString( + 'orchestrator.sonataFlowService.persistence.path', + ) ?? DEFAULT_SONATAFLOW_PERSISTENCE_PATH; + + const repoUrl = + config.getOptionalString( + 'orchestrator.sonataFlowService.workflowsSource.gitRepositoryUrl', + ) ?? ''; + + return { + host, + port, + containerImage, + resourcesPath, + persistencePath, + repoUrl, + }; + } + + public async loadDevWorkflows() { + if (!this.connection.repoUrl) { + this.logger.info( + 'No Git repository configured. Skipping dev workflows loading.', + ); + return; + } + + this.logger.info(`Loading dev workflows from ${this.connection.repoUrl}`); + const localPath = this.connection.resourcesPath; + if (await fs.pathExists(localPath)) { + this.logger.info(`Path ${localPath} already exists. Skipping clone.`); + return; + } + + await this.gitService.clone(this.connection.repoUrl, localPath); + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/GitService.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/GitService.ts new file mode 100644 index 00000000..48a29d5f --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/GitService.ts @@ -0,0 +1,108 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { LoggerService } from '@backstage/backend-plugin-api'; +import type { Config } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; + +import { Git } from './GitWrapper'; + +export class GitService { + private readonly git: Git; + + private readonly logger: LoggerService; + private authenticated: boolean; + + private readonly author = { + name: 'backstage-orchestrator', + email: 'orchestrator@backstage.io', + }; + + private readonly committer = { + name: 'backstage-orchestrator', + email: 'orchestrator@backstage.io', + }; + + constructor(logger: LoggerService, config: Config) { + this.logger = logger; + const githubIntegration = ScmIntegrations.fromConfig(config) + .github.list() + .pop(); + this.git = Git.fromAuth({ + username: 'x-access-token', + password: githubIntegration?.config.token, + }); + this.authenticated = !!githubIntegration?.config.token; + } + + async clone(repoURL: string, localPath: string): Promise { + this.logger.info(`cloning repo ${repoURL} into ${localPath}`); + return this.git + .clone({ + url: repoURL, + dir: localPath, + depth: 1, + }) + .then(() => this.git.checkout({ dir: localPath, ref: 'main' })); + } + + async push(dir: string, message: string): Promise { + if (!this.authenticated) { + this.logger.warn( + 'Git integration is required to be configured for push, with the token or credentials', + ); + return; + } + const branch = 'main'; + const force = true; + const remote = 'origin'; + const filepath = '.'; + this.git + .fetch({ remote, dir }) + .then(() => this.git.checkout({ dir, ref: branch })) + .then(() => this.git.add({ dir, filepath })) + .then(() => + this.git.commit({ + dir, + message, + author: this.author, + committer: this.committer, + }), + ) + .then(() => this.git.push({ dir, remote, remoteRef: branch, force })) + .finally(() => this.logger.info('push completed')) + .catch(ex => this.logger.error(ex)); + } + + async pull(localPath: string): Promise { + const remoteBranch = 'origin/main'; + const localBranch = 'main'; + const remote = 'origin'; + this.git + .fetch({ remote, dir: localPath }) + .then(() => this.git.checkout({ dir: localPath, ref: localBranch })) + .then(() => + this.git.merge({ + dir: localPath, + ours: localBranch, + theirs: remoteBranch, + author: this.author, + committer: this.committer, + }), + ) + .finally(() => this.logger.info('merge completed')) + .catch(ex => this.logger.error(ex)); + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/GitWrapper/git.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/GitWrapper/git.ts new file mode 100644 index 00000000..9ed2b4a7 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/GitWrapper/git.ts @@ -0,0 +1,355 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LoggerService } from '@backstage/backend-plugin-api'; + +import fs from 'fs-extra'; +import git, { + AuthCallback, + MergeResult, + ProgressCallback, + ReadCommitResult, +} from 'isomorphic-git'; +import http from 'isomorphic-git/http/node'; + +function isAuthCallbackOptions( + options: StaticAuthOptions | AuthCallbackOptions, +): options is AuthCallbackOptions { + return 'onAuth' in options; +} + +/** + * Configure static credential for authentication + * @public + */ +export type StaticAuthOptions = { + username?: string; + password?: string; + token?: string; + logger?: LoggerService; +}; + +/** + * Configure an authentication callback that can provide credentials on demand + * @public + */ +export type AuthCallbackOptions = { + onAuth: AuthCallback; + logger?: LoggerService; +}; + +/* +provider username password +Azure 'notempty' token +Bitbucket Cloud 'x-token-auth' token +Bitbucket Server username password or token +GitHub 'x-access-token' token +GitLab 'oauth2' token +From : https://isomorphic-git.org/docs/en/onAuth with fix for GitHub +Or token provided as `token` for Bearer auth header +instead of Basic Auth (e.g., Bitbucket Server). +*/ + +/** + * A convenience wrapper around the `isomorphic-git` library. + * @public + */ + +export class Git { + private readonly headers: { + [x: string]: string; + }; + + private constructor( + private readonly config: { + onAuth: AuthCallback; + token?: string; + logger?: LoggerService; + }, + ) { + this.onAuth = config.onAuth; + + this.headers = { + 'user-agent': 'git/@isomorphic-git', + ...(config.token ? { Authorization: `Bearer ${config.token}` } : {}), + }; + } + + async add(options: { dir: string; filepath: string }): Promise { + const { dir, filepath } = options; + this.config.logger?.info(`Adding file {dir=${dir},filepath=${filepath}}`); + + return git.add({ fs, dir, filepath }); + } + + async addRemote(options: { + dir: string; + remote: string; + url: string; + force?: boolean; + }): Promise { + const { dir, url, remote, force } = options; + this.config.logger?.info( + `Creating new remote {dir=${dir},remote=${remote},url=${url}}`, + ); + return git.addRemote({ fs, dir, remote, url, force }); + } + + async deleteRemote(options: { dir: string; remote: string }): Promise { + const { dir, remote } = options; + this.config.logger?.info(`Deleting remote {dir=${dir},remote=${remote}}`); + return git.deleteRemote({ fs, dir, remote }); + } + + async checkout(options: { dir: string; ref: string }): Promise { + const { dir, ref } = options; + this.config.logger?.info(`Checking out branch {dir=${dir},ref=${ref}}`); + + return git.checkout({ fs, dir, ref }); + } + + async branch(options: { dir: string; ref: string }): Promise { + const { dir, ref } = options; + this.config.logger?.info(`Creating branch {dir=${dir},ref=${ref}`); + + return git.branch({ fs, dir, ref }); + } + + async commit(options: { + dir: string; + message: string; + author: { name: string; email: string }; + committer: { name: string; email: string }; + }): Promise { + const { dir, message, author, committer } = options; + this.config.logger?.info( + `Committing file to repo {dir=${dir},message=${message}}`, + ); + return git.commit({ fs, dir, message, author, committer }); + } + + /** https://isomorphic-git.org/docs/en/clone */ + async clone(options: { + url: string; + dir: string; + ref?: string; + depth?: number; + noCheckout?: boolean; + }): Promise { + const { url, dir, ref, depth, noCheckout } = options; + this.config.logger?.info(`Cloning repo {dir=${dir},url=${url}}`); + + try { + return await git.clone({ + fs, + http, + url, + dir, + ref, + singleBranch: true, + depth: depth ?? 1, + noCheckout, + onProgress: this.onProgressHandler(), + headers: this.headers, + onAuth: this.onAuth, + }); + } catch (ex: any) { + this.config.logger?.error(`Failed to clone repo {dir=${dir},url=${url}}`); + if (ex.data) { + throw new Error(`${ex.message} {data=${JSON.stringify(ex.data)}}`); + } + throw ex; + } + } + + /** https://isomorphic-git.org/docs/en/currentBranch */ + async currentBranch(options: { + dir: string; + fullName?: boolean; + }): Promise { + const { dir, fullName = false } = options; + return git.currentBranch({ fs, dir, fullname: fullName }) as Promise< + string | undefined + >; + } + + /** https://isomorphic-git.org/docs/en/fetch */ + async fetch(options: { + dir: string; + remote?: string; + tags?: boolean; + }): Promise { + const { dir, remote = 'origin', tags = false } = options; + this.config.logger?.info( + `Fetching remote=${remote} for repository {dir=${dir}}`, + ); + + try { + await git.fetch({ + fs, + http, + dir, + remote, + tags, + onProgress: this.onProgressHandler(), + headers: this.headers, + onAuth: this.onAuth, + }); + } catch (ex: any) { + this.config.logger?.error( + `Failed to fetch repo {dir=${dir},remote=${remote}}`, + ); + if (ex.data) { + throw new Error(`${ex.message} {data=${JSON.stringify(ex.data)}}`); + } + throw ex; + } + } + + async init(options: { dir: string; defaultBranch?: string }): Promise { + const { dir, defaultBranch = 'master' } = options; + this.config.logger?.info(`Init git repository {dir=${dir}}`); + + return git.init({ + fs, + dir, + defaultBranch, + }); + } + + /** https://isomorphic-git.org/docs/en/merge */ + async merge(options: { + dir: string; + theirs: string; + ours?: string; + author: { name: string; email: string }; + committer: { name: string; email: string }; + }): Promise { + const { dir, theirs, ours, author, committer } = options; + this.config.logger?.info( + `Merging branch '${theirs}' into '${ours}' for repository {dir=${dir}}`, + ); + + // If ours is undefined, current branch is used. + return git.merge({ + fs, + dir, + ours, + theirs, + author, + committer, + }); + } + + async push(options: { + dir: string; + remote: string; + remoteRef?: string; + force?: boolean; + }) { + const { dir, remote, remoteRef, force } = options; + this.config.logger?.info( + `Pushing directory to remote {dir=${dir},remote=${remote}}`, + ); + try { + return await git.push({ + fs, + dir, + http, + onProgress: this.onProgressHandler(), + remoteRef, + force, + headers: this.headers, + remote, + onAuth: this.onAuth, + }); + } catch (ex: any) { + this.config.logger?.error( + `Failed to push to repo {dir=${dir}, remote=${remote}}`, + ); + if (ex.data) { + throw new Error(`${ex.message} {data=${JSON.stringify(ex.data)}}`); + } + throw ex; + } + } + + /** https://isomorphic-git.org/docs/en/readCommit */ + async readCommit(options: { + dir: string; + sha: string; + }): Promise { + const { dir, sha } = options; + return git.readCommit({ fs, dir, oid: sha }); + } + + /** https://isomorphic-git.org/docs/en/remove */ + async remove(options: { dir: string; filepath: string }): Promise { + const { dir, filepath } = options; + this.config.logger?.info( + `Removing file from git index {dir=${dir},filepath=${filepath}}`, + ); + return git.remove({ fs, dir, filepath }); + } + + /** https://isomorphic-git.org/docs/en/resolveRef */ + async resolveRef(options: { dir: string; ref: string }): Promise { + const { dir, ref } = options; + return git.resolveRef({ fs, dir, ref }); + } + + /** https://isomorphic-git.org/docs/en/log */ + async log(options: { + dir: string; + ref?: string; + }): Promise { + const { dir, ref } = options; + return git.log({ + fs, + dir, + ref: ref ?? 'HEAD', + }); + } + + private readonly onAuth: AuthCallback; + + private readonly onProgressHandler = (): ProgressCallback => { + let currentPhase = ''; + + return event => { + if (currentPhase !== event.phase) { + currentPhase = event.phase; + this.config.logger?.info(event.phase); + } + const total = event.total + ? `${Math.round((event.loaded / event.total) * 100)}%` + : event.loaded; + this.config.logger?.debug(`status={${event.phase},total={${total}}}`); + }; + }; + + static readonly fromAuth = ( + options: StaticAuthOptions | AuthCallbackOptions, + ) => { + if (isAuthCallbackOptions(options)) { + const { onAuth, logger } = options; + return new Git({ onAuth, logger }); + } + + const { username, password, token, logger } = options; + return new Git({ onAuth: () => ({ username, password }), token, logger }); + }; +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/GitWrapper/index.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/GitWrapper/index.ts new file mode 100644 index 00000000..1d7fb43f --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/GitWrapper/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Based on https://github.com/backstage/backstage/pull/24605/files +export * from './git'; diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/Helper.test.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/Helper.test.ts new file mode 100644 index 00000000..78b35e3f --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/Helper.test.ts @@ -0,0 +1,64 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { retryAsyncFunction } from './Helper'; + +describe('retryAsyncFunction', () => { + const successfulResponse = 'Success'; + it('should be successful in the first attempt', async () => { + const asyncFnSuccess = jest.fn().mockResolvedValueOnce(successfulResponse); + + const result = await retryAsyncFunction({ + asyncFn: asyncFnSuccess, + maxAttempts: 3, + delayMs: 100, + }); + + expect(result).toBe(successfulResponse); + expect(asyncFnSuccess).toHaveBeenCalledTimes(1); + }); + + it('should throw an error after maximum attempts', async () => { + const asyncFnFailure = jest.fn().mockResolvedValue(undefined); + + await expect( + retryAsyncFunction({ + asyncFn: asyncFnFailure, + maxAttempts: 5, + delayMs: 100, + }), + ).rejects.toThrow(); + + expect(asyncFnFailure).toHaveBeenCalledTimes(5); + }); + + it('should retry until successful after getting some undefined responses', async () => { + const asyncFns = jest + .fn() + .mockResolvedValueOnce(undefined) + .mockResolvedValueOnce(undefined) + .mockResolvedValueOnce(undefined) + .mockResolvedValueOnce(successfulResponse); + + const result = await retryAsyncFunction({ + asyncFn: asyncFns, + maxAttempts: 5, + delayMs: 100, + }); + + expect(result).toBe(successfulResponse); + expect(asyncFns).toHaveBeenCalledTimes(4); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/Helper.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/Helper.ts new file mode 100644 index 00000000..73fd4cb7 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/Helper.ts @@ -0,0 +1,91 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { LoggerService } from '@backstage/backend-plugin-api'; +import type { Config } from '@backstage/config'; + +import fs from 'fs-extra'; + +import os from 'os'; + +export async function retryAsyncFunction(args: { + asyncFn: () => Promise; + maxAttempts: number; + delayMs: number; +}): Promise { + let result: T | undefined; + for (let i = 0; i < args.maxAttempts; i++) { + result = await args.asyncFn(); + if (result !== undefined) { + return result; + } + await new Promise(resolve => setTimeout(resolve, args.delayMs)); + } + throw new Error('Exceeded maximum number of retries for async function'); +} + +export async function getWorkingDirectory( + config: Config, + logger: LoggerService, +): Promise { + if (!config.has('backend.workingDirectory')) { + return os.tmpdir(); + } + + const workingDirectory = config.getString('backend.workingDirectory'); + try { + // Check if working directory exists and is writable + await fs.access(workingDirectory, fs.constants.F_OK | fs.constants.W_OK); + logger.info(`using working directory: ${workingDirectory}`); + } catch (err: any) { + logger.error( + `working directory ${workingDirectory} ${ + err.code === 'ENOENT' ? 'does not exist' : 'is not writable' + }`, + ); + throw err; + } + return workingDirectory; +} + +export async function executeWithRetry( + action: () => Promise, + maxErrors = 15, +): Promise { + let response: Response; + let errorCount = 0; + // execute with retry + const backoff = 5000; + while (errorCount < maxErrors) { + try { + response = await action(); + if (response.status >= 400) { + errorCount++; + // backoff + await delay(backoff); + } else { + return response; + } + } catch (e) { + errorCount++; + await delay(backoff); + } + } + throw new Error('Unable to execute query.'); +} + +export function delay(time: number) { + return new Promise(r => setTimeout(r, time)); +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/OrchestratorService.test.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/OrchestratorService.test.ts new file mode 100644 index 00000000..6a45ec32 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/OrchestratorService.test.ts @@ -0,0 +1,671 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + ProcessInstance, + WorkflowDefinition, + WorkflowExecutionResponse, + WorkflowInfo, + WorkflowOverview, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import { DataIndexService } from './DataIndexService'; +import { OrchestratorService } from './OrchestratorService'; +import { SonataFlowService } from './SonataFlowService'; +import { WorkflowCacheService } from './WorkflowCacheService'; + +// Mocked data helpers +const createInstanceIdMock = (x: number): string => `instance${x}`; +const createDefinitionIdMock = (x: number): string => `definition${x}`; +const createWorkflowInfoMock = (x: number): WorkflowInfo => { + return { + id: createDefinitionIdMock(x), + }; +}; +const createWorkflowOverviewMock = (x: number): WorkflowOverview => { + return { + workflowId: createDefinitionIdMock(x), + format: 'yaml', + }; +}; +const createWorkflowOverviewsMock = (size: number): WorkflowOverview[] => + Array.from({ length: size }, (_, i) => createWorkflowOverviewMock(i + 1)); +const createInstanceMock = (x: number): ProcessInstance => { + return { + id: createInstanceIdMock(x), + processId: createDefinitionIdMock(x), + endpoint: `endpoint${x}`, + nodes: [], + }; +}; +const createInstancesMock = (size: number): ProcessInstance[] => { + const result: ProcessInstance[] = []; + for (let i = 1; i <= size; i++) { + result.push(createInstanceMock(i)); + } + return result; +}; + +// Mocked data +const instanceId = createInstanceIdMock(1); +const definitionId = createDefinitionIdMock(1); +const workflowInfo = createWorkflowInfoMock(1); +const workflowOverview = createWorkflowOverviewMock(1); +const workflowOverviews = createWorkflowOverviewsMock(3); +const instance = createInstanceMock(1); +const instances = createInstancesMock(3); +const serviceUrl = 'http://localhost'; +const inputData = { foo: 'bar' }; + +// Mocked dependencies +const sonataFlowServiceMock = {} as SonataFlowService; +const workflowCacheServiceMock = {} as WorkflowCacheService; +const dataIndexServiceMock = {} as DataIndexService; + +// Target +const orchestratorService = new OrchestratorService( + sonataFlowServiceMock, + dataIndexServiceMock, + workflowCacheServiceMock, +); + +describe('OrchestratorService', () => { + describe('abortWorkflowInstance', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should execute the operation when the workflow is available', async () => { + dataIndexServiceMock.fetchDefinitionIdByInstanceId = jest + .fn() + .mockResolvedValue(definitionId); + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(true); + dataIndexServiceMock.abortWorkflowInstance = jest.fn( + (_instanceId: string) => Promise.resolve(), + ); + + await orchestratorService.abortWorkflowInstance({ + instanceId, + cacheHandler: 'skip', + }); + + expect( + dataIndexServiceMock.fetchDefinitionIdByInstanceId, + ).toHaveBeenCalled(); + expect(dataIndexServiceMock.abortWorkflowInstance).toHaveBeenCalled(); + }); + + it('should skip and not execute the operation when the workflow is not available', async () => { + dataIndexServiceMock.fetchDefinitionIdByInstanceId = jest + .fn() + .mockResolvedValue(definitionId); + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(false); + + await orchestratorService.abortWorkflowInstance({ + instanceId, + cacheHandler: 'skip', + }); + + expect( + dataIndexServiceMock.fetchDefinitionIdByInstanceId, + ).toHaveBeenCalled(); + expect(dataIndexServiceMock.abortWorkflowInstance).not.toHaveBeenCalled(); + }); + + it('should throw an error and not execute the operation when the workflow is not available', async () => { + dataIndexServiceMock.fetchDefinitionIdByInstanceId = jest + .fn() + .mockResolvedValue(definitionId); + workflowCacheServiceMock.isAvailable = jest + .fn() + .mockImplementation(() => { + throw new Error(); + }); + + const promise = orchestratorService.abortWorkflowInstance({ + instanceId, + cacheHandler: 'throw', + }); + + await expect(promise).rejects.toThrow(); + + expect( + dataIndexServiceMock.fetchDefinitionIdByInstanceId, + ).toHaveBeenCalled(); + expect(workflowCacheServiceMock.isAvailable).toHaveBeenCalled(); + expect(dataIndexServiceMock.abortWorkflowInstance).not.toHaveBeenCalled(); + }); + }); + + describe('fetchInstances', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should throw error when data index returns error', async () => { + const errMsg = 'Failed to load instances'; + dataIndexServiceMock.fetchInstances = jest.fn().mockImplementation(() => { + throw new Error(errMsg); + }); + + const promise = orchestratorService.fetchInstances({}); + await expect(promise).rejects.toThrow(errMsg); + }); + + it('should execute the operation', async () => { + dataIndexServiceMock.fetchInstances = jest + .fn() + .mockResolvedValue(instances); + + const result = await orchestratorService.fetchInstances({}); + + expect(result).toHaveLength(instances.length); + expect(dataIndexServiceMock.fetchInstances).toHaveBeenCalled(); + }); + }); + + describe('fetchInstancesTotalCount', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should throw error when data index returns error', async () => { + const errMsg = 'Failed to get instances total count'; + dataIndexServiceMock.fetchInstancesTotalCount = jest + .fn() + .mockImplementation(() => { + throw new Error(errMsg); + }); + const promise = orchestratorService.fetchInstancesTotalCount(); + await expect(promise).rejects.toThrow(errMsg); + }); + + it('should execute the operation', async () => { + dataIndexServiceMock.fetchInstancesTotalCount = jest + .fn() + .mockResolvedValue(instances.length); + + const result = await orchestratorService.fetchInstancesTotalCount(); + + expect(result).toBe(instances.length); + expect(dataIndexServiceMock.fetchInstancesTotalCount).toHaveBeenCalled(); + }); + }); + + describe('fetchWorkflowOverviews', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should throw error when data index returns error', async () => { + const errMsg = 'Failed to get workflows overview'; + sonataFlowServiceMock.fetchWorkflowOverviews = jest + .fn() + .mockImplementation(() => { + throw new Error(errMsg); + }); + + const promise = orchestratorService.fetchWorkflowOverviews({}); + await expect(promise).rejects.toThrow(); + }); + + it('should execute the operation', async () => { + sonataFlowServiceMock.fetchWorkflowOverviews = jest + .fn() + .mockResolvedValue(workflowOverviews); + + const result = await orchestratorService.fetchWorkflowOverviews({}); + + expect(result).toHaveLength(workflowOverviews.length); + expect(sonataFlowServiceMock.fetchWorkflowOverviews).toHaveBeenCalled(); + }); + }); + + describe('fetchWorkflowInfo', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should execute the operation when the workflow is available', async () => { + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(true); + dataIndexServiceMock.fetchWorkflowInfo = jest + .fn() + .mockResolvedValue(workflowInfo); + + const result = await orchestratorService.fetchWorkflowInfo({ + definitionId, + cacheHandler: 'skip', + }); + + expect(result).toBeDefined(); + }); + + it('should skip and not execute the operation when the workflow is not available', async () => { + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(false); + + const result = await orchestratorService.fetchWorkflowInfo({ + definitionId, + cacheHandler: 'skip', + }); + + expect(result).toBeUndefined(); + expect(dataIndexServiceMock.fetchWorkflowInfo).not.toHaveBeenCalled(); + }); + + it('should throw an error and not execute the operation when the workflow is not available', async () => { + workflowCacheServiceMock.isAvailable = jest + .fn() + .mockImplementation(() => { + throw new Error(); + }); + + const promise = orchestratorService.fetchWorkflowInfo({ + definitionId, + cacheHandler: 'throw', + }); + + await expect(promise).rejects.toThrow(); + + expect(dataIndexServiceMock.fetchWorkflowInfo).not.toHaveBeenCalled(); + }); + }); + + describe('fetchWorkflowSource', () => { + const workflowSource = 'workflow source'; + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should execute the operation when the workflow is available', async () => { + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(true); + dataIndexServiceMock.fetchWorkflowSource = jest + .fn() + .mockResolvedValue(workflowSource); + + const result = await orchestratorService.fetchWorkflowSource({ + definitionId, + cacheHandler: 'skip', + }); + + expect(result).toBeDefined(); + }); + + it('should skip and not execute the operation when the workflow is not available', async () => { + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(false); + + const result = await orchestratorService.fetchWorkflowSource({ + definitionId, + cacheHandler: 'skip', + }); + + expect(result).toBeUndefined(); + expect(dataIndexServiceMock.fetchWorkflowSource).not.toHaveBeenCalled(); + }); + + it('should throw an error and not execute the operation when the workflow is not available', async () => { + workflowCacheServiceMock.isAvailable = jest + .fn() + .mockImplementation(() => { + throw new Error(); + }); + + const promise = orchestratorService.fetchWorkflowSource({ + definitionId, + cacheHandler: 'throw', + }); + + await expect(promise).rejects.toThrow(); + + expect(dataIndexServiceMock.fetchWorkflowSource).not.toHaveBeenCalled(); + }); + }); + + describe('fetchInstanceVariables', () => { + const variables: object = { foo: 'bar' }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should execute the operation when the workflow is available', async () => { + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(true); + dataIndexServiceMock.fetchDefinitionIdByInstanceId = jest + .fn() + .mockResolvedValue(definitionId); + dataIndexServiceMock.fetchInstanceVariables = jest + .fn() + .mockResolvedValue(variables); + + const result = await orchestratorService.fetchInstanceVariables({ + instanceId, + cacheHandler: 'skip', + }); + + expect(result).toBeDefined(); + }); + + it('should skip and not execute the operation when the workflow is not available', async () => { + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(false); + dataIndexServiceMock.fetchDefinitionIdByInstanceId = jest + .fn() + .mockResolvedValue(definitionId); + + const result = await orchestratorService.fetchInstanceVariables({ + instanceId, + cacheHandler: 'skip', + }); + + expect(result).toBeUndefined(); + expect( + dataIndexServiceMock.fetchInstanceVariables, + ).not.toHaveBeenCalled(); + }); + + it('should throw an error and not execute the operation when the workflow is not available', async () => { + workflowCacheServiceMock.isAvailable = jest + .fn() + .mockImplementation(() => { + throw new Error(); + }); + dataIndexServiceMock.fetchDefinitionIdByInstanceId = jest + .fn() + .mockResolvedValue(definitionId); + + const promise = orchestratorService.fetchInstanceVariables({ + instanceId, + cacheHandler: 'throw', + }); + + await expect(promise).rejects.toThrow(); + + expect( + dataIndexServiceMock.fetchInstanceVariables, + ).not.toHaveBeenCalled(); + }); + }); + + describe('fetchInstance', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should execute the operation when the workflow is available', async () => { + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(true); + dataIndexServiceMock.fetchInstance = jest + .fn() + .mockResolvedValue(instance); + + const result = await orchestratorService.fetchInstance({ + instanceId, + cacheHandler: 'skip', + }); + + expect(result).toBeDefined(); + }); + + it('should skip and not execute the operation when the workflow is not available', async () => { + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(false); + + const result = await orchestratorService.fetchInstance({ + instanceId, + cacheHandler: 'skip', + }); + + expect(result).toBeUndefined(); + }); + + it('should throw an error and not execute the operation when the workflow is not available', async () => { + workflowCacheServiceMock.isAvailable = jest + .fn() + .mockImplementation(() => { + throw new Error(); + }); + + const promise = orchestratorService.fetchInstance({ + instanceId, + cacheHandler: 'throw', + }); + + await expect(promise).rejects.toThrow(); + }); + }); + + describe('fetchWorkflowInfoOnService', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should execute the operation when the workflow is available', async () => { + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(true); + sonataFlowServiceMock.fetchWorkflowInfoOnService = jest + .fn() + .mockResolvedValue(workflowInfo); + + const result = await orchestratorService.fetchWorkflowInfoOnService({ + definitionId, + serviceUrl, + cacheHandler: 'skip', + }); + + expect(result).toBeDefined(); + }); + + it('should skip and not execute the operation when the workflow is not available', async () => { + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(false); + + const result = await orchestratorService.fetchWorkflowInfoOnService({ + definitionId, + serviceUrl, + cacheHandler: 'skip', + }); + + expect(result).toBeUndefined(); + expect( + sonataFlowServiceMock.fetchWorkflowInfoOnService, + ).not.toHaveBeenCalled(); + }); + + it('should throw an error and not execute the operation when the workflow is not available', async () => { + workflowCacheServiceMock.isAvailable = jest + .fn() + .mockImplementation(() => { + throw new Error(); + }); + + const promise = orchestratorService.fetchWorkflowInfoOnService({ + definitionId, + serviceUrl, + cacheHandler: 'throw', + }); + + await expect(promise).rejects.toThrow(); + + expect( + sonataFlowServiceMock.fetchWorkflowInfoOnService, + ).not.toHaveBeenCalled(); + }); + }); + + describe('fetchWorkflowDefinition', () => { + const definition: WorkflowDefinition = { + id: 'test_workflowId', + specVersion: '0.8', + states: [{}], + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should execute the operation when the workflow is available', async () => { + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(true); + sonataFlowServiceMock.fetchWorkflowDefinition = jest + .fn() + .mockResolvedValue(definition); + + const result = await orchestratorService.fetchWorkflowDefinition({ + definitionId, + cacheHandler: 'skip', + }); + + expect(result).toBeDefined(); + }); + + it('should skip and not execute the operation when the workflow is not available', async () => { + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(false); + + const result = await orchestratorService.fetchWorkflowDefinition({ + definitionId, + cacheHandler: 'skip', + }); + + expect(result).toBeUndefined(); + expect( + sonataFlowServiceMock.fetchWorkflowDefinition, + ).not.toHaveBeenCalled(); + }); + + it('should throw an error and not execute the operation when the workflow is not available', async () => { + workflowCacheServiceMock.isAvailable = jest + .fn() + .mockImplementation(() => { + throw new Error(); + }); + + const promise = orchestratorService.fetchWorkflowDefinition({ + definitionId, + cacheHandler: 'throw', + }); + + await expect(promise).rejects.toThrow(); + + expect( + sonataFlowServiceMock.fetchWorkflowDefinition, + ).not.toHaveBeenCalled(); + }); + }); + + describe('fetchWorkflowOverview', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should execute the operation when the workflow is available', async () => { + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(true); + sonataFlowServiceMock.fetchWorkflowOverview = jest + .fn() + .mockResolvedValue(workflowOverview); + + const result = await orchestratorService.fetchWorkflowOverview({ + definitionId, + cacheHandler: 'skip', + }); + + expect(result).toBeDefined(); + }); + + it('should skip and not execute the operation when the workflow is not available', async () => { + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(false); + + const result = await orchestratorService.fetchWorkflowOverview({ + definitionId, + cacheHandler: 'skip', + }); + + expect(result).toBeUndefined(); + expect( + sonataFlowServiceMock.fetchWorkflowOverview, + ).not.toHaveBeenCalled(); + }); + + it('should throw an error and not execute the operation when the workflow is not available', async () => { + workflowCacheServiceMock.isAvailable = jest + .fn() + .mockImplementation(() => { + throw new Error(); + }); + + const promise = orchestratorService.fetchWorkflowOverview({ + definitionId, + cacheHandler: 'throw', + }); + + await expect(promise).rejects.toThrow(); + + expect( + sonataFlowServiceMock.fetchWorkflowOverview, + ).not.toHaveBeenCalled(); + }); + }); + + describe('executeWorkflow', () => { + const executeResponse: WorkflowExecutionResponse = { + id: createInstanceIdMock(1), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should execute the operation when the workflow is available', async () => { + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(true); + sonataFlowServiceMock.executeWorkflow = jest + .fn() + .mockResolvedValue(executeResponse); + + const result = await orchestratorService.executeWorkflow({ + definitionId, + serviceUrl, + inputData, + cacheHandler: 'skip', + }); + + expect(result).toBeDefined(); + }); + + it('should skip and not execute the operation when the workflow is not available', async () => { + workflowCacheServiceMock.isAvailable = jest.fn().mockReturnValue(false); + + const result = await orchestratorService.executeWorkflow({ + definitionId, + serviceUrl, + inputData, + cacheHandler: 'skip', + }); + + expect(result).toBeUndefined(); + expect(sonataFlowServiceMock.executeWorkflow).not.toHaveBeenCalled(); + }); + + it('should throw an error and not execute the operation when the workflow is not available', async () => { + workflowCacheServiceMock.isAvailable = jest + .fn() + .mockImplementation(() => { + throw new Error(); + }); + + const promise = orchestratorService.executeWorkflow({ + definitionId, + serviceUrl, + inputData, + cacheHandler: 'throw', + }); + + await expect(promise).rejects.toThrow(); + + expect(sonataFlowServiceMock.executeWorkflow).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/OrchestratorService.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/OrchestratorService.ts new file mode 100644 index 00000000..a54c31b4 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/OrchestratorService.ts @@ -0,0 +1,229 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + Filter, + ProcessInstance, + ProcessInstanceVariables, + WorkflowDefinition, + WorkflowExecutionResponse, + WorkflowInfo, + WorkflowOverview, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import { Pagination } from '../types/pagination'; +import { DataIndexService } from './DataIndexService'; +import { SonataFlowService } from './SonataFlowService'; +import { CacheHandler, WorkflowCacheService } from './WorkflowCacheService'; + +export class OrchestratorService { + constructor( + private readonly sonataFlowService: SonataFlowService, + private readonly dataIndexService: DataIndexService, + private readonly workflowCacheService: WorkflowCacheService, + ) {} + + // Data Index Service Wrapper + + public async abortWorkflowInstance(args: { + instanceId: string; + cacheHandler?: CacheHandler; + }): Promise { + const { instanceId, cacheHandler } = args; + const definitionId = + await this.dataIndexService.fetchDefinitionIdByInstanceId(instanceId); + const isWorkflowAvailable = this.workflowCacheService.isAvailable( + definitionId, + cacheHandler, + ); + return isWorkflowAvailable + ? await this.dataIndexService.abortWorkflowInstance(instanceId) + : undefined; + } + + public async fetchWorkflowInfo(args: { + definitionId: string; + cacheHandler?: CacheHandler; + }): Promise { + const { definitionId, cacheHandler } = args; + const isWorkflowAvailable = this.workflowCacheService.isAvailable( + definitionId, + cacheHandler, + ); + return isWorkflowAvailable + ? await this.dataIndexService.fetchWorkflowInfo(definitionId) + : undefined; + } + + public async fetchInstances(args: { + pagination?: Pagination; + filter?: Filter; + workflowId?: string; + }): Promise { + const definitionIds = args.workflowId + ? [args.workflowId] + : this.workflowCacheService.definitionIds; + return await this.dataIndexService.fetchInstances({ + definitionIds: definitionIds, + pagination: args.pagination, + filter: args.filter, + }); + } + + public async fetchInstancesTotalCount( + workflowId?: string, + filter?: Filter, + ): Promise { + const definitionIds = workflowId + ? [workflowId] + : this.workflowCacheService.definitionIds; + return await this.dataIndexService.fetchInstancesTotalCount( + definitionIds, + filter, + ); + } + + public async fetchWorkflowSource(args: { + definitionId: string; + cacheHandler?: CacheHandler; + }): Promise { + const { definitionId, cacheHandler } = args; + const isWorkflowAvailable = this.workflowCacheService.isAvailable( + definitionId, + cacheHandler, + ); + return isWorkflowAvailable + ? await this.dataIndexService.fetchWorkflowSource(definitionId) + : undefined; + } + + public async fetchInstanceVariables(args: { + instanceId: string; + cacheHandler?: CacheHandler; + }): Promise { + const { instanceId, cacheHandler } = args; + const definitionId = + await this.dataIndexService.fetchDefinitionIdByInstanceId(instanceId); + const isWorkflowAvailable = this.workflowCacheService.isAvailable( + definitionId, + cacheHandler, + ); + return isWorkflowAvailable + ? await this.dataIndexService.fetchInstanceVariables(instanceId) + : undefined; + } + + public async fetchInstance(args: { + instanceId: string; + cacheHandler?: CacheHandler; + }): Promise { + const { instanceId, cacheHandler } = args; + const instance = await this.dataIndexService.fetchInstance(instanceId); + const isWorkflowAvailable = this.workflowCacheService.isAvailable( + instance?.processId, + cacheHandler, + ); + return isWorkflowAvailable ? instance : undefined; + } + + // SonataFlow Service Wrapper + + public async fetchWorkflowInfoOnService(args: { + definitionId: string; + serviceUrl: string; + cacheHandler?: CacheHandler; + }): Promise { + const { definitionId, cacheHandler } = args; + const isWorkflowAvailable = this.workflowCacheService.isAvailable( + definitionId, + cacheHandler, + ); + return isWorkflowAvailable + ? await this.sonataFlowService.fetchWorkflowInfoOnService(args) + : undefined; + } + + public async fetchWorkflowDefinition(args: { + definitionId: string; + cacheHandler?: CacheHandler; + }): Promise { + const { definitionId, cacheHandler } = args; + const isWorkflowAvailable = this.workflowCacheService.isAvailable( + definitionId, + cacheHandler, + ); + return isWorkflowAvailable + ? await this.sonataFlowService.fetchWorkflowDefinition(definitionId) + : undefined; + } + + public async fetchWorkflowOverviews(args: { + pagination?: Pagination; + filter?: Filter; + }): Promise { + return await this.sonataFlowService.fetchWorkflowOverviews({ + definitionIds: this.workflowCacheService.definitionIds, + pagination: args.pagination, + filter: args.filter, + }); + } + + public async executeWorkflow(args: { + definitionId: string; + serviceUrl: string; + inputData: ProcessInstanceVariables; + businessKey?: string; + cacheHandler?: CacheHandler; + }): Promise { + const { definitionId, cacheHandler } = args; + const isWorkflowAvailable = this.workflowCacheService.isAvailable( + definitionId, + cacheHandler, + ); + return isWorkflowAvailable + ? await this.sonataFlowService.executeWorkflow(args) + : undefined; + } + + public async retriggerWorkflow(args: { + definitionId: string; + instanceId: string; + serviceUrl: string; + cacheHandler?: CacheHandler; + }): Promise { + const { definitionId, cacheHandler } = args; + const isWorkflowAvailable = this.workflowCacheService.isAvailable( + definitionId, + cacheHandler, + ); + return isWorkflowAvailable + ? await this.sonataFlowService.retriggerInstance(args) + : undefined; + } + + public async fetchWorkflowOverview(args: { + definitionId: string; + cacheHandler?: CacheHandler; + }): Promise { + const { definitionId, cacheHandler } = args; + const isWorkflowAvailable = this.workflowCacheService.isAvailable( + definitionId, + cacheHandler, + ); + return isWorkflowAvailable + ? await this.sonataFlowService.fetchWorkflowOverview(definitionId) + : undefined; + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/ScaffolderService.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/ScaffolderService.ts new file mode 100644 index 00000000..7e873842 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/ScaffolderService.ts @@ -0,0 +1,133 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { loggerToWinstonLogger } from '@backstage/backend-common'; +import type { + LoggerService, + UrlReaderService, +} from '@backstage/backend-plugin-api'; +import type { CatalogApi } from '@backstage/catalog-client'; +import type { Config } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import { + createBuiltinActions, + TemplateActionRegistry, +} from '@backstage/plugin-scaffolder-backend'; +import { + ActionContext, + TemplateAction, +} from '@backstage/plugin-scaffolder-node'; +import type { JsonObject, JsonValue } from '@backstage/types'; + +import fs from 'fs-extra'; + +import { randomUUID } from 'crypto'; +import path from 'path'; +import { PassThrough } from 'stream'; + +import { getWorkingDirectory } from './Helper'; + +export interface ActionExecutionContext { + actionId: string; + instanceId: string | undefined; + input: JsonObject; +} + +export class ScaffolderService { + private actionRegistry: TemplateActionRegistry; + private streamLogger = new PassThrough(); + + constructor( + private readonly logger: LoggerService, + private readonly config: Config, + private readonly catalogApi: CatalogApi, + private readonly urlReader: UrlReaderService, + ) { + this.actionRegistry = new TemplateActionRegistry(); + } + + public loadActions(): void { + const actions = [ + ...createBuiltinActions({ + integrations: ScmIntegrations.fromConfig(this.config), + catalogClient: this.catalogApi, + reader: this.urlReader, + config: this.config, + }), + ]; + actions.forEach(a => this.actionRegistry.register(a)); + } + + public getAction(id: string): TemplateAction { + return this.actionRegistry.get(id); + } + + public async executeAction( + actionExecutionContext: ActionExecutionContext, + ): Promise { + if (this.actionRegistry.list().length === 0) { + this.loadActions(); + } + + const action: TemplateAction = this.getAction( + actionExecutionContext.actionId, + ); + const stepOutput: { [outputName: string]: JsonValue } = {}; + + let workspacePath: string; + try { + const workingDirectory = await getWorkingDirectory( + this.config, + this.logger, + ); + workspacePath = path.join( + workingDirectory, + actionExecutionContext.instanceId ?? randomUUID(), + ); + } catch (err: unknown) { + this.logger.error( + `Error getting working directory to execute action ${actionExecutionContext.actionId}`, + err as Error, + ); + throw err; + } + const actionContext: ActionContext = { + input: actionExecutionContext.input, + workspacePath: workspacePath, + // TODO: Move this to LoggerService after scaffolder-node moves to LoggerService + // https://github.com/backstage/backstage/issues/26933 + logger: loggerToWinstonLogger(this.logger), + logStream: this.streamLogger, + createTemporaryDirectory: async () => + await fs.mkdtemp(`${workspacePath}_step-${0}-`), + output(name: string, value: JsonValue) { + stepOutput[name] = value; + }, + getInitiatorCredentials: async () => { + return { + $$type: '@backstage/BackstageCredentials', + principal: 'mock-principal', + }; + }, + checkpoint: async (key, fn) => { + this.logger.info(`Orchestrator ScaffolderService checkpoint ${key}`); + return fn(); + }, + }; + await action.handler(actionContext); + + return stepOutput; + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/SonataFlowService.test.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/SonataFlowService.test.ts new file mode 100644 index 00000000..ca7c8ad0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/SonataFlowService.test.ts @@ -0,0 +1,370 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerService } from '@backstage/backend-plugin-api'; + +import { WorkflowExecutionResponse } from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import { DataIndexService } from './DataIndexService'; +import { SonataFlowService } from './SonataFlowService'; + +describe('SonataFlowService', () => { + let loggerMock: jest.Mocked; + let sonataFlowService: SonataFlowService; + + beforeAll(() => { + loggerMock = { + info: jest.fn(), + debug: jest.fn(), + error: jest.fn(), + warn: jest.fn(), + child: jest.fn(), + }; + sonataFlowService = new SonataFlowService( + {} as DataIndexService, + loggerMock, + ); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('fetchWorkflowInfoOnService', () => { + const serviceUrl = 'http://example.com'; + const definitionId = 'workflow-123'; + const urlToFetch = 'http://example.com/management/processes/workflow-123'; + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return workflow info when the fetch response is ok', async () => { + // Given + const mockResponse: Partial = { + ok: true, + json: jest.fn().mockResolvedValue({ id: 'workflow-123' }), + }; + global.fetch = jest.fn().mockResolvedValue(mockResponse as any); + + // When + const result = await sonataFlowService.fetchWorkflowInfoOnService({ + definitionId, + serviceUrl, + }); + + // Then + expect(fetch).toHaveBeenCalledWith(urlToFetch); + expect(result).toEqual({ id: definitionId }); + expect(loggerMock.debug).toHaveBeenCalledWith( + `Fetch workflow info result: {"id":"${definitionId}"}`, + ); + }); + + it('should propagate thrown error when the fetch response is not ok', async () => { + // Given + const mockResponse: Partial = { + ok: false, + status: 500, + statusText: 'Not Found', + json: jest.fn().mockResolvedValue({ + details: 'Error details', + stack: 'Error stack trace', + }), + }; + global.fetch = jest.fn().mockResolvedValue(mockResponse as any); + + // When + let result; + try { + await sonataFlowService.fetchWorkflowInfoOnService({ + definitionId, + serviceUrl, + }); + } catch (error) { + result = error; + } + + expect(result).toBeDefined(); + }); + + it('should propagate thrown error when fetch throws an error', async () => { + // Given + global.fetch = jest.fn().mockRejectedValue(new Error('Network Error')); + + // When + let result; + try { + await sonataFlowService.fetchWorkflowInfoOnService({ + definitionId, + serviceUrl, + }); + } catch (error) { + result = error; + } + + expect(result).toBeDefined(); + }); + }); + describe('executeWorkflow', () => { + const serviceUrl = 'http://example.com/workflows'; + const definitionId = 'workflow-123'; + const urlToFetch = `${serviceUrl}/${definitionId}`; + const inputData = { var1: 'value1' }; + + const expectedFetchRequestInit = (): RequestInit => { + return { + method: 'POST', + body: JSON.stringify(inputData), + headers: { 'content-type': 'application/json' }, + }; + }; + + const setupTest = (responseConfig: { + ok: boolean; + status?: number; + statusText?: string; + json: any; + }): Partial => { + const mockResponse: Partial = { + ok: responseConfig.ok, + status: responseConfig.status || (responseConfig.ok ? 200 : 500), + statusText: responseConfig.statusText, + json: jest.fn().mockResolvedValue(responseConfig.json), + }; + global.fetch = jest.fn().mockResolvedValue(mockResponse as any); + return mockResponse; + }; + + const runErrorTest = async (): Promise< + WorkflowExecutionResponse | undefined + > => { + return await sonataFlowService.executeWorkflow({ + definitionId, + serviceUrl, + inputData, + }); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + it('should return workflow execution response when the request is successful', async () => { + // Given + setupTest({ ok: true, json: { id: definitionId, status: 'completed' } }); + + // When + const result = await sonataFlowService.executeWorkflow({ + definitionId, + serviceUrl, + inputData: { var1: 'value1' }, + }); + + // Then + expect(fetch).toHaveBeenCalledWith( + urlToFetch, + expectedFetchRequestInit(), + ); + expect(result).toEqual({ id: definitionId, status: 'completed' }); + expect(loggerMock.debug).toHaveBeenCalledWith( + 'Execute workflow successful. Response: {"id":"workflow-123","status":"completed"}', + ); + // Verify that all other logger methods were not called + expect(loggerMock.debug).toHaveBeenCalledTimes(1); + expect(loggerMock.info).not.toHaveBeenCalled(); + expect(loggerMock.error).not.toHaveBeenCalled(); + expect(loggerMock.warn).not.toHaveBeenCalled(); + expect(loggerMock.child).not.toHaveBeenCalled(); + }); + + it('should include businessKey in the URL if provided', async () => { + // Given + const businessKey = 'key-123'; + setupTest({ ok: true, json: { id: definitionId, status: 'completed' } }); + + // When + const result = await sonataFlowService.executeWorkflow({ + definitionId, + serviceUrl, + inputData, + businessKey, + }); + + // Then + expect(fetch).toHaveBeenCalledWith( + `${serviceUrl}/${definitionId}?businessKey=${businessKey}`, + expectedFetchRequestInit(), + ); + expect(result).toEqual({ id: definitionId, status: 'completed' }); + }); + it('should propagate thrown error when the fetch response is not ok without extra info', async () => { + // When + setupTest({ + ok: false, + status: 500, + statusText: 'Internal Server Error', + json: { details: undefined, stack: undefined }, + }); + + let result; + try { + await runErrorTest(); + } catch (error) { + result = error; + } + + expect(result).toBeDefined(); + }); + it('should propagate thrown exception when the fetch response is not ok with extra info', async () => { + // When + setupTest({ + ok: false, + json: { details: 'Error details test', stack: 'Error stacktrace test' }, + }); + + let result; + try { + await runErrorTest(); + } catch (error) { + result = error; + } + + expect(result).toBeDefined(); + }); + it('should propagate thrown error when fetch throws an error', async () => { + // Given + global.fetch = jest.fn().mockRejectedValue(new Error('Network Error')); + + // When + let result; + try { + await sonataFlowService.executeWorkflow({ + definitionId, + serviceUrl, + inputData: inputData, + }); + } catch (error) { + result = error; + } + + expect(result).toBeDefined(); + }); + }); + + describe('createPrefixFetchErrorMessage', () => { + // Constants + const TEST_URL = 'http://example.com'; + const STATUS_TEXT_BAD_REQUEST = 'Bad Request'; + const STATUS_TEXT_NOT_FOUND = 'Not Found'; + const STATUS_TEXT_INTERNAL_SERVER_ERROR = 'Internal Server Error'; + const DETAILS = 'Some error details'; + const STACK_TRACE = 'Error stack trace'; + + it('should return the correct message with all fields provided', async () => { + // Given + const mockResponseJson = { details: DETAILS, stack: STACK_TRACE }; + const mockResponse = new Response(JSON.stringify(mockResponseJson), { + status: 400, + statusText: STATUS_TEXT_BAD_REQUEST, + }); + + // When + const result = await sonataFlowService.createPrefixFetchErrorMessage( + TEST_URL, + mockResponse, + 'POST', + ); + + // Then + const expectedMessage = `Request POST ${TEST_URL} failed with: StatusCode: 400 StatusText: ${STATUS_TEXT_BAD_REQUEST}, Details: ${DETAILS}, Stack: ${STACK_TRACE}`; + expect(result).toBe(expectedMessage); + }); + + it('should return the correct message without details and stack', async () => { + // Given + const mockResponseJson = {}; + const mockResponse = new Response(JSON.stringify(mockResponseJson), { + status: 404, + statusText: STATUS_TEXT_NOT_FOUND, + }); + + // When + const result = await sonataFlowService.createPrefixFetchErrorMessage( + TEST_URL, + mockResponse, + ); + + // Then + const expectedMessage = `Request GET ${TEST_URL} failed with: StatusCode: 404 StatusText: ${STATUS_TEXT_NOT_FOUND}`; + expect(result).toBe(expectedMessage); + }); + + it('should return the correct message with only status code', async () => { + // Given + const mockResponseJson = {}; + const mockResponse = new Response(JSON.stringify(mockResponseJson), { + status: 500, + }); + + // When + const result = await sonataFlowService.createPrefixFetchErrorMessage( + TEST_URL, + mockResponse, + ); + + // Then + const expectedMessage = `Request GET ${TEST_URL} failed with: StatusCode: 500 Unexpected error`; + expect(result).toBe(expectedMessage); + }); + + it('should return the unexpected error message if no other fields are present', async () => { + // Given + const mockResponseJson = {}; + const mockResponse = new Response(JSON.stringify(mockResponseJson)); + + // When + const result = await sonataFlowService.createPrefixFetchErrorMessage( + TEST_URL, + mockResponse, + ); + + // Then + const expectedMessage = `Request GET ${TEST_URL} failed with: StatusCode: 200 Unexpected error`; + expect(result).toBe(expectedMessage); + }); + + it('should handle response with undefined JSON gracefully', async () => { + // Given + const mockResponse = new Response(undefined, { + status: 500, + statusText: STATUS_TEXT_INTERNAL_SERVER_ERROR, + }); + jest.spyOn(mockResponse, 'json').mockResolvedValue(undefined); + + // When + const result = await sonataFlowService.createPrefixFetchErrorMessage( + TEST_URL, + mockResponse, + ); + + // Then + const expectedMessage = `Request GET ${TEST_URL} failed with: StatusCode: 500 StatusText: ${STATUS_TEXT_INTERNAL_SERVER_ERROR}`; + expect(result).toBe(expectedMessage); + }); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/SonataFlowService.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/SonataFlowService.ts new file mode 100644 index 00000000..e8b5e146 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/SonataFlowService.ts @@ -0,0 +1,276 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerService } from '@backstage/backend-plugin-api'; + +import { + extractWorkflowFormat, + Filter, + fromWorkflowSource, + getWorkflowCategory, + ProcessInstance, + ProcessInstanceStateValues, + ProcessInstanceVariables, + WorkflowDefinition, + WorkflowExecutionResponse, + WorkflowInfo, + WorkflowOverview, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import { Pagination } from '../types/pagination'; +import { DataIndexService } from './DataIndexService'; + +export class SonataFlowService { + constructor( + private readonly dataIndexService: DataIndexService, + private readonly logger: LoggerService, + ) {} + + public async fetchWorkflowInfoOnService(args: { + definitionId: string; + serviceUrl: string; + }): Promise { + const urlToFetch = `${args.serviceUrl}/management/processes/${args.definitionId}`; + const response = await fetch(urlToFetch); + + if (response.ok) { + const json = await response.json(); + this.logger.debug(`Fetch workflow info result: ${JSON.stringify(json)}`); + return json; + } + throw new Error( + await this.createPrefixFetchErrorMessage(urlToFetch, response), + ); + } + + public async fetchWorkflowDefinition( + definitionId: string, + ): Promise { + const source = await this.dataIndexService.fetchWorkflowSource( + definitionId, + ); + if (source) { + return fromWorkflowSource(source); + } + return undefined; + } + + public async fetchWorkflowOverviews(args: { + definitionIds?: string[]; + pagination?: Pagination; + filter?: Filter; + }): Promise { + const { definitionIds, pagination, filter } = args; + const workflowInfos = await this.dataIndexService.fetchWorkflowInfos({ + definitionIds, + pagination, + filter, + }); + if (!workflowInfos?.length) { + return []; + } + const items = await Promise.all( + workflowInfos + .filter(info => info.source) + .map(info => this.fetchWorkflowOverviewBySource(info.source!)), + ); + return items.filter((item): item is WorkflowOverview => !!item); + } + + public async executeWorkflow(args: { + definitionId: string; + serviceUrl: string; + inputData: ProcessInstanceVariables; + businessKey?: string; + }): Promise { + const urlToFetch = args.businessKey + ? `${args.serviceUrl}/${args.definitionId}?businessKey=${args.businessKey}` + : `${args.serviceUrl}/${args.definitionId}`; + + const response = await fetch(urlToFetch, { + method: 'POST', + body: JSON.stringify(args.inputData), + headers: { 'content-type': 'application/json' }, + }); + + const json = await response.json(); + if (json.id) { + this.logger.debug( + `Execute workflow successful. Response: ${JSON.stringify(json)}`, + ); + return json; + } else if (!response.ok) { + const errorMessage = await this.createPrefixFetchErrorMessage( + urlToFetch, + response, + 'POST', + ); + this.logger.error( + `Execute workflow failed. Response: ${JSON.stringify(json)}`, + ); + throw new Error(errorMessage); + } else { + this.logger.error( + `Execute workflow did not return a workflow instance ID. Response: ${JSON.stringify( + json, + )}`, + ); + throw new Error('Execute workflow did not return a workflow instance ID'); + } + } + + public async retriggerInstance(args: { + definitionId: string; + instanceId: string; + serviceUrl: string; + }): Promise { + const urlToFetch = `${args.serviceUrl}/management/processes/${args.definitionId}/instances/${args.instanceId}/retrigger`; + + const response = await fetch(urlToFetch, { + method: 'POST', + }); + + if (!response.ok) { + throw new Error( + `${await this.createPrefixFetchErrorMessage( + urlToFetch, + response, + 'POST', + )}`, + ); + } + + return true; + } + + public async fetchWorkflowOverview( + definitionId: string, + ): Promise { + const source = await this.dataIndexService.fetchWorkflowSource( + definitionId, + ); + if (!source) { + this.logger.debug(`Workflow source not found: ${definitionId}`); + return undefined; + } + return await this.fetchWorkflowOverviewBySource(source); + } + + private async fetchWorkflowOverviewBySource( + source: string, + ): Promise { + let processInstances: ProcessInstance[] = []; + const limit = 10; + let offset: number = 0; + + let lastTriggered: Date = new Date(0); + let lastRunStatus: ProcessInstanceStateValues | undefined; + let lastRunId: string | undefined; + let counter = 0; + let totalDuration = 0; + const definition = fromWorkflowSource(source); + + do { + processInstances = + await this.dataIndexService.fetchInstancesByDefinitionId({ + definitionId: definition.id, + limit, + offset, + }); + + for (const pInstance of processInstances) { + if (!pInstance.start) { + continue; + } + if (new Date(pInstance.start) > lastTriggered) { + lastRunId = pInstance.id; + lastTriggered = new Date(pInstance.start); + lastRunStatus = pInstance.state; + } + if (pInstance.end) { + const start: Date = new Date(pInstance.start); + const end: Date = new Date(pInstance.end); + totalDuration += end.valueOf() - start.valueOf(); + counter++; + } + } + offset += limit; + } while (processInstances.length > 0); + + return { + workflowId: definition.id, + name: definition.name, + format: extractWorkflowFormat(source), + lastRunId, + lastTriggeredMs: lastTriggered.getTime(), + lastRunStatus, + category: getWorkflowCategory(definition), + avgDurationMs: counter ? totalDuration / counter : undefined, + description: definition.description, + }; + } + + public async pingWorkflowService(args: { + definitionId: string; + serviceUrl: string; + }): Promise { + const urlToFetch = `${args.serviceUrl}/management/processes/${args.definitionId}`; + const response = await fetch(urlToFetch); + return response.ok; + } + + public async updateInstanceInputData(args: { + definitionId: string; + serviceUrl: string; + instanceId: string; + inputData: ProcessInstanceVariables; + }): Promise { + const { definitionId, serviceUrl, instanceId, inputData } = args; + const urlToFetch = `${serviceUrl}/${definitionId}/${instanceId}`; + const response = await fetch(urlToFetch, { + method: 'PATCH', + body: JSON.stringify(inputData), + headers: { 'content-type': 'application/json' }, + }); + return response.ok; + } + + public async createPrefixFetchErrorMessage( + urlToFetch: string, + response: Response, + httpMethod = 'GET', + ): Promise { + const res = await response.json(); + const errorInfo = []; + let errorMsg = `Request ${httpMethod} ${urlToFetch} failed with: StatusCode: ${response.status}`; + + if (response.statusText) { + errorInfo.push(`StatusText: ${response.statusText}`); + } + if (res?.details) { + errorInfo.push(`Details: ${res?.details}`); + } + if (res?.stack) { + errorInfo.push(`Stack: ${res?.stack}`); + } + if (errorInfo.length > 0) { + errorMsg += ` ${errorInfo.join(', ')}`; + } else { + errorMsg += ' Unexpected error'; + } + + return errorMsg; + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/WorkflowCacheService.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/WorkflowCacheService.ts new file mode 100644 index 00000000..2c121c99 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/WorkflowCacheService.ts @@ -0,0 +1,127 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerService } from '@backstage/backend-plugin-api'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; + +import { DataIndexService } from './DataIndexService'; +import { SonataFlowService } from './SonataFlowService'; + +export type CacheHandler = 'skip' | 'throw'; + +export class WorkflowCacheService { + private readonly TASK_ID = 'task__Orchestrator__WorkflowCacheService'; + private readonly DEFAULT_FREQUENCY_IN_SECONDS = 5; + private readonly DEFAULT_TIMEOUT_IN_MINUTES = 10; + private readonly definitionIdCache = new Set(); + + constructor( + private readonly logger: LoggerService, + private readonly dataIndexService: DataIndexService, + private readonly sonataFlowService: SonataFlowService, + ) {} + + public get definitionIds(): string[] { + return Array.from(this.definitionIdCache); + } + + public isEmpty(): boolean { + return this.definitionIdCache.size === 0; + } + + public isAvailable( + definitionId?: string, + cacheHandler: CacheHandler = 'skip', + ): boolean { + if (!definitionId) { + return false; + } + const isAvailable = this.definitionIdCache.has(definitionId); + if (!isAvailable && cacheHandler === 'throw') { + throw new Error( + `Workflow service "${definitionId}" not available at the moment`, + ); + } + return isAvailable; + } + + public schedule(args: { + scheduler: PluginTaskScheduler; + frequencyInSeconds?: number; + timeoutInMinutes?: number; + }): void { + const { + scheduler, + frequencyInSeconds = this.DEFAULT_FREQUENCY_IN_SECONDS, + timeoutInMinutes = this.DEFAULT_TIMEOUT_IN_MINUTES, + } = args; + + scheduler.scheduleTask({ + id: this.TASK_ID, + frequency: { seconds: frequencyInSeconds }, + timeout: { minutes: timeoutInMinutes }, + fn: async () => { + await this.runTask(); + }, + }); + } + + private async runTask() { + try { + const idUrlMap = await this.dataIndexService.fetchWorkflowServiceUrls(); + this.definitionIdCache.forEach(definitionId => { + if (!idUrlMap[definitionId]) { + this.definitionIdCache.delete(definitionId); + } + }); + await Promise.all( + Object.entries(idUrlMap).map(async ([definitionId, serviceUrl]) => { + let isServiceUp = false; + try { + isServiceUp = await this.sonataFlowService.pingWorkflowService({ + definitionId, + serviceUrl, + }); + } catch (err) { + this.logger.error( + `Ping workflow ${definitionId} service threw error: ${err}`, + ); + } + if (isServiceUp) { + this.definitionIdCache.add(definitionId); + } else { + this.logger.error( + `Failed to ping service for workflow ${definitionId} at ${serviceUrl}`, + ); + if (this.definitionIdCache.has(definitionId)) { + this.definitionIdCache.delete(definitionId); + } + } + }), + ); + + const workflowDefinitionIds = this.isEmpty() + ? 'empty cache' + : Array.from(this.definitionIdCache).join(', '); + + this.logger.debug( + `${this.TASK_ID} updated the workflow definition ID cache to: ${workflowDefinitionIds}`, + ); + } catch (error) { + this.logger.error(`Error running ${this.TASK_ID}: ${error}`); + return; + } + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/__fixtures__/mockProcessDefinitionArgumentsData.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/__fixtures__/mockProcessDefinitionArgumentsData.ts new file mode 100644 index 00000000..8958b318 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/__fixtures__/mockProcessDefinitionArgumentsData.ts @@ -0,0 +1,120 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + IntrospectionField, + TypeKind, + TypeName, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +export const mockProcessDefinitionArguments = { + __type: { + kind: 'INPUT_OBJECT', + name: 'ProcessDefinitionArgument', + inputFields: [ + { + name: 'and', + type: { + kind: 'LIST', + name: null, + ofType: { + kind: 'NON_NULL', + name: null, + ofType: { + kind: 'INPUT_OBJECT', + name: 'ProcessDefinitionArgument', + ofType: null, + }, + }, + }, + }, + { + name: 'or', + type: { + kind: 'LIST', + name: null, + ofType: { + kind: 'NON_NULL', + name: null, + ofType: { + kind: 'INPUT_OBJECT', + name: 'ProcessDefinitionArgument', + ofType: null, + }, + }, + }, + }, + { + name: 'not', + type: { + kind: 'INPUT_OBJECT', + name: 'ProcessDefinitionArgument', + ofType: null, + }, + }, + { + name: 'id', + type: { + kind: 'INPUT_OBJECT', + name: 'StringArgument', + ofType: null, + }, + }, + { + name: 'name', + type: { + kind: 'INPUT_OBJECT', + name: 'StringArgument', + ofType: null, + }, + }, + { + name: 'version', + type: { + kind: 'INPUT_OBJECT', + name: 'StringArgument', + ofType: null, + }, + }, + ], + }, +}; + +export const mockProcessDefinitionIntrospection: IntrospectionField[] = [ + { + name: 'id', + type: { + kind: TypeKind.InputObject, + name: TypeName.String, + ofType: null, + }, + }, + { + name: 'name', + type: { + kind: TypeKind.InputObject, + name: TypeName.String, + ofType: null, + }, + }, + { + name: 'version', + type: { + kind: TypeKind.InputObject, + name: TypeName.String, + ofType: null, + }, + }, +]; diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/__fixtures__/mockProcessInstanceArgumentsData.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/__fixtures__/mockProcessInstanceArgumentsData.ts new file mode 100644 index 00000000..2fc79080 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/__fixtures__/mockProcessInstanceArgumentsData.ts @@ -0,0 +1,369 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + IntrospectionField, + TypeKind, + TypeName, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +export const mockProcessInstanceArguments = { + __type: { + kind: 'INPUT_OBJECT', + name: 'ProcessInstanceArgument', + inputFields: [ + { + name: 'and', + type: { + kind: 'LIST', + name: null, + ofType: { + kind: 'NON_NULL', + name: null, + ofType: { + kind: 'INPUT_OBJECT', + name: 'ProcessInstanceArgument', + ofType: null, + }, + }, + }, + }, + { + name: 'or', + type: { + kind: 'LIST', + name: null, + ofType: { + kind: 'NON_NULL', + name: null, + ofType: { + kind: 'INPUT_OBJECT', + name: 'ProcessInstanceArgument', + ofType: null, + }, + }, + }, + }, + { + name: 'not', + type: { + kind: 'INPUT_OBJECT', + name: 'ProcessInstanceArgument', + ofType: null, + }, + }, + { + name: 'id', + type: { + kind: 'INPUT_OBJECT', + name: 'IdArgument', + ofType: null, + }, + }, + { + name: 'processId', + type: { + kind: 'INPUT_OBJECT', + name: 'StringArgument', + ofType: null, + }, + }, + { + name: 'processName', + type: { + kind: 'INPUT_OBJECT', + name: 'StringArgument', + ofType: null, + }, + }, + { + name: 'parentProcessInstanceId', + type: { + kind: 'INPUT_OBJECT', + name: 'IdArgument', + ofType: null, + }, + }, + { + name: 'rootProcessInstanceId', + type: { + kind: 'INPUT_OBJECT', + name: 'IdArgument', + ofType: null, + }, + }, + { + name: 'rootProcessId', + type: { + kind: 'INPUT_OBJECT', + name: 'StringArgument', + ofType: null, + }, + }, + // { + // name: 'state', + // type: { + // kind: 'INPUT_OBJECT', + // name: 'ProcessInstanceStateArgument', + // ofType: null, + // }, + // }, + // { + // name: 'error', + // type: { + // kind: 'INPUT_OBJECT', + // name: 'ProcessInstanceErrorArgument', + // ofType: null, + // }, + // }, + // { + // name: 'nodes', + // type: { + // kind: 'INPUT_OBJECT', + // name: 'NodeInstanceArgument', + // ofType: null, + // }, + // }, + // { + // name: 'milestones', + // type: { + // kind: 'INPUT_OBJECT', + // name: 'MilestoneArgument', + // ofType: null, + // }, + // }, + { + name: 'endpoint', + type: { + kind: 'INPUT_OBJECT', + name: 'StringArgument', + ofType: null, + }, + }, + // { + // name: 'roles', + // type: { + // kind: 'INPUT_OBJECT', + // name: 'StringArrayArgument', + // ofType: null, + // }, + // }, + { + name: 'start', + type: { + kind: 'INPUT_OBJECT', + name: 'DateArgument', + ofType: null, + }, + }, + { + name: 'end', + type: { + kind: 'INPUT_OBJECT', + name: 'DateArgument', + ofType: null, + }, + }, + // { + // name: 'addons', + // type: { + // kind: 'INPUT_OBJECT', + // name: 'StringArrayArgument', + // ofType: null, + // }, + // }, + { + name: 'lastUpdate', + type: { + kind: 'INPUT_OBJECT', + name: 'DateArgument', + ofType: null, + }, + }, + { + name: 'businessKey', + type: { + kind: 'INPUT_OBJECT', + name: 'StringArgument', + ofType: null, + }, + }, + { + name: 'createdBy', + type: { + kind: 'INPUT_OBJECT', + name: 'StringArgument', + ofType: null, + }, + }, + { + name: 'updatedBy', + type: { + kind: 'INPUT_OBJECT', + name: 'StringArgument', + ofType: null, + }, + }, + ], + }, +}; + +export const mockProcessInstanceIntrospection: IntrospectionField[] = [ + { + name: 'id', + type: { + kind: TypeKind.InputObject, + name: TypeName.Id, + ofType: null, + }, + }, + { + name: 'processId', + type: { + kind: TypeKind.InputObject, + name: TypeName.String, + ofType: null, + }, + }, + { + name: 'processName', + type: { + kind: TypeKind.InputObject, + name: TypeName.String, + ofType: null, + }, + }, + { + name: 'parentProcessInstanceId', + type: { + kind: TypeKind.InputObject, + name: TypeName.Id, + ofType: null, + }, + }, + { + name: 'rootProcessInstanceId', + type: { + kind: TypeKind.InputObject, + name: TypeName.Id, + ofType: null, + }, + }, + { + name: 'rootProcessId', + type: { + kind: TypeKind.InputObject, + name: TypeName.String, + ofType: null, + }, + }, + + // { + // name: 'error', + // type: { + // kind: TypeKind.InputObject, + // name: 'ProcessInstanceErrorArgument', + // ofType: null, + // }, + // }, + // { + // name: 'nodes', + // type: { + // kind: TypeKind.InputObject, + // name: 'NodeInstanceArgument', + // ofType: null, + // }, + // }, + // { + // name: 'milestones', + // type: { + // kind: TypeKind.InputObject, + // name: 'MilestoneArgument', + // ofType: null, + // }, + // }, + { + name: 'endpoint', + type: { + kind: TypeKind.InputObject, + name: TypeName.String, + ofType: null, + }, + }, + // { + // name: 'roles', + // type: { + // kind: TypeKind.InputObject, + // name: TypeName.StringArray, + // ofType: null, + // }, + // }, + { + name: 'start', + type: { + kind: TypeKind.InputObject, + name: TypeName.Date, + ofType: null, + }, + }, + { + name: 'end', + type: { + kind: TypeKind.InputObject, + name: TypeName.Date, + ofType: null, + }, + }, + // { + // name: 'addons', + // type: { + // kind: TypeKind.InputObject, + // name: TypeName.StringArray, + // ofType: null, + // }, + // }, + { + name: 'lastUpdate', + type: { + kind: TypeKind.InputObject, + name: TypeName.Date, + ofType: null, + }, + }, + { + name: 'businessKey', + type: { + kind: TypeKind.InputObject, + name: TypeName.String, + ofType: null, + }, + }, + { + name: 'createdBy', + type: { + kind: TypeKind.InputObject, + name: TypeName.String, + ofType: null, + }, + }, + { + name: 'updatedBy', + type: { + kind: TypeKind.InputObject, + name: TypeName.String, + ofType: null, + }, + }, +]; diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/mapping/V2Mappings.test.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/mapping/V2Mappings.test.ts new file mode 100644 index 00000000..7052d514 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/mapping/V2Mappings.test.ts @@ -0,0 +1,174 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import moment from 'moment'; + +import { + ProcessInstance, + ProcessInstanceState, + WorkflowOverview, + WorkflowRunStatusDTO, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import { + generateProcessInstance, + generateTestExecuteWorkflowResponse, + generateTestWorkflowOverview, +} from '../test-utils'; +import { + getProcessInstancesStatusDTOFromString, + mapToExecuteWorkflowResponseDTO, + mapToProcessInstanceDTO, + mapToWorkflowOverviewDTO, + mapToWorkflowRunStatusDTO, + mapWorkflowCategoryDTOFromString, +} from './V2Mappings'; + +describe('scenarios to verify executeWorkflowResponseDTO', () => { + it('correctly maps positive scenario response', async () => { + const execWorkflowResp = generateTestExecuteWorkflowResponse(); + const mappedValue = mapToExecuteWorkflowResponseDTO( + 'test_workflowId', + execWorkflowResp, + ); + expect(mappedValue).toBeDefined(); + expect(mappedValue.id).toBeDefined(); + expect(Object.keys(mappedValue).length).toBe(1); + }); + + it('throws error when no id attribute present in response', async () => { + expect(() => { + mapToExecuteWorkflowResponseDTO('workflowId', { id: '' }); + }).toThrow( + `Error while mapping ExecuteWorkflowResponse to ExecuteWorkflowResponseDTO for workflow with id`, + ); + }); +}); + +describe('scenarios to verify mapToWorkflowOverviewDTO', () => { + it('correctly maps WorkflowOverview', () => { + // Arrange + const overview: WorkflowOverview = generateTestWorkflowOverview({ + category: 'assessment', + }); + + // Act + const result = mapToWorkflowOverviewDTO(overview); + + // Assert + expect(result.workflowId).toBe(overview.workflowId); + expect(result.name).toBe(overview.name); + expect(result.format).toBe(overview.format); + expect(result.lastTriggeredMs).toBe(overview.lastTriggeredMs); + expect(result.lastRunStatus).toBe( + getProcessInstancesStatusDTOFromString(overview.lastRunStatus), + ); + expect(result.category).toBe('assessment'); + expect(result.avgDurationMs).toBe(overview.avgDurationMs); + expect(result.description).toBe(overview.description); + }); +}); +describe('scenarios to verify mapWorkflowCategoryDTOFromString', () => { + test.each([ + { input: 'assessment', expected: 'assessment' }, + { input: 'infrastructure', expected: 'infrastructure' }, + { input: 'random category', expected: 'infrastructure' }, + ])('mapWorkflowCategoryDTOFromString($input)', ({ input, expected }) => { + // Arrange + const overview: WorkflowOverview = generateTestWorkflowOverview({ + category: input, + }); + + // Act + const resultCategory = mapWorkflowCategoryDTOFromString(overview.category); + + // Assert + expect(resultCategory).toBeDefined(); + expect(resultCategory).toBe(expected); + }); +}); + +describe('scenarios to verify mapToProcessInstanceDTO', () => { + it('correctly maps ProcessInstanceDTO for not completed workflow', () => { + // Arrange + const processInstanceV1: ProcessInstance = generateProcessInstance(1); + processInstanceV1.end = undefined; + + // Act + const result = mapToProcessInstanceDTO(processInstanceV1); + + // Assert + expect(result).toBeDefined(); + expect(result.id).toBeDefined(); + expect(result.start).toBeDefined(); + expect(result.start).toEqual(processInstanceV1.start); + expect(result.end).toBeUndefined(); + expect(result.duration).toBeUndefined(); + expect(result.status).toEqual( + getProcessInstancesStatusDTOFromString(processInstanceV1.state), + ); + expect(result.description).toEqual(processInstanceV1.description); + expect(result.category).toEqual('infrastructure'); + expect(result.workflowdata).toEqual( + // @ts-ignore + processInstanceV1?.variables?.workflowdata, + ); + }); + it('correctly maps ProcessInstanceDTO', () => { + // Arrange + const processIntanceV1: ProcessInstance = generateProcessInstance(1); + + const start = moment(processIntanceV1.start); + const end = moment(processIntanceV1.end); + const duration = moment.duration(start.diff(end)).humanize(); + // Act + const result = mapToProcessInstanceDTO(processIntanceV1); + + // Assert + expect(result.id).toBeDefined(); + expect(result.start).toEqual(processIntanceV1.start); + expect(result.end).toBeDefined(); + expect(result.end).toEqual(processIntanceV1.end); + expect(result.duration).toEqual(duration); + + expect(result).toBeDefined(); + expect(result.status).toEqual( + getProcessInstancesStatusDTOFromString(processIntanceV1.state), + ); + expect(result.end).toEqual(processIntanceV1.end); + expect(result.duration).toEqual(duration); + expect(result.duration).toEqual('an hour'); + expect(result.description).toEqual(processIntanceV1.description); + expect(result.category).toEqual('infrastructure'); + expect(result.workflowdata).toEqual( + // @ts-ignore + processIntanceV1?.variables?.workflowdata, + ); + }); +}); + +describe('scenarios to verify mapToWorkflowRunStatusDTO', () => { + it('correctly maps ProcessInstanceState to WorkflowRunStatusDTO', async () => { + const mappedValue: WorkflowRunStatusDTO = mapToWorkflowRunStatusDTO( + ProcessInstanceState.Active, + ); + + expect(mappedValue).toBeDefined(); + expect(mappedValue.key).toBeDefined(); + expect(mappedValue.value).toBeDefined(); + expect(mappedValue.key).toEqual('Active'); + expect(mappedValue.value).toEqual('ACTIVE'); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/mapping/V2Mappings.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/mapping/V2Mappings.ts new file mode 100644 index 00000000..ad889a4c --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/mapping/V2Mappings.ts @@ -0,0 +1,209 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import moment from 'moment'; + +import { + capitalize, + ExecuteWorkflowResponseDTO, + extractWorkflowFormat, + fromWorkflowSource, + getWorkflowCategory, + NodeInstance, + NodeInstanceDTO, + ProcessInstance, + ProcessInstanceDTO, + ProcessInstanceState, + ProcessInstanceStatusDTO, + WorkflowCategory, + WorkflowCategoryDTO, + WorkflowDefinition, + WorkflowDTO, + WorkflowExecutionResponse, + WorkflowFormatDTO, + WorkflowOverview, + WorkflowOverviewDTO, + WorkflowRunStatusDTO, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +// Mapping functions +export function mapToWorkflowOverviewDTO( + overview: WorkflowOverview, +): WorkflowOverviewDTO { + return { + name: overview.name, + format: overview.format, + workflowId: overview.workflowId, + avgDurationMs: overview.avgDurationMs, + description: overview.description, + lastRunId: overview.lastRunId, + lastRunStatus: overview.lastRunStatus + ? getProcessInstancesStatusDTOFromString(overview.lastRunStatus) + : undefined, + lastTriggeredMs: overview.lastTriggeredMs, + category: mapWorkflowCategoryDTOFromString(overview.category), + }; +} + +export function mapWorkflowCategoryDTOFromString( + category?: string, +): WorkflowCategoryDTO { + return category?.toLocaleLowerCase() === 'assessment' + ? 'assessment' + : 'infrastructure'; +} + +export function getWorkflowCategoryDTO( + definition: WorkflowDefinition | undefined, +): WorkflowCategoryDTO { + return getWorkflowCategory(definition); +} + +export function getWorkflowFormatDTO(source: string): WorkflowFormatDTO { + return extractWorkflowFormat(source); +} + +export function mapToWorkflowDTO(source: string): WorkflowDTO { + const definition = fromWorkflowSource(source); + return { + annotations: definition.annotations, + category: getWorkflowCategoryDTO(definition), + description: definition.description, + name: definition.name, + format: getWorkflowFormatDTO(source), + id: definition.id, + }; +} + +export function mapWorkflowCategoryDTO( + category?: WorkflowCategory, +): WorkflowCategoryDTO { + if (category === WorkflowCategory.ASSESSMENT) { + return 'assessment'; + } + return 'infrastructure'; +} + +export function getProcessInstancesStatusDTOFromString( + state?: string, +): ProcessInstanceStatusDTO { + switch (state) { + case ProcessInstanceState.Active.valueOf(): + return 'Active'; + case ProcessInstanceState.Error.valueOf(): + return 'Error'; + case ProcessInstanceState.Completed.valueOf(): + return 'Completed'; + case ProcessInstanceState.Aborted.valueOf(): + return 'Aborted'; + case ProcessInstanceState.Suspended.valueOf(): + return 'Suspended'; + case ProcessInstanceState.Pending.valueOf(): + return 'Pending'; + default: + throw new Error( + `state ${state} is not one of the values of type ProcessInstanceStatusDTO`, + ); + } +} + +export function getProcessInstanceStateFromStatusDTOString( + status?: ProcessInstanceStatusDTO, +): string { + switch (status) { + case 'Active': + return ProcessInstanceState.Active.valueOf(); + case 'Error': + return ProcessInstanceState.Error.valueOf(); + case 'Completed': + return ProcessInstanceState.Completed.valueOf(); + case 'Aborted': + return ProcessInstanceState.Aborted.valueOf(); + case 'Suspended': + return ProcessInstanceState.Suspended.valueOf(); + case 'Pending': + return ProcessInstanceState.Pending.valueOf(); + default: + throw new Error( + `status ${status} is not one of the values of type ProcessInstanceState`, + ); + } +} + +export function mapToProcessInstanceDTO( + processInstance: ProcessInstance, +): ProcessInstanceDTO { + const start = moment(processInstance.start); + const end = moment(processInstance.end); + const duration = processInstance.end + ? moment.duration(start.diff(end)).humanize() + : undefined; + + let variables: Record | undefined; + if (typeof processInstance?.variables === 'string') { + variables = JSON.parse(processInstance?.variables); + } else { + variables = processInstance?.variables; + } + + return { + id: processInstance.id, + processId: processInstance.processId, + processName: processInstance.processName, + description: processInstance.description, + serviceUrl: processInstance.serviceUrl, + businessKey: processInstance.businessKey, + endpoint: processInstance.endpoint, + error: processInstance.error, + category: mapWorkflowCategoryDTO(processInstance.category), + start: processInstance.start, + end: processInstance.end, + duration: duration, + // @ts-ignore + workflowdata: variables?.workflowdata, + status: getProcessInstancesStatusDTOFromString(processInstance.state), + nodes: processInstance.nodes.map(mapToNodeInstanceDTO), + }; +} + +export function mapToNodeInstanceDTO( + nodeInstance: NodeInstance, +): NodeInstanceDTO { + return { ...nodeInstance, __typename: 'NodeInstance' }; +} + +export function mapToExecuteWorkflowResponseDTO( + workflowId: string, + workflowExecutionResponse: WorkflowExecutionResponse, +): ExecuteWorkflowResponseDTO { + if (!workflowExecutionResponse?.id) { + throw new Error( + `Error while mapping ExecuteWorkflowResponse to ExecuteWorkflowResponseDTO for workflow with id ${workflowId}`, + ); + } + + return { + id: workflowExecutionResponse.id, + }; +} + +export function mapToWorkflowRunStatusDTO( + status: ProcessInstanceState, +): WorkflowRunStatusDTO { + return { + key: capitalize(status), + value: status, + }; +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/mapping/__fixtures__/assessedProcessInstance.json b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/mapping/__fixtures__/assessedProcessInstance.json new file mode 100644 index 00000000..f6ebc90f --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/mapping/__fixtures__/assessedProcessInstance.json @@ -0,0 +1,143 @@ +{ + "instance": { + "id": "026f38fc-6121-46c7-9fa8-3f4b8207bab9", + "processName": "Assessment", + "processId": "assessment", + "businessKey": null, + "state": "COMPLETED", + "start": "2024-02-15T16:11:35.829Z", + "lastUpdate": "2024-02-15T16:11:35.829Z", + "end": "2024-02-15T16:11:35.822Z", + "nodes": [ + { + "id": "3290d3c5-c7c0-4920-a1dc-7a37d98d22b7", + "nodeId": "_jbpm-unique-51", + "definitionId": "_jbpm-unique-51", + "type": "WorkItemNode", + "name": "execute", + "enter": "2024-02-15T16:11:33.485Z", + "exit": "2024-02-15T16:11:35.828Z" + }, + { + "id": "2813e81e-0c38-424f-a505-639b1d33341b", + "nodeId": "_jbpm-unique-50", + "definitionId": "_jbpm-unique-50", + "type": "StartNode", + "name": "EmbeddedStart", + "enter": "2024-02-15T16:11:33.477Z", + "exit": "2024-02-15T16:11:35.829Z" + }, + { + "id": "f15d5540-0df7-401c-991e-3c45755b1302", + "nodeId": "_jbpm-unique-47", + "definitionId": "_jbpm-unique-47", + "type": "StartNode", + "name": "Start", + "enter": "2024-02-15T16:11:33.472Z", + "exit": "2024-02-15T16:11:35.829Z" + }, + { + "id": "142fcd3a-64e6-4539-a6b9-5ba2064623a1", + "nodeId": "_jbpm-unique-48", + "definitionId": "_jbpm-unique-48", + "type": "EndNode", + "name": "End", + "enter": "2024-02-15T16:11:35.821Z", + "exit": "2024-02-15T16:11:35.827Z" + }, + { + "id": "97e209c4-ae0a-4d09-8b00-4bd9ca062126", + "nodeId": "_jbpm-unique-59", + "definitionId": "_jbpm-unique-59", + "type": "ActionNode", + "name": "Script", + "enter": "2024-02-15T16:11:35.812Z", + "exit": "2024-02-15T16:11:35.827Z" + }, + { + "id": "f415ddd5-ba25-4e0a-be93-797b5f0ae6f3", + "nodeId": "_jbpm-unique-49", + "definitionId": "_jbpm-unique-49", + "type": "CompositeContextNode", + "name": "AssessRepository", + "enter": "2024-02-15T16:11:33.476Z", + "exit": "2024-02-15T16:11:35.827Z" + }, + { + "id": "10879e09-12c0-4770-be2b-d2fd27858edc", + "nodeId": "_jbpm-unique-58", + "definitionId": "_jbpm-unique-58", + "type": "EndNode", + "name": "EmbeddedEnd", + "enter": "2024-02-15T16:11:35.812Z", + "exit": "2024-02-15T16:11:35.827Z" + }, + { + "id": "96bb3b01-2a4a-44b2-b29c-da6dc7448450", + "nodeId": "_jbpm-unique-57", + "definitionId": "_jbpm-unique-57", + "type": "ActionNode", + "name": "Script", + "enter": "2024-02-15T16:11:35.811Z", + "exit": "2024-02-15T16:11:35.827Z" + }, + { + "id": "491975f5-4d6a-4a43-ac00-1e305bfcd97a", + "nodeId": "_jbpm-unique-56", + "definitionId": "_jbpm-unique-56", + "type": "ActionNode", + "name": "logOuput", + "enter": "2024-02-15T16:11:35.809Z", + "exit": "2024-02-15T16:11:35.828Z" + }, + { + "id": "bc4ddb8a-f551-442a-a0d9-7f96a8332a09", + "nodeId": "_jbpm-unique-55", + "definitionId": "_jbpm-unique-55", + "type": "ActionNode", + "name": "Script", + "enter": "2024-02-15T16:11:35.807Z", + "exit": "2024-02-15T16:11:35.828Z" + }, + { + "id": "effbf2d7-8201-48de-8f81-3983fb26d591", + "nodeId": "_jbpm-unique-54", + "definitionId": "_jbpm-unique-54", + "type": "WorkItemNode", + "name": "preCheck", + "enter": "2024-02-15T16:11:33.896Z", + "exit": "2024-02-15T16:11:35.828Z" + }, + { + "id": "54c7da48-de5c-4e62-868b-ad6c7ef25ad3", + "nodeId": "_jbpm-unique-53", + "definitionId": "_jbpm-unique-53", + "type": "ActionNode", + "name": "Script", + "enter": "2024-02-15T16:11:33.894Z", + "exit": "2024-02-15T16:11:35.828Z" + }, + { + "id": "bfc5796c-8aa7-4c4f-aa11-dc91aee24ea2", + "nodeId": "_jbpm-unique-52", + "definitionId": "_jbpm-unique-52", + "type": "ActionNode", + "name": "Script", + "enter": "2024-02-15T16:11:33.826Z", + "exit": "2024-02-15T16:11:35.828Z" + } + ], + "variables": { + "workflowdata": { + "result": "[Object]", + "preCheck": "[Object]", + "repositoryUrl": "https://java.com", + "workflowOptions": "[Object]" + } + }, + "parentProcessInstance": null, + "error": null, + "category": "assessment", + "description": "undefined" + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/test-utils.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/test-utils.ts new file mode 100644 index 00000000..010f4aa0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/test-utils.ts @@ -0,0 +1,158 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import moment from 'moment'; + +import { + ProcessInstance, + ProcessInstanceState, + ProcessInstanceStateValues, + WorkflowCategory, + WorkflowDefinition, + WorkflowExecutionResponse, + WorkflowFormat, + WorkflowInfo, + WorkflowOverview, + WorkflowOverviewListResult, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +const BASE_DATE = '2023-02-19T11:45:21.123Z'; + +interface WorkflowOverviewParams { + suffix?: string; + workflowId?: string; + name?: string; + format?: WorkflowFormat; + lastTriggeredMs?: number; + lastRunStatus?: ProcessInstanceStateValues; + category?: string; + avgDurationMs?: number; + description?: string; +} +export function generateTestWorkflowOverview( + params: WorkflowOverviewParams, +): WorkflowOverview { + return { + workflowId: params.workflowId ?? `testWorkflowId${params.suffix}`, + name: params.name ?? `Test Workflow${params.suffix}`, + format: params.format ?? 'yaml', + lastTriggeredMs: + params.lastTriggeredMs ?? Date.parse('2024-02-09T10:34:56Z'), + lastRunStatus: params.lastRunStatus ?? ProcessInstanceState.Completed, + category: params.category ?? 'assessment', // validate input + avgDurationMs: params.avgDurationMs ?? 1000, + description: params.description ?? 'Test Workflow Description', + }; +} + +export function generateTestWorkflowOverviewList( + howmany: number, + inputParams?: WorkflowOverviewParams, +): WorkflowOverviewListResult { + const res: WorkflowOverviewListResult = { + items: [], + totalCount: howmany, + offset: 0, + limit: 0, + }; + + for (let i = 0; i < howmany; i++) { + const params: WorkflowOverviewParams = inputParams ?? {}; + params.suffix = i.toString(); + res.items.push(generateTestWorkflowOverview(params)); + } + + return res; +} + +export function generateTestWorkflowInfo( + id: string = 'test_workflowId', +): WorkflowInfo { + return { + id: id, + serviceUrl: 'mock/serviceurl', + }; +} + +export function generateTestExecuteWorkflowResponse( + id: string = 'test_execId', +): WorkflowExecutionResponse { + return { + id: id, + }; +} + +export const generateWorkflowDefinition: WorkflowDefinition = { + id: 'quarkus-backend-workflow-ci-switch', + version: '1.0', + specVersion: '0.8', + name: '[WF] Create a starter Quarkus Backend application with a CI pipeline - CI Switch', + description: + '[WF] Create a starter Quarkus Backend application with a CI pipeline - CI Switch', + annotations: ['test_annotation'], + states: [ + { + name: 'Test state', + type: 'operation', + end: true, + }, + ], +}; + +export function generateProcessInstances(howmany: number): ProcessInstance[] { + const processInstances: ProcessInstance[] = []; + for (let i = 0; i < howmany; i++) { + processInstances.push(generateProcessInstance(i)); + } + return processInstances; +} + +export function generateProcessInstance(id: number): ProcessInstance { + return { + id: `processInstance${id}`, + processName: `name${id}`, + processId: `proceesId${id}`, + state: ProcessInstanceState.Active, + start: BASE_DATE, + end: moment(BASE_DATE).add(1, 'hour').toISOString(), + nodes: [], + endpoint: 'enpoint/foo', + serviceUrl: 'service/bar', + source: 'my-source', + category: WorkflowCategory.INFRASTRUCTURE, + description: 'test description 1', + variables: { + foo: 'bar', + workflowdata: { + workflowOptions: { + 'my-category': { + id: 'next-workflow-1', + name: 'Next Workflow One', + }, + 'my-secod-category': [ + { + id: 'next-workflow-20', + name: 'Next Workflow Twenty', + }, + { + id: 'next-workflow-21', + name: 'Next Workflow Twenty One', + }, + ], + }, + }, + }, + }; +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/v2.test.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/v2.test.ts new file mode 100644 index 00000000..ac29a9fd --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/v2.test.ts @@ -0,0 +1,577 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Request } from 'express'; + +import { + AssessedProcessInstanceDTO, + ExecuteWorkflowResponseDTO, + FieldFilterOperatorEnum, + ProcessInstanceListResultDTO, + SearchRequest, + toWorkflowYaml, + WorkflowOverview, + WorkflowOverviewDTO, + WorkflowOverviewListResultDTO, + WorkflowRunStatusDTO, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import { buildPagination, buildPaginationTmp } from '../../types/pagination'; +import { OrchestratorService } from '../OrchestratorService'; +import { mapToWorkflowOverviewDTO } from './mapping/V2Mappings'; +import { + generateProcessInstance, + generateProcessInstances, + generateTestExecuteWorkflowResponse, + generateTestWorkflowInfo, + generateTestWorkflowOverview, + generateTestWorkflowOverviewList, + generateWorkflowDefinition, +} from './test-utils'; +import { V2 } from './v2'; + +jest.mock('../Helper.ts', () => ({ + retryAsyncFunction: jest.fn(), +})); + +jest.mock('../OrchestratorService', () => ({ + OrchestratorService: jest.fn(), +})); + +// Helper function to create a mock OrchestratorService instance +const createMockOrchestratorService = (): OrchestratorService => { + const mockOrchestratorService = new OrchestratorService( + {} as any, // Mock sonataFlowService + {} as any, // Mock dataIndexService + {} as any, // Mock workflowCacheService + ); + + mockOrchestratorService.fetchWorkflowOverviews = jest.fn(); + mockOrchestratorService.fetchWorkflowOverview = jest.fn(); + mockOrchestratorService.fetchWorkflowDefinition = jest.fn(); + mockOrchestratorService.fetchWorkflowSource = jest.fn(); + mockOrchestratorService.fetchWorkflowInfo = jest.fn(); + mockOrchestratorService.fetchInstances = jest.fn(); + mockOrchestratorService.fetchInstance = jest.fn(); + mockOrchestratorService.fetchInstancesTotalCount = jest.fn(); + mockOrchestratorService.executeWorkflow = jest.fn(); + mockOrchestratorService.abortWorkflowInstance = jest.fn(); + + return mockOrchestratorService; +}; +const mockOrchestratorService = createMockOrchestratorService(); +const v2 = new V2(mockOrchestratorService); + +describe('getWorkflowOverview', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('0 items in workflow overview list', async () => { + // Arrange + const mockRequest = { + query: {}, + headers: {}, + params: {}, + body: { + paginationInfo: { + offset: 1, + pageSize: 50, + orderBy: 'lastUpdated', + orderDirection: 'DESC', + }, + }, + } as Request; + + const mockOverviewsV1 = { + items: [], + }; + + ( + mockOrchestratorService.fetchWorkflowOverviews as jest.Mock + ).mockResolvedValue(mockOverviewsV1.items); + + // Act + const result: WorkflowOverviewListResultDTO = await v2.getWorkflowsOverview( + buildPagination(mockRequest), + ); + + // Assert + expect(result).toEqual({ + overviews: mockOverviewsV1.items.map(item => + mapToWorkflowOverviewDTO(item), + ), + paginationInfo: { + offset: 1, + pageSize: 50, + totalCount: mockOverviewsV1.items.length, + }, + }); + }); + + it('1 item in workflow overview list', async () => { + // Arrange + const mockRequest: any = { + body: {}, + }; + const mockOverviewsV1 = generateTestWorkflowOverviewList(1, {}); + + ( + mockOrchestratorService.fetchWorkflowOverviews as jest.Mock + ).mockResolvedValue(mockOverviewsV1.items); + + // Act + const result: WorkflowOverviewListResultDTO = await v2.getWorkflowsOverview( + buildPagination(mockRequest), + ); + + // Assert + expect(result).toEqual({ + overviews: mockOverviewsV1.items.map((item: WorkflowOverview) => + mapToWorkflowOverviewDTO(item), + ), + paginationInfo: { + offset: undefined, + pageSize: undefined, + totalCount: mockOverviewsV1.items.length, + }, + }); + }); + + it('many items in workflow overview list', async () => { + // Arrange + const mockRequest: any = { + body: { + paginationInfo: { + offset: 1, + pageSize: 50, + orderBy: 'lastUpdated', + orderDirection: 'DESC', + }, + }, + }; + const mockOverviewsV1 = generateTestWorkflowOverviewList(100, {}); + + ( + mockOrchestratorService.fetchWorkflowOverviews as jest.Mock + ).mockResolvedValue(mockOverviewsV1.items); + + // Act + const result: WorkflowOverviewListResultDTO = await v2.getWorkflowsOverview( + buildPagination(mockRequest), + ); + + // Assert + expect(result).toEqual({ + overviews: mockOverviewsV1.items.map((item: WorkflowOverview) => + mapToWorkflowOverviewDTO(item), + ), + paginationInfo: { + offset: 1, + pageSize: 50, + totalCount: mockOverviewsV1.items.length, + }, + }); + }); + + it('filter test', async () => { + // Arrange + // category = "electronics" AND (price <= 1000 OR (brand IN ("Apple", "Samsung") AND brand like 'Apple')) + const mockRequest: SearchRequest = { + filters: { + operator: 'AND', + filters: [ + { + field: 'category', + operator: FieldFilterOperatorEnum.Eq, + value: 'electronics', + }, + { + operator: 'OR', + filters: [ + { + field: 'price', + operator: FieldFilterOperatorEnum.Lte, + value: 1000, + }, + { + operator: 'AND', + filters: [ + { + field: 'brand', + operator: FieldFilterOperatorEnum.In, + value: ['Apple', 'Samsung'], + }, + { + field: 'brand', + operator: FieldFilterOperatorEnum.Like, + value: 'Apple', + }, + ], + }, + ], + }, + ], + }, + paginationInfo: { + offset: 1, + pageSize: 50, + orderBy: 'lastUpdated', + orderDirection: 'DESC', + }, + }; + const mockOverviewsV1 = generateTestWorkflowOverviewList(100, {}); + + ( + mockOrchestratorService.fetchWorkflowOverviews as jest.Mock + ).mockResolvedValue(mockOverviewsV1.items); + + // Act + const result: WorkflowOverviewListResultDTO = await v2.getWorkflowsOverview( + buildPaginationTmp(mockRequest.paginationInfo), + ); + + // Assert + expect(result).toEqual({ + overviews: mockOverviewsV1.items.map((item: WorkflowOverview) => + mapToWorkflowOverviewDTO(item), + ), + paginationInfo: { + offset: 1, + pageSize: 50, + totalCount: mockOverviewsV1.items.length, + }, + }); + }); + + it('undefined workflow overview list', async () => { + // Arrange + const mockRequest: any = { + query: {}, + }; + ( + mockOrchestratorService.fetchWorkflowOverviews as jest.Mock + ).mockRejectedValue(new Error('no workflow overview')); + + // Act + const promise = v2.getWorkflowsOverview(buildPagination(mockRequest)); + + // Assert + await expect(promise).rejects.toThrow('no workflow overview'); + }); +}); +describe('getWorkflowOverviewById', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('0 items in workflow overview list', async () => { + // Arrange + const mockOverviewsV1 = { + items: [], + }; + ( + mockOrchestratorService.fetchWorkflowOverview as jest.Mock + ).mockResolvedValue(mockOverviewsV1.items); + // Act + const overviewV2 = await v2.getWorkflowOverviewById('test_workflowId'); + + // Assert + expect(overviewV2).toBeDefined(); + expect(overviewV2.workflowId).toBeUndefined(); + expect(overviewV2.name).toBeUndefined(); + expect(overviewV2.format).toBeUndefined(); + expect(overviewV2.lastTriggeredMs).toBeUndefined(); + expect(overviewV2.lastRunStatus).toBeUndefined(); + expect(overviewV2.category).toEqual('infrastructure'); + expect(overviewV2.avgDurationMs).toBeUndefined(); + expect(overviewV2.description).toBeUndefined(); + }); + + it('1 item in workflow overview list', async () => { + // Arrange + const mockOverviewsV1 = generateTestWorkflowOverview({ + name: 'test_workflowId', + }); + + ( + mockOrchestratorService.fetchWorkflowOverview as jest.Mock + ).mockResolvedValue(mockOverviewsV1); + + // Act + const result: WorkflowOverviewDTO = await v2.getWorkflowOverviewById( + 'test_workflowId', + ); + + // Assert + expect(result).toEqual(mapToWorkflowOverviewDTO(mockOverviewsV1)); + }); +}); + +describe('getWorkflowById', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("Workflow doesn't exists", async () => { + ( + mockOrchestratorService.fetchWorkflowSource as jest.Mock + ).mockRejectedValue(new Error('No definition')); + // Act + const promise = v2.getWorkflowById('test_workflowId'); + + // Assert + await expect(promise).rejects.toThrow('No definition'); + }); + + it('1 items in workflow list', async () => { + const testFormat = 'yaml'; + const wfDefinition = generateWorkflowDefinition; + const source = toWorkflowYaml(wfDefinition); + + ( + mockOrchestratorService.fetchWorkflowSource as jest.Mock + ).mockResolvedValue(source); + // Act + const workflowV2 = await v2.getWorkflowById('test_workflowId'); + + // Assert + expect(workflowV2).toBeDefined(); + expect(workflowV2.id).toBeDefined(); + expect(workflowV2.id).toEqual(wfDefinition.id); + expect(workflowV2.name).toEqual(wfDefinition.name); + expect(workflowV2.format).toEqual(testFormat); + expect(workflowV2.description).toEqual(wfDefinition.description); + expect(workflowV2.category).toEqual('infrastructure'); + expect(workflowV2.annotations).toBeDefined(); + }); +}); + +describe('executeWorkflow', () => { + beforeEach(async () => { + jest.clearAllMocks(); + }); + it('executes a given workflow', async () => { + // Arrange + const workflowInfo = generateTestWorkflowInfo(); + const execResponse = generateTestExecuteWorkflowResponse(); + (mockOrchestratorService.fetchWorkflowInfo as jest.Mock).mockResolvedValue( + workflowInfo, + ); + + (mockOrchestratorService.executeWorkflow as jest.Mock).mockResolvedValue( + execResponse, + ); + const workflowData = { + inputData: { + customAttrib: 'My customAttrib', + }, + }; + // Act + const actualResultV2: ExecuteWorkflowResponseDTO = await v2.executeWorkflow( + workflowData, + workflowInfo.id, + 'businessKey', + ); + + // Assert + expect(actualResultV2).toBeDefined(); + expect(actualResultV2.id).toBeDefined(); + expect(actualResultV2.id).toEqual(execResponse.id); + expect(actualResultV2).toEqual(execResponse); + }); +}); + +describe('getInstances', () => { + const mockRequest: any = { + query: {}, + }; + const pagination = buildPagination(mockRequest); + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("Instance doesn't exist", async () => { + // Arrange + (mockOrchestratorService.fetchInstances as jest.Mock).mockRejectedValue( + new Error('No instance'), + ); + // Act + const promise = v2.getInstances(pagination); + + // Assert + await expect(promise).rejects.toThrow('No instance'); + }); + + it('1 item in process instance list', async () => { + const processInstance = generateProcessInstance(1); + + (mockOrchestratorService.fetchInstances as jest.Mock).mockResolvedValue([ + processInstance, + ]); + + // Act + const processInstanceV2: ProcessInstanceListResultDTO = + await v2.getInstances(pagination); + + // Assert + expect(processInstanceV2).toBeDefined(); + expect(processInstanceV2.items).toBeDefined(); + expect(processInstanceV2.items).toHaveLength(1); + expect(processInstanceV2.items?.[0].id).toEqual(processInstance.id); + }); + it('10 items in process instance list', async () => { + const howmany = 10; + const processInstances = generateProcessInstances(howmany); + + (mockOrchestratorService.fetchInstances as jest.Mock).mockResolvedValue( + processInstances, + ); + + // Act + const processInstanceList: ProcessInstanceListResultDTO = + await v2.getInstances(pagination); + + // Assert + expect(processInstanceList).toBeDefined(); + expect(processInstanceList.items).toBeDefined(); + expect(processInstanceList.items).toHaveLength(howmany); + for (let i = 0; i < howmany; i++) { + expect(processInstanceList.items?.[i].id).toEqual(processInstances[i].id); + } + }); +}); + +describe('getInstanceById', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("Instance doesn't exist", async () => { + (mockOrchestratorService.fetchInstance as jest.Mock).mockRejectedValue( + new Error('No instance'), + ); + // Act + const promise = v2.getInstanceById('testInstanceId'); + + // Assert + await expect(promise).rejects.toThrow('No instance'); + }); + + it('Instance exists and do not include assessment', async () => { + const processInstance = generateProcessInstance(1); + + (mockOrchestratorService.fetchInstance as jest.Mock).mockResolvedValue( + processInstance, + ); + + // Act + const processInstanceV2: AssessedProcessInstanceDTO = + await v2.getInstanceById(processInstance.id); + + // Assert + expect(mockOrchestratorService.fetchInstance).toHaveBeenCalledTimes(1); + expect(processInstanceV2).toBeDefined(); + expect(processInstanceV2.instance).toBeDefined(); + expect(processInstanceV2.assessedBy).toBeUndefined(); + expect(processInstanceV2.instance.id).toEqual(processInstance.id); + }); + + it('Instance exists, assessment non empty string', async () => { + const processInstance = generateProcessInstance(1); + processInstance.businessKey = 'testBusinessKey'; + const assessedBy = generateProcessInstance(1); + assessedBy.id = processInstance.businessKey; + + (mockOrchestratorService.fetchInstance as jest.Mock) + .mockResolvedValueOnce(processInstance) + .mockResolvedValueOnce(assessedBy); + + // Act + const processInstanceV2: AssessedProcessInstanceDTO = + await v2.getInstanceById(processInstance.id, true); + + // Assert + expect(mockOrchestratorService.fetchInstance).toHaveBeenCalledTimes(2); + expect(processInstanceV2).toBeDefined(); + expect(processInstanceV2.instance).toBeDefined(); + expect(processInstanceV2.assessedBy).toBeDefined(); + expect(processInstanceV2.assessedBy?.id).toEqual( + processInstance.businessKey, + ); + expect(processInstanceV2.instance.id).toEqual(processInstance.id); + }); +}); + +describe('getWorkflowStatuses', () => { + beforeEach(async () => { + jest.clearAllMocks(); + }); + + it('returns all possible workflow status types', async () => { + const expectedResultV2 = [ + { key: 'Active', value: 'ACTIVE' }, + { key: 'Error', value: 'ERROR' }, + { key: 'Completed', value: 'COMPLETED' }, + { key: 'Aborted', value: 'ABORTED' }, + { key: 'Suspended', value: 'SUSPENDED' }, + { key: 'Pending', value: 'PENDING' }, + ]; + + // Act + const actualResultV2: WorkflowRunStatusDTO[] = + await v2.getWorkflowStatuses(); + + // Assert + expect(actualResultV2).toBeDefined(); + expect(actualResultV2).toEqual(expectedResultV2); + }); +}); + +describe('abortWorkflow', () => { + beforeEach(async () => { + jest.clearAllMocks(); + }); + + it('aborts workflows', async () => { + // Arrange + const workflowId = 'testInstanceId'; + ( + mockOrchestratorService.abortWorkflowInstance as jest.Mock + ).mockResolvedValue({} as any); + + const expectedResult = `Workflow instance ${workflowId} successfully aborted`; + + // Act + const actualResult: string = await v2.abortWorkflow(workflowId); + + // Assert + expect(actualResult).toBeDefined(); + expect(actualResult).toEqual(expectedResult); + }); + + it('throws error when abort workflows response has error attribute', async () => { + // Arrange + ( + mockOrchestratorService.abortWorkflowInstance as jest.Mock + ).mockRejectedValue(new Error('Simulated abort workflow error')); + + // Act + const promise = v2.abortWorkflow('instanceId'); + + // Assert + await expect(promise).rejects.toThrow('Simulated abort workflow error'); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/v2.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/v2.ts new file mode 100644 index 00000000..43ec479d --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/api/v2.ts @@ -0,0 +1,277 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ParsedRequest } from 'openapi-backend'; + +import { + AssessedProcessInstanceDTO, + ExecuteWorkflowRequestDTO, + ExecuteWorkflowResponseDTO, + Filter, + ProcessInstance, + ProcessInstanceListResultDTO, + ProcessInstanceState, + ProcessInstanceVariables, + WorkflowDTO, + WorkflowInfo, + WorkflowOverviewDTO, + WorkflowOverviewListResultDTO, + WorkflowRunStatusDTO, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import { Pagination } from '../../types/pagination'; +import { retryAsyncFunction } from '../Helper'; +import { OrchestratorService } from '../OrchestratorService'; +import { + mapToExecuteWorkflowResponseDTO, + mapToProcessInstanceDTO, + mapToWorkflowDTO, + mapToWorkflowOverviewDTO, + mapToWorkflowRunStatusDTO, +} from './mapping/V2Mappings'; + +const FETCH_INSTANCE_MAX_ATTEMPTS = 10; +const FETCH_INSTANCE_RETRY_DELAY_MS = 1000; + +export class V2 { + constructor(private readonly orchestratorService: OrchestratorService) {} + + public async getWorkflowsOverview( + pagination: Pagination, + filter?: Filter, + ): Promise { + const overviews = await this.orchestratorService.fetchWorkflowOverviews({ + pagination, + filter, + }); + if (!overviews) { + throw new Error("Couldn't fetch workflow overviews"); + } + const result: WorkflowOverviewListResultDTO = { + overviews: overviews.map(item => mapToWorkflowOverviewDTO(item)), + paginationInfo: { + pageSize: pagination.limit, + offset: pagination.offset, + totalCount: overviews.length, + }, + }; + return result; + } + + public async getWorkflowOverviewById( + workflowId: string, + ): Promise { + const overview = await this.orchestratorService.fetchWorkflowOverview({ + definitionId: workflowId, + cacheHandler: 'throw', + }); + + if (!overview) { + throw new Error(`Couldn't fetch workflow overview for ${workflowId}`); + } + return mapToWorkflowOverviewDTO(overview); + } + + public async getWorkflowById(workflowId: string): Promise { + const resultV1 = await this.getWorkflowSourceById(workflowId); + return mapToWorkflowDTO(resultV1); + } + + public async getWorkflowSourceById(workflowId: string): Promise { + const source = await this.orchestratorService.fetchWorkflowSource({ + definitionId: workflowId, + cacheHandler: 'throw', + }); + + if (!source) { + throw new Error(`Couldn't fetch workflow source for ${workflowId}`); + } + + return source; + } + + public async getInstances( + pagination?: Pagination, + filter?: Filter, + workflowId?: string, + ): Promise { + const instances = await this.orchestratorService.fetchInstances({ + pagination, + filter, + workflowId, + }); + const totalCount = await this.orchestratorService.fetchInstancesTotalCount( + workflowId, + filter, + ); + + const result: ProcessInstanceListResultDTO = { + items: instances?.map(mapToProcessInstanceDTO), + paginationInfo: { + pageSize: pagination?.limit, + offset: pagination?.offset, + totalCount: totalCount, + }, + }; + return result; + } + + public async getInstanceById( + instanceId: string, + includeAssessment: boolean = false, + ): Promise { + const instance = await this.orchestratorService.fetchInstance({ + instanceId, + cacheHandler: 'throw', + }); + + if (!instance) { + throw new Error(`Couldn't fetch process instance ${instanceId}`); + } + + let assessedByInstance: ProcessInstance | undefined; + + if (includeAssessment && instance.businessKey) { + assessedByInstance = await this.orchestratorService.fetchInstance({ + instanceId: instance.businessKey, + cacheHandler: 'throw', + }); + } + + return { + instance: mapToProcessInstanceDTO(instance), + assessedBy: assessedByInstance + ? mapToProcessInstanceDTO(assessedByInstance) + : undefined, + }; + } + + public async executeWorkflow( + executeWorkflowRequestDTO: ExecuteWorkflowRequestDTO, + workflowId: string, + businessKey: string | undefined, + ): Promise { + if (Object.keys(executeWorkflowRequestDTO?.inputData).length === 0) { + throw new Error( + `ExecuteWorkflowRequestDTO.inputData is required for executing workflow with id ${workflowId}`, + ); + } + + const definition = await this.orchestratorService.fetchWorkflowInfo({ + definitionId: workflowId, + cacheHandler: 'throw', + }); + if (!definition) { + throw new Error(`Couldn't fetch workflow definition for ${workflowId}`); + } + if (!definition.serviceUrl) { + throw new Error(`ServiceURL is not defined for workflow ${workflowId}`); + } + const executionResponse = await this.orchestratorService.executeWorkflow({ + definitionId: workflowId, + inputData: + executeWorkflowRequestDTO?.inputData as ProcessInstanceVariables, + serviceUrl: definition.serviceUrl, + businessKey, + cacheHandler: 'throw', + }); + + if (!executionResponse) { + throw new Error(`Couldn't execute workflow ${workflowId}`); + } + + // Making sure the instance data is available before returning + await retryAsyncFunction({ + asyncFn: () => + this.orchestratorService.fetchInstance({ + instanceId: executionResponse.id, + cacheHandler: 'throw', + }), + maxAttempts: FETCH_INSTANCE_MAX_ATTEMPTS, + delayMs: FETCH_INSTANCE_RETRY_DELAY_MS, + }); + + if (!executionResponse) { + throw new Error('Error executing workflow with id ${workflowId}'); + } + + return mapToExecuteWorkflowResponseDTO(workflowId, executionResponse); + } + + public async retriggerInstance( + workflowId: string, + instanceId: string, + ): Promise { + const definition = await this.orchestratorService.fetchWorkflowInfo({ + definitionId: workflowId, + cacheHandler: 'throw', + }); + if (!definition) { + throw new Error(`Couldn't fetch workflow definition for ${workflowId}`); + } + if (!definition.serviceUrl) { + throw new Error(`ServiceURL is not defined for workflow ${workflowId}`); + } + const response = await this.orchestratorService.retriggerWorkflow({ + definitionId: workflowId, + instanceId: instanceId, + serviceUrl: definition.serviceUrl, + cacheHandler: 'throw', + }); + + if (!response) { + throw new Error( + `Couldn't retrigger instance ${instanceId} of workflow ${workflowId}`, + ); + } + } + + public async abortWorkflow(instanceId: string): Promise { + await this.orchestratorService.abortWorkflowInstance({ + instanceId, + cacheHandler: 'throw', + }); + return `Workflow instance ${instanceId} successfully aborted`; + } + + public async getWorkflowStatuses(): Promise { + return [ + ProcessInstanceState.Active, + ProcessInstanceState.Error, + ProcessInstanceState.Completed, + ProcessInstanceState.Aborted, + ProcessInstanceState.Suspended, + ProcessInstanceState.Pending, + ].map(status => mapToWorkflowRunStatusDTO(status)); + } + + public async getWorkflowInputSchemaById( + workflowId: string, + serviceUrl: string, + ): Promise { + return this.orchestratorService.fetchWorkflowInfoOnService({ + definitionId: workflowId, + serviceUrl: serviceUrl, + cacheHandler: 'throw', + }); + } + + public extractQueryParam( + req: ParsedRequest, + key: string, + ): string | undefined { + return req.query[key] as string | undefined; + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/constants.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/constants.ts new file mode 100644 index 00000000..537f3c62 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/constants.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const WORKFLOW_DATA_KEY = 'workflowdata'; + +export const INTERNAL_SERVER_ERROR_MESSAGE = 'internal server error'; +export const FETCH_PROCESS_INSTANCES_SORT_FIELD = 'start'; diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/router.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/router.ts new file mode 100644 index 00000000..87d48c13 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/router.ts @@ -0,0 +1,841 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter'; +import { + HttpAuthService, + LoggerService, + PermissionsService, + resolvePackagePath, + SchedulerService, +} from '@backstage/backend-plugin-api'; +import type { Config } from '@backstage/config'; +import type { DiscoveryApi } from '@backstage/core-plugin-api'; +import { + AuthorizeResult, + BasicPermission, +} from '@backstage/plugin-permission-common'; +import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; +import type { JsonObject, JsonValue } from '@backstage/types'; + +import { UnauthorizedError } from '@backstage-community/plugin-rbac-common'; +import { + AuditLogger, + DefaultAuditLogger, +} from '@janus-idp/backstage-plugin-audit-log-node'; +import { fullFormats } from 'ajv-formats/dist/formats'; +import express, { Router } from 'express'; +import { Request as HttpRequest } from 'express-serve-static-core'; +import { OpenAPIBackend, Request } from 'openapi-backend'; + +import { + Filter, + openApiDocument, + orchestratorPermissions, + orchestratorWorkflowExecutePermission, + orchestratorWorkflowInstanceAbortPermission, + orchestratorWorkflowInstanceReadPermission, + orchestratorWorkflowInstancesReadPermission, + orchestratorWorkflowReadPermission, + QUERY_PARAM_BUSINESS_KEY, + QUERY_PARAM_INCLUDE_ASSESSMENT, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import * as pkg from '../../package.json'; +import { RouterOptions } from '../routerWrapper'; +import { buildPagination } from '../types/pagination'; +import { V2 } from './api/v2'; +import { INTERNAL_SERVER_ERROR_MESSAGE } from './constants'; +import { DataIndexService } from './DataIndexService'; +import { DataInputSchemaService } from './DataInputSchemaService'; +import { OrchestratorService } from './OrchestratorService'; +import { ScaffolderService } from './ScaffolderService'; +import { SonataFlowService } from './SonataFlowService'; +import { WorkflowCacheService } from './WorkflowCacheService'; + +interface PublicServices { + dataInputSchemaService: DataInputSchemaService; + orchestratorService: OrchestratorService; +} + +interface RouterApi { + openApiBackend: OpenAPIBackend; + v2: V2; +} + +const authorize = async ( + request: HttpRequest, + permission: BasicPermission, + permissionsSvc: PermissionsService, + httpAuth: HttpAuthService, +) => { + const decision = ( + await permissionsSvc.authorize([{ permission: permission }], { + credentials: await httpAuth.credentials(request), + }) + )[0]; + + return decision; +}; + +export async function createBackendRouter( + options: RouterOptions, +): Promise { + const { + config, + logger, + discovery, + catalogApi, + urlReader, + scheduler, + permissions, + auth, + httpAuth, + } = options; + const publicServices = initPublicServices(logger, config, scheduler); + + const routerApi = await initRouterApi(publicServices.orchestratorService); + + const auditLogger = new DefaultAuditLogger({ + logger: logger, + authService: auth, + httpAuthService: httpAuth, + }); + + const router = Router(); + const permissionsIntegrationRouter = createPermissionIntegrationRouter({ + permissions: orchestratorPermissions, + }); + router.use(express.json()); + router.use(permissionsIntegrationRouter); + router.use('/workflows', express.text()); + router.use('/static', express.static(resolvePackagePath(pkg.name, 'static'))); + router.get('/health', (_, response) => { + logger.info('PONG!'); + response.json({ status: 'ok' }); + }); + + const scaffolderService: ScaffolderService = new ScaffolderService( + logger, + config, + catalogApi, + urlReader, + ); + + setupInternalRoutes( + publicServices, + routerApi, + permissions, + httpAuth, + auditLogger, + ); + setupExternalRoutes(router, discovery, scaffolderService, auditLogger); + + router.use((req, res, next) => { + if (!next) { + throw new Error('next is undefined'); + } + + return routerApi.openApiBackend.handleRequest( + req as Request, + req, + res, + next, + ); + }); + + const middleware = MiddlewareFactory.create({ logger, config }); + + router.use(middleware.error()); + + return router; +} + +function initPublicServices( + logger: LoggerService, + config: Config, + scheduler: SchedulerService, +): PublicServices { + const dataIndexUrl = config.getString('orchestrator.dataIndexService.url'); + const dataIndexService = new DataIndexService(dataIndexUrl, logger); + const sonataFlowService = new SonataFlowService(dataIndexService, logger); + + const workflowCacheService = new WorkflowCacheService( + logger, + dataIndexService, + sonataFlowService, + ); + workflowCacheService.schedule({ scheduler: scheduler }); + + const orchestratorService = new OrchestratorService( + sonataFlowService, + dataIndexService, + workflowCacheService, + ); + + const dataInputSchemaService = new DataInputSchemaService(); + + return { + orchestratorService, + dataInputSchemaService, + }; +} + +async function initRouterApi( + orchestratorService: OrchestratorService, +): Promise { + const openApiBackend = new OpenAPIBackend({ + definition: openApiDocument, + strict: false, + ajvOpts: { + strict: false, + strictSchema: false, + verbose: true, + addUsedSchema: false, + formats: fullFormats, // open issue: https://github.com/openapistack/openapi-backend/issues/280 + }, + handlers: { + validationFail: async ( + c, + _req: express.Request, + res: express.Response, + ) => { + console.log('validationFail', c.operation); + res.status(400).json({ err: c.validation.errors }); + }, + notFound: async (_c, req: express.Request, res: express.Response) => { + res.status(404).json({ err: `${req.path} path not found` }); + }, + notImplemented: async (_c, req: express.Request, res: express.Response) => + res.status(500).json({ err: `${req.path} not implemented` }), + }, + }); + await openApiBackend.init(); + const v2 = new V2(orchestratorService); + return { v2, openApiBackend }; +} + +// ====================================================== +// Internal Backstage API calls to delegate to SonataFlow +// ====================================================== +function setupInternalRoutes( + services: PublicServices, + routerApi: RouterApi, + permissions: PermissionsService, + httpAuth: HttpAuthService, + auditLogger: AuditLogger, +) { + function manageDenyAuthorization( + endpointName: string, + endpoint: string, + req: HttpRequest, + ) { + const error = new UnauthorizedError(); + auditLogger.auditLog({ + eventName: `${endpointName}EndpointHit`, + stage: 'authorization', + status: 'failed', + level: 'error', + request: req, + response: { + status: 403, + body: { + errors: [ + { + name: error.name, + message: error.message, + }, + ], + }, + }, + errors: [error], + message: `Not authorize to request the ${endpoint} endpoint`, + }); + throw error; + } + + function auditLogRequestError( + error: any, + endpointName: string, + endpoint: string, + req: HttpRequest, + ) { + auditLogger.auditLog({ + eventName: `${endpointName}EndpointHit`, + stage: 'completion', + status: 'failed', + level: 'error', + request: req, + response: { + status: 500, + body: { + errors: [ + { + name: error.name, + message: error.message || INTERNAL_SERVER_ERROR_MESSAGE, + }, + ], + }, + }, + errors: [error], + message: `Error occured while requesting the '${endpoint}' endpoint`, + }); + } + + // v2 + routerApi.openApiBackend.register( + 'getWorkflowsOverview', + async (_c, req, res: express.Response, next) => { + const endpointName = 'getWorkflowsOverview'; + const endpoint = '/v2/workflows/overview'; + + auditLogger.auditLog({ + eventName: 'getWorkflowsOverview', + stage: 'start', + status: 'succeeded', + level: 'debug', + request: req, + message: `Received request to '${endpoint}' endpoint`, + }); + const decision = await authorize( + req, + orchestratorWorkflowInstancesReadPermission, + permissions, + httpAuth, + ); + if (decision.result === AuthorizeResult.DENY) { + manageDenyAuthorization(endpointName, endpoint, req); + } + return routerApi.v2 + .getWorkflowsOverview(buildPagination(req), getRequestFilters(req)) + .then(result => res.json(result)) + .catch(error => { + auditLogRequestError(error, endpointName, endpoint, req); + next(error); + }); + }, + ); + + // v2 + routerApi.openApiBackend.register( + 'getWorkflowSourceById', + async (c, _req, res, next) => { + const workflowId = c.request.params.workflowId as string; + const endpointName = 'getWorkflowSourceById'; + const endpoint = `/v2/workflows/${workflowId}/source`; + + auditLogger.auditLog({ + eventName: endpointName, + stage: 'start', + status: 'succeeded', + level: 'debug', + request: _req, + message: `Received request to '${endpoint}' endpoint`, + }); + + const decision = await authorize( + _req, + orchestratorWorkflowReadPermission, + permissions, + httpAuth, + ); + if (decision.result === AuthorizeResult.DENY) { + manageDenyAuthorization(endpointName, endpoint, _req); + } + + try { + const result = await routerApi.v2.getWorkflowSourceById(workflowId); + res.status(200).contentType('text/plain').send(result); + } catch (error) { + auditLogRequestError(error, endpointName, endpoint, _req); + next(error); + } + }, + ); + + // v2 + routerApi.openApiBackend.register( + 'executeWorkflow', + async (c, req: express.Request, res: express.Response, next) => { + const workflowId = c.request.params.workflowId as string; + const endpointName = 'executeWorkflow'; + const endpoint = `/v2/workflows/${workflowId}/execute`; + + auditLogger.auditLog({ + eventName: endpointName, + stage: 'start', + status: 'succeeded', + level: 'debug', + request: req, + message: `Received request to '${endpoint}' endpoint`, + }); + + const decision = await authorize( + req, + orchestratorWorkflowExecutePermission, + permissions, + httpAuth, + ); + if (decision.result === AuthorizeResult.DENY) { + manageDenyAuthorization(endpointName, endpoint, req); + } + + const businessKey = routerApi.v2.extractQueryParam( + c.request, + QUERY_PARAM_BUSINESS_KEY, + ); + + const executeWorkflowRequestDTO = req.body; + + return routerApi.v2 + .executeWorkflow(executeWorkflowRequestDTO, workflowId, businessKey) + .then(result => res.status(200).json(result)) + .catch(error => { + auditLogRequestError(error, endpointName, endpoint, req); + next(error); + }); + }, + ); + + // v2 + routerApi.openApiBackend.register( + 'retriggerInstance', + async (c, req: express.Request, res: express.Response, next) => { + const workflowId = c.request.params.workflowId as string; + const instanceId = c.request.params.instanceId as string; + const endpointName = 'retriggerInstance'; + const endpoint = `/v2/workflows/${workflowId}/${instanceId}/retrigger`; + + auditLogger.auditLog({ + eventName: endpointName, + stage: 'start', + status: 'succeeded', + level: 'debug', + request: req, + message: `Received request to '${endpoint}' endpoint`, + }); + + const decision = await authorize( + req, + orchestratorWorkflowExecutePermission, + permissions, + httpAuth, + ); + if (decision.result === AuthorizeResult.DENY) { + manageDenyAuthorization(endpointName, endpoint, req); + } + + await routerApi.v2 + .retriggerInstance(workflowId, instanceId) + .then(result => res.status(200).json(result)) + .catch(error => { + auditLogRequestError(error, endpointName, endpoint, req); + next(error); + }); + }, + ); + + // v2 + routerApi.openApiBackend.register( + 'getWorkflowOverviewById', + async (c, _req: express.Request, res: express.Response, next) => { + const workflowId = c.request.params.workflowId as string; + const endpointName = 'getWorkflowOverviewById'; + const endpoint = `/v2/workflows/${workflowId}/overview`; + + auditLogger.auditLog({ + eventName: endpointName, + stage: 'start', + status: 'succeeded', + level: 'debug', + request: _req, + message: `Received request to '${endpoint}' endpoint`, + }); + + const decision = await authorize( + _req, + orchestratorWorkflowReadPermission, + permissions, + httpAuth, + ); + if (decision.result === AuthorizeResult.DENY) { + manageDenyAuthorization(endpointName, endpoint, _req); + } + return routerApi.v2 + .getWorkflowOverviewById(workflowId) + .then(result => res.json(result)) + .catch(error => { + auditLogRequestError(error, endpointName, endpoint, _req); + next(error); + }); + }, + ); + + // v2 + routerApi.openApiBackend.register( + 'getWorkflowStatuses', + async (_c, _req: express.Request, res: express.Response, next) => { + const endpointName = 'getWorkflowStatuses'; + const endpoint = '/v2/workflows/instances/statuses'; + + auditLogger.auditLog({ + eventName: endpointName, + stage: 'start', + status: 'succeeded', + level: 'debug', + request: _req, + message: `Received request to '${endpoint}' endpoint`, + }); + const decision = await authorize( + _req, + orchestratorWorkflowInstanceReadPermission, + permissions, + httpAuth, + ); + if (decision.result === AuthorizeResult.DENY) { + manageDenyAuthorization(endpointName, endpoint, _req); + } + return routerApi.v2 + .getWorkflowStatuses() + .then(result => res.status(200).json(result)) + .catch(error => { + auditLogRequestError(error, endpointName, endpoint, _req); + next(error); + }); + }, + ); + + // v2 + routerApi.openApiBackend.register( + 'getWorkflowInputSchemaById', + async (c, req: express.Request, res: express.Response, next) => { + const workflowId = c.request.params.workflowId as string; + const instanceId = c.request.query.instanceId as string; + const endpointName = 'getWorkflowInputSchemaById'; + const endpoint = `/v2/workflows/${workflowId}/inputSchema`; + try { + auditLogger.auditLog({ + eventName: endpointName, + stage: 'start', + status: 'succeeded', + level: 'debug', + request: req, + message: `Received request to '${endpoint}' endpoint`, + }); + const decision = await authorize( + req, + orchestratorWorkflowInstanceReadPermission, + permissions, + httpAuth, + ); + if (decision.result === AuthorizeResult.DENY) { + manageDenyAuthorization(endpointName, endpoint, req); + } + + const workflowDefinition = + await services.orchestratorService.fetchWorkflowInfo({ + definitionId: workflowId, + cacheHandler: 'throw', + }); + + if (!workflowDefinition) { + throw new Error( + `Failed to fetch workflow info for workflow ${workflowId}`, + ); + } + const serviceUrl = workflowDefinition.serviceUrl; + if (!serviceUrl) { + throw new Error( + `Service URL is not defined for workflow ${workflowId}`, + ); + } + + const definition = + await services.orchestratorService.fetchWorkflowDefinition({ + definitionId: workflowId, + cacheHandler: 'throw', + }); + + if (!definition) { + throw new Error( + 'Failed to fetch workflow definition for workflow ${workflowId}', + ); + } + + if (!definition.dataInputSchema) { + res.status(200).json({}); + return; + } + + const instanceVariables = instanceId + ? await services.orchestratorService.fetchInstanceVariables({ + instanceId, + cacheHandler: 'throw', + }) + : undefined; + + const workflowData = instanceVariables + ? services.dataInputSchemaService.extractWorkflowData( + instanceVariables, + ) + : undefined; + + const workflowInfo = await routerApi.v2 + .getWorkflowInputSchemaById(workflowId, serviceUrl) + .catch((error: { message: string }) => { + auditLogRequestError(error, endpointName, endpoint, req); + res.status(500).json({ + message: error.message || INTERNAL_SERVER_ERROR_MESSAGE, + }); + }); + + if ( + !workflowInfo || + !workflowInfo.inputSchema || + !workflowInfo.inputSchema.properties + ) { + res.status(200).json({}); + return; + } + + const inputSchemaProps = workflowInfo.inputSchema.properties; + let inputData; + + if (workflowData) { + inputData = Object.keys(inputSchemaProps) + .filter(k => k in workflowData) + .reduce((result, k) => { + if (!workflowData[k]) { + return result; + } + result[k] = workflowData[k]; + return result; + }, {} as JsonObject); + } + + res.status(200).json({ + inputSchema: workflowInfo.inputSchema, + data: inputData, + }); + } catch (err) { + auditLogRequestError(err, endpointName, endpoint, req); + next(err); + } + }, + ); + + // v2 + routerApi.openApiBackend.register( + 'getWorkflowInstances', + async (c, req: express.Request, res: express.Response, next) => { + const endpointName = 'getWorkflowInstances'; + const workflowId = c.request.params.workflowId as string; + const endpoint = `/v2/workflows/${workflowId}/instances`; + + auditLogger.auditLog({ + eventName: endpointName, + stage: 'start', + status: 'succeeded', + level: 'debug', + request: req, + message: `Received request to '${endpoint}' endpoint`, + }); + + const decision = await authorize( + req, + orchestratorWorkflowInstancesReadPermission, + permissions, + httpAuth, + ); + if (decision.result === AuthorizeResult.DENY) { + manageDenyAuthorization(endpointName, endpoint, req); + } + return routerApi.v2 + .getInstances(buildPagination(req), getRequestFilters(req), workflowId) + .then(result => res.json(result)) + .catch(error => { + auditLogRequestError(error, endpointName, endpoint, req); + next(error); + }); + }, + ); + + // v2 + routerApi.openApiBackend.register( + 'getInstances', + async (_c, req: express.Request, res: express.Response, next) => { + const endpointName = 'getInstances'; + const endpoint = `/v2/workflows/instances`; + + auditLogger.auditLog({ + eventName: endpointName, + stage: 'start', + status: 'succeeded', + level: 'debug', + request: req, + message: `Received request to '${endpoint}' endpoint`, + }); + + const decision = await authorize( + req, + orchestratorWorkflowInstancesReadPermission, + permissions, + httpAuth, + ); + if (decision.result === AuthorizeResult.DENY) { + manageDenyAuthorization(endpointName, endpoint, req); + } + return routerApi.v2 + .getInstances(buildPagination(req), getRequestFilters(req)) + .then(result => res.json(result)) + .catch(error => { + auditLogRequestError(error, endpointName, endpoint, req); + next(error); + }); + }, + ); + + // v2 + routerApi.openApiBackend.register( + 'getInstanceById', + async (c, _req: express.Request, res: express.Response, next) => { + const instanceId = c.request.params.instanceId as string; + const endpointName = 'getInstanceById'; + const endpoint = `/v2/workflows/instances/${instanceId}`; + + auditLogger.auditLog({ + eventName: endpointName, + stage: 'start', + status: 'succeeded', + level: 'debug', + request: _req, + message: `Received request to '${endpoint}' endpoint`, + }); + + const decision = await authorize( + _req, + orchestratorWorkflowInstanceReadPermission, + permissions, + httpAuth, + ); + if (decision.result === AuthorizeResult.DENY) { + manageDenyAuthorization(endpointName, endpoint, _req); + } + const includeAssessment = routerApi.v2.extractQueryParam( + c.request, + QUERY_PARAM_INCLUDE_ASSESSMENT, + ); + return routerApi.v2 + .getInstanceById(instanceId, !!includeAssessment) + .then(result => res.status(200).json(result)) + .catch(error => { + auditLogRequestError(error, endpointName, endpoint, _req); + next(error); + }); + }, + ); + + // v2 + routerApi.openApiBackend.register( + 'abortWorkflow', + async (c, _req, res, next) => { + const instanceId = c.request.params.instanceId as string; + const endpointName = 'abortWorkflow'; + const endpoint = `/v2/workflows/instances/${instanceId}/abort`; + + auditLogger.auditLog({ + eventName: endpointName, + stage: 'start', + status: 'succeeded', + level: 'debug', + request: _req, + message: `Received request to '${endpoint}' endpoint`, + }); + + const decision = await authorize( + _req, + orchestratorWorkflowInstanceAbortPermission, + permissions, + httpAuth, + ); + if (decision.result === AuthorizeResult.DENY) { + manageDenyAuthorization(endpointName, endpoint, _req); + } + return routerApi.v2 + .abortWorkflow(instanceId) + .then(result => res.json(result)) + .catch(error => { + auditLogRequestError(error, endpointName, endpoint, _req); + next(error); + }); + }, + ); +} + +// ====================================================== +// External SonataFlow API calls to delegate to Backstage +// ====================================================== +function setupExternalRoutes( + router: express.Router, + discovery: DiscoveryApi, + scaffolderService: ScaffolderService, + auditLogger: AuditLogger, +) { + router.get('/actions', async (req, res) => { + auditLogger.auditLog({ + eventName: 'ActionsEndpointHit', + stage: 'start', + status: 'succeeded', + level: 'debug', + request: req, + message: `Received request to '/actions' endpoint`, + }); + const scaffolderUrl = await discovery.getBaseUrl('scaffolder'); + const response = await fetch(`${scaffolderUrl}/v2/actions`); + const json = await response.json(); + res.status(response.status).json(json); + }); + + router.post('/actions/:actionId', async (req, res) => { + const { actionId } = req.params; + auditLogger.auditLog({ + eventName: 'ActionsActionIdEndpointHit', + stage: 'start', + status: 'succeeded', + level: 'debug', + request: req, + message: `Received request to '/actions/${actionId}' endpoint`, + }); + const instanceId: string | undefined = req.header('kogitoprocinstanceid'); + const body: JsonObject = (await req.body) as JsonObject; + + const filteredBody = Object.fromEntries( + Object.entries(body).filter( + ([, value]) => value !== undefined && value !== null, + ), + ); + + const result: JsonValue = await scaffolderService.executeAction({ + actionId, + instanceId, + input: filteredBody, + }); + res.status(200).json(result); + }); +} + +function getRequestFilters(req: HttpRequest): Filter | undefined { + return req.body.filters ? (req.body.filters as Filter) : undefined; +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/types/pagination.test.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/types/pagination.test.ts new file mode 100644 index 00000000..8e54657b --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/types/pagination.test.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { buildPagination } from './pagination'; + +describe('buildPagination()', () => { + it('should build the correct pagination obj when no query parameters are passed', () => { + const mockRequest: any = { + body: {}, + }; + expect(buildPagination(mockRequest)).toEqual({}); + }); + it('should build the correct pagination obj when partial query parameters are passed', () => { + const mockRequest: any = { + body: { + paginationInfo: { + orderBy: 'lastUpdated', + }, + }, + }; + expect(buildPagination(mockRequest)).toEqual({ + limit: undefined, + offset: undefined, + order: undefined, + sortField: 'lastUpdated', + }); + }); + it('should build the correct pagination obj when all query parameters are passed', () => { + const mockRequest: any = { + body: { + paginationInfo: { + offset: 1, + pageSize: 50, + orderBy: 'lastUpdated', + orderDirection: 'DESC', + }, + }, + }; + expect(buildPagination(mockRequest)).toEqual({ + limit: 50, + offset: 1, + order: 'DESC', + sortField: 'lastUpdated', + }); + }); + it('should build the correct pagination obj when non numeric value passed to number fields', () => { + const mockRequest: any = { + body: { + paginationInfo: { + offset: 'abc', + pageSize: 'cde', + }, + }, + }; + expect(buildPagination(mockRequest)).toEqual({ + limit: undefined, + offset: undefined, + order: undefined, + sortField: undefined, + }); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/types/pagination.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/types/pagination.ts new file mode 100644 index 00000000..b60ab673 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/types/pagination.ts @@ -0,0 +1,90 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Request } from 'express-serve-static-core'; + +import { PaginationInfoDTO } from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +export interface Pagination { + offset?: number; + limit?: number; + order?: string; + sortField?: string; +} + +export function buildPagination(req: Request): Pagination { + const pagination: Pagination = { + limit: undefined, + offset: undefined, + order: undefined, + sortField: undefined, + }; + + if (!req.body?.paginationInfo) { + return pagination; + } + const { offset, pageSize, orderBy, orderDirection } = req.body + .paginationInfo as PaginationInfoDTO; + + if (!isNaN(Number(offset))) { + pagination.offset = Number(offset); + } + + if (!isNaN(Number(pageSize))) { + pagination.limit = Number(pageSize); + } + + if (orderBy) { + pagination.sortField = String(orderBy); + } + + if (orderDirection) { + pagination.order = String(orderDirection).toUpperCase(); + } + return pagination; +} + +export function buildPaginationTmp( + paginationInfo?: PaginationInfoDTO, +): Pagination { + const pagination: Pagination = { + limit: undefined, + offset: undefined, + order: undefined, + sortField: undefined, + }; + + if (!paginationInfo) { + return pagination; + } + const { offset, pageSize, orderBy, orderDirection } = paginationInfo; + + if (!isNaN(Number(offset))) { + pagination.offset = Number(offset); + } + + if (!isNaN(Number(pageSize))) { + pagination.limit = Number(pageSize); + } + + if (orderBy) { + pagination.sortField = String(orderBy); + } + + if (orderDirection) { + pagination.order = String(orderDirection).toUpperCase(); + } + return pagination; +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/tsconfig.json b/workspaces/orchestrator/plugins/orchestrator-backend/tsconfig.json new file mode 100644 index 00000000..f75e5771 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@backstage/cli/config/tsconfig.json", + "include": ["src", "dev"], + "exclude": ["node_modules"], + "compilerOptions": { + "outDir": "../../dist-types/plugins/orchestrator-backend", + "rootDir": "." + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/turbo.json b/workspaces/orchestrator/plugins/orchestrator-backend/turbo.json new file mode 100644 index 00000000..4ecb3baa --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/turbo.json @@ -0,0 +1,8 @@ +{ + "extends": ["//"], + "tasks": { + "tsc": { + "outputs": ["../../dist-types/plugins/orchestrator-backend/**"] + } + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-common/.eslintignore b/workspaces/orchestrator/plugins/orchestrator-common/.eslintignore new file mode 100644 index 00000000..812b3dbe --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/.eslintignore @@ -0,0 +1,7 @@ +dist-dynamic +dist-scalprum +CHANGELOG.md +**/generated/** +templates +*.hbs +renovate.json \ No newline at end of file diff --git a/workspaces/orchestrator/plugins/orchestrator-common/.eslintrc.js b/workspaces/orchestrator/plugins/orchestrator-common/.eslintrc.js new file mode 100644 index 00000000..11ceb061 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + ignorePatterns: ['src/generated/client/**'], +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-common/.gitignore b/workspaces/orchestrator/plugins/orchestrator-common/.gitignore new file mode 100644 index 00000000..cc6b2d49 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/.gitignore @@ -0,0 +1 @@ +# src/generated/client diff --git a/workspaces/orchestrator/plugins/orchestrator-common/.lintstagedrc.json b/workspaces/orchestrator/plugins/orchestrator-common/.lintstagedrc.json new file mode 100644 index 00000000..d73476f0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/.lintstagedrc.json @@ -0,0 +1,4 @@ +{ + "*": "prettier --ignore-unknown --no-warn-ignored --write", + "*.{js,jsx,ts,tsx,mjs,cjs}": "backstage-cli package lint --fix" +} diff --git a/workspaces/orchestrator/plugins/orchestrator-common/.prettierignore b/workspaces/orchestrator/plugins/orchestrator-common/.prettierignore new file mode 100644 index 00000000..f7783429 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/.prettierignore @@ -0,0 +1,13 @@ +dist +dist-types +coverage +.vscode +CHANGELOG.md +generated +templates +*.hbs +renovate.json +dist-dynamic +dist-scalprum +playwright-report +./src/generated diff --git a/workspaces/orchestrator/plugins/orchestrator-common/.prettierrc.js b/workspaces/orchestrator/plugins/orchestrator-common/.prettierrc.js new file mode 100644 index 00000000..5f81a8a0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/.prettierrc.js @@ -0,0 +1,20 @@ +// @ts-check + +/** @type {import("@ianvs/prettier-plugin-sort-imports").PrettierConfig} */ +module.exports = { + ...require('@spotify/prettier-config'), + plugins: ['@ianvs/prettier-plugin-sort-imports'], + importOrder: [ + '^react(.*)$', + '', + '^@backstage/(.*)$', + '', + '', + '', + '^@red-hat-developer-hub/(.*)$', + '', + '', + '', + '^[.]', + ], +}; diff --git a/workspaces/orchestrator/plugins/orchestrator-common/CHANGELOG.md b/workspaces/orchestrator/plugins/orchestrator-common/CHANGELOG.md new file mode 100644 index 00000000..df7a7013 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/CHANGELOG.md @@ -0,0 +1,222 @@ +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.13.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.13.0...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.13.1) (2024-08-02) + +## 1.24.0 + +### Minor Changes + +- 25f1787: Add enum filters to orchestrator plugin + +## 1.23.1 + +### Patch Changes + +- 0e6bfd3: feat: update Backstage to the latest version + + Update to Backstage 1.32.5 + +## 1.23.0 + +### Minor Changes + +- 8244f28: chore(deps): update to backstage 1.32 + +## 1.22.1 + +### Patch Changes + +- 8bd8660: fix(orchestrator): fix typo in package resolution + +## 1.22.0 + +### Minor Changes + +- d9551ae: feat(deps): update to backstage 1.31 + +### Patch Changes + +- d9551ae: change deps to peer deps in common packages +- d9551ae: upgrade to yarn v3 + +### Bug Fixes + +- **orchestrator:** remove default pagination on v2 endpoints ([#1983](https://github.com/janus-idp/backstage-plugins/issues/1983)) ([5e30274](https://github.com/janus-idp/backstage-plugins/commit/5e302748a25cbad127122407e5258576054eac3d)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.13.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.12.0...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.13.0) (2024-07-26) + +### Features + +- **deps:** update to backstage 1.29 ([#1900](https://github.com/janus-idp/backstage-plugins/issues/1900)) ([f53677f](https://github.com/janus-idp/backstage-plugins/commit/f53677fb02d6df43a9de98c43a9f101a6db76802)) +- **orchestrator:** use v2 endpoints to retrieve instances ([#1956](https://github.com/janus-idp/backstage-plugins/issues/1956)) ([537502b](https://github.com/janus-idp/backstage-plugins/commit/537502b9d2ac13f2fb3f79188422d2c6e97f41fb)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.12.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.11.0...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.12.0) (2024-07-24) + +### Features + +- **deps:** update to backstage 1.28 ([#1891](https://github.com/janus-idp/backstage-plugins/issues/1891)) ([1ba1108](https://github.com/janus-idp/backstage-plugins/commit/1ba11088e0de60e90d138944267b83600dc446e5)) +- **orchestrator:** use v2 endpoints to retrieve workflow overviews ([#1892](https://github.com/janus-idp/backstage-plugins/issues/1892)) ([cca1e53](https://github.com/janus-idp/backstage-plugins/commit/cca1e53bc6b3019b1c544f2f62bed8723ebf6130)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.10.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.9.0...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.10.0) (2024-06-28) + +### Features + +- **orchestrator:** remove unneeded orchestrator jira integration and endpoint ([#1833](https://github.com/janus-idp/backstage-plugins/issues/1833)) ([d2a76fd](https://github.com/janus-idp/backstage-plugins/commit/d2a76fd3db028f9774c821759bee5f38b7131c94)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.9.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.8.1...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.9.0) (2024-06-13) + +### Features + +- **deps:** update to backstage 1.27 ([#1683](https://github.com/janus-idp/backstage-plugins/issues/1683)) ([a14869c](https://github.com/janus-idp/backstage-plugins/commit/a14869c3f4177049cb8d6552b36c3ffd17e7997d)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.8.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.8.0...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.8.1) (2024-06-04) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.8.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.7.2...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.8.0) (2024-05-22) + +### Features + +- **orchestrator:** add permissions to orchestrator plugin ([#1599](https://github.com/janus-idp/backstage-plugins/issues/1599)) ([d0a4531](https://github.com/janus-idp/backstage-plugins/commit/d0a453181e177eb1da7b1e231253b76a2d9356a8)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.7.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.7.1...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.7.2) (2024-05-15) + +### Documentation + +- **orchestrator:** removes instructions related to the editor ([#1664](https://github.com/janus-idp/backstage-plugins/issues/1664)) ([10a75b2](https://github.com/janus-idp/backstage-plugins/commit/10a75b2706c72751bd774d6fae4332bbc527dc2b)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.7.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.7.0...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.7.1) (2024-05-09) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.7.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.6.4...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.7.0) (2024-05-09) + +### Features + +- **orchestrator:** add ability to re-trigger workflow in error state ([#1624](https://github.com/janus-idp/backstage-plugins/issues/1624)) ([8709a37](https://github.com/janus-idp/backstage-plugins/commit/8709a37d08c2eafc22f10bd2a41f0a105768222d)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.6.4](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.6.3...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.6.4) (2024-04-18) + +### Bug Fixes + +- **orchestrator:** allows serving the editor envelope in disconnected environments ([#1450](https://github.com/janus-idp/backstage-plugins/issues/1450)) ([1e778d8](https://github.com/janus-idp/backstage-plugins/commit/1e778d88336dfec79d48ece4fd8d2a035133b70e)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.6.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.6.2...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.6.3) (2024-04-05) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.6.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.6.1...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.6.2) (2024-04-04) + +### Bug Fixes + +- **orchestrator:** add lastRunId to overview endpoints ([#1449](https://github.com/janus-idp/backstage-plugins/issues/1449)) ([cce56f7](https://github.com/janus-idp/backstage-plugins/commit/cce56f7de3acc41ecd30b1b9962d7817be69de7d)) +- **orchestrator:** update devmode container tag ([#1439](https://github.com/janus-idp/backstage-plugins/issues/1439)) ([d59ad04](https://github.com/janus-idp/backstage-plugins/commit/d59ad044cd5d8d7566464f140cdbc1dfbad85a62)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.6.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.6.0...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.6.1) (2024-03-29) + +### Bug Fixes + +- **orchestrator:** fixes v2/instances endpoint ([#1414](https://github.com/janus-idp/backstage-plugins/issues/1414)) ([88b49df](https://github.com/janus-idp/backstage-plugins/commit/88b49df35cf10e231ba69c239e873cb10e7cc25b)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.6.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.5.1...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.6.0) (2024-03-14) + +### Features + +- **orchestrator:** verify availability and cache workflow definition IDs ([#1309](https://github.com/janus-idp/backstage-plugins/issues/1309)) ([4d322f1](https://github.com/janus-idp/backstage-plugins/commit/4d322f1fc5b6f8b1afedf40cfe1b24b2edae2ac1)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.5.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.5.0...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.5.1) (2024-03-12) + +### Bug Fixes + +- **orchestrator:** openapi files hash generation use nodejs script ([#1328](https://github.com/janus-idp/backstage-plugins/issues/1328)) ([e91c27e](https://github.com/janus-idp/backstage-plugins/commit/e91c27ecf7066149aa498e5b2e65a1d3653fa448)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.5.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.4.1...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.5.0) (2024-03-11) + +### Features + +- **orchestrator:** verify if auto-generated openapi files are up-to-date ([#1323](https://github.com/janus-idp/backstage-plugins/issues/1323)) ([650b435](https://github.com/janus-idp/backstage-plugins/commit/650b435ac53c517fc5e960734a4d3085399b1608)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.4.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.4.0...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.4.1) (2024-03-11) + +### Bug Fixes + +- **orchestrator:** add missing query parameter changes for /overview endpoint ([#1321](https://github.com/janus-idp/backstage-plugins/issues/1321)) ([241576d](https://github.com/janus-idp/backstage-plugins/commit/241576d242cd88e6d264180a69a5e1e9cd282df6)) + +### Other changes + +- **orchestrator:** add unit tests for v2 endpoints ([#1300](https://github.com/janus-idp/backstage-plugins/issues/1300)) ([9a13138](https://github.com/janus-idp/backstage-plugins/commit/9a13138c61d3cc7331f739da80f020bb68dd61e5)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.4.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.3.7...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.4.0) (2024-03-07) + +### Features + +- **orchestrator:** support pagination for /instances and /overview ([#1313](https://github.com/janus-idp/backstage-plugins/issues/1313)) ([79d5988](https://github.com/janus-idp/backstage-plugins/commit/79d598816f16c8346b6868bff4cc30d695cad518)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.3.7](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.3.6...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.3.7) (2024-03-03) + +### Bug Fixes + +- **orchestrator:** stop fetching workflow URI ([#1297](https://github.com/janus-idp/backstage-plugins/issues/1297)) ([2456a28](https://github.com/janus-idp/backstage-plugins/commit/2456a287dbff955a0916b9600e89a39511cd537a)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.3.6](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.3.5...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.3.6) (2024-02-29) + +### Bug Fixes + +- **orchestrator:** refactor 500 response to use ErrorResponse object ([#1290](https://github.com/janus-idp/backstage-plugins/issues/1290)) ([2580f3d](https://github.com/janus-idp/backstage-plugins/commit/2580f3d38cecf78334964666eb7c127c21b00924)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.3.5](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.3.4...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.3.5) (2024-02-28) + +### Bug Fixes + +- **orchestrator:** clean up the plugin code ([#1292](https://github.com/janus-idp/backstage-plugins/issues/1292)) ([ad27fb8](https://github.com/janus-idp/backstage-plugins/commit/ad27fb8e98913a6b80feb38ff58a7864e1953a7e)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.3.4](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.3.3...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.3.4) (2024-02-28) + +### Bug Fixes + +- **orchestrator:** regenerate Open API with new instance state ([#1289](https://github.com/janus-idp/backstage-plugins/issues/1289)) ([8755fdd](https://github.com/janus-idp/backstage-plugins/commit/8755fdd04dac406a4a02bfd7823d0993a6edf0b3)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.3.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.3.2...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.3.3) (2024-02-28) + +### Bug Fixes + +- **orchestrator:** handle nullable start/state properties of process instance ([#1277](https://github.com/janus-idp/backstage-plugins/issues/1277)) ([d8a43a5](https://github.com/janus-idp/backstage-plugins/commit/d8a43a5a164f83fc90d037ae3d7a355f5de543e0)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.3.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.3.1...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.3.2) (2024-02-27) + +### Bug Fixes + +- **orchestrator:** remove date-time format from spec ([#1282](https://github.com/janus-idp/backstage-plugins/issues/1282)) ([2b59dcf](https://github.com/janus-idp/backstage-plugins/commit/2b59dcf00082e617911289d8813ad02b83800470)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.3.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.3.0...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.3.1) (2024-02-21) + +### Bug Fixes + +- **orchestrator:** implementation of getWorkflowById (v2) ([#1233](https://github.com/janus-idp/backstage-plugins/issues/1233)) ([f9f9008](https://github.com/janus-idp/backstage-plugins/commit/f9f9008d29f244c2ae6d688d3e2dc9b65b705e5b)) +- **orchestrator:** minor improvements and fixes ([#1242](https://github.com/janus-idp/backstage-plugins/issues/1242)) ([c9ec4cb](https://github.com/janus-idp/backstage-plugins/commit/c9ec4cbe1847268e8068edc69c7937c5e133c315)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.3.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.2.1...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.3.0) (2024-02-20) + +### Features + +- **orchestrator:** add OpenAPI v2 implementations ([#1182](https://github.com/janus-idp/backstage-plugins/issues/1182)) ([43ac2f3](https://github.com/janus-idp/backstage-plugins/commit/43ac2f3f492b5c977142a3cfd9868d5e193ceb02)) + +### Bug Fixes + +- **orchestrator:** decommission the ProcessInstance.lastUpdate field ([#1230](https://github.com/janus-idp/backstage-plugins/issues/1230)) ([9724e27](https://github.com/janus-idp/backstage-plugins/commit/9724e27eaa84fe73d7724f28c86409681b7f79f8)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.2.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.2.0...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.2.1) (2024-02-16) + +### Bug Fixes + +- **orchestrator:** resolve mismatch between execution data and composed schema ([#1217](https://github.com/janus-idp/backstage-plugins/issues/1217)) ([af85114](https://github.com/janus-idp/backstage-plugins/commit/af851148935e1ed083709cac145520d7551de737)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.2.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.1.0...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.2.0) (2024-02-16) + +### Features + +- **orchestrator:** add OpenAPI support ([#1123](https://github.com/janus-idp/backstage-plugins/issues/1123)) ([bd88e23](https://github.com/janus-idp/backstage-plugins/commit/bd88e2304c93761ce6754985074f004a5a3c8c4b)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common [1.1.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.0.0...@red-hat-developer-hub/backstage-plugin-orchestrator-common@1.1.0) (2024-02-02) + +### Features + +- **orchestrator:** add the ability to rerun workflows in a new instance ([#1141](https://github.com/janus-idp/backstage-plugins/issues/1141)) ([fe326df](https://github.com/janus-idp/backstage-plugins/commit/fe326df569caa5a9e7b7ec809c1c371a2a936010)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator-common 1.0.0 (2024-01-17) + +### Features + +- **orchestrator:** add orchestrator plugin ([#783](https://github.com/janus-idp/backstage-plugins/issues/783)) ([cf5fe74](https://github.com/janus-idp/backstage-plugins/commit/cf5fe74db6992d9f51f5073bbcf20c8c346357a1)), closes [#28](https://github.com/janus-idp/backstage-plugins/issues/28) [#38](https://github.com/janus-idp/backstage-plugins/issues/38) [#35](https://github.com/janus-idp/backstage-plugins/issues/35) [#21](https://github.com/janus-idp/backstage-plugins/issues/21) diff --git a/workspaces/orchestrator/plugins/orchestrator-common/README.md b/workspaces/orchestrator/plugins/orchestrator-common/README.md new file mode 100644 index 00000000..d8c8dbe2 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/README.md @@ -0,0 +1,5 @@ +# Orchestrator Common Plugin for Backstage + +Welcome to the common package for the Orchestrator plugin! + +For more information about the Orchestrator plugin, see the [Orchestrator Plugin documentation](https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/orchestrator/plugins/orchestrator) on GitHub. diff --git a/workspaces/orchestrator/plugins/orchestrator-common/catalog-info.yaml b/workspaces/orchestrator/plugins/orchestrator-common/catalog-info.yaml new file mode 100644 index 00000000..c71e1444 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/catalog-info.yaml @@ -0,0 +1,25 @@ +# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: red-hat-developer-hub-orchestrator-common + title: '@red-hat-developer-hub/backstage-plugin-orchestrator-common' + description: Orchestrator Common Plugin for Backstage + annotations: + backstage.io/source-location: url:https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/orchestrator/plugins/orchestrator-common + backstage.io/view-url: https://github.com/redhat-developer/rhdh-plugins/blob/main/workspaces/orchestrator/plugins/orchestrator-common/catalog-info.yaml + backstage.io/edit-url: https://github.com/redhat-developer/rhdh-plugins/edit/main/workspaces/orchestrator/plugins/orchestrator-common/catalog-info.yaml + github.com/project-slug: red-hat-developer-hub/backstage-plugins + github.com/team-slug: red-hat-developer-hub/orchestrator-codeowners + sonarqube.org/project-key: red_hat_developer_hub_plugins + links: + - url: https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/orchestrator/plugins/orchestrator-common + title: GitHub Source + icon: source + type: source +spec: + type: backstage-common-library + lifecycle: production + owner: orchestrator-team + system: rhdh + subcomponentOf: red-hat-developer-hub-orchestrator diff --git a/workspaces/orchestrator/plugins/orchestrator-common/config.d.ts b/workspaces/orchestrator/plugins/orchestrator-common/config.d.ts new file mode 100644 index 00000000..c6dea9f7 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/config.d.ts @@ -0,0 +1,81 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export interface Config { + /** + * Configuration for the Orchestrator plugin. + */ + orchestrator?: { + sonataFlowService: { + /** + * Base URL of the Sonata Flow service. + * Default: http://localhost + */ + baseUrl?: string; + /** + * Port of the Sonata Flow service. + * Default: no port + */ + port?: string; + /** + * Whether to start the Sonata Flow service automatically. + * If set to `false`, the plugin assumes that the SonataFlow service is already running on `baseUrl`:`port` (or just `baseUrl` if `port` is not set). + * Default: false + */ + autoStart?: boolean; + /** + * Workflows definitions source configurations + */ + workflowsSource?: + | { + /** + * Remote git repository where workflows definitions are stored + */ + gitRepositoryUrl: string; + /** + * Path to map workflow resources to SonataFlow service. + * Example: /home/orchestrator/workflows + */ + localPath: string; + } + | { + localPath: string; + }; + + /** + * Container image name of the Sonata Flow service. + * Default: quay.io/kiegroup/kogito-swf-devmode-nightly:main-2024-02-19 + */ + container?: string; + /** + * Persistance configuration of the Sonata Flow service. + */ + persistance?: { + /** + * Path in the container image to store persistance data. + * Default: /home/kogito/persistence + */ + path?: string; + }; + }; + dataIndexService: { + /** + * URL of the Data Index service. + * Example: http://localhost:8099 + */ + url: string; + }; + }; +} diff --git a/workspaces/orchestrator/plugins/orchestrator-common/openapitools.json b/workspaces/orchestrator/plugins/orchestrator-common/openapitools.json new file mode 100644 index 00000000..937a5f35 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "7.3.0" + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-common/package.json b/workspaces/orchestrator/plugins/orchestrator-common/package.json new file mode 100644 index 00000000..830935ce --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/package.json @@ -0,0 +1,84 @@ +{ + "name": "@red-hat-developer-hub/backstage-plugin-orchestrator-common", + "version": "1.24.0", + "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "module": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "common-library", + "supported-versions": "1.32.5", + "pluginId": "orchestrator", + "pluginPackages": [ + "@red-hat-developer-hub/backstage-plugin-orchestrator", + "@red-hat-developer-hub/backstage-plugin-orchestrator-backend", + "@red-hat-developer-hub/backstage-plugin-orchestrator-common" + ] + }, + "homepage": "https://red.ht/rhdh", + "repository": { + "type": "git", + "url": "https://github.com/redhat-developer/rhdh-plugins", + "directory": "workspaces/orchestrator/plugins/orchestrator-common" + }, + "bugs": "https://github.com/redhat-developer/rhdh-plugins/issues", + "keywords": [ + "support:tech-preview", + "lifecycle:active", + "backstage", + "plugin", + "orchestrator", + "workflows" + ], + "files": [ + "config.d.ts", + "dist", + "src/generated/docs/html" + ], + "configSchema": "config.d.ts", + "sideEffects": false, + "scripts": { + "build": "backstage-cli package build", + "tsc": "tsc", + "prettier:check": "prettier --ignore-unknown --check .", + "prettier:fix": "prettier --ignore-unknown --write .", + "lint:check": "backstage-cli package lint", + "lint:fix": "backstage-cli package lint --fix", + "test": "backstage-cli package test --passWithNoTests --coverage", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "openapi:generate": "./scripts/openapi.sh generate", + "openapi:check": "./scripts/openapi.sh check" + }, + "peerDependencies": { + "@backstage/plugin-permission-common": "^0.8.1", + "@backstage/types": "^1.1.1", + "@severlessworkflow/sdk-typescript": "^3.0.3", + "axios": "^1.7.4", + "js-yaml": "^4.1.0" + }, + "devDependencies": { + "@backstage/cli": "0.28.2", + "@backstage/plugin-permission-common": "^0.8.1", + "@backstage/types": "1.1.1", + "@openapitools/openapi-generator-cli": "2.13.4", + "@severlessworkflow/sdk-typescript": "3.0.3", + "axios": "^1.7.4", + "js-yaml": "^4.1.0", + "js-yaml-cli": "0.6.0", + "json-schema": "0.4.0", + "prettier": "3.3.3" + }, + "maintainers": [ + "@mlibra", + "@batzionb", + "@gciavarrini" + ], + "author": "The Backstage Community" +} diff --git a/workspaces/orchestrator/plugins/orchestrator-common/report.api.md b/workspaces/orchestrator/plugins/orchestrator-common/report.api.md new file mode 100644 index 00000000..f8dbcab7 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/report.api.md @@ -0,0 +1,2117 @@ +## API Report File for "@red-hat-developer-hub/backstage-plugin-orchestrator-common" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import type { AxiosInstance } from 'axios'; +import type { AxiosPromise } from 'axios'; +import { AxiosResponse } from 'axios'; +import { BasicPermission } from '@backstage/plugin-permission-common'; +import type { JsonObject } from '@backstage/types'; +import type { JSONSchema7 } from 'json-schema'; +import type { JSONSchema7Definition } from 'json-schema'; +import type { RawAxiosRequestConfig } from 'axios'; +import type { Specification } from '@severlessworkflow/sdk-typescript'; + +// Warning: (ae-missing-release-tag) "AssessedProcessInstance" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface AssessedProcessInstance { + // (undocumented) + assessedBy?: ProcessInstance; + // (undocumented) + instance: ProcessInstance; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "AssessedProcessInstanceDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface AssessedProcessInstanceDTO { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'assessedBy'?: ProcessInstanceDTO; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'instance': ProcessInstanceDTO; +} + +// Warning: (ae-missing-release-tag) "ASSESSMENT_WORKFLOW_TYPE" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const ASSESSMENT_WORKFLOW_TYPE = "workflow-type/assessment"; + +// Warning: (ae-missing-release-tag) "capitalize" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const capitalize: (text: S) => Capitalize>; + +// Warning: (ae-missing-release-tag) "Capitalized" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type Capitalized = Capitalize>; + +// Warning: (ae-missing-release-tag) "ComposedSchema" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ComposedSchema = Omit & { + properties: { + [key: string]: Omit & { + properties: { + [key: string]: JsonObjectSchema; + }; + }; + }; +}; + +// Warning: (ae-missing-release-tag) "Configuration" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class Configuration { + constructor(param?: ConfigurationParameters); + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + baseOptions?: any; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + basePath?: string; + // Warning: (tsdoc-escape-greater-than) The ">" character should be escaped using a backslash to avoid confusion with an HTML tag + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + formDataCtor?: new () => any; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration + isJsonMime(mime: string): boolean; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + password?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + serverIndex?: number; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + username?: string; +} + +// Warning: (ae-missing-release-tag) "ConfigurationParameters" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface ConfigurationParameters { + // (undocumented) + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + // (undocumented) + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + // (undocumented) + baseOptions?: any; + // (undocumented) + basePath?: string; + // (undocumented) + formDataCtor?: new () => any; + // (undocumented) + password?: string; + // (undocumented) + serverIndex?: number; + // (undocumented) + username?: string; +} + +// Warning: (ae-missing-release-tag) "DEFAULT_SONATAFLOW_BASE_URL" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const DEFAULT_SONATAFLOW_BASE_URL = "http://localhost"; + +// Warning: (ae-missing-release-tag) "DEFAULT_SONATAFLOW_CONTAINER_IMAGE" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const DEFAULT_SONATAFLOW_CONTAINER_IMAGE = "docker.io/apache/incubator-kie-sonataflow-devmode:latest"; + +// Warning: (ae-missing-release-tag) "DEFAULT_SONATAFLOW_PERSISTENCE_PATH" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const DEFAULT_SONATAFLOW_PERSISTENCE_PATH = "/home/kogito/persistence"; + +// Warning: (ae-missing-release-tag) "DEFAULT_WORKFLOWS_PATH" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const DEFAULT_WORKFLOWS_PATH = "workflows"; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration +// Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@extends" is not defined in this configuration +// Warning: (ae-forgotten-export) The symbol "BaseAPI" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "DefaultApi" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export class DefaultApi extends BaseAPI { + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + abortWorkflow(instanceId: string, options?: RawAxiosRequestConfig): Promise>; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + executeWorkflow(workflowId: string, executeWorkflowRequestDTO: ExecuteWorkflowRequestDTO, options?: RawAxiosRequestConfig): Promise>; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + getInstanceById(instanceId: string, includeAssessment?: boolean, options?: RawAxiosRequestConfig): Promise>; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + getInstances(getInstancesRequest?: GetInstancesRequest, options?: RawAxiosRequestConfig): Promise>; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + getWorkflowInputSchemaById(workflowId: string, instanceId?: string, options?: RawAxiosRequestConfig): Promise>; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + getWorkflowInstances(workflowId: string, searchRequest?: SearchRequest, options?: RawAxiosRequestConfig): Promise>; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + getWorkflowOverviewById(workflowId: string, options?: RawAxiosRequestConfig): Promise>; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + getWorkflowSourceById(workflowId: string, options?: RawAxiosRequestConfig): Promise>; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + getWorkflowsOverview(searchRequest?: SearchRequest, options?: RawAxiosRequestConfig): Promise>; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + getWorkflowStatuses(options?: RawAxiosRequestConfig): Promise>; + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + // Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. + // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + retriggerInstance(workflowId: string, instanceId: string, options?: RawAxiosRequestConfig): Promise>; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (ae-missing-release-tag) "DefaultApiAxiosParamCreator" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export const DefaultApiAxiosParamCreator: (configuration?: Configuration) => { + abortWorkflow: (instanceId: string, options?: RawAxiosRequestConfig) => Promise; + executeWorkflow: (workflowId: string, executeWorkflowRequestDTO: ExecuteWorkflowRequestDTO, options?: RawAxiosRequestConfig) => Promise; + getInstanceById: (instanceId: string, includeAssessment?: boolean, options?: RawAxiosRequestConfig) => Promise; + getInstances: (getInstancesRequest?: GetInstancesRequest, options?: RawAxiosRequestConfig) => Promise; + getWorkflowInputSchemaById: (workflowId: string, instanceId?: string, options?: RawAxiosRequestConfig) => Promise; + getWorkflowInstances: (workflowId: string, searchRequest?: SearchRequest, options?: RawAxiosRequestConfig) => Promise; + getWorkflowOverviewById: (workflowId: string, options?: RawAxiosRequestConfig) => Promise; + getWorkflowSourceById: (workflowId: string, options?: RawAxiosRequestConfig) => Promise; + getWorkflowStatuses: (options?: RawAxiosRequestConfig) => Promise; + getWorkflowsOverview: (searchRequest?: SearchRequest, options?: RawAxiosRequestConfig) => Promise; + retriggerInstance: (workflowId: string, instanceId: string, options?: RawAxiosRequestConfig) => Promise; +}; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (ae-missing-release-tag) "DefaultApiFactory" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export const DefaultApiFactory: (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) => { + abortWorkflow(instanceId: string, options?: any): AxiosPromise; + executeWorkflow(workflowId: string, executeWorkflowRequestDTO: ExecuteWorkflowRequestDTO, options?: any): AxiosPromise; + getInstanceById(instanceId: string, includeAssessment?: boolean, options?: any): AxiosPromise; + getInstances(getInstancesRequest?: GetInstancesRequest, options?: any): AxiosPromise; + getWorkflowInputSchemaById(workflowId: string, instanceId?: string, options?: any): AxiosPromise; + getWorkflowInstances(workflowId: string, searchRequest?: SearchRequest, options?: any): AxiosPromise; + getWorkflowOverviewById(workflowId: string, options?: any): AxiosPromise; + getWorkflowSourceById(workflowId: string, options?: any): AxiosPromise; + getWorkflowStatuses(options?: any): AxiosPromise>; + getWorkflowsOverview(searchRequest?: SearchRequest, options?: any): AxiosPromise; + retriggerInstance(workflowId: string, instanceId: string, options?: any): AxiosPromise; +}; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (ae-missing-release-tag) "DefaultApiFp" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export const DefaultApiFp: (configuration?: Configuration) => { + abortWorkflow(instanceId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>; + executeWorkflow(workflowId: string, executeWorkflowRequestDTO: ExecuteWorkflowRequestDTO, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>; + getInstanceById(instanceId: string, includeAssessment?: boolean, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>; + getInstances(getInstancesRequest?: GetInstancesRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>; + getWorkflowInputSchemaById(workflowId: string, instanceId?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>; + getWorkflowInstances(workflowId: string, searchRequest?: SearchRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>; + getWorkflowOverviewById(workflowId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>; + getWorkflowSourceById(workflowId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>; + getWorkflowStatuses(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>>; + getWorkflowsOverview(searchRequest?: SearchRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>; + retriggerInstance(workflowId: string, instanceId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>; +}; + +// Warning: (ae-missing-release-tag) "ellipsis" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const ellipsis: (text: S, prefixLength?: number) => string; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "ErrorResponse" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface ErrorResponse { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'additionalInfo'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'message': string; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "ExecuteWorkflowRequestDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface ExecuteWorkflowRequestDTO { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'inputData': object; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "ExecuteWorkflowResponseDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface ExecuteWorkflowResponseDTO { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'id': string; +} + +// Warning: (ae-missing-release-tag) "extractWorkflowFormat" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function extractWorkflowFormat(source: string): WorkflowFormat; + +// Warning: (ae-missing-release-tag) "extractWorkflowFormatFromUri" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function extractWorkflowFormatFromUri(uri: string): WorkflowFormat; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "FieldFilter" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface FieldFilter { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'field': string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'operator': FieldFilterOperatorEnum; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'value': FieldFilterValue; +} + +// Warning: (ae-missing-release-tag) "FieldFilterOperatorEnum" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "FieldFilterOperatorEnum" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const FieldFilterOperatorEnum: { + readonly Eq: "EQ"; + readonly Gt: "GT"; + readonly Gte: "GTE"; + readonly Lt: "LT"; + readonly Lte: "LTE"; + readonly In: "IN"; + readonly IsNull: "IS_NULL"; + readonly Contains: "CONTAINS"; + readonly ContainsAll: "CONTAINS_ALL"; + readonly ContainsAny: "CONTAINS_ANY"; + readonly Like: "LIKE"; + readonly Between: "BETWEEN"; +}; + +// @public (undocumented) +export type FieldFilterOperatorEnum = typeof FieldFilterOperatorEnum[keyof typeof FieldFilterOperatorEnum]; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (ae-missing-release-tag) "FieldFilterValue" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export type FieldFilterValue = any | boolean | number | string; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (ae-missing-release-tag) "Filter" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export type Filter = FieldFilter | LogicalFilter; + +// Warning: (ae-missing-release-tag) "fromWorkflowSource" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function fromWorkflowSource(content: string): WorkflowDefinition; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "GetInstancesRequest" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface GetInstancesRequest { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'filters'?: SearchRequest; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'paginationInfo'?: PaginationInfoDTO; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "GetOverviewsRequestParams" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface GetOverviewsRequestParams { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'filters'?: SearchRequest; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'paginationInfo'?: PaginationInfoDTO; +} + +// Warning: (ae-missing-release-tag) "getWorkflowCategory" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function getWorkflowCategory(definition: WorkflowDefinition | undefined): WorkflowCategory; + +// Warning: (ae-missing-release-tag) "INFRASTRUCTURE_WORKFLOW_TYPE" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const INFRASTRUCTURE_WORKFLOW_TYPE = "workflow-type/infrastructure"; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "InputSchemaResponseDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface InputSchemaResponseDTO { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'data'?: object; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'inputSchema'?: object; +} + +// Warning: (ae-missing-release-tag) "IntrospectionField" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface IntrospectionField { + // (undocumented) + name: string; + // (undocumented) + type: IntrospectionTypeRef; +} + +// Warning: (ae-missing-release-tag) "IntrospectionQuery" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface IntrospectionQuery { + // (undocumented) + __type: IntrospectionType | null; +} + +// Warning: (ae-missing-release-tag) "IntrospectionType" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface IntrospectionType { + // (undocumented) + description: string | null; + // (undocumented) + fields: IntrospectionField[] | null; + // (undocumented) + kind: TypeKind; + // (undocumented) + name: string; +} + +// Warning: (ae-missing-release-tag) "IntrospectionTypeRef" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface IntrospectionTypeRef { + // (undocumented) + kind: TypeKind; + // (undocumented) + name: TypeName; + // (undocumented) + ofType: IntrospectionTypeRef | null; +} + +// Warning: (ae-missing-release-tag) "isComposedSchema" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isComposedSchema: (schema: JSONSchema7 | ComposedSchema) => schema is ComposedSchema; + +// Warning: (ae-missing-release-tag) "isJsonObjectSchema" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isJsonObjectSchema: (schema: JSONSchema7 | JsonObjectSchema | JSONSchema7Definition) => schema is JsonObjectSchema; + +// Warning: (ae-missing-release-tag) "JsonObjectSchema" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type JsonObjectSchema = Omit & { + properties: { + [key: string]: JSONSchema7; + }; +}; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "LogicalFilter" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface LogicalFilter { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'filters': Array; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'operator': LogicalFilterOperatorEnum; +} + +// Warning: (ae-missing-release-tag) "LogicalFilterOperatorEnum" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "LogicalFilterOperatorEnum" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const LogicalFilterOperatorEnum: { + readonly And: "AND"; + readonly Or: "OR"; + readonly Not: "NOT"; +}; + +// @public (undocumented) +export type LogicalFilterOperatorEnum = typeof LogicalFilterOperatorEnum[keyof typeof LogicalFilterOperatorEnum]; + +// Warning: (ae-missing-release-tag) "Milestone" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface Milestone { + // (undocumented) + __typename?: 'Milestone'; + // (undocumented) + id: string; + // (undocumented) + name: string; + // (undocumented) + status: MilestoneStatus; +} + +// Warning: (ae-missing-release-tag) "MilestoneStatus" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export enum MilestoneStatus { + // (undocumented) + Active = "ACTIVE", + // (undocumented) + Available = "AVAILABLE", + // (undocumented) + Completed = "COMPLETED" +} + +// Warning: (ae-missing-release-tag) "Node" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +interface Node_2 { + // (undocumented) + id: string; + // (undocumented) + name?: string; + // (undocumented) + nodeDefinitionId?: string; + // (undocumented) + type?: string; + // (undocumented) + uniqueId?: string; +} +export { Node_2 as Node } + +// Warning: (ae-missing-release-tag) "NodeInstance" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface NodeInstance { + // (undocumented) + __typename?: 'NodeInstance'; + // (undocumented) + definitionId: string; + // (undocumented) + enter: string; + // (undocumented) + exit?: string; + // (undocumented) + id: string; + // (undocumented) + name: string; + // (undocumented) + nodeId: string; + // (undocumented) + type: string; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "NodeInstanceDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface NodeInstanceDTO { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + '__typename'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'definitionId'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'enter'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'exit'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'id': string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'name'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'nodeId'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'type'?: string; +} + +// Warning: (ae-forgotten-export) The symbol "OmitDistributive" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "OmitRecursively" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type OmitRecursively = Omit<{ + [P in keyof T]: OmitDistributive; +}, K>; + +// Warning: (ae-missing-release-tag) "openApiDocument" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const openApiDocument: any; + +// Warning: (ae-missing-release-tag) "orchestratorPermissions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const orchestratorPermissions: BasicPermission[]; + +// Warning: (ae-missing-release-tag) "orchestratorWorkflowExecutePermission" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const orchestratorWorkflowExecutePermission: BasicPermission; + +// Warning: (ae-missing-release-tag) "orchestratorWorkflowInstanceAbortPermission" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const orchestratorWorkflowInstanceAbortPermission: BasicPermission; + +// Warning: (ae-missing-release-tag) "orchestratorWorkflowInstanceReadPermission" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const orchestratorWorkflowInstanceReadPermission: BasicPermission; + +// Warning: (ae-missing-release-tag) "orchestratorWorkflowInstancesReadPermission" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const orchestratorWorkflowInstancesReadPermission: BasicPermission; + +// Warning: (ae-missing-release-tag) "orchestratorWorkflowReadPermission" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const orchestratorWorkflowReadPermission: BasicPermission; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "PaginationInfoDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface PaginationInfoDTO { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'offset'?: number; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'orderBy'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'orderDirection'?: PaginationInfoDTOOrderDirectionEnum; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'pageSize'?: number; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'totalCount'?: number; +} + +// Warning: (ae-missing-release-tag) "PaginationInfoDTOOrderDirectionEnum" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "PaginationInfoDTOOrderDirectionEnum" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const PaginationInfoDTOOrderDirectionEnum: { + readonly Asc: "ASC"; + readonly Desc: "DESC"; +}; + +// @public (undocumented) +export type PaginationInfoDTOOrderDirectionEnum = typeof PaginationInfoDTOOrderDirectionEnum[keyof typeof PaginationInfoDTOOrderDirectionEnum]; + +// Warning: (ae-missing-release-tag) "parseWorkflowVariables" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function parseWorkflowVariables(variables?: object): object | undefined; + +// Warning: (ae-missing-release-tag) "ProcessInstance" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ProcessInstance { + // (undocumented) + addons?: string[]; + // (undocumented) + businessKey?: string; + // (undocumented) + category?: WorkflowCategory; + // (undocumented) + childProcessInstances?: ProcessInstance[]; + // (undocumented) + description?: WorkflowDefinition['description']; + // (undocumented) + diagram?: string; + end?: string; + // (undocumented) + endpoint: string; + // (undocumented) + error?: ProcessInstanceError; + // (undocumented) + errorMessage?: string; + // (undocumented) + id: string; + // (undocumented) + isOpen?: boolean; + // (undocumented) + isSelected?: boolean; + // (undocumented) + milestones?: Milestone[]; + // (undocumented) + nodeDefinitions?: TriggerableNode[]; + // (undocumented) + nodes: NodeInstance[]; + // (undocumented) + parentProcessInstance?: ProcessInstance; + // (undocumented) + parentProcessInstanceId?: string; + // (undocumented) + processId: string; + // (undocumented) + processName?: string; + // (undocumented) + roles?: string[]; + // (undocumented) + rootProcessId?: string; + // (undocumented) + rootProcessInstanceId?: string; + // (undocumented) + serviceUrl?: string; + // (undocumented) + source?: string; + start?: string; + // (undocumented) + state?: ProcessInstanceStateValues; + // (undocumented) + variables?: ProcessInstanceVariables | string; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "ProcessInstanceDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface ProcessInstanceDTO { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'businessKey'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'category'?: WorkflowCategoryDTO; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'description'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'duration'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'end'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'endpoint'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'error'?: ProcessInstanceErrorDTO; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'id': string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'nodes': Array; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'processId': string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'processName'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'serviceUrl'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'start'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'status'?: ProcessInstanceStatusDTO; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'workflowdata'?: WorkflowDataDTO; +} + +// Warning: (ae-missing-release-tag) "ProcessInstanceError" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ProcessInstanceError { + // (undocumented) + __typename?: 'ProcessInstanceError'; + // (undocumented) + message?: string; + // (undocumented) + nodeDefinitionId: string; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "ProcessInstanceErrorDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface ProcessInstanceErrorDTO { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + '__typename'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'message'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'nodeDefinitionId': string; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "ProcessInstanceListResultDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface ProcessInstanceListResultDTO { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'items'?: Array; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'paginationInfo'?: PaginationInfoDTO; +} + +// Warning: (ae-missing-release-tag) "ProcessInstanceState" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export enum ProcessInstanceState { + // (undocumented) + Aborted = "ABORTED", + // (undocumented) + Active = "ACTIVE", + // (undocumented) + Completed = "COMPLETED", + // (undocumented) + Error = "ERROR", + // (undocumented) + Pending = "PENDING", + // (undocumented) + Suspended = "SUSPENDED" +} + +// Warning: (ae-missing-release-tag) "ProcessInstanceStateValues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ProcessInstanceStateValues = Uppercase; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@enum" is not defined in this configuration +// Warning: (ae-missing-release-tag) "ProcessInstanceStatusDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "ProcessInstanceStatusDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export const ProcessInstanceStatusDTO: { + readonly Active: "Active"; + readonly Error: "Error"; + readonly Completed: "Completed"; + readonly Aborted: "Aborted"; + readonly Suspended: "Suspended"; + readonly Pending: "Pending"; +}; + +// @public (undocumented) +export type ProcessInstanceStatusDTO = typeof ProcessInstanceStatusDTO[keyof typeof ProcessInstanceStatusDTO]; + +// Warning: (ae-missing-release-tag) "ProcessInstanceVariables" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ProcessInstanceVariables = Record; + +// Warning: (ae-missing-release-tag) "QUERY_PARAM_ASSESSMENT_INSTANCE_ID" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const QUERY_PARAM_ASSESSMENT_INSTANCE_ID: "assessmentInstanceId"; + +// Warning: (ae-missing-release-tag) "QUERY_PARAM_BUSINESS_KEY" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const QUERY_PARAM_BUSINESS_KEY: "businessKey"; + +// Warning: (ae-missing-release-tag) "QUERY_PARAM_INCLUDE_ASSESSMENT" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const QUERY_PARAM_INCLUDE_ASSESSMENT: "includeAssessment"; + +// Warning: (ae-missing-release-tag) "QUERY_PARAM_INSTANCE_ID" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const QUERY_PARAM_INSTANCE_ID: "instanceId"; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "SearchRequest" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface SearchRequest { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'filters'?: Filter; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'paginationInfo'?: PaginationInfoDTO; +} + +// Warning: (ae-missing-release-tag) "toWorkflowJson" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function toWorkflowJson(definition: WorkflowDefinition): string; + +// Warning: (ae-missing-release-tag) "toWorkflowString" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function toWorkflowString(definition: WorkflowDefinition, format: WorkflowFormat): string; + +// Warning: (ae-missing-release-tag) "toWorkflowYaml" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function toWorkflowYaml(definition: WorkflowDefinition): string; + +// Warning: (ae-missing-release-tag) "TriggerableNode" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface TriggerableNode { + // (undocumented) + id: number; + // (undocumented) + name: string; + // (undocumented) + nodeDefinitionId: string; + // (undocumented) + type: string; + // (undocumented) + uniqueId: string; +} + +// Warning: (ae-missing-release-tag) "TypeKind" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export enum TypeKind { + // (undocumented) + InputObject = "INPUT_OBJECT" +} + +// Warning: (ae-missing-release-tag) "TypeName" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export enum TypeName { + // (undocumented) + Date = "DateArgument", + // (undocumented) + Id = "IdArgument", + // (undocumented) + String = "StringArgument", + // (undocumented) + StringArray = "StringArrayArgument" +} + +// Warning: (ae-missing-release-tag) "WorkflowCategory" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export enum WorkflowCategory { + // (undocumented) + ASSESSMENT = "assessment", + // (undocumented) + INFRASTRUCTURE = "infrastructure" +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@enum" is not defined in this configuration +// Warning: (ae-missing-release-tag) "WorkflowCategoryDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "WorkflowCategoryDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export const WorkflowCategoryDTO: { + readonly Assessment: "assessment"; + readonly Infrastructure: "infrastructure"; +}; + +// @public (undocumented) +export type WorkflowCategoryDTO = typeof WorkflowCategoryDTO[keyof typeof WorkflowCategoryDTO]; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "WorkflowDataDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface WorkflowDataDTO { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'result'?: WorkflowResultDTO; +} + +// Warning: (ae-missing-release-tag) "WorkflowDefinition" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type WorkflowDefinition = OmitRecursively; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "WorkflowDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface WorkflowDTO { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'annotations'?: Array; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'category': WorkflowCategoryDTO; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'description'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'format': WorkflowFormatDTO; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'id': string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'name'?: string; +} + +// Warning: (ae-missing-release-tag) "WorkflowExecutionResponse" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface WorkflowExecutionResponse { + // (undocumented) + id: string; +} + +// Warning: (ae-missing-release-tag) "WorkflowFormat" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type WorkflowFormat = 'yaml' | 'json'; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@enum" is not defined in this configuration +// Warning: (ae-missing-release-tag) "WorkflowFormatDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "WorkflowFormatDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export const WorkflowFormatDTO: { + readonly Yaml: "yaml"; + readonly Json: "json"; +}; + +// @public (undocumented) +export type WorkflowFormatDTO = typeof WorkflowFormatDTO[keyof typeof WorkflowFormatDTO]; + +// Warning: (ae-missing-release-tag) "WorkflowInfo" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface WorkflowInfo { + // (undocumented) + annotations?: string[]; + // (undocumented) + description?: string; + // (undocumented) + endpoint?: string; + // (undocumented) + id: string; + // (undocumented) + inputSchema?: JSONSchema7; + // (undocumented) + metadata?: Map; + // (undocumented) + name?: string; + // (undocumented) + nodes?: Node_2[]; + // (undocumented) + roles?: string[]; + // (undocumented) + serviceUrl?: string; + // (undocumented) + source?: string; + // (undocumented) + type?: string; + // (undocumented) + version?: string; +} + +// Warning: (ae-missing-release-tag) "WorkflowInputSchemaStep" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type WorkflowInputSchemaStep = { + schema: JsonObjectSchema; + title: string; + key: string; + data: JsonObject; + readonlyKeys: string[]; +}; + +// Warning: (ae-missing-release-tag) "WorkflowListResult" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type WorkflowListResult = { + items: WorkflowDefinition[]; + totalCount: number; + offset: number; + limit: number; +}; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "WorkflowListResultDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface WorkflowListResultDTO { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'items': Array; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'paginationInfo': PaginationInfoDTO; +} + +// Warning: (ae-missing-release-tag) "WorkflowOverview" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface WorkflowOverview { + // (undocumented) + avgDurationMs?: number; + // (undocumented) + category?: string; + // (undocumented) + description?: string; + // (undocumented) + format: WorkflowFormat; + // (undocumented) + lastRunId?: string; + // (undocumented) + lastRunStatus?: ProcessInstanceStateValues; + // (undocumented) + lastTriggeredMs?: number; + // (undocumented) + name?: string; + // (undocumented) + workflowId: string; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "WorkflowOverviewDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface WorkflowOverviewDTO { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'avgDurationMs'?: number; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'category'?: WorkflowCategoryDTO; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'description'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'format': WorkflowFormatDTO; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'lastRunId'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'lastRunStatus'?: ProcessInstanceStatusDTO; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'lastTriggeredMs'?: number; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'name'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'workflowId': string; +} + +// Warning: (ae-missing-release-tag) "WorkflowOverviewListResult" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type WorkflowOverviewListResult = { + items: WorkflowOverview[]; + totalCount: number; + offset: number; + limit: number; +}; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "WorkflowOverviewListResultDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface WorkflowOverviewListResultDTO { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'overviews'?: Array; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'paginationInfo'?: PaginationInfoDTO; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "WorkflowProgressDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface WorkflowProgressDTO { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + '__typename'?: any; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'definitionId'?: any; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'enter'?: any; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'error'?: ProcessInstanceErrorDTO; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'exit'?: any; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'id': any; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'name'?: any; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'nodeId'?: any; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'status'?: ProcessInstanceStatusDTO; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'type'?: any; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "WorkflowResultDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface WorkflowResultDTO { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'completedWith'?: WorkflowResultDTOCompletedWithEnum; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'message'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'nextWorkflows'?: Array; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'outputs'?: Array; +} + +// Warning: (ae-missing-release-tag) "WorkflowResultDTOCompletedWithEnum" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "WorkflowResultDTOCompletedWithEnum" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const WorkflowResultDTOCompletedWithEnum: { + readonly Error: "error"; + readonly Success: "success"; +}; + +// @public (undocumented) +export type WorkflowResultDTOCompletedWithEnum = typeof WorkflowResultDTOCompletedWithEnum[keyof typeof WorkflowResultDTOCompletedWithEnum]; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "WorkflowResultDTONextWorkflowsInner" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface WorkflowResultDTONextWorkflowsInner { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'id': string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'name': string; +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "WorkflowResultDTOOutputsInner" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface WorkflowResultDTOOutputsInner { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'format'?: WorkflowResultDTOOutputsInnerFormatEnum; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'key': string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'value': WorkflowResultDTOOutputsInnerValue; +} + +// Warning: (ae-missing-release-tag) "WorkflowResultDTOOutputsInnerFormatEnum" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "WorkflowResultDTOOutputsInnerFormatEnum" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const WorkflowResultDTOOutputsInnerFormatEnum: { + readonly Text: "text"; + readonly Number: "number"; + readonly Link: "link"; +}; + +// @public (undocumented) +export type WorkflowResultDTOOutputsInnerFormatEnum = typeof WorkflowResultDTOOutputsInnerFormatEnum[keyof typeof WorkflowResultDTOOutputsInnerFormatEnum]; + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "WorkflowResultDTOOutputsInnerValue" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface WorkflowResultDTOOutputsInnerValue { +} + +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// Warning: (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// Warning: (ae-missing-release-tag) "WorkflowRunStatusDTO" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface WorkflowRunStatusDTO { + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'key'?: string; + // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag + // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration + // Warning: (tsdoc-undefined-tag) The TSDoc tag "@memberof" is not defined in this configuration + 'value'?: string; +} + +// Warnings were encountered during analysis: +// +// src/QueryParams.d.ts:1:22 - (ae-undocumented) Missing documentation for "QUERY_PARAM_BUSINESS_KEY". +// src/QueryParams.d.ts:2:22 - (ae-undocumented) Missing documentation for "QUERY_PARAM_INSTANCE_ID". +// src/QueryParams.d.ts:3:22 - (ae-undocumented) Missing documentation for "QUERY_PARAM_INCLUDE_ASSESSMENT". +// src/QueryParams.d.ts:4:22 - (ae-undocumented) Missing documentation for "QUERY_PARAM_ASSESSMENT_INSTANCE_ID". +// src/constants.d.ts:1:22 - (ae-undocumented) Missing documentation for "DEFAULT_SONATAFLOW_CONTAINER_IMAGE". +// src/constants.d.ts:2:22 - (ae-undocumented) Missing documentation for "DEFAULT_SONATAFLOW_PERSISTENCE_PATH". +// src/constants.d.ts:3:22 - (ae-undocumented) Missing documentation for "DEFAULT_SONATAFLOW_BASE_URL". +// src/constants.d.ts:4:22 - (ae-undocumented) Missing documentation for "DEFAULT_WORKFLOWS_PATH". +// src/constants.d.ts:5:22 - (ae-undocumented) Missing documentation for "ASSESSMENT_WORKFLOW_TYPE". +// src/constants.d.ts:6:22 - (ae-undocumented) Missing documentation for "INFRASTRUCTURE_WORKFLOW_TYPE". +// src/generated/api/definition.d.ts:1:22 - (ae-undocumented) Missing documentation for "openApiDocument". +// src/generated/client/api.d.ts:105:22 - (ae-undocumented) Missing documentation for "FieldFilterOperatorEnum". +// src/generated/client/api.d.ts:119:1 - (ae-undocumented) Missing documentation for "FieldFilterOperatorEnum". +// src/generated/client/api.d.ts:206:22 - (ae-undocumented) Missing documentation for "LogicalFilterOperatorEnum". +// src/generated/client/api.d.ts:211:1 - (ae-undocumented) Missing documentation for "LogicalFilterOperatorEnum". +// src/generated/client/api.d.ts:304:22 - (ae-undocumented) Missing documentation for "PaginationInfoDTOOrderDirectionEnum". +// src/generated/client/api.d.ts:308:1 - (ae-undocumented) Missing documentation for "PaginationInfoDTOOrderDirectionEnum". +// src/generated/client/api.d.ts:463:1 - (ae-undocumented) Missing documentation for "ProcessInstanceStatusDTO". +// src/generated/client/api.d.ts:492:1 - (ae-undocumented) Missing documentation for "WorkflowCategoryDTO". +// src/generated/client/api.d.ts:558:1 - (ae-undocumented) Missing documentation for "WorkflowFormatDTO". +// src/generated/client/api.d.ts:756:22 - (ae-undocumented) Missing documentation for "WorkflowResultDTOCompletedWithEnum". +// src/generated/client/api.d.ts:760:1 - (ae-undocumented) Missing documentation for "WorkflowResultDTOCompletedWithEnum". +// src/generated/client/api.d.ts:805:22 - (ae-undocumented) Missing documentation for "WorkflowResultDTOOutputsInnerFormatEnum". +// src/generated/client/api.d.ts:810:1 - (ae-undocumented) Missing documentation for "WorkflowResultDTOOutputsInnerFormatEnum". +// src/generated/client/api.d.ts:844:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:845:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:845:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:846:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:846:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:846:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:847:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:847:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:849:5 - (ae-forgotten-export) The symbol "RequestArgs" needs to be exported by the entry point index.d.ts +// src/generated/client/api.d.ts:852:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:853:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:853:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:854:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:854:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:855:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:855:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:855:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:856:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:856:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:861:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:862:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:862:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:863:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:863:25 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:863:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:864:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:864:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:864:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:865:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:865:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:870:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:871:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:871:37 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:871:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:872:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:872:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:872:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:873:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:873:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:878:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:878:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:879:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:879:24 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:879:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:880:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:880:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:880:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:881:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:881:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:886:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:887:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:887:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:888:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:888:31 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:888:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:889:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:889:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:889:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:890:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:890:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:895:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:895:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:896:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:896:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:896:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:897:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:897:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:902:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:902:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:903:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:903:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:903:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:904:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:904:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:909:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:910:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:910:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:910:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:911:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:911:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:916:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:916:31 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:916:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:917:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:917:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:917:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:918:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:918:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:923:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:924:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:924:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:925:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:925:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:926:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:926:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:926:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:927:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:927:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:938:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:939:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:939:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:940:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:940:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:940:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:941:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:941:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:946:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:947:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:947:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:948:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:948:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:949:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:949:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:949:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:950:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:950:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:955:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:956:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:956:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:957:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:957:25 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:957:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:958:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:958:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:958:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:959:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:959:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:964:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:965:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:965:37 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:965:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:966:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:966:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:966:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:967:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:967:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:972:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:972:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:973:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:973:24 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:973:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:974:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:974:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:974:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:975:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:975:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:980:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:981:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:981:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:982:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:982:31 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:982:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:983:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:983:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:983:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:984:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:984:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:989:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:989:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:990:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:990:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:990:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:991:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:991:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:996:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:996:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:997:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:997:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:997:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:998:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:998:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:1003:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:1004:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1004:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1004:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1005:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:1005:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:1010:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1010:31 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1010:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1011:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1011:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1011:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1012:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:1012:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:1017:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:1018:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1018:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1019:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1019:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1020:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1020:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1020:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1021:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:1021:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:1032:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:1033:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1033:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1034:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1034:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1034:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1035:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:1035:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:1040:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:1041:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1041:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1042:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1042:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1043:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1043:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1043:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1044:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:1044:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:1049:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:1050:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1050:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1051:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1051:25 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1051:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1052:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1052:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1052:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1053:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:1053:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:1058:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:1059:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1059:37 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1059:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1060:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1060:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1060:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1061:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:1061:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:1066:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1066:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1067:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1067:24 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1067:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1068:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1068:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1068:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1069:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:1069:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:1074:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:1075:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1075:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1076:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1076:31 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1076:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1077:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1077:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1077:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1078:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:1078:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:1083:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1083:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1084:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1084:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1084:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1085:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:1085:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:1090:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1090:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1091:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1091:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1091:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1092:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:1092:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:1097:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:1098:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1098:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1098:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1099:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:1099:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:1104:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1104:31 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1104:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1105:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1105:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1105:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1106:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:1106:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/api.d.ts:1111:8 - (tsdoc-undefined-tag) The TSDoc tag "@summary" is not defined in this configuration +// src/generated/client/api.d.ts:1112:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1112:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1113:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1113:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1114:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/generated/client/api.d.ts:1114:19 - (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets. +// src/generated/client/api.d.ts:1114:15 - (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' +// src/generated/client/api.d.ts:1115:30 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/generated/client/api.d.ts:1115:16 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/generated/client/base.d.ts:27:4 - (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// src/generated/client/base.d.ts:28:4 - (tsdoc-undefined-tag) The TSDoc tag "@interface" is not defined in this configuration +// src/generated/client/base.d.ts:36:4 - (tsdoc-undefined-tag) The TSDoc tag "@export" is not defined in this configuration +// src/generated/client/base.d.ts:37:4 - (tsdoc-undefined-tag) The TSDoc tag "@class" is not defined in this configuration +// src/generated/client/configuration.d.ts:13:5 - (ae-undocumented) Missing documentation for "apiKey". +// src/generated/client/configuration.d.ts:14:5 - (ae-undocumented) Missing documentation for "username". +// src/generated/client/configuration.d.ts:15:5 - (ae-undocumented) Missing documentation for "password". +// src/generated/client/configuration.d.ts:16:5 - (ae-undocumented) Missing documentation for "accessToken". +// src/generated/client/configuration.d.ts:17:5 - (ae-undocumented) Missing documentation for "basePath". +// src/generated/client/configuration.d.ts:18:5 - (ae-undocumented) Missing documentation for "serverIndex". +// src/generated/client/configuration.d.ts:19:5 - (ae-undocumented) Missing documentation for "baseOptions". +// src/generated/client/configuration.d.ts:20:5 - (ae-undocumented) Missing documentation for "formDataCtor". +// src/generated/client/configuration.d.ts:22:1 - (ae-undocumented) Missing documentation for "Configuration". +// src/models.d.ts:2:1 - (ae-undocumented) Missing documentation for "ProcessInstanceState". +// src/models.d.ts:3:5 - (ae-undocumented) Missing documentation for "Active". +// src/models.d.ts:4:5 - (ae-undocumented) Missing documentation for "Completed". +// src/models.d.ts:5:5 - (ae-undocumented) Missing documentation for "Aborted". +// src/models.d.ts:6:5 - (ae-undocumented) Missing documentation for "Suspended". +// src/models.d.ts:7:5 - (ae-undocumented) Missing documentation for "Error". +// src/models.d.ts:8:5 - (ae-undocumented) Missing documentation for "Pending". +// src/models.d.ts:10:1 - (ae-undocumented) Missing documentation for "ProcessInstanceStateValues". +// src/models.d.ts:11:1 - (ae-undocumented) Missing documentation for "MilestoneStatus". +// src/models.d.ts:12:5 - (ae-undocumented) Missing documentation for "Available". +// src/models.d.ts:13:5 - (ae-undocumented) Missing documentation for "Active". +// src/models.d.ts:14:5 - (ae-undocumented) Missing documentation for "Completed". +// src/models.d.ts:16:1 - (ae-undocumented) Missing documentation for "NodeInstance". +// src/models.d.ts:17:5 - (ae-undocumented) Missing documentation for "__typename". +// src/models.d.ts:18:5 - (ae-undocumented) Missing documentation for "id". +// src/models.d.ts:19:5 - (ae-undocumented) Missing documentation for "name". +// src/models.d.ts:20:5 - (ae-undocumented) Missing documentation for "type". +// src/models.d.ts:21:5 - (ae-undocumented) Missing documentation for "enter". +// src/models.d.ts:22:5 - (ae-undocumented) Missing documentation for "exit". +// src/models.d.ts:23:5 - (ae-undocumented) Missing documentation for "definitionId". +// src/models.d.ts:24:5 - (ae-undocumented) Missing documentation for "nodeId". +// src/models.d.ts:26:1 - (ae-undocumented) Missing documentation for "TriggerableNode". +// src/models.d.ts:27:5 - (ae-undocumented) Missing documentation for "id". +// src/models.d.ts:28:5 - (ae-undocumented) Missing documentation for "name". +// src/models.d.ts:29:5 - (ae-undocumented) Missing documentation for "type". +// src/models.d.ts:30:5 - (ae-undocumented) Missing documentation for "uniqueId". +// src/models.d.ts:31:5 - (ae-undocumented) Missing documentation for "nodeDefinitionId". +// src/models.d.ts:33:1 - (ae-undocumented) Missing documentation for "Milestone". +// src/models.d.ts:34:5 - (ae-undocumented) Missing documentation for "__typename". +// src/models.d.ts:35:5 - (ae-undocumented) Missing documentation for "id". +// src/models.d.ts:36:5 - (ae-undocumented) Missing documentation for "name". +// src/models.d.ts:37:5 - (ae-undocumented) Missing documentation for "status". +// src/models.d.ts:39:1 - (ae-undocumented) Missing documentation for "ProcessInstanceError". +// src/models.d.ts:40:5 - (ae-undocumented) Missing documentation for "__typename". +// src/models.d.ts:41:5 - (ae-undocumented) Missing documentation for "nodeDefinitionId". +// src/models.d.ts:42:5 - (ae-undocumented) Missing documentation for "message". +// src/models.d.ts:44:1 - (ae-undocumented) Missing documentation for "ProcessInstanceVariables". +// src/models.d.ts:45:1 - (ae-undocumented) Missing documentation for "ProcessInstance". +// src/models.d.ts:46:5 - (ae-undocumented) Missing documentation for "id". +// src/models.d.ts:47:5 - (ae-undocumented) Missing documentation for "processId". +// src/models.d.ts:48:5 - (ae-undocumented) Missing documentation for "processName". +// src/models.d.ts:49:5 - (ae-undocumented) Missing documentation for "parentProcessInstanceId". +// src/models.d.ts:50:5 - (ae-undocumented) Missing documentation for "rootProcessInstanceId". +// src/models.d.ts:51:5 - (ae-undocumented) Missing documentation for "rootProcessId". +// src/models.d.ts:52:5 - (ae-undocumented) Missing documentation for "roles". +// src/models.d.ts:53:5 - (ae-undocumented) Missing documentation for "state". +// src/models.d.ts:54:5 - (ae-undocumented) Missing documentation for "endpoint". +// src/models.d.ts:55:5 - (ae-undocumented) Missing documentation for "serviceUrl". +// src/models.d.ts:56:5 - (ae-undocumented) Missing documentation for "nodes". +// src/models.d.ts:57:5 - (ae-undocumented) Missing documentation for "milestones". +// src/models.d.ts:58:5 - (ae-undocumented) Missing documentation for "variables". +// src/models.d.ts:63:5 - (ae-undocumented) Missing documentation for "parentProcessInstance". +// src/models.d.ts:64:5 - (ae-undocumented) Missing documentation for "childProcessInstances". +// src/models.d.ts:65:5 - (ae-undocumented) Missing documentation for "error". +// src/models.d.ts:66:5 - (ae-undocumented) Missing documentation for "addons". +// src/models.d.ts:67:5 - (ae-undocumented) Missing documentation for "businessKey". +// src/models.d.ts:68:5 - (ae-undocumented) Missing documentation for "isSelected". +// src/models.d.ts:69:5 - (ae-undocumented) Missing documentation for "errorMessage". +// src/models.d.ts:70:5 - (ae-undocumented) Missing documentation for "isOpen". +// src/models.d.ts:71:5 - (ae-undocumented) Missing documentation for "diagram". +// src/models.d.ts:72:5 - (ae-undocumented) Missing documentation for "nodeDefinitions". +// src/models.d.ts:73:5 - (ae-undocumented) Missing documentation for "source". +// src/models.d.ts:74:5 - (ae-undocumented) Missing documentation for "category". +// src/models.d.ts:75:5 - (ae-undocumented) Missing documentation for "description". +// src/models.d.ts:77:1 - (ae-undocumented) Missing documentation for "IntrospectionQuery". +// src/models.d.ts:78:5 - (ae-undocumented) Missing documentation for "__type". +// src/models.d.ts:80:1 - (ae-undocumented) Missing documentation for "IntrospectionType". +// src/models.d.ts:81:5 - (ae-undocumented) Missing documentation for "name". +// src/models.d.ts:82:5 - (ae-undocumented) Missing documentation for "kind". +// src/models.d.ts:83:5 - (ae-undocumented) Missing documentation for "description". +// src/models.d.ts:84:5 - (ae-undocumented) Missing documentation for "fields". +// src/models.d.ts:86:1 - (ae-undocumented) Missing documentation for "IntrospectionField". +// src/models.d.ts:87:5 - (ae-undocumented) Missing documentation for "name". +// src/models.d.ts:88:5 - (ae-undocumented) Missing documentation for "type". +// src/models.d.ts:90:1 - (ae-undocumented) Missing documentation for "IntrospectionTypeRef". +// src/models.d.ts:91:5 - (ae-undocumented) Missing documentation for "kind". +// src/models.d.ts:92:5 - (ae-undocumented) Missing documentation for "name". +// src/models.d.ts:93:5 - (ae-undocumented) Missing documentation for "ofType". +// src/models.d.ts:95:1 - (ae-undocumented) Missing documentation for "TypeKind". +// src/models.d.ts:96:5 - (ae-undocumented) Missing documentation for "InputObject". +// src/models.d.ts:98:1 - (ae-undocumented) Missing documentation for "TypeName". +// src/models.d.ts:99:5 - (ae-undocumented) Missing documentation for "Id". +// src/models.d.ts:100:5 - (ae-undocumented) Missing documentation for "String". +// src/models.d.ts:101:5 - (ae-undocumented) Missing documentation for "StringArray". +// src/models.d.ts:102:5 - (ae-undocumented) Missing documentation for "Date". +// src/permissions.d.ts:1:22 - (ae-undocumented) Missing documentation for "orchestratorWorkflowInstancesReadPermission". +// src/permissions.d.ts:2:22 - (ae-undocumented) Missing documentation for "orchestratorWorkflowInstanceReadPermission". +// src/permissions.d.ts:3:22 - (ae-undocumented) Missing documentation for "orchestratorWorkflowReadPermission". +// src/permissions.d.ts:4:22 - (ae-undocumented) Missing documentation for "orchestratorWorkflowExecutePermission". +// src/permissions.d.ts:5:22 - (ae-undocumented) Missing documentation for "orchestratorWorkflowInstanceAbortPermission". +// src/permissions.d.ts:6:22 - (ae-undocumented) Missing documentation for "orchestratorPermissions". +// src/types.d.ts:9:1 - (ae-undocumented) Missing documentation for "OmitRecursively". +// src/types.d.ts:12:1 - (ae-undocumented) Missing documentation for "WorkflowDefinition". +// src/types.d.ts:13:1 - (ae-undocumented) Missing documentation for "WorkflowListResult". +// src/types.d.ts:19:1 - (ae-undocumented) Missing documentation for "WorkflowOverviewListResult". +// src/types.d.ts:25:1 - (ae-undocumented) Missing documentation for "WorkflowFormat". +// src/types.d.ts:26:1 - (ae-undocumented) Missing documentation for "WorkflowInputSchemaStep". +// src/types.d.ts:33:1 - (ae-undocumented) Missing documentation for "JsonObjectSchema". +// src/types.d.ts:38:1 - (ae-undocumented) Missing documentation for "ComposedSchema". +// src/types.d.ts:47:22 - (ae-undocumented) Missing documentation for "isJsonObjectSchema". +// src/types.d.ts:48:22 - (ae-undocumented) Missing documentation for "isComposedSchema". +// src/types.d.ts:49:1 - (ae-undocumented) Missing documentation for "WorkflowExecutionResponse". +// src/types.d.ts:50:5 - (ae-undocumented) Missing documentation for "id". +// src/types.d.ts:52:1 - (ae-undocumented) Missing documentation for "WorkflowCategory". +// src/types.d.ts:53:5 - (ae-undocumented) Missing documentation for "ASSESSMENT". +// src/types.d.ts:54:5 - (ae-undocumented) Missing documentation for "INFRASTRUCTURE". +// src/types.d.ts:56:1 - (ae-undocumented) Missing documentation for "WorkflowOverview". +// src/types.d.ts:57:5 - (ae-undocumented) Missing documentation for "workflowId". +// src/types.d.ts:58:5 - (ae-undocumented) Missing documentation for "format". +// src/types.d.ts:59:5 - (ae-undocumented) Missing documentation for "name". +// src/types.d.ts:60:5 - (ae-undocumented) Missing documentation for "lastRunId". +// src/types.d.ts:61:5 - (ae-undocumented) Missing documentation for "lastTriggeredMs". +// src/types.d.ts:62:5 - (ae-undocumented) Missing documentation for "lastRunStatus". +// src/types.d.ts:63:5 - (ae-undocumented) Missing documentation for "category". +// src/types.d.ts:64:5 - (ae-undocumented) Missing documentation for "avgDurationMs". +// src/types.d.ts:65:5 - (ae-undocumented) Missing documentation for "description". +// src/types.d.ts:67:1 - (ae-undocumented) Missing documentation for "WorkflowInfo". +// src/types.d.ts:68:5 - (ae-undocumented) Missing documentation for "id". +// src/types.d.ts:69:5 - (ae-undocumented) Missing documentation for "type". +// src/types.d.ts:70:5 - (ae-undocumented) Missing documentation for "name". +// src/types.d.ts:71:5 - (ae-undocumented) Missing documentation for "version". +// src/types.d.ts:72:5 - (ae-undocumented) Missing documentation for "annotations". +// src/types.d.ts:73:5 - (ae-undocumented) Missing documentation for "description". +// src/types.d.ts:74:5 - (ae-undocumented) Missing documentation for "inputSchema". +// src/types.d.ts:75:5 - (ae-undocumented) Missing documentation for "endpoint". +// src/types.d.ts:76:5 - (ae-undocumented) Missing documentation for "serviceUrl". +// src/types.d.ts:77:5 - (ae-undocumented) Missing documentation for "roles". +// src/types.d.ts:78:5 - (ae-undocumented) Missing documentation for "source". +// src/types.d.ts:79:5 - (ae-undocumented) Missing documentation for "metadata". +// src/types.d.ts:80:5 - (ae-undocumented) Missing documentation for "nodes". +// src/types.d.ts:82:1 - (ae-undocumented) Missing documentation for "Node". +// src/types.d.ts:83:5 - (ae-undocumented) Missing documentation for "id". +// src/types.d.ts:84:5 - (ae-undocumented) Missing documentation for "type". +// src/types.d.ts:85:5 - (ae-undocumented) Missing documentation for "name". +// src/types.d.ts:86:5 - (ae-undocumented) Missing documentation for "uniqueId". +// src/types.d.ts:87:5 - (ae-undocumented) Missing documentation for "nodeDefinitionId". +// src/types.d.ts:89:1 - (ae-undocumented) Missing documentation for "AssessedProcessInstance". +// src/types.d.ts:90:5 - (ae-undocumented) Missing documentation for "instance". +// src/types.d.ts:91:5 - (ae-undocumented) Missing documentation for "assessedBy". +// src/utils/StringUtils.d.ts:1:1 - (ae-undocumented) Missing documentation for "Capitalized". +// src/utils/StringUtils.d.ts:2:22 - (ae-undocumented) Missing documentation for "capitalize". +// src/utils/StringUtils.d.ts:3:22 - (ae-undocumented) Missing documentation for "ellipsis". +// src/workflow.d.ts:2:1 - (ae-undocumented) Missing documentation for "fromWorkflowSource". +// src/workflow.d.ts:3:1 - (ae-undocumented) Missing documentation for "toWorkflowString". +// src/workflow.d.ts:4:1 - (ae-undocumented) Missing documentation for "toWorkflowJson". +// src/workflow.d.ts:5:1 - (ae-undocumented) Missing documentation for "toWorkflowYaml". +// src/workflow.d.ts:6:1 - (ae-undocumented) Missing documentation for "extractWorkflowFormatFromUri". +// src/workflow.d.ts:7:1 - (ae-undocumented) Missing documentation for "getWorkflowCategory". +// src/workflow.d.ts:8:1 - (ae-undocumented) Missing documentation for "parseWorkflowVariables". +// src/workflow.d.ts:9:1 - (ae-undocumented) Missing documentation for "extractWorkflowFormat". + +// (No @packageDocumentation comment for this package) + +``` diff --git a/workspaces/orchestrator/plugins/orchestrator-common/scripts/openapi.sh b/workspaces/orchestrator/plugins/orchestrator-common/scripts/openapi.sh new file mode 100755 index 00000000..64c714d6 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/scripts/openapi.sh @@ -0,0 +1,82 @@ +#!/bin/bash +pwd +set -e + +GENERATED_FOLDER="./src/generated" +OPENAPI_SPEC_FILE="./src/openapi/openapi.yaml" +API_FOLDER="${GENERATED_FOLDER}/api" +DEFINITION_FILE="${API_FOLDER}/definition.ts" +METADATA_FILE="${GENERATED_FOLDER}/.METADATA.sha1" +CLIENT_FOLDER="${GENERATED_FOLDER}/client" + +openapi_generate() { + # TypeScript Client generation + rm -rf ${CLIENT_FOLDER} + openapi-generator-cli generate -g typescript-axios -i ${OPENAPI_SPEC_FILE} -o ${CLIENT_FOLDER} + + # Docs generation + rm -rf ./src/generated/docs/markdown ./src/generated/docs/html + openapi-generator-cli generate -g markdown -i ${OPENAPI_SPEC_FILE} -o ./src/generated/docs/markdown/ + openapi-generator-cli generate -g html2 -i ${OPENAPI_SPEC_FILE} -o ./src/generated/docs/html + + yaml2json -f ${OPENAPI_SPEC_FILE} + + OPENAPI_SPEC_FILE_JSON=$(tr -d '[:space:]' < "$(dirname $OPENAPI_SPEC_FILE)"/openapi.json) + mkdir -p ${API_FOLDER} + cat << EOF > ${DEFINITION_FILE} +/* eslint-disable */ +/* prettier-ignore */ +// GENERATED FILE DO NOT EDIT. +const OPENAPI = \`${OPENAPI_SPEC_FILE_JSON}\`; +export const openApiDocument = JSON.parse(OPENAPI); +EOF + + rm ./src/openapi/openapi.json + NEW_SHA=$(openapi_checksum) + openapi_update "${NEW_SHA}" +} + +openapi_checksum() { + export CONCATENATED_CONTENT=$(cat ${DEFINITION_FILE} ${OPENAPI_SPEC_FILE}) + node -e $'console.log(crypto.createHash("sha1").update(`${process.env.CONCATENATED_CONTENT}`).digest("hex"))' +} + +openapi_update() { + echo "${1}" > "${METADATA_FILE}" +} + +# Function to check if OpenAPI files are up-to-date +openapi_check() { + + if [ ! -f "${METADATA_FILE}" ]; then + echo "Error: Metadata file '${METADATA_FILE}' not found. Run 'yarn openapi:generate' first." + exit 1 + else + STORED_SHA1=$(cat "${METADATA_FILE}") + fi + + # Generate new SHA-1 checksum + NEW_SHA1=$(openapi_checksum) + + # Check if the stored and current SHA-1 checksums differ + if [ "${STORED_SHA1}" != "${NEW_SHA1}" ]; then + echo "Changes detected in generated files or openapi.yaml. Please run 'yarn openapi:generate' to update." + exit 1 + else + echo "No changes detected in generated files or openapi.yaml. generated files are up to date." + fi +} + +# Check the command passed as an argument +case "$1" in + "generate") + openapi_generate + ;; + "check") + openapi_check + ;; + *) + echo "Error: Invalid command. Please use \"${0} generate\" to generate OpenAPI files or \"${0} check\" to verify their status." + exit 1 + ;; +esac diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/QueryParams.ts b/workspaces/orchestrator/plugins/orchestrator-common/src/QueryParams.ts new file mode 100644 index 00000000..d4339432 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/QueryParams.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const QUERY_PARAM_BUSINESS_KEY = 'businessKey' as const; +export const QUERY_PARAM_INSTANCE_ID = 'instanceId' as const; +export const QUERY_PARAM_INCLUDE_ASSESSMENT = 'includeAssessment' as const; +export const QUERY_PARAM_ASSESSMENT_INSTANCE_ID = + 'assessmentInstanceId' as const; diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/constants.ts b/workspaces/orchestrator/plugins/orchestrator-common/src/constants.ts new file mode 100644 index 00000000..31b2bd64 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/constants.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const DEFAULT_SONATAFLOW_CONTAINER_IMAGE = + 'docker.io/apache/incubator-kie-sonataflow-devmode:latest'; +export const DEFAULT_SONATAFLOW_PERSISTENCE_PATH = '/home/kogito/persistence'; +export const DEFAULT_SONATAFLOW_BASE_URL = 'http://localhost'; + +export const DEFAULT_WORKFLOWS_PATH = 'workflows'; + +export const ASSESSMENT_WORKFLOW_TYPE = 'workflow-type/assessment'; +export const INFRASTRUCTURE_WORKFLOW_TYPE = 'workflow-type/infrastructure'; diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/.METADATA.sha1 b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/.METADATA.sha1 new file mode 100644 index 00000000..806eec65 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/.METADATA.sha1 @@ -0,0 +1 @@ +f4fd9f652a05159a3d506540493e275885d54dcd diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/api/definition.ts b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/api/definition.ts new file mode 100644 index 00000000..f77d82c0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/api/definition.ts @@ -0,0 +1,5 @@ +/* eslint-disable */ +/* prettier-ignore */ +// GENERATED FILE DO NOT EDIT. +const OPENAPI = `{"openapi":"3.1.0","info":{"title":"Orchestratorplugin","description":"APItointeractwithorchestratorplugin","license":{"name":"Apache2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"0.0.1"},"servers":[{"url":"/"}],"paths":{"/v2/workflows/overview":{"post":{"operationId":"getWorkflowsOverview","description":"Returnsthekeyfieldsoftheworkflowincludingdataonthelastruninstance","requestBody":{"required":false,"description":"Paginationandfilters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowOverviewListResultDTO"}}}},"500":{"description":"Errorfetchingworkflowoverviews","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v2/workflows/{workflowId}/overview":{"get":{"operationId":"getWorkflowOverviewById","description":"Returnsthekeyfieldsoftheworkflowincludingdataonthelastruninstance","parameters":[{"name":"workflowId","in":"path","required":true,"description":"Uniqueidentifieroftheworkflow","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowOverviewDTO"}}}},"500":{"description":"Errorfetchingworkflowoverview","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v2/workflows/{workflowId}/source":{"get":{"operationId":"getWorkflowSourceById","description":"Gettheworkflow'sdefinition","parameters":[{"name":"workflowId","in":"path","description":"IDoftheworkflowtofetch","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"text/plain":{"schema":{"type":"string"}}}},"500":{"description":"Errorfetchingworkflowsourcebyid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v2/workflows/{workflowId}/inputSchema":{"get":{"operationId":"getWorkflowInputSchemaById","description":"Gettheworkflowinputschema.Itdefinestheinputfieldsoftheworkflow","parameters":[{"name":"workflowId","in":"path","description":"IDoftheworkflowtofetch","required":true,"schema":{"type":"string"}},{"name":"instanceId","in":"query","description":"IDofinstance","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InputSchemaResponseDTO"}}}},"500":{"description":"Errorfetchingworkflowinputschemabyid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v2/workflows/instances":{"post":{"operationId":"getInstances","summary":"Getinstances","description":"Retrieveanarrayofworkflowexecutions(instances)","requestBody":{"required":false,"description":"Parametersforretrievinginstances","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetInstancesRequest"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProcessInstanceListResultDTO"}}}},"500":{"description":"Errorfetchinginstances","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v2/workflows/{workflowId}/instances":{"post":{"operationId":"getWorkflowInstances","summary":"Getinstancesforaspecificworkflow","description":"Retrieveanarrayofworkflowexecutions(instances)forthegivenworkflow","parameters":[{"name":"workflowId","in":"path","required":true,"description":"IDoftheworkflow","schema":{"type":"string"}}],"requestBody":{"required":false,"description":"Parametersforretrievingworkflowinstances","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProcessInstanceListResultDTO"}}}},"500":{"description":"Errorfetchinginstances","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v2/workflows/instances/{instanceId}":{"get":{"summary":"GetWorkflowInstancebyID","description":"Getaworkflowexecution/run(instance)","operationId":"getInstanceById","parameters":[{"name":"instanceId","in":"path","required":true,"description":"IDoftheworkflowinstance","schema":{"type":"string"}},{"name":"includeAssessment","in":"query","required":false,"description":"Whethertoincludeassessment","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Successfulresponse","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssessedProcessInstanceDTO"}}}},"500":{"description":"Errorfetchinginstance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v2/workflows/instances/statuses":{"get":{"operationId":"getWorkflowStatuses","summary":"Getworkflowstatuslist","description":"Retrievearraywiththestatusofallinstances","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowRunStatusDTO"}}}}},"500":{"description":"Errorfetchingworkflowstatuses","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v2/workflows/{workflowId}/execute":{"post":{"summary":"Executeaworkflow","description":"Executeaworkflow","operationId":"executeWorkflow","parameters":[{"name":"workflowId","in":"path","description":"IDoftheworkflowtoexecute","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExecuteWorkflowRequestDTO"}}}},"responses":{"200":{"description":"Successfulexecution","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExecuteWorkflowResponseDTO"}}}},"500":{"description":"InternalServerError","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v2/workflows/{workflowId}/{instanceId}/retrigger":{"post":{"summary":"Retriggeraninstance","description":"Retriggeraninstance","operationId":"retriggerInstance","parameters":[{"name":"workflowId","in":"path","description":"IDoftheworkflow","required":true,"schema":{"type":"string"}},{"name":"instanceId","in":"path","description":"IDoftheinstancetoretrigger","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"500":{"description":"InternalServerError","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v2/workflows/instances/{instanceId}/abort":{"delete":{"summary":"Abortaworkflowinstance","operationId":"abortWorkflow","description":"AbortsaworkflowinstanceidentifiedbytheprovidedinstanceId.","parameters":[{"name":"instanceId","in":"path","required":true,"description":"Theidentifieroftheworkflowinstancetoabort.","schema":{"type":"string"}}],"responses":{"200":{"description":"Successfuloperation","content":{"text/plain":{"schema":{"type":"string"}}}},"500":{"description":"Errorabortingworkflow","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"schemas":{"ErrorResponse":{"description":"TheErrorResponseobjectrepresentsacommonstructureforhandlingerrorsinAPIresponses.Itincludesessentialinformationabouttheerror,suchastheerrormessageandadditionaloptionaldetails.","type":"object","properties":{"message":{"description":"Astringprovidingaconciseandhuman-readabledescriptionoftheencounterederror.ThisfieldisrequiredintheErrorResponseobject.","type":"string","default":"internalservererror"},"additionalInfo":{"description":"Anoptionalfieldthatcancontainadditionalinformationorcontextabouttheerror.Itprovidesflexibilityforincludingextradetailsbasedonspecificerrorscenarios.","type":"string"}},"required":["message"]},"GetInstancesRequest":{"type":"object","properties":{"paginationInfo":{"$ref":"#/components/schemas/PaginationInfoDTO"},"filters":{"$ref":"#/components/schemas/SearchRequest"}}},"GetOverviewsRequestParams":{"type":"object","properties":{"paginationInfo":{"$ref":"#/components/schemas/PaginationInfoDTO"},"filters":{"$ref":"#/components/schemas/SearchRequest"}}},"WorkflowOverviewListResultDTO":{"type":"object","properties":{"overviews":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowOverviewDTO"},"minItems":0},"paginationInfo":{"$ref":"#/components/schemas/PaginationInfoDTO"}}},"WorkflowOverviewDTO":{"type":"object","properties":{"workflowId":{"type":"string","description":"Workflowuniqueidentifier","minLength":1},"name":{"type":"string","description":"Workflowname","minLength":1},"format":{"$ref":"#/components/schemas/WorkflowFormatDTO"},"lastRunId":{"type":"string"},"lastTriggeredMs":{"type":"number","minimum":0},"lastRunStatus":{"$ref":"#/components/schemas/ProcessInstanceStatusDTO"},"category":{"$ref":"#/components/schemas/WorkflowCategoryDTO"},"avgDurationMs":{"type":"number","minimum":0},"description":{"type":"string"}},"required":["workflowId","format"]},"PaginationInfoDTO":{"type":"object","properties":{"pageSize":{"type":"number"},"offset":{"type":"number"},"totalCount":{"type":"number"},"orderDirection":{"enum":["ASC","DESC"]},"orderBy":{"type":"string"}},"additionalProperties":false},"WorkflowFormatDTO":{"type":"string","description":"Formatoftheworkflowdefinition","enum":["yaml","json"]},"WorkflowCategoryDTO":{"type":"string","description":"Categoryoftheworkflow","enum":["assessment","infrastructure"]},"WorkflowListResultDTO":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowDTO"}},"paginationInfo":{"$ref":"#/components/schemas/PaginationInfoDTO"}},"required":["items","paginationInfo"]},"WorkflowDTO":{"type":"object","properties":{"id":{"type":"string","description":"Workflowuniqueidentifier","minLength":1},"name":{"type":"string","description":"Workflowname","minLength":1},"format":{"$ref":"#/components/schemas/WorkflowFormatDTO"},"category":{"$ref":"#/components/schemas/WorkflowCategoryDTO"},"description":{"type":"string","description":"Descriptionoftheworkflow"},"annotations":{"type":"array","items":{"type":"string"}}},"required":["id","category","format"]},"ProcessInstanceListResultDTO":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ProcessInstanceDTO"}},"paginationInfo":{"$ref":"#/components/schemas/PaginationInfoDTO"}}},"AssessedProcessInstanceDTO":{"type":"object","properties":{"instance":{"$ref":"#/components/schemas/ProcessInstanceDTO"},"assessedBy":{"$ref":"#/components/schemas/ProcessInstanceDTO"}},"required":["instance"]},"ProcessInstanceDTO":{"type":"object","properties":{"id":{"type":"string"},"processId":{"type":"string"},"processName":{"type":"string"},"status":{"$ref":"#/components/schemas/ProcessInstanceStatusDTO"},"endpoint":{"type":"string"},"serviceUrl":{"type":"string"},"start":{"type":"string"},"end":{"type":"string"},"duration":{"type":"string"},"category":{"$ref":"#/components/schemas/WorkflowCategoryDTO"},"description":{"type":"string"},"workflowdata":{"$ref":"#/components/schemas/WorkflowDataDTO"},"businessKey":{"type":"string"},"nodes":{"type":"array","items":{"$ref":"#/components/schemas/NodeInstanceDTO"}},"error":{"$ref":"#/components/schemas/ProcessInstanceErrorDTO"}},"required":["id","processId","nodes"]},"WorkflowDataDTO":{"type":"object","properties":{"result":{"$ref":"#/components/schemas/WorkflowResultDTO"}},"additionalProperties":true},"WorkflowResultDTO":{"description":"Resultofaworkflowexecution","type":"object","properties":{"completedWith":{"description":"Thestateofworkflowcompletion.","type":"string","enum":["error","success"]},"message":{"description":"High-levelsummaryofthecurrentstatus,free-formtext,humanreadable.","type":"string"},"nextWorkflows":{"description":"Listofworkflowssuggestedtorunnext.Itemsatlowerindexesareofhigherpriority.","type":"array","items":{"type":"object","properties":{"id":{"description":"Workflowidentifier","type":"string"},"name":{"description":"Humanreadabletitledescribingtheworkflow.","type":"string"}},"required":["id","name"]}},"outputs":{"description":"Additionalstructuredoutputofworkflowprocessing.Thiscancontainidentifiersofcreatedresources,linkstoresources,logsorotheroutput.","type":"array","items":{"type":"object","properties":{"key":{"description":"Uniqueidentifieroftheoption.Preferablyhuman-readable.","type":"string"},"value":{"description":"Freeformvalueoftheoption.","anyOf":[{"type":"string"},{"type":"number"}]},"format":{"description":"Moredetailedtypeofthe'value'property.Defaultsto'text'.","enum":["text","number","link"]}},"required":["key","value"]}}}},"ProcessInstanceStatusDTO":{"type":"string","description":"Statusoftheworkflowrun","enum":["Active","Error","Completed","Aborted","Suspended","Pending"]},"WorkflowRunStatusDTO":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"string"}}},"ExecuteWorkflowRequestDTO":{"type":"object","properties":{"inputData":{"type":"object","additionalProperties":true}},"required":["inputData"]},"ExecuteWorkflowResponseDTO":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]},"WorkflowProgressDTO":{"allOf":[{"$ref":"#/components/schemas/NodeInstanceDTO"},{"type":"object","properties":{"status":{"$ref":"#/components/schemas/ProcessInstanceStatusDTO"},"error":{"$ref":"#/components/schemas/ProcessInstanceErrorDTO"}}}]},"NodeInstanceDTO":{"type":"object","properties":{"__typename":{"type":"string","default":"NodeInstance","description":"Typename"},"id":{"type":"string","description":"NodeinstanceID"},"name":{"type":"string","description":"Nodename"},"type":{"type":"string","description":"Nodetype"},"enter":{"type":"string","description":"Datewhenthenodewasentered"},"exit":{"type":"string","description":"Datewhenthenodewasexited(optional)"},"definitionId":{"type":"string","description":"DefinitionID"},"nodeId":{"type":"string","description":"NodeID"}},"required":["id"]},"ProcessInstanceErrorDTO":{"type":"object","properties":{"__typename":{"type":"string","default":"ProcessInstanceError","description":"Typename"},"nodeDefinitionId":{"type":"string","description":"NodedefinitionID"},"message":{"type":"string","description":"Errormessage(optional)"}},"required":["nodeDefinitionId"]},"SearchRequest":{"type":"object","properties":{"filters":{"$ref":"#/components/schemas/Filter"},"paginationInfo":{"$ref":"#/components/schemas/PaginationInfoDTO"}}},"Filter":{"oneOf":[{"$ref":"#/components/schemas/LogicalFilter"},{"$ref":"#/components/schemas/FieldFilter"}]},"LogicalFilter":{"type":"object","required":["operator","filters"],"properties":{"operator":{"type":"string","enum":["AND","OR","NOT"]},"filters":{"type":"array","items":{"$ref":"#/components/schemas/Filter"}}}},"FieldFilter":{"type":"object","required":["field","operator","value"],"properties":{"field":{"type":"string"},"operator":{"type":"string","enum":["EQ","GT","GTE","LT","LTE","IN","IS_NULL","CONTAINS","CONTAINS_ALL","CONTAINS_ANY","LIKE","BETWEEN"]},"value":{"oneOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]}}]}}},"InputSchemaResponseDTO":{"type":"object","properties":{"inputSchema":{"type":"object"},"data":{"type":"object"}}}}}}`; +export const openApiDocument = JSON.parse(OPENAPI); diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.gitignore b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.gitignore new file mode 100644 index 00000000..149b5765 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.gitignore @@ -0,0 +1,4 @@ +wwwroot/*.js +node_modules +typings +dist diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.npmignore b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.npmignore new file mode 100644 index 00000000..999d88df --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.npmignore @@ -0,0 +1 @@ +# empty npmignore to ensure all required files (e.g., in the dist folder) are published by npm \ No newline at end of file diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.openapi-generator-ignore b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.openapi-generator-ignore new file mode 100644 index 00000000..7484ee59 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.openapi-generator/FILES b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.openapi-generator/FILES new file mode 100644 index 00000000..16b445ee --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.openapi-generator/FILES @@ -0,0 +1,9 @@ +.gitignore +.npmignore +.openapi-generator-ignore +api.ts +base.ts +common.ts +configuration.ts +git_push.sh +index.ts diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.openapi-generator/VERSION b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.openapi-generator/VERSION new file mode 100644 index 00000000..8b23b8d4 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.3.0 \ No newline at end of file diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/api.ts b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/api.ts new file mode 100644 index 00000000..210da157 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/api.ts @@ -0,0 +1,1698 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Orchestrator plugin + * API to interact with orchestrator plugin + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from './configuration'; +import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; +// Some imports not used depending on template conditions +// @ts-ignore +import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common'; +import type { RequestArgs } from './base'; +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError, operationServerMap } from './base'; + +/** + * + * @export + * @interface AssessedProcessInstanceDTO + */ +export interface AssessedProcessInstanceDTO { + /** + * + * @type {ProcessInstanceDTO} + * @memberof AssessedProcessInstanceDTO + */ + 'instance': ProcessInstanceDTO; + /** + * + * @type {ProcessInstanceDTO} + * @memberof AssessedProcessInstanceDTO + */ + 'assessedBy'?: ProcessInstanceDTO; +} +/** + * The ErrorResponse object represents a common structure for handling errors in API responses. It includes essential information about the error, such as the error message and additional optional details. + * @export + * @interface ErrorResponse + */ +export interface ErrorResponse { + /** + * A string providing a concise and human-readable description of the encountered error. This field is required in the ErrorResponse object. + * @type {string} + * @memberof ErrorResponse + */ + 'message': string; + /** + * An optional field that can contain additional information or context about the error. It provides flexibility for including extra details based on specific error scenarios. + * @type {string} + * @memberof ErrorResponse + */ + 'additionalInfo'?: string; +} +/** + * + * @export + * @interface ExecuteWorkflowRequestDTO + */ +export interface ExecuteWorkflowRequestDTO { + /** + * + * @type {object} + * @memberof ExecuteWorkflowRequestDTO + */ + 'inputData': object; +} +/** + * + * @export + * @interface ExecuteWorkflowResponseDTO + */ +export interface ExecuteWorkflowResponseDTO { + /** + * + * @type {string} + * @memberof ExecuteWorkflowResponseDTO + */ + 'id': string; +} +/** + * + * @export + * @interface FieldFilter + */ +export interface FieldFilter { + /** + * + * @type {string} + * @memberof FieldFilter + */ + 'field': string; + /** + * + * @type {string} + * @memberof FieldFilter + */ + 'operator': FieldFilterOperatorEnum; + /** + * + * @type {FieldFilterValue} + * @memberof FieldFilter + */ + 'value': FieldFilterValue; +} + +export const FieldFilterOperatorEnum = { + Eq: 'EQ', + Gt: 'GT', + Gte: 'GTE', + Lt: 'LT', + Lte: 'LTE', + In: 'IN', + IsNull: 'IS_NULL', + Contains: 'CONTAINS', + ContainsAll: 'CONTAINS_ALL', + ContainsAny: 'CONTAINS_ANY', + Like: 'LIKE', + Between: 'BETWEEN' +} as const; + +export type FieldFilterOperatorEnum = typeof FieldFilterOperatorEnum[keyof typeof FieldFilterOperatorEnum]; + +/** + * @type FieldFilterValue + * @export + */ +export type FieldFilterValue = any | boolean | number | string; + +/** + * @type Filter + * @export + */ +export type Filter = FieldFilter | LogicalFilter; + +/** + * + * @export + * @interface GetInstancesRequest + */ +export interface GetInstancesRequest { + /** + * + * @type {PaginationInfoDTO} + * @memberof GetInstancesRequest + */ + 'paginationInfo'?: PaginationInfoDTO; + /** + * + * @type {SearchRequest} + * @memberof GetInstancesRequest + */ + 'filters'?: SearchRequest; +} +/** + * + * @export + * @interface GetOverviewsRequestParams + */ +export interface GetOverviewsRequestParams { + /** + * + * @type {PaginationInfoDTO} + * @memberof GetOverviewsRequestParams + */ + 'paginationInfo'?: PaginationInfoDTO; + /** + * + * @type {SearchRequest} + * @memberof GetOverviewsRequestParams + */ + 'filters'?: SearchRequest; +} +/** + * + * @export + * @interface InputSchemaResponseDTO + */ +export interface InputSchemaResponseDTO { + /** + * + * @type {object} + * @memberof InputSchemaResponseDTO + */ + 'inputSchema'?: object; + /** + * + * @type {object} + * @memberof InputSchemaResponseDTO + */ + 'data'?: object; +} +/** + * + * @export + * @interface LogicalFilter + */ +export interface LogicalFilter { + /** + * + * @type {string} + * @memberof LogicalFilter + */ + 'operator': LogicalFilterOperatorEnum; + /** + * + * @type {Array} + * @memberof LogicalFilter + */ + 'filters': Array; +} + +export const LogicalFilterOperatorEnum = { + And: 'AND', + Or: 'OR', + Not: 'NOT' +} as const; + +export type LogicalFilterOperatorEnum = typeof LogicalFilterOperatorEnum[keyof typeof LogicalFilterOperatorEnum]; + +/** + * + * @export + * @interface NodeInstanceDTO + */ +export interface NodeInstanceDTO { + /** + * Type name + * @type {string} + * @memberof NodeInstanceDTO + */ + '__typename'?: string; + /** + * Node instance ID + * @type {string} + * @memberof NodeInstanceDTO + */ + 'id': string; + /** + * Node name + * @type {string} + * @memberof NodeInstanceDTO + */ + 'name'?: string; + /** + * Node type + * @type {string} + * @memberof NodeInstanceDTO + */ + 'type'?: string; + /** + * Date when the node was entered + * @type {string} + * @memberof NodeInstanceDTO + */ + 'enter'?: string; + /** + * Date when the node was exited (optional) + * @type {string} + * @memberof NodeInstanceDTO + */ + 'exit'?: string; + /** + * Definition ID + * @type {string} + * @memberof NodeInstanceDTO + */ + 'definitionId'?: string; + /** + * Node ID + * @type {string} + * @memberof NodeInstanceDTO + */ + 'nodeId'?: string; +} +/** + * + * @export + * @interface PaginationInfoDTO + */ +export interface PaginationInfoDTO { + /** + * + * @type {number} + * @memberof PaginationInfoDTO + */ + 'pageSize'?: number; + /** + * + * @type {number} + * @memberof PaginationInfoDTO + */ + 'offset'?: number; + /** + * + * @type {number} + * @memberof PaginationInfoDTO + */ + 'totalCount'?: number; + /** + * + * @type {string} + * @memberof PaginationInfoDTO + */ + 'orderDirection'?: PaginationInfoDTOOrderDirectionEnum; + /** + * + * @type {string} + * @memberof PaginationInfoDTO + */ + 'orderBy'?: string; +} + +export const PaginationInfoDTOOrderDirectionEnum = { + Asc: 'ASC', + Desc: 'DESC' +} as const; + +export type PaginationInfoDTOOrderDirectionEnum = typeof PaginationInfoDTOOrderDirectionEnum[keyof typeof PaginationInfoDTOOrderDirectionEnum]; + +/** + * + * @export + * @interface ProcessInstanceDTO + */ +export interface ProcessInstanceDTO { + /** + * + * @type {string} + * @memberof ProcessInstanceDTO + */ + 'id': string; + /** + * + * @type {string} + * @memberof ProcessInstanceDTO + */ + 'processId': string; + /** + * + * @type {string} + * @memberof ProcessInstanceDTO + */ + 'processName'?: string; + /** + * + * @type {ProcessInstanceStatusDTO} + * @memberof ProcessInstanceDTO + */ + 'status'?: ProcessInstanceStatusDTO; + /** + * + * @type {string} + * @memberof ProcessInstanceDTO + */ + 'endpoint'?: string; + /** + * + * @type {string} + * @memberof ProcessInstanceDTO + */ + 'serviceUrl'?: string; + /** + * + * @type {string} + * @memberof ProcessInstanceDTO + */ + 'start'?: string; + /** + * + * @type {string} + * @memberof ProcessInstanceDTO + */ + 'end'?: string; + /** + * + * @type {string} + * @memberof ProcessInstanceDTO + */ + 'duration'?: string; + /** + * + * @type {WorkflowCategoryDTO} + * @memberof ProcessInstanceDTO + */ + 'category'?: WorkflowCategoryDTO; + /** + * + * @type {string} + * @memberof ProcessInstanceDTO + */ + 'description'?: string; + /** + * + * @type {WorkflowDataDTO} + * @memberof ProcessInstanceDTO + */ + 'workflowdata'?: WorkflowDataDTO; + /** + * + * @type {string} + * @memberof ProcessInstanceDTO + */ + 'businessKey'?: string; + /** + * + * @type {Array} + * @memberof ProcessInstanceDTO + */ + 'nodes': Array; + /** + * + * @type {ProcessInstanceErrorDTO} + * @memberof ProcessInstanceDTO + */ + 'error'?: ProcessInstanceErrorDTO; +} + + +/** + * + * @export + * @interface ProcessInstanceErrorDTO + */ +export interface ProcessInstanceErrorDTO { + /** + * Type name + * @type {string} + * @memberof ProcessInstanceErrorDTO + */ + '__typename'?: string; + /** + * Node definition ID + * @type {string} + * @memberof ProcessInstanceErrorDTO + */ + 'nodeDefinitionId': string; + /** + * Error message (optional) + * @type {string} + * @memberof ProcessInstanceErrorDTO + */ + 'message'?: string; +} +/** + * + * @export + * @interface ProcessInstanceListResultDTO + */ +export interface ProcessInstanceListResultDTO { + /** + * + * @type {Array} + * @memberof ProcessInstanceListResultDTO + */ + 'items'?: Array; + /** + * + * @type {PaginationInfoDTO} + * @memberof ProcessInstanceListResultDTO + */ + 'paginationInfo'?: PaginationInfoDTO; +} +/** + * Status of the workflow run + * @export + * @enum {string} + */ + +export const ProcessInstanceStatusDTO = { + Active: 'Active', + Error: 'Error', + Completed: 'Completed', + Aborted: 'Aborted', + Suspended: 'Suspended', + Pending: 'Pending' +} as const; + +export type ProcessInstanceStatusDTO = typeof ProcessInstanceStatusDTO[keyof typeof ProcessInstanceStatusDTO]; + + +/** + * + * @export + * @interface SearchRequest + */ +export interface SearchRequest { + /** + * + * @type {Filter} + * @memberof SearchRequest + */ + 'filters'?: Filter; + /** + * + * @type {PaginationInfoDTO} + * @memberof SearchRequest + */ + 'paginationInfo'?: PaginationInfoDTO; +} +/** + * Category of the workflow + * @export + * @enum {string} + */ + +export const WorkflowCategoryDTO = { + Assessment: 'assessment', + Infrastructure: 'infrastructure' +} as const; + +export type WorkflowCategoryDTO = typeof WorkflowCategoryDTO[keyof typeof WorkflowCategoryDTO]; + + +/** + * + * @export + * @interface WorkflowDTO + */ +export interface WorkflowDTO { + /** + * Workflow unique identifier + * @type {string} + * @memberof WorkflowDTO + */ + 'id': string; + /** + * Workflow name + * @type {string} + * @memberof WorkflowDTO + */ + 'name'?: string; + /** + * + * @type {WorkflowFormatDTO} + * @memberof WorkflowDTO + */ + 'format': WorkflowFormatDTO; + /** + * + * @type {WorkflowCategoryDTO} + * @memberof WorkflowDTO + */ + 'category': WorkflowCategoryDTO; + /** + * Description of the workflow + * @type {string} + * @memberof WorkflowDTO + */ + 'description'?: string; + /** + * + * @type {Array} + * @memberof WorkflowDTO + */ + 'annotations'?: Array; +} + + +/** + * + * @export + * @interface WorkflowDataDTO + */ +export interface WorkflowDataDTO { + /** + * + * @type {WorkflowResultDTO} + * @memberof WorkflowDataDTO + */ + 'result'?: WorkflowResultDTO; +} +/** + * Format of the workflow definition + * @export + * @enum {string} + */ + +export const WorkflowFormatDTO = { + Yaml: 'yaml', + Json: 'json' +} as const; + +export type WorkflowFormatDTO = typeof WorkflowFormatDTO[keyof typeof WorkflowFormatDTO]; + + +/** + * + * @export + * @interface WorkflowListResultDTO + */ +export interface WorkflowListResultDTO { + /** + * + * @type {Array} + * @memberof WorkflowListResultDTO + */ + 'items': Array; + /** + * + * @type {PaginationInfoDTO} + * @memberof WorkflowListResultDTO + */ + 'paginationInfo': PaginationInfoDTO; +} +/** + * + * @export + * @interface WorkflowOverviewDTO + */ +export interface WorkflowOverviewDTO { + /** + * Workflow unique identifier + * @type {string} + * @memberof WorkflowOverviewDTO + */ + 'workflowId': string; + /** + * Workflow name + * @type {string} + * @memberof WorkflowOverviewDTO + */ + 'name'?: string; + /** + * + * @type {WorkflowFormatDTO} + * @memberof WorkflowOverviewDTO + */ + 'format': WorkflowFormatDTO; + /** + * + * @type {string} + * @memberof WorkflowOverviewDTO + */ + 'lastRunId'?: string; + /** + * + * @type {number} + * @memberof WorkflowOverviewDTO + */ + 'lastTriggeredMs'?: number; + /** + * + * @type {ProcessInstanceStatusDTO} + * @memberof WorkflowOverviewDTO + */ + 'lastRunStatus'?: ProcessInstanceStatusDTO; + /** + * + * @type {WorkflowCategoryDTO} + * @memberof WorkflowOverviewDTO + */ + 'category'?: WorkflowCategoryDTO; + /** + * + * @type {number} + * @memberof WorkflowOverviewDTO + */ + 'avgDurationMs'?: number; + /** + * + * @type {string} + * @memberof WorkflowOverviewDTO + */ + 'description'?: string; +} + + +/** + * + * @export + * @interface WorkflowOverviewListResultDTO + */ +export interface WorkflowOverviewListResultDTO { + /** + * + * @type {Array} + * @memberof WorkflowOverviewListResultDTO + */ + 'overviews'?: Array; + /** + * + * @type {PaginationInfoDTO} + * @memberof WorkflowOverviewListResultDTO + */ + 'paginationInfo'?: PaginationInfoDTO; +} +/** + * + * @export + * @interface WorkflowProgressDTO + */ +export interface WorkflowProgressDTO { + /** + * Type name + * @type {any} + * @memberof WorkflowProgressDTO + */ + '__typename'?: any; + /** + * Node instance ID + * @type {any} + * @memberof WorkflowProgressDTO + */ + 'id': any; + /** + * Node name + * @type {any} + * @memberof WorkflowProgressDTO + */ + 'name'?: any; + /** + * Node type + * @type {any} + * @memberof WorkflowProgressDTO + */ + 'type'?: any; + /** + * Date when the node was entered + * @type {any} + * @memberof WorkflowProgressDTO + */ + 'enter'?: any; + /** + * Date when the node was exited (optional) + * @type {any} + * @memberof WorkflowProgressDTO + */ + 'exit'?: any; + /** + * Definition ID + * @type {any} + * @memberof WorkflowProgressDTO + */ + 'definitionId'?: any; + /** + * Node ID + * @type {any} + * @memberof WorkflowProgressDTO + */ + 'nodeId'?: any; + /** + * + * @type {ProcessInstanceStatusDTO} + * @memberof WorkflowProgressDTO + */ + 'status'?: ProcessInstanceStatusDTO; + /** + * + * @type {ProcessInstanceErrorDTO} + * @memberof WorkflowProgressDTO + */ + 'error'?: ProcessInstanceErrorDTO; +} + + +/** + * Result of a workflow execution + * @export + * @interface WorkflowResultDTO + */ +export interface WorkflowResultDTO { + /** + * The state of workflow completion. + * @type {string} + * @memberof WorkflowResultDTO + */ + 'completedWith'?: WorkflowResultDTOCompletedWithEnum; + /** + * High-level summary of the current status, free-form text, human readable. + * @type {string} + * @memberof WorkflowResultDTO + */ + 'message'?: string; + /** + * List of workflows suggested to run next. Items at lower indexes are of higher priority. + * @type {Array} + * @memberof WorkflowResultDTO + */ + 'nextWorkflows'?: Array; + /** + * Additional structured output of workflow processing. This can contain identifiers of created resources, links to resources, logs or other output. + * @type {Array} + * @memberof WorkflowResultDTO + */ + 'outputs'?: Array; +} + +export const WorkflowResultDTOCompletedWithEnum = { + Error: 'error', + Success: 'success' +} as const; + +export type WorkflowResultDTOCompletedWithEnum = typeof WorkflowResultDTOCompletedWithEnum[keyof typeof WorkflowResultDTOCompletedWithEnum]; + +/** + * + * @export + * @interface WorkflowResultDTONextWorkflowsInner + */ +export interface WorkflowResultDTONextWorkflowsInner { + /** + * Workflow identifier + * @type {string} + * @memberof WorkflowResultDTONextWorkflowsInner + */ + 'id': string; + /** + * Human readable title describing the workflow. + * @type {string} + * @memberof WorkflowResultDTONextWorkflowsInner + */ + 'name': string; +} +/** + * + * @export + * @interface WorkflowResultDTOOutputsInner + */ +export interface WorkflowResultDTOOutputsInner { + /** + * Unique identifier of the option. Preferably human-readable. + * @type {string} + * @memberof WorkflowResultDTOOutputsInner + */ + 'key': string; + /** + * + * @type {WorkflowResultDTOOutputsInnerValue} + * @memberof WorkflowResultDTOOutputsInner + */ + 'value': WorkflowResultDTOOutputsInnerValue; + /** + * More detailed type of the \'value\' property. Defaults to \'text\'. + * @type {string} + * @memberof WorkflowResultDTOOutputsInner + */ + 'format'?: WorkflowResultDTOOutputsInnerFormatEnum; +} + +export const WorkflowResultDTOOutputsInnerFormatEnum = { + Text: 'text', + Number: 'number', + Link: 'link' +} as const; + +export type WorkflowResultDTOOutputsInnerFormatEnum = typeof WorkflowResultDTOOutputsInnerFormatEnum[keyof typeof WorkflowResultDTOOutputsInnerFormatEnum]; + +/** + * Free form value of the option. + * @export + * @interface WorkflowResultDTOOutputsInnerValue + */ +export interface WorkflowResultDTOOutputsInnerValue { +} +/** + * + * @export + * @interface WorkflowRunStatusDTO + */ +export interface WorkflowRunStatusDTO { + /** + * + * @type {string} + * @memberof WorkflowRunStatusDTO + */ + 'key'?: string; + /** + * + * @type {string} + * @memberof WorkflowRunStatusDTO + */ + 'value'?: string; +} + +/** + * DefaultApi - axios parameter creator + * @export + */ +export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * Aborts a workflow instance identified by the provided instanceId. + * @summary Abort a workflow instance + * @param {string} instanceId The identifier of the workflow instance to abort. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + abortWorkflow: async (instanceId: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'instanceId' is not null or undefined + assertParamExists('abortWorkflow', 'instanceId', instanceId) + const localVarPath = `/v2/workflows/instances/{instanceId}/abort` + .replace(`{${"instanceId"}}`, encodeURIComponent(String(instanceId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Execute a workflow + * @summary Execute a workflow + * @param {string} workflowId ID of the workflow to execute + * @param {ExecuteWorkflowRequestDTO} executeWorkflowRequestDTO + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + executeWorkflow: async (workflowId: string, executeWorkflowRequestDTO: ExecuteWorkflowRequestDTO, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'workflowId' is not null or undefined + assertParamExists('executeWorkflow', 'workflowId', workflowId) + // verify required parameter 'executeWorkflowRequestDTO' is not null or undefined + assertParamExists('executeWorkflow', 'executeWorkflowRequestDTO', executeWorkflowRequestDTO) + const localVarPath = `/v2/workflows/{workflowId}/execute` + .replace(`{${"workflowId"}}`, encodeURIComponent(String(workflowId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(executeWorkflowRequestDTO, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Get a workflow execution/run (instance) + * @summary Get Workflow Instance by ID + * @param {string} instanceId ID of the workflow instance + * @param {boolean} [includeAssessment] Whether to include assessment + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getInstanceById: async (instanceId: string, includeAssessment?: boolean, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'instanceId' is not null or undefined + assertParamExists('getInstanceById', 'instanceId', instanceId) + const localVarPath = `/v2/workflows/instances/{instanceId}` + .replace(`{${"instanceId"}}`, encodeURIComponent(String(instanceId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + if (includeAssessment !== undefined) { + localVarQueryParameter['includeAssessment'] = includeAssessment; + } + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Retrieve an array of workflow executions (instances) + * @summary Get instances + * @param {GetInstancesRequest} [getInstancesRequest] Parameters for retrieving instances + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getInstances: async (getInstancesRequest?: GetInstancesRequest, options: RawAxiosRequestConfig = {}): Promise => { + const localVarPath = `/v2/workflows/instances`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(getInstancesRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Get the workflow input schema. It defines the input fields of the workflow + * @param {string} workflowId ID of the workflow to fetch + * @param {string} [instanceId] ID of instance + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getWorkflowInputSchemaById: async (workflowId: string, instanceId?: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'workflowId' is not null or undefined + assertParamExists('getWorkflowInputSchemaById', 'workflowId', workflowId) + const localVarPath = `/v2/workflows/{workflowId}/inputSchema` + .replace(`{${"workflowId"}}`, encodeURIComponent(String(workflowId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + if (instanceId !== undefined) { + localVarQueryParameter['instanceId'] = instanceId; + } + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Retrieve an array of workflow executions (instances) for the given workflow + * @summary Get instances for a specific workflow + * @param {string} workflowId ID of the workflow + * @param {SearchRequest} [searchRequest] Parameters for retrieving workflow instances + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getWorkflowInstances: async (workflowId: string, searchRequest?: SearchRequest, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'workflowId' is not null or undefined + assertParamExists('getWorkflowInstances', 'workflowId', workflowId) + const localVarPath = `/v2/workflows/{workflowId}/instances` + .replace(`{${"workflowId"}}`, encodeURIComponent(String(workflowId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(searchRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Returns the key fields of the workflow including data on the last run instance + * @param {string} workflowId Unique identifier of the workflow + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getWorkflowOverviewById: async (workflowId: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'workflowId' is not null or undefined + assertParamExists('getWorkflowOverviewById', 'workflowId', workflowId) + const localVarPath = `/v2/workflows/{workflowId}/overview` + .replace(`{${"workflowId"}}`, encodeURIComponent(String(workflowId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Get the workflow\'s definition + * @param {string} workflowId ID of the workflow to fetch + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getWorkflowSourceById: async (workflowId: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'workflowId' is not null or undefined + assertParamExists('getWorkflowSourceById', 'workflowId', workflowId) + const localVarPath = `/v2/workflows/{workflowId}/source` + .replace(`{${"workflowId"}}`, encodeURIComponent(String(workflowId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Retrieve array with the status of all instances + * @summary Get workflow status list + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getWorkflowStatuses: async (options: RawAxiosRequestConfig = {}): Promise => { + const localVarPath = `/v2/workflows/instances/statuses`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Returns the key fields of the workflow including data on the last run instance + * @param {SearchRequest} [searchRequest] Pagination and filters + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getWorkflowsOverview: async (searchRequest?: SearchRequest, options: RawAxiosRequestConfig = {}): Promise => { + const localVarPath = `/v2/workflows/overview`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(searchRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Retrigger an instance + * @summary Retrigger an instance + * @param {string} workflowId ID of the workflow + * @param {string} instanceId ID of the instance to retrigger + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + retriggerInstance: async (workflowId: string, instanceId: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'workflowId' is not null or undefined + assertParamExists('retriggerInstance', 'workflowId', workflowId) + // verify required parameter 'instanceId' is not null or undefined + assertParamExists('retriggerInstance', 'instanceId', instanceId) + const localVarPath = `/v2/workflows/{workflowId}/{instanceId}/retrigger` + .replace(`{${"workflowId"}}`, encodeURIComponent(String(workflowId))) + .replace(`{${"instanceId"}}`, encodeURIComponent(String(instanceId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * DefaultApi - functional programming interface + * @export + */ +export const DefaultApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration) + return { + /** + * Aborts a workflow instance identified by the provided instanceId. + * @summary Abort a workflow instance + * @param {string} instanceId The identifier of the workflow instance to abort. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async abortWorkflow(instanceId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.abortWorkflow(instanceId, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['DefaultApi.abortWorkflow']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Execute a workflow + * @summary Execute a workflow + * @param {string} workflowId ID of the workflow to execute + * @param {ExecuteWorkflowRequestDTO} executeWorkflowRequestDTO + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async executeWorkflow(workflowId: string, executeWorkflowRequestDTO: ExecuteWorkflowRequestDTO, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.executeWorkflow(workflowId, executeWorkflowRequestDTO, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['DefaultApi.executeWorkflow']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Get a workflow execution/run (instance) + * @summary Get Workflow Instance by ID + * @param {string} instanceId ID of the workflow instance + * @param {boolean} [includeAssessment] Whether to include assessment + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getInstanceById(instanceId: string, includeAssessment?: boolean, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getInstanceById(instanceId, includeAssessment, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['DefaultApi.getInstanceById']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Retrieve an array of workflow executions (instances) + * @summary Get instances + * @param {GetInstancesRequest} [getInstancesRequest] Parameters for retrieving instances + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getInstances(getInstancesRequest?: GetInstancesRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getInstances(getInstancesRequest, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['DefaultApi.getInstances']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Get the workflow input schema. It defines the input fields of the workflow + * @param {string} workflowId ID of the workflow to fetch + * @param {string} [instanceId] ID of instance + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getWorkflowInputSchemaById(workflowId: string, instanceId?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getWorkflowInputSchemaById(workflowId, instanceId, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['DefaultApi.getWorkflowInputSchemaById']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Retrieve an array of workflow executions (instances) for the given workflow + * @summary Get instances for a specific workflow + * @param {string} workflowId ID of the workflow + * @param {SearchRequest} [searchRequest] Parameters for retrieving workflow instances + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getWorkflowInstances(workflowId: string, searchRequest?: SearchRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getWorkflowInstances(workflowId, searchRequest, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['DefaultApi.getWorkflowInstances']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Returns the key fields of the workflow including data on the last run instance + * @param {string} workflowId Unique identifier of the workflow + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getWorkflowOverviewById(workflowId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getWorkflowOverviewById(workflowId, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['DefaultApi.getWorkflowOverviewById']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Get the workflow\'s definition + * @param {string} workflowId ID of the workflow to fetch + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getWorkflowSourceById(workflowId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getWorkflowSourceById(workflowId, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['DefaultApi.getWorkflowSourceById']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Retrieve array with the status of all instances + * @summary Get workflow status list + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getWorkflowStatuses(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getWorkflowStatuses(options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['DefaultApi.getWorkflowStatuses']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Returns the key fields of the workflow including data on the last run instance + * @param {SearchRequest} [searchRequest] Pagination and filters + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getWorkflowsOverview(searchRequest?: SearchRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getWorkflowsOverview(searchRequest, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['DefaultApi.getWorkflowsOverview']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Retrigger an instance + * @summary Retrigger an instance + * @param {string} workflowId ID of the workflow + * @param {string} instanceId ID of the instance to retrigger + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async retriggerInstance(workflowId: string, instanceId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.retriggerInstance(workflowId, instanceId, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['DefaultApi.retriggerInstance']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + } +}; + +/** + * DefaultApi - factory interface + * @export + */ +export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = DefaultApiFp(configuration) + return { + /** + * Aborts a workflow instance identified by the provided instanceId. + * @summary Abort a workflow instance + * @param {string} instanceId The identifier of the workflow instance to abort. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + abortWorkflow(instanceId: string, options?: any): AxiosPromise { + return localVarFp.abortWorkflow(instanceId, options).then((request) => request(axios, basePath)); + }, + /** + * Execute a workflow + * @summary Execute a workflow + * @param {string} workflowId ID of the workflow to execute + * @param {ExecuteWorkflowRequestDTO} executeWorkflowRequestDTO + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + executeWorkflow(workflowId: string, executeWorkflowRequestDTO: ExecuteWorkflowRequestDTO, options?: any): AxiosPromise { + return localVarFp.executeWorkflow(workflowId, executeWorkflowRequestDTO, options).then((request) => request(axios, basePath)); + }, + /** + * Get a workflow execution/run (instance) + * @summary Get Workflow Instance by ID + * @param {string} instanceId ID of the workflow instance + * @param {boolean} [includeAssessment] Whether to include assessment + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getInstanceById(instanceId: string, includeAssessment?: boolean, options?: any): AxiosPromise { + return localVarFp.getInstanceById(instanceId, includeAssessment, options).then((request) => request(axios, basePath)); + }, + /** + * Retrieve an array of workflow executions (instances) + * @summary Get instances + * @param {GetInstancesRequest} [getInstancesRequest] Parameters for retrieving instances + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getInstances(getInstancesRequest?: GetInstancesRequest, options?: any): AxiosPromise { + return localVarFp.getInstances(getInstancesRequest, options).then((request) => request(axios, basePath)); + }, + /** + * Get the workflow input schema. It defines the input fields of the workflow + * @param {string} workflowId ID of the workflow to fetch + * @param {string} [instanceId] ID of instance + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getWorkflowInputSchemaById(workflowId: string, instanceId?: string, options?: any): AxiosPromise { + return localVarFp.getWorkflowInputSchemaById(workflowId, instanceId, options).then((request) => request(axios, basePath)); + }, + /** + * Retrieve an array of workflow executions (instances) for the given workflow + * @summary Get instances for a specific workflow + * @param {string} workflowId ID of the workflow + * @param {SearchRequest} [searchRequest] Parameters for retrieving workflow instances + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getWorkflowInstances(workflowId: string, searchRequest?: SearchRequest, options?: any): AxiosPromise { + return localVarFp.getWorkflowInstances(workflowId, searchRequest, options).then((request) => request(axios, basePath)); + }, + /** + * Returns the key fields of the workflow including data on the last run instance + * @param {string} workflowId Unique identifier of the workflow + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getWorkflowOverviewById(workflowId: string, options?: any): AxiosPromise { + return localVarFp.getWorkflowOverviewById(workflowId, options).then((request) => request(axios, basePath)); + }, + /** + * Get the workflow\'s definition + * @param {string} workflowId ID of the workflow to fetch + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getWorkflowSourceById(workflowId: string, options?: any): AxiosPromise { + return localVarFp.getWorkflowSourceById(workflowId, options).then((request) => request(axios, basePath)); + }, + /** + * Retrieve array with the status of all instances + * @summary Get workflow status list + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getWorkflowStatuses(options?: any): AxiosPromise> { + return localVarFp.getWorkflowStatuses(options).then((request) => request(axios, basePath)); + }, + /** + * Returns the key fields of the workflow including data on the last run instance + * @param {SearchRequest} [searchRequest] Pagination and filters + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getWorkflowsOverview(searchRequest?: SearchRequest, options?: any): AxiosPromise { + return localVarFp.getWorkflowsOverview(searchRequest, options).then((request) => request(axios, basePath)); + }, + /** + * Retrigger an instance + * @summary Retrigger an instance + * @param {string} workflowId ID of the workflow + * @param {string} instanceId ID of the instance to retrigger + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + retriggerInstance(workflowId: string, instanceId: string, options?: any): AxiosPromise { + return localVarFp.retriggerInstance(workflowId, instanceId, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * DefaultApi - object-oriented interface + * @export + * @class DefaultApi + * @extends {BaseAPI} + */ +export class DefaultApi extends BaseAPI { + /** + * Aborts a workflow instance identified by the provided instanceId. + * @summary Abort a workflow instance + * @param {string} instanceId The identifier of the workflow instance to abort. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public abortWorkflow(instanceId: string, options?: RawAxiosRequestConfig) { + return DefaultApiFp(this.configuration).abortWorkflow(instanceId, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Execute a workflow + * @summary Execute a workflow + * @param {string} workflowId ID of the workflow to execute + * @param {ExecuteWorkflowRequestDTO} executeWorkflowRequestDTO + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public executeWorkflow(workflowId: string, executeWorkflowRequestDTO: ExecuteWorkflowRequestDTO, options?: RawAxiosRequestConfig) { + return DefaultApiFp(this.configuration).executeWorkflow(workflowId, executeWorkflowRequestDTO, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Get a workflow execution/run (instance) + * @summary Get Workflow Instance by ID + * @param {string} instanceId ID of the workflow instance + * @param {boolean} [includeAssessment] Whether to include assessment + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getInstanceById(instanceId: string, includeAssessment?: boolean, options?: RawAxiosRequestConfig) { + return DefaultApiFp(this.configuration).getInstanceById(instanceId, includeAssessment, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Retrieve an array of workflow executions (instances) + * @summary Get instances + * @param {GetInstancesRequest} [getInstancesRequest] Parameters for retrieving instances + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getInstances(getInstancesRequest?: GetInstancesRequest, options?: RawAxiosRequestConfig) { + return DefaultApiFp(this.configuration).getInstances(getInstancesRequest, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Get the workflow input schema. It defines the input fields of the workflow + * @param {string} workflowId ID of the workflow to fetch + * @param {string} [instanceId] ID of instance + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getWorkflowInputSchemaById(workflowId: string, instanceId?: string, options?: RawAxiosRequestConfig) { + return DefaultApiFp(this.configuration).getWorkflowInputSchemaById(workflowId, instanceId, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Retrieve an array of workflow executions (instances) for the given workflow + * @summary Get instances for a specific workflow + * @param {string} workflowId ID of the workflow + * @param {SearchRequest} [searchRequest] Parameters for retrieving workflow instances + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getWorkflowInstances(workflowId: string, searchRequest?: SearchRequest, options?: RawAxiosRequestConfig) { + return DefaultApiFp(this.configuration).getWorkflowInstances(workflowId, searchRequest, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Returns the key fields of the workflow including data on the last run instance + * @param {string} workflowId Unique identifier of the workflow + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getWorkflowOverviewById(workflowId: string, options?: RawAxiosRequestConfig) { + return DefaultApiFp(this.configuration).getWorkflowOverviewById(workflowId, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Get the workflow\'s definition + * @param {string} workflowId ID of the workflow to fetch + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getWorkflowSourceById(workflowId: string, options?: RawAxiosRequestConfig) { + return DefaultApiFp(this.configuration).getWorkflowSourceById(workflowId, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Retrieve array with the status of all instances + * @summary Get workflow status list + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getWorkflowStatuses(options?: RawAxiosRequestConfig) { + return DefaultApiFp(this.configuration).getWorkflowStatuses(options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Returns the key fields of the workflow including data on the last run instance + * @param {SearchRequest} [searchRequest] Pagination and filters + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getWorkflowsOverview(searchRequest?: SearchRequest, options?: RawAxiosRequestConfig) { + return DefaultApiFp(this.configuration).getWorkflowsOverview(searchRequest, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Retrigger an instance + * @summary Retrigger an instance + * @param {string} workflowId ID of the workflow + * @param {string} instanceId ID of the instance to retrigger + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public retriggerInstance(workflowId: string, instanceId: string, options?: RawAxiosRequestConfig) { + return DefaultApiFp(this.configuration).retriggerInstance(workflowId, instanceId, options).then((request) => request(this.axios, this.basePath)); + } +} + + + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/base.ts b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/base.ts new file mode 100644 index 00000000..ab6429a0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/base.ts @@ -0,0 +1,86 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Orchestrator plugin + * API to interact with orchestrator plugin + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from './configuration'; +// Some imports not used depending on template conditions +// @ts-ignore +import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; + +export const BASE_PATH = "http://localhost".replace(/\/+$/, ""); + +/** + * + * @export + */ +export const COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", +}; + +/** + * + * @export + * @interface RequestArgs + */ +export interface RequestArgs { + url: string; + options: RawAxiosRequestConfig; +} + +/** + * + * @export + * @class BaseAPI + */ +export class BaseAPI { + protected configuration: Configuration | undefined; + + constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) { + if (configuration) { + this.configuration = configuration; + this.basePath = configuration.basePath ?? basePath; + } + } +}; + +/** + * + * @export + * @class RequiredError + * @extends {Error} + */ +export class RequiredError extends Error { + constructor(public field: string, msg?: string) { + super(msg); + this.name = "RequiredError" + } +} + +interface ServerMap { + [key: string]: { + url: string, + description: string, + }[]; +} + +/** + * + * @export + */ +export const operationServerMap: ServerMap = { +} diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/common.ts b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/common.ts new file mode 100644 index 00000000..1bc8f423 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/common.ts @@ -0,0 +1,150 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Orchestrator plugin + * API to interact with orchestrator plugin + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from "./configuration"; +import type { RequestArgs } from "./base"; +import type { AxiosInstance, AxiosResponse } from 'axios'; +import { RequiredError } from "./base"; + +/** + * + * @export + */ +export const DUMMY_BASE_URL = 'https://example.com' + +/** + * + * @throws {RequiredError} + * @export + */ +export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) { + if (paramValue === null || paramValue === undefined) { + throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`); + } +} + +/** + * + * @export + */ +export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) { + if (configuration && configuration.apiKey) { + const localVarApiKeyValue = typeof configuration.apiKey === 'function' + ? await configuration.apiKey(keyParamName) + : await configuration.apiKey; + object[keyParamName] = localVarApiKeyValue; + } +} + +/** + * + * @export + */ +export const setBasicAuthToObject = function (object: any, configuration?: Configuration) { + if (configuration && (configuration.username || configuration.password)) { + object["auth"] = { username: configuration.username, password: configuration.password }; + } +} + +/** + * + * @export + */ +export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const accessToken = typeof configuration.accessToken === 'function' + ? await configuration.accessToken() + : await configuration.accessToken; + object["Authorization"] = "Bearer " + accessToken; + } +} + +/** + * + * @export + */ +export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const localVarAccessTokenValue = typeof configuration.accessToken === 'function' + ? await configuration.accessToken(name, scopes) + : await configuration.accessToken; + object["Authorization"] = "Bearer " + localVarAccessTokenValue; + } +} + +function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ""): void { + if (parameter == null) return; + if (typeof parameter === "object") { + if (Array.isArray(parameter)) { + (parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key)); + } + else { + Object.keys(parameter).forEach(currentKey => + setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`) + ); + } + } + else { + if (urlSearchParams.has(key)) { + urlSearchParams.append(key, parameter); + } + else { + urlSearchParams.set(key, parameter); + } + } +} + +/** + * + * @export + */ +export const setSearchParams = function (url: URL, ...objects: any[]) { + const searchParams = new URLSearchParams(url.search); + setFlattenedQueryParams(searchParams, objects); + url.search = searchParams.toString(); +} + +/** + * + * @export + */ +export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) { + const nonString = typeof value !== 'string'; + const needsSerialization = nonString && configuration && configuration.isJsonMime + ? configuration.isJsonMime(requestOptions.headers['Content-Type']) + : nonString; + return needsSerialization + ? JSON.stringify(value !== undefined ? value : {}) + : (value || ""); +} + +/** + * + * @export + */ +export const toPathString = function (url: URL) { + return url.pathname + url.search + url.hash +} + +/** + * + * @export + */ +export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) { + return >(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs = {...axiosArgs.options, url: (axios.defaults.baseURL ? '' : configuration?.basePath ?? basePath) + axiosArgs.url}; + return axios.request(axiosRequestArgs); + }; +} diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/configuration.ts b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/configuration.ts new file mode 100644 index 00000000..aacabb28 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/configuration.ts @@ -0,0 +1,110 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Orchestrator plugin + * API to interact with orchestrator plugin + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface ConfigurationParameters { + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + username?: string; + password?: string; + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + basePath?: string; + serverIndex?: number; + baseOptions?: any; + formDataCtor?: new () => any; +} + +export class Configuration { + /** + * parameter for apiKey security + * @param name security name + * @memberof Configuration + */ + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + username?: string; + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + password?: string; + /** + * parameter for oauth2 security + * @param name security name + * @param scopes oauth2 scope + * @memberof Configuration + */ + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + /** + * override base path + * + * @type {string} + * @memberof Configuration + */ + basePath?: string; + /** + * override server index + * + * @type {number} + * @memberof Configuration + */ + serverIndex?: number; + /** + * base options for axios calls + * + * @type {any} + * @memberof Configuration + */ + baseOptions?: any; + /** + * The FormData constructor that will be used to create multipart form data + * requests. You can inject this here so that execution environments that + * do not support the FormData class can still run the generated client. + * + * @type {new () => FormData} + */ + formDataCtor?: new () => any; + + constructor(param: ConfigurationParameters = {}) { + this.apiKey = param.apiKey; + this.username = param.username; + this.password = param.password; + this.accessToken = param.accessToken; + this.basePath = param.basePath; + this.serverIndex = param.serverIndex; + this.baseOptions = param.baseOptions; + this.formDataCtor = param.formDataCtor; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + public isJsonMime(mime: string): boolean { + const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i'); + return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json'); + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/git_push.sh b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/git_push.sh new file mode 100644 index 00000000..f53a75d4 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/git_push.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 +git_host=$4 + +if [ "$git_host" = "" ]; then + git_host="github.com" + echo "[INFO] No command line input provided. Set \$git_host to $git_host" +fi + +if [ "$git_user_id" = "" ]; then + git_user_id="GIT_USER_ID" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="GIT_REPO_ID" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="Minor update" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=$(git remote) +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/index.ts b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/index.ts new file mode 100644 index 00000000..abcbe1fc --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/client/index.ts @@ -0,0 +1,18 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Orchestrator plugin + * API to interact with orchestrator plugin + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export * from "./api"; +export * from "./configuration"; + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/html/.openapi-generator-ignore b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/html/.openapi-generator-ignore new file mode 100644 index 00000000..7484ee59 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/html/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/html/.openapi-generator/FILES b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/html/.openapi-generator/FILES new file mode 100644 index 00000000..af3fdae9 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/html/.openapi-generator/FILES @@ -0,0 +1,2 @@ +.openapi-generator-ignore +index.html diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/html/.openapi-generator/VERSION b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/html/.openapi-generator/VERSION new file mode 100644 index 00000000..8b23b8d4 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/html/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.3.0 \ No newline at end of file diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/html/index.html b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/html/index.html new file mode 100644 index 00000000..7e00a245 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/html/index.html @@ -0,0 +1,8028 @@ + + + + + Orchestrator plugin + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+

Orchestrator plugin

+
+
+
+ +
+
+

Default

+
+
+
+

abortWorkflow

+

Abort a workflow instance

+
+
+
+

+

Aborts a workflow instance identified by the provided instanceId.

+

+
+
/v2/workflows/instances/{instanceId}/abort
+

+

Usage and SDK Samples

+

+ + +
+
+
curl -X DELETE \
+ -H "Accept: text/plain,application/json" \
+ "http://localhost/v2/workflows/instances/{instanceId}/abort"
+
+
+
+
import org.openapitools.client.*;
+import org.openapitools.client.auth.*;
+import org.openapitools.client.model.*;
+import org.openapitools.client.api.DefaultApi;
+
+import java.io.File;
+import java.util.*;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+
+        // Create an instance of the API class
+        DefaultApi apiInstance = new DefaultApi();
+        String instanceId = instanceId_example; // String | The identifier of the workflow instance to abort.
+
+        try {
+            'String' result = apiInstance.abortWorkflow(instanceId);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#abortWorkflow");
+            e.printStackTrace();
+        }
+    }
+}
+
+
+ +
+
import 'package:openapi/api.dart';
+
+final api_instance = DefaultApi();
+
+final String instanceId = new String(); // String | The identifier of the workflow instance to abort.
+
+try {
+    final result = await api_instance.abortWorkflow(instanceId);
+    print(result);
+} catch (e) {
+    print('Exception when calling DefaultApi->abortWorkflow: $e\n');
+}
+
+
+
+ +
+
import org.openapitools.client.api.DefaultApi;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+        DefaultApi apiInstance = new DefaultApi();
+        String instanceId = instanceId_example; // String | The identifier of the workflow instance to abort.
+
+        try {
+            'String' result = apiInstance.abortWorkflow(instanceId);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#abortWorkflow");
+            e.printStackTrace();
+        }
+    }
+}
+
+ +
+

+
+// Create an instance of the API class
+DefaultApi *apiInstance = [[DefaultApi alloc] init];
+String *instanceId = instanceId_example; // The identifier of the workflow instance to abort. (default to null)
+
+// Abort a workflow instance
+[apiInstance abortWorkflowWith:instanceId
+              completionHandler: ^('String' output, NSError* error) {
+    if (output) {
+        NSLog(@"%@", output);
+    }
+    if (error) {
+        NSLog(@"Error: %@", error);
+    }
+}];
+
+
+ +
+
var OrchestratorPlugin = require('orchestrator_plugin');
+
+// Create an instance of the API class
+var api = new OrchestratorPlugin.DefaultApi()
+var instanceId = instanceId_example; // {String} The identifier of the workflow instance to abort.
+
+var callback = function(error, data, response) {
+  if (error) {
+    console.error(error);
+  } else {
+    console.log('API called successfully. Returned data: ' + data);
+  }
+};
+api.abortWorkflow(instanceId, callback);
+
+
+ + +
+
using System;
+using System.Diagnostics;
+using Org.OpenAPITools.Api;
+using Org.OpenAPITools.Client;
+using Org.OpenAPITools.Model;
+
+namespace Example
+{
+    public class abortWorkflowExample
+    {
+        public void main()
+        {
+
+            // Create an instance of the API class
+            var apiInstance = new DefaultApi();
+            var instanceId = instanceId_example;  // String | The identifier of the workflow instance to abort. (default to null)
+
+            try {
+                // Abort a workflow instance
+                'String' result = apiInstance.abortWorkflow(instanceId);
+                Debug.WriteLine(result);
+            } catch (Exception e) {
+                Debug.Print("Exception when calling DefaultApi.abortWorkflow: " + e.Message );
+            }
+        }
+    }
+}
+
+
+ +
+
<?php
+require_once(__DIR__ . '/vendor/autoload.php');
+
+// Create an instance of the API class
+$api_instance = new OpenAPITools\Client\Api\DefaultApi();
+$instanceId = instanceId_example; // String | The identifier of the workflow instance to abort.
+
+try {
+    $result = $api_instance->abortWorkflow($instanceId);
+    print_r($result);
+} catch (Exception $e) {
+    echo 'Exception when calling DefaultApi->abortWorkflow: ', $e->getMessage(), PHP_EOL;
+}
+?>
+
+ +
+
use Data::Dumper;
+use WWW::OPenAPIClient::Configuration;
+use WWW::OPenAPIClient::DefaultApi;
+
+# Create an instance of the API class
+my $api_instance = WWW::OPenAPIClient::DefaultApi->new();
+my $instanceId = instanceId_example; # String | The identifier of the workflow instance to abort.
+
+eval {
+    my $result = $api_instance->abortWorkflow(instanceId => $instanceId);
+    print Dumper($result);
+};
+if ($@) {
+    warn "Exception when calling DefaultApi->abortWorkflow: $@\n";
+}
+
+ +
+
from __future__ import print_statement
+import time
+import openapi_client
+from openapi_client.rest import ApiException
+from pprint import pprint
+
+# Create an instance of the API class
+api_instance = openapi_client.DefaultApi()
+instanceId = instanceId_example # String | The identifier of the workflow instance to abort. (default to null)
+
+try:
+    # Abort a workflow instance
+    api_response = api_instance.abort_workflow(instanceId)
+    pprint(api_response)
+except ApiException as e:
+    print("Exception when calling DefaultApi->abortWorkflow: %s\n" % e)
+
+ +
+
extern crate DefaultApi;
+
+pub fn main() {
+    let instanceId = instanceId_example; // String
+
+    let mut context = DefaultApi::Context::default();
+    let result = client.abortWorkflow(instanceId, &context).wait();
+
+    println!("{:?}", result);
+}
+
+
+
+ +

Scopes

+ + +
+ +

Parameters

+ +
Path parameters
+ + + + + + + + + +
NameDescription
instanceId* + + +
+
+
+ + String + + +
+The identifier of the workflow instance to abort. +
+
+
+ Required +
+
+
+
+ + + + + +

Responses

+

+

+ + + + + + +
+
+
+ +
+ +
+
+

+

+ + + + + + +
+
+
+ +
+ +
+
+
+
+
+
+
+
+

executeWorkflow

+

Execute a workflow

+
+
+
+

+

Execute a workflow

+

+
+
/v2/workflows/{workflowId}/execute
+

+

Usage and SDK Samples

+

+ + +
+
+
curl -X POST \
+ -H "Accept: application/json" \
+ -H "Content-Type: application/json" \
+ "http://localhost/v2/workflows/{workflowId}/execute" \
+ -d '{
+  "inputData" : "{}"
+}'
+
+
+
+
import org.openapitools.client.*;
+import org.openapitools.client.auth.*;
+import org.openapitools.client.model.*;
+import org.openapitools.client.api.DefaultApi;
+
+import java.io.File;
+import java.util.*;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+
+        // Create an instance of the API class
+        DefaultApi apiInstance = new DefaultApi();
+        String workflowId = workflowId_example; // String | ID of the workflow to execute
+        ExecuteWorkflowRequestDTO executeWorkflowRequestDTO = ; // ExecuteWorkflowRequestDTO | 
+
+        try {
+            ExecuteWorkflowResponseDTO result = apiInstance.executeWorkflow(workflowId, executeWorkflowRequestDTO);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#executeWorkflow");
+            e.printStackTrace();
+        }
+    }
+}
+
+
+ +
+
import 'package:openapi/api.dart';
+
+final api_instance = DefaultApi();
+
+final String workflowId = new String(); // String | ID of the workflow to execute
+final ExecuteWorkflowRequestDTO executeWorkflowRequestDTO = new ExecuteWorkflowRequestDTO(); // ExecuteWorkflowRequestDTO | 
+
+try {
+    final result = await api_instance.executeWorkflow(workflowId, executeWorkflowRequestDTO);
+    print(result);
+} catch (e) {
+    print('Exception when calling DefaultApi->executeWorkflow: $e\n');
+}
+
+
+
+ +
+
import org.openapitools.client.api.DefaultApi;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+        DefaultApi apiInstance = new DefaultApi();
+        String workflowId = workflowId_example; // String | ID of the workflow to execute
+        ExecuteWorkflowRequestDTO executeWorkflowRequestDTO = ; // ExecuteWorkflowRequestDTO | 
+
+        try {
+            ExecuteWorkflowResponseDTO result = apiInstance.executeWorkflow(workflowId, executeWorkflowRequestDTO);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#executeWorkflow");
+            e.printStackTrace();
+        }
+    }
+}
+
+ +
+

+
+// Create an instance of the API class
+DefaultApi *apiInstance = [[DefaultApi alloc] init];
+String *workflowId = workflowId_example; // ID of the workflow to execute (default to null)
+ExecuteWorkflowRequestDTO *executeWorkflowRequestDTO = ; // 
+
+// Execute a workflow
+[apiInstance executeWorkflowWith:workflowId
+    executeWorkflowRequestDTO:executeWorkflowRequestDTO
+              completionHandler: ^(ExecuteWorkflowResponseDTO output, NSError* error) {
+    if (output) {
+        NSLog(@"%@", output);
+    }
+    if (error) {
+        NSLog(@"Error: %@", error);
+    }
+}];
+
+
+ +
+
var OrchestratorPlugin = require('orchestrator_plugin');
+
+// Create an instance of the API class
+var api = new OrchestratorPlugin.DefaultApi()
+var workflowId = workflowId_example; // {String} ID of the workflow to execute
+var executeWorkflowRequestDTO = ; // {ExecuteWorkflowRequestDTO} 
+
+var callback = function(error, data, response) {
+  if (error) {
+    console.error(error);
+  } else {
+    console.log('API called successfully. Returned data: ' + data);
+  }
+};
+api.executeWorkflow(workflowId, executeWorkflowRequestDTO, callback);
+
+
+ + +
+
using System;
+using System.Diagnostics;
+using Org.OpenAPITools.Api;
+using Org.OpenAPITools.Client;
+using Org.OpenAPITools.Model;
+
+namespace Example
+{
+    public class executeWorkflowExample
+    {
+        public void main()
+        {
+
+            // Create an instance of the API class
+            var apiInstance = new DefaultApi();
+            var workflowId = workflowId_example;  // String | ID of the workflow to execute (default to null)
+            var executeWorkflowRequestDTO = new ExecuteWorkflowRequestDTO(); // ExecuteWorkflowRequestDTO | 
+
+            try {
+                // Execute a workflow
+                ExecuteWorkflowResponseDTO result = apiInstance.executeWorkflow(workflowId, executeWorkflowRequestDTO);
+                Debug.WriteLine(result);
+            } catch (Exception e) {
+                Debug.Print("Exception when calling DefaultApi.executeWorkflow: " + e.Message );
+            }
+        }
+    }
+}
+
+
+ +
+
<?php
+require_once(__DIR__ . '/vendor/autoload.php');
+
+// Create an instance of the API class
+$api_instance = new OpenAPITools\Client\Api\DefaultApi();
+$workflowId = workflowId_example; // String | ID of the workflow to execute
+$executeWorkflowRequestDTO = ; // ExecuteWorkflowRequestDTO | 
+
+try {
+    $result = $api_instance->executeWorkflow($workflowId, $executeWorkflowRequestDTO);
+    print_r($result);
+} catch (Exception $e) {
+    echo 'Exception when calling DefaultApi->executeWorkflow: ', $e->getMessage(), PHP_EOL;
+}
+?>
+
+ +
+
use Data::Dumper;
+use WWW::OPenAPIClient::Configuration;
+use WWW::OPenAPIClient::DefaultApi;
+
+# Create an instance of the API class
+my $api_instance = WWW::OPenAPIClient::DefaultApi->new();
+my $workflowId = workflowId_example; # String | ID of the workflow to execute
+my $executeWorkflowRequestDTO = WWW::OPenAPIClient::Object::ExecuteWorkflowRequestDTO->new(); # ExecuteWorkflowRequestDTO | 
+
+eval {
+    my $result = $api_instance->executeWorkflow(workflowId => $workflowId, executeWorkflowRequestDTO => $executeWorkflowRequestDTO);
+    print Dumper($result);
+};
+if ($@) {
+    warn "Exception when calling DefaultApi->executeWorkflow: $@\n";
+}
+
+ +
+
from __future__ import print_statement
+import time
+import openapi_client
+from openapi_client.rest import ApiException
+from pprint import pprint
+
+# Create an instance of the API class
+api_instance = openapi_client.DefaultApi()
+workflowId = workflowId_example # String | ID of the workflow to execute (default to null)
+executeWorkflowRequestDTO =  # ExecuteWorkflowRequestDTO | 
+
+try:
+    # Execute a workflow
+    api_response = api_instance.execute_workflow(workflowId, executeWorkflowRequestDTO)
+    pprint(api_response)
+except ApiException as e:
+    print("Exception when calling DefaultApi->executeWorkflow: %s\n" % e)
+
+ +
+
extern crate DefaultApi;
+
+pub fn main() {
+    let workflowId = workflowId_example; // String
+    let executeWorkflowRequestDTO = ; // ExecuteWorkflowRequestDTO
+
+    let mut context = DefaultApi::Context::default();
+    let result = client.executeWorkflow(workflowId, executeWorkflowRequestDTO, &context).wait();
+
+    println!("{:?}", result);
+}
+
+
+
+ +

Scopes

+ + +
+ +

Parameters

+ +
Path parameters
+ + + + + + + + + +
NameDescription
workflowId* + + +
+
+
+ + String + + +
+ID of the workflow to execute +
+
+
+ Required +
+
+
+
+ + +
Body parameters
+ + + + + + + + + +
NameDescription
executeWorkflowRequestDTO * +

+ +
+
+ + + +

Responses

+

+

+ + + + + + +
+
+
+ +
+ +
+
+

+

+ + + + + + +
+
+
+ +
+ +
+
+
+
+
+
+
+
+

getInstanceById

+

Get Workflow Instance by ID

+
+
+
+

+

Get a workflow execution/run (instance)

+

+
+
/v2/workflows/instances/{instanceId}
+

+

Usage and SDK Samples

+

+ + +
+
+
curl -X GET \
+ -H "Accept: application/json" \
+ "http://localhost/v2/workflows/instances/{instanceId}?includeAssessment=true"
+
+
+
+
import org.openapitools.client.*;
+import org.openapitools.client.auth.*;
+import org.openapitools.client.model.*;
+import org.openapitools.client.api.DefaultApi;
+
+import java.io.File;
+import java.util.*;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+
+        // Create an instance of the API class
+        DefaultApi apiInstance = new DefaultApi();
+        String instanceId = instanceId_example; // String | ID of the workflow instance
+        Boolean includeAssessment = true; // Boolean | Whether to include assessment
+
+        try {
+            AssessedProcessInstanceDTO result = apiInstance.getInstanceById(instanceId, includeAssessment);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#getInstanceById");
+            e.printStackTrace();
+        }
+    }
+}
+
+
+ +
+
import 'package:openapi/api.dart';
+
+final api_instance = DefaultApi();
+
+final String instanceId = new String(); // String | ID of the workflow instance
+final Boolean includeAssessment = new Boolean(); // Boolean | Whether to include assessment
+
+try {
+    final result = await api_instance.getInstanceById(instanceId, includeAssessment);
+    print(result);
+} catch (e) {
+    print('Exception when calling DefaultApi->getInstanceById: $e\n');
+}
+
+
+
+ +
+
import org.openapitools.client.api.DefaultApi;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+        DefaultApi apiInstance = new DefaultApi();
+        String instanceId = instanceId_example; // String | ID of the workflow instance
+        Boolean includeAssessment = true; // Boolean | Whether to include assessment
+
+        try {
+            AssessedProcessInstanceDTO result = apiInstance.getInstanceById(instanceId, includeAssessment);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#getInstanceById");
+            e.printStackTrace();
+        }
+    }
+}
+
+ +
+

+
+// Create an instance of the API class
+DefaultApi *apiInstance = [[DefaultApi alloc] init];
+String *instanceId = instanceId_example; // ID of the workflow instance (default to null)
+Boolean *includeAssessment = true; // Whether to include assessment (optional) (default to false)
+
+// Get Workflow Instance by ID
+[apiInstance getInstanceByIdWith:instanceId
+    includeAssessment:includeAssessment
+              completionHandler: ^(AssessedProcessInstanceDTO output, NSError* error) {
+    if (output) {
+        NSLog(@"%@", output);
+    }
+    if (error) {
+        NSLog(@"Error: %@", error);
+    }
+}];
+
+
+ +
+
var OrchestratorPlugin = require('orchestrator_plugin');
+
+// Create an instance of the API class
+var api = new OrchestratorPlugin.DefaultApi()
+var instanceId = instanceId_example; // {String} ID of the workflow instance
+var opts = {
+  'includeAssessment': true // {Boolean} Whether to include assessment
+};
+
+var callback = function(error, data, response) {
+  if (error) {
+    console.error(error);
+  } else {
+    console.log('API called successfully. Returned data: ' + data);
+  }
+};
+api.getInstanceById(instanceId, opts, callback);
+
+
+ + +
+
using System;
+using System.Diagnostics;
+using Org.OpenAPITools.Api;
+using Org.OpenAPITools.Client;
+using Org.OpenAPITools.Model;
+
+namespace Example
+{
+    public class getInstanceByIdExample
+    {
+        public void main()
+        {
+
+            // Create an instance of the API class
+            var apiInstance = new DefaultApi();
+            var instanceId = instanceId_example;  // String | ID of the workflow instance (default to null)
+            var includeAssessment = true;  // Boolean | Whether to include assessment (optional)  (default to false)
+
+            try {
+                // Get Workflow Instance by ID
+                AssessedProcessInstanceDTO result = apiInstance.getInstanceById(instanceId, includeAssessment);
+                Debug.WriteLine(result);
+            } catch (Exception e) {
+                Debug.Print("Exception when calling DefaultApi.getInstanceById: " + e.Message );
+            }
+        }
+    }
+}
+
+
+ +
+
<?php
+require_once(__DIR__ . '/vendor/autoload.php');
+
+// Create an instance of the API class
+$api_instance = new OpenAPITools\Client\Api\DefaultApi();
+$instanceId = instanceId_example; // String | ID of the workflow instance
+$includeAssessment = true; // Boolean | Whether to include assessment
+
+try {
+    $result = $api_instance->getInstanceById($instanceId, $includeAssessment);
+    print_r($result);
+} catch (Exception $e) {
+    echo 'Exception when calling DefaultApi->getInstanceById: ', $e->getMessage(), PHP_EOL;
+}
+?>
+
+ +
+
use Data::Dumper;
+use WWW::OPenAPIClient::Configuration;
+use WWW::OPenAPIClient::DefaultApi;
+
+# Create an instance of the API class
+my $api_instance = WWW::OPenAPIClient::DefaultApi->new();
+my $instanceId = instanceId_example; # String | ID of the workflow instance
+my $includeAssessment = true; # Boolean | Whether to include assessment
+
+eval {
+    my $result = $api_instance->getInstanceById(instanceId => $instanceId, includeAssessment => $includeAssessment);
+    print Dumper($result);
+};
+if ($@) {
+    warn "Exception when calling DefaultApi->getInstanceById: $@\n";
+}
+
+ +
+
from __future__ import print_statement
+import time
+import openapi_client
+from openapi_client.rest import ApiException
+from pprint import pprint
+
+# Create an instance of the API class
+api_instance = openapi_client.DefaultApi()
+instanceId = instanceId_example # String | ID of the workflow instance (default to null)
+includeAssessment = true # Boolean | Whether to include assessment (optional) (default to false)
+
+try:
+    # Get Workflow Instance by ID
+    api_response = api_instance.get_instance_by_id(instanceId, includeAssessment=includeAssessment)
+    pprint(api_response)
+except ApiException as e:
+    print("Exception when calling DefaultApi->getInstanceById: %s\n" % e)
+
+ +
+
extern crate DefaultApi;
+
+pub fn main() {
+    let instanceId = instanceId_example; // String
+    let includeAssessment = true; // Boolean
+
+    let mut context = DefaultApi::Context::default();
+    let result = client.getInstanceById(instanceId, includeAssessment, &context).wait();
+
+    println!("{:?}", result);
+}
+
+
+
+ +

Scopes

+ + +
+ +

Parameters

+ +
Path parameters
+ + + + + + + + + +
NameDescription
instanceId* + + +
+
+
+ + String + + +
+ID of the workflow instance +
+
+
+ Required +
+
+
+
+ + + + +
Query parameters
+ + + + + + + + + +
NameDescription
includeAssessment + + +
+
+
+ + Boolean + + +
+Whether to include assessment +
+
+
+
+
+ +

Responses

+

+

+ + + + + + +
+
+
+ +
+ +
+
+

+

+ + + + + + +
+
+
+ +
+ +
+
+
+
+
+
+
+
+

getInstances

+

Get instances

+
+
+
+

+

Retrieve an array of workflow executions (instances)

+

+
+
/v2/workflows/instances
+

+

Usage and SDK Samples

+

+ + +
+
+
curl -X POST \
+ -H "Accept: application/json" \
+ -H "Content-Type: application/json" \
+ "http://localhost/v2/workflows/instances" \
+ -d '{
+  "paginationInfo" : {
+    "offset" : 5.962133916683182,
+    "pageSize" : 1.4658129805029452,
+    "orderDirection" : "ASC",
+    "orderBy" : "orderBy",
+    "totalCount" : 5.637376656633329
+  },
+  "filters" : {
+    "paginationInfo" : {
+      "offset" : 5.962133916683182,
+      "pageSize" : 1.4658129805029452,
+      "orderDirection" : "ASC",
+      "orderBy" : "orderBy",
+      "totalCount" : 5.637376656633329
+    },
+    "filters" : {
+      "filters" : [ null, null ],
+      "operator" : "AND"
+    }
+  }
+}'
+
+
+
+
import org.openapitools.client.*;
+import org.openapitools.client.auth.*;
+import org.openapitools.client.model.*;
+import org.openapitools.client.api.DefaultApi;
+
+import java.io.File;
+import java.util.*;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+
+        // Create an instance of the API class
+        DefaultApi apiInstance = new DefaultApi();
+        GetInstancesRequest getInstancesRequest = ; // GetInstancesRequest | 
+
+        try {
+            ProcessInstanceListResultDTO result = apiInstance.getInstances(getInstancesRequest);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#getInstances");
+            e.printStackTrace();
+        }
+    }
+}
+
+
+ +
+
import 'package:openapi/api.dart';
+
+final api_instance = DefaultApi();
+
+final GetInstancesRequest getInstancesRequest = new GetInstancesRequest(); // GetInstancesRequest | 
+
+try {
+    final result = await api_instance.getInstances(getInstancesRequest);
+    print(result);
+} catch (e) {
+    print('Exception when calling DefaultApi->getInstances: $e\n');
+}
+
+
+
+ +
+
import org.openapitools.client.api.DefaultApi;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+        DefaultApi apiInstance = new DefaultApi();
+        GetInstancesRequest getInstancesRequest = ; // GetInstancesRequest | 
+
+        try {
+            ProcessInstanceListResultDTO result = apiInstance.getInstances(getInstancesRequest);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#getInstances");
+            e.printStackTrace();
+        }
+    }
+}
+
+ +
+

+
+// Create an instance of the API class
+DefaultApi *apiInstance = [[DefaultApi alloc] init];
+GetInstancesRequest *getInstancesRequest = ; //  (optional)
+
+// Get instances
+[apiInstance getInstancesWith:getInstancesRequest
+              completionHandler: ^(ProcessInstanceListResultDTO output, NSError* error) {
+    if (output) {
+        NSLog(@"%@", output);
+    }
+    if (error) {
+        NSLog(@"Error: %@", error);
+    }
+}];
+
+
+ +
+
var OrchestratorPlugin = require('orchestrator_plugin');
+
+// Create an instance of the API class
+var api = new OrchestratorPlugin.DefaultApi()
+var opts = {
+  'getInstancesRequest':  // {GetInstancesRequest} 
+};
+
+var callback = function(error, data, response) {
+  if (error) {
+    console.error(error);
+  } else {
+    console.log('API called successfully. Returned data: ' + data);
+  }
+};
+api.getInstances(opts, callback);
+
+
+ + +
+
using System;
+using System.Diagnostics;
+using Org.OpenAPITools.Api;
+using Org.OpenAPITools.Client;
+using Org.OpenAPITools.Model;
+
+namespace Example
+{
+    public class getInstancesExample
+    {
+        public void main()
+        {
+
+            // Create an instance of the API class
+            var apiInstance = new DefaultApi();
+            var getInstancesRequest = new GetInstancesRequest(); // GetInstancesRequest |  (optional) 
+
+            try {
+                // Get instances
+                ProcessInstanceListResultDTO result = apiInstance.getInstances(getInstancesRequest);
+                Debug.WriteLine(result);
+            } catch (Exception e) {
+                Debug.Print("Exception when calling DefaultApi.getInstances: " + e.Message );
+            }
+        }
+    }
+}
+
+
+ +
+
<?php
+require_once(__DIR__ . '/vendor/autoload.php');
+
+// Create an instance of the API class
+$api_instance = new OpenAPITools\Client\Api\DefaultApi();
+$getInstancesRequest = ; // GetInstancesRequest | 
+
+try {
+    $result = $api_instance->getInstances($getInstancesRequest);
+    print_r($result);
+} catch (Exception $e) {
+    echo 'Exception when calling DefaultApi->getInstances: ', $e->getMessage(), PHP_EOL;
+}
+?>
+
+ +
+
use Data::Dumper;
+use WWW::OPenAPIClient::Configuration;
+use WWW::OPenAPIClient::DefaultApi;
+
+# Create an instance of the API class
+my $api_instance = WWW::OPenAPIClient::DefaultApi->new();
+my $getInstancesRequest = WWW::OPenAPIClient::Object::GetInstancesRequest->new(); # GetInstancesRequest | 
+
+eval {
+    my $result = $api_instance->getInstances(getInstancesRequest => $getInstancesRequest);
+    print Dumper($result);
+};
+if ($@) {
+    warn "Exception when calling DefaultApi->getInstances: $@\n";
+}
+
+ +
+
from __future__ import print_statement
+import time
+import openapi_client
+from openapi_client.rest import ApiException
+from pprint import pprint
+
+# Create an instance of the API class
+api_instance = openapi_client.DefaultApi()
+getInstancesRequest =  # GetInstancesRequest |  (optional)
+
+try:
+    # Get instances
+    api_response = api_instance.get_instances(getInstancesRequest=getInstancesRequest)
+    pprint(api_response)
+except ApiException as e:
+    print("Exception when calling DefaultApi->getInstances: %s\n" % e)
+
+ +
+
extern crate DefaultApi;
+
+pub fn main() {
+    let getInstancesRequest = ; // GetInstancesRequest
+
+    let mut context = DefaultApi::Context::default();
+    let result = client.getInstances(getInstancesRequest, &context).wait();
+
+    println!("{:?}", result);
+}
+
+
+
+ +

Scopes

+ + +
+ +

Parameters

+ + + +
Body parameters
+ + + + + + + + + +
NameDescription
getInstancesRequest +

Parameters for retrieving instances

+ +
+
+ + + +

Responses

+

+

+ + + + + + +
+
+
+ +
+ +
+
+

+

+ + + + + + +
+
+
+ +
+ +
+
+
+
+
+
+
+
+

getWorkflowInputSchemaById

+

+
+
+
+

+

Get the workflow input schema. It defines the input fields of the workflow

+

+
+
/v2/workflows/{workflowId}/inputSchema
+

+

Usage and SDK Samples

+

+ + +
+
+
curl -X GET \
+ -H "Accept: application/json" \
+ "http://localhost/v2/workflows/{workflowId}/inputSchema?instanceId=instanceId_example"
+
+
+
+
import org.openapitools.client.*;
+import org.openapitools.client.auth.*;
+import org.openapitools.client.model.*;
+import org.openapitools.client.api.DefaultApi;
+
+import java.io.File;
+import java.util.*;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+
+        // Create an instance of the API class
+        DefaultApi apiInstance = new DefaultApi();
+        String workflowId = workflowId_example; // String | ID of the workflow to fetch
+        String instanceId = instanceId_example; // String | ID of instance
+
+        try {
+            InputSchemaResponseDTO result = apiInstance.getWorkflowInputSchemaById(workflowId, instanceId);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#getWorkflowInputSchemaById");
+            e.printStackTrace();
+        }
+    }
+}
+
+
+ +
+
import 'package:openapi/api.dart';
+
+final api_instance = DefaultApi();
+
+final String workflowId = new String(); // String | ID of the workflow to fetch
+final String instanceId = new String(); // String | ID of instance
+
+try {
+    final result = await api_instance.getWorkflowInputSchemaById(workflowId, instanceId);
+    print(result);
+} catch (e) {
+    print('Exception when calling DefaultApi->getWorkflowInputSchemaById: $e\n');
+}
+
+
+
+ +
+
import org.openapitools.client.api.DefaultApi;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+        DefaultApi apiInstance = new DefaultApi();
+        String workflowId = workflowId_example; // String | ID of the workflow to fetch
+        String instanceId = instanceId_example; // String | ID of instance
+
+        try {
+            InputSchemaResponseDTO result = apiInstance.getWorkflowInputSchemaById(workflowId, instanceId);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#getWorkflowInputSchemaById");
+            e.printStackTrace();
+        }
+    }
+}
+
+ +
+

+
+// Create an instance of the API class
+DefaultApi *apiInstance = [[DefaultApi alloc] init];
+String *workflowId = workflowId_example; // ID of the workflow to fetch (default to null)
+String *instanceId = instanceId_example; // ID of instance (optional) (default to null)
+
+[apiInstance getWorkflowInputSchemaByIdWith:workflowId
+    instanceId:instanceId
+              completionHandler: ^(InputSchemaResponseDTO output, NSError* error) {
+    if (output) {
+        NSLog(@"%@", output);
+    }
+    if (error) {
+        NSLog(@"Error: %@", error);
+    }
+}];
+
+
+ +
+
var OrchestratorPlugin = require('orchestrator_plugin');
+
+// Create an instance of the API class
+var api = new OrchestratorPlugin.DefaultApi()
+var workflowId = workflowId_example; // {String} ID of the workflow to fetch
+var opts = {
+  'instanceId': instanceId_example // {String} ID of instance
+};
+
+var callback = function(error, data, response) {
+  if (error) {
+    console.error(error);
+  } else {
+    console.log('API called successfully. Returned data: ' + data);
+  }
+};
+api.getWorkflowInputSchemaById(workflowId, opts, callback);
+
+
+ + +
+
using System;
+using System.Diagnostics;
+using Org.OpenAPITools.Api;
+using Org.OpenAPITools.Client;
+using Org.OpenAPITools.Model;
+
+namespace Example
+{
+    public class getWorkflowInputSchemaByIdExample
+    {
+        public void main()
+        {
+
+            // Create an instance of the API class
+            var apiInstance = new DefaultApi();
+            var workflowId = workflowId_example;  // String | ID of the workflow to fetch (default to null)
+            var instanceId = instanceId_example;  // String | ID of instance (optional)  (default to null)
+
+            try {
+                InputSchemaResponseDTO result = apiInstance.getWorkflowInputSchemaById(workflowId, instanceId);
+                Debug.WriteLine(result);
+            } catch (Exception e) {
+                Debug.Print("Exception when calling DefaultApi.getWorkflowInputSchemaById: " + e.Message );
+            }
+        }
+    }
+}
+
+
+ +
+
<?php
+require_once(__DIR__ . '/vendor/autoload.php');
+
+// Create an instance of the API class
+$api_instance = new OpenAPITools\Client\Api\DefaultApi();
+$workflowId = workflowId_example; // String | ID of the workflow to fetch
+$instanceId = instanceId_example; // String | ID of instance
+
+try {
+    $result = $api_instance->getWorkflowInputSchemaById($workflowId, $instanceId);
+    print_r($result);
+} catch (Exception $e) {
+    echo 'Exception when calling DefaultApi->getWorkflowInputSchemaById: ', $e->getMessage(), PHP_EOL;
+}
+?>
+
+ +
+
use Data::Dumper;
+use WWW::OPenAPIClient::Configuration;
+use WWW::OPenAPIClient::DefaultApi;
+
+# Create an instance of the API class
+my $api_instance = WWW::OPenAPIClient::DefaultApi->new();
+my $workflowId = workflowId_example; # String | ID of the workflow to fetch
+my $instanceId = instanceId_example; # String | ID of instance
+
+eval {
+    my $result = $api_instance->getWorkflowInputSchemaById(workflowId => $workflowId, instanceId => $instanceId);
+    print Dumper($result);
+};
+if ($@) {
+    warn "Exception when calling DefaultApi->getWorkflowInputSchemaById: $@\n";
+}
+
+ +
+
from __future__ import print_statement
+import time
+import openapi_client
+from openapi_client.rest import ApiException
+from pprint import pprint
+
+# Create an instance of the API class
+api_instance = openapi_client.DefaultApi()
+workflowId = workflowId_example # String | ID of the workflow to fetch (default to null)
+instanceId = instanceId_example # String | ID of instance (optional) (default to null)
+
+try:
+    api_response = api_instance.get_workflow_input_schema_by_id(workflowId, instanceId=instanceId)
+    pprint(api_response)
+except ApiException as e:
+    print("Exception when calling DefaultApi->getWorkflowInputSchemaById: %s\n" % e)
+
+ +
+
extern crate DefaultApi;
+
+pub fn main() {
+    let workflowId = workflowId_example; // String
+    let instanceId = instanceId_example; // String
+
+    let mut context = DefaultApi::Context::default();
+    let result = client.getWorkflowInputSchemaById(workflowId, instanceId, &context).wait();
+
+    println!("{:?}", result);
+}
+
+
+
+ +

Scopes

+ + +
+ +

Parameters

+ +
Path parameters
+ + + + + + + + + +
NameDescription
workflowId* + + +
+
+
+ + String + + +
+ID of the workflow to fetch +
+
+
+ Required +
+
+
+
+ + + + +
Query parameters
+ + + + + + + + + +
NameDescription
instanceId + + +
+
+
+ + String + + +
+ID of instance +
+
+
+
+
+ +

Responses

+

+

+ + + + + + +
+
+
+ +
+ +
+
+

+

+ + + + + + +
+
+
+ +
+ +
+
+
+
+
+
+
+
+

getWorkflowInstances

+

Get instances for a specific workflow

+
+
+
+

+

Retrieve an array of workflow executions (instances) for the given workflow

+

+
+
/v2/workflows/{workflowId}/instances
+

+

Usage and SDK Samples

+

+ + +
+
+
curl -X POST \
+ -H "Accept: application/json" \
+ -H "Content-Type: application/json" \
+ "http://localhost/v2/workflows/{workflowId}/instances" \
+ -d '{
+  "paginationInfo" : {
+    "offset" : 5.962133916683182,
+    "pageSize" : 1.4658129805029452,
+    "orderDirection" : "ASC",
+    "orderBy" : "orderBy",
+    "totalCount" : 5.637376656633329
+  },
+  "filters" : {
+    "filters" : [ null, null ],
+    "operator" : "AND"
+  }
+}'
+
+
+
+
import org.openapitools.client.*;
+import org.openapitools.client.auth.*;
+import org.openapitools.client.model.*;
+import org.openapitools.client.api.DefaultApi;
+
+import java.io.File;
+import java.util.*;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+
+        // Create an instance of the API class
+        DefaultApi apiInstance = new DefaultApi();
+        String workflowId = workflowId_example; // String | ID of the workflow
+        SearchRequest searchRequest = ; // SearchRequest | 
+
+        try {
+            ProcessInstanceListResultDTO result = apiInstance.getWorkflowInstances(workflowId, searchRequest);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#getWorkflowInstances");
+            e.printStackTrace();
+        }
+    }
+}
+
+
+ +
+
import 'package:openapi/api.dart';
+
+final api_instance = DefaultApi();
+
+final String workflowId = new String(); // String | ID of the workflow
+final SearchRequest searchRequest = new SearchRequest(); // SearchRequest | 
+
+try {
+    final result = await api_instance.getWorkflowInstances(workflowId, searchRequest);
+    print(result);
+} catch (e) {
+    print('Exception when calling DefaultApi->getWorkflowInstances: $e\n');
+}
+
+
+
+ +
+
import org.openapitools.client.api.DefaultApi;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+        DefaultApi apiInstance = new DefaultApi();
+        String workflowId = workflowId_example; // String | ID of the workflow
+        SearchRequest searchRequest = ; // SearchRequest | 
+
+        try {
+            ProcessInstanceListResultDTO result = apiInstance.getWorkflowInstances(workflowId, searchRequest);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#getWorkflowInstances");
+            e.printStackTrace();
+        }
+    }
+}
+
+ +
+

+
+// Create an instance of the API class
+DefaultApi *apiInstance = [[DefaultApi alloc] init];
+String *workflowId = workflowId_example; // ID of the workflow (default to null)
+SearchRequest *searchRequest = ; //  (optional)
+
+// Get instances for a specific workflow
+[apiInstance getWorkflowInstancesWith:workflowId
+    searchRequest:searchRequest
+              completionHandler: ^(ProcessInstanceListResultDTO output, NSError* error) {
+    if (output) {
+        NSLog(@"%@", output);
+    }
+    if (error) {
+        NSLog(@"Error: %@", error);
+    }
+}];
+
+
+ +
+
var OrchestratorPlugin = require('orchestrator_plugin');
+
+// Create an instance of the API class
+var api = new OrchestratorPlugin.DefaultApi()
+var workflowId = workflowId_example; // {String} ID of the workflow
+var opts = {
+  'searchRequest':  // {SearchRequest} 
+};
+
+var callback = function(error, data, response) {
+  if (error) {
+    console.error(error);
+  } else {
+    console.log('API called successfully. Returned data: ' + data);
+  }
+};
+api.getWorkflowInstances(workflowId, opts, callback);
+
+
+ + +
+
using System;
+using System.Diagnostics;
+using Org.OpenAPITools.Api;
+using Org.OpenAPITools.Client;
+using Org.OpenAPITools.Model;
+
+namespace Example
+{
+    public class getWorkflowInstancesExample
+    {
+        public void main()
+        {
+
+            // Create an instance of the API class
+            var apiInstance = new DefaultApi();
+            var workflowId = workflowId_example;  // String | ID of the workflow (default to null)
+            var searchRequest = new SearchRequest(); // SearchRequest |  (optional) 
+
+            try {
+                // Get instances for a specific workflow
+                ProcessInstanceListResultDTO result = apiInstance.getWorkflowInstances(workflowId, searchRequest);
+                Debug.WriteLine(result);
+            } catch (Exception e) {
+                Debug.Print("Exception when calling DefaultApi.getWorkflowInstances: " + e.Message );
+            }
+        }
+    }
+}
+
+
+ +
+
<?php
+require_once(__DIR__ . '/vendor/autoload.php');
+
+// Create an instance of the API class
+$api_instance = new OpenAPITools\Client\Api\DefaultApi();
+$workflowId = workflowId_example; // String | ID of the workflow
+$searchRequest = ; // SearchRequest | 
+
+try {
+    $result = $api_instance->getWorkflowInstances($workflowId, $searchRequest);
+    print_r($result);
+} catch (Exception $e) {
+    echo 'Exception when calling DefaultApi->getWorkflowInstances: ', $e->getMessage(), PHP_EOL;
+}
+?>
+
+ +
+
use Data::Dumper;
+use WWW::OPenAPIClient::Configuration;
+use WWW::OPenAPIClient::DefaultApi;
+
+# Create an instance of the API class
+my $api_instance = WWW::OPenAPIClient::DefaultApi->new();
+my $workflowId = workflowId_example; # String | ID of the workflow
+my $searchRequest = WWW::OPenAPIClient::Object::SearchRequest->new(); # SearchRequest | 
+
+eval {
+    my $result = $api_instance->getWorkflowInstances(workflowId => $workflowId, searchRequest => $searchRequest);
+    print Dumper($result);
+};
+if ($@) {
+    warn "Exception when calling DefaultApi->getWorkflowInstances: $@\n";
+}
+
+ +
+
from __future__ import print_statement
+import time
+import openapi_client
+from openapi_client.rest import ApiException
+from pprint import pprint
+
+# Create an instance of the API class
+api_instance = openapi_client.DefaultApi()
+workflowId = workflowId_example # String | ID of the workflow (default to null)
+searchRequest =  # SearchRequest |  (optional)
+
+try:
+    # Get instances for a specific workflow
+    api_response = api_instance.get_workflow_instances(workflowId, searchRequest=searchRequest)
+    pprint(api_response)
+except ApiException as e:
+    print("Exception when calling DefaultApi->getWorkflowInstances: %s\n" % e)
+
+ +
+
extern crate DefaultApi;
+
+pub fn main() {
+    let workflowId = workflowId_example; // String
+    let searchRequest = ; // SearchRequest
+
+    let mut context = DefaultApi::Context::default();
+    let result = client.getWorkflowInstances(workflowId, searchRequest, &context).wait();
+
+    println!("{:?}", result);
+}
+
+
+
+ +

Scopes

+ + +
+ +

Parameters

+ +
Path parameters
+ + + + + + + + + +
NameDescription
workflowId* + + +
+
+
+ + String + + +
+ID of the workflow +
+
+
+ Required +
+
+
+
+ + +
Body parameters
+ + + + + + + + + +
NameDescription
searchRequest +

Parameters for retrieving workflow instances

+ +
+
+ + + +

Responses

+

+

+ + + + + + +
+
+
+ +
+ +
+
+

+

+ + + + + + +
+
+
+ +
+ +
+
+
+
+
+
+
+
+

getWorkflowOverviewById

+

+
+
+
+

+

Returns the key fields of the workflow including data on the last run instance

+

+
+
/v2/workflows/{workflowId}/overview
+

+

Usage and SDK Samples

+

+ + +
+
+
curl -X GET \
+ -H "Accept: application/json" \
+ "http://localhost/v2/workflows/{workflowId}/overview"
+
+
+
+
import org.openapitools.client.*;
+import org.openapitools.client.auth.*;
+import org.openapitools.client.model.*;
+import org.openapitools.client.api.DefaultApi;
+
+import java.io.File;
+import java.util.*;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+
+        // Create an instance of the API class
+        DefaultApi apiInstance = new DefaultApi();
+        String workflowId = workflowId_example; // String | Unique identifier of the workflow
+
+        try {
+            WorkflowOverviewDTO result = apiInstance.getWorkflowOverviewById(workflowId);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#getWorkflowOverviewById");
+            e.printStackTrace();
+        }
+    }
+}
+
+
+ +
+
import 'package:openapi/api.dart';
+
+final api_instance = DefaultApi();
+
+final String workflowId = new String(); // String | Unique identifier of the workflow
+
+try {
+    final result = await api_instance.getWorkflowOverviewById(workflowId);
+    print(result);
+} catch (e) {
+    print('Exception when calling DefaultApi->getWorkflowOverviewById: $e\n');
+}
+
+
+
+ +
+
import org.openapitools.client.api.DefaultApi;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+        DefaultApi apiInstance = new DefaultApi();
+        String workflowId = workflowId_example; // String | Unique identifier of the workflow
+
+        try {
+            WorkflowOverviewDTO result = apiInstance.getWorkflowOverviewById(workflowId);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#getWorkflowOverviewById");
+            e.printStackTrace();
+        }
+    }
+}
+
+ +
+

+
+// Create an instance of the API class
+DefaultApi *apiInstance = [[DefaultApi alloc] init];
+String *workflowId = workflowId_example; // Unique identifier of the workflow (default to null)
+
+[apiInstance getWorkflowOverviewByIdWith:workflowId
+              completionHandler: ^(WorkflowOverviewDTO output, NSError* error) {
+    if (output) {
+        NSLog(@"%@", output);
+    }
+    if (error) {
+        NSLog(@"Error: %@", error);
+    }
+}];
+
+
+ +
+
var OrchestratorPlugin = require('orchestrator_plugin');
+
+// Create an instance of the API class
+var api = new OrchestratorPlugin.DefaultApi()
+var workflowId = workflowId_example; // {String} Unique identifier of the workflow
+
+var callback = function(error, data, response) {
+  if (error) {
+    console.error(error);
+  } else {
+    console.log('API called successfully. Returned data: ' + data);
+  }
+};
+api.getWorkflowOverviewById(workflowId, callback);
+
+
+ + +
+
using System;
+using System.Diagnostics;
+using Org.OpenAPITools.Api;
+using Org.OpenAPITools.Client;
+using Org.OpenAPITools.Model;
+
+namespace Example
+{
+    public class getWorkflowOverviewByIdExample
+    {
+        public void main()
+        {
+
+            // Create an instance of the API class
+            var apiInstance = new DefaultApi();
+            var workflowId = workflowId_example;  // String | Unique identifier of the workflow (default to null)
+
+            try {
+                WorkflowOverviewDTO result = apiInstance.getWorkflowOverviewById(workflowId);
+                Debug.WriteLine(result);
+            } catch (Exception e) {
+                Debug.Print("Exception when calling DefaultApi.getWorkflowOverviewById: " + e.Message );
+            }
+        }
+    }
+}
+
+
+ +
+
<?php
+require_once(__DIR__ . '/vendor/autoload.php');
+
+// Create an instance of the API class
+$api_instance = new OpenAPITools\Client\Api\DefaultApi();
+$workflowId = workflowId_example; // String | Unique identifier of the workflow
+
+try {
+    $result = $api_instance->getWorkflowOverviewById($workflowId);
+    print_r($result);
+} catch (Exception $e) {
+    echo 'Exception when calling DefaultApi->getWorkflowOverviewById: ', $e->getMessage(), PHP_EOL;
+}
+?>
+
+ +
+
use Data::Dumper;
+use WWW::OPenAPIClient::Configuration;
+use WWW::OPenAPIClient::DefaultApi;
+
+# Create an instance of the API class
+my $api_instance = WWW::OPenAPIClient::DefaultApi->new();
+my $workflowId = workflowId_example; # String | Unique identifier of the workflow
+
+eval {
+    my $result = $api_instance->getWorkflowOverviewById(workflowId => $workflowId);
+    print Dumper($result);
+};
+if ($@) {
+    warn "Exception when calling DefaultApi->getWorkflowOverviewById: $@\n";
+}
+
+ +
+
from __future__ import print_statement
+import time
+import openapi_client
+from openapi_client.rest import ApiException
+from pprint import pprint
+
+# Create an instance of the API class
+api_instance = openapi_client.DefaultApi()
+workflowId = workflowId_example # String | Unique identifier of the workflow (default to null)
+
+try:
+    api_response = api_instance.get_workflow_overview_by_id(workflowId)
+    pprint(api_response)
+except ApiException as e:
+    print("Exception when calling DefaultApi->getWorkflowOverviewById: %s\n" % e)
+
+ +
+
extern crate DefaultApi;
+
+pub fn main() {
+    let workflowId = workflowId_example; // String
+
+    let mut context = DefaultApi::Context::default();
+    let result = client.getWorkflowOverviewById(workflowId, &context).wait();
+
+    println!("{:?}", result);
+}
+
+
+
+ +

Scopes

+ + +
+ +

Parameters

+ +
Path parameters
+ + + + + + + + + +
NameDescription
workflowId* + + +
+
+
+ + String + + +
+Unique identifier of the workflow +
+
+
+ Required +
+
+
+
+ + + + + +

Responses

+

+

+ + + + + + +
+
+
+ +
+ +
+
+

+

+ + + + + + +
+
+
+ +
+ +
+
+
+
+
+
+
+
+

getWorkflowSourceById

+

+
+
+
+

+

Get the workflow's definition

+

+
+
/v2/workflows/{workflowId}/source
+

+

Usage and SDK Samples

+

+ + +
+
+
curl -X GET \
+ -H "Accept: text/plain,application/json" \
+ "http://localhost/v2/workflows/{workflowId}/source"
+
+
+
+
import org.openapitools.client.*;
+import org.openapitools.client.auth.*;
+import org.openapitools.client.model.*;
+import org.openapitools.client.api.DefaultApi;
+
+import java.io.File;
+import java.util.*;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+
+        // Create an instance of the API class
+        DefaultApi apiInstance = new DefaultApi();
+        String workflowId = workflowId_example; // String | ID of the workflow to fetch
+
+        try {
+            'String' result = apiInstance.getWorkflowSourceById(workflowId);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#getWorkflowSourceById");
+            e.printStackTrace();
+        }
+    }
+}
+
+
+ +
+
import 'package:openapi/api.dart';
+
+final api_instance = DefaultApi();
+
+final String workflowId = new String(); // String | ID of the workflow to fetch
+
+try {
+    final result = await api_instance.getWorkflowSourceById(workflowId);
+    print(result);
+} catch (e) {
+    print('Exception when calling DefaultApi->getWorkflowSourceById: $e\n');
+}
+
+
+
+ +
+
import org.openapitools.client.api.DefaultApi;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+        DefaultApi apiInstance = new DefaultApi();
+        String workflowId = workflowId_example; // String | ID of the workflow to fetch
+
+        try {
+            'String' result = apiInstance.getWorkflowSourceById(workflowId);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#getWorkflowSourceById");
+            e.printStackTrace();
+        }
+    }
+}
+
+ +
+

+
+// Create an instance of the API class
+DefaultApi *apiInstance = [[DefaultApi alloc] init];
+String *workflowId = workflowId_example; // ID of the workflow to fetch (default to null)
+
+[apiInstance getWorkflowSourceByIdWith:workflowId
+              completionHandler: ^('String' output, NSError* error) {
+    if (output) {
+        NSLog(@"%@", output);
+    }
+    if (error) {
+        NSLog(@"Error: %@", error);
+    }
+}];
+
+
+ +
+
var OrchestratorPlugin = require('orchestrator_plugin');
+
+// Create an instance of the API class
+var api = new OrchestratorPlugin.DefaultApi()
+var workflowId = workflowId_example; // {String} ID of the workflow to fetch
+
+var callback = function(error, data, response) {
+  if (error) {
+    console.error(error);
+  } else {
+    console.log('API called successfully. Returned data: ' + data);
+  }
+};
+api.getWorkflowSourceById(workflowId, callback);
+
+
+ + +
+
using System;
+using System.Diagnostics;
+using Org.OpenAPITools.Api;
+using Org.OpenAPITools.Client;
+using Org.OpenAPITools.Model;
+
+namespace Example
+{
+    public class getWorkflowSourceByIdExample
+    {
+        public void main()
+        {
+
+            // Create an instance of the API class
+            var apiInstance = new DefaultApi();
+            var workflowId = workflowId_example;  // String | ID of the workflow to fetch (default to null)
+
+            try {
+                'String' result = apiInstance.getWorkflowSourceById(workflowId);
+                Debug.WriteLine(result);
+            } catch (Exception e) {
+                Debug.Print("Exception when calling DefaultApi.getWorkflowSourceById: " + e.Message );
+            }
+        }
+    }
+}
+
+
+ +
+
<?php
+require_once(__DIR__ . '/vendor/autoload.php');
+
+// Create an instance of the API class
+$api_instance = new OpenAPITools\Client\Api\DefaultApi();
+$workflowId = workflowId_example; // String | ID of the workflow to fetch
+
+try {
+    $result = $api_instance->getWorkflowSourceById($workflowId);
+    print_r($result);
+} catch (Exception $e) {
+    echo 'Exception when calling DefaultApi->getWorkflowSourceById: ', $e->getMessage(), PHP_EOL;
+}
+?>
+
+ +
+
use Data::Dumper;
+use WWW::OPenAPIClient::Configuration;
+use WWW::OPenAPIClient::DefaultApi;
+
+# Create an instance of the API class
+my $api_instance = WWW::OPenAPIClient::DefaultApi->new();
+my $workflowId = workflowId_example; # String | ID of the workflow to fetch
+
+eval {
+    my $result = $api_instance->getWorkflowSourceById(workflowId => $workflowId);
+    print Dumper($result);
+};
+if ($@) {
+    warn "Exception when calling DefaultApi->getWorkflowSourceById: $@\n";
+}
+
+ +
+
from __future__ import print_statement
+import time
+import openapi_client
+from openapi_client.rest import ApiException
+from pprint import pprint
+
+# Create an instance of the API class
+api_instance = openapi_client.DefaultApi()
+workflowId = workflowId_example # String | ID of the workflow to fetch (default to null)
+
+try:
+    api_response = api_instance.get_workflow_source_by_id(workflowId)
+    pprint(api_response)
+except ApiException as e:
+    print("Exception when calling DefaultApi->getWorkflowSourceById: %s\n" % e)
+
+ +
+
extern crate DefaultApi;
+
+pub fn main() {
+    let workflowId = workflowId_example; // String
+
+    let mut context = DefaultApi::Context::default();
+    let result = client.getWorkflowSourceById(workflowId, &context).wait();
+
+    println!("{:?}", result);
+}
+
+
+
+ +

Scopes

+ + +
+ +

Parameters

+ +
Path parameters
+ + + + + + + + + +
NameDescription
workflowId* + + +
+
+
+ + String + + +
+ID of the workflow to fetch +
+
+
+ Required +
+
+
+
+ + + + + +

Responses

+

+

+ + + + + + +
+
+
+ +
+ +
+
+

+

+ + + + + + +
+
+
+ +
+ +
+
+
+
+
+
+
+
+

getWorkflowStatuses

+

Get workflow status list

+
+
+
+

+

Retrieve array with the status of all instances

+

+
+
/v2/workflows/instances/statuses
+

+

Usage and SDK Samples

+

+ + +
+
+
curl -X GET \
+ -H "Accept: application/json" \
+ "http://localhost/v2/workflows/instances/statuses"
+
+
+
+
import org.openapitools.client.*;
+import org.openapitools.client.auth.*;
+import org.openapitools.client.model.*;
+import org.openapitools.client.api.DefaultApi;
+
+import java.io.File;
+import java.util.*;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+
+        // Create an instance of the API class
+        DefaultApi apiInstance = new DefaultApi();
+
+        try {
+            array[WorkflowRunStatusDTO] result = apiInstance.getWorkflowStatuses();
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#getWorkflowStatuses");
+            e.printStackTrace();
+        }
+    }
+}
+
+
+ +
+
import 'package:openapi/api.dart';
+
+final api_instance = DefaultApi();
+
+
+try {
+    final result = await api_instance.getWorkflowStatuses();
+    print(result);
+} catch (e) {
+    print('Exception when calling DefaultApi->getWorkflowStatuses: $e\n');
+}
+
+
+
+ +
+
import org.openapitools.client.api.DefaultApi;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+        DefaultApi apiInstance = new DefaultApi();
+
+        try {
+            array[WorkflowRunStatusDTO] result = apiInstance.getWorkflowStatuses();
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#getWorkflowStatuses");
+            e.printStackTrace();
+        }
+    }
+}
+
+ +
+

+
+// Create an instance of the API class
+DefaultApi *apiInstance = [[DefaultApi alloc] init];
+
+// Get workflow status list
+[apiInstance getWorkflowStatusesWithCompletionHandler: 
+              ^(array[WorkflowRunStatusDTO] output, NSError* error) {
+    if (output) {
+        NSLog(@"%@", output);
+    }
+    if (error) {
+        NSLog(@"Error: %@", error);
+    }
+}];
+
+
+ +
+
var OrchestratorPlugin = require('orchestrator_plugin');
+
+// Create an instance of the API class
+var api = new OrchestratorPlugin.DefaultApi()
+var callback = function(error, data, response) {
+  if (error) {
+    console.error(error);
+  } else {
+    console.log('API called successfully. Returned data: ' + data);
+  }
+};
+api.getWorkflowStatuses(callback);
+
+
+ + +
+
using System;
+using System.Diagnostics;
+using Org.OpenAPITools.Api;
+using Org.OpenAPITools.Client;
+using Org.OpenAPITools.Model;
+
+namespace Example
+{
+    public class getWorkflowStatusesExample
+    {
+        public void main()
+        {
+
+            // Create an instance of the API class
+            var apiInstance = new DefaultApi();
+
+            try {
+                // Get workflow status list
+                array[WorkflowRunStatusDTO] result = apiInstance.getWorkflowStatuses();
+                Debug.WriteLine(result);
+            } catch (Exception e) {
+                Debug.Print("Exception when calling DefaultApi.getWorkflowStatuses: " + e.Message );
+            }
+        }
+    }
+}
+
+
+ +
+
<?php
+require_once(__DIR__ . '/vendor/autoload.php');
+
+// Create an instance of the API class
+$api_instance = new OpenAPITools\Client\Api\DefaultApi();
+
+try {
+    $result = $api_instance->getWorkflowStatuses();
+    print_r($result);
+} catch (Exception $e) {
+    echo 'Exception when calling DefaultApi->getWorkflowStatuses: ', $e->getMessage(), PHP_EOL;
+}
+?>
+
+ +
+
use Data::Dumper;
+use WWW::OPenAPIClient::Configuration;
+use WWW::OPenAPIClient::DefaultApi;
+
+# Create an instance of the API class
+my $api_instance = WWW::OPenAPIClient::DefaultApi->new();
+
+eval {
+    my $result = $api_instance->getWorkflowStatuses();
+    print Dumper($result);
+};
+if ($@) {
+    warn "Exception when calling DefaultApi->getWorkflowStatuses: $@\n";
+}
+
+ +
+
from __future__ import print_statement
+import time
+import openapi_client
+from openapi_client.rest import ApiException
+from pprint import pprint
+
+# Create an instance of the API class
+api_instance = openapi_client.DefaultApi()
+
+try:
+    # Get workflow status list
+    api_response = api_instance.get_workflow_statuses()
+    pprint(api_response)
+except ApiException as e:
+    print("Exception when calling DefaultApi->getWorkflowStatuses: %s\n" % e)
+
+ +
+
extern crate DefaultApi;
+
+pub fn main() {
+
+    let mut context = DefaultApi::Context::default();
+    let result = client.getWorkflowStatuses(&context).wait();
+
+    println!("{:?}", result);
+}
+
+
+
+ +

Scopes

+ + +
+ +

Parameters

+ + + + + + +

Responses

+

+

+ + + + + + +
+
+
+ +
+ +
+
+

+

+ + + + + + +
+
+
+ +
+ +
+
+
+
+
+
+
+
+

getWorkflowsOverview

+

+
+
+
+

+

Returns the key fields of the workflow including data on the last run instance

+

+
+
/v2/workflows/overview
+

+

Usage and SDK Samples

+

+ + +
+
+
curl -X POST \
+ -H "Accept: application/json" \
+ -H "Content-Type: application/json" \
+ "http://localhost/v2/workflows/overview" \
+ -d '{
+  "paginationInfo" : {
+    "offset" : 5.962133916683182,
+    "pageSize" : 1.4658129805029452,
+    "orderDirection" : "ASC",
+    "orderBy" : "orderBy",
+    "totalCount" : 5.637376656633329
+  },
+  "filters" : {
+    "filters" : [ null, null ],
+    "operator" : "AND"
+  }
+}'
+
+
+
+
import org.openapitools.client.*;
+import org.openapitools.client.auth.*;
+import org.openapitools.client.model.*;
+import org.openapitools.client.api.DefaultApi;
+
+import java.io.File;
+import java.util.*;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+
+        // Create an instance of the API class
+        DefaultApi apiInstance = new DefaultApi();
+        SearchRequest searchRequest = ; // SearchRequest | 
+
+        try {
+            WorkflowOverviewListResultDTO result = apiInstance.getWorkflowsOverview(searchRequest);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#getWorkflowsOverview");
+            e.printStackTrace();
+        }
+    }
+}
+
+
+ +
+
import 'package:openapi/api.dart';
+
+final api_instance = DefaultApi();
+
+final SearchRequest searchRequest = new SearchRequest(); // SearchRequest | 
+
+try {
+    final result = await api_instance.getWorkflowsOverview(searchRequest);
+    print(result);
+} catch (e) {
+    print('Exception when calling DefaultApi->getWorkflowsOverview: $e\n');
+}
+
+
+
+ +
+
import org.openapitools.client.api.DefaultApi;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+        DefaultApi apiInstance = new DefaultApi();
+        SearchRequest searchRequest = ; // SearchRequest | 
+
+        try {
+            WorkflowOverviewListResultDTO result = apiInstance.getWorkflowsOverview(searchRequest);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#getWorkflowsOverview");
+            e.printStackTrace();
+        }
+    }
+}
+
+ +
+

+
+// Create an instance of the API class
+DefaultApi *apiInstance = [[DefaultApi alloc] init];
+SearchRequest *searchRequest = ; //  (optional)
+
+[apiInstance getWorkflowsOverviewWith:searchRequest
+              completionHandler: ^(WorkflowOverviewListResultDTO output, NSError* error) {
+    if (output) {
+        NSLog(@"%@", output);
+    }
+    if (error) {
+        NSLog(@"Error: %@", error);
+    }
+}];
+
+
+ +
+
var OrchestratorPlugin = require('orchestrator_plugin');
+
+// Create an instance of the API class
+var api = new OrchestratorPlugin.DefaultApi()
+var opts = {
+  'searchRequest':  // {SearchRequest} 
+};
+
+var callback = function(error, data, response) {
+  if (error) {
+    console.error(error);
+  } else {
+    console.log('API called successfully. Returned data: ' + data);
+  }
+};
+api.getWorkflowsOverview(opts, callback);
+
+
+ + +
+
using System;
+using System.Diagnostics;
+using Org.OpenAPITools.Api;
+using Org.OpenAPITools.Client;
+using Org.OpenAPITools.Model;
+
+namespace Example
+{
+    public class getWorkflowsOverviewExample
+    {
+        public void main()
+        {
+
+            // Create an instance of the API class
+            var apiInstance = new DefaultApi();
+            var searchRequest = new SearchRequest(); // SearchRequest |  (optional) 
+
+            try {
+                WorkflowOverviewListResultDTO result = apiInstance.getWorkflowsOverview(searchRequest);
+                Debug.WriteLine(result);
+            } catch (Exception e) {
+                Debug.Print("Exception when calling DefaultApi.getWorkflowsOverview: " + e.Message );
+            }
+        }
+    }
+}
+
+
+ +
+
<?php
+require_once(__DIR__ . '/vendor/autoload.php');
+
+// Create an instance of the API class
+$api_instance = new OpenAPITools\Client\Api\DefaultApi();
+$searchRequest = ; // SearchRequest | 
+
+try {
+    $result = $api_instance->getWorkflowsOverview($searchRequest);
+    print_r($result);
+} catch (Exception $e) {
+    echo 'Exception when calling DefaultApi->getWorkflowsOverview: ', $e->getMessage(), PHP_EOL;
+}
+?>
+
+ +
+
use Data::Dumper;
+use WWW::OPenAPIClient::Configuration;
+use WWW::OPenAPIClient::DefaultApi;
+
+# Create an instance of the API class
+my $api_instance = WWW::OPenAPIClient::DefaultApi->new();
+my $searchRequest = WWW::OPenAPIClient::Object::SearchRequest->new(); # SearchRequest | 
+
+eval {
+    my $result = $api_instance->getWorkflowsOverview(searchRequest => $searchRequest);
+    print Dumper($result);
+};
+if ($@) {
+    warn "Exception when calling DefaultApi->getWorkflowsOverview: $@\n";
+}
+
+ +
+
from __future__ import print_statement
+import time
+import openapi_client
+from openapi_client.rest import ApiException
+from pprint import pprint
+
+# Create an instance of the API class
+api_instance = openapi_client.DefaultApi()
+searchRequest =  # SearchRequest |  (optional)
+
+try:
+    api_response = api_instance.get_workflows_overview(searchRequest=searchRequest)
+    pprint(api_response)
+except ApiException as e:
+    print("Exception when calling DefaultApi->getWorkflowsOverview: %s\n" % e)
+
+ +
+
extern crate DefaultApi;
+
+pub fn main() {
+    let searchRequest = ; // SearchRequest
+
+    let mut context = DefaultApi::Context::default();
+    let result = client.getWorkflowsOverview(searchRequest, &context).wait();
+
+    println!("{:?}", result);
+}
+
+
+
+ +

Scopes

+ + +
+ +

Parameters

+ + + +
Body parameters
+ + + + + + + + + +
NameDescription
searchRequest +

Pagination and filters

+ +
+
+ + + +

Responses

+

+

+ + + + + + +
+
+
+ +
+ +
+
+

+

+ + + + + + +
+
+
+ +
+ +
+
+
+
+
+
+
+
+

retriggerInstance

+

Retrigger an instance

+
+
+
+

+

Retrigger an instance

+

+
+
/v2/workflows/{workflowId}/{instanceId}/retrigger
+

+

Usage and SDK Samples

+

+ + +
+
+
curl -X POST \
+ -H "Accept: application/json" \
+ "http://localhost/v2/workflows/{workflowId}/{instanceId}/retrigger"
+
+
+
+
import org.openapitools.client.*;
+import org.openapitools.client.auth.*;
+import org.openapitools.client.model.*;
+import org.openapitools.client.api.DefaultApi;
+
+import java.io.File;
+import java.util.*;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+
+        // Create an instance of the API class
+        DefaultApi apiInstance = new DefaultApi();
+        String workflowId = workflowId_example; // String | ID of the workflow
+        String instanceId = instanceId_example; // String | ID of the instance to retrigger
+
+        try {
+            Object result = apiInstance.retriggerInstance(workflowId, instanceId);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#retriggerInstance");
+            e.printStackTrace();
+        }
+    }
+}
+
+
+ +
+
import 'package:openapi/api.dart';
+
+final api_instance = DefaultApi();
+
+final String workflowId = new String(); // String | ID of the workflow
+final String instanceId = new String(); // String | ID of the instance to retrigger
+
+try {
+    final result = await api_instance.retriggerInstance(workflowId, instanceId);
+    print(result);
+} catch (e) {
+    print('Exception when calling DefaultApi->retriggerInstance: $e\n');
+}
+
+
+
+ +
+
import org.openapitools.client.api.DefaultApi;
+
+public class DefaultApiExample {
+    public static void main(String[] args) {
+        DefaultApi apiInstance = new DefaultApi();
+        String workflowId = workflowId_example; // String | ID of the workflow
+        String instanceId = instanceId_example; // String | ID of the instance to retrigger
+
+        try {
+            Object result = apiInstance.retriggerInstance(workflowId, instanceId);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#retriggerInstance");
+            e.printStackTrace();
+        }
+    }
+}
+
+ +
+

+
+// Create an instance of the API class
+DefaultApi *apiInstance = [[DefaultApi alloc] init];
+String *workflowId = workflowId_example; // ID of the workflow (default to null)
+String *instanceId = instanceId_example; // ID of the instance to retrigger (default to null)
+
+// Retrigger an instance
+[apiInstance retriggerInstanceWith:workflowId
+    instanceId:instanceId
+              completionHandler: ^(Object output, NSError* error) {
+    if (output) {
+        NSLog(@"%@", output);
+    }
+    if (error) {
+        NSLog(@"Error: %@", error);
+    }
+}];
+
+
+ +
+
var OrchestratorPlugin = require('orchestrator_plugin');
+
+// Create an instance of the API class
+var api = new OrchestratorPlugin.DefaultApi()
+var workflowId = workflowId_example; // {String} ID of the workflow
+var instanceId = instanceId_example; // {String} ID of the instance to retrigger
+
+var callback = function(error, data, response) {
+  if (error) {
+    console.error(error);
+  } else {
+    console.log('API called successfully. Returned data: ' + data);
+  }
+};
+api.retriggerInstance(workflowId, instanceId, callback);
+
+
+ + +
+
using System;
+using System.Diagnostics;
+using Org.OpenAPITools.Api;
+using Org.OpenAPITools.Client;
+using Org.OpenAPITools.Model;
+
+namespace Example
+{
+    public class retriggerInstanceExample
+    {
+        public void main()
+        {
+
+            // Create an instance of the API class
+            var apiInstance = new DefaultApi();
+            var workflowId = workflowId_example;  // String | ID of the workflow (default to null)
+            var instanceId = instanceId_example;  // String | ID of the instance to retrigger (default to null)
+
+            try {
+                // Retrigger an instance
+                Object result = apiInstance.retriggerInstance(workflowId, instanceId);
+                Debug.WriteLine(result);
+            } catch (Exception e) {
+                Debug.Print("Exception when calling DefaultApi.retriggerInstance: " + e.Message );
+            }
+        }
+    }
+}
+
+
+ +
+
<?php
+require_once(__DIR__ . '/vendor/autoload.php');
+
+// Create an instance of the API class
+$api_instance = new OpenAPITools\Client\Api\DefaultApi();
+$workflowId = workflowId_example; // String | ID of the workflow
+$instanceId = instanceId_example; // String | ID of the instance to retrigger
+
+try {
+    $result = $api_instance->retriggerInstance($workflowId, $instanceId);
+    print_r($result);
+} catch (Exception $e) {
+    echo 'Exception when calling DefaultApi->retriggerInstance: ', $e->getMessage(), PHP_EOL;
+}
+?>
+
+ +
+
use Data::Dumper;
+use WWW::OPenAPIClient::Configuration;
+use WWW::OPenAPIClient::DefaultApi;
+
+# Create an instance of the API class
+my $api_instance = WWW::OPenAPIClient::DefaultApi->new();
+my $workflowId = workflowId_example; # String | ID of the workflow
+my $instanceId = instanceId_example; # String | ID of the instance to retrigger
+
+eval {
+    my $result = $api_instance->retriggerInstance(workflowId => $workflowId, instanceId => $instanceId);
+    print Dumper($result);
+};
+if ($@) {
+    warn "Exception when calling DefaultApi->retriggerInstance: $@\n";
+}
+
+ +
+
from __future__ import print_statement
+import time
+import openapi_client
+from openapi_client.rest import ApiException
+from pprint import pprint
+
+# Create an instance of the API class
+api_instance = openapi_client.DefaultApi()
+workflowId = workflowId_example # String | ID of the workflow (default to null)
+instanceId = instanceId_example # String | ID of the instance to retrigger (default to null)
+
+try:
+    # Retrigger an instance
+    api_response = api_instance.retrigger_instance(workflowId, instanceId)
+    pprint(api_response)
+except ApiException as e:
+    print("Exception when calling DefaultApi->retriggerInstance: %s\n" % e)
+
+ +
+
extern crate DefaultApi;
+
+pub fn main() {
+    let workflowId = workflowId_example; // String
+    let instanceId = instanceId_example; // String
+
+    let mut context = DefaultApi::Context::default();
+    let result = client.retriggerInstance(workflowId, instanceId, &context).wait();
+
+    println!("{:?}", result);
+}
+
+
+
+ +

Scopes

+ + +
+ +

Parameters

+ +
Path parameters
+ + + + + + + + + + + + + +
NameDescription
workflowId* + + +
+
+
+ + String + + +
+ID of the workflow +
+
+
+ Required +
+
+
+
instanceId* + + +
+
+
+ + String + + +
+ID of the instance to retrigger +
+
+
+ Required +
+
+
+
+ + + + + +

Responses

+

+

+ + + + + + +
+
+
+ +
+ +
+
+

+

+ + + + + + +
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/.openapi-generator-ignore b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/.openapi-generator-ignore new file mode 100644 index 00000000..7484ee59 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/.openapi-generator/FILES b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/.openapi-generator/FILES new file mode 100644 index 00000000..e774366e --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/.openapi-generator/FILES @@ -0,0 +1,34 @@ +.openapi-generator-ignore +Apis/DefaultApi.md +Models/AssessedProcessInstanceDTO.md +Models/ErrorResponse.md +Models/ExecuteWorkflowRequestDTO.md +Models/ExecuteWorkflowResponseDTO.md +Models/FieldFilter.md +Models/FieldFilter_value.md +Models/Filter.md +Models/GetInstancesRequest.md +Models/GetOverviewsRequestParams.md +Models/InputSchemaResponseDTO.md +Models/LogicalFilter.md +Models/NodeInstanceDTO.md +Models/PaginationInfoDTO.md +Models/ProcessInstanceDTO.md +Models/ProcessInstanceErrorDTO.md +Models/ProcessInstanceListResultDTO.md +Models/ProcessInstanceStatusDTO.md +Models/SearchRequest.md +Models/WorkflowCategoryDTO.md +Models/WorkflowDTO.md +Models/WorkflowDataDTO.md +Models/WorkflowFormatDTO.md +Models/WorkflowListResultDTO.md +Models/WorkflowOverviewDTO.md +Models/WorkflowOverviewListResultDTO.md +Models/WorkflowProgressDTO.md +Models/WorkflowResultDTO.md +Models/WorkflowResultDTO_nextWorkflows_inner.md +Models/WorkflowResultDTO_outputs_inner.md +Models/WorkflowResultDTO_outputs_inner_value.md +Models/WorkflowRunStatusDTO.md +README.md diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/.openapi-generator/VERSION b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/.openapi-generator/VERSION new file mode 100644 index 00000000..8b23b8d4 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.3.0 \ No newline at end of file diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Apis/DefaultApi.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Apis/DefaultApi.md new file mode 100644 index 00000000..0d275149 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Apis/DefaultApi.md @@ -0,0 +1,318 @@ +# DefaultApi + +All URIs are relative to *http://localhost* + +| Method | HTTP request | Description | +|------------- | ------------- | -------------| +| [**abortWorkflow**](DefaultApi.md#abortWorkflow) | **DELETE** /v2/workflows/instances/{instanceId}/abort | Abort a workflow instance | +| [**executeWorkflow**](DefaultApi.md#executeWorkflow) | **POST** /v2/workflows/{workflowId}/execute | Execute a workflow | +| [**getInstanceById**](DefaultApi.md#getInstanceById) | **GET** /v2/workflows/instances/{instanceId} | Get Workflow Instance by ID | +| [**getInstances**](DefaultApi.md#getInstances) | **POST** /v2/workflows/instances | Get instances | +| [**getWorkflowInputSchemaById**](DefaultApi.md#getWorkflowInputSchemaById) | **GET** /v2/workflows/{workflowId}/inputSchema | | +| [**getWorkflowInstances**](DefaultApi.md#getWorkflowInstances) | **POST** /v2/workflows/{workflowId}/instances | Get instances for a specific workflow | +| [**getWorkflowOverviewById**](DefaultApi.md#getWorkflowOverviewById) | **GET** /v2/workflows/{workflowId}/overview | | +| [**getWorkflowSourceById**](DefaultApi.md#getWorkflowSourceById) | **GET** /v2/workflows/{workflowId}/source | | +| [**getWorkflowStatuses**](DefaultApi.md#getWorkflowStatuses) | **GET** /v2/workflows/instances/statuses | Get workflow status list | +| [**getWorkflowsOverview**](DefaultApi.md#getWorkflowsOverview) | **POST** /v2/workflows/overview | | +| [**retriggerInstance**](DefaultApi.md#retriggerInstance) | **POST** /v2/workflows/{workflowId}/{instanceId}/retrigger | Retrigger an instance | + + + +# **abortWorkflow** +> String abortWorkflow(instanceId) + +Abort a workflow instance + + Aborts a workflow instance identified by the provided instanceId. + +### Parameters + +|Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **instanceId** | **String**| The identifier of the workflow instance to abort. | [default to null] | + +### Return type + +**String** + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: text/plain, application/json + + +# **executeWorkflow** +> ExecuteWorkflowResponseDTO executeWorkflow(workflowId, ExecuteWorkflowRequestDTO) + +Execute a workflow + + Execute a workflow + +### Parameters + +|Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **workflowId** | **String**| ID of the workflow to execute | [default to null] | +| **ExecuteWorkflowRequestDTO** | [**ExecuteWorkflowRequestDTO**](../Models/ExecuteWorkflowRequestDTO.md)| | | + +### Return type + +[**ExecuteWorkflowResponseDTO**](../Models/ExecuteWorkflowResponseDTO.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: application/json +- **Accept**: application/json + + +# **getInstanceById** +> AssessedProcessInstanceDTO getInstanceById(instanceId, includeAssessment) + +Get Workflow Instance by ID + + Get a workflow execution/run (instance) + +### Parameters + +|Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **instanceId** | **String**| ID of the workflow instance | [default to null] | +| **includeAssessment** | **Boolean**| Whether to include assessment | [optional] [default to false] | + +### Return type + +[**AssessedProcessInstanceDTO**](../Models/AssessedProcessInstanceDTO.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + + +# **getInstances** +> ProcessInstanceListResultDTO getInstances(GetInstancesRequest) + +Get instances + + Retrieve an array of workflow executions (instances) + +### Parameters + +|Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **GetInstancesRequest** | [**GetInstancesRequest**](../Models/GetInstancesRequest.md)| Parameters for retrieving instances | [optional] | + +### Return type + +[**ProcessInstanceListResultDTO**](../Models/ProcessInstanceListResultDTO.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: application/json +- **Accept**: application/json + + +# **getWorkflowInputSchemaById** +> InputSchemaResponseDTO getWorkflowInputSchemaById(workflowId, instanceId) + + + + Get the workflow input schema. It defines the input fields of the workflow + +### Parameters + +|Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **workflowId** | **String**| ID of the workflow to fetch | [default to null] | +| **instanceId** | **String**| ID of instance | [optional] [default to null] | + +### Return type + +[**InputSchemaResponseDTO**](../Models/InputSchemaResponseDTO.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + + +# **getWorkflowInstances** +> ProcessInstanceListResultDTO getWorkflowInstances(workflowId, SearchRequest) + +Get instances for a specific workflow + + Retrieve an array of workflow executions (instances) for the given workflow + +### Parameters + +|Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **workflowId** | **String**| ID of the workflow | [default to null] | +| **SearchRequest** | [**SearchRequest**](../Models/SearchRequest.md)| Parameters for retrieving workflow instances | [optional] | + +### Return type + +[**ProcessInstanceListResultDTO**](../Models/ProcessInstanceListResultDTO.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: application/json +- **Accept**: application/json + + +# **getWorkflowOverviewById** +> WorkflowOverviewDTO getWorkflowOverviewById(workflowId) + + + + Returns the key fields of the workflow including data on the last run instance + +### Parameters + +|Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **workflowId** | **String**| Unique identifier of the workflow | [default to null] | + +### Return type + +[**WorkflowOverviewDTO**](../Models/WorkflowOverviewDTO.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + + +# **getWorkflowSourceById** +> String getWorkflowSourceById(workflowId) + + + + Get the workflow's definition + +### Parameters + +|Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **workflowId** | **String**| ID of the workflow to fetch | [default to null] | + +### Return type + +**String** + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: text/plain, application/json + + +# **getWorkflowStatuses** +> List getWorkflowStatuses() + +Get workflow status list + + Retrieve array with the status of all instances + +### Parameters +This endpoint does not need any parameter. + +### Return type + +[**List**](../Models/WorkflowRunStatusDTO.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + + +# **getWorkflowsOverview** +> WorkflowOverviewListResultDTO getWorkflowsOverview(SearchRequest) + + + + Returns the key fields of the workflow including data on the last run instance + +### Parameters + +|Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **SearchRequest** | [**SearchRequest**](../Models/SearchRequest.md)| Pagination and filters | [optional] | + +### Return type + +[**WorkflowOverviewListResultDTO**](../Models/WorkflowOverviewListResultDTO.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: application/json +- **Accept**: application/json + + +# **retriggerInstance** +> Object retriggerInstance(workflowId, instanceId) + +Retrigger an instance + + Retrigger an instance + +### Parameters + +|Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| **workflowId** | **String**| ID of the workflow | [default to null] | +| **instanceId** | **String**| ID of the instance to retrigger | [default to null] | + +### Return type + +**Object** + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/AssessedProcessInstanceDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/AssessedProcessInstanceDTO.md new file mode 100644 index 00000000..885be91f --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/AssessedProcessInstanceDTO.md @@ -0,0 +1,10 @@ +# AssessedProcessInstanceDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **instance** | [**ProcessInstanceDTO**](ProcessInstanceDTO.md) | | [default to null] | +| **assessedBy** | [**ProcessInstanceDTO**](ProcessInstanceDTO.md) | | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ErrorResponse.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ErrorResponse.md new file mode 100644 index 00000000..1a22cc3c --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ErrorResponse.md @@ -0,0 +1,10 @@ +# ErrorResponse +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **message** | **String** | A string providing a concise and human-readable description of the encountered error. This field is required in the ErrorResponse object. | [default to internal server error] | +| **additionalInfo** | **String** | An optional field that can contain additional information or context about the error. It provides flexibility for including extra details based on specific error scenarios. | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ExecuteWorkflowRequestDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ExecuteWorkflowRequestDTO.md new file mode 100644 index 00000000..5aad2bd3 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ExecuteWorkflowRequestDTO.md @@ -0,0 +1,9 @@ +# ExecuteWorkflowRequestDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **inputData** | [**Object**](.md) | | [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ExecuteWorkflowResponseDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ExecuteWorkflowResponseDTO.md new file mode 100644 index 00000000..1058e878 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ExecuteWorkflowResponseDTO.md @@ -0,0 +1,9 @@ +# ExecuteWorkflowResponseDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **id** | **String** | | [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/FieldFilter.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/FieldFilter.md new file mode 100644 index 00000000..e0ce280f --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/FieldFilter.md @@ -0,0 +1,11 @@ +# FieldFilter +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **field** | **String** | | [default to null] | +| **operator** | **String** | | [default to null] | +| **value** | [**FieldFilter_value**](FieldFilter_value.md) | | [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/FieldFilter_value.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/FieldFilter_value.md new file mode 100644 index 00000000..7d9890ad --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/FieldFilter_value.md @@ -0,0 +1,8 @@ +# FieldFilter_value +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/Filter.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/Filter.md new file mode 100644 index 00000000..cd24e64b --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/Filter.md @@ -0,0 +1,12 @@ +# Filter +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **operator** | [**oas_any_type_not_mapped**](AnyType.md) | | [default to null] | +| **filters** | [**oas_any_type_not_mapped**](.md) | | [default to null] | +| **field** | [**oas_any_type_not_mapped**](.md) | | [default to null] | +| **value** | [**FieldFilter_value**](FieldFilter_value.md) | | [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/GetInstancesRequest.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/GetInstancesRequest.md new file mode 100644 index 00000000..ccd82afa --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/GetInstancesRequest.md @@ -0,0 +1,10 @@ +# GetInstancesRequest +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **paginationInfo** | [**PaginationInfoDTO**](PaginationInfoDTO.md) | | [optional] [default to null] | +| **filters** | [**SearchRequest**](SearchRequest.md) | | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/GetOverviewsRequestParams.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/GetOverviewsRequestParams.md new file mode 100644 index 00000000..4765b258 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/GetOverviewsRequestParams.md @@ -0,0 +1,10 @@ +# GetOverviewsRequestParams +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **paginationInfo** | [**PaginationInfoDTO**](PaginationInfoDTO.md) | | [optional] [default to null] | +| **filters** | [**SearchRequest**](SearchRequest.md) | | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/InputSchemaResponseDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/InputSchemaResponseDTO.md new file mode 100644 index 00000000..c9309f5d --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/InputSchemaResponseDTO.md @@ -0,0 +1,10 @@ +# InputSchemaResponseDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **inputSchema** | [**Object**](.md) | | [optional] [default to null] | +| **data** | [**Object**](.md) | | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/LogicalFilter.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/LogicalFilter.md new file mode 100644 index 00000000..d9aab1e8 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/LogicalFilter.md @@ -0,0 +1,10 @@ +# LogicalFilter +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **operator** | **String** | | [default to null] | +| **filters** | [**List**](Filter.md) | | [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/NodeInstanceDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/NodeInstanceDTO.md new file mode 100644 index 00000000..c0f472d4 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/NodeInstanceDTO.md @@ -0,0 +1,16 @@ +# NodeInstanceDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **\_\_typename** | **String** | Type name | [optional] [default to NodeInstance] | +| **id** | **String** | Node instance ID | [default to null] | +| **name** | **String** | Node name | [optional] [default to null] | +| **type** | **String** | Node type | [optional] [default to null] | +| **enter** | **String** | Date when the node was entered | [optional] [default to null] | +| **exit** | **String** | Date when the node was exited (optional) | [optional] [default to null] | +| **definitionId** | **String** | Definition ID | [optional] [default to null] | +| **nodeId** | **String** | Node ID | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/PaginationInfoDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/PaginationInfoDTO.md new file mode 100644 index 00000000..3b2200a6 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/PaginationInfoDTO.md @@ -0,0 +1,13 @@ +# PaginationInfoDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **pageSize** | **BigDecimal** | | [optional] [default to null] | +| **offset** | **BigDecimal** | | [optional] [default to null] | +| **totalCount** | **BigDecimal** | | [optional] [default to null] | +| **orderDirection** | **String** | | [optional] [default to null] | +| **orderBy** | **String** | | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ProcessInstanceDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ProcessInstanceDTO.md new file mode 100644 index 00000000..e09ee615 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ProcessInstanceDTO.md @@ -0,0 +1,23 @@ +# ProcessInstanceDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **id** | **String** | | [default to null] | +| **processId** | **String** | | [default to null] | +| **processName** | **String** | | [optional] [default to null] | +| **status** | [**ProcessInstanceStatusDTO**](ProcessInstanceStatusDTO.md) | | [optional] [default to null] | +| **endpoint** | **String** | | [optional] [default to null] | +| **serviceUrl** | **String** | | [optional] [default to null] | +| **start** | **String** | | [optional] [default to null] | +| **end** | **String** | | [optional] [default to null] | +| **duration** | **String** | | [optional] [default to null] | +| **category** | [**WorkflowCategoryDTO**](WorkflowCategoryDTO.md) | | [optional] [default to null] | +| **description** | **String** | | [optional] [default to null] | +| **workflowdata** | [**WorkflowDataDTO**](WorkflowDataDTO.md) | | [optional] [default to null] | +| **businessKey** | **String** | | [optional] [default to null] | +| **nodes** | [**List**](NodeInstanceDTO.md) | | [default to null] | +| **error** | [**ProcessInstanceErrorDTO**](ProcessInstanceErrorDTO.md) | | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ProcessInstanceErrorDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ProcessInstanceErrorDTO.md new file mode 100644 index 00000000..c21f9ce2 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ProcessInstanceErrorDTO.md @@ -0,0 +1,11 @@ +# ProcessInstanceErrorDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **\_\_typename** | **String** | Type name | [optional] [default to ProcessInstanceError] | +| **nodeDefinitionId** | **String** | Node definition ID | [default to null] | +| **message** | **String** | Error message (optional) | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ProcessInstanceListResultDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ProcessInstanceListResultDTO.md new file mode 100644 index 00000000..f6d3ecb0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ProcessInstanceListResultDTO.md @@ -0,0 +1,10 @@ +# ProcessInstanceListResultDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **items** | [**List**](ProcessInstanceDTO.md) | | [optional] [default to null] | +| **paginationInfo** | [**PaginationInfoDTO**](PaginationInfoDTO.md) | | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ProcessInstanceStatusDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ProcessInstanceStatusDTO.md new file mode 100644 index 00000000..04a01f90 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/ProcessInstanceStatusDTO.md @@ -0,0 +1,8 @@ +# ProcessInstanceStatusDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/SearchRequest.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/SearchRequest.md new file mode 100644 index 00000000..086b2924 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/SearchRequest.md @@ -0,0 +1,10 @@ +# SearchRequest +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **filters** | [**Filter**](Filter.md) | | [optional] [default to null] | +| **paginationInfo** | [**PaginationInfoDTO**](PaginationInfoDTO.md) | | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowCategoryDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowCategoryDTO.md new file mode 100644 index 00000000..8c4a5e4d --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowCategoryDTO.md @@ -0,0 +1,8 @@ +# WorkflowCategoryDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowDTO.md new file mode 100644 index 00000000..9b8de669 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowDTO.md @@ -0,0 +1,14 @@ +# WorkflowDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **id** | **String** | Workflow unique identifier | [default to null] | +| **name** | **String** | Workflow name | [optional] [default to null] | +| **format** | [**WorkflowFormatDTO**](WorkflowFormatDTO.md) | | [default to null] | +| **category** | [**WorkflowCategoryDTO**](WorkflowCategoryDTO.md) | | [default to null] | +| **description** | **String** | Description of the workflow | [optional] [default to null] | +| **annotations** | **List** | | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowDataDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowDataDTO.md new file mode 100644 index 00000000..c4a00feb --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowDataDTO.md @@ -0,0 +1,9 @@ +# WorkflowDataDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **result** | [**WorkflowResultDTO**](WorkflowResultDTO.md) | | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowFormatDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowFormatDTO.md new file mode 100644 index 00000000..8e76cc57 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowFormatDTO.md @@ -0,0 +1,8 @@ +# WorkflowFormatDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowListResultDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowListResultDTO.md new file mode 100644 index 00000000..a634642d --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowListResultDTO.md @@ -0,0 +1,10 @@ +# WorkflowListResultDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **items** | [**List**](WorkflowDTO.md) | | [default to null] | +| **paginationInfo** | [**PaginationInfoDTO**](PaginationInfoDTO.md) | | [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowOverviewDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowOverviewDTO.md new file mode 100644 index 00000000..26ee8165 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowOverviewDTO.md @@ -0,0 +1,17 @@ +# WorkflowOverviewDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **workflowId** | **String** | Workflow unique identifier | [default to null] | +| **name** | **String** | Workflow name | [optional] [default to null] | +| **format** | [**WorkflowFormatDTO**](WorkflowFormatDTO.md) | | [default to null] | +| **lastRunId** | **String** | | [optional] [default to null] | +| **lastTriggeredMs** | **BigDecimal** | | [optional] [default to null] | +| **lastRunStatus** | [**ProcessInstanceStatusDTO**](ProcessInstanceStatusDTO.md) | | [optional] [default to null] | +| **category** | [**WorkflowCategoryDTO**](WorkflowCategoryDTO.md) | | [optional] [default to null] | +| **avgDurationMs** | **BigDecimal** | | [optional] [default to null] | +| **description** | **String** | | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowOverviewListResultDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowOverviewListResultDTO.md new file mode 100644 index 00000000..a23dfe25 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowOverviewListResultDTO.md @@ -0,0 +1,10 @@ +# WorkflowOverviewListResultDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **overviews** | [**List**](WorkflowOverviewDTO.md) | | [optional] [default to null] | +| **paginationInfo** | [**PaginationInfoDTO**](PaginationInfoDTO.md) | | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowProgressDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowProgressDTO.md new file mode 100644 index 00000000..42a4826a --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowProgressDTO.md @@ -0,0 +1,18 @@ +# WorkflowProgressDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **\_\_typename** | [**oas_any_type_not_mapped**](.md) | Type name | [optional] [default to NodeInstance] | +| **id** | [**oas_any_type_not_mapped**](.md) | Node instance ID | [default to null] | +| **name** | [**oas_any_type_not_mapped**](.md) | Node name | [optional] [default to null] | +| **type** | [**oas_any_type_not_mapped**](.md) | Node type | [optional] [default to null] | +| **enter** | [**oas_any_type_not_mapped**](.md) | Date when the node was entered | [optional] [default to null] | +| **exit** | [**oas_any_type_not_mapped**](.md) | Date when the node was exited (optional) | [optional] [default to null] | +| **definitionId** | [**oas_any_type_not_mapped**](.md) | Definition ID | [optional] [default to null] | +| **nodeId** | [**oas_any_type_not_mapped**](.md) | Node ID | [optional] [default to null] | +| **status** | [**ProcessInstanceStatusDTO**](ProcessInstanceStatusDTO.md) | | [optional] [default to null] | +| **error** | [**ProcessInstanceErrorDTO**](ProcessInstanceErrorDTO.md) | | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowResultDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowResultDTO.md new file mode 100644 index 00000000..aa14267f --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowResultDTO.md @@ -0,0 +1,12 @@ +# WorkflowResultDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **completedWith** | **String** | The state of workflow completion. | [optional] [default to null] | +| **message** | **String** | High-level summary of the current status, free-form text, human readable. | [optional] [default to null] | +| **nextWorkflows** | [**List**](WorkflowResultDTO_nextWorkflows_inner.md) | List of workflows suggested to run next. Items at lower indexes are of higher priority. | [optional] [default to null] | +| **outputs** | [**List**](WorkflowResultDTO_outputs_inner.md) | Additional structured output of workflow processing. This can contain identifiers of created resources, links to resources, logs or other output. | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowResultDTO_nextWorkflows_inner.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowResultDTO_nextWorkflows_inner.md new file mode 100644 index 00000000..40db1d4d --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowResultDTO_nextWorkflows_inner.md @@ -0,0 +1,10 @@ +# WorkflowResultDTO_nextWorkflows_inner +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **id** | **String** | Workflow identifier | [default to null] | +| **name** | **String** | Human readable title describing the workflow. | [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowResultDTO_outputs_inner.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowResultDTO_outputs_inner.md new file mode 100644 index 00000000..b4b62ad0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowResultDTO_outputs_inner.md @@ -0,0 +1,11 @@ +# WorkflowResultDTO_outputs_inner +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **key** | **String** | Unique identifier of the option. Preferably human-readable. | [default to null] | +| **value** | [**WorkflowResultDTO_outputs_inner_value**](WorkflowResultDTO_outputs_inner_value.md) | | [default to null] | +| **format** | **String** | More detailed type of the 'value' property. Defaults to 'text'. | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowResultDTO_outputs_inner_value.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowResultDTO_outputs_inner_value.md new file mode 100644 index 00000000..9238c591 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowResultDTO_outputs_inner_value.md @@ -0,0 +1,8 @@ +# WorkflowResultDTO_outputs_inner_value +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowRunStatusDTO.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowRunStatusDTO.md new file mode 100644 index 00000000..cea0fc3a --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/Models/WorkflowRunStatusDTO.md @@ -0,0 +1,10 @@ +# WorkflowRunStatusDTO +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +| **key** | **String** | | [optional] [default to null] | +| **value** | **String** | | [optional] [default to null] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/README.md b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/README.md new file mode 100644 index 00000000..3f832609 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/generated/docs/markdown/README.md @@ -0,0 +1,62 @@ +# Documentation for Orchestrator plugin + + +## Documentation for API Endpoints + +All URIs are relative to *http://localhost* + +| Class | Method | HTTP request | Description | +|------------ | ------------- | ------------- | -------------| +| *DefaultApi* | [**abortWorkflow**](Apis/DefaultApi.md#abortworkflow) | **DELETE** /v2/workflows/instances/{instanceId}/abort | Abort a workflow instance | +*DefaultApi* | [**executeWorkflow**](Apis/DefaultApi.md#executeworkflow) | **POST** /v2/workflows/{workflowId}/execute | Execute a workflow | +*DefaultApi* | [**getInstanceById**](Apis/DefaultApi.md#getinstancebyid) | **GET** /v2/workflows/instances/{instanceId} | Get Workflow Instance by ID | +*DefaultApi* | [**getInstances**](Apis/DefaultApi.md#getinstances) | **POST** /v2/workflows/instances | Get instances | +*DefaultApi* | [**getWorkflowInputSchemaById**](Apis/DefaultApi.md#getworkflowinputschemabyid) | **GET** /v2/workflows/{workflowId}/inputSchema | Get the workflow input schema. It defines the input fields of the workflow | +*DefaultApi* | [**getWorkflowInstances**](Apis/DefaultApi.md#getworkflowinstances) | **POST** /v2/workflows/{workflowId}/instances | Get instances for a specific workflow | +*DefaultApi* | [**getWorkflowOverviewById**](Apis/DefaultApi.md#getworkflowoverviewbyid) | **GET** /v2/workflows/{workflowId}/overview | Returns the key fields of the workflow including data on the last run instance | +*DefaultApi* | [**getWorkflowSourceById**](Apis/DefaultApi.md#getworkflowsourcebyid) | **GET** /v2/workflows/{workflowId}/source | Get the workflow's definition | +*DefaultApi* | [**getWorkflowStatuses**](Apis/DefaultApi.md#getworkflowstatuses) | **GET** /v2/workflows/instances/statuses | Get workflow status list | +*DefaultApi* | [**getWorkflowsOverview**](Apis/DefaultApi.md#getworkflowsoverview) | **POST** /v2/workflows/overview | Returns the key fields of the workflow including data on the last run instance | +*DefaultApi* | [**retriggerInstance**](Apis/DefaultApi.md#retriggerinstance) | **POST** /v2/workflows/{workflowId}/{instanceId}/retrigger | Retrigger an instance | + + + +## Documentation for Models + + - [AssessedProcessInstanceDTO](./Models/AssessedProcessInstanceDTO.md) + - [ErrorResponse](./Models/ErrorResponse.md) + - [ExecuteWorkflowRequestDTO](./Models/ExecuteWorkflowRequestDTO.md) + - [ExecuteWorkflowResponseDTO](./Models/ExecuteWorkflowResponseDTO.md) + - [FieldFilter](./Models/FieldFilter.md) + - [FieldFilter_value](./Models/FieldFilter_value.md) + - [Filter](./Models/Filter.md) + - [GetInstancesRequest](./Models/GetInstancesRequest.md) + - [GetOverviewsRequestParams](./Models/GetOverviewsRequestParams.md) + - [InputSchemaResponseDTO](./Models/InputSchemaResponseDTO.md) + - [LogicalFilter](./Models/LogicalFilter.md) + - [NodeInstanceDTO](./Models/NodeInstanceDTO.md) + - [PaginationInfoDTO](./Models/PaginationInfoDTO.md) + - [ProcessInstanceDTO](./Models/ProcessInstanceDTO.md) + - [ProcessInstanceErrorDTO](./Models/ProcessInstanceErrorDTO.md) + - [ProcessInstanceListResultDTO](./Models/ProcessInstanceListResultDTO.md) + - [ProcessInstanceStatusDTO](./Models/ProcessInstanceStatusDTO.md) + - [SearchRequest](./Models/SearchRequest.md) + - [WorkflowCategoryDTO](./Models/WorkflowCategoryDTO.md) + - [WorkflowDTO](./Models/WorkflowDTO.md) + - [WorkflowDataDTO](./Models/WorkflowDataDTO.md) + - [WorkflowFormatDTO](./Models/WorkflowFormatDTO.md) + - [WorkflowListResultDTO](./Models/WorkflowListResultDTO.md) + - [WorkflowOverviewDTO](./Models/WorkflowOverviewDTO.md) + - [WorkflowOverviewListResultDTO](./Models/WorkflowOverviewListResultDTO.md) + - [WorkflowProgressDTO](./Models/WorkflowProgressDTO.md) + - [WorkflowResultDTO](./Models/WorkflowResultDTO.md) + - [WorkflowResultDTO_nextWorkflows_inner](./Models/WorkflowResultDTO_nextWorkflows_inner.md) + - [WorkflowResultDTO_outputs_inner](./Models/WorkflowResultDTO_outputs_inner.md) + - [WorkflowResultDTO_outputs_inner_value](./Models/WorkflowResultDTO_outputs_inner_value.md) + - [WorkflowRunStatusDTO](./Models/WorkflowRunStatusDTO.md) + + + +## Documentation for Authorization + +All endpoints do not require authorization. diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/index.ts b/workspaces/orchestrator/plugins/orchestrator-common/src/index.ts new file mode 100644 index 00000000..521a2c91 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './types'; +export * from './generated/api/definition'; +export * from './generated/client'; +export * from './constants'; +export * from './models'; +export * from './workflow'; +export * from './QueryParams'; +export * from './utils/StringUtils'; +export * from './permissions'; diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/models.ts b/workspaces/orchestrator/plugins/orchestrator-common/src/models.ts new file mode 100644 index 00000000..1fa56edf --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/models.ts @@ -0,0 +1,134 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { WorkflowCategory, WorkflowDefinition } from './types'; + +export enum ProcessInstanceState { + Active = 'ACTIVE', + Completed = 'COMPLETED', + Aborted = 'ABORTED', + Suspended = 'SUSPENDED', + Error = 'ERROR', + Pending = 'PENDING', +} + +export type ProcessInstanceStateValues = Uppercase< + keyof typeof ProcessInstanceState +>; + +export enum MilestoneStatus { + Available = 'AVAILABLE', + Active = 'ACTIVE', + Completed = 'COMPLETED', +} + +export interface NodeInstance { + __typename?: 'NodeInstance'; + id: string; + name: string; + type: string; + enter: string; + exit?: string; + definitionId: string; + nodeId: string; +} + +export interface TriggerableNode { + id: number; + name: string; + type: string; + uniqueId: string; + nodeDefinitionId: string; +} + +export interface Milestone { + __typename?: 'Milestone'; + id: string; + name: string; + status: MilestoneStatus; +} + +export interface ProcessInstanceError { + __typename?: 'ProcessInstanceError'; + nodeDefinitionId: string; + message?: string; +} + +export type ProcessInstanceVariables = Record; + +export interface ProcessInstance { + id: string; + processId: string; + processName?: string; + parentProcessInstanceId?: string; + rootProcessInstanceId?: string; + rootProcessId?: string; + roles?: string[]; + state?: ProcessInstanceStateValues; + endpoint: string; + serviceUrl?: string; + nodes: NodeInstance[]; + milestones?: Milestone[]; + variables?: ProcessInstanceVariables | string; + /** Format: date-time */ + start?: string; + /** Format: date-time */ + end?: string; + parentProcessInstance?: ProcessInstance; + childProcessInstances?: ProcessInstance[]; + error?: ProcessInstanceError; + addons?: string[]; + businessKey?: string; + isSelected?: boolean; + errorMessage?: string; + isOpen?: boolean; + diagram?: string; + nodeDefinitions?: TriggerableNode[]; + source?: string; + category?: WorkflowCategory; + description?: WorkflowDefinition['description']; +} +export interface IntrospectionQuery { + __type: IntrospectionType | null; +} + +export interface IntrospectionType { + name: string; + kind: TypeKind; + description: string | null; + fields: IntrospectionField[] | null; +} + +export interface IntrospectionField { + name: string; + type: IntrospectionTypeRef; +} + +export interface IntrospectionTypeRef { + kind: TypeKind; + name: TypeName; + ofType: IntrospectionTypeRef | null; +} + +export enum TypeKind { + InputObject = 'INPUT_OBJECT', +} + +export enum TypeName { + Id = 'IdArgument', + String = 'StringArgument', + StringArray = 'StringArrayArgument', + Date = 'DateArgument', +} diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/openapi/openapi.yaml b/workspaces/orchestrator/plugins/orchestrator-common/src/openapi/openapi.yaml new file mode 100644 index 00000000..330276e3 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/openapi/openapi.yaml @@ -0,0 +1,727 @@ +openapi: 3.1.0 +info: + title: Orchestrator plugin + description: API to interact with orchestrator plugin + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + version: 0.0.1 +servers: + - url: / +paths: + /v2/workflows/overview: + post: + operationId: getWorkflowsOverview + description: Returns the key fields of the workflow including data on the last run instance + requestBody: + required: false + description: Pagination and filters + content: + application/json: + schema: + $ref: '#/components/schemas/SearchRequest' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/WorkflowOverviewListResultDTO' + '500': + description: Error fetching workflow overviews + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /v2/workflows/{workflowId}/overview: + get: + operationId: getWorkflowOverviewById + description: Returns the key fields of the workflow including data on the last run instance + parameters: + - name: workflowId + in: path + required: true + description: Unique identifier of the workflow + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/WorkflowOverviewDTO' + '500': + description: Error fetching workflow overview + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /v2/workflows/{workflowId}/source: + get: + operationId: getWorkflowSourceById + description: Get the workflow's definition + parameters: + - name: workflowId + in: path + description: ID of the workflow to fetch + required: true + schema: + type: string + responses: + '200': + description: Success + content: + text/plain: + schema: + type: string + '500': + description: Error fetching workflow source by id + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /v2/workflows/{workflowId}/inputSchema: + get: + operationId: getWorkflowInputSchemaById + description: Get the workflow input schema. It defines the input fields of the workflow + parameters: + - name: workflowId + in: path + description: ID of the workflow to fetch + required: true + schema: + type: string + - name: instanceId + in: query + description: ID of instance + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/InputSchemaResponseDTO' + '500': + description: Error fetching workflow input schema by id + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /v2/workflows/instances: + post: + operationId: getInstances + summary: Get instances + description: Retrieve an array of workflow executions (instances) + requestBody: + required: false + description: Parameters for retrieving instances + content: + application/json: + schema: + $ref: '#/components/schemas/GetInstancesRequest' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ProcessInstanceListResultDTO' + '500': + description: Error fetching instances + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /v2/workflows/{workflowId}/instances: + post: + operationId: getWorkflowInstances + summary: Get instances for a specific workflow + description: Retrieve an array of workflow executions (instances) for the given workflow + parameters: + - name: workflowId + in: path + required: true + description: ID of the workflow + schema: + type: string + requestBody: + required: false + description: Parameters for retrieving workflow instances + content: + application/json: + schema: + $ref: '#/components/schemas/SearchRequest' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ProcessInstanceListResultDTO' + '500': + description: Error fetching instances + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /v2/workflows/instances/{instanceId}: + get: + summary: Get Workflow Instance by ID + description: Get a workflow execution/run (instance) + operationId: getInstanceById + parameters: + - name: instanceId + in: path + required: true + description: ID of the workflow instance + schema: + type: string + - name: includeAssessment + in: query + required: false + description: Whether to include assessment + schema: + type: boolean + default: false + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/AssessedProcessInstanceDTO' + '500': + description: Error fetching instance + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /v2/workflows/instances/statuses: + get: + operationId: getWorkflowStatuses + summary: Get workflow status list + description: Retrieve array with the status of all instances + responses: + '200': + description: Success + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/WorkflowRunStatusDTO' + '500': + description: Error fetching workflow statuses + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /v2/workflows/{workflowId}/execute: + post: + summary: Execute a workflow + description: Execute a workflow + operationId: executeWorkflow + parameters: + - name: workflowId + in: path + description: ID of the workflow to execute + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExecuteWorkflowRequestDTO' + responses: + '200': + description: Successful execution + content: + application/json: + schema: + $ref: '#/components/schemas/ExecuteWorkflowResponseDTO' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /v2/workflows/{workflowId}/{instanceId}/retrigger: + post: + summary: Retrigger an instance + description: Retrigger an instance + operationId: retriggerInstance + parameters: + - name: workflowId + in: path + description: ID of the workflow + required: true + schema: + type: string + - name: instanceId + in: path + description: ID of the instance to retrigger + required: true + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /v2/workflows/instances/{instanceId}/abort: + delete: + summary: Abort a workflow instance + operationId: abortWorkflow + description: Aborts a workflow instance identified by the provided instanceId. + parameters: + - name: instanceId + in: path + required: true + description: The identifier of the workflow instance to abort. + schema: + type: string + responses: + '200': + description: Successful operation + content: + text/plain: + schema: + type: string + '500': + description: Error aborting workflow + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' +components: + schemas: + ErrorResponse: + description: + The ErrorResponse object represents a common structure for handling errors in API responses. + It includes essential information about the error, such as the error message and additional optional details. + type: object + properties: + message: + description: + A string providing a concise and human-readable description of the encountered error. + This field is required in the ErrorResponse object. + type: string + default: internal server error + additionalInfo: + description: + An optional field that can contain additional information or context about the error. + It provides flexibility for including extra details based on specific error scenarios. + type: string + required: + - message + GetInstancesRequest: + type: object + properties: + paginationInfo: + $ref: '#/components/schemas/PaginationInfoDTO' + filters: + $ref: '#/components/schemas/SearchRequest' + GetOverviewsRequestParams: + type: object + properties: + paginationInfo: + $ref: '#/components/schemas/PaginationInfoDTO' + filters: + $ref: '#/components/schemas/SearchRequest' + WorkflowOverviewListResultDTO: + type: object + properties: + overviews: + type: array + items: + $ref: '#/components/schemas/WorkflowOverviewDTO' + minItems: 0 + paginationInfo: + $ref: '#/components/schemas/PaginationInfoDTO' + WorkflowOverviewDTO: + type: object + properties: + workflowId: + type: string + description: Workflow unique identifier + minLength: 1 + name: + type: string + description: Workflow name + minLength: 1 + format: + $ref: '#/components/schemas/WorkflowFormatDTO' + lastRunId: + type: string + lastTriggeredMs: + type: number + minimum: 0 + lastRunStatus: + $ref: '#/components/schemas/ProcessInstanceStatusDTO' + category: + $ref: '#/components/schemas/WorkflowCategoryDTO' + avgDurationMs: + type: number + minimum: 0 + description: + type: string + required: + - workflowId + - format + PaginationInfoDTO: + type: object + properties: + pageSize: + type: number + offset: + type: number + totalCount: + type: number + orderDirection: + enum: + - ASC + - DESC + orderBy: + type: string + additionalProperties: false + WorkflowFormatDTO: + type: string + description: Format of the workflow definition + enum: + - yaml + - json + WorkflowCategoryDTO: + type: string + description: Category of the workflow + enum: + - assessment + - infrastructure + WorkflowListResultDTO: + type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/WorkflowDTO' + paginationInfo: + $ref: '#/components/schemas/PaginationInfoDTO' + required: + - items + - paginationInfo + WorkflowDTO: + type: object + properties: + id: + type: string + description: Workflow unique identifier + minLength: 1 + name: + type: string + description: Workflow name + minLength: 1 + format: + $ref: '#/components/schemas/WorkflowFormatDTO' + category: + $ref: '#/components/schemas/WorkflowCategoryDTO' + description: + type: string + description: Description of the workflow + annotations: + type: array + items: + type: string + required: + - id + - category + - format + ProcessInstanceListResultDTO: + type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/ProcessInstanceDTO' + paginationInfo: + $ref: '#/components/schemas/PaginationInfoDTO' + AssessedProcessInstanceDTO: + type: object + properties: + instance: + $ref: '#/components/schemas/ProcessInstanceDTO' + assessedBy: + $ref: '#/components/schemas/ProcessInstanceDTO' + required: + - instance + ProcessInstanceDTO: + type: object + properties: + id: + type: string + processId: + type: string + processName: + type: string + status: + $ref: '#/components/schemas/ProcessInstanceStatusDTO' + endpoint: + type: string + serviceUrl: + type: string + start: + type: string + end: + type: string + duration: + type: string + category: + $ref: '#/components/schemas/WorkflowCategoryDTO' + description: + type: string + workflowdata: + $ref: '#/components/schemas/WorkflowDataDTO' + businessKey: + type: string + nodes: + type: array + items: + $ref: '#/components/schemas/NodeInstanceDTO' + error: + $ref: '#/components/schemas/ProcessInstanceErrorDTO' + required: + - id + - processId + - nodes + WorkflowDataDTO: + type: object + properties: + result: + $ref: '#/components/schemas/WorkflowResultDTO' + additionalProperties: true + WorkflowResultDTO: + # Based on https://github.com/parodos-dev/serverless-workflows/blob/main/shared/schemas/workflow-result-schema.json + description: Result of a workflow execution + type: object + properties: + completedWith: + description: The state of workflow completion. + type: string + enum: + - error + - success + message: + description: High-level summary of the current status, free-form text, human readable. + type: string + nextWorkflows: + description: List of workflows suggested to run next. Items at lower indexes are of higher priority. + type: array + items: + type: object + properties: + id: + description: Workflow identifier + type: string + name: + description: Human readable title describing the workflow. + type: string + required: + - id + - name + outputs: + description: Additional structured output of workflow processing. This can contain identifiers of created resources, links to resources, logs or other output. + type: array + items: + type: object + properties: + key: + description: Unique identifier of the option. Preferably human-readable. + type: string + value: + description: Free form value of the option. + anyOf: + - type: string + - type: number + format: + description: More detailed type of the 'value' property. Defaults to 'text'. + enum: + - text + - number + - link + required: + - key + - value + ProcessInstanceStatusDTO: + type: string + description: Status of the workflow run + enum: + - Active + - Error + - Completed + - Aborted + - Suspended + - Pending + WorkflowRunStatusDTO: + type: object + properties: + key: + type: string + value: + type: string + ExecuteWorkflowRequestDTO: + type: object + properties: + inputData: + type: object + additionalProperties: true + required: + - inputData + ExecuteWorkflowResponseDTO: + type: object + properties: + id: + type: string + required: + - id + WorkflowProgressDTO: + allOf: + - $ref: '#/components/schemas/NodeInstanceDTO' + - type: object + properties: + status: + $ref: '#/components/schemas/ProcessInstanceStatusDTO' + error: + $ref: '#/components/schemas/ProcessInstanceErrorDTO' + NodeInstanceDTO: + type: object + properties: + __typename: + type: string + default: 'NodeInstance' + description: Type name + id: + type: string + description: Node instance ID + name: + type: string + description: Node name + type: + type: string + description: Node type + enter: + type: string + description: Date when the node was entered + exit: + type: string + description: Date when the node was exited (optional) + definitionId: + type: string + description: Definition ID + nodeId: + type: string + description: Node ID + required: + - id + ProcessInstanceErrorDTO: + type: object + properties: + __typename: + type: string + default: 'ProcessInstanceError' + description: Type name + nodeDefinitionId: + type: string + description: Node definition ID + message: + type: string + description: Error message (optional) + required: + - nodeDefinitionId + SearchRequest: + type: object + properties: + filters: + $ref: '#/components/schemas/Filter' + paginationInfo: + $ref: '#/components/schemas/PaginationInfoDTO' + Filter: + oneOf: + - $ref: '#/components/schemas/LogicalFilter' + - $ref: '#/components/schemas/FieldFilter' + LogicalFilter: + type: object + required: + - operator + - filters + properties: + operator: + type: string + enum: [AND, OR, NOT] + filters: + type: array + items: + $ref: '#/components/schemas/Filter' + + FieldFilter: + type: object + required: + - field + - operator + - value + properties: + field: + type: string + operator: + type: string + enum: + [ + EQ, + GT, + GTE, + LT, + LTE, + IN, + IS_NULL, + CONTAINS, + CONTAINS_ALL, + CONTAINS_ANY, + LIKE, + BETWEEN, + ] + # The `value` field should be defined as follows. However, due to a bug (open since May 2023), + # https://github.com/OpenAPITools/openapi-generator/issues/15701 + # using `oneOf` to specify enum values for a property in the schema doesn't generate the enums correctly. + value: + oneOf: + - type: string + - type: number + - type: boolean + - type: array + items: + oneOf: + - type: string + - type: number + - type: boolean + # - type: string + # enum: + # - A + # - B + + InputSchemaResponseDTO: + type: object + properties: + inputSchema: + type: object + data: + type: object diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/permissions.ts b/workspaces/orchestrator/plugins/orchestrator-common/src/permissions.ts new file mode 100644 index 00000000..a2ba67b7 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/permissions.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createPermission } from '@backstage/plugin-permission-common'; + +export const orchestratorWorkflowInstancesReadPermission = createPermission({ + name: 'orchestrator.workflowInstances.read', + attributes: { + action: 'read', + }, +}); + +export const orchestratorWorkflowInstanceReadPermission = createPermission({ + name: 'orchestrator.workflowInstance.read', + attributes: { + action: 'read', + }, +}); + +export const orchestratorWorkflowReadPermission = createPermission({ + name: 'orchestrator.workflow.read', + attributes: { + action: 'read', + }, +}); + +export const orchestratorWorkflowExecutePermission = createPermission({ + name: 'orchestrator.workflow.execute', + attributes: {}, +}); + +export const orchestratorWorkflowInstanceAbortPermission = createPermission({ + name: 'orchestrator.workflowInstance.abort', + attributes: {}, +}); + +export const orchestratorPermissions = [ + orchestratorWorkflowReadPermission, + orchestratorWorkflowExecutePermission, + orchestratorWorkflowInstancesReadPermission, + orchestratorWorkflowInstanceReadPermission, + orchestratorWorkflowInstanceAbortPermission, +]; diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/types.ts b/workspaces/orchestrator/plugins/orchestrator-common/src/types.ts new file mode 100644 index 00000000..55b8461d --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/types.ts @@ -0,0 +1,142 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { JsonObject } from '@backstage/types'; + +import type { Specification } from '@severlessworkflow/sdk-typescript'; +import type { JSONSchema7, JSONSchema7Definition } from 'json-schema'; + +import type { ProcessInstance, ProcessInstanceStateValues } from './models'; + +type Id = { [P in keyof T]: T[P] }; + +type OmitDistributive = T extends any + ? T extends object + ? Id> + : T + : never; + +export type OmitRecursively = Omit< + { [P in keyof T]: OmitDistributive }, + K +>; + +export type WorkflowDefinition = OmitRecursively< + Specification.Workflow, + 'normalize' +>; + +export type WorkflowListResult = { + items: WorkflowDefinition[]; + totalCount: number; + offset: number; + limit: number; +}; + +export type WorkflowOverviewListResult = { + items: WorkflowOverview[]; + totalCount: number; + offset: number; + limit: number; +}; + +export type WorkflowFormat = 'yaml' | 'json'; + +export type WorkflowInputSchemaStep = { + schema: JsonObjectSchema; + title: string; + key: string; + data: JsonObject; + readonlyKeys: string[]; +}; + +export type JsonObjectSchema = Omit & { + properties: { [key: string]: JSONSchema7 }; +}; + +export type ComposedSchema = Omit & { + properties: { + [key: string]: Omit & { + properties: { [key: string]: JsonObjectSchema }; + }; + }; +}; + +export const isJsonObjectSchema = ( + schema: JSONSchema7 | JsonObjectSchema | JSONSchema7Definition, +): schema is JsonObjectSchema => + typeof schema === 'object' && + !!schema.properties && + Object.values(schema.properties).filter( + curSchema => typeof curSchema !== 'object', + ).length === 0; + +export const isComposedSchema = ( + schema: JSONSchema7 | ComposedSchema, +): schema is ComposedSchema => + !!schema.properties && + Object.values(schema.properties).filter( + curSchema => !isJsonObjectSchema(curSchema), + ).length === 0; + +export interface WorkflowExecutionResponse { + id: string; +} + +export enum WorkflowCategory { + ASSESSMENT = 'assessment', + INFRASTRUCTURE = 'infrastructure', +} + +export interface WorkflowOverview { + workflowId: string; + format: WorkflowFormat; + name?: string; + lastRunId?: string; + lastTriggeredMs?: number; + lastRunStatus?: ProcessInstanceStateValues; + category?: string; + avgDurationMs?: number; + description?: string; +} + +export interface WorkflowInfo { + id: string; + type?: string; + name?: string; + version?: string; + annotations?: string[]; + description?: string; + inputSchema?: JSONSchema7; + endpoint?: string; + serviceUrl?: string; + roles?: string[]; + source?: string; + metadata?: Map; + nodes?: Node[]; +} + +export interface Node { + id: string; + type?: string; + name?: string; + uniqueId?: string; + nodeDefinitionId?: string; +} + +export interface AssessedProcessInstance { + instance: ProcessInstance; + assessedBy?: ProcessInstance; +} diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/utils/StringUtils.ts b/workspaces/orchestrator/plugins/orchestrator-common/src/utils/StringUtils.ts new file mode 100644 index 00000000..0e28425e --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/utils/StringUtils.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type Capitalized = Capitalize>; + +export const capitalize = (text: S): Capitalized => + (text[0].toLocaleUpperCase('en-US') + + text.slice(1).toLocaleLowerCase('en-US')) as Capitalized; + +export const ellipsis = (text: S, prefixLength: number = 8) => + `${text.slice(0, prefixLength)}...`; diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/workflow.test.ts b/workspaces/orchestrator/plugins/orchestrator-common/src/workflow.test.ts new file mode 100644 index 00000000..a158c536 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/workflow.test.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { extractWorkflowFormat } from './workflow'; + +describe('extractWorkflowFormat', () => { + it('should return "json" when input is valid JSON', () => { + const source = '{"name": "workflow", "steps": ["step1", "step2"]}'; + expect(extractWorkflowFormat(source)).toEqual('json'); + }); + + it('should return "yaml" when input is valid YAML', () => { + const source = 'name: workflow\nsteps:\n - step1\n - step2\n'; + expect(extractWorkflowFormat(source)).toEqual('yaml'); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-common/src/workflow.ts b/workspaces/orchestrator/plugins/orchestrator-common/src/workflow.ts new file mode 100644 index 00000000..7a599af0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/src/workflow.ts @@ -0,0 +1,121 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Specification } from '@severlessworkflow/sdk-typescript'; +import { dump } from 'js-yaml'; + +import { ASSESSMENT_WORKFLOW_TYPE } from './constants'; +import { WorkflowCategory, WorkflowDefinition, WorkflowFormat } from './types'; + +export function fromWorkflowSource(content: string): WorkflowDefinition { + const parsed = Specification.Workflow.fromSource(content); + const workflow = parsed.sourceModel ?? parsed; + return removeProperty(workflow, 'normalize'); +} + +export function toWorkflowString( + definition: WorkflowDefinition, + format: WorkflowFormat, +): string { + switch (format) { + case 'json': + return toWorkflowJson(definition); + case 'yaml': + return toWorkflowYaml(definition); + default: + throw new Error(`Unsupported format ${format}`); + } +} + +export function toWorkflowJson(definition: WorkflowDefinition): string { + return JSON.stringify(definition, null, 2); +} + +export function toWorkflowYaml(definition: WorkflowDefinition): string { + return dump(definition); +} + +export function extractWorkflowFormatFromUri(uri: string): WorkflowFormat { + const match = RegExp(/\.sw\.(json|yaml|yml)$/).exec(uri); + if (match) { + if (match[1] === 'yml' || match[1] === 'yaml') { + return 'yaml'; + } + if (match[1] === 'json') { + return 'json'; + } + } + throw new Error(`Unsupported workflow format for uri ${uri}`); +} + +export function getWorkflowCategory( + definition: WorkflowDefinition | undefined, +): WorkflowCategory { + if (definition === undefined) { + return WorkflowCategory.INFRASTRUCTURE; + } + return definition?.annotations?.find( + annotation => annotation === ASSESSMENT_WORKFLOW_TYPE, + ) + ? WorkflowCategory.ASSESSMENT + : WorkflowCategory.INFRASTRUCTURE; +} + +function removeProperty(obj: T, propToDelete: string): T { + if (typeof obj !== 'object' || obj === null) { + return obj; + } + + if (Array.isArray(obj)) { + return obj.map(item => removeProperty(item, propToDelete)) as T; + } + + const newObj: any = {}; + + for (const key in obj) { + if (key !== propToDelete) { + newObj[key] = removeProperty(obj[key], propToDelete); // Recurse into nested objects + } + } + + return newObj; +} + +export function parseWorkflowVariables(variables?: object): object | undefined { + if (variables === undefined) { + return undefined; + } + + if (typeof variables === 'string') { + try { + return JSON.parse(variables); + } catch { + throw new Error( + `Error when parsing process instance variables: ${variables}`, + ); + } + } + + return variables; +} + +export function extractWorkflowFormat(source: string): WorkflowFormat { + try { + JSON.parse(source); + return 'json'; + } catch (_) { + return 'yaml'; + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-common/tsconfig.json b/workspaces/orchestrator/plugins/orchestrator-common/tsconfig.json new file mode 100644 index 00000000..a86c4d53 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@backstage/cli/config/tsconfig.json", + "include": ["src"], + "exclude": ["node_modules"], + "compilerOptions": { + "outDir": "../../dist-types/plugins/orchestrator-common", + "rootDir": "." + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-common/turbo.json b/workspaces/orchestrator/plugins/orchestrator-common/turbo.json new file mode 100644 index 00000000..31c08650 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-common/turbo.json @@ -0,0 +1,8 @@ +{ + "extends": ["//"], + "tasks": { + "tsc": { + "outputs": ["../../dist-types/plugins/orchestrator-common/**"] + } + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-form-api/.eslintignore b/workspaces/orchestrator/plugins/orchestrator-form-api/.eslintignore new file mode 100644 index 00000000..b19336e4 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-api/.eslintignore @@ -0,0 +1,3 @@ +playwright.config.ts +dist/ +dist-types/ \ No newline at end of file diff --git a/workspaces/orchestrator/plugins/orchestrator-form-api/.eslintrc.js b/workspaces/orchestrator/plugins/orchestrator-form-api/.eslintrc.js new file mode 100644 index 00000000..e2a53a6a --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-api/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/workspaces/orchestrator/plugins/orchestrator-form-api/.lintstagedrc.json b/workspaces/orchestrator/plugins/orchestrator-form-api/.lintstagedrc.json new file mode 100644 index 00000000..14b2263d --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-api/.lintstagedrc.json @@ -0,0 +1,4 @@ +{ + "*": "prettier --ignore-unknown --write", + "*.{js,jsx,ts,tsx,mjs,cjs}": "backstage-cli package lint --fix" +} diff --git a/workspaces/orchestrator/plugins/orchestrator-form-api/.prettierignore b/workspaces/orchestrator/plugins/orchestrator-form-api/.prettierignore new file mode 100644 index 00000000..fc8357d9 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-api/.prettierignore @@ -0,0 +1,12 @@ +dist +dist-types +coverage +.vscode +CHANGELOG.md +generated +templates +*.hbs +renovate.json +dist-dynamic +dist-scalprum +playwright-report diff --git a/workspaces/orchestrator/plugins/orchestrator-form-api/.prettierrc.js b/workspaces/orchestrator/plugins/orchestrator-form-api/.prettierrc.js new file mode 100644 index 00000000..5f81a8a0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-api/.prettierrc.js @@ -0,0 +1,20 @@ +// @ts-check + +/** @type {import("@ianvs/prettier-plugin-sort-imports").PrettierConfig} */ +module.exports = { + ...require('@spotify/prettier-config'), + plugins: ['@ianvs/prettier-plugin-sort-imports'], + importOrder: [ + '^react(.*)$', + '', + '^@backstage/(.*)$', + '', + '', + '', + '^@red-hat-developer-hub/(.*)$', + '', + '', + '', + '^[.]', + ], +}; diff --git a/workspaces/orchestrator/plugins/orchestrator-form-api/.versionhistory.md b/workspaces/orchestrator/plugins/orchestrator-form-api/.versionhistory.md new file mode 100644 index 00000000..b17e6983 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-api/.versionhistory.md @@ -0,0 +1 @@ +- Bumped to 1.1.0 in main branch for next release 1.3.0 diff --git a/workspaces/orchestrator/plugins/orchestrator-form-api/CHANGELOG.md b/workspaces/orchestrator/plugins/orchestrator-form-api/CHANGELOG.md new file mode 100644 index 00000000..9a1c1c85 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-api/CHANGELOG.md @@ -0,0 +1,33 @@ +# @red-hat-developer-hub/backstage-plugin-orchestrator-form-api + +## 1.4.1 + +### Patch Changes + +- 0e6bfd3: feat: update Backstage to the latest version + + Update to Backstage 1.32.5 + +- 67f466a: Resolved the following issues: + + 1. enabled validation using customValidate, and replaced extraErrors with getExtraErrors, since extraErrors is supposed to be populated when running onSubmit, and that isn't exposed to the user. Added busy handling while calling getExtraErrors. + 2. moved FormComponent to a separate component, to avoid buggy behavior and code smells with component generated in a different component. + 3. update formData on each change instead of when moving to next step, to avoid data being cleared. + 4. fix bug in validator - it only worked in first step, because of issue in @rjsf form + 5. removed unnecessary package json-schema that was used just for lint error, and fixed the root cause of lint error when importing types from @types/json-schema + +## 1.4.0 + +### Minor Changes + +- 8244f28: chore(deps): update to backstage 1.32 + +## 1.3.0 + +### Minor Changes + +- d9551ae: feat(deps): update to backstage 1.31 + +### Patch Changes + +- d9551ae: upgrade to yarn v3 diff --git a/workspaces/orchestrator/plugins/orchestrator-form-api/README.md b/workspaces/orchestrator/plugins/orchestrator-form-api/README.md new file mode 100644 index 00000000..122abc16 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-api/README.md @@ -0,0 +1,5 @@ +# @red-hat-developer-hub/backstage-plugin-orchestrator-form-api + +This library provides the interface for implementing a factory providing a decorator to customize the orchestrator workflow execution form. + +Details available [here](../orchestrator/docs/extensibleForm.md). diff --git a/workspaces/orchestrator/plugins/orchestrator-form-api/package.json b/workspaces/orchestrator/plugins/orchestrator-form-api/package.json new file mode 100644 index 00000000..2b436d1c --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-api/package.json @@ -0,0 +1,61 @@ +{ + "name": "@red-hat-developer-hub/backstage-plugin-orchestrator-form-api", + "description": "library for orchestrator form api, enabling creating a factory to extend the workflow execution form", + "version": "1.4.1", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "web-library", + "supported-versions": "1.32.5" + }, + "sideEffects": false, + "scripts": { + "build": "backstage-cli package build", + "lint:check": "backstage-cli package lint", + "lint:fix": "backstage-cli package lint --fix", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "tsc": "tsc", + "prettier:check": "prettier --ignore-unknown --check .", + "prettier:fix": "prettier --ignore-unknown --write ." + }, + "dependencies": { + "@backstage/core-plugin-api": "^1.10.0", + "@backstage/types": "^1.1.1", + "@rjsf/core": "^5.21.2", + "@rjsf/utils": "^5.21.2" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0" + }, + "devDependencies": { + "@backstage/cli": "0.28.2", + "@types/json-schema": "7.0.15", + "@types/react": "^18.2.58", + "prettier": "3.3.3", + "react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-router-dom": "^6.0.0" + }, + "files": [ + "dist" + ], + "maintainers": [ + "@mlibra", + "@batzionb", + "@gciavarrini" + ], + "repository": { + "type": "git", + "url": "https://github.com/redhat-developer/rhdh-plugins", + "directory": "workspaces/orchestrator/plugins/orchestrator-form-api" + }, + "bugs": "https://github.com/redhat-developer/rhdh-plugins/issues" +} diff --git a/workspaces/orchestrator/plugins/orchestrator-form-api/report.api.md b/workspaces/orchestrator/plugins/orchestrator-form-api/report.api.md new file mode 100644 index 00000000..39816322 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-api/report.api.md @@ -0,0 +1,34 @@ +## API Report File for "@red-hat-developer-hub/backstage-plugin-orchestrator-form-api" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +/// + +import { ApiRef } from '@backstage/core-plugin-api'; +import { ErrorSchema } from '@rjsf/utils'; +import { FormProps } from '@rjsf/core'; +import { JsonObject } from '@backstage/types'; +import type { JSONSchema7 } from 'json-schema'; +import { UiSchema } from '@rjsf/utils'; + +// @public +export type FormDecoratorProps = Pick, 'formData' | 'formContext' | 'widgets' | 'onChange' | 'customValidate'> & { + getExtraErrors?: (formData: JsonObject) => Promise> | undefined; +}; + +// @public +export interface OrchestratorFormApi { + getFormDecorator(schema: JSONSchema7, uiSchema: UiSchema): OrchestratorFormDecorator; +} + +// @public +export const orchestratorFormApiRef: ApiRef; + +// @public +export type OrchestratorFormDecorator = (FormComponent: React.ComponentType) => React.ComponentType; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/workspaces/orchestrator/plugins/orchestrator-form-api/src/api.ts b/workspaces/orchestrator/plugins/orchestrator-form-api/src/api.ts new file mode 100644 index 00000000..a5058622 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-api/src/api.ts @@ -0,0 +1,87 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createApiRef } from '@backstage/core-plugin-api'; +import { JsonObject } from '@backstage/types'; + +import { FormProps } from '@rjsf/core'; +import { ErrorSchema, UiSchema } from '@rjsf/utils'; +import type { JSONSchema7 } from 'json-schema'; + +/** + * @public + * FormDecoratorProps + * + * Type definition for properties passed to a form decorator component. + * This interface extends selected fields from `FormProps` provided by `react-jsonschema-form`, + * with additional custom functionality. + * + * @see {@link https://rjsf-team.github.io/react-jsonschema-form/docs/api-reference/form-props|RJSF Form Props Documentation} + * + * Core properties include: + * - formData: The form's current data + * - formContext: Contextual data shared across form components + * - widgets: Custom widget components for form fields + * - onChange: Handler for form data changes + * - customValidate: Custom validation function + * + * Additional properties: + * - getExtraErrors: Async function to fetch additional validation errors. + * This replaces the static 'extraErrors' prop from react-jsonschema-form, which can't be used as is, since onSubmit isn't exposed. + * The orchestrator form component will call getExtraErrors when running onSubmit. + */ +export type FormDecoratorProps = Pick< + FormProps, + 'formData' | 'formContext' | 'widgets' | 'onChange' | 'customValidate' +> & { + getExtraErrors?: ( + formData: JsonObject, + ) => Promise> | undefined; +}; + +/** + * @public + * OrchestratorFormDecorator + * + */ +export type OrchestratorFormDecorator = ( + FormComponent: React.ComponentType, +) => React.ComponentType; + +/** + * @public + * OrchestratorFormApi + * API to be implemented by factory in a custom plugin + */ +export interface OrchestratorFormApi { + /** + * @public + * getFormDecorator + * return the form decorator + */ + getFormDecorator( + schema: JSONSchema7, + uiSchema: UiSchema, + ): OrchestratorFormDecorator; +} + +/** + * @public + * OrchestratorFormApiRef + * + */ +export const orchestratorFormApiRef = createApiRef({ + id: 'plugin.orchestrator.form', +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-form-api/src/index.ts b/workspaces/orchestrator/plugins/orchestrator-form-api/src/index.ts new file mode 100644 index 00000000..547eece5 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-api/src/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { orchestratorFormApiRef } from './api'; +export type { OrchestratorFormApi } from './api'; +export type { OrchestratorFormDecorator } from './api'; +export type { FormDecoratorProps } from './api'; diff --git a/workspaces/orchestrator/plugins/orchestrator-form-api/tsconfig.json b/workspaces/orchestrator/plugins/orchestrator-form-api/tsconfig.json new file mode 100644 index 00000000..e036e04d --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-api/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@backstage/cli/config/tsconfig.json", + "include": ["src"], + "exclude": ["node_modules"], + "compilerOptions": { + "outDir": "../../dist-types/plugins/orchestrator-form-api", + "rootDir": "." + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-form-api/turbo.json b/workspaces/orchestrator/plugins/orchestrator-form-api/turbo.json new file mode 100644 index 00000000..b68f313a --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-api/turbo.json @@ -0,0 +1,8 @@ +{ + "extends": ["//"], + "tasks": { + "tsc": { + "outputs": ["../../dist-types/plugins/orchestrator-form-react/**"] + } + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/.eslintignore b/workspaces/orchestrator/plugins/orchestrator-form-react/.eslintignore new file mode 100644 index 00000000..b19336e4 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/.eslintignore @@ -0,0 +1,3 @@ +playwright.config.ts +dist/ +dist-types/ \ No newline at end of file diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/.eslintrc.js b/workspaces/orchestrator/plugins/orchestrator-form-react/.eslintrc.js new file mode 100644 index 00000000..e2a53a6a --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/.lintstagedrc.json b/workspaces/orchestrator/plugins/orchestrator-form-react/.lintstagedrc.json new file mode 100644 index 00000000..14b2263d --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/.lintstagedrc.json @@ -0,0 +1,4 @@ +{ + "*": "prettier --ignore-unknown --write", + "*.{js,jsx,ts,tsx,mjs,cjs}": "backstage-cli package lint --fix" +} diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/.prettierignore b/workspaces/orchestrator/plugins/orchestrator-form-react/.prettierignore new file mode 100644 index 00000000..fc8357d9 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/.prettierignore @@ -0,0 +1,12 @@ +dist +dist-types +coverage +.vscode +CHANGELOG.md +generated +templates +*.hbs +renovate.json +dist-dynamic +dist-scalprum +playwright-report diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/.prettierrc.js b/workspaces/orchestrator/plugins/orchestrator-form-react/.prettierrc.js new file mode 100644 index 00000000..5f81a8a0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/.prettierrc.js @@ -0,0 +1,20 @@ +// @ts-check + +/** @type {import("@ianvs/prettier-plugin-sort-imports").PrettierConfig} */ +module.exports = { + ...require('@spotify/prettier-config'), + plugins: ['@ianvs/prettier-plugin-sort-imports'], + importOrder: [ + '^react(.*)$', + '', + '^@backstage/(.*)$', + '', + '', + '', + '^@red-hat-developer-hub/(.*)$', + '', + '', + '', + '^[.]', + ], +}; diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/.versionhistory.md b/workspaces/orchestrator/plugins/orchestrator-form-react/.versionhistory.md new file mode 100644 index 00000000..b17e6983 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/.versionhistory.md @@ -0,0 +1 @@ +- Bumped to 1.1.0 in main branch for next release 1.3.0 diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/CHANGELOG.md b/workspaces/orchestrator/plugins/orchestrator-form-react/CHANGELOG.md new file mode 100644 index 00000000..17e3af5f --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/CHANGELOG.md @@ -0,0 +1,131 @@ +### Dependencies + +## 1.4.2 + +### Patch Changes + +- aee9d4a: Hotfix for button background - to share the one with theme. + +## 1.4.1 + +### Patch Changes + +- 0e6bfd3: feat: update Backstage to the latest version + + Update to Backstage 1.32.5 + +- 67f466a: Resolved the following issues: + + 1. enabled validation using customValidate, and replaced extraErrors with getExtraErrors, since extraErrors is supposed to be populated when running onSubmit, and that isn't exposed to the user. Added busy handling while calling getExtraErrors. + 2. moved FormComponent to a separate component, to avoid buggy behavior and code smells with component generated in a different component. + 3. update formData on each change instead of when moving to next step, to avoid data being cleared. + 4. fix bug in validator - it only worked in first step, because of issue in @rjsf form + 5. removed unnecessary package json-schema that was used just for lint error, and fixed the root cause of lint error when importing types from @types/json-schema + +- Updated dependencies [0e6bfd3] +- Updated dependencies [67f466a] + - @red-hat-developer-hub/backstage-plugin-orchestrator-form-api@1.4.1 + +## 1.4.0 + +### Minor Changes + +- 8244f28: chore(deps): update to backstage 1.32 + +### Patch Changes + +- Updated dependencies [8244f28] + - @red-hat-developer-hub/backstage-plugin-orchestrator-form-api@1.4.0 + +## 1.3.1 + +### Patch Changes + +- 7342e9b: chore: remove @janus-idp/cli dep and relink local packages + + This update removes `@janus-idp/cli` from all plugins, as it’s no longer necessary. Additionally, packages are now correctly linked with a specified version. + +## 1.3.0 + +### Minor Changes + +- d9551ae: feat(deps): update to backstage 1.31 + +### Patch Changes + +- d9551ae: Change local package references to a `*` +- d9551ae: upgrade to yarn v3 +- Updated dependencies [d9551ae] +- Updated dependencies [d9551ae] + - @red-hat-developer-hub/backstage-plugin-orchestrator-form-api@1.3.0 + +* **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.21.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.20.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.19.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.18.2 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.18.1 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.18.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.17.3 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-api:** upgraded to 1.1.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.17.2 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.17.1 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.17.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.17.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.0.0 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-api:** upgraded to 1.0.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.16.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.15.2 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.15.1 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.15.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-api:** upgraded to 1.0.1 diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/README.md b/workspaces/orchestrator/plugins/orchestrator-form-react/README.md new file mode 100644 index 00000000..cc0f0fba --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/README.md @@ -0,0 +1,5 @@ +# backstage-plugin-orchestrator-form-react + +This library provides the form component used in the workflow execution form. It is decoupled from the orchestrator plugin to allow plugins implementing the OrchestratorFormApi test the behavior in a simple backstage developer environment. + +Details available [here](../orchestrator/docs/extensibleForm.md). diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/package.json b/workspaces/orchestrator/plugins/orchestrator-form-react/package.json new file mode 100644 index 00000000..bba8ffb1 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/package.json @@ -0,0 +1,75 @@ +{ + "name": "@red-hat-developer-hub/backstage-plugin-orchestrator-form-react", + "description": "Web library for the orchestrator-form plugin", + "version": "1.4.2", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "web-library", + "pluginId": "orchestrator-form", + "pluginPackages": [ + "@red-hat-developer-hub/backstage-plugin-orchestrator-form-react" + ], + "supported-versions": "1.32.5" + }, + "sideEffects": false, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint:check": "backstage-cli package lint", + "lint:fix": "backstage-cli package lint --fix", + "test": "backstage-cli package test --passWithNoTests --coverage", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "tsc": "tsc", + "prettier:check": "prettier --ignore-unknown --check .", + "prettier:fix": "prettier --ignore-unknown --write ." + }, + "dependencies": { + "@backstage/core-components": "^0.15.1", + "@backstage/core-plugin-api": "^1.10.0", + "@backstage/types": "^1.1.1", + "@material-ui/core": "^4.12.4", + "@red-hat-developer-hub/backstage-plugin-orchestrator-form-api": "workspace:^", + "@rjsf/core": "^5.21.2", + "@rjsf/material-ui": "^5.21.2", + "@rjsf/utils": "^5.21.2", + "@rjsf/validator-ajv8": "^5.21.2", + "json-schema-library": "^9.0.0", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0" + }, + "devDependencies": { + "@backstage/cli": "0.28.2", + "@types/json-schema": "7.0.15", + "@types/lodash": "^4.14.151", + "@types/react": "^18.2.58", + "prettier": "3.3.3", + "react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-router-dom": "^6.0.0" + }, + "files": [ + "dist" + ], + "maintainers": [ + "@mlibra", + "@batzionb", + "@gciavarrini" + ], + "repository": { + "type": "git", + "url": "https://github.com/redhat-developer/rhdh-plugins", + "directory": "workspaces/orchestrator/plugins/orchestrator-form-react" + }, + "bugs": "https://github.com/redhat-developer/rhdh-plugins/issues" +} diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/report.api.md b/workspaces/orchestrator/plugins/orchestrator-form-react/report.api.md new file mode 100644 index 00000000..499df749 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/report.api.md @@ -0,0 +1,31 @@ +## API Report File for "@red-hat-developer-hub/backstage-plugin-orchestrator-form-react" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { JsonObject } from '@backstage/types'; +import type { JSONSchema7 } from 'json-schema'; +import { default as React_2 } from 'react'; + +// @public +export const OrchestratorForm: ({ schema, handleExecute, isExecuting, data, isDataReadonly, }: OrchestratorFormProps) => React_2.JSX.Element; + +// @public +export type OrchestratorFormProps = { + schema: JSONSchema7; + isExecuting: boolean; + handleExecute: (parameters: JsonObject) => Promise; + data?: JsonObject; + isDataReadonly?: boolean; +}; + +// @public +export const SubmitButton: ({ submitting, handleClick, children, focusOnMount, }: { + submitting: boolean; + handleClick?: (() => void) | undefined; + children: React_2.ReactNode; + focusOnMount?: boolean | undefined; +}) => React_2.JSX.Element; + +``` diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/DefaultFormApi.tsx b/workspaces/orchestrator/plugins/orchestrator-form-react/src/DefaultFormApi.tsx new file mode 100644 index 00000000..32d402ec --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/DefaultFormApi.tsx @@ -0,0 +1,33 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; + +import type { JSONSchema7 } from 'json-schema'; + +import { + FormDecoratorProps, + OrchestratorFormApi, + OrchestratorFormDecorator, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-form-api'; + +class DefaultFormApi implements OrchestratorFormApi { + getFormDecorator(_schema: JSONSchema7): OrchestratorFormDecorator { + return (FormComponent: React.ComponentType) => + FormComponent; + } +} + +export const defaultFormExtensionsApi = new DefaultFormApi(); diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorForm.tsx b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorForm.tsx new file mode 100644 index 00000000..e6ec9fef --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorForm.tsx @@ -0,0 +1,157 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { Fragment } from 'react'; + +import { JsonObject } from '@backstage/types'; + +import { UiSchema } from '@rjsf/utils'; +import type { JSONSchema7 } from 'json-schema'; + +import generateUiSchema from '../utils/generateUiSchema'; +import { StepperContextProvider } from '../utils/StepperContext'; +import OrchestratorFormStepper, { + OrchestratorFormStep, + OrchestratorFormToolbar, +} from './OrchestratorFormStepper'; +import OrchestratorFormWrapper from './OrchestratorFormWrapper'; +import ReviewStep from './ReviewStep'; + +const getNumSteps = (schema: JSONSchema7): number | undefined => { + if (schema.type !== 'object' || !schema.properties) return undefined; + const isMultiStep = Object.values(schema.properties).every( + prop => (prop as JSONSchema7).type === 'object', + ); + return isMultiStep ? Object.keys(schema.properties).length : undefined; +}; + +const SingleStepForm = ({ + schema, + formData, + onChange, + uiSchema, +}: { + schema: JSONSchema7; + formData: JsonObject; + onChange: (formData: JsonObject) => void; + uiSchema: UiSchema; +}) => { + const steps = React.useMemo(() => { + return [ + { + title: schema.title || 'Inputs', + key: 'schema', + content: ( + + + + ), + }, + ]; + }, [schema, formData, onChange, uiSchema]); + return ; +}; + +/** + * @public + * OrchestratorForm component properties + */ +export type OrchestratorFormProps = { + schema: JSONSchema7; + isExecuting: boolean; + handleExecute: (parameters: JsonObject) => Promise; + data?: JsonObject; + isDataReadonly?: boolean; +}; + +/** + * @public + * The component contains the react-json-schema-form and serves as an extensible form. It allows loading a custom plugin decorator to override the default react-json-schema-form properties. + */ +const OrchestratorForm = ({ + schema, + handleExecute, + isExecuting, + data, + isDataReadonly, +}: OrchestratorFormProps) => { + const [formData, setFormData] = React.useState(data || {}); + const numStepsInMultiStepSchema = React.useMemo( + () => getNumSteps(schema), + [schema], + ); + const isMultiStep = numStepsInMultiStepSchema !== undefined; + + const _handleExecute = React.useCallback(() => { + handleExecute(formData || {}); + }, [formData, handleExecute]); + + const onChange = React.useCallback( + (_formData: JsonObject) => { + setFormData(_formData); + }, + [setFormData], + ); + + const uiSchema = React.useMemo>(() => { + return generateUiSchema( + schema, + isMultiStep, + isDataReadonly ? data : undefined, + ); + }, [schema, isMultiStep, isDataReadonly, data]); + + const reviewStep = React.useMemo( + () => ( + + ), + [formData, schema, isExecuting, _handleExecute], + ); + + return ( + + {isMultiStep ? ( + + + // it is required to pass the fragment so rjsf won't generate a Submit button + ) : ( + + )} + + ); +}; + +export default OrchestratorForm; diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorFormStepper.tsx b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorFormStepper.tsx new file mode 100644 index 00000000..f4ea0731 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorFormStepper.tsx @@ -0,0 +1,124 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; + +import { + Button, + makeStyles, + Step, + StepLabel, + Stepper, + Typography, +} from '@material-ui/core'; + +import { useStepperContext } from '../utils/StepperContext'; +import SubmitButton from './SubmitButton'; + +const useStyles = makeStyles(theme => ({ + // Hotfix: this should be fixed in the theme + step: { + '& form': { + '& .field-array > div > div': { + outline: 'inherit !important', + padding: 'inherit !important', + backgroundColor: 'inherit !important', + + '& div > div > div > div': { + // unfortunately there are no better CSS selectors + backgroundColor: 'inherit !important', + }, + }, + }, + }, + regularButton: { + // hotifx for https://issues.redhat.com/browse/FLPATH-1825 + backgroundColor: 'inherit !important', + }, + footer: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'right', + marginTop: theme.spacing(2), + }, + formWrapper: { + padding: theme.spacing(2), + }, +})); + +export type OrchestratorFormStep = { + content: React.ReactNode; + title: string; + key: string; +}; + +const OrchestratorFormStepper = ({ + steps, +}: { + steps: OrchestratorFormStep[]; +}) => { + const { activeStep, reviewStep } = useStepperContext(); + const stepsWithReview = [ + ...steps, + { content: reviewStep, title: 'Review', key: 'review' }, + ]; + const styles = useStyles(); + return ( + <> + + {stepsWithReview?.map((step, index) => ( + + + + {step.title} + + + + ))} + +
+ {stepsWithReview[activeStep].content} +
+ + ); +}; + +export const OrchestratorFormToolbar = () => { + const { activeStep, handleBack, isValidating } = useStepperContext(); + const styles = useStyles(); + return ( +
+ + Next +
+ ); +}; + +export default OrchestratorFormStepper; diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorFormWrapper.tsx b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorFormWrapper.tsx new file mode 100644 index 00000000..cf5cae95 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorFormWrapper.tsx @@ -0,0 +1,168 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; + +import { ErrorPanel } from '@backstage/core-components'; +import { useApiHolder } from '@backstage/core-plugin-api'; +import { JsonObject } from '@backstage/types'; + +import { Grid } from '@material-ui/core'; +import { withTheme } from '@rjsf/core'; +import { Theme as MuiTheme } from '@rjsf/material-ui'; +import { ErrorSchema, UiSchema } from '@rjsf/utils'; +import type { JSONSchema7 } from 'json-schema'; +import omit from 'lodash/omit'; + +import { + FormDecoratorProps, + orchestratorFormApiRef, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-form-api'; + +import { defaultFormExtensionsApi } from '../DefaultFormApi'; +import { useStepperContext } from '../utils/StepperContext'; +import useValidator from '../utils/useValidator'; +import StepperObjectField from './StepperObjectField'; + +const MuiForm = withTheme(MuiTheme); + +type OrchestratorFormWrapperProps = { + schema: JSONSchema7; + numStepsInMultiStepSchema?: number; + children: React.ReactNode; + formData: JsonObject; + onChange: (formData: JsonObject) => void; + uiSchema: UiSchema; +}; + +const WrapperFormPropsContext = + React.createContext(null); + +const useWrapperFormPropsContext = (): OrchestratorFormWrapperProps => { + const context = React.useContext(WrapperFormPropsContext); + if (context === null) { + throw new Error('OrchestratorFormWrapperProps not provided'); + } + return context; +}; + +const FormComponent = (decoratorProps: FormDecoratorProps) => { + const props = useWrapperFormPropsContext(); + const { + numStepsInMultiStepSchema, + uiSchema, + schema, + onChange, + formData, + children, + } = props; + const [extraErrors, setExtraErrors] = React.useState< + ErrorSchema | undefined + >(); + const isMultiStep = numStepsInMultiStepSchema !== undefined; + const { handleNext, activeStep, handleValidateStarted, handleValidateEnded } = + useStepperContext(); + const [validationError, setValidationError] = React.useState< + Error | undefined + >(); + const validator = useValidator(isMultiStep); + const getActiveKey = () => { + if (!isMultiStep) { + return undefined; + } + return Object.keys(schema.properties || {})[activeStep]; + }; + + const onSubmit = async (_formData: JsonObject) => { + setExtraErrors(undefined); + let _extraErrors: ErrorSchema | undefined = undefined; + let _validationError: Error | undefined = undefined; + if (decoratorProps.getExtraErrors) { + try { + handleValidateStarted(); + _extraErrors = await decoratorProps.getExtraErrors(formData); + const activeKey = getActiveKey(); + setExtraErrors( + activeKey && _extraErrors?.[activeKey] + ? (_extraErrors[activeKey] as ErrorSchema) + : _extraErrors, + ); + } catch (err) { + _validationError = err as Error; + } finally { + handleValidateEnded(); + } + } + setValidationError(_validationError); + if ( + (!_extraErrors || Object.keys(_extraErrors).length === 0) && + !_validationError && + activeStep < (numStepsInMultiStepSchema || 1) + ) { + handleNext(); + } + }; + + return ( + + {validationError && ( + + + + )} + + onSubmit(e.formData || {})} + onChange={e => { + onChange(e.formData || {}); + if (decoratorProps.onChange) { + decoratorProps.onChange(e); + } + }} + > + {children} + + + + ); +}; + +const OrchestratorFormWrapper = ({ + schema, + uiSchema, + ...props +}: OrchestratorFormWrapperProps) => { + const formApi = + useApiHolder().get(orchestratorFormApiRef) || defaultFormExtensionsApi; + const NewComponent = React.useMemo(() => { + const formDecorator = formApi.getFormDecorator(schema, uiSchema); + return formDecorator(FormComponent); + }, [schema, formApi, uiSchema]); + return ( + + + + ); +}; + +export default OrchestratorFormWrapper; diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/ReviewStep.tsx b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/ReviewStep.tsx new file mode 100644 index 00000000..0479515e --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/ReviewStep.tsx @@ -0,0 +1,75 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; + +import { Content, StructuredMetadataTable } from '@backstage/core-components'; +import { JsonObject } from '@backstage/types'; + +import { Box, Button, makeStyles, Paper } from '@material-ui/core'; +import type { JSONSchema7 } from 'json-schema'; + +import generateReviewTableData from '../utils/generateReviewTableData'; +import { useStepperContext } from '../utils/StepperContext'; +import SubmitButton from './SubmitButton'; + +const useStyles = makeStyles(theme => ({ + footer: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'right', + marginTop: theme.spacing(2), + }, +})); + +const ReviewStep = ({ + busy, + schema, + data, + handleExecute, +}: { + busy: boolean; + schema: JSONSchema7; + data: JsonObject; + handleExecute: () => void; +}) => { + const styles = useStyles(); + const { handleBack } = useStepperContext(); + const displayData = React.useMemo(() => { + return generateReviewTableData(schema, data); + }, [schema, data]); + return ( + + + + +
+ + + Run + +
+
+
+ ); +}; + +export default ReviewStep; diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/StepperObjectField.tsx b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/StepperObjectField.tsx new file mode 100644 index 00000000..fad459dd --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/StepperObjectField.tsx @@ -0,0 +1,84 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; + +import { JsonObject } from '@backstage/types'; + +import ObjectField from '@rjsf/core/lib/components/fields/ObjectField'; +import { ErrorSchema, FieldProps, IdSchema } from '@rjsf/utils'; +import type { JSONSchema7 } from 'json-schema'; + +import OrchestratorFormStepper, { + OrchestratorFormStep, + OrchestratorFormToolbar, +} from './OrchestratorFormStepper'; + +const StepperObjectField = ({ + formData, + schema, + uiSchema, + onChange, + registry, + idSchema, + errorSchema, + ...props +}: FieldProps) => { + if (schema.properties === undefined) { + throw new Error( + "Stepper object field is not supported for schema that doesn't contain properties", + ); + } + const steps = Object.entries(schema.properties).reduce< + OrchestratorFormStep[] + >((prev, [key, subSchema]) => { + if (typeof subSchema === 'boolean') { + return prev; + } + return [ + ...prev, + { + content: ( + <> + + {...props} + schema={{ ...subSchema, title: '' }} // the title is in the step + uiSchema={uiSchema?.[key] || {}} + formData={(formData?.[key] as JsonObject) || {}} + onChange={data => { + onChange({ ...formData, [key]: data }); + }} + idSchema={idSchema[key] as IdSchema} + registry={{ + ...registry, + fields: { + ...registry.fields, + ObjectField: ObjectField, // undo override of objectfield + }, + }} + errorSchema={errorSchema?.[key] as ErrorSchema} + /> + + + ), + title: subSchema.title || key, + key, + }, + ]; + }, []); + return ; +}; + +export default StepperObjectField; diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/SubmitButton.tsx b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/SubmitButton.tsx new file mode 100644 index 00000000..3ea463fa --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/SubmitButton.tsx @@ -0,0 +1,59 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; + +import { Button, CircularProgress } from '@material-ui/core'; + +/** + * @public + * Button with loading state. + */ +const SubmitButton = ({ + submitting, + handleClick, + children, + focusOnMount, +}: { + submitting: boolean; + handleClick?: () => void; + children: React.ReactNode; + focusOnMount?: boolean; +}) => { + const ref = React.useRef(null); + React.useEffect(() => { + if (focusOnMount) { + ref.current?.focus(); + } + }, [focusOnMount]); + return ( + + ); +}; + +export default SubmitButton; diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/index.ts b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/index.ts new file mode 100644 index 00000000..95d06912 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type { OrchestratorFormProps } from './OrchestratorForm'; +export { default as OrchestratorForm } from './OrchestratorForm'; +export { default as SubmitButton } from './SubmitButton'; diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/useStyles.ts b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/useStyles.ts new file mode 100644 index 00000000..94adcafa --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/useStyles.ts @@ -0,0 +1,15 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/index.ts b/workspaces/orchestrator/plugins/orchestrator-form-react/src/index.ts new file mode 100644 index 00000000..94429e1e --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Web library for the orchestrator-form plugin. + * + * @packageDocumentation + */ + +// In this package you might for example export components or hooks +// that are useful to other plugins or modules. + +export * from './components'; diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/StepperContext.tsx b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/StepperContext.tsx new file mode 100644 index 00000000..fc1534d5 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/StepperContext.tsx @@ -0,0 +1,61 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; + +export type StepperContext = { + activeStep: number; + handleNext: () => void; + handleBack: () => void; + reviewStep: React.ReactNode; + isValidating: boolean; + handleValidateStarted: () => void; + handleValidateEnded: () => void; +}; + +const context = React.createContext(null); + +export const useStepperContext = (): StepperContext => { + const multiStepFormContext = React.useContext(context); + if (!multiStepFormContext) { + throw new Error('Context StepperContext is not defined'); + } + return multiStepFormContext; +}; + +export const StepperContextProvider = ({ + children, + reviewStep, +}: { + children: React.ReactNode; + reviewStep: React.ReactNode; +}) => { + const [activeStep, setActiveStep] = React.useState(0); + const [isValidating, setIsValidating] = React.useState(false); + const contextData = React.useMemo(() => { + return { + activeStep, + handleNext: () => { + setActiveStep(curActiveStep => curActiveStep + 1); + }, + handleBack: () => setActiveStep(curActiveStep => curActiveStep - 1), + reviewStep, + isValidating, + handleValidateStarted: () => setIsValidating(true), + handleValidateEnded: () => setIsValidating(false), + }; + }, [setActiveStep, activeStep, reviewStep, isValidating, setIsValidating]); + return {children}; +}; diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateReviewTableData.test.ts b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateReviewTableData.test.ts new file mode 100644 index 00000000..d2066670 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateReviewTableData.test.ts @@ -0,0 +1,169 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { JSONSchema7 } from 'json-schema'; + +import generateReviewTableData from './generateReviewTableData'; + +describe('mapSchemaToData', () => { + it('should map schema titles to data values correctly', () => { + const schema: JSONSchema7 = { + type: 'object', + title: 'Person', + properties: { + firstName: { + type: 'string', + title: 'First Name', + }, + lastName: { + type: 'string', + title: 'Last Name', + }, + age: { + type: 'number', + title: 'Age', + }, + address: { + type: 'object', + title: 'Address', + properties: { + street: { + type: 'string', + title: 'Street', + }, + city: { + type: 'string', + title: 'City', + }, + }, + }, + }, + }; + + const data = { + firstName: 'John', + lastName: 'Doe', + age: 30, + address: { + street: '123 Main St', + city: 'Somewhere', + }, + }; + + const expectedResult = { + 'First Name': 'John', + 'Last Name': 'Doe', + Age: 30, + Address: { + Street: '123 Main St', + City: 'Somewhere', + }, + }; + + const result = generateReviewTableData(schema, data); + expect(result).toEqual(expectedResult); + }); + + it('should map schema titles to data values with arrays correctly', () => { + const schema: JSONSchema7 = { + type: 'object', + title: 'Person', + properties: { + firstName: { + type: 'string', + title: 'First Name', + }, + hobbies: { + type: 'array', + title: 'Hobbies', + items: { + type: 'string', + }, + }, + }, + }; + + const data = { + firstName: 'Jane', + hobbies: ['reading', 'hiking'], + }; + + const expectedResult = { + 'First Name': 'Jane', + Hobbies: ['reading', 'hiking'], + }; + + const result = generateReviewTableData(schema, data); + expect(result).toEqual(expectedResult); + }); + + it('should map schema titles to data values with complex nesting correctly', () => { + const schema: JSONSchema7 = { + type: 'object', + properties: { + person: { + type: 'object', + title: 'Person', + properties: { + name: { + type: 'string', + title: 'Name', + }, + addresses: { + type: 'array', + title: 'Addresses', + items: { + type: 'object', + properties: { + street: { type: 'string' }, + city: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }; + + const data = { + person: { + name: 'John', + addresses: [ + { street: '123 A St', city: 'City A' }, + { street: '456 B St', city: 'City B' }, + ], + }, + }; + + const expectedResult = { + Person: { + Name: 'John', + Addresses: [ + { + street: '123 A St', + city: 'City A', + }, + { + street: '456 B St', + city: 'City B', + }, + ], + }, + }; + + const result = generateReviewTableData(schema, data); + expect(result).toEqual(expectedResult); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateReviewTableData.ts b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateReviewTableData.ts new file mode 100644 index 00000000..85131ba0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateReviewTableData.ts @@ -0,0 +1,76 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// import { JSONSchema7 } from 'json-schema'; +import { JsonObject, JsonValue } from '@backstage/types'; + +import type { JSONSchema7 } from 'json-schema'; +import { JsonSchema, Draft07 as JSONSchema } from 'json-schema-library'; + +export function isJsonObject(value?: JsonValue): value is JsonObject { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +export function processSchema( + key: string, + value: JsonValue | undefined, + schema: JSONSchema7, + formState: JsonObject, +): JsonObject { + const parsedSchema = new JSONSchema(schema); + const definitionInSchema = + key === '' + ? (schema as JsonSchema) + : parsedSchema.getSchema({ + pointer: `#/${key}`, + data: formState, + }); + + const name = definitionInSchema?.title ?? key; + if (definitionInSchema) { + if (definitionInSchema['ui:widget'] === 'password') { + return { [name]: '******' }; + } + + if (isJsonObject(value)) { + // Recurse nested objects + const nestedValue = Object.entries(value).reduce( + (prev, [nestedKey, _nestedValue]) => { + const curKey = key ? `${key}/${nestedKey}` : nestedKey; + return { + ...prev, + ...processSchema(curKey, _nestedValue, schema, formState), + }; + }, + {}, + ); + return { [name]: nestedValue }; + } + } + + return { [name]: value }; +} + +function generateReviewTableData( + schema: JSONSchema7, + data: JsonObject, +): JsonObject { + schema.title = ''; + const result = processSchema('', data, schema, data); + return result[''] as JsonObject; +} + +export default generateReviewTableData; diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateUiSchema.test.ts b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateUiSchema.test.ts new file mode 100644 index 00000000..46ec7889 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateUiSchema.test.ts @@ -0,0 +1,531 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { UiSchema } from '@rjsf/utils'; +import type { JSONSchema7 } from 'json-schema'; + +import generateUiSchema from './generateUiSchema'; + +describe('extract ui schema', () => { + it('if has properties ui: should create ui schema with properties', () => { + const expected = { + name: { 'ui:validationType': 'product', 'ui:autofocus': true }, + color: { 'ui:widget': 'color1', 'ui:validationType': 'color' }, + }; + const mixedSchema: JSONSchema7 = { + title: 'Product', + type: 'object', + properties: { + name: { + type: 'string', + title: 'Product Name', + 'ui:validationType': 'product', + }, + color: { + type: 'string', + title: 'Product Color', + description: 'The color of the product', + 'ui:widget': 'color1', + 'ui:validationType': 'color', + }, + }, + required: ['name', 'color'], + } as JSONSchema7; + const uiSchema = generateUiSchema(mixedSchema, false); + expect(uiSchema).toEqual(expected); + }); + + it('if no properties ui: should create ui schema just with auto focus', () => { + const mixedSchema: JSONSchema7 = { + title: 'Product', + type: 'object', + properties: { + name: { + type: 'string', + title: 'Product Name', + }, + color: { + type: 'string', + title: 'Product Color', + description: 'The color of the product', + }, + }, + required: ['name', 'color'], + } as JSONSchema7; + const uiSchema = generateUiSchema(mixedSchema, false); + expect(uiSchema).toEqual({ name: { 'ui:autofocus': true } }); + }); + + it('should extract from array', () => { + const mixedSchema = { + title: 'A list of tasks', + type: 'object', + required: ['title'], + properties: { + title: { + type: 'string', + title: 'Task list title', + }, + tasks: { + type: 'array', + title: 'Tasks', + items: { + type: 'object', + required: ['title'], + properties: { + title: { + type: 'string', + title: 'Title', + description: 'A sample title', + }, + details: { + type: 'string', + title: 'Task details', + description: 'Enter the task details', + 'ui:widget': 'textarea', + }, + done: { + type: 'boolean', + title: 'Done?', + default: false, + }, + }, + }, + }, + }, + } as JSONSchema7; + const expected = { + title: { + 'ui:autofocus': true, + }, + tasks: { + items: { + details: { + 'ui:widget': 'textarea', + }, + }, + }, + } as UiSchema; + const uiSchema = generateUiSchema(mixedSchema, false); + expect(uiSchema).toEqual(expected); + }); + + it('should extract from array with fixed number of items', () => { + const mixedSchema = { + type: 'object', + properties: { + fixedItemsList: { + type: 'array', + title: 'A list of fixed items', + items: [ + { + title: 'A string value', + type: 'string', + default: 'lorem ipsum', + 'ui:widget': 'textarea', + }, + { + title: 'a boolean value', + type: 'boolean', + }, + ], + additionalItems: { + title: 'Additional item', + type: 'number', + }, + }, + }, + } as JSONSchema7; + const expected = { + fixedItemsList: { + items: [ + { + 'ui:widget': 'textarea', + }, + ], + 'ui:autofocus': true, + }, + } as JSONSchema7; + + const uiSchema = generateUiSchema(mixedSchema, false); + expect(uiSchema).toEqual(expected); + }); + + it('should handle anyOf', () => { + const schemaWithAnyOf = { + title: 'A selection of items', + type: 'object', + properties: { + selectedItem: { + anyOf: [ + { type: 'number', title: 'Number item' }, + { type: 'boolean', title: 'Boolean item' }, + { type: 'string', title: 'Color', 'ui:widget': 'color' }, + ], + }, + }, + } as JSONSchema7; + + const expected = { + selectedItem: { + anyOf: [{}, {}, { 'ui:widget': 'color' }], + 'ui:autofocus': true, + }, + }; + + const uiSchema = generateUiSchema(schemaWithAnyOf, false); + expect(uiSchema).toEqual(expected); + }); + + it('should handle oneOf', () => { + const schemaWithAnyOf = { + title: 'A selection of items', + type: 'object', + properties: { + selectedItem: { + oneOf: [ + { type: 'string', title: 'Color', 'ui:widget': 'color' }, + { type: 'number', title: 'Number item' }, + { type: 'boolean', title: 'Boolean item' }, + ], + }, + }, + } as JSONSchema7; + + const expected = { + selectedItem: { + oneOf: [{ 'ui:widget': 'color' }], + 'ui:autofocus': true, + }, + }; + + const uiSchema = generateUiSchema(schemaWithAnyOf, false); + expect(uiSchema).toEqual(expected); + }); + + it('should handle allOf', () => { + const schemaWithAnyOf = { + title: 'A selection of items', + type: 'object', + properties: { + selectedItem: { + allOf: [ + { type: 'string', title: 'Color', 'ui:widget': 'color' }, + { type: 'number', title: 'Number item' }, + { type: 'boolean', title: 'Boolean item' }, + ], + }, + }, + } as JSONSchema7; + + const expected = { + selectedItem: { + allOf: [{ 'ui:widget': 'color' }], + 'ui:autofocus': true, + }, + }; + + const uiSchema = generateUiSchema(schemaWithAnyOf, false); + expect(uiSchema).toEqual(expected); + }); + + it('should handle referenced schemas', () => { + const refSchema = { + title: 'A referenced schema', + type: 'object', + properties: { + user: { + $ref: '#/definitions/User', + }, + }, + definitions: { + User: { + type: 'object', + properties: { + firstName: { + type: 'string', + title: 'First name', + 'ui:widget': 'textarea', + 'ui:autofocus': true, + }, + lastName: { type: 'string', title: 'Last name' }, + }, + }, + }, + } as JSONSchema7; + + const expected = { + user: { + firstName: { 'ui:autofocus': true, 'ui:widget': 'textarea' }, + }, + }; + + const uiSchema = generateUiSchema(refSchema, true); + expect(uiSchema).toEqual(expected); + }); + + it('should handle schemas with multiple hierarchies', () => { + const complexSchema = { + title: 'Complex schema with multiple hierarchies', + type: 'object', + properties: { + person: { + type: 'object', + properties: { + name: { type: 'string', title: 'Name' }, + password: { + type: 'string', + title: 'Name', + 'ui:widget': 'password', + }, + address: { + type: 'object', + properties: { + street: { + type: 'string', + title: 'Street', + 'ui:widget': 'textarea', + }, + city: { + type: 'string', + title: 'City', + 'ui:widget': 'textarea', + }, + }, + }, + }, + }, + }, + } as JSONSchema7; + + const expected = { + person: { + name: { 'ui:autofocus': true }, + password: { 'ui:widget': 'password' }, + address: { + street: { 'ui:widget': 'textarea' }, + city: { 'ui:widget': 'textarea' }, + }, + }, + }; + + const uiSchema = generateUiSchema(complexSchema, true); + expect(uiSchema).toEqual(expected); + }); + + it('should handle if/then/else schema with ui:widget: "textarea"', () => { + const schemaWithIfThenElse = { + title: 'Conditional Schema', + type: 'object', + properties: { + age: { type: 'number', title: 'Age', 'ui:autofocus': true }, + }, + if: { + properties: { age: { minimum: 18 } }, + }, + then: { + properties: { + canVote: { + type: 'boolean', + title: 'Can vote?', + 'ui:description': 'can vote', + }, + }, + }, + else: { + properties: { + needsConsent: { + type: 'boolean', + title: 'Needs parental consent?', + 'ui:description': 'needs consent', + }, + }, + }, + } as JSONSchema7; + + const expected = { + age: { 'ui:autofocus': true }, + canVote: { 'ui:description': 'can vote' }, + needsConsent: { 'ui:description': 'needs consent' }, + }; + + const uiSchema = generateUiSchema(schemaWithIfThenElse, false); + expect(uiSchema).toEqual(expected); + }); + + it('should handle a complex schema with various ui: properties and $ref, including readonly data', () => { + const complexSchema = { + title: 'Complex Schema Example', + type: 'object', + properties: { + userInfo: { + type: 'object', + properties: { + name: { + type: 'string', + title: 'Name', + 'ui:autofocus': true, + 'ui:widget': 'text', + 'ui:placeholder': 'Enter your name', + 'ui:description': 'Full legal name', + }, + age: { + type: 'number', + title: 'Age', + 'ui:widget': 'updown', + 'ui:help': 'Enter your age in years', + }, + address: { + $ref: '#/definitions/Address', + }, + }, + }, + tasks: { + type: 'array', + title: 'Tasks', + items: { + type: 'object', + required: ['title'], + properties: { + title: { + type: 'string', + title: 'Title', + 'ui:widget': 'color', + 'ui:description': 'Color-coded task title', + }, + details: { + type: 'string', + title: 'Task details', + 'ui:widget': 'textarea', + 'ui:placeholder': 'Describe the task in detail', + }, + done: { + type: 'boolean', + title: 'Done?', + 'ui:widget': 'checkbox', + }, + }, + }, + }, + preferences: { + type: 'object', + properties: { + notifications: { + anyOf: [ + { + type: 'boolean', + title: 'Receive Notifications', + 'ui:widget': 'radio', + 'ui:options': { inline: true }, + }, + { + type: 'string', + title: 'Notification Email', + 'ui:widget': 'email', + 'ui:placeholder': 'you@example.com', + }, + ], + }, + }, + }, + }, + definitions: { + Address: { + type: 'object', + properties: { + street: { + type: 'string', + title: 'Street', + 'ui:widget': 'textarea', + 'ui:placeholder': '123 Main St', + }, + city: { + type: 'string', + title: 'City', + 'ui:widget': 'select', + 'ui:emptyValue': 'Select a city', + }, + }, + }, + }, + } as unknown as JSONSchema7; + + const expected = { + userInfo: { + name: { + 'ui:autofocus': true, + 'ui:widget': 'text', + 'ui:placeholder': 'Enter your name', + 'ui:description': 'Full legal name', + }, + age: { + 'ui:widget': 'updown', + 'ui:help': 'Enter your age in years', + }, + address: { + street: { + 'ui:widget': 'textarea', + 'ui:placeholder': '123 Main St', + }, + city: { + 'ui:widget': 'select', + 'ui:emptyValue': 'Select a city', + }, + }, + }, + tasks: { + items: { + title: { + 'ui:widget': 'color', + 'ui:description': 'Color-coded task title', + }, + details: { + 'ui:widget': 'textarea', + 'ui:placeholder': 'Describe the task in detail', + }, + done: { + 'ui:widget': 'checkbox', + }, + 'ui:readonly': true, + }, + }, + preferences: { + notifications: { + anyOf: [ + { + 'ui:widget': 'radio', + 'ui:options': { inline: true }, + }, + { + 'ui:widget': 'email', + 'ui:placeholder': 'you@example.com', + }, + ], + }, + }, + }; + + const uiSchema = generateUiSchema(complexSchema, true, { + tasks: { + items: { + title: 'purple', + details: 'abc', + done: true, + }, + }, + }); + expect(uiSchema).toEqual(expected); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateUiSchema.ts b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateUiSchema.ts new file mode 100644 index 00000000..9567ad2e --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateUiSchema.ts @@ -0,0 +1,236 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* eslint-disable @typescript-eslint/no-use-before-define */ + +import { JsonObject } from '@backstage/types'; + +import { UiSchema } from '@rjsf/utils'; +import type { JSONSchema7, JSONSchema7Definition } from 'json-schema'; +import get from 'lodash/get'; +import set from 'lodash/set'; + +/** + * Extracts the uiSchema from a mixed JSON Schema that includes + * both standard JSON Schema properties and react-json-schema-form specific + * UI Schema properties (prefixed with 'ui:'). The function does not modify + * the original JSON Schema. + * + * @param mixedSchema - The JSON Schema that contains both standard and UI Schema properties. + * @returns An object representing the uiSchema. + */ + +const getSchemaDefinition = (ref: string, rootSchema: JSONSchema7) => { + const path = ref.replace(/^#\//, '').replace(/\//g, '.'); + return get(rootSchema, path); +}; + +const getStringAfterDot = (input: string) => + input.startsWith('.') ? input.slice(1) : input; + +function replaceSparseArrayElementsdWithEmptyObject(value: any): any { + /* handle cases where ui: properties exists for some of the itmes in the array, for example: + { + "selectedItem": { + "anyOf": [ + , + , + { + "ui:widget": "color", + }, + ], + } + the function will return + { + "selectedItem": { + "anyOf": [ + {}, + {}, + { + "ui:widget": "color", + }, + ], + } + */ + if (Array.isArray(value)) { + return [...value].map(item => { + return item ? replaceSparseArrayElementsdWithEmptyObject(item) : {}; + }); + } else if (value && typeof value === 'object') { + return Object.keys(value).reduce((acc, key) => { + acc[key] = replaceSparseArrayElementsdWithEmptyObject(value[key]); + return acc; + }, {} as Record); + } + return value; +} + +function extractUiSchema(mixedSchema: JSONSchema7): UiSchema { + const rootSchema = mixedSchema; + const result = {}; + + const processObjectProperties = ( + properties: { + [key: string]: JSONSchema7Definition; + }, + path: string, + ) => { + for (const [key, value] of Object.entries(properties)) { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + processObject(value, `${path}.${key}`); + } + }; + + const processObject = (curSchema: JSONSchema7Definition, path: string) => { + if (typeof curSchema === 'boolean') { + return; + } + if (curSchema.$ref) { + processObject(getSchemaDefinition(curSchema.$ref, rootSchema), path); + } else if (curSchema.properties) { + processObjectProperties(curSchema.properties, path); + } else if (curSchema.items) { + processArraySchema(curSchema, path); + } else { + processLeafSchema(curSchema, path); + } + processComposedSchema(curSchema, path); + }; + + const processLeafSchema = ( + leafSchema: JSONSchema7Definition, + path: string, + ) => { + for (const [subSchemaKey, value] of Object.entries(leafSchema)) { + if (subSchemaKey.startsWith('ui:')) { + set(result, getStringAfterDot(`${path}.${subSchemaKey}`), value); + } + } + }; + + const processArrayItems = (items: JSONSchema7Definition[], path: string) => { + for (let i = 0; i < items.length; ++i) { + processObject(items[i], `${path}[${i}]`); + } + }; + + const processArraySchema = (schema: JSONSchema7, path: string) => { + if (Array.isArray(schema.items)) { + processArrayItems(schema.items, `${path}.items`); + } else if (typeof schema.items === 'object') { + processObject(schema.items, `${path}.items`); + } + if (schema.additionalItems && typeof schema.additionalItems === 'object') { + processObject(schema.additionalItems, `${path}.additinalItems`); + } + }; + + const processComposedSchema = (curSchema: JSONSchema7, path: string) => { + if (curSchema.anyOf) { + processArrayItems(curSchema.anyOf, `${path}.anyOf`); + } else if (curSchema.oneOf) { + processArrayItems(curSchema.oneOf, `${path}.oneOf`); + } else if (curSchema.allOf) { + processArrayItems(curSchema.allOf, `${path}.allOf`); + } else if (curSchema.then) { + processObject(curSchema.then, `${path}`); + if (curSchema.else) { + processObject(curSchema.else, `${path}`); + } + } + }; + + processObject(mixedSchema, ''); + return replaceSparseArrayElementsdWithEmptyObject(result); +} + +const addReadonly = ( + data: JsonObject, + uiSchema: UiSchema, + isMultiStep: boolean, +) => { + // make inputs that came from assessment instance variables readonly + if (!isMultiStep) { + for (const key of Object.keys(data)) { + uiSchema[key] = { + ...uiSchema[key], + 'ui:readonly': true, + }; + } + return; + } + for (const [stepKey, stepValue] of Object.entries(data)) { + uiSchema[stepKey] = { + ...uiSchema[stepKey], + }; + for (const key of Object.keys(stepValue as JsonObject)) { + uiSchema[stepKey][key] = { + ...uiSchema[stepKey][key], + 'ui:readonly': true, + }; + } + } +}; + +const addFocusOnFirstElement = ( + schema: JSONSchema7, + uiSchema: UiSchema, + isMultiStep: boolean, +) => { + if (!schema.properties) { + return; + } + if (!isMultiStep) { + const firstKey = Object.keys(schema.properties)[0]; + uiSchema[firstKey] = { + ...uiSchema[firstKey], + 'ui:autofocus': true, + }; + } + for (const [stepKey, subSchema] of Object.entries(schema.properties)) { + if (typeof subSchema !== 'object') { + return; + } + const _subSchema = subSchema.$ref + ? getSchemaDefinition(subSchema.$ref, schema) + : subSchema; + if (!_subSchema.properties) { + return; + } + const subSchemaFirstKey = Object.keys(_subSchema.properties)[0]; + uiSchema[stepKey] = { + ...uiSchema[stepKey], + [subSchemaFirstKey]: { + ...uiSchema[stepKey]?.[subSchemaFirstKey], + 'ui:autofocus': true, + }, + }; + } +}; + +const generateUiSchema = ( + schema: JSONSchema7, + isMultiStep: boolean, + readonlyData?: JsonObject, +): UiSchema => { + const uiSchema = extractUiSchema(schema); + if (readonlyData) { + addReadonly(readonlyData, uiSchema, isMultiStep); + } + addFocusOnFirstElement(schema, uiSchema, isMultiStep); + return uiSchema; +}; + +export default generateUiSchema; diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/useValidator.ts b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/useValidator.ts new file mode 100644 index 00000000..09a949f1 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/useValidator.ts @@ -0,0 +1,102 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { JsonObject } from '@backstage/types'; + +import { + createErrorHandler, + CustomValidator, + ErrorSchema, + RJSFValidationError, + unwrapErrorHandler, + ValidationData, + validationDataMerge, + ValidatorType, +} from '@rjsf/utils'; +import validatorAjv from '@rjsf/validator-ajv8'; +import _validator from '@rjsf/validator-ajv8'; +import type { JSONSchema7 } from 'json-schema'; + +import { useStepperContext } from './StepperContext'; + +// add the activeStep to the validator to force rjsf form to rerender when activeStep changes. This doesn't happen because it assumes function are equal. +// see https://github.com/rjsf-team/react-jsonschema-form/blob/v5.18.5/packages/utils/src/deepEquals.ts#L12 + +export type ValidatorTypeForceRender = ValidatorType< + JsonObject, + JSONSchema7 +> & { + activeStep: number; +}; + +const useValidator = (isMultiStepSchema: boolean) => { + const { activeStep } = useStepperContext(); + const validator: ValidatorTypeForceRender = { + activeStep, + validateFormData: ( + formData: JsonObject, + _schema: JSONSchema7, + customValidate: CustomValidator, + ): ValidationData => { + let validationData = validatorAjv.validateFormData(formData, _schema); + + if (customValidate) { + const errorHandler = customValidate( + formData, + createErrorHandler(formData), + ); + const userErrorSchema = unwrapErrorHandler(errorHandler); + validationData = validationDataMerge( + validationData, + userErrorSchema, + ); + } + + if (!isMultiStepSchema) { + return validationData; + } + + const activeKey = Object.keys(_schema.properties || {})[activeStep]; + return { + errors: validationData.errors.filter(err => + err.property?.startsWith(`.${activeKey}.`), + ), + errorSchema: validationData.errorSchema[activeKey] || {}, + }; + }, + + toErrorList: ( + errorSchema?: ErrorSchema, + fieldPath?: string[], + ): RJSFValidationError[] => { + return validatorAjv.toErrorList(errorSchema, fieldPath); + }, + + isValid: ( + _schema: JSONSchema7, + formData: JsonObject | undefined, + rootSchema: JSONSchema7, + ) => { + return validatorAjv.isValid(_schema, formData, rootSchema); + }, + + rawValidation: (_schema: JSONSchema7, formData?: JsonObject) => + validatorAjv.rawValidation(_schema, formData), + }; + + return validator; +}; + +export default useValidator; diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/tsconfig.json b/workspaces/orchestrator/plugins/orchestrator-form-react/tsconfig.json new file mode 100644 index 00000000..d94e805d --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@backstage/cli/config/tsconfig.json", + "include": ["src"], + "exclude": ["node_modules"], + "compilerOptions": { + "outDir": "../../dist-types/plugins/orchestrator-form-react", + "rootDir": "." + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/turbo.json b/workspaces/orchestrator/plugins/orchestrator-form-react/turbo.json new file mode 100644 index 00000000..b68f313a --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/turbo.json @@ -0,0 +1,8 @@ +{ + "extends": ["//"], + "tasks": { + "tsc": { + "outputs": ["../../dist-types/plugins/orchestrator-form-react/**"] + } + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.eslintignore b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.eslintignore new file mode 100644 index 00000000..b19336e4 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.eslintignore @@ -0,0 +1,3 @@ +playwright.config.ts +dist/ +dist-types/ \ No newline at end of file diff --git a/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.eslintrc.js b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.eslintrc.js new file mode 100644 index 00000000..e2a53a6a --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.lintstagedrc.json b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.lintstagedrc.json new file mode 100644 index 00000000..14b2263d --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.lintstagedrc.json @@ -0,0 +1,4 @@ +{ + "*": "prettier --ignore-unknown --write", + "*.{js,jsx,ts,tsx,mjs,cjs}": "backstage-cli package lint --fix" +} diff --git a/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.prettierignore b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.prettierignore new file mode 100644 index 00000000..fc8357d9 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.prettierignore @@ -0,0 +1,12 @@ +dist +dist-types +coverage +.vscode +CHANGELOG.md +generated +templates +*.hbs +renovate.json +dist-dynamic +dist-scalprum +playwright-report diff --git a/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.prettierrc.js b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.prettierrc.js new file mode 100644 index 00000000..5f81a8a0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/.prettierrc.js @@ -0,0 +1,20 @@ +// @ts-check + +/** @type {import("@ianvs/prettier-plugin-sort-imports").PrettierConfig} */ +module.exports = { + ...require('@spotify/prettier-config'), + plugins: ['@ianvs/prettier-plugin-sort-imports'], + importOrder: [ + '^react(.*)$', + '', + '^@backstage/(.*)$', + '', + '', + '', + '^@red-hat-developer-hub/(.*)$', + '', + '', + '', + '^[.]', + ], +}; diff --git a/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/README.md b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/README.md new file mode 100644 index 00000000..f9741626 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/README.md @@ -0,0 +1,32 @@ +# @red-hat-developer-hub/backstage-plugin-orchestrator-swf-editor-envelope + +## Description + +This package includes assets that are meant to be served as a single page application. +This package has no entrypoint, therefore it is not suitable to be consumed as a library. +The Orchestrator plugin uses these assets when it renders the Serverless Workflow editor by injecting an `iframe` that loads this application. + +## Development + +1. Build the project using `yarn build`. The `postbuild` script updates the `orchestrator-backend`'s static directory with your changes. +1. Serve the files in the `dist` directroy + +- Either use `@red-hat-developer-hub/backstage-plugin-orchestrator-backend` internal `static` directory (files under `plugins/orchestrator-backend/static/*` are served statically). +- Or, serve the files directly with: + ```sh + yarn dlx serve \ + --port 8080 \ + --cors \ + --debug \ + node_modules/@red-hat-developer-hub/backstage-plugin-orchestrator-swf-editor-envelope/dist + ``` + +3. Add this configuration to the `app-config.yaml`: + ```yaml + backend: + csp: + frame-ancestors: ['http://localhost:3000', 'http://localhost:7007'] + script-src: ["'self'", "'unsafe-inline'", "'unsafe-eval'"] + script-src-elem: ["'self'", "'unsafe-inline'", "'unsafe-eval'"] + connect-src: ["'self'", 'http:', 'https:', 'data:'] + ``` diff --git a/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/catalog-info.yaml b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/catalog-info.yaml new file mode 100644 index 00000000..0eb4df62 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/catalog-info.yaml @@ -0,0 +1,25 @@ +# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: red-hat-developer-hub-orchestrator-swf-editor-envelope + title: '@red-hat-developer-hub/backstage-plugin-orchestrator-swf-editor-envelope' + description: Serverless workflow editor envelope for the Orchestrator plugin + annotations: + backstage.io/source-location: url:https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope + backstage.io/view-url: https://github.com/redhat-developer/rhdh-plugins/blob/main/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/catalog-info.yaml + backstage.io/edit-url: https://github.com/redhat-developer/rhdh-plugins/edit/main/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/catalog-info.yaml + github.com/project-slug: red-hat-developer-hub/backstage-plugins + github.com/team-slug: red-hat-developer-hub/orchestrator-codeowners + sonarqube.org/project-key: red_hat_developer_hub_plugins + links: + - url: https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope + title: GitHub Source + icon: source + type: source +spec: + type: backstage-frontend-plugin + lifecycle: production + owner: orchestrator-team + system: rhdh + subcomponentOf: red-hat-developer-hub-plugins diff --git a/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/package.json b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/package.json new file mode 100644 index 00000000..5b3a6780 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/package.json @@ -0,0 +1,87 @@ +{ + "name": "@red-hat-developer-hub/backstage-plugin-orchestrator-swf-editor-envelope", + "description": "Serverless workflow editor envelope for the Orchestrator plugin", + "version": "1.0.0", + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "private": true, + "workspaces": { + "nohoist": [ + "@kie-tools/**" + ] + }, + "backstage": { + "role": "web-library", + "supported-versions": "1.32.5", + "pluginId": "orchestrator-swf-editor-envelope", + "pluginPackages": [ + "@red-hat-developer-hub/backstage-plugin-orchestrator-swf-editor-envelope" + ] + }, + "sideEffects": false, + "scripts": { + "build": "scripts/build.sh", + "postbuild": "node scripts/postbuild.js copy", + "clean": "backstage-cli package clean", + "lint:check": "backstage-cli package lint", + "lint:fix": "backstage-cli package lint --fix", + "postpack": "backstage-cli package postpack", + "prepack": "backstage-cli package prepack", + "test": "backstage-cli package test --passWithNoTests --coverage", + "tsc": "tsc", + "prettier:check": "prettier --ignore-unknown --check .", + "prettier:fix": "prettier --ignore-unknown --write ." + }, + "dependencies": { + "@kie-tools-core/editor": "^0.32.0", + "@kie-tools-core/keyboard-shortcuts": "^0.32.0", + "@kie-tools/serverless-workflow-combined-editor": "^0.32.0", + "@kie-tools/serverless-workflow-diagram-editor-assets": "^0.32.0", + "@kie-tools/serverless-workflow-diagram-editor-envelope": "^0.32.0", + "@kie-tools/serverless-workflow-text-editor": "^0.32.0" + }, + "devDependencies": { + "@backstage/cli": "0.28.2", + "clean-webpack-plugin": "4.0.0", + "css-loader": "6.11.0", + "filemanager-webpack-plugin": "8.0.0", + "html-webpack-plugin": "5.6.0", + "monaco-editor": "0.50.0", + "monaco-editor-webpack-plugin": "7.1.0", + "monaco-yaml": "5.1.1", + "node-polyfill-webpack-plugin": "3.0.0", + "prettier": "3.3.3", + "sass": "1.77.2", + "sass-loader": "14.2.1", + "style-loader": "3.3.4", + "terser-webpack-plugin": "5.3.10", + "ts-loader": "9.5.1", + "typescript": "5.4.5", + "webpack": "5.94.0", + "webpack-cli": "5.1.4" + }, + "files": [ + "dist" + ], + "repository": { + "type": "git", + "url": "https://github.com/redhat-developer/rhdh-plugins", + "directory": "workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope" + }, + "keywords": [ + "support:tech-preview", + "lifecycle:active", + "backstage", + "plugin" + ], + "homepage": "https://red.ht/rhdh", + "bugs": "https://github.com/redhat-developer/rhdh-plugins/issues", + "maintainers": [ + "@mlibra", + "@batzionb", + "@gciavarrini" + ], + "author": "The Backstage Community" +} diff --git a/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/scripts/build.sh b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/scripts/build.sh new file mode 100755 index 00000000..50543162 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/scripts/build.sh @@ -0,0 +1,7 @@ +#!/usr/bin/bash +/* eslint-disable no-console */ + +// disabling building temporarily to not break backstage plugins release job +// https://github.com/janus-idp/backstage-plugins/actions/runs/9255511136/job/25459744222#step:4:4465 +// command should be: webpack --progress +echo "build temporarily disabled"; mkdir -p dist/ diff --git a/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/scripts/postbuild.js b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/scripts/postbuild.js new file mode 100755 index 00000000..58afda73 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/scripts/postbuild.js @@ -0,0 +1,129 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable no-console */ + +const { dirname } = require('node:path'); +const { + cp, + symlink, + stat, + lstat, + rm, + mkdir, + constants, +} = require('node:fs/promises'); + +const errors = { + ENOENT: 2, // No such file or directory + EINVAL: 22, // Invalid argument +}; + +function locatePkg(pkgName) { + return dirname(require.resolve(`${pkgName}/package.json`)); +} + +async function validateSourceAndDestination(source, destination) { + try { + await stat(source); + } catch (e) { + console.error(`${e.message}\nDid you forget to build the project?`); + process.exit(errors[e.code]); + } + + try { + await stat(destination); + } catch (e) { + console.error(e.message); + process.exit(errors[e.code]); + } +} + +async function main([op]) { + const source = `${dirname(__dirname)}/dist`; + const destination = `${locatePkg( + '@red-hat-developer-hub/backstage-plugin-orchestrator-backend', + )}/static/generated`; + await validateSourceAndDestination(source, destination); + + switch (op) { + case 'debug': { + const msg = [`source=${source}`, `destination=${destination}`] + .join('\n') + .trimEnd(); + console.log(msg); + break; + } + case 'clean': { + await rm(`${destination}/envelope`, { recursive: true, force: true }); + break; + } + case 'copy': { + console.log( + `Copying Editor Envelope files: ${source} -> ${destination}/envelope`, + ); + + let dirExists = false; + try { + const stats = await stat(`${destination}/envelope`, constants.S_IFDIR); + dirExists = stats.isDirectory(); + } catch (error) { + // skip... + } + + if (!dirExists) { + await mkdir(`${destination}/envelope`); + } + + await cp(source, `${destination}/envelope`, { + recursive: true, + force: true, + }); + break; + } + case 'link': { + /** + * This option exists for testing/dev purposes because it saves some space. + * In the orchestrator-backend production artifact the files are copied into its static directory. + */ + + let existsAndIsSymLink = false; + try { + const stats = await lstat(`${destination}/envelope`); + existsAndIsSymLink = stats.isSymbolicLink(); + } catch { + // skip... + } + + if (existsAndIsSymLink) { + await rm(`${destination}/envelope`); + } + + console.log( + `Linking Editor Envelope files: ${destination}/envelope -> ${source}`, + ); + await symlink(source, `${destination}/envelope`); + break; + } + default: + console.error(`Invalid argument: ${op}`); + process.exit(errors.EINVAL); + } +} + +if (require.main === module) { + main(process.argv.slice(2)); +} diff --git a/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/src/index.ejs b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/src/index.ejs new file mode 100644 index 00000000..734b66c9 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/src/index.ejs @@ -0,0 +1,25 @@ + + + + + + + <%= htmlWebpackPlugin.options.title %> + + + + +
+ + diff --git a/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/src/init/SwfEditorEnvelopeCombined.ts b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/src/init/SwfEditorEnvelopeCombined.ts new file mode 100644 index 00000000..f5e12815 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/src/init/SwfEditorEnvelopeCombined.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { initCustom } from '@kie-tools-core/editor/dist/envelope'; +import { NoOpKeyboardShortcutsService } from '@kie-tools-core/keyboard-shortcuts/dist/envelope'; +import { + ServerlessWorkflowCombinedEditorApi, + ServerlessWorkflowCombinedEditorChannelApi, +} from '@kie-tools/serverless-workflow-combined-editor/dist/api'; +import { ServerlessWorkflowCombinedEditorEnvelopeApi } from '@kie-tools/serverless-workflow-combined-editor/dist/api/ServerlessWorkflowCombinedEditorEnvelopeApi'; +import { ServerlessWorkflowCombinedEditorFactory } from '@kie-tools/serverless-workflow-combined-editor/dist/editor'; +import { ServerlessWorkflowCombinedEditorEnvelopeApiImpl } from '@kie-tools/serverless-workflow-combined-editor/dist/envelope'; + +initCustom< + ServerlessWorkflowCombinedEditorApi, + ServerlessWorkflowCombinedEditorEnvelopeApi, + ServerlessWorkflowCombinedEditorChannelApi +>({ + container: document.getElementById('root')!, + bus: { + postMessage: (message, targetOrigin: string, _) => + window.parent.postMessage(message, targetOrigin, _), + }, + apiImplFactory: { + create: args => + new ServerlessWorkflowCombinedEditorEnvelopeApiImpl( + args, + new ServerlessWorkflowCombinedEditorFactory(), + ), + }, + keyboardShortcutsService: new NoOpKeyboardShortcutsService(), +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/src/init/SwfEditorEnvelopeDiagram.ts b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/src/init/SwfEditorEnvelopeDiagram.ts new file mode 100644 index 00000000..6577f806 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/src/init/SwfEditorEnvelopeDiagram.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { initCustom } from '@kie-tools-core/editor/dist/envelope'; +import { + ServerlessWorkflowDiagramEditorChannelApi, + ServerlessWorkflowDiagramEditorEnvelopeApi, +} from '@kie-tools/serverless-workflow-diagram-editor-envelope/dist/api'; +import { + ServerlessWorkflowDiagramEditor, + ServerlessWorkflowDiagramEditorEnvelopeApiImpl, + ServerlessWorkflowDiagramEditorFactory, +} from '@kie-tools/serverless-workflow-diagram-editor-envelope/dist/envelope'; + +initCustom< + ServerlessWorkflowDiagramEditor, + ServerlessWorkflowDiagramEditorEnvelopeApi, + ServerlessWorkflowDiagramEditorChannelApi +>({ + container: document.getElementById('root')!, + bus: { + postMessage: (message, targetOrigin: string, _) => + window.parent.postMessage(message, targetOrigin, _), + }, + apiImplFactory: { + create: args => + new ServerlessWorkflowDiagramEditorEnvelopeApiImpl( + args, + new ServerlessWorkflowDiagramEditorFactory({ + shouldLoadResourcesDynamically: true, + }), + ), + }, +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/src/init/SwfEditorEnvelopeText.ts b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/src/init/SwfEditorEnvelopeText.ts new file mode 100644 index 00000000..50a61e88 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/src/init/SwfEditorEnvelopeText.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { initCustom } from '@kie-tools-core/editor/dist/envelope'; +import { + ServerlessWorkflowTextEditorApi, + ServerlessWorkflowTextEditorChannelApi, + ServerlessWorkflowTextEditorEnvelopeApi, +} from '@kie-tools/serverless-workflow-text-editor/dist/api'; +import { ServerlessWorkflowTextEditorFactory } from '@kie-tools/serverless-workflow-text-editor/dist/editor'; +import { ServerlessWorkflowTextEditorEnvelopeApiImpl } from '@kie-tools/serverless-workflow-text-editor/dist/envelope'; + +initCustom< + ServerlessWorkflowTextEditorApi, + ServerlessWorkflowTextEditorEnvelopeApi, + ServerlessWorkflowTextEditorChannelApi +>({ + container: document.getElementById('root')!, + bus: { + postMessage: (message, targetOrigin: string, _) => + window.parent.postMessage(message, targetOrigin, _), + }, + apiImplFactory: { + create: args => + new ServerlessWorkflowTextEditorEnvelopeApiImpl( + args, + new ServerlessWorkflowTextEditorFactory(), + ), + }, +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/tsconfig.json b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/tsconfig.json new file mode 100644 index 00000000..378948f9 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "@backstage/cli/config/tsconfig.json", + "include": ["src"], + "exclude": ["node_modules"], + "compilerOptions": { + "outDir": "./dist", + "rootDir": ".", + "declaration": false, + "emitDeclarationOnly": false + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/webpack.config.js b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/webpack.config.js new file mode 100644 index 00000000..da43638b --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-swf-editor-envelope/webpack.config.js @@ -0,0 +1,181 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const path = require('node:path'); +const editorAssets = require('@kie-tools/serverless-workflow-diagram-editor-assets'); +const { CleanWebpackPlugin } = require('clean-webpack-plugin'); +const FileManagerPlugin = require('filemanager-webpack-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); +const TerserPlugin = require('terser-webpack-plugin'); +const NodePolyfillPlugin = require('node-polyfill-webpack-plugin'); + +const loaders = new Map(); +loaders.set('style-loader', require.resolve('style-loader')); +loaders.set('css-loader', require.resolve('css-loader')); +loaders.set('sass-loader', require.resolve('sass-loader')); + +const paths = new Map(); +paths.set('src', path.resolve(__dirname, 'src')); +paths.set('dist', path.resolve(__dirname, 'dist')); +paths.set('diagram', path.resolve(paths.get('dist'), 'diagram')); +paths.set('tsconfig', path.resolve(__dirname, 'tsconfig.json')); + +const entryPoints = new Map(); +entryPoints.set( + 'diagram', + path.resolve(paths.get('src'), 'init/SwfEditorEnvelopeDiagram.ts'), +); +entryPoints.set( + 'combined', + path.resolve(paths.get('src'), 'init/SwfEditorEnvelopeCombined.ts'), +); +entryPoints.set( + 'text', + path.resolve(paths.get('src'), 'init/SwfEditorEnvelopeText.ts'), +); + +function makeHtmlWebpackPluginOptions(editorType) { + return { + title: 'Serverless Workflow Editor Envelope', + filename: `serverless-workflow-${editorType}-editor-envelope.html`, + templateParameters: { + jsBundleFilename: `${editorType}.bundle.js`, + }, + inject: false, + }; +} + +/** @type {import('webpack-cli').CallableOption} */ +module.exports = (_env, argv) => ({ + mode: argv.mode ?? 'production', + optimization: { + splitChunks: { + cacheGroups: { + monacoEditorMin: { + test: /[\\/]node_modules[\\/]monaco-editor/, + name: 'monaco-editor', + chunks: 'async', + }, + }, + }, + minimizer: [ + new TerserPlugin({ + terserOptions: { + format: { + comments: false, + }, + }, + extractComments: false, + }), + ], + }, + performance: { + maxEntrypointSize: 1024 * 1024 * 15, + maxAssetSize: 1024 * 1024 * 15, + }, + entry: { + combined: entryPoints.get('combined'), + diagram: entryPoints.get('diagram'), + text: entryPoints.get('text'), + }, + plugins: [ + new CleanWebpackPlugin(), + new NodePolyfillPlugin(), + new HtmlWebpackPlugin(makeHtmlWebpackPluginOptions('combined')), + new HtmlWebpackPlugin(makeHtmlWebpackPluginOptions('diagram')), + new HtmlWebpackPlugin(makeHtmlWebpackPluginOptions('text')), + new MonacoWebpackPlugin({ + languages: ['json'], + customLanguages: [ + { + label: 'yaml', + entry: ['monaco-yaml', 'vs/basic-languages/yaml/yaml.contribution'], + worker: { + id: 'monaco-yaml/yamlWorker', + entry: 'monaco-yaml/yaml.worker.js', + }, + }, + ], + }), + new FileManagerPlugin({ + events: { + onEnd: [ + { + copy: [ + { + source: editorAssets.swEditorPath(), + destination: paths.get('diagram'), + options: { + globOptions: { ignore: ['**/WEB-INF/**/*'] }, + }, + }, + ], + }, + ], + }, + }), + ], + module: { + rules: [ + { + test: /\.m?js$/, + resolve: { + fullySpecified: false, + }, + }, + { + test: /\.tsx?$/, + include: [paths.get('src')], + use: [ + { + loader: 'ts-loader', + options: { + configFile: paths.get('tsconfig'), + allowTsInNodeModules: true, + }, + }, + ], + }, + { + test: /\.s[ac]ss$/i, + use: [ + loaders.get('style-loader'), + loaders.get('css-loader'), + loaders.get('sass-loader'), + ], + }, + { + test: /\.css$/, + use: [loaders.get('style-loader'), loaders.get('css-loader')], + }, + { + test: /\.(svg|ttf|eot|woff|woff2)$/, + type: 'asset/inline', + }, + ], + }, + output: { + path: paths.get('dist'), + filename: '[name].bundle.js', + chunkFilename: '[name].chunk.js', + }, + resolve: { + extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs'], + alias: { + prettier$: require.resolve('prettier/standalone'), + }, + }, +}); diff --git a/workspaces/orchestrator/plugins/orchestrator/.eslintignore b/workspaces/orchestrator/plugins/orchestrator/.eslintignore new file mode 100644 index 00000000..b19336e4 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/.eslintignore @@ -0,0 +1,3 @@ +playwright.config.ts +dist/ +dist-types/ \ No newline at end of file diff --git a/workspaces/orchestrator/plugins/orchestrator/.eslintrc.js b/workspaces/orchestrator/plugins/orchestrator/.eslintrc.js new file mode 100644 index 00000000..e2a53a6a --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/workspaces/orchestrator/plugins/orchestrator/.lintstagedrc.json b/workspaces/orchestrator/plugins/orchestrator/.lintstagedrc.json new file mode 100644 index 00000000..14b2263d --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/.lintstagedrc.json @@ -0,0 +1,4 @@ +{ + "*": "prettier --ignore-unknown --write", + "*.{js,jsx,ts,tsx,mjs,cjs}": "backstage-cli package lint --fix" +} diff --git a/workspaces/orchestrator/plugins/orchestrator/.prettierignore b/workspaces/orchestrator/plugins/orchestrator/.prettierignore new file mode 100644 index 00000000..fc8357d9 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/.prettierignore @@ -0,0 +1,12 @@ +dist +dist-types +coverage +.vscode +CHANGELOG.md +generated +templates +*.hbs +renovate.json +dist-dynamic +dist-scalprum +playwright-report diff --git a/workspaces/orchestrator/plugins/orchestrator/.prettierrc.js b/workspaces/orchestrator/plugins/orchestrator/.prettierrc.js new file mode 100644 index 00000000..5f81a8a0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/.prettierrc.js @@ -0,0 +1,20 @@ +// @ts-check + +/** @type {import("@ianvs/prettier-plugin-sort-imports").PrettierConfig} */ +module.exports = { + ...require('@spotify/prettier-config'), + plugins: ['@ianvs/prettier-plugin-sort-imports'], + importOrder: [ + '^react(.*)$', + '', + '^@backstage/(.*)$', + '', + '', + '', + '^@red-hat-developer-hub/(.*)$', + '', + '', + '', + '^[.]', + ], +}; diff --git a/workspaces/orchestrator/plugins/orchestrator/CHANGELOG.md b/workspaces/orchestrator/plugins/orchestrator/CHANGELOG.md new file mode 100644 index 00000000..b0d73e69 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/CHANGELOG.md @@ -0,0 +1,812 @@ +### Dependencies + +## 2.4.0 + +### Minor Changes + +- 603a162: make error handling consistent in backend and UI + +### Patch Changes + +- 8a76b49: Makes very long workflow result messages still readable. +- b2a7181: Fix filtering by status on the Workflow Runs tab. +- Updated dependencies [aee9d4a] +- Updated dependencies [25f1787] + - @red-hat-developer-hub/backstage-plugin-orchestrator-form-react@1.4.2 + - @red-hat-developer-hub/backstage-plugin-orchestrator-common@1.24.0 + +## 2.3.2 + +### Patch Changes + +- 76674da: Fixes issue when WorkflowResult panel fails on malformed provided result. + +## 2.3.1 + +### Patch Changes + +- 0e6bfd3: feat: update Backstage to the latest version + + Update to Backstage 1.32.5 + +- Updated dependencies [0e6bfd3] +- Updated dependencies [67f466a] + - @red-hat-developer-hub/backstage-plugin-orchestrator-form-react@1.4.1 + - @red-hat-developer-hub/backstage-plugin-orchestrator-form-api@1.4.1 + - @red-hat-developer-hub/backstage-plugin-orchestrator-common@1.23.1 + +## 2.3.0 + +### Minor Changes + +- 8244f28: chore(deps): update to backstage 1.32 + +### Patch Changes + +- Updated dependencies [8244f28] + - @red-hat-developer-hub/backstage-plugin-orchestrator-form-react@1.4.0 + - @red-hat-developer-hub/backstage-plugin-orchestrator-form-api@1.4.0 + - @red-hat-developer-hub/backstage-plugin-orchestrator-common@1.23.0 + +## 2.2.1 + +### Patch Changes + +- 7342e9b: chore: remove @janus-idp/cli dep and relink local packages + + This update removes `@janus-idp/cli` from all plugins, as it’s no longer necessary. Additionally, packages are now correctly linked with a specified version. + +- Updated dependencies [7342e9b] + - @red-hat-developer-hub/backstage-plugin-orchestrator-form-react@1.3.1 + +## 2.2.0 + +### Minor Changes + +- d9551ae: feat(deps): update to backstage 1.31 + +### Patch Changes + +- d9551ae: Change local package references to a `*` +- d9551ae: pin the @janus-idp/cli package +- d9551ae: upgrade to yarn v3 +- Updated dependencies [d9551ae] +- Updated dependencies [d9551ae] +- Updated dependencies [d9551ae] +- Updated dependencies [d9551ae] + - @red-hat-developer-hub/backstage-plugin-orchestrator-form-react@1.3.0 + - @red-hat-developer-hub/backstage-plugin-orchestrator-common@1.22.0 + - @red-hat-developer-hub/backstage-plugin-orchestrator-form-api@1.3.0 + +* **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.2.1 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.21.0 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.2.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.21.0 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-api:** upgraded to 1.2.0 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.2.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.20.0 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.1.6 +- **@janus-idp/cli:** upgraded to 1.15.2 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.19.0 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.1.5 + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.15.1 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.18.2 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.1.4 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.18.1 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.1.3 + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.15.0 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.18.0 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.1.2 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.17.3 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.1.1 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-api:** upgraded to 1.1.0 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.1.0 +- **@janus-idp/cli:** upgraded to 1.14.0 + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.13.2 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.17.2 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.0.10 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.17.1 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.0.9 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.17.0 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.0.8 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.16.0 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.0.6 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.15.2 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.0.5 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.0.4 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.15.1 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.0.3 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.15.0 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.0.2 + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.13.1 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-api:** upgraded to 1.0.1 +- **@red-hat-developer-hub/backstage-plugin-orchestrator-form-react:** upgraded to 1.0.1 + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.14.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.18.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.18.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.18.1) (2024-08-02) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.13.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.18.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.17.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.18.0) (2024-07-26) + +### Features + +- **deps:** update to backstage 1.29 ([#1900](https://github.com/janus-idp/backstage-plugins/issues/1900)) ([f53677f](https://github.com/janus-idp/backstage-plugins/commit/f53677fb02d6df43a9de98c43a9f101a6db76802)) +- **orchestrator:** use v2 endpoints to retrieve instances ([#1956](https://github.com/janus-idp/backstage-plugins/issues/1956)) ([537502b](https://github.com/janus-idp/backstage-plugins/commit/537502b9d2ac13f2fb3f79188422d2c6e97f41fb)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.13.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.17.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.16.1...@red-hat-developer-hub/backstage-plugin-orchestrator@1.17.0) (2024-07-24) + +### Features + +- **deps:** update to backstage 1.28 ([#1891](https://github.com/janus-idp/backstage-plugins/issues/1891)) ([1ba1108](https://github.com/janus-idp/backstage-plugins/commit/1ba11088e0de60e90d138944267b83600dc446e5)) +- **orchestrator:** use v2 endpoints to retrieve workflow overviews ([#1892](https://github.com/janus-idp/backstage-plugins/issues/1892)) ([cca1e53](https://github.com/janus-idp/backstage-plugins/commit/cca1e53bc6b3019b1c544f2f62bed8723ebf6130)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.12.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.16.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.16.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.16.1) (2024-07-11) + +### Bug Fixes + +- **orchestrator:** returned scrolling bars to instance page cards ([#1880](https://github.com/janus-idp/backstage-plugins/issues/1880)) ([08545da](https://github.com/janus-idp/backstage-plugins/commit/08545daabd02a7ba6f9f12dedf237afbff1cd67a)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.16.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.15.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.16.0) (2024-06-28) + +### Features + +- **orchestrator:** remove unneeded orchestrator jira integration and endpoint ([#1833](https://github.com/janus-idp/backstage-plugins/issues/1833)) ([d2a76fd](https://github.com/janus-idp/backstage-plugins/commit/d2a76fd3db028f9774c821759bee5f38b7131c94)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.10.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.15.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.14.1...@red-hat-developer-hub/backstage-plugin-orchestrator@1.15.0) (2024-06-26) + +### Features + +- **orchestrator:** disable buttons based on permissions ([#1818](https://github.com/janus-idp/backstage-plugins/issues/1818)) ([36504b0](https://github.com/janus-idp/backstage-plugins/commit/36504b05d96dbbf0b2395dc6e5c155c21fa73bcd)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.14.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.14.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.14.1) (2024-06-19) + +### Bug Fixes + +- **matomo:** add default export for new backend system ([#1822](https://github.com/janus-idp/backstage-plugins/issues/1822)) ([5e72920](https://github.com/janus-idp/backstage-plugins/commit/5e72920209589535d503bb28e77f54175a0bd946)) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.11.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.14.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.13.7...@red-hat-developer-hub/backstage-plugin-orchestrator@1.14.0) (2024-06-13) + +### Features + +- **deps:** update to backstage 1.27 ([#1683](https://github.com/janus-idp/backstage-plugins/issues/1683)) ([a14869c](https://github.com/janus-idp/backstage-plugins/commit/a14869c3f4177049cb8d6552b36c3ffd17e7997d)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.9.0 +- **@janus-idp/cli:** upgraded to 1.11.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.13.7](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.13.6...@red-hat-developer-hub/backstage-plugin-orchestrator@1.13.7) (2024-06-13) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.10.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.13.6](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.13.5...@red-hat-developer-hub/backstage-plugin-orchestrator@1.13.6) (2024-06-05) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.10.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.13.5](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.13.4...@red-hat-developer-hub/backstage-plugin-orchestrator@1.13.5) (2024-06-04) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.8.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.13.4](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.13.3...@red-hat-developer-hub/backstage-plugin-orchestrator@1.13.4) (2024-06-03) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.9.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.13.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.13.2...@red-hat-developer-hub/backstage-plugin-orchestrator@1.13.3) (2024-05-31) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.13.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.13.1...@red-hat-developer-hub/backstage-plugin-orchestrator@1.13.2) (2024-05-29) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.10 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.13.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.13.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.13.1) (2024-05-29) + +### Bug Fixes + +- **orchestrator:** upgrade to mui v5 ([#1727](https://github.com/janus-idp/backstage-plugins/issues/1727)) ([8b935dc](https://github.com/janus-idp/backstage-plugins/commit/8b935dc3c85fbe4030564301820d946effa78426)) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.9 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.13.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.12.6...@red-hat-developer-hub/backstage-plugin-orchestrator@1.13.0) (2024-05-28) + +### Features + +- **orchestrator:** add permissions to orchestrator plugin ([#1599](https://github.com/janus-idp/backstage-plugins/issues/1599)) ([d0a4531](https://github.com/janus-idp/backstage-plugins/commit/d0a453181e177eb1da7b1e231253b76a2d9356a8)) +- **orchestrator:** label a Workflow assessment result as recommended ([#1705](https://github.com/janus-idp/backstage-plugins/issues/1705)) ([7e24e86](https://github.com/janus-idp/backstage-plugins/commit/7e24e86eb3094fa00b22aa77f79fb0e04dbf86f7)) + +### Bug Fixes + +- **deps:** update dependency monaco-editor to ^0.49.0 ([#1690](https://github.com/janus-idp/backstage-plugins/issues/1690)) ([34308a3](https://github.com/janus-idp/backstage-plugins/commit/34308a3ba669666ab2ddd61b2ac0073edd98f8ce)) +- **orchestrator:** bump `rjsf` dependencies ([#1715](https://github.com/janus-idp/backstage-plugins/issues/1715)) ([ea31cdb](https://github.com/janus-idp/backstage-plugins/commit/ea31cdbd7cb0a8842119f6d5d5dbd689e31040aa)) +- **orchestrator:** fix the common package reference version ([#1704](https://github.com/janus-idp/backstage-plugins/issues/1704)) ([942b2a3](https://github.com/janus-idp/backstage-plugins/commit/942b2a3b6eb29c0fe88f9c98dea581309d02fded)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.12.6](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.12.5...@red-hat-developer-hub/backstage-plugin-orchestrator@1.12.6) (2024-05-21) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.12.5](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.12.4...@red-hat-developer-hub/backstage-plugin-orchestrator@1.12.5) (2024-05-20) + +### Bug Fixes + +- **orchestrator:** fixes many security-related issues ([#1681](https://github.com/janus-idp/backstage-plugins/issues/1681)) ([3e801c8](https://github.com/janus-idp/backstage-plugins/commit/3e801c84015f925bdecd226a161ef81a5fc69432)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.12.4](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.12.3...@red-hat-developer-hub/backstage-plugin-orchestrator@1.12.4) (2024-05-16) + +### Bug Fixes + +- **orchestrator:** remove the need of react dev dependencies ([#1650](https://github.com/janus-idp/backstage-plugins/issues/1650)) ([5e60875](https://github.com/janus-idp/backstage-plugins/commit/5e60875932b906fd40e282d53b277a0f29efc67f)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.12.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.12.2...@red-hat-developer-hub/backstage-plugin-orchestrator@1.12.3) (2024-05-16) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.7 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.12.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.12.1...@red-hat-developer-hub/backstage-plugin-orchestrator@1.12.2) (2024-05-15) + +### Documentation + +- **orchestrator:** removes instructions related to the editor ([#1664](https://github.com/janus-idp/backstage-plugins/issues/1664)) ([10a75b2](https://github.com/janus-idp/backstage-plugins/commit/10a75b2706c72751bd774d6fae4332bbc527dc2b)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.7.2 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.12.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.12.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.12.1) (2024-05-15) + +### Bug Fixes + +- **orchestrator:** export the `OrchestratorPlugin` accordingly ([#1644](https://github.com/janus-idp/backstage-plugins/issues/1644)) ([4a9d1f8](https://github.com/janus-idp/backstage-plugins/commit/4a9d1f821a30437e73631fac98b1aabc65473fba)) + +### Other changes + +- **orchestrator:** add OrchestratorClient unit tests ([#1640](https://github.com/janus-idp/backstage-plugins/issues/1640)) ([2a2dc55](https://github.com/janus-idp/backstage-plugins/commit/2a2dc5581aa04b20bdf973ecb8310d179d6fd1a5)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.12.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.11.2...@red-hat-developer-hub/backstage-plugin-orchestrator@1.12.0) (2024-05-14) + +### Features + +- **deps:** use RHDH themes in the backstage app and dev pages ([#1480](https://github.com/janus-idp/backstage-plugins/issues/1480)) ([8263bf0](https://github.com/janus-idp/backstage-plugins/commit/8263bf099736cbb0d0f2316082d338ba81fa6927)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.11.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.11.1...@red-hat-developer-hub/backstage-plugin-orchestrator@1.11.2) (2024-05-13) + +### Bug Fixes + +- **orchestrator:** typos mentioning OpenShift ([#1639](https://github.com/janus-idp/backstage-plugins/issues/1639)) ([7ff4c75](https://github.com/janus-idp/backstage-plugins/commit/7ff4c754f73681e1a596d56721972af8872f3211)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.11.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.11.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.11.1) (2024-05-09) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.7.1 +- **@janus-idp/cli:** upgraded to 1.8.6 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.11.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.10.6...@red-hat-developer-hub/backstage-plugin-orchestrator@1.11.0) (2024-05-09) + +### Features + +- **orchestrator:** add ability to re-trigger workflow in error state ([#1624](https://github.com/janus-idp/backstage-plugins/issues/1624)) ([8709a37](https://github.com/janus-idp/backstage-plugins/commit/8709a37d08c2eafc22f10bd2a41f0a105768222d)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.7.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.10.6](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.10.5...@red-hat-developer-hub/backstage-plugin-orchestrator@1.10.6) (2024-05-06) + +### Bug Fixes + +- **orchestrator:** disabled MUI table thirdSortClick ([#1614](https://github.com/janus-idp/backstage-plugins/issues/1614)) ([5e541bd](https://github.com/janus-idp/backstage-plugins/commit/5e541bd217500c83bd8d9eb94cf060805ef4b8a9)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.10.5](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.10.4...@red-hat-developer-hub/backstage-plugin-orchestrator@1.10.5) (2024-05-02) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.5 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.10.4](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.10.3...@red-hat-developer-hub/backstage-plugin-orchestrator@1.10.4) (2024-05-02) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.4 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.10.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.10.2...@red-hat-developer-hub/backstage-plugin-orchestrator@1.10.3) (2024-05-02) + +### Bug Fixes + +- **orchestrator:** disable sorting ID column in workflow runs table ([#1595](https://github.com/janus-idp/backstage-plugins/issues/1595)) ([4d4875e](https://github.com/janus-idp/backstage-plugins/commit/4d4875eb4f91a3a3464b1ecbdcf647e9f1b84be5)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.10.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.10.1...@red-hat-developer-hub/backstage-plugin-orchestrator@1.10.2) (2024-04-30) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.3 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.10.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.10.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.10.1) (2024-04-30) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.2 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.10.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.9.4...@red-hat-developer-hub/backstage-plugin-orchestrator@1.10.0) (2024-04-25) + +### Features + +- **orchestrator:** add endpoint to retrigger workflow in error state ([#1343](https://github.com/janus-idp/backstage-plugins/issues/1343)) ([328d23a](https://github.com/janus-idp/backstage-plugins/commit/328d23a7992da125becc8d7775a4ebd68165f243)) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.9.4](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.9.3...@red-hat-developer-hub/backstage-plugin-orchestrator@1.9.4) (2024-04-18) + +### Bug Fixes + +- **orchestrator:** allows serving the editor envelope in disconnected environments ([#1450](https://github.com/janus-idp/backstage-plugins/issues/1450)) ([1e778d8](https://github.com/janus-idp/backstage-plugins/commit/1e778d88336dfec79d48ece4fd8d2a035133b70e)) + +### Documentation + +- **orchestrator:** fix quick start urls to private repo and make image urls raw ([#1521](https://github.com/janus-idp/backstage-plugins/issues/1521)) ([eefd264](https://github.com/janus-idp/backstage-plugins/commit/eefd2642b0dd3a2d6eb26eaf229c97a280adf07c)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.6.4 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.9.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.9.2...@red-hat-developer-hub/backstage-plugin-orchestrator@1.9.3) (2024-04-16) + +### Bug Fixes + +- fix typo in orchestrator documentation ([#1508](https://github.com/janus-idp/backstage-plugins/issues/1508)) ([bfa360a](https://github.com/janus-idp/backstage-plugins/commit/bfa360af97b5daf1902c267cd682e51cb6d71c83)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.9.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.9.1...@red-hat-developer-hub/backstage-plugin-orchestrator@1.9.2) (2024-04-15) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.8.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.9.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.9.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.9.1) (2024-04-15) + +### Documentation + +- **orchestrator:** add a quickstart for users ([#1499](https://github.com/janus-idp/backstage-plugins/issues/1499)) ([28fe8da](https://github.com/janus-idp/backstage-plugins/commit/28fe8da644350facb4c414f1bd5ff48ba4801b24)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.9.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.9...@red-hat-developer-hub/backstage-plugin-orchestrator@1.9.0) (2024-04-10) + +### Features + +- **orchestrator:** make workflow last run status as link to the workflow last run details page ([#1488](https://github.com/janus-idp/backstage-plugins/issues/1488)) ([fc2f94e](https://github.com/janus-idp/backstage-plugins/commit/fc2f94ed4ff2cb0795ba3b65eeea57eae3a8640c)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.8.9](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.8...@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.9) (2024-04-09) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.7.10 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.8.8](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.7...@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.8) (2024-04-09) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.7.9 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.8.7](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.6...@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.7) (2024-04-05) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.6.3 +- **@janus-idp/cli:** upgraded to 1.7.8 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.8.6](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.5...@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.6) (2024-04-04) + +### Documentation + +- **orchestrator:** add OpenAPI doc ([#1441](https://github.com/janus-idp/backstage-plugins/issues/1441)) ([f6275e2](https://github.com/janus-idp/backstage-plugins/commit/f6275e2b37f467e65c267f951db8c413a69eb923)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.6.2 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.8.5](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.4...@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.5) (2024-04-02) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.7.7 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.8.4](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.3...@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.4) (2024-03-29) + +### Bug Fixes + +- **orchestrator:** fixes v2/instances endpoint ([#1414](https://github.com/janus-idp/backstage-plugins/issues/1414)) ([88b49df](https://github.com/janus-idp/backstage-plugins/commit/88b49df35cf10e231ba69c239e873cb10e7cc25b)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.6.1 +- **@janus-idp/cli:** upgraded to 1.7.6 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.8.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.2...@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.3) (2024-03-26) + +### Bug Fixes + +- **orchestrator:** remove error on Reset workflow ([#1393](https://github.com/janus-idp/backstage-plugins/issues/1393)) ([6ce210d](https://github.com/janus-idp/backstage-plugins/commit/6ce210dfb3ac82a887985057ea234cf8b6065068)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.8.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.1...@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.2) (2024-03-17) + +### Bug Fixes + +- **orchestrator:** fix dropdown look ([#1344](https://github.com/janus-idp/backstage-plugins/issues/1344)) ([9284299](https://github.com/janus-idp/backstage-plugins/commit/9284299710f4d498deb098a94a2be57e6d7516a6)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.8.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.1) (2024-03-14) + +### Bug Fixes + +- **orchestrator:** update the installation instructions ([#1336](https://github.com/janus-idp/backstage-plugins/issues/1336)) ([d77e388](https://github.com/janus-idp/backstage-plugins/commit/d77e3887ee838a0d4ce075ab976203f13f2037c8)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.8.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.8...@red-hat-developer-hub/backstage-plugin-orchestrator@1.8.0) (2024-03-14) + +### Features + +- **orchestrator:** verify availability and cache workflow definition IDs ([#1309](https://github.com/janus-idp/backstage-plugins/issues/1309)) ([4d322f1](https://github.com/janus-idp/backstage-plugins/commit/4d322f1fc5b6f8b1afedf40cfe1b24b2edae2ac1)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.6.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.7.8](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.7...@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.8) (2024-03-12) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.5.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.7.7](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.6...@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.7) (2024-03-11) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.5.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.7.6](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.5...@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.6) (2024-03-11) + +### Other changes + +- **orchestrator:** add unit tests for v2 endpoints ([#1300](https://github.com/janus-idp/backstage-plugins/issues/1300)) ([9a13138](https://github.com/janus-idp/backstage-plugins/commit/9a13138c61d3cc7331f739da80f020bb68dd61e5)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.4.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.7.5](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.4...@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.5) (2024-03-07) + +### Bug Fixes + +- **orchestraotr:** resolved grey background appears in actions column in workflows table ([#1317](https://github.com/janus-idp/backstage-plugins/issues/1317)) ([cd7b4e7](https://github.com/janus-idp/backstage-plugins/commit/cd7b4e7267c804c75b4bccf927b48c32f7943ed6)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.7.4](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.3...@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.4) (2024-03-07) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.4.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.7.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.2...@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.3) (2024-03-07) + +### Bug Fixes + +- **orchestrator:** fix abort button and rerun button disable issue ([#1311](https://github.com/janus-idp/backstage-plugins/issues/1311)) ([0c98279](https://github.com/janus-idp/backstage-plugins/commit/0c982798872f2cb1a3b9fef7ab15850474cb03a7)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.7.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.1...@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.2) (2024-03-04) + +### Bug Fixes + +- **orchestrator:** walk around the state field is empty issue when fetch instance ([#1299](https://github.com/janus-idp/backstage-plugins/issues/1299)) ([e5c33c0](https://github.com/janus-idp/backstage-plugins/commit/e5c33c06fc66a6ff393365282f825c5fdc4713c9)) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.7.5 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.7.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.1) (2024-03-03) + +### Bug Fixes + +- **orchestrator:** stop fetching workflow URI ([#1297](https://github.com/janus-idp/backstage-plugins/issues/1297)) ([2456a28](https://github.com/janus-idp/backstage-plugins/commit/2456a287dbff955a0916b9600e89a39511cd537a)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.3.7 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.7.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.6.7...@red-hat-developer-hub/backstage-plugin-orchestrator@1.7.0) (2024-03-03) + +### Features + +- **orchestrator:** display a description modal before triggering infra-wfs that resulted from an assessment wf ([#1284](https://github.com/janus-idp/backstage-plugins/issues/1284)) ([ec293c9](https://github.com/janus-idp/backstage-plugins/commit/ec293c9e79efd77873e17d07b1511ad9fdda8842)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.6.7](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.6.6...@red-hat-developer-hub/backstage-plugin-orchestrator@1.6.7) (2024-02-29) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.3.6 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.6.6](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.6.5...@red-hat-developer-hub/backstage-plugin-orchestrator@1.6.6) (2024-02-28) + +### Bug Fixes + +- **orchestrator:** clean up the plugin code ([#1292](https://github.com/janus-idp/backstage-plugins/issues/1292)) ([ad27fb8](https://github.com/janus-idp/backstage-plugins/commit/ad27fb8e98913a6b80feb38ff58a7864e1953a7e)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.3.5 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.6.5](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.6.4...@red-hat-developer-hub/backstage-plugin-orchestrator@1.6.5) (2024-02-28) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.3.4 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.6.4](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.6.3...@red-hat-developer-hub/backstage-plugin-orchestrator@1.6.4) (2024-02-28) + +### Bug Fixes + +- **orchestrator:** handle nullable start/state properties of process instance ([#1277](https://github.com/janus-idp/backstage-plugins/issues/1277)) ([d8a43a5](https://github.com/janus-idp/backstage-plugins/commit/d8a43a5a164f83fc90d037ae3d7a355f5de543e0)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.3.3 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.6.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.6.2...@red-hat-developer-hub/backstage-plugin-orchestrator@1.6.3) (2024-02-27) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.3.2 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.6.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.6.1...@red-hat-developer-hub/backstage-plugin-orchestrator@1.6.2) (2024-02-27) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.7.4 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.6.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.6.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.6.1) (2024-02-26) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.7.3 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.6.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.5.2...@red-hat-developer-hub/backstage-plugin-orchestrator@1.6.0) (2024-02-22) + +### Features + +- **orchestrator:** display a alert dialog when the user fails to abort a running workflow ([#1239](https://github.com/janus-idp/backstage-plugins/issues/1239)) ([44cb11b](https://github.com/janus-idp/backstage-plugins/commit/44cb11b80739f772f4caa4c2834287eec162b826)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.5.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.5.1...@red-hat-developer-hub/backstage-plugin-orchestrator@1.5.2) (2024-02-21) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.3.1 +- **@janus-idp/cli:** upgraded to 1.7.2 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.5.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.5.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.5.1) (2024-02-20) + +### Bug Fixes + +- **orchestrator:** decommission the ProcessInstance.lastUpdate field ([#1230](https://github.com/janus-idp/backstage-plugins/issues/1230)) ([9724e27](https://github.com/janus-idp/backstage-plugins/commit/9724e27eaa84fe73d7724f28c86409681b7f79f8)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.3.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.5.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.4.3...@red-hat-developer-hub/backstage-plugin-orchestrator@1.5.0) (2024-02-18) + +### Features + +- **orchestrator:** display a confirmation dialog before the user aborts a running workflow ([#1215](https://github.com/janus-idp/backstage-plugins/issues/1215)) ([1453cf8](https://github.com/janus-idp/backstage-plugins/commit/1453cf8d42b14372c1a5c1973510450d24ae4b5a)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.4.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.4.2...@red-hat-developer-hub/backstage-plugin-orchestrator@1.4.3) (2024-02-16) + +### Bug Fixes + +- **orchestrator:** resolve mismatch between execution data and composed schema ([#1217](https://github.com/janus-idp/backstage-plugins/issues/1217)) ([af85114](https://github.com/janus-idp/backstage-plugins/commit/af851148935e1ed083709cac145520d7551de737)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.2.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.4.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.4.1...@red-hat-developer-hub/backstage-plugin-orchestrator@1.4.2) (2024-02-16) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.2.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.4.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.4.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.4.1) (2024-02-14) + +### Bug Fixes + +- **orchestrator:** the instance details card content is cropped ([#1196](https://github.com/janus-idp/backstage-plugins/issues/1196)) ([eb45070](https://github.com/janus-idp/backstage-plugins/commit/eb450709e8e34972386f4e34ee842208e323a3fb)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.4.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.3.3...@red-hat-developer-hub/backstage-plugin-orchestrator@1.4.0) (2024-02-12) + +### Features + +- build Information dialog component to show confirmation or alert ([#1176](https://github.com/janus-idp/backstage-plugins/issues/1176)) ([ee8cc1d](https://github.com/janus-idp/backstage-plugins/commit/ee8cc1dad2f10d698b8fb7e19ef0f9abe3b6c6c7)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.3.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.3.2...@red-hat-developer-hub/backstage-plugin-orchestrator@1.3.3) (2024-02-08) + +### Bug Fixes + +- **orchestrator:** resolve inconsistency with workflow run average duration format ([#1191](https://github.com/janus-idp/backstage-plugins/issues/1191)) ([0d82e90](https://github.com/janus-idp/backstage-plugins/commit/0d82e90a15fc8e90a4855188586986235394e3d3)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.3.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.3.1...@red-hat-developer-hub/backstage-plugin-orchestrator@1.3.2) (2024-02-07) + +### Bug Fixes + +- **orchestrator:** removes the divider from the workflow definition card ([#1181](https://github.com/janus-idp/backstage-plugins/issues/1181)) ([c2fe940](https://github.com/janus-idp/backstage-plugins/commit/c2fe940fa395842c705f1371872791fdbd77095c)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.3.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.3.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.3.1) (2024-02-05) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.7.1 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.3.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.2.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.3.0) (2024-02-02) + +### Features + +- **orchestrator:** add the ability to rerun workflows in a new instance ([#1141](https://github.com/janus-idp/backstage-plugins/issues/1141)) ([fe326df](https://github.com/janus-idp/backstage-plugins/commit/fe326df569caa5a9e7b7ec809c1c371a2a936010)) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.1.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.2.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.1.3...@red-hat-developer-hub/backstage-plugin-orchestrator@1.2.0) (2024-01-30) + +### Features + +- add new backend system support for existing backend plugins that have not been migrated over yet ([#1132](https://github.com/janus-idp/backstage-plugins/issues/1132)) ([06e16fd](https://github.com/janus-idp/backstage-plugins/commit/06e16fdcf64257dd08297cb727445d9a8a23c522)) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.7.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.1.3](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.1.2...@red-hat-developer-hub/backstage-plugin-orchestrator@1.1.3) (2024-01-30) + +### Bug Fixes + +- **orchestrator:** resolve bug in workflow instance page assessed by link ([#1142](https://github.com/janus-idp/backstage-plugins/issues/1142)) ([48724f8](https://github.com/janus-idp/backstage-plugins/commit/48724f8d90ec9927ed07382061bce78171ccb1b2)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.1.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.1.1...@red-hat-developer-hub/backstage-plugin-orchestrator@1.1.2) (2024-01-29) + +### Bug Fixes + +- **orchestrator:** fixes sorting by name in the workflows list ([#1135](https://github.com/janus-idp/backstage-plugins/issues/1135)) ([2a023e1](https://github.com/janus-idp/backstage-plugins/commit/2a023e156a69ca3cf102ba9a77f076e3289b60b4)) +- **orchestrator:** fixes sorting workflow runs ([#1136](https://github.com/janus-idp/backstage-plugins/issues/1136)) ([7c3d0f6](https://github.com/janus-idp/backstage-plugins/commit/7c3d0f62abf861faae82d84cf1d25213d1791dc5)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.1.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.1.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.1.1) (2024-01-25) + +### Bug Fixes + +- **orchestrator:** set default workflow runs table size to 20 ([#1127](https://github.com/janus-idp/backstage-plugins/issues/1127)) ([c5e14fd](https://github.com/janus-idp/backstage-plugins/commit/c5e14fd8e343df7d8c6db7f539fbbd2747e7792e)) + +### Documentation + +- **orchestrator:** adds a section about deploying as a dynamic plugins ([#1125](https://github.com/janus-idp/backstage-plugins/issues/1125)) ([eaff621](https://github.com/janus-idp/backstage-plugins/commit/eaff621cf39ab76909446616230de48512714187)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.1.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.0.2...@red-hat-developer-hub/backstage-plugin-orchestrator@1.1.0) (2024-01-25) + +### Features + +- **orchestrator:** add auto refresh to workflow instance list and details pages ([#1081](https://github.com/janus-idp/backstage-plugins/issues/1081)) ([fc30645](https://github.com/janus-idp/backstage-plugins/commit/fc30645ff740e914708a20f1fa1e2e118f771433)) + +### Dependencies + +- **@janus-idp/cli:** upgraded to 1.6.0 + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.0.2](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.0.1...@red-hat-developer-hub/backstage-plugin-orchestrator@1.0.2) (2024-01-24) + +### Bug Fixes + +- **orchestrator:** do not show duration when ProcessInstance.end time is n/a ([#1112](https://github.com/janus-idp/backstage-plugins/issues/1112)) ([75e6bbe](https://github.com/janus-idp/backstage-plugins/commit/75e6bbe8737742494817112b8da0fc50be5ff245)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator [1.0.1](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-orchestrator@1.0.0...@red-hat-developer-hub/backstage-plugin-orchestrator@1.0.1) (2024-01-18) + +### Bug Fixes + +- **orchestrator:** update the navigation bar icon according to UX ([#1078](https://github.com/janus-idp/backstage-plugins/issues/1078)) ([da3d8fc](https://github.com/janus-idp/backstage-plugins/commit/da3d8fc7a33f01729ead1d515d16ebefc47326c3)) + +## @red-hat-developer-hub/backstage-plugin-orchestrator 1.0.0 (2024-01-17) + +### Features + +- **orchestrator:** add orchestrator plugin ([#783](https://github.com/janus-idp/backstage-plugins/issues/783)) ([cf5fe74](https://github.com/janus-idp/backstage-plugins/commit/cf5fe74db6992d9f51f5073bbcf20c8c346357a1)), closes [#28](https://github.com/janus-idp/backstage-plugins/issues/28) [#38](https://github.com/janus-idp/backstage-plugins/issues/38) [#35](https://github.com/janus-idp/backstage-plugins/issues/35) [#21](https://github.com/janus-idp/backstage-plugins/issues/21) + +### Dependencies + +- **@red-hat-developer-hub/backstage-plugin-orchestrator-common:** upgraded to 1.0.0 diff --git a/workspaces/orchestrator/plugins/orchestrator/README.md b/workspaces/orchestrator/plugins/orchestrator/README.md new file mode 100644 index 00000000..9b62ee24 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/README.md @@ -0,0 +1,269 @@ +# Orchestrator Plugin for Backstage + +The Orchestrator for Backstage is a mechanism designed to facilitate the implementation and execution of developer self-service flows. It serves as a vital component that enhances and augments the existing scaffolder functionality of Backstage with a more flexible and powerful set of features including long-running and asynchronous flows. + +The orchestrator works harmoniously with other Backstage components such as the Software Catalog, permissions, and plugins as well as others. By leveraging its capabilities, organizations can orchestrate and coordinate developer self-service flows effectively. + +## Context + +The Backstage Orchestrator plugin aims to provide a better option to Scaffolder, based on workflows to have a more flexible and powerful tool that addresses the need by streamlining and automating processes, allowing developers to focus more on coding and innovation. + +The orchestrator relies on [SonataFlow](https://sonataflow.org/), a powerful tool for building cloud-native workflow applications. + +The main idea is to keep the same user experience for users, leveraging the UI components, input forms, and flow that Scaffolder provides, this way it should be straightforward for users and transparent no matter whether using Templates or Workflows, both can live together being compatible with integration points. + +The orchestrator controls the flow orchestrating operations/tasks that may be executed in any external service including Scaffolder Actions, this way it is possible to leverage any existing Action hence Software Templates can be easily migrated to workflows opening the door to extend them to more complex use cases. + +## Capabilities + +**Advanced core capabilities** + +- Stateful/long-lived +- Branching and parallelism +- Error management and compensation +- Event-driven supporting [CloudEvents](https://cloudevents.io) +- Audit logging +- Sub-flows +- Choreography +- Timer/timeout control +- Built-in powerful expression evaluation with JQ +- Low Code/No code +- Cloud-native architecture Kubernetes/OpenShift with Operator support +- OpenAPI / REST built-in integration etc. + +**Client-side tooling** + +- Orchestration visualization / graphical editor +- Integration with service catalog/actions +- GitHub integration +- Form generation +- Runtime monitoring of instances +- Dashboards +- Potential custom integrations (user interaction, notifications, etc.) + +## For administrators + +### Installation + +The Orchestrator plugin is composed of the following packages: + +- `@red-hat-developer-hub/backstage-plugin-orchestrator-backend` package connects the Backstage server to the Orchestrator. For setup process, see [Backend Setup](#setting-up-the-orchestrator-backend-package) +- `@red-hat-developer-hub/backstage-plugin-orchestrator` package contains frontend components for the Orchestrator plugin. For setup process, see [Frontend Setup](#setting-up-the-orchestrator-frontend-package) +- `@red-hat-developer-hub/backstage-plugin-orchestrator-common` package contains shared code between the Orchestrator plugin packages. + +#### Prerequisites for running the plugins locally in development mode + +- Docker up and running + +#### Setting up the Orchestrator as a dynamic plugin in a Red Hat Developer Hub Deployment + +- Follow https://github.com/janus-idp/backstage-showcase/blob/main/docs/dynamic-plugins/installing-plugins.md#installing-external-dynamic-plugins + +#### Setting up the configuration for the Orchestrator plugin + +The following configuration is required for the Orchestrator plugin to work properly: + +```yaml title="app-config.yaml" +backend: + csp: + frame-ancestors: ['http://localhost:3000', 'http://localhost:7007'] + script-src: ["'self'", "'unsafe-inline'", "'unsafe-eval'"] + script-src-elem: ["'self'", "'unsafe-inline'", "'unsafe-eval'"] + connect-src: ["'self'", 'http:', 'https:', 'data:'] +orchestrator: + sonataFlowService: + baseUrl: http://localhost + port: 8899 + autoStart: true + workflowsSource: + gitRepositoryUrl: https://github.com/parodos-dev/backstage-orchestrator-workflows + localPath: /tmp/orchestrator/repository + dataIndexService: + url: http://localhost:8899 +``` + +- When interacting with an existing SonataFlow infrastructure, the `sonataFlowService` config section must be entirely omitted and the `dataIndexService.url` must point to the existing Data Index Service. + +For more information about the configuration options, including other optional properties, see the [config.d.ts](../orchestrator-common/config.d.ts) file. + +#### Setting up the Orchestrator backend package + +1. Install the Orchestrator backend plugin using the following command: + + ```console + yarn workspace backend add @red-hat-developer-hub/backstage-plugin-orchestrator-backend + ``` + +1. Add the following code to the `packages/backend/src/index.ts` file: + + ```ts title="packages/backend/src/index.ts" + const backend = createBackend(); + + /* highlight-add-next-line */ + backend.add( + import('@red-hat-developer-hub/backstage-plugin-orchestrator-backend'), + ); + + backend.start(); + ``` + +#### Setting up the Orchestrator frontend package + +1. Install the Orchestrator frontend plugin using the following command: + + ```console + yarn workspace app add @red-hat-developer-hub/backstage-plugin-orchestrator + ``` + +1. Add a route to the `OrchestratorPage` and the customized template card component to Backstage App (`packages/app/src/App.tsx`): + + ```tsx title="packages/app/src/App.tsx" + /* highlight-add-next-line */ + import { OrchestratorPage } from '@red-hat-developer-hub/backstage-plugin-orchestrator'; + + const routes = ( + + {/* ... */} + {/* highlight-add-next-line */} + } /> + + ); + ``` + +1. Add the Orchestrator to Backstage sidebar (`packages/app/src/components/Root/Root.tsx`): + + ```tsx title="packages/app/src/components/Root/Root.tsx" + /* highlight-add-next-line */ + import { OrchestratorIcon } from '@red-hat-developer-hub/backstage-plugin-orchestrator'; + + export const Root = ({ children }: PropsWithChildren<{}>) => ( + + + }> + {/* ... */} + {/* highlight-add-start */} + + {/* highlight-add-end */} + + {/* ... */} + + {children} + + ); + ``` + +### Extensible Workflow Execution Form + +The `orchestrator` plugin includes an extensible form for executing forms. For detailed guidance see the [Extensible Workflow Execution Form Documentation](./docs/extensibleForm.md). + +## For users + +### Using the Orchestrator plugin in Backstage + +The Orchestrator plugin enhances the Backstage with the execution of developer self-service flows. It provides a graphical editor to visualize workflow definitions, and a dashboard to monitor the execution of the workflows. + +Refer to the [Quick start](https://github.com/janus-idp/backstage-plugins/blob/main/plugins/orchestrator/docs/quickstart.md) to install the Orchestrator using the helm chart and execute a sample workflow through the Red Hat Developer Hub orchestrator plugin UI. + +## OpenAPI + +The plugin provides OpenAPI `v2` endpoints definition to facilitate communication between the frontend and backend. This approach minimizes the data that needs to be sent to the frontend, provides flexibility and avoids dependencies on changes in the [CNCF serverless specification](https://github.com/serverlessworkflow/specification/blob/main/specification.md). It also allows for a seamless transition if there's a need to replace the backend implementation. + +In addition, by leveraging on OpenAPI spec, it is possible to generate clients and create CI steps. + +OpenAPI specification [file](https://github.com/janus-idp/backstage-plugins/blob/main/plugins/orchestrator-common/src/openapi/openapi.yaml) is available in [orchestrator-common](https://github.com/janus-idp/backstage-plugins/blob/main/plugins/orchestrator-common). +OpenAPI specification documentation is available [here](https://github.com/janus-idp/backstage-plugins/blob/main/plugins/orchestrator-common/src/generated/docs/markdown/README.md) + +> **NOTE:**\ +> While the OpenAPI specification is available in the Orchestrator plugin, the UI currently does not rely on this spec. \ +> We plan to incorporate v2 endpoints into the UI in the near future. + +### orchestrator-common + +The typescript schema is generated in [auto-generated](https://github.com/janus-idp/backstage-plugins/blob/main/plugins/orchestrator-common/src/auto-generated/api/models/schema.ts) folder from openapi.yaml specification file. + +### orchestrator-backend + +The orchestrator backend can use the generated schema to validate the HTTP requests and responses. + +> NOTE: Temporary the validation has been disabled. It will be enabled when the orchestrator frontend will switch to the use of v2 endpoints only. + +#### audit log + +The orchestrator backend has audit logs for all incoming requests. + +For more information about audit logs in RHDH, please refer to [the official documentation](https://docs.redhat.com/en/documentation/red_hat_developer_hub/1.2/html/getting_started_with_red_hat_developer_hub/assembly-audit-log#con-audit-log-config_assembly-audit-log). +[The official Log storage OpenShift documentation](https://docs.openshift.com/container-platform/4.15/observability/logging/log_storage/about-log-storage.html) may also be of interest. + +#### Development instruction + +Checkout the backstage-plugin + +`git clone git@github.com:janus-idp/backstage-plugins.git` + +If you need to change the OpenAPI spec, edit the [openapi.yaml](https://github.com/janus-idp/backstage-plugins/blob/main/plugins/orchestrator-common/src/openapi/openapi.yaml) according to your needs and then execute from the project root folder: + +`yarn --cwd plugins/orchestrator-common openapi` + +This command updates the [auto-generated files](https://github.com/janus-idp/backstage-plugins/blob/main/plugins/orchestrator-common/src/auto-generated/api/) and the [auto-generated docs](https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/orchestrator/plugins/orchestrator-common/src/auto-generated/docs). + +> NOTE: Do not manually edit auto-generated files + +If you add a new component in the spec, then you need to export the generated typescript object [here](https://github.com/janus-idp/backstage-plugins/blob/main/plugins/orchestrator-common/src/openapi/types.ts). For example, if you define + +```yaml +components: + schemas: + Person: + type: object + properties: + name: + type: string + surname: + type: string +``` + +then + +```typescript +export type Person = components['schemas']['Person']; +``` + +When defining a new endpoint, you have to define the `operationId`. +That `id` is the one that you can use to implement the endpoint logic. + +For example, let's assume you add + +```yaml +paths: + /names: + get: + operationId: getNames + description: Get a list of names + responses: + '200': + description: Success + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Person' +``` + +Then you can implement the endpoint in [router.ts](https://github.com/janus-idp/backstage-plugins/blob/main/plugins/orchestrator-backend/src/service/router.ts) referring the operationId `getNames`: + +```typescript +api.register('getNames', async (_c, _req, res: express.Response, next) => { + // YOUR LOGIC HERE + const result: Person[] = [ + { name: 'John', surname: 'Snow' }, + { name: 'John', surname: 'Black' }, + ]; + + res.status(200).json(result); +}); +``` diff --git a/workspaces/orchestrator/plugins/orchestrator/app-config.yaml b/workspaces/orchestrator/plugins/orchestrator/app-config.yaml new file mode 100644 index 00000000..7320e931 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/app-config.yaml @@ -0,0 +1,14 @@ +dynamicPlugins: + frontend: + red-hat-developer-hub.backstage-plugin-orchestrator: + appIcons: + - name: orchestratorIcon + module: OrchestratorPlugin + importName: OrchestratorIcon + dynamicRoutes: + - path: /orchestrator + importName: OrchestratorPage + module: OrchestratorPlugin + menuItem: + icon: orchestratorIcon + text: Orchestrator diff --git a/workspaces/orchestrator/plugins/orchestrator/catalog-info.yaml b/workspaces/orchestrator/plugins/orchestrator/catalog-info.yaml new file mode 100644 index 00000000..d320e9ac --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/catalog-info.yaml @@ -0,0 +1,51 @@ +# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: red-hat-developer-hub-orchestrator + title: Orchestrator plugin + description: Orchestrator Plugin for Backstage + annotations: + backstage.io/source-location: url:https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/orchestrator/plugins/orchestrator + backstage.io/view-url: https://github.com/redhat-developer/rhdh-plugins/blob/main/workspaces/orchestrator/plugins/orchestrator/catalog-info.yaml + backstage.io/edit-url: https://github.com/redhat-developer/rhdh-plugins/edit/main/workspaces/orchestrator/plugins/orchestrator/catalog-info.yaml + github.com/project-slug: red-hat-developer-hub/backstage-plugins + github.com/team-slug: red-hat-developer-hub/orchestrator-codeowners + sonarqube.org/project-key: red_hat_developer_hub_plugins + links: + - url: https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/orchestrator/plugins/orchestrator + title: GitHub Source + icon: source + type: source +spec: + type: backstage-plugin + lifecycle: production + owner: orchestrator-team + system: rhdh + subcomponentOf: red-hat-developer-hub-plugins +--- +# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: red-hat-developer-hub-orchestrator-frontend + title: '@red-hat-developer-hub/backstage-plugin-orchestrator' + description: Orchestrator Plugin for Backstage + annotations: + backstage.io/source-location: url:https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/orchestrator/plugins/orchestrator + backstage.io/view-url: https://github.com/redhat-developer/rhdh-plugins/blob/main/workspaces/orchestrator/plugins/orchestrator/catalog-info.yaml + backstage.io/edit-url: https://github.com/redhat-developer/rhdh-plugins/edit/main/workspaces/orchestrator/plugins/orchestrator/catalog-info.yaml + github.com/project-slug: red-hat-developer-hub/backstage-plugins + github.com/team-slug: red-hat-developer-hub/orchestrator-codeowners + sonarqube.org/project-key: red_hat_developer_hub_plugins + links: + - url: https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/orchestrator/plugins/orchestrator + title: GitHub Source + icon: source + type: source +spec: + type: backstage-frontend-plugin + lifecycle: production + owner: orchestrator-team + system: rhdh + subcomponentOf: red-hat-developer-hub-orchestrator diff --git a/workspaces/orchestrator/plugins/orchestrator/dev/index.tsx b/workspaces/orchestrator/plugins/orchestrator/dev/index.tsx new file mode 100644 index 00000000..1f072b9a --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/dev/index.tsx @@ -0,0 +1,32 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; + +import { createDevApp } from '@backstage/dev-utils'; + +import { getAllThemes } from '@redhat-developer/red-hat-developer-hub-theme'; + +import { OrchestratorPage, orchestratorPlugin } from '../src'; + +createDevApp() + .registerPlugin(orchestratorPlugin) + .addThemes(getAllThemes()) + .addPage({ + element: , + title: 'Root Page', + path: '/orchestrator', + }) + .render(); diff --git a/workspaces/orchestrator/plugins/orchestrator/docs/Permissions.md b/workspaces/orchestrator/plugins/orchestrator/docs/Permissions.md new file mode 100644 index 00000000..80151873 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/docs/Permissions.md @@ -0,0 +1,47 @@ +The Orchestrator plugin protects its backend endpoints with the builtin permission mechanism and combines it with +the RBAC plugin. The result is control over what users can see or execute. + +## Orchestrator Permissions + +| Name | Resource Type | Policy | Description | Requirements | +| ----------------------------------- | -------------- | ------ | ----------------------------------------------------------------- | ------------ | +| orchestrator.workflowInstances.read | named resource | read | Allows the user to read orchestrator workflows overview | | +| orchestrator.workflowInstance.read | named resource | read | Allows the user to read the details of a single workflow instance | | +| orchestrator.workflowInstance.abort | named resource | use | Allows the user to abort a workflow instance | | +| orchestrator.workflow.read | named resource | read | Allows the user to read the workflow definitions | | +| orchestrator.workflow.execute | named resource | use | Allows the user to execute a workflow | | + +## Policy File + +To get started with policies, we recommend defining 2 roles and assigning them to groups or users. + +See the example [policy file](./rbac-policy.csv) + +```csv +p, role:default/workflowViewer, orchestrator.workflowInstances.read, read, allow +p, role:default/workflowViewer, orchestrator.workflowInstance.read, read, allow + +p, role:default/workflowAdmin, orchestrator.workflow.read, read, allow +p, role:default/workflowAdmin, orchestrator.workflow.execute, use, allow +p, role:default/workflowAdmin, orchestrator.workflowInstance.abort, use, deny +p, role:default/workflowAdmin, orchestrator.workflowInstances.read, read, allow +p, role:default/workflowAdmin, orchestrator.workflowInstance.read, read, allow + +g, user:default/guest, role:default/workflowViewer +g, user:default/myOrgUser, role:default/workflowAdmin +g, group:default/platformAdmins, role:default/worflowAdmin +``` + +See https://casbin.org/docs/rbac for more information about casbin rules + +## Enable permissions + +To enable permissions, you need to add the following in the [app-config file](../../../app-config.yaml): + +``` +permission: + enabled: true + rbac: + policies-csv-file: + policyFileReload: true +``` diff --git a/workspaces/orchestrator/plugins/orchestrator/docs/executePageNext.png b/workspaces/orchestrator/plugins/orchestrator/docs/executePageNext.png new file mode 100644 index 0000000000000000000000000000000000000000..0d6c04ad5a2d0fc01405069b8b9fd5fe7f107b24 GIT binary patch literal 138885 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 literal 0 HcmV?d00001 diff --git a/workspaces/orchestrator/plugins/orchestrator/docs/executePageRun.png b/workspaces/orchestrator/plugins/orchestrator/docs/executePageRun.png new file mode 100644 index 0000000000000000000000000000000000000000..108d87a5069c065448f8db5fbc0f9f95be8fa06e GIT binary patch literal 135197 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 OcT~0Dxqtrk=6?Zyh{H|* literal 0 HcmV?d00001 diff --git a/workspaces/orchestrator/plugins/orchestrator/docs/extensibleForm.md b/workspaces/orchestrator/plugins/orchestrator/docs/extensibleForm.md new file mode 100644 index 00000000..7a6d7e46 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/docs/extensibleForm.md @@ -0,0 +1,136 @@ +# Extensible Workflow Execution Form + +This capability enables developers to extend and customize the `react-jsonschema-form` workflow execution form component. It is designed to enable developers to implement a Backstage plugin that provides a custom decorator for the workflow execution form. This decorator supports overriding a selected set of [react-json-schema-form properties](https://rjsf-team.github.io/react-jsonschema-form/docs/api-reference/form-props) enabling the following features: + +- **Custom Validations:** Extend default JSON schema validation with: + - **Synchronous Validation** via the `customValidate` property. + - **Asynchronous Validation** via the `getExtraErrors` property, for validations requiring backend calls. +- **Custom Components:** Replace default form components by overriding the `widgets` property. +- **Interdependent Field Values:** Manage complex inter-field dependencies using the `onChange` and `formData` properties. + +The custom decorator is delivered via a factory method that leverages a [Backstage utility API](https://backstage.io/docs/api/utility-apis) provided by the orchestrator. To trigger the desired behavior, the workflow schema should include custom UI properties. + +For reference, an example plugin can be found [here](https://github.com/parodos-dev/custom-form-example-plugin). + +## API + +To implement the API, include @red-hat-developer-hub/backstage-plugin-orchestrator-form-api package as a dependency. +This package provides the `FormExtensionsApi` interface and related types. + +```typescript +export type FormDecoratorProps = Pick< + FormProps, + 'formData' | 'formContext' | 'widgets' | 'onChange' | 'customValidate' +> & { + getExtraErrors?: ( + formData: JsonObject, + ) => Promise> | undefined; +}; + +export type FormDecorator = ( + FormComponent: React.ComponentType, +) => React.ComponentType; + +export interface FormExtensionsApi { + getFormDecorator(schema: JSONSchema7): FormDecorator; +} +``` + +### Example API Implementation + +```typescript +class CustomFormApi implements FormExtensionsApi { + getFormDecorator(schema: JSONSchema7) { + return (FormComponent: React.ComponentType>) => { + const widgets = {CountryWidget}; // CountryWidget needs to be implemneted and imported + return () => ; + }; + } +} +``` + +### Plugin Creation Example + +```typescript +export const formApiFactory = createApiFactory({ + api: orchestratorFormApiRef, + deps: {}, + factory() { + return new CustomFormApi(); + }, +}); + +export const testFactoryPlugin = createPlugin({ + id: 'custom-form-plugin', + apis: [formApiFactory], +}); +``` + +### Schema example for above plugin + +```typescript +{ + "type": "object", + "properties": { + "personalDetails": { + "type": "object", + "title": "Personal Details", + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "country": { + "type": "string", + "title": "Country", + "ui:widget": "CountryWidget" + } + } + }, + "contactDetails": { + "type": "object", + "title": "Contact Details", + "properties": { + "email": { + "type": "string", + "title": "Email" + }, + "phone": { + "type": "string", + "title": "Phone Number" + } + } + } + } +} +``` + +### Dynamic plugin configuration example + +Add the following to backstage config to integrate the plugin: + +```yaml +dynamicPlugins: + frontend: + custom-form-plugin: + apiFactories: + - importName: formApiFactory +``` + +### Referencing the custom behavior in the schema + +The workflow execution schema adheres to the [json-schema](https://json-schema.org/) format, which allows for extending the schema with custom properties beyond the official specification. This flexibility enables the inclusion of additional [uiSchema](https://rjsf-team.github.io/react-jsonschema-form/docs/api-reference/uiSchema/) fields directly within the schema, as demonstrated in the example above. + +### How It All Comes Together + +The `orchestrator-form-react` plugin implements the form component for workflow execution. It integrates with the custom API provided by the developer's plugin to generate and customize the form. The `orchestrator` plugin then incorporates this form into the workflow execution page. + +The `orchestrator-form-react` plugin handles the following key tasks: + +- **Generating the UI Schema:** It extracts custom UI schema fields from the main schema, automatically generates the [uiSchema](https://rjsf-team.github.io/react-jsonschema-form/docs/api-reference/uiSchema/), and passes it to the `react-jsonschema-form` component, enabling advanced UI customizations. + +- **Organizing Forms into Wizard-Style Steps:** If the schema is an object containing nested objects (i.e., the root is an object, and its properties are also objects), the plugin organizes the form into multiple steps. Each nested object becomes a separate step in a wizard-style interface. For example, the schema provided above results in two steps: _Personal Details_ and _Contact Details_. + +The [`orchestrator-form-react`](https://github.com/janus-idp/backstage-plugins/tree/main/plugins/orchestrator-form-react) plugin is designed to operate independently of the main orchestrator plugin. This modularity allows developers to test and validate form behavior in a standalone Backstage development environment before integrating it with the full orchestrator setup. + +To use this plugin, add the `@red-hat-developer-hub/backstage-plugin-orchestrator-form-react` package as a dependency in your project. diff --git a/workspaces/orchestrator/plugins/orchestrator/docs/orchestratorIcon.png b/workspaces/orchestrator/plugins/orchestrator/docs/orchestratorIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..7fb8aade99555000d06bce0d4cdcc0b9e9952354 GIT binary patch literal 136688 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 literal 0 HcmV?d00001 diff --git a/workspaces/orchestrator/plugins/orchestrator/docs/quickstart.md b/workspaces/orchestrator/plugins/orchestrator/docs/quickstart.md new file mode 100644 index 00000000..064cb238 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/docs/quickstart.md @@ -0,0 +1,35 @@ +## Quickstart Guide + +This quickstart guide will help you install the Orchestrator using the helm chart and execute a sample workflow through the Red Hat Developer Hub orchestrator plugin UI. + +1. **Install Orchestrator**: + Follow the [installation instructions for Orchestrator](https://www.parodos.dev/orchestrator-helm-chart/). + +2. **Install a sample workflow**: + Follow the [installation instructions for the greetings workflow](https://github.com/parodos-dev/serverless-workflows-config/blob/gh-pages/docs/greeting/README.md). + +3. **Access Red Hat Developer Hub**: + Open your web browser and navigate to the Red Hat Developer Hub application. Retrieve the URL using the following OpenShift CLI command. + + ```bash + oc get route backstage-backstage -n rhdh-operator -o jsonpath='{.spec.host}' + ``` + + Make sure the route is accessible to you locally. + +4. **Login to backstage** + Login to backstage with the Guest account. + +5. **Navigate to Orchestrator**: + Navigate to the Orchestrator page by clicking on the Orchestrator icon in the left navigation menu. + ![orchestratorIcon](https://raw.githubusercontent.com/janus-idp/backstage-plugins/main/plugins/orchestrator/docs/orchestratorIcon.png) + +6. **Execute Greeting Workflow**: + Click on the 'Execute' button in the ACTIONS column of the Greeting workflow. + ![workflowsPage](https://raw.githubusercontent.com/janus-idp/backstage-plugins/main/plugins/orchestrator/docs/workflowsPage.png) + The 'Run workflow' page will open. Click 'Next step' and then 'Run' + ![executePageNext](https://raw.githubusercontent.com/janus-idp/backstage-plugins/main/plugins/orchestrator/docs/executePageNext.png) + ![executePageRun](https://raw.githubusercontent.com/janus-idp/backstage-plugins/main/plugins/orchestrator/docs/executePageRun.png) +7. **Monitor Workflow Status**: + Wait for the status of the Greeting workflow execution to become _Completed_. This may take a moment. + ![workflowCompleted](https://raw.githubusercontent.com/janus-idp/backstage-plugins/main/plugins/orchestrator/docs/workflowCompleted.png) diff --git a/workspaces/orchestrator/plugins/orchestrator/docs/rbac-policy.csv b/workspaces/orchestrator/plugins/orchestrator/docs/rbac-policy.csv new file mode 100644 index 00000000..6dbc6d70 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/docs/rbac-policy.csv @@ -0,0 +1,11 @@ +p, role:default/workflowViewer, orchestrator.workflowInstances.read, read, allow +p, role:default/workflowViewer, orchestrator.workflowInstance.read, read, allow + +p, role:default/workflowAdmin, orchestrator.workflow.read, read, allow +p, role:default/workflowAdmin, orchestrator.workflow.execute, use, allow +p, role:default/workflowAdmin, orchestrator.workflowInstance.abort, use, allow +p, role:default/workflowAdmin, orchestrator.workflowInstances.read, read, allow +p, role:default/workflowAdmin, orchestrator.workflowInstance.read, read, allow + +g, user:default/guest, role:default/workflowViewer +g, user:default/rgolangh, role:default/workflowAdmin diff --git a/workspaces/orchestrator/plugins/orchestrator/docs/workflowCompleted.png b/workspaces/orchestrator/plugins/orchestrator/docs/workflowCompleted.png new file mode 100644 index 0000000000000000000000000000000000000000..1180e60edd63adc4f84a29faa56478dc294dd027 GIT binary patch literal 248260 zcmXt$SLJ{fih5=;g7?AGHp=;>wMv+dbfgz;B#M+7T*B(irH(=txLN7_u@FYDh>Zu1HA8ZK$vReOXaJ8~gV_b`_J= zKt)AeURVC}@AZ?Lq>h`qqlKHNi3u@7cF;~*<>pG6O{5xs z_<1W8Qzes&waLoyjq#W{bc>n=wH5uaRv+KP)5n7i1bUh|i7RSt*#labXuO799udo>iY?A(EGo>5F5Vhy04l`1AG@s zImYa?qa4p^5K>3kH+j3-Klz-xMNp_0eUmT^QBd>g zZag{Q@t4~2&SPygV%K$gk~YH{4z?EcTH9{M+Sb-nUU1f^8>>I;h*~;ap^0T;NcU0Ieu29{L+3hIlT=y^?SQS8LN3G7RhE*Uukx0(p^P zj6Q?)E98(V%W?)LsD1+4@(wO6zg(~!BOjlnzDh2A_|1)e{1A0Nhs_1d8BoBW>aA6l zoXo<)qNO1vUGw|n{Mz8*&oWE5f{E;!iBqljYgJ2r={x12pM4TmaLh?!IOftHHWF*E zN5NDt(@M%M9t-Kxb)z#iR#CM25q@RKJ5oPPTF8mb?8-up(CQZ)w$A4WdgSP5oD3WD zT=nZ&4Fac9BGzizmLy zdOA7!CXF`nG}%7v_~G_hokA}`_N){kvENyhXx>phHL*>Q;4x$A#M8C_VY#4gIhnhe zkFYNs8FkXKIZ`j97Rn>UnSkWp+!ayd%oOfcB=7UrmT>_;CBL&RNn+1wP9z_dX=CuO zcUeln4vWvFYbiKdaf~A!I%&nk$KUX2LWPBsQ#NfTgc(@kTo$pUm#8< zNqI{7A#H&{;)Ch^`GXw@4g4i&c7Bvo5f{iDBP{Zd@4_D z&oG=JlNMI&ThwuO1}VyTZE`5PT!V-gPRA!wn887K7qlqUWg(W5^OwaC@r7!pX_3iJ z*;C|&jU#)>^>e7o_Ief9m70^{ws_;ftPZtu=#4>uXT=f@Nfgv(wR+Wlc1o8=cc-xv z753(W>~4h#)}30%J$^EoC<`OCECE}qzsW7nYEi(SKBtD{TG}-G*=Ut%YE_n(>%&-$ z9Ewjw6)U+4HR$`wmP`!!A?%FR>yx_-wrjW;@wq*hilEZ;TBjtRY+V=IJE4s1AtmdT zq2OfmCR2JQ9yJ$l4CXebBU%448Q)`I-XLc5F~hXyw%P}S2m+R+U13u_p8in+fGZt) z<>i|0vQ^h(TQ;&%as({}w&#S($GNoUfa!UHXp14TAFryFfLsr3y`S8(VfKj;vXhhM z6tDW6bIC1k^L3q6;+E6{g+aqT!@>6IVnX?E3_zvKiR3L48-K2?6!ZU8fQIUU0Tvpzlq1Ltp zW-F2Kl*RL(@7G=5$aR&bdJ_B# z3Iy#oN9lNvFOW-fo6Y^EkQ-;Lh&tBGH2T)di0u_#5v=p!ulLJRy<|g%G~HkC&uLTa z%+A5N3^K|qvGeuH12gmDm0LXYQAhBX)?$CU;myq%^WfG|SIakb-n5eDyHA+`4L8{> zLYY@T`PhwIAN^}>CsG=It~bnfA3YEkAJ*{uF7(C8aO1O0vG&d3k>$C98}aJ=m+U>} z;QrTu)CE_M7#UlBYMezIcaHD+h-j{2g-gHCi2!oA;~8H)myr-aq;qUI9^6PQW!^pZ z9`r}1KC16Eyo}0#m+rSknbS(8O6zRbxw;<{oaYCmL+bo1sqof!!#LxVHF!R z5HIc7Y0TfpJ##^ETh43)p8MgEZiNwPdhvFxKNl51W)$eSeL8Pm(YUw1dAjFUHSR%}4374)+s`DRUCnu#Z}@z2(A;*cOAlpP~}e)v0zBU9{iLuwbvDSQLW z--f{Qp(q+N+OmQXG9!A>z-<6JFsDVD1uqvEF)M!Rn*luQ2v@kwVsTDTyWI%ZchTQg z#g&N!$<{OuPWdcqt&$g-bHvB(YwZMkhn@Tt%Ejh$Yq313bG*MlT*=7H#xxZi3_OKU z_qoa-qTh-iypFukNf&uo)ESXa-6}Y!@o-=KETo{gr>|diYlKmgtBc=at#IT@ZG;jr zcnS5KqVigjn`U`VSv!%e5!y>YJMwDF<$r|$n_ekixP)fi_U=dG*&V;STa0|CE7PBg z&ztvXPRPy3aP-C;hmo))LHkpR@?uwhoHgNeG|i003Y}8`p@p-_a#prun68|rLeWnv zvnE7%3eNGmH%kftrwv(k+ZTwRoDyM(Swjc|yl%{*#rX|S@;8;=X=P8$(jlAjRBT7k zhWf@>22Gpi%IKB##d}b$7A>A{CF8N&YgGh&d&S{9`Wl*CEWnaTNF$e!<=g7qUn!@o zJ7+aLk({NjdLA+vBHsO)p%RPSmRS@2u4XFEm2NF^w^QQE`y%{V_BGbf|sG|LpAT{@nBRQgBu~D)Dn7{FUQ2$(pnAwbDsYXge%jof znX4cd8yg(?#Dt*jNYOCQHNQ#pcAe)u_!nW|zedsJlE&TisorF7$$v^Y~7O?%~+KbC8{e5^_>s;H9HzO_@^zrDz z#y@;`%tefI`Q>Pn%mVvGaSSlYOe<$MyydAv!T1(GG)ptobl;H+62(~^q<$=@6%G+0 zFo8~+n+nav@?9u+(-(NXJ1C+|Ojs?Q+2o$whugZ#nG{1Mie-o%+DO;p1&kS2Z0Omn5UkQngUQw2RGb0tUEEo-=}h83kPUz zh$>BpJFniLvpX!*Zj<$OGofiZESI>}q*;!^MY`&j|VX z@n2WU-qW?Ke$?YGGb9#zP_GOMj0|8>m4HSbG80Q>kgNGTVc~PJ9lgrVqhds7%F4IP zZhH*tWNFoLcKXEq{aJAw96#a3JwxxSzQ90c6;o_2*H&CA^E_-%4jo8 z%c_vp%hjvTA3#Z)kM(og&GtMvH9OKV7X%XIe{u13(-Y!-xNhzF{TzC~=f@PeKKo#y zw5CES6k*}vgN)jC=X9oY*mjcWrI{zL)Wo_&K)EA$TOE@#22ync5U2)ju5GcwyS$>S zxtDn*jfXK>_1?N8EUC=w&eEQ%!)Vw6UVnYK%Oc5&b^w$pd(N{B?yWwY12_0f5~F=U z#21%ops;4X+3FjhQBKqEN~wGBPnsCs#Rd9niFMIc)QCuWo`iQ0NvcJ^qNv%mm4{pJ z?AYf~`6y1R&nGpfo(=^TggbKiymusFl$T;<#geY!<l8;2&{EdY;O-|dM5W@YXw=}9XU#$ z08F4Ml>;lYeE=3`oM3z^QN{r65)~gwnmE+EvQx<^kfG^J`u6=4uNT*Rkvgq(v(|JL z$6&I%p}!K6Gs*%^oa>mgUQyP`fQw!pPR#C*E_S24>DU~{uX^35&CTfL(u2^t@tgZO z$U6znpsmM(rS9kQfeGBd8Fa8!rtPWh|36O7s9?G6WBuip-=Vy%&$!8Z*idO>ow-9o794|YCQM2}YHx>pcYR{fZzRNdUw^lJZL09cq&<}Nq!XLO z2#HItWWwd#LIh8jbqO$iKwt|plTC=duF&!a-+UBswH3q;!Pr~qR;6cHIyq({l%PLWc*w_eDQwi!=*9kvQ((g zR1S_&z7pDp57TlieOWAC5SF)M@Q6waZSKGBg)^JJxNAwdDJ7KAXxBarr|#?Vc#Aj; z&*?FO$5#nmn)qDe7X0uHCf6{%9pXqQb62Ga9+YkU%SW>>NME*XE1NCQ;B9W8?aJ}G zZN%2SPv6V_gDf4prEmXN3FR938hXgsO`S?dx2kB5q;FO%QHqiUqxBP(D+F6OtpZtb z$b!MiC{pl^Z5Ta$sqNeZ2i`cBB?S&;Jv2iLy~tJ@0K|6cP7=zbabJG}Q%+nZ+S<)O z!RLB;Md$+?bQkbDoo|PGym5`)^Mp6;DW1c{LRC~=F z;cUtQoK#NHJj!8hJqGxw#+n)6dDH*ZN-Pzgo#muXR9PCSjJAcxs2jEumul81;)}B9 z)5#4=K%8qP;W?K$-*Q1rkVbd;vR=mI=Bmn@Xa%)ctc&Y~GaGo4+()Wx6T0iq$~|`s z1>#?@ey*1^1XRbBgvn6RMM;t3H{-C~^prkbuAR6uj96vjhFysiTuc(0*{|A(!C}0_ zp&{CEOr20mBJ|QNK-3?$x6$VL{$xfOr>^LM4%j3)t!_#tec9SIvveLhySAVVKja_1 z*=2EL{x3!S@DCGaTLF$jh@WTQQ(^+G0Zu-M-BF#9b;k3ElrH@g^Jh(!k@nGW33F%m zwhJF+pYBICbhv&81F`O-)g`T3&!pYJ}V=%;_X+FA}k4+%MVK>)g2S|4an#0f^2ynX@p) z9re`c+gJ6$96KnQrTIwj6Nk-^%+G;=@RB^!m)OQ_?bV2V zV1=g&W}>K1;xrU~lSu5d`Z!!JlL~?4FAzyMf049nnFVTOC6EuhPVf7z6)XhDXQ3t> z#wM-SnmTHdn@8s<{Ie1|9L(_K$u|qIYP12@LYWnOs`Q{ko7`);U#z9Tv$9XUHiEKC zOqvD4B935KzK-^B(x1pU_3ImV0fD#}ZF*Cv{JNg{Ms+xf$Xj1>1dAC7iC{BT7r@ge zHuqGBqOrR0vzfH{pz%M5@t=K`wF6sJ5t@;V`|W7KbqwoydJa2%*2KSFWcP z;AMg)LKKo|NSb;hZ#+_sBswartScuuhlq z2mn<5w+U6hriB)LA)0?Hb4_uHPcj$pdy)ffC~Y*B<5})SrbPpBZ^9l?Z5AgAC`Rco*e9|JdLS~DXtkYZx83DmruNG0ry+Yq7|5J@k<@+= z-UdNHS@jzJx36q{MqoW!W8(d?y2ilJ*$(={aW;8~EFQR-v{X^YcXw?Bxc? z@BKp>V`E*G&Ptc~XD3114FF46nJdno#bXIo5{%h#*YsAk=Rz0nChbs2*wx3;EhKDj zdY^ENM0fHz@sSfM87yd7o}qxM0bb1Pcb-G?{x!>p`1RAu>uxv{HJdLo6%|+m(PbNO ze2;bLP2Ey0fdF>0J_}-HFT)k&&c2tS9!nz^jaV7iR!nSEOKa1+;Hg@-2_YewBT}@r zD@x0ClV@!xXX%2IS&nv#2!1JKiViEOkqw|$ap*ZK(ELv5Cui*f>aC~q-E2AewXun& zUWmd29HL~eNG#VJFFG!MpHRc!m3JH$vj?c^ETDyDto7dYA-SD*KzfEhXt#$C% zox)jh7g#e|XwP-{==bJU>)&ZF50RsbQe%C;yD7P@mz;{bj@z&BlPlN1?z2yXPjPkc zbtq+_k-~PFflvqcTykA87;|MY_^lvFPSYXKf98r-b(_kerM)V`lpk_5-2-Voae%YRJ3-}HI} zD1V7;-OM)({^tz>;*yna5@RD`k4I6Tc@sS4HkmB)+;!-g9%S#M*~Uj)uIbMC%F5!e zyd*1p6{D+aQVVxiBgh7Jcj3hz*rwH^7VcZa)W%r775PO!ybnG%*3*9D3WbDz8*{d* zZPJij`?SNp<&oc3X5EJ3rLb3iWBCcplsI8Sb)W7ME-l48tR7u`@ir|k)(npmpc&VMsZ`ZMq3L^M5XPyFD`xg+zx*dMqD_>$AQmCIF@og+eMB0Mh(XU^WI< z3Q>yl9UEYZj_H%2UIm+&k%>+r@t93LJ1n+8*URl#UTvz9P^ohiBNL*v(V(?WAGK-W z4J{Dh9J9?Ok|uGHA~8yt0*@uY)#EvjAHy_V#7wC2_6}H1`#en6i%$WKC+rKKyJrU| zkFB%kxyiPe&&KzQHkrwPy4ZdH2O!eomtUfBS>or&g7w)D5d@=2DUf?5OH>6YS{%Dl zZvnk|rJhGFpFM|-^C-h#a?>xFcHPY1-EA)1v`~wLo-+k`FdGXzhP8IU( z^g}Y}_T!aqgIM&JzmlLxA;i|=f;>WEDfx;PM{>#P#^K$&(lRYg?W!>->xE_e03 zkP{#c$(1lzbpPC6o?uQl71Q8S#FbL;7(6xz*qXw&^$%w3{MX6CF~GVYx=_e*K$f`v zkl{n%gN{bS*SrfA*&W9Yjs&R!g;xIlBV3(V5weF`@Q6 zu&jfO?u-$|dYk(Pnkl4j1&TWoYzPsOJ_(lOO#xb!1hik$40or%uqxR@H0!neq2ROk zzL9*=90+H-4&g#3m)O`6>K@$x4#1Z54&!bFwyVnDN2|eL|0yn5U73cNneLbbLtHHV z#kp&zB_uSf1R6&^er`&UBlHm@;?yO4Ym63XDA1FKlC|a8hTIMh{?DOyO$h~S(EtBFQ>_!L*n_*c`bQT(0 zvi|T)UVFIvFv84?EPj@Db#>J&{)is~jvb)Z95ZS&yCGWi18B-xtRjh!fG*NB?xhK# z?rrHpYd9PTh3wzod?!;KUt7S&$eh9uEF*0;AHQ4H7q%U>J3lkm;Qip2Ra;p7*YV4i zBXtnEnt8!YN?q(S9}ubIEhwV#S3z&;i(E9XupzwRq{Vr0JMyBulRvu)Mop znBhHWp@svtRlm#!Ac!jZB97N)A85!!j64}RB&Pj7d<`FpIbOqY*ZcKI|DXO>v3McR zJ{fu%P)M+&*OaEXxo#9bxC(dGNC52!%a7h9WueaD{qqVB%QLe@bc+rPv;Q{#w9 z1&3dTHOLo(85JN-PP>=WiuGy1ziuc*U^sAT^1tS!Zq{RK=h;%`32K?8mBVS|wKBXm z`1!NETW%@UBHr+lKjr=+&#GIGnq%sb<}$QF0F!1n-J3=Yq82Y$(x{npON^&~5U&v#Nm-??Cb%XDg;@Hm=rYN;?<>Qx-_(Kj zC4inpG0HSJ4!;NJKgDRw-%@1STy)h&@F&vv$*-Goqr?jW5D)6^90x8S^}a%UqTKAg z_6I3Yw373AqXBub!~B!jm?GnYIA*3LClg^$cChh!x%x46)n%)NkhjZr9?nq^h(mPA zY;<4}V+(2rp2-S+RFV9E3wreUS$34^CH}X&zIG7vV+F+H%htAJ(r-SOXXB~q8kcRX zC=tah2%ntJn3~YwPt6V}d8V``J+`z^5tZo!#L_?a!}Ry&sIG(}x9^hBuG}I8q3ti7 zn!`3sS9EB3W|$OXH%BaZOviMk|Mn%^?_!kK6PfIP4c>M>qlr5kbv{32dPNV0ank^^ za`Pk>{OtDh-BI;YiOh>|>OeYjwPE*g^B}K`0s|tJ#3aUq<-P(ZJ>*K-`iAz9+E_jC zaCt7g8tlRUHUDCCl8^qJPkgFGT2#q5prgf9lIJs}>Ds5V4kgE#j_l+n1>vFbxtaT*1#)+Wz6)aX5EmTGB@_{y|-gePkhx1QURSf^w*7viT* z3fRz#r)9L`yTD;n&ZJVhmgpqM7UAc+--0o>jE|jwDV0)R`7bal7$z5mINrYLhcZp| z5}mbCQlSd$KU#uwU{uqBVnjf5~!Z=FTm-Dj$IANivkMR)KQZk zs%(!@|D5>L$6NBy&bPxJ^`y-wfKIEMwS0F+OMfZ-?LvD;M;SB=&sV5(D2pzrTlOpH z+u#eNi3UY*NH;@jfNbwTTOHmC zBf(8$)R$N43ZDDDmaO(c>)ZwRu%F1U9<=H>YaM%xW0DV>PXF+A(BG#PYSgmZz|*xPSCI<^hne)FOT^#SDo|$V*p!OS>zG!0jY!#3K(+#oPalbuoklfh^fy8c)K(8t89Mx zd!8i>P)q)kLJ(K}6T4s`Alu9Bi)cT=!OnJa%mKfMN}3?(uqulARi>zFrmcuC4E0Hw zxSpK42*QD2dY2Oxr%_h#l^Wij${pS`uk?OOL z5e!${qh@1gSz?q0_K^oy$O{ ztZpB8i1sAy({-Cfk{`#)+*}aHlkGgTAK+UviK-qvcBGSmrK}16z3R z0XywU;R@9<3f%3V6#GArV>7EU2To-^+!0-ANS%I#~&}oIlK@ zwadXoUk5r+i}0-pM=>eIM+^(DNiNrDCX!!A#>-N7hab2)RB+Izm?w-yp^z4)p0}vM z4i0{@;q)o9vfxnL>+Sy)_-beT^CZ>mp7+0u!E}j2gawQb*Jti4fZxmAYP}VY+%7yx zWYiBfXPYLUy7)$B0j|RfNXM?#i3kq$r1oPm>g>LH1G%F9fY8vREmtq=1FG2i6ju>2 ziqls&>NPOa`RMvD;B3x4B`Gj(O>RFbn^heB6sG&=CjJP51~LVKEy}*gK?I)MU)_C! zC|Th1gBC|nezB$jIQ{(#)%Mu#hpeTrTK4lT`1*l~j^-+ip{j;%rLX@{k z?b{~VO*pUKiiz~4w043}%to3c?m8>i2>X82S_sA4vxC%GHjSw#_pQ=N3%Toe&{X$m z*_KxCF}H|5Ucr?wg%65R8^#Nkl;Z?b%o*6R{H1_<7!_^d&o}sc$7H;j&rA$BS(1t{ z1e*OjU&b4KfBy-aMH=boW2$`423KeDItR587k8%EW;c)p`zk!&3GL*M#^zR`(^SIa zJmK`%>pP{QVThT4(Hjx0%+!~hV}l(k=WOe7Cfhh0g;vl9y={nC!4WOZ;ED{0J@pSd zI`b0n>=AvNZ=Djgk zc_nxg^QSP~N$Skco{EAfD?KCt?RtWOmF8-3kLUdF8(!R@;1qXh6qVyojpOkKQV_N1 zJVIq>=$h@K7s@C}V5l0*9M;i-Pav2y}c@}y$_p%p4S(cFz-*4j~`jP_(- z#H+B%Ufv>Z*vawl8kf=Xt1bN`FFpt10EU#yiT(WMxE6Oyliho>$Tw{tZW%@6R-d+a z0RPV@`8iu_K4)7)@|P2yRn*C*vz&}pAxN7}V9_yMPMaK&AbMFJRR5uFd~a98a(`$) zPvU6uI8qD)Bj4LgJN0BEc0#2-JLC<2Qj<3CwnkMJtvnTGKw7}FF1q-5 z)1Nka4!FVoGnYq~lW~@M{}5!Q(39Tw%kOgDllWnuw0Jqdpm~av|Fx^@2zK1HRe}Se zhAFQ+eYO67*lIrNLQ5zr{53}nT(QNn^tCu~fu35H9)IKuD}uEvi}U>t!(U-zd&fRd zraR?KbM!A|m$VA6tI1%Y)+@7*%=j@tXjkWlR~-)oahsMitRm`Xc6 z%@XiycmcN84;=mwb-uv3AcgR{oSw&2pIUqa|6uo+il>`;7N6BG67*E1WC#iV@h>om z4mxFXorZ=MANQA<0j>E8&Znm@4_}(o^HO4)Esmmd*EJmD?Q<^Ua~c*t*^j#FJ&5c+ zzPW58W$`3A*mo)k_#L5gQt&gGJ39;#L@iXRi^u^fh_N7_0xFYC7F#LIa@BRwMcMf^kP?{|ult&5iodjVJdKuLe)H{6+o{@10Wo+<&%4$*M1ywZ z96X@o-#Zy^AwjK9Ixu7yivm81I0;LAUF`N(CW5m|uJ3p#k?jn?ZXkj(Z;4Ng6(=28 zm~5)X&K;Gjr5vjusdbrj2XI9ty|M%xZB<)(4tk!G#|<|Trzn*8zotFy88*~Sc-`wI z^U&W9wf|k3QB8%;nP~zZMX_2;lEBxU4Lh(u)?pH%m6mn8;l)n5G23P4tWr$m3B;C< zzLW)Y`TPsJY)=lcvEe&nNUH+V9Tz$HD!uEdi||V6D0lEfPUb1lF@lCOmP93)`64rN1jpIy7CiWqo3)^ zW^@Cl6jOKx?rbk_eZ66B&QW`<#O@e!8=`D1IQzdCh9yiSLil*=j;T^RI(Q1sBk=@I znEMWUQ}F!#l}$d+6G7eMBvaG3PMK6gA%R>`GK>i6k%|Y^5f|#vF=%&`s-G=&PpzY7 z{xu$ti9y(62Agza>04 zThy+Ty7m7E&Y}4i7FSW<(b`GrHyI&}We?I+rt?A|n!bJ6nq?WzrJFz0sQ1nR)4Fr6 z%TeLn@VmjO2C;dkxp(*6zaWs}WtRM~w~2TpV7A48;U{^a|K>D?`i`AQ*_p!^wTIuT z2URrj8miieehO6lFoZbeVv5je-m!nKACoFrt{nr#=&bVfYbw$Ms*3-Xc}wULTZ!#) zrd&=l|M4>vnb*~zCy&cYCRdzdj7#QTShx-K3J}gt8-DlB4-~;iMAhTs_a65=%6)(} ziN7a?@l4f6LD}HhVE|EnN;$w67&9Iv$B!~g^zDu-gy4}%ji7H@f!utLa23~U4ZeQ-GEc05)q-Vm~H1Y z2Kt#(5auEy#ImWea{&x^hanr$myXa?Y7P-q@BT$HA7-TRWEwNFEPJ?DFfw{&Xkltz`Qq(Rs`;ZSr}_T) zi}D-uXe>i8JaJHj#YOq1A3ox8pswU0O5daijY|hYnL8+cOW~n#-t%ypjOMOPcX+ZM ztHpO7J(-w;xYk$BI>Rr6*BoR==OM_e{k`YhZ?Yfg zoSw6ebDn0wjIZF(8WU+ME^Dhr6yMMo>_A3!icg2XlkO@7NG6{g+Za?aQhXBnPom@L zlHh!x7Up_oPCuLRn3i;zNPjec3#n;Botkb1#l@a5-+=z98{hfK0f2~OX-7bgw`btX zu+~9P4@&lD-W!>EwHv$Rsd~3PZ4$V`oxfXv)Y?y)=QS<>=CDofJ5do99A{=a!*y4x z22+c$@ArGISYjETZ@^fnAxIeAE8VD@rg|PxFhF2HmKg$G*+M zIq@&R9rjsQhYo2Qf*EMuxGfU(wPFHVce)HPejazkQt`;-tO$S;-R~ai24St-IB%8r$P{b9pCkwGEK3On(;I$isvf?bhD;_bkeX|zkRz|G>KI6&% zV?cNC4_}vU=>}+(d%*w`(PdVB zo*xjsZIpW^7?$(k&X;iZgq1jEJVG|F7sm+i6>Bn+WQdB=vw%_sy5OQXzC1-|G(X+s zLvh$}s#tiQ=TZ=L8+JB*S=jRf;5)qgeRh{fdZgEn4z&ugdUT{{^}nS2DJEBt+(Ty_ z5W(t+lsRMR1ewqrW^xCeG~=pL-=GNAp6>O!8eucU!Woz>ha~chgLF;(+%Wk(GsB7i z%AT(+L?YaCk`(e2_rZsOky#Qr`x#3qJ@1R0B&Bu)5kJ&R*;HzERcV1+;8@e0tLIM` zCk@$<&)k}aYu}%6cgAS|BI+p$$DWq#i>_neq?0Z0w!$#mx7NdujF}Oo`0wm}o0n?r zcJFg?sy@V#%kB_?Tb^Hhtc4K6m1x5nZ25ZqyG$PEjdG_3h0+Cd2s-CUdL04Q`Jzja ztr@JbX+LZ&_3NHN#<_1iwj`OoeX;=ab1q=OO!jRJ5fUKk63yn=>`QA;3F}&fO}5 z?)Ey< zRC8e9pO~x7$@%jx;v{`|MNpf^S#p~bT_wR+chwiAf^NmA(m&7>tG))%Q%g+tr%ZVI zUwNg{P+po%%p!ymVZ__KU*L34HYn(rqdoQ2`RZ~(W^#dLr|-h~$&NWm%GV`X#JO0$ z@bHS3Px{+^Wzr`jsdr9qCiq4r&+ZlW z=a>Q50D!?qDIgnR9Q9gXocKE@!pItj`f7xaUaDrg`bdP^;lLq3Im>L?PD@&<_Lpdz znKn&6@l9QDzN@MBW$^V{qAUpfd|c<$32>ko_5AZW zwA^qAIFvWJOb#qTcfcA_H(6w(Np!Q8w!vkbYH-!mF5g|N&I$Z2P^{(O@mm9=!OGyu z=@mM19nD% z%tW6wCmn|Jh+j1+KyHRTQkbm(x#jrk`X6}%Y^qEd){q?Zc7&?KYR5!n?b9 z3cuTbrKI4`-c)kJ$9BW8xh$q3TS3{gwK3u}#Y*$Sp6U zzgqRZ*^KedXh!~Z$4TMmaIm3wENy1jXQ;fzoW9GMTcEV#eC@r4`eG(Q-(jw%C7r#z zwAqxeUkUi0%wdsZSXHUpM@zmXNvNX*2y`cGSW{y`>fImWb4P%L%5 zUkNku&HZ@tJ3td1DN-kBH2rkPCw&hpiG_~j-K__7JQp56vrtTn+d&Rb-Oj!l1`DaT#>ho%ST8+5-rE{id?hHu|DbQ zc8u>^P`bXNgpvrfiCi7JCwr3b@sx!*Fvg5s#pxE3dM7AoSjhH%&%pSoHPC6MVeBW&0)Qlyc1r8r$dUk6rSm#vSz zqjr$ia!WcPshGDqzO}@@)vrm4!Ur+!l)V9#YdDRQJ=joT1?jp+8BDlfP26Zdek7o_ z*miFS>JwA*?RFO5tY6r{^*9i;$#(if%>Njc+N7=&z;muU3h)~=z1>WOSQsNQ<1O1Q zSxmo}iqb&qjF4OsBCEWpw&zt~)nMgV5dysgHyC46H)9+;{o_p5Eh47Ne$;%RojuJB z_6aR43`X0f6yiQNSoxD)e9V$(_&H^H$fGsqI55VII=t2hIi5ym<-Pcu^b+UEpst#+ ztOZ;)>Rj@DA%wM;cZSyw^V|zSN^e8@4}B8aEUSi>;UP`cBeiSbbF;)$6C?3)g)_Cw z+X^dv{ctJHXmVF4%dHUK*<%>WVA+{BbZ9?XDtnx<8tCcpVc?qsl$Dc3dkKZYt+Fba zUIRs8JNvJ>&cQGlQRF1KGza-@G;D7d6z4dSxni{fE}M*#l9CE|Xk>GG$;`}L^qwB& zU2Wn#n}2yE3%q{6lXX$H)9&QsLyTnTxo(T{?p+1PZPY=Rzo63qtPwOj!ob~jtBT= ze@Xj3I$v>zc#`EHu5iXRhz!-DfEkC`Uf%Yoy;)z0zNN0&j7IWVpB6{9ksn4`apA5U zA0Z-D01+$CV8nrPrt~V5+0I?Y#98+AU@PH|D~fI}G5zf|1e;NgvrdT(D;_?6?% zaXm$bfmZcFP{_TtyglC}ey50)!ACpXmUB|(WS((gUrepgp(LW0{5+3okE&714Wgt6 z-37mu@FPHcH$PtTyq<_51OHa=TK(dAjAXIiwG@=4^n+^SyqjASJU5Hex%0fhU*X#F ztKqQmM2~sKbuB9x*l76RU4|D7%V>8I#!S0<`!Q2V{BM~#5#`X|<^nza&T(%C7CZ(R zlJ&tdqi)o9C(QLohmx==mp3H5g}5&y_d?HYJ^qziIcgkWi*!Xjnex&ARG`QX~zRMmQm|v@=gNML^H365QkSHT~x9egLa5Y|4vAx zWqzuy<0UfM!^Q7LckJ`8$Cd^!f=q8tIj+dsHy~oRW9TgFcVQzR`_^808^Yd(A8NLf zQ5?TB%{{%b4N!}rHUWe3*>cwkeWhA{G`Cl;SvUuL;ouEn#*nYTKlhlpKbY6~+4AYy z%cE)z<%F+ki2!OJcc$rwkoCaP@?dD$9K08s=@aXG{WBSe)U(ZSozE$?rR(7nb%+cZQ+&Cc$g2G3lqZfRBkBxlh$ygBQxYooT_2;5N zV2+Fxu6Ue3l9GVQ$%wo!(i?JkU+#=LP8kC48Ut)}m8th;=CkIA>>yeX_wpFc_ z;Q$J_$5HxP-BG+Wc|0|O-GAur#P_66+y8mLVDNnWdBZ7%m_CZO_5)Ja;F9vHQ}=dR z!0k$BG4IkHG$-nL6Roq;dz~cE>Ea~7Iwuo{T<>Pl(UJJ+7SqsaDE=)XgHiFq4!P51 z>N*a?|F}(0xaqy}6^@Zlci39(_LJc521QXw!rvOd$LBD97ABB}$95>R9L+=9ckR;y ztWT61{M#mj4p%TUJo>;W?6(EJ9fFu|mf1E8WeaBp+6yz{OLJ7sVU#0SI?JW=1p8O} zyNvy4Q(1ENe|pe^@#duiqkMMFK2S+K5hxKxIi)bqGrr5{G194zmhVkQ`YJBqiYn-H z?DzFR0+pMGWM$}PX@ZFhI{fgu`XkfeP(BmT&e98nnH7Ysn`E-{n^ZsAOGX`ST))~ISs@}9cGudu9s`}ubf+Ax<9V3 zhG;WHa}~s_^L`hKOxribPjk!{!YeSUyGj)Z&7Ff3PT5k1dsBFl(W3NHobr0JaE&TI zU}z5X{~K>iQwlA4PpC==om^$-I?<}y+qNS%u&rL1XxY}oerqgwoZ=)KDo=}I7754? zGr*9F-}g2#V@d9Q7A!OMYx>bi?{j7Nfq^|aKR;{`w`u;hs=Oxzdw{o~+t}I0fz)5W z)VHXZCMVqkEodOkAsBFdFd#lOG5PO0G6!3S0%_sGQ2ws>S;>i*U=mA0vj~TO>(Ncd zAAwe!x0ILvHw&OJd@Ra>hYCwuIixoi`xXDlz)w+ewOdM%Dw_)#AA%e5>cPF}Ept$TsNfp=z&~Dx56C zPfaAo@2}aXqw^h&sXW%;Ra!7BKE+2Xb2FR~kc^?`5$FnPjqE!lvy=3umr-DLTap!` z;^q3!)?r(uhq8@|qO*ftFk8jTnc`qH6?`}6XBs)?z|5V20W&&kXJ+DYm`-9$!(97z zTYk{RMwU-*G)|mvtZz2^rl&4yG+vu!t79Zz>AHL*+zwzrY z{=}7^yz|bxHf=iYInR0a{QUg4zV+?>`|mIAtkr7gJm=XbopjP&cim-77U(6gb;Ird zo^{sKPua4iUa$Yxwbvawbm;l#{n{`5!npu&>cqsG-ni}C*I$3rO*bzEcz07JxpCyF8}oXjdd6AHYhOAvXAOW|cE<3QTH~wVnOU$* z%N(Z0*iLV76s9)v+iw^@dGg@pS4{2QHM{XloPj48Mh#~qu@y_$0V0ziZ_j%0(phRQ zU*-@o*23}UF5ht5!{4r30q+)|m1Pu|MLqB2(gT^$yn6A`z!~Fz_^QF{U(;*>0GPqa z*&W}Qym=0+VKK|TOqg+Gt6x>jGJ{rTGar7RC2S@D0JJ72+mYoIhGmJHGUX&CAJ^l{ zMBqX0Vr&t{jnxx2=Sev_5n{{7Rnhj9Jab2$M&R<6xUTR_4BrVjfAy2Hq%)ULHke|T zb+#OI-nz5h;Q{hZQclw8U>8w2EcsTiP}OZC-p9h|7Mw~ z)l8+5&&}C9w|Sm*(9kr~!+I>1)|?f1H+rJ1Ygxh!c2@kZ5088vl-uh76yh@BkhWCv zK916_l61ckSkds6(Gb)!@`rs)u7sZU=8;7`M>cy7%NW~-01^`2DQI6KSf(8u0B zh1q)|+sJ9Y@o=V0|Iy!EJ3$RA8Tud>PW6Nv%62eO))S`QE0m-d0Yp_YzJ^HICW&=g z;d~Eq+?I@H5)U$q;s=1V8r-ho(e;Yu3Ue4?$%9H{ODDcWyyt%VYM|4y(Gve1>FlL6 z8RC{X>^yruFmt|zydv;*a%{AO)HGx(SK6s5d*p~1hY6!zR*%CZu&*pCvi!`M>GwRa ze>l$p;5&VN0B};CU)o=L#e75g*3jIX-t70jcSG^VgAYFRxW^rT{PD*>@d;1(!291< zyg55N_xj)c{qJnwzG40PPyOSSn>KA?on~uu0GQaib#QPH05+d^qWkoJZrHJM<;vo= zsi~=#zv5Lde8Kad^PFb`z{JGXiHWT;AaI4onf)4a*i~>IZ8ZDEdMOYrWP`3)$0-G zLqJRzj`Nr8xz5LpF)xVmDuv%x0!<*C6e1zz_`Z|s5+Gp7?3Nayv6Hx)J=RMr%f#xY zN%1Uwj2^*S8sW8z6ZALXOb%V-w}o^bWP#t-st}kiuAk-nC194aJjfzGLc*T=Jy$t@ zfg*G$;VH$Q5QKP=Ob{ELE)!n4Ft$C)vn-xno`#_dcurGfBJC4W7?#79d6@_;gE86i z<<;@=zVUH_2nxA_eXiRynOmv8l9jelOCio6$^Zy5zSUZXy^78S{~Q7=bIpL6Zl+40 z@k?vJ^!wblCk0y2$KWG|Wn{1DeD~OX zZCEL7Mw8Z^K$~aTO5D>R z^MtbJSP+@u6SfHg9f*BnaD`G88kI@H30jn$oKZzdS1G%C$PWO0eOZ>-X0wRtEQ)6t z1WsX}!I|TJ<#|idqaoK(kqN7ZzJ@Ct!L(+=A5*qX9p8#-cXUo(G&&NOE3&0j4s5*4 zk7jxk5LY&^3ZSHi<@JC+O$23|e6S0#PXd|Rzej$PXT2wZ&FY`BaQ4Vy*D^@i@jKc^r31SZH z;?C|X36C?QCsYqnAF9=C#fp4-n)W$Tqp!xMlq*|I^zYmwQ^TE<{xh4+;|B);VB37- zR|c2;Q-4kR*7aZqRglPMKl7>2e&$nOzUGUMd))C;Q&ao*@BhI2-`8j~{`9SX_TKls ze{gW%Jr}{JXEe@B<(G>!OA0uKVv-yz+vteD&)~q1~VHh4Lx|)+5iYwKmT! zfGz%ZSMjEIfwiq$Z=60<8#-em1IY6{JL|0FmEDt{zILj6>>qA;`mTRF?%cDFzwVBcpEm+0Z+pV-pKN{IM(|)bw*SP{e|qH;Z@A-WcinO3 z^puUJr+w;Gh39Vby#ICo^0d3I99w3s9UlM3+t0Z4 zwd3#o%#(Kh=xKM{@}$qcYxT+fg=G4F=`CBn@~t!P_{mdt-SEUuzH8OVwKjgie+?u& zJbc!vjqTr=X#tgU0idvXZu7o@UwiAxU;Exu?zrQOFT8*33FjPt-5saCakI6y^0F(R zeB+15p7Vy2uf6#xH-BP$t<6q3f8*s}KKlfU)0<(1mV#W$b!?x$z{XFcvaKR)fwsbc*eX-GNO402EDidVTKB9e)ND zujr#rZ{0^-tOr6|x^JPDTPsHsANRQG*qHSJ zcgQS z2lm8Qbx#`B4N&p;#VcdT!@?9dcl-gewm6X8A5*G$;d^;r_woIrt`b#ZZ zB3YUH_5?|zkhWvZ?tC$K?nJ1Hd8=iu&08&G!`qT z;mf@E$jKZ$e-*hp9c}cD1NNyxliQu|NTt~Yu9!8&D}ls9=neXiL(5__{WUtvOsxie zeUN2HT>Mgw^c)G?SM|_J0MZuM6SP<$srrr|`Nzn_VJ)2=ax)Y=QPd|7CE#}wx9Kf& z!I(M&h1x`x(DfgU4r$-gSKcus7hNm;ZAhmlJnozR3)djLK2pEJe>C*PuuI4uf?qJ< z1{C##t$Oh{l5}^20M4i_N014&+x$9D zdhYgC5~VUbnpE=Y{fb_R+6Y2Dy077!t?>6j{6C}v;ecAsk_X-hQWdtuEsPIXHbj7S zAEZr7JA(?C&>koB>aC>GTucT+8Jf{i7#^m5=38cC8l>m^fh&v0ETij_&E_iy2JV}i zyJ2?b27tHs_n%#qO ze*ypp9)8#%rkAeA2y!`+Cui{^#sB7a)NgI9nK-krvLlDV&)A*{t{N&@FHTl3Fz53p3>wWkAdgHC1-L&yv@4V>Kvv=>! zUfW6gn6cF#{(qZ)aqjRZ{(RrAx!T#kwf?Q292|Z55C3Y{;qUKV_lwUMt$pe6ys4b^ zjAd2GCZ0Sr@a1~L^gZjGq1KIeUq3~%L$#6LdENBYm+n1io8P^^?}c7YzKb0w(w()w zU;aPG{`aN#zW%+9<`utJi<9 z7oavYu{mor*1dIX^5cJgu)cS4eA_9Xe#_tk*WCY(OJ-nV{GvCW{GqAae*0667GzJ@ zcFJeoGWfukIv;I2`NQ?wUj6CjzrXV*r+nhX=kK}qRhLZe+ntZUYH+MEyX&?bY9kYy zGZ;AbT?3DN^iOvE@%)PS{nK%Obm5_Y|BI;x47~iZQ!YNE@wrQW{5O+>&)arf(WTq= z`Iwy)Zfp09an2(jE$;E&^2|he-kO}Oj*OtfSRO0k;ao)gGRi{xJPe3C+e7|hhrmNF z+eCg_pRco0X|pK$YY%(2Z^-S);2BY$9s4JcGtfAhGx=4AS@XFmJ*%SN#{=$Z4lzOT zj66rPghtOY>xP5nn+sWDnLIhctw&@calR8hWcnPU_;5~}U>FttTpI!|FoGUo^C^y= zo;buI;+gi*I1)S&ZCS*4BBV27xuv>IxM+PVM13`pXPLu~bZg?g8k8iN&~YSo#tOW3 zK;XtyCd{<*^@Q^C7KUngxNq&+>e!grE3ckiLjuw&b(Uc{#M@#!?1M20x|6R{p}el0 zVFJW!?8R4!Ls6CeRLsZ3C>-20qfMG>rs^*f30+i!|sT@7f=ykh(MtRZ?%Wujp`mANU^ zDMF0zTh(wKFAN;7u_$u}o0>to=o0i6dDn*JC{M&tBO-p5V;$97(8vaBPn7en+>X_+ zS|Hzw@GPhBeN$3(YOj15Rw~(w6}U)R=ABPLMuS|Jv#7}OwabTJSf6zNJ#As(2MY`5 z4KCY0H*2k(oS**+%m-g+HUXLdaN^wDtM}de!LjkHMpsguJMpAbjyvwSZ~pt&hKGk6 zjYgx<=GOS$4L7*gMx)VeHfyz-M3!A(mf!Ig@4WTa9{|8D7Et?`xrYFJx)*|zcZd-uQmHTQgf4vcx^>py@m zf8m%nyz0U0J~{^gP@BE_kAM7;T^7L9er2r%fYFgZ{lMnu!@-MQefKvG06_E3U5%Lr zv<@_Gy=}H-hTKNvNf)mFrID!*z5I^9-wgopm22m!|9SE&-?;jduYUO3JDV>%bNLoH zd~5&mr=ALT?{2O;b9j@TykmIetW)8ZznwhDDdf(}?|l0eO#pzgb+(;YZp$P9jR4fY ze(xXt-6M4{uxsztFL=W1&se_9HnwaX?0@Ltk6&@%hFQqIKKanxre{5DDi#(1gLaFy zjLFV8YdP$^cjuf1o}U-2g$)-R_ttas&%OGNcU;%97QVU5eDOclk8Pjc)c_b9I&mY6 z?0n=kufFg0d8nMY?$g_r*S~bv%l_ctK?~;E$;Rd)n?ALF_XFA3IPAV^@|N3XTaZ0(Vi5Ko*i{D@TXy0`sP2654`2Pjod8ff zvS$)b8>v`pH(apsPtVCe_nIHQ{o0l>@E^O(7yskfvF$Ur&FS3MPKXS*bw?(M`H0~x z_MidEIjReX57#zqKzrAKJ6Pd$RcoIy6qL*$@OdOD4>e+xrrT?u7u6M^yEE&RJUPgW ztB9KtJf10!8I$F`>M}$t9=y-(DDgPU@-(WkvJzujc$OLydFgPLu`)+`XptVeag_PQ zS++Y0&t&<|EZcIxJ8zYi8O~qC`z+af%AqOg3CD2Vi%ddZdk|W-h~t}Z3Ntd{&PHct zg1BPSd6ro$5{BTV%LIk$79o>v%RR}Yddx9>GRij zFGuhgp5mkka+d!Y#c7ae$LQ*)ke`QCf){lXXjwNk14$wmL? zyWjiXe}7{*Q0|Cjflnip$>g#%&*X|N9092U0@A z#~;550It36x=&pBfA{?O$L{^PMgstz{j9U!^u}!y6I+*px{nRWvIiP9rpVryEHfRD zSN9)9x*21PY3#Uhwl=YRqGpagdt_66YR8SaF^z9;Z`4j5J*#Gpf5y<*fvNBA0W&ai z#zc0{wTEtMv|FCP>Ckn%v#nhPQ6Zv(Q-2LT!dSb5s zjEOP?zwAvrY)1k!AYppvH_Ftx@mf9<(#r?O>8)M*<7p-|}ZRU%Y9y-(>ID5zL28<7Gu9+<_So4(H z%ojg;=umqc^B?VQ!1&-v12Fi6Wt(fwJAc?{0RRk~vbi#|dwLH*?bM;o(E8>T2kt7m zG%$GLIPBZkXqln&U$UaM^P$VGD{x1q)1{e1SWj49Fhjwco=lAY8pU1-EP(E zt%6x!z+4!g(Urx&NRy{?v~+v!7-Kv=X}`Muqq$RHg4l#P5na1w*M6m9e7xeQb`@vXeRKv!O5_dKE282ig05X7!K&3W5vicA`x18e*9CJJJS@qdd?dXZv@B;YMIe<+H$f?D$AD-Y~DO@!U>h3A;Y3XeG}x% zT$-Dl9;W&t9=19FZb(fim}O+3ihmxy72|Fw>;=g@+L@4t6bN-AqbG#lHo&*~tDBPy zM+!-uG^S-_U45Bk8Fa8eXf(TK1P!Nx1}%i|?3F*JemAnLz3AYE@u3CGBI3m2M$^+7 z!&<^lvstlNQ!J-%4=WYt@6nzI-FAP=a})ehhRzJakV9{x@lR?@=FsK7(z<_P%gB0* zSD`1B3Xg9^tApQuC`N-Ul_wcsIhVsnKwcjsbEJ=`Wg0e;JPrNG3eJAEkG1UYHvc= z;!@H=~D=e<&emLwk~v#lA`PrpRJ6&kEJoW3*+%*A@OXBLf?u(s zD_GWE`E44*ZuX-)Wmr5L#^tNlEYI^-96sc~xwW-$S!)3R?#r^9tJPguw%cTVU{A94 zRgL-83$yOd7fnw7&6+W`<&XUBrRV>~um7+A_1w!p{)s<(`#UcA>%V%z^Ur(1^Us@| zo!hzdrq5mV`Aa_ZH%~tEjHf^SX-|Lp(;TijdR_P5-`cxx-*3L+<-hrgmmfKD#7S?* zj_>Z@fB&(^u6y&FwmtaZgS&S9a4E3+GY2e-)PZu=%UYWOX4YL(#v z2t2tk)5#)Q3l^#;j!xgSckrBT>!1F$d%imlc@CMEPxrdpH#!QfhvpA-!8@%->I*PZ z8G(HAJCi$M(^JmM{%vIWroB_&`s)0%-g4|yo}S(H%+XbQAHMc3046)MP@ikHQKD_e zXM_O(N1(aT$QL?bkOcq$ljrcz=XSq3-}uJgT6NyG6))Qc(|Zs9gE+s**N``?F$HF7+CO;WslzscVAJz-*)<3s|=5{eza?5!P?^|23FRmuKlmvTCleA zq|*l~kp13&JoWc{Q*>Z<&SqzvF;Io<_x>G!G@IKMCk`Idn7!=>IRMzgK!6vlN0cX(j+BgP1=h|1K zZM~DX@V~)v&h5|y#koLh!CMAR{F*29GA|kmQCYR27)`qxl6WljGYdgH( zs)!r^h-y(!5XzWVLU7u`wx8O=%}lkPau#e$VN!u(43K2-DJtSiz( z*K>qm>ehZG=my0sJJeEGU1uo(ncxs{b#2k-qJ3Ad{~<>YXbe%*$LEYs!=^>dsadYH1H=LDJV_-v%f)~B$|}KYvYl~9=L805Zi2N8<~YH- z@pxM&6Nf|hWumw5>Qj+D47pJ7IpqG_4D00DefE7q--ic|1%+a+5ihDQ2jms|=tZt# zuZzuO>j_Z%u0p85@>7htH2M%w=md3IsNTqL&BwxrJ-6Z9?D|3gCeQJ?&Hl>bC$0p- zR^J{1BB%j|Mqk22TWPHfYdCn!FUJI8*(xH0ok#d(c`gTNYa$bcrz=1%vlPZ4Foy^( zkd=8_M7@du1No{%y8aT(uVl51;=IZBlgH%B_oM0r-e+KY4$N7XR6lXOAVLANm8och zAhZdcP&lnVX@4{_Vn#>pp+mIiR$YTw4k!h=d=z+=KeT52zlWCp(ZdgIA>rNidA@#r z{#WJi9=p(Ve?RolL$7${1@6E1-+%uLUi9ljLqm@|^2pAeH{EjUts6IPJaXiSi}~Jv z|NXyu-V0AXbpinHx#!;X>yNEgt1~k*#itj({&%a@>YhDqRPydUdtUL%3!2U5%*@QM zo%g~5m3-G-yH7f4^VHN706g-@BhUM_=WpHm_*Sd+KmT*%Qe5}3fLO+7`}&b1qcLn{ zMOxYzXx=n^E3BS)#){UNaO>Yq79=x`8z*Jey6w(8;8JZfwdt0#cYmV+?3t%y&m7sZ z`^cZ}^>e&*2a*ldV5Yv%>hyW!+|e^@vmf7?8hp7yS95 z0IJ()-Z|g5WguJFx&MMcKX}An7w4LH&R5Q!7_2<>$gU{>fPs^iZLT$M*wtu3HZf5% zyAR(|Z})uF#NaxZ-L*5%vTRumX6kvX>!oYG%ewVk`^`Gzq;^4C1=K_tc@Y{Cy7Q0@QJ7Bsa6H6n= zAe@Ts%g~|fT+7Ak>+?8AaTiV|9fm1~2Xv>#W8V=S`Ygy36Pck`WuzpYWj{Y(yj7{R zu}F*K`~|}KEAk)*vLcuRd=V;#xjttspd4OsL_$gm8K zf3{=d-43D6b?Y%KOFaj6rI2ema^~{Jqh)S=Z1EvI8J)TvGgMLb2nc?*2VT3 z!PSP_kT>72S_pP!{__%s zdfuhjM%a9mcc=XL{%H+}8`_7Yg`p*kHxw)(=AL$=8Gg|SOyUw5eZcwaL-<)pAlJUg zt(?rC8!)2!BCXY+T7~&}D2_}_<61QYp^48IQ5__nr&xhWA{;tf*@jRQROSMii}P0F9^W3E|HFp`PSTPocz;TaJ^(_eQwH7-2HJOwt3Mue?U36a{g=Z33l z0#_QS^B_}x*zkN8{JeB7q-Z$E#fvPvv~Pr?RRv+37f72X%5E$(L@Q5fko@&d(rKbU z7fb4$_*?XS{aYMIuy~G9z>UB-+?qk&G( zyY04J?(hBk@87@we)RR}>1p?s2OfAJ=+cclr8|!tIpY5Ie{pJRYU%jC$HaxQ$11-q z!e!(Rvz%plo*OfH_hY1;FKwT<7GSQvb65V0 z=dL{clKKs^Hp`&@^p(%q414}{su2Ji9cDQ{@X*5HAKdq$udn{dg~$Kamv&utS9^kz zz3TkV8)xBy(dTX6_xHOk0KmxTFaJVcNDKI9Hf!H5#yPL#<+`TL$cgp{HyHcnEhiR^{?*N=LF*Ix& zQwE^6{IwUZTzTM;A3g-uE}Pg~Ik0{DksK^ofabo3S}>6{@0$L>JXj0k|NDgh_wHra z{lO34vm4qU?Vi4M&KP6Y{PGE(yLj1kZ@&HFyJ}lc?VH~{`=dDk10$yn9#b6O2g@fm zSL(Z_?`cEW%+?bJ?cRgC4nqz_mj*{*t_}ujLtd9KX4&yNC1REdyA#8s#PW(#4F_l* zI%(YqnfGOvd|_jEcBGQxQYd}n^Nj$w#C zsyxWdI0D3(+Gxv#RrEN2MNVOkGmgh#vfR)tn=r+Ll;J_PA(v}8o@I{RGLQ+vpRzp2 zoU|jDWs1>bWg_#0BAHZHudc3LTNxeg9#v0q&>==1c-MFHV@s1DbE>)@{&u zx|O(A^Bf#97hxMwtx||$3r5h#FMHoIm5MF+zPU3AaEiJkbBY_q{JB1w*!p=lPS3rb zcX7~^Q`qOzBs`(KpTOZjHEyW!E+T#1Sre*&DXh%GgqjEBD|dR`$l*H4u{CN+)4=E?>3gxksjof6@DI9T>cP)taR^>e9vMS4llWfPWG4djbB`7yxK|_r}H_U$_$X zKCol2vy!bHJElk8HafERfgSsbUzxx9(uZGh<*|QvrTK@erVfq{pZ~^nC+>ad%~#E~ zi~~7%8UA*rvH6wv?K}S&kNe%Xt^LMp9=zM~u&BB$xcSYWJZ1T%2X2|Go%37k zUo&$ab^+t!^0(;wQoU<`bJ=k&r$S8S;t{OI;vW=-s%uIf`a?8^W6 zr6;_9^1&PGwR0{Q-_poiE*sW$H+;-;^ZPp+%igx}@86w$a(kmSzWkLh9h$lE-W_%R z9E@ym^jT*veE;*aEj^n$U%%#SjTc>T+=q9YtM~Swb-}uqO=OMj)3-Fhj4t20G2gvw zX5N4S1CU?$`G@bja_##+(z@(Rjb&%7e)R<_YS;ec)7vcoT=)5h@4IsR{U2Gl>|Yy0 zXRiM3SFNaB`;$-a$jv})<2XQL*{{BEWXG3|JZ|e?<)MRj)&WL_wr+&GKR?rGBWDLD zPVJlDGyB7NV_>T2(ua0euHM^s)&sd1JAnL_$y^T%nq=3~&?H znmljS>(!AFjFMw9N4jI-r?q_?lMmODa;F|@fWa(NY!^L<+{dD_{*AkzlObx^Qc63i zWgqY0;ids0M>>uYBeE=uSR?@i;pfBS$VWPkjt$L)&$7&RFv|$jhxL%4DJinmaF|Lz zyDs^Z7=uRISFjHOMnIlBRzNfuh*0urbJLTFA*X~J3H6CkKM2c-Y7`KzkoK9wP#}~j zl?hjwLS!z8i=xIDnNUoPZz}`@5XLOCdo6UdH!Blq96K_}`unSE*Y+KAjHyPYN#+ZfGQJ{(d^I)9|g#_M4*T!s1pDUqi+*GB>@Rv~j*@tzof~5>8pNv7O*@ z=%Iu=mJq{>W$x-@!pKzj)CYDoq&Ohba`Dqa`;t=V;H=`25H=hD1U#%yJHTLqd<<{3 zigDKNZz)&DVf0LRy1ifE!kU7tkgc#r@SMD%GY<|W8dg7m{G)()kWquE3fM;|vo$>- z1k!kV80a$sN;?Vr$X41Dgpp@iXk~3tb~JHpW}ZW}>d#+Ot3jm#%_g*3pzs=p{vL2u z4h%k*pmDKP<&2nL^~I-osPFCc1eF?4d&2T8ORFUEGZ8}$>!+b3ytwVEh_z(Ll%0?2 z=wm(f&eZWcW>R3JG!slK8pB89^-u~cE9-tD@>E#&bz8x7Pnba>Lo~biCd|z^wwGN^ z$_fMkbZ&0Ypsz{4#a0lO)T<~7FNCWPkNFiUM-v65p?TOi62j!hev6M3OWGp%-r|r6 zb5r_?$g?bUe&xz+YASEFaL+j*#%r%1?Z|R*eRKKnHTDQUb*U<%ZHbOs7n{07ZR9l5m#&%Wk#~QT|08eccuPln-oZC~1Lb@=fA4+xk6#WK{n`5S-+j{SU}4{`Lx1(#cVD)>Jtdky zn7rY@x)bN7Znzh$wbS33x)WBMeBrKOtd&|p?{on;vs5gFi?Y=j>=l(rje%;5_)J`8c zy*B-!>st;<#O0ZdF|BXC=dO2-KJLxiHlN*?{q|K4@7TO<-Hk`;7WyX!j;}SYxoyFX z=JbEv`?^1YcV4*e;*V7p4oqKv#a-|F+k^K4-vU;f>}yRCt_Bj5bm%rCxl?b|PG zz3NMi$DhvC!F1w zz5c34c5FW8*qu}L4vdeo6?>Q+%o2-u3?+|&?hARGeI`vJQG!7CF)wVJ4pmWLwaRn;`qGy8z60*U5H-MZ4Ktj=T+7|L<73Ap z&R@d8tUA-CzoD;Y2aIZdEnaJ6d(;b++k-u!X3N0 zjBxHw+K9JZ3SRhlE|w)El)E1@ zEPEUm43}#$cBp;Mo+FOVAU?(Nq9RUVz!a0rt`e>92*2<12s=b)t0hhxr?R#}~KsYq5^H|6{>b8#Xq zEf_(c&$?)t;#Ga$*o`Bq#qm?liXRBTW8o=JG}~tgtM?FIkMx9zbcgLD4wqxEN< z1RB#UZu7O5W#Iq@O>9}7WyCu#*<>d}UnWY1r#T<-omfX-xpC8_N8P`60G69kXU=DQUsR30W`8GsiD<)BIyl-;f_17KWMk&cH z8Xu5$B1NQlXvhu^!@-06>18E(mU;TV6E<)8DTE4_<@vD-&01~`S1Oa0$`K*iwe1bR zpLIUkvuDp&zWTMJ^6T$?&%3X@^8fDn@sA&kM_apg?Mq*B{$-b6;Z6}e<&-VYI{WM^ zKK=>z{^vaB*%MnI|MwreY$<>&U7u;rMOe=zT#Q=RqvsO!xPTt%Q-Cd6tUlMK!EMGRTd{?Mtbo_}io{eL~Y=l^5x-NP)aio4-j`<&A~Gd zO+Zn2%`2nE3%&t05kjJP14$y7#Mdv;cnhF!5=}JdYZQzTP{d0V5*19Me@vJ6# z0yzN&&=Xr{3S{y8!H;RgGYAKGfzt? zz2lyMw5f4zz4^#Ie%I|1Yh1?3JMPMKd~y9c)>a1#nTSf*gO!Qo zqtI34wbE!P6NMY~O)RZ7zRs$qyZQ_=b1MV)3y_KTo#o3r%a>>K=1FerW=iXmTr>n% zv&p&EWP>-^9oGNCyNBIQGj%s>`UG+^DLrX2bJR|-5j%%9v2=AjkJceQ@eP;2y6+gjLTX%Sk zA=Ny~FgQiY9pCTDKd}p4Rer%UU0ZWNfyYE}>VoI$*as zY2*bZ)&=&0OMGfTy>bmdqU*f|_xD_rPc>KI^lBc&rv}~W4foFAlgGU6X)JJBubS{I zL(k_4?@f%kJ_vynk?X0c{DBAhEH1S(uiJ6m_r8~iZoBQa@S9&eW*NTrX4$5p;o7(T z(H~xT?REe3&_fTkTI~xje9L*~zUlYha>14@k35mDZnauxo%!l7fB7H(iym#o^5t*6 z@GVzfc~yukKjqkC&p-b>E3$mjv!AtY-MZQEaijZ~&e@jPp@QQ{peF?O>bVa-I4jHM zuOA$Ld~f$|-oe~Ai)eW7UF*0d@A!++dyjnZ(>M3pYv=#QxrfcWW%K86$&LFh=W&*O znVa+bh+CY%Ft(0c{EojEz30e>KJ(pfd(G%?yy>ucw`{)pmTt+!Ewm+du5cVT>~anx z&H|ZY$FmAp(9Et~!=s~~u^5~Esl|=j#w<&ji2NtKu`^_GluL(bXvO84(i6dV8AcA= zk(QniS>s3f^Dw?H3ZGWzH;UbvLliMJ*kI8DazLHeBSOVYU806nzO(B&F| zh`E+|j6=t@1qqq(`KvP}+9^Dk9_q;w*%}Uv<(=6S$9jUtnRIQF;`^4hM9M0PPet)} zfu6IRd27xhOTqbbIlfgBDG2zg7$2=Bi&^O{xG zIG0_F5~W@*(4$naE6LcUu9AWp= z_!RToYLo+hQr58p+bgSUg=T3(dBL6jP@4KvYEZ+nB~NL;P`CsxEO11W(}>_l)x9OR`US4 zSa*MvewvCrfct`~v9o02Mn>Gig?`75guQ}sm}utq>;I+5a`V@<*IswicWx%4zxnV- zF8k!)ed6OE`yZ#jviQ@YMT?F;`lv^?Jo517%|vw4NzcCL?t6Ca-d+6lgcE-Cp@$yY zwr%_H@bELAar~~`yYIQ@UOoQ!`1nyrt$XaT#~yf~A9j59v!3Il4)&zg4SNj*X7}^zOTfXx`kp zPe1-l}soJV`UxcS}d*8IU4Yyall7EMmyapRWr-~ZUXlsQMvWlVH%{3wSX zWFj~j75qW4+(ud7I2fU2(%Ze;tzBEm1Y!XYmz?0p^GVFFdFaNxr7q@8E`IWOfLzV; z{T`B_6*AGip#!^U<+G&tbG_05$C%*2_?kHpvcwYM-srWQiOm_71t(x&8nyVX1i!7t ziwa>ltgE|AbJ7_yajN%sq~XQSJh%A7YdL?t1t=(HZrLlLCt`e2+BUdMDk5%;%tid* zlJhs5LCs~t-VcMzRLir>`ExCq81~aCc6gw@B~6VSzg8yUzx9sGG9qdncG%GJ<*kJa zDP|JK2MsAc&9L=s@Vm43jyDRQqX9JcF=x88)`GDK^Ko1#5DwDFvg&POFQd$d6Vl-9 z>gX)vwhMAw%e~2Gu2+*J7k}_BOe`lM*6G;7U^hdYm5M9hDI4y^ARJa%C~9bQ2KQ!Q z(M4VCJPX-GKlxc718F6vaNvqnme$81p=$4Ne5=9~n>^9N--}%iviZIvze4OdM8xbk z$G1KuH8zuJTg1<+S+F%{u|UrWnUvoMxpd{}Cd8<@Ze!kgiJ1-jyd2-EXo9&rjgfBp zgX*+0&t>H^7d#$v^~3x-ovhW$opS{CT>(Acto$uWImw)vkB0B1*bzi*Z>QkQg{9k$IGd}r= zk8ir~XUmr@z5AYffBz3I-~+z%9e?u1H@yCyd+t5vn4|yw-~Z2B|LARxKmPdVufFQG zAOE=BYOh?mBG3Kn-}t7V{N$%Z^f!O~!C(EAXFl@CqsJb5?C-zjg8%%_TVM66SNzs* zzG`Y}YRAORbI&_}YHBJy|1(ZM{W&K+`!(mho`}}0U32wkuRQ*lClJwdPI~s|KX>Kk z&0CH?{^?);`Zq58!#|!4BAZxFW<7n#*D3x-ZrxyZNcK2x#^WsBUz2w#gC=2~#m{v#^7$Ud_G# z^ntsty@cnG&HSNQ%^)<527-DLqpoCL9eI{oCf%JoXC@}vix!zY%Vp+VXR3AP-;#-? zgpMdzoWdMGo4|8L@g3c}JjVQ*{JFLG)_6Vwqm5USqX;3;NwA5Pv#rlO#hvIQ)Gb3g zlZBzS=3E}=x!|`|SkcrCgt6m+e#gRASiQNeY#is$6>KnyKbIPF{%TCIWc*+H26^RiLTEBr*`x*b52e72Aqnh>KsLzYR@zt9uTWiQUZu2hE|)7vl$4- zMw~B^|G;G;DispgD+tsOxG>JMtYyxz;bG)n&$n9D>8wwDi%C-<&?Bu2GKXubB=1UG zr>@?>ct-Bqh&mWC&(d$NWYX(Z(MG}lTLca|9cr~`dOGS(VR1Jje%t8%LFQa07UELt zi9on>JSk#C^%f&)fj|J=Kq9~A)nI&lqyDL{@@fYGtfiJfIkMOn(M}Bi&7X*Do@EiG zLa6@~mp!SU8I!zbXROkg3uziC$xn`5oW# z_YhM#_MCNnn6V>Lac7&IOrzXn}g})vsPVckY}oUUT(1uYJvDKX-L8+OIwDzrFDd zuYc`1Z@BrETUM`HbRH{`T+e*|X;v#~=T>&tCc4XPx=^YrZfWN3PA;M(N!m=WJMWonvtb8w+ur zAUniUyH?%_VIIS5^V$jpD;JlJXo&uyJde#O=QMUe%S4!}W>^NNvePb+rQ~xCS<2$X zt87`telzO5ufC|6kSlbY% zB!>N)5>TKK2hr~p4n4v$Xk3^WN8lZJI^DqL7iETC^Kt9FW8sXno(mL)ja#qcSqa>z zB@+c!^6|_e1*e4sbOsI)$VA`Dlnr;uz3(BzK)~l(yqw{9DovSd%!5q4zB_GiN!3H} zb&Rp|81K+{9W!lJ1mYth6SyDQ_}SdKofRuO%a`*@rq_5wt2&s?-(*;hi!P&egfM68 z1ZP-d`HYZ0lJ9qpLgiRpj%QTww}Q)#tC=V%)p{ausoYIcNWEe;Lt(gs2dkdB7Nf@I zbDu;%WATj&mg|&76>AFdM}XX^)rwBZus5Lv;rQYh=T7m3t-`yN=@Sy}V)!RCP;oaq z4k1)p8_(Qvw6!`7A`gw95o+~KY(9E_g+LGgonU2! zG7sCZD9Ps|w_4GfipZ&T?r1c(UdMoK=jU&ji4uE(>vW=cJoXR48VNk6f*`gbbc#QLkVXrHPD%fiI3groSLuCqTyNnl@cx_ z+Bh?0`Aeyosi@IWlXOOOy9Rp;PZm+PYJH@c4Aak8|6OUlN_-c}UZ=ima6vWELo-@Z zJEn*X=vp6@Il#3nRvaeqHHrOh>tW5_6o*tp%(c+u7>^%~T+TdOxX|z3O}!qEdOcCh z^`ReGCZdH47wp};_a!g+^{J_;Q=k9*Q=k8QBHFfX`-@-vqKhy2z@NSI9slFyr~TWv zzIDdwr@!Hi=RWJi6P7Jqx@Pt23om#J5l!se_2L)*dWbBaeDcZH-|)?wZ@GntHgDPT z51+a6*I)F)OF#OtUaxoW#*M{$F1qNFJ8u8cbDne3%TIgR#Kgp#-uQ;%_e+;7dFC^p zk!9Ka4?GB=%9maKsmIIT|F=_4`R%h_Z3j!;qmMqiXU`rY`tj|zf9cEr_}u3{XEw5& z0v~bAa)AKKGOs(cD=wIdCs8w<>AaI>v9WNAkn>mL7Gun^g&PGdHqB*H^BxQA_H17eD__$U)HA&c|63BRqHxi?x}dC`kJ!GjDre=U|+B93O_=;H`OH=s;R z6m5bpTXFsZoTJ)1!m7wlqy|10q6Pq$tJ=#=WdhHgAZ#+*_SN$&Yc5wP6NE8I@gNJN zt;Q@v{I6!_<*Y~L_^t8r&WaW7u`yySz@*VwJkYQ4@|ykA4&qaqVcE*0GCW`n-d4b5 zV0DG&S&s3o;MOE2SRypN?hXgDo{V4E3w~Rn;i8MW?!Yd%n=OWAs3%0hGm+UKEyFs7 zWw191^KnO&C}M9SjBtcmRwylO+cmCGvw2$F(0xfqlEgCCdctG1rM>k@J+E|wV>#L0 z447rB=X=Ds%4=doHv-784DF$)etu|NYs|de(uNnOnctr#8Ozd*9o!eFqU;dDUmnKKrb(v9T?WZ28{zzyI9l zK8J{Y^rIggn3*Zwwr}6Q0!r*X&6zVCS6}&g^X6KCo?hPDwrzX(#$@W-PQ3hC&}^X-*=BRjEn#T3E|k&Lfdi zE%6H;;dD%#wL;H%sB#F)1F%tO<`DCnPfhi9?dlwM7<^KV^^oPXgxHF8mm##01!mbp z*Cfqv8^S1%&x;|^%v^eT9RmEeERPCsMq%VSe%o4ngwxDx0wie2#FVGt6SR1oE$l$6 zMcpoG&fmbFYcx8q=Rp=Mu?^WaIdbO-aQ=G3e%8o@L&FB-L5{}P9O&V%s7|ea+t>EW zl8KE3fig*3`x>5Q$l#pdK^9saV>vOKXFwD-ser3P{}oW>Y|b3H;=D6b9hxTUtUwsv z>y6d)9KqKBt>e(;(*;`uB@oNp8GMe>n+;}gi57wNI%4E_=Q^%?f~Q#HTZu4+E(W|u z_E2;FYK%Or8e#nar1W$c&X{>{TD*PECibR4RR{1qsc0j%QQCfT;#a{YjJgEtaF0Y- zbD1E}Q;I7#-WL(eE!RV4551}pamq8S@U4QUm~{of=WN)I0@WT9FM$SG_g1}yA3be_eS&|Vz`=9go`(|1V&V+h6uf_bPYr7k8PwQw& zBP@CG&V>uz?%f1g76y!c{c=Q>U;gsbK5)rJfA@(`-f_pB+qdudqd$D>@X*kI-1HqH zde(_2Zo2P2BKrI_U-;ucdh43it3UjaONr>a-~G?cTedvqDNniV@=p=bDJP$N`|Wpx z0k6C6pT7L1FMRf-XWx43_tval{iZj);ZvXbG!eC0tz(Wps_4k~XYYK+<}F)pz4d!v z{pxjZed`4mU;O@EyLYczwX)OcOiWA+`Unp`_|SRh9=mMWvd12K?39yF4p4;^D^`q- zj_%pB=eXmZ`kTM?s&~BeU9*v7i4BRt9n_3#hBK)}=nobJ^A^69p(mPh&BWYZ8nZ*{ zzd>F%&1Hjx1Mz4nwqk*&Nb>uT#;<8^Yms}0b7qJPlc(558_5iOn%kO_L~CyAWKU$h zjt=scS|JIJno00+3+7+c$-Nl8TTm@q{JhqF(WtHj&oalz#K=ME5vFAl5q=$aS4)I+cPt#gZ~&P{oP662@4FmQorhGl)zW=L>=2iMv{RPljY zS4+*^Y{=Xz#3(+qjM|_?PjiVewCqW|g4Ny;?C{Fp(^yE4_;AB{5#1___muB6vDIvR zpV&bf`!Bem!}wW7y`E>Rj$XTPkO$o2Sq`W-#wQznY4P~R%%w~awYK9Td7PW>~QOqK34-*TWp^5UVcEL3A$b_g9uG5MmIn z3ykhKSw=%cG&AG7U5jVg@|mN`28Fp!d)**Pr8$53PXqId>kl|5vbgtYOQ^?Z+^nRl zKQ$(a?F)c~{I)U9ni9OsyuN|o^_a~BG6}jJR~?x( zucD$c_Tgp4p7zGIWcY7%H8ZRJ#64-PJpyZ(m?sAK+p@lXF`udlK0zk-4$yn=Q^_FKQITPmOa|Rt4<(vU-M4 zpCO;lXAH`-Tu5Hu-whc8wmsb~9C_DnWWS@C8R5wQh29&u^Xi+mAi=Sn;PH-S*=@zv$wtKYL}-!SAbI zz4p`p@R{OIXPj~Rt6uraqJ!TX&pnTbzVO8_9eeDtU;o;5J9q7R%CX0O^ke_~BY*qR zL7(Lt*WYm3X)n9wJOAg0Kl~BT@6DSx_sd_pX7AMA26X+xXY6~~Xbe~xZX9}~3Vof=ZBtx>8JkE&Q+ObY7y~#;GGm{Mu1MW_C z2?vRBSRSr#sT&svZI`EH^|CiP{Co zuq-TtKz9l~6tx1PYkbU8@6`L{_;s;z$C&RbT&=w$_{g#}fh0R~|NM$Mh4f5>w_`CZ zM`H>sFyHl}D*!(!oO4Aa&PK6mK5mJ@VV0Sn9iSc}K0#ntPs{&FRkw(}sjWw8Rb*7c zvdP7Jil1C81<`>(0gDqSVNaM<@$muSG)`Ycks!Vp`mP2(&IxvP>v+;wVCEhGu)nT3 z!7lxObi)qCMt3DU5u^1*{caI zdZLO8Rhm-F+zpny$#?1MYKuEw*pS5JA>%Xuig;f;b*2=PcNQkvzF%`a#Mpl;Mxl17 ziCWWrAS{HwTKB{bg0=5a*Q@kd*(5fl*x_s9HciH-3bss>lTptDG_r-c#-X?;uRG?K zCEz$RGV-*i9XB;Kb?2RTrQdnti6`WF{=*;sNdIYkeEg`R)@|Lo_5S-G=!dX3-hKA( zyynNZ-*NoYpZ>k?f1kg3&Fa-FSFL>T!H0hS^PeB|v=2Xg?Yz13etP%a;`c`%ebnBm zy_>genGGe2*HzPdHSUJe86iR(XBzL8K!*u@XzW$vTu_`NJ;*S61DrCLZJlL_#vfEB z0+7VA!MM7l8C!AvoDjP){s_}OMMskB97^w2sECK>1Eu3Ohxu|lAsosk=? z(TX}mBtYkBnOnS~I>)ZKu+6BPji9WC7x+6Ry_B9P1wVPOo-ANUW*M+=}CeEy-p^+`J59rJYicBx@{GdG?;l}&KR#D z21bbKrsqPFe&Px2c^Ql_Wn$USy3&f|k|xE%9t6=#iw=S6yg=dby`ZOn2sL!m9OCI;>rOa2L&p!67t`X~PWLC-Ji zT#lK4anw)a&C^~`oTmY`AV`L((KL4f-#dKH&~n;pqqh61a+_F+8ii=jrHXs(oJp%XANMJL={lC)zNyqel z>UL>nh8lBdNd`N=1-dD@`m;^ruXsi?yqtE z37AVW?X8(%lRaXk$lxLIL z>qa9d=RdGa$}#f?9>^yro7_PlXI=Nb?kBrF+1H`) z`s{&y!Sd$N^J`*{60j!gRt6ivH{1V>aLT`kSE9R4(^IN}WEw?q8pmN9b(4V6gh6FO z(#7D-wAbZ43c%}l}hTNpP7-dOWkFtE^}aZhcIN~(|f1c$Tbye zPe3N-__d2@?@iBi8|xv&==~yO(lj6WUndh|d~c1cD?=??(dl$ntmv#<*+>5wQJ$!B zIdsfRYOL4YNSer`R{ff!D~QrO22FDw38sjHo0Va;wDC;oN!rpWFJg#wl|KQrSK*&Pw3aB!VCG;&w3C#d)@{ZKe2HBhwlU}b&~TEXQY=1+B~(wgr=F^ANr4niii>uAXEAnw;@ zPlfdT_aOG;K`&)u@1*75IBG9(#2Q)h#AXie@3o(5zIW8>`(3;8haNiUy{RM1twrOD z|9>9GeAcs0{MNsHd;9k7vmxC>|;-oh$`Ht-kLKSC&hzI6%Cey_nhKc0{tRqG3Kjd zag+xcD%_keac4jYjkt{D11+LdO0T4D!-!Ad(-dXlwQ z7!6l9VJwrt->3V(CpK)Xx2B$0-L!3)1bL>`G3befTS=c04dP#^AXjZ3Zt!ekV8HN= zPl{iROp@!Evwe}`J#1!YvU&4{)~@ZWSP@|`8P_d%-X^TmiE|brpc6<~$gmuCw~pBb z9Ttp--1-T-s&+&)eme8Squ~Zx8QfaMUKYDAF>_n~90y&)e=TGV-;eax=eh9NYBGG* zX;@n3O3v)80rBnznZ<}6|gZpYaN z&1y77E+%#c|u}OBh||!yo$J__mz!GU_WCS@@MS3;zs67?oG|d-R#~Wh0kv{`aaTJ z578XL?EtV#scAIvx>H%_AQPpok^O{uGOkzrUL*8G>`gs>gDfMIcFL$)S{LtyMlnk0 zX`^P6^~mMMk?w;Wty#o&Fk_qE3x*7FL{+Rv^O{|8jvM!1^QJ#g79kzmg&oeD#2gyN zVZ!~oA-=dl4yI8JMK)Z|9-!JzZ{?2D6!v}cID51J`r(hhgr<> z?CX$wT{XS8Chyen2GTqim#EoKT8%Ue>c0xVoWNW+h_E!xYq2QvV3yQ68m(Mh-JQRw zHAntaedSt%2soZ|bC!=f$>{OtJn2cpt5;*NUP3GMNn=#**T)Fzyqu<(Vj2L8s>kO~ z?$VFk9Gg!R#gc08z*ZH!j?Rnj9L-1OV}-xwx$pIW%+tDk&@y3>6dv2E8M$sBaYSlO z?rp%4E14MBfBe}7HhfTgt~q`pTF0Y@4bO!nmyWElcmgu<>1|a3j zF1%f9=rzGyC!%kOOf5vzCYX7f=^?ABoHxUqrZF6;jdgGWHR!{-#F>P2mAki^ta=~@tirN*N{wjkI(af7_HQ@X1P+rJg?R< z{)uH?j8?>C)!ylE_Hy%T4JXkun?G{#*!Xds zJDV#*ZOOdG2F7|~()B=l6(rD54+TD#*%`3kd94FD)=|hWe(aPDWcuv+j*IsuTy(Mb zNqatA^00i0k6&2q;&k8>F73It_VNhOVnKQ+_gbOb+NwZMBs-B!2P|H-{?B-IqLbZN z&1mg{#}fZ9G-dJ#HqPagpBa0?!M5aVm7^1eEtysW?T~0oXomP>;X@My#`1pREIE(9KF6W%UO#6UB z`*DDDqrHO~%@Ce(39LQ8P@wxS_zL6hRudbCVq*XRAOJ~3K~#qc=erftfOEF)l9rzE z_<;6}V2;H0EfbDku%9u7Qx5AG<1auT zQl4OUQCIWS_*TDlqtB_pdWdW%NSO21;i`kGd`2h}g)4Mfmhalt+r7KJaAEisUki~I z8W{APApkuDn3U9hVCJgfY}Gy1IiF=!wWe;6%_#Y}WJpW9luy+^TDPBWE_18zb9hhj z6#@q>^dy!^|M#sHg>LUcPii^5bdP(ZN4$^cBUe-79b%HTluVMlo~)6}hBjh+3{f#; zf<+Q~E|*|TJiexqjJvLT*b+K`?9|ASWGpOasU(soQAk_N*~YhWyOd(EQN6ZnWdgfh zTW~p7w(pbUm;1uU&ELWKXy6xIyWLr}sRP`qzpij^J`)Wbrzg;jxjlaDD_IO3zW(u%k{yyL4M{_*8Q+|d2w*mid# zJ>g(dh+EWqUnA^dZ3%zKf-@sjE^OXrSW73)dZ;I2&Ib3iLI-2SeW0^ zfS6`P*==xd)iyA>nh{nVo(r}+T8}?Xpy2D+jfFJ4zAvP`1?V)|m|O?Z#7({n>0!8KjJo@A{ zeuDi7e>Rl4zW3F|zRcArl_6WOAbyYT&<-L}SDU*24x%ZiK;is<3tbq{ne#c5%WE8CEbe!it%V6@~ zwA4(1kf~GjRMoH)!5lraV@GS@LW`G5at<L-&x#7B+H?tCu;9kEPIGUpf3}yEqu)Ij73lRSP#=9 zSCPJkdZ^b?GtMVv;u~nMbUe!-s2hDujk#;Y=lwYLnwAA*Jn!)i(MS79D~mTj#~Eq z{(gzI1?XvPe|#Dnu~^udopMmg}3d);X;d6P_amX7zP7k)CC#WX`mjY2lP-e2zh7;p&n{E2d_2UC9_O$ z;(5c;MpfsUU7gwOh%Z~YbCC^fwZg8)r=*S`7c=KfR@|T0GY_NjTd=;XYQ#gH2g!JC zy~Ug<0<=c@9ON7%ul90Z50yRScLd!J+!?l!!YL;9yYMsu7ZcZW4Fc2oT0UC@Hwpf* z=^F^<5W!<(#WaFU^bWFpY;GBF`KcADOmXvTd-hg)vETQMZ)Gtv&P$JD~BFq z|6^(R1O=Y1CV^In0G75t&tiuL>ygmIAZ-N71NNQ0U!L#C2s>saB3!M-ImlE1MX zh^ON{+0~^{BlWz(W_@aW#oVOK1s*}joOg>t*i0~T(8HkHOw%OB(CWO$bz8G~m1sUn>>Yu2G%Dr2 zff%Bg_5sM;7`c&5;A(P2C+vCP@_&<-og%)p$%NCVRf5S z{lNf@aw5|yWMau&>j|d}w#N5959=soUfM&AhAw`#7B3!JxiT9a^`h3nLjH0FPwTAY zbeTys7LMiFeGa1sWP0cze%8`Mfm!Bum2^Q)+|^T@?-uPoN9c2O49uj)_}R#Jg1$;Q zbATB)&_*>f=Q#1=%oVGtd(d;tvdGqGgsGB-#$Hjt*&mb0 zTh~sB9`^~pZ(S3nz9kx8&m3BbAlsnfVm~r_rE76GR#!~e=ecXOgv<**O^#)bForRT z6L&r=Y*(*G&P94c3V+S4tEk)Sd#wfMa?#U|TVRc|j_nDkS;?}fN-}ZoGYzjQA>wZl z;dPK*hF&YhRgCtzBWCV_TkdYQ6ez zjoS_Wo@Zn)a=#nYv6UPkwRC}5j_;CQfLiqUjQl#Php;+urkNyeF zHSe=p9e{6TC?O}X=P^BQ4Q7YUInu*`D@2^a&PHO2kxicE#ICpB?+jKiQ`%}|m*7Db zjHpmg1fLfnT%m=Ns`b(=nV=dj$3oIFVq#(R)X2oJ?dF?VlsSEx{K8}ciGAboGpm}9 zW200osk36m(8`r=csSB1q2Xk5xMM7&W-v??8yd(OZNpRhC19h<3Dn43W0nQRIeos1 zYImciB*N5Ke2$D8I@q@=B9GXa`v|5&)>d2~)(}(XdcOy45jPOMf?Vb>dnC}r!Jfca zImZqgY|EPO8)A;+swJ>*F}9uJI3TP@aFBaC*g%1UhBrg8j+#k4oLPYkAzrV{(o>7A zSaW%$QK;6)LF*dgPdVSWg+I0Qu$C8|LA;}H(O^=J2N(ERwf9MTsv6R$(ZP_fGc=qK zs~?cg0tpC>JVSB%*yAL!8QVE(-Z^Tw3(6f|bVWvTfXLPef7*v)BffIst8i%)-_mMD zbB35=Si^_Fxuj5>fM;3nbXDIe>SGf1ofS4bF@72pqOEIiGi<6~ymy4z2?FF?SlZ*^ z`$eEQZ7x?_2ds^QwkO;>wclS~KWdDRPc*f8Yw)uIn{VHLWy~Rg-L8!`z_9Yc%G{+- z_GB-aG`BVA9o1Y<;QcyLXAI}xP*1oYWzD|{*SzrUk43t#)z<)^P7 zUAMF|Ox=lxr*8V{&o94h;vO3RyA8*jHdcIk-{oiAe$j^9Ir6@9?zJbr=Y;;SH@*A& zzw?ce^WVMZ?DZq7hiP(h`o5bVecuI--C1T4v%^NuJb(FVr;HxArZYB{^(JRFZ+!gL zZ*RZqitTqzf?Zww9DVBI^Io_3`6tgmVrge=IG@}$^S~XuZ@gjamDfDJjajR)bDnzJ z#bZVqXwUmkzUvbYlXD|)`^MAX`n3LUC$7Esqzfme`#i;Ce|F<>=dUaN(!K7y4R5*L zFFkR|>(5#8qEqIr9&5L!XCB?S=cenoUVg={2c|JPY4M3m-+a#EQ%{+BuDuh#W9FL~v15=<@?pGg`H?E_@v7%;))ZEr4Z z=mlDQnwCt2h8PUXkscz0vDYt3?G&GQHMCPlyt3mXe9KrG{=xYX;e?(luea zk^{=B7?~s7>*~x>r;qj}5_%#eS>h3n_on_OG<90GMm&uWIh&WHn?}|D@NH$6s14QzjPdKJl&83-M({)@AXqt`GXJonHg{giFai< zt-62y=R|bdZMTKNW--gNuS4&pv(wt!PA1%{qw#kvKre*1#c z&f0$QdAr02W&SyS4UfI^%A?Ldu5X^lm&_em>UQ+8OWBd9Kj~waEPZMN1m|g z$P*U5`gP0y{_Xet)s5Y*%sX{n_^zY>;@r_C%wZT?GJO2{rN^&d`kHgLUUb0&U)hv5 z^SMJBU7Q&KkH-Jv$PKT5&ygSEUp?G8;)L-dP8fgXD;~S>ocsTEtH{W8PJR2)AHHDz zczFL<=g1Ql|G^34r@v(LA6^-8E0e*=t9en4{3IY9);!LztFJ(~b8brYew{ybn!{J0 zVM!c!1hXB6?1(YxdSyEu}oTX=CoI?99ppgNY8ul)J#t-3g9$G!^6r9X2)u{ z;Kd9f!?Nc5r6jAh(1*mum+k$E@f=~>gZ&I}N{%4&5Fi#mZ$;z^qp<>8C)lxggtXKv zudZ`Yl70JGFf5CDRRLJ$Sm3gW5NYf`5n#r)1MX=efw`{qR-EI`xl;I~e2+|#(L1Z=DUItyK|@O5$b^kWl@M5l z9N`;lOVk^UJq)jL1h;F$GE1bS=7tt{!}v>iMU+?#tBAzK`wR2u>I)I{ZP>vr4i3qe z9eXK}mC(Lrc#L-2JLkGx-|dQ~2o~?E73qUvWqGbyGEWyr^3@P}!T{-z^ViH_E1O_) zH(Ok>S|%#eXY#Hh8RETzf_iXd2<%PVucc&yOOU`^!^l@-X-9shRj;5C34$*h$*_ib zR$VCSV-ATpuZ_}8asJwRYnoqGhmGw?QYMxVz1h*Xvti8H*CF>3By@S+XXaI$n$f@F4aLvmBSnIJ@k@@p`cfWNu3jT+7GHAZnr940 zzsz$Gc{ujXPkz&Je7EF3yne@)oQUYjXCC#rOQWzdf3=s4zWI~Kz3by{_<%b zkHX5)Yy7ySAG%`QE7vrVR^`lk_@~@^{ug)bdB<2^J#yT#zj)X9IAiC`^VW0UbMkDkC9Yt3UUYl!|K!RUj}K^`1gmoS0y6R56V2@089Jy$pDku<$s`Gf zj=nU!oeQwsbY%6i*N+xv6(plYm*qRM-M$3dt+g#0F4=hAj zP5!)E=3KXx9{OI-3z?{q!D<$I7^BI+BbRytT0-;Hw52W8AgD)a2{B=1P?9>CK$G6r z6KijZkiWT_$RidMEiv@apXth!@$@3aNLHtIjrPfJtB=vd}+i*(jA+)_- zy|}Gb_RVBi4$td>S(JJhj~opS_onuvnBqk$>W#gk&0z5#MWuXL=-32#F2M4P2#%Jw z7F6gVoJ=_G`B{v#5v*gZdvF!x0d0{2+s;X~Ui#<{#~C}*E7wH}AsleU+zOd1B`?mJ zTxn_XM7dsBMfxl^GBaPmX@N4)%OvbJGI87h$`e6rqmYT7x#G%?&RjKTua!iPEKHf$ z`-Mu!=d2GNrV@xRcMaa}K@@EKN;8Fx)w{CrJ(yDjoIU%Aj#?Y3*TBkpO- zZgaI4p0@Yq6-J#w9%&$$+uWOp3rMW+9_@W_*Q>3R5_47~i+|Y1hDW>#Ye<=h z6^tK^!Yx&gFC6<|UWB%UgI?@PE}6QYI1W>KBi=G&2_IgJ4{ac~D$07-cq!sBp*=~Q zW!B7@i)CKyxpQToJK)|V=C38sJxt;ASpJE1nSyf|8{>B|{NzQG4CW4_rK6fw!io|NYwSw`}arUpw~VGZr0F#9W8xzUG66-?aW`-<<5f zYsKkLzUZuqopSo&J=a~o>+Y?z@`UlzUpi96eA{cr-*fTO@4as8W}@jkw}0xgso{0w zuU{WNG;_-}+kY}iMANtLE}vSX|GdU5XUCi{mx!hw-gVuzdmh-ItvhAu=_hx>xJzET z^o3(PzqF?>?YSqe`tx%~Mp#JwzHe{)`YqFsFBm@IrHfy%t~I`9SY;?pTw#)XI>NYF zJjjVWYLOKe2t$jvRC6A|+$pSB;rY6N7>Ze3tvoQx{25rw`2G2wJ-wYf+e?;^#w-uc zupH$!A)YIsl@mlX7M?<5I`qgkzEz@V6P&*gr)+TkCPtP#$RX{o^+Zz{=zKNHjhpyW zt%sH=QvWk zWHA}j2G;5cdQ!4XTnl__0_`i<&%^k|_YMuYEX!wR zYSs~AMIpv5_MCw|hnQbMhpqP}8SD^go@I;kcTj%YG*>LXtD=$h_)J@@qfH}}U6VdT z&@<8ewrQ@||4(?9A?GjTw@rr~(zTMt>X;RHXx*!mm)jHderI8dl~u3I7hFwNF+-w% zOoBgGV`do|7w9&}@IuUN0>cY2zi|9GG(=?gwg^|Y@ZXw$-}2|-S*F@)V!mnkDSa0< zVh-FxFd1w_uOpH*TXJtAhpN_>c4K4N*qEQ394HPm8(E%x9onw|CgMp~jxN!BWGeB} z-;FEISbiEq=Wf1!pFZQLFJ8L12zHOJU-8-#w!Z%dG`(T#`+qN`<-({AKm)B&piD@>&tQga)@R3(u`qU4dG2G@~n)g$A?AB_*<%sulhrO^NC$i$h zO^gq`v^ZI3MK`<{Ik@Ro|6D%j(vO!+rn+}Igj&Siu@6j4v=%Rp`qbMz&Xngc5FzVgN8W+&$m>9F~%hc6r8OnFM`SJ)v8Sr2}4P zJ{TOS8V&Lf7VnAIlyvubJ~xYvTCmO7tJz0FSsXQuBypIvt@f9BlpD}CvYP3IhWL1J~%4kTf|LA*lng)k^ba)m4#}xT%Pw! zJ;XW^gHBsn2sP5IRpIebvjuWb!ofJ`NXYt|KwI`Q5uyF#&ath%z9vVa(arq5I-VHvY53^#n$_>OTU} zoO8~(_WJds72bAo=SSYP{ef!O{HIsl|FK(g@r_1eC%^H78~^6U=>v{-{cPgzKQ-}K z=G>fxFMmOWWq#nR4}M}p9>$s8^w?irGu173+VL+tY-Q%0b3-Q{_M&y+Lo+vAy7{I_ z7jjLTuYK_HYD#lYed$OClfDrrqB3}vGv~5GP;rQS*GDei{=jrFXVcexVb?_U=hpZ# z$}-9_=jM#9Kc$KrPj7hSJ(umB?lU+$q61%f*Tdi37EdXo0Q*1$zbqqXBJCnzvU3Fs zVa9$||1|S&*%!M)bS9FeOfp(M`d=+n%fj7+HxwTUA1ps0{)InfSyudXS(cTbs+CDG zejyo`WxZXy^2Z-9oH@tYisMaDq1Y@{VVD#k4JIOL*k?9gm;OHzgZ77w6g8f6z)}IQ zvvbsH^?$ZneF<=xhw<503vF@52qF((e5BCxQlycdgz*Kfb1`zsD@xAwe`-Z4J}acn z=Ui%tV?5OT6>PQiQexfK|I#vHSSr(=1eqw)N&<%t_0Rwj5ZiaaXooo)dIAS`h@3s~ zZy+S59+`|WT@owg5P(;fFld>Sngfu5K~K1n@bSeKK+?{fEB?vx`N$6;UkV{ z&7Th*Q*24S97%u`X=#Kp!`6d5gUVc(GO?^yVNznNLSpY|nQ+?`?8H3sfYHY%)$X z;hss_AJrAVjTmas2Gssv|4g}h|T9#i0z|F=CLW4cgc7k$<>Ul zWi=X`IatSX=G<1`ImZ?N%sK3Cp-v_YIl`}0C;;Qig!7U)j9#A-r4(N3V2wsDZv&$*$@`6+gA zG8g}1jmj1*$VNuYIE?a|W+Th9uU{(gGQ}*q6!D$;nAJ;hDh-VscT}a~(?6K_*R2t3 zB+sY+`5SwtsRn^XL^QqohOf*B8sFBs`Nvg&=lt#sQ`2$YY14+O5O*D3H~K3b@4a7q z!u-RlargcB2faA)HM8N)=`tQoM?7u*LX*x$GpG;;Vkz*K%@?GEB`JhNGPVW5L54tg!(}H=1Iai!?LFGBJB2gd{T`&Rl(88$3+Nj4b_EN6L z=PYu0PDDeiR&@?P+|8Ne6Z22PkbT_=3rX=x>l}Gv^tSw!l5AebJN_=kY^b>SmDTv z`yWt(pBHht658qDeCMrDAlgx%zlIHwJj;o_ljMuUT=_9eFWG$D7RNgF;sVVmbVuQq zR5ljy=f)nI*KEiMzHIg%K~JiY1G}qWx$X7HW=a)o=`PEt)hcMj0OzmJ6Q;4isvE4Y zazARBFpl+TZzlM86E@Z`~d7;MgJs-{em!-DZGZtFB>ahnI*1Efeho8kcCz$8EA& z4=Qu6-cL;Ccn+(`nz6c2LA!p8SL;Bi((>x68qGxlAI%@F-} zw6iRklx4i2r?!Rk6z4^nBe>ljTD7XPW=)U)o^C_-1qwZ zf|<8qS7#-wVCGu4IZr)1O#t6Fe@_^{mSMU0S?GDNA)0TuoH?fytiIRaOt-2>ln2q10ZR9{6c$?(@Zg34q$#EmOt!D#q{m%b`->!dF$w3BR);d=t`LHJHfMz$G7-% z)zM?g+^TMI2H&@J8cGgU&4wJL&6t0&Qzuq4%g7?@*M4lC6CXdQhu#0cS$wHnW83Y1 z5UtlUXRf+~I6)0j7*9y4FJy>#OZzT?Vpp%fRSE_kOSgp{3J!FtaD`qm|Av6R)I*Jh zY{<`Ram8{53|Nwac$T?UG503o6h;tXN--g59%MsrF*mfnca+MXl8LqzX#V1z{LGT9 z+csNWZe@;arcADQ85j{S=>Y>gn5>&I%CZ}jqkzs`}6%O8C7tN@mF_icVa)~vDI!3wT1_H zoNRo|6ZxUxK`I62v=6hTQ)G}z{;ynN?t7T-%JV#HwfZAleB72E2A)BS2bp4)TLdyCXs=4!QZXcIJ&Czfo3{BbrQu3$ z$Aq4nHr5*5;z3sIqL!KByBP0!i}N?Zv)oW7I-(TM&heEpMmC`wfy>oqSmw_d@#iL* zWX4?EavcTdFVsWW2AwgZMf~qEuV{!iy5ZrWRjWEHR}$niB?CdjxZtJaH7Z## zX-;$g@)W7C;k*l_&~3#RtYwnswuaevxsGK=oYeS6IaoG7r!R) zz?{FjrvefS&=b9Pl!kaCkuMpdA^W*CzF0@2O93T1OLJChW9HOEPtr2Cc$T#H9SmXV}9E=5>E>x5Co)oM-ij`R2R!+`n6SB;HZ~4A}p`q$6^@Y3{$w+;z*o2^U~6Q7)6kWLbK4qwWn)Xjd~UAu;sF6Dt>iPcqj zJ;ALU1m!i1ok*aS9b8k64bk9M7-;6Ojy#8%)dGtx@QKw8p^%A&itztddGf5lcNZfw zzDN&y4T;g14rJvONYaL)(10A7%Q?nS_pexdWN|Iz*r=dLLc?JL_pn|@Uu+jg#3aU$ z-D3)Ha{@J$Knm^7{v=XS|^U+0IZF!;ZQa|vdR2Caj*%jDcrw;JGF={ z3K>E@Nir;xMv!qm5vPf&djQYKENb5}C|21jqQNS*+mNrC=#6F@3 z%P+Beyb~S6>-9tW!Nb%TQVZ|T^-x&R@YkXz1S#aQ?hwUyhmfk#-o_!B^Yay060V03 zZ-}t8UN{5I@xNk6G{OI>6WpmvN{wBO{FCPO1Y&$q9N7R9E$7e5>By>sgffRRF^Ul> z!TFn(xsB$*w+}1kAS%GZUDAF(X-fJef=cS47#|@_ffb66Cs)G%$*4X7dZPWPv`nB4 zft?-_2zNpa51{onjsuB67aZj}#~iPt#GwmpzU7m^y(8>uLQmk{X)bdi6Lkf^YYJzM zG(m5}Y;@F5Oi-_f>&R{7@@!;z_H_uq7z+%}m5I&B@ovufJfGgWm1s@>&r6P)yU6c+ zG#-D&adVdd)D<8hoi1yEtLx=M`zMG>BI10*rFXyO8+jcPt@rTO16>;K@4V@KSD$y+ zmAgfJq_KFs_r8yg35c6Gn1VZZ<~b3=w_3Eky~k#@5|#4Fo^;flHcib$ahj~i~M z;!06eLQt7m>nQqk2dsycB{T59yw^6wUx54@>aEC;mE_Kdoh?{{Tw#_49weJTe`xjU&XOfUXEZx1r8Q7UKEb~)WTG*-)_PeOUJd+V zKZ8A#xW#b(^1Z{OegLJ)Wt80mcOA>Y!t|OZY#YkHc zIAV$!OI1}6z~eJ+YZK9__ha3fiZh6xaT0OK))eo3fsZQ#-yCxtRhiWKX`${-WB1g^ zc+MP2hxnic)=BQufLYcvXP9M-&JL(6QDdPZ-~pQ~&jqM0{B(m{?MjDJ8GS;iSY5rL zZD5cvs~qAHG>nO6)hFVj9?W2bHWgVt=%J~->5eNW zpdLz@2-dPfCIurNLN3c1HG?(Cs!qdaD^o~_$~OZaNCNsqlXppv>d}qH64v;*785dh zL7ZmnmFa4^XyysFJ$=8n@cyw!6y3+x;MtG$~=NWMg_iRavE@vRm6+sxGM8}qZ* z^mk1AY2&Y2_sIXgk6HXVqrdaY(YB5It6tMScjV&Z`M_OMcTLg}{m^pzgp)_-Tt9h$ zd1l9)wf5{ay-gdZ?!I&CC!1!b91+o@8}|L2M*Gap!}CsF-?{4AF3X^HUjF~Add9we z8#nE{>(0IRJly3x6+O0P+jA-)aNm^ZM6o9bP>u1e&Vcf{?xwx>Y@^Vn@W_`g{*_Dj zet){kU$u^0`kSX@296ITpW|gRoxcQ6Q)3<|js4X43y^;+v0!Ee988}QRou$qf?>T= z&pF^*?aaUL^?DN%?Xj^mr?8px=U^6y7i-3_EHTS#BdjVlR)CZ{=nNnqTdOc?6lTQ!~rrdYF!n#nUG zqh|7hYs^la*Met`g1?Op8oq5&wHK_sCWJr5rU^!(xs6KjEF(;@oH?w#wkh+dWLUP) zf34Bcp*3sTc;u1+A7W}eA&zw-R|GvRneY|2;A|S-7i>OD z%*glXEQrR#3Oabr5FPJ`gwd3M_e`x{(C51t9{s%!t^M(9AG&FhyeAr-`&%De_xg45 zSLZ(P27&~y-~`;a>tAo~p7zrIE?Rxo;cvVCC-1+x+n?u>MQ?fg%G1lR?DU3*UUJ5k z`yB22{`UWPc@`c&d#f=>Rg5Q?k^YdJ?c|tse)@{BYYdL>KNp+fG zSxeh)a96_>o51acp7M#2oA4~dnKzV)!}H?ULxANJOl|haCc`rSgvNSUyN01w0WL& z{{v<=!Qd=dtp#F8X%5DiSg2}5i__WhPDCGBYZY)4LIUkbBK$X$85!3SA+=O2765EGBIXO*37lcIewYbd8xZzB=g`^LeB)@SL*ddGdLSE z7nK}Py9Smy5kbC3s|JG4&6@e>D9xQqd-s~i8)SB7Bg?a|L+>RzjVAC-`u#kwlYqW@W53gDJv2V`3 z?wakl-s=}Uec_pBjaRYm>B*}nYp*uBY zlru@n93}EvF)XP2tt+FU zx}z><60~xCsP8teDsA3p7G~r`7ZIKoEIE~YT(dR+kF(J=Mdt?dy?cARcem!uk>eL! zilyT&PbLGcmd-uO+(hPJD|3JqXvk`xq=6o!X=hu(O-%yR`Y{%-e>KcA^fE=>+$g+@4R!x2~|~4hL31g zYyLBK+=+AA>Mlvb5NYgxJby3V9{{JKwjj%#B3<<&1fcXAbl2hbN@E+f8z8u7OBqH) z9Hk5^j%;O_HN!Hfhe+E%@&2%m*6J%ZWCuGy1j`&4mIe0T!shFrF)FhqskajRS@c$`&DHvvbg?3Jp=(ztqbR}=KB^9OXxFbUrY1CbF416f$)6S zSY4a7*5rxSCyv^c{LakJAni%=-+KXg7iT4R!bQF_ezU|g!l;(wc?bNzLMFkDX|*b) zV%WgK3T0()i7-g!T>tmto66v_i#yXe=cv_kM84MpRq)DUR~rHHcXPz3*lR`qm_TW& z`dE;~>zcsT7Ty|k?exsKsj)HFCYNjMs7KbrxTi$AUCSvfTt^G%4zd3N%WRCV?Vj}a z5?rym#6j%~Y)_0QZHUzcHrBog%e!P6Ys@v1+W|3wCVFD8ehQbWw5y))TNM!3$b29^ z?&g?l(aka;6H5;TzkgzU!N2d!3os3Y{ntFswb*~b$8BS@2djq|T;>ELFyW1zPBu1{ z@7rg^Vc__)k>%Ofq4z>i6tkZ%k-#m+_p#}nAG~Pm$(JuZBB=b>+~>V?ZW#B`jr&H9 znmbl~buUIx>J$>^ady)i9)9~LM?ZhzXbFoRe%?82pQk?f==Bd=bnQ&Y2;FzteSdlC z(=Ixtzx{@njK2DU(O1h4b#J=({(sn1#qy@_nA$i^OUhvG@RE_|ttXYB=gyHt zCRf-iB;iC3wvPt&&e6W_Z+XuZ3$HwHB*c)%){MPmO?YeXo=y2N>pEp9yq^Y-v6&zX z2YHqSHzi-kMBX^&r7|%f+*S%a8tQAT6*7z*aa$*OoJrkPAmg6Zdsp|P@9x;qS+-2_ z=N1@SOXdQ;j8-WC03ZNKL_t)GByeiUe0u`3tP!J~Ty=dd&1!mbR ze(;n`>dy*KHg9YSW0J=5+ei(`O{qJf#i%a5nwoLd3%)bR&&%~BX%8_!FA*V*&LH#7 zqD4b%*0e@P0YAINi>hT3XC|mFF#z`t#;Yo3^M2&QK-yy7d4^dAdMIQP@N~-b0q$wB zHwEWlT2Bb^3tJrM_BkTeTj+_U;o)altp#|Ng&qp{D(7)Vv24Myj%ytRA7x|AvR5qF zsSF&Ho<@#ynG;!Kf;C0;Lvp_aJtv}CnZ*8Zq;)aFY-A5PW|`X)wy&M${AC*G8G;Ge z6Cw&bUF5~X$O0$DqaLVw!#o#aR69U{A?|*8zUz7ml+ouhY!EJ4wWt@vY%)i&8f_yAg&apk77cGm?-j@@O^-!KcMV_YH^)UlApSd(f952jP z)2Dez3ln6pu{*=}12th><`#c$iQ<%XR2(l1xm+R7vdCbedNqRGESmE;r@5`soJ}5Q zp6P@v+jTSdhK8r=)f7+$&YbT}yx;k~&tf=C_}dAdXvmMBzN?bWvz*`*POqaLytsyk zx)7u%usX0-=BYRglr48MWc?F6?qAHl<+M_N8R@A`OHkEA*de}e`T{_%-Po=_tH&8%iOptay;Q@ z@vnT5{z*jJZ@B+WZ=ZNzT7OUe!1ee4-iP)~v0wG3`N$xN$wn?raQ>qY-u;HZ+PQh! zdYb&6Yk&6I3wLa$BEepKc>iZ!_tSS?yKl<+(Dd{T7vFRK75ln8zq)P5RoBmSM6$q&;^WVK>;~!o#g{C@l(_h{D-fwdzXrE^~bLh}&;&l|6 z77hPsfoO7?_D9>@@01$h^SLr0L;Y{0h=2B`=KwUq}Am#yUnaXJckr z7Ku^lq2@tW)Xe>l7vmHoE3}K&lm46$&$56%L1{aU!hm|H!Ex!mlN{N_G<-4{WCG1iW8Pl>_>>bQX@-fJhvy>QdPgkd()|Nlixnf zLqXgQEZvSzH-}~poIpKiuG`w}2{LEMVy8o{T(7cB)=xm*Sm6{|9%j3pwcAoA7S=NU zCr2*NEa=H&Br*e2Ynj9INFJ5qr**ECBRf5Fegud$Rku{k>&p3g!w%wm$JQ1(a|^c^ zWDX339GPlYQ*a8S)r31SA@f9tAR%*nA4$&~`hid;;?P&8q>D+ zv*B=jYAkczc*c~8_)~luTJEq#ho+L-LyJkg7;|KV7A%NX)9@8%G0U^BUjoc>if38mOcE?MMEO0}-2JjU zmc04=r7t;Uv46n@7!^1ge^NAG^Y z^^4B@?WHd{dE}_0I*W!gn$CA_-FNqf-PeEqkuTmdvtP4k?tlEM3vU0nYZkxuti``} z!n`AvcE*Njdb+#q;k~!ryz`5ndF;kbUGFoNUnQcMe}3B?Gxx52!z&g%V{K<-IG@}$ zz3GlU-@dm<4tkAQE)kdU(-h)zcK*`~2Cn`8{6Fuy^ZFIPch2~)oj7;-7)@`Rx$g%% zueoB&=Wp&l_1)})`+JY)5?5v6TNMi}%&K8dq8Ptc&&MtBzZ%=FdL{WdcU^sjVo?Nh z);U{S~6@?9N4j=Jw7fl#RU0z5n7oHM5p8thTO^tjGi7jo|TKiWu{C712vRM z;KYpgKIB0LeB2D_<`r@+;^`Bd!lF~0$RTr%QDH;W$YkaADPnw%_#(AT5cbyTZuLHe z7X{dG=($2%3GC`7(47jitj9F*2x*>Wg(OjGJk5V+eDtIbJ$@+Yu9G;=5cdsV!I$md7?_dVr14`*TddPtR`b}M5{i)Ng0xn=*W^`nIJbb zXISQFWutbfl8NAoh4v?|`5=woSMP_sSP^Rz@0ZZ)Nngb#C4}r%m4)Z;RlDAtGaR4s z^8()X;v5u+POUAW%2?DZ1RERhO?;1M8TER;)smQEfe~i6U@vNYAikBOy12Fo{FK73 zT0G0qeSupNE^`axZ()$b=~@1jV+{GH07e*|3&KV{L7rtUbD<}k1zV$W84JHtl>{L* z;(I;UYVqn5Q8gkNOA$sd0P%d(f*i5o>2)+_ZdDWNs&gM>52XyzIhE)JAy`}?b{=s4 z1|h&TwPeB@fNLzL&~u1DK^X=QMXbG^HPhNM-Y$S8`bx6Ig+V{Bj0BX8LSrsV5W5Y{^ zXMVmd?_$@?&;5;KzyGoEJ{!jL&OiCppI$wgg1MoNXVyD0e{O>kZ!-o&h~FpOK}&n; zl%9B8^*Od=&dBu4qm#Mu{_NL2_{`6p)n~)#ZrHs36`Sw(1Iq-)Ad<`gP49b_OzLXk z6W(wRZ@-qFq=PJ$w{3mUIoIjTd+JkL^XKc+#VgM*@{H3mNvNGco@-Vd8#5P~hV&+? zIon}kx`4C`mCyIhj;{wlNa0A0c9fN zxki`2gC4oIi(u`v4r0tk`=-g9t!`IDW-Q){$m?o8)pZ1_L1*dGp|xwXIdf8nPBKDg z?#c$H56Z;y_)~+xGY;t$??=~Y@9>c|w}!GR%^x`>BCAUhTu1mt#>`Vp4v8+WFej8Y zZbD-Ra(f!iL}&Y&D-2R0oM}F&F}?1Ht1I7|j27o?yy7*M+Q`}i)VJ-md|JVX%JVDw zo?q()q0<>eUEP3I*6^~RhD#f0CoIE3PV5{MBb$RH4lEdrYIyF5HL^$^h-5``h&YHl zRF>7$O6!Z&pyzKGv)2>BWh@*^CTV{@*(PMrdxTTHE+y==nI1+g?KZ!yIhr-H#gCuh zS#CUYdDcKZKZu#v-rtqR4m$H1nKvBG(rw5$eQ*P+u5>kiCS=*Jv$R#lOU=B|IyUIN zG{F73p-Hdh<8HE-2eMI#y_ue;E%Q{bmqE0y=2@&e)Du|KMt|}?zwbUjg{$nl7hOa| zx7~JI_{}V4dG>V(zpUQ443%Kw=?V|~i1UxR;ewGi<&)cHCU$op`PSy&yJW|X>Ppsm z`DvqpWV!p$zB?yH#>wo{owh_A7fBL}RPP7F?((pqlO2*lDM9!rXRle0)o8#$g7PNsNxHZdnGu56B|2 z{2^S&8ky)}R(a^l{e;{4@Ya+XYz!4b>>sy#DHCXHir77ua)vs}-xY{)L?ZN3;rbHyeysSLBs zpTf*wm6&Dy2p(dlbPa^?9}p+6w1F}2yp(y6Pk0YxASK+XBBk4gUST{q5#Y9lYsApg z@_g6)xj_PT@qHhM;63X>PnNwxS-q-@0K*#VHI-SzL{_iZHOjte1iDT-!n0UZePAbsi!=pNXk0@lBY#w_hw~N2>VA; zXKz8)_RO6P=JlG2NOr{b_iRaCuJvSh($Z4hv`N&^NfQax^VfRgV|S-<%3o67-7J56 z|4bd$3zZ@~e@)G@Q$FkEYOW%fF{xImShV`4?F~(itF_bXCFQ>FCQP955ay+Pg@lvnAd7n|n0PnCN*PX+AHFtA@j} z;vudr$|`vn>FlUne%$I9mHKNcO8u49toS-433YVPE3ZUm;gMMegAzsG>Uh)GeZ#fj z>{(durC}qe&@rmiF{<>ooyX4}yY;UhEx$zR(9WTg_ucm3$&JM5GrwN5h;ae4%qv3+ zK5m`OBiiMqQDV(Qxzt`bCgvf+-U-+KNy=oNlE+mQkZxMvW@%ddb^I zOYWbk#>D9RsiN#)^&QR`zD7GtS=U8dyF`L7Tbi9jUq4$s_1j+N zl2#8IQ+{n4ZC_(zOd~Y?Y*w<+>$UqR?a8u_vF7V6We>9!Wi%&cRa`7z(G-XD^2~;1 zvlrWwQBBL~k0-joU)k?9YsSch0uaquaW=3Z;@8ip*oZ zUW)syktgwqak2hui4#M&=GlnnT&Ccl*YJ{a$~U)^npeg8{nfA3y^l#EZ3sx}FPZ$V zKz}2l;Iquwh#RMhGjEbc{ZsboDQVoeJuA*VO}Q68bG))H%{fOQI}e@hX^Cgl#ro&U z=O{Q=+CQpz@4$&;-RR#_c1+Gj+AL$sz6mR3nh~p|duroScDg;Q`Q?(!d`#*y3TF(j zos*W-lfQ}cNOhv&c%ZWe)~2Sl{?*BTHtxQWyfbGGyv=Bq6|J|EEHX;FCB9vbnq z-{oCPp`)X^YE`;v6A=Z~=mYPDQ79CSIQ+1kcG{7M<}diuZ+`Pzq)9<88g3ZsYI$I1 z6Hzry3rYL13w}EJ%*hc)Rjyoh#vx07veNN6)pR9}SB`pCrZT_GGK_^Jsp`5IX$>t- z8|$tKv;IEr+S4xE-Tc#W@9GH0Z_V$X{@{Q7W2rnRmUeTi+Eqp?6tja*-H9pQ(DV^Mt6hXxO_k591|r)eH`0oF#+Z`X>_)KHD6T}ZYQ`#G*&!(PW#?WPCAV(3U1{vt zq^*s({R&E6gF)(>x<`S(w;Y8KyjoO|l4^4aIIP_p}0Dedo3 z`nA-L3I+0ax9Dys_6}=yO~vVJqBo^kb7$X-J_Z`)1iVDd@|C%e`Tbf(^*nnyU3Sp8jJamiD2-u**7b1281RUAdMnwz7R+RjD)+(@qsISy_+$<@O3wkO3o z(tSd%d7%89eNf6xhu`8OGCTv&_WWm}G;fKIIqRw{xh*FNrD?UNr@DHz6NfZSD_{B& z*=`sEoLPSNzVG_n=RP}rr=7Cz{QmcIzwpINmM&Y?WQn7uOgs0qGR9a zuet2ZrQdn1lIyK3G$!VoS+cnRb>tFvSVZP+xtVlg$d~%d9ChHBYrnd~J4Y!MuKxXZ ze?Rxk=YP90A~2y{rFzXnkr8K6x|gedFXa92T04x-run-*LqE{6_0~gn+bx^|7S6N` zdZY&1p>iKhKXEpz6^>8HqdZmGIT6LgM0cB!+M!-&HA5}F^Gmz@oZ>4L?LMk26X86s z5tBgQ$=lAIy_xI|$|j;6k%?}?VL38~9v;B;l9ipXG0_gWFiCG9SujY>K{AeY0kxqc(=&QnrUaUSdByCLb%6n{0#iwwJmBl0wb)PGy>D#JS@#Q4<5 zjkHl2bA4?f<{nWVzhkL|`#u}W={Ld1_)T42OGsko9_k~LKj$Fl+HERmv_-#fHXkP| z@7VWh(eFFnlh;{MGxdhPVjyOPkA3nZ=6sf=JWk|2 zt;NBNd7+ThR@_-%RKHm6?kII;wEv3da$cY1rd)25LLtZ9F*3ioepkTDRmuzNF;QZk zXtu18-#51vhCPvU#jw6rBFa-Z=+B$i6FK;nCoAYRC0QB1M9OqaM4R&da3%%GyVsCpXK7VBSsEXLh#?-z3+E# zx?!tf!>+&K#_wEz!>@kzz{@Yc{JujE`QQis{YO9k={mvtS)F_Q^HXz6vERg|_yj z*pd{8%H_V5e|vTQuUFsn<)_d6pVdp(1r$JjWR&}8x_Z_8E`wPW_U)9rMr}51>r%rr zC)@LvUHK5#In6hJ|30cS_{yuV7KaQe3>gxux>EGNe&(oY&2m=0l-VJ}SdFcD?S4ru z-0Y!j`A(+EziRLBboaiq{k``6YSlU?-SGxhG|Pq#lqu6Jfz6ajro&w3$Zy+GcQ7Jr zoa>D`wg-i>!?}srsi2)c7c;i7D_FL=v$@q2vu*Py=0&bdaLKN0&7*C*?P_Ud(ceF7 zjq#dgV>KvBv&`09*<)cloc8zDT7Q&d=(I-uBzoes9)=5l?24~Ho~!M|>hD>v_tk&S z818s&bEg6%!I*RRj?sn-Z6on(<)0R)JVV)-=%bvW3wEQ&tp|uc&h{t!wVAAvogMw8 zQ-ZVK=&Yp1mCToK+{aYDQvMB)yF0v}xU`2X+lwRl;hMR4zvSS2Pbryfm4mZSNO~sv zJxE$5QnKE`m5GR$2fU#p@|dyfos!1iORS6MfY}RF4PR7tUjo@jO*o^L@?0>^9qIRR zu2hm-*-7OD^1RuoTDy(U20HCaXHi9Dh%R?`#PzC%YmZuDP`O8*JOkp4z+$ z@!%>&eod>U4{dJcAo%|7M?0_PHcVZ#DeLqu>9`GtXw?zO3cU z_pD7*ZS0flpT?YF$d+tGCf^fLjf__3JT4Q`&4`If_#Hm@q=}44z2ugWBZo|w5SXRe zYN^14HPjkobBk%3>I!kmme+Yx&7Qu(30i?UiqbbD-zOtVHX|I!P6l<>vZrwE{4w!9 zhIT5XF)@*LVm6h{bn?qduDAEdcA{pY^9q*56~z3R?ldwZ5Tk1MW3Kdf^-ZWi)SRCW8$gPr2x-^%RVdZ2Gg zHJ4VN;swh>hA!aPkZVmyIDL^{Cy>V5JR64mvwW@9_xLjHyTfYy8}}%3;kN$yG|%d| z<=CKv#W*3B$w?_LWbCeOvv9!|&h;2=LiZ<)hu+GN`GAu~W^s2*y%Ep=>Xc(LfV!4b z%H~K}DyMwk?>kLvR^3dN%KvpznbIy&$sa69T;;NU8g^bM1oK*QsgKpHP%yu#=tG%y z9@*3##|){ROjOdM4#FMaf`tG@i-cP=RtQP*J? z%$jk`JH~aC`g)&V@|)|w_?g)Y$~5k@+wYyxz3}$y)*W!to^6Y+{I6qg+<54@=bv-r zl$}RVZ_oT+Tz%QhI~HfT~J_|k0Wjm6eS#H@ds=dJVi(V5NH&wQvl-qzB2tWM3dmAZ*3^0~ebvQhKYDA}p1RkG=A&Gtv7o!BqHshaFCvqedE z)8({Lpl?c7uENqPjm@0c}jj4sx_XdOMet*a~1)yiHBrT(5O9_TSH3bpg%E0xsDL6|Y|JSb{< zx9pg#)H{XTzxxe`Wn)Ick0)#k$ztwwaW)QV%JfaAotP1J_9)bT&WU;boOJ2)uCKAb zZ$`SCX36ZLLKYKe3^ZfN%@7(pKl~)V_nfjclJ&c3#?bPqXIC!0`nd0_zQeM;kHk8O zzBZR&1=OkCP*?vZtHJ3hbm0Uk;laaI&iQ4&Um08u6OHR$fs&>id9t*Sl zddbZEq2B7!?1@jd7+*TO!t`Cu_9xkGz#31okzYPfB9*+PjmS5~)vV6h5t{mQLDJ^? z>u`Rw0YnTfeFy6lSh&K0#7!mN_6jJkD%)F=BxShGH*T3@jG3%T4S&+N( zr0n5vVs5V{^3O{4y@?E8a#lT>y`-2gEdCg1_DyHQgfj;EIfrVs(Vm?)Q>{|wOGCu( zH)YRu^2b{(2u!LbKXOo zlQ&tt(zti2x+cxmJa!G+*F_n7Alwz5plU5f$>IEU_;6aWqJQ6soY5}O05NWC7ZE-B z*kk@b&7C`sh$hJLI+5k~-TJ#;Dvdw>e||iF?)|r4d-2hud*4AWbuJjch`6tAOltpS2mT5%vFco-c4=8;l$g}MW-&>U zr1odU?5SQcHzwv!`7y~NW5g%PzEeB?*onDxg00g+oS4{=$^NOttjuE3Z-UdX$!@Q7I)V`C1`>o)HKRks0vvKrs3S7$?Zf>Ng76kr zwdDFx6eu^IM_uwcU2HUo4h-rb^;Oy=1zMp|Ld+dA(m5xXaODq5xd4yKyvz$(X5F^z zlZ-5d8+n@2WUsA#9*@mzhBkp z8i>#J&E#hNaTNXYtTH-8icD~5Fy*Bme!3*fkt_oQgvFAj>C03>at$s{Q*piUsXm5? z1@eZH0M>303a%wM9@kLcZ`keIk-HrGxw*1XvF3~X5^SJ;?w=peV>axpWbd$X1X%%z z(Ne^yHwP^PMmm4VS@b-L`r8V&xPPP>_`pU`>}2xSxLam%N<+_MK;pQw zfKFB~@Y4*NvdnE5VQ|%MA(MA)-eCMo9w`8o|6>~Q%&=efUGbN!*YE9@2NxI}N?%0e zlxRc?_Jp*rFSp!U3k}?uC@JO$@Lm1aDP78gmsbDdyznIaA*>k?Ua8O;wzVQ2E=?dE ztIQ{CD*l}XU*gkkC~95>R7H6S@|)kO%zX|w*<`kNV2VCO-5}Q}(>BLwBIq!(DQrTsTj%JhJ=U|iFhZFM2 z6Q7n6cH$Zj&a1DsXAO|@`&~I1#uX0u#et_ud!e3y(dMfMWc_y;<`n;jW|uWXQ_E%~ zw5w;_Jp=WuX_}kr+~%OEHO4Sp@&#Tv-zB!S`K>z2d&~mga0FLzYj#Js5N)bNUY@;k zSEXzOrhavyHRh%6fa6dP4*O&@J8ca^pkg~>s_rRmHKJL`1y^VEgJ!h;9u+LYusRI5 zPjCnaRvH6sfS8K@zF4j7&||`L3l|x&5Um|(d?rH@wkgQZZ&CK(rseS+>3aj0N-)2c z(gZs9DC4YSfXgn=b|LVHsho8#l;6)hKw-{Yt&Rc_Czv&2sF|-Z#w&02YiY?;4VfGjLl%V?>pSi#*p8T79Qxdhtu^ehGd-ckNhMJ}KYdWz9e$G+% zMYt<*93@c^7~FN*e|K!Qy3?Xt&H{lYRes%P%0hlOjC(>f7@k}hV_atuZM%1{WA`Xv z65HX!(AH`v1nX34zBaSuB2Ew2mu!)G!=C3TkXc84f3rux_X=WLPkwb?Eo zs}lK*hjp}a8EYsfr_EzC-o{D_p3Zt0nJ@)yCxx6iSi?gHIEt(l1q`L$At)-K$`wcl z5}(9&#CY?oavpPs?=&pCX215v{tkTLUPr*D@_qNykV`ow_-rg>Y#3$;{@e~YUZxzkQ)L=gk7%Uy5FF14(KCV~l=y@1{xz0+b^IrjzvhgsgZ zni58t)C-fHkN*{vjs>3@GU?{hgVG5u&*W#{fpI{Vyf%r%@%40%rqa7P;`tRaQPaaR zr=B9Ie<$rhEwo-t&*=raY%C5<>-Jq`ZhM9N1tR(=%TPM6w?~avBgaga@5_v(Ihk1*7RkxDAL)v0pD6PesO4=R0kQE=pqLcN{#s;Xi_jatCe2 zWxl)KbLC1wgof}%Ppu@^-r-gye;*>Y&f8&Q@8*f(blCZAol~^-Vv2vkv=JwNdSjnh zt=T!A3lM#bSc#%J9t+>O$zD{7lOyyIXlV4kAJEX$hS=Lt(@c&$5uw$utnH4tgC}(l zWIGKXX3P|OYfAZB^)KEx0NI zx|*!0}5AnbA-Z1FtnG z3l~h@A?18)MUJLRzTBMvnN&_tX}708*`4bd3aYlXsDamQ->Kx)s4z{9qe?k*<~g+OO0=*jlopYC0%(+c^zztzA3~!r*2hYxZiX|i}%XC-Ca&X+_2%xN;~xC(NStz z6GMS+9%0+F)A-Os@V!oyl!HCUsXoeP?eGG5hi84D(88Ey>sZ616>aF_au8e%{gW06 zfvy{m0d?(ox-L;a+wXbBInDd?v+Sl#OC5B2&YQ%mi@MC{dT5O`U<&jzb2=Y(hlj6q zA$bO9vo>>pYjCkVffmLZ?QI8d$>VrCWb5hv(BXq$KVg)R@* zWh#-#5;Q|)jXuNwq<+1)FVJrsScxj&G z41f;iPBXd{odktT$uiSEhJ<}(QgADfBU{39ijG4Z(|mPuwF%C^gVg%#sc>?kQoE&` zItpYGLaG5}v+99X z4qlC1bnA;r>9>wNVCBNgdQ0V&8&o%Ea)60OvYI;XJdJp0Dx>X%d)C-LNtc(vv@vsi zfqZxn>3!Ibf;X82Os{-;pox=9{pDvqZt9i3z5=BoQC}abb_e#8r$XzN%aIsREZft$ z=%e&4eA2WbHR;ge;v!>_jbrd-ZbS-;v7Bu33+1VY%+|4E2M&oB`@=jLVIFf$1xtY$ zZp|ToU*GOI>Arn+ka2S1&>z#~bUtRyi7o6gvg0>5A6DVa`-JHV<#>G;j*szn|-8i$OI>tdAv3Con@1e)cb92Be z5kFr&FKD1Wn+}1@(;M_J6Vo+RXta&9okFx>xWy!F))eqKPd1*ESbDRfvxeWgZZAdJ zw^E!PS0XwRVc~4M*;0FNUUDa_WQt@Z zIlNito6m3c`^BM^)Vdn2fvYmAMXZ&?spW-i>2 z94=g5<~<+1SSRM?G|7=>>7|m)l0N+5yMIWRnX&_w~010W^a^#^B% zU_9nE7LKi(jC44g(#>TB`RJJeM;&m&RwjRW=Gle*fvoQUt-Ewl*#eqtUwbL!Y?@ZL zD5GN$#GE8bE+$#l8D<&?AFE_Gdg%ESN09ciw|>GqHasqlJB6{$4oR0EW+f17)Y@0xZZ8b} zzKchoejc4WU4YF9ZaQnW<59qsmQeqjHjO@1wMAVGY@goW#{h+^@>0Q(s}KbNyk2bW z<{bjri41@&DW>`_7u*A;iZnmkXB`j7c+mId)Cy>?)wti+{&^(OB-urjnU=@JzU8kV z-j*fox9YDWPskPlv*7>OtI1RCc3!jRp-{5IYZar95?enI4@=}9+;!n+IK~XzTT7*D zp%M~mnlWy&`+WL^^~ab`b{Re|{3rGL*d=xmvJ`-VJU!psFI0})={~W(@;>)gIr;%* z6QsL0a|$k^INw_@H~ivYB9oNETsIrs{k6!$ynr9i9KKdScB>8=ofOhebeI&Xnp~F_ znmH1CPNz;F@_vMeha*wXrYH}TDcCiBb?$G>M zpyW<|C4X&>#&-2(HrVQBG4NLHoGh@nw{l@maL{HuC2bOIUO=$ef`7cTU7aBLB8L<$ z3(-COm3yZo$0!Gm$ysoPc^cowL!pn2Y(ks8cIR3OmC3BPV&8_l`a4)Z9c#STw)2fv z&CR|t_&fxtKvjA2807W<=xy776CpSbPK_+84{BjS}ZQ@rBGP4T8tz*8& z9H^OSln-x!=(xU%HlWUeOV&q0Jn@c?ssSH3Qz)oZC7;gr2;E*8(;^f``d!T^lfW=3 z>F;5;G4UhpjxunitZB=kW*(d`Vo0 zjgzxt^FG$hR7_+dGhN~=b}~_&70XncFmvDnN1z=#9BNPVa!x-C8nGh5CV`W;h(>#9Jv2UTWIeO*i}Mx@ z0;%so7isfpikx%V{rM}-oSeGEm>&lcG>fPZP*{mzl7F<5pD;b`VV9f|m~$qJx3x?z zxFqP9$EJO(C;3~OP5NXUc`gKIMLN-+o!Pp4@2~WwO5;cfvUu(1*h@{()>b!70%pfC za`LI1|IFnxI%6^ce2{NNy9NWVz#Ej&w+oi=ER?RV{iN_zu1!*T~bB3hYWG)V7RJiOZAC5}g+gy14r5l}5#y4X!ZJ=|#*R`_?Hk9>L z#-(=5diz}*soSVW9z23336U}>)j}kEmj^V~w2X9CHfxqFME~@Z{d^*1L2%=HZ}irm z18nE+IZNKa$mBq75sJGnu{d)N)^9U!#vWvU5Dw%PHIR5-C&`a0AKLAk;h1J$5wN@P zw;wF=nyEtT7bn{{c=yzYCwLO&LgJ&b2+zFl^N^6YRpr{>g!N>Zt)>LOjyS1}eyiod z_?gyirDzMJl(v;?@wirJ3VCKsQ>_VjZg7QK#aT5{n2{2rt7`L;@8)(nUsl|6h;2&w z%vKqKxufIxv^dUL*UzNMHg0`1F;dC80c#h?rct#8I;R~7v`VgRT6-ty_rXI>)P&~7 z_;=bA3GG-5uSH2`ExXy52Pev>E}y_Fk(Hw<=QWG?grXJkQ3=ScA?miv9=+|AEX`>Q z3@iXe_?05mUDw4pLj}ssBpo(d6zW*H6Wo^!Iqm_F1N5z}(v41snx!q{ia*V|+c)WU zYAjret5H;?nc$VQ>So^oPdpkEDyh8~4>fc4-3Y@qA!AMaQ%XEY#{}|)f~e8V`on{{ zCkBm}Ip)a^=B(RZKiEct+;Zj(w8=X%F`NU(RF9!N*ww}allrFpDj=ELyjr0w*+~K{hJkCv| z2KDh-{(`65mbK$MLW~==WN_Uu@peT?fP%wO`E~F=G9L4GeJK-+(lFhc^ zqITGm6Fq6G_d~P%yN6y-HV03Ai#4-_UPFZ*vlz zn1RgmUjDc!m-tL$JGdbHNUGK9Fs+(CJvzuae5+=v9I50D-%}o(9JvWWb{)k+>bTDS zIKetZ@-CM+kTT&TjkrZWd@pT6zJzyc-p0}~2pzPSp{vkISl(3g)1Au;3SJ-YIcqe7 z3()FpF>AFU`_CGt%jkzUnX`EsTV<=~-B}y(*y$uG&#Y<*UDl#5h}%bUjTVdlKGCjg1u*vbeay^sx(AF) zwyrrC5eTK^`6WDC3^?bNbq&1d%7v5Jr6zo|otA$>1a<~VDD4-HosS7w?F7{Xrq-97 zIb!0C*c_Ls6fpCJ)kacBXu7~C{uGWjTqSz)DOk?vwmm9!DM^8yz4VB)7~FM$b45QAikb(vWxsviWL+%17Dio&p=6y zhs0MFjt;!ig^iIzVI6Nlf9;l8-ls<^B0ADh;WmHSYIYVKO`tc0Twa+}xXM_VFVwgW zDPdSU*&W#3TD6b8^_B;_nZ>SFL`R$Sz-d$W%_RLs4)6{L)Uw}5S+;2a+}U0@kMT!~Cpy+T=R&YoL*y+k502h_Ef7raLrDozYrU3FtNB)I(L zb|Ic+45NOUi39|F8q;@R4Epv%L=vBx%bnzB>c1f0e|;kLiJO{95%NEW&iFhloj?cs z>4I)mF7B={mHj7c?7#XrSCt8t9Gho0<&JM@S-Z+LwPyXR`&2yfcmE$fF*($eRn&e+ zxRX;z$llQ~YAsTB?BulWs(K0pNOV@<4$6%W_iOZ7y{Oq39CzR42>o^}d!K3R!~Kk{ z+{zy@{Y~#*SV3tfac@JlY1Qjq6Dt~JxMENAeH=^-X6&MzY*I>_KD2mgo>{g)c*!qj zleMW)@}e53rnVVl&y|7ZIEEK!o(f~`$AHO%JtJP6o8Opq?$^vVz%*cST{o*+ZdzyP z$UP(U7jwO0kS2*jM-2DV9K)K*ut9;Gfvg?)&kG+yhyal{+6#D*ZD(3SeeO7POO$55 zh2TSv8jTb1>w0Q{)s@%ldHO5tW;X}u4heDyIGOj?J7&uTXp9a}nA`;#i3QLg2WRD< zxsNmBF=d44sx-)CPLap4Rna-*x z(8oWYUDwS(-ug@C4XT>^yQb5)EG(?Q>D<?OKo0 z9rxTI{>^o0F2GVd`V5WxhN;evF=(3J&I zJ%w(efaLO7$g+~|LAzrw9#yj*qx_~~eWYS2w}O^&{`W{}U`K*X&zk%E0pCxwE_OTC z{?8>D{vpxli1jD#S9{SkZnDPZt?~*i#m4_ampwQ>aH6T>O)f7;{*9-B6W44wh0BBz zLEFogfVHy zx$}@jWzbFGQ_DlCREhob86&zR05tF`M|E4Vm+O3S_DGfM4c)w094s{66;wv?xY%WS$!Lw3@BH%H3pmpADTo0@f! zbOlYyE8CvHyjVw=3F~;v5N$pG(!l)XDRSU%zP*Zc_Qu)h6%z~aUTl82ga;v+v)0&v ziz&f8Yi4;fN%RRyMwoXepD8%OL~`MvKf-(djGKXuOzA|IAz zJY`N9$Rq^P47jFWry4y_DEfW+DlQz^^*D%L0Fa^jZ?-rTKF{a8N0V#fh_nyy zN=8>Swx^g`Z0@Nz?*fVATXo!FiFxIGcR3rMoPgKbY1=hlkh(Pm*mUG1@aoPAdDzCs z8o^~F!S|TG)x}Uo$^Bg+`IzA?)+tjq;5(&qBIq4@&ceyE8PL))!K?h+>n2ahrR#|OIan^Qre7U-QOe@R9hMGyLpMw7*WfA z`xLWc3iO#4JW8>oT0PH#$0N)#I;ApR8xn51=SyEqo*^R_Q} zF3-F=%m0~~xl#G?lqaapOnpo~(=$rD?@Q;87qr|>l)YdtYidrz(Ol0b>ds|=7HBTy zEQpY@oY>bVU%@jYB9DA_N9?$K{~n0w$RLi|-LN?B{ai=a>jC(F8T;(4DGRs$q;70V zGTb+QYDYbTrO+Ko;Tuq5X1>5wOTiZSuBXtvSHUVoQ3LEIM5(BExM-sh%2E(Z?XL+#`N^p)uf%O{cFRkeh~n|TsT1~j4U(?1cRN|9G5J@fgO~9K3_P68ul!~y!I6?v zQGQf^BHHMcT~0R=e8--caVqpOe!^QW0e_xTrN~8uOu&K~EDse>zvt&$e@Y)}KHE+r z6VGi%tSiGC*)llo<*#va$!$}yWod)a|A9?v_$;`0tWHFChKLn$ryDvj^NM5UujOQE zqTHM3!l^PFw{3LI9^X7JEDDpne0q0HAZV6TwU!0yWkg(*DsA>y2Q>ny-F-@B0=pDV zpn-p_bctr@C0x#g8S{#+VYn|*tMB;YUv86V4J|ou`^UwT370+TQEtZP^QLaj1#&qk zo!k?#(!5n@%k0Xs>IvWY*tRJX-;{gdhL)pDr5y!d3q zHLY1^_#XZ9hko`4(aP?k^0J<>qekCUx-C$Ul$70)0T<8czyhk>g7I8S#4YmexKRTa zFi6$EODtGnp6Jf~dFS+3OY7-_w$)B1rAS_{`#FSq!88IX^mdcJ2X3EzAc$ktdi=dn z$GXB3i;SicI{`*g-iq0L%RA8amizA3J?ReV81Xu8W0JEL%SASB{UTKU5?okz!|~)h z1Hk))Uiw&+oN#2Bp8r`?NM1Dxv=l12?#LPhl3mRNP|F*9$u39e-t^nUw&8bMKiqEx z_8vjN&Q1jW`&CbwW%UfIfxwt7GB4@h?IfhVeam6yz5#!`HCS!M>sYgryO3061MX3h z%z(o%kgS^4ra?s3lC+?I!^wX|sjejn{&n2P`z-yHx7t6wLpxJMAHU)2B4{j-C zA$>Hb$INZI4iHG!jiuC>0lm4q%ppa1?U6odzrHx%Ks9;|2yL-9aj;ok8)Be{nE9Rv z+cK@HT~KvL`|Jr1Z?EK1_F4@wM~qc%N@r1bt-+#~%FJH>oaH*{K?-!bKXTutlQGL% zh}-+J#Tlm?C*E8{d~q+-K+~fgD?wD0e6oAZ46B)jCDMN&i;hySqH{DuI%Pn@+?7iw zY2=M=o-ft439Ft~R-RtL7AwtNG6k;K7um;jIu4&|;DG*cYYh&cp2Izc>OOh+ug1>* zlrN1N`uw;P-xPfXqv3!N7aV~_JM0*k;gW*paf&$>^UNRQ0M-3_q93l7taI$BtIFlA zMun3~`W(fiXY|ZSraE{TF+(9s2qmA@Ie7BE`5&8q$A?s1UlDq4C>sHk|iQIUR z8UOV`zgtoJPzexYTo6j1?etgn_jTq`l6hduQa$pGY((_yUZrQh1O2IjnNCY zULN+kca~VCEXC=d*dNwdc-LjB1Xz@7lcfh;hGim>_UMu%{`|JY%sYdRstWJ?cDbvP z%313b7;9ls9>FZCTRk&9`JLni@0If`Gt)6;TUEcZtXnL*tWBM3dGFE!s?jmf6O5Sy zcY`!rCMWGPJw<3YH_QslBu2I61aDiM{^Oedc~`|o=G);-zUs*oF}V@(Pt479B5#2> z<;&#emffa?pyAqLwTyvy+QgaVI>X>Fja@w-=STVZX!HM6|4B=EjRv=@%k0Ui1e?8` z%#u=dD9SzEO8u1%mFJ5?#%441+hrZcA{*0_%1_Z^Ro3JE;x9tdDjFKO1}q6V$F4V; z)8m|Fd@9Fk0*%Aa799Q+>Y`rL?R$@QRsKc72gleO4L+qIrMiPWq0_epOaX*V%)&9! z$%R{UxbXPaiP>hx&hdU^9ont9S78cmNH;E))P(Exjy^V;+7tewr|Z)FZ_4S+_d8y$ zTvFvEZ*s@m!o`jr3mvUh^b~IV)0r9Kj~=sB>R3k8r_xhF0TQ$V_4Rc%)|oybRk+%KisJ+D{XwoB-_hy8b4NB zJ3Vi)=%v&M7!?|F`1JZdbnPf3Dc-5I#Q&a3w|L4`;0pAiOOd%3GU4o)Kk2yiOzkoI zWq`eQxVc5#nq-``O><@+xon2xS1MAt-vwjZ87Ek>fT!gUGahaRHbuN#9z(|6HM928 z8eaMhoHk5ouNPiyQ*{|}vh>@+z|rQ(UxaWm(W0s+;R`)vbFCfjxvLpr89M?%AGb9V zO5qK8bb5woHOsmjYL;`Wl)Z4MC$!bp>8W>pO}=2DpKn>Ua`G3wh&$bZ$cPTtHF!_P zGoV2oOFEA$S+g;w;oEjc96NQdC>3XG#hO$2mIIG0wWiKvSMA_li=u#bj5pY?qs z+=sQsOEcBShaXwr9QY9gZ<#AxE^#D2CoFzu)YW4BHu;~dtXxq|&ZUZ8ogNF-cz0$w zNjJB<0W*O%zv!kxMNo<#hAU1q#wLbHGq+-DbukvV2(OCR6Y)&X{CQJ~{@OdX!Gc!|zi0N8#h< z>O|Y)CwC+M1Bdq!5}QYlfV|X;2|>c&kWPc9Yc;muBd|oIzl&6`E4%j73-4xnMp(q1 zt#-jV$9tuvo7D=&rk;l1){k2sx_$aHi0vvrzFTtmtua4tT}!(9bHj~UhCEK85{WwJ zB)i*oO6!*kMuGQo6o$e-NF#A`0UJ#pf;9^Q6uz3Z{w*N%+r#I5T zS$PsH#Y}|=$%=fiU^a-*oCx`r_T)fteq-~#*wx0rzZ$0@zHE+*?kEFlx1O!{wWeO0 zNIR^1R5H|67-{2l^%|u^=dCz2haAX2CVU*B>Y4CP?YX6Q03I?4o1mG&_-QI0{7eqL zvINI*6>;)`Iz1^F+HZn`ckX&6P?TAuf9?F4Xzs|5?42$%6BT7m$va@>s@ZfNRq(VL zr0%yfOKdM*p<03r_8Z|ke~_3Cv8r!>ZO90|>lx@uADq@^22i)@!-UyeIE)peoUQ^L zTXxOI<55)q3FD(9l3R$Ct3EbKiktltUFGHv^B8a9l}5KlYhY|8?b<_J={s_A8p5QdFV*F;Gr+eLT=u6d30ABKP{{`C3VO_K9s)N5fHgNuVZi>SK{>|Ut z2E$CI(URmxlL~8JOE3cEj-H7TmlqS(rYDy6!ljRx!Gxnl&SlN(ilfe2JG99M@$*Rt z{!bJ9f5U2pjosF6i)Xr<%RkBGE!tY>bDQ74e75x@+7E=Nl`xI6#FGJqmO3a+h}2IP z#s@wemN zpPeOryCBim)E~!FJP&(93wGjNvNJPJOZ8aeP!f4NaP@L~^+%BIpmg;trZR7KrG!IS z;JsT3=y}~}`Baas|4H2n^ZJwgtt9;=!%%`-=ts-NNx`1GWH0#7{TR>r9r%n;wXVjh39N--wpYJfie3?516 zAmO)kSf?Twa#C*nI5fH7={e`tbZ92MHo3jXZ0;VFln+k|>x{%h=W%%#kGx+zna?T8 z^V(W+LpCz7Ij%j85WBt?kcOGbW;F>+0yoh@LWBieg!4ZkMgzsCo8iX%Yb%L7r-Gkf zCzP2v>pGh_&sGuMYh5)=2hu$2*|W$Z_*5h75A$CO!jIiRLoJ%q5P^im=u)roK#eV!VJ>s7x=W5N<$H2Zh(on4o2HYs z?9;%wU-zp0l%7I0_G3LnyxdHuD5b<=_W*kmwJSj1j<{yh!uK z#X))Hf-{`GHMP33Z=-8VUG7zn`3-z<0}SEHA6t?>1jQGS#mPyP`|J=4geH`qKndbjS`{4;hs9g@yHBm2jJI*(6CEJ>5Nr`d-JCi1a4f6b`oX7 z#h`?C43m^ptH5dD_I72o!aE4QRFvE*k1ri5(YR4Sxf1+q1*|&0KDaUk+P+5MM=GS4 z0qwipijdD+&ONt5DnFfGOZt@mn4qN0P9C{f1e8!W?!5(eh|h;A{SgzZ3m3*W&$X!? z_r)`Ve^w-PC1;H#ZPn*WQ>{;V80kyY_qh3sBScjJ7@aTvnfHEuB?$2*G^l2>-2M}p zj$+egQ8ToG{0f@MT6^d9JW`~b5yJ04CEIplRe?LewvnjN$eKcrVylyOY zgr{!aFEMQ*{<^E$02;#%s~Ur0AvU_@ifpujb-+MnGRId3A!vVyy5g&RdH!rOT^q_+`hMS_kSD^br0)wRmt&Z#dZK~GMrn( zXexJWySio9q<0O_D|%P5`ekW0%d$d_LOQ9 zCq&NowOW1B;R^jL^{ex|i{%^6wL?)Y>8B?JIq zoU>-!dlerlD0r2DXvqe#9yjZJ+2Y$uKi!)>cpJ|Eozfd$JfKh= zSGKANfc-E&8!&k1KuyR7l_w)e4#VcNL2SQszvkE(<82Hpq^k)2N-di9Lw362HP9wUViO50`cj7-M5=h?~gAof^*MlV+FYf5bhkw9q&I{W3 zsLa?r6sxU+j%S3VvqC+?Wq*zfoOXZS!E5~8KLNRNaIDCvh@0CIWvaE>LzO_`mTC6M zo`)uR61`_@-(4DPTr86K-8+;=@y@J|jd?0gVGUkdKdDArew6`A9Jd#4>e`LEY0k}a zy7`UE%FTtWJIHZ#UHS zE#xGMhK>lPEE<$LQtC7*oT!S(zSaoCy+`~chkVCu>qp-5y0rS`_dim9eLG^Kn{!Eh zWNETX=i9i=^GZG$hh9ZKKmqq=EkKdG*)n`p14tuhoNVts^yThFKEu9n(`$^!iXF`C zvTI9OApXb*pAUAkG-ls98Fx}T=H8G#%wAgf=sL&+Ev*4EvF)DjbM7hL66m=Znx1oT zJ5j`Je(|wy(*({i%_@tAtCf;kLq6R_CKX*PqHwr)0SJZ9%)p$ZO zJo_hDDk5#&6!H|VbVd>kZ8K0cC(bz_#J?Grw^6GDs2ME4gseDV3d#3Q-M7Hqmxm37 zzr((M?V$}6;|KSaW2LyHR=);w%1YDTT$gMo!o3g-+wr;a`FjDIXJj9C9gHmtLl+Ie zPU@AqTVLDIjU}pC%P#S7aT6}=h3=(rKxxWWIlp%pZV&yad%c;!yc~Lxe2U zeM&qFD5^Lb2EQ}>izLO{FZ9w&PJiY3)&hski6qJ;%O`;QOKAGxpxWeCnfCmt=7lH} z_~i;pkv?e;$_<04JOP6h5$(A~2XcBqDtVPSLW*7(Y1ZMc;;8Mn!;kUI>QfyQ^|won ziH-p*^3H}pN*MA%twfBt2gT`a)8)-!JE@kU9@~f7@N)~fgKWJ_~ z`%Oph#G=Vj;nd#TPF^3K2%gx&{a73r@n*Ya{tTe&=LJ-T?F@2eSf?EiwM<)BO3LU_ z?r*8NdR_3^@buOTz{FNa?H3vMVNa2VdmcjtkO^_uhE4G`5QviJyr*A>TwN7R-ZmZ2 z|5KK@dS=BvX(oNRqI0mMMv&?6SgZ9Cif7KCi>=&Tt=>4wBTP$-F2TN z%@tLj(Wf(UlI12&Ti=ddaY<$e5g8#fhs3Z^8g6UzsWK*J z*rAc6x7kjg(%>fnM^xpPf7AT@$tf8yy3SND^9!3RsYfX>$pq27b|v`De%TBBhxlrB zbhe5--^(BMdm$}PT%xvMf}B@8953ybKB;r23$`)Y*rrblqk$E9b=)OaM|u>=4}>oL zU2&#}!^lWhQ$LhdfkDJ8&Zv7nt1nHQSzo(KbrAPvlV?rnPU}$O%lRWV^D^wBRB3w& z-EwJ&f}VOcCPP|qQt@bpSy6h~z}}gHY_9F=ej$0GUfXRQ`hA@|;gR?5%9?GqL%XE1 z$Z|68nkrlv^=;X}wAGPQ>QBZp)iUV+DqvuL_K`Qs2{*dSFPwauclSfZp%DL)M6khG z%eVqo_n6)5j48Ch$u<4wcMg;w&&UHs=bL;bOyAHCP+tvpO7wpNX85G`Z`t?I-&=__ zzAB?z_=w`1mL24*?>$3VZU-kg^MsYVJY&^XE+5Meqeb=33~_3_iRk@ym)87zRcv1F zsApwolxlD0VcD(e5g^w)f-_BRL?|tqf1o|%hI9H(<(1E>7VU=qI|mkA_W1zkEF%9K${J^x^S_cE~mx9fc zW8MjL)(p1NfUCJh%Q<}J4u9M&*h0NbL(o&XpSPFW@srX|>*RjQzH_?e(8D!gTDo2) z5^|m+)F}%RzycuLR>aZxKg*pBCGu$piY@Tk#vb;d8hlo~luf1t8~I7UNN2__s?#3>57Bg*IGFAd-+a$# zZAg&rE5`Beim%xlwu4k|MF5IiQCAwOqq@;4ky(CFGO-nKW$X;hby$p{tUYxcFZ)uP z9_GqtuOeytcF7V%!^mZ;55=&3YjIGf?b2*sPRahuA+dk^nz+~6*m2|K_InFHS_JSz zm`lukPf6h2R1LbJ{#EVTtw>{0CU;cKoJP?9=K_qU?24W7)9KIXwMViloe$k%{9wH} z?Cf}yjK!X>?<`WaS4V|@`|R%Y+J-(sgL2qz<^L^B7fUs&sb%`KUu!V6j8ETtww{vl zWiBf#C}LjeXe?#Ut5?ddIN4~J!ANVfA{ZAOM;r;JKhz(etF3%6&f@c#ap|LvDZRaG z`-|HSAX!1?mGV=|3D&6GG)aH28=VrPTyHumrxK`pT z^#p5`WyH(y`f>Z-X11_6qq81-z2Mn%7?OrZY~=1E=i&B{^t^0=7(--4oW4q=@$b|$ z5AS0IIKEivS);J$PH)w^x8=Mp0h@rm*4@j_tl-GszF zQPh16WyYBpV!Q`8YIc!4h#vaJyofld)ahqRsIzX6pr@?di*vFmWZW54dL-z8KP0*- zq-Ow!J{sn#f5T^3ofNBMVY=sshPk+Y#LK*Fxo68kaEI;O-Cgl_#;K$4$I@!D@KH-~ z%Fm?`^#n||zh)MWf9sl!4U6w`0=WCmv^*5ostA%Y%4YlV%3m=fC0R_I#_&0)8nGta z?~czw9BwI&y4wupsU<+*O%|U{N#8PRpk|=g&uAY=%7&3fkGC=Kv#shJNzbkA?P}v{ zeK+ZU{@Z@6Crtx>Q=|z>OG}%Z{f551P>!Nv#fLu5a(cPJo2Chf{Z~6j(rDZ9D&}15 zzr+5yo>1v3JniP`DU_yJ`hNIIUW@{2>H{nL?Hiy_5p9&Yxr`^8f#!ck{9q68ZdgciyTEInPb zG?`@ky!OF=jeh`Dxh;h0?os|)kN33TwKL-bS#xvqDG4F^SS&V_N?qdme~n-MtKP!U zyedY94pFcVwei~=7I!~Bx%FRzf1q3dskj{^<&!uI*?t-mV~Bg$YATpqsIBVE_+PVs zF%QIR1w(KB-_P`NX5{Pt&Tu8zIObFO-uv<{<=24u>PF80{CD6#r#$ap-T7gA{jSsh zbl#$t{z=`{!z}PWeYW2wreu69RIdF`-#@S9g#?Ui-_reW-#>lV3;uVqfBqr8`rl{$ z=bu;qhq?CvYO0GK2eBY3Dg>kpXpkbk_o5Pd6G1veq)G2Bl%NO%5Rl$Mx`2T64$_g( ztMuL>bOMAV8~ncCuHS!lW@l%2cJ@sslXhfP9}o159`M5dL%72ay?-+`o}c&Q z^1nC>w>{E#v;W0CczE3e|IfRSBYgQEDEfYkB*>&gwn5TO^^38GmtFB{&1PelhcuVN zIL800sbB81`Af7#$)#5;?A!0M<$eY(k?+?1Y$Z+0;jT1ugYjYiMTcK0U>xH!JCidu zHNS>AS-n4vhSNq@r=2ak9u?ZJY|oV?u9li~kqkKxNCe&bdn6BySNZ1a)0OUa&cC2L z4n4)YyU9IA^w_RiDFYFA3ELxkF{}?(MZ1XBx2HHjY7V{f#(w$)0~r}v(Is%{{L zCw=c0s*qnM=fLG#Knt(3gA>iJaqyldZ~j7it!Mmr$OT54EeY5!14_1ta%D_MNT zLqPzn%(d1&RG!~BFP=w za1~UMhM3Bds)}O@NmF?gvdL?CayOYfZZBtl%RBw(RnO_4JD=f-kw7Z7x+FDs4c|d%c$ezdOj6aX@VydQ%D1w zB8WEkT;PM#C!Y=x_G2}po{pDTTAr^775_dx0w*{h5#2hBcU2b0%n+)2+9UfJ-mjK< zN;O_IH@hLrc!$14+b1G7CDx#OWz1N!EfjM&Ye^Dx+f3+m2YPYbcDljB<+VSZUb|n1 z-w4XN(`DpxV7a$Pr@@M3uiNWXY8JQf<%0aZQ#?*mbloM~rqj`5gq(j^Jw{QPAcXCf z#q4}HWis6|?-y#PI_>s!gk@b8K*8!c{Yezg{-?cqna?%(3F#XqB!1daf-hUwxf&L{ znTvKVtr0InXGiSou`Q@Q=spHT-{9OSnX`j3QN5UQw@BqSTaQbV3Dt`0+>_cZoJaRW zGL)xpV+J(bbls3U1Q10r<;2r4>w(14qY?T!iiW@Kj0{^H)dcfB%?mb~WTJeGXD9`CXG?(jC5fa+D61!k(Xm zR!^;!%SCv^mx#@QMvZJr(B~YxVpUC&oNhmP;R0_CM@c2WLw8I@Brp2x4b<7mk75kB zej6)K)J#Dc%G;ebQ<>AtpadMvsGY0`;Vpjxdh~e^B$Ocna~2%EWfs3~=yMUE-FtDk z8_Eibl_?((vmcL_Izr0@E1I~`fvH_r?(EHf(88?nb)~w_F-tmi?l9ugPfg~9sCViA zLS^vLSgq7vt?!%77!z6d>fz8M?LJ+xorZ{T3A2gQ;QH`7jI_PP4^7>MIdtSXm(ABy zS>F@GHe{mkVNX5-vz8HkyeN0#YEty!Vo)R3T3ZnPX%mh8S^t+OyQMkWYkiQ3>8`!Q z(}Vg3pGz!i$N!{_M2vZFT&BWlt`W(}41{2c>(`2zr!1FNjM4NPK2>?~FbLQ;7&hMc z<5(BS+FVhrtjQXyD7Gis1mfRHu&DvbGB>6+peuD*Xo0BO=-sW;zBJhi!|E8gLYFp!(!};0MVF*W=@L%Ichk;L zCv0GP3Q=xJcu7;;;-`FOz))jzjm7%Ga;zGsVcqdyTe!Dvv8{B@%$ZWo43Fo!hS#urMfHyN9~Lg>l>bGEWWV zDrw%T>(DlXqCGh(8xRcx1J%%>g}UQwr@FcPeV7`T&EX*Ies>$x!*p{vXQtVnlZ~9q zC++=cj5HCg3TV`IR2_AX5n^>fy8X_#uE9lDE%BSRi{<3rT3;%7YF$cA67yv?5SbzL z7m4tm`^8=391yPG+n%pEw2;E$185!3jOWiTR_Aa7ta=LhVuz$Tr;fVednsq7X~PK$ zCZeno?nY>|3|WW>%zkyiz8P8ZOm5E$voondUg=I>*VR2Yk6yF>@}HCTIjnWN1`%sE z;j?sA!^hroK0zT`Ha1?uWp*;p=*Rz9JQegf3 zZqm)aVZJHvHsv{SjlKU^SMumPRg?L7<+1Lq5)E>R1Lm85QN7``BK@SDW+z0@+vLzM zMuRgx_YbO56_9`rzAWAYq_{JW^DyEYJ|A%5*2Ed_?{7=^PZd@A3xss zyZL^dx!)`|-f!=Pzoiw-v!C~+uiH`n-4aib;=hs$-v6as27^_xk~xS({x5}FD*2z<=Hcz5b-R8(RZrx-3U+Lo4 zB3*01bd?NYI{$&@Z#m)rBZ=w%1)=Nzx{Hd(Hy7?JC*|J$bMpL*V^Sbd9{WSPIqsth zd7K1ouk-pwL*1WSHo?^~PZi3f-WL2J$NsH^ybk|fTn)0QT+2#5m63{~qN1D}v(Vy! zKg}B~Eew2oaIcuQQ;y1h#nL`d!~N!uTuGU7RAGRdySaqaJiQ{V!ZG8vz(pDStB2ka zMm|-rwzgJMg4elj!&nY;@HGUk{_txNQG*kz?%%NIxAI~|U0i&H{$XMd+c|xl;&X9v z!D~(UFX%?@M-@ysQM%z8E}`gkenn;FlYexFl54iMwy}zghpzwWl^4EnUw+p$=hgG0 zKZtp7uspo$$p=p;_%p=4Do;H`D*(J7n5|a?ynO`?p1<>HKtA==ub&Gkd8^m|PZWuU zYm+bULa&*5?-|M%tVihFN$EmA!@W1e}D4mEfZL6?)Yx1eEMRTU#% zc>}_$8_!7VMZdY9FV#xb`i_qwCBWlQ^WJOBdtl65O6_(4(Zep~=2eXI=|&=Yk-=P2 zPgd*?WdDBodEDYH7dLL@^x8X71?u6}_I^oZ43jc?T!n(3JLKEU^t3+itc{S)gC5Z? z_ls@q)N3nBX`~3FQS%ZONEtr+G(HOV|19fUNiPtAJZooa>Cm4ndgnjQx|*y@5RIpz z#~SWZ;0*PkR7qQ+^B_IZuARXT`u|z4`mhh9>|DBsD=+{9Yy($vJMs$t)^N-WP7!fhwd2AbObn0!Txs1}zEgEIm>9_Ammtp>n`<$l>4}XFy&~0y zUY9JuB;KP^DO!g`qr4d*$0-U)_a)!udQrvISoj*|I3y9=xaWx!3Y)Q*`s^&_(w)u2 zs8_qwBGx3iolGPFegfPQUHPcHPJa|WRPhc&I}ge98n2L81H0{)PMmz*{F|0@757Zd z&uQixhWTHOW2vPrhwYX_L2z=ZDshz2p8tg5>D%21G38X(U%yZ>kTvYqVtm!MFKHyEnBN!sj6fN6$-IVM%fyyL z2aefF(o5y?8>}`n(}vF!f>g1cXN!4s$Q;i`Qq}rPs49YPs@`SpGOZ{g(UT;5@NZzb zyVdq-L>oY$)p)@&Y~8^?wJ$oP)*4a2g)rl{+CoCT4kU3^;54>skD5#7)_+*n%d$6~ zr0`iF>7+hy4`&%IYl^k_^XMr-aMahbEBq?-RAW!hKV2RQY&ma)*+zaZIC(om|bNnLf~ zuSC=zLXrQVLPV<0a;-wTz9$rc z?#IcNHdM4hzN>QXhQ2KdUUrR3L(>*gKSE%F?Gs<)JnWz8LjP=Z|FiIfgl;728kMd5 z`d4asDpsa;ZvAjz4mome#;THdTALp&3j3CYGNDMX@ap<@&}M1tIh+yX;yyS$Y4ON) z%fbR}s+$Od^{hou1S_H%u|Ie!Fd2#TmxIib(36?U8GFyI0r)r|Y z67%}h0(Ir(H7f;NbHd1gMO2D6Mno(#b) z3|;ofEK<)_J7MDP~2?M+uBx}bBQILWKev&(21 z6BzE3M^|k<&=~KtK`I5FQOc3#!m_A)c1*<&+08u^cUfE^n*WhGu*s7%{G(`os&X5l z@gNCu_>5rh^;9qTa2OmHGcTFJ=RVt?cDX3Rb^!~dbzBD9iD!a975l^P^v5TRAk8T( zTg2??km}5fE5bJzds_AfF{h&od5^eqhR5}rU3bDsF{^$is_AaMpNa_Nd?scc4_8L^ zbmc~e&xdd|B4g16?_T~nGxrB_{fJp&f?VmP&>s`yp%(p>;Ks`-dhf*_54KW;+>NLr zJ&%U9Ih!k%YMFh#23+~ngdOIi?e&Jet#uAI?x%~tpC_8#pOE}Sb_L7Zujc!!@mk{5 z`-`Y`1GkG8a;ZK8AuWdb%D1mh?R|*(V*$8QoVdul_S?Xo@Er}z#YI9q=8(+-tbTFq z=oK-K?N;zvja8zn+v?%!GYU69%FW{c9@AJDQ|<{pul17jY5fe4s=wHnA&EY0D}R73 zfm0MenZl&r=DHgHr+LF*jN%$6uSt~_2A)FL6c!!{?vG><YQ0uadGvY=b&TWKnnW(^3$J9au$f$TKKWo#r-6#TN}@`yPF+! z@H_Jky_CbiFEKY)yZNz7_=-EdQ|lzwYaq(Cv&3U#=NGOS?m~DRO4N-wpVy*F>h@Q% z<|I7hi~D!Q>fI6>)}M5x_->}iJ^6Aq*FS0E`%CR{IRYZ(236~_NZ;*NBKnH;>H0di z8MDK=pmYaJH3C{T(|fPLesPuHs&Cvy>)YqtDaBrU`J))K2R?ZOZXW!!pVPJ0o8;U3 z!fS7MY;1B82V8R#YCpo@tjH_9&GXO1Q)=RbW;}jr%M$-Q8=tljE`Yk{L2L7=x&Y0&$I?1YS8_mc6Epy3IALwHiQW3MqVF}k$n$WkxtnHhLI4_ zjKYNa?njp+dl}V@kQ=M?7yI9Xz`ROcb}b?6%iG3;K*bV)ID*B^0o?v;4;Sqaxk z$u@5FJqSx2N2jB@Hz&mP(rIF~#nBsRH7N+zR!9wf(HBoI;r6BXtJaL?#p1}GM}G>D zBo;eae^m3W2Tim9PFVf<gN7N-l$Sg$amX%l1y(47Zcg_RtalCLrr49G6{(Y^Ut=<>`EYm zoqbA0`0wGGO8S6LJGUt^nQ&zNN?}ZP*qZKEG@UY;mT+?Sj-xeHsmfWwOD~b_`1aj9 z4*Zd3X{x=^8cK^z6BJ^eDLRWMxZ+{6SucJ^Q8qHEc9J*-L?uQVORBzn{g7#QI{vAN zKuB+2p$;4*Cy8X1@@%AlWm*CIo!l5w_R`^O)AQHy^-{?x+kjd#=u$j$HJgwF$T)ApQ;`8cV{1>H&Zg0?Nx znM8y2epvaJ?pnHC4vu1vsaA&wf1Uv9s63`z>zCJSBkJ+Z%-{WbGE5wcfH*>QK|1n3 ze?OP9&+9H?#>p)>iVU{l)YAPg4~tR<{7Xra0G7q%aTOHuLbpPNFmCs&zJp|+Heut; zHuI&-%vTKJ)=aa((d9`&V$YdORcl7vOL^}{W^$X^8~4mn0%dmuc)4As8ayLEa8mAG z>DOwK8PV-3@?mcRa%hW~_#*b9opuOidQw2-rJj21;%r<^0H>q-?{EXALIx5k1bK{? zNBf3t;;*|vtu(S`4mMvJz@1d@u0Z7!0F{U|P?q4q)q=Wb@(FJthTOeMChhw$}M zRRgW0-A)7T>)t=+8a2^Q_l`ySs3_kaxEL9!ojj`QVP|8x>FyoRi4JO9VWR2R{+X+Q z%C8m8>ryKZD+SzKH#jUEUw8AWwaaonpc6aUJxl)Pj011u8P0~_!IISaIpvf&kdI_O z!vOe>vi$VpPomfMF>`0T%?%OKh)NYf=UFhhy;5vxAWBH;cN13Ad?+?u@Rs{AyYMW+ z%CmRQ0m!!9o_3#YE$~d#&GYv?Hl@q==T+A=pW+5NCV9En-B+)5K|N){G5p(%t8)dD zbhCf2)}cM7TsrLNMGUgX}%VY#AGd5 zi>iudMmI+aYQ1-r+JxJp)0X#UmDQA^0~v^7&OH|K(ab=PDvo$>zs_f<^`ksvHluXP} z*`q8q&CSTzdq~XAyi-h2Bae>#p!k5cm2#B>L8cjfdYX@7}NL}!1RyrKV6 zHTu)=@&VPZQia=gzoN`#lyP|YTvJQAIp@5_``c%>@-|K?nUUQFkH>JMRqKJ*?Zx-AQrqV&AB=*D~++ zRP{Urdw#8+_*QSSDMhuMY`eK#ia{; z&*Xb?upA5;)5Sbx1PZc2#*g8~O>Q9{+n4is<50=Q-R@57w-!IcGSba*SzvbU9BIX{ z6taaUPh}LWW?CM`!C;ZfgwbD#uABi9*{cD>crz4UUB3={#ak!r=EbEit%KiB1#+O@DgScL7~e8ET?`-+ zXbpB5muxFg<$S4|p8|VW1{A>$p+Ei>@WI->q@Ks!Fe-)ru-AW@m&i@RLt!*GZ1LSF z2M&&7QG`;~ynIp>6OI%diPLkT_oawe+ZiLw#00^Km6tz0Gitf<*iCozb4Cs#PiVJu z@77O$jAA#S!tA!z(11XPGiSdIfWSpqjuHV~ga}JL)^V=%g)plXDz3E_{w{~3ot&}L zPI;#;P}t*SZIxv7N})U^PPl*Ls~2U)Z%ZBdvu&ECenwlUy3_P5tkrL99!*(oe83Op zD*f{0-KTdYKlg>I(uFG}W^%TjMg({CD5w^m@S9LZ{x;EswTHvnN2(spVeL%6#wQrV z03i~xD*AP+^`u~q6Ehg>ckkKqEQGoy(R6bzJ*uiW{NBk4F(DNvfZ<_1O4ZA87NNl` zs4Qc26>|Y$#x0LRF+|_%%ZKRG7a{<_#4~TX;b7WA27g9XU|>x|J zHv50|`a4?V|91}}=&LPpIU>nqWQwtO)oXA_IIS%5T$&f|Es32jK`K3+ zV~2Bs&)4!b@&NB~av3pXSlgN!Iy8#S%r6`h6xv9lsVrbhEi;;)`KFP7k`y#o_N3Y7 z7vOdn{>$*hoYJpc69rrobzIt}=Dm*(|E2cqVUTGY+S`Yb#2$Czn)|L8T+w?Wydt|k zqnOjSuwZml+iHtd;N(pbq|yi_w^OK)XEmJ9%3s;+10U8IOlRKeszksm-4>?m4pDlJ zz3TsCGDhy>dM^6@DYay`eQCb=1^gLS0rRvs%BsomVS{L*Ef*PRM-l7by)MkhF?|M) zjRrR~+hNJ#5)SB@b|L+zQ~m4GLTi~x*YF_WtSW7lRXt08KCfNh=(`6#ads8|F#dTc zkr)8(5*ns)JP{91GXZsqbAau$HE5~<0JyP2i8hU;OQ#-(rG9OJmIE5)p2S@ZTNwkf%Oh=!c-L%tXjAI-KR7ILb+x0Zv+}<6oBG!^Q zVDlnOq|DfLyyHh>)E$gf$4=WPY&&60vp#F{6z}_rroI=A0-Y;!o?F{x3J8qd?X6g2 z($gpoM-Fy3^fL3^URWP)-wZMJw!aI7GSv*2!Ucf#jK z9#tgK*Q67#`BTa@0Vv4+jR)C(U)Pl-8+Pe{M!9>#M@LOZ<(p4CWG_y3r;!~2T(~TZ z@#r)vYXv>vgA*z-hS9_0(oW7jE9a_yUq=f80N?weeyqO4l=0!Q`nIolPys%kM}tn| zg@u+|ZRPRn?Gr_n*;(@P!P^}LzQD?&AANe?s>lmuyl3fopa0fm;~u;F{nuKRlY}~9 zN$Cgel7(@B{cS5sPl2P*PMz6Wzwl#T*GgqW+WD9kp(O~<12rB_wZt0XeH-)bd zS3%h)&8TTH>^4+eUv|VJYkx)?d-2vnk70`rZz1C6)>vXeW_pY^XZeJHdlFCLP!ep? zcWK<$%|?o6dH}@W(Li7WGA*v+*VY&PQl0^_&n{V?=uT9uRWKvd&>bk*Oi{>EDXE=- zpm9^#7{XWe2tIVZ1h3`mHF2@gfBFA%^7v98V#};X)ko~L*fl#JBFnY!aFR|p#cT&Y zv8Q-=@9uqS5GZWyP=BrU#{(~D+tNWy`o7A?nW^=}XU2s>6-LPy5#@@bl6XHEvWBd= zN^{m@wRzu6;HZBZ8WuhQUfsxca(wmRhgJFJB9?-oF%q`R@DAl6@PH8*R$4cleHplL zasP#-1a=}_(7Gb5D|t9VC^~25-MAekBZDiY>90jroxPu_)#ru#%s%1>?qMs7CZa`K z!mGQwg4vwQd5HLM06R$=Iu|gme>_wyFRrw%1w_Kg#0qW+vl?C$JQ9ZVM?-Q(4<5yG zhAUEJ-r<7RCBihlxt^v3-p{>EW*<5>RK_#Dyh>cv1O0JZ!Qwt6go=N_)|_DAgtE!v7|VfG%tqL8 zGdEcPxfHm(CR5OkuR(uCOLs=A7VIb}|2WYK!M1kSG3_6kKes<&>0+mNXyC)Mm9x|9 zB8e>~0`rOw+T#}Jon`;auJ947y~L%}n*Q6goy0>wR`1}$#T2R1^G67t zS{eL%GVUPZ?6aw}@8S(w51rQ|1||)C_ZxGQY<~d&{GDBZ31) zKTxt>M02i_6G7S!obRVfLPolXT;$oOEzZgT81v=Xprg9uh-1iui|P4?^8Dl z>Lwy>O$uI459@YM@X&?LwLSD*vr_s58{#49aIo*#zxyIlGvOJZb^6m4J^fX=v3ZZf z-E@j@ns#B}tAD^66cbPCEvv7e#+@w$MAQ#SNWAwL;cTpPdX-7X7roziP zif|}2t6X0Ha@XAw9*8e;E9e}0`u0dSX2v`_5d@gG1Y?0xbysL4)$xg+;7Q*>*G!_u)KRo$jbLMAB?A`JbJw*XoYGB=z z)`9QI2SBdffbK2PgNw)ig1O)B{(+#*6N4#IPUSrxQ4^fk($RG3n`<$8YX#9?T*)_v ziOop+&gIOP*BO;eyywS7JF}BBB;Yk`80&XXoZMO6O6n(Zu3UxPf|hh<$U=JQyrCR3 zYmW*;wLjJBWu30yr36UWo{oAi-o!~lOE>TugAF(-Zr?QEoapYpt#aki_hT+jbpx}* zN!0BSCH_A2eLCgS4+?8*Jr9N_I1S`UZ{JKdk$m6_xn4kwx9}@-xYB1N!Kpw*UoKFA zwp)|Uqo74?cmY1-Oq@<&n#)7z-PhuyU?(mnz5+x>f{ zBjXlReUlTtC6}Y?a%6b{FwTPFwbBW2RhszJDCxn!%aa+7Lm@uSDE*;mWkd}+5=6F2 zW;mZJgvDzc_UxAW^Qn$n#5<)7wB6 z0ks4b*$Z;o`r6a?KPEK@i(b~qCYb*lKa@r}BUhObH9U;_h&8z$yc4~CA7>fMVS&!+ zLPN$1??WGG-J>z2X`kSM^)hsAB={vQeBhw_wG@5VNCJSPJgfFZgWb{R+k7VfP~|6S zE9DbBytC+$jmr8hHQS<4q3qamQA|5HjY?bMj^(P9CN$QhJ*>Hgzgnt&&q}{z`i+;b zxf$$VZ=^WjN;f#uFge+RPS8otMp>VY6!e*!pLLv~bWsJJc+@mHB^e*7Ke7Njr1(B<14zVDF#r|XV4JH0 zg#2ru>>+EZdzs5WhcrSBD%30>BLBfSem2>$26G+g{W?m-ILO`!=o4y$?vkSru?q2R}hFlj&>-NzD> zR@U|3Y^nF57_jtfJL^0DJE+8eK;@igdF><`6?;vf#jekvIGQe?a9QQtEY8px3LK8I zQ`#*}PSX4IuLm1KeWg)gKxLW};Q~2SMiJkT^qG|53CGV3XziU=l?lVXz%+5GUzJ@t zjnw}GARYc607GR{505UQAwZy$mE%d#{pZRMc8#LRm7YWc+6b8G*4B*>(|^29IGyw% z=S#dn1r1+Jcd7I6p{M`@H8(r|ySj*P=q`#k{qZypRvhJIi^BJpqF5zP(bD*Job=fl zT;AMr=+&efhIdtbjJ%Eh!2UT{o?$@T_vy2BNMlHE zyImWTlvsy;J0q)bg`l9}Sd*bfr2l+5Ve8CRg#Dab8uGYof_XApHLb$d`Q>(k8N4m! z;7%aow>nZfOH~21W(8j;u#`1D#$&UjDb7yonZAGnCd35vc-X_{tv z`=cb3R!jtyI_5*Amy@QU+v;hrmpb`;(IlAu5;@^-3ri|yqz#h-YYt@#lAR!_Nl$lb zuH}MM)~k!8UT+_@_SDXqkH(a_qIK)F=fWJ?{*CcGV|c=aKbY~%0$a>rhW58;Ug{_k zhOVIez4~^*L;zC2Gbi4V(cD~C%Q}~&_=xxqernVubc&SkSJ zi$$T$R z0*{u5?zbuhzd3H)f1wcUnUS{9e3FN;rRd;Wddx2I3H`~;SuUx-E=7vOon?F!(YJ}b z)Dk=j_Ymj}%d@QWek!N$+~{fWGKa1!3on|!z5*WFv2odYb`pNnR7yUdQTuA%rR#t#o8 z^d;%~`-vD_Ouy5EWYR5;r_^t;A#mZeqTl;*;$q8nzxF4egvo`c4SYl5Ue!_g0}xr< zKbp`z`qG902()4VE+vJ61j(E2G_~C%i#`kSTN!C9mgu(6(MN7Hhz^C@k{X!_5~j=!IUb;oFn; zyBbt*OSFszR@!;(UZ^@uLCx}IVraV{xhW~R-WSqjuA+oB?~*60o!29Ep7Jt{NRk^A zY&9IpbCG|UC?80A`lu#Bokwsq&H72%lh?^k!5-xABJTqtbDbH4Mx5;q8`9Hhc5D?v zrvv$H7|+KNjqhe2D&gpsC}{pjf%3qdd+XMMP*DTD`*+Wy?OrwkTYk)J&w#m(#90A# zPG*MguL6{6je!>m2O^ztV3L@Z$H7~esA;sX_}7dIA)0Wq}m=T ztsR+WWGuqo+Lf2TplJiq>!^8l>OL*6dwEg46byr{OCTF5ygzSKCwISmxIEzWbdeZiy261Va~PH)aJM%<`Il;Qd6=h@|rhF#up}%`_%S z(TEbk@P*kl56to)+@X1*x<0owKUlfZMnz0A4H)SclAHu%53b4ef9by!kuL5%+(!_5 zy>T)lI&axm7(MOe%-2$&ojG%BW;1Uj=~D#OOHytDg}8w6+z68N(9?YeZ`gN^9dCd6 z^NkRjo+mm8|6VopYso@fK}fQqSM|IBo4u*U=!s)-6aT@CU!%~S>v_P zm;gYRRDGn9P^75ecMh#0&_Us3mH*5PuKx@qnz{4Ynsj$BZnBaHVurZgw((E{kS@<$ zR`GR-x;C&KIA05&UEM^P3~Y{qa>>^W3Zk_)yXO*M-->g?J>C&Y8%^u*nVPtu6L7BHzUKN6(ynzP#RvsFpd~mr-o>WY(`F^@W%J*CZ5Siv&DZh#E2@2;68hBEqWql@H@?^emEyOM)d`nThLD66$jDqL z)Br9W)MTsEbW&Q6f!g^HHF^o>y}quWSr2|5ydx|D7zL)2u?>&F`Bluk<`kHLvThZk+pjdquJA$3~63L%T^@BKs`lu%2H7D5iPuSPKx%OfqEq zYt<_!Huzg^?rUi#wLTB$wNG}RwU|>MP0a|gYs&IaJ!@5f#F0b`_W(*p& zZLc^c;3NY7Cm*x%D#m)3J@CSlkxmiAsqk>AgF{J_68t!6ZFe^PE~jGhqmSUkWfMzf z&3Mde+UKM53_Iz)apbzANMJ;)-k2=8_c~A3?g(l4UiJE|+9bi)HL?lPNs`EX)9j~< zg)7qYy2?l8c_E+A_-g~6_7+N*^$Jv$xzjYfwL6LXz1OT<2BzEk6u~OqP|oqX%^$_i z|57a=+l#Gww&8e$vEV+nRJ&f5)d+D=Tm8tSj@Is9dY8H@!nOL@vjdN7B0*t4n603iOx1fe7B`%$n6$p&ZxKMsr=#FV1vb*iMWk|| za$PmOiSazxUc=-}e96J10rfqLrBQR{D}E6+7Ig!GIp70^{h-8Zb%uPFcZ9RmZIknJ+j#-jL$dX5N~ z{Ol`r13jaWUXu7y;@TRokjNsOEz7U#x}k>OgVsoAu$6=pyC=5lCNEh%et)ur7D#1t zy`&&T`f}zX1O{to@VQ7+s{1m+@6tJa4d|UX&F&u!zV8MDlsV)lKnNzEegj|`od_y1nSeCHJF;*w zDR}myIl;W3c_gl4iK@dvRW<{2L%iqO3V-nBWkW~cGql|Ky|V*(*+6e)*jaHse-)7t zrHtJ20R2qT*EPN4_)krgf{5{s-E?0!<+*WZj&JlEtD@0xq#BL^a*N(5ftr`oxO|RW zbsK{l?VC}iajxQoj#9EuqKzhk>n~#pO+NXc?j*5h$Bv_K{hoFTwyvkm{GV8WZqR$- zlySvP8Oz3;(85BA5hkuP5e?Scr&^}{1hblTVnq)|3Yoi+AdDP_yensKdj zqH`nQ(&iaqG1uxgcYIIuR@9qL{AoVW%CBlD_))MQ-g7mEcsTy`Yw>UaUfzp&{;>zC z;u5{^qqMBpY3eO|%-Li?xAcUe5r!9g60~);G1;TEp2JEYCFQ6E3jUwSp zh0=zz{>&s`)&qNuN%TCY-6qU*80On4ypF?cA+l-&5o)Fj?CI;glgn9L2V1j|mtJ^s|nO5+Ui|&xRSdKxBL*?npq7*!$O#AYl%ECmd~%(E_b97o?Ci8yr|E3^;*vzQ-di6f8i;~5$blP}R4*VGi;lAqVq z9w@B>L=`vXzIbcP@Yq!)taWBQNiJJM^9`#G5I9^ea0sbf=W&<4KE)WyDwccr6#SXHS)UKzKbj53a91N$QdYc1OE;|w{9EL>xtFVgH; z7EfsCcC4Yu(w2*hOTP8c*uY0TW6Zu>jA`h7g*KG?=jBo-hq5U0)UbgGs%ePYzB_<8 z@~0(C+qLSrmk2+XX>?qX{DG0=Y4-Pg;{eW=nVof_Cf33D>ME%pxts(ThS{tYIaepl zk0?n^ijqtu-k#moR<64;VMhS2tSyaNvAHe#FSDy;_#xMDUY;L)b)DHguWHG?uk0#x zegOdj>efT6^lY8tw${e2Gw%4#$e)&oS=7fWy36B=7Nm|JfQXL98+F~ldP2R0)26&E z>@(E|KYMxU`dgn!0Fkj1pWXt2TMT#727TWaW1`UD%sg=r_;uat1K`+AsrU`0 zk&e|TnC#x${8FounecR6ywif@4s$P!$%``Sk(bin=Y_{dnk2%goSg}e2-HYv_&(Zk zTw$@_^)Jin=J7UgkG&=DG-}mk+xRx41-gN^&~gm5LyOkdkCN)bn=5?{rm_{rUISHFgLtS8#ogsz|KzCEt{$S=RRo7R)*=;OA)-cELLL44Jc zj^Up%HB6co@!`gRu(^!B8Xt%mDT8F@Kjt>Ph#~r9T{_7bi!%p$5Eb4+>5o+%Y9oF} zG8|b3+wDsf^@|z*Y8$KT=1BF!XFHFeS;S#K`Nnj$@7Buks{#01rp|1NhZK*D1oxq2 zL@(NlKYm;~#(v_Bc&Fis-yuVbz00|pf)<6$eMQ+OI3|~Q^m7No5)`n%p0TpbQMD|H zeH;JKI#`Z6oq=U|pZHec? zEK6%5Asq;%B>|+7YTB8vhhKbFR{dUsMA>}3Mi|i9-XM2vDAhsc07YW@O_|$aWwI*0 zvX=`xLOWq3S!dhVBXhLgn^?YoBA_2(Xg7#y@rv<1uxjrWNyaH+=@oV296ZvP1$_>z z+iGv3v!wXgpfqbmz;yNsKWSt>54}m8m!u$<+pnkga&mHej@z#xL!bBq=m|nQ!jDdS z)I0u6G}I}Ax160@zz1z#!kDg-B!u*Q5Hxba@gRs)#NO^OvAr36BJd&Kav&_|@GOLf z!RO^fx{s#BbKCy$$tTOwFJ&VVsZz(~9|ipyk()KSSC&uqr^q$iHvYh9?P;rQ2EwxM3T zUD+ef=}<(~s55f%f(vzwI*Jb=z+zcWoI)QSP*0A_Y90m*#kRBc37;(1TF672gQMs{>tnZswue^5} zWeO$L1Ekbm#-aPTy7a75pRt|P#Sjtx_Y6B zV)V?*SYPksk$aJ=pVc^X&|dGnm}49hjus~@K*o*k7pg^?@^>%P2dXn{^L77k(EDKG@?9bzF+Z#aiU4ru`@__1s9Uw)tt)$%#&IJ#{rBRbj#yKq&sO!;H~$cpC&V74aQXLTE?q zNf`G2jxgGvTRL!8CV3EIAXvP+BNW6rI{hH6>~hw#7_@RW(_GDwzR{_$ei8I}9 zTpJGk_#)$lKkQqnUjN*=ux`g##ibQ6)5`KN;BxEq=Hf;Nn~z@k$f2&%!~uL>|LK#m zf1(h4RxFw)tC3w!8Bc9a0;@X-9eAZ)+g!e+C|y3@@X?P~rd>pAAjD3>d;-{qf*Hfh z->p)=BjR##xfjvcV4s%t+YwQhAaf0Ym^(V{hH-rMViW&4tv)k9K#&=wLp z9jAjkD$dO9s=u~zjuGwv)PJX}eYI8|qf#VzGRwKpVhcD6CEbET)M z&*`C<>Z}$B1QL4JM%xjt@$T+@0-YawyS3NmIR7`gzA`F~plNprA;AK{g9i`p5IiBc zyGw9)Uo^P8ySuyX;_kXQ!QI{O=KbF9p8MzSnKQH2J>4_Y)m8OWKNT6N16I_H8dLgA zQRNC$CY23#^%DN=X43~3Kk>!Bm}G)h2EA@0tv)nfa) z!gZyqS$DIb+@rRen2)+QntrwMrOjobS)J)p{l*Y)7AW!2*Ku{)hBY;6r#swFK% zF7P;>p5nks#MfprW&xFp+RgS?sH$g7*sVL}&+|=5qgkvjuVrVw1PL6U(X^LZH^b=I z`7ybPM-FL10427)$bvwE?Kl^gIt_i+|717-CPbhuTDKb%?waWr88nG zN6Qz^f&L}T<*CxnGW6vzIVoCyn7kZ6OH($X=3O0-T84MIBA2C2* z7nb{`rg(m@r8aj*Zt`eRFEwGb={x^;x;#6MYfKt3FyRR$-0LHV)VZ&3$_sPvJa)Pu zOoDZ@AMGvz5o))?>V%6|c>4T$=d)gMyvvxWS0_1q<^-7%cxyA+zCz;bF}8WV^vN+i zeinxQ2K`7Z!+SM|dhlCt+P|1W_BUOc*Gi54{rwNcGx@?Q!lN}zRj9vHL`Pkoq)PRW z$b;Uqj!u@wqE{UX6M6^M_ne&_9XiT0g4&Hwo^EbXul>IjD5%TXI&}g}EG&+KRw!73 zuU9=^I9x1dbyc2U<{sX7R|k3XeV0nyft>x+J}|9}m;qt3IbJw%welNouxV z&6&*<@pYUQ-CE1u9Ngu-)^I(Z(2JIZ2wDGnTr<;Mdd~2EvA7;MQhn29K->Du6-^Q} zX(Fnru(jyTPkeD$HnV3l4i@DM-KsnOy}4KyVul0OJQ#`o7izz4zzj!cAlM;3>I8ed=f290dM*AT+R{oxmw zg>tK@0clDw0TyZH@XFI|tIf`=z*o`*s2P;hImp#@`VP9N#BG_fWJiuswnI;5dQ39$ zOx(;CAuJVtbCxt>xtWwQteC=Xwd`oq`g;()4#=_5t{UYVPOe5t5j8zOVATvMJLmlp)Y2&UxaMW2oW3&dvZ<&~iVh6& zhjUvwTh!5Z$q)9nSc}a#sEDl&{#`>#Vyw5_DwQq|3a}AHyet*YeAVNO)s1=c2~bi| zD5uPP>HLG8AFFBcyYI zccYyANiVtFaeA^UjIx!9U~dY7&cI>4;}lF4k=xbjB~< zbsE9NmqF5RYgv?vJiP3VzYR7|;|ln`^?m(jy;B@K7-+!kV-n=>R1@1*snt#|Zj6J8 z#y*yTh;FbJEZ)9jF(ZMFx4WDbOq)0w9DZJt=bMa!BQ&sz`C&Bld`oNXZhU3u<94`> zST;pM0n}EnNh!glla~|`A&L=6O)dFq_uEG|NqX%;L2x#YSi;~d}pMt(hLbF3(xo}ND#>`7yNE-iey}R*&WL`kP(6WkE zut`WUoAXfa{{6dovTt$YPhx%XZY8@n}J2j}U0IdQ#)`RPmuxa1TSl{EU@${ie*U1m|lFEJ36c^qz zui%)BR5Ha)MW{6Zg1?Kg6fT=PUH1Olb{5(Gd?^`*4RUNkV4gLH1YU?(o~b1%`b7#)B(F7Hi^@+S@+#G8A>?1Z&8^%_`JmVgm0v{yI{0g51A@gWwOf8Ja6A5N z?hVh+_4!M0WMo7af>EdvXFJqYWXRB~E{DKDw8_RP*@T`w@(shI{p>l0wGbEL@q113A5+rukg*Q-*siYcTVU0Hc@*O$Ql|83Hn&iDjzQ8Z6ZwpWHo0p<05A)&tzJZYxx(216a zl{IcS7opRvqZy1{s_k$+Sq}*@k<3gXM#oK0)JmqfO>5FW?M%F44q<8N3CB9xLnSS> zzaJQWp$<{^#$`T#KJdl1PrjJ%5BTM_bouA3V#T4LNXZCX{em;_mDNV7=D8W+1>0JU zvxnOLXLhuq43+_8XV}o5bYL8=m&B%>QIaQ9-2&&^m!Y^%11{IWh@dA$_5t%I8L{ z{iV%C6g|O>7oWh>Rd+M>a|e$OHCt%>M5DN-|DU(;^^QS5y#i)5#XRq&dOj`|%I232 z>|5*OKy|^?;ld&j$uPtbQ#``uq{^>ReKl14@P=&Am55zO3~NWxfK0l7Fo*mKYEc_@ zY8rwH2z`emTXe$EA@33gpH?!2=TyHXD>YT(QwlHp)>sNgd23Co%-GqT(xk2CA7(Um zoFYn`L7IeHSfXtec?Z=u@yZUX4|9V%bX)%J($Ih@PPh0m?*fU2ow$ixuLkd(P{`hB z@D|k~irHW`^@s7a-O72!otSFXE52!6v~SlM zc=2FyMVf^`#OD#+^EW%VEAOM`cvb^7&fZm?@zyB=B`Nwcd`R()wG%lJ&A}jLu&Dn% zJ8|fGGdVYuRXsL5SZ`6q9vR^(Y)ERgN&gXyB}ldYw|X!s=yA=+VDz`Z&lZg&}7jrtT!uuml#N8Lw;YMR@ZcUlJ&gg(hEl z(-$;ChuTP-PCN2qwL$1?(#^Rg7gpMmvJV+gYurMrJ2V%Ey+{{*-vYJW9v`A^A4zH3 z@}cQoSVo**jz@zBdso7L8S0zZ{1W12#o#xaI~-r12q*m5nu3OQ@xhJj%B#4t)vnH> zs-itVwwT@GD3>e4FGGW7WR9)+$WA38q5g9NfhNzM+MX(K?h{waYINCt(AO>`a)wqIl|N&!SOh|sm4=gh)Edt8jgGV!VOk0c^*Y&KLf7*m z^r;A0TF{o|lnUZX3JxvAAfwswy%Xh`>8o`oK<~uZYuvv4m7jo0Nq)lX!M1TF+lqb4 zlWDuJH0_f092N|g1o3KAPw1B z!^+9iFQs!<(6&RVA_t)>TKw+zH{G$to5e`V1Q|-uK-&a&=(J?tCk;}TgDv)joUKmB zo4zDTEN&-<2n`EoJ|0mKa@#M*6dWES7OAw7VYKF$67&-*qZ6gAyk+BNNylUMh-aH?o z?&eQJS3!qoHZtj71cd}Lxt`A6Jhar**z|kPo7R9=HdR&kpf-7I-KT)(6GFNKE}Ut# zfj$0kBGJVG7emd!03cI*o89g4D(81U6@JitVguXkqu6VlrJWs@r02(nk1f{p)W#oV zvzV&g@qc@qx}^(^@?}{MA|q;IGq=j_Toly3bs=iuSn;$2%a1pbQGJRj+|(e!t<%6Q4XYU4@lho|&qI=s5ZB0eR%a5gfYegcBa4(~qI#?c+ zZT0Wxr;|h5Eu(4=hYOLFZ8a&{0yMiTt?DweCB1Y}U)h7>gvGgc3h{TL6h(pr$s;2F;;rBD_evw9skrb-Pf05q0 zuvX=CNhdwuK9FKq=d2jryGVb$r67%wGc})H8^6TeR#&-@G>{A{NKxO5b%TTkxw(Cg zso!7XNo%U|+F{%ZL=6*gdA-?}7B~*JJ6*4G5a*lnzCC{W+n~CVK~Q&z)ZDJgAkYMb z=1pI(hpvib`O{sEuab1Lo-VIGJ`0?30ZADyj{6g;xE4YTZi&=dUv;Sj)<-vN13z6x zV`I6D(XGcrX4yITcXoaX(Y`e-&MzIF??wa|)w(ntS7X1X!dY9&zgoRI5RJ=>N$fll zw#{2DVl#T{_0PKKs=IbD zo;0e^PuWlvx!OMN9@PRd+}FMCpz+17*qdJAf|Q_249QkZ)^702dugm`i6bxAW2+V+ zLsB*Z8H<4(E4296_FD>p|SB^Bo5rPpDP$mjqk^U%W2clE!buZA{)rn?^DKvC?mlsM+*%HIp zQL9#=;(hUOYtylIwy{>wl;d5^CBRjQ1P+!J+rP(oJ9zh$ZWFa#w_W`=4z`W|!DEjH z?X@e99=~j{HI0w`Eids#P))ydm3KMdS zN@X!AsRE|B5g`bOis}-mb|6cQNNO&w&NDvdIU9Q4db@E|zRO>>4kkl&kwvMYP~P4- z;_22-Xtq$*$_?*R`T`0fgwXt$=8TP-g3Kb)9(6YsKxPi$OqJ?qKztYzN1 zn56N1@@~8!<{S;zm_|9}R$Y@2I=obD>lm~6)}bPsDC7)FI3}i@nBXyX{sW9;24~KK zyU(7@BbvuBG)$+8As@$9CM3MU()wzxHR%zzu$L{j&0tF zV#>kDo6X&N5D{f=N8YWupYpq%xx;ZF%dLKc!l8gUNL2?mR$>9r#Rd=a9RTiY^c9Eo zbd`>dLN^5$GZR#wEez%YcbmMu>vE~t^VOg!*>k(QyxrA`deYVMZGH#nTw9+YBJb!g zh}FshgFGJ}KN}&~nb~)bte=JKi7as?z-y23PV0(f z+)Yc`FU;9*ZD3DLocqQ2yP(@6D+{Bku_3*s1$o@X44`kVU(HhZiR9F`GNUGR)T|bS z-;DM8AT<(cLpPaS=~9DifP!Is! zn190fd=QURGoG%?N~&X@7#;0jToY2Ht@0~e8m6gsbgtttVeRbf?;D#=&VCnG@DZS) zelW(ZXcFUBkP?DBu{5>1ibqRF=KmR(KwZ<3CkMr)&)Qi{&%iHf9B%+XnW}KRfSqrp z9kRqxBMEhe<+oU-CMPzV7XGhe2rS+s&x`d&VSXQw@pjc5_MS8M=VDC6d=?tg)wH^ zQBTAGFQZoONHD>vKy-Ms$I4klHf)_BzH>2SCo_loT~!dv~CmiBo82~BZnEA*!C$fKV|2py*ePk9q0Wj za%gbM>e{|6dN?Xdu*4fXhhw^9vBOwM&jFTi^j}6>tJkvk_G|phuF`Y)Edw;PEbWI1 z%O`G<)$9!!oQBR+AJ{J6&bO{O8@0I`%+><5MZzBsHL!fZN6ml#hyW&!K+KhUUSO|p z`jrbIy57G$id5E}rv1GFvppi+#P@#1=|(6WgixlG9y-@c@oH1BN?cBX?q#iu*Fhen1 zh}h6~2Sj$F>FF&S94NLpooc_?DL$rleyt&<;-PKyV{CMXdRmIs= zc5O$Z&__;!xBp~lT$Z7Tki^P0$j@~PsYX*|^}sv`k`Zstd%;2Lw0M&0+6k0di^ooQ z-GN`nlpSAPDNiGJ8O#lM;dx}AGFmb0pjR*9!G0h2lL1TI&O~f=qVObh^)t}mz?`82 zfEX~nI>%Mj+?#6Sb-itd{j+%q!FmN;R#rzxoz%)z|LnD8OhpH}+h$DSTUn|+={Qe| zfR@=Q(Dax$WpgRO8rw<6wUdr$iM@0O{Jlv$+U0v<*Eor(S1j3M98dl7%}Z&ywf>kW zuFJGi=fs?<{p+{|!@S7y*%DP_Csl?RoaE;+O)Wt+S-nOi+t44d*M6j#?5PdX6VeavDbw~B62Vh+7Q6Dqww&;&Fnkd z>XsZ2()^moJ2!sg;p|-^ah-9=)Td{x-J>tJ=OZty%t#Zxe|MiAPh@i!*8_39h!R@4 zq>hjA+31TmpZt#&Teb({IN#nqvrmbC2)gMcQK%D4HQ5U^nLd7#Q5mg%sz=SMIBSOAXe*&4_loAnm$K!z2RJ1b+m)6ipB% z<2r%QTjP&SucPl#*nPb369wKCplJX)u0VYBiY_F(>&FN&vq z;Az~o(H!7-9ht`?4iNxy7yZzfV5_|&_)hBD{rTf+L{!fLR@s8xMMoCxAR{1i@a<)Y zILh{S=ape1Zl!^7@aglTrga1Os52yM{P3wab|GA?QSfS}a(z7G=_n8^U3C%1pK;s^ z00a)4Gp`~=?yk3J7rb|_GSj6qJmt5S@HoUAThKc%$_hkgdIwN^3)r|3cs(0z5m3k{ zeT$+>eQxHwo2zUi$O~gh!~`T`J{q2WT}jqO@*P4eW5uzQ3Dxjgop*n}7N5hv!`w9< z>8e>W(B1|~^-7$C^M_Qu^x=hjZOlpdX)Q*wy^64H!9Y^xU>cvDz$b(sZ z;o>HHZhJp#M{h%3+}&BigG!WcH>g7P%r^1!BjcsL0~QW8{^61LcVnT)g5xp@&X&Lr zf|5u8Q(sDnAZ2q?%u=GV6U2rrnJ~SHJUvF0W9xOO{9X{w- zmg7M9$c-%CwwrAr>0z17{yYe@wp>9(5hj?9Kmk4GFD5>QLkC?i*>0wKe+ zv|Vb3)2(S*lor3!)M`tjN(C{D8&{DZER~HC(k+bpeFBGRro{#eeA{a!42}zFXGTG~ z0+IR8;BxY&AETJJxRV({Lq`6e1*=f{x}bUACE1vmk_BwWgP?(L8lb;FN|a95Fq({x z8e3aiIR#ApA(y?sW(aZ$x2sqoVk1O;zy%-It9=CKKD?FO)*oIv<0Ii~v$OdsW%zh_ zuDl*Fgo5;#(!&&Y`bMdEfa)?A#BZqi?QTf&c8(7#S)qjduU@NPM&5cg$_$6m-?y=2 zvI9Ii37O`r8Lfxlb0BwLwB@^u>kb@&1M0I3?MiV&hQ`LmRYRLuW8*LSlJ$d&X7jzB z4R23FGm96->TcJeMu6()r{cEhCxNOL3C`_7uCN!Nvo18n#H#8l{_|zjiFa=2bgg?2 zf5x5WsIeyP$lroDlj169MhJt6c&Ewn%QjYlC(}`A_!X>%%XJn%=cM=KvG^9u4;6Z1 zrkfcn+C=b78xBC~(IX%!?P{#{GD;*$OkChf^RXoh;( zu4qDO@zW`lrE-!wG;-(hfC3MDiCIRh}lHGyVBKgLSlb;u{M8|larGL8RQAo^R8(<>Zp~R zstEZOHCU_ZRX0ZE1AxW-2nS8Ha%Fx28E9kZgLHJ8(p+7wovb1P4;oj)G%g-;$IJ$F z(!XqK+;5m$9a9H(TxtweG!Yy>x$DW|qXNzIol%y~U7x2<8+4@Rvl`5(>BO~XYXGhlg(PEw|XHz|rI!#0W zoD4jGXgqS2;G~IIY}UM?z-VVc#)wU}dwf{1VA@c(4Fv$PN#N(RX;i_MNT7j^SCLqy zlDQ0mvMchPJE*4!DMuHL9p}pBClZ?qopNfNb%(uWgz-c>(|Q=F}Q$oOmfK5o<08|YD@($CvxH#!tEtcBGF=crA7Lr$m3LuUmq%MmMvRYDsO#VIC z!~~I+w?r5)x-?}3MLpaek7v*D5fI{GlAp*|MM&cKTZIDMhp7Z5U-w;}WzyRwc_nH0 zylU^;&KgoFX)_<{?eaK|9}8}H7c|tzvz}j7<=@=pC|nwFh*mGPP5K`-_pyanAJ7iT zAvP*88iN9=F;z$1)yEG??Arq}%g|AiNpCi}6P!bHllBk!HvgSDkq}GjQx>K(psZRaKkaufR9kX`sjDoADZ@+IwvVVyof+==)XrQH4*fgm`;m+9I>-B=95k5M{xI(fud&@oi7#Ki;5qV3 zOHnLLu=5JSlU(0r4l=!m9<%fXS=FpL{zy{*vLodkdbwrBi@U4a`76KZez$N|xNE@~ zF$lRf#Lnr4k{ zuoMIJ+z7SUqy=n%5_gLc)eobNY*^pEVE?OYhbh9p@&yRIbKvMx`%g9S|EWfId#DzP zGc8+G@7)jBiql|e54bXdo=Ze_m~?g?x5fMP(=r|sEn|*M@f-s~Xn#qdmBE8Dnd+dO zf0J)rDX3IOjx80d<(4be4cBv{m{pn+4>M>HQE#mRsmW{@Z#lVnLyNq9NkPu~3l)t} zBNZiCT?9Voje)b_Cv7F)oS!>&!9kGvk=R->DYB5Rm!(_bTp7|4I2LegFVWpQ%94 z^kfwQrB{uLl8-*mkitc8$xjDa5Li_)Pp6RC8@lWjAZ& zWiKp=N|3%}6fTSWg?HxG`;|BEdRXzf*o7YW?kI_wkR7onRs5D*$y|oycV!rwOlA1CCJWz&Y`U zM*a(6_W7fDZC#mJ#K?}*P*lP3PX=w0(Y4qdC<)x{0X%EqfE$%~+r*W(k$OU|nB8yF zf)Cr!#K*6|KhS}PIr`5tWRCw+@4dsv%3WzYs`sr7c%i#^oK!mZ9g>tLSoI>v@&m)_ zF0th(1dWljGi|E`(=~yfoa&?JQx5(2P+r$#_>BjKwB1CUFL zDQCw9F9gtxNWim{lsa)n2)Pl%(~{h*qw7J>^rFWDm2Uprn}hYw(;l(ObgQ%iPQTnx zp)!JHhJ35`f@^@g>2!|2kQ_B{HZ?J~r9?N~BA(hPp&{Lxy8e%46hiu|h??v_2@&@? z+}>^s<-Xa0eFKB$@IIyWX*AhJR;ofxk`%NfFZB+4<SKb(EJU|&CdG{ zkc=F7Z|iZinAUO2<11dwo_a00sP)S)c#-uP$X9;9Bo<5S70hLKnp?umX?VOpx9pO+ zf@?I!>HX)c(g@Kv(?IP0(LKV8pNIT$F8zw86V2{(vyk}mw2j)wP63cJmM~Tg+H|<1 zLKnq~FP%wH+G6{8nA=d`Ed0=Gejp-O-}Ho|M1F+LfSZCsWButL{(#9_mP3ZQTI}6&MYRC8`Qyk6`UHhh5j80D3T~55&gP|Z7gl}1IIZBq zC<@t1CP$%+ir10$aPTR(vuO^If))~Noz~})brA7U9ICA${+rwUGo^(VLS2bNho*H) z-{_Q;0X~7b)NJhR0D!@u8Z~z*_Xg`WW)g$%EBao_10wJ+n^NqFkhgwe!KaVW=_1fq zk@{3pCud9D;WM5M^FzMGaQx7woBv@DQuZ{)V>H5H&Cbk1a0=54c0H?d4PflIO>+M0x+skOniEg0Py~k^#XHcRy@e4$jnT-Yu;B{~0JwNCB zR-rMj&yr=m>1Sd&%FdQ8ZzhO@#0~06O;0VaTFeH9E?o zEUWw7VQi#IG_@vL?*|~F?=OPc1h}=NSPjQj71#2~D4$V;evL^{MZ1K{*y&}fk)>VGQo3aX<-<|#2f>QWBPkGC{YXhG zF$dLRZeFsFneB`V-J4m-u1=2C9r&vufXfpza;s0Vt38DofxXQ7mtu&31TsS=b=UV$ zLk#_&+Cnm`PTI{8`!>}QXhH3iBoU9gOxP~F!ZRe{p9VXxAuVe=63%BfR*Y!!)kNEjT)K*|CHw> z{k^1uio}SQdXX~q&pZx%ywM9hHK$#*u^mN?T6y#>Xg0b2}K0D#o6@i{N} zqbeD)Zhnn{LS}0I0k$>gN*YXO>gOSzBU+vq3R|xf#0tdzNq9X062K(Ea|Fy^nM=QS znTwDON)*7?`n1^0D=R@G<*!+=06|YiG|ly(Y~?T4_8i3*ddHwjkNNj%d8DJVk;a=*AwmY-NDL_nNEj^Q z>H&qA;UAvnu|wHc$!SXEq9CsSwY^SYLAJ}G9Yyk~@mJ&bj++q~m$LbpWr%LVp zfRpfUboXw2`m_V8e@vhGq^c}!4k*rp=rDtA`|f>fre|x$$6Dqy%2-}qf^tKq9iy5x zOSCX|zs=8q<#SsXQ(aCfhqC z+s?Iv{FJgJjd|QI17-avD++T^0O3zjET;m8!29nTHXZNU-kvh|bl#7A4l3x9 z!UOp1S_;XNLJuNnC8NzR%x}&kw(pxl-@E>C%uymqU|rE7Qm(+Ng`-n;%Ry|%Wh8Q8 zSU#wcmef=c^9@Tm$&=*a!Fk6rWDh_ABxJL|B-7C(2phd)LYR!!`1E673~@$CB~D~; zK18}u%32v;Rm&;Imv1TP1AqbiA@#geYkHwg+3$~>%@eU_6fnPozm^N(wl?AV9wC*SVgI2BH*)ey5FT5>DG9%H2bJ6K`k1-`@yzim& zv!6o%&AJ=|S>2SHVZ^5q68|;B|HzeVQi5b`6sfR_^jE6GI>jS<^LkX>M4dj5Xe0aG z*dg4^?%~Qc@yXvd5|`9(xp1oT2`<+X6vz0SpAfUWB64_0JRsRcO_{7%-nipymvK595-Q) zUE>6S6{bH58%C&E$1r05rw;~Vzw3zaVGT<*$TErQw5Ikt; zKUV5~AMOk?rTufigGW_t?97dudW3LAZ~UHF@2a zLU2cAC^hXIV@sY(nbcWiVy;_m75i#E@p6U06TRjAI}-oNnd9Kra0RDyz0QKO+=>L+VG z6RV{Sz|Y$A`2F}NVO7JDr$eOL`#*I&-y#a{j>AG#q*)bzEMK4hO3Gd2X2mpf;_O^# z0l9rmjW^UZc!wo+Vj#hFR#<;F@v;udK^Yyat)CcdpmC&ve43GKa@BCgXFa=Zlc$QO zfSNsN*;T!yMj6_)sqcB8p33*U+q?81U1hwb{<#UvXOc-^%PcH7p9vVLJ8(b(IWEi1 z_Wv6Tpmm$gYA^=$*6QC``6MuaBYCRTW@lltS(6bmkhv&1Fdv&0lR;LN7$=dSQs%kA zRV4Qf1NPbAmP%`!aged4g=_JboAHmwhl%6=X@s?Sp-o_ z99+C{b=kMCCIYJ&`zv{svOQh3_2?0kF&EN;o)85yTF{G_){HxDIG!qi6`QrW;L+~Zx(T8B>2LG0BUs;V*S~_W^!axbKL)sTmTt0OvZUReTy2T`i;@Nf% zv#d@NLeWtA)pV1S$})cAU^h~U4?C!ZX+Zt!@!D&k zh$`;lI80KNXPHn~BVHl-2!U6e067*lamEdJ(a}eiF($g z00W0{qUDvTY44x2g6{R({S3kucV@(c;#!BUI#i=mN&8|~7=>R4d>S7geGV%b>5y}x z3=uSt+eaP=xzc?1-E}jA_$_LfV>{x}-|Fo3I#?s&(s}ffvO;4I|h&#_4uy=AS}m*126l1tAE@$_%bSiTS_e1H-tA+ zokdnR^z|Ej26|xv+&`>mb`e`FG}VZ!3ngOFjz}u)Pz+z1OTQSn0J9TVOSi*md}8NU zaWyD4^ClGPZt5-h*wZTJOiXdFDY+{`WTq5wrKJ!7ici(%(TTY#^0U&}%2V-x9|~gh z!e!a)-%KMu)jxjF0Qs&JXLFcRPk1pTe^7$y8I3VJ=&g6u*MNGAtAQ-Q;Nf|)*#b!2 z)pzU27eN)|nVA{bY`oOucg5HH0OVhpA=CiCHzXDZ3(FTjE5>86YmRGh#7rI%V{0bD zL@%b#5NrNV3MiNI^-y4D>GPM zZ*UHe2n)e8w@|$O!eeE5X=5@$G$J$On`6)!G!wYccq8Owa4K9gZzd>v(;}h6bL(DP{Y0VqjIngF+!~yu2FSsv-UPN_}NJLc(_=Kiv0HAKn zYhas(he405ol`^v3dg)=B4bTMt*hd$R>`#Gs=Hk6MJ9}z z9lg&kbQN`S5T*A69;63V3FQW6BW@x(IeJCc(GKkk*?uYV_3#53P;Io*X76T|cMgZ` zSG|?zV5Oxa8s+EmPcOys<$EPltw=cz>t4>|=SXIvQVhjZGEw{a~JFE_$KzW#Ii zX9hxngQddCvT~2fg>PdrZS#Xi^tJtkP-rZTnc9)0M{CgavX3D`de~fA8%m!dG9y<} z5@&%QFKw8RI>naXU?pPQluG=XCkQ@1vL1B{?_a)y;xABBF8iq7gj5_IP^r~2y#kkY{y+c! zPa!B`=keMv`kW$F7?(!|z{J8SQ=$oH|1XB~A5`sy$I;U`MWWzCBtHIk>++~5&IAcK zK!k|Hh5l~WFN6*vNpo}a?(F{svi=pF#IFd`?5S?RBH{tMzO*(ta=&={+gdxnb4%%U~-&7eR^$8R#b?Ndx3z9;eNphsg5yjT(6af@WHR3 z{4WypuR+CEt+nH|VPRKYFYYkEUwws#hN#C@<6>iBetXFM?p4(#;!9^@Fx@;^ZsJj= z++g84EW+{|g&9mu#q%{-CHp;|nAs>PLNC?&R(mCRt(n>Ekos`((1xPbMGy0z4*z$@ zT{dz^cXV|0b~f-OL4w^}>bHZ&;*8?<0rj*ee|+?Jz0HsB!y5R2_Q%>3`*76Y>&w^?ZtLWf!tAM0gxEmHUC0Qt{a{t*Yy<%S)IAK9dtKT% zyc!-%seMk$f5E{~eC$AZqb3tL+ssk7fl0netJ_&vOpo zZ=2;bTPf?La^(6W2pzcjwvHDa2vObc2d6e;V>>(TGN==6&vwONzk!?&bR3#eJDkhc z8{dSy=JHVZ`emJBlH%6}G1hHUYn=9cb0UW=24-q^uwhLspY`NX-dX{gXgDy6mM7xz zIcA4_$B*c}n~~_ce*8nFM8 z?jZN%^%QwN3Qbd7)|rwFJ=-VK-fr!a=bVJ+Tq4Tut_7lehBNPkO7wbCmBb>>kGuE} zbYd8wWmG)DYIU)llmgM=3xf=FEG-=^Tg@FeX=tcH2)M5htj>=o&^No)pUjnh2dwSw zTfB7cN~&|BA&G7p14^4vAO)ihhVO^WR1m-b@6!VV2!Z3u1Lv_n;=G)75q^6wjcECt z`r@N+KdJ)r6PlsC1H37hRPq+>t9OBp%yQXq67;Xeo11_N(dDLG?}<~ZuM;HubDf86vMl(ZibtK`-1f?IuH|0hyIXWk(GTxA6YprjNB8rov#$N?zh{?Sy=_I zRq$7J?{lV%5;R=5j2#P_oz}5+Wci+5G(8dfk7F|TimDqv6Ra*%(4eNh|w{}p@%UV-fvmHGA zuEVY4obok=4D7{e@$v0&<+_*Tlf?{Cdn=U}$d4G=_W{|1zrdv@5hA!5iP9vcCId43 zad+Vyf$xQA;yrDz(sTW-)v_Y08cPQD)74}m3h>*LihHkPvr7Xt?(6F6sLNwMVe``B z5wscEAO?R=Du>hLOd>9mi(rg5H%Fk*3lkLURUd%f6JAr>p zREB)#K_CS&sf9UR$gOX+IIk_wVD&@7EX-ToZ0{enkPmq2iKczwk-kb`$DdUG?9@|x z^LKH%qn&Ry@9fsXV6Abb7EQt4ipzjfoQv{4j}4{XRCL0;w+R2C%Cdw^Tu` zwY=({!tk_w)8rGb-x_S}at-4|>c|UT6!XN!`wa|h(io8MX+Z+=J*>v{LUG;A@LXMn`C>JDH9Cyhm;bWnTGHD7ijv}N-dl1g`3<)p8JCBd zk)$4FP&j)G`aPgaV=u+$w20?w*AgRyLsDPV^s0?qsHEN9-Rgri|)ji z{$%d`dy(=89%1mFveg%55sCFesf4w*yMCW_4(2sxC)6b2xwLjvPtJX`rSYyl=?rh? z^$}R#pb77E;Q%3-K^<;3@g7ZxjvmH;pK^f$-v1^wadC!#l9Td>yZ10CTG?sO7S4_A zY}5RZi(613LGZ+tEY!7DPRLSr)5X2)_N{mCjwG30>o&sBU>zZ9#3uX9+{|>V7hIuy z$@U=E%;hO}nPZ)djLfqaf2H}8cq?xcSjPyQj}*J534yKIOJ~I)A>!2`9MDM$?kUBe z5be1abE)s(LAvMu=Q&g3A|ahSL}B~6I1m6VzCJkNIe8S_etiq=0QV9N$#0PB zw<0hgIT6fzVq|{*Y;6TvCX-l3J1+S{TVl+dsnUr?kf!mb0shvhB4 z5JYf%j7oxcJPhbYX1Jf*e;0=ObX-TYEI$uYn-Zfpe4Z&m`#`&3LhS}2Mr&(UrOoM5 zxxdg4yZ-S6e+EPZtGA=0Bh<^?kmTfKd~SVi9VWFzbWY|9tMao5%hlEwe$|HUm+p)K zDgD0YM$V`5F-uuyv$L1lA8Q5m4#J*zQSw6Iz)Wgr681!MbGG=oAle&)ZCiV`-91$^ z2=DE0F2p}A!T6KU{&Q%&jRka^*xB#bNb}11Z8RVNgS#b^h%w7;{jUViC5_bJ5Pqw{;AYRa)d*2y~>05Kz0oThmUV2z>J`xRfr#dQW!N)*TO) zj*$$$3H6ZaAiMe4Wn@%rP)8Lu7r8sGX#qb!M$zI(Id1<_jffuB|N0hGE~3D{IOd zKy;7~DudU^VQKQaJVikFe3P4sBOjgMih>943Dj6?K;YRxG(SxM$R*14ku}idBN;pm5EHf2 zu|w0xIsGB3>%q(GqLp@bU0P+U(i~W?6>n%5VD4y?M^4qwZZ+y;C6|82W~;ZpaR_9l z%=wzScx3-+{P_W^IUcuZC>cjh={EYj%?Y1V@ZO2>{0RRLL5pYVAE)btv+A05e;z@SZLyD*=2M5VWX`l{NpM41LmCN?RcoF^Krt$&{RKCGfVo# zq;gj-M4?3wJ_6`q&7U*OG#nQfClyc2x@8-QFZ1COo8mkpyGm*b&78;EhHr3iAX3HR zN#ix<%*>4L!6dHVM%~Jy?JXGaz=H%PZ^L1$|20ck zXiqFR9yfTU;p*B3fj==$9u!<=V=Xw43|ZN?X!X0Agn?MkRaJ;wfcvLoWsA(Tp~cs> zyp8*hm&etp$^o!a@E*?cM1^{q9Mzd*;Rn>*s^9I{Y1A>G(Srd`?%FWy1V_hP zLZ}Cj^M1w8+aCcUIc+|ZO}WGGWd-%jv3a8Yo-azSyx!_9Hcur5+u!3gtUNuGyAlWk z$(8NVh5h9BYe5TR9~KTc>XNSfbv~=HFg97`Qc>_8ipqbdft&;iU9e0ivE**%98hRa z#dQ24HD*_-6ojVifp=H@vKaWpMlo~O*>)N{m+y6JV?1*yWPL_Am5_H1mr}#in6^^I zWA<7yFo&En2}?R1A44q&lWFJDlAnP}8d@ePK@=q3&qXf?c<sM{D)`uB6AFF zwN_?nZ`^L)T|BEJOXm36Yc-+qof4Rkky0_|-US2j7Ao#QF;aW&adv#KDStZaj+J8^ zsz`S8o6OW$>IC~Ca9y9i!?6MKNvC`!L;p|rwdWi5Ol|ecMdaxIo394^yt8A`L9+}d zuk+H%fd+!O#FYl2x{VxYTfZ@tZ>}iW_DW5BUlgKr#_*Q9Rvn*ddz#s3TyJGE#qSLNO?*ul>klOYtTobN%>puzgIpv@*u7Z*H;-^gI}$6aCNgYvp-D3*O(|^*n>|^(U`42C2L4VkAiY_ z)6f%vS&g=Ypw&!Fx6({^$ks5jlHy29{&N_I8*?>7m1MVAq3Qf7Dai)S`D>?GZh<`jO}BVEsv|~P4*}a!!80LX4>(4Z_^d_WXMx#>9l%uvtKKYAq+k z2>)m~QGQwcxsLK{x}0!$1R~+SS~ob$Xih z?&LSCb#oN3mPr~FBrj_2Z2A$pSO}|^GM?4Svd`ykUf^VsaEG={!zUTj-y?pK(O**> z^-2!vtq@J7{3`7yk=#YYXV7WpbG#665_jVY&y%ONT90we`|L(j{3``{okxK_W|obG z<$mj+I>n6FtY#?$1{wF>Lh*CiIyE93o_;TL_V>cxT)K9%By3#lirZuaT55|&;vv6o zM&>QUJ+0$tSVgJfvq4ktPaT;Wgw0Uv)ek~yM|_8yi4juShuA!JA*I?+;y!^?ZbSSv zZ6;&12t-l{gn4(x@ECH6<$u61BucM1Lku_`lEV?59w^eSNTYN*LfJt&RyjefS*Vok zqs2kReFWa?Y_a(Lgc@@;NaLxwY?k9s+T74>w|e)<>R#ja;KKKf(6*YoInPJD{0Pev zAiaFCQ{C;)P2z-U=Ns?;Y(xAibxwTT3m80oj}*5s{;ihMy*;EUVmW$Ck(29$AyDmL zLjk~^`|$B_sX6m0Aa(3t64E4(?c&rb?VavOtZw%av~}uGpx33r{5CpO90BqeER2YsPGkotL*d8#XOK#MU&CEN}S_%*%_QqSQ*70#$6gHzcSy48rR z&sfQBp6^yDxy1e(3!pye?8=yR@zv5l`QirC229iS@!~K?m6l>zP5NM_2DEP6R(F&3 zd&#s8-pkm*I#OzA7#K)B1EgSVoTWYdYrs2(6nemjG+&;VeyO`&ekTTY2v%aPWBB!y ze@jU<-}t8l+`@B<{!+)|^xm)JX}x&q{mx2!b?=k~!7(m7Fg*Og7c@UsWMVc9vKtNU z3?LSUoK<({!xCzgD)acH++U0n@S1BYI%`3!JdMamVFi>1%gonabzUW*Rnxe76cAzo z4ekU8fCOr$xN~1^NvsmxIg!z!9rpq)3dl@77nQY2N(TH^VY2I-?hiFSH--bOx#>@u z?JqvUX(Xs6(XGNuIBre{Dc=0^U5NY}7JmOsobX%kA-!dh;SbCttBVvbc4H zj`WNO#$A3S(;N8vvxD6fGb{VkX|_?FMW21_C2TZTxw(%bZiNLluEPLt#qGPGwNC`H zNJ^{@?LSlFl$7S#`OGUi*7~TAk63HPvL}t(!dJ89?Wrdp-rR&EGdR6Y#&4#ot_BFb z^d>a!jF7SVLxze%T~tz%lkZ-Vr&52;=~vQ9%Z%^tfP1&pS#F=Tj%Le$jtvHTV|+%V z-^bcXSXqg>FgrFGO=2N7UGEh3N{x+OZ%);z*z4CasSbW^H@!fw&x5^yP?0O7Lp!5NXnum&I_ivB|s*E^k^*A>>0(OM-e=uDztuR`mLSW@a&vqpxm>g z_Km%9jkIr>PeLvP4zX-M&*H-Ck70s=+(8b5Lg%2@i!lj19kHG%)k_qU#y!O+CSKF< z8LPy0LORx4-JX83_nio$^pthjdU5RE5O8{^=FRt*-~)0V7(AqP^6R|}9r=vhtUqKV z{5{pX!YIqtmer*T=wrZvem`VDw`U(*BKJF0O>5#rp-C-PlVcMNS#&cQpyFzud@f%2 z-D7e9>UdrTgID^r20L(0DtDZ93tUlxjI3coQc{f-plYOnvPMCIi_G9&)Ino4P`REE z`{Nz8h5@IB!83W0<@r9o*4odibdL$?ok2gUVnC-xc0(72c7&udNuhgZkaG$K3=Ambo_|Gl z*bJq->@CIRN?~Bg>y)Lo^r%7@+=nRDO!!?RYiWHE(<=d&>Bd91=!_sxAlKB)eB&Gl z350*u-+^)AymzkO@g=^GA@g5kaH+H!qm}9ZEh?5>_61JQLxO0)lEE%dq?{d0@;f9@ zfhWQ39qOtD@o;Nh97uq3L@?Ri>W?a!U&G52vPL>y4yG3-A_Q*#TGEdaK;e~v!=|0U zl9d8u{)HUd#LalK#XqX$EV*n-nGv8wn3@BPzkbJ*4T}usx4VwXvWCAT&A0-i`BxIm zEx2F;%rg2hI6Ljv&mP;NO>eVQKA}31;NJJyhV*YFt6p>N5w*9mMj3bLNaw?O>G<3f zmaYrp$IR;L>e|R&hxOg+AT+u;weElhTIbuidOj@+VbwsM`x0&|iGKA$xJ#*Nb^}cG z#pT65X#^!Df}_OSxv&I=8Lg0~dvV!Ud_y^p%j{d+($Xb9Oc6*KCf1bpp~XsrQx(}9 z--pV{md=?B^gqRccREk3>%V^f?5wj*zfue+PsX{_rOTW`{{o4!qU zAgp0Y83|V@Zg);t_3HSY6uio|`7+d1okYVxU1h!^rcqIPUf~-cr6a-M&{BnDyBy8< zdwP3H`E8JNtT*F{Bop)DPJvXipDgBQey&=Z@LCY(+fVaddYlzBaj&)x%#9<8b<#$q z((Ce;aaO=vZfKiOR8=inUc-7XW+%^;lq2LM->^Z3>@xYE18%}|brtM<8TK&99m(MO zu<%H?8ihLwktc%rg-+(t!^be9f_2q!Aw9~9=oK@t_6?rqLlj-fcTBt)_;p&QzB z-eQVde@Fb&=F81#Xg|0jf)2dIPz()UY=G^jrcPL(0~{%9cr_;z&Sa=g<_AQFYU-CC z5j@v!GADZAfVT`Z9m4{;5V+PyW@764&&==U#`ez(j9PcU9?e+8Vpu5DKM3qwMVtA+ zYbpFDkm!mEdx*oCiZBF%ppz}}MLV~CJI>-ZFOJT&nzUzEfT?zT39AfTJ;|lZG|@j^ zK>jr?eBcUC)y)DVvNXDU*?_3 zDBSOZDjSRmE|4I8^li#1{^;xVELLOZd0vm3_m&Z~_cyC?EICBPx!wITjgO)YnDivB z94uPnsdcc_%uK8!6$=C2cRHvKg25SAoZI@oJl<3RjLf@DXs$q(jkmy&T!U;vFO2oq zcu^6HL768ek_UzMIy|mY+2jOyT_Qglgl;a+LuMs(cRG~}DAQE$VLexFIus#~>D4r_ zj-1mwB)H7tht}k6I+RMm#qQN;`mbM?Btx;tMXxY8)8=N1lxjll&G%Q#=7(1nTuD*%tqTbUkPX0?=rwsG!E=AcF%0!r;Oo0fl;p zeUo10#t8Cw(I9RyC#i(OVm`n(J%7L-ufmMqAMOOVpnG8^ zCEbkmQfVo?JJ3*hMYP=*4{EV|o7{^&Z9LST_(mjHjXO7P?_mME*e{y&+7yd}I z?$&&}|BB(Is-cExf3nVaKRf7CE{oJMpJo)Sf_bj9cuXQIC+FbmYRW$Ck?3BGG#Ilon8He|r@kE&Ma&OjwIfLMq^64PRWV;~ zw_v`HcA%+|$nUHo0D7VfVfWIRC*5ia4Ej@A9ke}eo~tX!#s%&p(2^;k0V-%Hh;lx< zed+v5&DIyLNUknnRM+J(oDvL>2rdr->}tzebJDZE@ag?1iy#lvOv4$3_@bnw#MpsD zg%SfQ)-p+j``ut#6msI~G5y!v9#1x(&ibn*cpzX*SZWCV1w!y+YPw@}ew}oPpUtni zI-c&So7KRw{NJdMpcD~%N$sbQ*b&w!_laGjgd5aA`X96uJ)z9=y^_xvBjcdmIh~pU zkZ5i*q=7{O0~DQ)?rH}QH4*~37?WF4s{*u%VY$ZWVG?B+WEl^U1JKS->m%JgX%)oQ~R zzoPjgF}4!uCP)um6{h30-g(`!;@fOlZaAWgi)ftC< z_&agD38d=iShQHE9cN*c1#$^kusRx-cjEOEE|)C%ds>;&#aB!8yy5w zn0PZ_472efqLAmpvp;P5W0b=XexZ$O{{ zUQ12h@td4)W&(%3ML1y%88b*X@-}o4fc}WSUK?qIIPZ_3yU}C~2UKidQ+eZfv^M4o zPb11y3c)DWls|~^nHWiLylC>0$3sIipkJ$$QyH&9+SesO?K7X3yh_$UVLmQzR*Hxxs3cR*DKo7Fsbjq9VD%hVJQ zLSx6hvuH>oqcM@&e%5+-B%uD51Pr zwEFt8K6^~K4>ta+0m@2;fw7c~rzVPdMGX=#fP6mnb++--~F7FK&`L8Vdj#~Lq8hUoz6xw^zv}m*= z>%m~$<^Z)%GGK*6M{azgCI$!-yR61sDD&KYe$qr&sq`C%KK0CD>4d%h?D`z zwby9yh<@VsPwc^4cX-HY_^e&s%ooB?h=2pN3?hU%{p_|$@V`>*V2IWAI|&LPfb1m2 z{q~Ham9e9c+pNQN=NEdlmj@(RP zfltY0*;XDn+V|nVWZ%ZXT`;mv9hGxG!=^2LdZ;8cubU~ z(0g(bktmG<9gIJdkaQCVhT#&Ua}o{6WrDWZEko`)xIP_B{6-z%{N?XWC_w4=bhaQKQqKe0A;b$bWBEzGf z{({HvMb&QA42Ou4^=l<84FSAHvX2^s+ZLWma`Kb?c>=TH$sVgIJwKG>Pj1Q<#&N#f zXAJ9zLI>I(C-(f{MQ$O&DzU~j5NV~-@CdYZ(^QdP6m>_;&)d>jd;AU#7;V*pX^r*x z+6A+-;4X2q^dbhbo`vlY6ddB>!M<`_i3!3ZiAL#8(J(QS8HGI-a@N4X2K?}bPseU~ zn%8{9738WEb|_4ycs01r71267vS)<=SQ`=2$E;Sn?22co#>E3J;X&pDu%*hKMy7DE z2j*;Fs*RhE!hRFH2Zu<5=p*I8w;+|{kNWRtXYo8g_T)~Xgo~%ZDTi3Lw-M?@m{Np` zPeoAWK*iz?nKIDvuoH6z#)8rdwzOxAec;mwq`{2a&fb*QEriLupJFmQYG(z_XxROo zo|qMo!zt{hzJnIY1QtWmK_evKO{exWohJ`-d(_c^p2pJ3#9UL~uFSX;Guwd*_n@^l zg-#ekS|?zjtn?Lt6()IYla(*az)(Ge3dF!+#<Abj=18!ISAT)87Fd^3vKG zohv_$^Zy&hN5J!sz{B$M(tAoFX;oJn#e||8lE_$$x6HnEh33ea#z#IT>m$qa!l8C7 zm{Zi=gYL0hil-$99e%Evnd1FJPVI*>Sj7b8Uz$oDG_DhK><{N|79?MW$vHV;1X*}F z&6EyGee?Y7n5yDMLxPK5&aZFjrydYfP+OP1RGi?Yv~dSZ>8ZG-Lb_wBl_8@tm0n6L zUVGEa%y6rgx!S@$NQ}KODRX_O$^FSCgxLHs^9q#BU#hxb~NqJag`Uw-gosTto ze%bOaw=`X7e%gLRG;cVDwEwq#?p{?ji=&0%A1el%2G+2`Lc5oS+hitTuXZ&mH8axl zjlzBNA18eyJs(lNWltvi3!a-V;ZZh?W*tWBGiq7_UBR{um;HQQR)VJP-o<=YgTHnk zFZ4=g;68zj5a6a?P3NwR`M>HYe}^K-s*=I31lRqFLGAzm69V=4ioR>c&_ua|S2qiQ z%61e}>7Wu$J=M7a)uPk~6ruYx$!uzN_+Hea`R1QapW=$ZMNr|u0A8Iv0cejVPEJkf z^jT#)kt{+>StPQoeT~;s`^R)gxp31E3@(M^xn)WngRs0d7}4@_2=BKY?{q9|%(TmJ z+8djQGR4B`>(Cb#8rD>B-|ty^ps)j6y6F*Cp5cF^HlL(a8zMgie-rSc38eOFPngwE zSC^(x(AOsw{q{*3#1jQp2PJ^>ug7rOobY@h#hbW<>G87w&&DE6dB_HV~R9{vvhpuZ6N z5jd2MlNkmCJv>-b-57U+{eyt;QOF7MFBAd@(}n97W!Jv*-^Pjk&R~_h1^)iwKX3L% zf0HHu_p5_3-G6X){`LyHd>>rB&yi9&HEGAsFr&}$x9_KaQ5Ha-v&0j{6WvFvA~S0E zTIAj?#}qyx|8r*+U)Mj$%>T4vj@ z8t`6$>Xx$6e)xCnI{!OeSY{n|?ZyVHj6FO7FBrG~p~qhYp1;#BIjR0(=adcuXnZ@! ziTW=|^%NlR8R7qS3nF~1k)n1VdPOxMTbIQbpMME(L{pb_hbs1;AnTi;Dkm4)5`=)i z*9Wpc{@cMnL1|g7cI?I=Nac<)0eoE6XMjHXH>4dwerVUw9I|3WuE*^5aVB9JJ1 zzFK*WHG0@Y(KR^g_I<&CkaD<8gXknM=$BCds1P1bh=R(Skmx&Qm+*R}H+hBRe7?3j ztF!$ygAT?1F_%N6`_XMrei6TwhCd>OFPE3N&10d|d`AS%=4+SK`<1;OgyOFI&8-y@ z$Xu%NRq(npRjzG{|2l(%-;Jl`ztaTq zjz6s`aj)uPk7}T%<3jMUMIZSeAN=5&*ev$3*%%f|7vJ2WXvXmBHdt4w-!6_!c7@SFWUl=Q~LN0P%IQu`vqyTa4x!xNI|N zPo=3>IGfl>4=nwCeW+!MMbzbu&~hm|lHd)9E*=Zh$`FNh7)$lK%me9&PnPO+H4cAL z3k5rORu5l&7pK+{C$X$hrv4}O>5>PQtH2xtnJ#n-5=mu6n< zh5=|TP8Mf-UEFxHKlA+gKU5$02CeKSeUo!tn0cIIjZpoLxTX#h73Ey4)zXxu7q&FN_^Q?yy$*ChvXyP;$)c}J zZGjp=9a5jacE2n{si*SsEG=7ul#Nw*Hlw7ZG;XrZ&8oegQ4rKrlKuAPL!`kI`+r6x z_jg3o?zwGX=FnUmH3cts;aLnRon;3x}Ni{=*^K1=I7M31s|qlnJcw`0cf?lRV8 zAhKu`HiK`#0xA)wH0(`Zd9=&{;KaRapVvVqD$Z+VO+PGfx|D9orH$p;A7#!w?Zj}brYUXUAi1R7ZfPD7QdVJjLaV2v?*(|#zv)a-^ zMdzB-mY^c2fSYC^x1C5M?{OgEJzo1omeQjPNHvk5g! zz(P=8$O)pGZQVIiH867?&AjeOMa2mUdCsT5uEkg+k?GgVvdEBmmu$Krl<~#MvxN^Wohjd$5>oAs zu1m&0sy!c+SeS|xrkP+o-u02>rovOU`Bj6m8|dith2hTAW&Q9k0Vc8!E`k+EeY?SQ z)t&S5~ty@I6ke4`Ym20r$jgIeealY>Qd_SSNMoN49O|Xl# z?(U?&R%U5bsT!{Uk#0kBK5i$e$FF@Ux_6&&bSw@Xm@%pIAg)OweaiG>caFLt)`g7h zV5PCHP-RYP#-=d*6oC~(v`AR*Xr$F*fJ%^ZY`*z9hJD;xYva*ZyG6FI(W`Nw4}8&| z>+m96J>z`Fk`MXw_fhd^7UH)2A*&e@^CeLFBdTj&)2oBW+GN7xQPO2XSv`aNr$VH? z`22X5`Zm7k9G9qN%!PoKY~q(}{@dCr;%b_kdNrn|V`AeK%$wzxnc}_Y_Ud}E1!{Tv zv3dTtyc-6oFG*6eBeAK}fqT)3>w6Shj1WQx3V5ZUyjdbZQbbVE=P&NcUwQf(XF8Tf zII%8B_GwesNL*FphXBC`w$L~{!hR=$u&f8lA0J$;uWJb#8H&HH)>`h; z))dUMVmJ3~wjgOfu$6d8_@M}St+OkD_Sb&}RP7Kw_1f=y;mN0QZn@ZftlgJ#=jVMI zK-cYZ+(fN$-_*>-w-G@n)cI_%5ez7UM-#)((EMi&NT?o~BU8bS9;p9}AtOOnj8(=L@~8E1Zd zU7f4rNh0#z7|H1~SjX7=^KPq>j{RAB|Cf2fHr2&v_10&#+hxjQ_QPZPulTj z1*|y^q7B*wioN$IZ}Bf61hrKOD_0kHsG8MS$0LQw?uEA=H?d1+3*|l2w@(Uaa6BU> zMzPE!tRD|>>3vZjB5*H=O&Iyj^l z7JLTX!aoG)Xxg6p&rY5%aZsmLru1QHYIyop7fk^s`ve? zB15*Y8Q$=Dn3;Ur6E7myQt;o<+Yd=Bc883>I&!0#O(bh~lPNZqB0@KN#g>vCKEIkTz3ZPN8E zB4S!X36JB-vM?-=T%=E!lQ-^usq<*20`>gdmR8gBbkaB`RR@%;(CXav9T!=o#P_z2 z)g3YYInM)aUL%PwU6U^-IZ&0Fl=<0@i{jVSRw>HR-lP2Jd(@t9_JS*StTcEhr+WPA zKiELXQf{&G3JxvT(ME{14!5_FZm?jwn;|ovj%+T4!$z&>H4gKUX;{Tp#_nc)!~E`z zw$uIZpe;f+zAnq*3FLISvL5+!a0{7W#Bkk^%qZ)Ge19KpC*kZNCwKV${K=o$L(O)e zIM}@34L@Skc1Pou)VsIx&qE0x88VJtcI4@wLC(hPc4}-!|CSb#BQee(?io$`;;#x- zd6qb@N|kParY62eUeP^kkWZ+?miy~Pa87RfOR#$@$9rB5rH5qtA`?}&nMT~fHl!^P z&@o$bpy2i?-@o@yZBlH^_j+lu`tTRl4*kJjjcp@kv3-=x{^jtv2)33AYF&(e>@qXI z6UW+dyPT@4aP%|j~erkD^Y?z#G* z;LpuR3limVkLgj4W0lXO{F)~%{7QMq<^qQL=$+K|OylCoKdP<&gym3%c%;PzkmgF{+FRAt=E39;eoKOB0YA6Xe(hfHpa2C(E+E)E|ROen-(cs93*U)I-NfeSMi(LpO1tWi&;Y~DUX9%tY% zvyjX2p{MZOIlw7MfMpT`^n;n&UiZ`uRqBZ5Ik-IsW{zFUvNY-u$3Y*NyfTfkpx0&BsuhyXgMf zwYWz?+TZJ8y^x}UTue{h)~swd^SWAFsvlo|ShLT>UyZ-%@VdoWp?kSEKufDO8m=O1 zrDfUV?z7Exbwv2Qx-MhHy`ngiV(qT52?{kJY@^Q^PCd#I?v!NLRu2T3cBID6C+<=d zn07$9+#Ei^iop-Rs!W59wRni4vM?U^`cYTGdzFFI3o_Gkt5Zu>IctXTZDw^|%5DBZ z7$ClR1_c;7YI>hmmmB;<0)hs2(mvT;tWvKHb4LXp9QjBzxZygPZg5EtR2V9UxLjWy z5v?hPpzT^nn&f|-qb8t zb3dMimw&e&z^?K+g9NO5rf|3YM_FEFdJ1t(cAKa4{d(&huZsJ9t_qplqYORi7Z-)+ zol`fZF;;gs#N!harf_(ZDEX>UvS1MY$7V3o*VVOk8TD&Ozdz6#=JjHl0q@tR!#?hB zc6kmXRcEPd-K(|wKaklyZ(UwxG!8V?v#K93io^Skh3ef(Wp&w7#t#tjm$4Tz@HQll z#?oK_koR7p-rdO3a_w7-_&0SnFZG~2j~a*D#p^j`ugKStv|n=}?I&+X()&|?cs~vO`SazNV+ZHb>np4oCgAN-UP6rw03UGlTeB{i zv!1GP5vo3WFvFhlXce#@nb08raGSJgql=ujmtBV3a{DYSyA*NYYJuZ0Pj6$cxs06a zJ7DO2dBn*Jc$5DyLDU_stl3djhfqb+HB24_S~Iq3qlRxXmkh$E!v8}b(MX(&zcppQ8~qdP%<(v zp7ay$Lc`wHy3aDhQ!*0XA)GK9&e`Lyem$ic=JW|^wAr1noMYZ@9FuM#U!-QNIJDfP z=XoT)HRpGj;}7m0mK{O8@<_NN3ms(n{@1?uv8l6PKS#`MWNqwh7RxslX6T*6GrHz{ z{wcK{)3~zY;52297C{PE_Wq}k4syoQ)YMf5!`!noEDR^-nm%RoY2U$Sce2I^rKzln zqLM!^f91{n*YLZA9Jp3WcdDoQ z=(&UsGZ^e`6sB@fXs`LX7k#!c975Bp=Qy1yPtcdOXaX_LFFu6-?3+~qB@K&M+za=Y0H@2V7+J$&$O^jJB$mJ4^| zbuU`}-5G2)^|Xeqfs16NOh%~VDeIzIKI9wvS($#?OYlfT5-1v zUR*Bw$pn%X3==A4j_C(`Ti+du@)}!hFDs9d+aLuG_KJ#l?DMOaxUDRhlFnXBJ{>Hy zXOxac-Bo~O#Mw`?J}Ip6j7H-V7az~aa665PfbQLS>bk@F^}G?-;k`R#*&1Dk7rIXH zWR1628YAx@{ru*qz_NY5k96}%XN~}^kI9*BvK<_Kexdquy7?^Y+^5o1f93o7ZoIbe z>`V3jtde=n=7CDvy|w)cywUt)kF=N&-g!*7GCG3SA0PHt&ZP3KM(XY3Y+pr7+|2wo zE;WkKRaDa*k^IuUtyc1lx2`Nybq`5uz)LG%^Jd+@4tvu~211tQ3M}b-?8(3~eItIm zAqR#4u=U`7A9!OMT4D-c)S7zq@!WHTunV??_M2G8{~tr{ zK|nt*IC6==99H$-BoGx%GNP5#y4BO9=2lbra(9nZMq30O(Hw~fH?e2KQ(2yEr)Ja;`+Q|6!gF(N|Qj+L}j z%`Quv;+YpPe80<6k9(%3^9>vTN$`<*oU?AsQV$rps87{;vadO8M-JhE=ue1@th?-c z%kV85kt+yF0eMn2jn^5}pQB2B@1`Q*^-Hq~+gO7oe~$-Ly-7@C6otFur==MeU~E-A9&pT11QlRIBv^2F zcemh91Xw({ySuwA?(XjHzBunNZ;4?h?YN_3c9~wY==ZuVBe((8 z)`i`yDKm~zaH6G(hF8GOy2Yu-D{!bQ8U{*^crN;^j5>oU+Sx>TUFVpI0yKF zABm7P#6#H4VxhSo=HFd3V==sX6fEpJ$!C6fhm0XNmK@I*b3BlAR=9lRamQU>c@bQ9 zT&OW_VP%_!AS9X8Xf{4Iq&jKNouOC-6$CPuIp(vFv>%l29i_ZBWGb|kRU=P!){{B*)^7Wdq1z6tnWlqU-5`Tq* zPZpN3$%I$ErbVj#?QT)ObfNQNmnF-5Gc+sevdL+ljYmL-lsf{;q36@jpTEylT2@-w zw=g0M1Rf!r4PO=PlI%4K2_FsaP@<22v+IR;;i=aJBEULEQ|L=y==dA{aA8`6p-1fp z)AS9W9tNcm!T5(Bj0|d7Nt17ARy5$d#oqMY$!wV*+7MU784YGVa8VO46JTknfX^7q z9ieNt%uOY|8jJY$8(pMAoV_eZO_@`5ABt3;i?0|WfsQcLr4ZWfs-A;ZxXF!Tn|~`p z6RfH?zIFJ2&=WW^*kRx0V0T&h0g=~64AOevG#GFsTRcuJRfIOe!k`p^Oq4=?#MlN} zdB4X#G1GHiW&Ay>rbID1O&rUjV$?ktPK2(t&FSN>$0M2rk)TF;f^aeTC9mV8)uW@u zAxY7P`~E6ZT&oG3|Myc`0C?H1!!R>9S3Y~N)c~XgwaQuQ;)S% z0BA~7pA2kJ=?{fVQ=M&KqZMA9JxuAbeFtctv|#tlXK#2J?{u}s`Aa%9&~DdnD+m2J z3I*0_d`oiii9cr%yPW}KbF$JJLJ2#RF6=iT{#Lh$^O4JL%i{h=jhb5gbaqj@zW=|6 zJE4UOi~Rn-K4>7IQr#S`9E%k2xlnKE?z$*e9SIK3mT6^_Fiwv{`%$l89?s)^pb(t2umX?`spKuCLhmfRfG@ejrZUNKb+o~@Vr z)6k{g-#`EBuPRMsoVZ)6aE=D*dKeEdY;E_G;)4DazpU-)gaK_M!+oWcV`KAmdKa+m zQK8@MClzkn#bxnsOpFKAvP?YbuZ$=!dEXtWvj*j7_|ecdcoD7GeDD5ELN~i?Xj`|i zu}OXOzRrT6y6j-xnCsOJ^MKFZ88P&c!nE5K0d0{V=iifQ*PVZNwA1`Gg7&S8ZqKZkcC_Xd&~h1j~7V~C*a#@)ZuZto-jvfIz_;mkU>>Ry*8O+8bv^Q z*GCFTkciwf4GJ^8fUXJkJM_kRT07#Xj}T1?EsfTn$ff>D*1d`e0JgK>8ASk2{p^Vo z7hEOf)0%AKNKm}`*2rF@YIAbCGtY5Lq%}qd-CJu*J88e1ff8Qe6_ z5le9q=JK>?36k`~#pT!?%q2(Saol-K2Ll#Oho4{`a>9oDZx_2J3987xyo4z?V=z7Ul5vbtF#wQgMdJ>8Oif4vb8&3?TfbV+&M%9GRjc=homRPcJ}Q^#x{d99 zp&dL&K8InVwg~)3T`x(VpR{z*)a)f{G$*hPRNcvq2hxF1PE$4nK!1FpRNLE)E-i-= z-!xU~c<7y+geXM)TlRu6kAJR%&7Q`Q}(81h`S7jCOZZHa1n5 z-MNEo=vum>Aw0L9!uqI1mfPw?LFnZ=Ms_`?&P^7VnpfhClzQmc214gv7px}6MVZI`5?>u8%@EYo~4*!Yx z3{)$eok<(N#tWH>JmR+Iy}fxsDH_Iu%VoSv&fFc*?GoNGA@}_X$VTxLxP&d}N1C^>bpkube3}y0O}C+g)EPjgl>!( zZu}-(WT5eY??QDmFJaUe`aHgnoSPqWdBigp_LyUxfOTF?isdXb@2hX%AF)3WKss-2 zpY;jRP{V=LB1i*ccPMeYFeP3FYv)+a-wwLjA9XpJf6Hj34A>^nHjwe8c&$4N@@Q z-4M4-Tv((k{YA_x$@m|eqm28NKm@vA)lyX#7ZsZedrIi%hz1_MP0**GU)ZFh)z|TP z_!-&UGf$_3u3bdUFhL_Pjzu~wD3}9&v_GS$ry!-0%QgZ@#AnW)j|Wu!@|y@Pk-Yn; zJT2MuTKGoPegGjW`5iUkGStBI$Gye$tlOB9`MX9QVX?;EBlX>82RV)*X%objtw4dc zk90LG=1d!&;hC2Z2r3R^_p9(FY1ssz?8xa1xqt$sVIBTv$ICm@cy+PgZ}jp~;1yz` zP2q4I`~%=jWb&xsB;qkxQ6p1Ip4%s62rP-pCvLp#(67UZIz!9xrBP;HJP5O!&qQz# zv-tA;`-OU97?1y&D12W9VR(tVH%pgw&9lUS7XDWW@w|JiTpuJNZQ! zl%5!+PYxAi0)S@$JZT<2D1{$7_&c+`iu~v*EF|B6K*C#G zn>V;w~*%UXDPWRl1Yah$e}fbB=CwXmMh-KKOINpOg#s%$2m@G9ipf3#^8HOgn(S7 ztvsivqlAv8*6i<))jk7xv9h`hUOC8q z*-{B<{PkVVjR#ZSefpV&xD86Lk6+rQ}j_LNi;1jwLQEo1~t`cCET? zL9^~u0jnM>q$=nLhGPmr~#OhG4@5S*uTUp8skX$oE723+DKQ4u`HZ|bKck}jHo z`Q+s0G@J8xy6ja&qT1R>t?wvh@jhjYjevBk2%B{OP&1ptu-$ne*oUp8}NkwTx7_MLXh zEy#H zlfZxL_elG(bT17WjW?2l4!8G%HX!)y0|;WAuHQ5-MI!L|`wxUKG#(+(-aO}Jf90*Th2$d~b% zG(HVUoS4hKL7C3PuNUfpp;MU8K?t0+qTP!Wb%C`ZOdwV6e>z02P%Vm$8`>(;g+_IGu=%$Jz48frZPJdz0{R0N1374 zS1`vl%}gS**M)wD2)a#tD<+&nTiFLVkktk@>J4`k3g|%eBokvUMz7CtYsaYb%vwW2 z3xgP(A(JdC@beQ*+9x|4OJ;WM)aQY+p15zyr20=I5u^h`9EK(OiV@gA>bMPADv`9q zG%^&E_zqg}^|?Wh+Pa1QaG*ARM&{Px?1whnl`t9i@o1!HEza`$*;jP2uC?qlVV2UR z`*~QBXyevymQI5=ta>La@YXLi)6<-`U~BvT97O)^+^o}aH5K{zT!ebR+fZtVkD zf<+R3^8i-7Ma+Rdd~61WQ*gpD59Ji=J;EDq%qAN!=J^KwN>sF>7Pt@92a%v zpt`B^l*$a52KOqx&PcXw%32bbd~*D+yy@{ppV>J(9evI|l*z{}#S|4@E*G(+7b;6J zE_Q1VO~sCjpysYyKq;~W3i}^EUYor&SZp6AV>_0nx|Na}&sZXjG zP8SXErfK8MFO8{yz>(%wd5md1Y|Eyfm9g!qmD_Fe7b*H+NEE=>-|>EW^(UxhKx5?S zW_5pJl3NNM6dP&{@knwpZVmG1%MfYzS$AV|uhYBqGKT|6_2{n`#x(Q0jhP&OmN-m% z%JQO$m}Y6)i*;K48Ee|C%iPU;X&i!6q*k`kpvHqFb8OFT*N6Ka-q0uE!#pw`?#6g` z2TsBHIm>BaEur{Xr+%G9)sUdGKk4&atEG|NV8-t^grZ7qq)ZM1l_%Nazl#Q)Nk1_s zmryI3e2*0V_3C>zg|~iwB5IvZj#@(2i}!m3xbH9eBu!t~H!ERMkvKGno4(I#=6in9 z1}4j*bTaCYnB*tVU2f7eT@ezYL9hvd5LL)l^OKtK5lWbTt1*dH7g?MW{g91xK22#T z4<8`XI{H(sK1a|-x@hK-p{Z)1Q&dP!C3kCZ0MgvyAz`wyP7v`rQ!*ttj6<=2%I&%{ zjMnuC&$ADm@C}AGfW26(6!8EI81K zyN3vR-%pfP$}kzhr&A2DdTZ&;n6aSq=Rb=btnY(Fv#>_9-C#rKS*Vi~QsTZZ$zROx zt@u4<+-9;E)$(cSW>?~DGG`O5&F)IH*YMofV&d(X1V$(%&W$jWU6=0MV%w2X;bnL~Y2L8CAm_Y3p>;??D z7M7N?7S%pJojKO?4f5YPW!U_J+{7#9@a=z{X?QHwzx4YVt zU1sC^6r`4&46A?lant;Ueir+}P4`1J-`1NQi!$5XW2cCxj1hF9yu-WBq~eYp1i5U^0#9jU;WQn)J3T-ZU!Dl_0^w1=)eQF z1^>56LMbDF%`Ij4$5!C4(+(lU?NDMbWK6#fTvo!tcPK9!J4(w@Aq1HSMnCEs(jS#Z zm3oh}XR-|nuH@a?VMbv5n&WPPZMZfXf39E;Qzd$?JSx5F}u2hLkUFa|8x|PS`G#64%rAXd0AYJ zQUo_3o#m$TuRVaUrhik7pSeoKI}E+br_&Ys+wswVvu);U$>KJ(LvWin&l8c>*ALLh zgF@%eT%;rvMSM50wcMhf%&VMjg#w4#iHzxLJ0WB1IrLy+wx=k1ItT zQ4Wi;QGvmPh#zT(Dd!Z`$~`3IHmsLuX%{OVE=l^Z-es+^+xNvb2SC2=%kt}T|5tlp z-EG`vZ0ruZmY3pkmH9gPrDg-2^ZBKSAI~?Tgad|+-n<9(hO+=G+v7)b)w2^KHK#CF z*&-415W=FeJn$M0x5GCTKP1QFpT4fmVRqIWx@zQESHK&UY zgUc zo1%?p8foS}ZjO+jwzx6tPCgq8r>|H};#D~=zDS8lIx7XQ@vapS-jtv!#RvP^wkr9L zJxO!A_0>SR_2v5V6T>yR1FC7niOfn4u8#O`==UY&#l^(M9XBl9jk{Wg8kzr0HXtoD z7JoBx8EJuw(OO;o46nJgQ+6Nh!#ZYlGA`d1AEbY<6iUn^B9zQ$s)B;e~HdmVutlvCHdvdYRf&$L41;?RV3 z+nV=abUO?$kJ8pv_Ey@9*Hnf0aP>Zzcw5=^5|_ip>PxK(bdWv|13iyv+ln?%PJN6D z@%VcLG3?3l;Y`vk(yg8B-n8$NP$XQ1oDtfQZT^mSTpWRT$MGsw+n7I`O3 zDh|=y-CWjQyyA7Wck(C@;a82VQpbA8#fu+1ASC*&aZKB%0KIhEP5n9v#evrCbpAS({>XDFc*c(gu^(}g7gK7iT_^-Vp%3%&{^EfI zHJ9l{-<%3%s!X-+iE%OqRIf{@!md1FPT0p*nwMbKsAUV)7{2Y(WxnKV!Fz(I;8q`KtvIFMRX6LnTa^`<({5j+I0`vsdu!eOyH{YVL^X!X?T4&6rAE_zMFv<7Htv{~ ze^ppUg{p6RFz9~IE}hv}lV@TbT)98`q%1$i^vF`mYAy9o@gR1}8ujd~+XTAi%Gm!F7_w)#q^Hi2n1}%`2rpBFmq&pin%A;>mmOHVPbJ{Y@v(PXLR3 zFx0ZjkxCNW08H-DaB)So|Azs$9?n_b`rv5a9yL50nhF4jWTM}zg8 z6pQ;vy8p=yBGCM|aF7ks|DPn3|1adK!OI?+hK2?=XXggC1H z&c2snn^b8w8SrQR*K+DJd|?Fy$^>R9yGe(>tLR8~Of0Nk)s9R1tpPkPfQ_we1vR{! z1n#vO;waiImN&XNn62m(>I{AD@@w3Gza7Bbm{pAdAVbhJ5V*r(_qEXf6xe%7kk%%r z=x}u~>zPK9D63%(5ZE^-r;_~q{F0I!cHo)fyxZH`{Cp}WCs=f-@8?`#jBPNxOpEJ_ z^@*+T?d2sTE<9ikcDssVEv1o{IpAdcyaqE***sq{%Oms=8b8WU!*Nq1 zO|$IwARRFFG|PG{wO4zZmqm=Ny|29bYL=f0KUlb6GMUpZyRp%If2xQr;SlID``3WD zsfbd~f-mX!ny}vKxOmcx6<@MIsff;P^vmCd+jW^LuecgVODFT(^ih{Q942+H6DK&| zUhMW49xl0^HRiP*W|Dc?wh@oj5Gh5uA@Ve>RP}lw@;q29MEAx;O+R$(G&m>D4P~P zeRrV{d#pb6^0ZwloE42xtr~`)d%We0f|?BdDDo2$oH(|VPgXw-tdhU|g-ZO*dJAJP za+1LKd4e*19wA5Tj_lc$wFoUQufbMfj-Ss>RZKj*P)a5=Jn=G^Ij7)S()pqHy49mm zD$+fg@2+kjTpkR*O{<77?zlS6{yLdV8&@)xwq6h z!ZGY%x(Wd_qiu%t4xa1flou;ZNqzK5@N)?-)pT8R=3BA9`icEdqzR8X; zqIpbLPVUveDm;9&d^Y)=(xcu1`s69pjD;T7oI$2=I9u`5L3a*&FfNjCEA&%{vKZt) zG)5sfAJd*V)(>|A1pS8A@ip;k>@7DQj@02&)#0N7Lg6ae=^iXD}NykHe`<7Uj5@ip9^WK^pQpJp0`$#Igqp5HzMOT(t zH~;R98!vqQ-+lCKmQ@bN{He#W#>N(l7k7(;T9dXmd&}CyqpD`j^pvso&a9L- z*)d~|anQr2bTNeqX(_pN7H?QhHw!K))2aW=T!96lzf?=mVM^#~+=foEY2CIA|CCNHEGj9Xr>ejI-M+LG00vi)(*zp_S109il7a)A%(s~@ z-J*HW&$CrK+*Ib&c?D^u@SYM#-8zrUrXz?W@sC4L3uwK}EX30CQ_fnW<2)W*uVgaK zbTkT6l26PaNVDu%NZ> zEhmwFlSN)`RHyoi^**e#4Q|tqgg7*2PCAogl2K!KyPrgw?M#Ku)DLUCO8o< zKV+N_y^)TC0FUgil76e*DL57qbcDhNuE~>K&gG4xs~wNM2J#^)`GxtAu{nW-hNa~} z^$xd{lyfo11BzJ=ZWZ+11x0P`az>bU{srFowjn`{wf5yf?&IQHPKPihwu7d}mMO`n z$Kl;O%q6vItA50B*FXqZtfDF^1@ZEYEcDTN(MnM;#Oj6vDkivGQL?mH7WVWu>^~-N zBj50fwZA(*l+}~NWW~D+O+X+B)v$lD;qG;y|6{jbfm*F$)j>kvEu1HuE__4pQPYqy zeA}%PmMrXXn?FC}CZ~v1QfqtF^vSDpUfs+LziT6x-^=+m+9;%FG?<>hrg5OIbg5pw zD9?43-IlvM6z^8%X@$?V-5bf5fu9uU@O|FGv}xLfTxEW{+J_AgFc8A zxH{Sq2%nMf8c+j-!u*rG6Xq0iRUaQ$TDuJUH#Zvy=!%q}Lr&4B>wRBVKK_X(ri3zn=FR^0jk{}%l;;ePs_ zaGsryAX~%Y?$XW4OZmgt=aMRm2k_JW+!7{PXYft|=5e#Wkpv;8Le;&07`%Xh2rV7D z?9uUfgD$t&I=+?AG89QLZq(xrG<# zx{Rx)aU0R9DoFDKBUW!==Z#&0xnDhTMzRs3*=6seHmeF;8Ds6Wt?drB%#o;lUC!e!4JYa9yZ+5y?dpb;WTU%Q$+}PBV zWhOn!kHf=vmAC0e-*`I}lk(M_-pk3Oo6ooS+k3AYJE-){m!^p;sI$Xa;y}?|mh{F- zN)2DBVK1<40Y$I69?Sy@vkx^M$wdl9g=$wAuMn;xYeNnVj!&AbePCB82xxgNw=;a|DssUi(8M zaH#M%EikD$!1iy(Yt9xbL}g{so^+=Y+uBNow7D!)2HGar1viqQbS{@w)4TQ>YQ4pT z_Rs!#&gNW3QBg1;Fpwa*j*GS5N84jFW19YEpF5O~D|1yeSa=-jH6su88cS9x=5oDw zfH8Cax9MV2(7?4$W4&;_D|KG{USK61N<%}xY)LG7^BvK%cr&YFtubF-&9~TDX$1nx zMw_w38^sw$P+CtVW;W6{XO@h98OO-hDw0g%b&Gxkbluu1wJNiJSjv2dDUO1+Ha!@K z%Vgy&Y==c~d$X<1_v9`fF}k$^g4~D{PQ)H7Pp>_MGwExk}UX#m5Jj!@iMMwl;Lc^WjTDK>@q_eXnmQKd1eU zjF1p1Iqvk_ETA7Ha@t)Tr~*|0F)|PNz5p$H>PL?mG~)KSwFd;Yg@pw*B3tx)puUAf z+j#Lm!{$O;O@RKW1EgauUT%91_PMUspMF$S)cn`oe%OINm_dP*vsLRkUd}^6>40oa zPft&$0F+#O=^$=tS(K2Vj!duipP+p>rl5!wwzjtBYs@m8yO`ZbFX5tX^JpW$$9$Dy z$iu@^tMsR_e|juy)I=(U&!d~rsb|xw=Hp*sj&r?|dPNv#{R(KtEFkv-EvW~HWrgeH z$^H@k7-78O%}E$S@2^1_0%8=#bFpaD|h|CWn4gU6w@v;Z{H_aB8J zgZ_Wy`~Rozwa#WAOmz>u*2(z)QYx_ikJrz>pGRe(PVU=tB}wHFCzzU+_k=Ufm3_?_ ze=)?ws+zm?5rGR^s9Cq!Y>-kqu=-e@I8M#AE($GPE~ncUaqrT#$!qy!2Z@q1I-oKv zgiWMbuUWKVK`C}&cyx1JNv%W&e##Qhu1grjiyr-L%;$6Vo~u<0=RcPmM&lJ-Bv^O5$;_ zHZm%kDG<=wE_GCOJ$?2r`E9zyJR`_f2kz7$}v9(TEive3HF*8MYa0gLP-8Va*_U3o@!bq{5FFBumgpgg^si7@8z z@<8gs{9Gk*q1`K{f;J=|%t8$vswOCrzQ<&QF0V8I;*jH#B6#^tE9@=KTb5!Q%3Y^`h)?yf*gg?~W-B8WfE?Mx?<0mn? z+DLCYYufyl>MQOf)t%Z1W1dpVO&2@?T+gidnh+bD#3X8h)1wmx^RGE7DzIM6<9pqy zBcIjuzuac89@NlrT)>$;H2%!Tp1O!Vju0_PL|x4;h>A2eMTGWppUfdq7Eoggz!fMO z-fSEl+P!oNLKEdlMg9a!O9vZ}U$_L;B;AwwOfJYLtJcJ)oLYn{j^Y zSmHS>bs^5|<_NH*vbsBW*tq);lI(S|Ybd^!l=tZUVjiqj=5Hcc7e2mvejQ#SJn0Xi zP}?T6j4rpPqO-}!pDs8!I7}%Y6|s0flN(l< zKDleJ`Dc2$3Gt^>9y-+1acSZtU7fKHJWhB3@hPIcg#2)Sz6%@T+g33Eo9QO6FEJHO zJvtOd14B&1nm{O!NcnlSmHnfX*-Mp3sru?fA)tE+-UHnW>3V~4kSot8w?Si5hRq?o zFOOo2`QdH%*kMRk)r%J)VHA}J`X=iIkbuUZ4a%}rv>D?VX5kP?301@Dx%h*Ta+)c= zte~fhcjl%>Yc?5Bt>dRp8xv7D#T<0%Y(t!%gZbQ*LN*IFPUyIvb~!_kqdiPV|1`yO zEM8ptx!r8FDpNO?5n7O@dok(WT;5QgSr=>kTB)v~<_1B`JO4JlK4(V_1Aecj626nP znGPRLtUWXwOFdJ^y^EQzZpe4OBGOVrJ-o`S7AXNax?}tflbLqRR~j}-O@kqvUKbh6 zG;9Sfif5UttKT18J?Ql>IyMGM@TG}n+!6Tq`)qINM)Nj9G~<$4fd%AYgGP~#^JmJS zTrHE~-p*Hj`X;RsnAdwIN3m~4TI2pCSzycB$Ax}syD_7XK=&5y5*V601>0%x0n^qw z;ExpD(&nr8L(al&lFty=Ce!x&{`#G3m8OS%S7Y7pcBZFN-P4Uw#@tg>VI{k(whmi? z;P1_@O(k9{0;3VhlKr#O7X{Ns@#o&W-ViD3s*Oe_IvyFI3KZfvz4{di@5yXVG|p}J z*FlohMSY2>(+>VpcEaeK%)t=yYW zo>9Z%<2}M|!tIQ>o@MQHV(opF@2Xe_6Q4}q-4YNJu^X4=^%l9&?nXpk6ftWt(Izt= z>1IGnmY~+o2LmkQpR4rB^t*(f;y7thV=b}A%Qr-UmwyCS?$pyTTYqbVxM6OUedRdH zalH?BH~W`dNq^|T`J#iID@9o+thWk*(r#gL75cf_q!b@J zRA1^B^u+U;4xQP-6`AERSP!vS4-MQiFg_$@Le52`IAU2p&{5CxMct%*NY>=iT%r2l zBz2$iEpb}vT)FXG)_mCZ8A)}nWj&-v#;`&Z%i#`(Y(J92t=_{;>pOFl`fugjuGwz* zGVBekd@wlPJGUng+p-`FFJf(LgJavQCNiE$o5`{-c-JKjxgvnof8EF^niD4vvH#U{ zG?rD51}~?_EV68U$~WJADkbSqQPs)c6-5*azm;k^bKEz#HckqZyA66pJ>AXbs_k%C z%(7+`OxcPC2V!e=c99wRwjT&AXeInTz>^A#xn5$D2?6r#G@<9Kc7QYFq!dO$@~xj> zw*4W^J5r`knQf8BwKX&!?@wQ<3wQAq%+S2>F~ZY(TXwXtc|EH>{W}V~%t}1zwj+TP zanf#7##GnQRQIY6tBl^52A5!jhb&ha`H_juFp4+Ma+W@+-X)2>>}L^;{@g^6oT6Yy z|L;bMDj^ymV${byIJXO#-`ihx`@^2e$8j8GFw0U!X$iN+5%Z@)heZE zEa2lAU*);g7ixr$WsDX?z?+kXD5x+gr(3NMdtR<=od`8jbq7VzKLdRlWU^u8Ywfba z7b$J1&=BlM+@|@3gv@^J<2!fmZuU7@6$@QFY?iUBER^JeNCc#IJ(h`wnm2z1*LCvp zXUT`(H5=JoPi&L!S}hiX4#K_QF;5$b*l(Vx{0?fqyd?Rk(rR@SLn0r>mfo;(u~nxq z!*;O#TsdCkLacDOo1?1-$(hDHA5yV5HfNa|+3d7*1s=}oPCrewy%X^du%1|H2|T(Q z{8blHSuRY#@Se?6PeXyeh?IWbfYW+$EmCx-vYd{A%1Y1uz#CK~++jqk!Q{|&_6q&< zfYo_oXXd50G5w8jM@UF8A>NT!-6{2irD<X>|cN>L+om@LvLFmlPXqm0R4>~T067qcZFJYg3K|zZ< zNtckfHB}>(T4`pe`t@FUm3_pW9s4i>zNn-CeY- zlokM?+uQZ&?GkryR$pRU&TVaye-)^h#~Al&G8QgC<<+bHVawgGt&VQAL7daJ)~V74 z8=a0t0486yEn`%usnKY+10*D9;U6cTjycWN;9ODc#oexjaKy~y(LP_s6%6^>*(W;6 zTtUlbKC~9pR@U3q(=qY2H!fL!E++t@7bLCyc75j4q2W*|9hXBpw=G`)M8!|i&1Q@$ za0XGgQgp&+uH+Yawx3WY3PY5tkl-$-k>~A(coe$2QdZ{NcptvzgkAqw4qg>lPCc5_ zPGc?p75Zf3M^0KtJG}~;M!l~pb!40x)Zo3$32ihw`x6IS1a4|An9Kf~p71I*|sN&`YW8_%I3$FpxTc#HD48k55Tm$o*l$Fl|W+Q=CChH^%9%)Hto>W&}I9X$U+kgZXAO8W(+xtSZj>m8!00=kd|H!^b3 z75l(?NhaCtJnZJW^eDo}TGkZ-0q_0>`xJ}(|y+FfH znVFRq*~QB8_%z|2T{AmL+#7$`SXb^b(Fh$5HS_X8+!mXEmJV+4hs@BpxEO-<2A$rv zQD_A~aLNdi>qZ?6ud8z5tW)Yn z=;j{l1FtbNS(QHI!r~+vyxMLip9kG~BvvS?60ml-%;pl+XeE6Prrn?3m|4c0gA+y0 z<}B9hq<()Cbz^&VL!xLm1$~tOJx1sv=Q@^AbQMB%)Tqg2RiY*q1A*|L^SqhpB_dL( zPC(1sy0?Qxzc3NJ7@F@l8+>6@7LWW6n!EoasQKzEIk|ZTZ)YxUiiVD~M*t)}G!8bm z9RnL$?I_z6%w-|HXjKWl14iL zj^O>+PhCl_g5^e7{BXr&t`*|pDnQCXl`f7vml`VWO4f!K_Phi7dj#TFQI|FU&a}(tl5(ceSckORKdBd`f}YGrA|cfpo#$uHnaWwc~k0~ z&n&uoV%qE ztjP#=48Pe}v|sFF>OSeK4hRaXx5M8;sZ@+Sd_dyVaUy;aj-d{)!*D6{*N9S8#b0(H zz#Gk^5+y*#+S<8zXlS-n%3tiQ*{;J(A2plj;;sl$y$69zI!0vRWvu9^=98CqmYC2- zaogGt+65KfS}-5eIC@ee`aPe8o;u;gnjb7z!=X!V`^k7><5>~U4)8(j|Vb8xcgIF`_SE?B|LzR8ejfu*Ci(V?NZ zAK=Nbo|?Y_Kg)8xEOxPTe72|H{3x%6$jPd=!K1KJ*RQD=K~s&K`gVEeOH3k5GDt{vOTy7mKI<4>ynNgsk`Tzc!J@4Q%1E{Rc`FHi zvh3tK>tJ&Sw}V-)42B+nPd7S>Lcl7nl4R##nvhLStXW*Et&u>&dt?1Xs=lZwqcfWv zV@)hUt8?;!mb&`#%%a}7Q?{KtUrpI5?$2~|sLIbsXsonMuu>Xz)oOGBR_^-s7y<;z zqN=GG!47gyS>dtY*_#_v0d1&wl=5KWpyY_d*m7yc0gZZRwO`T^7gbey>W*{g5pBdl zlD!`qQ2v>07;k@$$`;C+_i5-4ubF*l`}r=A6^?`?L@l;(bN`U_wU|wNCIjkejY>g+ zGLCvdl23l}Ty^r``ZYKGC|R)dz=d;03 z@!C|mTO=71hzgSo)Qk3UN+95#-M_V@$vC)t; zbwfEpnubLJ-B_|(bU@=^oc7EyE%PtG8;)=LW}lb(2FdQ+ogQUnn`zAjE>E*-6!Vls z_LO8BdFaQ%1TTAmP4^a-^~X*TZgSn~idZ@0?D*Ow06NUn-;z~{ zhGhGEIe+inObh3JAcTShjjK z$=?*lG6$Iz$KhwEHw7HObcVC?7oCXxP*a}I78DdVVFj3?xb%E+00iSLcDl(syOrEd zJku-2u&PyAczWBqti!GS+Gh%Zui{bQ%L`IdRYiXQB?$>PDn>_m5w94BOyvD44+pp}3`>Q9csW_-F`cFgPx zn5(#QbyIp}#zIX3U+~F>E>TZvw!xN$*@K3`>;*?NiB3J}t}iYR9ha2vI|()jYE~X@ zMC~vS)-emX$K;e$i#J*{EED*s7POk1xS46^ZLL*vwd{3C5QI96R8x{ot3w~(aSn`> zpNP_$@t~#lq#e!^b~YySdT?uIOKX^VkuN8Q2s$`?)-ugdG)F!QpeNd59q_W(k*Z51P5gq7vPZSK}3nUc~>W<7m$*66V7Kh&B^>+@ohZQJ#3 zz=u?<=U2x~Kifvs(bDB{;Y9Bc_4l9i$kf*_0bSDwulBhlyS~kMV~Urva|U@O3A&_k z>bQvj(f`HVUk1h1Mel=X69^#?Jh+A+A!zX65F}_P!68_1ch}$!!JXhP!QCaeySq2q zG&DAcyubg{t*JXxGgJ4&J;fKgq5GV@*WP=rXFcm#JlyfAZ)gXwn=~qSMn>$zh#8eH zHi+m`$P}8y?_B3xoT#;$^{1EV+=E20X*T-B`j$L$iRcH*sf!$HECzhLvYN%kN~V>S zj{I5Gv1zE6w9xcwR7e?m3xZ_i-y0JWZtJa{v<~eOyyj(AE%7g8G2mc17?%TyR{#2+cvijT9IgTS42NgH_<(`%Gi43;4lQ*Qg#Xq-DIW%v_ zc06ks^1(6e=O{onRFQaT$%|iM{Hc>z?rq6S>sK{P6|HZmb95OQEv70EB0guI9uxI9 zM7*w0PZEG(p73_!tbWZNNfFqm(svR{!@z|IPp(^vmMNj@V=+ISQi=<)w}MhBRVlxVl&7f zmh(gI$5y=krwsW67~k+M=|_oU@9wQXDvEy=XqZjD1?;0&y;q)GES%3)(ECn?-#J}t z(v{sATY^544s3DLeB~Okq(;ltIK29&xsj4Ctb&4!>>t5+fACw+4qtEw1lX&*N0r<0 z-JQ+ZlMorO$pn+SUXE$c7wx*WS5Mep2yLCiy%7-QpoO=oTLW^*F^?V7-3;|1$`BkJ;K6d@{ZP}-_JG4_H%XX&2wHA07{iw& zmj+N9t&p0f*cPtEBxU@JJ(vB_YDQO^%=?hDq~}s29q$xnlIUmk1q)1XX=ufG2ZJ=G z^fT(YCpM3>edZWFrxOeK1kKP9!2I)CUO z=qJGt>)b8te%j5Sr`nI2k8U>7=T&ymtA9C;6~x8P!qXlld@w15K_I{@O;|VMS6RyP z^UH9O)c@iv_UZc)Jx{euP$5z`)52X(@>KU9Y-H`U8JVy4f&qW)b9z$RRFjPF=r2ZV zBBfG)fT2Y@2J}gBWmX+MwVaK_AGzmUH{A#*L;{jnn7r)$UMRX9nUo!n(aDXClGxO}CJY}o z^L2RB8?fjbOQ~mlu_wY!Nq@MSlF%B3H3>9vZpm^sad_xWq`#B+l*mV8n1(NzMd%uz+^h?{_&^Ua#=ll{E5K(eLwCD_Ibv)!vMhsNo{!TLO_StKG%UysR7oQ05gm$vh}%W@}?()5SjZuUrgd>6Z+( zBS*cQJ-loQTUTHk9hSA|AEd8Ma5eL|BXR+{;-BEvv6ksGN$7b61Zn>+?~IWDKde0a|H|uDo8r>@UtECy z-_*`!X9C&%f7hDwR|N$-b`IZUU}yYy{lBi?TUwYY({xLNeg5BT{w#H`s?QzdnPdJH zDgvMGt)aB(cMg^P|9oZQrf};p{`<_>?f=|>H3RX7AKl>#oSeYC|9Ac0C>Yd57W$t* zuJ3tGK%njF-|BIzHHi$A8WN|rh%b5^&DqG4gFw4xK;7B%jK|r!)g8||uVn2=asgMy z@37+*r`4O~U+UOrm)5ZTIrtYSrh`y=l3??$#XOz1m*!My3Rq^Q`4)oF><@gA^~-;C zkf%cU2u%(5C3bX9PEOeLh1D4MHv&+jk&vMBvf**uqSox$HbCB0GjbW=07IHib_9U` z^)ALznS9=UoT3*8ks`FN@F zvklVgh77W{xG}B~RJZt&FTWC!Wn*XNl%F4|Y{sGsnVxRC*BtPaIqv=o$%z9;>xW%;b^g3H~Ql*ZDW+O9+wkJ>|@(jh)s?eb*L>FGZ0q1V-s z?`Ukq;CJ>?7!9|E%2h@me!QpW5YzR#n}A*Er&U6JB|0i71)M?6aNSz-a1v|;Yj==(fCa~v-I`x6=mnbTA05pFjI#=Q@tG-mz#jS{%>_<o*PQ;7%Tar#l2}1M!Fe1?J?Pk2(yS8Zrr&W(#GN-$?_FK#} z9-^tjNyvdIwuw`|(IFqixImj($leG3w1(GwfSt0=*E=JUF}S+ra>OlgL_no(XERc{ zI{3lR@po*gn{!;L4Ne2(lza7vPs^xjs|^7vnW3$!PQYHzwoqliZG`Q>Rz2Ri!4etf zcJ;NY+-*5CdD9YlbFwX1Q@&W#_6nnE(|8~>FQ3cd&bngv%*P^IsKgE4pzYzhnl!q) z%&%+0cVU0;F^-v%eHcHYaeLGCb|uu>YEGwo9Wy0xHfLzby}*P)8rHwMBLt5pP2#*5 z_s7TXf8_I^*gk-J&CEsyd%0u>{Qs-;eKtrscTN?K0N-Hs3r%F_qB zM|B?7FEC)y{!&gS2gbpaD?Wbq-q{d-njR`dKU-g;es9oIcCQ@n#?a;l;l1gKtYtUK z=RW|0pEzz_MA^EaxSQ7$ue6O>Uuyw_OV%&JKz2psS0{lf#^ci&2c4 z@K@;zU7wZ&UZA&?UqlR)LYXNf%cE}Lt|88jC=mFjQ#>!gyd@xSvC=6Qu@JWxz=6Bi z0$jne^KnxF&Sc2(0_Ur8e%3DpQ>CS?dS)JdGno%gW=qSLWG&L~OQ|N*$Ez(iv8PIp zN8J}AT*mt9VpJBn$V>4K*a0&*ZHq`Gte2ag`ouM=UHwPMc(S49P^03(dTDTz+)ea` zd9;>X4&Sl$cEk>I}IMM6fP_a&~dSQNZrIC(m~~c+;>;01Fv9FLT<#NKk## z3@RAhhsM7wb|AiWc@|>}=93g{D7}&&<(20GT!s`FZ-R}OM>}=-L+&!O&LK-D$Ez#pM6#HzMdvv9$O`#A1!*%EywjjZ zB^m3=cu@jldMemC=&#KoCN+>JD}$@;%7--VF!ywIEw>m2Vl)*ON6t>8pB>sYa#Tq3pPV@5tal z!IBoV4@P#^T((*6=7vG+JX!*4)4E~boO2U6cyPPiTxKiK4y>i3a;k>fuBzCS$n(|< zCi?eL>IJIJ8)OKXlU{Bvr~y5QV*}871(KTrU z@d8IPs16T61Zxa6`0g<`xq^cJ4jJ(c9bCF!rK+jG7S4x$CMbJW?nu_@(SZjftt`ik z;h1nuL+#PM%&xW}^@~t@xh5_$_eC?}*{d0{<+}27TlVV)Cud#P@;-da2_x4_3U{#A zs9u69r}>>X!9%M>XLVX2xT&rse|c`z{;uZpqGzvCAF5GHJ)}?%ntt5;0v&^VVf4?# z7@QA(g7n92x_B46y&q{wKxYr@VacI)PSS z7<;sErl92su!>WL9S^1o{|QTQ+8Bl-3Q@3X zl$g`ox1UQ1noH_Kc5lxx*<5y9*hXc0`}_New6#~8G@;mOA7c7nfkfX-N9xU)8n!AX zT#0`R*zKx0&ebM!UCC@@Rq?YT1ynSgiYqF<1odObzP@x zsr52fF2Yy*wFxldqk&@MuTQht-P*M@P+;U+^I> z{l-f77)WG>F+hP=VwzG>83%7(GB~N7k7^i$DmWnTf8b>M)#n2eAIpdh-!VY^PCgrU zfiD!^>Q4>*5n&Sohm3AF;I2RTN({D;jaXIf805xw5Oc~E^AaWmsLG_&Ph+~yq`pTvKWCdWe?az4H;YF_ zdgM}b79DYd8%ox(|LrU8H($|k@i$Banh#gxXSIHuw9XtcKi;$DX)=Qk^t}?>h0o>iYV>mU<|zt!*g|9o=^Pm@0}nF(tyS z$_Pf1D%Maht9C+qMI1Z1#6P|MmyqBJhU*|9LucU>7WLACV^Y^ik+b$b`y#rw9~nfQ zK(^I2Sz|w@IYKiTaCa}3HgD)L6RblWy?A+Lm|c)-AtLIQLydRFNDW+RA>s#ji- z8r%G?!uM&!rXo*&{3n(xpd}G>Pngd>Fk3gIgOd3UbnTQa-@Vx*^XyZJ7CGq%u84?5 z+Lio^E}k@VE)ozDzkE%Z969S>U!bwylGroYIecbI)hn=y1f<{UuBVYd)pyaJ6u|2% z*S0m0)3CZGBw(B$&d5e=Rzk<=!4y@<4MJ>6P|SXXkGbxrBVG&`UdlB@mTKD#xJp zv3A;e7)Nf}r_Oe(%F)hK<=a91@PZ+%!6Ftjy`(psXFUS2B0rw zukdI&)$bR(ynfwpJe92BOJAP-$n0p(M;}cp=i=wT%%96`pznPc*#P!mCw(ADE8xGG zchX*x8l2NHu+i-ATP-8O=i#6=?zUL0dvIGk06RV$+8f|*;+2plJe&`x`7u7nB(nt`Nr+orlClTlwE`%1M_3@43{_w?F{162bWQv=c=LCjT z6Byv>T9T8mVYs@&`BI1w0YtdjnriXs@hLBR(_|A0M5>7vpa70*5=Lw(?aIC5BB%G>lrhQMGr0<3tuYtLLE9W1uq}%`%>Zr zm@cl$Lx%-xjr(^09M1MYn8v?Xg}W5@_KwVzwARU?#g34%kZlgOdJJY%H(=~{(m#vd zGLlXB)&sraAa@5W+Y&Ngpli;p9F>spA`qA5CABJJ<_n^&Z5nGQr)`$(K;@>E`!uVS zrkMCPfFZKj%z`ge^LPH({Wisk=!IPh-hmTyY70DPw_Izn!edr@B@It20lE=Z7g_fV z!~t;MF7Q$fLR zxu3P%`J;1~&}r;PlSF=ytXFZcNbQAul6rxGu9^5D6cZG- z08+akFn>0DUV1xg8kZvKru-XfTGK1b?quDn>o%N7Cbg^}QTI?ee8dn(bfB_3Kl!ai z|0*wp0rd0Hr0!`;$a%A}@~OE64Gm4{{q&68ZtC(fa>{T_mmI;Zzn`Bu&!xiey1@T- zWX+-vazR&B4))KTFAiSSQc|RM$nvEwvw-6^Wd)rd83oL%wHhp{rq=sU`0piwW1Lx% z0Aw8QAf%Ws`%QLF53lQ&2c7%w@#^KmyJsMsrP%yC#%!(C`|>#4$rkICf0|vE$aeAA zuEM-5X`c7ZI{UGn@BBqRPku`3B7lN~XNxY7LSAEgS9ux{r3rebhF}Uyvq{3Y?sJZZ zH)vi+vV>-S22qMv7LOZ-f6f1cNFP<;-meicoz-u4mQf!41+?*b+Psxkaxn%W2PIP3 z7NLGZ6}!~r=A$l;frgA^Kgj?eoH&$}U7lEKWsr2t@rCugb)n~?P1-_7R{OljYM>=> zkLWtK4WuBS=H|~nD0ry8&CTCm4g$MImoI%GCitL>gbAkgD^I6tDMv7!{QH%019ykv zSNF21K3uxfwvE663d=>qiHiIcz%LyhJ|0xytKIS1QFpF9-?C7)b9ynD+Zeynr`6`F zw+h+bY)(w|*V6_Bo?!CL_K;S2?~wtIlc8jOX=Z4gM1-_9P$Ps3TI(+QikndC<+1S2 zMHb%Vm6-T2D-%Fg<(~1~<7Y=&0AQ<`2xw2kN?G<@6%K2hyS&_Whi`|M5YuKy7EI%c z6W1u0IDZ3lBU2*s&@e3efrdIn5_^lf$XHD?YYGpuj_uJcNnDU&+y-25;?HOnH(n<8 zHGH}k3#LC@;C7mrpFd!tE|`D>K_ZQ?646RfDkfavb-#h{vPdtTVh`e6Q7z)2uWuk3 za3ifjVpO04lMm>&yR{Q(9D1P_ln84oHb0GuHBjPH#wX@yRva2~H;-2(Hthwd-`mHv zcrp0uNgtH8QNXu!!S^>w(~R6R+Fo;4`)yn>=C(Q26{peGNrI7wA2XeFEc*F*f}B^u zbm@d-Z^Fr+gQ6NaPu_l-5TYetek@vQxmZ0J(s0}eu2~c!-jpnMD*Jvu!Z#q*#O98$ zH{dakZB@RygW0hu8F@^8b2|clXt|p zjH^28FwiiOXy5dMpRo{Ge8*7M@(fZn4n2&diSguy1rt0}I8Fbm%x)Q#42|d#1G9;@RnQP*8HsxmWLV1duS9hz}xbN!Y(3K`+*&oElv?Sef@6 zU^6^8&`nPZLxj^OBt+%;vCT`$lkPBl`;M&?Z|_JQ6Q`{>fdL9rD__a4S#kByTZu$> zL5rtFIBGVxZo&dD9!00kEy(dRR8_<$?_tJ-m2$s=Hv+!!xOY-JqBQ#W80v< z(a?*!U9mQ|KUuJ|s3`q{Ro5X=Cp9OE67I+-+8;M)Pr=Drb88{sfl{Mz4Kn37xNIg> zaaGoYJLQ%&ji*>V$cbVl=y)Wl+yd^Wmw@_x-APS~?R%Svhfow{@b9+4@$H?Rt8pvA zz0!1k<@5?j-QzAQLpk4hzXJnr6ItEKG6NOoMfS1e=yXEb&DTaac^zI&@Gp@L&-%cpGviWKGiCiXB-PMJ7XU- z>Fq|jS1QN)gIE41YV??-P?>J<$d~V$S;-uS_ir1jxG5$-_(esJB)?0uDXvMgm+J2z zup6SKyz{^_%ZSJVwHLZvRNXfjv#OO=crEMte4$~UBYLY9A78>(E$Ns{d$Tb=X*jg8 zx)V1p5uW!j0$FoQ}ZC|d%Y6-q$khMoUHR6f}af8m_ zhm2z^Hjc|;nvU21PFjffn6Dx?$=l|Zz}uU&W_$X9+1a@j=1WQ!(g+5Bwf4?TK!0sAtG2i!V8*oqAd5o1Elm?py!*8vo6!=|<3-*puLp-tR;w${WA(_kbohYK^B6C7she(vDlHFF zg@M|1qtBE}%Ql$*OYFS?-)48QeV& zw%9V2NB$}anzIo!ZeHsXFe}2q*9_b(IZKy={!%6~Xk`(+Z}p~Y-WBYY=k40eVTLg+utC1C&db9k_~%{c=`t$=-c1A>Xkc_d7<)#daZC9V!E2 zdwmcc{Alj5P(>@ewZgS`Xm<8w3dptaBk;E@b$qeE>wb>7zkNJi zB#IueVDswdyTGX( zOBcz`A9CC68F89YLMC+5v>I=drtq*{W?-3Y8u8%35VCzTEn{z6G;|7|DsS@l+XlOj zG1GvQ5>bGOl@$yMZCO;ZIe!X+`vS*}Uk8hQR8n#+j??+GG~Z3wYV`T@-(NUsUGvK; z{UFe2n(^V@kE$hSh~MP=@{;uJO96~jlSHgzNhh{^hH`3jnA=tGnxrvMfo|5>)de|? z>C}Xpvl2SE*X8u3}=awPt)(M`$rY@ySlpi z#PGw14JCK60FJ^g_rk@cp@<31nKYeGq(Uuwdj;@>RQ)kWCb|qet_j#<@K?C@1A2ax ze%4C=uv5pQ_wTmu5#A1>WIiIBeLXl>zCGf>23~y_Y-Mhm+t+y)-8?bg3eJDLTkUaJ z4$r+RV)k4#Q3ww=w*cJ(UMsK$r>CaY>;?GoWu4C^=QFNRE!DogHLkb}eY_boS3(}a zd%DFhLLH`rC{T8V?SZz88+j`e$HqW^dgZ+Ev^A)4^J(;3k{I<~6Smmb0GCT4FhimYvsXUIMmC4-% ztVcimm8Qp2keer`?ktz0?>=L5aUk8lGy(|{%b4)i*UH&LdMy!?@3`dThRF7P5mPg)jl{y0ve=w}0(4D^k zThy4+MYkIT^p24+wfq~|-9DYd3k!3r^2P}^LF#MSu5F!x!5aO_FN8Qa=OYlbbp+oc zlEsz&AVKTA%#}HEY@R)e=g^EcUib;!fvhs$Ao?D2eqDP@Y0KaUPZQzpImdGlDbFMb z1n!f0Ps~W+l^uabA?x(<{$2F$h~_X2hyAra+SbF&lu5{`>IBqB%x-8 z)>{ta7rXF|CD7&2KE*iFN(QTe+GA4%>hBcAKgj*{+2G2jDRKsZbY_jV$+a{xXAuff zh!UXYsZ{Uy`&}5pm_Q!|iJ1KSZ*(Cb(2$Z9IdA;5PF_RSJg$*>h|Tk$q^|kHm0cQI zqR=a%Qrh?*MmyEiJ|ldsPA)br0dM?#yH>pndrLY%I?GUKEjNiV8PoDj8xecGfQB%M zbUel1Rg>RRKkXl4s}VU90e%N1NJ>mZ5%%^P6jkT7Y-?mM+LYIB{9NjXW8*hRhl6wF za80cbX1lGjJp!^SzwIq4g+-1nNaE8irzFH%@H=Qk8to@~>V1&!r#^omp9X^0wZFiY ze7a`+ooIl2hp`DKz6%lA1#?5R{Wq^~IG`F&LN=$L<$(loKu#GNt;&u$!*vBFi1p1v z_XW2uK=mb4Di=*k5A1l~cZ_gv~kcKCksoxy$hzdlhB$y46tFbVB z`EzMjcQSDIftoMCs*ESmGs{k`cVhDgEeou8zG36s{`t$x^;AuY>f%Z-Zm`q@%{Fum ziBg{)$F*wa6%HzK_4DuZ?jJyevyf#k9vyY503TEO)E2!}^?n}%m6%AGfnuXktuM^9 zd-&N&&wsPHS0$=GOcbfT)|;6%bM^Uqr#8x*ZonjcUky)|8!KP-D&cXD-=TS_Y4dKs zJu?%H-S7;-`zr~h*@KVVZ?#s3NA*(M$!S)BQk7y(!{i3vNy0_5-lNY4S~xvq$;cW( z-IMMQ8~ACf-@F7(DvZe}9X+;S@nr+nertVRz;THsMuiP3NC|)Ny;iSE+TJnTKhjSf z;k7ek-FBFta4cN>IKS>}={}b=Iw}`*zU+2@11dOVO4E#>#6AJL4SYBGOAgU2%zn1O zM>g$xXjC2nITjM!*wsFerC>tvj{Su&+Y-Rjzzsop!hPOcqIB?Gzqtgh7)9r@jPhO& zlq*MkmBlk_{rpX{uD9t71QxtD9h@0gOBnQ4xq)!EM zt3yhoT%|nt^CK~0;m+~1bpv<&FS%seH^Y>!*WCZJ=bz|LK{eKrfD6v(+D-Q5q*FSo zHazEC5OCvjWTh1-gg!im>Ji5qJN~fpNA)>B3eHEB!c2zz?kK7PL;~Cb;sKXC=7R{z zF_N*>B;SkN6|Bp=&aObua{7uv2jVKzoG>I%v64}__CrrEwF;A! zLrAqN=-vC@Qk{sAhJeKdp+2ELN-@)}TB}=oj^yRjVVEz=*z4Hjjbf&E_8he|j9g|h z9m;qIjf%^@YZ!f$pk*xOgwCj~SnuVh%&0whxzylTk*93;+9E!7kpC|Yb%89_%CIrS z?q|lFN>1JrK*g(Ue?snH0zx)DUt;n_*LndFJram@XNC)e4care5O$4fhwc7)IeuX) z@yO}C-QE53$p`5Z;(2D0T~Tl6i3)?Ui3CV=P5`e zjw07j+x~-^aY3xGrh_Sz`2wVq_Fv}YGN|*(NhmjTFqwJo@kt}UC+BhT*ST$`@!b8W ztAX_UDH>&DZy<2RB0WFg)6lfB%phu4k5jUgiw=wurVd9@$@thf_!k7SFqu!1piWC#Qg=}f~{GyQAP3LDV)V7qrYeBhSO9_R}J z=y=R8cs#XI+XU#~%b|EG6un_DQRs`8)b(E2ZN_hMDfu8-tpYI%;y0@_1JGFV@Uf<5 z{D^!DplA7GqjElh$)Z6*Pfl*wZu67v2xm0u%!)#Sw5e?1h?d}2OB9^5T2Bon#c5He zkB2E1z(%tKNM^=ocu>tvd%61+)J>gN*SB*UNep|uXEe|kJ`n;1#kdJ^in{CF|4&Ma zT>c4cezY~3M~&RIY@M(~a0;e2Uq1S9Z}UP(!DBt^)2)Le<#T`^W}<={^3njyYd3=P zqEyUWaX?$|8YGOKmTne!Qa4O_e>qd2a+0H;U^Jae5C4&CQI>==8P3<_yBf|x zW5e)XnhZ2X$X)Cs``K6n0^d6MJ_oBc}bAbY#)5w8~s(W;EVAA zpewX^`>sBWoV&NG$#KWb*k)porGR{c<(SCfY9VWl#!li_{oudD2&AL`UoUWhRxQF0 z&p=%19}k6stSaWEFFC}c`7tDC4bk(Od5QXRXJ$U*GiWhuU*0sl{M!0xTqzQqyj989 zMCu;>uE;X@|L8S(oi3rI&i(YCDLA}Wcw}Wq6%u%I6;bYAm4{;ySDZj%jg)iNIG%d# zIIX2KFy3NbQO=bh8WH!tI%}OU>_Z)lWX}7urvp(V8)kJ}la%-5QX6-3dM@`c;v)et z;9SOTjxMp5#}Or`4UxSsDUx^vellL0(+L1pu0(hAUk)b1=4Vgs4JW4g z9*JdOWIVaqX|GQ8!Fh6#PQCyB&b1OFk>3RE$MPLC-Wu03Uj|E=do5E#p3s(0HGj*ig}&1wJbt>Hac`xOm5 zLTs;mk{~nE#qTFL-d^A-hYSo?X5Uv`-Aw7+1y@LFRnwwQm5Xit{*Shb3RV@%^I%#u zP}KLVs!$Wk%)Jbwjm{Ul_=n#c`fKE7%^!^T&M5$v3IgqB9kRsE2u*-uL80ct0Lis5g2rpW|vr<NvR4XV%t&*OJ?$mea@H?Fbs_R|qWEnp~?{lyu+XN9~cA3APnm~V+yzL30 zx6meFm11EG&P(mhDsLMST)#Ny0W~ENvAI2bi5@3kNwurh-P6Aoyk&GczoS8Iz3EIZ zc@F~J&o`@|O``37R-Hln7|0pG^JnWI^p6?p$k;ehk~C`Kfp3c4DA%iuS2y!U!8teO z^w;>%*5lhm9gtan2cpu35xbGp=f*2*4j_{18TlbsIR}wO zC?=Um>nTx-DVms;j)w(cfN`qI%6iU!1&+ON(%RHVVw`-+`1$h zf=-3($ijs~{8#e#2@DDwW%nDt3>gp26-AW=72Q)ys$ljBW>(Ycu%)eMSOtGUbUtmXsS~ z49#uGsR3fH@`FFEP^P4V9?5800s9<&u0*|Z$I{l%LDlQ+gdYCwy*CdbUnq7aFxQ%0 z_yTA=&;4&~+(DpTQ-jZf6#)Z#{0<-6)JO$1BAA8EjmrTHmkhMd?fd`(Gw)MG5)Oy2a*EBF`rIG%W~Pir8wnM+}D6WKiKlLuBl84 zkyh~m!&-exbMrI}bC7LqZx5)^&OK$d1DD@G*IlgV0R)Ce22tD1BQ8B8E&4S<_NkFQ zbVh^Yt%0q!-WfeQI!GDX!6L|4WZ+Hn)h}@*y@(({s-H3m+=cm^>03|D=ud4})77)D4c(1l+{nM~TzCcgVl<7cv%=Cr3FZ!>!UF4|L z-WAe+)j()3VlK=@096Nadwms_$x_122LpkpW`m&{aWTht@d@fU+k)7!S(3DN?{`#4 z95g_q&T17r6%7RMsK0mNLg^OzAn%#<`*Etc^}6>@!`O(h!}#`6xH`VPX$@r)WA1p; z)9pNo^vX>ppW@Xsi-y{<4Ui7(B&rp`&V%CDyp1X+t!0qV3@usb{}uC&YtGTvqv#804gT-Z^V^)pb; zCw*CyDn>t~bXnTp4pjb^3rbQqF?!SUeB7x9*hp_k1!&}va`pl|Q4&+j_!DDl*58Fv zcE8+L@aZ`<@61G0%h*8(T?>@=bZjPUx8F2N2m^z8>?zH@x1}dMlO5lT)?|>+E)XqQJ4{Y>WL<0 zZ?CLY){xfVeR<%>o%OFiPn-8)gMR>Khng^E+bkmH_=?5SpQhE? z!i&GFnC{$+$QY@%8Jw`RvD@XAs`cU0TaHZI#;@62?5NX2X)k}^w10OozdA#EZb)3O z_|4o_IV;STtGU4eOddH#7<5T!BVzKLEIAbx_U)Cc_dqLOr%tmv_<-S{VnU&tiU`z&9>6z8SH}mO50WA-Yg8>xQVix8H zGXu*{1T-&I;YFspge0If>3a zEmጄ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�`Ae#+GCkXUndE3EtVN1Nk|N=hollN|pS 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 literal 0 HcmV?d00001 diff --git a/workspaces/orchestrator/plugins/orchestrator/docs/workflowsPage.png b/workspaces/orchestrator/plugins/orchestrator/docs/workflowsPage.png new file mode 100644 index 0000000000000000000000000000000000000000..499d07c236a6af0136a7ac7c02855549e6d33bd3 GIT binary patch literal 114122 zcmXtF@vw6UkL z`~HgMEob1Z<8J5eXX$ARqvPP??QQF69X^K)149L)BrB!szkbmjl%1pJBiQrcH3Q*I z9-p!SPaO(Js9kN*CJ20z7FLjiv- z8Xwl3YrB{ILec?W4#w-AjDp3C%>`SI1K&oCxJn;+gO1*4x~up{44lzJ5l&ufL=(kX zm8jIHpMwAKjFgru;fT(TeR?#_S?iG4y3U>bo{9{PqoT}GVNi0(%*?0T0xdStLN2lv z6kZ`gJfEO!wdKtM+zj}1v^9>{IsLTu)&vV-!8V3GC(mIAiPy%x;%vB&ogO=r3(s&w z58W4I|NJgauaQRlUW- z@h->kd}^*_Sf|!R%uTj5=^)O8KfX^f_c;~O z;eHzU_iDZJv)Rr;EjcnF584xRMY)it2)z{0Wy!fQG#^V16&ZZ}nKUcz7hm_5b{e;< zqI1oWPIvpaY^tb>-%-whl!CWs-|K}U)|Jnt(b1TF%sYd8Cr4wfQZOW>)X0Zn|C&EA zOR{#eGO_zgh*srG+ZV+0ua}!@;NsOx8rMVU)OkU$;FP`gF2Qb<l7Q8KmpXpdl++4WwWaGHLDv$kbEy*`PunwQ}dj^F*Q?O zm+{Gr6nQ~}ouyhq$q7~G1)Yg0e$+vg8q%2$#sAhG=}8J^yBr_!ea**O`jQh+zs3l2PmYG=P|scIr-Ccx6P>NUeyZ zRDVTScyLsjzlOMrXWQzTryP1f!FNIoZ+eEl2S~eWDrKy};A=cJ#KXNC&B+=k< z2On(L(?j4R*kV+F_TwwCGfG56DpKRIC z8fOU^MZg7M->50gR`(=I&wMYTt{7XI@-ZNm5Fw}g9*sevdYAvm(!${oqz7?mQGsM} zOGOy$-ZOX9M%B6N(y$Oc;Op})`?0OVF*lhtfv1EFab`%0>40|E%S9=UM*X817h>E z_B?`16{qK@iZbA6?hlZIZ_)PNA=CJO7!;Vn5jhN?XC=L3MfRd>* zE=z_e2C>;`N{XFTlJ}|t@Hta$9^^l$5;mtBp)Rjq{7BKWJ4*hlAi&C6a0V@c`#ihh z+vy&>smro{lx?;(e+}X5ATqe@R@NMu|A3k9R9)Y~C??QWjtCYe5{{(u!uGfwL%^Dr z3m3tPR=)b;Ob|jW&&OJjX2KY?t9Y!TY@nmC0&P=SR+?Vc?Kfslcl9$@91k^tMpYHM z$FBM`DSnToI@j9$wu0NuAqxti*e~Gmd&DLOj0|`y*n#58|D4Kkmf_h>(GU}wFw{_- z71+xeSEb^7AsDCQnL1PHYRZ4Mf5R6O+b*rA@BTJv)Ew{e%T z?96>A9I8wCGIJc!lr!~%*K-ANKNMT8vys}-yGK)Hz_PMiIg_f;aKZOYpmg9?wf0COuAr8K%yTgxbl&`8?npff|aER1Fq?bB{ zP#pb;0y{+|dx2eLk?~VBFU}j6DwJ?G+_ExcV<|Yw_32aGQf$JNOkx5Ti&zlqgL03V z_sk+5IjQTc@#o?!;F@i;nGm#LPL&4JYjS02&`V+nkYy?oYjqW2;IF$7bI8?~*m_Id zIFh@0SsW$4zQXD?n&e20u7KyR2JX64SF^+!A)&c*A}*>lj1uPu*liVSq9$7L0<+9#(o8O=t*yw;i1u=sBX5%aM?=B2&`0P_uqXTO5f7)mm(iol{Rm^*T>( zEKPSqKs8WRp>ljeG-C|8z&SNG9v$a*S>P(FRRf{L?{HT=i0rsYD_n&tY)cK^#D)q- znv5mtc*b*K#sQgfHijV*gzF*6bWt;g)kEqxZsGIOc%P)t(ZpE{eyLe{UyG=|QDHZZ z^v1e47-xYyl>;0MAH?y3iT%(UD)woFGpyQfa9MX2Uzq%ghq5_w6+X^p72$fAbnzp> z{k@6s@_fL`U%blvtq7p$FfOo7l_&Zu0hXHm7e2GVcE31E3c!~`Vn%w|`SNuq+Y{&v zuM+H+TUxRc?veZ7KB1_}RNtz1z3beMB8G2xRwj>&u!HNFPItqV^{PwiB2|M~+{8z6 zlniA(!ZZ!}VsOt3!jbfu$>|{QbLlZ?2Xmz~6Ky9X$m?ckQRF|4t(=Oh)X&aXCPdqS z!@s-wMj*$nv1QbnR#K?E$zx|8Um1qkLT%g)9ScP+cxQ_{eTP3Lpvt;^|eUq{5d#z!zD_#a?AVlSPR}YSsQ*D%x+2KZ&r2G4K$##;@J1aJb zMi{pg1Ug}yaF2SxkO!1+=4*f38B^^SVk+`5Y-ke| z@Zh|3i{X>))WgVtg=yO0_{XX-3t!tWKxtx0o-z!Qt`O^MjUNWbvc#4RxYzsyIs_^z=nG|j2nSln zJpLe1-N z7KMD@3^S~{(O*!863=o_wHwrw0}Db9XXezrlxaI1_{|5O(<}FCW)x}tR$ys+e8?>V zTEi>`bFQN}D$8x|3|;ccY`*)F(ynYep;go)&8t4z{R@IxBVEj{O3)Fx(iZ!C_R&Q{ zg1IC|s6KrQvb2bFYff^r$58|q>qjUL!>sylXnqyJiQSskcJL}aR^h;%m_}hCLb&;% z^JlP72O;Pv_lH(3fMktKUWmdpPW{vGA1HKhs!4EMn;hkKN`yVg78eK>dcVZ z%~N7c27aX>eIP91xUtuEnR@o%gZm{G7EU%z?=d|hX->&U(lXz-goX;fGnIYkl-wiE zQeq3_gbX2LSOt7?GTvjVKR@`s7gnFq5F7mlpLxoQVvxrjTKf!Tdx|E-PQxlQwCrg( zs8tRQM$CqAU?37gRQmVh21jIjhO$GC9#f+7hb55&;mzRVhKre ze({k4)La{he|IM!f_0%a%~<`O6ix@Fso^6Qx-!Bh3A2Jp`f-Z-js&#Y4B&$rcdXFW zST$sCFKUWkMg22uj+4PQ(zqQ;+J^OoEuOZvHnO-u6sw7IAG-4*4saAA!2uaKx@eS^8$GejMZbLIa9+A>@SGj5nKcO+JD7 z@OxNVl3WI9A2gqgj8l%AuoS#NNbs?0Bc0ns*RY%jvcV9)7kGiJdR_PDzf4N1U%yhp z>!cI`h6o5X54B&*FMA+?oQIMqT@klqcUFp7Or~W!YqzVWEb+N6!=9r z8?hB5X`PsUQBzqXKq#*$xR@N5m9C&bhv1R(eiL61qu*T*ldnOp(2>Ca>}Q}WyRL?c+It4g3 zSLYqDEX2@LuY!J;)>0h+5g;i;fR{gobEdRuv#mfzENC^yze^&q=)K$JQOsLx!u^_f z!yHAkizIa9$zs_cF>0!r$w*t6L=6%-#FK-t&!Eb>{NN!j%n*(hjFevHMabCoQ5NR8 zfWm@Ii4pgo?p{hL)WSFI`Un1NIbsnZ_H=Bm{qOS;{UX&hBUz!5P8dIh1cj%Od>@qj zj94L19CI|L8^NA|4M*}38A*&lOee)^_!a-u4-oLn^QSEy%|2P|JqkEYiGNT3qLEpD z1ups%Nc;rRL%(?Hxc|74Dj|SjpGFLZ?mJIR4xsFN0fbU40owPB zFRDQcNVywYlBU|h-sUpwXzK8^^*K`3ss47nGabSr+=mDrxR~naHDdO#YoGkBDI&>z zdnB!NFYTTodqo-2m2XF)n7(2ya5Y(C(K7DW z*us*1E)ZsS*e|@KdH#wWLzkZTlZhdZcfwZ7$!<8fG1~<2mXZ|Z^&}@!-yD~)#zM%O z11Bs$b|vToXvf2Fh$0jjN>IaIOo3acQ*w1tb?ITOTU;lS1;onF0))N3Xb4F$ogw2y*_yc)5T_|=LbU_G9c^;b z;Tl5AE7?IMnOJ4gCj~$74>L6%2PY$Im|499{Tb^e&a?jjDn%v#aIqjK;pxi}3(e?Q^V!_gX$+kq+qO_7v) zSkxjSzT;)3Y4-7rWH|93s9m{a6Tvpoh2dN*1qMqHot0Rm6p}6eBZ|vjl$Uub|~hjyuT_v!T%$oGR$zP@Yin_(!d{7cJIZa%R2SfIzHiSN*03^sUSnE~Se+*3&7^r`D4Nm>bTA7Rt9)0?NQ_z_ z^ia-By@)&{;S6+e+E@lc?5K5*b%8c{s0V@iXV`l-ver^t664PU%%S0&6CZL!%29_B za(+6!1A-e5SKqlRe20KQ0y8}_^t~Bebb16$Vfj@9d1Bcr(Hh!bfbNmzIwOhW54)(v zS&WJO)-6UYTNq!<3l+72cE@-ezm8m)m0{-e)nBT9-~6744#+0d%zLgpK$Du%lIu5QI?4%ZSZwuV$*2DWxXBX&|Ia> zkVgk&@$#ZtqOHmx{33MQS#*Q1TaF{dv2v<%?10F0TuE4(iZnhnWz9f;-$kdu8wsiq z?X&F3I&0KTsa1TwA1hXEpj>Ebu=WB{JLI-U^E|IQT?RGIWLv$xsut!^@^QKB(` zu|N8vM5YOE>2h?A^50XHn*MCa^kMtDY;m=l9=JY!4ga>#nJ@1txww?*jfD4b+%_bS z_OVHA@oY}h)<>OLj*eZ09R z7afeDW)|wq2(pOT16JSaK^ zN$Y-SK5}ACb7#Fd1Q$2@C(q={2!){AYl8lI+vyvH<2l|2Q#R*^5f4DNJpi6eUHs@^ zk&9zM{}_4QVhO0cF}x}qvz;nBQg|^{)1q=p5dIUj_^8;7%Hwt4r z$4M^LR}!~%J{F+1fzysLXZ)xfgHTiyq+$W7yZ2-Akt+X`l9Jk)$w4EO(Ukv43*T?~ zb)k_6=_%BfYvq_$l(NtNn`Sr7t!c(r3$?93`ETSRk2(_?-0u}rs-ye7z@i%D8C zlE&nB0T{)_NXX{;wfn=@w2B&r!7=7UYE5;(4vlZt0V}Aa?2B8@X+I`D zYj(jY=VwGn%X|oo7LYR4>Bh{I|AEV0PocxBvRKv0%>8tu4YWq`1VwyP*0fnTkkCuE zm#XoKA7EmT#EQS=7~-O+>mHaYL3D^yMQH~|VeAT38XGWZVm+(lc2F1fPl%qLSJs#q zl2U#TB(JybwAB3k50GtGt%={`q$54X==mjw?{Q?XDxQJNN`IlZo z5iwOy*v9;kgMOQzN>SHA{kURKL<)l(efh^~tX6;OAveQq*T6j%?A5an6Gi)9D*ZeP zb)aTJ(91*ChJn|*jityZm@j#JVAKz$~L#Fz87zm!$`nQzQVEa;!;48-Wma} ziI3kCz`Nbz>6>ILaQ0&3J!(r4N^vY(k&~ciP>xgv4Hq9K-6)?!@6i7$wl=11Yh@#(=TV_HhrL7Or2Ft7=-C5c% zvy$pU;4^?e2?xRON3Yu6<*}AckvV{hD$Z{?fMd-xoA4?Ql-JXXsRkP&h>94DG_U>d z|Ck&*FTa=SxyLj~mG5;5wBwtQK`pFS=A^~lYl73nJ_gzn=ElOuoyLxlG$A(P0uH1( z5dQ}O%DmODwbrt6pp>Sl-@aJxJ`RHI{z|TLSb4R48oxTRlOmhO%#~bo20AbEJ~IP2 z1JA?1{J`-jNtvY#44zhvQr0ht9I@As+`GtEROR$PTWQ@pq%ULgQGjj-4DFwTW^61| z@>`0YS*s%qi>o8W(*P;Ii}RImbSiDRsU}0F)g^^x)^vW_M~Xt!*%w|N2UQP~n0m@~ zzds5ofn4X?&FPa*rr<3V`RBm7GB|cLR=OZMTXWr_r)0ZAK^M+R)ALK(2@*gRU8ojaVLJRa_F!;>0Y2nEJt0W zLo-PGu@sIMnTE>7cX9xDn)Hk+)Gr4}==|=-!rzQk<2a?i9=JKnB`n+ zDsQop)AbMUSn0n&$kdqHUD%`RN^j?xj_4$q|M{!Gmg>6YUI8XvoE})Cx1USp6{YYC z!FoTIuen-(H|Y6Kp(tTMnW4i6U#O;GbjBm}t*oJ3MZ^_?J2t&Gdjc)xud? zac2BYaeB==btE=r%!rGrrZK*6OJepwQnIRqc)igPo(1hmeraUdQb$R~40WjV*)Z&~ zr}R&B4q5sZ({tVIoc2C!T<#ts?>RWJo?I1qgQ{!;EMGfqNCAH%*eM#%iGeX28_uw< zpI2ByrjsDMAdnUv2NDv~VcSrq5lv{k^#h69UP&*$l9&EfHQKHK#@(j?Q@fwwE(d^I zDb?wkrMBVXi^HyN)2~}us?X}ZBbA%=eT;+K3qA{|K*`EE(B^dCvdUf6$+Dnx519Pi z3x@2j5yW*$ITrt5!M*%f;z=0`X8XP^7l>2KerK{+c&Cd!y-(Z`$`kERNcm*uVRsnN zSjf4mn#6$D+<`hXkM<>1@|U==GFVxxRj|jT76#^YCg-6q42+Fw@E@h&Ut*x?vK#l- z5-k&_?2y|ggOp)r-yYp(b$n?)TsHYQ+!5ML+N`{k-Oz-gQjjxItW7=krqVaNB^%Kk ze<}8%BlLRHh~Yk+?3uYqjfIs@_;kY*o?G-Z#=<0Tk|fR@z3w$BH8?Q@gF`? z!-3LqsgH=gsu}X}D*XB1{dL9W!h`b{+n{!Z$Ckm#l`b{X#m@tkkM!j}3cT6`*&!oeczbEA20QDAvy3%fjz+|V+Il|CDifl z(xaugg=C=`F9G~@P(XBkFL+hHp31t9DCi`~=X|NnAX&F0Qgijcj)RYNC3a8#2vK<+ zVnQ>Qgq>CedV+k>{oVu=vuJ=m$`ia*XDN++1|nfnZPwL4*^XBP@lMTLU=+$Gq{J%l&2%uv>a&_0 z5;cm1t1+%l8kkv#yB`KQV%P@#d z=4v~?!{XZ74m-7X5Ex%5k#F$#CUblp!$aNQL|f+|q&+x^YxOe;q7mhE>^ypM<zE4q=#ksv(TD%k7QI zvFD)NG7B>YP$MkU2aytgqiS>Eyd+=L7$DLqmmI-Jq5siH)x$kCCI=TDj?AvvDH9Tk zWymWb9yABfT(Q}icp&k}ma8L2&2U@{%}Zh3Y%b|g73!kX;S6^u5_13O1;nM$lVf-d zPH{tNC%3-N=$MUWaiQ~Oygf1)@hvcd^#U~pKcd8a6TPl>1Oo*`d%)2zkxb3RUi!-E z-VYR@SxBZs#}5P7<9{+4`tuHo7u#~UpKf2d*FV);fOwH76qdcF36Q~PR8?DT=&?`^ zYNZarEudzJ6(;#vGaR$Hr}#gb!hoCKRbPCBGHHd{pU|=1tOf;9M?a8{b;|@CR>^&I z4Q5<)5JsY^F#}*YOajWaoQ=}p)=|jjEXje+FP~E8`GGH0?yGaXmNHLZO5ED-TogLV zaWNyXA94sM<14+~SO^m{x;5Z9g!4F2ZYzRz&Uo4AamkMzHZEr@DW!p<6Lext#)Nvk~CaO4&cPvt{1v30yblrwv--E@9)6{ukD>?SyHF~PT`Fj1wvqHF^Q>+<+fT09^<=f?FB zUMn*BfV+C&z`RTp@JViXpbbej=E)40uFqf&3sMjW*COgWhF)x%5a98KgoIJ?u;LOp zin!|@z`i>?P(XYtV2E1JkOdGGxyfdEJ0tH$?et`Iq5JtnZha|6o-PR5IjTNc&%gCs;_@^Op7+C?U1q}Dd zxDyUB=vvCJ>#9R+*n}&Qa85q}fkWeCK~SaycZW`OYzvFnSDW62Qq4Y&GeFss6#oTL5U zEE{wLef|f$(BtWm#PPKZGoR&-Tmb9|8qN#W?y)JspA47EN%PqnBqSjb$4jj5Jm|uW zVCvs8Q$Ll|P0r%gc0}ZiAa`mtn<{ zuHRahYOGwS=;X$Bd4*#ER1EAm$^XWU2p-a{%+U`sWZJnQ;3JKF!2bSD3c3h#NIZk( zak~86N+w9WWihP+X|X2#Q}Q0|w@CY*f31T<9_}Mck;{7z3tK`0oIX+p-RU_8z=?4Q z^{P8Rm?1eFUH$bpyBJfp6$E_eD!#Em<)U1E^uB#fUS$GW)s^{3e(@A^&PHvdA>b z8)~1(I1WA*{UOYw%{J1XP)dV42(-q3b7P*4T_92>-(g{|yN8csfv=1lNCnx7VOrzR zpOH#22xP)8y}J@iKgoNj(mO(H_|@B3@&~brzDCIyrUU?I1$UE%uhShW6X3!@Sz72! zcWp+N!N!rq=FYw(oXPJ=2?S7dMjzHl?K?k^lUCZK;jR&TyD2L<_y!ZqR2|d;Vt)Mk zk|+azjErNqbIOw#yiKUzw@omttfF-FB%@YVa}ottOve`_DZBCU5|)z-6(I!eXcO7< z#TaQ3!M|&XgG5DnTt!4nlF$0e!a8oM$aPG>+a65lH3W-V?E6)5HsrzH!jO&DC|2cx zZh3p43y>MXph(iYHbugwfXD!QGGEwcDuhBdEll&|;K_Hl*$KOCk|!a-6WgB%LIct9 z+7)ynKPGBroJ*@A6d)C*P1HpSRKRi%+v1qe>bR-dDxLWm=p?9go5-{;tZp~dg>Vz- za;#STnNq&bA9^i0`}Le(tTNjRR9y)9v#`~<22>XPc>mugM(L5i!=mKDzGkcd_7q@o=Pt5x7#@wIZD>elMC_b zXG#PAlt|ISPv$QVRC%?rNA`wzhN^}@=2dcY)Kbv!jrn>yRgSv|xMoZw_O*kdki($%wc8(TBgc?+ zu@={WF|Fmo(eIyZ?AplQv0eP>d8nS-?c*1(OHDVuBPZ2xaNgsWmb!OgAb-$Dm1$@n znlM|a7UCCPKsxa@(IBD1O~mVqg!YJu|iN!NoqbCV@m|t1U+Z)`}EM7=?D4CN2kE7y||Oq*P)TY@<+Luf~3lR7a`ts4RK3VbcF%jHE>3l3uV; zLj*xpuVWuRj{U9K;g9!u*xyk>-&zxL&U0ESGXR8!ADg!Q^v=DQ`Zt9-R?H;Q$a{A- zk-Btqqb$ffYV7mLjfW+<7lv@FPWX&>J8&%ZD+xT6=E%uQ13FFxTyV7d>oC>W6rGCbEjMz?szV4sP+J%7cm z)&pUp9jfou!p^m2@jfB$Tj58un&#b;pH1=)A-J&Tw9y4&o^4PIJ}#R#wQ9u6mYl4~ z6E~EtL}A*tRS#dD8)uTid9sMXl1C{5G4g{Ao#L-(G{(e+3cbqf`@#D4{5Z?rme|t{ zw1la}4=viO58#un-BuA4AQZu5G2l{1iN}`Sk-(@kfkVOT1_IB52?e|b6edz@2ITW2 zr!S|Fa&7fWY&EkAEDucw{MUitlWmLSxPG8QaR9la4} z`i`(jR!TKmzkjGCy24%HiY$ny$}&`-Htd`mi9|pKiw6xYOs9`QNwEdb%;+m9B=?A0 zU{A9gmu?$G@7TO(MrerdzcHzg3IlGYbx-iqjOtt z*~xfu2V%@wZxjdjhqrY47SVlga3LjzCpfx8_heGo;LytD?ppAEC4wONO15&zgH#;l8MDUfQqeg1X)Shd^J3BasU!V z^qZ`K3&iHHW;o`ek!z5^F(nfae!9E_fX(-tgMe^ii>!ZP zemzo=pNlj%gCq5gKI(6q@=!FM5aU1+yLE-^%co{ zWi)PD{~-#alBhj`qq?i3C@NsIkV2jh6=)P<&ic4m7Gi|-z4ESD=jyj&tgbghpGOhz zj@Y;4Oz{XCQWq2ADJhl@Zl_n@wpUi{?d`)5_&GiQqcTbS>Zk{h?%u^CeT&)q@j9W! zq=I{ShF{39DNyYl#Js6kjMe^y8pEMAPI!d6sK%*Xn);SJHdRyV^HtY-lEo3qZL<9y z=Kq)=Qu7`(&ERMG^}}D>{2dhV5B-6UYw~x`x3^j#uz-IVYnh;gkIkf9aw7bXb)ZM94YI9Rt?m#ph=$pwu?Ovd!WDV;rA1;VLep zX8rRb{MEJDrDz_NT`tjOACM67bajD=lE9C_J4QQ72rI@?7)NWX-m8ul%0Q=dlS)_C ze+rw=d9(|9kSEr1#=B%0o=4*bJ%+E_LhDQSL+P8aZze~7q@Y+PAMi^VEg~>lC1fbM zm=uyFH&8^HB8^s6Au=MVye3a?MnWMW>g*#OAsi0PF>VFH<})Od6D0^6r>D> z0va3tRHl|I+j4*Dc!~*r7Zx9~MU^K@+=LkS_Uw2jD3D@y+Sm!KS>O~4QAKDtYD85u0X!F$nE1-V2KjC1s_-u|D z0?ZbOv8C7$gp#Vlg_AeDCKAU>mf&vvj=%ZW%xAClz_$Oj4?&D}O6z3mxR}aF%#ahT zTmqa&w z)OmS^Os-Qe2j)B!orO+d>k?Q)BPNeZ?|D;k-UOV2O-xj zK1FFgv7lH8`a7X_&@-Ku02IHHf^VJFYLsLHGtc(998PwLu%VeD>a`}LcVtbNlbn9| zBh5Kij2blw7V%rOb~p=gJpq-KRaWEqsno2sv9VeZVao2SKiFcX%rfH0`5$dc#9suGX?%jr@9HJ)dTaMM1CfzmLv zBljajo}W=@Y?kPzo9^D*-~3#(j$V;&pS)-xH~G$N!)g8`5nZ2R>bp-Xl5}=&8B-AM z&GU=F?_W-5EufBTG7V=4sC){0;>$LVmP;=RoG?fYpR_ZnsUta5-hu(b%gq`tP4T&TPW!B`)SCD zU|gPyRmtR;^KozLLOj(;yx|wenu5MNgi05gD2j07y=Slsnv!MH-DPKGtP6MhM|le2 zd@5K`3$wm!k`kd>V@+pFpD^L(ak@-V^eAqWU_JEjm$lH6@Yt40+ulircjCX!gF+2{ zNPCxl_~J=6t%+khUgapNz{{!>RRnQ;qoBn_xS5`y`xY*fq=W};wa8~77-jL|E6N=q zX;)?@XTs$}E4YQbW@*!~HWc4)pFDC80W2tVjGP-w508EA9N^ zU6Reu*8_MfOfrY-O=8l=Uyt)zvV7*9$K{|VbQm9J=LAcLgTSnW=TrFLcVE3fzW2Y` zN3;peC(avulcmUH`T8Z>e)2{B8cwhVzNK@*C0csp*J!LQtKD@b&3+)C{9I)x!o1ED zhhZU6iN(t^`IpX=fmSGA4eMxH8PPw^K7&xilmB@1gW6}O)UdF#F`yh1z+Tl(?TD=B zyO@p6%|tf#EOTs?7S{B;v%|Jc4b1r@#J80nJsEphgFUJT4INiE%~iv>2>(8%=Yn zW8iBiNJWZr@sQMo1gZI@4ENmzPchkrPk_u2kco2i$a9pE0(9|j;RC~rEvYDLhpk(3XPGE6g%20t%)PQ8GE#mR`bA5K9x%$AtAN-;i!~G#(T-zk z|Euw^H-@S=VAkB(NF~ak77l^`%^Q%}Iz`rgHO_c%|1U4_EsH^b?f;ABJvfo&?`G2A z;|h;yiuc05y@XnjL2qqiO~zw2$B*YV#{@sghydBpMkh0A?d1B%7r9m+@JajgkDvTY zPr!)FRFrXefYbj*n5j-vQQ5-NxCt2N2(rb%I%9WiVsEn}D3=se0YtuGgc+u%UQBIv zdJCOJH!fywGBO(L#5#97Opmc&Oh9sDDdUr!#Husxpg8juzLO-Ase4u})3ewu94_c8 zx^PV@A|5ZcI8gX&#mfm9D(GT;PfssZI$K*?+6dQor-@Ge>eD{U3h0BVH1LiVSEU@U zS;mr3%5L_r6ObFTd%RdHV{Lr`wbN$Q)uxagv4PQ_>4HCqpt8Z_RxykA)NZJHIfj{U zO9MGzMEyBAy~ZP<`+9a1ETLmSj2LW@!$*ufUw=-`!@-MHoC5Z>69*XxWhO?^$^^2}f}IUVn;h{lB?&FkIbs|z zCTyr9UvcI5{Q{sMBdJ{VlZ?44n2NB zbVwT+`AIOB&+jz`2M67shXs${{Oq7(EkV70vtk=G5`P~hesvO>{aX0c{lfnplM*qs z)%ZBAu+{+&45`pQxMg_qwR`>r;G)iOftP`jmI@?_Vkaf z2OD*H0=TOl7M`5&`KKxbOEMQT0Q7R#TkJCesh6u};3UhHGv$%4u!>pl7bq8Nh;sw;P)?`QMkJ!fV=ge=Oycym|@p+|+7`dt^YRV9a zX7-oYwI&C&igY2`?N{<=UZCBh-^vQTwIri2hzsGI5eby^~}H-5pd-5l2t;KuNUHT0Lb z)pcl`oLoJXXCTiG36-X4`X8JcG1)X0iHKr@WJ>eftKTPR6SJC!&gDrUk2>+zUxS0= zbDF|{@i;c;KmWP9d%Q-!X5}D@KYXmzf7_Tt`uiCD``o?vQvW{j?MdV9b?3{UzubBM zBCg$y8*IkCW`xeiK(sI(vwppeE;fh!>3;_>zj#Q%WPg4+eS6g~-|YSR2H+IR3;Baz z+x?39cHC_Kv@g;1<|k1{bo%$S$IWF%yI7Um+XGt`MmmsSM(DI`ifZ(80UP8iHD?-q z?2m|i!?p#{0=c<<(ODNmZx1cJV4_I|>IUnKaJ|tPvBH-ZrI!J)Z8;jHKDcS4)(^HCZnqhd9B=9CdlkRUnMEi(^oZVqXa3;(XWX+ePR= zfs3`BnGIzC{dflg(zMCqV1NhN;{uGdG~WUZf(=bPCE!kT`umW#Ng%&k_x3yZ`TTZ* zF5nb()L>FYCK5wrQAF*sX7h3k14>Ws_Bi*>kz>O#wdEQanO$*i2CRrG|NPHy?b@|Fd$L}yzw{+9 zKJmm8AA0B^V-i>0O~;h+{OISN`+`$WK6z?t>KnJ+e&E1?OJ4o67hZS)0GzgY^EL0j z`dfG0aqqp~TMYRA)LtCg7vUXjhD<@|2^SeuJ8|7V-+IjH-7%+bdFb4CK7H7C(_wi0 zQ+J}i-#zZ!?q`4N1K)kq-?pjnQI=0XwrRFjz~Q>LM-LsG=V`l$Fs96zy7IcYL?eLmm+v}m)RwnKRiA6N~{F_$L z7JwxK7rl1r?3ec4agbiBJx*kTz;&<9-uMm{NeO}3bYRKPyl(02Kkd6?${RmNHL~*Y zWr7E$$RjFqrRg~Uy%I2)`C#T>3HQ5*W)_pqhWp!X4bkkQ>_rY}A1xK0v%9Yy94pV~ zi=--yPw|o#0v!4*0?w5oE!fB7OHQ7G#1WW?y^$T%14G&)A!{U=bjN2A&7xz76nk=NHS?!S-!lsdUbVl)VdWaNGwR^><&`Hf4od$f43sa6@7oEygP5{{m3}F{yQ1yd-3d)|TRia#|v`YsN)Te~vr-^xC z?@P2K(!0d^Dz>F$=AyXgzB_XP(cD7;XFpLwksk3>CM*iRyZbPD)NP{Z4(=lIy6916 z{QPbrR73KTDTiq0p6{KNoVR0YHERsa%_%;B6ujdJcCrt0JVqT2oq$p;Pyu@!hBhtvVty@g(CXT(vdIeS05^?dhRF(IX}~ z+|ovN9a#(D`s9@g9iLWC^!zT#ucqlP%DJCc2B4pE<3ye#zN@`FT|-{!MV@C+*qkrz z)iFqolu;iY3Hht;Q}J#X?57fQ$02^K3&`-m87#m^|18$wL2(1_4lYud#<0D+x{YdU zs87z?a0!~@#%+YX$)wDsGeXG8GqE7F_ucV>b|6QRjf|L)5qt2UG(Ho5Tfji``9}_a z=&4;pX$k<}uGIkG#5Dc$zWVFu8p^u{XJ_?czw@2DvS0S>*?Zh^#~yp^v8O-hIUoL` zKg|AdW@h#s?|j#{w{Bg(Zru%^{mh098(0v8wJ88>-n408U;qF%o^V3`*YDnS=jiBY z_Sp3F^p&rF(`zohB0}gK<+*E)1?ndX~?2Y~BkDTGNxKuX|oilD8+~--%%EqBnhI{|=`1Yx% zJ8>eKWn!;+`8@elU_NJ!#L#Qx&7)JZF^11O=JS8OW>fyX>dEj}eR!8x4mLu*q?f}UKyJh7@HV4 z`+~7|{BGmxpM2tf{rQ1M8m!DS?MbNjW(1jJbIyiEfdKIqVW(#qM+ z>M0kmeA8=3j$bnK_csGfp^>r7Ic;INYfX}XF{V-h!Lu~I2s?-@fn0YHHU@6V%hiVuBpi9>&6Yy{BzqP&AF>5>}8VfYfoF0G&@BG(K&Z-7t$c2 zJ?zSmK{B#Do^&5&l96z=s_mkyZ_sMyBXc^|kvX@T#_?jQVR3h;$Z?q@8by*|>!M`R z)qj3`R9&dpC;@#_UAL~bdUev@5ALm?dmhnm%E%lMNuhI@XbWv?7KY*EHrzkyJli7Z zD*9=H2~0Uy?+`8|K`#R1eV?^xxhYv-|em++1zR_hzo=E<%mv zTs8(A|G`_+Tvv%Ob3MTbIOGO%_KKH`Yj3F^C_OLu>NZV{8#~CiSXaZ7TFoX2G@BU< zisl{BnJ9?hOeT_TAr_2TQ-E0HxGQt&y^!AnNJDq;hro|mPjo#e+AiIvB;TIBBjfoy zlBely6NIv&m_XXfs_MzYuv?A^6Uxt+=7Mdikx>BM5>``u8F z*X1rJ&*LaFw~stMN9(IpoFLu?fIwHn?iq?G6R}M6G1Y^^J&6o*;~k-k4K`eNR9T~( z9?i8{uF46QxVX5AH-9$PBw_ZMGeVA#^x`TJC~2eK_r2^X&oyosSn{TJ`wc?4`3|rHX*wgNeE|Oc#v1@& zXn5$j-S_|YhpzpD3tsj!*IxTSHf-4No$uUr z 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%0A>sA543=f;(VLLe)HD_y+g$e_H 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%0D0{p3~82v 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�u* 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 Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +/// + +import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { JSX as JSX_2 } from 'react'; +import { default as React_2 } from 'react'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { SvgIconProps } from '@material-ui/core'; + +// @public +export const OrchestratorIcon: (props: SvgIconProps) => React_2.JSX.Element; + +// @public +export const OrchestratorPage: () => JSX_2.Element; + +// @public +export const orchestratorPlugin: BackstagePlugin< { +root: RouteRef; +}, {}, {}>; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/workspaces/orchestrator/plugins/orchestrator/src/api/OrchestratorClient.test.ts b/workspaces/orchestrator/plugins/orchestrator/src/api/OrchestratorClient.test.ts new file mode 100644 index 00000000..9eb1bdf8 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/api/OrchestratorClient.test.ts @@ -0,0 +1,627 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; +import type { JsonObject } from '@backstage/types'; + +import axios, { + AxiosRequestConfig, + AxiosResponse, + InternalAxiosRequestConfig, + RawAxiosResponseHeaders, +} from 'axios'; + +import { + AssessedProcessInstanceDTO, + DefaultApi, + ExecuteWorkflowResponseDTO, + PaginationInfoDTO, + ProcessInstanceListResultDTO, + QUERY_PARAM_INCLUDE_ASSESSMENT, + WorkflowFormatDTO, + WorkflowOverviewDTO, + WorkflowOverviewListResultDTO, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import { + OrchestratorClient, + OrchestratorClientOptions, +} from './OrchestratorClient'; + +jest.mock('axios'); + +describe('OrchestratorClient', () => { + let mockDiscoveryApi: jest.Mocked; + let mockIdentityApi: jest.Mocked; + let orchestratorClientOptions: jest.Mocked; + let orchestratorClient: OrchestratorClient; + const baseUrl = 'https://api.example.com'; + const mockToken = 'test-token'; + const defaultAuthHeaders = { Authorization: `Bearer ${mockToken}` }; + + const mockFetch = jest.fn(); + (global as any).fetch = mockFetch; // Cast global to any to avoid TypeScript errors + + beforeEach(() => { + jest.clearAllMocks(); + // Create a mock DiscoveryApi with a mocked implementation of getBaseUrl + mockDiscoveryApi = { + getBaseUrl: jest.fn().mockResolvedValue(baseUrl), + } as jest.Mocked; + mockIdentityApi = { + getCredentials: jest.fn().mockResolvedValue({ token: mockToken }), + getProfileInfo: jest + .fn() + .mockResolvedValue({ displayName: 'test', email: 'test@test' }), + getBackstageIdentity: jest + .fn() + .mockResolvedValue({ userEntityRef: 'default/test' }), + signOut: jest.fn().mockImplementation(), + } as jest.Mocked; + + // Create OrchestratorClientOptions with the mocked DiscoveryApi + orchestratorClientOptions = { + discoveryApi: mockDiscoveryApi, + identityApi: mockIdentityApi, + axiosInstance: axios, + }; + orchestratorClient = new OrchestratorClient(orchestratorClientOptions); + }); + + describe('executeWorkflow', () => { + const workflowId = 'workflow123'; + + const setupTest = ( + executionId: string, + parameters: JsonObject, + businessKey?: string, + ) => { + const mockExecResponse: ExecuteWorkflowResponseDTO = { id: executionId }; + const mockResponse: AxiosResponse = { + data: mockExecResponse, + status: 200, + statusText: 'OK', + headers: {} as RawAxiosResponseHeaders, + config: {} as InternalAxiosRequestConfig, + }; + + const executeWorkflowSpy = jest.spyOn( + DefaultApi.prototype, + 'executeWorkflow', + ); + axios.request = jest.fn().mockResolvedValueOnce(mockResponse); + + const args = { workflowId, parameters, businessKey }; + + return { mockExecResponse, executeWorkflowSpy, args }; + }; + + const getExpectations = ( + result: any, + mockExecResponse: ExecuteWorkflowResponseDTO, + executeWorkflowSpy: jest.SpyInstance, + parameters: JsonObject, + ) => { + return () => { + expect(result).toBeDefined(); + expect(result.data).toEqual(mockExecResponse); + expect(axios.request).toHaveBeenCalledTimes(1); + expect(axios.request).toHaveBeenCalledWith({ + ...getAxiosTestRequest(`/v2/workflows/${workflowId}/execute`), + data: JSON.stringify({ inputData: parameters }), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...defaultAuthHeaders, + }, + }); + expect(executeWorkflowSpy).toHaveBeenCalledTimes(1); + expect(executeWorkflowSpy).toHaveBeenCalledWith( + workflowId, + { inputData: parameters }, + getDefaultTestRequestConfig(), + ); + }; + }; + + it('should execute workflow with empty parameters', async () => { + // Given + const { mockExecResponse, executeWorkflowSpy, args } = setupTest( + 'execId001', + {}, + ); + + // When + const result = await orchestratorClient.executeWorkflow(args); + + // Then + expect( + getExpectations(result, mockExecResponse, executeWorkflowSpy, {}), + ).not.toThrow(); + }); + it('should execute workflow with business key', async () => { + // Given + const { mockExecResponse, executeWorkflowSpy, args } = setupTest( + 'execId001', + {}, + 'business123', + ); + + const result = await orchestratorClient.executeWorkflow(args); + + expect( + getExpectations(result, mockExecResponse, executeWorkflowSpy, {}), + ).not.toThrow(); + }); + it('should execute workflow with parameters and business key', async () => { + // Given + const parameters = { param1: 'one', param2: 2, param3: true }; + const { mockExecResponse, executeWorkflowSpy, args } = setupTest( + 'execId001', + parameters, + 'business123', + ); + + const result = await orchestratorClient.executeWorkflow(args); + + expect( + getExpectations( + result, + mockExecResponse, + executeWorkflowSpy, + parameters, + ), + ).not.toThrow(); + }); + }); + describe('abortWorkflow', () => { + it('should abort a workflow instance successfully', async () => { + // Given + const instanceId = 'instance123'; + + const mockResponse: AxiosResponse = { + data: instanceId, + status: 200, + statusText: 'OK', + headers: {} as RawAxiosResponseHeaders, + config: {} as InternalAxiosRequestConfig, + }; + + const abortWorkflowSpy = jest.spyOn( + DefaultApi.prototype, + 'abortWorkflow', + ); + axios.request = jest.fn().mockResolvedValueOnce(mockResponse); + // When + const result = await orchestratorClient.abortWorkflowInstance(instanceId); + + // Then + expect(result).toBeDefined(); + expect(result.data).toEqual(instanceId); + expect(axios.request).toHaveBeenCalledTimes(1); + expect(axios.request).toHaveBeenCalledWith({ + ...getAxiosTestRequest(`/v2/workflows/instances/${instanceId}/abort`), + method: 'DELETE', + headers: { + ...defaultAuthHeaders, + }, + }); + expect(abortWorkflowSpy).toHaveBeenCalledTimes(1); + expect(abortWorkflowSpy).toHaveBeenCalledWith( + instanceId, + getDefaultTestRequestConfig(), + ); + }); + + it('should throw a ResponseError if aborting the workflow instance fails', async () => { + // Given + const instanceId = 'instance123'; + + // Mock fetch to simulate a failure + axios.request = jest + .fn() + .mockRejectedValueOnce(new Error('Simulated error')); + // When + const promise = orchestratorClient.abortWorkflowInstance(instanceId); + + // Then + await expect(promise).rejects.toThrow(); + }); + }); + describe('getWorkflowSource', () => { + it('should return workflow source when successful', async () => { + // Given + const workflowId = 'workflow123'; + const mockWorkflowSource = 'test workflow source'; + const responseConfigOptions = getDefaultTestRequestConfig(); + responseConfigOptions.responseType = 'text'; + const mockResponse: AxiosResponse = { + data: mockWorkflowSource, + status: 200, + statusText: 'OK', + headers: {} as RawAxiosResponseHeaders, + config: {} as InternalAxiosRequestConfig, + }; + // Mock axios request to simulate a successful response + jest.spyOn(axios, 'request').mockResolvedValueOnce(mockResponse); + + // Spy DefaultApi + const getSourceSpy = jest.spyOn( + DefaultApi.prototype, + 'getWorkflowSourceById', + ); + + // When + const result = await orchestratorClient.getWorkflowSource(workflowId); + + // Then + expect(result).toBeDefined(); + expect(result.data).toEqual(mockWorkflowSource); + expect(axios.request).toHaveBeenCalledTimes(1); + expect(axios.request).toHaveBeenCalledWith({ + ...getAxiosTestRequest(`/v2/workflows/${workflowId}/source`), + method: 'GET', + headers: { + ...defaultAuthHeaders, + }, + responseType: 'text', + }); + expect(getSourceSpy).toHaveBeenCalledTimes(1); + expect(getSourceSpy).toHaveBeenCalledWith( + workflowId, + responseConfigOptions, + ); + }); + + it('should throw a ResponseError when fetching the workflow source fails', async () => { + // Given + const workflowId = 'workflow123'; + + // Mock fetch to simulate a failure + axios.request = jest + .fn() + .mockRejectedValueOnce(new Error('Simulated error')); + // When + const promise = orchestratorClient.getWorkflowSource(workflowId); + + // Then + await expect(promise).rejects.toThrow(); + }); + }); + describe('listWorkflowOverviews', () => { + it('should return workflow overviews when successful', async () => { + // Given + const paginationInfo: PaginationInfoDTO = { + offset: 1, + pageSize: 5, + orderBy: 'name', + orderDirection: 'ASC', + }; + const mockWorkflowOverviews: WorkflowOverviewListResultDTO = { + overviews: [ + { + workflowId: 'workflow123', + name: 'Workflow 1', + format: WorkflowFormatDTO.Yaml, + }, + { + workflowId: 'workflow456', + name: 'Workflow 2', + format: WorkflowFormatDTO.Yaml, + }, + ], + paginationInfo: paginationInfo, + }; + + const mockResponse: AxiosResponse = { + data: mockWorkflowOverviews, + status: 200, + statusText: 'OK', + headers: {} as RawAxiosResponseHeaders, + config: {} as InternalAxiosRequestConfig, + }; + + // Spy DefaultApi + const getWorkflowsOverviewSpy = jest.spyOn( + DefaultApi.prototype, + 'getWorkflowsOverview', + ); + + // Mock axios request to simulate a successful response + jest.spyOn(axios, 'request').mockResolvedValueOnce(mockResponse); + + // When + const result = await orchestratorClient.listWorkflowOverviews( + paginationInfo, + ); + + // Then + expect(result).toBeDefined(); + expect(result.data).toEqual(mockWorkflowOverviews); + expect(axios.request).toHaveBeenCalledTimes(1); + expect(axios.request).toHaveBeenCalledWith({ + ...getAxiosTestRequest('v2/workflows/overview'), + data: JSON.stringify({ paginationInfo }), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...defaultAuthHeaders, + }, + }); + expect(getWorkflowsOverviewSpy).toHaveBeenCalledTimes(1); + expect(getWorkflowsOverviewSpy).toHaveBeenCalledWith( + { paginationInfo }, + getDefaultTestRequestConfig(), + ); + }); + it('should throw a ResponseError when listing workflow overviews fails', async () => { + // Given + + // Mock fetch to simulate a failure + axios.request = jest + .fn() + .mockRejectedValueOnce(new Error('Simulated error')); + + // When + const promise = orchestratorClient.listWorkflowOverviews(); + + // Then + await expect(promise).rejects.toThrow(); + }); + }); + describe('listInstances', () => { + it('should return instances when successful', async () => { + // Given + const paginationInfo: PaginationInfoDTO = { + offset: 1, + pageSize: 5, + orderBy: 'name', + orderDirection: 'ASC', + }; + + const mockInstances: ProcessInstanceListResultDTO = { + items: [{ id: 'instance123', processId: 'process001', nodes: [] }], + paginationInfo, + }; + + const mockResponse: AxiosResponse = { + data: mockInstances, + status: 200, + statusText: 'OK', + headers: {} as RawAxiosResponseHeaders, + config: {} as InternalAxiosRequestConfig, + }; + + // Spy DefaultApi + const getInstancesSpy = jest.spyOn(DefaultApi.prototype, 'getInstances'); + + // Mock axios request to simulate a successful response + jest.spyOn(axios, 'request').mockResolvedValueOnce(mockResponse); + + // When + const result = await orchestratorClient.listInstances({ paginationInfo }); + // Then + expect(result).toBeDefined(); + expect(result.data).toEqual(mockInstances); + expect(axios.request).toHaveBeenCalledTimes(1); + expect(axios.request).toHaveBeenCalledWith({ + ...getAxiosTestRequest('v2/workflows/instances'), + data: JSON.stringify({ paginationInfo }), + headers: { + 'Content-Type': 'application/json', + ...defaultAuthHeaders, + }, + method: 'POST', + }); + expect(getInstancesSpy).toHaveBeenCalledTimes(1); + expect(getInstancesSpy).toHaveBeenCalledWith( + { paginationInfo }, + getDefaultTestRequestConfig(), + ); + }); + + it('should throw a ResponseError when listing instances fails', async () => { + // Given + axios.request = jest + .fn() + .mockRejectedValueOnce(new Error('Simulated error')); + // When + const promise = orchestratorClient.listInstances({}); + + // Then + await expect(promise).rejects.toThrow(); + }); + }); + describe('getInstance', () => { + it('should return instance when successful', async () => { + // Given + const instanceId = 'instance123'; + const instanceIdParent = 'instance000'; + const includeAssessment = false; + const mockInstance: AssessedProcessInstanceDTO = { + instance: { id: instanceId, processId: 'process002', nodes: [] }, + assessedBy: { + id: instanceIdParent, + processId: 'process001', + nodes: [], + }, + }; + + const mockResponse: AxiosResponse = { + data: mockInstance, + status: 200, + statusText: 'OK', + headers: {} as RawAxiosResponseHeaders, + config: {} as InternalAxiosRequestConfig, + }; + // Mock axios request to simulate a successful response + jest.spyOn(axios, 'request').mockResolvedValueOnce(mockResponse); + + // Spy DefaultApi + const getInstanceSpy = jest.spyOn( + DefaultApi.prototype, + 'getInstanceById', + ); + // When + const result = await orchestratorClient.getInstance( + instanceId, + includeAssessment, + ); + + // Then + expect(result).toBeDefined(); + expect(result.data).toEqual(mockInstance); + expect(axios.request).toHaveBeenCalledTimes(1); + expect(axios.request).toHaveBeenCalledWith( + getAxiosTestRequest( + `v2/workflows/instances/${instanceId}`, + includeAssessment, + ), + ); + expect(getInstanceSpy).toHaveBeenCalledTimes(1); + expect(getInstanceSpy).toHaveBeenCalledWith( + instanceId, + includeAssessment, + getDefaultTestRequestConfig(), + ); + }); + + it('should throw a ResponseError when fetching the instance fails', async () => { + // Given + const instanceId = 'instance123'; + + axios.request = jest + .fn() + .mockRejectedValueOnce(new Error('Simulated error')); + // When + const promise = orchestratorClient.getInstance(instanceId); + + // Then + await expect(promise).rejects.toThrow(); + }); + }); + describe('getWorkflowOverview', () => { + it('should return workflow overview when successful', async () => { + // Given + const workflowId = 'workflow123'; + const mockOverview = { + workflowId: workflowId, + name: 'Workflow 1', + format: WorkflowFormatDTO.Yaml, + }; + + const mockResponse: AxiosResponse = { + data: mockOverview, + status: 200, + statusText: 'OK', + headers: {} as RawAxiosResponseHeaders, + config: {} as InternalAxiosRequestConfig, + }; + + // Spy DefaultApi + const getWorkflowOverviewByIdSpy = jest.spyOn( + DefaultApi.prototype, + 'getWorkflowOverviewById', + ); + + // Mock axios request to simulate a successful response + jest.spyOn(axios, 'request').mockResolvedValueOnce(mockResponse); + + // When + const result = await orchestratorClient.getWorkflowOverview(workflowId); + + // Then + expect(result).toBeDefined(); + expect(result.data).toEqual(mockOverview); + expect(axios.request).toHaveBeenCalledTimes(1); + expect(axios.request).toHaveBeenCalledWith( + getAxiosTestRequest(`v2/workflows/${workflowId}/overview`), + ); + expect(getWorkflowOverviewByIdSpy).toHaveBeenCalledTimes(1); + expect(getWorkflowOverviewByIdSpy).toHaveBeenCalledWith( + workflowId, + getDefaultTestRequestConfig(), + ); + }); + + it('should throw a ResponseError when fetching the workflow overview fails', async () => { + // Given + const workflowId = 'workflow123'; + + // Given + // Mock fetch to simulate a failure + axios.request = jest + .fn() + .mockRejectedValueOnce(new Error('Simulated error')); + + // When + const promise = orchestratorClient.getWorkflowOverview(workflowId); + + // Then + await expect(promise).rejects.toThrow(); + }); + }); + function getDefaultTestRequestConfig(): AxiosRequestConfig { + return { + baseURL: baseUrl, + headers: { Authorization: `Bearer ${mockToken}` }, + }; + } + + function getAxiosTestRequest( + endpoint: string, + includeAssessment?: boolean, + paginationInfo?: PaginationInfoDTO, + method: string = 'GET', + ): AxiosRequestConfig { + const req = getDefaultTestRequestConfig(); + + return { + ...req, + method, + url: buildURLWithPagination(endpoint, includeAssessment, paginationInfo), + }; + } + + function buildURLWithPagination( + endpoint: string, + includeAssessment?: boolean, + paginationInfo?: PaginationInfoDTO, + ): string { + const url = new URL(endpoint, baseUrl); + if (includeAssessment !== undefined) { + url.searchParams.append( + QUERY_PARAM_INCLUDE_ASSESSMENT, + String(includeAssessment), + ); + } + if (paginationInfo?.offset !== undefined) { + url.searchParams.append('page', paginationInfo.offset.toString()); + } + + if (paginationInfo?.pageSize !== undefined) { + url.searchParams.append('pageSize', paginationInfo.pageSize.toString()); + } + + if (paginationInfo?.orderBy !== undefined) { + url.searchParams.append('orderBy', paginationInfo.orderBy); + } + + if (paginationInfo?.orderDirection !== undefined) { + url.searchParams.append('orderDirection', paginationInfo.orderDirection); + } + return url.toString(); + } +}); diff --git a/workspaces/orchestrator/plugins/orchestrator/src/api/OrchestratorClient.ts b/workspaces/orchestrator/plugins/orchestrator/src/api/OrchestratorClient.ts new file mode 100644 index 00000000..3d1d8ebe --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/api/OrchestratorClient.ts @@ -0,0 +1,243 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; +import type { JsonObject } from '@backstage/types'; + +import axios, { + AxiosInstance, + AxiosRequestConfig, + AxiosResponse, + isAxiosError, + RawAxiosRequestHeaders, +} from 'axios'; + +import { + AssessedProcessInstanceDTO, + Configuration, + DefaultApi, + ExecuteWorkflowResponseDTO, + Filter, + GetInstancesRequest, + InputSchemaResponseDTO, + PaginationInfoDTO, + ProcessInstanceListResultDTO, + WorkflowOverviewDTO, + WorkflowOverviewListResultDTO, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import { OrchestratorApi } from './api'; + +const getError = (err: unknown): Error => { + if ( + isAxiosError<{ error: { message: string; name: string } }>(err) && + err.response?.data?.error?.message + ) { + const error = new Error(err.response?.data?.error?.message); + error.name = err.response?.data?.error?.name || 'Error'; + return error; + } + return err as Error; +}; + +export interface OrchestratorClientOptions { + discoveryApi: DiscoveryApi; + identityApi: IdentityApi; + axiosInstance?: AxiosInstance; +} +export class OrchestratorClient implements OrchestratorApi { + private readonly discoveryApi: DiscoveryApi; + private readonly identityApi: IdentityApi; + private axiosInstance?: AxiosInstance; + + private baseUrl: string | null = null; + constructor(options: OrchestratorClientOptions) { + this.discoveryApi = options.discoveryApi; + this.identityApi = options.identityApi; + this.axiosInstance = options.axiosInstance; + } + + async getDefaultAPI(): Promise { + const baseUrl = await this.getBaseUrl(); + const { token: idToken } = await this.identityApi.getCredentials(); + + // Fixme: Following makes mocking of global axios complicated in the tests, ideally there should be just one axios instance: + this.axiosInstance = + this.axiosInstance || + axios.create({ + baseURL: baseUrl, + headers: { + ...(idToken && { Authorization: `Bearer ${idToken}` }), + }, + withCredentials: true, + }); + const config = new Configuration({ + basePath: baseUrl, + }); + + return new DefaultApi(config, baseUrl, this.axiosInstance); + } + private async getBaseUrl(): Promise { + if (!this.baseUrl) { + this.baseUrl = await this.discoveryApi.getBaseUrl('orchestrator'); + } + + return this.baseUrl; + } + + async executeWorkflow(args: { + workflowId: string; + parameters: JsonObject; + businessKey?: string; + }): Promise> { + const defaultApi = await this.getDefaultAPI(); + const reqConfigOption: AxiosRequestConfig = + await this.getDefaultReqConfig(); + try { + return await defaultApi.executeWorkflow( + args.workflowId, + { inputData: args.parameters }, + reqConfigOption, + ); + } catch (err) { + throw getError(err); + } + } + + async abortWorkflowInstance( + instanceId: string, + ): Promise> { + const defaultApi = await this.getDefaultAPI(); + const reqConfigOption: AxiosRequestConfig = + await this.getDefaultReqConfig(); + try { + return await defaultApi.abortWorkflow(instanceId, reqConfigOption); + } catch (err) { + throw getError(err); + } + } + + async getWorkflowSource(workflowId: string): Promise> { + const defaultApi = await this.getDefaultAPI(); + const reqConfigOption: AxiosRequestConfig = + await this.getDefaultReqConfig(); + reqConfigOption.responseType = 'text'; + try { + return await defaultApi.getWorkflowSourceById( + workflowId, + reqConfigOption, + ); + } catch (err) { + throw getError(err); + } + } + + async listWorkflowOverviews( + paginationInfo?: PaginationInfoDTO, + filters?: Filter, + ): Promise> { + const defaultApi = await this.getDefaultAPI(); + const reqConfigOption: AxiosRequestConfig = + await this.getDefaultReqConfig(); + try { + return await defaultApi.getWorkflowsOverview( + { paginationInfo, filters }, + reqConfigOption, + ); + } catch (err) { + throw getError(err); + } + } + + async listInstances( + args: GetInstancesRequest, + ): Promise> { + const defaultApi = await this.getDefaultAPI(); + const reqConfigOption: AxiosRequestConfig = + await this.getDefaultReqConfig(); + try { + return await defaultApi.getInstances(args, reqConfigOption); + } catch (err) { + throw getError(err); + } + } + + async getInstance( + instanceId: string, + includeAssessment = false, + ): Promise> { + const defaultApi = await this.getDefaultAPI(); + const reqConfigOption: AxiosRequestConfig = + await this.getDefaultReqConfig(); + try { + return await defaultApi.getInstanceById( + instanceId, + includeAssessment, + reqConfigOption, + ); + } catch (err) { + throw getError(err); + } + } + + async getWorkflowDataInputSchema( + workflowId: string, + instanceId?: string, + ): Promise> { + const defaultApi = await this.getDefaultAPI(); + const reqConfigOption: AxiosRequestConfig = + await this.getDefaultReqConfig(); + try { + return await defaultApi.getWorkflowInputSchemaById( + workflowId, + instanceId, + reqConfigOption, + ); + } catch (err) { + throw getError(err); + } + } + + async getWorkflowOverview( + workflowId: string, + ): Promise> { + const defaultApi = await this.getDefaultAPI(); + const reqConfigOption: AxiosRequestConfig = + await this.getDefaultReqConfig(); + try { + return await defaultApi.getWorkflowOverviewById( + workflowId, + reqConfigOption, + ); + } catch (err) { + throw getError(err); + } + } + + // getDefaultReqConfig is a convenience wrapper that includes authentication and other necessary headers + private async getDefaultReqConfig( + additionalHeaders?: RawAxiosRequestHeaders, + ): Promise { + const idToken = await this.identityApi.getCredentials(); + const reqConfigOption: AxiosRequestConfig = { + baseURL: await this.getBaseUrl(), + headers: { + Authorization: `Bearer ${idToken.token}`, + ...additionalHeaders, + }, + }; + return reqConfigOption; + } +} diff --git a/workspaces/orchestrator/plugins/orchestrator/src/api/api.ts b/workspaces/orchestrator/plugins/orchestrator/src/api/api.ts new file mode 100644 index 00000000..472f130a --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/api/api.ts @@ -0,0 +1,67 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createApiRef } from '@backstage/core-plugin-api'; +import type { JsonObject } from '@backstage/types'; + +import { AxiosResponse } from 'axios'; + +import { + AssessedProcessInstanceDTO, + ExecuteWorkflowResponseDTO, + GetInstancesRequest, + InputSchemaResponseDTO, + ProcessInstanceListResultDTO, + WorkflowOverviewDTO, + WorkflowOverviewListResultDTO, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +export interface OrchestratorApi { + abortWorkflowInstance(instanceId: string): Promise>; + + executeWorkflow(args: { + workflowId: string; + parameters: JsonObject; + businessKey?: string; + }): Promise>; + + getWorkflowSource(workflowId: string): Promise>; + + getInstance( + instanceId: string, + includeAssessment: boolean, + ): Promise>; + + getWorkflowDataInputSchema( + workflowId: string, + instanceId?: string, + ): Promise>; + + getWorkflowOverview( + workflowId: string, + ): Promise>; + + listWorkflowOverviews(): Promise< + AxiosResponse + >; + + listInstances( + args?: GetInstancesRequest, + ): Promise>; +} + +export const orchestratorApiRef = createApiRef({ + id: 'plugin.orchestrator.api', +}); diff --git a/workspaces/orchestrator/plugins/orchestrator/src/api/index.ts b/workspaces/orchestrator/plugins/orchestrator/src/api/index.ts new file mode 100644 index 00000000..e1d7b02f --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/api/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { OrchestratorClient } from './OrchestratorClient'; +export type { OrchestratorClientOptions } from './OrchestratorClient'; +export { orchestratorApiRef } from './api'; diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/BaseOrchestratorPage.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/BaseOrchestratorPage.tsx new file mode 100644 index 00000000..21c62e92 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/components/BaseOrchestratorPage.tsx @@ -0,0 +1,47 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { PropsWithChildren } from 'react'; + +import { Content, Header, Page } from '@backstage/core-components'; + +export interface BaseOrchestratorProps { + title?: string; + subtitle?: string; + type?: string; + typeLink?: string; + noPadding?: boolean; +} + +export const BaseOrchestratorPage = ({ + title, + subtitle, + type, + typeLink, + noPadding, + children, +}: PropsWithChildren) => { + return ( + +

+ {children} + + ); +}; diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/ExecuteWorkflowPage.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/ExecuteWorkflowPage.tsx new file mode 100644 index 00000000..3254a121 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/ExecuteWorkflowPage.tsx @@ -0,0 +1,151 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { useCallback, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useAsync } from 'react-use'; + +import { + InfoCard, + Progress, + ResponseErrorPanel, + useQueryParamState, +} from '@backstage/core-components'; +import { + useApi, + useRouteRef, + useRouteRefParams, +} from '@backstage/core-plugin-api'; +import type { JsonObject } from '@backstage/types'; + +import { Grid } from '@material-ui/core'; + +import { + InputSchemaResponseDTO, + QUERY_PARAM_ASSESSMENT_INSTANCE_ID, + QUERY_PARAM_INSTANCE_ID, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; +import { OrchestratorForm } from '@red-hat-developer-hub/backstage-plugin-orchestrator-form-react'; + +import { orchestratorApiRef } from '../../api'; +import { + executeWorkflowRouteRef, + workflowInstanceRouteRef, +} from '../../routes'; +import { getErrorObject } from '../../utils/ErrorUtils'; +import { BaseOrchestratorPage } from '../BaseOrchestratorPage'; +import JsonTextAreaForm from './JsonTextAreaForm'; + +export const ExecuteWorkflowPage = () => { + const orchestratorApi = useApi(orchestratorApiRef); + const { workflowId } = useRouteRefParams(executeWorkflowRouteRef); + const [isExecuting, setIsExecuting] = useState(false); + const [updateError, setUpdateError] = React.useState(); + const [instanceId] = useQueryParamState(QUERY_PARAM_INSTANCE_ID); + const [assessmentInstanceId] = useQueryParamState( + QUERY_PARAM_ASSESSMENT_INSTANCE_ID, + ); + const navigate = useNavigate(); + const instanceLink = useRouteRef(workflowInstanceRouteRef); + const { + value, + loading, + error: responseError, + } = useAsync(async (): Promise => { + const res = await orchestratorApi.getWorkflowDataInputSchema( + workflowId, + assessmentInstanceId || instanceId, + ); + return res.data; + }, [orchestratorApi, workflowId]); + const schema = value?.inputSchema; + const data = value?.data; + const { + value: workflowName, + loading: workflowNameLoading, + error: workflowNameError, + } = useAsync(async (): Promise => { + const res = await orchestratorApi.getWorkflowOverview(workflowId); + return res.data.name || ''; + }, [orchestratorApi, workflowId]); + + const handleExecute = useCallback( + async (parameters: JsonObject) => { + setUpdateError(undefined); + try { + setIsExecuting(true); + const response = await orchestratorApi.executeWorkflow({ + workflowId, + parameters, + businessKey: assessmentInstanceId, + }); + navigate(instanceLink({ instanceId: response.data.id })); + } catch (err) { + setUpdateError(getErrorObject(err)); + } finally { + setIsExecuting(false); + } + }, + [orchestratorApi, workflowId, navigate, instanceLink, assessmentInstanceId], + ); + + const error = responseError || workflowNameError; + let pageContent; + + if (loading || workflowNameLoading) { + pageContent = ; + } else if (error) { + pageContent = ; + } else { + pageContent = ( + + {updateError && ( + + + + )} + + + {!!schema ? ( + + ) : ( + + )} + + + + ); + } + + return ( + + {pageContent} + + ); +}; diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/JsonTextAreaForm.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/JsonTextAreaForm.tsx new file mode 100644 index 00000000..f2edc3b5 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/JsonTextAreaForm.tsx @@ -0,0 +1,83 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; + +import type { JsonObject } from '@backstage/types'; + +import { Box, Grid, useTheme } from '@material-ui/core'; +import { Alert, AlertTitle } from '@material-ui/lab'; +import { Editor } from '@monaco-editor/react'; + +import { SubmitButton } from '@red-hat-developer-hub/backstage-plugin-orchestrator-form-react'; + +const DEFAULT_VALUE = JSON.stringify({ myKey: 'myValue' }, null, 4); + +const JsonTextAreaForm = ({ + isExecuting, + handleExecute, +}: { + isExecuting: boolean; + handleExecute: (parameters: JsonObject) => Promise; +}) => { + const [jsonText, setJsonText] = React.useState(DEFAULT_VALUE); + const theme = useTheme(); + const getParameters = (): JsonObject => { + if (!jsonText) { + return {}; + } + const parameters = JSON.parse(jsonText); + return parameters as JsonObject; + }; + + return ( + + + + Missing JSON Schema for Input Form. + Type the input data in JSON format below. +
+ If you prefer using a form to start the workflow, ensure a valid JSON + schema is provided in the 'dataInputSchema' property of your workflow + definition file. +
+
+ + + setJsonText(value ?? '')} + height="30rem" + theme={theme.palette.type === 'dark' ? 'vs-dark' : 'light'} + options={{ + minimap: { enabled: false }, + }} + /> + + + + handleExecute(getParameters())} + > + Run + + +
+ ); +}; + +export default JsonTextAreaForm; diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/InfoDialog.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/InfoDialog.tsx new file mode 100644 index 00000000..d4452899 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/components/InfoDialog.tsx @@ -0,0 +1,77 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { forwardRef, ForwardRefRenderFunction } from 'react'; + +import { + Box, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, + makeStyles, + Typography, +} from '@material-ui/core'; +import CloseIcon from '@material-ui/icons/Close'; + +export type InfoDialogProps = { + title: string; + open: boolean; + onClose?: () => void; + dialogActions?: React.ReactNode; + children?: React.ReactNode; +}; + +export type ParentComponentRef = HTMLElement; + +const useStyles = makeStyles(_theme => ({ + closeBtn: { + position: 'absolute', + right: 8, + top: 8, + }, +})); + +export const RefForwardingInfoDialog: ForwardRefRenderFunction< + ParentComponentRef, + InfoDialogProps +> = (props, forwardedRef): JSX.Element | null => { + const { title, open = false, onClose, children, dialogActions } = props; + const classes = useStyles(); + + return ( + onClose} open={open} ref={forwardedRef}> + + + {title} + + + + + + + {children} + + {dialogActions} + + ); +}; + +export const InfoDialog = forwardRef(RefForwardingInfoDialog); diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/OrchestratorIcon.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/OrchestratorIcon.tsx new file mode 100644 index 00000000..fd6709fa --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/components/OrchestratorIcon.tsx @@ -0,0 +1,32 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; + +import { SvgIcon, SvgIconProps } from '@material-ui/core'; + +/** + * @public + * Orchestrator icon + */ +const OrchestratorIcon = (props: SvgIconProps) => ( + + + + + +); + +export default OrchestratorIcon; diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/OrchestratorPage.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/OrchestratorPage.tsx new file mode 100644 index 00000000..ea9eacca --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/components/OrchestratorPage.tsx @@ -0,0 +1,41 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; + +import { TabbedLayout } from '@backstage/core-components'; + +import { workflowInstancesRouteRef } from '../routes'; +import { BaseOrchestratorPage } from './BaseOrchestratorPage'; +import { WorkflowRunsTabContent } from './WorkflowRunsTabContent'; +import { WorkflowsTabContent } from './WorkflowsTabContent'; + +export const OrchestratorPage = () => { + return ( + + + + + + + + + + + ); +}; diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/Paragraph.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/Paragraph.tsx new file mode 100644 index 00000000..482a856e --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/components/Paragraph.tsx @@ -0,0 +1,33 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { PropsWithChildren } from 'react'; + +import { Typography } from '@material-ui/core'; +import { Variant } from '@material-ui/core/styles/createTypography'; + +export const Paragraph = ( + props: PropsWithChildren<{ variant?: Variant | 'inherit' }>, +) => { + return ( + + {props.children} + + ); +}; diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/Router.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/Router.tsx new file mode 100644 index 00000000..db1b8908 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/components/Router.tsx @@ -0,0 +1,47 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { Route, Routes } from 'react-router-dom'; + +import { + executeWorkflowRouteRef, + workflowDefinitionsRouteRef, + workflowInstanceRouteRef, +} from '../routes'; +import { ExecuteWorkflowPage } from './ExecuteWorkflowPage/ExecuteWorkflowPage'; +import { OrchestratorPage } from './OrchestratorPage'; +import { WorkflowDefinitionViewerPage } from './WorkflowDefinitionViewerPage'; +import { WorkflowInstancePage } from './WorkflowInstancePage'; + +export const Router = () => { + return ( + + } /> + } + /> + } + /> + } + /> + + ); +}; diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/Selector.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/Selector.tsx new file mode 100644 index 00000000..39b08ae9 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/components/Selector.tsx @@ -0,0 +1,84 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; + +import { Select, SelectedItems } from '@backstage/core-components'; + +import { makeStyles, Typography } from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + root: { + display: 'flex', + alignItems: 'baseline', + '& label + div': { + marginTop: '0px', + }, + }, + select: { + width: '10rem', + }, + label: { + color: theme.palette.text.primary, + fontSize: theme.typography.fontSize, + paddingRight: '0.5rem', + fontWeight: 'bold', + }, +})); + +const ALL_ITEMS = '___all___'; + +type BackstageSelectProps = Parameters[0]; +export type SelectorProps = Omit & { + includeAll?: boolean; + onChange: (item: string) => void; +}; + +export const Selector = ({ + includeAll = true, + ...otherProps +}: SelectorProps) => { + const styles = useStyles(); + + const selectItems = React.useMemo( + () => + includeAll + ? [{ label: 'All', value: ALL_ITEMS }, ...otherProps.items] + : otherProps.items, + [includeAll, otherProps.items], + ); + + const handleChange = React.useCallback( + (item: SelectedItems) => otherProps.onChange(item as string), + [otherProps], + ); + + return ( +
+ {otherProps.label} +
+