Skip to content
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

Sync with template repo #6

Merged
merged 41 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
311c45a
Add support for adding filenames in event attachments
vgarcia13 Jul 15, 2024
5bf2dec
add run unit test step
davidovichmarcos Jul 16, 2024
38b62ed
Merge pull request #19 from PADAS/add-support-for-filenames-in-attach…
vgarcia13 Jul 16, 2024
bce2606
adding test dockerfile
davidovichmarcos Jul 16, 2024
257112b
install pytest
davidovichmarcos Jul 16, 2024
6e33259
install base dependencies
davidovichmarcos Jul 16, 2024
2296f10
use python 3.10
davidovichmarcos Jul 17, 2024
11ab858
adding dev dependencies
davidovichmarcos Jul 17, 2024
8616e21
Remove test branch
davidovichmarcos Jul 18, 2024
d9c7b21
removing test dockerfile
davidovichmarcos Jul 18, 2024
9930d5a
fix unit test commands
davidovichmarcos Jul 18, 2024
a09485f
skip build step
davidovichmarcos Jul 18, 2024
09564d6
removing dep step too
davidovichmarcos Jul 18, 2024
ba3647b
fix
davidovichmarcos Jul 18, 2024
6ed4236
Merge pull request #20 from PADAS/gundi-3067
davidovichmarcos Jul 18, 2024
4e72fd4
execute unit test on PR
danielf-er Jul 18, 2024
f551a6c
Merge pull request #21 from PADAS/gundi-3264
daniefdz Jul 18, 2024
c714c28
Fix Dockerfile
vgarcia13 Jul 27, 2024
d4de520
Merge pull request #23 from PADAS/fix-dockerfile
vgarcia13 Jul 29, 2024
cc1d5ae
Fix missing needed step in main.yaml`
vgarcia13 Jul 29, 2024
c2e14ec
Merge pull request #24 from PADAS/fix-mail-yaml
vgarcia13 Jul 29, 2024
40b95be
Process pubsub messages in foreground by default
marianobrc Oct 15, 2024
51ca63b
Merge pull request #26 from PADAS/gundi-3498-default-settings
marianobrc Oct 15, 2024
53d707c
Support ui schema in action config models
marianobrc Oct 25, 2024
6375455
Minor improvements
marianobrc Oct 25, 2024
2a4c9a0
Fix unit tests for self-registration
marianobrc Oct 25, 2024
6bc24d6
Test coverage for ui schemas
marianobrc Oct 25, 2024
ee2d0d1
Test coverage for ui schemas
marianobrc Oct 25, 2024
913760f
Remove unecessary default properties from ui schema
marianobrc Oct 25, 2024
bd2761c
Support ui schemas in webhook configs
marianobrc Oct 25, 2024
1a4d90c
Change default widget for output type used in generic integrations
marianobrc Oct 25, 2024
1ea4aba
Add test coverage for webhooks with ui schema
marianobrc Oct 25, 2024
b6fda51
Add usage examples for ui schemas in the readme
marianobrc Oct 25, 2024
3d68ea0
Update dependencies
marianobrc Oct 29, 2024
633621e
Update dependencies
marianobrc Oct 29, 2024
4da2ad1
Merge pull request #28 from PADAS/gundi-3648-ui-schema
marianobrc Oct 29, 2024
8d9dca8
Update api mocks for integration status
marianobrc Nov 5, 2024
a33d42f
Update api mocks for integration status
marianobrc Nov 5, 2024
85166d8
Merge pull request #30 from PADAS/gundi-3248-update-gundi-core
marianobrc Nov 5, 2024
1092d5a
Merge branch 'main' of github.com:PADAS/gundi-integration-action-runn…
marianobrc Nov 6, 2024
33a8583
re-generate requirements
marianobrc Nov 6, 2024
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
23 changes: 23 additions & 0 deletions .github/workflows/_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Run tests
on:
workflow_call

jobs:
run_unit_tests:
runs-on: ubuntu-latest
steps:
- name: Checkout branch
uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install pip
run: python -m ensurepip --upgrade
- name: Install pip tools
run: pip install pip-tools
- name: Install compile dependencies
run: pip-compile --output-file=requirements.txt requirements-base.in requirements-dev.in requirements.in
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run unit tests
run: pytest
8 changes: 6 additions & 2 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,20 @@ jobs:
echo "tf_file_dev=environments/dev/integrations/${{ github.event.repository.name }}/terragrunt.hcl" >> $GITHUB_OUTPUT
echo "tf_file_stage=environments/stage/integrations/${{ github.event.repository.name }}/terragrunt.hcl" >> $GITHUB_OUTPUT
echo "tf_file_prod=environments/prod/integrations/${{ github.event.repository.name }}/terragrunt.hcl" >> $GITHUB_OUTPUT


run_unit_tests:
uses: ./.github/workflows/_tests.yml

build:
uses: PADAS/gundi-workflows/.github/workflows/build_docker.yml@v2
needs: vars
needs: [run_unit_tests, vars]
with:
repository: ${{ needs.vars.outputs.repository }}
tag: ${{ needs.vars.outputs.tag }}
workload_identity_provider: ${{ vars.GUNDI_INTEGRATIONS_WORKLOAD_IDENTITY_PROVIDER}}
service_account: ${{ vars.GUNDI_INTEGRATIONS_SERVICE_ACCOUNT }}


deploy_dev:
uses: PADAS/gundi-workflows/.github/workflows/update_hcl.yml@v2
if: startsWith(github.ref, 'refs/heads/main')
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: Test integration
on:
pull_request

jobs:
pr_unit_tests:
uses: ./.github/workflows/_tests.yml
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
7 changes: 5 additions & 2 deletions app/services/gundi.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,14 @@ async def send_events_to_gundi(events: List[dict], **kwargs) -> dict:


@stamina.retry(on=httpx.HTTPError, wait_initial=1.0, wait_jitter=5.0, wait_max=32.0)
async def send_event_attachments_to_gundi(event_id: str, attachments: List[bytes], **kwargs) -> dict:
async def send_event_attachments_to_gundi(event_id: str, attachments: List[tuple], **kwargs) -> dict:
"""
Send Event Attachments to Gundi using the REST API v2
:param event_id: Created event in which the attachments are going to be linked
:param attachments: A list of attachments (in bytes)
:param attachments: A list of attachments (tuples with filename, file in bytes). Example:
filename = 'example.png'
file_in_bytes = open(filename, 'rb')
attachments = [(filename, file_in_bytes)]
:param kwargs: integration_id: The UUID of the related integration
:return: A dict with the response from the API
"""
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
4 changes: 2 additions & 2 deletions app/services/tests/test_gundi_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ async def test_send_event_attachments_to_gundi(
mocker.patch("app.services.gundi.GundiDataSenderClient", mock_gundi_sensors_client_class)
mocker.patch("app.services.gundi._get_gundi_api_key", mock_get_gundi_api_key)
attachments = [
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00x\x00x\x00\x00\xff\xdb\x00C\x00\x02\x01\x01\x02',
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x06\x01\x01\x00x\x00x\x01\x00\xff\xd5\x00C\x00\x98\x01\x01\x56'
("file1.png", b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00x\x00x\x00\x00\xff\xdb\x00C\x00\x02\x01\x01\x02'),
("file2.png", b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x06\x01\x01\x00x\x00x\x01\x00\xff\xd5\x00C\x00\x98\x01\x01\x56')
]
response = await send_event_attachments_to_gundi(
event_id="dummy-1234",
Expand Down
Loading