Skip to content

improve documentation #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Functions are deeply integrated in the CIM Database Cloud Webhooks technology. T

Python 3.10+

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

## Installation
Install using pip:
Expand Down
Binary file added docs/assets/connect_function.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/create_codespace.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/display_function_credentials.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/portal-user-menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/private_repo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/use_template.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 13 additions & 12 deletions docs/development_server.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Development Server

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.

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

## Starting the Server

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

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:
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:

```bash
python -m csfunctions.devserver --dir ./my_functions
Expand All @@ -32,7 +34,7 @@ python -m csfunctions.devserver --secret my_secret

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

## Exposing the server
## Exposing the Server

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:

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

**ngrok and Cloudflare**

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.
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.

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


## Create a webhook in CIM Database Cloud
## Create a Webhook in CIM Database Cloud

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

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

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

For example the `example` function would be available at:

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

```
https://mycodespace-5g7grjgvrv9h4jrx-8000.app.github.dev/example
```

Make sure to set the webhooks event to the correct event you want to test with your Function.
Make sure to set the webhook's event to the correct event you want to test with your Function.

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).


## Securing the development server
## Securing the Development Server

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

Expand Down
98 changes: 98 additions & 0 deletions docs/examples/enforce_field_rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
Functions can be used to validate user input and ensure that fields on, for example, parts or documents are filled out correctly.


### Required field based on Part category
This example shows how you can enforce that parts in the category *"Single Part"* must have a material assigned.

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.

```python
from csfunctions import MetaData, Service
from csfunctions.actions import AbortAndShowErrorAction
from csfunctions.events import (
PartCreateCheckEvent,
PartModifyCheckEvent,
)


def single_part_needs_material(
metadata: MetaData,
event: PartCreateCheckEvent | PartModifyCheckEvent,
service: Service,
):
"""
If a part of category 'Single Part' is created or modified, a material must be assigned.
This should be checked when the part is created or modified.
"""

for part in event.data.parts:
# The event contains a list of parts that are about to be created or modified
if part.t_kategorie_name_en == "Single Part" and not part.material_object_id:
return AbortAndShowErrorAction(
message="A material must be assigned to a part of category 'Single Part'."
)

```

### Require parts to be classified before release

Classification is a powerful tool for organizing your parts. However, even the best tool is only effective if users actually use it.
With this example Function, you can require that parts are classified before they can be released.

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.

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.

```python
from csfunctions import MetaData, Service
from csfunctions.actions import AbortAndShowErrorAction
from csfunctions.events import (
PartReleaseCheckEvent,
)
import requests


def fetch_part_classification_property_codes(cdb_object_id: str, metadata: MetaData) -> list[str]:
"""
Returns a list of classification property codes for a given object ID.
"""

graphql_url = str(metadata.db_service_url).rstrip("/") + "/graphql/v1"
query = f"""{{
object_property_values(ref_object_id: "{cdb_object_id}") {{
property_code
}}
}}
"""
response = requests.post(
graphql_url,
headers={"Authorization": f"Bearer {metadata.service_token}"},
json={"query": query},
)
response.raise_for_status()
data = response.json()
return [
item["property_code"]
for item in data["data"]["object_property_values"]
]


def parts_need_classification(
metadata: MetaData,
event: PartReleaseCheckEvent,
service: Service,
):
"""
Parts must be classified before they can be released.
"""

for part in event.data.parts:
# The event contains a list of parts that are about to be released
# For each part, fetch the classification property codes and check if they are empty
property_codes = fetch_part_classification_property_codes(part.cdb_object_id, metadata)
if not property_codes:
return AbortAndShowErrorAction(
message=f"The part '{part.eng_benennung or part.benennung}' is missing classification data."
)

```
101 changes: 101 additions & 0 deletions docs/examples/field_calculation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Field calculation

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.

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*.

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.

```python
return DataResponse(data={"somefield": "new value"})
```


## Custom part number for external parts

This example shows you the basics of calculating fields with Functions and how to use the `service` parameter to generate a new number.

The example Function checks if the part is an *"External"* part and generates a custom part number for it.

```python
from csfunctions import DataResponse
from csfunctions.events import PartFieldCalculationEvent
from csfunctions.metadata import MetaData
from csfunctions.service import Service

def calculate_part_number(metadata: MetaData, event: PartFieldCalculationEvent, service: Service):
"""
Example Function.
This function is triggered when a part field should be calculated.
For "External" parts, we want to set the part number as "E-000123".
All other parts should keep the standard part number.
"""
if event.data.action != "create":
# Part number can only be set when the part is created
return

# Match "External Single Part" or "External Assembly"
if event.data.part.t_kategorie_name_en.startswith("External"):
# Generate a new number using the service
new_number = service.generator.get_number("external_part_number")
# new_number is an integer, so we need to convert it to a string
# and pad it with leading zeros to 6 digits
new_part_number = str(new_number).zfill(6)
# Add the prefix "E-" to the number
new_part_number = "E-" + new_part_number
# Return the new part number (teilenummer)
return DataResponse(data={"teilenummer": new_part_number})
```

!!! tip
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.
Some fields, like part number (*teilenummer*), can only be set during the initial creation.

## Translate a field with DeepL

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.

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.

```python
import os
from csfunctions import DataResponse
from csfunctions.events import PartFieldCalculationEvent
import requests

# Set the DEEPL_API_KEY during deployment like this:
# cfc env deploy <environment name> --environment-variables "DEEPL_API_KEY=<your API key>"
DEEPL_API_KEY = os.getenv("DEEPL_API_KEY")

def part_field_calculation(metadata, event: PartFieldCalculationEvent, service):
if event.data.action != "create":
# Only translate on creation
return

if event.data.part.cssaas_frame_add_attr_1:
translated_text = translate_text(
event.data.part.cssaas_frame_add_attr_1, "EN", "DE")
return DataResponse(data={"cssaas_frame_add_attr_2": translated_text})

def translate_text(text, target_lang, source_lang=None):
url = "https://api-free.deepl.com/v2/translate"
data = {
"auth_key": DEEPL_API_KEY,
"text": text,
"target_lang": target_lang.upper()
}
if source_lang:
data["source_lang"] = source_lang.upper()

response = requests.post(url, data=data)
response.raise_for_status()
return response.json()["translations"][0]["text"]

```

!!! note
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.

You can set environment variables during deployment of your Function to the CIM Database Cloud Functions infrastructure like this:

`cfc env deploy <environment name> --environment-variables "DEEPL_API_KEY=<your API key>"`
4 changes: 4 additions & 0 deletions docs/examples/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Examples

This section contains example Functions that you can copy and adapt to your specific use case.
Remember to [register the Function](../getting_started.md#register-the-function) in the `environment.yaml` after copying it into your code base.
72 changes: 72 additions & 0 deletions docs/examples/workflows.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Working with workflows

Functions can interact with workflows. You can trigger Functions from within workflows using the [Trigger Webhook](https://saas-docs.contact-cloud.com/latest-en/admin/admin-contact_cloud/saas_admin/webhooks_workflow){:target="_blank"} task, and you can even start new workflows by using the [StartWorkflowAction](../reference/actions.md#startworkflowaction)!


## Start a workflow on EC status change

This example shows how to start a workflow template in response to an engineering change status change.

!!! note
Starting workflows in response to engineering change status changes is already possible in CIM Database Cloud without the use of Functions. However, Functions allow you to dynamically select different templates and fill out task parameters based on the nature of the change.

This example uses a very simple template containing just an *information task*. If an engineering change contains external parts, users with the *External Part Manager* role should be notified of the planned change during the evaluation phase.

You can easily adapt this example to your use case by adding additional tasks to the template or changing the conditions under which the workflow should be started.

```python
from csfunctions.actions.start_workflow import (
StartWorkflowAction,
Subject,
TaskConfiguration,
)
from csfunctions.events import EngineeringChangeStatusChangedEvent
from csfunctions import MetaData

# Change these to match your template and roles!
TEMPLATE_ID = "PT00000002"
INFORMATION_TASK_ID = "T00000008"
INFORM_ROLE = "External Part Manager"


def start_workflow_on_ec_status_change(
metadata: MetaData, event: EngineeringChangeStatusChangedEvent, service
):
if event.data.engineering_change.status != 30:
# Only start the workflow if the status changed to 30 (Evaluation)
return

# Check if the EC contains external parts
if not any(
part.t_kategorie_name_en.startswith("External")
for part in event.data.engineering_change.planned_changes_parts
):
# No external parts, so we don't need to start the workflow
return

return StartWorkflowAction(
template_id=TEMPLATE_ID,
title=f"Information about EC {event.data.engineering_change.cdb_ec_id}",
# Attach the engineering change to the workflow
global_briefcase_object_ids=[
event.data.engineering_change.cdb_object_id],
task_configurations=[
TaskConfiguration(
task_id=INFORMATION_TASK_ID,
description="An engineering change containing external parts moved to the evaluation phase.",
recipients=[
Subject(
subject_type="Common Role",
subject_id=INFORM_ROLE,
)
],
)
],
)
```

!!! note
To successfully execute this example, you need to:

- Create a workflow template with an information task and adjust the `TEMPLATE_ID` and `INFORMATION_TASK_ID` to match them.
- Create and assign an "External Part Manager" role to a user.
Loading
Loading