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:
_27const reducers = combineReducers({_27 flex: Flex.FlexReducer,_27 app: myReducer_27});_27_27const middleware = applyMiddleware();_27_27const store = createStore(_27 reducers,_27 compose(_27 middleware,_27 Flex.applyFlexMiddleware()_27 )_27);_27_27Flex_27 .Manager.create(configuration, store)_27 .then(manager => {_27 ReactDOM.render(_27 <Provider store={store}>_27 <Flex.ContextProvider manager={manager}>_27 <Flex.RootContainer />_27 </Flex.ContextProvider>_27 </Provider>,_27 container_27 );_27 })
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 theme
colors
: object - to define a set of base colors that are used throughout the UI to define your custom theme
light
: 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 more
overrides
: object - a set of style overrides for each component
An example of setting the color configurations in appConfig
:
_32config.colorTheme = {_32 baseName: "DarkTheme",_32 colors: {_32 base1: "blue",_32 base2: "orange",_32 base3: "yellow",_32 base4: "black",_32 base5: "white",_32 base6: "pink",_32 base7: "red",_32 base8: "blue",_32 base9: "brown",_32 base10: "black",_32 base11: "white",_32 },_32 light: false,_32 overrides: {_32 MainHeader: {_32 Container: {_32 background: "#35372c"_32 }_32 },_32 SideNav: {_32 Container: {_32 background: "#35372c"_32 },_32 Button: {_32 background: "35372c"_32 },_32 },_32 }_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 name
Support 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:
_41const reducers = combineReducers({_41 flex: Flex.FlexReducer,_41 app: myReducer_41});_41_41const history = createHistory();_41_41const middleware = applyMiddleware();_41_41const store = createStore(_41 reducers,_41 compose(_41 middleware,_41 Flex.applyFlexMiddleware(history)_41 )_41);_41_41Flex_41 .Manager.create(configuration, store)_41 .then(manager => {_41 ReactDOM.render(_41 <Provider store={store}>_41 <ConnectedRouter history={history}>_41 <Switch>_41 <Route path="/hi" component={() => {_41 setTimeout(() => { history.push("/"); }, 5000);_41 return (_41 <div>Hi! I will redirect to Flex in 5 seconds flat!</div>_41 );_41 }}></Route>_41 <Route component={() => {_41 return (<Flex.ContextProvider manager={manager}>_41 <Flex.RootContainer />_41 </Flex.ContextProvider>);_41 }}></Route>_41 </Switch>_41 </ConnectedRouter>_41 </Provider>,_41 container_41 );_41 })
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
instead
SetActivity
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 params 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 running
flexFlowSid
- Flex Flow SID created at onboarding for chat
The 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 dropped
We 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 service
updateConfig
merges provided configurations on top of any existing configuration
The 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")
"@twilio/flex-ui": "^1.0.0",
"react": "^16.5.2",
"react-dom": "^16.5.2"
"eslintConfig": {
"extends": "react-app"
},
react-app-rewired": "1.5.2”
scripts: {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test"
"eject": "react-scripts eject
}
Update index.js
_61import React from "react";_61import ReactDOM from "react-dom";_61import "regenerator-runtime/runtime";_61import * as Flex from "@twilio/flex-ui";_61import "./index.css";_61import App from "./App";_61import registerServiceWorker from "./registerServiceWorker";_61 const configStorageKey = "FLEX_CONFIG";_61const mountNode = document.getElementById("root");_61 window.onload = () => {_61 const predefinedConfig = window.appConfig || {};_61 const runtimeConfig = getRuntimeConfig();_61 const configuration = {_61 ...predefinedConfig,_61 ...runtimeConfig_61 };_61 Flex_61 .progress(mountNode)_61 .Manager.create(configuration)_61 .then(manager => renderApp(manager))_61 .catch(error => handleError(error));_61};_61 function renderApp(manager) {_61 ReactDOM.render(_61 <App manager={manager} />,_61 mountNode_61 );_61}_61 function handleError(error) {_61 console.error("Failed to initialize Flex", error);_61 const missingAccountSid = error instanceof Flex.ConfigError && error.key === "accountSid";_61 if (!missingAccountSid) {_61 throw error;_61 }_61 ReactDOM.render(_61 <Flex.RuntimeLoginView_61 onSuccess={(loginData, runtimeDomain) => {_61 setRuntimeConfig(loginData, runtimeDomain);_61 window.location.reload();_61 }}_61 />,_61 mountNode_61 );_61}_61 function setRuntimeConfig(loginData, runtimeDomain) {_61 const config = {_61 serviceBaseUrl: runtimeDomain,_61 sso: {_61 accountSid: loginData.accountSid_61 }_61 };_61 const serializedConfig = JSON.stringify(config);_61 localStorage.setItem(configStorageKey, serializedConfig);_61}_61 function getRuntimeConfig() {_61 const serializedConfig = localStorage.getItem(configStorageKey);_61 localStorage.removeItem(configStorageKey);_61 const config = JSON.parse(serializedConfig || "{}");_61 return config;_61}_61 registerServiceWorker();
Update App.js
Move customization to manager into the render method below:****
_20import React from "react";_20import * as Flex from "@twilio/flex-ui";_20_20class App extends React.Component {_20 render() {_20 const { manager } = this.props;_20_20 if (!manager) {_20 return null;_20 }_20_20 return (_20 <Flex.ContextProvider manager={manager}>_20 <Flex.RootContainer />_20 </Flex.ContextProvider>_20 );_20 }_20}_20_20export default App;
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