Skip to content

Commit

Permalink
[Integration][ServiceNow] - Add support for api query filtering (#1315)
Browse files Browse the repository at this point in the history
  • Loading branch information
PeyGis authored Jan 13, 2025
1 parent 068cd17 commit c04093c
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 5 deletions.
9 changes: 9 additions & 0 deletions integrations/servicenow/.port/resources/port-app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ resources:
- kind: sys_user_group
selector:
query: 'true'
apiQueryParams:
sysparmDisplayValue: 'true'
sysparmExcludeReferenceLink: 'false'
port:
entity:
mappings:
Expand All @@ -16,6 +19,9 @@ resources:
- kind: sc_catalog
selector:
query: 'true'
apiQueryParams:
sysparmDisplayValue: 'true'
sysparmExcludeReferenceLink: 'false'
port:
entity:
mappings:
Expand All @@ -30,6 +36,9 @@ resources:
- kind: incident
selector:
query: 'true'
apiQueryParams:
sysparmDisplayValue: 'true'
sysparmExcludeReferenceLink: 'false'
port:
entity:
mappings:
Expand Down
8 changes: 8 additions & 0 deletions integrations/servicenow/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

<!-- towncrier release notes start -->

## 0.1.101 (2025-01-13)


### Improvements

- Added support for fitering the data from serviceNow API using `apiQueryParams`


## 0.1.100 (2025-01-12)


Expand Down
15 changes: 13 additions & 2 deletions integrations/servicenow/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
70 changes: 70 additions & 0 deletions integrations/servicenow/integration.py
Original file line number Diff line number Diff line change
@@ -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: <col_name><operator><value>"
"<col_name>: Name of the table column to filter against"
"<operator>: =, !=, ^, ^OR, LIKE, STARTSWITH, ENDSWITH, ORDERBY<col_name>, ORDERBYDESC<col_name>"
"<value>: 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
12 changes: 10 additions & 2 deletions integrations/servicenow/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion integrations/servicenow/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "servicenow"
version = "0.1.100"
version = "0.1.101"
description = "ServiceNow Ocean Integration"
authors = ["Isaac Coffie <isaac@getport.io>"]

Expand Down

0 comments on commit c04093c

Please sign in to comment.