feat(google-sheet): add ten Pro actions and fix the free row insert - #218
Open
RishadAlam wants to merge 16 commits into
Open
feat(google-sheet): add ten Pro actions and fix the free row insert#218RishadAlam wants to merge 16 commits into
RishadAlam wants to merge 16 commits into
Conversation
The three refresh endpoints tested for an error at the wrong path. refreshSpreadsheetsAjaxHelper checked `$response->response->error`, but Google returns `error` at the top level, so a 401 body passed the success guard and `->files` was read off an error object: PHP warnings, a `foreach` over null, and an empty Spreadsheets dropdown with no reason shown to the user. The error branch dereferenced the same wrong path, so even when it did fire it emitted another warning instead of a message. refreshWorksheetsAjaxHelper and refreshWorksheetHeadersAjaxHelper had the same shape problem, testing `->status === 'error'` and `->message`, which the Sheets API never sends, and reading `->sheets` unguarded. Route all three through hasApiError()/apiErrorMessage(), which understand both the Sheets/Drive error object and the OAuth `error`/`error_description` pair. Success responses carry no `error` key, so the behaviour there is unchanged.
Adds ten write actions on top of the existing row insert: append or update row, update row, delete row, create column, and create, copy, clear and delete worksheet, plus create and delete spreadsheet. Ported from Bit-Pi's GoogleSheet services; read-only operations (find spreadsheets, find worksheet, export sheet, get rows) and the polling triggers are out of scope. The existing insert stays free and untouched: execute() falls through to the original RecordApiHelper whenever mainAction is absent or 'insertRow', so flows saved before this change keep their exact behaviour. Everything else fires bit_integrations_google_sheet_* for the Pro plugin to answer, and logs the outcome through LogHandler so the Task Log has a diagnosis either way. ProRecordApiHelper resolves smart tags on the per-action inputs and maps header-mapped values onto their column offsets before handing them over, so Pro receives plain data and never re-reads the flow config. resolveTokenDetails() reuses the existing refresh and persist helpers, giving the Pro path a valid access token without changing how the legacy path obtains its own.
Puts an action dropdown at the top of the integration layout, Pro-gated through checkIsPro/getProLabel. Picking anything other than the existing row insert renders GoogleSheetProLayout; picking the insert renders the original layout unchanged, so saved integrations look and behave exactly as before. GoogleSheetProLayout reuses the existing spreadsheet, worksheet and header refresh endpoints, and renders only what each action needs: the header field map for the row actions, a column-to-match select for append or update, smart-tag inputs for free text and row numbers, and a keep-headers option for clear worksheet. isActionConfigured() replaces the hardcoded spreadsheet + worksheet + field map check in the wizard and the edit screen, since actions such as create spreadsheet have neither a spreadsheet nor a field map. It resolves to the previous condition for the row insert. handleMainAction ignores a no-op selection so a saved field map cannot be cleared by a re-render.
Row number, column name, worksheet title, spreadsheet title and destination spreadsheet id were smart-tag text inputs stored on the config. That made each one a value the user types once, when they are inputs that should come from the trigger on every run — a row number picked at build time targets one row forever. They are now target fields in the field map, the same shape FluentCart uses: staticData declares the fields an action accepts, the layout offers them as mapping targets alongside the worksheet headers where an action uses both, and selecting an action pre-fills one row per declared field so the required ones are visible rather than discovered. ProRecordApiHelper resolves the whole field map once, then splits it: rows naming one of the action's own fields become that input, the rest are header values mapped onto their column offsets. The Pro handlers read the same $fieldData keys as before and are unchanged. isActionConfigured now checks that each required field is actually mapped instead of that a config property is non-empty, and the spreadsheet and worksheet change handlers rebuild the field map for the current action rather than resetting it to a single blank row. The row insert resolves to one blank row exactly as before.
A new integration now opens with only the action dropdown. Each section below it appears once the chosen action actually calls for it and its prerequisites are met: the spreadsheet select for actions that target one, the worksheet select once a spreadsheet is picked, the header row and the field map once a worksheet is picked, the column-to-match select once the headers are known, and the utilities checkbox only for clear worksheet. Delete spreadsheet no longer asks for a worksheet, and create spreadsheet asks for neither. mainAction starts empty for a new integration and is read with `??` rather than `||`, which separates "not chosen yet" from a saved flow that predates the field: those have no mainAction key at all and still resolve to the row insert, so editing one is unchanged. Saving is blocked until an action is selected.
…hared ones GoogleSheetProLayout and GoogleSheetProFieldMap were near-copies of the components they sat beside: the same spreadsheet and worksheet selects, the same header row input, the same form-field/custom/smart-tag column. Keeping two of each meant every fix had to be made twice. There is now one layout and one field map, driven entirely by the needsX lists in staticData. The row insert is simply the action whose lists happen to include every section, so it renders the same controls it always did: spreadsheet, worksheet, header row with its explanatory note, and a header-driven field map. The field map takes its options as a targetFields prop instead of reading worksheet headers out of the config itself, which is what let the two versions collapse into one — the layout decides whether an action's targets are its own fields, the worksheet headers, or both. Net 280 lines removed.
Removes the block comments describing what the readiness flags, the field map generator and the field-map row resolver do, since the code says it. Keeps four one-liners that carry something the code cannot: why the action falls back with `??` rather than `||`, why column offsets are shifted by the header row's column, why worksheet titles are quoted for A1 notation, and why a Google error body has to be ruled out before reading its payload key. Each is a change a reader would otherwise undo.
…elper The two helpers resolved the field map with near-identical loops and both looked up the worksheet header list, one inline and one through a method. Every fix to either had to be repeated. There is now one RecordApiHelper taking the flow details and dispatching on mainAction: the row insert builds and posts the append request as before, anything else fires its bit_integrations_google_sheet_* filter. Field-map resolution, the header lookup and the array formatting are shared. Array values now go through formatArrayObject on both paths, so a flat array still joins with commas rather than turning into JSON — the Pro path picked up the row insert's behaviour, not the other way round. Log entries keep their existing shapes: the row insert still records type_name "insert" and returns the raw API response, the Pro actions record the action slug and return the response envelope. The controller collapses to one construction and one call. Its token handling now runs through resolveTokenDetails for both paths, and the unreachable `empty($tokenDetails)` guard is gone — normalizeConnectionToken always returns an object, which empty() never reports as empty.
Selecting an action left the Spreadsheets dropdown empty until the user noticed the refresh button. It now loads on selection, for any action that targets a spreadsheet, and only when the list is not already populated — switching between actions reuses what was fetched, and the refresh button still forces a reload. The new config is built before the state update rather than inside a setState callback, because refreshSpreadsheets writes back the conf it is handed: passing the previous one would have landed a response that still carried the old action.
Drops the last explanatory comments from the Google Sheet action: the token-resolution and API-error doc blocks in the controller, the record helper's file header, the column-offset and A1-quoting notes in the Pro helper, and the action-change note in the layout. The only comment left in the touched code is the `// translators:` line, which the i18n tooling reads.
Selecting an action pre-fills a row for each field it requires. Those rows now keep their target: the Google field select is disabled, so the user only chooses what to map into it, and the delete button is disabled too — otherwise the row could just be removed and the lock would mean nothing. The "(required)" suffix on those options is gone; a disabled select already says the target is fixed.
Clear worksheet, delete worksheet and delete spreadsheet identified their target through the spreadsheet and worksheet dropdowns, which fixes each flow to the one sheet the admin picked while building it. The id now comes from the field map instead, so the target can be supplied by the trigger: delete spreadsheet maps a Spreadsheet Id, and the two worksheet actions map a Spreadsheet Id and a Worksheet Name. The dropdowns no longer render for these three, so there is one place the target can come from. Clear worksheet keeps its keep-headers utility. Their handlers read the ids out of $fieldData, so the two delete filters gain that argument and clear sheet now takes four: response, field data, utilities, config.
The keep-headers option was a bare checkbox with an inline label, unlike every other integration. It now follows the FluentCart shape: a dedicated GoogleSheetActions component rendering a TableCheckBox, placed under a "Utilities" heading with the usual rule above it. The value still lands in conf.utilities.selected_first_row_headers, so the clear-worksheet handler is unchanged.
…exercising every action Ran all ten actions plus the row insert end to end through the free dispatcher into the Pro handlers with outbound HTTP mocked, and reviewed both sides. Fixes: Next was permanently blocked on update row. generateMappedField added a spare blank row next to the pre-filled required ones, and checkMappedFields counts a fully blank row as an unmapped field, so the wizard refused to advance no matter what the user mapped. It now pre-fills only the action's own fields, falling back to a single blank row when it declares none — matching FluentCart, and leaving the row insert with the one blank row it always had. A worksheet whose title contains a space produced a malformed append range and an unencoded URL. The title is now quoted for A1 notation and the range percent-encoded, the same way the Pro handlers already build theirs. The spreadsheet dropdown silently stopped at Drive's default of 100 files. The listing now asks for 1000, sorted by name, excludes trashed files, and requests only the two fields it reads. Also: the worksheet-headers refresh bailed only when every parameter was missing rather than any of them; the worksheet and header refreshes could throw on a config with no `default` object; the append falls back to ROWS and A1 rather than emitting a notice when those are absent; and the utilities checkbox no longer regenerates its DOM id on every render.
🔍 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds ten Google Sheet write actions on top of the existing row insert, selected from a new action dropdown that shows only the configuration each action needs. Also fixes three bugs in the integration as it shipped: swallowed API errors, worksheet titles containing spaces, and a silently truncated spreadsheet list.
All new actions are Pro-gated and fire
bit_integrations_google_sheet_*filters for the Pro plugin to answer. The existing row insert stays free and behaves exactly as before — flows saved without amainActionstill route through the original code path.Motivation & Context
The Google Sheet action could only append a row. Everything Bit-Pi offers — updating and deleting rows, adding columns, and managing worksheets and spreadsheets — had no equivalent here, so any flow needing more than an append had nowhere to go.
Separately, the three refresh endpoints tested for an error at the wrong path (
$response->response->error, while Google returnserrorat the top level). A 401 from an expired connection passed the success guard,->fileswas read off an error body, and the user saw an empty Spreadsheets dropdown with PHP warnings in the log and no explanation in the UI.Related Links: (if applicable)
Type of Change
Key Changes
Integrations
Frontend
checkIsPro/getProLabel, as the first thing a new integration showsTableCheckBoxpresentation used elsewhereBackend
RecordApiHelperdispatch onmainAction, with one literal hook string per action andLogHandler::saveon every pathCode Refactor
ProRecordApiHelper,GoogleSheetProLayoutandGoogleSheetProFieldMap, folding each back into the component it sat beside — field-map resolution, header lookup and value formatting existed twice and had to be fixed twiceTesting
Every action was exercised end to end against a live Google account: the run creates its own spreadsheet, performs each action on it, reads the sheet back to confirm the effect, then deletes the spreadsheet. The whole suite passes twice — once with a plain worksheet name and once with a name containing a space. A second harness covers the same actions with HTTP mocked, including the append-vs-update branch, a header row starting away from column A, and a missing required field.
Checklist
Changelog