From bae425c7a92fd9e2d4b95ebb0fd8ea77fb9e9926 Mon Sep 17 00:00:00 2001 From: Matt Dodson <47385188+MattDodsonEnglish@users.noreply.github.com> Date: Tue, 2 Dec 2025 10:45:38 -0300 Subject: [PATCH 01/12] [Refactor] move 3.2 to new dir --- content/versions/v3.2.1/_index.md | 30 + content/versions/v3.2.1/deploy/_index.md | 38 + .../versions/v3.2.1/deploy/backup/_index.md | 21 + .../versions/v3.2.1/deploy/backup/audit.md | 52 + .../versions/v3.2.1/deploy/backup/binary.md | 79 ++ .../versions/v3.2.1/deploy/backup/grafana.md | 105 ++ .../versions/v3.2.1/deploy/backup/graphdb.md | 134 ++ .../versions/v3.2.1/deploy/backup/influx.md | 67 + .../versions/v3.2.1/deploy/backup/keycloak.md | 60 + .../versions/v3.2.1/deploy/cluster-sizing.md | 92 ++ .../v3.2.1/deploy/get-keycloak-token.md | 46 + .../versions/v3.2.1/deploy/install/_index.md | 26 + .../versions/v3.2.1/deploy/install/image.png | Bin 0 -> 227817 bytes .../v3.2.1/deploy/install/keycloak.md | 387 ++++++ .../v3.2.1/deploy/install/overview.md | 46 + .../install/row-level-access-control.md | 50 + .../v3.2.1/deploy/install/services.md | 512 ++++++++ .../v3.2.1/deploy/install/setup-kubernetes.md | 117 ++ .../versions/v3.2.1/deploy/maintain/_index.md | 19 + .../versions/v3.2.1/deploy/maintain/audit.md | 74 ++ .../v3.2.1/deploy/maintain/bpmn-nodes.md | 45 + .../v3.2.1/deploy/maintain/keycloak-events.md | 77 ++ .../versions/v3.2.1/deploy/restore/_index.md | 17 + .../versions/v3.2.1/deploy/restore/audit.md | 54 + .../versions/v3.2.1/deploy/restore/binary.md | 74 ++ .../versions/v3.2.1/deploy/restore/grafana.md | 103 ++ .../versions/v3.2.1/deploy/restore/graphdb.md | 137 ++ .../v3.2.1/deploy/restore/influxdb.md | 113 ++ .../v3.2.1/deploy/restore/keycloak.md | 108 ++ content/versions/v3.2.1/deploy/upgrade.md | 88 ++ content/versions/v3.2.1/how-to/_index.md | 29 + content/versions/v3.2.1/how-to/audit.md | 75 ++ content/versions/v3.2.1/how-to/bpmn/_index.md | 16 + .../v3.2.1/how-to/bpmn/bpmn-elements.md | 404 ++++++ .../v3.2.1/how-to/bpmn/create-workflow.md | 240 ++++ .../v3.2.1/how-to/bpmn/debug-workflows.md | 248 ++++ .../v3.2.1/how-to/bpmn/learning-resources.md | 15 + .../v3.2.1/how-to/bpmn/naming-conventions.md | 204 +++ .../bpmn/screenshot-rhize-flamegraph-json.png | Bin 0 -> 80274 bytes .../v3.2.1/how-to/bpmn/trigger-workflows.md | 151 +++ .../v3.2.1/how-to/bpmn/tune-performance.md | 93 ++ .../v3.2.1/how-to/bpmn/use-jsonata.md | 839 ++++++++++++ .../versions/v3.2.1/how-to/bpmn/variables.md | 17 + content/versions/v3.2.1/how-to/gql/_index.md | 9 + .../v3.2.1/how-to/gql/call-the-graphql-api.md | 371 ++++++ content/versions/v3.2.1/how-to/gql/default.md | 83 ++ .../versions/v3.2.1/how-to/gql/directives.md | 137 ++ content/versions/v3.2.1/how-to/gql/filter.md | 361 ++++++ .../versions/v3.2.1/how-to/gql/generate.md | 62 + content/versions/v3.2.1/how-to/gql/mutate.md | 239 ++++ content/versions/v3.2.1/how-to/gql/query.md | 161 +++ .../versions/v3.2.1/how-to/gql/subscribe.md | 34 + .../v3.2.1/how-to/kpi-service/_index.md | 17 + .../how-to/kpi-service/about-kpi-service.md | 69 + .../kpi-service/configure-kpi-service.md | 198 +++ .../how-to/kpi-service/query-kpi-service.md | 1125 +++++++++++++++++ .../versions/v3.2.1/how-to/model/_index.md | 22 + .../v3.2.1/how-to/model/create-objects-ui.md | 90 ++ .../v3.2.1/how-to/model/master-definitions.md | 345 +++++ .../v3.2.1/how-to/publish-subscribe/_index.md | 16 + .../publish-subscribe/connect-datasource.md | 45 + .../create-equipment-class-rule.md | 412 ++++++ .../screenshot-rhize-rules-engine.png | Bin 0 -> 208283 bytes .../how-to/publish-subscribe/track-changes.md | 135 ++ .../v3.2.1/how-to/work-calendars/_index.md | 25 + .../about-calendars-and-overrides.md | 111 ++ .../work-calendars/create-work-calendar.md | 491 +++++++ content/versions/v3.2.1/reference/_index.md | 13 + .../v3.2.1/reference/default-ports.md | 24 + content/versions/v3.2.1/reference/glossary.md | 16 + .../versions/v3.2.1/reference/gql-types.md | 125 ++ content/versions/v3.2.1/reference/image.png | Bin 0 -> 168606 bytes .../v3.2.1/reference/nats-configuration.md | 94 ++ .../v3.2.1/reference/observability-metrics.md | 341 +++++ .../v3.2.1/reference/service-config/_index.md | 13 + .../service-config/adminUI-configuration.md | 42 + .../service-config/agent-configuration.md | 121 ++ .../service-config/audit-configuration.md | 62 + .../service-config/bpmn-configuration.md | 102 ++ .../service-config/calendar-configuration.md | 109 ++ .../service-config/core-configuration.md | 82 ++ content/versions/v3.2.1/releases/3-0-1.md | 130 ++ content/versions/v3.2.1/releases/3-0-3.md | 141 +++ content/versions/v3.2.1/releases/3-0.md | 156 +++ content/versions/v3.2.1/releases/3-1-0.md | 428 +++++++ content/versions/v3.2.1/releases/3-2-0.md | 131 ++ content/versions/v3.2.1/releases/3-2-1.md | 76 ++ content/versions/v3.2.1/releases/_index.md | 13 + .../v3.2.1/releases/changelog/3-0-0.md | 230 ++++ .../v3.2.1/releases/changelog/3-0-0rc05.md | 112 ++ .../v3.2.1/releases/changelog/3-0-0rc06.md | 95 ++ .../v3.2.1/releases/changelog/3-0-0rc07.md | 133 ++ .../v3.2.1/releases/changelog/3-0-0rc08.md | 177 +++ .../v3.2.1/releases/changelog/3-0-0rc09.md | 102 ++ .../v3.2.1/releases/changelog/_index.md | 6 + content/versions/v3.2.1/use-cases/_index.md | 13 + .../v3.2.1/use-cases/calculate-oee.md | 243 ++++ .../v3.2.1/use-cases/data-collection-ebr.md | 174 +++ content/versions/v3.2.1/use-cases/ebr.md | 454 +++++++ .../versions/v3.2.1/use-cases/genealogy.md | 625 +++++++++ content/versions/v3.2.1/use-cases/overview.md | 66 + 101 files changed, 14309 insertions(+) create mode 100644 content/versions/v3.2.1/_index.md create mode 100644 content/versions/v3.2.1/deploy/_index.md create mode 100644 content/versions/v3.2.1/deploy/backup/_index.md create mode 100644 content/versions/v3.2.1/deploy/backup/audit.md create mode 100644 content/versions/v3.2.1/deploy/backup/binary.md create mode 100644 content/versions/v3.2.1/deploy/backup/grafana.md create mode 100644 content/versions/v3.2.1/deploy/backup/graphdb.md create mode 100644 content/versions/v3.2.1/deploy/backup/influx.md create mode 100644 content/versions/v3.2.1/deploy/backup/keycloak.md create mode 100644 content/versions/v3.2.1/deploy/cluster-sizing.md create mode 100644 content/versions/v3.2.1/deploy/get-keycloak-token.md create mode 100644 content/versions/v3.2.1/deploy/install/_index.md create mode 100644 content/versions/v3.2.1/deploy/install/image.png create mode 100644 content/versions/v3.2.1/deploy/install/keycloak.md create mode 100644 content/versions/v3.2.1/deploy/install/overview.md create mode 100644 content/versions/v3.2.1/deploy/install/row-level-access-control.md create mode 100644 content/versions/v3.2.1/deploy/install/services.md create mode 100644 content/versions/v3.2.1/deploy/install/setup-kubernetes.md create mode 100644 content/versions/v3.2.1/deploy/maintain/_index.md create mode 100644 content/versions/v3.2.1/deploy/maintain/audit.md create mode 100644 content/versions/v3.2.1/deploy/maintain/bpmn-nodes.md create mode 100644 content/versions/v3.2.1/deploy/maintain/keycloak-events.md create mode 100644 content/versions/v3.2.1/deploy/restore/_index.md create mode 100644 content/versions/v3.2.1/deploy/restore/audit.md create mode 100644 content/versions/v3.2.1/deploy/restore/binary.md create mode 100644 content/versions/v3.2.1/deploy/restore/grafana.md create mode 100644 content/versions/v3.2.1/deploy/restore/graphdb.md create mode 100644 content/versions/v3.2.1/deploy/restore/influxdb.md create mode 100644 content/versions/v3.2.1/deploy/restore/keycloak.md create mode 100644 content/versions/v3.2.1/deploy/upgrade.md create mode 100644 content/versions/v3.2.1/how-to/_index.md create mode 100644 content/versions/v3.2.1/how-to/audit.md create mode 100644 content/versions/v3.2.1/how-to/bpmn/_index.md create mode 100644 content/versions/v3.2.1/how-to/bpmn/bpmn-elements.md create mode 100644 content/versions/v3.2.1/how-to/bpmn/create-workflow.md create mode 100644 content/versions/v3.2.1/how-to/bpmn/debug-workflows.md create mode 100644 content/versions/v3.2.1/how-to/bpmn/learning-resources.md create mode 100644 content/versions/v3.2.1/how-to/bpmn/naming-conventions.md create mode 100644 content/versions/v3.2.1/how-to/bpmn/screenshot-rhize-flamegraph-json.png create mode 100644 content/versions/v3.2.1/how-to/bpmn/trigger-workflows.md create mode 100644 content/versions/v3.2.1/how-to/bpmn/tune-performance.md create mode 100644 content/versions/v3.2.1/how-to/bpmn/use-jsonata.md create mode 100644 content/versions/v3.2.1/how-to/bpmn/variables.md create mode 100644 content/versions/v3.2.1/how-to/gql/_index.md create mode 100644 content/versions/v3.2.1/how-to/gql/call-the-graphql-api.md create mode 100644 content/versions/v3.2.1/how-to/gql/default.md create mode 100644 content/versions/v3.2.1/how-to/gql/directives.md create mode 100644 content/versions/v3.2.1/how-to/gql/filter.md create mode 100644 content/versions/v3.2.1/how-to/gql/generate.md create mode 100644 content/versions/v3.2.1/how-to/gql/mutate.md create mode 100644 content/versions/v3.2.1/how-to/gql/query.md create mode 100644 content/versions/v3.2.1/how-to/gql/subscribe.md create mode 100644 content/versions/v3.2.1/how-to/kpi-service/_index.md create mode 100644 content/versions/v3.2.1/how-to/kpi-service/about-kpi-service.md create mode 100644 content/versions/v3.2.1/how-to/kpi-service/configure-kpi-service.md create mode 100644 content/versions/v3.2.1/how-to/kpi-service/query-kpi-service.md create mode 100644 content/versions/v3.2.1/how-to/model/_index.md create mode 100644 content/versions/v3.2.1/how-to/model/create-objects-ui.md create mode 100644 content/versions/v3.2.1/how-to/model/master-definitions.md create mode 100644 content/versions/v3.2.1/how-to/publish-subscribe/_index.md create mode 100644 content/versions/v3.2.1/how-to/publish-subscribe/connect-datasource.md create mode 100644 content/versions/v3.2.1/how-to/publish-subscribe/create-equipment-class-rule.md create mode 100644 content/versions/v3.2.1/how-to/publish-subscribe/screenshot-rhize-rules-engine.png create mode 100644 content/versions/v3.2.1/how-to/publish-subscribe/track-changes.md create mode 100644 content/versions/v3.2.1/how-to/work-calendars/_index.md create mode 100644 content/versions/v3.2.1/how-to/work-calendars/about-calendars-and-overrides.md create mode 100644 content/versions/v3.2.1/how-to/work-calendars/create-work-calendar.md create mode 100644 content/versions/v3.2.1/reference/_index.md create mode 100644 content/versions/v3.2.1/reference/default-ports.md create mode 100644 content/versions/v3.2.1/reference/glossary.md create mode 100644 content/versions/v3.2.1/reference/gql-types.md create mode 100644 content/versions/v3.2.1/reference/image.png create mode 100644 content/versions/v3.2.1/reference/nats-configuration.md create mode 100644 content/versions/v3.2.1/reference/observability-metrics.md create mode 100644 content/versions/v3.2.1/reference/service-config/_index.md create mode 100644 content/versions/v3.2.1/reference/service-config/adminUI-configuration.md create mode 100644 content/versions/v3.2.1/reference/service-config/agent-configuration.md create mode 100644 content/versions/v3.2.1/reference/service-config/audit-configuration.md create mode 100644 content/versions/v3.2.1/reference/service-config/bpmn-configuration.md create mode 100644 content/versions/v3.2.1/reference/service-config/calendar-configuration.md create mode 100644 content/versions/v3.2.1/reference/service-config/core-configuration.md create mode 100644 content/versions/v3.2.1/releases/3-0-1.md create mode 100644 content/versions/v3.2.1/releases/3-0-3.md create mode 100644 content/versions/v3.2.1/releases/3-0.md create mode 100644 content/versions/v3.2.1/releases/3-1-0.md create mode 100644 content/versions/v3.2.1/releases/3-2-0.md create mode 100644 content/versions/v3.2.1/releases/3-2-1.md create mode 100644 content/versions/v3.2.1/releases/_index.md create mode 100644 content/versions/v3.2.1/releases/changelog/3-0-0.md create mode 100644 content/versions/v3.2.1/releases/changelog/3-0-0rc05.md create mode 100644 content/versions/v3.2.1/releases/changelog/3-0-0rc06.md create mode 100644 content/versions/v3.2.1/releases/changelog/3-0-0rc07.md create mode 100644 content/versions/v3.2.1/releases/changelog/3-0-0rc08.md create mode 100644 content/versions/v3.2.1/releases/changelog/3-0-0rc09.md create mode 100644 content/versions/v3.2.1/releases/changelog/_index.md create mode 100644 content/versions/v3.2.1/use-cases/_index.md create mode 100644 content/versions/v3.2.1/use-cases/calculate-oee.md create mode 100644 content/versions/v3.2.1/use-cases/data-collection-ebr.md create mode 100644 content/versions/v3.2.1/use-cases/ebr.md create mode 100644 content/versions/v3.2.1/use-cases/genealogy.md create mode 100644 content/versions/v3.2.1/use-cases/overview.md diff --git a/content/versions/v3.2.1/_index.md b/content/versions/v3.2.1/_index.md new file mode 100644 index 000000000..0b9cd02f0 --- /dev/null +++ b/content/versions/v3.2.1/_index.md @@ -0,0 +1,30 @@ +--- +title: ##Leave only home page without title +description: User guides, deploy docs, references, and deep dives about the + Rhize manufacturing data hub. +cascade: + type: docs + v: "3.2.1" +--- + + + +

+The Rhize Manufacturing Data Hub +

+ +Rhize is a real-time, event-driven manufacturing data hub. + +Rhize unites all events from your manufacturing processes, relates these events as a single graph structure, +and provides access to any combination of them through a single API endpoint. +The tight integration of all levels of manufacturing data, from real-time sensor data to operations orders, serves a wide variety of business needs, including as: + +- **A manufacturing knowledge graph.** Help humans and algorithms analyze plant processes and discover places to optimize. +- **An integrator of systems.** Orchestrate processes across applications to standardize, coordinate, and transform data flows. +- **A backend for {{< abbr "MES" >}} applications.** Rapidly build frontends on top of the database and workflow engine. + Design the MES system that makes sense for your processes and people. + + +{{< card-list >}} + + diff --git a/content/versions/v3.2.1/deploy/_index.md b/content/versions/v3.2.1/deploy/_index.md new file mode 100644 index 000000000..de92f8929 --- /dev/null +++ b/content/versions/v3.2.1/deploy/_index.md @@ -0,0 +1,38 @@ +--- +title: Deploy +description: >- + A collection of pages to administrate Rhize: install, upgrade, back up, and more. +weight: 100 +icon: server +identifier: deploy +cascade: + icon: server + domain_name: libremfg.ai + brand_name: Libre + application_name: libre + db: libreBaas + pre_reqs: |- + - Optional: [kubectx](https://github.com/ahmetb/kubectx) utilities + - `kubectx` to manage multiple clusters + - `kubens` to switch between and configure namespaces easily + - Optional: the [k8 Lens IDE](https://k8slens.dev), if you prefer to use Kubernetes graphically + k8s_cluster_ns: |- + ```bash + ## context + kubectl config current-context + ## namespace + kubectl get namespace + ``` + + To change the namespace for all subsequent [`kubectl` commands](https://kubernetes.io/docs/reference/kubectl/cheatsheet/) to `libre`, run this command: + + ```bash + kubectl config set-context --current --namespace=libre + ``` + +--- + +A collection of pages to administrate Rhize: install, upgrade, back up, and more. + + +{{< card-list >}} diff --git a/content/versions/v3.2.1/deploy/backup/_index.md b/content/versions/v3.2.1/deploy/backup/_index.md new file mode 100644 index 000000000..b69b7a7bf --- /dev/null +++ b/content/versions/v3.2.1/deploy/backup/_index.md @@ -0,0 +1,21 @@ +--- +date: "2023-09-12T19:35:35+11:00" +title: Back up +description: Guides to back up your data on Rhize +categories: ["how-to"] +weight: 200 +cascade: + icon: database +--- + +Backup is critical to ensure reliability and recovery. + +These guides show you how to back up different services and data on Rhize. +They also serve as blueprints for automation. + +Your organization must determine how frequently you backup services, and how long you store them for. +The correct practice here is highly contextual, +depending on the size of the data, the importance of the data, and the general regulatory and governance demands of your industry. + + +{{< card-list >}} diff --git a/content/versions/v3.2.1/deploy/backup/audit.md b/content/versions/v3.2.1/deploy/backup/audit.md new file mode 100644 index 000000000..0b0bd9d7e --- /dev/null +++ b/content/versions/v3.2.1/deploy/backup/audit.md @@ -0,0 +1,52 @@ +--- +title: 'Back up Audit PostgreSQL' +date: '2024-03-26T11:20:56-03:00' +categories: ["how-to"] +description: How to backup Audit PostgreSQL on your Rhize deployment +weight: 300 +--- + +This guide shows you the procedure to backup your Audit PostgreSQL database on your Rhize Kubernetes deployment. + +## Prerequisites + +Before you start, ensure you have the following: + +- A designated backup location, for example `~/rhize-backups/libre-audit`. +- Access to the [Rhize Kubernetes Environment](/deploy/install/setup-kubernetes) +{{% param pre_reqs %}} + + +Also, before you start, confirm you are in the right context and namespace. + +{{% param k8s_cluster_ns %}} + +## Steps + +To back up Audit PostgreSQL, follow these steps: + +1. Check the logs for the Audit pods, either in Lens or with [`kubectl logs`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#logs). + Ensure there are no errors. + +1. Retrieve the Audit user password using the following command: + + + ```bash + kubectl get secret -o jsonpath="{.data.}" | base64 --decode + ``` + +1. Execute a command on the Audit Postgres pod to perform a full backup: + + ```bash + kubectl exec -i audit-postgres-0 -- pg_dumpall -U | gzip > audit-postgres-backup-$(date +"%Y%m%dT%I%M%p").sql.gz + ``` + +On success, the backup creates a GZIP file, `audit-postgres-backup-YYYYMMDDTHHMMSS.sql.gz`. +To check that the backup succeeded, unzip the files and inspect the data. + +## Next Steps + +- To back up other Rhize services, read how to backup: + - [Keycloak]({{< relref "keycloak" >}}). + - [Grafana]({{< relref "grafana" >}}). + - [The Graph Database]({{< relref "graphdb" >}}). diff --git a/content/versions/v3.2.1/deploy/backup/binary.md b/content/versions/v3.2.1/deploy/backup/binary.md new file mode 100644 index 000000000..a81db4be3 --- /dev/null +++ b/content/versions/v3.2.1/deploy/backup/binary.md @@ -0,0 +1,79 @@ +--- +title: 'Back up the Graph DB to S3' +date: '2024-11-04T11:01:46-03:00' +categories: ["how-to"] +description: How to back up the Rhize graph database to Amazon S3 storage. +weight: 100 +--- + +This guide shows you how to back up the Rhize Graph database to Amazon S3 and S3-compatible storage. + +## Prerequisites + +Before you start, ensure you have the following: + + +- A designated S3 backup location, for example `s3://s3..amazonaws.com/`. +- Access to your [Rhize Kubernetes Environment]({{< relref "../install" >}}) +{{% param pre_reqs %}}. + + +Before you start, confirm you are in the right context and namespace: + +{{% param "k8s_cluster_ns" %}} + +## Steps + +To back up the database, follow these steps: + +1. Check the logs for the alpha and zero pods, either in Lens or with [`kubectl logs`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#logs). + Ensure there are no errors. + + ```bash + kubectl logs {{< param application_name >}}-baas-baas-alpha-0 --tail=80 + ``` +1. Set the following environmental variables: + - `AWS_ACCESS_KEY_ID`. Your AWS access key with permissions to write to the destination bucket + - `AWS_SECRET_ACCESS_KEY`. Your AWS access key with permissions to write to the destination bucket + - `AWS_SESSION_TOKEN`. Your AWS session token (if required) + +1. Make a POST request to your Keycloak `/token` endpoint to get an `access_token` value. +For example, with `curl` and `jq`: + + ```bash + ## replace USERNAME and PASSWORD with your credentials + USERNAME=backups@libremfg.com \ + && PASSWORD=password \ + && curl --location \ + --request POST "${BAAS_OIDC_URL}/realms/libre/protocol/openid-connect/token" \ + --header 'Content-Type\ application/x-www-form-urlencoded' \ + --data-urlencode 'grant_type=password' \ + --data-urlencode "username=" \ + --data-urlencode "password=" \ + --data-urlencode "client_id=" \ + --data-urlencode "client_secret=" | jq .access_token + ``` + +1. Using the token from the previous step, send a POST to `:8080/admin` to create a backup of the node to your S3 bucket. +For example, with `curl`: + + ```bash + curl --location 'http://alpha:8080/admin' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer ' \ + --data '{"query":"mutation {\n backup(input: {destination: \"s3://s3..amazonaws.com/\"}) {\n response {\n message\n code\n }\n taskId\n }\n}","variables":{}}' + ``` + +1. List available backups to confirm your backup succeeded: + + ```bash + curl --location 'http://alpha:8080/admin' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer ' \ + --data '{"query":"query backup {\n\tlistBackups(input: {location: \"s3://s3.>.amazonaws.com/\"}) {\n\t\tbackupId\n\t\tbackupNum\n\t\tpath\n\t\tsince\n\t\ttype\n\t}\n}","variables":{}}' + ``` + +## Next Steps + +- Test the [Restore Graph Database From S3]({{< relref "../restore/binary" >}}) procedure to ensure you can recover data from Amazon S3 in case of an emergency. +- To back up other Rhize services, read how to backup [Grafana]({{< relref "grafana" >}}). diff --git a/content/versions/v3.2.1/deploy/backup/grafana.md b/content/versions/v3.2.1/deploy/backup/grafana.md new file mode 100644 index 000000000..c5d47f98e --- /dev/null +++ b/content/versions/v3.2.1/deploy/backup/grafana.md @@ -0,0 +1,105 @@ +--- +title: 'Back up Grafana' +date: '2023-10-18T11:01:56-03:00' +categories: ["how-to"] +description: How to backup Grafana on your Rhize deployment +weight: 300 +--- + +This guide shows you the procedure to back up Grafana on your Rhize Kubernetes deployment. +For general instructions, refer to the official [Back up Grafana](https://grafana.com/docs/grafana/latest/administration/back-up-grafana/) documentation. + +## Prerequisites + +Before you start, ensure you have the following: + +- A designated backup location, for example `~/rhize-backups/grafana`. +- Access to the [Rhize Kubernetes Environment](/deploy/install/setup-kubernetes) +{{% param pre_reqs %}} + + +Also, before you start, confirm you are in the right context and namespace. + +{{% param k8s_cluster_ns %}} + +## Steps + +To back up the Grafana, follow these steps: + +1. Check the logs for the Grafana pods, either in Lens or with [`kubectl logs`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#logs). + Ensure there are no errors. + +1. Open a pod shell for one of the Grafana pods: + + ```bash + kubectl exec --stdin --tty -- /bin/bash + ``` + + For details, read the Kubernetes topic [Get Shell to a Running Container](https://kubernetes.io/docs/tasks/debug/debug-application/get-shell-running-container/). + +1. Use `tar` to backup the Grafana data and `conf` directories: + + ```bash + ## Data Directory Backup Command + tar -v -c -f /home/grafana/grafana-data-$(date +"%Y-%m-%dT%H.%M.%S").tar.gz /var/lib/grafana + ## Conf Directory Backup Command + tar -v -c -f /home/grafana/grafana-conf-$(date +"%Y-%m-%dT%H.%M.%S").tar.gz /usr/share/grafana/conf + ``` + +1. Change to the backup directory. For example: + + ```bash + cd /home/grafana/ + ``` + +1. Check for the latest `.gz` files (for example, with `ls -lt`). + There should be new backup `data` and `conf` files whose names include timestamps from when you ran the preceding `tar` commands. + +1. Create a checksum file for the latest backups: + + ```bash + sha256sum .tar.gz .tar.gz > backup.sums + ``` + + +1. Exit the container shell, and then copy files out of the container to your backup location: + + ```bash + ## exit shell + exit + ## copy container files to backup + kubectl cp :/home/grafana/ \ + ./ -c grafana + + kubectl cp :/home/grafana/ \ + ./ -c grafana + kubectl cp :/home/grafana/backup.sums \ + ./backup.sums -c grafana + ``` + +## Confirm success + + +To confirm the backup, check their sha256 sums and their content. + +To check the sums: + +1. Change to the directory where you sent the backups: + + ```bash + cd // + ``` + +1. Confirm the checksums match: + + ```bash + sha256sum -c backup.sums \ + .tar.gz .tar.gz + ``` + +To check that the content is correct, unzip the files and inspect the data. + +## Next steps + +- Test the [Restore Grafana]({{< relref "../restore" >}}) procedure to ensure you can recover data in case of an emergency. +- To back up other Rhize services, read how to backup [the Graph Database]({{< relref "graphdb" >}}). diff --git a/content/versions/v3.2.1/deploy/backup/graphdb.md b/content/versions/v3.2.1/deploy/backup/graphdb.md new file mode 100644 index 000000000..10ace29cf --- /dev/null +++ b/content/versions/v3.2.1/deploy/backup/graphdb.md @@ -0,0 +1,134 @@ +--- +title: 'Back up the Graph DB' +date: '2023-10-18T11:01:46-03:00' +categories: ["how-to"] +description: How to back up the Rhize graph database +weight: 100 +--- + +This guide shows you how to back up the Rhize Graph database. +You can also use it to model an automation workflow. + +## Prerequisites + +Before you start, ensure you have the following: + + +- A designated backup location, for example `~/rhize-backups/database`. +- Access to your [Rhize Kubernetes Environment]({{< relref "../install" >}}) +{{% param pre_reqs %}}. + + +Before you start, confirm you are in the right context and namespace: + +{{% param "k8s_cluster_ns" %}} + +## Steps + +To back up the database, follow these steps: + +1. Check the logs for the alpha and zero pods, either in Lens or with [`kubectl logs`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#logs). + Ensure there are no errors. + + ```bash + kubectl logs {{< param application_name >}}-baas-baas-alpha-0 --tail=80 + ``` + +1. Open a pod shell for one of the alpha pods. If you are using the terminal, run this command: + + ```bash + kubectl exec --stdin --tty {{< param application_name >}}-baas-baas-alpha-0 \ + -n {{< param "application_name" >}} -- /bin/bash + ``` + + For details, read the Kubernetes topic [Get Shell to a Running Container](https://kubernetes.io/docs/tasks/debug/debug-application/get-shell-running-container/). + +1. Make a POST request to your Keycloak `/token` endpoint to get an `access_token` value. +For example, with `curl` and `jq`: + + ```bash + ## replace USERNAME and PASSWORD with your credentials + USERNAME=backups@libremfg.com \ + && PASSWORD=password \ + && curl --location \ + --request POST "${BAAS_OIDC_URL}/realms/libre/protocol/openid-connect/token" \ + --header 'Content-Type\ application/x-www-form-urlencoded' \ + --data-urlencode 'grant_type=password' \ + --data-urlencode "username=${USERNAME}" \ + --data-urlencode "password=${PASSWORD}" \ + --data-urlencode "client_id=${BAAS_OIDC_CLIENT_ID}" \ + --data-urlencode "client_secret=${OIDC_SECRET}" | jq .access_token + ``` + +1. Using the token from the previous step, send a POST to `localhost:8080/admin` to create a backup of the node. +For example, with `curl`: + + ```bash + curl --location --request POST 'http://localhost:8080/admin' \ + --header 'Authorization: Bearer ' \ + --header 'Content-Type: application/json' \ + --data-raw '{"query":"mutation {\r\n export(input: {format: \"json\", destination: \"/dgraph/backups/'"$(date +"%Y-%m-%dT%H.%M.%SZ")"'\"}) {\r\n response {\r\n message\r\n code\r\n }\r\n}\r\n}","variables":{}}' + ``` + +1. Change to the backup directory (the `destination` parameter in the preceding `curl` command). For example: + + ```bash + cd /dgraph/backups + ``` + +1. Check for the latest directory. Its name should be the timestamp of when you sent the preceding `curl` request. For example: + + ```bash + ls -lt + ``` + + With these flags, the first listed directory should be the latest backup, named something like `2023-10-31T16.55.56Z` + +1. Create a file that holds the sha256 checksums of the latest backup files. You'll use this file to confirm the copy is identical. + + ```bash + sha256sum /dgraph./*.gz > /backup.sums + ``` + +1. Exit the container shell, then copy files out of the container to your backup location: + + ```bash + ## exit shell + exit + ## copy container files to backup + kubectl cp --retries=10 /:backups/ \ + .// + ``` + +1. Use the checksum to confirm that the pod files and the local files are the same. +If you are using Windows, you can run an equivalent check with the [`CertUtil`](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/certutil) utility: + + {{< tabs items="bash,cmd">}} + {{% tab "bash" %}} + ```bash + ## Change to the directory + cd ./// + ## Check sums + sha256sum -c backup.sums *.gz + ``` + {{% /tab %}} + {{% tab "cmd" %}} + ```cmd + CertUtil -hashfile C:\\\backup.sums sha256 + ``` + {{% /tab %}} + {{< /tabs >}} + +## Confirm success + +On success, the backup creates three zipped files: +- The GraphQL schema +- The DB schema +- A JSON file with the real database data. + +To check that the backup succeeded, unzip the files and inspect the data. + +## Next Steps + +- Test the [Restore Graph Database]({{< relref "../restore/graphdb" >}}) procedure to ensure you can recover data in case of an emergency. +- To back up other Rhize services, read how to backup [Grafana]({{< relref "grafana" >}}). diff --git a/content/versions/v3.2.1/deploy/backup/influx.md b/content/versions/v3.2.1/deploy/backup/influx.md new file mode 100644 index 000000000..aecdb39bf --- /dev/null +++ b/content/versions/v3.2.1/deploy/backup/influx.md @@ -0,0 +1,67 @@ +--- +title: 'Back up Influx' +date: '2023-10-18T11:01:56-03:00' +categories: ["how-to"] +description: How to backup InfluxDB on your Rhize deployment +draft: true +weight: 300 +--- + +This guide shows you the procedure to back up the InfluxDB on your Rhize Kubernetes deployment. +For general instructions, refer to the official [Backup Grafana](https://grafana.com/docs/grafana/latest/administration/back-up-grafana/) documentation. + +## Prerequisites + +Before you start, ensure you have the following: + +- A designated backup location, for example `~/rhize-backups/influx`. +- Access to the [Rhize Kubernetes Environment](/deploy/install/setup-kubernetes) +{{% param pre_reqs %}} + + +Also, before you start, confirm you are in the right context and namespace. + +```bash +## context +kubectl config current-context +## namespace +kubectl get namespace +``` + + +## Steps + +Then, to back up the Influx, follow these steps: + +1. Check the logs for the Influx pods, either in Lens or with [`kubectl logs`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#logs). + Ensure there are no errors. + +1. Open a pod shell for one of the Influx pods: + + ```bash + kubectl exec --stdin --tty -- /bin/bash + ``` + + For details, read the Kubernetes topic [Get Shell to a Running Container](https://kubernetes.io/docs/tasks/debug/debug-application/get-shell-running-container/). + +1. Use the `influx backup` command to backup the data. + + ```bash + influx backup --org {{< param brand_name >}} --bucket {{< param brand_name >}} --token /backups/$(date +"%Y-%m-%dT%H.%M.%S") + ``` + +1. Open the backup directory. Check the latest directory (for example with `ls -lt`) for the latest `.gz` files. Its name should be a timestamp from when you ran the preceding backup command. + +1. Leave the container shell. Copy files out of the container to your backup location: + + ```bash + kubectl cp /:backups/ \ + ./ + ``` + +To check that the backup succeeded, unzip the files and inspect the data. + +## Next steps + +- Test the [Restore Influxdb]({{< relref "../restore/influxdb" >}}) procedure to ensure you can recover data in case of an emergency. +- To back up other Rhize services, read how to backup [the Graph Database]({{< relref "graphdb" >}}) and [Grafana]({{< relref "grafana" >}}). diff --git a/content/versions/v3.2.1/deploy/backup/keycloak.md b/content/versions/v3.2.1/deploy/backup/keycloak.md new file mode 100644 index 000000000..1c0b1ba0f --- /dev/null +++ b/content/versions/v3.2.1/deploy/backup/keycloak.md @@ -0,0 +1,60 @@ +--- +title: 'Back up Keycloak' +date: '2024-01-08T14:30:15-05:00' +categories: ["how-to"] +description: How to backup Keycloak on your Rhize deployment +weight: 300 +--- + +This guide shows you how to back up Keycloak on your Rhize Kubernetes deployment. + +## Prerequisites + +Before you start, ensure you have the following: + +- A designated backup location, for example `~/rhize-backups/keycloak`. +- Access to the [Rhize Kubernetes Environment](/deploy/install/setup-kubernetes) +{{% param pre_reqs %}} + +Also, before you start, confirm you are in the right context and namespace. + +{{% param k8s_cluster_ns %}} + +## Steps + +To back up Keycloak, follow these steps: + +1. Check the logs for the Keycloak pods, either in Lens or with [`kubectl logs`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#logs). + Ensure there are no errors. + +1. Retrieve the Keycloak user password using the following command, replacing with your namespace: + + + ```bash + kubectl get secret keycloak--postgresql -o jsonpath="{.data.postgres-password}" | base64 --decode + ``` + +1. Execute a command on the Keycloak Postgres pod to perform a full backup, replacing with your namespace: + + ```bash + kubectl exec -i keycloak--postgresql-0 -- pg_dumpall -U postgres | gzip > keycloak-postgres-backup-$(date +"%Y%m%dT%I%M%p").sql.gz + ``` + + +1. When prompted, use the password from the previous step. Expect the prompt multiple times for each database. + +1. Check the logs for the Keycloak Postgres pods, either in Lens or with [`kubectl logs`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#logs). + Ensure there are no errors relating to the backup. + +## Confirm success + +On success, the backup creates a gzip file, `keycloak-postgres-backup-YYYYMMDDTHHMMSS.sql.gz`. + +To check that the backup succeeded, unzip the files and inspect the data. + +## Next Steps + +- Test the [Restore Keycloak]({{< relref "../restore/keycloak" >}}) procedure to ensure you can recover data in case of an emergency. +- To back up other Rhize services, read how to backup: + - [Grafana]({{< relref "grafana" >}}). + - [The Graph Database]({{< relref "graphdb" >}}). diff --git a/content/versions/v3.2.1/deploy/cluster-sizing.md b/content/versions/v3.2.1/deploy/cluster-sizing.md new file mode 100644 index 000000000..e657e6b1c --- /dev/null +++ b/content/versions/v3.2.1/deploy/cluster-sizing.md @@ -0,0 +1,92 @@ +--- +title: Recommended Kubernetes cluster sizing +description: The recommended number of nodes and compute per pod in your Rhize Kubernetes cluster +--- + +Rhize runs on Kubernetes. + +This document provides compute recommendations for the nodes, pods services of your [Rhize Install]({{< relref "install" >}}). +Some services also have recommended replication factors to increase reliability. + +## Node recommendations + +The following tables are the minimum recommended sizes to provision your cluster for Rhize {{% param v %}}. + +### Rhize nodes + +For high availability, Rhize recommends a **minimum of three nodes** with the following specifications. + + +| Property | Value | +|-----------------------|-------------------| +| Number of nodes | 3 | +| CPU Speed (GHz) | 3.3 | +| vCPU per Node | 16 | +| Memory per node (GiB) | 32 (64 is better) | +| Persisted volumes | 12 | +| Persisted Volume IOPS | 5000 | +| PV Throughput (MBps) | 500 | +| Total Disk Space (TB) | 3 | +| Disk IOPS | 5000 | +| Disk MBps | 500MBps | + +### Rhize agent + +The Rhize agent typically runs on the edge, outside of the cluster entirely. +For the Rhize Agent, the minimum recommended specifications are as follows: + +| Property | Value | +|-----------------------|-------| +| CPU Speed (GHz) | 2.8 | +| vCPU per Node | 2 | +| Memory per node (GiB) | 1 | +| Persisted volumes | 1 | + +## Service-level recommendations + +The following table lists the **minimum** recommended specifications for the main services. +Services with stateful PV have a persistent volume per pod. + + +| Service | Pods for HA (replica count) | vCPU per Pod | Memory Per Pod | Stateful PV | DiskSize (GiB) | Comments | +|------------------------|-----------------------------|--------------|----------------|-------------|----------------|----------------------------------------------------------------------| +| `baas-alpha` | 3 | 8 | 16 (at least) | Yes | 750 | High throughput and IOPS | +| `baas-zero` | 3 | 2 | 2 | Yes | 350 | High throughput and IOPS | +| `libre-core` | 3 | 1 | 2 | No | N/A | HA requires 2 pods, but 3 is to avoid hotkey issues and balance load | +| `bpmn-engine` | 3 | 1 | 2 | No | N/A | HA requires 2 pods, but 3 is to avoid hotkey issues and balance load | +| `nats` | 3 | 1 | 2 | Yes | 100 | High IOPS | +| `nats-box` | 1 | 0.25 | 0.25 | No | N/A | | +| `libre-audit` | 2 | 1 | 1 | No | N/A | | +| `libre-audit-postgres` | 2 | 1 | 2 | Yes | 250 | Runs in pod with `libre-audit` | +| `libre-ui` | 3 | 0.25 | 0.25 | No | N/A | | +| `keycloak` | 2 | 1 | 2 | No | N/A | | +| `keycloak-postgres` | 2 | 1 | 2 | No | 200 | Runs in pod with `keycloak` | +| `router` | 2 | 1 | 2 | Yes | <1 | Requires volume to compose supergraph | +| `grafana`* | 3 | 0.5 | 2 | No | 20-50 | Storage can be in host or in object bucket. | + + * May run [in separate cluster](#monitoring-stack) + +### Monitoring stack + +The following table provides minimal compute recommendations for the monitoring stack. + +The default recommendation is to run your Rhize observability stack in the nodes that also run the Rhize application. +However, some deployments prefer to separate monitoring to its own cluster. + +| Service | Pods for HA (replica count) | vCPU cores per pod | Memory per pod | DiskSize (GiB) | +|-------------------------|-----------------------------|--------------------|----------------|----------------| +| `grafana` | 3 | 0.5 | 2 | 50GB | +| `prometheus-node` | 4 | 0.25 | 0.05 | N/A | +| `prometheus-server` | 1 per pod | 1 | 2 | 1 | +| `promtail` | 4 | 0.25 | 0.2 | N/A | +| `loki` | 1 | 1 | 1 | 1 | +| `loki-logs` | 1 per pod | 0.25 | 0.1 | N/A | +| `loki-canary` | 4 | 0.25 | 0.1 | N/A | +| `loki-gateway` | 1 | 0.25 | 0.05 | 0.25 | +| `loki-grafana-operator` | 1 | 0.25 | 0.1 | 0.25 | +| `tempo-compactor` | 1 | 0.25 | 2 | 0.25 | +| `tempo-ingester` | 3 | 0.5 | 0.75 | 1.5 | +| `tempo-querier` | 1 | 0.25 | 0.5 | 0.25 | +| `tempo-distributor` | 1 | 0.25 | 0.5 | 0.25 | +| `tempo-query-frontend` | 1 | 0.25 | 0.5 | 0.25 | +| `temp-memcache` | 1 | 0.25 | 0.1 | 0.25 | diff --git a/content/versions/v3.2.1/deploy/get-keycloak-token.md b/content/versions/v3.2.1/deploy/get-keycloak-token.md new file mode 100644 index 000000000..006bb674e --- /dev/null +++ b/content/versions/v3.2.1/deploy/get-keycloak-token.md @@ -0,0 +1,46 @@ +--- +title: Get Keycloak Token +description: Get a Keycloak bearer token +--- + +When build applications on Rhize, your clients to need authenticate requests. +To do that, they need to periodically request a bearer token from the Keycloak service. + +## Get token + +With `grant_type` set for client credentials, you can get a bearer token with the following request: + +```shell +curl -X POST "https:///realms//protocol/openid-connect/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=client_credentials" \ + -d "client_id=" \ + -d "client_secret=" +``` + +If the `grant_type` is set for password authentication the request will also require a username and password: + +```shell +curl -X POST "https:///realms//protocol/openid-connect/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=password" \ + -d "client_id=" \ + -d "client_secret=" \ + -d "username=" \ + -d "password=" +``` + +## Response + +An example response returns a JSON object with the following structure. +The `access_token` property has the token value. + +```json +{ + "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldU...", + "expires_in": 300, + "token_type": "Bearer", + "scope": "email profile" +} +``` + diff --git a/content/versions/v3.2.1/deploy/install/_index.md b/content/versions/v3.2.1/deploy/install/_index.md new file mode 100644 index 000000000..73587e378 --- /dev/null +++ b/content/versions/v3.2.1/deploy/install/_index.md @@ -0,0 +1,26 @@ +--- +title: 'Install' +date: '2023-09-22T13:54:26-03:00' +category: how-to +description: >- + A guide to install Rhize services on your Kubernetes cluster. +weight: 100 +cascade: + domain_name: libremfg.ai + brand_name: Libre + application_name: libre + icon: terminal +--- + +This guide shows you how to install Rhize services on your Kubernetes cluster. + + + +{{< callout type="info">}} +This procedure aims to be as generic and vendor-neutral as possible. +Some configuration depends on where and how you run your IT infrastructure—what cloud provider you use, preferred auxiliary tools, and so on---so your team must adapt the process for its particular use cases. +{{< /callout >}} + + + +{{< card-list >}} diff --git a/content/versions/v3.2.1/deploy/install/image.png b/content/versions/v3.2.1/deploy/install/image.png new file mode 100644 index 0000000000000000000000000000000000000000..f86a2c0d1cbf5bed49186544fce05e1d897a4154 GIT binary patch literal 227817 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

fumPtBV% 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 DM(Yi* literal 0 HcmV?d00001 diff --git a/content/versions/v3.2.1/deploy/install/keycloak.md b/content/versions/v3.2.1/deploy/install/keycloak.md new file mode 100644 index 000000000..bb97b5d84 --- /dev/null +++ b/content/versions/v3.2.1/deploy/install/keycloak.md @@ -0,0 +1,387 @@ +--- +title: Configure Keycloak +description: The Rhize GraphQL implementation uses OpenIDConnect for + Authentication and role-based access control. This section describes how to + set up Keycloak +weight: 100 +icon: key +categories: "how-to" +--- + +Rhize uses [Keycloak](https://keycloak.org) as an OpenID provider. +In your cluster, the Keycloak server to authenticate users, services, and manage Role-based access controls. + +This topic describes how to set up Keycloak in your Rhize cluster. +For a conceptual overview of the authentication flow, +read [About OpenID Connect](/explanations/about-openidconnect) + +## Prerequisites + +First, ensure that you have followed the instructions from [Set up Kubernetes](/deploy/install/setup-kubernetes). +All prerequisites for that step apply here. + +## Steps + +Follow these steps to configure a Keycloak realm and associate Rhize services to Keycloak clients, groups, roles, and policies. + +{{% steps %}} + +### Log in + +1. Go to `localhost` on the port where you forwarded the URL. If you used the example values from the last step, that's `localhost:5101`. +1. Use the container credentials to log in. + + To find this, look in the `keycloak.yaml` file. + +### Create a realm + +A Keycloak _realm_ is like a tenant that contains all configuration. + +To create your Rhize realm, follow these steps. + +1. In the side menu, select **Master** then **Create Realm**. +1. For the **Realm Name**, enter `{{< param application_name >}}`. **Create.** + +### Configure realm settings + +#### Configure frontend URL and SSL + +1. In the side menu, select **Realm Settings**. +1. Enter the following values: + | Field | value | + |--------------|-----------------------| + | Frontend URL | Keycloak frontend URL | + | Require SSL | External requests | + +#### Enable Keycloak Audit Trail + +1. Select **Realm Settings**, and then **Events**. +1. Select the tab **User event settings**. +1. Enable **Save Events** and set an expiration. +1. **Save**. +1. Repeat the process for the **Admin event settings** tab. + +#### Configure password policy + +1. Select **Authentication** and then the **Policies** tab. +1. Select the **Password policy** tab. +1. Add your organisation's password policy. + +#### Configure brute-force protections + +1. Select **Realm settings** and then the **Security defenses** tab. +1. In **Brute force detection**, enable the feature and configure it to your requirements. + +#### Configure theme (Optional) +If created with the Libre Theme `init` container, configure the **Login Theme** in **Realm settings** for `libre`. + +### Create clients + +In Keycloak, _clients_ are entities that request Keycloak to authenticate a user. +You need to create a client for each service. + +The DB client requires additional configuration of flows and grants. +Other clients, such as the UI and Dashboard, use the standard flow to coordinate authorization between the browser and Keycloak to simplify security and improve user convenience. + +{{< callout type="info" >}} +Each standard-flow client has its own subdomain. +Refer to [Default URLs and Ports]({{< relref "../../reference/default-ports" >}}) for our recommended conventions. +{{< /callout >}} + +#### Create DB client + +Create a client for the DB as follows: +1. In the side menu, select **Clients > create client**. +1. Configure the **General Settings**: + + - **Client Type**: `OpenID Connect` + - **Client ID**: `{{< param db >}}` + - **Name**: `{{< param brand_name >}} Backend as a Service` + - **Description**: `{{< param brand_name >}} Backend as a Service` + + When finished, select **Next.** + +1. Configure the **Capability config**: + - **Client Authentication**: On + - **Authorization**: On + - For **Authentication flow**, enable: + - 🗸 Standard flow + - 🗸 Direct access grants + - 🗸 Implicit flow + +1. Select **Next**, then **Save**. + + On success, this opens the **Client details** page for the newly created client. + +1. Select the **Service accounts roles** tab and assign the following roles to the `{{< param db >}}` service account. To locate roles, change the filter to **Filter by clients**: + - `manage-clients` + - `manage-account` + - `manage-users` + +#### Create UI client + +Create a client for the UI as follows: +1. In the side menu, select **Clients > create client**. +1. Configure the **General Settings**: + + - **Client Type**: `OpenID Connect` + - **Client ID**: `{{< param application_name >}}UI` + - **Name**: `{{< param brand_name >}} User Interface` + - **Description**: `{{< param brand_name >}} User Interface` + + When finished, select **Next.** + +1. Configure the **Capability config**: + - **Client Authentication**: On + - **Authorization**: On + - For **Authentication flow**, enable: + - 🗸 Standard flow + - 🗸 Direct access grants + - 🗸 Implicit flow + +1. Configure the **Access Settings**: + + - **Root URL**: `.` without trailing slashes + - **Home URL**: `.` without trailing slashes + - **Web Origins**: `.` without trailing slashes + +1. Select **Next**, then **Save**. + +#### Create dashboard client + +1. In the side menu, select **Clients > create client**. +1. Configure the **General Settings**: + + - **Client Type**: `OpenID Connect` + - **Client ID**: `dashboard` + - **Name**: `{{< param brand_name >}} Dashboard` + - **Description**: `{{< param brand_name >}} Dashboard` + +1. Configure the **Capability config**: + + - **Client Authentication**: On + - **Authorization**: On + - For **Authentication flow**, enable: + - 🗸 Standard flow + - 🗸 Direct access grants + - 🗸 Implicit flow + +1. Configure the **Access Settings**: + + - **Root URL**: `.` without trailing slashes + - **Home URL**: `.` without trailing slashes + - **Valid redirect URIs**: `/login/generic_oauth` without trailing slashes + - **Valid post logout redirect URIs**: `+` without trailing slashes + - **Web origins**: `.` without trailing slashes + +1. Select **Next**, then **Save**. + +#### Create other service clients + +The other services do not need authorization but do need client authentication. +By default you need to add only the client ID. + +For example, to create the BPMN engine client: +1. In the side menu, select **Clients > create client**. +1. For **Client ID**, enter `{{< param application_name >}}Bpmn` +1. Configure the **Capability config**: + - **Client Authentication**: On +1. Select **Next**, then **Save**. + +**Repeat this process for each of the following services:** + +| Client ID | Description | +|----------------------------------------|-----------------------| +| `{{< param application_name >}}Audit` | The audit log service | +| `{{< param application_name >}}Core` | The edge agent | +| `{{< param application_name >}}Router` | API router | + +Based on your architecture, repeat for any Libre Edge Agents, `{{< param application_name >}}Agent`. + +### Scope services + +In Keycloak, a _scope_ bounds the access a service has. +Rhize creates a default client scope, then binds services to that scope. + +#### Create a client scope + +To create a scope for your Rhize services, follow these steps: + + +1. Select **Client Scopes > Create client scope**. +1. Fill in the following values: + - **Name**: `{{< param application_name >}}ClientScope` + - **Description**: `{{< param brand_name >}} Client Scope` + - **Type**: `None` + - **Display on consent screen**: `On` + - **Include in token scope**: `On` +1. **Create**. +1. Select the **Mappers** tab, then **Configure new mapper**. Add an audience mapper for the DB client: + - **Mapper Type**: `Audience` + - **Name**: `{{< param db >}}AudienceMapper` + - **Include Client Audience**: `{{< param db >}}` + - **Add to ID Token**: `On` + - **Add to access token**: `On` +1. Repeat the preceding step for a mapper for the UI client: + - **Mapper Type**: `Audience` + - **Name**: `{{< param application_name >}}UIAudienceMapper` + - **Include Client Audience**: `{{< param application_name >}}UI` + - **Add to ID Token**: `On` + - **Add to access token**: `Off` +1. Repeat the preceding step for a mapper for the BPMN client: + - **Mapper Type**: `Audience` + - **Name**: `{{< param application_name >}}BpmnAudienceMapper` + - **Include Client Audience**: `{{< param application_name >}}Bpmn` + - **Add to ID Token**: `On` + - **Add to access token**: `On` +1. If using the Rhize Audit microservice, repeat the preceding step for an Audit scope and audience mapper: + - **Mapper Type**: `Audience` + - **Name**: `{{< param application_name >}}AuditAudienceMapper` + - **Include Client Audience**: + - **Included Custom Audience**: `audit` + - **Add to ID Token**: `On` + - **Add to access token**: `On` + +#### Add services to the scope + +1. Go to **Clients**. Select `{{< param db >}}`. +1. Select the **Client Scopes** tab. +1. Select **Add Client scope** +1. Select `{{< param application_name >}}ClientScope` from the list. +1. **Add > Default**. + +Repeat this process for the `dashboard`, `{{< param application_name >}}UI`, `{{< param application_name >}}Bpmn`, `{{< param application_name >}}Core`, `{{< param application_name >}}Router`, `{{< param application_name >}}Audit` (if applicable). Based on your architecture repeat for any Libre Edge Agent clients. + +### Create roles and groups + +In Keycloak, _roles_ identify a category or type of user. +_Groups_ are a common set of attributes for a set of users. + + +#### Add the Admin Group + +1. In the left hand menu, select **Groups > Create group**. +1. Give the group a name like `{{< param application_name >}}AdminGroup`. +1. **Create**. + +#### Add the dashboard realm roles + +1. Select **Realm Roles**, and then **Create role**. +1. Name the role `dashboard-admin`. +1. **Save**. +1. Repeat the process to create a role `dashboard-dev`. + +#### Add the dashboard groups + +1. In the left hand menu, select **Groups**, and then **Create Group**. +1. Name the group `dashboard-admin` +1. **Create.** +1. Repeat the process to create `dashboard-dev` and `dashboard-user` groups. + +Now map the group to a role: +1. Select dashboard-admin from the list +1. Select the **Role mapping** tab. +1. Select **Assign Role.** +1. Select **`dashboard-admin`** +1. **Assign.** +1. Repeat the process for `dashboard-dev` + + +#### Add the group client scope + +1. In the left hand menu, select **Client scopes** and **Create client scope**. +1. Name it `groups` and provide a description. +1. **Save**. + +Now map the scope: +1. Select the **Mappers** tab. +1. **Add predefined mappers.** +1. Select `groups`. +1. **Add**. + +#### Add new client scopes to dashboard client + +1. In the left hand menu, select **Clients**, and then `dashboard`. +1. Select the **Client scopes** tab. +1. **Add client scope**. +1. Select `groups`. +1. **Add > Default**. + +### Add Client Policy + +In Keycloak, _policies_ define authorization. +Rhize requires authorization for the database service. + +1. In the left hand menu, select **Clients**, and then `{{< param db >}}`. +1. Select the **Authorization** tab. +1. Select the **Policies** sub-tab. +1. Select **Create Policy > Group**. +1. Name the policy `{{< param application_name >}}AdminGroupPolicy`. +1. Select **Add Groups**. +1. Select `{{< param application_name >}}AdminGroup`. +1. **Add**. +1. For **Logic**, choose `Positive`. +1. **Save**. + +### Add users + +1. In the left hand menu, select **Users**, and **Add User**. +1. Fill in the following values: + - **Username**: `system@{{< param domain_name >}}`. + - **Email**: `system@{{< param domain_name >}}`. + - **Email Verified**: `On` + - **First name**: `system` + - **Last name**: `{{< param brand_name >}}` + - **Join Groups**: `{{< param application_name >}}AdminGroup` +1. **Create**. + +Now create a user password: +1. Select the **Credentials** tab. +1. **Set Password**. +1. Enter a strong password. +1. For **Temporary**, choose `Off`. +1. **Save**. + +Repeat this process for the following accounts: + +- Audit: + - **Username**: `{{< param application_name >}}Audit@{{< param domain_name >}}` + - **Email**: `{{< param application_name >}}Audit@{{< param domain_name >}}` + - **Email Verified**: `On` + - **First name**: `Audit` + - **Last name**: `{{< param brand_name >}}` + - **Join Groups**: `{{< param application_name >}}AdminGroup` +- Core: + - **Username**: `{{< param application_name >}}Core@{{< param domain_name >}}` + - **Email**: `{{< param application_name >}}Core@{{< param domain_name >}}` + - **Email Verified**: `On` + - **First name**: `Core` + - **Last name**: `{{< param brand_name >}}` + - **Join Groups**: `{{< param application_name >}}AdminGroup` +- BPMN + - **Username**: `{{< param application_name >}}Bpmn@{{< param domain_name >}}` + - **Email**: `{{< param application_name >}}Bpmn@{{< param domain_name >}}` + - **Email Verified**: `On` + - **First name**: `Bpmn` + - **Last name**: `{{< param brand_name >}}` + - **Join Groups**: `{{< param application_name >}}AdminGroup` +- Router + - **Username**: `{{< param application_name >}}Router@{{< param domain_name >}}` + - **Email**: `{{< param application_name >}}Router@{{< param domain_name >}}` + - **Email Verified**: `On` + - **First name**: `Router` + - **Last name**: `{{< param brand_name >}}` + - **Join Groups**: `{{< param application_name >}}AdminGroup` +- Agent + - **Username**: `{{< param application_name >}}Agent@{{< param domain_name >}}` + - **Email**: `{{< param application_name >}}Agent@{{< param domain_name >}}` + - **Email Verified**: `On` + - **First name**: `Agent` + - **Last name**: `{{< param brand_name >}}` + - **Join Groups**: `{{< param application_name >}}AdminGroup` + +{{% /steps %}} + +## Next steps + +[Install services]({{< relref "services" >}}). diff --git a/content/versions/v3.2.1/deploy/install/overview.md b/content/versions/v3.2.1/deploy/install/overview.md new file mode 100644 index 000000000..8e50c08bd --- /dev/null +++ b/content/versions/v3.2.1/deploy/install/overview.md @@ -0,0 +1,46 @@ +--- +title: 'Overview' +categories: ["how-to"] +description: >- + A high-level overview of the Rhize install process. +weight: 010 +--- + +This guide walks you through how to Install Rhize and its services in a Kubernetes environment. +You can also use these docs to model automation workflows in your CI. + +> This procedure aims to be as generic and vendor-neutral as possible. +> Some configuration depends on where and how you run your IT infrastructure—what cloud provider you use, preferred auxiliary tools, and so on---so your team must adapt the process for its particular use cases. + +## Condensed instructions + +This guide has three steps, each of which has its own page. +The essential procedure is as follows: + +1. **[Set up the Kubernetes environment](/deploy/install/setup-kubernetes)**. + + 1. Within a Kubernetes cluster, create a new namespace. + 1. In this namespace, add the Rhize container images and Helm repositories. + 1. In the cluster, create passwords. + 1. Use Helm to install Keycloak. + +1. **[Configure Keycloak]({{< relref "keycloak" >}})**. + + 1. In Keycloak, create a realm and clients for each service. + 1. In the cluster, create secrets for the KeyCloak clients. + +1. **[Install services]({{< relref "services" >}})**. + + + 1. Use Helm to install {{< param db >}} . + 1. In Keycloak, give {{< param db >}} admin permissions. + 1. Use these admin permissions to POST the database schema. + 1. Return to Keycloak and add the newly created permissions to the {{< param db >}} group. + 1. Use Helm to install all other services in this sequence: + 1. Install the service dependencies. + 1. Edit its YAML files to override defaults as needed. + 1. Install through Helm. + + + + diff --git a/content/versions/v3.2.1/deploy/install/row-level-access-control.md b/content/versions/v3.2.1/deploy/install/row-level-access-control.md new file mode 100644 index 000000000..9c52208e7 --- /dev/null +++ b/content/versions/v3.2.1/deploy/install/row-level-access-control.md @@ -0,0 +1,50 @@ +--- +title: Row Level Access Control +description: >- + Instructions to configure Rhize BAAS scopemap for Row level Access Control. +weight: 200 +categories: "how-to" +--- + + +Row Level Access Control (RLAC) restricts access to specific rows of data based on user roles and permissions. This provides a way to enforce fine-grained access policies and ensure that users can access only the data they are authorized to see. + +For example, in a contract manufacturing organization (CMO), RLAC enables the CMO to access and manage their specific data while allowing the parent company to view all data across the organization. + + +## Configure Row Level Access Control in Rhize BAAS + +Configure RLAC in Rhize BAAS through the `alpha.scopemap.scopemap.json` property in the `values.yaml` file of the BAAS Helm chart. +The scope map is a JSON file that defines rules, actions, jurisdictions, and resources. These rules combine OpenID Connect (OIDC) roles with resources and actions across jurisdictions (modeled as {{< abbr "hierarchy scope" >}}s in ISA-95). + +### Example Configuration + +Consider the following scenario: Acme Inc. contracts part of its supply chain to a CMO. To implement RLAC: + +1. Create an OIDC Role: Define a role called `cmoAccess` in your OIDC provider (e.g., Keycloak). +2. Define a Hierarchy Scope. Create a hierarchy scope in Rhize called `CMO`. This scope is applied to objects or nodes in the graph that relate to the CMO. +3. Add a Rule to the Scope Map: Define a rule in the `scopemap.json` file as follows: + +```json +{ + { + "rules": [ + { + "id": "cmo-data-access", + "description": "CMO data access to CMO hierarchy scoped resources and entities", + "roles": ["cmoAccess"], + "actions": ["query", "mutation"], + "jurisdictions": ["CMO"] + }, + ...other rules here... + ], + "actions": [ + "delete", + "mutation", + "query" + ], + .... +} +``` + +4. **Assign Roles to Users**: As users sign up in Keycloak, assign them the `cmoAccess` role. This grants them permission to access equipment and data within the `CMO` hierarchy scope, while restricting access to data outside this scope. diff --git a/content/versions/v3.2.1/deploy/install/services.md b/content/versions/v3.2.1/deploy/install/services.md new file mode 100644 index 000000000..6cc3fad43 --- /dev/null +++ b/content/versions/v3.2.1/deploy/install/services.md @@ -0,0 +1,512 @@ +--- +title: Install Rhize services +description: >- + Instructions to install services in the Rhize Kubernetes cluster. +weight: 100 +categories: "how-to" +--- + +The final installation step is to install the Rhize services in your Kubernetes cluster. + +> [!NOTE] +> For the recommended compute per pod for each service, refer to [Cluster sizing]({{< relref "../cluster-sizing" >}}) + +## Prerequisites + +This topic assumes you have done the following: +- [Set up Kubernetes]({{< relref "setup-kubernetes" >}}) and [Configured Keycloak]({{< relref "keycloak" >}}). All the prerequisites for those topics apply here. +- Configured load balancing for the following DNS records: + + {{< reusable/default-urls >}} + + _Note that `rhize-` is only the recommended prefix of the subdomain. Your organization may use something else._ + + +### Overrides + +Each service is installed through a Helm YAML file. +For some of these services, you might need to edit this file to add credential information and modify defaults. + +Common values that are changed include: +- URLs and URL links +- The number of replicas running for each pod +- Ingress values for services exposed on the internet + +## Get client secrets + +Client secrets are necessary for Rhize services to authenticate with Keycloak. These secrets are stored with Kubernetes secrets. + +1. Go to Keycloak and get the secrets for each client you've created. +1. Create Kubernetes secrets for each service. You can either create a secret file, or pass raw data from the command line. + + {{< callout type="caution" >}} + How you create Kubernetes secrets **depends on your implementation details and security procedures.** + For guidance, refer to the official Kubernetes topic, [Managing Secrets using `kubectl`](https://kubernetes.io/docs/tasks/configmap-secret/managing-secret-using-kubectl/). + {{< /callout >}} + + With raw data, the command might look something like this: + + ```bash + kubectl create secret generic {{< param application_name >}}-client-secrets -n {{< param application_name >}} \ + --from-literal=dashboard=VKIZ6zkQYyPutDzWqIZ9uIEnQRviyqsS \ + --from-literal={{< param application_name >}}Audit=q8JBjuEefWTmhv9IX4KKYxNtXXnYtDPD \ + --from-literal={{< param application_name >}}Baas=KYbMHlRLhXwiDNFuDCl3qtPj1cNdeMSl \ + --from-literal={{< param application_name >}}Bpmn=7OrjB7FhOdsNeb819xzEDBbMyVb6kNdr \ + --from-literal={{< param application_name >}}Core=SH28Wlx2uEXcgf1NffStbmSuruxvrpi6 \ + --from-literal={{< param application_name >}}UI=0qQ7c1EtOKvwsAcpd0xYIvle4zsMcGRq \ + --from-literal={{< param application_name >}}Router=0qQ7c1EtOKvwsAcpd0xYIvle4zsMcGRq + ``` + +1. Create secrets for login passwords. Each service with its own user in Keycloak can have its password supplied through Kubernetes secrets. + + As you install services through Helm, their respective YAML files reference these secrets. + +## Install and add roles for the DB {#db} + +You must install the {{< param db >}} database service first. +You also need to configure the {{< param db >}} service to have roles in Keycloak. + +If enabling the Audit Trail, also the include the configuration in [Enable change data capture](#enable-change-data-capture). + +If you need Row Level Access Control, [configure your scope map]({{< relref "row-level-access-control.md" >}}). + +1. Modify the DB Helm file with your code editor. Edit any necessary overrides. + + +1. Use Helm to install the database: + + ```bash + helm install -f baas.yaml {{< param application_name >}}-baas {{< param application_name >}}/baas -n {{< param application_name >}} + ``` + + To confirm it works, run the following command: + + ```bash + kubectl get pods + ``` + + All statuses should be `RUNNING`. + + +1. Return to the Keycloak UI and add all `{{< param application_name >}}` roles to the admin group. + +1. Proxy the `http:8080` port on `{{< param application_name >}}-baas-dgraph-alpha`. + + ``` + kubectl port-forward -n {{< param application_name >}} pod/baas-baas-alpha-0 8080:8080 + ``` + +1. Get a token using the credentials. With `curl`, it looks like this: + + ```bash + curl --location --request POST 'https://- + auth.{{< param application_name >}}/realms/{{< param application_name >}}/protocol/openid-connect/token' \ + --header 'Content-Type: application/x-www-form-urlencoded' \ + --data-urlencode 'grant_type=password' \ + --data-urlencode 'username=system@{{< param application_name >}}.com' \ + --data-urlencode 'password=' \ + --data-urlencode 'client_id={{< param application_name >}}Baas' \ + --data-urlencode 'client_secret=' + ``` + +1. Post the schema: + + ```bash + curl --location --request POST 'http://localhost:/admin/schema' \ + --header 'Authorization: Bearer ' \ + --header 'Content-Type: application/octet-stream' \ + --data-binary '@' + ``` + + This creates more roles. + +1. Go to Keycloak UI and add all new {{< param db >}} roles to the `ADMIN` group. + +If the install is successful, the Keycloak UI is available on its +[default port]({{< relref "../../reference/default-ports" >}}). + + +## Install services + +Each of the following procedures installs a service through Helm. + +The syntax to install a Rhize service must have arguments for the following: +- The chart YAML file +- The packaged chart +- The path to the unpackaged chart or directory + +Additionally, use the `-n` flag to ensure that the install is scoped to the correct namespace: + + +``` +helm install \ + -f .yaml \ + \ + -n +``` + +For the full configuration options, +read the official [Helm `install` reference](https://helm.sh/docs/helm/helm_install/). + + +### NATS {#nats} + + + +[NATS](https://nats.io) is the message broker that powers Rhize's event-driven architecture. + +Install NATS with these steps: + +1. If it doesn't exist, add the NATS repository: + + ```bash + helm repo add nats https://nats-io.github.io/k8s/helm/charts/ + ``` + +1. Modify the NATS Helm file with your code editor. Edit any necessary overrides. +1. Install with Helm: + + ``` + helm install nats -f nats.yaml nats/nats -n {{< param application_name >}} + ``` + + +### Tempo + +Rhize uses [Tempo](https://grafana.com/oss/tempo/) to trace BPMN processes. + +Install Tempo with these steps: + +1. If it doesn't exist, add the Tempo repository: + + ```bash + helm repo add grafana https://grafana.github.io/helm-charts + ``` + +1. Modify the Helm file as needed. +1. Install with Helm: + + ```bash + helm install tempo -f tempo.yaml grafana/tempo -n {{< param application_name >}} + ``` + +### Core + +The {{< param brand_name >}} Core service is the custom edge agent that monitors data sources, like OPC-UA servers, and publishes and subscribes topics to NATS. + +> **Requirements**: Core requires the [{{< param db >}}](#db) and [NATS](#nats) services. + +Install the Core agent with these steps: + +1. In the `core.yaml` Helm file, edit the `clientSecret` and `password` with settings from the Keycloak client. +1. Override any other values, as needed. +1. Install with Helm: + + ```bash + helm install core -f core.yaml {{< param application_name >}}/core -n {{< param application_name >}} + ``` + +### BPMN + +The BPMN service is the custom engine Rhize uses to process low-code workflows modeled in the BPMN UI. + +> **Requirements**: The BPMN service requires the [{{< param db >}}](#db), [NATS](#nats), and [Tempo](#tempo) services. + +Install the BPMN engine with these steps: + +1. Open `bpmn.yaml` Update the `clientSecret` and `password` for your BPMN Keycloak credentials. +1. Modify any other values, as needed. +1. Install with Helm: + + ```bash + helm install bpmn -f bpmn.yaml {{< param application_name >}}/bpmn -n {{< param application_name >}} + ``` + +### Router + +Rhize uses the [Apollo router](https://www.apollographql.com/docs/router) to unite queries for different services in a single endpoint. + +> **Requirements:** Router requires the [GraphDB](#db), [BPMN](#bpmn), and [Core](#core) services. + +Install the router with these steps: + +1. Modify the router Helm YAML file as needed. +1. Install with Helm: + + ```bash + helm install router -f router.yaml {{< param application_name >}}/router -n {{< param application_name >}} + ``` + +If the install is successful, the Router explorer is available on its +[default port]({{< relref "../../reference/default-ports" >}}). + +### Grafana + +Rhize uses [Grafana](https://grafana.com) for its dashboard to monitor real time data. + +Install Grafana with these steps: + +1. Modify the Grafana Helm YAML file as needed. + +1. Add the Helm repository + ```bash + helm repo add grafana https://grafana.github.io/helm-charts + ``` + +1. Install with Helm: + + ```bash + helm install grafana -f grafana.yaml grafana/grafana -n {{< param application_name >}} + ``` + +If the install is successful, the Grafana service is available on its +[default port]({{< relref "../../reference/default-ports" >}}). + +### Agent + +The Rhize agent bridges your plant processes with the Rhize data hub. +It collects data emitted from the plant and publishes it to the NATS message broker. + +> **Requirements:** Agent requires the [Graph DB](#db), [Nats](#nats), and [Tempo](#tempo) services. + +Install the agent with these steps: + +1. Modify the Agent Helm file as needed. + +1. In the Rhize UI, add a Data Source for Agent to interact with: + - In the lefthand menu, open **Master Data > Data Sources > + Create Data Source**. + - Input a name for the Data Source. + - Add a Connection String and Create. + - Add any relevant Topics. + - Activate the Data Source. + +1. Install with Helm: + + ```bash + helm install agent -f agent.yaml {{< param application_name >}}/agent -n {{< param application_name >}} + ``` + +## Install Admin UI + +The Admin UI is the graphical frontend to [handle events]({{< relref "../../how-to/bpmn" >}}) and [define work masters]({{< relref "../../how-to/model" >}}). + +> **Requirements:** The UI requires the [GraphDB](#db), [BPMN](#bpmn), [Core](#core), and [Router](#router) services. + +After installing all other services, install the UI with these steps: + +1. Forward the port from the Router API. In the example, this forwards Router traffic to port `4000` on `localhost`. + + ```bash + kubectl port-forward svc/router 4000:4000 -n {{< param application_name >}} + ``` + +1. Open the Admin UI Helm file. Update the `envVars` object to reflect the URL for Router and Keycloak. If following the prior examples for port-forwarding, it will look something like this: + + ```yaml + envVars: + APP_APOLLO_CLIENT: "http://localhost:4000" + APP_APOLLO_CLIENT_ADMIN: "http://localhost:4000" + APP_AUTH_KEYCLOAK_SERVER_URL: "http://localhost:8080" + ``` + +1. Modify any other values, as needed. +1. Install with Helm: + + ```bash + helm install admin-ui -f admin-ui.yaml {{< param application_name >}}/admin-ui -n {{< param application_name >}} + ``` + +If the install is successful, Admin UI is available on its +[default port]({{< relref "../../reference/default-ports" >}}). + +## Optional: Audit Trail service + + +The Rhize [Audit]({{< relref "../../how-to/audit" >}}) service provides an audit trail for database changes to install. The Audit service uses PostgreSQL for storage. + +Install Audit Service with these steps: + +1. Modify the Audit trail Helm YAML file. It is *recommended* to change the PostgreSQL username and password values. + +2. Install with Helm: + + ```bash + helm install audit -f audit.yaml {{< param application_name >}}/audit -n {{< param application_name >}} + ``` + +3. Create partition tables in the PostgreSQL database: + + ```sql + create table public.audit_log_partition( like public.audit_log ); + select partman.create_parent( p_parent_table := 'public.audit_log', p_control := 'time', p_interval := '1 Month', p_template_table := 'public.audit_log_partition'); + ``` + +For details about maintaining the Audit trail, read [Archive the PostgresQL Audit trail]({{< relref "../maintain/audit/" >}}). + +### Enable change data capture + +The Audit trail requires [change data capture (CDC)]({{< relref "../../how-to/publish-subscribe/track-changes" >}}) to function. To enable CDC in {{< param application_name >}} BAAS, include the following values for the Helm chart overrides: + +```yaml +alpha: + # Change Data Capture (CDC) + cdc: + # Enable + enabled: true + # If configured for security, configure in NATS url. For example `nats://username:password@nats:4222` + nats: nats://nats:4222 + # Adjust based on high-availability requirements and cluster size. + replicas: 1 +``` + +### Enable Audit subgraph + +To use the Audit trail in the UI, you must add the Audit trail subgraph into the router. To enable router to use and compose the subgraph: + +1. Update the Router Helm chart overrides, `router.yaml`, to include: + + ```yaml + # Add Audit to the router subgraph url override + router: + configuration: + override_subgraph_url: + AUDIT: http://audit.{{< param application_name >}}.svc.cluster.local:8084/query + + # If supergraph compose is enabled + supergraphCompose: + supergraphConfig: + subgraphs: + AUDIT: + routing_url: http://audit.{{< param application_name >}}.svc.cluster.local:8084/query + schema: + subgraph_url: http://audit.{{< param application_name >}}.svc.cluster.local:8084/query + ``` + +2. Update the Router deployment + +```shell +$ helm upgrade --install router -f router.yaml {{< param application_name >}}/router -n {{< param application_name >}} +``` + +## Optional: calendar service + +The [{{< param brand_name >}} calendar service]({{< relref "../../how-to/work-calendars">}}) monitors work calendar definitions and creates work calendar entries in real time, both in the [Graph](#db) and time-series databases. + +> **Requirements:** The calendar service requires the [GraphDB](#db), [Keycloak](#keycloak), and [NATS](#nats) services. + +{{% callout type="info" %}} +The work calendar requires a time-series DB installed such as [InfluxDB](https://influxdata.com/), [QuestDB](https://questdb.io) or [TimescaleDB](https://www.timescale.com/). The following instructions are specific to QuestDB. +{{% /callout %}} + +Install the calendar service with these steps: + +1. Create tables in the time series. For example: + + + ```sql + CREATE TABLE IF NOT EXISTS PSDT_POT( + EquipmentId SYMBOL, + EquipmentVersion STRING, + WorkCalendarId STRING, + WorkCalendarIid STRING, + WorkCalendarDefinitionId STRING, + WorkCalendarDefinitionEntryId STRING, + WorkCalendarDefinitionEntryIid STRING, + WorkCalendarEntryId STRING, + WorkCalendarEntryIid SYMBOL, + HierarchyScopeId STRING, + EntryType STRING, + ISO22400CalendarState STRING, + isDeleted boolean, + updatedAt TIMESTAMP, + time TIMESTAMP, + lockerCount INT, + lockers STRING + ) TIMESTAMP(time) PARTITION BY month + DEDUP UPSERT KEYS(time, EquipmentId, WorkCalendarEntryIid); + + CREATE TABLE IF NOT EXISTS PDOT_PBT( + EquipmentId SYMBOL, + EquipmentVersion STRING, + WorkCalendarId STRING, + WorkCalendarIid STRING, + WorkCalendarDefinitionId STRING, + WorkCalendarDefinitionEntryId STRING, + WorkCalendarDefinitionEntryIid STRING, + WorkCalendarEntryId STRING, + WorkCalendarEntryIid SYMBOL, + HierarchyScopeId STRING, + EntryType STRING, + ISO22400CalendarState STRING, + isDeleted boolean, + updatedAt TIMESTAMP, + time TIMESTAMP, + lockerCount INT, + lockers STRING + ) TIMESTAMP(time) PARTITION BY month + DEDUP UPSERT KEYS(time, EquipmentId, WorkCalendarEntryIid); + + CREATE TABLE IF NOT EXISTS Calendar_AdHoc( + EquipmentId SYMBOL, + EquipmentVersion STRING, + WorkCalendarId STRING, + WorkCalendarIid STRING, + WorkCalendarDefinitionId STRING, + WorkCalendarDefinitionEntryId STRING, + WorkCalendarDefinitionEntryIid STRING, + WorkCalendarEntryId STRING, + WorkCalendarEntryIid SYMBOL, + HierarchyScopeId STRING, + EntryType STRING, + ISO22400CalendarState STRING, + isDeleted boolean, + updatedAt TIMESTAMP, + time TIMESTAMP, + lockerCount INT, + lockers STRING + ) TIMESTAMP(time) PARTITION BY month + DEDUP UPSERT KEYS(time, EquipmentId, WorkCalendarEntryIid); + ``` + +1. Modify the calendar YAML file as needed. + +1. Deploy with helm + + ```bash + helm install calendar-service -f calendar-service.yaml {{< param application_name >}}/calendar-service -n {{< param application_name >}} + ``` + +## Optional: change service configuration + +The services installed in the previous step have many parameters that you can configure for your performance and deployment requirements. +Review the full list in the [Service configuration]({{< relref "../../reference/service-config" >}}) reference. + +## Troubleshoot + +For general Kubernetes issues, the [Kubernetes dashboard](https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/) is great for troubleshooting, and you can configure it to be accessible through the browser. + +For particular problems, try these commands: + +- **Is my service running?** + + To check deployment status, use this command: + + ```bash + kubectl get deployments + ``` + + Look for the pod name and its status. + +- **Access service through browser** + + Some services are accessible through the browser. + To access them, visit local host on the service's [default port]({{< relref "../../reference/default-ports" >}}). + +- **I installed a service too early**. + If you installed a service too early, use Helm to uninstall: + + ```bash + helm uninstall {{< param db >}} + ``` + + Then perform the steps you need and reinstall when ready. diff --git a/content/versions/v3.2.1/deploy/install/setup-kubernetes.md b/content/versions/v3.2.1/deploy/install/setup-kubernetes.md new file mode 100644 index 000000000..6e4048e83 --- /dev/null +++ b/content/versions/v3.2.1/deploy/install/setup-kubernetes.md @@ -0,0 +1,117 @@ +--- +title: 'Set up Kubernetes' +date: '2023-09-22T14:49:53-03:00' +categories: ["how-to"] +description: + How to install Rhize services on your Kubernetes cluster. +weight: 050 +--- + +This guide shows you how to install Rhize services on your Kubernetes cluster. +You can also use this procedure as the model for an automation workflow in your CI. + + +## Prerequisites {#prereqs} + +Before starting, ensure that you have the following technical requirements. + +**Software requirements**: +- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) +- [Helm](https://helm.sh) +- Curl, or some similar program to make HTTP requests from the command line + +**Access requirements**: +- Administrative privileges for a running Kubernetes cluster in your environment. + Your organization must set this up. +- Access to Rhize Helm charts and its build repository. + Rhize provides these to all customers. + +**Optional utilities.** +For manual installs, the following auxiliary tools might make +the experience a little more human friendly: +{{% param pre_reqs %}} + + Again, these are helpers, not requirements. + You can install everything with only the `kubectl` and `helm` commands. + + +## Steps to set up Kubernetes + +First, record your site and environment. +Then, follow these steps. + +1. Create a namespace called {{< param application_name >}}. + + ```bash + kubectl create ns {{< param application_name >}} + ``` + + Confirm it works with `kubectl get ns`. + + On success, the output shows an active `{{< param application_name >}}` namespace. + +1. Set this namespace as a default with + + ```bash + kubectl config set-context --current --namespace={{< param application_name >}} + ``` + + Alternatively, you can modify the kube `config` file or use the `kubens` tool. + +1. Add the Rhize Helm Chart Repository: + + ```bash + helm repo add \ + --username \ + --password \ + {{< param application_name >}} \ + https://gitlab.com/api/v4/projects/42214456/packages/helm/stable + ``` + +1. Create the container image pull secret: + + ```bash + kubectl create secret docker-registry {{< param application_name >}}-registry-credential \ + --docker-server= \ + --docker-password= \ + --docker-email= + ``` + + Confirm the secrets with this command: + + ```bash + kubectl get secrets + ``` + +1. Add the Bitnami Helm repository: + + ```bash + helm repo add bitnami https://charts.bitnami.com/bitnami + ``` + + And update repositories with: + + ```bash + helm repo update + ``` + +1. Pull the build template repository (we will supply this). + +1. Update overrides to `keycloak.yaml`. Then install with this command: + + ```bash + helm install keycloak -f ./keycloak.yaml bitnami/keycloak -n libre + ``` + +> Note: Version may have to be specified by appending on `--version` and the desired chart version. + +1. Set up port forwarding from Keycloak. For example, this forwards traffic to port `8080` on `localhost`. + + ```bash + kubectl port-forward svc/keycloak 8080:80 -n libre + ``` + +## Next steps + +1. [Configure Keycloak]({{< relref "keycloak.md" >}}) +1. [Install services]({{< relref "services.md" >}}). diff --git a/content/versions/v3.2.1/deploy/maintain/_index.md b/content/versions/v3.2.1/deploy/maintain/_index.md new file mode 100644 index 000000000..6d090b4c5 --- /dev/null +++ b/content/versions/v3.2.1/deploy/maintain/_index.md @@ -0,0 +1,19 @@ +--- +date: "2024-03-26T19:35:35+11:00" +title: Maintain +description: Guides to maintain your data on Rhize +categories: ["how-to"] +weight: 250 +--- + +Maintenance is critical to ensure reliability over time. + +These guides show you how to maintain different services and data on Rhize. +They also serve as blueprints for automation. + +Your organization must determine how you maintain your services, and how often you archive or remove data. +The correct practice here is highly contextual, depending on the size of the data, the importance of the data, and the general regulatory and governance demands of your industry. + + + +{{< card-list >}} diff --git a/content/versions/v3.2.1/deploy/maintain/audit.md b/content/versions/v3.2.1/deploy/maintain/audit.md new file mode 100644 index 000000000..3bb7d26b8 --- /dev/null +++ b/content/versions/v3.2.1/deploy/maintain/audit.md @@ -0,0 +1,74 @@ +--- +title: 'Archive the PostgreSQL Audit trail' +date: '2024-03-26T11:20:56-03:00' +categories: ["how-to"] +description: How to archive a partition of the Audit trail on your Rhize deployment +weight: 100 +--- + +The [audit trail]({{< relref "../../how-to/audit" >}}) can generate a high volume of data, so it is a good practice to periodically _archive_ portions of it. +An archive separates a portion of the data from the database and keeps it for long-term storage. This process involves the use of PostgreSQL [Table Partitions](https://www.postgresql.org/docs/current/ddl-partitioning.html). + +Archiving a partition improves query speed for current data, while providing a cost-effective way to store older data. + + +## Prerequisites + +Before you start, ensure you have the following: + +- A designated backup location, for example `~/rhize-archives/libre-audit`. +- Access to the [Rhize Kubernetes Environment]({{< relref "../install/setup-kubernetes" >}}) + +{{% param pre_reqs %}} + +Also, before you start, confirm you are in the right context and namespace. + +{{% param k8s_cluster_ns %}} + +## Steps + +To archive the PostgreSQL Audit trail, follow these steps: + +1. Record the `` of the partition you wish to detach and archive. + This is based on the retention-period query for the names of the existing partitions: + + ```bash + kubectl exec -i audit-postgres-0 -- psql -h localhost \ + -d audit -U \ + -c "select * from partman.show_partitions('public.audit_log')" + ``` + +1. Detach the target partitions from the main table: + + ```bash + + kubectl exec -i audit-postgres-0 -- psql -h localhost \ + -d audit -U \ + -c 'alter table audit_log detach partition ;' + + ``` + +1. Backup the partition table: + + ```bash + pg_dump -U -h audit-postgres-0 -p5433 \ + --file ./audit-p20240101.sql --table public.audit_log_p20240101 audit + ``` + + On success, the backup creates a GZIP file, `.sql`. + To check that the backup succeeded, unzip the files and inspect the data. + +1. Drop the partition table to remove it from the database: + + ```bash + kubectl exec -i audit-postgres-0 -- psql -h localhost -d audit \ + -U -c 'drop table ;' + ``` + +## Next Steps + +- For full backups or Rhize services, read how to back up: + - [Keycloak]({{< relref "../backup/keycloak" >}}) + - [The Audit trail]({{< relref "../backup/audit" >}}) + - [Grafana]({{< relref "../backup/grafana" >}}) + - [The Graph Database]({{< relref "../backup/graphdb" >}}) diff --git a/content/versions/v3.2.1/deploy/maintain/bpmn-nodes.md b/content/versions/v3.2.1/deploy/maintain/bpmn-nodes.md new file mode 100644 index 000000000..e6cfbf47f --- /dev/null +++ b/content/versions/v3.2.1/deploy/maintain/bpmn-nodes.md @@ -0,0 +1,45 @@ +--- +title: "BPMN execution recovery" +weight: 200 +description: >- + If a BPMN node suddenly fails, Rhize has a number of recovery methods to ensure that the workflow finishes executing. +categories: ["concepts"] +--- + +[{{< abbr "BPMN" >}} processes]({{< relref "../../how-to/bpmn" >}}) often have longer execution durations and many steps. +If a BPMN node suddenly fails (for example through a panic or loss of power), +Rhize needs to ensure that the workflow completes. + +To achieve high availability and resiliency, Rhize services run in [Kubernetes nodes](https://kubernetes.io/docs/concepts/architecture/nodes/), and the NATS message broker typically has [data replication](https://docs.nats.io/running-a-nats-service/nats_admin/jetstream_admin/replication). +As long as the remaining BPMN nodes are not already at full processing capacity, +if a BPMN node fails while executing a process, +the Rhize system recovers and finishes the workflow. + +This recovery is automatic, though users may experience an execution gap of up to 30 seconds. + +## BPMN failure and recovery modes + +How Rhize recovers from a halted process depends on where the system failed. + +### BPMN node failure + +If a BPMN container suddenly fails, the process that was currently executing times out after 30 seconds. +As long as the node had not been running for [longer than 10 minutes](#bpmn-age-out), +NATS re-sends the message to another BPMN node and the process finishes. + +### NATS node unavailable + +If the NATS node fails, recovery depends on your replication and backup strategy. + +- If the stream has R3 replication or greater, a new NATS node picks up the process. No noticeable performance issues should occur. + +- If the stream has no replication, everything in the node is lost. However, if you took a snapshot of a stream with `nats stream backup` before the node became unavailable, and the `WorkflowSpecifications` KV is the same at backup and restore sites, then you can use the `nats stream restore` command to replay the stream from when the backup was made. + +To learn more, read the NATS topic on [Disaster recovery](https://docs.nats.io/running-a-nats-service/nats_admin/jetstream_admin/disaster_recovery). + +## All BPMN elements age out after ten minutes {#bpmn-age-out} + +If an element in a BPMN workflow takes longer than 10 minutes, NATS ages the workflow out of the queue. The process continues, but if the pod executing the element dies or is interrupted, that workflow is permanently dropped. + +This ten-minute execution limit should be sufficient for any element in a BPMN process. +Processes that take longer, such as cooling or fermentation periods, should be implemented as [BPMN event triggers]({{ relref "../../how-to/bpmn/bpmn-elements" >}}) or as polls that periodically check data sources between intervals of sleep. diff --git a/content/versions/v3.2.1/deploy/maintain/keycloak-events.md b/content/versions/v3.2.1/deploy/maintain/keycloak-events.md new file mode 100644 index 000000000..f3429e3ce --- /dev/null +++ b/content/versions/v3.2.1/deploy/maintain/keycloak-events.md @@ -0,0 +1,77 @@ +--- +title: Export Keycloak events +description: Guide to export events from Keycloak +--- + +Keycloak stores User and Admin event data in its database. This information can be valuable for your audits. + +This guide shows you how to export your Keycloak events to a file. +To read Keycloak event data, use its [Admin CLI](https://docs.redhat.com/en/documentation/red_hat_build_of_keycloak/22.0/html/server_administration_guide/admin_cli). You can access the CLI from within the Keycloak's container. + +## Prerequisites + +Ensure you have the following: +- The ability to run commands in a Keycloak container or pod. +- A Keycloak admin username and password. + +## Procedure + +To export Keycloak events, first open a shell in your Keycloak container or pod. For example, in Kubernetes and Docker: + +{{< tabs items="Kubernetes,Docker" >}} + +{{% tab "kubernetes" %}} +```sh +kubectl exec -it keycloak_pod_name -n namespace_name -- /bin/sh +``` +{{% /tab %}} + +{{% tab "Docker" %}} +```sh +docker exec -it keycloak_container_name /bin/sh +``` + +{{% /tab %}} + +{{< /tabs >}} + +Then follow these steps: + +1. Change to the directory where the script for the Admin CLI is. This directory is by default `/opt/bitnami/keycloak/bin`. +3. Run `./kcadm.sh get realms/libre/events --server http://localhost:8080 --realm master --user `. Replace `` with the Keycloak admin username. + If the Keycloak port differs from the default, replace `:8080` with the configured port number. +4. When prompted, enter the Keycloak admin password. + + +On success, event data prints to the console. + +## Write event data to file + +The event output can be long. +You can use the following commands write the data to a file (replacing `` with the Keycloak admin password). + +{{< tabs items="Kubernetes,Docker" >}} +{{% tab Kubernetes %}} + +```shell +kubectl exec -it keycloak_pod_name -n namespace_name -- \ + /bin/sh -c "cd /opt/bitnami/keycloak/bin && (echo "" \ + | ./kcadm.sh get realms/libre/events --server http://localhost:8080 \ + --realm master --user admin)" \ + | sed '1,2d' > output.json +``` + +{{% /tab %}} + +{{% tab docker %}} + +```shell +docker exec -it keycloak_container_name \ + /bin/sh -c "cd /opt/bitnami/keycloak/bin && (echo "" \ + | ./kcadm.sh get realms/libre/events --server http://localhost:8080 \ + --realm master --user admin)" \ + | sed '1,2d' > output.json +``` + +{{% /tab %}} +{{< /tab >}} diff --git a/content/versions/v3.2.1/deploy/restore/_index.md b/content/versions/v3.2.1/deploy/restore/_index.md new file mode 100644 index 000000000..faae7da82 --- /dev/null +++ b/content/versions/v3.2.1/deploy/restore/_index.md @@ -0,0 +1,17 @@ +--- +date: "2023-09-12T19:35:35+11:00" +title: Restore +description: Guides to restore your data on Rhize +categories: ["how-to"] +cascade: + icon: database +weight: 200 +--- + +These guides show you how to restore data from [backup]({{< relref "../backup" >}}). +They also serve as blueprints for automation. + +Even if you don't need to restore data, it's a good practice to test restoration periodically. + + +{{< card-list >}} diff --git a/content/versions/v3.2.1/deploy/restore/audit.md b/content/versions/v3.2.1/deploy/restore/audit.md new file mode 100644 index 000000000..d94e189cf --- /dev/null +++ b/content/versions/v3.2.1/deploy/restore/audit.md @@ -0,0 +1,54 @@ +--- +title: 'Restore Audit backup' +date: '2024-03-26T11:20:56-03:00' +categories: ["how-to"] +description: How to restore the backup of the Audit PostgreSQL on your Rhize deployment +weight: 300 +--- + +This guide shows you the procedure to restore your Audit PostgreSQL database in your Rhize Kubernetes deployment. + +## Prerequisites + +Before you start, ensure you have the following: + +- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) +- An [Audit PostgreSQL backup]({{< relref "../backup/audit" >}}) + +Also, before you start, confirm you are in the right context and namespace. + +{{% param k8s_cluster_ns %}} + +## Steps + +To restore Audit PostgreSQL, follow these steps: + +## Steps + +1. Confirm the cluster and namespace are correct: + + {{% param "k8s_cluster_ns" %}} + +1. Retrieve the Audit user password using the following command: + + ```bash + kubectl get secret -o jsonpath="{.data.}" | base64 --decode + ``` + +1. Extract your backup file: + + ```bash + gzip -d audit-postgres-backup-YYYYMMDDTHHMMAA.sql + ``` + +1. Restore the backup: + + ```bash + cat audit-postgres-backup-YYYYMMDDTHHMMAA.sql | kubectl exec -i audit-postgres-0 -- psql postgresql://postgres:@localhost:5432 -U + ``` + + +## Next Steps + +- Test the [Backup Audit]({{< relref "../backup/audit" >}}) procedure +- Plan and execute a [Maintenance Strategy]({{< relref "../maintain/audit" >}}) to handle your audit data. diff --git a/content/versions/v3.2.1/deploy/restore/binary.md b/content/versions/v3.2.1/deploy/restore/binary.md new file mode 100644 index 000000000..63fe383d6 --- /dev/null +++ b/content/versions/v3.2.1/deploy/restore/binary.md @@ -0,0 +1,74 @@ +--- +title: 'Restore the GraphDB from S3' +date: '2023-10-19T13:52:23-03:00' +ategories: ["how-to"] +description: How to restore a backup of the Rhize Graph DB from Amazon S3. +weight: 200 +--- + +This guide shows you how to restore the Graph database from Amazon S3 to your Rhize environment. + +## Prerequisites + +Before you start, ensure you have the following: + +- The GraphDB Helm chart +- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) +- A [Database backup]({{< relref "../backup/binary" >}}) + +## Steps + + + +1. Set the follow environmental variables: + - `AWS_ACCESS_KEY_ID` your AWS access key with permissions to write to the destination bucket + - `AWS_SECRET_ACCESS_KEY` your AWS access key with permissions to write to the destination bucket + - `AWS_SESSION_TOKEN` your AWS session token (if required) + +1. Confirm the cluster and namespace are correct. + + {{% param k8s_cluster_ns %}} + +1. Upgrade or install the Helm chart. + + ```bash + helm upgrade --install -f baas.yaml {{< param application_name >}}-baas {{< param application_name >}}/baas -n {{< param application_name >}} + ``` + +1. Wait for `{{< param application_name >}}-baas-alpha-0` to start serving the GraphQL API. + +1. Make a POST request to your Keycloak `/token` endpoint to get an `access_token` value. + For example, with `curl` and `jq`: + + ```bash + ## replace USERNAME and PASSWORD with your credentials + USERNAME=backups@libremfg.com \ + && PASSWORD=password \ + && curl --location \ + --request POST "${BAAS_OIDC_URL}/realms/libre/protocol/openid-connect/token" \ + --header 'Content-Type\ application/x-www-form-urlencoded' \ + --data-urlencode 'grant_type=password' \ + --data-urlencode "username=" \ + --data-urlencode "password=" \ + --data-urlencode "client_id=" \ + --data-urlencode "client_secret=" | jq .access_token + ``` + +1. Using the token from the previous step, send a POST to `:8080/admin` to retrieve a list of available backups from the s3 bucket. + + ```bash + curl --location 'http://alpha-0:8080/admin' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer ' \ + --data '{"query":"query {\n\tlistBackups(input: {location: \"s3://s3..amazonaws.com/\"}) {\n\t\tbackupId\n\t\tbackupNum\n\t\tencrypted\n\t\tpath\n\t\tsince\n\t\ttype\n readTs\n\t}\n}","variables":{}}' + ``` + +1. Using the backup id and token from the previous step, send a POST to `:8080/admin` to start the restore from the s3 bucket to the alpha node. + For example, with `curl`: + + ```bash + curl --location 'http://alpha-0:8080/admin' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer ' \ + --data '{"query":"mutation{\n restore(input:{\n location: \"s3://s3..amazonaws.com/\",\n backupId: \"\"\n }){\n message\n code\n }\n}","variables":{}}' + ``` diff --git a/content/versions/v3.2.1/deploy/restore/grafana.md b/content/versions/v3.2.1/deploy/restore/grafana.md new file mode 100644 index 000000000..55c045f9f --- /dev/null +++ b/content/versions/v3.2.1/deploy/restore/grafana.md @@ -0,0 +1,103 @@ +--- +title: 'Restore Grafana' +date: '2023-10-19T13:52:23-03:00' +categories: ["how-to"] +description: How to restore a Grafana backup on Rhize +weight: 300 +--- + +This guide shows you how to restore Grafana in your Rhize environment. + +## Prerequisites + +Before you start, ensure you have the following: + +- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) +- A [Grafana backup]({{< relref "../backup/grafana" >}}) + +## Steps + +1. Confirm the cluster and namespace are correct: + + {{% param "k8s_cluster_ns" %}} + +1. If a checksum file does not exist for the latest backups, create one: + + ```bash + sha256sum .tar.gz .tar.gz > backup.sums + ``` +1. Copy the checksum file into the new Grafana Pod within the `/home/grafana` directory: + + ```bash + kubectl cp ./backup.sums \ + :/home/grafana + ``` + +1. Copy the Grafana data tar file into the new Grafana Pod within the `/home/grafana` directory: + + ```bash + kubectl cp ./.tar.gz \ + :/home/grafana + ``` + +1. Copy the Grafana configuration tar file into the new Grafana Pod within the `/home/grafana` directory: + + + ```bash + kubectl cp ./.tar.gz \ + :/home/grafana + ``` + +1. Confirm that the checksums match: + + ```bash + kubectl exec -it -- /bin/bash + + :~$ cd /home/grafana + :~$ sha256sum -c backup.sums + ./.tar.gz: OK + ./.tar.gz: OK + + ``` + + + +1. Untar the data file: + + ```bash + tar -xvf .tar.gz --directory / + ``` + +1. Untar the configuration file: + + ```bash + tar -xvf .tar.gz --directory /home/grafana/ + ``` + + + +1. Move over the top of current configuration. + + {{< callout type="info" >}} +Typically some files are configured as a Kubernetes [`ConfigMap`](https://kubernetes.io/docs/concepts/configuration/configmap/) and may need to be configured as part of installation. The following command prompts when it is going to overwrite a file, and if it has the permissions to do so. + {{< /callout >}} + + + ```bash + mv /home/grafana/usr/share/grafana/conf/* /usr/share/grafana/conf/ + ``` + +1. Remove restore files and directory + + ```bash + rm /home/grafana/.tar.gz + rm /home/grafana/.tar.gz + rm /home/grafana/backup.sums + rm -r /home/grafana/usr + ``` + +1. Restart the Grafana Deployment. + + ```bash + kubectl rollout restart deployment grafana -n libre + ``` diff --git a/content/versions/v3.2.1/deploy/restore/graphdb.md b/content/versions/v3.2.1/deploy/restore/graphdb.md new file mode 100644 index 000000000..1a41a8284 --- /dev/null +++ b/content/versions/v3.2.1/deploy/restore/graphdb.md @@ -0,0 +1,137 @@ +--- +title: 'Restore the GraphDB' +date: '2023-10-19T13:52:23-03:00' +ategories: ["how-to"] +description: How to restore a backup of the Rhize Graph DB. +weight: 200 +--- + +This guide shows you how to restore the Graph database in your Rhize environment. + +## Prerequisites + +Before you start, ensure you have the following: + +- The GraphDB Helm chart +- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) +- A [Database backup]({{< relref "../backup/graphdb" >}}) + +## Steps + + + +1. Confirm the cluster and namespace are correct. + + {{% param k8s_cluster_ns %}} + +1. Change to the {{< param application_name >}}-baas helm chart overrides, `baas.yaml`. + Set `alpha.initContainers.init.enable` to `true`. + +1. Upgrade or install the Helm chart. + + ```bash + helm upgrade --install -f baas.yaml {{< param application_name >}}-baas {{< param application_name >}}/baas -n {{< param application_name >}} + ``` + +1. In the Alpha 0 initialization container, create the backup directory. + + + + ```bash + kubectl exec -t {{< param application_name >}}-baas-alpha-0 -c {{< param application_name >}}-baas-alpha-init -- \ + mkdir -p /dgraph/backups + ``` + + +1. If the backup directory does not have a checksums file, create one. + + ```bash + sha256sum .//*.gz > .//backup.sums + ``` + +1. Copy the backup into the initialization container. + + ```bash + kubectl cp --retries=10 ./ \ + {{< param application_name >}}-baas-alpha-0:/dgraph/backups/ \ + -c {{< param application_name >}}-baas-alpha-init + ``` + + After the process finishes, confirm that the checksums match: + + ```bash + kubectl exec -it {{< param application_name >}}-baas-alpha-0 -c {{< param application_name >}}-baas-alpha-init -- \ + 'sha256sum -c /dgraph/backups//backup.sums /dgraph/backups//*.gz' + ``` + +1. Restore the backup to the restore directory. + Replace the `` and `` in the arguments for the following command: + + + ```bash + kubectl exec -t {{< param application_name >}}-baas-alpha-0 -c {{< param application_name >}}-baas-alpha-init -- \ + dgraph bulk -f /dgraph/backups//g01.json.gz \ + -g /dgraph/backups//g01.gql_schema.gz \ + -s /dgraph/backups//g01.schema.gz \ + --zero={{< param application_name >}}-baas-zero-0.{{< param application_name >}}-baas-zero-headless..svc.cluster.local:5080 \ + --out /dgraph/restore --replace_out + ``` +1. Copy the backup to the correct directory: + + + + ```bash + kubectl exec -t {{< param application_name >}}-baas-alpha-0 -c {{< param application_name >}}-baas-alpha-init -- \ + mv /dgraph/restore/0/p /dgraph/p + ``` + + +1. Complete the initialization container for alpha 0. + + ```bash + kubectl exec -t {{< param application_name >}}-baas-alpha-0 -c {{< param application_name >}}-baas-alpha-init -- touch /dgraph/doneinit + ``` + +1. Wait for `{{< param application_name >}}-baas-alpha-0` to start serving the GraphQL API. + +1. Make a database mutation to force a snapshot to be taken. +For example, create a `UnitOfMeasure` then delete it: + + ```bash + kubectl exec -t {{< param application_name >}}-baas-alpha-0 -c {{< param application_name >}}-baas-alpha -- \ + curl --location --request POST 'http://localhost:8080/graphql' \ + --header 'Content-Type: application/json' \ + --data-raw '{"query":"mutation RestoringDatabase($input:[AddUnitOfMeasureInput!]!){\r\n addUnitOfMeasure(input:$input){\r\n unitOfMeasure{\r\n id\r\n dataType\r\n code\r\n }\r\n}\r\n}","variables":{"input":[{"code":"Restoring","isActive":true,"dataType":"BOOL"}]}}' + ``` + Wait until you see {{< param application_name >}}-baas creating a snapshot in the logs. For example: + + ```bash + $ kubectl logs {{< param application_name >}}-baas-alpha-0 + ++ hostname -f + ++ awk '{gsub(/\.$/,""); print $0}' + ... + I0314 20:32:21.282271 19 draft.go:805] Creating snapshot at Index: 16, ReadTs: 9 + ``` + + Revert any database mutations: + + ```bash + kubectl exec -t {{< param application_name >}}-baas-alpha-0 -c {{< param application_name >}}-baas-alpha -- \ + curl --location --request POST 'http://localhost:8080/graphql' \ + --header 'Content-Type: application/json' \ + --data-raw '{"query":"mutation {\r\n deleteUnitOfMeasure(filter:{code:{eq:\"Restoring\"}}){\r\n unitOfMeasure{\r\n id\r\n }\r\n }\r\n}","variables":{"input":[{"code":"Restoring","isActive":true,"dataType":"BOOL"}]}}' + ``` + +1. Complete the initialization container for alpha 1: + + ```bash + kubectl exec -t {{< param application_name >}}-baas-alpha-1 -c {{< param application_name >}}-baas-alpha-init -- \ + touch /dgraph/doneinit + ``` + + And alpha 2: + + ```bash + kubectl exec -t {{< param application_name >}}-baas-alpha-2 -c {{< param application_name >}}-baas-alpha-init -- \ + touch /dgraph/doneinit + ``` diff --git a/content/versions/v3.2.1/deploy/restore/influxdb.md b/content/versions/v3.2.1/deploy/restore/influxdb.md new file mode 100644 index 000000000..37917c082 --- /dev/null +++ b/content/versions/v3.2.1/deploy/restore/influxdb.md @@ -0,0 +1,113 @@ +--- +title: 'Restore InfluxDB' +date: '2023-10-19T13:52:23-03:00' +categories: ["how-to"] +description: How to restore an InfluxDB backup on Rhize +draft: true +weight: 400 +--- + + +This guide shows you how to restore InfluxDB in your Rhize environment. + +## Prerequisites + +Before you start, ensure you have the following: + +- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) +- An [InfluxDB backup]({{< relref "../backup/" >}}) + +## Steps + +1. Confirm the cluster and namespace are correct. + + {{% param k8s_cluster_ns %}} + +1. Create a `PersistentVolumeClaim` for the InfluxDB backup file +(adjust size as needed): + + ```yaml + kind: PersistentVolumeClaim + apiVersion: v1 + metadata: + name: influxdb-backup + spec: + accessModes: + - ReadWriteOnce + resources + requests: + storage: 1Gi + ``` + +1. Modify the Influx deployment: + + ```yaml + apiVersion: extensions/v1beta1 + kind: Deployment + metadata: + name: influxdb + labels: + name: influxdb + ... + volumes: + - name: influx + persistentVolumeClaim: + claimName: influxdb + - name: influx-backup + persistentVolumeClaim: + claimName: influxdb-backup + containers: + - name: influxdb + image: "influxdb:alpine" + volumeMounts: + - mountPath: /var/lib/influxdb + name: influx + - mountPath: /tmp/backup + name: influx-backup + ``` + + +1. Copy the backup file in the Kubernetes backup destination created in the preceding step: + + ```bash + kubectl cp /:/tmp/backup/ + ``` + +1. Delete the InfluxDB deployment, as it needs to be stopped for the backup import. +1. Create a job that uses the same container image and volume. Modify the command: + + ```yaml + kind: Job + metadata: + name: influx-restore + spec: + template: + metadata: + name: influx-restore + labels: + task: influx-restore + spec: + volumes: + - name: influx + persistentVolumeClaim: + claimName: influxdb + - name: backup + persistentVolumeClaim: + claimName: influx-backup + containers: + - name: influx + image: "influxdb:alpine" + command: ["/bin/sh"] + args: ["-c", "influxd restore - metadir /var/lib/influxdb/meta -database -datadir /var/lib/influxdb/data /tmp/backup/"] + volumeMounts: + - mountPath: /var/lib/influxdb + name: influx + - mountPath: /tmp/backup + name: backup + restartPolicy: Never + ``` + +1. Apply the job config. Check that it ran successfully + +1. Re-create your InfluxDB deployment. Use the CLI or HTTP to test that it's available. +1. Remove the backup persistent claim and remove its use from the deployment config. diff --git a/content/versions/v3.2.1/deploy/restore/keycloak.md b/content/versions/v3.2.1/deploy/restore/keycloak.md new file mode 100644 index 000000000..c61eecc86 --- /dev/null +++ b/content/versions/v3.2.1/deploy/restore/keycloak.md @@ -0,0 +1,108 @@ +--- +title: 'Restore Keycloak' +date: '2024-01-08T13:26:23-05:00' +categories: ["how-to"] +description: How to restore a Keycloak backup on Rhize +icon: key +weight: 300 +--- + +This guide shows you how to restore Keycloak in your Rhize environment. + +{{% callout type="caution" %}} + +Restoring Keycloak to a running instance involves downtime. + +Typically, this downtime lasts less than a minute. The exact duration needed depends on network constraints, backup size, and the performance of the Kubernetes cluster. + +{{% /callout %}} + +## Prerequisites + +Before you start, ensure you have the following: + +- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) +- A [Keycloak backup]({{< relref "../backup/keycloak" >}}) + +## Steps + +1. Confirm the cluster and namespace are correct: + + {{% param "k8s_cluster_ns" %}} + +1. Retrieve the Keycloak user password using the following command, replacing `` with your namespace: + + ```bash + kubectl get secret keycloak--postgresql -o jsonpath="{.data.postgres-password}" | base64 --decode + ``` + +1. Extract your backup file: + + ```bash + gzip -d keycloak-postgres-backup-YYYYMMDDTHHMMAA.sql + ``` + +1. To prevent new records from being created while the backup is restored, scale down the Keycloak replicas to `0`. Keycloak will be unavailable after this command. + + ```bash + kubectl scale statefulsets keycloak --replicas=0 + ``` + +1. Scale down the replicas of PostgreSQL to 0, so that existing persistent volume claims and persistent volumes can be removed: + + ```bash + kubectl scale statefulsets keycloak-postgresql --replicas=0 + ``` + +1. Remove the Postgres persistent volume claim: + + ```bash + kubectl delete pvc data-keycloak-postgresql-0 + ``` + +1. Identify the Keycloak Postgres volumes: + + ```bash + kubectl get pv | grep keycloak + ``` + + This displays a list of persistent volume claims related to Keycloak. For example: + + ``` + pvc-95176bc4-88f4-4178-83ab-ee7b256991bc 10Gi RWO Delete Terminating libre/data-keycloak-postgresql-0 hostpath 48d + ``` + + Note the names of the ´pvc-*` items. You'll need them for the next step. + +1. Remove the persistent volumes with this command, replacing `` with the `pvc-*` name from the previous step: + + ``` + $ kubectl delete pv + ``` + +1. Scale up the replicas of PostgreSQL to 1: + + ```bash + kubectl scale statefulsets keycloak-postgresql --replicas=1 + ``` + +1. Restore the backup: + + ```bash + cat keycloak-postgres-backup-YYYYMMDDTHHMMAA.sql | kubectl exec -i keycloak-postgresql-0 -- psql postgresql://postgres:@localhost:5432 -U postgres + ``` + +1. Scale up the replicas of Keycloak to `1`: + + ```bash + kubectl scale statefulsets keycloak --replicas=1 + ``` + +1. Proxy the web portal of Keycloak: + + ```bash + kubectl port-forward svc/keycloak 5101:80 + ``` + + +Confirm access by checking `http://localhost:80`. diff --git a/content/versions/v3.2.1/deploy/upgrade.md b/content/versions/v3.2.1/deploy/upgrade.md new file mode 100644 index 000000000..8ef8008af --- /dev/null +++ b/content/versions/v3.2.1/deploy/upgrade.md @@ -0,0 +1,88 @@ +--- +title: 'Upgrade' +date: '2023-10-18T15:02:24-03:00' +categories: ["how-to"] +description: How to upgrade Rhize +weight: 500 +--- + +This guide shows you how to upgrade Rhize. + +{{< reusable/backup >}} + +## Prerequisites + +Before you start, ensure you have the following: + +- Access to the [Rhize Kubernetes Environment]({{< relref ".." >}}) +- [helm](https://helm.sh/docs/helm/helm_install/) +{{% param pre_reqs %}} + +Be sure that you notify relevant parties of the coming upgrade. + +## Procedure + +First, record the old and new versions, their context, and namespaces. + +1. Check the logs for the {{< param application_name >}} pods, either in Lens or with [`kubectl logs`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#logs). + Ensure there are no errors. + +1. Use Git to pull your Rhize customer build directory. +1. Change to the `kubernetes/charts/{{< param application_name >}}` directory. +1. Check your Kubernetes context and namespace. + + {{% param k8s_cluster_ns %}} + +1. Update Helm repositories with the following command: + + ```bash + helm repo update + ``` + +1. Use the `helm list` command to check for {{< param application_name >}} services. +1. Upgrade with the following command: + + + ```bash + helm upgrade {{< param application_name >}} -f .yaml -n namespace + ``` + +1. Get a token using your credentials. + With `curl`, it looks like this: + + ```bash + curl --location --request POST 'https://- + auth.{{< param application_name >}}/realms/{{< param application_name >}}/protocol/openid-connect/token' \ + --header 'Content-Type: application/x-www-form-urlencoded' \ + --data-urlencode 'grant_type=password' \ + --data-urlencode 'username=system@{{< param application_name >}}.com' \ + --data-urlencode 'password=' \ + --data-urlencode 'client_id={{< param application_name >}}Baas' \ + --data-urlencode 'client_secret=' + ``` + +1. Redeploy the schema. To do so, you need to interact with the `alpha` service on port `8080`. You can do this in multiple ways. Either enter the alpha shell with a command such as `kubectl exec --stdin baas-alpha-0 -- sh`, or forward the port to your local instance using a command such as `kubectl port-forward baas-alpha-0 8080:8080`. + + For example, using port forwarding, a `curl` command to deploy the schema looks like this: + + ```bash + curl --location -X POST 'http://localhost:/admin/schema' \ + -H "Authorization: Bearer $" \ + -H "content-Type: application/octet-stream" \ + --data-binary .sdl + ``` + + The schema file is likely called something like `schema.sdl`. + + +1. Restart the Apollo Router Statefulset so that the Supergraph is composed with all the latest changes. For example: + +```bash +kubectl rollout restart statefulset router +``` + +## Verify success + +Verify success in Kubernetes by checking that the version upgraded properly and that the logs are correct. + +Inform your team that the upgrade was successful. diff --git a/content/versions/v3.2.1/how-to/_index.md b/content/versions/v3.2.1/how-to/_index.md new file mode 100644 index 000000000..660f57a5b --- /dev/null +++ b/content/versions/v3.2.1/how-to/_index.md @@ -0,0 +1,29 @@ +--- +title: User guides +description: Topics about how to use Rhize to query data, build and run workflows, and build frontends. +weight: 200 +identifier: how-to +icon: clipboard-list +cascade: + domain_name: libremfg.ai + brand_name: Libre + application_name: libre + pre_reqs: |- + - Permissions to access the [Rhize Kubernetes Environment](/how-to/install/configure-kubernetes") + - [kubectl](https://kubernetes.io/docs/tasks/tools/) + - Optional: [kubectx](https://github.com/ahmetb/kubectx) utilities + - `kubectx` to manage multiple clusters + - `kubens` to switch between and configure namespaces easily + - Optional: the [k8 Lens IDE](https://k8lens.dev), if you prefer to manage Kubernetes graphically + k8s_cluster_ns: |- + ```bash + ## context + kubectl config current-context + ## namespace + kubectl get namespace + ``` +--- + +Topics about how to use Rhize to query data, build and run workflows, and build frontends. + +{{< card-list >}} diff --git a/content/versions/v3.2.1/how-to/audit.md b/content/versions/v3.2.1/how-to/audit.md new file mode 100644 index 000000000..083caabd7 --- /dev/null +++ b/content/versions/v3.2.1/how-to/audit.md @@ -0,0 +1,75 @@ +--- +title: 'Audit' +date: '2023-12-20T12:47:09-03:00' +categories: ["how-to"] +description: How to use the Audit log to inspect all events in the Rhize system +weight: 600 +icon: search +--- + +The _Audit Log_ provides a tamper-proof and immutable audit trail of all events that occur in the Rhize system. +Users with appropriate permissions can access the audit log either through the UI menu or the GraphQL API. + +## Prerequisites + +To use the audit log, ensure you have the following: + +- If accessing to your Rhize UI environment, a user account with appropriate permissions +- If accessing through GraphQL, you also need: + - The ability to [Use the Rhize GraphQL API]({{< relref "gql" >}}) + - A token configured so that `audience` includes `audit`, and the scopes contain `audit:query`. + + This scope should be created by BaaS, not manually. For details, refer to [Set up Keycloak]({{< relref "../deploy/install/keycloak/" >}}). + + +## Audit through the UI + +To inspect the audit log through the Rhize UI, follow these steps: + +1. From the UI menu, select **Audit**. +1. Select the users that you want to include in the audit. +1. Use the time filters to select the pre-defined or custom range that you want to return. + +On success, a log of events appears for the users and time ranges specified. +For a description of the fields returned, refer to the [Audit fields](#audit-fields) section. + + +### Audit fields + +In the audit UI, each record in the audit has the following fields: + +| Field | Description | +|--------------------|-------------------------------------------------------------------------------------------------------------------------| +| Timestamp | The time when the event occurred | +| User | The user who performed the operation | +| Operation | The [GraphQL operation]({{< relref "gql/call-the-graphql-api#operations" >}}) involved | +| Entity Internal ID | The ID of the resource that was changed | +| Attribute | What changed in the resource. This corresponds to the object properties as defined by the API and its underlying schema | +| Value | The new value of the updated attribute | + + +## Audit through GraphQL + +The audit log is also exposed through the GraphQL API. +To access it, use the `queryAuditLog` operation, and add [filters]({{< relref "gql/call-the-graphql-api#filters" >}}) for the time range and users. + + +Here's an example query: + +```gql +query { + queryAuditLog(filter: {start: "2023-01-01T00:00:00Z", end:"2023-12-31T00:00:00Z", tagFilter:[{id:"user", in: ["admin@libremfg.com"]},{id:"operation", in: ["set"]}]}, order: {}) { + operationType + meta { + time + user + } + event { + operation + uid + attribute + value + } + } +} +``` diff --git a/content/versions/v3.2.1/how-to/bpmn/_index.md b/content/versions/v3.2.1/how-to/bpmn/_index.md new file mode 100644 index 000000000..65323fbf9 --- /dev/null +++ b/content/versions/v3.2.1/how-to/bpmn/_index.md @@ -0,0 +1,16 @@ +--- +title: 'Write BPMN workflows' +date: '2023-09-22T14:50:39-03:00' +draft: false +categories: "how-to" +cascade: + icon: decision-node +description: Create BPMN workflows to handle inputs, listen for events, and throw triggers. +weight: 200 +--- + +In the following topics, learn how to use Rhize's BPMN engine to orchestrate processes. +Coordinate tasks between different systems, transform and calculate data, and set triggers to run workflows automatically. + + +{{< card-list >}} diff --git a/content/versions/v3.2.1/how-to/bpmn/bpmn-elements.md b/content/versions/v3.2.1/how-to/bpmn/bpmn-elements.md new file mode 100644 index 000000000..9af3c3e0e --- /dev/null +++ b/content/versions/v3.2.1/how-to/bpmn/bpmn-elements.md @@ -0,0 +1,404 @@ +--- +title: 'BPMN elements' +date: '2023-09-26T11:10:37-03:00' +draft: false +categories: [reference] +description: >- + A reference of all BPMN elements used in the Rhize BPMN engine. +weight: 1000 +boilerplate: + jsonata_response: >- + Optional [JSONata](https://docs.jsonata.org/1.7.0/overview) expression to map to the [process variable context](#process-variable-context) + max_payload: >- + Number. If the response length exceeds this number of characters, Rhize throws an error. + connection_timeout: >- + Number. Time in milliseconds to establish a connection. + graph_vars: >- + JSON. Variables for the GraphQL query. + data_id: >- + The ID of the datasource + data_expression: >- + JSON or JSONata expression. Topics and values to write to + headers: >- + Additional headers to send in the request +--- + +This document describes the parameters available to each BPMN element in the Rhize UI. +These parameters control how users set conditions, transform data, access variables, call services, and so on. + + + + +## Common parameters + + +Every BPMN workflow and every element that the workflow contains have the following parameters: + +| Parameter | Description | +|----------------------|---------------------------------------------------------------------------------------------------------------------------| +| ID | Mandatory unique ID. For guidance, follow the [BPMN naming conventions]({{< relref "./naming-conventions" >}}). | +| Name | Optional human readable name. If empty, takes ID value. | +| Documentation | Optional freeform text for additional information | +| Extension properties | Optional metadata to add to workflow or node | + + +## Events + +_Events_ are something that happen in the course of a process. +In BPMN, events are drawn with circles. +Events have a _type_ and a _dimension_. + +{{< tabs items="Events,Message type,Timer type" >}} +{{% tab "Events" %}} +![A simplified model of events with no activities](/images/bpmn/rhize-bpmn-events.png) +{{% /tab %}} +{{% tab "Message type" %}} +Message events subscribe or publish to the Rhize broker.
+![A message event](/images/bpmn/bpmn-message-event.svg) +{{% /tab %}} + +{{% tab "Timer type" %}} +Timer events start according to some interval or date, or wait for some duration.
+![Timer event](/images/bpmn/bpmn-timer-event.svg ) +{{% /tab %}} +{{< /tabs >}} + + +In event-driven models, events can happen in one of three _dimensions_: + +Start +: All processes begin with some trigger that starts an event. Start events are drawn with a single thin circle. + +Intermediate. +: Possible events between the start and end. Intermediate events might start from some trigger, or create some result. They are drawn with a double thin line. + +End +: All processes end with some result. End events are drawn with a single thick line. + +Besides these dimensions, BPMN also classifies events by whether they _catch_ a trigger or _throw_ a result. +All start events are catch events; that is, they react to some trigger. +All end events are throw events; that is, they terminate with some output—even an error. +Intermediate events may throw or catch. + +Rhize supports various event types to categorize an event, as described in the following sections. +As with [Gateways](#gateways) and [Activities](#activities), event types are marked by their icons. +Throwing events are represented with icons that are filled in. + + + +### Start events + + +Start events are triggered by the `CreateAndRunBPMN` and `CreateAndRunBPMNSync` {{< abbr "mutation" >}} operations. +The parameters for a start event are as follows: + +| Parameter | Description | +|-----------|------------------------------------------------------------------------------------------------------------------------| +| Outputs | Optional variables to add to the {{< abbr "process variable context" >}}. JSON or JSONata. | + + +### Message start events + +Message events are triggered from a message published to the Rhize broker. +The parameters for a message event are as follows: + +| Parameter | Description | +|-----------|---------------------------------------------------------------------------------------------------| +| Message | The topic the message subscribes to on the Rhize Broker. The topic structure follows MQTT syntax. | +| Outputs | Optional variables to add to the {{< abbr "process variable context" >}}. JSON or JSONata. | + +### Timer start events + +Timer start events are triggered either at a specific date or recurring intervals. +The parameters for a timer start event are as follows: + +| Parameter | Description | +|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Timer | One of

  • `Cycle`, to begin at recurring intervals. For example,`R5/2024-05-09T08:12:55/PT10S` starts on `2024-05-09` and executes every 10 seconds for 5 repetitions. If `` is not set, Rhize uses `2023-01-01T00:00:00Z`.
  • `Date`, to happen at a certain time, for example, `2024-05-09T08:12:55`
Enter values in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format. | +| Outputs | Optional variables to add to the {{< abbr "process variable context" >}}. JSON or JSONata. | + +### Intermediate message events + +Intermediate message events throw a message to the Rhize NATS broker. +This may provide info for a subscribing third-party client, or initiate another BPMN workflow. + +The parameters for an intermediate message event are as follows: + +| Parameter | Description | +|-----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Message | The topic the message publishes to on the Rhize Broker. The topic structure follows MQTT syntax | +| Inputs | Variables to send in the body. For messages to the Rhize broker, use the [special variable]({{< relref "./variables">}}) `BODY`. Value can be JSON or JSONata. | +| Headers | {{< param boilerplate.headers >}} | +| Outputs | JSON or JSONata. Optional variables to add to the {{< abbr "process variable context" >}}. | + +### Intermediate timer events + +An intermediate message pauses for some duration. +The parameters for an intermediate timer event are as follows: + +| Parameter | Description | +|-----------|------------------------------------------------------------------------------------------------------------------------| +| Timer| A duration to pause. Enter values in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format.| +| Outputs | Optional variables to add to the {{< abbr "process variable context" >}}. The assignment value can be JSON or JSONata. | + + +## Service tasks + +In BPMN, an _activity_ is work performed within a business process. + +On the Rhize platform, most activities are _tasks_, work that cannot be broken down into smaller levels of detail. +Tasks are drawn with rectangles with rounded corners. + +{{< callout type="info" >}} +Besides tasks, you can also use [_call activities_](#call-activities), processes which call and invoke other processes. +{{< /callout >}} + +A service task uses some service. +In Rhize workflows, service tasks include [Calls to the GraphQL API]({{< relref "../gql/call-the-graphql-api" >}}) (and REST APIs), data source reads and writes, and JSON manipulation. +These service tasks come with templates. + +As with [Gateways](#gateways) and [events](#events), service task are marked by their icons. + + +{{< figure +caption="Service tasks have a gear icon marker" +alt="An empty service task" +src="/images/bpmn/bpmn-service-task.svg" +width="100" +>}} + + +To add a service task, select the change icon ("wrench"), then select **Service Task**. +Use **Templates** to structure the service task call and response. + +The service task templates are as follows + +### JSONata transform + + +Transform JSON data with a JSONata expression. +For detailed examples, read [The Rhize Guide to JSONata]({{< relref "use-jsonata" >}}). + + +| Call parameters | Description | +|------------------|-------------------------------------------------------------------------------| +| Input | Input data for the transform | +| Transform | The transform expression | +| Max Payload size | {{< param boilerplate.max_payload >}} | + + +Besides the call parameters, the JSONata task has following additional fields: + +| Parameter | Description | +|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Input response | The name of the variable to add to the {{< abbr "process variable context" >}}| + + +### GraphQL Query + +Run a [GraphQL query]({{< relref "../gql/query" >}}) + +| Call parameters | Description | +|--------------------|----------------------------------------------| +| Query body | GraphQL query expression | +| Variables | {{< param boilerplate.graph_vars >}} | +| Connection Timeout | {{< param boilerplate.connection_timeout >}} | +| Max Payload size | {{< param boilerplate.max_payload >}} | + +Besides the call parameters, the Query task has following additional fields: + +| Parameter | Description | +|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Input response | {{% param boilerplate.jsonata_response %}}. For GraphQL operations, use this only to map values. Rely on [GQL filters]({{< relref "../gql/filter" >}}) to limit the payload. | +| Headers | {{< param boilerplate.headers >}} | + +### GraphQL Mutation + +Run a [GraphQL mutation]({{< relref "../gql/mutate" >}}) + +| Call parameters | description | +|--------------------|----------------------------------------------| +| Mutation body | GraphQL Mutation expression | +| Variables | {{< param boilerplate.graph_vars >}} | +| Connection Timeout | {{< param boilerplate.connection_timeout >}} | +| Max Payload size | {{< param boilerplate.max_payload >}} | + +Besides the call parameters, the mutation task has following additional fields: + +| Parameter | Description | +|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Input response | {{% param boilerplate.jsonata_response %}}. For mutations, use this only to map values. Use the mutation call to limit the payload. | +| Headers | {{% param boilerplate.headers %}} | + +### Call REST API + +HTTP call to a REST API service. + +| Call parameters | Description | +|--------------------|------------------------------------------------------------------------------------| +| Method Type | One of `GET`, `POST`, `PATCH`, `PUT`, `DELETE` | +| Verification | Boolean. Whether verify the Certificate Authority provided in the TLS certificate. | +| URL | The target URL | +| URL Parameters | JSON. The key-value pairs to be used as query parameters in the URL | +| HTTP Headers | JSON. The key-value pairs to be used as request headers | +| Connection Timeout | {{% param boilerplate.connection_timeout %}} | +| Max Payload size | {{< param boilerplate.max_payload >}} | + +Besides the call parameters, the REST task has following additional fields: + +| Parameter | Description | +|----------------|--------------------------------------------| +| Input response | {{% param boilerplate.jsonata_response %}} | +| Headers | {{% param boilerplate.headers %}} | + +### JSON schema + +Validate that a payload conforms to a configured [JSON schema](https://json-schema.org/). +For example, you can validate that `data.arr` contains an array of numbers +and that `userID` contains a string of certain length. + +| Call Parameters | Description | +|------------------|------------------------------------------------------------------------------------------------------------------------------------------------| +| Schema | A JSON schema. You can also create one from a JSON file with a tool such as [JSON to JSON schema](https://transform.tools/json-to-json-schema) | +| Variable | Optional. Key of specific variable to validate (default checks all variables in {{< abbr "process-variable-context" >}} | + +The Schema task has the following output that you can define as a variable: + +| Response mapping | Description | +|------------------|-------------------------------------------------------------------------| +| Valid | The boolean output of the schema evaluation. `True` if schema is valid. | +| Validation error | A string that reports the validation errors if the schema is invalid. | + +### Read Datasource + +Read values from topics of a datasource (for example, an OPC-UA server) + +| Call parameters | Description | +|------------------|-------------------------------------------| +| Data source | {{< param boilerplate.data_id >}} | +| Data | {{< param boilerplate.data_expression >}} | +| Max Payload size | {{< param boilerplate.max_payload >}} | + +Besides the call parameters, the data source task has following additional fields: + +| Parameter | Description | +|----------------|--------------------------------------------------------------------------------| +| Input response | The variable name to store the response in {{< abbr "process variable context" >}} | +| Headers | {{< param boilerplate.headers >}} | + +### Write Datasource + +Write values to topics of a datasource. + +| Call parameters | Description | +|------------------|-------------------------------------------| +| Data source | {{< param boilerplate.data_id >}} | +| Data | {{< param boilerplate.data_expression >}} | +| Max Payload size | {{< param boilerplate.max_payload >}} | + + +Besides the call parameters, the data source task has following additional fields: + +| Parameter | Description | +|----------------|--------------------------------------------------------------------------------| +| Input response | The variable name to store the response in {{< abbr "process variable context" >}} | +| Headers | {{< param boilerplate.headers >}} | + + +## Call activities + +![Call activities have a task with an icon to expand](/images/bpmn/bpmn-call-activity.svg) + +A _call activity_ invokes another workflow. +In this flow, the process that contains the call is the _parent_, and the process that is called is the _child_. + +Call activities have the following parameters: + +| Parameters | Description | +|--------------------|-----------------------------------------------------------------------| +| Called element | The ID of the called process | + +The inputs have the following parameters: + +| Parameters | Description | +|--------------------|-----------------------------------------------------------------------| +|Local variable name | The name of the variable as it will be accessed in the child process (that is, the key name) | +|assignment value | The value to pass from the parent variable context| + +The outputs have the following parameters: + +| Parameters | Description | +|---------------------|-------------------------------------------------------------------------------------------------------| +|Local variable name | What to name the incoming data, as it will be accessed in the parent process (that is, the key name) | +| assignment value | The value to pass from the child variable context | + +For a guide to reusing functions, read the [Reuse workflows section]({{< relref "./create-workflow/#reuse-workflows" >}}) in the "Create workflow" guide. + +## Gateways + +_Gateways_ control how sequence flows interact as they converge and diverge within a process. +They represent mechanisms that either allow or disallow a passage. + +BPMN notation represents gateways as diamonds with single thin lines, as is common in many diagrams with decision flows. +Besides decisions, however, Rhize's BPMN notation also includes parallel gateways. + +As with [Events](#events) and [Activities](#activities), gateway types are marked by their icons. + +{{< figure +alt="Gateway with two branches" +caption="Drawn as diamonds, gateways represent branches in a sequence flow." +src="/images/bpmn/bpmn-gateway-overview.svg" +>}} + +### Exclusive gateway + +![exclusive gateways are marked by an "x" icon](/images/bpmn/bpmn-gateway-exclusive.svg) + +Marked by an "X" icon, an _exclusive gateway_ represents a point in a process where only one path is followed. +In some conversations, an exclusive gateway is also called an _XOR_. + +If a gateway has multiple sequence flows, all flows except one must have a conditional [JSONata expression](https://docs.jsonata.org/1.7.0/overview) that the engine can evaluate. +To designate a default, leave one flow without an expression. + +{{< figure +alt="An exclusive gateway that has a condition and a default" +src="/images/bpmn/screenshot-rhize-bpmn-exclusive-gateway.png" +width="50%" +caption="An exclusive gateway with a condition and default. Configure conditions as JSONata expressions" +>}} + +Exclusive gateways can only branch. That is, they cannot join multiple flows. + +### Parallel gateway + +![Parallel gateways are marked by a "+" icon](/images/bpmn/bpmn-gateway-parallel.svg) + +Marked by a "+" icon, _parallel gateways_ indicate a point where parallel tasks are run. + +{{< figure +alt="A parallel gateway that branches and rejoins" +src="/images/bpmn/screenshot-rhize-bpmn-parallel-gateway.png" +width="50%" +caption="Parallel gateways run jobs in parallel." +>}} + +{{% details title="Parallel joins" %}} + +You can join parallel tasks with another parallel gateway. +This joins the variables from both branches to [process variable context](#process-variable-context). +Note that parallel joins have performance costs, so be mindful of using them, especially in large tasks. +To learn more, read [Tune BPMN performance]({{< relref "tune-performance" >}}). + +{{< figure +alt="A parallel gateway that branches and rejoins" +src="/images/bpmn/screenshot-rhize-bpmn-parallel-join.png" +width="50%" +caption="Parallel joins join variable context, but have performance costs." +>}} + +{{% /details %}} + +## Variables and expressions + +As data passes and transforms from one element to another, variables remain in the _process variable context_. +You can access these variables through JSONata expressions. diff --git a/content/versions/v3.2.1/how-to/bpmn/create-workflow.md b/content/versions/v3.2.1/how-to/bpmn/create-workflow.md new file mode 100644 index 000000000..a0a50fc7a --- /dev/null +++ b/content/versions/v3.2.1/how-to/bpmn/create-workflow.md @@ -0,0 +1,240 @@ +--- +title: "Overview: orchestrate processes" +categories: "how-to" +description: > + An overview of how to use Rhize's custom BPMN engine and UI to orchestrate workflows. +weight: 10 +aliases: + - "/how-to/bpmn/orchestration-overview" +--- + +This guide provides a quick overview of the major features of the Rhize {{< abbr "BPMN">}} engine and interface, with links to detailed guides for specific topics. +For a reference of all BPMN elements and their parameters, refer to [BPMN elements]({{< relref "./bpmn-elements" >}}). + +The Rhize BPMN UI provides a graphical interface to transform and standardize data flows across systems. +Such _process orchestration_ has many uses for manufacturing. +For example, you can write a BPMN workflow to do any of the following: +- Automatically ingest data from ERP and SCADA systems, then transform and store the payloads in the standardized ISA-95 representation +- Coordinate tasks across various systems, creating a layer for different data and protocols to pass through +- Calculate derived values from the data that is exchanged to perform functions such as waste calculation and process control. + + +{{< bigFigure +src="/images/bpmn/rhize-bpmn-coordination-between-multiple-systems.png.webp" +alt="An example of a workflow that transforms, calculates, stores, and sends to external systems" +width="70%" +>}} + + +{{< callout type="info" >}} +Rhize BPMN workflows conform to the visual grammar described in the OMG standard for [Business Process Model and Notation](https://www.omg.org/spec/BPMN/2.0/). +Each process is made of _events_ (circles), _activities_ (rectangles), _gateways_ (diamonds), and _flows_ (arrows). +Some elements are extended for Rhize-specific features, such as service tasks that call the GraphQL API. +Some elements from the standard are unused and thus do not appear in the UI. +{{< /callout >}} + +## Request and send data + +Workflows often exchange data between Rhize and one or more external systems. +The BPMN activity _task templates_ provide multiple ways to communicate with internal and external systems, +and pass data over different protocols. +Additionally, _message_ events provide templates to publish and subscribe to the Rhize broker. + +Each template has a set of parameters to configure it. +**To use a template**: +1. Select the _activity_ (rectangle) element. +1. Select **Template** and then choose the template you want. +1. Configure the template according to its [Task parameters]({{< relref "./bpmn-elements#jsonata-transform" >}}). + +### Interact with the Rhize API + +Use GraphQL tasks to query and change data in your manufacturing knowledge graph. +For example: +- A scheduling workflow could use the [Query task]({{< relref "./bpmn-elements/#graphql-query" >}}) to find all `JobResponses` whose state is `COMPLETED`. +- An ingestion workflow might use a [Mutation task]({{< relref "./bpmn-elements/#graphql-mutation" >}}) to update new `jobResponse` data that was published from a SCADA system. + +You can also use [JSONata]({{< relref "./bpmn-elements#jsonata-transform" >}}) in your GraphQL payloads to dynamically add values at runtime. +For details about how to use the Rhize API, read the [Guide to GraphQL]({{< relref "../gql" >}}). + +### Interact with external systems + +To make HTTP requests to external systems, use the [REST task]({{< relref "./bpmn-elements#call-rest-api" >}}). +For example, you might send a `POST` with performance values to an ERP system, or use a `GET` operation to query test results. + +{{< callout type="info" >}} +Besides REST, you can use this template to interact with any HTTP API. +{{< /callout >}} + +### Publish and subscribe + +Besides HTTP, workflows can also publish and subscribe messages over MQTT, NATS, and OPC UA. + +{{< bigFigure +alt="A workflow that listens to a message and throws a message" +src="/images/bpmn/rhize-bpmn-message-start-throw-conditional.png" +caption="A workflow that evaluates a message and throws a if the payload meets a certain condition message" +width="60%" +>}} + +**To publish and subscribe to the Rhize broker:** +1. Select a start (thin circle) or intermediate (double-line) circle. +1. Select the wrench icon. +1. Select the message event (circle with an envelope). +1. Configure the message topic and body according to the [Event parameters]({{< relref "./bpmn-elements/#events" >}}). +1. If using an [Intermediate throw event]({{< relref "./bpmn-elements#service-tasks" >}}), name the variable `BODY`. + +**To listen and publish to an edge device:** +1. [Create a data source]({{< relref "../publish-subscribe/connect-datasource/" >}}). +1. In your workflow, select the task. And choose the **Data source** template. +1. Configure the [Data Source task parameters]({{< relref "./bpmn-elements#service-tasks" >}}). + +The strategy you choose to send and receive message data depends on your architectural setup. +Generally, data-source messages come from level-1 and level-2 devices on the edge, +and messages published to the Rhize broker come from any NATS, MQTT, or OPC UA client. +The following diagram shows some common ways to interact with messages through BPMN. + +{{< bigFigure +src="/images/bpmn/diagram-rhize-bpmn-control-message-flow.svg" +alt="Diagram providing decision of control flows" +width="50%" +>}} + +## Transform and calculate + +As the data in a workflow passes from start node to end node, it often undergoes some secondary processing. +Mew properties might be added, some data might be filtered, or a node might create a set of derived values from the original input. +For example, you might use calculate statistics, or transform a message payload into a format to be received by the Rhize API or an external system. + + +{{< bigFigure +src="/images/bpmn/screenshot-rhize-jsonata-map.png" +alt="Annotated and truncated version of an example transformation to the operationEvent definition" +caption="Annotated and truncated version of mapping an external event to the `operationEvent` definition" +width=" 70%" +>}} + +To calculate and transform data, BPMN nodes can interpret the JSONata expression language. +For details, read the complete [Rhize guide to JSONata](/how-to/bpmn/use-jsonata). + +## Control flows + +As data passes through a workflow, you might need to conditionally direct it to specific tasks, transformations, and events. +For this, Rhize has _gateways_, represented as diamonds. + +### Exclusive gateway. + +Represented by an `X` symbol, exclusive gateways create decision branches based on whether a condition is true. +While you can use JSONata conditionals to control flows within tasks, +exclusive gateways are the most common and visually understandable way to execute conditional steps. + +To use an exclusive gateway: +1. Create an arrow from the control start to an exclusive gateway (diamond with `X` symbol). +1. Use arrows to create outgoing conditions to new tasks or events. +1. Leave the default condition blank. +1. In all other arrows, use the **Condition** field to write a boolean expression. + +{{< bigFigure +src="/images/bpmn/screenshot-rhize-bpmn-exclusive-gateway.png" +alt="Screenshot showing how gateways create a Job order only if the material state is ready." +caption="This gateway creates a job order only if its material state is ready." +width="70%" +>}} + +### Parallel gateways + +Represented by a `+` (plus sign) symbol, _parallel gateways_ execute multiple tasks at the same time. +To use a parallel gateway: +1. Select a gateway. +1. Use the wrench sign to change its condition to parallel. +1. Use arrows to create parallel conditions. + +When the workflow runs, each parallel branch executes at the same time. + +{{< bigFigure +src="/images/bpmn/screenshot-rhize-bpmn-parallel-gateway.png" +alt="Simultaneously add a record to the database and send an alert" +caption="Simultaneously add a record to the database and send an alert" +width="50%" +>}} + + +## Trigger workflows + +You have multiple ways to trigger a start condition. + +{{% reusable/bpmn-triggers %}} + +To learn more, read [Trigger workflows]({{< relref "./trigger-workflows" >}}). + +## Reuse workflows + +BPMN workflows are _composable_, where each element can be reused by others. +For example, you might create a workflow that calculates common statistics, or one that makes a specified call to an external system. +Using _call activities_ other workflows can reuse the workflow. + + +{{< bigFigure +alt="A call activity" +src="/images/bpmn/diagram-rhize-bpmn-call-activity.png" +width="55%" +caption="An example of a main workflow calling a function. [Template](https://github.com/libremfg/rhize-templates/tree/main/bpmn/call-activity-calculate-stats)" +>}} + +To reuse a workflow: +1. Drag the task element (rectangle) into the workflow. +1. Select the wrench icon. +1. Select **Call Activity**. +1. Configure it according to the [call activity parameters]({{< relref "./bpmn-elements#call-activities" >}}). + +## Access process variable context + +As data passes through the nodes of a workflow, the nodes share access to a variable space. +Nodes can access these variables, create new variables, and mutate existing ones. +This overall variable object is called _process variable context_. + +When working with variables, keep the following in mind: +- **Access the root variable context through `$.`**. + + This follows the conventions of JSONata. + For details and examples, read [Use JSONata]({{< relref "./use-jsonata" >}}). + +- **Access nested properties with dot notation.** + + For example, the following is a reference to the first item in the `orders` object in the variable context: + ``` + $.orders[] + ``` + +- **You can store a node's output in a variable.** + + Many output fields offer a way to create a variable. + For example, the JSON schema field has two variables that you can name, + one that outputs a boolean based on whether the input is valid, and another that outputs + the error string if the variable is invalid. + + You can access these variables in later nodes (unless you mutate them). + +- **Variables.** + + If you direct output to a variable that already exists, the new value overwrites the old one. + This behavior can be used to manage the overall memory footprint of a workflow. + +- **The maximum context size is configurable.** + + By default, the process variable context has a maximum size of 1MB. + When an activity outputs data, the output is added to the process variable context. + When variable size gets large, you have multiple strategies to reduce its size (besides mutating variables). + For ideas, refer to [Tune BPMN performance]({{< relref "./tune-performance" >}}). + +- **You can trace variable context.** + + For details, refer to the [Debug guide]({{< relref "./debug-workflows" >}}). + + +## Examples + +Rhize has a repository of templates that you can import and use in your system. +Use these to explore how the key functionality works. +[Rhize BPMN templates](https://github.com/libremfg/bpmn-templates) + + diff --git a/content/versions/v3.2.1/how-to/bpmn/debug-workflows.md b/content/versions/v3.2.1/how-to/bpmn/debug-workflows.md new file mode 100644 index 000000000..05b9ec486 --- /dev/null +++ b/content/versions/v3.2.1/how-to/bpmn/debug-workflows.md @@ -0,0 +1,248 @@ +--- +title: Handle errors and debug +date: '2024-04-24T19:35:09+03:00' +categories: ["how-to"] +description: Strategies to handle errors in your BPMN workflows, and ways to debug workflows when things don't work as expected. +weight: 250 +--- + +Errors come in two categories: expected and unexpected. +The Rhize BPMN engine has ways to handle both. + +A robust workflow should have built-in logic to anticipate errors. +For unexpected issues, Rhize also creates a _trace_ for each workflow, +which you can use to observe the behavior and performance of the workflow at each element as it executes sequentially. +You can also use debug flags and variables to trace variable context as it transforms across the workflow. + +## Strategies to handle errors + +All error handling likely uses some conditional logic. +The workflow author anticipates the error and then writes some logic to conditionally handle it. +However, you have many ways to handle conditions. When deciding how to direct flows, consider both the context of the error and overall readability of your diagram. +This section describes some key strategies. + +### Gateways + +Use [exclusive gateways]({{< relref "./bpmn-elements#gateways" >}}) for any type of error handling. +For example, you might define a normal range for a value, then send alerts for when the value falls outside of this range. +If it makes sense, these error branches also might flow into an early end event. + + +{{< bigFigure +src="/images/bpmn/screenshot-rhize-bpmn-error-handling-custom-response.png" +alt="A BPMN workflow with customResponse in the output of the end node" +caption="Download this workflow from [BPMN templates](https://github.com/libremfg/rhize-templates/tree/main/bpmn/custom-response-error-events)" +width="80%" +>}} + + +### JSON schema validation + +Validation can greatly limit the scope of possible errors. +To validate your JSON payloads, use the [JSON schema task]({{< relref "./bpmn-elements#json-schema" >}}). + +The JSON schema task outputs a boolean value that indicates whether the input conforms to the schema that you set. +You can then set a condition based on whether this `valid` variable is true, and create logic to handle errors accordingly. +For example, this schema requires that the input variables include a property `arr` whose +value is an array of numbers. + + +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Generated schema for Root", + "type": "object", + "properties": { "arr": { "type": "array", "items": { "type": "number" } } }, + "required": ["arr"] +} +``` + +In a production workflow, you might use this exact schema to validate the input for a function that calculates statistics (perhaps choosing a different variable name). + + +{{< bigFigure +src="/images/bpmn/screenshot-rhize-bpmn-json-schema.png" +alt="Screenshot of a conditional that branches when the JSON schema task receives invalid input." +caption="A conditional that branches when the JSON schema task receives invalid input. [Download the template](https://github.com/libremfg/rhize-templates/tree/main/bpmn/call-activity-calculate-stats)" +width="35%" +>}} + + + + +### JSONata conditions + +Besides the logical gateway, it may make sense to use JSONata ternary expressions in one of the many parameters that accepts JSONata expressions. +For example, this expression creates one message body if `valid` is `true` and another if not: + +```jsonata += +{ + "message": $.valid ? "payload is valid" : "Invalid payload" +} +``` + +### Check JSONata output + +If a field has no value, JSONata outputs nothing. +For example, the following expression outputs only `{"name": "Rhize"}`, +because no `$err` field exists. + +{{< tabs items="Expression,Output" >}} +{{% tab "Expression" %}} +```js +=( + $name := "Rhize"; + + + { + "name": $name, + "error": $err + } +) +``` +{{% /tab %}} +{{% tab "Output" %}} +``` +{ + "name": "Rhize" +} +``` +{{% /tab %}} +{{% /tabs %}} + +You can use this behavior to direct flows. +For example, an exclusive gateway may have a condition such as `$exists(err)` that flows into an error-handling condition. + + + +### Create event logging + +To persist error handling, you can set gateways that flow to mutation tasks that use the `addEvent` operation. +The added event may be a successful operation, an error, or both, +creating a record of events emitted in the workflow that are stored in your manufacturing knowledge graph. +This strategy increases the observability of errors and facilitates future analysis. +It may also be useful when combined with the debugging strategies described in the next section. + +## Strategies to debug + +For detailed debugging, +you can use an embedded instance of [Grafana Tempo](https://github.com/grafana/tempo) to inspect each step of the workflow, node by node. +To debug on the fly, you may also find it useful to use `customResponse` and intermediate message throws to print variables and output at different checkpoints. + +### Debug from the API calls + +When you first test or run a workflow, consider starting the testing and debugging process from an API trigger. +All API triggers return information about the workflow state (for example `COMPLETED` or `ABORTED`). +With the `createAndRunBpmnSync` operation, you can also use the `customResponse` to provide information from the workflow's variable context. +For details of how this works, read the guide to [triggering workflows]({{< relref "trigger-workflows" >}}). + +For example, consider a workflow that has two nodes, a Message throw event and a REST task. +1. When the message completes, the user writes `Message sent` into `customResponse` as an output variable. +1. When the REST task completes, the response is saved into `customResponse`. + +So the `jobState` property reports on the overall workflow status, and `customResponse` serves as a checkpoint to report the state of each node execution. +You can also request the `dataJSON` field, which reports the entire variable context at the last node. +Now imagine that the user has started the workflow from the API and receives this response: + +``` +{ + "data": { + "createAndRunBpmnSync": { + "jobState": "ABORTED", + "customResponse": "message sent", + "traceID": "993ee32af9522f5b35b4ec80f4ff58a8" + } + } +} +``` + + +Note how `ABORTED` indicates the workflow failed somewhere. +Yet, the value of `customResponse` must have been set after the message event executed. +So the problem is likely with the REST node. + +You could also use a similar strategy with intermediate message events. +However, while `customResponse` and messages are undoubtedly useful debugging methods, they are also limited— the BPMN equivalents of `printf()` debugging. +For full-featured debugging, use the `traceID` to explore the workflow through Tempo. + +### Debug in Tempo + +{{< callout type="info" >}} +The instructions here provide the minimum about using Tempo as a tool. +To discover the many ways you can filter your BPMN traces for debugging and analysis, +refer to the [official documentation](https://grafana.com/docs/tempo/latest/). +{{< /callout >}} + +Rhize creates a unique ID and trace for each workflow that runs. +This ID is reported as the `traceID` in the `createAndRunBPMN` mutation operation. +Within this trace, each node is _instrumented_, with spans emitted at every input and output along each node of execution. +With the trace ID, you can find the workflow run in Tempo and follow the behavior. + +To inspect a workflow in Tempo: +1. Go to your Grafana instance. +2. Select **Explore** and then Tempo. +3. From the **TraceQL** tab, enter the `traceID` and query. +Alternatively, use the **Search** tab with the `bpmn-engine` to find traces for all workflows. + +{{< bigFigure +src="/images/bpmn/screenshot-rhize-bpmn-spans-in-tempo-compact.png" +alt="Screenshot of a compact view of spans for a BPMN process in Tempo" +caption="Screenshot of a compact view of spans for a BPMN process in Tempo" +width="55%" +>}} + + +Each workflow instance displays _spans_ that trace the state of each node at its start, execution, and end states. +When debugging, you are likely interested in the spans that result in `ABORTED`. +To inspect the errors: +1. Select the nodes with errors. +1. Use the `events` property to inspect for exceptions. + +For example, this REST task failed because the URL was invalid. + +{{< bigFigure +src="/images/bpmn/screenshot-rhize-bpmn-tempo-error-node.png" +alt="Screenshot of a detailed view of an error for a BPMN process in Tempo" +caption="A detailed view of an error for a BPMN process in Tempo" +width="55%" +>}} + +Also note the names of the spans in the previous two screenshots. +Names that convey semantic information it easier to find specific nodes and easier to understand and follow the overall workflow. +Well-named nodes make debugging easier. +This is one of the reasons we recommend always following a set of [naming conventions]({{< relref "naming-conventions" >}}) when you author BPMN workflows. + +### Adding the debug flag + +For granular debugging, it also helps to trace the variable context as it passes from node to node. +To facilitate this, Rhize provides a debugging option that you can pass in multiple ways: +- From an API call with the `debug:true` [argument]({{< relref "../gql/call-the-graphql-api/#request-body" >}}). +- In the process variable context, by setting `__traceDebug: true` +- In the [BPMN service configuration]({{< relref "../../reference/service-config/bpmn-configuration/" >}}) by setting `OpenTelemetry.defaultDebug` to `true` + +When the debugging variable is set, Tempo reports the entire variable context in the **Span Attributes** at the end of each node. + +{{< bigFigure +src="/images/bpmn/screenshot-rhize-variable-spans.png" +alt="Screenshot showing the process variable context at the end of a node in a BPMN workflow." +caption="The process variable context at the end of a node in a BPMN workflow." +width="55%" +>}} diff --git a/content/versions/v3.2.1/how-to/bpmn/learning-resources.md b/content/versions/v3.2.1/how-to/bpmn/learning-resources.md new file mode 100644 index 000000000..fd01b72df --- /dev/null +++ b/content/versions/v3.2.1/how-to/bpmn/learning-resources.md @@ -0,0 +1,15 @@ +--- +title: 'BPMN learning resources' +categories: ["reference"] +description: Links to supplemental tools and material to learn BPMN +weight: 999 +--- + + +Here are some links to supplemental tools and material to help you build BPMN workflows in Rhize: + +- [BPMN templates](https://github.com/libremfg/bpmn-templates). A repository of BPMN workflows that you can download and run yourself. +- [ bpmn.io](https://github.com/bpmn-io/bpmn-js). Open source rendering toolkits and editors for BPMN 2.0. You can use the `bpmn-js` for local offline building. +- [Rhize Youtube channel](https://www.youtube.com/@rhizemanufacturingdatahub). Includes demos of BPMN. +- 📝 [OMG BPMN standard](https://www.omg.org/spec/BPMN/2.0.2/). The standard on which the Rhize BPMN engine and UI is based. +- [`vscode-language-jsonata`](https://marketplace.visualstudio.com/items?itemName=bigbug.vscode-language-jsonata). A VS code extension to interactively pipe JSONata expressions together, in the literate-programming style of Jupyter notebooks. diff --git a/content/versions/v3.2.1/how-to/bpmn/naming-conventions.md b/content/versions/v3.2.1/how-to/bpmn/naming-conventions.md new file mode 100644 index 000000000..354038d53 --- /dev/null +++ b/content/versions/v3.2.1/how-to/bpmn/naming-conventions.md @@ -0,0 +1,204 @@ +--- +title: 'Naming conventions' +categories: ["reference"] +description: Recommended naming conventions for BPMN processes and their nodes +weight: 950 +--- + +{{< callout type="info" >}} +These are recommendations. Your organization may adapt the conventions to its needs. +{{< /callout >}} + +Each BPMN workflow has an ID, as does each node in the workflow. +Rhize recommends adopting a set of conventions about how you name these elements. +Standardizing BPMN names across an environment has multiple benefits: +- Consistent workflow naming conventions help you filter and find workflows in the process list. +- Well-named nodes make the workflow behavior easier to understand at a glance. +- These names provide discovery and context when debugging and tracing a workflow. + +The following list describes our default naming conventions. + + +## BPMN processes + +Name BPMN Processes according to the following convention: + +`__` + +Where: +- `` describes how the BPMN is expected to be triggered + +- `` Links to the ID supplied in the sequence diagram (if applicable) +- `` describes what the workflow does + +| InvocationTypes | Description | +|-----------------|------------------------------------------------------------------------------------------------| +| `NATS` | Invoked when a message is received in the Rhize NATS broker | +| `API` | Expects to be called from the API using `createAndRunBPMNSync` or `createAndRunBPMN` mutations | +| `RULE` | Invocation is expected from the [rule engine]({{< relref "../publish-subscribe/create-equipment-class-rule" >}}) | +| `FUNC` | Internal functions to be invoked as helpers | + +Examples: +- `NATS_ProcessOrderV1TransformAndPersist` +- `NATS_PLMMaterialMasterV2TransformPersistAndPublish` +- `RULE_ChangeOEEStatusOfCNCEquipment` +- `API_WST04_GetNextLibreBatchNumber` +- `API_WAT01_CloseOpenJobOrders` + +## BPMN Nodes + +Name nodes in a workflow according to the following convention: +- `__` + +Where: +- `` + is the type of node +- `` further categorizes the node +- `` describes the node behavior. + +### Start Events + +For [start events]({{< relref "./bpmn-elements">}}), +use the name to describe each trigger according to the following convention: + +`START__` + +For message starts, include the topics. +For timers, indicate the frequency. + +| SubType | Description | +| --- | --- | +| `API` | Manual start via API | +| `MSG` | Message Start | +| `TIMER` | Timer start | + +Examples: +- `START_MSG_InboundOrders` +- `START_API` +- `START_TIMER_EveryTenMinutes` + +### Query task + +Runs a GraphQL [{{< abbr "query" >}}]({{< relref "../gql/query/" >}}). + +Prefix: `Q`. + +| SubTypes | Description | +|----------|-----------------------------------------------------------------------------------------| +| `GET` | [Get query]({{< relref "../gql/query/#get" >}}). Expected to return one item | +| `QUERY` | [Query operation]({{< relref "../gql/query/#query" >}}). May return multiple items | +| `AGG` | [Aggregate query]({{< relref "../gql/query/#aggregate" >}}) | + +Examples: + +- `Q_GET_OperationsScheduleByOperationId` +- `Q_QUERY_JobOrdersByOperationsRequestId` + +### Mutation task + +Runs a GraphQL [{{< abbr "mutation" >}}]({{< relref "../gql/mutate/" >}}). + + +Prefix: `M` + +| SubTypes | Description | +|----------|------------------------------------------------------------------------------------------| +| `ADD` | [Adds]({{< relref "../gql/mutate/#add" >}}) a new record | +| `UPDATE` | [Updates]({{< relref "../gql/mutate/#update" >}}) existing | +| `UPSERT` | Updates existing [or adds new]({{< relref "../gql/mutate/#upsert" >}}) if not found | +| `DELETE` | [Deletes]({{< relref "../gql/mutate/#delete" >}}) a record | +| | | + +Examples: + +- `M_UPSERT_ProcessOrder` +- `M_ADD_UnitOfMeasure` + +### JSONata transform task + +Prefix: `J` + +| SubType | Description | +|-------------|---------------------------------------------| +| `INIT` | Initialising some new variables | +| `INDEX` | Updating an index variable | +| `MAP` | Mapping one entity to another | +| `TRANSFORM` | Updates existing or adds new if not found | +| `DIFF` | Calculating the difference between entities | +| `CLEANUP` | Deletes the record | +| `CALC` | Performing some calculation | + +Examples: + +- `J_INDEX_CreateLoop1` +- `J_TRANSFORM_PO_to_JO` +- `J_INIT_ProcessingLimits` + +### REST task + + +REST nodes should also indicate the target system and endpoints, +according to the following naming convention: + +`REST____` + +Where: + +- `SYSTEM` abbreviates the system being called, for example `SAP`. + + +| SubType | Description | +|----------|---------------------------------------------| +| `GET` | Initialising some new variables | +| `POST` | Updating an index variable | +| `PUT` | Mapping one entity to another | +| `PATCH` | Updates existing or adds new if not found | +| `DELETE` | Calculating the difference between entities | + +Examples: + +- `REST_SAP_POST_bill-material-bom` +- `REST_EWM_GET_serial-number-by-object-v1_RingSerialNumbers` + +### Gateway + +Prefix = `GW` + +| SubType | Description | +|-----------|-------------------------| +| `X_SPLIT` | Exclusive gateway split | +| `X_JOIN` | Parallel gateway join | +| `P_SPLIT` | Parallel gateway split | +| `P_JOIN` | Parallel gateway join | + +Examples: + +- `GW_X_SPLIT_DifferenceLoop01` +- `GW_P_JOIN_DifferenceLoop01` + +### Sequence Flows + +Only name sequence flows that have conditions. +If a sequence flow carries a condition, indicate the condition in the naming as follows: + +`F_` + +Examples: + +- `F_NoNewEquipment` +- `F_AbandonFlagTrue` + +### End Events + + If a workflow has multiple end events, indicate their number according to this convention: +- `END_` + +Examples: + +- `END_01` +- `END_02` + +## Response Field Naming + +When nodes generate a response, give the response field the same name as its node. + diff --git a/content/versions/v3.2.1/how-to/bpmn/screenshot-rhize-flamegraph-json.png b/content/versions/v3.2.1/how-to/bpmn/screenshot-rhize-flamegraph-json.png new file mode 100644 index 0000000000000000000000000000000000000000..594b763c17f2c8b191ec278c39f8b2e1db610082 GIT binary patch literal 80274 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 literal 0 HcmV?d00001 diff --git a/content/versions/v3.2.1/how-to/bpmn/trigger-workflows.md b/content/versions/v3.2.1/how-to/bpmn/trigger-workflows.md new file mode 100644 index 000000000..16e82a04b --- /dev/null +++ b/content/versions/v3.2.1/how-to/bpmn/trigger-workflows.md @@ -0,0 +1,151 @@ +--- +title: 'Trigger workflows' +date: '2024-04-24T19:35:09+03:00' +categories: ["how-to"] +description: How to trigger a workflow in Rhize. Use the API, publish a message to the broker, listen to a data source, or set timers. +weight: 150 +--- + + +You also have multiple ways to start, or _trigger_, a BPMN workflow. +The best choice of trigger depends on the context of the event and the system that initiates the workflow. + +{{% reusable/bpmn-triggers %}} + + +## Start a workflow from an API + +No matter the start event, **all workflows can be triggered by an API call**. +However, if the workflow uses the default blank start event, you must trigger it through API call. +For example, an API trigger may originate from a custom frontend application or a developer's local machine. + +The Rhize API has two mutations to start workflows. +Both must specify the workflow ID as an [argument in the request body]({{< relref "../gql/call-the-graphql-api/#request-body" >}}). +Each run for a workflow returns a unique `ID` that you can use for debugging. + + +### Synchronous and asynchronous API triggers + +To start BPMN workflows, Rhize has two API operations: +- `createAndRunBPMNSync` starts a workflow and waits for the process to complete or abort (synchronous). +- `createAndRunBpmn` starts a workflow and does not wait for the response (asynchronous). + +Use the synchronous operation if you want to receive information about the result of the workflow in the call response. +On the other hand, the asynchronous operation frees up the call system to do more work, no matter whether the workflow runs correctly. + +Both operations have similar call syntax. +For example, compare the syntax for these calls: + +{{% tabs items="Synchronous,Async" %}} +{{% tab "Synchronous" %}} +```gql +mutation sychronousCall{ + createAndRunBpmnSync(id: "API_demo_custom_response") { + id + jobState + customResponse + } +} + +``` +{{% /tab %}} + +{{% tab "Async" %}} +```gql +mutation asyncCall{ + createAndRunBpmn(id: "API_demo_custom_response") { + id + jobState + customResponse + } +} +``` +{{% /tab %}} +{{% /tabs %}} + +The responses for these calls have two differences: +- For synchronous calls, the returned `JobState` should be a finished value (such as `COMPLETED` or `ABORTED`). Asynchronous calls likely return an in-progress status, such as `RUNNING`. +- Synchronous calls can request the `dataJSON` field to report the entire variable context at the final node. +- Only the synchronous call receives data in the `customResponse`. For details, refer to the next section. + +### `customResponse` + +The `customResponse` is a special variable to return data in the response to clients that run `createAndRunBPMNSync` operations. + +You can set the `customResponse` in any [element]({{< relref "./bpmn-elements" >}}) that has an `Output` or `Input response` parameter. +It can use any data from the {{< abbr "process variable context" >}}, including variables added on the fly. + +Functionally, only the last value of the `customResponse` is returned to the client that sent the response. +However, you can use conditional branches and different end nodes to add error handling. +For example, this workflow returns `Workflow ran correctly` if the call variables include the message `CORRECT` and an error message in all other cases. + + +{{< bigFigure +src="/images/bpmn/screenshot-rhize-bpmn-error-handling-custom-response.png" +alt="A BPMN workflow with customResponse in the output of the end node" +caption="Download this workflow from [BPMN templates](https://github.com/libremfg/rhize-templates/tree/main/bpmn/custom-response-error-events)" +width="80%" +>}} + + +### Additional properties for workflow calls {#variables-versions} + +API operations can also include parameters to add variables to the {{< abbr "process variable context" >}} and to specify a workflow version. + +To add variables, use the `variables` input argument. +Note that the variables are accessible from their root object. +For example, a workflow would access the following string value at `$.input.message`: + +```gql{ +"variables": "{\"input\":{\"message\":\"CORRECT\"}}" +} +``` + +To specify a version, use the `version` property. For example, this input instructs Rhize to run version `3` of the `API_demoCallToRemoteAPI` workflow: + +```json +{ + "createAndRunBpmnSyncId": "API_demoCallToRemoteAPI", + "version": "3" +} +``` + + +If the `version` property is empty, Rhize runs the active version of the workflow (if an active version exists). + +## Start from a message + +The [message start event]({{< relref "./bpmn-elements#message-start-event" >}}) subscribes to a topic on the Rhize broker. +Whenever a message is published to this topic, the workflow is triggered. +The Rhize broker can receive messages published over MQTT, NATS, and OPC UA. + +For example, this workflow subscribes to the topic `material/stuff`. +Whenever a message is published to the topic, it evaluates whether the quantity is in the correct threshold. +If the quantity is correct, it uses the [mutation service task]({{< relref "./bpmn-elements#graphql-mutation/" >}}) to add a material lot. +If incorrect, it sends an alert back to the broker. + + +{{% bigFigure +alt="BPMN message start with conditional evaluation" +src="/images/bpmn/rhize-bpmn-message-start-throw-conditional.png" +width="65%" +caption="Download this workflow as a [BPMN template](https://github.com/libremfg/rhize-templates/tree/main/bpmn/msg-start-and-throw)." + %}} + + +Note that, for a workflow to run from a message start event, the workflow **must be enabled.** + +## Rule-based triggers + +Rule-based triggers subscribe to tag changes from a data source and trigger when the rule change condition is met. +Typically, users choose this workflow trigger when they want to orchestrate processes originating from level-1 and level-2 systems. + +To add a data source and create a rule based trigger, refer to [Turn values into events]({{< relref "../publish-subscribe/create-equipment-class-rule" >}}). + +## Timer triggers + +Timer triggers run according to a configured time or interval. +For example, a timer trigger may start a workflow each day, or once at a certain time. + +To use timer triggers, use the [timer start event]({{< relref "./bpmn-elements#timer-start-event" >}}). As with message start events, the workflow **must be enabled** for it to run. + diff --git a/content/versions/v3.2.1/how-to/bpmn/tune-performance.md b/content/versions/v3.2.1/how-to/bpmn/tune-performance.md new file mode 100644 index 000000000..7ce160a73 --- /dev/null +++ b/content/versions/v3.2.1/how-to/bpmn/tune-performance.md @@ -0,0 +1,93 @@ +--- +title: 'Tune BPMN performance' +date: '2024-02-09T09:47:47-03:00' +categories: ["how-to"] +description: Tips to debug and improve the performance of your BPMN process +weight: 300 +--- + +This page documents some tips to debug [BPMN workflows]({{< relref "./create-workflow" >}}) and improve their performance. + +Manufacturing events can generate a vast amount of data. +And a BPMN workflow can have any number of logical flows and data transformations. +So an inefficient BPMN process can introduce performance degradations. + +## Manage the process context size + +{{< callout type="info" >}} +The max size of the process variable context comes from the default max payload size of NATS Jetstreams. +To increase this size, change your NATS configuration. +{{< /callout >}} + +By default, the size of the {{< abbr "process variable context" >}} is 1MB. +If the sum size of all variables exceeds this limit, the BPMN process fails to execute. + +### Be mindful of variable output + +Pay attention the overall size of your variables, especially when outputting to new variables. +For example, imagine an initial JSON payload, `data`, that is 600KB. +If a JSONata task slightly modifies and outputs it to a new variable, `data2`, the process variable context will exceed 1MB and the BPMN process will exit. + +To work around this constraint, you can save memory by mutating variables. +That is, instead of outputting a new variable, you can output the transformed payload to the original variable name. + +### Discard unneeded data from API responses + +Additionally, in service tasks that call APIs, use the **Response Transform Expression** to minimize the returned data to only the necessary fields. +Rhize stores only the output of the expression, and discards the other part of the response. This is especially useful in service tasks that [Call a REST API](https://docs.rhize.com/how-to/bpmn/bpmn-elements/#call-rest-api), since you cannot precisely specify the fields in the response (as you can with a GraphQL query). + +If you still struggle to find what objects create memory bottlenecks, use a tool to observe their footprint, as documented in the next section. + +### Observe payload size + +Each element in a BPMN workflow passes, evaluates, or transforms a JSON body. +Any unnecessary fields occupy unnecessary space in the {{< abbr "process variable context" >}}. +However, it's hard for a human to reason about the size of the inflight payload without a tool to provide measurements and context. + +It's easier to find places to reduce the in-flight payload size if you can visualize its memory footprint. +We recommend the [JSON site analyzer](https://www.debugbear.com/json-size-analyzer), which presents a flame graph of the memory used by the objects in a JSON data structure. + + +{{< figure +src="/how-to/bpmn/screenshot-rhize-flamegraph-json.png" +alt="A simplified diagram of Rhize's architecture" +width="70%" +caption="The material lot object is dominating the size of this JSON payload. This a good place to start looking for optimizations." +>}} + + +## Look for inefficient execution logic + +When you first write a workflow, you may use some logical flows slow down execution time. +If a process seems slow, look for these places to refactor performance. + +### Avoid parallel joins + +Running processes in [parallel]({{< relref "./bpmn-elements#parallel-gateway" >}}) can increase the workflow's complexity. +Parallel joins in particular can also increase memory usage of the NATS service. + +Where possible, prefer exclusive branching and sequential execution. +When a task requires concurrency, keep the amount of data processed and the complexity of the tasks to the minimum necessary. + +### Control wildcards in message start events + +BPMN message start tasks can start on any topic or data source. However, performance varies with the events that the start task subscribes to. + +Subscribing to multiple wildcards can especially drag performance. +To avoid a possible performance hit, try to subscribe to an exact topic, or limit subscriptions to a single wildcard. + +### Avoid loops + +A BPMN process can loop back to a previous task node to repeat execution. +This process can also increase execution time. +If a process with a loop is taking too long to execute, consider refactoring the loop to process the variables as a batch in JSONata tasks. + +## Use the JSONata book extension + +A BPMN process performs better when the JSONata transformations are precise. +A strategy to debug and minimize necessary computation is to break transformations into smaller steps. + +If you use Visual Studio Code, consider the [`jsonata-language`](https://marketplace.visualstudio.com/items?itemName=bigbug.vscode-language-jsonata) extension. +Similar to a Jupyter notebook, the extension provides an interactive environment to write JSONata expressions and pass the output from one expression into the input of another. +Besides its benefit for monitoring performance, we have used it to incrementally build complex JSONata in a way that we can document and share (in the style of literate programming). + diff --git a/content/versions/v3.2.1/how-to/bpmn/use-jsonata.md b/content/versions/v3.2.1/how-to/bpmn/use-jsonata.md new file mode 100644 index 000000000..0bc9f1080 --- /dev/null +++ b/content/versions/v3.2.1/how-to/bpmn/use-jsonata.md @@ -0,0 +1,839 @@ +--- +title: 'Use JSONata' +date: '2024-05-10T16:20:35-03:00' +categories: ["how-to"] +description: The Rhize guide to JSONata, with example transformations and calculations that are relevant to manufacturing. +weight: 200 +--- + + +[JSONata](https://jsonata.org/) +is a query language to filter, transform, and create JSON objects. +Rhize BPMN workflows use JSONata expressions to +transform JSON payloads as they pass through workflow nodes and across integrated systems. +In BPMN workflows, JSONata expressions have some essential functions: +- **Map data.** Moving values from one data structure to another. +- **Calculate data.** Receiving values as input and create new data from them. +- **Create logical conditions.** Generate values to feed to gateways to direct the flow of the BPMN. + +This guide details how to use JSONata in your Rhize environment and provides some examples relevant to manufacturing workflows. +For the full details of the JSONata expression language, read the [Official JSONata documentation](https://docs.jsonata.org/overview.html). + +## Use JSONata in Rhize + +JSONata returns the final value of its expression as output. +This output can be of any data type that JSON supports. +Generally, we recommend outputting a JSON object with the keys and values of the data you want to subsequently work with. + +In practice, creating an expression usually follows these steps: +1. Begin with an `=`. +1. Embed the expression in parenthesis. +1. At the top of expression, write your logic, variables, and functions. +1. At the bottom of the expression, create a JSON object whose keys are names you configure and whose values are derived from your logic. + +For example: + +{{< tabs items="expression,output" >}} +{{< tab >}} + +```js +=( + $logic := "Hello" & " " & "World"; + + { + "output": $logic + } +) + +``` +{{< /tab >}} +{{< tab >}} + +```JSON +{ + "output": "Hello World" +} +``` + +{{< /tab >}} +{{< /tabs >}} + + + +### Begin each expression with a `=` + +Note that the previous expression begins with the equals sign, `=`. +This character instructs Rhize to parse the subsequent data as JSONata (as opposed to raw JSON or some other data structure). + +### Access root variable context with `$.` + +To access the root of the entire BPMN variable space, +use the dollar character followed by a dot, `$.`. +For example, this expression accesses all IDs for an `equipmentClass` object from the root variable context, `$.`. + +```js +$.equipmentClass.id +``` + +{{% tabs items="Input,Output" %}} + +{{% tab %}} + +```json +{ + "equipmentClass": [ + { + "id": "Vessel-A012", + "description": "Stock Solution Vessel", + "effectiveStart": "2023-05-24T09:58:00Z", + "equipmentClassProperties": [ + { + "id": "Volume", + "description": "Vessel Volume" + } + ] + }, + { + "id": "Vessel-A013", + "description": "Stock Solution Vessel" + } + ] +} + +``` +{{% /tab%}} + +{{% tab %}} + +``` +[ + "Vessel-A012", + "Vessel-A013" +] +``` +{{% /tab %}} + + +{{% /tabs %}} + +### JSONata in BPMN elements + +JSONata can be used in many Rhize BPMN elements +Particularly, the [JSONata service task]({{< relref "./bpmn-elements/#jsonata-transform" >}}) exists to receive input and pass it to another element or system. + +Though JSONata tasks are the most common use of JSONata, +you can use the `=` prefix to declare an expression in many other fields. +Parameters that accept expressions include API payloads, message payloads, and flow conditions. + +To review the full list of elements and fields that accept JSONata, read the [BPMN element reference]({{< relref "bpmn-elements" >}}). + +### JSONata version + +Many implementations of JSONata exist. +Rhize uses a custom Go implementation for high performance and safe calculation. + +## JSONata examples + +These snippets provide some examples of JSONata from manufacturing workflows. +To experiment with how they work, copy the data and expression into a [JSONata exerciser](https://try.jsonata.org/) and try changing values. + +### Filter for items that contain + +This expression returns the ID of all `equipmentActual` items that are associated with a specified job response `JR-4`. +It outputs the IDs as an array of strings in a new custom object. + +This is a minimal example of how you can use JSONata to transform data into new representations. +Such transformation is a common prerequisite step for post-processing and service interoperability. + +```js +$.data.queryJobResponse[`id`="JR-4"].( + {"associatedEquipment": equipmentActual.id} +) +``` + +{{% tabs items="Input,Output" %}} + +{{% tab %}} +```json +{ + "data": { + "queryJobResponse": [ + { + "id": "JR-1", + "data": [ + { + "value": 100 + } + ], + "equipmentActual": [ + { + "id": "hauler" + }, + { + "id": "actuator-121" + } + ] + }, + { + "id": "JR-4", + "data": [ + { + "value": "101.8" + } + ], + "equipmentActual": [ + { + "id": "actuator-132" + }, + { + "id": "actuator-133" + } + ] + } + ] + } +} +``` + +{{% /tab %}} + +{{% tab %}} + +```json +{ + "associatedEquipment": [ + "actuator-132", + "actuator-133" + ] +} +``` +{{% /tab %}} +{{% /tabs %}} + +### Find actual associated with high values + +This expression finds all job responses whose `value` exceeds `100`. +It outputs the matching job response IDs along with the associated equipment actual used in the job. + +In production, you may use a similar analysis to isolate all {{< abbr "resource actual" >}}s associated with an abnormal production outcome. + +```js +$map($.data.queryJobResponse, function($v){ + $number($v.data.value) > 102 + ? {"jobResponseId": $v.id, "EquipmentActual": $v.equipmentActual} + } + ) + +``` + +{{% tabs items="Input,Output" %}} +{{% tab "input" %}} +```json +{ + "data": { + "queryJobResponse": [ + { + "id": "JR-1", + "data": [ + { + "value": 100 + } + ], + "equipmentActual": [ + { + "id": "hauler" + }, + { + "id": "actuator-121" + } + ] + }, + { + "id": "JR-5", + "data": [ + { + "value": 103.2 + } + ], + "equipmentActual": [ + { + "id": "actuator-122" + }, + { + "id": "actuator-13" + } + ] + }, + { + "id": "JR-2", + "data": [], + "equipmentActual": [ + { + "id": "actuator-13" + } + ] + }, + { + "id": "JR-4", + "data": [ + { + "value": "101.8" + } + ], + "equipmentActual": [ + { + "id": "actuator-132" + }, + { + "id": "actuator-133" + } + ] + }, + { + "id": "JR-3", + "data": [], + "equipmentActual": [ + { + "id": "actuator-091" + } + ] + }, + { + "id": "JR-12", + "data": [], + "equipmentActual": [] + }, + { + "id": "JR-123", + "data": [], + "equipmentActual": [ + { + "id": "actuator-121" + } + ] + }, + { + "id": "JR-6", + "data": [], + "equipmentActual": [] + }, + { + "id": "JR-8", + "data": [ + { + "value": "96.7" + } + ], + "equipmentActual": [ + { + "id": "actuator-091" + } + ] + }, + { + "id": "JR-9", + "data": [], + "equipmentActual": [] + }, + { + "id": "JR-10", + "data": [ + { + "value": "105.0" + } + ], + "equipmentActual": [ + { + "id": "actuator-12" + } + ] + }, + { + "id": "JR-7", + "data": [ + { + "value": "103.2" + } + ], + "equipmentActual": [ + { + "id": "actuator-12" + } + ] + } + ] + } +} +``` + +{{% /tab %}} +{{% tab %}} +```json +[ + { + "jobResponseId": "JR-5", + "EquipmentActual": [ + { + "id": "actuator-122" + }, + { + "id": "actuator-13" + } + ] + }, + { + "jobResponseId": "JR-10", + "EquipmentActual": [ + { + "id": "actuator-12" + } + ] + }, + { + "jobResponseId": "JR-7", + "EquipmentActual": [ + { + "id": "actuator-12" + } + ] + } +] + +``` + +{{% /tab %}} +{{% /tabs %}} + + + +### Map event to operations event + +This function takes data from an external weather API +and maps it onto the `operationsEvent` ISA-95 object. +It takes the earliest value from the event time data as the start, and last value as the end. +If no event data exists, it outputs a message. + +Although this example uses data that is unlikely to be a source of a real manufacturing event, the practice of receiving data from a remote API and mapping it to ISA-95 representation is quite common. +In production, you may perform a similar operation to map an SAP schedule order to an `operationsSchedule`, or the results from a QA service to the `testResults` object. + + +```js +( + +$count(events[0]) > 0 + + ? events.{ + "id":id, + "description":title, + "hierarchyScope":{ + "id":"Earth", + "label": Earth, + "effectiveStart": $sort(geometry.date)[0] + }, + "category":categories.title, + "recordTimestamp": $sort(geometry.date)[0], + "effectiveStart": $sort(geometry.date)[0], + "effectiveEnd": $sort(geometry.date)[$count(geometries.date)-1], + "source": sources.id & " " & sources.url, + "operationsEventDefinition": { + "id": "Earth event", + "label": "Earth event" + } + } + + : {"message":"No earth events lately"} + +) +``` + +{{% tabs items="Input,Output"%}} +{{% tab "Input" %}} + +{{% details title="Long JSON" closed="false" %}} +```json +{ + "title": "EONET Events", + "description": "Natural events from EONET.", + "link": "https://eonet.gsfc.nasa.gov/api/v3/events", + "events": [ + { + "id": "EONET_6516", + "title": "Ubinas Volcano, Peru", + "description": null, + "link": "https://eonet.gsfc.nasa.gov/api/v3/events/EONET_6516", + "closed": null, + "categories": [ + { + "id": "volcanoes", + "title": "Volcanoes" + } + + ], + "sources": [ + { + "id": "SIVolcano", + "url": "https://volcano.si.edu/volcano.cfm?vn=354020" + } + + + ], + "geometry": [ + { + "magnitudeValue": null, + "magnitudeUnit": null, + "date": "2024-05-06T00:00:00Z", + "type": "Point", + "coordinates": [ -70.8972, -16.345 ] + } + + + ] + }, + + { + "id": "EONET_6513", + "title": "Iceberg D28A", + "description": null, + "link": "https://eonet.gsfc.nasa.gov/api/v3/events/EONET_6513", + "closed": null, + "categories": [ + { + "id": "seaLakeIce", + "title": "Sea and Lake Ice" + } + + ], + "sources": [ + { + "id": "NATICE", + "url": "https://usicecenter.gov/pub/Iceberg_Tabular.csv" + } + + + ], + "geometry": [ + { + "magnitudeValue": 208.00, + "magnitudeUnit": "NM^2", + "date": "2024-02-16T00:00:00Z", + "type": "Point", + "coordinates": [ -33.27, -51.88 ] + }, + + { + "magnitudeValue": 208.00, + "magnitudeUnit": "NM^2", + "date": "2024-03-01T00:00:00Z", + "type": "Point", + "coordinates": [ -32.82, -51.09 ] + }, + + { + "magnitudeValue": 208.00, + "magnitudeUnit": "NM^2", + "date": "2024-03-07T00:00:00Z", + "type": "Point", + "coordinates": [ -30.95, -51.21 ] + } + ] + }, + + { + "id": "EONET_6515", + "title": "Sheveluch Volcano, Russia", + "description": null, + "link": "https://eonet.gsfc.nasa.gov/api/v3/events/EONET_6515", + "closed": null, + "categories": [ + { + "id": "volcanoes", + "title": "Volcanoes" + } + + ], + "sources": [ + { + "id": "SIVolcano", + "url": "https://volcano.si.edu/volcano.cfm?vn=300270" + } + + + ], + "geometry": [ + { + "magnitudeValue": null, + "magnitudeUnit": null, + "date": "2024-04-28T00:00:00Z", + "type": "Point", + "coordinates": [ 161.36, 56.653 ] + } + + + ] + } + + ] +} +``` +{{% /details %}} +{{% /tab %}} + +{{% tab "Output" %}} +```json +[ + { + "id": "EONET_6516", + "description": "Ubinas Volcano, Peru", + "hierarchyScope": { + "id": "Earth", + "effectiveStart": "2024-05-06T00:00:00Z" + }, + "category": "Volcanoes", + "recordTimestamp": "2024-05-06T00:00:00Z", + "effectiveStart": "2024-05-06T00:00:00Z", + "effectiveEnd": "2024-05-06T00:00:00Z", + "source": "SIVolcano https://volcano.si.edu/volcano.cfm?vn=354020", + "operationsEventDefinition": { + "id": "Earth event", + "label": "Earth event" + } + }, + { + "id": "EONET_6513", + "description": "Iceberg D28A", + "hierarchyScope": { + "id": "Earth", + "effectiveStart": "2024-02-16T00:00:00Z" + }, + "category": "Sea and Lake Ice", + "recordTimestamp": "2024-02-16T00:00:00Z", + "effectiveStart": "2024-02-16T00:00:00Z", + "effectiveEnd": "2024-03-07T00:00:00Z", + "source": "NATICE https://usicecenter.gov/pub/Iceberg_Tabular.csv", + "operationsEventDefinition": { + "id": "Earth event", + "label": "Earth event" + } + }, + { + "id": "EONET_6515", + "description": "Sheveluch Volcano, Russia", + "hierarchyScope": { + "id": "Earth", + "effectiveStart": "2024-04-28T00:00:00Z" + }, + "category": "Volcanoes", + "recordTimestamp": "2024-04-28T00:00:00Z", + "effectiveStart": "2024-04-28T00:00:00Z", + "effectiveEnd": "2024-04-28T00:00:00Z", + "source": "SIVolcano https://volcano.si.edu/volcano.cfm?vn=300270", + "operationsEventDefinition": { + "id": "Earth event", + "label": "Earth event" + } + } +] +``` + +{{% /tab %}} +{{% /tabs %}} + +### Calculate summary statistics + +These functions calculate statistics for an array of numbers. +Some of the output uses built-in JSONata functions, such as `$max()`. +Others, such as the ones for median and standard deviation, +are created in the expression. + +You might use statistics such as these to calculate metrics on historical or streamed data. + +```js +( + $mode := function($arr) { + ( + $uniq := $distinct($arr); + $counted := $map($uniq, function($v){ + { "value": $v, "count": $count($filter($arr, function($item) { $item = $v })) } + }); + $modes := $filter($counted, function($item) { + $item.count = $max($counted.count) + }); + $sort($modes.value) + ) + }; + $stdPop := function($arr) { + ( + $variance := $map($arr, function($v, $i, $a) { $power($v - $average($a), 2) }); + $sum($variance) / $count($arr) ~> $sqrt() + ) + }; + $median := function($arr) { + ( + $sorted := $sort($arr); + $length := $count($arr); + $mid := $floor($length / 2); + $length % 2 = 0 ? $median := ($sorted[$mid - 1] + $sorted[$mid]) / 2 : $median := $sorted[$mid] + ) + }; + { + "std_population": $stdPop($.data.arr), + "mean": $average($.data.arr), + "median": $median($.data.arr), + "mode": $mode($.data.arr), + "max": $max($.data.arr), + "min": $min($.data.arr) + } +) +``` +{{% tabs items="Input,Output" %}} +{{% tab "input" %}} +```json +{ + "data": { + "arr": [ + 1, + 1, + 6, + 2, + 3, + 32, + 4, + 5, + 5, + 3, + 3, + 6, + 6 + ] + } +} +``` +{{% /tab %}} +{{% tab "output" %}} +```json +{ + "std_population": 7.72071677084591, + "mean": 5.923076923076923, + "median": 4, + "mode": [ + 3, + 6 + ], + "max": 32, + "min": 1 +} +``` +{{% /tab %}} +{{% /tabs %}} + +### Select random item + +This expression randomly selects an item from the plant's array of available equipment, and then adds that item as the `equipmentRequirement` for a segment associated with a specific job order. + +You might use randomizing functions for scheduling, quality control, and simulation. + +```js +( + +$randomChoice := function($a) { + ( + $selection := + $random() * ($count($a)+1) ~> $floor(); + $a[$selection] + + )}; + +{ +"segmentRequirement": { + "workRequirement": {"id": $.PO}, + "equipmentRequirements":[$randomChoice($.available)], + "id": "Make widget" + } +} + +) +``` +{{% tabs items="Input,Output" %}} +{{% tab "Input" %}} +```json +{ + "available":["line_1","line_2","line_3","line_4","line_5"], + "PO":"po-123" + } +``` +{{% /tab %}} + +{{% tab "Output" %}} +```json +{ + "segmentRequirement": { + "workRequirement": { + "id": "po-123" + }, + "equipmentRequirements": [ + "line_2" + ], + "id": "Make widget" + } +} +``` +{{% /tab %}} +{{% /tabs %}} + +### Recursively find child IDs + +This function uses recursion and a predefined set of naming rules +to find (or generate) a set of child IDs for an entity. +The `n` value determines how many times it's called. + +Many payloads in manufacturing have nested data. +Recursive functions such as the following provide a concise means of traversing a set of subproperties. + +``` +( + $next := function($x, $y) {$x > 1 ? + ( + $namingRules := "123456789ABCDFGHJKLMNOPQRSTUVWXYZ"; + $substring($y[-1],-1) = "Z" ? + $next($x - 1, $append($y, $y[-1] & '1')) : + $next($x - 1, $append( + $y, + $substring($y[-1],0,$length($y[-1])-1) & $substring($substringAfter($namingRules,$substring($y[-1],-1)),0,1) + )) + ) + : $y}; + { + "children": $next(n, [nextId]) + } +) +``` + +{{% tabs items="Input,Output" %}} +{{% tab "Input" %}} +```json +{ +"n":10, +"nextId": "molten-widet-X2FCS" +} +``` +{{% /tab %}} + +{{% tab "output" %}} +```json +{ + "children": [ + "molten-widet-X2FCS", + "molten-widet-X2FCT", + "molten-widet-X2FCU", + "molten-widet-X2FCV", + "molten-widet-X2FCW", + "molten-widet-X2FCX", + "molten-widet-X2FCY", + "molten-widet-X2FCZ", + "molten-widet-X2FCZ1", + "molten-widet-X2FCZ2" + ] +} +``` +{{% /tab %}} +{{% /tabs %}} diff --git a/content/versions/v3.2.1/how-to/bpmn/variables.md b/content/versions/v3.2.1/how-to/bpmn/variables.md new file mode 100644 index 000000000..913599a43 --- /dev/null +++ b/content/versions/v3.2.1/how-to/bpmn/variables.md @@ -0,0 +1,17 @@ +--- +title: 'Special variables' +categories: ["reference"] +description: Special variables used by Rhize BPMN workflows +aliases: + - "/how-to/bpmn/special-variables" +weight: 900 +--- + +Rhize designates some variable names for a special purpose in BPMN workflow. +This list these special variables is as follows: + +| Variable | Purpose | +|------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `BODY` | The name of the variable as **Input** in [Intermediate message throws]({{< relref "./bpmn-elements.md#intermediate-message-events" >}}). The value of this variable is the payload sent to the Rhize message broker. | +| `customResponse` | A value to report at the end of a synchronous API call to trigger a workflow. On completion, the call reports whatever the value was in the `customResponse` field of the GraphQL response. For details, read [Trigger workflows]({{< relref "./trigger-workflows.md" >}}). | +| `__traceDebug` | If `true` at the start of the workflow, the BPMN workflow reports the variable context at each node as spans in Tempo. | diff --git a/content/versions/v3.2.1/how-to/gql/_index.md b/content/versions/v3.2.1/how-to/gql/_index.md new file mode 100644 index 000000000..9115fc057 --- /dev/null +++ b/content/versions/v3.2.1/how-to/gql/_index.md @@ -0,0 +1,9 @@ +--- +title: Use GraphQL +description: Guides to use the GraphQL interface to query information, add records, and build custom UIs. +weight: 100 +cascade: + icon: gql +--- + +{{< card-list >}} diff --git a/content/versions/v3.2.1/how-to/gql/call-the-graphql-api.md b/content/versions/v3.2.1/how-to/gql/call-the-graphql-api.md new file mode 100644 index 000000000..5f22a41b8 --- /dev/null +++ b/content/versions/v3.2.1/how-to/gql/call-the-graphql-api.md @@ -0,0 +1,371 @@ +--- +title: >- + Overview: the Rhize API +date: '2023-11-22T09:43:30-03:00' +categories: ["how-to"] +description: How to query your manufacturing knowledge graph +weight: 100 +--- + +In a manufacturing operation, all event data is interrelated. +To make these relations explorable, Rhize stores data in a special-purpose graph database designed to represent all levels of the manufacturing process. +This database is enforced by our ISA-95 schema, the most comprehensive data representation of ISA-95 in the world. + +Rhize exposes this database through a [GraphQL API](https://graphql.org/). +Unlike REST, GraphQL requires only one endpoint, and you can define exactly the data that you return for each operation. + + +If you are a customer, the best way to learn both GraphQL and ISA-95 modelling is to use the [Apollo Explorer](https://www.apollographql.com/) for our schema. +However, for newcomers to GraphQL, the flexibility may look overwhelming. +These topics introduce the basics of how to use GraphQL with Rhize's custom database. + +Once you learn how to explore the API, you'll find that the interface is more comfortable and discoverable than a comparable OpenAPI (Swagger) document—and that's before considering the improvements GraphQL brings to precision, performance, and developer experience. + +## Operation types {#operations} + +In GraphQL, an _operation_ is a request to the server. +Rhize supports three types of operations: + +- **[Queries]({{< relref "query" >}})** return data and subsets of data. +- **[Mutations]({{< relref "mutate" >}})** change the data on the server side. +- **[Subscriptions]({{< relref "subscribe" >}})** notify about data changes in real time. + +For details and examples, refer to their specific documentation pages. + +## Call syntax + +The following sections show you the essential features to make a query. + +### Authenticate + +To authenticate your requests, pass a bearer token as an `Authorization` header. +Be sure to preface the value with the word `Bearer `: + + +{{< figure +alt="Example of how it looks in Apollo explorer" +src="/images/screenshot-rhize-apollo-auth-headers.png" +width="50%" +>}} + +For an overview of how Rhize handles token exchange, read [About OpenID connect](/explanations/about-openidconnect). + +### Request body + +By default, all GraphQL operations have the following structure: +1. Define the operation type (one of, `query`, `mutation`, or `subscription`). +1. Name the query anything you want. This example builds a `query` called `myCustomName`: + ```graphql + query myCustomName { + #operations will go here + } + + ``` +1. In curly brackets, define the _operation_ you want to query. +1. In parenthesis next to the operation, add the _arguments_ to pass. This example uses the `getEquipment` operation, and its arguments specify which item of equipment to get. + + ```graphql + query myCustomName { + getEquipment(id: "Kitchen_mixer_b_01") { + #Fields go here + } + } + ``` + +1. Within the operation, define the fields you want to return. This example queries for the equipment ID and the person who created the entity. + + ```graphql + query myCustomName { + getEquipment(id: "Kitchen_mixer_b_01") { + id + _createdBy + } + } + ``` + + As you might expect, the request returns only these fields for the equipment named `Kitchen_mixer_b_01`. + + ```json + { + "data": { + "getEquipment": { + "id": "Kitchen_mixer_b_01", + "_createdBy": "john_snow" + } + } + } + ``` + +### Request exactly the data you want + +A major benefit of GraphQL is that you can modify queries to return only the fields you want. + You can join data entities in a single query and query for entity relationships in the same way that you would for entity attributes. + +Unlike calls to a REST API, where the server-side code defines what a response looks like, GraphQL calls instruct the server to return only what is specified. +Furthermore, you can query diverse sets of data in one call, so you can get exactly the entities you want without calling multiple endpoints, as you would in REST, or composing queries with complex recursive joins, as you would in SQL. +Besides precision, this also brings performance benefits to minimize network calls and their payloads. + +For example, this expands the fields requested from the previous example. +Besides `id` and `_createdBy`, it now returns the `description`, unique ID, and version information about the requested equipment item: + +{{< tabs items="Request,Response" >}} +{{% tab "request" %}} +```graphql + +query ExampleQuery { + queryEquipment(filter: { id: { eq: "Kitchen_mixer_b_0 1" } }) { + id + _createdBy + versions { + iid + description + } + activeVersion { + iid + description + + } + } +} +``` +{{% /tab %}} +{{% tab "response" %}} +```json +{ + "data": { + "queryEquipment": [ + { + "id": "Kitchen_mixer_b_01", + "_createdBy": "john_snow", + "versions": [ + { + "iid": "0xcc701", + "description": "First generation of the mixer 9000" + }, + { + "iid": "0xcc71a", + "description": "Second generation (in testing)" + } + ], + "activeVersion": { + "iid": "0xcc701", + "description": "First generation of the mixer 9000" + } + } + ] + } +} +``` +{{% /tab %}} +{{< /tabs >}} + +You can also add multiple operations to one call. +For example, this query requests all data sources and all persons: + + +{{< tabs items="Request,Response" >}} +{{% tab "request" %}} +```graphql +query peopleAndDataSources { + queryPerson { + id + label + } + queryDataSource { + id + } +} +``` +{{% /tab %}} +{{% tab "response" %}} +```json +{ + "data": { + "queryPerson": [ + { + "id": "235", + "label": "John Ramirez" + }, + { + "id": "234", + "label": "Jan Smith" + } + ], + "queryDataSource": [ + { + "id": "x44_mqtt" + }, + { + "id": "x45_opcUA" + } + + ] + } +} +``` +{{% /tab %}} +{{< /tabs >}} + +## Shortcuts for more expressive requests + +The following sections provide some common ways to reduce boilerplate and shorten the necessary coding for a call. + +### Make input dynamic with variables {#variables} + +The preceding examples place the query input as _inline_ arguments. +Often, calls to production systems separate these arguments out as JSON _variables_. + +Variables add dynamism to your requests, which serves to make them more reusable. +For example: +- If you build a low-code reporting application, you could use variables to change the arguments based on user input. +- In a BPMN event orchestration, you could use variables to make a GraphQL call based on a previous JSONata filter. Refer to the example, [Write ERP material definition to DB]({{< relref "../bpmn/create-workflow/#write-erp-material-definition-to-database" >}}). + + +For example, this query places the ID of the resource that it requests as an inline variable: + +```graphql +query myCustomName { + getEquipment(id: "Kitchen_mixer_b_01") { + _createdBy + } +} +``` + +Instead, you can pass this argument as a variable. +This requires the following changes: + +1. In the argument for your query, name the variable and state its type. +This instructs the query to receive data from outside of its context: + + ```graphql + ## Name variable and type + query myCustomName ($getEquipmentId: String) { + ## operations go here. + } + ``` +1. In the operation, pass the variable as a value in the argument. +In this example, add the variable as a value to the `id` key like this: + + ```graphql + query GetEquipment($getEquipmentId: String) { + + ## pass variable to one or more operations + getEquipment(id: $getEquipmentId) { + ## fields go here + } + } + ``` + +1. In a separate `variables` section of the query, define the JSON object that is your variable: + + ```json + { + "getEquipmentId": "Kitchen_mixer_b_01" + } + ``` + + +{{< tabs items="Query,Mutation" >}} +{{% tab "Query" %}} + +```graphql +query GetEquipment($getEquipmentId: String) { + getEquipment(id: $getEquipmentId) { + _createdBy + } +} +``` +**Variables**: +```json +{ + "getEquipmentId": "Kitchen_mixer_b_01" +} +``` +{{% /tab %}} +{{% /tab %}} + +The preceding example is minimal, but the use of variables to _parameterize_ arguments also applies to complex object creation and filtering. +For example, this _mutation_ uses variables to create an array of Persons: + +```graphql +mutation AddPerson($input: [AddPersonInput!]!) { + addPerson(input: $input) { + person { + id + } + } +} +``` + +**Variables**: + +```json +{ + "input": [ + {"id": "234", "label":"Jan Smith"}, + {"id": "235", "label": "John Ramirez"} + ] +} +``` + +To learn more, read the official GraphQL documentation on [Variables](https://graphql.org/learn/queries/#variables). + +### Template requested fields with fragments + +Along with [variables](#variables), you can use _fragments_ to reduce repetitive writing. + +Fragments are common fields that you use when querying an object. +For example, imagine you wanted to make queries to different equipment objects for their `id`, `label`, `_createdBy`, and `versions[]` properties. +Instead of writing these fields in each operation, you could define them in a fragment, and then refer to that fragment in each specific operation or query. + +To use a fragment: +1. Define them with the `fragment` keyword, declaring its name and object. + ```graphql + ## name ## object + fragment CommonFields on Equipment + ``` +1. Include the fragment in the fields for your operation by prefacing its name with three dots: + ``` + ...CommonFields + ``` + +For example: + +{{< tabs items="Query,Response">}} +{{% tab "query" %}} +```graphql +## Define common fields +fragment CommonFields on Equipment{ + id + label + _createdBy + versions { + id + } +} + +## Use them in your query. +query kitchenEquipment { + getEquipment(id: "Kitchen_mixer_b_02") { + ...CommonFields + } +} +``` +{{% /tab %}} +{{% tab %}} +**Variables:** +```json +{ + "data": { + "getEquipment": { + "id": "Kitchen_mixer_b_02", + "label": "Kitchen mixer B02", + "_createdBy": "admin@rhize.com", + "versions": [] + } + } +} +``` +{{% /tab %}} +{{% /tabs %}} + diff --git a/content/versions/v3.2.1/how-to/gql/default.md b/content/versions/v3.2.1/how-to/gql/default.md new file mode 100644 index 000000000..6dba903e4 --- /dev/null +++ b/content/versions/v3.2.1/how-to/gql/default.md @@ -0,0 +1,83 @@ ++++ +title = "Use the @default Directive" +description = "The @default directive specifies which GraphQL APIs are generated for a given type. Without it, all queries & mutations are generated except subscription." +weight = 210 +draft = true +[menu.main] + parent = "how-to-query" ++++ + + + +The `@default` directive provides default values to be stored when not supplied in a mutation (`add`/`update`). + +Here's the GraphQL definition of the directives: + +```graphql +directive @default(add: DgraphDefault, update: DgraphDefault) on FIELD_DEFINITION +input DgraphDefault { + value: String +} +``` +Syntax: +```graphql +type Type { + field: FieldType @default( + add: {value: "value"} + update: { value: "value"} + ) +} +``` +Where a value is not provided as input for a mutation, the add value will be used if the node is being created, and the update value will be used if the node exists and is being updated. Values are provided as strings, parsed into the correct field type by Dgraph. + +The string $now is replaced by the current DateTime string on the server, ie: +```graphql +type Type { + createdAt: DateTime! @default( + add: { value: "$now" } + ) + updatedAt: DateTime! @default( + add: { value: "$now" } + update: { value: "$now" } + ) +} +``` + +The string $token.email is replaced by the email claim from the authorization bearer token used for the mutation, ie: +```graphql +type Type { + createdBy: String! @default( + add: { value: "$token.email" } + ) + updatedBy: String! @default( + add: { value: "$token.email" } + update: { value: "$token.email" } + ) +} +``` + +Schema validation will check that: + +Int field values can be parsed strconv.ParseInt +Float field values can be parsed by strconv.ParseFloat +Boolean field values are true or false +$now can only be used with fields of type DateTime (could be extended to include String?) +Schema validation does not currently ensure that @default values for enums are a valid member of the enum, so this is allowed: +```graphql +enum State { + HOT + NOT +} + +type Type { + state: State @default(add: { value: "FOO"}) +} +``` + +## Restrictions / Roadmap + +Our default directive is still in beta and we are improving it quickly. Here's a few points that we plan to work on soon: + +* adding the ability to specify a query to get the default value +* adding additional expressions to default times other than ${now} +--- diff --git a/content/versions/v3.2.1/how-to/gql/directives.md b/content/versions/v3.2.1/how-to/gql/directives.md new file mode 100644 index 000000000..cc7fe1d29 --- /dev/null +++ b/content/versions/v3.2.1/how-to/gql/directives.md @@ -0,0 +1,137 @@ ++++ +title = "GraphQL Directives" +description = "The list of all directives supported by Rhize's GraphQL implementation. Full details linked within for all directives available with GraphQL." +categories = "reference" +weight = 200 +draft = true +[menu.main] + name = "Directives" + identifier = "directives" + parent = "how-to-query" ++++ + + + +The list of all [directives](https://www.apollographql.com/docs/apollo-server/schema/directives/) supported by Rhize's implementation of Dgraph. + +### @auth + +`@auth` allows you to define how to apply authorization rules on the queries/mutation for a type. + +Reference: [Auth directive](/graphql/authorization/directive) + +### @cascade + +`@cascade` allows you to filter out certain nodes within a query. + +Reference: [Cascade](/graphql/queries/cascade) + +### @custom + +`@custom` directive is used to define custom queries, mutations and fields. + +Reference: [Custom directive](/graphql/custom/directive) + +### @default + +The `@default` directive allows you to specify values that should be used when nil values are received for either `add` mutations or `update` mutations + +Reference: [Default directive](/graphql/schema/default) + +### @deprecated + +The `@deprecated` directive lets you mark the schema definition of a field or `enum` value as deprecated, and also lets you provide an optional reason for the deprecation. + + +### @dgraph + +`@dgraph` directive tells us how to map fields within a type to existing predicates inside Dgraph. + + +### @generate + +The `@generate` directive is used to specify which GraphQL APIs are generated for a type. + +Reference: [Generate directive](/graphql/schema/generate) + +### @hasInverse + +`@hasInverse` is used to setup up two way edges such that adding a edge in +one direction automically adds the one in the inverse direction. + +Reference: [Linking nodes in the graph](/graphql/schema/graph-links) + +### @id + +`@id` directive is used to annotate a field which represents a unique identifier coming from outside + of Dgraph. + +Reference: [Identity](/graphql/schema/ids) + +### @include + +The `@include` directive can be used to include a field based on the value of an `if` argument. + +Reference: [Include directive](/graphql/queries/skip-include) + +### @lambda + +The `@lambda` directive allows you to call custom JavaScript resolvers. The `@lambda` queries, mutations, and fields are resolved through the lambda functions implemented on a given lambda server. + +Reference: [Lambda directive](/graphql/lambda/overview) + +### @primary-key + +The `@primary-key` allows you to specify a list of fields where the concatenation of values of those fields must be unique in the database + +Reference: [Primary Key](/graphql/schema/primarykey) + +### @remote + +`@remote` directive is used to annotate types for which data is not stored in Dgraph. These types +are typically used with custom queries and mutations. + +Reference: [Remote directive](/graphql/custom/directive/#remote-types) + +### @remoteResponse + +The `@remoteResponse` directive allows you to annotate the fields of a `@remote` type in order to map a custom query's JSON key response to a GraphQL field. + +Reference: [Remote directive](/graphql/custom/directive/#remote-response) + +### @search + +`@search` allows you to perform filtering on a field while querying for nodes. + +Reference: [Search](/graphql/schema/search) + +### @secret + +`@secret` directive is used to store secret information, it gets encrypted and then stored in Dgraph. + +Reference: [Password Type](/graphql/schema/types/#password-type) + +### @skip + +The `@skip` directive can be used to fetch a field based on the value of a user-defined GraphQL variable. + +Reference: [Skip directive](/graphql/queries/skip-include) + +### @withSubscription + +`@withSubscription` directive when applied on a type, generates subsciption queries for it. + +Reference: [Subscriptions](/graphql/subscriptions) + +### @lambdaOnMutate + +The `@lambdaOnMutate` directive allows you to listen to mutation events(`add`/`update`/`delete`). Depending on the defined events and the occurrence of a mutation event, `@lambdaOnMutate` triggers the appropriate lambda function implemented on a given lambda server. + +Reference: [LambdaOnMutate directive](/graphql/lambda/webhook) + + +### @default + +The `@default` directive provides default values to be stored when not supplied in a mutation (`add`/`update`). The directive can be used with the current DateTime (via `$now') to allow timestamping of mutation events. + +Reference: [Default directive](/graphql/schema/default) diff --git a/content/versions/v3.2.1/how-to/gql/filter.md b/content/versions/v3.2.1/how-to/gql/filter.md new file mode 100644 index 000000000..c5d8a59b5 --- /dev/null +++ b/content/versions/v3.2.1/how-to/gql/filter.md @@ -0,0 +1,361 @@ +--- +title: 'Filter' +categories: ["how-to"] +description: How to filter a GraphQL call to a subset of manufacturing items. +weight: 210 +--- + + +_Filters_ limit an operation to a subset of resources. +You can use filters to make operations more precise, remove unneeded items from a payload, and reduce the need for secondary processing. + +To use a filter, specify it in the operation's argument. +Most fields in an object can serve as a filter. +{{< callout type="info" >}} +This page provides a detailed guide of how to use the filters, with examples. +For a bare reference of filters and data types, refer to the [GraphQL type reference]({{< relref "../../reference/gql-types" >}}). +{{< /callout >}} + + +## Filter by property + +The following sections show some common [scalar filters]({{< relref "../../reference/gql-types#scalar-filters" >}}), filters that work on `string`, `dateTime`, and numeric values. +These filters return only the resources that have some specified property or property range. + +### `between` dates + +The `between` property returns items within time ranges for a specific property. +This query returns job responses that started between January 01, 2023 and January 05, 2023. + +```graphql +query { + queryJobResponse( + filter: { + effectiveStart: { between: { min: "2023-01-01", max: "2023-01-05" } } + } + ) { + id + effectiveStart + } +} +``` + +### `has` property + +The `has` keyword returns results only if an item has the specified field. +For example, this query returns only equipment items that have been modified at least once. + +{{< tabs items="Query,Response" >}} +{{% tab "query" %}} +```graphql +query QueryEquipment { + queryEquipment(filter: {has: _modifiedOn}) { + id + _createdOn + _modifiedOn + } +} +``` +{{% /tab %}} +{{% tab "Response" %}} +```json +{ + "data": { + "queryEquipment": [ + { + "id": "AN-19670-Equipment-1", + "_createdOn": "2023-12-24T16:50:45Z", + "_modifiedOn": "2024-01-23T20:06:30Z" + }, + { + "id": "AN-19670-Equipment-2", + "_createdOn": "2023-12-24T18:16:35Z", + "_modifiedOn": "2024-01-23T20:06:35Z" + }, + // more items + ] + } + } +``` +{{% /tab %}} +{{< /tabs >}} + +To filter for items that have multiple properties, include the fields in an array. +This query returns equipment objects that both have been modified and have next versions: + +```gql +query QueryEquipment { + queryEquipment(filter: {has: [_modifiedOn, nextVersion]}) { + nextVersion + _modifiedOn + } +} +``` + +### `in` this subset + +The `in` keyword filters for objects that have properties with specified values. +For example, this query returns material lots that have material definitions that are either `dough` or `cookie_unit`. + +```graphQL +query{ + queryMaterialLot @cascade { + id + materialDefinition(filter: {id: {in: ["dough", "cookie_unit"]}}) { + id + } + } + } +} +``` + +### `regexp` + +The `regexp` keyword searches for matches using the [RE2](https://github.com/google/re2) regular expression engine. + +For example, +this query uses a regular expression in its variables to filter for items that begin with either `Kitchen_` or `Cooling_` (case insensitive): + +```graphql +query getEquipment($filter: EquipmentFilter) { + aggregateEquipment(filter: $filter) { + count + } + queryEquipment(filter: $filter) { + id + } +} +``` + +**Variables** + +```json +{ + "EquipmentFilter": { + "id": { + "regexp": "/|Kitchen_.*|Cooling_.*/i" + } + }, +} +``` + +{{< callout type="warning" >}} + +The `regexp` filters can have performance costs. +After you refine a query filter to return exactly what you need, consider ways to simplify the regular expression +or, if possible, use a different filter. + +{{< /callout >}} + +## Combine filters with `and`, `or`, `not` + +To filter by multiple properties, use the `and`, `or`, and `not`, operators. +GraphQL syntax uses [infix notation](https://en.wikipedia.org/wiki/Infix_notation), so: "a and b" is `a, and: { b }`, “a or b or c” is `a, or: { b, or: c }`, and “not” is a prefix (`not:`). + +### this `and` that property + +The `and` operator filters for objects that include all specified properties. + +For example, this query returns equipment objects that match two properties: +- The `effectiveStart` must be the 1st and the 10th of January, 2024. +- It must have a non-null `nextVersion`. + +The `and` function is implicit unless you are searching on the same field. +So this filter has an implied `and`: + +```gql +query{ + queryEquipment(filter: { + effectiveStart: { + between: {min: "2024-01-01", max: "2024-01-10"} + + } + has: nextVersion + + } + ) + { + effectiveStart + id + nextVersion + } +} +``` + +{{< callout type="info" >}} + +This preceding filter syntax is a shorter equivalent to `and: {has: nextVersion}`. + +{{< /callout >}} + +### One `or` more properties + +The `or` operator filters for objects that have at least one of the specified properties. +For example, you can take the preceding query and modify it so that it returns objects that have an effective start between the specified range or a `nextVersion` property (inclusive). + +```graphql +queryEquipment(filter: { + effectiveStart: { + between: {min: "2024-01-01", max: "2024-01-10"} + + } + or: {has: nextVersion} + + } + ) +``` + + +### `not` these properties + +The `not` operator filters for objects that do not contain the specified property. +For example, you can take the preceding query and modify it so that it returns objects that have an effective start between the specified range and _do not_ have a `nextVersion` property: + +``` +queryEquipment(filter: { + effectiveStart: { + between: {min: "2024-01-01", max: "2024-01-10"} + + } + not: {has: nextVersion} + + } + ) +``` + +To modify this to include both objects within the range and objects that do not have a `nextVersion`, use `or` with `not`: + +```graphql +or: { not: {has: nextVersion} } +``` + +### This list of filters + +The `and` and `or` operators accept lists of filters. +For example, this query filters for equipment objects whose `id` matches `A`, `B`, or `C`: + +```graphql +queryEquipment (filter: { + or: [ + { id: { eq: "A" } }, + { id: { eq: "B" } }, + { id: { eq: "C" } }, + ] + }) +``` + +## Use directives + +Rhize offers [_query directives_](https://the-guild.dev/graphql/tools/docs/schema-directives#what-about-query-directives), special instructions about how to look up and return values in a query. +These directives can extend your filtering to look at nested properties or to conditionally display a field. + +All directives begin with the `@` sign. + +### Cascade + +The `@cascade` directive filters for certain nodes within a query. +Use it to filter requested resources by a nested sub-property, similar to a `WHERE` clause in SQL. + +{{< callout type="caution" >}} + +`@cascade` is not as performant as flatter queries. +Consider using it only after you've exhausted other query structures to return the data you want. + +{{< /callout >}} + +For example, this query filters for job responses with an ID of `12341`, and then filters that set for only the items that have a `data.properyLabel` field with a value of `INSTANCE ID`. + +{{< tabs items="Query,Response" >}} + +{{% tab "Query" %}} + +```graphql +query QueryJobResponse($filter: JobResponseFilter, $propertyLabel: String) { + queryJobResponse(filter: $filter) @cascade(fields:["data"]){ + id + iid + data(filter: { label: { anyoftext: $propertyLabel } }) { + id + iid + label + value + } + } +} +``` +**Variables**: +```json +{ + "filter": { + "id": { + "alloftext": "12341" + } + }, + "propertyLabel": "INSTANCE ID" +} + +``` +{{% /tab %}} +{{< /tabs >}} + +#### Avoid using @cascade with the [`order`]({{< relref "./query#order" >}}) argument + +The `order` argument returns only the first 1000 records of the query. +If a record matches the `@cascade` filter but comes after these first 1000 records, the API does not return it. + +For example, this query logic works as follows: +1. Return the first 1000 records of equipment as ordered by `effectiveStart`. +1. From these 1000 records, return only the equipment items that are part of `parentEquipment1`. + +```graphql +query($filter: EquipmentFilter){ + queryEquipment (filter: { order: {desc:effectiveStart}) @cascade{ + id + isPartOf (filter: {id:{eq:"parentEquipment1"}}) { + id + } + } +} +``` + +This behavior can be surprising and undesirable, so avoid `@cascade` with the `order` argument. + +### Include + +The `@include` directive returns a field only if its variable is `true`. + +For example, when `includeIf` is `true`, this query omits specified values for `versions`. + + +{{< tabs items="Query,Response" >}} + +{{% tab "query" %}} + +```graphql +query($includeIf: Boolean!) { + queryEquipment { + id + versions @include(if: $includeIf) { + id + } + } +} +``` + +{{% /tab %}} + +{{% tab "variables" %}} + +Change to `true` to include `versions` fields. + +```json +{ + "includeIf": false +} +``` + +{{% /tab %}} + +{{< /tabs >}} + + diff --git a/content/versions/v3.2.1/how-to/gql/generate.md b/content/versions/v3.2.1/how-to/gql/generate.md new file mode 100644 index 000000000..f5a38c978 --- /dev/null +++ b/content/versions/v3.2.1/how-to/gql/generate.md @@ -0,0 +1,62 @@ ++++ +title = "The @generate Directive" +description = "The @generate directive specifies which GraphQL APIs are generated for a given type. Without it, all queries & mutations are generated except subscription." +weight = 220 +draft = true +[menu.main] + identifier = "schema-generate" + parent = "how-to-query" ++++ + + + +The `@generate` directive is used to specify which GraphQL APIs are generated for a given type. + +Here's the GraphQL definition of the directive +```graphql +input GenerateQueryParams { + get: Boolean + query: Boolean + password: Boolean + aggregate: Boolean +} + +input GenerateMutationParams { + add: Boolean + update: Boolean + delete: Boolean +} +directive @generate( + query: GenerateQueryParams, + mutation: GenerateMutationParams, + subscription: Boolean) on OBJECT | INTERFACE + +``` + +The corresponding APIs are generated by setting the `Boolean` variables inside the `@generate` directive to `true`. Passing `false` forbids the generation of the corresponding APIs. + +The default value of the `subscription` variable is `false` while the default value of all +other variables is `true`. Therefore, if no `@generate` directive is specified for a type, all queries and mutations except `subscription` are generated. + +## Example of @generate directive + +```graphql +type Person @generate( + query: { + get: false, + query: true, + aggregate: false + }, + mutation: { + add: true, + delete: false + }, + subscription: false +) { + id: ID! + name: String! +} +``` + +The GraphQL schema above will generate a `queryPerson` query and `addPerson`, `updatePerson` mutations. It won't generate `getPerson`, `aggregatePerson` queries nor a `deletePerson` mutation as these have been marked as `false` using the `@generate` directive. +Note that the `updatePerson` mutation is generated because the default value of the `update` variable is `true`. diff --git a/content/versions/v3.2.1/how-to/gql/mutate.md b/content/versions/v3.2.1/how-to/gql/mutate.md new file mode 100644 index 000000000..5b68a961a --- /dev/null +++ b/content/versions/v3.2.1/how-to/gql/mutate.md @@ -0,0 +1,239 @@ +--- +title: 'Mutate' +categories: ["how-to"] +description: A guide to adding, creating, and deleting data in the Rhize DB +weight: 250 +--- + +{{< watch +text="Add manufacturing data through GraphQL" +src="https://www.youtube.com/watch?v=zQ5X0mg3i_w&t=217s" +>}} + +_Mutations_ change the database in someway by creating, updating, or deleting a resource. +You might use a mutation to update a personnel class, or in to a [{{< abbr "BPMN" >}}]({{< relref "../bpmn" >}}) workflow that automatically creates records of incoming material lots. + +Rhize supports the following ways to change the API. + +## `add` {#add} + +{{< callout type="info" >}} +The `add` operation corresponds to the `Process` verb defined in [Part 5](https://www.isa.org/products/ansi-isa-95-00-05-2018-enterprise-control-system-i) of the ISA-95 standard. +{{< /callout >}} + +Mutations that start with `add` create a resource on the server. + +For example, this mutation adds one more items of equipment. +To add multiple, send the variable as an array of objects, rather than a single object. +The `numUids` property reports how many new objects were created. + +{{% tabs items="Mutation,create 1, Create many"%}} +{{% tab "mutation" %}} + +```graphql +mutation AddEquipment($input: [AddEquipmentInput!]!) { + addEquipment(input: $input) { + equipment { + id + label + } + numUids + } +} +``` + +{{% /tab %}} +{{% tab "Vars: create one object" %}} + +```json +{ + + "input": { + "id": "Kitchen_mixer_a_20", + "label": "Kitchen mixer A11" + } +} +``` +{{% /tab %}} +{{% tab "Vars: Create many" %}} + +```json +{ + + "input": [{ + "id": "Kitchen_mixer_b_01", + "label": "Kitchen mixer A11" + },{ + "id": "Kitchen_mixer_b_02", + "label": "Kitchen mixer A12" + }, + ] +} +``` +{{% /tab %}}{{< /tabs >}} + +### `upsert` + +Many `add` operations support _upserting_, which _update_ or _insert_ (create). +That is, if the object already exists, the operation will update it with the additional fields. +If the object doesn't exist, the operation will create it. + +Besides general UX convenience, upsert is useful when data comes from multiple sources and in no guaranteed order, like from multiple streams from the message broker. + +To enable upsert, set the `upsert:` argument to true: + +```graphql +addEquipment(input: $input, upsert: true) +``` + +## `update` {#update} + +Mutations that start with `update` change something in an object that already exists. +The `update` operations can use [filters]({{< relref "./filter" >}}). + +{{< callout type="info" >}} +The `update` operation corresponds to the `Change` verb defined in [Part 5](https://www.isa.org/products/ansi-isa-95-00-05-2018-enterprise-control-system-i) of the ISA-95 standard. +{{< /callout >}} + +For example, this operation updates the description for a specific version of an equipment item. + + +```graphql +mutation updateMixerVersion( $updateEquipmentVersionInput2: UpdateEquipmentVersionInput!){ + updateEquipmentVersion(input: $updateEquipmentVersionInput2) { + equipmentVersion { + description + id + } + } +} +``` + +**Variables**: +```json +{ + "updateEquipmentVersionInput2": { + "filter": {"iid":"0xcc701"}, + "set": { + "description": "Second generation of the mixer 9000" + } + } +} +``` + +## `delete` {#delete} + +{{< callout type="warning" >}} +Be careful! Without a [Database backup]({{< relref "../../deploy/backup/graphdb" >}}), deleted items cannot be recovered. +{{< /callout >}} + + +Mutations that start with `delete` remove a resource from the database. +The `delete` operations can use [filters]({{< relref "./filter" >}}). + + +{{< callout type="info" >}} +The `delete` operation corresponds to the `Cancel` verb defined in [Part 5](https://www.isa.org/products/ansi-isa-95-00-05-2018-enterprise-control-system-i) of the ISA-95 standard. +{{< /callout >}} + +For example, this operation deletes a unit of measure: + + +```graphql +mutation deleteUoM($filter: UnitOfMeasureFilter!){ + deleteUnitOfMeasure(filter: $filter) { + numUids + } +} +``` + +**Variables:** +```json +{ + "filter": { + "id": { + "eq": "example unit of measure" + } + } +} + +``` + +## Deep mutations + +You can perform deep mutations at multiple levels. +Deep mutations don't alter linked objects but can add nested new objects or link to existing objects. + +For example, this mutation creates a new version of equipment, and associates a new item of equipment with it. Both the `equipmentVersion` and the `equipment` did not exist in the database. + +```graphql + mutation AddEquipmentVersion($addEquipmentVersionInput2: [AddEquipmentVersionInput!]!) { + addEquipmentVersion(input: $addEquipmentVersionInput2) { + equipmentVersion { + id + equipment { + id + } + } + } +} +``` + +**Variables:** + +```json + "addEquipmentVersionInput2": { + "id": "widget_machine_version_1", + "version": "1", + "versionStatus": "DRAFT", + "equipment": { + "id": "widget_maker_1", + "label": "Widget maker 1" + + } + } +} +``` + +You can confirm that the record and its nested property exists with a `get` query. +If the preceding operation succeeded, this query returns both the new `Widget Maker` and +its corresponding version: + +{{< tabs items="Query,Response" >}} +{{% tab "query" %}} +```graphql +query{ + getEquipment(id: "widget_maker_1") { + id + versions{ + id + version + } + } +} +``` +{{% /tab %}} + + +{{% tab "result" %}} + +```json +{ + "addEquipmentVersionInput2": { + "id": "widget_machine_version_1", + "version": "1", + "versionStatus": "DRAFT", + "equipment": { + "id": "widget_maker_1", + "label": "Widget maker 1" + + } + } +} +``` + +{{% /tab %}} + +{{< /tabs >}} + +To update an existing nested object, use the update mutation for its type. diff --git a/content/versions/v3.2.1/how-to/gql/query.md b/content/versions/v3.2.1/how-to/gql/query.md new file mode 100644 index 000000000..2d5382fe0 --- /dev/null +++ b/content/versions/v3.2.1/how-to/gql/query.md @@ -0,0 +1,161 @@ +--- +title: 'Query' +categories: ["how-to"] +description: A guide to the three GraphQL operations in Rhize +weight: 200 +--- + +A _query_ returns one or more resources from the database. +Whether you want to investigate manufacturing processes or build a custom report, +a good query is likely the foundation of your workflow. + +Most queries start with these three verbs, each of which indicates the resources to return. + +- `get` for a single resource +- `query` for multiple resources +- `aggregate` for calculations on arrays + + +{{< callout type="info" >}} + +These operations correspond to the `Get` verb defined in [Part 5](https://www.isa.org/products/ansi-isa-95-00-05-2018-enterprise-control-system-i) of the ISA-95 standard. + +{{< /callout >}} + +## `query` multiple resources {#query} + +Queries that start with `query` return an array of objects. +For example, a custom dashboard may use `queryEquipmentVersion` to create a page that displays all active versions of equipment that are running in a certain {{< abbr "hierarchy scope" >}}. + +For example, this query returns the ID of all pieces of equipment. + +```graphql +query allEquipment{ + queryEquipment { + id + } +} +``` + +### Query specified IDs + +To filter your query to a specific set of items, use the `filter` argument with the requested IDs. + +The least verbose way to filter is to specify the requested items' `iid` (their unique database addresses) in an array: +For example, this query returns only equipment with an `iid` of `0xf9b49` or `0x102aa5`. + +```graphql +query ExampleQuery { + queryEquipment(filter: { iid: ["0xf9b49", "0x102aa5"] }) { + iid + id + } +} +``` + +If you don't have the precise `iid`, you can use one of the string [filters]({{< relref "filter" >}}). + + +## `get` single resource {#get} + +Queries that start with `get` return one object. +A common use of `get` is to explore all data related to a particular object. +For example, in a custom dashboard, you may use `getDataSource` to make a custom page that reports a specified data source. + +Typically, the argument specifies the resource by either its human-readable ID (`id`) or its unique address in the database (`iid`). + +For example, this query gets the `iid`, `_createdBy`, and `versions` for the equipment item `Kitchen_mixer_b_01`: + +```graphql +query mixerCheck { + getEquipment(id: "Kitchen_mixer_b_01") { + iid + _createdBy + versions{ + id + } + } +} +``` + +## `Aggregate` data from multiple resources {#aggregate} + +Operations that start with `aggregate` provide aggregated statistics for a specified set of items. + +The syntax and filtering for an `aggregate` operation is the same as for a `query` operation. +However, rather than returning items, the aggregate operation returns one or more computed statistics about these items. +For example, you might use an `aggregate` query to create a summary report about a set of process segments within a certain time frame. + +This request returns the count of all Equipment items that match a certain filter: + +```graphql +query countItems($filter: EquipmentFilter) { + aggregateEquipment(filter: $filter) { + count + } +} +``` + +## Sort and paginate + +A query can take arguments to order and paginate your results. + +{{< callout type="info" >}} +Without an `order` parameter, a query returns items without any default or guaranteed order. +{{< /callout >}} + +### Order + +{{< callout type="caution" >}} + +Ordered queries **return only the first 1000 records of the ordered field.** +This behavior might exclude records that you expect, especially if you [combine `order` with a `@cascade`]({{< relref "filter#avoid-using-cascade-with-the-orderhahahugoshortcode50s8hbhb-argument" >}}) filter in a nested field. + +{{< /callout >}} + +The `order` argument works with any property whose type is `Int`, `Float`, `String`, or `DateTime`. +For example, this query sorts Person objects by ID in ascending alphabetical order: + +```graphql +query{ + queryPerson(order:{ asc: id}) { + id + } +} +``` + +And this orders by the Person's `effectiveStart` date in descending chronological order. + +``` +query{ + queryPerson(order:{ desc: effectiveStart}) { + id + effectiveStart + } +} +``` + +### Paginate with `offset` + +The `offset` argument specifies what item to start displaying results from, and the `first` argument specifies how many items to show. + +For example, this skips the five most recent Person items (as measured by `effectiveStart`), and then displays the next 10: + +```graphql +query{ + queryPerson(order:{ + desc: effectiveStart + }, + offset: 5, + first: 10 + ) { + id + effectiveStart + } +} +``` + +## Filter queries + +Rhize also has many queries to filter or return subsets of items. +To learn how to filter, read [Use query filters]({{< relref "./filter" >}}). diff --git a/content/versions/v3.2.1/how-to/gql/subscribe.md b/content/versions/v3.2.1/how-to/gql/subscribe.md new file mode 100644 index 000000000..d09f5f7b7 --- /dev/null +++ b/content/versions/v3.2.1/how-to/gql/subscribe.md @@ -0,0 +1,34 @@ +--- +title: 'Subscribe' +categories: ["how-to"] +description: A guide to using GraphQL to subscribe to changes in the database. +weight: 280 +--- + +The operations for a `subscription` are similar to the operations for a [`query`]({{< relref "./query" >}}). +But rather than providing information about the entire item, the purpose of subscriptions is to notify about real-time changes to a manufacturing resource. + + +{{< callout type="info" >}} + +These operations correspond to the `SyncGet` verb defined in [Part 5](https://www.isa.org/products/ansi-isa-95-00-05-2018-enterprise-control-system-i) of the ISA-95 standard. + +{{< /callout >}} + + +This example query subscribes to changes in a specified set of `workResponses`, reporting only their `id` and effective end time. + +```graphql + +subscription GetWorkResponse($getWorkResponseId: String) { + getWorkResponse(id: $getWorkResponseId){ + jobResponses { + effectiveEnd + } + } +} +``` + +Try to minimize the payload for subscription operations. +Additionally, you need to subscribe only to changes that persist to the knowledge graph. +For general event handling, it's often better to use a [BPMN workflow]({{< relref "../bpmn" >}}) that subscribes to a NATS, MQTT, or OPC UA topic. diff --git a/content/versions/v3.2.1/how-to/kpi-service/_index.md b/content/versions/v3.2.1/how-to/kpi-service/_index.md new file mode 100644 index 000000000..7b0d3bec4 --- /dev/null +++ b/content/versions/v3.2.1/how-to/kpi-service/_index.md @@ -0,0 +1,17 @@ +--- +title: 'Use the KPI service' +categories: "how-to" +description: How to configure KPI Service to record key ISO22400 OEE Metrics. +weight: 500 +cascade: + experimental: true + icon: oui-stats +--- + +{{< experimental-kpi >}} + +The KPI service records {{< abbr "equipment" >}}-centric metrics related to the manufacturing operation. +To use it, you must: +1. Record machine state data using the [rule pipeline]({{< relref "../publish-subscribe/create-equipment-class-rule/" >}}). +1. Persist this data to a time-series database. + diff --git a/content/versions/v3.2.1/how-to/kpi-service/about-kpi-service.md b/content/versions/v3.2.1/how-to/kpi-service/about-kpi-service.md new file mode 100644 index 000000000..d6473182c --- /dev/null +++ b/content/versions/v3.2.1/how-to/kpi-service/about-kpi-service.md @@ -0,0 +1,69 @@ +--- +title: About KPI Service and overrides +description: >- + An explanation of how the Rhize KPI service works +weight: 200 +--- + +{{< experimental-kpi >}} + +Key Performance Indicators (KPIs) in manufacturing are metrics to help monitor, assess, and optimize the performance of various aspects of your production process. + +Rhize has an optional `KPI` service that queries process values persisted to a time-series database and then calculates various KPIs. +Rhize's implementation of work calendars is inspired by ISO/TR [22400-10](https://www.iso.org/obp/ui/?_escaped_fragment_=iso:std:71283:en), a standard on KPIs in operations management. + +## What the service does + +```mermaid +sequenceDiagram + actor U as User + participant K as KPI Service + participant TSDB as Time Series Database + + U->>K: Query KPI in certain interval + K->>TSDB: Query State Records + TSDB->>K: Response: State records + K->>TSDB: Query Quantity Records + TSDB->>K: Response: Quantity records + K->>TSDB: Query JobResponse Records + TSDB->>K: Response: JobResponse records + K-->>TSDB: (Optional:) Query Planned Downtime Records + TSDB-->>K: Response: Downtime Records + K-->>TSDB: (Optional:) Query Shift Records + TSDB-->>K: Response: Downtime Records + K->>K: Calculate KPIs + K->U: Response: KPI Result +``` + +The KPI service provides an interface in the graph database for the user to query a list of pre-defined KPIs on a piece of equipment in the `equipmentHierarchy` within a certain time interval. +The service then queries the time-series database for all state changes, produced quantities, and job response data. +With the returned data, the service calculates the KPI value and returns it to the user. + +## Supported KPIs + +The service supports all KPIs described by the ISO/TR 22400-10, +along with some other useful KPIs: + +- `ActualProductionTime` +- `ActualUnitSetupTime` +- `ActualSetupTime` +- `ActualUnitDelayTime` +- `ActualUnitDownTime` +- `TimeToRepair` +- `ActualUnitProcessingTime` +- `PlannedShutdownTime` +- `PlannedDownTime` +- `PlannedBusyTime` +- `Availability` +- `GoodQuantity` +- `ScrapQuantity` +- `ReworkQuantity` +- `ProducedQuantityMachineOrigin` +- `ProducedQuantity` +- `Effectiveness` +- `EffectivenessMachineOrigin` +- `QualityRatio` +- `OverallEquipmentEffectiveness` +- `ActualCycleTime` +- `ActualCycleTimeMachineOrigin` + diff --git a/content/versions/v3.2.1/how-to/kpi-service/configure-kpi-service.md b/content/versions/v3.2.1/how-to/kpi-service/configure-kpi-service.md new file mode 100644 index 000000000..b4a16906c --- /dev/null +++ b/content/versions/v3.2.1/how-to/kpi-service/configure-kpi-service.md @@ -0,0 +1,198 @@ +--- +title: Configure the KPI service +description: >- + An explanation of how to configure the KPI service to feed it with process data +weight: 200 +--- + +{{< experimental-kpi >}} + +This guide shows you how to configure the time-series you need for the KPI service. +It does not suggest how to persist these values. + +To learn how the KPI service works, read [About KPI service]({{< ref "about-kpi-service" >}}). +Example use cases include {{< abbr "OEE" >}} and various performance metrics. + +## Prerequisites + +Before you start, ensure you have the following: +- The KPI service installed +- An `equipmentHierarchy` is configured + +## Procedure + +In short, to configure the KPI Service, the procedure works as follows: + +1. Persist machine state records to the `EquipmentState` table +1. Persist quantity records to the `QuantityLog` table +1. Persist job response data to the `JobOrderState` table +1. (Optional) Configure the calendar service to record planned downtime events and shift records to time series. Refer to [Use work calendars]({{< relref "../work-calendars" >}}) + +## Record machine states + +Every time an equipment changes state, it is persisted to the time-series table `EquipmentState`. + +### `EquipmentState` table schema + +{{< tabs items="Schema,Example">}} +{{% tab "schema" %}} + +```sql +CREATE TABLE IF NOT EXISTS EquipmentState( + EquipmentId SYMBOL, + ISO22400State VARCHAR, -- ADOT, AUST, ADET, APT + time TIMESTAMP +) TIMESTAMP(time) PARTITION BY MONTH DEDUP UPSERT KEYS(time, EquipmentId); +``` + +{{< callout type="info" >}} +This table shows a QuestDB specific schema. +You may also add additional columns as required. + +To use the service for another time-series DB, get in touch. +{{< /callout >}} +{{% /tab %}} +{{% tab "example" %}} + +```json +[ + { + "EquipmentId": "Machine A", + "ISO22400State": "ADET", + "PackMLState": "Held", + "time": "2024-03-28T13:13:47.814086Z", + } +] +``` + +{{< callout type="info" >}} +This record includes an additional field, `PackMLState`, to show that additional data can also be recorded. +{{< /callout >}} +{{% /tab %}} +{{< /tabs >}} + +## Record quantity records + +You can persist two categories of quantity records: + +1. (Optional) Values generated by the machine. +1. Final produced quantities (these should be categorised into `Good`, `Scrap`, and `Rework`). + +### QuantityLog table schema + +{{< tabs items="Schema, Machine example, User Example" >}} +{{% tab "schema" %}} + +```sql +CREATE TABLE IF NOT EXISTS QuantityLog( + EquipmentId SYMBOL, + Origin SYMBOL, -- Machine, User + QtyType SYMBOL, -- Delta, RunningTotal (running total not currently supported) + ProductionType SYMBOL, -- Good, Scrap, Rework + Qty FLOAT, + time TIMESTAMP +) TIMESTAMP(time) PARTITION BY MONTH DEDUP UPSERT KEYS(time, EquipmentId, Origin, QtyType, ProductionType); +``` + +{{% /tab %}} +{{% tab "machine example" %}} + +```json +[ + { + "EquipmentId": "Machine A", + "Origin": "Machine", + "QtyType": "Delta", + "ProductionType": "Unknown", + "Qty": 6, + "time": "2024-03-28T09:30:34.000325Z" + } +] +``` + +{{% /tab %}} +{{% tab "user example" %}} + +```json +[ + { + "EquipmentId": "Machine A", + "Origin": "User", + "QtyType": "Delta", + "ProductionType": "Good", + "Qty": 10, + "time": "2024-03-28T09:30:34.000325Z" + }, +{ + "EquipmentId": "Machine A", + "Origin": "User", + "QtyType": "Delta", + "ProductionType": "Scrap", + "Qty": 2, + "time": "2024-03-28T09:30:34.000325Z" + }, +{ + "EquipmentId": "Machine A", + "Origin": "User", + "QtyType": "Delta", + "ProductionType": "Rework", + "Qty": 1, + "time": "2024-03-28T09:30:34.000325Z" + } +] +``` + +{{% /tab %}} +{{< /tabs >}} + +## Record job response records + +Job response records persist to `JobOrderState` and are used to identify the current planned cycle time of each part produced from the machine. +When an operation starts, a record is created setting the planned cycle time. +When the operation is finished, another record is created to reset the planned cycle time to 0. + +### JobOrderState table schema + +{{< tabs items="Schema,Start operation,End operation" >}} +{{% tab "schema" %}} + +```sql +CREATE TABLE IF NOT EXISTS JobOrderState( + EquipmentId SYMBOL, + JobOrderId SYMBOL, + PlanningCycleTime FLOAT, -- Number of seconds per produced part + time TIMESTAMP +) TIMESTAMP(time) PARTITION BY MONTH DEDUP UPSERT KEYS(time, EquipmentId, JobOrderId); +``` + +{{% /tab %}} +{{% tab "start operation" %}} + +```json +[ + { + "EquipmentId": "Machine A", + "JobOrderId": "Order001", + "PlanningCycleTime": 100, + "time": "2024-04-02T14:32:21.947000Z" + } +] +``` + +{{% /tab %}} +{{% tab "end operation" %}} + +```json +[ + { + "EquipmentId": "Machine A", + "JobOrderId": "Order001", + "PlanningCycleTime": 0, + "time": "2024-04-02T14:59:58.947000Z" + } +] +``` + +{{% /tab %}} +{{< /tabs >}} + diff --git a/content/versions/v3.2.1/how-to/kpi-service/query-kpi-service.md b/content/versions/v3.2.1/how-to/kpi-service/query-kpi-service.md new file mode 100644 index 000000000..015e97714 --- /dev/null +++ b/content/versions/v3.2.1/how-to/kpi-service/query-kpi-service.md @@ -0,0 +1,1125 @@ +--- +title: Query the KPI service +description: >- + An explanation of how to query the KPI service to obtain OEE values +weight: 200 +--- + +{{< experimental-kpi >}} + +The KPI service offers a federated GraphQL interface to query KPI values. +This guide provides information on the different querying interfaces. + +## Root level queries + +The KPI service offers two root-level queries: + +- `GetKPI()` +- `GetKPIByShift()` + +### `GetKPI()` + +The `GetKPI()` query is the base-level KPI Query. +You can use it to input an equipment ID or hierarchy-scope ID, a time range, and a list of desired KPIs. +The result is a single KPI object per requested KPI. + +#### GetKPI() - Definition + +{{< tabs items="query,response" >}} +{{% tab "query" %}} +query: + +```graphql +query GetKPI($filterInput: KPIFilter!, $startDateTime: DateTime!, $endDateTime: DateTime!, $kpi: [KPI!], $ignorePlannedDownTime: Boolean, $ignorePlannedShutdownTime: Boolean) { + GetKPI(filterInput: $filterInput, startDateTime: $startDateTime, endDateTime: $endDateTime, kpi: $kpi, ignorePlannedDownTime: $ignorePlannedDownTime, ignorePlannedShutdownTime: $ignorePlannedShutdownTime) { + name + to + from + error + value + units + } +} +``` + +input: + +```json +{ + "filterInput": { + "equipmentIds": ["MachineA", "MachineB"], + "hierarchyScopeId": "Enterprise1.SiteA.Line1" + }, + "startDateTime": "2024-09-01T00:00:00Z", + "endDateTime": "2024-09-01T18:00:00Z", + "kpi": ["ActualProductionTime","Availability", "GoodQuantity", "ProducedQuantity", "Effectiveness", "QualityRatio", "ActualCycleTime", "OverallEquipmentEffectiveness"], + "ignorePlannedDownTime": false, + "ignorePlannedShutdownTime": false, + "onlyIncludeActiveJobResponses": false +} +``` + +{{% /tab %}} +{{% tab "response" %}} + +```json +{ + "data": { + "GetKPI": [ + { + "name": "ActualProductionTime", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 0, + "units": "seconds" + }, + { + "name": "ActualUnitDelayTime", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 0, + "units": "seconds" + }, + { + "name": "PlannedDownTime", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 0, + "units": "seconds" + }, + { + "name": "Availability", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 0, + "units": "%" + }, + { + "name": "GoodQuantity", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 0, + "units": "units" + }, + { + "name": "ProducedQuantity", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 0, + "units": "units" + }, + { + "name": "Effectiveness", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 100, + "units": "%" + }, + { + "name": "QualityRatio", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 100, + "units": "%" + }, + { + "name": "ActualCycleTime", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 0, + "units": "seconds per unit" + }, + { + "name": "OverallEquipmentEffectiveness", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 0, + "units": "%" + } + ] + } +} +``` + +{{% /tab %}} +{{< /tabs >}} + +#### Example 1. + +Imagine a scenario where `Machine A` produces parts at a planned cycle time of 10-seconds per part. +The order starts at 09:00 and finishes at 12:00 with 30 minutes of unplanned downtime in between (this could be from loading materials, unplanned maintenance, switching tools, and so on). +After the operation finishes, the user has registered 800 Good parts and 200 scrap parts. +The tables in time series appear as follows: + +{{< tabs items="Equipmentstate,QuantityLog,JobOrderState" >}} +{{% tab "EquipmentState" %}} + +| EquipmentId | ISO22400State | time | +|-------------|---------------|----------------------| +| Machine A | APT | 2024-09-03T09:00:00Z | +| Machine A | ADET | 2024-09-03T10:30:00Z | +| Machine A | APT | 2024-09-03T11:00:00Z | +| Machine A | ADOT | 2024-09-03T12:00:00Z | + +{{% /tab %}} +{{% tab "QuantityLog" %}} + +| EquipmentId | Origin | QtyType | ProductionType | Qty | time | +|-------------|--------|---------|----------------|-----|----------------------| +| Machine A | User | Delta | Good | 800 | 2024-09-03T12:00:00Z | +| Machine A | User | Delta | Scrap | 200 | 2024-09-03T12:00:00Z | + +{{% /tab %}} +{{% tab "JobOrderState" %}} + +| EquipmentId | JobOrderId | PlanningCyleTime | time | +|-------------|------------|------------------|----------------------| +| Machine A | Order A | 10 | 2024-09-03T09:00:00Z | +| Machine A | NONE | 0 | 2024-09-03T12:00:00Z | + +{{% /tab %}} +{{< /tabs >}} + +Calling this KPI Query appears as follows: + +{{< tabs items="Query,Response">}} +{{% tab "query" %}} +query: + +```graphql +query GetKPI($filterInput: KPIFilter!, $startDateTime: DateTime!, $endDateTime: DateTime!, $kpi: [KPI!], $ignorePlannedDownTime: Boolean, $ignorePlannedShutdownTime: Boolean) { + GetKPI(filterInput: $filterInput, startDateTime: $startDateTime, endDateTime: $endDateTime, kpi: $kpi, ignorePlannedDownTime: $ignorePlannedDownTime, ignorePlannedShutdownTime: $ignorePlannedShutdownTime) { + name + to + from + error + value + units + } +} +``` + +input: + +```json +{ + "filterInput": { + "equipmentIds": ["MachineA"] + }, + "startDateTime": "2024-09-03T09:00:00Z", + "endDateTime": "2024-09-03T12:00:00Z", + "kpi": ["ActualProductionTime","Availability", "GoodQuantity", "ProducedQuantity", "Effectiveness", "QualityRatio", "ActualCycleTime", "OverallEquipmentEffectiveness"] +} +``` + +{{% /tab %}} +{{% tab "response" %}} + +```json +{ + "data": { + "GetKPI": [ + { + "_comment": "This is the total time spent in APT", + "name": "ActualProductionTime", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 9000, + "units": "seconds" + }, + { + "_comment": "This is the total time spent in ADET", + "name": "ActualUnitDelayTime", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 1800, + "units": "seconds" + }, + { + "_comment": "This is the total time spent in PDOT", + "name": "PlannedDownTime", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 0, + "units": "seconds" + }, + { + "_comment": "This is APT/PBT", + "name": "Availability", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 83.3333333, + "units": "%" + }, + { + "_comment": "This is the total recorded good quantity", + "name": "GoodQuantity", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 800, + "units": "units" + }, + { + "_comment": "This is the total quantity produced in the order", + "name": "ProducedQuantity", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 1000, + "units": "units" + }, + {"_comment": "This is (ProducedQuantity * PlannedCycleTime)/APT", + "name": "Effectiveness", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 111.111111, + "units": "%" + }, + { + "_comment": "This is GoodQuantity/ProducedQuantity", + "name": "QualityRatio", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 80, + "units": "%" + }, + { + "_comment": "This is APT/ProducedQuantity", + "name": "ActualCycleTime", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 10.8, + "units": "seconds per unit" + }, + { + "_comment": "This is Availability * Effectiveness * QualityRatio", + "name": "OverallEquipmentEffectiveness", + "to": "2024-09-01T18:00:00Z", + "from": "2024-09-01T00:00:00Z", + "error": null, + "value": 74.074, + "units": "%" + } + ] + } +} +``` + +{{% /tab %}} +{{< /tabs >}} + +### `GetKPIByShift()` + +The `GetKPIByShift()` query is another base-level KPI Query. +It is similar to GetKPI(), but rather than returning a single result per KPI query, it also accepts `WorkCalendarEntryProperty IDs` to filter against and return a result for each instance of a shift. + +#### GetKPIByShift() - Definition + +{{< tabs items="Query,Response" >}} +{{% tab "query" %}} +query: + +```graphql +query GetKPIByShift($filterInput: GetKPIByShiftFilter!, $startDateTime: DateTime!, $endDateTime: DateTime!, $kpi: [KPI!], $ignorePlannedDownTime: Boolean, $ignorePlannedShutdownTime: Boolean, $groupByShift: Boolean, $groupByEquipment: Boolean, $onlyIncludeActiveJobResponses: Boolean) { + GetKPIByShift(filterInput: $filterInput, startDateTime: $startDateTime, endDateTime: $endDateTime, kpi: $kpi, ignorePlannedDownTime: $ignorePlannedDownTime, ignorePlannedShutdownTime: $ignorePlannedShutdownTime, groupByShift: $groupByShift, groupByEquipment: $groupByEquipment, OnlyIncludeActiveJobResponses: $onlyIncludeActiveJobResponses) { + name + equipmentIds + shiftsContained + from + to + error + value + units + } +} +``` + +input: + +```json +{ + "filterInput": { + "shiftFilter": [ + { + "propertyName": "Shift Name", + "eq": "Morning" + } + ], + "equipmentIds": ["Machine A", "Machine B"], + "hierarchyScopeId": "Enterprise1.SiteA.Line1" + }, + "startDateTime": "2024-09-01T00:00:00Z", + "endDateTime": "2024-09-03T18:00:00Z", + "kpi": ["ActualProductionTime", "OverallEquipmentEffectiveness"], + "ignorePlannedDownTime": false, + "ignorePlannedShutdownTime": false, + "onlyIncludeActiveJobResponses": false, + "groupByShift": false, + "groupByEquipment": true +} +``` + +{{% /tab %}} +{{% tab "response" %}} + +```json +{ + "data": { + "GetKPIByShift": [ + { + "name": "ActualProductionTime", + "equipmentIds": ["Machine A", "Machine B"], + "shiftsContained": ["Shift.Sunday.Morning"], + "from": "2024-09-01T09:00:00Z", + "to": "2024-09-01T17:00:00Z", + "error": null, + "value": 0, + "units": "seconds" + }, + { + "name": "ActualProductionTime", + "equipmentIds": ["Machine A", "Machine B"], + "shiftsContained": ["Shift.Monday.Morning"], + "from": "2024-09-02T00:00:00Z", + "to": "2024-09-02T17:00:00Z", + "error": null, + "value": 0, + "units": "seconds" + }, + { + "name": "ActualProductionTime", + "equipmentIds": ["Machine A", "Machine B"], + "shiftsContained": ["Shift.Tuesday.Morning"], + "from": "2024-09-03T00:00:00Z", + "to": "2024-09-03T17:00:00Z", + "error": null, + "value": 0, + "units": "seconds" + }, + { + "name": "OverallEquipmentEffectiveness", + "equipmentIds": ["Machine A", "Machine B"], + "shiftsContained": ["Shift.Sunday.Morning"], + "from": "2024-09-01T09:00:00Z", + "to": "2024-09-01T17:00:00Z", + "error": null, + "value": 0, + "units": "%" + }, + { + "name": "OverallEquipmentEffectiveness", + "equipmentIds": ["Machine A", "Machine B"], + "shiftsContained": ["Shift.Monday.Morning"], + "from": "2024-09-02T00:00:00Z", + "to": "2024-09-02T17:00:00Z", + "error": null, + "value": 0, + "units": "%" + }, + { + "name": "OverallEquipmentEffectiveness", + "equipmentIds": ["Machine A", "Machine B"], + "shiftsContained": ["Shift.Tuesday.Morning"], + "from": "2024-09-03T00:00:00Z", + "to": "2024-09-03T17:00:00Z", + "error": null, + "value": 0, + "units": "%" + }, + ] + } +} +``` + +{{% /tab %}} +{{< /tabs >}} + +#### Example 2 + +Following on from Example 1. `Machine A` exists on a production line alongside `Machine B`, they both produce parts with a planned cycle time of 10 seconds per part and runs on the same shift pattern. The [work calendar service]({{< relref "../work-calendars" >}}) is configured with 3 distinct daily shifts: + +- Morning (06:00-14:00) +- Afternoon (14:00 - 22:00) +- Night (22:00-06:00) + +Which results in the following tables: + +{{< tabs items="EquipmentState,QuantityLog,Calendar" >}} +{{% tab "EquipmentState" %}} + +| EquipmentId | ISO22400State | time | +|-------------|---------------|----------------------| +| Machine A | APT | 2024-09-01T06:00:00Z | +| Machine B | APT | 2024-09-01T06:00:00Z | +| Machine A | ADET | 2024-09-01T10:30:00Z | +| Machine B | ADET | 2024-09-01T10:30:00Z | +| Machine A | APT | 2024-09-01T11:00:00Z | +| Machine B | APT | 2024-09-01T11:00:00Z | +| Machine A | ADOT | 2024-09-01T14:00:00Z | +| Machine B | ADOT | 2024-09-01T14:00:00Z | +| Machine A | APT | 2024-09-01T14:00:00Z | +| Machine B | APT | 2024-09-01T14:00:00Z | +| Machine A | ADET | 2024-09-01T17:30:00Z | +| Machine B | ADET | 2024-09-01T17:30:00Z | +| Machine A | APT | 2024-09-01T18:00:00Z | +| Machine B | APT | 2024-09-01T18:00:00Z | +| Machine A | ADOT | 2024-09-01T22:00:00Z | +| Machine B | ADOT | 2024-09-01T22:00:00Z | +| Machine A | APT | 2024-09-01T22:00:00Z | +| Machine B | APT | 2024-09-01T22:00:00Z | +| Machine A | ADET | 2024-09-02T04:00:00Z | +| Machine B | ADET | 2024-09-02T04:00:00Z | +| Machine A | APT | 2024-09-02T04:30:00Z | +| Machine B | APT | 2024-09-02T04:30:00Z | +| Machine A | ADOT | 2024-09-02T06:00:00Z | +| Machine B | ADOT | 2024-09-02T06:00:00Z | +| Machine A | APT | 2024-09-02T06:00:00Z | +| Machine B | APT | 2024-09-02T06:00:00Z | +| Machine A | ADET | 2024-09-02T10:30:00Z | +| Machine B | ADET | 2024-09-02T10:30:00Z | +| Machine A | APT | 2024-09-02T11:00:00Z | +| Machine B | APT | 2024-09-02T11:00:00Z | +| Machine A | ADOT | 2024-09-02T14:00:00Z | +| Machine B | ADOT | 2024-09-02T14:00:00Z | +| Machine A | APT | 2024-09-02T14:00:00Z | +| Machine B | APT | 2024-09-02T14:00:00Z | +| Machine A | ADET | 2024-09-02T18:30:00Z | +| Machine B | ADET | 2024-09-02T18:30:00Z | +| Machine A | APT | 2024-09-02T19:00:00Z | +| Machine B | APT | 2024-09-02T19:00:00Z | +| Machine A | ADOT | 2024-09-02T22:00:00Z | +| Machine B | ADOT | 2024-09-02T22:00:00Z | +| Machine A | APT | 2024-09-02T22:00:00Z | +| Machine B | APT | 2024-09-02T22:00:00Z | +| Machine A | ADET | 2024-09-03T04:30:00Z | +| Machine B | ADET | 2024-09-03T04:30:00Z | +| Machine A | APT | 2024-09-03T05:00:00Z | +| Machine B | APT | 2024-09-03T05:00:00Z | +| Machine A | ADOT | 2024-09-03T06:00:00Z | +| Machine B | ADOT | 2024-09-03T06:00:00Z | + +{{% /tab %}} +{{% tab "QuantityLog" %}} + +| EquipmentId | Origin | QtyType | ProductionType | Qty | time | +|-------------|--------|---------|----------------|-----|----------------------| +| Machine A | User | Delta | Good | 800 | 2024-09-01T14:00:00Z | +| Machine A | User | Delta | Scrap | 200 | 2024-09-01T14:00:00Z | +| Machine B | User | Delta | Good | 700 | 2024-09-01T14:00:00Z | +| Machine B | User | Delta | Scrap | 300 | 2024-09-01T14:00:00Z | +| Machine A | User | Delta | Good | 900 | 2024-09-01T22:00:00Z | +| Machine A | User | Delta | Scrap | 100 | 2024-09-01T22:00:00Z | +| Machine B | User | Delta | Good | 950 | 2024-09-01T22:00:00Z | +| Machine B | User | Delta | Scrap | 50 | 2024-09-01T22:00:00Z | +| Machine A | User | Delta | Good | 999 | 2024-09-01T06:00:00Z | +| Machine A | User | Delta | Scrap | 1 | 2024-09-01T06:00:00Z | +| Machine B | User | Delta | Good | 900 | 2024-09-01T06:00:00Z | +| Machine B | User | Delta | Scrap | 100 | 2024-09-01T06:00:00Z | +| Machine A | User | Delta | Good | 850 | 2024-09-02T14:00:00Z | +| Machine A | User | Delta | Scrap | 150 | 2024-09-02T14:00:00Z | +| Machine B | User | Delta | Good | 800 | 2024-09-02T14:00:00Z | +| Machine B | User | Delta | Scrap | 200 | 2024-09-02T14:00:00Z | +| Machine A | User | Delta | Good | 700 | 2024-09-02T22:00:00Z | +| Machine A | User | Delta | Scrap | 300 | 2024-09-02T22:00:00Z | +| Machine B | User | Delta | Good | 750 | 2024-09-02T22:00:00Z | +| Machine B | User | Delta | Scrap | 250 | 2024-09-02T22:00:00Z | +| Machine A | User | Delta | Good | 600 | 2024-09-02T06:00:00Z | +| Machine A | User | Delta | Scrap | 400 | 2024-09-02T06:00:00Z | +| Machine B | User | Delta | Good | 750 | 2024-09-02T06:00:00Z | +| Machine B | User | Delta | Scrap | 250 | 2024-09-02T06:00:00Z | + +{{% /tab %}} +{{% tab "JobOrderState" %}} + +| EquipmentId | JobOrderId | PlanningCyleTime | time | +|-------------|-------------|------------------|----------------------| +| Machine A | Order A1 | 10 | 2024-09-01T06:00:00Z | +| Machine B | Order A2 | 10 | 2024-09-01T06:00:00Z | +| Machine A | NONE | 0 | 2024-09-01T14:00:00Z | +| Machine B | NONE | 0 | 2024-09-01T14:00:00Z | +| Machine A | Order B1 | 10 | 2024-09-01T14:00:00Z | +| Machine B | Order B2 | 10 | 2024-09-01T14:00:00Z | +| Machine A | NONE | 0 | 2024-09-01T22:00:00Z | +| Machine B | NONE | 0 | 2024-09-01T22:00:00Z | +| Machine A | Order C1 | 10 | 2024-09-01T22:00:00Z | +| Machine B | Order C2 | 10 | 2024-09-01T22:00:00Z | +| Machine A | NONE | 0 | 2024-09-02T06:00:00Z | +| Machine B | NONE | 0 | 2024-09-02T06:00:00Z | +| Machine A | Order D1 | 10 | 2024-09-02T06:00:00Z | +| Machine B | Order D2 | 10 | 2024-09-02T06:00:00Z | +| Machine A | NONE | 0 | 2024-09-02T14:00:00Z | +| Machine B | NONE | 0 | 2024-09-02T14:00:00Z | +| Machine A | Order E1 | 10 | 2024-09-02T14:00:00Z | +| Machine B | Order E2 | 10 | 2024-09-02T14:00:00Z | +| Machine A | NONE | 0 | 2024-09-02T22:00:00Z | +| Machine B | NONE | 0 | 2024-09-02T22:00:00Z | +| Machine A | Order F1 | 10 | 2024-09-02T22:00:00Z | +| Machine B | Order F2 | 10 | 2024-09-02T22:00:00Z | +| Machine A | NONE | 0 | 2024-09-03T06:00:00Z | +| Machine B | NONE | 0 | 2024-09-03T06:00:00Z | + +{{% /tab %}} +{{% tab "Calendar_AdHoc" %}} + +| EquipmentId | WorkCalendarDefinitionID | WorkCalendarDefinitionEntryId | EntryType | time | +|-------------|--------------------------|----------------------------------|-----------|----------------------| +| Machine A | ShiftCalendar | ShiftCalendar.Sunday.Morning | START | 2024-09-01T06:00:00Z | +| Machine B | ShiftCalendar | ShiftCalendar.Sunday.Morning | START | 2024-09-01T06:00:00Z | +| Machine A | ShiftCalendar | ShiftCalendar.Sunday.Morning | END | 2024-09-01T14:00:00Z | +| Machine B | ShiftCalendar | ShiftCalendar.Sunday.Morning | END | 2024-09-01T14:00:00Z | +| Machine A | ShiftCalendar | ShiftCalendar.Sunday.Afternoon | START | 2024-09-01T14:00:00Z | +| Machine B | ShiftCalendar | ShiftCalendar.Sunday.Afternoon | START | 2024-09-01T14:00:00Z | +| Machine A | ShiftCalendar | ShiftCalendar.Sunday.Afternoon | END | 2024-09-01T22:00:00Z | +| Machine B | ShiftCalendar | ShiftCalendar.Sunday.Afternoon | END | 2024-09-01T22:00:00Z | +| Machine A | ShiftCalendar | ShiftCalendar.Sunday.Night | START | 2024-09-01T22:00:00Z | +| Machine B | ShiftCalendar | ShiftCalendar.Sunday.Night | START | 2024-09-01T22:00:00Z | +| Machine A | ShiftCalendar | ShiftCalendar.Sunday.Night | END | 2024-09-02T06:00:00Z | +| Machine B | ShiftCalendar | ShiftCalendar.Sunday.Night | END | 2024-09-02T06:00:00Z | +| Machine A | ShiftCalendar | ShiftCalendar.Monday.Morning | START | 2024-09-02T06:00:00Z | +| Machine B | ShiftCalendar | ShiftCalendar.Monday.Morning | START | 2024-09-02T06:00:00Z | +| Machine A | ShiftCalendar | ShiftCalendar.Monday.Morning | END | 2024-09-02T14:00:00Z | +| Machine B | ShiftCalendar | ShiftCalendar.Monday.Morning | END | 2024-09-02T14:00:00Z | +| Machine A | ShiftCalendar | ShiftCalendar.Monday.Afternoon | START | 2024-09-02T14:00:00Z | +| Machine B | ShiftCalendar | ShiftCalendar.Monday.Afternoon | START | 2024-09-02T14:00:00Z | +| Machine A | ShiftCalendar | ShiftCalendar.Monday.Afternoon | END | 2024-09-02T22:00:00Z | +| Machine B | ShiftCalendar | ShiftCalendar.Monday.Afternoon | END | 2024-09-02T22:00:00Z | +| Machine A | ShiftCalendar | ShiftCalendar.Monday.Night | START | 2024-09-02T22:00:00Z | +| Machine B | ShiftCalendar | ShiftCalendar.Monday.Night | START | 2024-09-02T22:00:00Z | +| Machine A | ShiftCalendar | ShiftCalendar.Monday.Night | END | 2024-09-03T06:00:00Z | +| Machine B | ShiftCalendar | ShiftCalendar.Monday.Night | END | 2024-09-03T06:00:00Z | + +{{% /tab %}} +{{< /tabs >}} + +You can run this query in multiple ways: + +- **`groupByEquipment = false and groupByShift = false` -** returns a separate result per shift instance per equipment + +{{< tabs items="Query,Response" >}} +{{% tab "query" %}} +query: + +```graphql +query GetKPIByShift($filterInput: GetKPIByShiftFilter!, $startDateTime: DateTime!, $endDateTime: DateTime!, $kpi: [KPI!], $ignorePlannedDownTime: Boolean, $ignorePlannedShutdownTime: Boolean, $groupByShift: Boolean, $groupByEquipment: Boolean, $onlyIncludeActiveJobResponses: Boolean) { + GetKPIByShift(filterInput: $filterInput, startDateTime: $startDateTime, endDateTime: $endDateTime, kpi: $kpi, ignorePlannedDownTime: $ignorePlannedDownTime, ignorePlannedShutdownTime: $ignorePlannedShutdownTime, groupByShift: $groupByShift, groupByEquipment: $groupByEquipment, OnlyIncludeActiveJobResponses: $onlyIncludeActiveJobResponses) { + name + equipmentIds + shiftsContained + from + to + error + value + units + } +} +``` + +input: + +```json +{ + "filterInput": { + "shiftFilter": [ + { + "propertyName": "Shift Name", + "eq": "Morning" + } + ], + "equipmentIds": ["Machine A", "Machine B"], + }, + "startDateTime": "2024-09-01T00:00:00Z", + "endDateTime": "2024-09-03T18:00:00Z", + "kpi": ["ActualProductionTime"], + "ignorePlannedDownTime": false, + "ignorePlannedShutdownTime": false, + "onlyIncludeActiveJobResponses": false, + "groupByShift": false, + "groupByEquipment": false +} +``` + +{{% /tab %}} +{{% tab "response" %}} + +```json +{ + "data": { + "GetKPIByShift": [ + { + "name": "ActualProductionTime", + "equipmentIds": ["Machine A"], + "shiftsContained": ["Shift.Sunday.Morning"], + "from": "2024-09-01T06:00:00Z", + "to": "2024-09-01T14:00:00Z", + "error": null, + "value": 27000, + "units": "seconds" + }, + { + "name": "ActualProductionTime", + "equipmentIds": ["Machine B"], + "shiftsContained": ["Shift.Sunday.Morning"], + "from": "2024-09-01T06:00:00Z", + "to": "2024-09-01T14:00:00Z", + "error": null, + "value": 27000, + "units": "seconds" + }, + { + "name": "ActualProductionTime", + "equipmentIds": ["Machine A"], + "shiftsContained": ["Shift.Monday.Morning"], + "from": "2024-09-02T06:00:00Z", + "to": "2024-09-02T14:00:00Z", + "error": null, + "value": 27000, + "units": "seconds" + }, + { + "name": "ActualProductionTime", + "equipmentIds": ["Machine B"], + "shiftsContained": ["Shift.Monday.Morning"], + "from": "2024-09-02T06:00:00Z", + "to": "2024-09-02T14:00:00Z", + "error": null, + "value": 27000, + "units": "seconds" + } + ] + } +} +``` + +{{% /tab %}} +{{< /tabs >}} + +- **`groupByEquipment = true and groupByShift = false` -** returns a separate result per shift instance containing all equipment + +{{< tabs items="Query,Response" >}} +{{% tab "query" %}} +query: + +```graphql +query GetKPIByShift($filterInput: GetKPIByShiftFilter!, $startDateTime: DateTime!, $endDateTime: DateTime!, $kpi: [KPI!], $ignorePlannedDownTime: Boolean, $ignorePlannedShutdownTime: Boolean, $groupByShift: Boolean, $groupByEquipment: Boolean, $onlyIncludeActiveJobResponses: Boolean) { + GetKPIByShift(filterInput: $filterInput, startDateTime: $startDateTime, endDateTime: $endDateTime, kpi: $kpi, ignorePlannedDownTime: $ignorePlannedDownTime, ignorePlannedShutdownTime: $ignorePlannedShutdownTime, groupByShift: $groupByShift, groupByEquipment: $groupByEquipment, OnlyIncludeActiveJobResponses: $onlyIncludeActiveJobResponses) { + name + equipmentIds + shiftsContained + from + to + error + value + units + } +} +``` + +input: + +```json +{ + "filterInput": { + "shiftFilter": [ + { + "propertyName": "Shift Name", + "eq": "Morning" + } + ], + "equipmentIds": ["Machine A", "Machine B"], + }, + "startDateTime": "2024-09-01T00:00:00Z", + "endDateTime": "2024-09-03T18:00:00Z", + "kpi": ["ActualProductionTime"], + "ignorePlannedDownTime": false, + "ignorePlannedShutdownTime": false, + "onlyIncludeActiveJobResponses": false, + "groupByShift": false, + "groupByEquipment": true +} +``` + +{{% /tab %}} +{{% tab "response" %}} + +```json +{ + "data": { + "GetKPIByShift": [ + { + "name": "ActualProductionTime", + "equipmentIds": ["Machine A", "Machine B"], + "shiftsContained": ["Shift.Sunday.Morning"], + "from": "2024-09-01T06:00:00Z", + "to": "2024-09-01T14:00:00Z", + "error": null, + "value": 54000, + "units": "seconds" + }, + { + "name": "ActualProductionTime", + "equipmentIds": ["Machine A", "Machine B"], + "shiftsContained": ["Shift.Monday.Morning"], + "from": "2024-09-02T06:00:00Z", + "to": "2024-09-02T14:00:00Z", + "error": null, + "value": 54000, + "units": "seconds" + } + + ] + } +} +``` + +{{% /tab %}} +{{< /tabs >}} + +- **groupByEquipment = true and groupByShift = true -** groups shifts and equipment together + +{{< tabs items="Query,Response" >}} +{{% tab "query" %}} +query: + +```graphql +query GetKPIByShift($filterInput: GetKPIByShiftFilter!, $startDateTime: DateTime!, $endDateTime: DateTime!, $kpi: [KPI!], $ignorePlannedDownTime: Boolean, $ignorePlannedShutdownTime: Boolean, $groupByShift: Boolean, $groupByEquipment: Boolean, $onlyIncludeActiveJobResponses: Boolean) { + GetKPIByShift(filterInput: $filterInput, startDateTime: $startDateTime, endDateTime: $endDateTime, kpi: $kpi, ignorePlannedDownTime: $ignorePlannedDownTime, ignorePlannedShutdownTime: $ignorePlannedShutdownTime, groupByShift: $groupByShift, groupByEquipment: $groupByEquipment, OnlyIncludeActiveJobResponses: $onlyIncludeActiveJobResponses) { + name + equipmentIds + shiftsContained + from + to + error + value + units + } +} +``` + +input: + +```json +{ + "filterInput": { + "shiftFilter": [ + { + "propertyName": "Shift Name", + "eq": "Morning" + } + ], + "equipmentIds": ["Machine A", "Machine B"], + }, + "startDateTime": "2024-09-01T00:00:00Z", + "endDateTime": "2024-09-03T18:00:00Z", + "kpi": ["ActualProductionTime"], + "ignorePlannedDownTime": false, + "ignorePlannedShutdownTime": false, + "onlyIncludeActiveJobResponses": false, + "groupByShift": true, + "groupByEquipment": true +} +``` + +{{% /tab %}} +{{% tab "response" %}} + +```json +{ + "data": { + "GetKPIByShift": [ + { + "name": "ActualProductionTime", + "equipmentIds": ["Machine A", "Machine B"], + "shiftsContained": ["Shift.Sunday.Morning","Shift.Monday.Morning"], + "from": "2024-09-01T06:00:00Z", + "to": "2024-09-01T14:00:00Z", + "error": null, + "value": 108000, + "units": "seconds" + } + + ] + } +} +``` + +{{% /tab %}} +{{< /tabs >}} + +## Federated Queries + +The KPI service extends the equipment, work schedule, work request, job order, and job response GraphQL entities with a KPI object. +This makes KPIs easier to query. + +### Query Equipment + +Extending the equipment type allows the equipment ID to be inferred from parent equipment type + +{{< tabs items="Query,Response" >}} +{{% tab "query" %}} +query: + +```graphql +query QueryEquipment($startDateTime: DateTime!, $endDateTime: DateTime!, $kpi: [KPI!], $ignorePlannedDownTime: Boolean, $ignorePlannedShutdownTime: Boolean) { + queryEquipment { + id + kpi(startDateTime: $startDateTime, endDateTime: $endDateTime, kpi: $kpi, ignorePlannedDownTime: $ignorePlannedDownTime, ignorePlannedShutdownTime: $ignorePlannedShutdownTime) { + name + from + to + error + value + units + } + } +} +``` + +input: + +```json +{ + "startDateTime": "2024-09-01T06:00:00Z", + "endDateTime": "2024-09-01T14:00:00Z", + "kpi": ["ActualProductionTime"], + "ignorePlannedDownTime": false, + "ignorePlannedShutdownTime": false +} +``` + +{{% /tab %}} +{{% tab "response" %}} + +```json +{ + "data": { + "queryEquipment": [ + { + "id": "Machine A", + "kpi": [ + { + "name": "ActualProductionTime", + "from": "2024-09-01T06:00:00Z", + "to": "2024-09-01T14:00:00Z", + "error": null, + "value": 27000, + "units": "seconds" + } + ] + }, + { + "id": "Machine B", + "kpi": [ + { + "name": "ActualProductionTime", + "from": "2024-09-01T06:00:00Z", + "to": "2024-09-01T14:00:00Z", + "error": null, + "value": 27000, + "units": "seconds" + } + ] + } + ] + } +} +``` + +{{% /tab %}} +{{< /tabs >}} + +### Query JobResponse + +Extending the job response type allows: + +- `startDateTime` to be inferred from `jobResponse.startDateTime` +- `endDateTime` to be inferred from `jobResponse.endDateTime` +- `equipmentIds` to be inferred from `jobResponse.equipmentActual.EquipmentVersion.id` + +{{< tabs items="Query,Response" >}} +{{% tab "query" %}} +query: + +```graphql +query QueryJobResponse($kpi: [KPI!], $ignorePlannedDownTime: Boolean, $ignorePlannedShutdownTime: Boolean, $filter: KPIFilter) { + queryJobResponse { + id + startDateTime + endDateTime + equipmentActual { + id + equipmentVersion { + id + } + } + kpi(kpi: $kpi, ignorePlannedDownTime: $ignorePlannedDownTime, ignorePlannedShutdownTime: $ignorePlannedShutdownTime, filter: $filter) { + name + from + to + error + value + units + } + } +} +``` + +input: + +```json +{ + "kpi": [ + "ActualProductionTime" + ], + "ignorePlannedDownTime": false, + "ignorePlannedShutdownTime": false +} +``` + +{{% /tab %}} +{{% tab "response" %}} + +```json +{ + "data": { + "queryJobResponse": [ + { + "id": "Order A1.JobResponse 1", + "startDateTime": "2024-09-01T08:00:00Z", + "endDateTime": "2024-09-01T14:00:00Z", + "equipmentActual": [ + { + "id": "Machine A.2024-09-01T08:00:00Z", + "equipmentVersion": { + "id": "Machine A" + } + } + ], + "kpi": [ + { + "name": "ActualProductionTime", + "from": "2024-09-01T08:00:00Z", + "to": "2024-09-01T14:00:00Z", + "error": null, + "value": 27000, + "units": "seconds" + } + ] + } + ] + } +} +``` + +{{% /tab %}} +{{< /tabs >}} + +### Query Job Order, Work Request, and Work Schedule + +Extending the Job order, Work Request, and Work Schedule entities makes it possible to recursively query all of the attached job responses: + +```mermaid +flowchart TD + WorkSchedule --> WorkRequests + WorkRequests --> JobOrders + JobOrders --> JobResponses +``` + +Imagine that from the data from example 2 has this hierarchy: + +```mermaid +flowchart TD + WorkScheduleA --> WorkRequestA + WorkScheduleA --> WorkRequestB + WorkRequestA --> OrderA1 + WorkRequestA --> OrderA2 + WorkRequestB --> OrderB1 + WorkRequestB --> OrderC1 +``` + +Querying KPI on `workSchedule A` combines all results for order A1, A2, B1 and C1: + +{{< tabs items="Query,Response">}} +{{% tab "query" %}} +query: + +```graphql +query QueryWorkSchedule($kpi: [KPI!], $ignorePlannedDownTime: Boolean, $ignorePlannedShutdownTime: Boolean, $filter: KPIFilter) { + queryWorkSchedule { + id + kpi(kpi: $kpi, ignorePlannedDownTime: $ignorePlannedDownTime, ignorePlannedShutdownTime: $ignorePlannedShutdownTime, filter: $filter) { + name + from + to + error + value + units + } + } +} +``` + +input: + +```json +{ + "kpi": [ + "ActualProductionTime" + ], + "ignorePlannedDownTime": false, + "ignorePlannedShutdownTime": false +} +``` + +{{% /tab %}} +{{% tab "response" %}} + +```json +{ + "data": { + "queryWorkSchedule": [ + { + "id": "WorkScheduleA", + "kpi": [ + { + "name": "ActualProductionTime", + "from": "2024-09-01T08:00:00Z", + "to": "2024-09-02T06:00:00Z", + "error": null, + "value": 108000, + "units": "seconds" + } + ] + } + ] + } +} +``` + +{{% /tab %}} +{{< /tabs >}} + +## Additional Filters + +Some KPI Queries provide additional filters that are not mentioned in the preceding examples: + +- `ignorePlannedDownTime` (default: `false`) - Ignores planned down time events. For example if a state change happens while in the planned downtime calendar state, by default it is ignored. If `ignorePlannedDowntime = true`, the underlying state change is still returned. +- `ignorePlannedShutdownTime` (default: `false`). Similar to `ignorePlannedDowntime` except with planned shutdown calendar events. +- `onlyIncludeActiveJobResponses` (default: `false`) - if set to true will adjust the time interval of the KPI query to only be whilst a job response is active. For example if a user queries a KPI between 00:00 - 23:59 but there are only active job responses from 08:00-19:00, the query time range would be adjusted to 08:00-19:00. diff --git a/content/versions/v3.2.1/how-to/model/_index.md b/content/versions/v3.2.1/how-to/model/_index.md new file mode 100644 index 000000000..132c3faab --- /dev/null +++ b/content/versions/v3.2.1/how-to/model/_index.md @@ -0,0 +1,22 @@ +--- +title: 'Define production models' +description: Create models for equipment, data sources, operations definitions, work definitions, and so on. +date: '2023-09-22T14:50:39-03:00' +draft: false +weight: 300 +cascade: + icon: model-cubes +--- + +You have multiple ways to update your production models: + +- Use the UI to define it for an individual unit or class +- POST a batch over the GraphQL API +- Use BPMN as a filter receive an incoming ERP document and map into the system + +The trade offs are usually upfront configuration time, number of items to add, and level of automation. +Adding an item over the UI requires no programming skill, but you can only add only one unit at a time. +Creating a BPMN process to listen for an event and automatically map new units brings the highest automation, but it requires upfront investment to write and test the BPMN workflow. + + +{{< card-list >}} diff --git a/content/versions/v3.2.1/how-to/model/create-objects-ui.md b/content/versions/v3.2.1/how-to/model/create-objects-ui.md new file mode 100644 index 000000000..b37714d22 --- /dev/null +++ b/content/versions/v3.2.1/how-to/model/create-objects-ui.md @@ -0,0 +1,90 @@ +--- +title: 'Create objects from the UI' +date: '2023-11-20T15:36:03-03:00' +draft: false +categories: ["how-to"] +description: How to create manufacturing objects from the Rhize UI. +weight: 010 +--- + +To make a production object visible to the Rhize data hub, you must define it as a data model. +Along with its API, Rhize also has a graphical interface to create and update objects in your role-based equipment hierarchy. + +Often, one object references another: for example, a piece of equipment may belong to an equipment class, have a unit of measure as a property, and be associated with process segment. +These associations form nodes and edges in your knowledge graph, so the more information relationships that you accurately create, the better. + +## Prerequisites + +Ensure that you have the following: + +- Access to the Rhize UI +- Information about the equipment that you want to model + +## General procedure + +1. From the UI, select the menu in the top corner. +1. Select **Master Data**, then the object you want to configure. +1. **Create new** (or the plus sign). +1. Name the object according to your naming conventions. +1. Fill in the other fields. For details, refer to the [Master definitions and fields]({{< relref "master-definitions" >}}). + +You can create many different objects, all with their own parameters and associations. +For that reason, a general procedure such as the preceding lacks any interesting detail. + +To make the action more concrete, +the next section provides an example to create plausible group of objects. + +## Example: create oven class and instance + +AG holdings is a fictional enterprise that makes product called `Alleman Brownies`. +These brownies are produced in its UK site, `AG_House`, specifically in the `brownie_kitchen_1` work center of the `south_wing` area. + +The `brownie_kitchen_1` kitchen has `oven_123`, an instance of the `Oven` class. +This equipment item also has a data source that gives temperature readings, which are published to a dashboard. + +Here's how you could use the Rhize UI to model this. + +{{< callout type="info" >}} +If you are actively following to learn, make sure to use names that will easily identify the objects as example objects for testing. +{{< /callout >}} + +Model the equipment levels: + +1. From **Master Data**, select **Equipment** and enter `AG_house` as the name. +1. Give it a description. Then for **Equipment Level**, choose `Site`. +1. From the new `AG_House` object, create a sub-object with the **+** button. +1. Name the object `south_wing` and choose `Area` as its level. +1. Repeat the preceding steps to make `brownie_kitchen1` a work center in the `south_wing`. + + Once complete, the hierarchy should look like this: + + ![Screenshot of three equipment levels](/images/screenshot-rhize-equipment-levels.png) + + +Model the `Oven` equipment class: + +1. From **Master Data**, select **Equipment Class**. +1. Give it a name that makes sense for your organization. Give it a description, such as `Oven for baking`. +1. Add any additional properties. +1. **Create** . +1. Make it active by changing its version state. + +Create the associated data source: +1. From **Master Data**, select **Data Source**. +1. Add the source's connection string and protocol, along with any credentials (to configure authentication, refer to [Agent configuration]({{< relref "../../reference/service-config/agent-configuration" >}}). +1. Select the **Topics** tab and add the label and data type. +1. **Create** and make the version active. + +Now, create an instance of the Oven. + +1. From **Master Data**, select **Equipment.** Then create a sub-object for the `brownie_kitchen1` work center. +1. Add its unique, globally identifiable ID and give it a description. +1. For **Equipment Class**, add the `Oven` class you just created. +1. For **Equipment Level**, select `WorkUnit`. +1. **Create.** + + After the object is successfully created, you can add the data source. +1. From the **Data Sources** tab, select **Link Data Sources**. Select the data source you just created. + +On success, your UI should now have an item equipment that is associated with an equipment level, equipment class, and data source. +For a complete reference of all objects and properties that you can add through the UI, refer to the Master definitions and Fields]({{< relref "master-definitions" >}}). diff --git a/content/versions/v3.2.1/how-to/model/master-definitions.md b/content/versions/v3.2.1/how-to/model/master-definitions.md new file mode 100644 index 000000000..ee24f6acf --- /dev/null +++ b/content/versions/v3.2.1/how-to/model/master-definitions.md @@ -0,0 +1,345 @@ +--- +title: 'Master definitions and fields' +date: '2023-11-15T16:29:21-03:00' +draft: false +categories: ["reference"] +description: >- + A reference of all manufacturing data objects and properties that you can create in the Rhize UI +weight: 100 +--- + +To make a production object visible to the Rhize data hub, you must define it as a data model. + +These sections document all the objects that you can add through the UI, and the fields and properties that you can associate with them. +All these models are based on the ISA-95 standard, mostly from [Part 2](https://www.isa.org/products/ansi-isa-95-00-02-2018-enterprise-control-system-i), which describes the role-based equipment hierarchy. + +{{< callout type="info" >}} +- For an introduction to the language of ISA-95, read [How to speak ISA-95](/isa-95/how-to-speak-isa-95) +- For visual examples of how some of these models relate, +look at our page of [ISA-95 Diagrams]({{< relref "../../isa-95/isa-95-diagrams" >}}). +{{< /callout >}} + +## Global object fields + +All objects that you define must have a unique name. +Additionally, most objects have the following fields: + +| Global field | Description | +|--------------|----------------------------------------------------------------------------------------| +| Version | The version of the object (and each version has a [state](#version-states)) | +| Description | Freeform text to describe what the object does and help colleagues understand its role | + +### Version states + +Each version of an object can have the following states: +- `Draft` +- `Active` +- `For review` +- `Deprecated` + + +{{< callout type="info" >}} +When recording actual execution, what matters is version of the object, not its general definition. +Thus, **to add a class to an object, you must give that object a version first.** +{{< /callout >}} + + +## Common models + +_Common models_ are data objects that can apply to different resources in your manufacturing process + +### Units of Measure {#uom} + +A _Unit of measure_ is a defined unit to consistently compare values, duration or quantities. + +You can create units of measure in the UI and give them the following parameters: +- Name +- Data type + +### Data Sources + +A _data source_ is a source of real-time data that is collected by the Rhize agent. +For example, in a baking process, a data source might be an OPC UA server that sends readings from an oven thermometer. + +The general fields for a data source are as follows: + +| General fields | Description | +|--------------------------|-------------------------------------------------------------------------------------| +| Connection string | A string to specify information about the data source and the way to connect to it | +| The Data source protocol | Either `MQTT` or `OPCUA` | +| username | If needed, username for [Agent authentication]({{< relref "../../reference/service-config/agent-configuration" >}}) | +| password | If needed, password for [Agent authentication]({{< relref "../../reference/service-config/agent-configuration" >}}) | +| certificate | If needed, certificate for [Agent authentication]({{< relref "../../reference/service-config/agent-configuration" >}}) | + +Additionally, each data source can have _topics_ that Rhize should be able to subscribe to. +Each topic has the following fields: + +| Topic field | Description | +|-------------------|-------------------------------------------------------------------------------| +| Data type | The data type Rhize expects to find when it receives data from that topic | +| Deduplication key | The field that NATS uses to de-duplicate messages from multiple data sources. | +| Label | The name of the topic on the side of the data source | +| Description | A freeform text field to add context | + +Some data sources, such as OPC UA, have methods for RPC calls. + +### Hierarchy Scope + +The _hierarchy scope_ represents the scope within which data information is exchanged. +It provides a flexible way to group entities and data outside of the scope defined by the role-based equipment hierarchy. + +## Resource models + +_Resource models_ are data objects that have a specific role in your role-based equipment hierarchy. + +### Equipment class + +An _equipment class_ is a grouping of [equipment](#equipment) for a definite purpose. +For example, in a baking process, an equipment class might be the category of all ovens, with properties such as `maximum temperature` and `number of shelves`. + + + +Along with the [Global properties](#global-object-fields), an equipment class can include an indefinite number of properties with the following fields: + +| Properties | Description | +|-----------------|------------------------------------------| +| Name | Name of the property | +| Description | A freeform text to describe the property | +| Unit of measure | The property [unit of measure](#uom) | + + +### Equipment + +A piece of _equipment_ is a tool with a defined role in a [process segment](#process-segment). +For example, in a baking process, equipment might be a specific brownie oven. + +Equipment also might be part of hierarchy of levels, starting with Enterprise and ending with granular levels such as `WorkUnit`. + +Along with the following fields, you can also connect an equipment item to a [data source](#data-source), add additional properties, and toggle it to be active or inactive. + +{{% introTable.inline "equipment" %}} +{{ $term := (.Get 0) }} +{{ $vowels := slice "a" "e" "i" "o" "u" }} +Along with the [global object fields](#global-object-fields), +{{cond (in $vowels (index (split (lower $term) "") 0 )) "an" "a" }} +{{ $term }} object has the following fields: +{{% /introTable.inline %}} + +| General equipment fields | Description | +|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Equipment class | The [class of equipment](#equipment-class) that it belongs to. | +| Equipment level | Associated level for the equipment. One of: `Enterprise`, `Site`, `Area`, `ProcessCell`, `Unit`, `ProductionLine`, `WorkCell`, `ProductionUnit`, `Warehouse`, `StorageZone`, `StorageUnit`, `WorkCenter`, `WorkUnit`, `EquipmentModule`, `ControlModule`, `Other` | + +### Material Class + +A _material class_ is a group of material with a shared purpose in the manufacturing process. + +{{% introTable.inline "material-class" /%}} + +| General fields | Description | +|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Assembly type | Can be one of: