Skip to content

feat(eventsmanager): add Events Manager action integration - #201

Open
RishadAlam wants to merge 2 commits into
mainfrom
feat/eventsmanager
Open

feat(eventsmanager): add Events Manager action integration#201
RishadAlam wants to merge 2 commits into
mainfrom
feat/eventsmanager

Conversation

@RishadAlam

@RishadAlam RishadAlam commented Jul 19, 2026

Copy link
Copy Markdown
Member

Description

Adds Events Manager as an action integration with one action, Unregister User From Event, plus the React UI for configuring it. The free plugin stays hook-only: it fires bit_integrations_eventsmanager_* and the Pro plugin supplies the handler.

Pairs with Bit-Apps-Pro/bit-integrations-pro#139, which implements the handler and the ten triggers.

Motivation & Context

Events Manager was available in bit-pi but missing from the Bit Integrations free/pro pair. This ports it across.

bit-pi's getAllEvents is a fetch rather than a write, so it is not carried over as an action — per the integration scope rules, fetched data belongs in a dropdown, not an action list.

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 💥 Breaking change
  • 📚 Documentation update
  • ⚡ Improvement
  • 🔄 Code refactor

Key Changes

Backend (Actions)

  • Added EventsManagerController with the activation check (class_exists('EM_Events')) and the authorize endpoint
  • Added RecordApiHelper firing the literal bit_integrations_eventsmanager_unregister_user_from_event hook, so the wiring is greppable from either plugin
  • Added Routes.php with the authorize route
  • Registered the integration in AllTriggersName.php

Frontend

  • Added the seven-file component set under AllIntegrations/EventsManager/ (wizard, authorization, integration layout, field map, shared helpers, edit view, static data)
  • Registered the integration in NewInteg.jsx, EditInteg.jsx, IntegInfo.jsx and SelectAction.jsx, and added the logo

Design notes

  • Both of the action's inputs go through the field map rather than a dropdown. Event ID and User Email together identify the booking being cancelled, and both come from trigger data at run time — a hand-picked event would make the action usable for exactly one event. This follows the same rule as FluentCart's delete_order
  • Mapping -1 as the Event ID cancels every active booking the user holds
  • No refresh route exists, because no dropdown feeds off one

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Tests added/updated
  • Documentation updated if needed
  • README updated if needed

Testing

Verified against Events Manager 7.4.0.1 on a live install — ESLint (zero warnings), Prettier, php -l and php-cs-fixer all clean.

Changelog

  • New Actions: Events Manager — Unregister User From Event
  • Feature: Cancel a user's event registration automatically from any flow, by event or across every event they are booked into

Add Events Manager as an action integration with one action, Unregister User
From Event, plus the React UI for configuring it.

The free side stays hook-only — it fires bit_integrations_eventsmanager_* and
the Pro plugin supplies the handler.

Both inputs identify the booking being cancelled, so both go through the field
map rather than a dropdown: the event comes from trigger data at run time, and
pinning it to a hand-picked id would make the action usable for exactly one
event. Mapping -1 cancels every active booking the user holds.

bit-pi's getAllEvents is a fetch rather than a write, so it is not ported as an
action and no refresh route exists for it.
Copilot AI review requested due to automatic review settings July 19, 2026 09:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new integration for the Events Manager plugin, allowing users to unregister a user from an event. It includes backend controllers, API helpers, and routes, alongside frontend React components for authorization, field mapping, and editing the integration. Feedback focuses on ensuring that non-persisted static fields are correctly rebuilt on mount when editing the integration by utilizing a useEffect hook in EventsManagerIntegLayout.jsx. Additionally, it is recommended to use self:: instead of static:: when calling the private static method generateReqDataFromFieldMap in RecordApiHelper.php to prevent potential runtime errors.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +1 to +11
import { create } from 'mutative'
import MultiSelect from 'react-multiple-select-dropdown-lite'
import { useRecoilValue } from 'recoil'
import { $appConfigState } from '../../../GlobalStates'
import { __ } from '../../../Utils/i18nwrap'
import Note from '../../Utilities/Note'
import { checkIsPro, getProLabel } from '../../Utilities/ProUtilHelpers'
import { addFieldMap } from '../IntegrationHelpers/IntegrationHelpers'
import { generateMappedField } from './EventsManagerCommonFunc'
import EventsManagerFieldMap from './EventsManagerFieldMap'
import { modules, UnregisterUserFields } from './staticData'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

To support rebuilding the non-persisted static fields (like eventsManagerFields) on mount when editing an integration, we need to import the useEffect hook from React.

Suggested change
import { create } from 'mutative'
import MultiSelect from 'react-multiple-select-dropdown-lite'
import { useRecoilValue } from 'recoil'
import { $appConfigState } from '../../../GlobalStates'
import { __ } from '../../../Utils/i18nwrap'
import Note from '../../Utilities/Note'
import { checkIsPro, getProLabel } from '../../Utilities/ProUtilHelpers'
import { addFieldMap } from '../IntegrationHelpers/IntegrationHelpers'
import { generateMappedField } from './EventsManagerCommonFunc'
import EventsManagerFieldMap from './EventsManagerFieldMap'
import { modules, UnregisterUserFields } from './staticData'
import { useEffect } from 'react'
import { create } from 'mutative'
import MultiSelect from 'react-multiple-select-dropdown-lite'
import { useRecoilValue } from 'recoil'
import { $appConfigState } from '../../../GlobalStates'
import { __ } from '../../../Utils/i18nwrap'
import Note from '../../Utilities/Note'
import { checkIsPro, getProLabel } from '../../Utilities/ProUtilHelpers'
import { addFieldMap } from '../IntegrationHelpers/IntegrationHelpers'
import { generateMappedField } from './EventsManagerCommonFunc'
import EventsManagerFieldMap from './EventsManagerFieldMap'
import { modules, UnregisterUserFields } from './staticData'
References
  1. When editing an integration, ensure that non-persisted configuration fields (like static fields) are rebuilt on mount from the saved action without modifying the saved field mappings, to prevent empty options and component crashes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Declining, together with the paired suggestion this import is for.

eventsManagerFields is persisted with the flow — verified against a saved flow in this database, where the equivalent klaviyoFields key is stored in flow_details alongside field_map. So there's no unpopulated-on-edit case to rebuild, and adding useEffect here would leave an unused import.

Details in the reply on the effect itself.

const btcbi = useRecoilValue($appConfigState)
const { isPro } = btcbi

const mainAction = eventsManagerConf?.mainAction

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

When editing an integration, static fields like eventsManagerFields are not persisted in the database. We must rebuild them on mount from the saved action to prevent empty options and component crashes.

Suggested change
const mainAction = eventsManagerConf?.mainAction
const mainAction = eventsManagerConf?.mainAction
useEffect(() => {
if (mainAction && (!eventsManagerConf.eventsManagerFields || eventsManagerConf.eventsManagerFields.length === 0)) {
setEventsManagerConf(prevConf =>
create(prevConf, draftConf => {
draftConf.eventsManagerFields = fieldsByAction[mainAction] || []
})
)
}
}, [mainAction])
References
  1. When editing an integration, ensure that non-persisted configuration fields (like static fields) are rebuilt on mount from the saved action without modifying the saved field mappings, to prevent empty options and component crashes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Declining — the premise doesn't hold for this integration: eventsManagerFields is persisted.

handleMainAction writes it into the conf, and the whole conf is what saveActionConf stores in flow_details. Confirmed against a real saved flow in this database, where the equivalent key is present:

{"name":"Klaviyo","type":"Klaviyo","field_map":[],
 "klaviyoFields":[{"key":"email","label":"Email","required":true}, ]}

So on edit the field list comes back from the database and the map renders normally — there's no empty-options or crash case for the effect to guard.

A near-identical suggestion was declined on #200 for the same reason. This variant is better (it only fills when the array is missing, rather than overwriting saved config on mount), but it would still be guarding a state that can't currently be reached, and the import in the paired comment exists only to serve it.

If {Name}Fields going stale across plugin updates is the underlying worry, that one is real — but it applies to every integration in the repo, not this one, and the conditional-fill wouldn't help since the stale array is non-empty.

];
}

$fieldData = static::generateReqDataFromFieldMap($fieldMap, $fieldValues);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Since generateReqDataFromFieldMap is a private static method, it is safer and more idiomatic to call it using self:: instead of static::. Using static:: invokes late static binding, which can lead to runtime errors if the class is extended, as private methods are not accessible to subclasses.

        $fieldData = self::generateReqDataFromFieldMap($fieldMap, $fieldValues);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Declining, on consistency grounds rather than disagreement.

The technical point is fair — with a private method, static:: would resolve in the subclass scope and fatal if a child declared its own generateReqDataFromFieldMap. But RecordApiHelper is never subclassed (each integration ships its own), and the repo is near-unanimous the other way:

21 x static::generateReqDataFromFieldMap
 2 x self::generateReqDataFromFieldMap

Making this one file the exception would leave a reader wondering what's different about Events Manager. If the preference is self::, that's worth doing as one sweep across all 23 call sites rather than piecemeal here.

@github-actions

Copy link
Copy Markdown

✅ WordPress Plugin Check Report

✅ Status: Passed

📊 Report

All checks passed! No errors or warnings found.


🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

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.

2 participants