feat(flodesk): add Flodesk integration - #214
Open
RishadAlam wants to merge 16 commits into
Open
Conversation
Free owns the connection and the dropdown data; the nine write actions are Pro, so RecordApiHelper only builds the payload and fires the matching hook. The ApiClient it hands over carries the Basic auth Flodesk wants — the API key as the username with no password. The four fetch endpoints exist because the corresponding Bit-Pi read actions are not actions here: segments, workflows, custom fields and segment colours all back selects instead. Colours have to be fetched rather than hardcoded, since Flodesk only accepts values from its own palette. Paged endpoints wrap rows in `data` while custom-fields/all and segments/colors answer with a bare array, so the list reader handles both.
Three-step wizard cloned from BrilliantDirectories: connection, action plus field mapping, then the shared step three. Field maps hold only what has to come from trigger data — the identifier of the record an action targets, and free text. Segments, the segment colour and the workflow are fetched selects instead, because they are configuration choices rather than the target of the action; putting them in the map would force the user to type a magic id, and putting the target id in a dropdown would pin the action to one record. Create or Update Subscriber also merges the account's own custom fields into its mappable list, so those can be written without being declared here. Its segments and double opt-in are optional, so they sit in Utilities behind a checkbox rather than in the always-visible layout. Every action is gated with is_pro, the logo is generated from the Bit-Pi artwork.
Lazy import plus a case in the new, edit and info switches, and an entry in the action picker so the integration is reachable.
Leaves only the translators pragma, which phpcs requires beside the %s placeholder. API behaviour the comments recorded — the literal "@" in subscriber paths, the mandatory segment colour, the error array getAccessToken returns, and Flodesk creating duplicate webhooks — stays in the commit history.
Two faults found running the actions against the live API for the first time. Every write returned 415. ApiClient::addHeaders() de-duplicates with array_unique(SORT_REGULAR) over an associative array, so it drops any header whose value repeats — Accept and Content-Type were both application/json and Content-Type was the one discarded. Sending no Accept keeps Content-Type alive. Nothing here can fix the underlying helper; an integration only has to avoid giving two headers the same value. The Segments and Workflows dropdowns were silently empty. Paged endpoints decode to an object while custom-fields/all and segments/colors answer with a bare array, and the row reader only understood the array form, so it fell through and returned nothing for exactly the two lists that page.
The custom fields an account defines never reached the mapping dropdown. They were fetched when the action was chosen, but the mappable list had already been snapshotted into conf.flodeskFields a moment earlier, and nothing recomputed it when the request came back — so only email, first name and last name were ever offered. The field map now derives its options from the current config instead of that snapshot, which makes the fetched fields appear as soon as they arrive. The snapshot is gone rather than kept in sync, since it had no other reader. Adds the refresh control next to the Flodesk Fields heading, so a field created in Flodesk after the node was opened can be pulled in without re-picking the action.
It only ever worked when the subscribers value arrived as a JSON string: a mapped array is flattened to [0 => NULL] by the custom-value helper before the action sees it, so the array branch was unreachable in practice. Taken out end to end rather than left half-wired — the machine and its root entry, the module and field map, the dispatch case, the hook registration, the handler, and the batch limit. The JSON decoding and field-map readers that existed only for it go with it.
The pane was pinned to height 500 with overflow visible, so once the Utilities checkboxes pushed the layout past that, the content spilled out of the box and landed on top of the Conditional Logics row that IntegrationStepThree renders below it. Height now grows with the content, with 500 kept as a minimum so a short action still gets a full pane. Overflow stays visible so select dropdowns can escape the box. Also reserves space above the Next button, which is floated and so contributes no height of its own.
$authConfig['fields'] maps a credential onto a flat property of flow_details or the request params, for integrations whose code reads it there instead of from the connection. Flodesk reads nothing that way: the controller resolves the connection and ApiClient applies the credential, so mapping api_key to username only wrote a decrypted copy of the key onto an object nobody consulted. Declaring an empty map keeps the rest of $authConfig doing its job — the slug still binds a connection_id to this integration during injection.
bodyMessage existed because Brilliant Directories needs it: that API answers 200 with a status of error, and its message field carries the rows on a good list call, so a type check is the only way to tell a reason from a payload. Flodesk returns real status codes, keeps rows in data, and only ever puts a string in message, so the separate method guarded an assertion that does not hold here. The body read stays — ApiClient leaves getError null on a non-2xx, so without it every API failure would report "Could not reach Flodesk" instead of what Flodesk actually said. failureReason is private now; nothing outside the controller calls it.
getError is null on a non-2xx, so an integration wanting a usable message has to reach into the body — three of them now hand-roll the same chain, differing only in the app name. errorMessage moves it onto the response, next to getValue, which justifies itself the same way: every caller would otherwise repeat it. It stays out of the 2xx-with-an-error-body case, since success() already puts that on the caller, and Brilliant Directories is the only integration that needs it. Additive, so no existing caller changes. Flodesk drops its copy; SureContact and Brilliant Directories keep theirs for now — they are unrelated to this branch, and their duplication is harmless while it waits.
✅ WordPress Plugin Check Report
📊 ReportAll checks passed! No errors or warnings found. 🤖 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 Flodesk to Bit Integrations: 8 write actions with the connection, dropdown data and UI in Free, and the API calls in Pro. Pairs with Bit-Apps-Pro/bit-integrations-pro#144.
Motivation & Context
Ported from the Bit Flows (bit-pi) integration. Follows the Brilliant Directories and SureContact shape rather than the local-plugin templates, since Flodesk is a SaaS API: Free owns the connection and builds the
ApiClient, and hands it to Pro to make the call.Only write operations ship as actions — the read endpoints back dropdowns instead. Flodesk's triggers are external webhooks rather than WordPress hooks, so they are out of scope here.
Type of Change
Key Changes
Backend
FlodeskControllerwith the connection config, four dropdown fetchers (segments, workflows, custom fields, segment colours) and theApiClientfactoryRecordApiHelper, which builds the payload and fires one hook per action, logging every path throughLogHandler415.ApiClient::addHeaders()de-duplicates witharray_unique(SORT_REGULAR)over an associative array, so it drops any header whose value repeats —AcceptandContent-Typewere bothapplication/jsonandContent-Typewas discarded. Sending noAcceptkeeps it alive; the underlying helper is untouched herecustom-fields/allandsegments/colorsreturn a bare array, and the row reader only understood the array form$authConfig['fields']writing a decrypted copy of the API key onto flow details that nothing readsCore
ApiResponse::errorMessage().getError()is null on a non-2xx, so an integration wanting a usable message has to reach into the body — three integrations now hand-roll the same chain. Additive, so no existing caller changes; Flodesk drops its copy and the other two are left for a follow-upFrontend
Checklist
Testing
Driven against the live Flodesk API through the real
Controller→ hook → Pro handler path. 7 of 8 actions succeed; all validation refusals are correct; all four dropdowns populate. The two workflow actions reachdraft_workflow_not_allowedbecause the test account has no published workflow — request shape and auth are confirmed correct.Not verified: the UI has not been rendered in a browser. It compiles, lints and builds, but the authorization screen in particular is unexercised — every test connection was written directly to the database.
Changelog