Skip to content

feat(fluent-player): add FluentPlayer integration (26 actions) - #195

Open
RishadAlam wants to merge 4 commits into
mainfrom
feat/fluentPlayer
Open

feat(fluent-player): add FluentPlayer integration (26 actions)#195
RishadAlam wants to merge 4 commits into
mainfrom
feat/fluentPlayer

Conversation

@RishadAlam

Copy link
Copy Markdown
Member

Description

Adds the free half of the FluentPlayer integration: 26 write actions dispatched to the Pro plugin over bit_integrations_fluent_player_* filters, plus the action UI. Pairs with bit-integrations-pro — the free plugin only fires the hooks and logs the result; all plugin work happens in Pro.

Motivation & Context

FluentPlayer had no Bit Integrations support, so its media, playlists, tags, leads and watch analytics could not be automated from a flow. This is the free-side half of that.

Type of Change

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

Key Changes

Backend (backend/Actions/FluentPlayer/)

  • Added FluentPlayerController — activation check, authorize, and the list endpoints backing the dropdowns (media, tags, presets, users, attachments).
  • Added RecordApiHelper — a switch with one case per action, each firing its own Config::withPrefix('fluent_player_*') filter and passing only the arguments that action's Pro handler consumes, then recording the outcome through LogHandler.
  • Added Routes.php; registered FluentPlayer in AllTriggersName.

Frontend (frontend/src/components/AllIntegrations/FluentPlayer/)

  • Added the 3-step wizard (Authorization → IntegLayout → StepThree) plus the edit screen, field map, and a Utilities panel for the optional status / view-type / ended selects.
  • Field map vs select split: the field map carries free text and the required identifier of the record each action targets (media_id, playlist_id, media_ids, tag name, user_id) so those can be mapped from trigger data. Fixed option sets (provider, status) and fetchable config values (preset, tags, attachment, optional media/user) are selects instead.
  • Registered the integration in NewInteg, EditInteg, IntegInfo and SelectAction, and added the app logo.

Checklist

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

Changelog

  • New Integration: FluentPlayer — automate media, playlists, tags, lead capture and watch analytics.
  • New Actions (26): Media (create, update, trash, restore, delete, change status), Tags (create, rename, delete, set/add/remove on media), Playlists (create, update, trash, restore, delete, change status, add/remove media), Leads (create submission, subscribe to providers), Watch Progress (record progression), Analytics (record visit), Presets (save, delete).

Reviewer notes

Requires bit-integrations-pro feat/fluentplayer — without it every action returns the "Bit Integrations Pro is not installed" default.

A review pass raised a set of defects that are not addressed in this branch and are worth folding in before merge:

  • checkMappedFields only checks that an action is selected — it does not reject incomplete field-map rows or missing required selects (provider / status / preset / tags), so a flow can be saved that fails on every run.
  • The dropdown feed queries are unbounded (get_users() with no number, two get_posts() with posts_per_page => -1) and the fetched lists are persisted into flow_details.
  • The tag dropdown carries term names; a tag containing a comma is split by the comma-joined multi-select value.
  • The field-map "+" button renders for actions that have no optional fields.

Adds the free half of the FluentPlayer integration: 26 write actions that are
dispatched to the Pro plugin over bit_integrations_fluent_player_* filters, plus
the action UI.

Backend:
- FluentPlayerController: activation check, authorize, and list endpoints for
  media, playlists, tags, presets, users and attachments.
- RecordApiHelper: switch over the 26 actions, each firing its own filter, then
  records the outcome through LogHandler.

Frontend:
- Action picker (all Pro-gated), record pickers, and a Utilities section for the
  optional status/view-type/ended selects.
- Field map carries free-text values only; anything with a fixed option set or a
  fetchable record list is a select instead.

Registers FluentPlayer in AllTriggersName, NewInteg, EditInteg, IntegInfo and
SelectAction, and ships the integration logo.
Copilot AI review requested due to automatic review settings July 18, 2026 10:35

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 FluentPlayer, adding backend controllers, routes, and API helpers alongside frontend components for authorization, field mapping, and layout configuration. The feedback highlights critical improvements for robustness and code quality: adding .catch blocks to asynchronous API requests to handle failures gracefully, implementing a useEffect hook on mount to rebuild non-persisted configuration fields when editing an integration, and declaring several controller methods as static since they do not access instance state.

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 +21 to +34
const authorizeHandler = () => {
setIsLoading('auth')
bitsFetch({}, 'fluent_player_authorize').then(result => {
if (result?.success) {
setIsAuthorized(true)
setSnackbar({
show: true,
msg: __('Connected with FluentPlayer Successfully', 'bit-integrations')
})
}
setIsLoading(false)
setShowAuthMsg(true)
})
}

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 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.

  const authorizeHandler = () => {
    setIsLoading('auth')
    bitsFetch({}, 'fluent_player_authorize')
      .then(result => {
        if (result?.success) {
          setIsAuthorized(true)
          setSnackbar({
            show: true,
            msg: __('Connected with FluentPlayer Successfully', 'bit-integrations')
          })
        }
        setIsLoading(false)
        setShowAuthMsg(true)
      })
      .catch(() => {
        setIsLoading(false)
        setShowAuthMsg(true)
        setSnackbar({
          show: true,
          msg: __('Connection failed. Please try again.', '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.

Fixed — the promise now has a .catch that clears the loading state, still shows the auth message, and surfaces the failure:

.catch(() => {
  setIsLoading(false)
  setShowAuthMsg(true)
  setSnackbar({ show: true, msg: __('Connection failed. Please try again.', 'bit-integrations') })
})

setIsLoading(false)
toast.error(errorMsg)
})
.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.

high

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(errorMsg)
    })
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.

Fixed. There was a .catch but it only cleared the spinner, so a network failure looked identical to a no-op. It now reports the error too:

.catch(() => {
  setIsLoading(false)
  toast.error(errorMsg)
})

@@ -0,0 +1,261 @@
import { create } from 'mutative'

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

Import useEffect from 'react' to fetch the required lists on mount when editing an integration.

Suggested change
import { create } from 'mutative'
import { useEffect } from 'react'
import { create } from 'mutative'

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 — useEffect is imported and used for the mount-time refetch described in your other comment.

Comment on lines +46 to +47
const action = fluentPlayerConf?.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, 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.

  const action = fluentPlayerConf?.mainAction

  useEffect(() => {
    if (action) {
      if (needsPreset.includes(action)) refreshFluentPlayerPresets(setFluentPlayerConf, setIsLoading)
      if (needsTags.includes(action)) refreshFluentPlayerTags(setFluentPlayerConf, setIsLoading)
      if (needsAttachment.includes(action)) refreshFluentPlayerAttachments(setFluentPlayerConf, setIsLoading)
      if (needsOptionalUser.includes(action)) refreshFluentPlayerUsers(setFluentPlayerConf, setIsLoading)
      if (needsOptionalMedia.includes(action) || needsOptionalMediaIds.includes(action)) {
        refreshFluentPlayerMedia(setFluentPlayerConf, setIsLoading)
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])
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.

Fixed. Extracted the per-action fan-out into fetchListsFor(action) and call it once on mount, so an edit rebuilds the option lists without touching the saved field_map:

useEffect(() => {
  fetchListsFor(action)
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

handleMainAction now calls the same helper, so the two paths can't drift.

wp_send_json_success(true);
}

public function refreshMedia()

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

The refreshMedia method does not access any instance properties ($this) and is registered as a static route callback. It should be explicitly declared as static to maintain consistency and adhere to coding standards.

    public static function refreshMedia()
References
  1. In PHP, controller methods that do not access instance state ($this) and are registered as static route callbacks should be explicitly declared as static to maintain consistency and adhere to coding standards.

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.

Fixed — declared static. All five refresh* methods are registered as [FluentPlayerController::class, 'refreshX'] route callbacks and none touch $this.

wp_send_json_success($response, 200);
}

public function refreshTags()

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

The refreshTags method does not access any instance properties ($this) and is registered as a static route callback. It should be explicitly declared as static to maintain consistency and adhere to coding standards.

    public static function refreshTags()
References
  1. In PHP, controller methods that do not access instance state ($this) and are registered as static route callbacks should be explicitly declared as static to maintain consistency and adhere to coding standards.

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.

Fixed — declared static. All five refresh* methods are registered as [FluentPlayerController::class, 'refreshX'] route callbacks and none touch $this.

wp_send_json_success($response, 200);
}

public function refreshPresets()

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

The refreshPresets method does not access any instance properties ($this) and is registered as a static route callback. It should be explicitly declared as static to maintain consistency and adhere to coding standards.

    public static function refreshPresets()
References
  1. In PHP, controller methods that do not access instance state ($this) and are registered as static route callbacks should be explicitly declared as static to maintain consistency and adhere to coding standards.

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.

Fixed — declared static. All five refresh* methods are registered as [FluentPlayerController::class, 'refreshX'] route callbacks and none touch $this.

wp_send_json_success($response, 200);
}

public function refreshUsers()

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

The refreshUsers method does not access any instance properties ($this) and is registered as a static route callback. It should be explicitly declared as static to maintain consistency and adhere to coding standards.

    public static function refreshUsers()
References
  1. In PHP, controller methods that do not access instance state ($this) and are registered as static route callbacks should be explicitly declared as static to maintain consistency and adhere to coding standards.

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.

Fixed — declared static. All five refresh* methods are registered as [FluentPlayerController::class, 'refreshX'] route callbacks and none touch $this.

wp_send_json_success($response, 200);
}

public function refreshAttachments()

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

The refreshAttachments method does not access any instance properties ($this) and is registered as a static route callback. It should be explicitly declared as static to maintain consistency and adhere to coding standards.

    public static function refreshAttachments()
References
  1. In PHP, controller methods that do not access instance state ($this) and are registered as static route callbacks should be explicitly declared as static to maintain consistency and adhere to coding standards.

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.

Fixed — declared static. All five refresh* methods are registered as [FluentPlayerController::class, 'refreshX'] route callbacks and none touch $this.

@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

- Authorization: a rejected authorize request left the spinner running with
  no feedback. Clear the loading state, show the auth message and surface a
  snackbar.
- CommonFunc: the refresh `.catch` only cleared the spinner, so a network
  failure was indistinguishable from an empty list. Report it with a toast.
- IntegLayout: option lists were only built when the action select changed,
  so opening a saved integration rendered empty dropdowns. Extract
  `fetchListsFor(action)` and call it on mount as well as on change.
- Controller: the five `refresh*` route callbacks are registered statically
  and never touch `$this`; declare them `static`.
Copilot AI review requested due to automatic review settings July 18, 2026 11:04

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