Skip to content

Commit

Permalink
Merge pull request #2 from PADAS/main
Browse files Browse the repository at this point in the history
Sync with template repo
  • Loading branch information
chrisdoehring authored Nov 6, 2024
2 parents b400ee4 + 85166d8 commit 7592523
Show file tree
Hide file tree
Showing 9 changed files with 515 additions and 101 deletions.
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,3 +403,57 @@ Sample configuration in Gundi:
"""
```
Notice: This can also be combined with Dynamic Schema and JSON Transformations. In that case the hex string will be parsed first, adn then the JQ filter can be applied to the extracted data.

### Custom UI for configurations (ui schema)
It's possible to customize how the forms for configurations are displayed in the Gundi portal.
To do that, use `FieldWithUIOptions` in your models. The `UIOptions` and `GlobalUISchemaOptions` will allow you to customize the appearance of the fields in the portal by setting any of the ["ui schema"](https://rjsf-team.github.io/react-jsonschema-form/docs/api-reference/uiSchema) supported options.

```python
# Example
import pydantic
from app.services.utils import FieldWithUIOptions, GlobalUISchemaOptions, UIOptions
from .core import AuthActionConfiguration, PullActionConfiguration


class AuthenticateConfig(AuthActionConfiguration):
email: str # This will be rendered with default widget and settings
password: pydantic.SecretStr = FieldWithUIOptions(
...,
format="password",
title="Password",
description="Password for the Global Forest Watch account.",
ui_options=UIOptions(
widget="password", # This will be rendered as a password input hiding the input
)
)
ui_global_options = GlobalUISchemaOptions(
order=["email", "password"], # This will set the order of the fields in the form
)


class MyPullActionConfiguration(PullActionConfiguration):
lookback_days: int = FieldWithUIOptions(
10,
le=30,
ge=1,
title="Data lookback days",
description="Number of days to look back for data.",
ui_options=UIOptions(
widget="range", # This will be rendered ad a range slider
)
)
force_fetch: bool = FieldWithUIOptions(
False,
title="Force fetch",
description="Force fetch even if in a quiet period.",
ui_options=UIOptions(
widget="radio", # This will be rendered as a radio button
)
)
ui_global_options = GlobalUISchemaOptions(
order=[
"lookback_days",
"force_fetch",
],
)
```
5 changes: 4 additions & 1 deletion app/actions/core.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import importlib
import inspect
from typing import Optional

from pydantic import BaseModel
from app.services.utils import UISchemaModelMixin


class ActionConfiguration(BaseModel):
class ActionConfiguration(UISchemaModelMixin, BaseModel):
pass


Expand Down
94 changes: 68 additions & 26 deletions app/conftest.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import asyncio
import datetime
import json

import pydantic
import pytest
from unittest.mock import MagicMock
from app import settings
from gcloud.aio import pubsub
from gundi_core.schemas.v2 import Integration, IntegrationActionConfiguration, IntegrationActionSummary
from gundi_core.schemas.v2 import Integration
from gundi_core.events import (
SystemEventBaseModel,
IntegrationActionCustomLog,
CustomActivityLog,
IntegrationActionStarted,
Expand All @@ -28,9 +26,9 @@
CustomWebhookLog,
LogLevel
)

from app.actions import PullActionConfiguration
from app.webhooks import GenericJsonTransformConfig, GenericJsonPayload, WebhookPayload
from app.services.utils import GlobalUISchemaOptions, FieldWithUIOptions, UIOptions
from app.webhooks import GenericJsonTransformConfig, GenericJsonPayload, WebhookPayload, WebhookConfiguration


class AsyncMock(MagicMock):
Expand Down Expand Up @@ -107,9 +105,9 @@ def integration_v2():
'value': 'auth'}, 'data': {'token': 'testtoken2a97022f21732461ee103a08fac8a35'}}],
'additional': {},
'default_route': {'id': '5abf3845-7c9f-478a-bc0f-b24d87038c4b', 'name': 'Gundi X Provider - Default Route'},
'status': {'id': 'mockid-b16a-4dbd-ad32-197c58aeef59', 'is_healthy': True,
'details': 'Last observation has been delivered with success.',
'observation_delivered_24hrs': 50231, 'last_observation_delivered_at': '2023-03-31T11:20:00+0200'}}
'status': 'healthy',
'status_details': '',
}
)


Expand Down Expand Up @@ -139,6 +137,14 @@ def integration_v2_with_webhook():
"allowed_devices_list": {"title": "Allowed Devices List", "type": "array", "items": {}},
"deduplication_enabled": {"title": "Deduplication Enabled", "type": "boolean"}},
"required": ["allowed_devices_list", "deduplication_enabled"]
},
"ui_schema": {
"allowed_devices_list": {
"ui:widget": "select"
},
"deduplication_enabled": {
"ui:widget": "radio"
}
}
}
},
Expand All @@ -163,13 +169,8 @@ def integration_v2_with_webhook():
},
"additional": {},
"default_route": None,
"status": {
"id": "mockid-b16a-4dbd-ad32-197c58aeef59",
"is_healthy": True,
"details": "Last observation has been delivered with success.",
"observation_delivered_24hrs": 50231,
"last_observation_delivered_at": "2023-03-31T11:20:00+0200"
}
"status": "healthy",
"status_details": "",
}
)

Expand Down Expand Up @@ -218,6 +219,17 @@ def integration_v2_with_webhook_generic():
"description": "Output type for the transformed data: 'obv' or 'event'"
}
}
},
"ui_schema": {
"jq_filter": {
"ui:widget": "textarea"
},
"json_schema": {
"ui:widget": "textarea"
},
"output_type": {
"ui:widget": "text"
}
}
}
},
Expand Down Expand Up @@ -455,13 +467,8 @@ def integration_v2_with_webhook_generic():
},
"additional": {},
"default_route": None,
"status": {
"id": "mockid-b16a-4dbd-ad32-197c58aeef59",
"is_healthy": True,
"details": "Last observation has been delivered with success.",
"observation_delivered_24hrs": 50231,
"last_observation_delivered_at": "2023-03-31T11:20:00+0200"
}
"status": "healthy",
"status_details": "",
}
)

Expand Down Expand Up @@ -898,7 +905,30 @@ def mock_publish_event(gcp_pubsub_publish_response):


class MockPullActionConfiguration(PullActionConfiguration):
lookback_days: int = 10
lookback_days: int = FieldWithUIOptions(
30,
le=30,
ge=1,
title="Data lookback days",
description="Number of days to look back for data.",
ui_options=UIOptions(
widget="range",
)
)
force_fetch: bool = FieldWithUIOptions(
False,
title="Force fetch",
description="Force fetch even if in a quiet period.",
ui_options=UIOptions(
widget="select",
)
)
ui_global_options = GlobalUISchemaOptions(
order=[
"lookback_days",
"force_fetch",
],
)


@pytest.fixture
Expand Down Expand Up @@ -1172,9 +1202,21 @@ class MockWebhookPayloadModel(WebhookPayload):
lon: float


class MockWebhookConfigModel(pydantic.BaseModel):
allowed_devices_list: list
deduplication_enabled: bool
class MockWebhookConfigModel(WebhookConfiguration):
allowed_devices_list: list = FieldWithUIOptions(
...,
title="Allowed Devices List",
ui_options=UIOptions(
widget="list",
)
)
deduplication_enabled: bool = FieldWithUIOptions(
...,
title="Deduplication Enabled",
ui_options=UIOptions(
widget="radio",
)
)


@pytest.fixture
Expand Down
5 changes: 3 additions & 2 deletions app/services/self_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@


async def register_integration_in_gundi(gundi_client, type_slug=None, service_url=None):
#from ..webhooks.configurations import LiquidTechPayload
#print(GenericJsonTransformConfig.schema_json())
# Prepare the integration name and value
integration_type_slug = type_slug or INTEGRATION_TYPE_SLUG
if not integration_type_slug:
Expand All @@ -38,6 +36,7 @@ async def register_integration_in_gundi(gundi_client, type_slug=None, service_ur
_, config_model = handler
action_name = action_id.replace("_", " ").title()
action_schema = json.loads(config_model.schema_json())
action_ui_schema = config_model.ui_schema()
if issubclass(config_model, AuthActionConfiguration):
action_type = ActionTypeEnum.AUTHENTICATION.value
elif issubclass(config_model, PullActionConfiguration):
Expand All @@ -53,6 +52,7 @@ async def register_integration_in_gundi(gundi_client, type_slug=None, service_ur
"value": action_id,
"description": f"{integration_type_name} {action_name} action",
"schema": action_schema,
"ui_schema": action_ui_schema,
"is_periodic_action": True if issubclass(config_model, PullActionConfiguration) else False,
}
)
Expand All @@ -70,6 +70,7 @@ async def register_integration_in_gundi(gundi_client, type_slug=None, service_ur
"value": f"{integration_type_slug}_webhook",
"description": f"Webhook Integration with {integration_type_name}",
"schema": json.loads(config_model.schema_json()),
"ui_schema": config_model.ui_schema(),
}

logger.info(f"Registering '{integration_type_slug}' with actions: '{actions}'")
Expand Down
Loading

0 comments on commit 7592523

Please sign in to comment.