Skip to content

Commit

Permalink
Feature: add rules engine (#481)
Browse files Browse the repository at this point in the history
This PR adds a mechanism to allow QGIS profile editors to condition
their deployment with a minimalist system of rules that can be edited in
the `profile.json` file.

## Featured changes

- [x] add a minimal rules engine
- [x] add a demonstration profile which is deployed only on Linux

## Example

```json
{
  "$schema": "https://raw.githubusercontent.com/Guts/qgis-deployment-cli/main/docs/schemas/profile/qgis_profile.json",
  "name": "QDT Only Linux",
  "folder_name": "qdt_only_linux",
  "description": "Demonstrating a QDT profile that's deployed only on Linux.",
  "author": "Julien Moura",
  "email": "infos+qdt@oslandia.com",
  "qgisMinimumVersion": "3.34.0",
  "qgisMaximumVersion": "3.99.10",
  "version": "1.0.0",
  "rules": [
    {
      "name": "Environment",
      "description": "Profile is configured to run only on Linux.",
      "conditions": {
        "all": [
          {
            "path": "$.environment.operating_system_code",
            "value": "linux",
            "operator": "equal"
          }
        ]
      }
    }
  ]
}

```


## Other changes 

It also initiates a change in the documentation organization to comply
with https://documentation.divio.com/.

## Related to

- #343
- #393 will add the ability to condition the deployment to Active
Directory groups
  • Loading branch information
Guts authored Apr 18, 2024
2 parents 284d624 + 9a8bb42 commit f8620f3
Show file tree
Hide file tree
Showing 13 changed files with 560 additions and 38 deletions.
9 changes: 9 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@ guides/howto_windows_sign_executable
guides/*
```
```{toctree}
---
caption: Reference
glob:
maxdepth: 1
---
reference/*
```
```{toctree}
---
caption: Miscellaneous
Expand Down
63 changes: 63 additions & 0 deletions docs/reference/qdt_profile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# QDT Profile

## Rules

You can add rules to make the profile deployment conditional. In the following example, the profile will be deployed only on Linux:

```json
{
"$schema": "https://raw.githubusercontent.com/Guts/qgis-deployment-cli/main/docs/schemas/profile/qgis_profile.json",
"name": "only_linux",
"folder_name": "qdt_only_linux",
"description": "A QGIS profile for QDT with a conditional deployment rule.",
"author": "Julien Moura",
"email": "infos+qdt@oslandia.com",
"qgisMinimumVersion": "3.34.0",
"qgisMaximumVersion": "3.99.10",
"version": "1.7.0",
"rules": [
{
"name": "Environment",
"description": "Profile is configured to run only on Linux.",
"conditions": {
"all": [
{
"path": "$.environment.operating_system_code",
"value": "linux",
"operator": "equal"
}
]
}
}
]
}
```

## Model definition

The project comes with a [JSON schema](https://raw.githubusercontent.com/Guts/qgis-deployment-cli/main/docs/schemas/profile/qgis_profile.json) describing the model of a profile:

```{eval-rst}
.. literalinclude:: ../schemas/profile/qgis_profile.json
:language: json
```

With a submodel for plugin object:

```{eval-rst}
.. literalinclude:: ../schemas/profile/qgis_plugin.json
:language: json
```

:::{tip}
To retrieve the ID of a plugin see [this page](../guides/howto_qgis_get_plugin_id.md).
:::

----

## Sample profile.json

```{eval-rst}
.. literalinclude:: ../../tests/fixtures/profiles/good_sample_profile.json
:language: json
```
8 changes: 8 additions & 0 deletions docs/schemas/profile/qgis_profile.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@
"pattern": "^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)$",
"type": "string"
},
"rules": {
"description": "Logical rules based on contextual elements that condition profile deployment.",
"title": "QGIS Plugins",
"type": "array",
"items": {
"$ref": "rules.json"
}
},
"splash": {
"description": "Relative path to the splash image.",
"type": "string"
Expand Down
203 changes: 203 additions & 0 deletions docs/schemas/profile/rules.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/Guts/qgis-deployment-cli/main/docs/schemas/profile/rules.json",
"$comment": "A set of JSON rules usable for QDT profiles to make conditional deployment.",
"type": "object",
"description": "Rules contain a set of conditions. When the engine is run, each rule condition is evaluated. If the results are truthy, the profile's is installed.",
"required": [
"conditions",
"name"
],
"properties": {
"conditions": {
"$id": "#/properties/conditions",
"type": "object",
"title": "Conditions",
"description": "Rule conditions are a combination of facts, operators, and values that determine whether the rule is a success or a failure. The simplest form of a condition consists of a fact, an operator, and a value. When the engine runs, the operator is used to compare the fact against the value. Each rule's conditions must have either an all or an any operator at its root, containing an array of conditions. The all operator specifies that all conditions contained within must be truthy for the rule to be considered a success. The any operator only requires one condition to be truthy for the rule to succeed.",
"default": {},
"examples": [
{
"all": [
{
"path": "$.environment.operating_system_code",
"operator": "equal",
"value": "linux"
}
]
}
],
"oneOf": [
{
"required": [
"any"
]
},
{
"required": [
"all"
]
}
],
"properties": {
"any": {
"$ref": "#/definitions/conditionArray"
},
"all": {
"$ref": "#/definitions/conditionArray"
}
}
},
"name": {
"$id": "#/properties/name",
"default": {},
"description": "A way of naming your rules, allowing them to be easily identifiable in Rule Results. This is usually of type String, but could also be Object, Array, or Number. Note that the name should be unique but it's not mandatory and that it has no impact on execution of the rule.",
"examples": [
"My Rule Name"
],
"title": "The Name Schema",
"type": "string"
}
},
"definitions": {
"conditionArray": {
"type": "array",
"title": "Condition Array",
"description": "An array of conditions with a possible recursive inclusion of another condition array.",
"default": [],
"items": {
"anyOf": [
{
"$ref": "#/definitions/conditions"
},
{
"$ref": "#/definitions/condition"
}
]
}
},
"condition": {
"type": "object",
"title": "Condition",
"description": "Rule conditions are a combination of facts, operators, and values that determine whether the rule is a success or a failure. The simplest form of a condition consists of a fact, an operator, and a value. When the engine runs, the operator is used to compare the fact against the value. Sometimes facts require additional input to perform calculations. For this, the params property is passed as an argument to the fact handler. params essentially functions as fact arguments, enabling fact handlers to be more generic and reusable.",
"examples": [
{
"fact": "gameDuration",
"operator": "equal",
"value": 40.0
},
{
"value": 5.0,
"fact": "personalFoulCount",
"operator": "greater_than_inclusive"
},
{
"fact": "product-price",
"operator": "greater_than",
"path": "$.price",
"value": 100.0,
"params": {
"productId": "widget"
}
}
],
"required": [
"path",
"operator",
"value"
],
"properties": {
"operator": {
"required": true,
"type": "string",
"anyOf": [
{
"const": "equal",
"title": "fact must equal value"
},
{
"const": "not_equal",
"title": "fact must not equal value"
},
{
"const": "greater_than",
"title": "fact must be greater than value"
},
{
"const": "greater_than_inclusive",
"title": "fact must be greater than or equal to value"
},
{
"const": "less_than",
"title": "fact must be less than value"
},
{
"const": "less_than_inclusive",
"title": "fact must be less than or equal to value"
},
{
"const": "in",
"title": "fact must be included in value (an array)"
},
{
"const": "not_in",
"title": "fact must not be included in value (an array)"
},
{
"const": "contains",
"title": "fact (an array) must include value"
},
{
"const": "not_contains",
"title": "fact (an array) must not include value"
}
],
"title": "Operator",
"description": "The operator compares the value returned by the fact to what is stored in the value property. If the result is truthy, the condition passes.",
"default": "",
"examples": [
"equal"
]
},
"value": {
"anyOf": [
{
"type": "string"
},
{
"type": "object"
},
{
"type": "array"
},
{
"type": "number"
},
{
"type": "boolean"
}
],
"title": "Value",
"description": "The value the fact should be compared to.",
"default": 0,
"examples": [
40
]
},
"path": {
"type": "string",
"title": "Path",
"description": "For more complex data structures, writing a separate fact handler for each object property quickly becomes verbose and unwieldy. To address this, a path property may be provided to traverse fact data using json-path syntax. Json-path support is provided by jsonpath-ng.",
"default": "",
"examples": [
"$.price"
]
},
"params": {
"type": "array",
"title": "Parameters",
"description": "A dict that can provide the operator more information about how to process the object."
}
}
}
}
}
31 changes: 0 additions & 31 deletions docs/usage/profile.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,34 +108,3 @@ profiles/*/previewImages/

- [gitignore explained on GitHub official documentation](https://docs.github.com/get-started/getting-started-with-git/ignoring-files)
- the [.gitignore file](https://github.com/Guts/qgis-deployment-cli/blob/main/examples/.gitignore) used in official examples from QDT repository

----

## Model definition

The project comes with a [JSON schema](https://raw.githubusercontent.com/Guts/qgis-deployment-cli/main/docs/schemas/profile/qgis_profile.json) describing the model of a profile:

```{eval-rst}
.. literalinclude:: ../schemas/profile/qgis_profile.json
:language: json
```

With a submodel for plugin object:

```{eval-rst}
.. literalinclude:: ../schemas/profile/qgis_plugin.json
:language: json
```

:::{tip}
To retrieve the ID of a plugin see [this page](../guides/howto_qgis_get_plugin_id.md).
:::

----

## Sample profile.json

```{eval-rst}
.. literalinclude:: ../../tests/fixtures/profiles/good_sample_profile.json
:language: json
```
9 changes: 9 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,12 @@ As QDT developer, you might want to launch QGIS with this profile to edit or che
```sh
qgis --profile "Viewer Mode" --profiles-path examples/
```

----

## Only Linux

Just an empty profile to demonstrate that you can condition the profile deployment to rules. So:

- if you are running on Linux, you should have a profile called `QDT Only Linux`
- if you are running on Windows, you should not have it!
4 changes: 4 additions & 0 deletions examples/profiles/only_linux/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# QGIS Profiles
python/plugins/
previewImages/
*.db
26 changes: 26 additions & 0 deletions examples/profiles/only_linux/profile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"$schema": "https://raw.githubusercontent.com/Guts/qgis-deployment-cli/main/docs/schemas/profile/qgis_profile.json",
"name": "QDT Only Linux",
"folder_name": "qdt_only_linux",
"description": "Demonstrating a QDT profile that's deployed only on Linux.",
"author": "Julien Moura",
"email": "infos+qdt@oslandia.com",
"qgisMinimumVersion": "3.34.0",
"qgisMaximumVersion": "3.99.10",
"version": "1.0.0",
"rules": [
{
"name": "Environment",
"description": "Profile is configured to run only on Linux.",
"conditions": {
"all": [
{
"path": "$.environment.operating_system_code",
"value": "linux",
"operator": "equal"
}
]
}
}
]
}
Loading

0 comments on commit f8620f3

Please sign in to comment.