This guide contains a list of the breaking changes introduced between version 0.13 and 1.0 of Flex UI. If you have done programmatic customizations to Flex UI or Flex WebChat UI, you will need to make sure that your custom code is updated to support these changes.
To use a custom redux store, use the Flex store enhancer with a store creation method:
1const reducers = combineReducers({2flex: Flex.FlexReducer,3app: myReducer4});56const middleware = applyMiddleware();78const store = createStore(9reducers,10compose(11middleware,12Flex.applyFlexMiddleware()13)14);1516Flex17.Manager.create(configuration, store)18.then(manager => {19ReactDOM.render(20<Provider store={store}>21<Flex.ContextProvider manager={manager}>22<Flex.RootContainer />23</Flex.ContextProvider>24</Provider>,25container26);27})28
After Flex GA, the preferred way of customizing Flex, both Twilio hosted and locally hosted deployment models, is plugins.
Flex plugins are essential to customizing Flex instances so your development team can share components with agents and supervisors across your organization. Read more about customizing Flex with plugins here
If you have styled you UI, using the theming object, then you will need to make sure you do the following changes:
The colorTheme
object now has 4 parameters:
baseName
: string - to set a predefined themecolors
: object - to define a set of base colors that are used throughout the UI to define your custom themelight
: Boolean - controls whether UI will aim at choosing dark texts or light text colors to allow for readability. It also controls icon colors, hover colors and moreoverrides
: object - a set of style overrides for each componentAn example of setting the color configurations in appConfig
:
1config.colorTheme = {2baseName: "DarkTheme",3colors: {4base1: "blue",5base2: "orange",6base3: "yellow",7base4: "black",8base5: "white",9base6: "pink",10base7: "red",11base8: "blue",12base9: "brown",13base10: "black",14base11: "white",15},16light: false,17overrides: {18MainHeader: {19Container: {20background: "#35372c"21}22},23SideNav: {24Container: {25background: "#35372c"26},27Button: {28background: "35372c"29},30},31}32}
We have introduced React Router for routing in Flex UI
HistoryPush
HistoryReplace
HistoryGo
HistoryGoBack
HistoryGoForward
route
for a View
component to mount a view to a route different from its nameSupport for browser and memory history that is configurable through the configuration file.
In case you are using routing libraries like react-router-redux
or connected-react-router
, you may wish to sync history between your application and Flex. To do so, provide the history object that you are using for your Router as a parameter to Flex store enhancer:
1const reducers = combineReducers({2flex: Flex.FlexReducer,3app: myReducer4});56const history = createHistory();78const middleware = applyMiddleware();910const store = createStore(11reducers,12compose(13middleware,14Flex.applyFlexMiddleware(history)15)16);1718Flex19.Manager.create(configuration, store)20.then(manager => {21ReactDOM.render(22<Provider store={store}>23<ConnectedRouter history={history}>24<Switch>25<Route path="/hi" component={() => {26setTimeout(() => { history.push("/"); }, 5000);27return (28<div>Hi! I will redirect to Flex in 5 seconds flat!</div>29);30}}></Route>31<Route component={() => {32return (<Flex.ContextProvider manager={manager}>33<Flex.RootContainer />34</Flex.ContextProvider>);35}}></Route>36</Switch>37</ConnectedRouter>38</Provider>,39container40);41})42
We have introduced Component.Content.remove
to allow the removal of components from dynamic component children (both native and programmatically-added ones). See the specification here.
This feature now requires a key property for all custom components passed to Component.Content.register/add
. E.g. <div key="custom-key"/>
If you have added custom components to Flex UI, make sure they have a key property defined.
task
type changed to ITask
for task-based components. Previously, the TaskState
interface, which had only source
and reservation
properties, was used. The source
and reservation
properties remain the same, but now attributes and other task properties can be accessed from the task
object itself. For example, this.props.task.attributes
can be used where applicable and there should be no further need to use the source
sub-property (which refers to the Task Router SDK object).sourceObject
, which will point to the actual SDK object:
Reservation
in case the data source for the task is TaskRouterSDK. Applies for components and actions in AgentDesktopView
.InsightsObject
in case the data source for the task is InsightsSDK. Applies for components and actions in TeamsView
taskSid
in the payload will now expect sid
insteadSetActivity
action now has a payload in the form of {activitySid: string; activityName?: string; activityAvailable?: boolean}
. Only activitySid
is used in the default implementation, and is required when invoking the action by the user, but the other two parameters are filled for better context for users who override the action and need more information on what the new activity will be. Additionally, SetActivity
can now be called with just activityName
in the payload.SelectTaskInSupervisor
now expects/provides an object as its payload in the form of {task?: ITask, sid?: string}
. Providing either will autofill the other, so both will be available for whoever taps into the action via the Actions framework.SelectWorkerInSupervisor
now expects/provides an object as its payload in the form of {worker?: ITask, workerSid?: string}
. Providing either will autofill the other, so both will be available for whoever taps into the action via the Actions framework.MonitorCall
now expects/provides an object as its payload in the form of {task?: ITask, sid?: string}
. Providing either will autofill the other, so both will be available for whoever taps into the action via the Actions framework.HoldCall
will no longer toggle the hold state, but will instead be meant only for call holding. A separate UnholdCall
action was added.The HangupCall
and HoldCall
actions will now accept optional parameters sid:string
or task:ITask
in the payload object. Actions will work without them if just one call is available, but it is advised to use those parameters to be more specific for future multi-call scenarios. Even if the task/taskSid are not specified, when adding listeners or overriding these actions, those parameters are filled out automatically.
Call recording can be enabled from the configuration service. This option sets the following parameters in the conference payload when a conference is created:
"conferenceRecord": true,
"conferenceRecordingStatusCallback": "https://webhooks.twilio.com/v1/Accounts/{accountSid}/Workspaces/{workspaceSid}/Tasks/{taskSid}/FlexRecordingWebhook",
"conferenceRecordingStatusCallbackMethod": "POST"
You can retrieve the accountSid
and workspaceSid
via the configuration services, i.e.
manager.serviceConfiguration.account_sid
manager.serviceConfiguration.taskrouter_workspace_sid
If you have replaced an AcceptTask action, registered a custom action that issues a conference instruction, or directly issued a conference instruction via TaskRouterSDK (or any other way of setting a conference payload), make sure that the above parameters are set properly to support call recording.
To enable call transfers, calls will be currently accepted with endConferenceOnExit set to false, meaning that the call will not stop for the customer when an agent hangs up, and to end the call, the customer will need to hang up themselves (otherwise, they will stay in the call alone).
If the above behavior is not acceptable for your use case and you will not be using transfer functionality, you may opt out of transfers by setting the disableTransfers config option to true. If this option is set to true, the endConferenceOnExit option will be set to true, but transfers to other agents will not be available.
All of the functions to orchestrate different channels and tokens have been removed. Now, a combination of backend services and Studio flows is used instead.
channel.attributes.pre_engagement_data
), that can be accessed in the Studio Flow or directly from Programmable Chat via the SDK or REST API.startEngagementUrl
and serviceBaseUrl
have been removed and new configuration options are required in the application configuration:
accountSid
- Account SID where Flex is runningflexFlowSid
- Flex Flow SID created at onboarding for chatThe accountSid
and flexFlowSid
can be found on the admin dashboard development configuration page: https://flex.twilio.com/admin/developers
ContactCenterManager
was renamed to Manager
Manager.create
method signature - first accountSid
parameter was droppedWe have started using a Twilio configuration service for project-level remote configuration that can be shared between different instances of Flex UI.
fetchConfiguration
asynchronously retrieves a configuration from the Flex Configuration serviceupdateConfig
merges provided configurations on top of any existing configurationThe Task Channel Definition API has been introduced. This is how all of the native channels are defined in Flex UI, and is the advised way of registering your own custom channels or customizing existing ones. The specification can be found here.
SupervisorDesktopView
component was renamed to TeamsView
SideNavSupervisorView
was renamed to SideNavTeamsView
channelType
attribute from a task to detect the chat channel type. Previously, the endpoint
parameter was used.A new Flex API method, progress
, renders a fancy loading indicator to a provided selector: Flex.progress("#container")
Upgrade packages in package.json "@twilio/flex-ui": "^1.0.0",
"react": "^16.5.2",
"react-dom": "^16.5.2"
ADD:"eslintConfig": {
"extends": "react-app"
},
REMOVE: react-app-rewired": "1.5.2”
UPDATE: scripts: {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test"
"eject": "react-scripts eject
}
Update index.js
1import React from "react";2import ReactDOM from "react-dom";3import "regenerator-runtime/runtime";4import * as Flex from "@twilio/flex-ui";5import "./index.css";6import App from "./App";7import registerServiceWorker from "./registerServiceWorker";8const configStorageKey = "FLEX_CONFIG";9const mountNode = document.getElementById("root");10window.onload = () => {11const predefinedConfig = window.appConfig || {};12const runtimeConfig = getRuntimeConfig();13const configuration = {14...predefinedConfig,15...runtimeConfig16};17Flex18.progress(mountNode)19.Manager.create(configuration)20.then(manager => renderApp(manager))21.catch(error => handleError(error));22};23function renderApp(manager) {24ReactDOM.render(25<App manager={manager} />,26mountNode27);28}29function handleError(error) {30console.error("Failed to initialize Flex", error);31const missingAccountSid = error instanceof Flex.ConfigError && error.key === "accountSid";32if (!missingAccountSid) {33throw error;34}35ReactDOM.render(36<Flex.RuntimeLoginView37onSuccess={(loginData, runtimeDomain) => {38setRuntimeConfig(loginData, runtimeDomain);39window.location.reload();40}}41/>,42mountNode43);44}45function setRuntimeConfig(loginData, runtimeDomain) {46const config = {47serviceBaseUrl: runtimeDomain,48sso: {49accountSid: loginData.accountSid50}51};52const serializedConfig = JSON.stringify(config);53localStorage.setItem(configStorageKey, serializedConfig);54}55function getRuntimeConfig() {56const serializedConfig = localStorage.getItem(configStorageKey);57localStorage.removeItem(configStorageKey);58const config = JSON.parse(serializedConfig || "{}");59return config;60}61registerServiceWorker();62
Update App.js
Move customization to manager into the render method below:****
1import React from "react";2import * as Flex from "@twilio/flex-ui";34class App extends React.Component {5render() {6const { manager } = this.props;78if (!manager) {9return null;10}1112return (13<Flex.ContextProvider manager={manager}>14<Flex.RootContainer />15</Flex.ContextProvider>16);17}18}1920export default App;21
Refactor Checklist:
this.props.task.reservation
now can access status like this: this.props.task.status
const reservation = StateHelper.getReservation(task.sid);
now can use this: const reservation = payload.task.sourceObject;
sid
, not taskSid