Skip to content

feat(wptablebuilder): add WP Table Builder action integration#200

Open
RishadAlam wants to merge 3 commits into
mainfrom
feat/wptablebuilder
Open

feat(wptablebuilder): add WP Table Builder action integration#200
RishadAlam wants to merge 3 commits into
mainfrom
feat/wptablebuilder

Conversation

@RishadAlam

@RishadAlam RishadAlam commented Jul 19, 2026

Copy link
Copy Markdown
Member

Description

Adds WP Table Builder as an action integration with four actions — Create Table, Update Table, Delete Table and Add Row to Table — plus the React UI for configuring them. The free plugin stays hook-only: it fires bit_integrations_wptablebuilder_* and the Pro plugin supplies the handlers.

Pairs with Bit-Apps-Pro/bit-integrations-pro#138, which implements the handlers and the four triggers.

Motivation & Context

WP Table Builder was one of the integrations available in bit-pi but missing from the Bit Integrations free/pro pair. This ports it across, dropping the read-only actions (Get Table, Get Row, Get Column, Get Cell, List Rows) per the integration scope rules — fetched data belongs in a dropdown, not an action.

Add Row is new relative to the bit-pi port: bit-pi only exposed whole-table writes, so building a table row-by-row from form submissions was not possible.

Type of Change

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

Key Changes

Backend (Actions)

  • Added WpTableBuilderController with the activation check, plus refreshTables() and refreshColumns() for the Add Row dropdown and its column list
  • Added RecordApiHelper with one switch case per action, each firing a literal bit_integrations_wptablebuilder_* hook so the wiring is greppable from either plugin
  • Added Routes.php exposing the authorize route and the two refresh routes
  • Registered the integration in AllTriggersName.php

Frontend

  • Added the eight-file component set under AllIntegrations/WpTableBuilder/ (wizard, authorization, integration layout, field map, utilities, edit view, shared helpers, static data)
  • Added a fetched Table dropdown for Add Row whose column list drives the field map
  • Added a Delete Permanently checkbox under Utilities, so a mis-mapped table id stays recoverable by default
  • Registered the integration in NewInteg.jsx, EditInteg.jsx, IntegInfo.jsx, SelectAction.jsx and webhookIntegrations.js, and added the logo

Design notes

  • Create, Update and Delete take table_id through the field map, so one flow can target a different table per run
  • Add Row is the exception: its column list has to be known while the flow is being configured, so the table is picked from a dropdown. Columns are positional (cell_0cell_N) because WP Table Builder cells carry no stable identifier — reordering columns in WP Table Builder means refreshing them here
  • The Table dropdown remounts on its option count, working around react-multiple-select-dropdown-lite matching defaultValue in an effect keyed on defaultValue alone and never on the options

Checklist

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

Testing

Verified against WP Table Builder 2.1.18 on a live install:

  • Column discovery reads the header row correctly (4 columns off a real table)
  • Add Row appends while preserving every data-wptb-* attribute, class and inline style
  • ESLint (zero warnings), Prettier, php -l and php-cs-fixer all clean

Changelog

  • New Actions: WP Table Builder — Create Table, Update Table, Delete Table and Add Row to Table
  • Feature: Append a row to an existing WP Table Builder table on every form submission, mapping each column to a form field

Add WP Table Builder as an action integration with four write actions:
Create Table, Update Table, Delete Table and Add Row to Table.

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

Add Row needs its target table's column list while the flow is being
configured, so the table is picked from a fetched dropdown rather than the
field map, and refresh_wptablebuilder_columns reads the column labels from
the first row of the stored markup. Columns are positional (cell_0..cell_N)
because WP Table Builder cells carry no stable identifier. Create, Update
and Delete keep table_id in the field map so a flow can target a different
table per run.
Copilot AI review requested due to automatic review settings July 19, 2026 08:32

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 WP Table Builder, allowing users to create, update, delete tables, and add rows. It includes backend controllers, helpers, and routes, as well as frontend React components for authorization, layout, and field mapping. The review feedback highlights several improvement opportunities: rebuilding non-persisted configuration fields on mount when editing, adding proper error handling and loading state resets in asynchronous catch blocks, implementing defensive checks to prevent PHP warnings and undefined property notices, and providing default parameters to prevent 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 +72 to +76
useEffect(() => {
if (needsColumns && !wpTableBuilderConf?.tables?.length) {
refreshTables(wpTableBuilderConf, setWpTableBuilderConf, setIsLoading)
}
}, [needsColumns])

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, non-persisted configuration fields (like static fields) should be rebuilt on mount from the saved action without modifying the saved field mappings. This ensures that the static fields are populated correctly and prevents empty options or component crashes.

  useEffect(() => {
    if (needsColumns && !wpTableBuilderConf?.tables?.length) {
      refreshTables(wpTableBuilderConf, setWpTableBuilderConf, setIsLoading)
    }
  }, [needsColumns])

  useEffect(() => {
    if (mainAction && mainAction !== 'add_row') {
      setWpTableBuilderConf(prevConf =>
        create(prevConf, draftConf => {
          draftConf.wpTableBuilderFields = 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.

Not applying this one.

wpTableBuilderFields is persisted with the flow, so an edit screen already opens with the correct definitions — there is no empty-options or crash case to fix here. Rebuilding it in a mount effect would rewrite saved configuration merely as a side effect of opening the edit view, which is a heavier contract than the problem warrants.

Add Row is the case that genuinely cannot rely on persisted definitions, since its columns come from the selected table rather than from staticData. That path already refetches — the table list on mount, and the columns via the Refresh Columns control.

The broader point about static field definitions going stale across plugin updates is a real one, but it applies to every integration in the repo, not just this one, so it belongs in a repo-wide change rather than here.

typeof result?.data === 'string' ? result.data : __('Failed to fetch tables', 'bit-integrations')
)
})
.catch(() => setIsLoading(false))

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

When handling asynchronous API requests, always append a .catch block to ensure that loading states are reset and error messages are displayed if the request fails or encounters a network error.

    .catch(() => {
      setIsLoading(false)
      toast.error(__('An error occurred while fetching tables', 'bit-integrations'))
    })
References
  1. When handling asynchronous API requests, always append a .catch block to ensure that loading states are reset and error messages are displayed if the request fails or encounters a network error.

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.

Done — the .catch now clears the loading state and surfaces a toast:

.catch(() => {
  setIsLoading(false)
  toast.error(__('An error occurred while fetching tables', 'bit-integrations'))
})

Previously a network failure left the dropdown silently empty with no explanation.

: __('Failed to fetch columns', 'bit-integrations')
)
})
.catch(() => setIsLoading(false))

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

When handling asynchronous API requests, always append a .catch block to ensure that loading states are reset and error messages are displayed if the request fails or encounters a network error.

    .catch(() => {
      setIsLoading(false)
      toast.error(__('An error occurred while fetching columns', 'bit-integrations'))
    })
References
  1. When handling asynchronous API requests, always append a .catch block to ensure that loading states are reset and error messages are displayed if the request fails or encounters a network error.

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.

Done, same treatment as the tables fetch:

.catch(() => {
  setIsLoading(false)
  toast.error(__('An error occurred while fetching columns', 'bit-integrations'))
})

This one matters a little more — a silent failure here leaves the field map empty, which reads as "this table has no columns" rather than "the request failed".

{
self::isExists();

if (empty($requestParams->selectedTable)) {

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

To prevent potential PHP warnings or errors (such as attempting to read property on null) when the request body is empty or invalid, add a defensive check to ensure $requestParams is not empty before accessing its properties.

        if (empty($requestParams) || empty($requestParams->selectedTable)) {

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 — empty() already covers this.

empty($requestParams->selectedTable) does not evaluate the property read when $requestParams is null; it returns true silently. Verified on the PHP this runs against:

PHP 8.4.23
empty($null->p)   bool(true)    no warning
isset($null->p)   bool(false)   no warning

Only a direct read warns. So empty($requestParams) || is a second condition that can never change the outcome.

The same review flagged a case where the read is direct — $item->formField in RecordApiHelper — and that one was real and is now fixed.

)
}

export const generateMappedField = fields => {

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

Add a default parameter to generateMappedField to prevent runtime errors if the function is called with an undefined or null value.

Suggested change
export const generateMappedField = fields => {
export const generateMappedField = (fields = []) => {

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.

Done:

export const generateMappedField = (fields = []) => {

Both current callers already pass an array (fieldsByAction[value] || [] and result.data?.columns || []), so this is guarding future callers rather than fixing a live crash — but it costs nothing.

Comment on lines +109 to +110
$triggerValue = $item->formField;
$actionValue = $item->wpTableBuilderField;

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

Use the null coalescing operator (??) when accessing properties on $item to prevent 'Undefined property' notices if any expected fields are missing from the mapped item.

            $triggerValue = $item->formField ?? '';
            $actionValue = $item->wpTableBuilderField ?? '';

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.

Good catch, applied:

$triggerValue = $item->formField ?? '';
$actionValue = $item->wpTableBuilderField ?? '';

These are direct property reads, which is what makes this one different from the isset()/empty() cases elsewhere in this review — a direct read on a missing property does emit Warning: Undefined property, so the ?? is doing real work here.

@github-actions

Copy link
Copy Markdown

🔍 WordPress Plugin Check Report

⚠️ Status: Passed with warnings

📊 Report

🎯 Total Issues ❌ Errors ⚠️ Warnings
1 0 1

⚠️ Warnings (1)

📁 readme.txt (1 warning)
📍 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

Copilot AI review requested due to automatic review settings July 19, 2026 08:41

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.

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