diff --git a/integrations/servicenow/.port/resources/port-app-config.yaml b/integrations/servicenow/.port/resources/port-app-config.yaml index 635b7e69a6..43208f4fa6 100644 --- a/integrations/servicenow/.port/resources/port-app-config.yaml +++ b/integrations/servicenow/.port/resources/port-app-config.yaml @@ -2,6 +2,9 @@ resources: - kind: sys_user_group selector: query: 'true' + apiQueryParams: + sysparmDisplayValue: 'true' + sysparmExcludeReferenceLink: 'false' port: entity: mappings: @@ -16,6 +19,9 @@ resources: - kind: sc_catalog selector: query: 'true' + apiQueryParams: + sysparmDisplayValue: 'true' + sysparmExcludeReferenceLink: 'false' port: entity: mappings: @@ -30,6 +36,9 @@ resources: - kind: incident selector: query: 'true' + apiQueryParams: + sysparmDisplayValue: 'true' + sysparmExcludeReferenceLink: 'false' port: entity: mappings: diff --git a/integrations/servicenow/CHANGELOG.md b/integrations/servicenow/CHANGELOG.md index a22f944ee9..b6c4565b6c 100644 --- a/integrations/servicenow/CHANGELOG.md +++ b/integrations/servicenow/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +## 0.1.101 (2025-01-13) + + +### Improvements + +- Added support for fitering the data from serviceNow API using `apiQueryParams` + + ## 0.1.100 (2025-01-12) diff --git a/integrations/servicenow/client.py b/integrations/servicenow/client.py index 587bcb1a06..b53f5895fc 100644 --- a/integrations/servicenow/client.py +++ b/integrations/servicenow/client.py @@ -32,12 +32,23 @@ def api_auth_params(self) -> dict[str, Any]: } async def get_paginated_resource( - self, resource_kind: str + self, resource_kind: str, api_query_params: dict[str, Any] = {} ) -> AsyncGenerator[list[dict[str, Any]], None]: + + user_query = api_query_params.pop("sysparm_query", "") + default_ordering = "ORDERBYDESCsys_created_on" + enhanced_query = ( + f"{user_query}^{default_ordering}" if user_query else default_ordering + ) + params: dict[str, Any] = { "sysparm_limit": PAGE_SIZE, - "sysparm_query": "ORDERBYsys_created_on", + "sysparm_query": enhanced_query, + **api_query_params, } + logger.info( + f"Fetching Servicenow data for resource: {resource_kind} with request params: {params}" + ) url = f"{self.servicenow_url}/api/now/table/{resource_kind}" while url: diff --git a/integrations/servicenow/integration.py b/integrations/servicenow/integration.py new file mode 100644 index 0000000000..556e73ee28 --- /dev/null +++ b/integrations/servicenow/integration.py @@ -0,0 +1,70 @@ +from typing import Any, Literal +from pydantic import Field, BaseModel + +from port_ocean.core.handlers import APIPortAppConfig +from port_ocean.core.handlers.port_app_config.models import ( + ResourceConfig, + PortAppConfig, + Selector, +) +from port_ocean.core.integrations.base import BaseIntegration + + +class APIQueryParams(BaseModel): + sysparm_display_value: Literal["true", "false", "all"] | None = Field( + alias="sysparmDisplayValue", + default="true", + description="Determines the type of data returned, either the actual values from the database or the display values of the fields", + ) + sysparm_fields: str | None = Field( + alias="sysparmFields", + description="Comma-separated list of fields to return in the response", + default=None, + ) + sysparm_exclude_reference_link: Literal["true", "false"] | None = Field( + alias="sysparmExcludeReferenceLink", + default="false", + description="Flag that indicates whether to exclude Table API links for reference fields", + ) + sysparm_query: str | None = Field( + alias="sysparmQuery", + description=( + "Encoded query used to filter the result set. Syntax: " + ": Name of the table column to filter against" + ": =, !=, ^, ^OR, LIKE, STARTSWITH, ENDSWITH, ORDERBY, ORDERBYDESC" + ": Value to match against" + "Queries can be chained using ^ or ^OR for AND/OR logic. Example: active=true^nameLIKEincident^urgency=3" + ), + ) + + def generate_request_params(self) -> dict[str, Any]: + params = {} + for field, value in self.dict(exclude_none=True).items(): + params[field] = value + return params + + class Config: + allow_population_by_field_name = True # This allows fields in a model to be populated either by their alias or by their field name + + +class ResourceSelector(Selector): + api_query_params: APIQueryParams | None = Field( + alias="apiQueryParams", + default_factory=APIQueryParams, + description="The query parameters used to filter resources from the ServiceNow API", + ) + + +class ServiceNowResourceConfig(ResourceConfig): + selector: ResourceSelector + + +class ServiceNowPortAppConfig(PortAppConfig): + resources: list[ServiceNowResourceConfig | ResourceConfig] = Field( + default_factory=list + ) + + +class ServiceNowIntegration(BaseIntegration): + class AppConfigHandlerClass(APIPortAppConfig): + CONFIG_CLASS = ServiceNowPortAppConfig diff --git a/integrations/servicenow/main.py b/integrations/servicenow/main.py index f4887c8b58..0825255256 100644 --- a/integrations/servicenow/main.py +++ b/integrations/servicenow/main.py @@ -2,6 +2,9 @@ from port_ocean.context.ocean import ocean from client import ServicenowClient from port_ocean.core.ocean_types import ASYNC_GENERATOR_RESYNC_TYPE +from port_ocean.context.event import event +from integration import ServiceNowResourceConfig +from typing import cast def initialize_client() -> ServicenowClient: @@ -16,8 +19,13 @@ def initialize_client() -> ServicenowClient: async def on_resources_resync(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: logger.info(f"Listing Servicenow resource: {kind}") servicenow_client = initialize_client() - - async for records in servicenow_client.get_paginated_resource(resource_kind=kind): + api_query_params = {} + selector = cast(ServiceNowResourceConfig, event.resource_config).selector + if selector.api_query_params: + api_query_params = selector.api_query_params.generate_request_params() + async for records in servicenow_client.get_paginated_resource( + resource_kind=kind, api_query_params=api_query_params + ): logger.info(f"Received {kind} batch with {len(records)} records") yield records diff --git a/integrations/servicenow/pyproject.toml b/integrations/servicenow/pyproject.toml index ae868ac33c..6e84d3952b 100644 --- a/integrations/servicenow/pyproject.toml +++ b/integrations/servicenow/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "servicenow" -version = "0.1.100" +version = "0.1.101" description = "ServiceNow Ocean Integration" authors = ["Isaac Coffie "]