Skip to content

Conversation

jcortes
Copy link
Collaborator

@jcortes jcortes commented Oct 16, 2025

WHY

Resolves #18614

Summary by CodeRabbit

  • New Features

    • Added five new actions to manage events, guides, and experience schedules (add/remove guides, patch event details, create/update/delete schedules).
    • Added 12+ new trigger sources for workflows: experience, order, purchase, and user events with real-time and polling-based detection.
  • Chores

    • Updated platform dependency version.

@jcortes jcortes self-assigned this Oct 16, 2025
Copy link

vercel bot commented Oct 16, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
pipedream-docs Ignored Ignored Oct 16, 2025 9:44pm
pipedream-docs-redirect-do-not-edit Ignored Ignored Oct 16, 2025 9:44pm

Copy link
Contributor

coderabbitai bot commented Oct 16, 2025

Walkthrough

Adds comprehensive Xola integration with new actions for event and schedule management, polling sources tracking experience/purchase lifecycle changes, instant webhook sources for real-time events, base infrastructure for sources, API client methods, and configuration constants.

Changes

Cohort / File(s) Summary
Event & Guide Management Actions
components/xola/actions/add-event-guide/add-event-guide.mjs, components/xola/actions/remove-event-guide/remove-event-guide.mjs, components/xola/actions/patch-event/patch-event.mjs
New action modules for associating/removing guides with events and patching event fields (capacity, name, description, dates). Each exports async run methods calling corresponding app methods.
Schedule Management Actions
components/xola/actions/create-experience-schedule/create-experience-schedule.mjs, components/xola/actions/update-experience-schedule/update-experience-schedule.mjs, components/xola/actions/delete-experience-schedule/delete-experience-schedule.mjs
New CRUD action modules for experience schedules with comprehensive props including schedule type, days, times, pricing, and date ranges.
Polling Source Infrastructure
components/xola/sources/common/base.mjs
New base class providing state persistence helpers (lastCreatedAt, lastUpdatedAt, processedIds), abstract hooks for resource fetching and event processing, and polling timer setup.
Webhook Source Infrastructure
components/xola/sources/common/webhook.mjs
New base class for instant webhook sources with lifecycle hooks (activate/deactivate) to manage webhook registration, HTTP endpoint handling, and event emission.
Experience Lifecycle Polling Sources
components/xola/sources/experience-created/experience-created.mjs, components/xola/sources/experience-updated/experience-updated.mjs, components/xola/sources/experience-deleted/experience-deleted.mjs
Polling sources tracking created/updated/deleted experiences by filtering API results against last-known state and emitting events with metadata.
Experience & Order Instant Webhook Sources
components/xola/sources/new-experience-created-instant/new-experience-created-instant.mjs, components/xola/sources/experience-updated-instant/experience-updated-instant.mjs, components/xola/sources/experience-deleted-instant/experience-deleted-instant.mjs, components/xola/sources/new-order-created-instant/new-order-created-instant.mjs, components/xola/sources/order-updated-instant/order-updated-instant.mjs, components/xola/sources/order-deleted-instant/order-deleted-instant.mjs
Instant webhook sources extending webhook base, implementing event name resolution and metadata generation (id, summary, timestamp).
Purchase Lifecycle Polling Sources
components/xola/sources/new-purchase-created/new-purchase-created.mjs, components/xola/sources/purchase-updated/purchase-updated.mjs, components/xola/sources/purchase-canceled/purchase-canceled.mjs
Polling sources tracking purchase creation, updates, and cancellations with state tracking and deduplication via processed ID tracking.
User Instant Webhook Source
components/xola/sources/user-updated-instant/user-updated-instant.mjs
Instant webhook source for user updates, extracting user identifiers and changes via audit trail.
Test Event Fixtures
components/xola/sources/\*/test-event.mjs
Static test event objects representing sample payloads for each source type (experiences, orders, purchases, users) with typical field values.
Configuration & Core App
components/xola/common/constants.mjs
New constants module exporting API version path ("/api"), API date version ("2025-07-07"), and pagination defaults (20, 100).
Core App Integration
components/xola/xola.app.mjs
Comprehensive overhaul adding HTTP abstraction layer (getUrl, getHeaders, makeRequest), HTTP verb wrappers (post, put, patch, delete), resource list methods (listExperiences, listGuides, listEvents, listPurchases), action helpers (createExperienceSchedule, updateExperienceSchedule, deleteExperienceSchedule, patchEvent, addEventGuide, removeEventGuide, createWebhook, deleteWebhook), and propDefinitions for dynamic ID selection.
Package Dependencies
components/xola/package.json
Updated version from 0.0.1 to 0.1.0 and added @pipedream/platform dependency (^3.1.0).

Sequence Diagram

sequenceDiagram
    participant User
    participant Action/Source
    participant App
    participant API[Xola API]
    
    rect rgb(230, 245, 255)
    note over Action/Source,API: Action Flow
    User->>Action/Source: Trigger action
    Action/Source->>App: Call action method (e.g., addEventGuide)
    App->>App: Build URL via getUrl()
    App->>App: Assemble headers via getHeaders()
    App->>API: POST/PATCH/DELETE via makeRequest()
    API-->>App: Response
    App-->>Action/Source: API response
    Action/Source->>Action/Source: Export summary
    Action/Source-->>User: Return result
    end
    
    rect rgb(240, 250, 240)
    note over Action/Source,API: Polling Source Flow
    Action/Source->>Action/Source: Timer triggered
    Action/Source->>App: Call listExperiences()
    App->>API: GET with limit/skip params
    API-->>App: Paginated results
    App-->>Action/Source: Experience list
    Action/Source->>Action/Source: Read lastCreatedAt from db
    Action/Source->>Action/Source: Filter by createdAt > lastCreatedAt
    Action/Source->>Action/Source: For each new: generateMeta(), emit event
    Action/Source->>Action/Source: Store updated lastCreatedAt in db
    Action/Source-->>User: Emitted events
    end
    
    rect rgb(255, 250, 240)
    note over Action/Source,API: Webhook Source Flow
    API->>Action/Source: POST /webhook event
    Action/Source->>Action/Source: Respond 200
    Action/Source->>Action/Source: Extract event.data
    Action/Source->>Action/Source: generateMeta() from payload
    Action/Source->>Action/Source: Emit with body + meta
    Action/Source-->>User: Event emitted
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Rationale: While individual files follow repeating patterns (16+ similar source modules with consistent structure), the PR spans 40+ files across diverse concerns (polling/webhook infrastructure, multiple action types, app authentication redesign) with varying complexity. The core app.mjs requires careful review for HTTP abstraction correctness and API method implementations. Source implementations are pattern-consistent but require spot-checking state management logic (lastCreatedAt tracking, deduplication), webhook lifecycle management, and filtering rules. High file count with moderate pattern homogeneity elevates scope; logic density per file remains moderate.

Poem

🐰 Hoppity-hops! We've woven the threads,
Xola now flows through polls and webhooks spread,
Schedules bloom, guides dance, events take their flight,
From purchase to user, each update shines bright!
Thump-thump goes the heart of automation's delight! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning The PR implements most requirements from issue #18614, including all purchase sources (create, update, cancel) [#18614], all experience/product sources (create, update, delete) [#18614], schedule actions (create, update, delete) [#18614], and guide assignment/unassignment actions [#18614]. However, two explicit requirements appear to be missing: a "User/traveler create" source (only user-updated-instant is implemented, not user-created) [#18614], and an "Update product price" action (the patch-event action updates event capacity and other fields but does not include price) [#18614]. Verify that the "User/traveler create" source and "Update product price" action are supported by the Xola API before concluding they are missing. If the Xola API provides these capabilities, implement the missing user-created source and add a price field to the product/experience update action. If the API does not support these operations, update the linked issue to clarify the actual scope of implementable features.
Description Check ⚠️ Warning The PR description is largely incomplete. While the required "WHY" section heading is present, the content only references the issue number "Resolves #18614" without elaborating on the motivation, purpose, or scope of the changes. The description does not meaningfully explain why these Xola components are being added or what problems they solve, falling short of what would be expected for a complete PR description.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "[Components] Xola - new components" directly and accurately summarizes the main change in the changeset. The title clearly conveys that new Xola components are being introduced, is concise, and provides sufficient context for understanding the primary modification without requiring examination of the full changeset details.
Out of Scope Changes Check ✅ Passed All changes in this PR are directly related to implementing the Xola integration as specified in issue #18614. The changeset includes new action modules (add-event-guide, remove-event-guide, patch-event, schedule operations), new source modules (purchase and experience event sources, user updates), a constants configuration file, the app authentication/API layer, and updated package metadata. No unrelated changes or feature creep are present.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch xola-new-components

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

♻️ Duplicate comments (1)
components/xola/sources/purchase-updated/purchase-updated.mjs (1)

17-21: Critical: Pagination not implemented — updates beyond first 100 purchases will be missed.

Identical issue to experience-updated.mjs: the hardcoded skip: 0 limits monitoring to only the first 100 purchases, causing updates beyond that threshold to be missed.

Apply the same pagination solution as suggested for experience-updated.mjs.

🧹 Nitpick comments (6)
components/xola/sources/order-updated-instant/order-updated-instant.mjs (1)

4-27: LGTM with a minor optimization suggestion.

The source implementation correctly extends the common webhook base and implements the required methods. The event name matches the test event payload, and the metadata generation follows the established pattern.

Optional micro-optimization: Date.now() is called twice (lines 20 and 22). Consider storing the timestamp in a variable:

 generateMeta(body) {
   const { data } = body;
+  const now = Date.now();
   return {
-    id: `${data.id}-${Date.now()}`,
+    id: `${data.id}-${now}`,
     summary: `Order Updated: ${data.id}`,
-    ts: Date.now(),
+    ts: now,
   };
 },
components/xola/sources/experience-deleted-instant/test-event.mjs (1)

1-8: Consider adding a deletedAt timestamp.

While the minimal payload is acceptable, including a deletedAt field would make the deletion event more informative and consistent with other timestamped events in the fixture suite.

Apply this diff to add the timestamp:

 export default {
   eventName: "experience.delete",
   data: {
     id: "507f1f77bcf86cd799439012",
     name: "City Walking Tour",
+    deletedAt: "2024-01-17T10:00:00Z",
   },
   audit: {},
 };
components/xola/sources/order-deleted-instant/order-deleted-instant.mjs (1)

17-24: Minor: Consider capturing Date.now() once.

The generateMeta method calls Date.now() twice (lines 20 and 22), which could produce slightly different timestamps, though the difference would be negligible in practice.

Apply this diff for consistency:

 generateMeta(body) {
   const { data } = body;
+  const now = Date.now();
   return {
-    id: `${data.id}-deleted-${Date.now()}`,
+    id: `${data.id}-deleted-${now}`,
     summary: `Order Deleted: ${data.id}`,
-    ts: Date.now(),
+    ts: now,
   };
 },
components/xola/actions/create-experience-schedule/create-experience-schedule.mjs (1)

153-168: Consider conditionally including optional fields in the data payload.

Unlike the update action (see components/xola/actions/update-experience-schedule/update-experience-schedule.mjs lines 98-120), this action passes all fields directly without checking if they're provided. This may send undefined or null values to the API.

Apply this pattern for optional fields:

 data: {
-  start,
-  end,
-  dates,
+  ...start && { start },
+  ...end && { end },
+  ...dates && { dates },
   type,
   name,
-  repeat,
-  days,
-  times,
-  departure,
-  priceDelta,
+  ...repeat && { repeat },
+  ...days && { days },
+  ...times && { times },
+  ...departure && { departure },
+  ...priceDelta && { priceDelta },
 },
components/xola/sources/experience-deleted/experience-deleted.mjs (1)

50-53: Hardcoded placeholder name reduces usefulness of deletion events.

The generic "Deleted Experience" name provides no context about what was deleted. While the deleted experience's actual name may not be available (since it's already deleted from the API), consider including the id in a more descriptive format.

         const deletedExperience = {
           id,
-          name: "Deleted Experience",
+          name: `Experience ID: ${id}`,
           deletedAt: new Date().toISOString(),
         };
components/xola/sources/purchase-updated/purchase-updated.mjs (1)

30-59: Consider extracting shared update-tracking logic to the base.

The processEvent() implementation is nearly identical to experience-updated.mjs (lines 30-59), differing only in:

  • Resource function name (listPurchases vs listExperiences)
  • Filtered array name (filteredPurchases vs filteredExperiences)

This duplication increases maintenance burden and risk of inconsistent bug fixes.

Extract the common pattern into the base class (components/xola/sources/common/base.mjs), parameterized by resource type. Example:

// In base.mjs
async processUpdatedResources() {
  const lastUpdatedAt = this._getLastUpdatedAt();
  let maxUpdatedAt = lastUpdatedAt;
  
  // Fetch with pagination (see earlier comment)
  const allResources = await this.fetchAllResources();
  
  const filteredResources = allResources.filter((resource) => {
    const updatedAt = resource.updatedAt;
    const createdAt = resource.createdAt;
    return updatedAt !== createdAt
      && resource.status !== "deleted"
      && (!lastUpdatedAt || new Date(updatedAt) > new Date(lastUpdatedAt));
  });
  
  filteredResources.forEach((resource) => {
    const updatedAt = resource.updatedAt;
    if (!maxUpdatedAt || new Date(updatedAt) > new Date(maxUpdatedAt)) {
      maxUpdatedAt = updatedAt;
    }
    const meta = this.generateMeta(resource);
    this.$emit(resource, meta);
  });
  
  if (maxUpdatedAt) {
    this._setLastUpdatedAt(maxUpdatedAt);
  }
}

Then each source would simply call await this.processUpdatedResources() from processEvent().

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe7f45d and ec3f3d9.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (37)
  • components/xola/actions/add-event-guide/add-event-guide.mjs (1 hunks)
  • components/xola/actions/create-experience-schedule/create-experience-schedule.mjs (1 hunks)
  • components/xola/actions/delete-experience-schedule/delete-experience-schedule.mjs (1 hunks)
  • components/xola/actions/patch-event/patch-event.mjs (1 hunks)
  • components/xola/actions/remove-event-guide/remove-event-guide.mjs (1 hunks)
  • components/xola/actions/update-experience-schedule/update-experience-schedule.mjs (1 hunks)
  • components/xola/common/constants.mjs (1 hunks)
  • components/xola/package.json (2 hunks)
  • components/xola/sources/common/base.mjs (1 hunks)
  • components/xola/sources/common/webhook.mjs (1 hunks)
  • components/xola/sources/experience-created/experience-created.mjs (1 hunks)
  • components/xola/sources/experience-created/test-event.mjs (1 hunks)
  • components/xola/sources/experience-deleted-instant/experience-deleted-instant.mjs (1 hunks)
  • components/xola/sources/experience-deleted-instant/test-event.mjs (1 hunks)
  • components/xola/sources/experience-deleted/experience-deleted.mjs (1 hunks)
  • components/xola/sources/experience-deleted/test-event.mjs (1 hunks)
  • components/xola/sources/experience-updated-instant/experience-updated-instant.mjs (1 hunks)
  • components/xola/sources/experience-updated-instant/test-event.mjs (1 hunks)
  • components/xola/sources/experience-updated/experience-updated.mjs (1 hunks)
  • components/xola/sources/experience-updated/test-event.mjs (1 hunks)
  • components/xola/sources/new-experience-created-instant/new-experience-created-instant.mjs (1 hunks)
  • components/xola/sources/new-experience-created-instant/test-event.mjs (1 hunks)
  • components/xola/sources/new-order-created-instant/new-order-created-instant.mjs (1 hunks)
  • components/xola/sources/new-order-created-instant/test-event.mjs (1 hunks)
  • components/xola/sources/new-purchase-created/new-purchase-created.mjs (1 hunks)
  • components/xola/sources/new-purchase-created/test-event.mjs (1 hunks)
  • components/xola/sources/order-deleted-instant/order-deleted-instant.mjs (1 hunks)
  • components/xola/sources/order-deleted-instant/test-event.mjs (1 hunks)
  • components/xola/sources/order-updated-instant/order-updated-instant.mjs (1 hunks)
  • components/xola/sources/order-updated-instant/test-event.mjs (1 hunks)
  • components/xola/sources/purchase-canceled/purchase-canceled.mjs (1 hunks)
  • components/xola/sources/purchase-canceled/test-event.mjs (1 hunks)
  • components/xola/sources/purchase-updated/purchase-updated.mjs (1 hunks)
  • components/xola/sources/purchase-updated/test-event.mjs (1 hunks)
  • components/xola/sources/user-updated-instant/test-event.mjs (1 hunks)
  • components/xola/sources/user-updated-instant/user-updated-instant.mjs (1 hunks)
  • components/xola/xola.app.mjs (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-07-24T02:06:47.016Z
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-07-24T02:06:47.016Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.

Applied to files:

  • components/xola/sources/common/webhook.mjs
🧬 Code graph analysis (18)
components/xola/actions/create-experience-schedule/create-experience-schedule.mjs (2)
components/xola/actions/delete-experience-schedule/delete-experience-schedule.mjs (1)
  • response (36-40)
components/xola/actions/update-experience-schedule/update-experience-schedule.mjs (1)
  • response (94-121)
components/xola/sources/order-deleted-instant/order-deleted-instant.mjs (6)
components/xola/sources/experience-deleted-instant/experience-deleted-instant.mjs (1)
  • body (18-18)
components/xola/sources/experience-updated-instant/experience-updated-instant.mjs (1)
  • body (18-18)
components/xola/sources/new-experience-created-instant/new-experience-created-instant.mjs (1)
  • body (18-18)
components/xola/sources/new-order-created-instant/new-order-created-instant.mjs (1)
  • body (18-18)
components/xola/sources/order-updated-instant/order-updated-instant.mjs (1)
  • body (18-18)
components/xola/sources/user-updated-instant/user-updated-instant.mjs (1)
  • body (18-18)
components/xola/sources/experience-created/experience-created.mjs (4)
components/xola/sources/new-purchase-created/new-purchase-created.mjs (6)
  • lastCreatedAt (31-31)
  • maxCreatedAt (32-32)
  • params (33-33)
  • createdAt (40-40)
  • createdAt (45-45)
  • meta (49-49)
components/xola/sources/experience-deleted/experience-deleted.mjs (2)
  • params (38-38)
  • meta (55-55)
components/xola/sources/experience-updated/experience-updated.mjs (4)
  • params (33-33)
  • filteredExperiences (39-45)
  • createdAt (41-41)
  • meta (52-52)
components/xola/sources/common/webhook.mjs (1)
  • meta (56-56)
components/xola/sources/order-updated-instant/order-updated-instant.mjs (6)
components/xola/sources/experience-deleted-instant/experience-deleted-instant.mjs (1)
  • body (18-18)
components/xola/sources/experience-updated-instant/experience-updated-instant.mjs (1)
  • body (18-18)
components/xola/sources/new-experience-created-instant/new-experience-created-instant.mjs (1)
  • body (18-18)
components/xola/sources/new-order-created-instant/new-order-created-instant.mjs (1)
  • body (18-18)
components/xola/sources/order-deleted-instant/order-deleted-instant.mjs (1)
  • body (18-18)
components/xola/sources/user-updated-instant/user-updated-instant.mjs (1)
  • body (18-18)
components/xola/actions/update-experience-schedule/update-experience-schedule.mjs (3)
components/xola/actions/create-experience-schedule/create-experience-schedule.mjs (1)
  • response (153-168)
components/xola/actions/delete-experience-schedule/delete-experience-schedule.mjs (1)
  • response (36-40)
components/xola/actions/patch-event/patch-event.mjs (1)
  • response (64-84)
components/xola/actions/add-event-guide/add-event-guide.mjs (1)
components/xola/actions/remove-event-guide/remove-event-guide.mjs (1)
  • response (36-40)
components/xola/sources/new-order-created-instant/new-order-created-instant.mjs (6)
components/xola/sources/experience-deleted-instant/experience-deleted-instant.mjs (1)
  • body (18-18)
components/xola/sources/experience-updated-instant/experience-updated-instant.mjs (1)
  • body (18-18)
components/xola/sources/new-experience-created-instant/new-experience-created-instant.mjs (1)
  • body (18-18)
components/xola/sources/order-deleted-instant/order-deleted-instant.mjs (1)
  • body (18-18)
components/xola/sources/order-updated-instant/order-updated-instant.mjs (1)
  • body (18-18)
components/xola/sources/user-updated-instant/user-updated-instant.mjs (1)
  • body (18-18)
components/xola/sources/experience-updated/experience-updated.mjs (2)
components/xola/sources/purchase-updated/purchase-updated.mjs (7)
  • lastUpdatedAt (31-31)
  • maxUpdatedAt (32-32)
  • params (33-33)
  • updatedAt (40-40)
  • updatedAt (48-48)
  • createdAt (41-41)
  • meta (52-52)
components/xola/sources/experience-created/experience-created.mjs (5)
  • params (33-33)
  • filteredExperiences (39-42)
  • createdAt (40-40)
  • createdAt (45-45)
  • meta (49-49)
components/xola/sources/experience-deleted/experience-deleted.mjs (6)
components/xola/sources/experience-created/experience-created.mjs (2)
  • params (33-33)
  • meta (49-49)
components/xola/sources/experience-updated/experience-updated.mjs (2)
  • params (33-33)
  • meta (52-52)
components/xola/sources/new-purchase-created/new-purchase-created.mjs (2)
  • params (33-33)
  • meta (49-49)
components/xola/sources/purchase-canceled/purchase-canceled.mjs (2)
  • params (34-34)
  • meta (46-46)
components/xola/sources/purchase-updated/purchase-updated.mjs (2)
  • params (33-33)
  • meta (52-52)
components/xola/sources/common/webhook.mjs (1)
  • meta (56-56)
components/xola/actions/patch-event/patch-event.mjs (2)
components/xola/actions/create-experience-schedule/create-experience-schedule.mjs (1)
  • response (153-168)
components/xola/actions/update-experience-schedule/update-experience-schedule.mjs (1)
  • response (94-121)
components/xola/actions/remove-event-guide/remove-event-guide.mjs (2)
components/xola/actions/add-event-guide/add-event-guide.mjs (1)
  • response (43-52)
components/xola/actions/delete-experience-schedule/delete-experience-schedule.mjs (1)
  • response (36-40)
components/xola/sources/purchase-updated/purchase-updated.mjs (2)
components/xola/sources/experience-updated/experience-updated.mjs (7)
  • lastUpdatedAt (31-31)
  • maxUpdatedAt (32-32)
  • params (33-33)
  • updatedAt (40-40)
  • updatedAt (48-48)
  • createdAt (41-41)
  • meta (52-52)
components/xola/sources/new-purchase-created/new-purchase-created.mjs (5)
  • params (33-33)
  • filteredPurchases (39-42)
  • createdAt (40-40)
  • createdAt (45-45)
  • meta (49-49)
components/xola/sources/purchase-canceled/purchase-canceled.mjs (2)
components/xola/sources/new-purchase-created/new-purchase-created.mjs (3)
  • params (33-33)
  • filteredPurchases (39-42)
  • meta (49-49)
components/xola/sources/purchase-updated/purchase-updated.mjs (3)
  • params (33-33)
  • filteredPurchases (39-45)
  • meta (52-52)
components/xola/sources/new-purchase-created/new-purchase-created.mjs (1)
components/xola/sources/experience-created/experience-created.mjs (6)
  • lastCreatedAt (31-31)
  • maxCreatedAt (32-32)
  • params (33-33)
  • createdAt (40-40)
  • createdAt (45-45)
  • meta (49-49)
components/xola/sources/common/base.mjs (4)
components/xola/sources/experience-created/experience-created.mjs (1)
  • lastCreatedAt (31-31)
components/xola/sources/new-purchase-created/new-purchase-created.mjs (1)
  • lastCreatedAt (31-31)
components/xola/sources/experience-updated/experience-updated.mjs (1)
  • lastUpdatedAt (31-31)
components/xola/sources/purchase-updated/purchase-updated.mjs (1)
  • lastUpdatedAt (31-31)
components/xola/actions/delete-experience-schedule/delete-experience-schedule.mjs (3)
components/xola/actions/create-experience-schedule/create-experience-schedule.mjs (1)
  • response (153-168)
components/xola/actions/remove-event-guide/remove-event-guide.mjs (1)
  • response (36-40)
components/xola/actions/update-experience-schedule/update-experience-schedule.mjs (1)
  • response (94-121)
components/xola/xola.app.mjs (1)
components/xola/sources/common/webhook.mjs (2)
  • userId (33-33)
  • hookId (32-32)
components/xola/sources/common/webhook.mjs (13)
components/xola/sources/experience-created/experience-created.mjs (1)
  • meta (49-49)
components/xola/sources/experience-deleted/experience-deleted.mjs (1)
  • meta (55-55)
components/xola/sources/experience-updated/experience-updated.mjs (1)
  • meta (52-52)
components/xola/sources/new-purchase-created/new-purchase-created.mjs (1)
  • meta (49-49)
components/xola/sources/purchase-canceled/purchase-canceled.mjs (1)
  • meta (46-46)
components/xola/sources/purchase-updated/purchase-updated.mjs (1)
  • meta (52-52)
components/xola/sources/experience-deleted-instant/experience-deleted-instant.mjs (1)
  • body (18-18)
components/xola/sources/experience-updated-instant/experience-updated-instant.mjs (1)
  • body (18-18)
components/xola/sources/new-experience-created-instant/new-experience-created-instant.mjs (1)
  • body (18-18)
components/xola/sources/new-order-created-instant/new-order-created-instant.mjs (1)
  • body (18-18)
components/xola/sources/order-deleted-instant/order-deleted-instant.mjs (1)
  • body (18-18)
components/xola/sources/order-updated-instant/order-updated-instant.mjs (1)
  • body (18-18)
components/xola/sources/user-updated-instant/user-updated-instant.mjs (1)
  • body (18-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: pnpm publish
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Verify TypeScript components
  • GitHub Check: Lint Code Base
🔇 Additional comments (23)
components/xola/sources/order-updated-instant/test-event.mjs (1)

1-26: LGTM!

The test event payload is well-structured with appropriate sample data for an order update webhook event. The structure includes all necessary fields (order details, experience, user info, and audit trail) in the correct format.

components/xola/sources/order-updated-instant/order-updated-instant.mjs (1)

1-2: LGTM!

Imports are correct and follow the established pattern for instant webhook sources.

components/xola/sources/new-order-created-instant/test-event.mjs (1)

1-21: LGTM!

Well-structured test fixture for order creation webhook events. The payload includes appropriate nested objects for experience and user, with realistic sample data.

components/xola/sources/new-purchase-created/test-event.mjs (1)

1-17: Clarify the distinction between "order" and "purchase".

This fixture is structurally identical to the order creation test event, but uses the "purchase" terminology. The PR includes both "new-order-created-instant" and "new-purchase-created" sources with nearly identical payloads.

Consider documenting whether these represent the same entity in Xola's API or if there's a meaningful distinction. This will help prevent confusion when users choose between these sources.

components/xola/sources/order-deleted-instant/test-event.mjs (1)

1-8: LGTM!

Appropriate minimal payload for order deletion events. The "canceled" status correctly reflects the deleted state.

components/xola/sources/experience-updated/test-event.mjs (1)

1-12: LGTM!

Clean test fixture with realistic experience data. The timestamps correctly show updatedAt after createdAt, reflecting an update scenario.

components/xola/sources/user-updated-instant/test-event.mjs (1)

1-17: LGTM!

Excellent test fixture demonstrating the audit trail functionality. The example clearly shows a phone number being added to a user profile, which is helpful for understanding the webhook payload structure.

components/xola/sources/experience-updated-instant/test-event.mjs (1)

1-29: LGTM!

Excellent comprehensive test fixture with a detailed audit trail. The payload demonstrates multiple field changes (description, duration, price) with clear before/after values, making it highly useful for testing and documentation.

components/xola/sources/new-experience-created-instant/test-event.mjs (1)

1-16: LGTM!

Properly structured creation event fixture. The empty audit object is correct since creation events have no prior state to track, and the matching createdAt/updatedAt timestamps appropriately reflect a newly created entity.

components/xola/sources/experience-created/test-event.mjs (1)

1-12: LGTM!

The test event fixture is well-structured and provides appropriate sample data for the experience-created source.

components/xola/sources/purchase-updated/test-event.mjs (1)

1-17: LGTM!

The test event fixture is well-structured with appropriate nested objects for experience and user data.

components/xola/actions/patch-event/patch-event.mjs (1)

64-84: LGTM!

The conditional spread syntax correctly handles optional fields, ensuring only provided values are sent to the API.

components/xola/sources/experience-created/experience-created.mjs (2)

30-56: LGTM with note on pagination.

The processEvent implementation correctly filters by createdAt, tracks the maximum timestamp, and persists state. The logic is sound assuming the API returns results in the expected order (newest first or all within limit).


17-22: Verify that the API returns experiences in descending order by createdAt and that 100 is sufficient for the polling interval.

The getParams method hardcodes skip: 0 with limit: 100 consistently across all polling sources (experience-created, experience-updated, experience-deleted, and all purchase sources). This pattern suggests intentional design rather than an oversight, but it relies on assumptions about API behavior:

  • The filtering logic depends on the API returning experiences sorted by recency (newest first)
  • If the API returns them in ascending order or unsorted, older experiences could be missed
  • If more than 100 experiences are created between polling intervals, any beyond position 100 won't be detected

Confirm with the Xola API documentation that /experiences returns results sorted by createdAt descending and that 100 is an appropriate limit for your polling frequency.

components/xola/actions/delete-experience-schedule/delete-experience-schedule.mjs (2)

24-28: LGTM!

The destructiveHint: true annotation correctly indicates this action permanently deletes data.


36-40: LGTM!

The delete operation is implemented correctly with appropriate error handling delegated to the app layer.

components/xola/sources/experience-deleted-instant/experience-deleted-instant.mjs (1)

1-27: LGTM!

The instant webhook source follows the established pattern correctly. The ID generation with timestamp suffix ensures uniqueness for deduplication, and the fallback from data.name to data.id in the summary handles missing names gracefully.

components/xola/sources/new-purchase-created/new-purchase-created.mjs (1)

1-59: LGTM!

The polling source implementation correctly:

  • Filters purchases by createdAt timestamp using state persistence
  • Updates maxCreatedAt while processing to track the latest item
  • Uses Date.parse() for parsing ISO date strings from the API
  • Handles the case where no new items exist (line 53 check)
components/xola/sources/new-order-created-instant/new-order-created-instant.mjs (1)

17-24: LGTM!

The ID generation using data.id directly is appropriate for a "created" event, as newly created entities have unique IDs. The dedupe: "unique" setting will handle any duplicate webhook deliveries correctly.

components/xola/actions/remove-event-guide/remove-event-guide.mjs (1)

1-45: LGTM!

The action correctly removes a guide from an event with appropriate destructive annotations. The summary message clearly indicates the operation performed.

components/xola/sources/user-updated-instant/user-updated-instant.mjs (1)

1-27: LGTM!

The instant webhook source correctly handles user update events with:

  • Timestamp-suffixed ID for deduplication across multiple updates
  • Proper fallback chain (data.name || data.email) for the summary
  • Consistent structure following the established pattern
components/xola/sources/experience-updated/experience-updated.mjs (1)

39-45: Filter logic correctly identifies genuine updates.

The conditions properly exclude:

  • New creations (updatedAt !== createdAt)
  • Soft-deleted experiences (status !== "deleted")
  • Previously processed updates (updatedAt > lastUpdatedAt)
components/xola/sources/experience-deleted/experience-deleted.mjs (1)

36-61: Snapshot comparison logic is sound for detecting deletions.

The approach of comparing previous and current ID sets correctly identifies deletions. Note that experiences created and deleted between polling intervals won't be detected, but this is documented and an acceptable limitation of the polling approach.

Comment on lines +43 to +52
const response = await app.addEventGuide({
$,
eventId,
data: {
guide: {
id: guideId,
forceConfirm,
},
},
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify the payload structure matches the API contract.

The data structure wraps the guide parameters in a nested data.guide object, which differs from the simpler structure used in the related remove-event-guide action (which passes eventId and guideId directly). Please confirm this nested structure matches the Xola API specification for assigning guides.


🏁 Script executed:

#!/bin/bash
# Description: Check how addEventGuide is implemented in xola.app.mjs to understand the expected payload structure

# Find the addEventGuide method implementation
rg -A 10 'addEventGuide' components/xola/xola.app.mjs

Length of output: 217





🏁 Script executed:

#!/bin/bash
# Check removeEventGuide implementation and compare with addEventGuide
rg -A 5 'removeEventGuide' components/xola/xola.app.mjs

# Also check the post() method to understand how it handles the data parameter
rg -A 15 'post\s*\(\s*\{' components/xola/xola.app.mjs | head -40

Length of output: 1048


🏁 Script executed:

#!/bin/bash
# Check how remove-event-guide action calls removeEventGuide for comparison
cat components/xola/actions/remove-event-guide/remove-event-guide.mjs | grep -A 10 'removeEventGuide'

Length of output: 509



---

🏁 Script executed:

```shell
#!/bin/bash
# Look for any documentation links or comments about Xola API structure
rg -i 'xola.*api|addEventGuide' components/xola/actions/add-event-guide/add-event-guide.mjs

# Check for test files that might show expected payload
fd -type f -name '*.test.*' -o -name '*.spec.*' | xargs rg 'addEventGuide' 2>/dev/null || echo "No test files found with addEventGuide"

# Look at other POST actions in xola to see if nested data structures are common
rg -B 5 'this\.post\(' components/xola/xola.app.mjs | head -60

Length of output: 676


Comment on lines +31 to +66
days: {
type: "string[]",
label: "Days",
description: "Days of week",
optional: true,
options: [
{
label: "Sunday",
value: "0",
},
{
label: "Monday",
value: "1",
},
{
label: "Tuesday",
value: "2",
},
{
label: "Wednesday",
value: "3",
},
{
label: "Thursday",
value: "4",
},
{
label: "Friday",
value: "5",
},
{
label: "Saturday",
value: "6",
},
],
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Critical: Inconsistent days representation across schedule actions.

The days prop uses string[] with values "0" (Sunday) through "6" (Saturday), but the update action at components/xola/actions/update-experience-schedule/update-experience-schedule.mjs (lines 50-54) uses integer[] with values 1 (Monday) through 7 (Sunday).

This inconsistency will break workflows where users create a schedule and then attempt to update it, as the day numbering systems are incompatible.

Choose one consistent convention:

  • Option 1: Use the create action's Sunday=0 convention (JavaScript Date.getDay() standard)
  • Option 2: Use the update action's Monday=1 convention (ISO 8601 standard)

Then update both actions to match.

🤖 Prompt for AI Agents
In
components/xola/actions/create-experience-schedule/create-experience-schedule.mjs
(lines 31-66) and
components/xola/actions/update-experience-schedule/update-experience-schedule.mjs
(around lines 50-54), the days representations are inconsistent: create uses
string[] "0"–"6" (Sunday=0) while update uses integer[] 1–7 (Monday=1). Pick one
convention (recommend Option 1: Sunday=0 as string[] to match JS Date.getDay()
or Option 2: Monday=1 as integer[] for ISO), then make both files use the exact
same type and numeric mapping: update the create action to use the chosen
numeric range and type (or update the update action to match create), adjust the
options array values and types accordingly, and ensure any input
parsing/validation and downstream code expecting days is updated to accept the
unified format.

Comment on lines +29 to +37
type: {
type: "string",
label: "Type",
description: "The type of schedule",
options: [
"available",
"unavailable",
],
optional: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Major: Inconsistent type options across schedule actions.

The type prop offers "available" and "unavailable", but the create action at components/xola/actions/create-experience-schedule/create-experience-schedule.mjs (lines 22-30) uses "available" and "blackout".

Users won't be able to update a schedule created with type "blackout" because it's not an option here.

Verify the correct options from the Xola API documentation and align both actions.

🤖 Prompt for AI Agents
In
components/xola/actions/update-experience-schedule/update-experience-schedule.mjs
around lines 29 to 37, the type options list uses "available" and "unavailable"
which is inconsistent with the create action (create-experience-schedule) that
uses "available" and "blackout"; verify the Xola API docs for the correct enum
(likely "available" and "blackout") and update this file to match the canonical
values, then update the create action if needed so both actions use the exact
same allowed type strings across both files.

Comment on lines +39 to +48
repeat: {
type: "string",
label: "Repeat",
description: "How often the schedule repeats",
options: [
"daily",
"weekly",
"monthly",
],
optional: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Major: Inconsistent repeat options across schedule actions.

The repeat prop offers "daily", "weekly", and "monthly", but the create action at components/xola/actions/create-experience-schedule/create-experience-schedule.mjs (lines 103-111) uses "weekly" and "custom".

This prevents users from updating schedules that were created with the "custom" repeat option.

Verify the correct options from the Xola API documentation and align both actions.

🤖 Prompt for AI Agents
In
components/xola/actions/update-experience-schedule/update-experience-schedule.mjs
around lines 39-48, the repeat options are inconsistent with the create action
(which uses "weekly" and "custom"), causing inability to update schedules
created with "custom"; update the repeat.options to match the create action
(include "custom" and "weekly" exactly as used in
create-experience-schedule.mjs) and verify against the Xola API docs, ensuring
both create and update actions use the same canonical set of repeat values.

Comment on lines +17 to +21
getParams() {
return {
limit: 100,
skip: 0,
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Critical: Pagination not implemented — deletions beyond first 100 experiences will never be detected.

The hardcoded skip: 0 limits tracking to only the first 100 experiences. Any experiences beyond that threshold will never appear in the currentIds or previousIds snapshots, making their deletions undetectable. This fundamentally undermines the deletion tracking mechanism.

Implement pagination following the same pattern suggested for experience-updated.mjs. The snapshot comparison approach requires tracking all experiences, not just the first page.

Comment on lines +17 to +24
generateMeta(body) {
const { data } = body;
return {
id: `${data.id}-${Date.now()}`,
summary: `Experience Updated: ${data.name}`,
ts: Date.now(),
};
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add fallback for missing experience name.

The summary uses data.name without a fallback, which could display "Experience Updated: undefined" if the name field is missing. Consider adding a fallback similar to other instant sources in this PR (e.g., experience-deleted-instant uses data.name || data.id).

Apply this diff to add a fallback:

     generateMeta(body) {
       const { data } = body;
       return {
         id: `${data.id}-${Date.now()}`,
-        summary: `Experience Updated: ${data.name}`,
+        summary: `Experience Updated: ${data.name || data.id}`,
         ts: Date.now(),
       };
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
generateMeta(body) {
const { data } = body;
return {
id: `${data.id}-${Date.now()}`,
summary: `Experience Updated: ${data.name}`,
ts: Date.now(),
};
},
generateMeta(body) {
const { data } = body;
return {
id: `${data.id}-${Date.now()}`,
summary: `Experience Updated: ${data.name || data.id}`,
ts: Date.now(),
};
},
🤖 Prompt for AI Agents
In
components/xola/sources/experience-updated-instant/experience-updated-instant.mjs
around lines 17 to 24, the generateMeta function builds the summary using
data.name which can be undefined; update the summary to use a fallback (e.g.,
data.name || data.id) so it reads `Experience Updated: ${data.name || data.id}`
to avoid "undefined" in the output and keep behavior consistent with other
instant sources.

Comment on lines +17 to +21
getParams() {
return {
limit: 100,
skip: 0,
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Critical: Pagination not implemented — updates beyond first 100 experiences will be missed.

The hardcoded skip: 0 means only the first 100 experiences are fetched on each poll. If the Xola account has more than 100 experiences, any updates to experiences beyond that limit will never be detected, leading to missed events and incomplete monitoring.

Implement pagination to fetch all experiences. Example approach:

-    getParams() {
-      return {
-        limit: 100,
-        skip: 0,
-      };
-    },
+    getParams(skip = 0) {
+      return {
+        limit: 100,
+        skip,
+      };
+    },

Then in processEvent(), add a pagination loop:

     async processEvent() {
       const lastUpdatedAt = this._getLastUpdatedAt();
       let maxUpdatedAt = lastUpdatedAt;
-      const params = this.getParams();
+      
+      let skip = 0;
+      let hasMore = true;
+      const allExperiences = [];
 
-      const { data } = await this.getResourceFn()({
-        params,
-      });
+      while (hasMore) {
+        const params = this.getParams(skip);
+        const { data } = await this.getResourceFn()({ params });
+        
+        if (data.length === 0) {
+          hasMore = false;
+        } else {
+          allExperiences.push(...data);
+          skip += data.length;
+          if (data.length < 100) hasMore = false;
+        }
+      }
 
-      const filteredExperiences = data.filter((experience) => {
+      const filteredExperiences = allExperiences.filter((experience) => {

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In components/xola/sources/experience-updated/experience-updated.mjs around
lines 17-21, the current getParams() returns a hardcoded skip: 0 causing only
the first 100 experiences to be fetched; implement pagination by making limit
configurable (keep 100) and remove the fixed skip, then update the
polling/processEvent logic to loop requests incrementing skip by limit until a
page returns fewer than limit items (or no items), aggregating results across
pages; ensure each request uses the updated params (limit and skip), respects
any API rate limits or backoff, and stops when no more pages remain so updates
beyond the first 100 experiences are not missed.

Comment on lines +17 to +24
generateMeta(body) {
const { data } = body;
return {
id: data.id,
summary: `New Experience Created: ${data.name}`,
ts: Date.now(),
};
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add fallback for missing experience name.

The summary uses data.name without a fallback. If the name field is missing or empty, the summary will display "New Experience Created: undefined". Consider adding a fallback to data.id for consistency with other instant sources.

Apply this diff to add a fallback:

     generateMeta(body) {
       const { data } = body;
       return {
         id: data.id,
-        summary: `New Experience Created: ${data.name}`,
+        summary: `New Experience Created: ${data.name || data.id}`,
         ts: Date.now(),
       };
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
generateMeta(body) {
const { data } = body;
return {
id: data.id,
summary: `New Experience Created: ${data.name}`,
ts: Date.now(),
};
},
generateMeta(body) {
const { data } = body;
return {
id: data.id,
summary: `New Experience Created: ${data.name || data.id}`,
ts: Date.now(),
};
},
🤖 Prompt for AI Agents
In
components/xola/sources/new-experience-created-instant/new-experience-created-instant.mjs
around lines 17 to 24, the summary uses data.name directly which can be
undefined; update the summary to fall back to data.id when name is missing or
empty (e.g., use data.name || data.id or a ternary to check for a non-empty
string) so the summary becomes "New Experience Created: <name or id>" and
remains consistent with other instant sources.

Comment on lines +14 to +38
getResourceFn() {
return this.app.listPurchases;
},
getParams() {
return {
limit: 100,
skip: 0,
status: "canceled",
};
},
generateMeta(purchase) {
return {
id: purchase.id,
summary: `Purchase Canceled: ${purchase.id}`,
ts: Date.parse(purchase.updatedAt),
};
},
async processEvent() {
const processedIds = this._getProcessedIds();
const newProcessedIds = [];
const params = this.getParams();

const { data } = await this.getResourceFn()({
params,
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bind the resource function before invoking

Line 15 returns this.app.listPurchases, and Line 36 calls it without binding. In modules, the function loses its this context, so inside listPurchases the call to this.makeRequest throws at runtime. Wrap or bind the method when you return it so this still points to the Xola app.

     getResourceFn() {
-      return this.app.listPurchases;
+      return (args = {}) => this.app.listPurchases(args);
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getResourceFn() {
return this.app.listPurchases;
},
getParams() {
return {
limit: 100,
skip: 0,
status: "canceled",
};
},
generateMeta(purchase) {
return {
id: purchase.id,
summary: `Purchase Canceled: ${purchase.id}`,
ts: Date.parse(purchase.updatedAt),
};
},
async processEvent() {
const processedIds = this._getProcessedIds();
const newProcessedIds = [];
const params = this.getParams();
const { data } = await this.getResourceFn()({
params,
});
getResourceFn() {
return (args = {}) => this.app.listPurchases(args);
},
getParams() {
return {
limit: 100,
skip: 0,
status: "canceled",
};
},
generateMeta(purchase) {
return {
id: purchase.id,
summary: `Purchase Canceled: ${purchase.id}`,
ts: Date.parse(purchase.updatedAt),
};
},
async processEvent() {
const processedIds = this._getProcessedIds();
const newProcessedIds = [];
const params = this.getParams();
const { data } = await this.getResourceFn()({
params,
});
🤖 Prompt for AI Agents
In components/xola/sources/purchase-canceled/purchase-canceled.mjs around lines
14 to 38, getResourceFn currently returns this.app.listPurchases which loses its
class context when later invoked; change the return to a bound function so the
Xola app remains the this inside listPurchases (e.g., return
this.app.listPurchases.bind(this.app) or return
(...args)=>this.app.listPurchases(...args)) so subsequent calls to the resource
function don’t throw when using this.makeRequest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Xola

1 participant