feat(integrations): add Heffl CRM action with lead/client/deal support#169
feat(integrations): add Heffl CRM action with lead/client/deal support#169RishadAlam wants to merge 3 commits into
Conversation
- Implemented backend routes for Heffl CRM authorization and data refresh. - Created frontend components for editing and managing Heffl CRM integration. - Added common functions for handling input, authorization, and data refresh. - Developed field mapping functionality for Heffl CRM fields. - Included static data for lead, client, and deal fields. - Integrated loading indicators and snack messages for user feedback. - Added a new image asset for Heffl CRM integration.
…and additional fields
There was a problem hiding this comment.
Code Review
This pull request introduces a new integration for Heffl CRM, featuring backend controllers, API helpers, and a comprehensive frontend interface for managing leads, clients, and deals. The review feedback highlights several critical improvements for PHP 8.0+ compatibility, specifically the need to use is_wp_error() to validate API responses before accessing properties. Other recommendations include declaring methods as static when instance properties are not used and adding type checks during field map iteration to prevent potential runtime warnings.
| wp_send_json_error(__('Failed to fetch pipeline stages', 'bit-integrations'), HttpHelper::$responseCode ?: 400); | ||
| } | ||
|
|
||
| $stages = \is_object($response) && isset($response->stages) ? $response->stages : []; |
There was a problem hiding this comment.
According to the general rules, when handling responses that may return a WP_Error object, you should always use the is_wp_error() function to validate the response before accessing it as an object. This prevents potential issues in PHP 8.0+.
$stages = \is_object($response) && !is_wp_error($response) && isset($response->stages) ? $response->stages : [];References
- In PHP, when handling responses that may return a WP_Error object, always use the is_wp_error() function to validate the response before accessing it as an array or object. This prevents fatal errors in PHP 8.0+.
There was a problem hiding this comment.
Fixed. Added is_wp_error guard:
$stages = \is_object($response) && !is_wp_error($response) && isset($response->stages) ? $response->stages : [];Also added a WP_Error transport-failure branch earlier in refreshPipelineStages so we return a connection error before reaching the property access.
|
|
||
| private static function extractList($response) | ||
| { | ||
| if (\is_object($response) && isset($response->items) && \is_array($response->items)) { |
There was a problem hiding this comment.
As per the general rules, ensure the response is not a WP_Error before checking for properties like items.
if (\is_object($response) && !is_wp_error($response) && isset($response->items) && \is_array($response->items)) {References
- In PHP, when handling responses that may return a WP_Error object, always use the is_wp_error() function to validate the response before accessing it as an array or object. This prevents fatal errors in PHP 8.0+.
There was a problem hiding this comment.
Fixed. Added is_wp_error guard in extractList:
if (\is_object($response) && !is_wp_error($response) && isset($response->items) && \is_array($response->items)) {|
|
||
| private static function nextCursor($response) | ||
| { | ||
| if (\is_object($response) && !empty($response->hasMore) && !empty($response->nextCursor)) { |
There was a problem hiding this comment.
As per the general rules, ensure the response is not a WP_Error before checking for properties like hasMore or nextCursor.
if (\is_object($response) && !is_wp_error($response) && !empty($response->hasMore) && !empty($response->nextCursor)) {References
- In PHP, when handling responses that may return a WP_Error object, always use the is_wp_error() function to validate the response before accessing it as an array or object. This prevents fatal errors in PHP 8.0+.
There was a problem hiding this comment.
Fixed. Added is_wp_error guard in nextCursor:
if (\is_object($response) && !is_wp_error($response) && !empty($response->hasMore) && !empty($response->nextCursor)) {| ); | ||
| } | ||
|
|
||
| public function execute($integrationData, $fieldValues) |
There was a problem hiding this comment.
The execute method does not access any instance properties ($this). According to the general rules, it should be declared as static.
public static function execute($integrationData, $fieldValues)References
- In PHP, declare methods as static if they do not access any instance properties ($this) and are intended to be called statically.
There was a problem hiding this comment.
Keeping execute as an instance method. The Flow engine instantiates the controller and invokes it as an instance call (backend/Flow/Flow.php:524):
$handler = new $integration($flowData->id);
$handler->execute($flowData, $data);Every other action controller in backend/Actions/* follows the same public function execute(...) convention. Changing only this one would diverge from the project-wide contract without a functional benefit.
| foreach ($fieldMap as $item) { | ||
| $triggerValue = $item->formField; |
There was a problem hiding this comment.
There was a problem hiding this comment.
Fixed. Added is_object guard before property access:
foreach ($fieldMap as $item) {
if (!\is_object($item)) {
continue;
}
$triggerValue = $item->formField;
🔍 WordPress Plugin Check Report
📊 Report
|
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
0 |
mismatched_plugin_name | Plugin name "Bit integrations - Form Integration, Webhook, Spreadsheets, CRM, LMS & Email Automation" is different from the name declared in plugin header "Bit Integrations". |
🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check
There was a problem hiding this comment.
Pull request overview
Adds a new Heffl CRM action integration (frontend setup/edit/info flows + backend AJAX endpoints) so users can authorize via API key, select an action (lead/client/deal), refresh reference data, map fields, and execute via Pro hooks.
Changes:
- Wired Heffl CRM into action selection, new integration creation, edit integration, and integration info screens.
- Implemented a new frontend integration UI (authorization, action-specific layout, field mapping, refresh helpers, static field definitions).
- Added backend AJAX routes + controller for authorization and refreshing CRM reference lists; added execution helper that delegates to Pro hooks with logging.
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/components/Flow/New/SelectAction.jsx | Adds “Heffl CRM” to the action picker list. |
| frontend/src/components/AllIntegrations/NewInteg.jsx | Lazy-loads and renders the new Heffl CRM integration setup flow. |
| frontend/src/components/AllIntegrations/IntegInfo.jsx | Adds Heffl CRM authorization/info rendering in integration info view. |
| frontend/src/components/AllIntegrations/HefflCRM/staticData.js | Defines Heffl CRM actions and field metadata for mapping and select options. |
| frontend/src/components/AllIntegrations/HefflCRM/HefflCRMIntegLayout.jsx | Step 2 UI: action selection, dynamic reference-data selects, refresh buttons, and field-map section. |
| frontend/src/components/AllIntegrations/HefflCRM/HefflCRMFieldMap.jsx | Field mapping row component with custom value + smart tag support. |
| frontend/src/components/AllIntegrations/HefflCRM/HefflCRMCommonFunc.js | Common handlers for authorize/refresh + required-field mapping validation. |
| frontend/src/components/AllIntegrations/HefflCRM/HefflCRMAuthorization.jsx | Step 1 UI for name + API key authorization and instructions. |
| frontend/src/components/AllIntegrations/HefflCRM/HefflCRM.jsx | 3-step “new integration” wizard wiring for Heffl CRM. |
| frontend/src/components/AllIntegrations/HefflCRM/EditHefflCRM.jsx | Edit screen for existing Heffl CRM action configs. |
| frontend/src/components/AllIntegrations/EditInteg.jsx | Wires Heffl CRM into the edit-integration switch. |
| backend/Actions/HefflCRM/Routes.php | Registers AJAX endpoints for authorize + refresh list data. |
| backend/Actions/HefflCRM/RecordApiHelper.php | Builds mapped payload and dispatches execution via Pro hooks + logging. |
| backend/Actions/HefflCRM/HefflCRMController.php | Implements authorize + refresh endpoints and action execution entrypoint. |
Comments suppressed due to low confidence (2)
backend/Actions/HefflCRM/HefflCRMController.php:71
refreshPipelineStages()doesn’t handleHttpHelper::get()returning aWP_Error. In that caseHttpHelper::$responseCodemay be empty and the method will respond with a generic “Failed to fetch pipeline stages”/400, dropping the underlying error. Consider checkingis_wp_error($response)and returning the error message (and an appropriate status) to make failures diagnosable.
$response = HttpHelper::get(
self::BASE_URL . '/pipelines/' . rawurlencode($requestParams->pipelineId),
null,
self::buildHeaders($requestParams->api_key)
);
if (HttpHelper::$responseCode < 200 || HttpHelper::$responseCode >= 300) {
wp_send_json_error(__('Failed to fetch pipeline stages', 'bit-integrations'), HttpHelper::$responseCode ?: 400);
}
backend/Actions/HefflCRM/HefflCRMController.php:146
fetchListItems()assumesHttpHelper::get()returns a decoded response object/array, but it doesn’t guard againstWP_Errorreturns. If the request fails, the current code will likely emit “Failed to fetch from Heffl CRM” with a potentially empty/incorrect status code, and then continue cursor logic against an error object. Consider handlingis_wp_error($response)immediately after the request and surfacing$response->get_error_message().
do {
$separator = strpos($path, '?') === false ? '?' : '&';
$query = $separator . 'limit=' . self::PAGE_LIMIT . ($cursor ? '&cursor=' . rawurlencode($cursor) : '');
$response = HttpHelper::get(self::BASE_URL . $path . $query, null, $headers);
if (HttpHelper::$responseCode < 200 || HttpHelper::$responseCode >= 300) {
wp_send_json_error(__('Failed to fetch from Heffl CRM', 'bit-integrations'), HttpHelper::$responseCode ?: 400);
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| draftConf.mainAction = value | ||
|
|
||
| delete draftConf.leadSources | ||
| delete draftConf.leadStages | ||
| delete draftConf.pipelines | ||
| delete draftConf.pipelineStages | ||
| delete draftConf.clients | ||
| delete draftConf.leads | ||
| delete draftConf.dealSourceId | ||
| delete draftConf.priority | ||
| delete draftConf.clientType | ||
|
|
There was a problem hiding this comment.
Fixed. The switch now also clears the stored selection IDs so each action starts clean:
delete draftConf.leadSourceId
delete draftConf.leadStageId
delete draftConf.pipelineId
delete draftConf.dealStageId
delete draftConf.dealSourceId
delete draftConf.clientId
delete draftConf.leadId
delete draftConf.priority
delete draftConf.clientType| public static function hefflCRMAuthorize($requestParams) | ||
| { | ||
| if (empty($requestParams->api_key)) { | ||
| wp_send_json_error(__('API key is required', 'bit-integrations'), 400); | ||
| } | ||
|
|
||
| HttpHelper::get(self::BASE_URL . '/leads?limit=1', null, self::buildHeaders($requestParams->api_key)); | ||
|
|
||
| if (HttpHelper::$responseCode >= 200 && HttpHelper::$responseCode < 300) { | ||
| wp_send_json_success(__('Authorization successful', 'bit-integrations'), 200); | ||
| } | ||
|
|
||
| wp_send_json_error(__('Invalid Heffl CRM API key', 'bit-integrations'), 400); | ||
| } |
There was a problem hiding this comment.
Fixed at all three locations (hefflCRMAuthorize, refreshPipelineStages, fetchListItems). Each HttpHelper::get call now captures the return value and surfaces a distinct connection-failure message before falling back to API-level errors:
$response = HttpHelper::get(self::BASE_URL . '/leads?limit=1', null, self::buildHeaders($requestParams->api_key));
if (is_wp_error($response)) {
wp_send_json_error(__('Failed to connect to Heffl CRM: ', 'bit-integrations') . $response->get_error_message(), 400);
}|
Reviewed Plugin Check report: Error — Warning — |
Description
This PR adds a new Heffl CRM action integration to Bit Integrations with end-to-end setup, editing, and execution support. Users can authorize with an API key, choose an action type (lead/client/deal), map fields, and use live-fetched Heffl CRM reference data (sources, stages, pipelines, clients, leads). It also wires the integration into action selection, integration info, and edit flows.
Motivation & Context
Users need a native way to send WordPress trigger data into Heffl CRM workflows without manual export/import. This change introduces a dedicated Heffl CRM action setup experience and backend endpoints to make configuration reliable and faster, especially for dynamic lists used during lead/deal mapping.
Related Links: (if applicable)
Type of Change
Key Changes
Backend
HefflCRMControllerwith API-key authorization and refresh endpoints for lead sources, lead stages, pipelines, pipeline stages, clients, and leads.RecordApiHelperto build mapped payloads and route execution by selected action (create_lead,create_client,create_deal).heffl_crm_create_*) with clear fallback responses and integration logging.Frontend
HefflCRM.jsx) with 3-step setup flow and required-map validation before save.HefflCRMAuthorization.jsx) with setup guidance and API-key validation.HefflCRMIntegLayout.jsx) for lead/client/deal-specific settings and refresh controls.HefflCRMFieldMap.jsx) supporting required/optional fields, custom values, and smart tags.HefflCRMCommonFunc.js) for authorization, refresh, map generation, and map validation.Integration Wiring & Assets
hefflCRM.webplogo asset for integration cards/icons.Checklist
Changelog