feat(imis): add iMIS RiSE integration#380
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 29ac06dc94
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if inputs.get("additional_fields"): | ||
| body.update(inputs["additional_fields"]) | ||
|
|
||
| data = await api_request(context, "POST", "EventRegistration", json_data=body) |
There was a problem hiding this comment.
Post registrations to the RegisterEvent operation
When this action is used to register a contact, iMIS expects the event-registration workflow to be invoked via POST /api/EventRegistration/_execute with an EventRegistrationRequest/OperationName: RegisterEvent payload; posting only EventId and PartyId to the EventRegistration collection skips that workflow and will fail validation for normal events that require registration type, registrant/bill-to, and option-function fields.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 1fa212c. create_registration now posts to EventRegistration/_execute with OperationName: RegisterEvent and the required PersonData contract shape.
| body: Dict[str, Any] = { | ||
| "FirstName": inputs.get("first_name", ""), | ||
| "LastName": inputs["last_name"], | ||
| "PrimaryEmail": inputs.get("email", ""), | ||
| } |
There was a problem hiding this comment.
Use the iMIS PersonData shape when creating contacts
For create_contact, iMIS Party creation expects a PersonData contract with $type and nested PersonName fields rather than top-level FirstName/LastName/PrimaryEmail; with the current default payload, a normal call such as {last_name: "Smith"} does not populate the fields the Party endpoint reads and is likely to be rejected or create an incomplete contact.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 9af1399. create_contact now sends the full PersonData shape with $type and nested PersonName fields instead of top-level FirstName/LastName.
|
|
||
| data = await api_request(context, "GET", "Party", params=params) | ||
| items = data.get("Items", []) if isinstance(data, dict) else [] | ||
| count = data.get("Count", len(items)) if isinstance(data, dict) else len(items) |
There was a problem hiding this comment.
Report TotalCount for paged list actions
iMIS paged results expose Count as the number of rows in the current page and TotalCount as the full number of matches, so with limit=20 against a larger contact set this reports count: 20 even though the schema says it is the total number of matching contacts. Use TotalCount when present, and apply the same fix to the other list/query handlers that copied this expression.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 01ce5db. All list handlers now use data.get('TotalCount', data.get('Count', len(items))) so the full match count is returned, not just the current page size.
| url, | ||
| params=params, | ||
| json=json, | ||
| data=body, |
There was a problem hiding this comment.
Forward form bodies in the live fetch shim
get_access_token() calls context.fetch(..., data=body) for the form-encoded token request, but this live-test shim only forwards its body parameter to aiohttp and drops data into **kwargs. When IMIS_* credentials are present, the token request is sent without the grant payload, so every integration test fails before reaching the action under test.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 22e8cef. The real_fetch shim now accepts a data keyword argument and forwards it to aiohttp, so the form-encoded token body is sent correctly.
TheRealAgentK
left a comment
There was a problem hiding this comment.
Has open codex comments, no integration test run evidence so far,
|
|
||
| async def get_access_token(context: ExecutionContext) -> str: | ||
| creds = context.auth.get("credentials", context.auth) | ||
| site_url = creds.get("site_url", "").rstrip("/") |
There was a problem hiding this comment.
🚫 Blocker: site_url is user-provided and is used to post the iMIS username/password to /token/ after only rstrip("/"). Please validate this before any credential exchange: require an https scheme, require a hostname, and reject malformed URLs/userinfo. It would also be worth adding format: "uri" and/or an HTTPS pattern/help text in config.json so users cannot accidentally send credentials to plain HTTP or an unintended URL.
There was a problem hiding this comment.
Fixed in 3909df3. Added _validate_site_url() that requires HTTPS scheme, a valid hostname, and rejects embedded credentials. Added format: uri to config.json and updated help text.
| async def execute(self, inputs: Dict[str, Any], context: ExecutionContext): | ||
| try: | ||
| party_id = inputs["party_id"] | ||
| data = await api_request(context, "GET", f"Party/{party_id}") |
There was a problem hiding this comment.
party_id contains /, ?, etc., the workflow could hit an unintended endpoint under the same iMIS host. Please URL-encode path components with quote(str(value), safe="") before interpolation, and apply the same pattern to event, group, registration, and media-asset IDs.
There was a problem hiding this comment.
Fixed in f0d3663. Added _encode_id() using quote(str(value), safe='') and applied it to all dynamic path segments across get_contact, get_event, update_contact, update_event, get_group, remove_group_member, delete_registration, and get_media_asset.
|
|
||
| | Action | Description | Key Inputs | Key Outputs | | ||
| |--------|-------------|------------|-------------| | ||
| | `get_contact` | Get a contact by Party ID | `party_id` | `contact` | |
There was a problem hiding this comment.
list_contacts exists in both config.json and imis.py, but it is missing from this action table. Please add it so the README matches the integration surface.
There was a problem hiding this comment.
Fixed in 9d49f41. Added list_contacts row to the README action table.
| if addr.get("country"): | ||
| existing["PrimaryAddress"]["Country"] = addr["country"] | ||
| if inputs.get("additional_fields"): | ||
| existing.update(inputs["additional_fields"]) |
There was a problem hiding this comment.
additional_fields after the canonical fields lets callers override validated fields or system fields. For example, a caller can replace fields this action already controls, and the same pattern appears in create/update event, create registration, and create contact. Prefer explicit supported fields; if this escape hatch stays, merge it first and then set canonical fields, or block overrides of known/system fields.
There was a problem hiding this comment.
Fixed in 6143801. additional_fields now filters out Id and $type and is merged after canonical fields are set in create_contact, update_contact, create_event, update_event, and create_registration.
| async def test_list_media_assets_live(live_context): | ||
| result = await imis.execute_action("list_media_assets", {"limit": 5}, live_context) | ||
| assert result.type == ResultType.ACTION, result.result.message | ||
| assert isinstance(result.result.data["assets"], list) |
There was a problem hiding this comment.
create_contact, update_contact, create_event, update_event, create_registration, delete_registration, group membership changes, and tagging). Per the integration-test guidance, please add opt-in @pytest.mark.destructive coverage for at least a safe lifecycle path where cleanup is possible, or document why specific write flows cannot be safely exercised.
There was a problem hiding this comment.
Fixed in 8891178. Added three destructive test flows: create and update contact lifecycle, create and delete registration lifecycle, and add and remove group member lifecycle, all with cleanup.
34e9d72 to
9d49f41
Compare
… mocks, assertion patterns, .env.example
…between HubSpot and LinkedIn
Changed create_registration to POST to EventRegistration/_execute with the RegisterEvent OperationName and the required EventRegistrationData $type contract. Replaced PartyId with ContactId per the API spec.
iMIS requires the full PersonData shape with $type and nested PersonName object. Replaced flat FirstName/LastName fields with the correct contract.
Count returns the current page size, TotalCount returns the full result set size. Updated list_contacts, list_events, list_registrations, list_groups, list_tags, run_query, and list_media_assets.
Added _validate_site_url() that enforces HTTPS, a valid hostname, and rejects embedded credentials. Applied to both get_access_token and api_request. Added format: uri and updated help text in config.json.
Added _encode_id() helper using quote(str(value), safe='') and applied it to all dynamic URL path segments: party_id in get_contact and update_contact, event_id in get_event and update_event, group_id in get_group and remove_group_member, registration_id in delete_registration, and asset_id in get_media_asset.
additional_fields now filters out Id and \$type before merging so callers cannot overwrite system-controlled contract fields. Applied to update_contact, create_event, update_event, and create_contact.
The shim was missing the data keyword argument so the form-encoded token body was never forwarded to aiohttp, causing OAuth token requests to fail. Changed data=body to data=None and passes data=data or body to aiohttp.
Added three write-and-cleanup tests marked with @pytest.mark.destructive: contact create and update lifecycle, registration create and delete lifecycle, and group member add and remove lifecycle.
Added missing list_contacts row to imis/README.md action table. Added iMIS RiSE section to main README in alphabetical position between Instagram and Gmail.
9d49f41 to
f8a39b6
Compare
🔍 Integration Validation ResultsCommit: Changed directories:
✅ Structure Check output✅ Code Check output✅ Tests Check output✅ README Check output✅ Version Check output |
…lidation The platform handles credential presence checks itself. Custom auth fields must only define properties, not a required array.
Summary
Adds a new iMIS RiSE integration for managing contacts, events, registrations, groups, tags, media assets, and running custom queries through the iMIS RiSE OData REST API.
Actions (20)
Auth
Custom auth — users provide
site_url,username,password, andclient_id. The integration handles OAuth2 password grant token exchange internally.Validation
validate_integration.py— 0 errors, 0 warningscheck_code.py— all checks passedcheck_readme.py— passedcheck_version_bump.py— passedIMIS_*env vars)Applied skills
building-integration, writing-unit-tests, writing-integration-tests