Skip to content

Commit 1ae92e2

Browse files
jens-kuertenJens Kürten
andauthored
improve documentation (#30)
* improve docs * fix grammar mistakes * add missing target=_blank * re-order sections * remove toc from release note page --------- Co-authored-by: Jens Kürten <[email protected]>
1 parent 85c703b commit 1ae92e2

20 files changed

+881
-81
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Functions are deeply integrated in the CIM Database Cloud Webhooks technology. T
1212

1313
Python 3.10+
1414

15-
csfunctions is build with [Pydantic 2](https://docs.pydantic.dev/latest/)
15+
csfunctions is built with [Pydantic 2](https://docs.pydantic.dev/latest/)
1616

1717
## Installation
1818
Install using pip:

docs/assets/connect_function.png

13.5 KB
Loading

docs/assets/create_codespace.png

21.8 KB
Loading
17.7 KB
Loading

docs/assets/portal-user-menu.png

9.95 KB
Loading

docs/assets/private_repo.png

17.7 KB
Loading

docs/assets/use_template.png

11.7 KB
Loading

docs/development_server.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
# Development Server
2+
13
The Functions SDK includes a development server that allows you to run your Functions in your development environment. The server reads Functions from the `environment.yaml` file and makes them available via HTTP endpoints. You can then connect these Functions to your CIM Database Cloud instance using webhooks.
24

3-
This speeds up the development of Functions, because you can instantly test your changes, without deploying them to the cloud infrastructure first.
5+
This speeds up the development of Functions because you can instantly test your changes without deploying them to the cloud infrastructure first.
46

57
## Starting the Server
68

@@ -16,7 +18,7 @@ You can set the port of the server using the `--port` flag (default is 8000), or
1618
python -m csfunctions.devserver --port 8080
1719
```
1820

19-
You can set the directory containing the `environment.yaml` file using the `--dir` flag (by default the current working directory is used) or by setting the `CON_DEV_DIR` environment variable:
21+
You can set the directory containing the `environment.yaml` file using the `--dir` flag (by default, the current working directory is used) or by setting the `CON_DEV_DIR` environment variable:
2022

2123
```bash
2224
python -m csfunctions.devserver --dir ./my_functions
@@ -32,7 +34,7 @@ python -m csfunctions.devserver --secret my_secret
3234

3335
The development server will automatically restart if you make changes to your Functions code or to the `environment.yaml` file.
3436

35-
## Exposing the server
37+
## Exposing the Server
3638

3739
To enable your CIM Database Cloud instance to send webhook requests to your Functions, you need to make the server accessible from the internet. Here are several ways to do this:
3840

@@ -46,30 +48,29 @@ You can then copy the URL of the server and use it to connect your Functions to
4648

4749
**ngrok and Cloudflare**
4850

49-
If you are developing Functions locally, you can use services like [ngrok](https://ngrok.com/) or [Cloudflare](https://cloudflare.com) to expose your server to the internet.
51+
If you are developing Functions locally, you can use services like [ngrok](https://ngrok.com/){:target="_blank"} or [Cloudflare](https://cloudflare.com){:target="_blank"} to expose your server to the internet.
5052

5153
Please refer to the documentation of the specific service for instructions on how to do this.
5254

53-
54-
## Create a webhook in CIM Database Cloud
55+
## Create a Webhook in CIM Database Cloud
5556

5657
To test your Functions locally, create a webhook in your CIM Database Cloud instance and point it to your development server.
5758

5859
The webhook URL should combine your development server URL with the Function name from your `environment.yaml` file using this format:
5960

6061
`https://<development-server-url>/<function-name>`
6162

62-
For example the `example` function would be available at:
63-
64-
```https://mycodespace-5g7grjgvrv9h4jrx-8000.app.github.dev/example```
63+
For example, the `example` function would be available at:
6564

65+
```
66+
https://mycodespace-5g7grjgvrv9h4jrx-8000.app.github.dev/example
67+
```
6668

67-
Make sure to set the webhooks event to the correct event you want to test with your Function.
69+
Make sure to set the webhook's event to the correct event you want to test with your Function.
6870

6971
For more detailed information on how to create a webhook in CIM Database Cloud, please refer to the [CIM Database Cloud documentation](https://saas-docs.contact-cloud.com/2025.7.0-en/admin/admin-contact_cloud/saas_admin/webhooks).
7072

71-
72-
## Securing the development server
73+
## Securing the Development Server
7374

7475
Since the development server is exposed to the outside world, you should secure it to prevent unauthorized access.
7576

docs/examples/enforce_field_rules.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
Functions can be used to validate user input and ensure that fields on, for example, parts or documents are filled out correctly.
2+
3+
4+
### Required field based on Part category
5+
This example shows how you can enforce that parts in the category *"Single Part"* must have a material assigned.
6+
7+
The example Function can be connected to the [PartCreateCheckEvent](../reference/events.md#partcreatecheckevent) and [PartModifyCheckEvent](../reference/events.md#partmodifycheckevent). It will return an [AbortAndShowErrorAction](../reference/actions.md#abortandshowerroraction) to abort the creation or modification of the part if the condition is not met.
8+
9+
```python
10+
from csfunctions import MetaData, Service
11+
from csfunctions.actions import AbortAndShowErrorAction
12+
from csfunctions.events import (
13+
PartCreateCheckEvent,
14+
PartModifyCheckEvent,
15+
)
16+
17+
18+
def single_part_needs_material(
19+
metadata: MetaData,
20+
event: PartCreateCheckEvent | PartModifyCheckEvent,
21+
service: Service,
22+
):
23+
"""
24+
If a part of category 'Single Part' is created or modified, a material must be assigned.
25+
This should be checked when the part is created or modified.
26+
"""
27+
28+
for part in event.data.parts:
29+
# The event contains a list of parts that are about to be created or modified
30+
if part.t_kategorie_name_en == "Single Part" and not part.material_object_id:
31+
return AbortAndShowErrorAction(
32+
message="A material must be assigned to a part of category 'Single Part'."
33+
)
34+
35+
```
36+
37+
### Require parts to be classified before release
38+
39+
Classification is a powerful tool for organizing your parts. However, even the best tool is only effective if users actually use it.
40+
With this example Function, you can require that parts are classified before they can be released.
41+
42+
This Function should be connected to the [PartReleaseCheckEvent](../reference/events.md#partreleasecheckevent) and will return an [AbortAndShowErrorAction](../reference/actions.md#abortandshowerroraction) to prevent the release if classification data is missing.
43+
44+
The example code demonstrates how to fetch classification data for parts from the [CIM Database Cloud GraphQL API](https://saas-docs.contact-cloud.com/latest-en/admin/admin-contact_cloud/saas_admin/webhooks_graphql){:target="_blank"}. The Function then checks if any classification data is present, but you can easily expand this to check for specific classes.
45+
46+
```python
47+
from csfunctions import MetaData, Service
48+
from csfunctions.actions import AbortAndShowErrorAction
49+
from csfunctions.events import (
50+
PartReleaseCheckEvent,
51+
)
52+
import requests
53+
54+
55+
def fetch_part_classification_property_codes(cdb_object_id: str, metadata: MetaData) -> list[str]:
56+
"""
57+
Returns a list of classification property codes for a given object ID.
58+
"""
59+
60+
graphql_url = str(metadata.db_service_url).rstrip("/") + "/graphql/v1"
61+
query = f"""{{
62+
object_property_values(ref_object_id: "{cdb_object_id}") {{
63+
property_code
64+
}}
65+
}}
66+
"""
67+
response = requests.post(
68+
graphql_url,
69+
headers={"Authorization": f"Bearer {metadata.service_token}"},
70+
json={"query": query},
71+
)
72+
response.raise_for_status()
73+
data = response.json()
74+
return [
75+
item["property_code"]
76+
for item in data["data"]["object_property_values"]
77+
]
78+
79+
80+
def parts_need_classification(
81+
metadata: MetaData,
82+
event: PartReleaseCheckEvent,
83+
service: Service,
84+
):
85+
"""
86+
Parts must be classified before they can be released.
87+
"""
88+
89+
for part in event.data.parts:
90+
# The event contains a list of parts that are about to be released
91+
# For each part, fetch the classification property codes and check if they are empty
92+
property_codes = fetch_part_classification_property_codes(part.cdb_object_id, metadata)
93+
if not property_codes:
94+
return AbortAndShowErrorAction(
95+
message=f"The part '{part.eng_benennung or part.benennung}' is missing classification data."
96+
)
97+
98+
```

docs/examples/field_calculation.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Field calculation
2+
3+
The datasheet editor in CIM Database Cloud already allows you to define some basic [field calculations](https://saas-docs.contact-cloud.com/2025.13.1-en/admin/admin-contact_cloud/saas_admin/app_setup_data_edit_field_calc){:target="_blank"} to fill out fields automatically.
4+
5+
However, the Python expressions available in the datasheet editor are limited. Functions allow for much more flexibility in defining your field calculations, enabling you to do things like *fetching external data* or *referencing other objects*.
6+
7+
Field calculations with Functions utilize the `<Object>FieldCalculationEvent`, e.g. [PartFieldCalculationEvent](../reference/events.md#partfieldcalculationevent), which expects the response to contain a `DataResponse` with a dictionary of the fields that should be updated.
8+
9+
```python
10+
return DataResponse(data={"somefield": "new value"})
11+
```
12+
13+
14+
## Custom part number for external parts
15+
16+
This example shows you the basics of calculating fields with Functions and how to use the `service` parameter to generate a new number.
17+
18+
The example Function checks if the part is an *"External"* part and generates a custom part number for it.
19+
20+
```python
21+
from csfunctions import DataResponse
22+
from csfunctions.events import PartFieldCalculationEvent
23+
from csfunctions.metadata import MetaData
24+
from csfunctions.service import Service
25+
26+
def calculate_part_number(metadata: MetaData, event: PartFieldCalculationEvent, service: Service):
27+
"""
28+
Example Function.
29+
This function is triggered when a part field should be calculated.
30+
For "External" parts, we want to set the part number as "E-000123".
31+
All other parts should keep the standard part number.
32+
"""
33+
if event.data.action != "create":
34+
# Part number can only be set when the part is created
35+
return
36+
37+
# Match "External Single Part" or "External Assembly"
38+
if event.data.part.t_kategorie_name_en.startswith("External"):
39+
# Generate a new number using the service
40+
new_number = service.generator.get_number("external_part_number")
41+
# new_number is an integer, so we need to convert it to a string
42+
# and pad it with leading zeros to 6 digits
43+
new_part_number = str(new_number).zfill(6)
44+
# Add the prefix "E-" to the number
45+
new_part_number = "E-" + new_part_number
46+
# Return the new part number (teilenummer)
47+
return DataResponse(data={"teilenummer": new_part_number})
48+
```
49+
50+
!!! tip
51+
You can check `event.data.action` to decide for which operations (*copy*, *create*, *index*, and *modify*) you want your field calculation to return a new value.
52+
Some fields, like part number (*teilenummer*), can only be set during the initial creation.
53+
54+
## Translate a field with DeepL
55+
56+
Inside Functions, you can fetch data from external systems and fill out fields based on that data. This is something that would not be possible with the field calculations in the datasheet editor. For example, you could use this to fetch new part numbers from an ERP system.
57+
58+
This example uses the API from [DeepL](https://www.deepl.com){:target="_blank"} to translate a field from German to English. The example uses the additional attributes 1 and 2 on parts, but you can of course change that to any attributes that fit your use case.
59+
60+
```python
61+
import os
62+
from csfunctions import DataResponse
63+
from csfunctions.events import PartFieldCalculationEvent
64+
import requests
65+
66+
# Set the DEEPL_API_KEY during deployment like this:
67+
# cfc env deploy <environment name> --environment-variables "DEEPL_API_KEY=<your API key>"
68+
DEEPL_API_KEY = os.getenv("DEEPL_API_KEY")
69+
70+
def part_field_calculation(metadata, event: PartFieldCalculationEvent, service):
71+
if event.data.action != "create":
72+
# Only translate on creation
73+
return
74+
75+
if event.data.part.cssaas_frame_add_attr_1:
76+
translated_text = translate_text(
77+
event.data.part.cssaas_frame_add_attr_1, "EN", "DE")
78+
return DataResponse(data={"cssaas_frame_add_attr_2": translated_text})
79+
80+
def translate_text(text, target_lang, source_lang=None):
81+
url = "https://api-free.deepl.com/v2/translate"
82+
data = {
83+
"auth_key": DEEPL_API_KEY,
84+
"text": text,
85+
"target_lang": target_lang.upper()
86+
}
87+
if source_lang:
88+
data["source_lang"] = source_lang.upper()
89+
90+
response = requests.post(url, data=data)
91+
response.raise_for_status()
92+
return response.json()["translations"][0]["text"]
93+
94+
```
95+
96+
!!! note
97+
This example requires a DeepL API key to function. Adding secrets like API keys to your code is a bad practice, which is why the example fetches the API key from an environment variable.
98+
99+
You can set environment variables during deployment of your Function to the CIM Database Cloud Functions infrastructure like this:
100+
101+
`cfc env deploy <environment name> --environment-variables "DEEPL_API_KEY=<your API key>"`

0 commit comments

Comments
 (0)