Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PoC] First implementation Worker Availability #597

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions plugin-hrm-form/src/HrmFormPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ const setUpComponents = setupObject => {
Components.setUpStandaloneSearch();

if (featureFlags.enable_canned_responses) Components.setupCannedResponses();

// set feature flag here
if (true) Components.setUpWorkerStatusHandler();
};

/**
Expand Down
31 changes: 23 additions & 8 deletions plugin-hrm-form/src/components/ManualPullButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,44 @@ type Props = OwnProps & ReturnType<typeof mapStateToProps>;
const ManualPullButton: React.FC<Props> = ({ queuesStatusState, chatChannelCapacity, worker, workerClient }) => {
const [isWaitingNewTask, setWaitingNewTask] = useState(false);

const [, availableActivity] = React.useMemo(() => {
return Array.from(workerClient.activities).find(([k, v]) => v.name === 'Available');
}, [workerClient.activities]);

// move to utils for reusability
const setKeepAvailable = async () => {
const attributes = { ...workerClient.attributes, keepAvailable: true };
await workerClient.setAttributes(attributes);
};

// move to utils for reusability
const unsetKeepAvailable = async () => {
const { keepAvailable, ...attributes } = workerClient.attributes;
await workerClient.setAttributes(attributes);
};

// Increase chat capacity, if no reservation is created within 5 seconds, capacity is decreased and shows a notification.
const increaseChatCapacity = async () => {
setWaitingNewTask(true);
let alertTimeout = null;

const cancelTimeout = () => {
const cancelTimeout = async () => {
setWaitingNewTask(false);
clearTimeout(alertTimeout);
await unsetKeepAvailable();
};

alertTimeout = setTimeout(async () => {
setWaitingNewTask(false);
workerClient.removeListener('reservationCreated', cancelTimeout);
Notifications.showNotification('NoTaskAssignableNotification');
await adjustChatCapacity('decrease');
await Promise.all([unsetKeepAvailable(), adjustChatCapacity('decrease')]);
}, 5000);

workerClient.once('reservationCreated', cancelTimeout);

await adjustChatCapacity('increase');
await Promise.all([setKeepAvailable(), adjustChatCapacity('increase')]);
await availableActivity.setAsCurrent();
};

const { maxMessageCapacity } = worker.attributes;
Expand All @@ -46,11 +64,8 @@ const ManualPullButton: React.FC<Props> = ({ queuesStatusState, chatChannelCapac
const noTasks = worker.tasks.size === 0;

const disabled =
!isAvailable ||
noTasks ||
maxCapacityReached ||
!isAnyChatPending(queuesStatusState.queuesStatus) ||
isWaitingNewTask;
// !isAvailable || this should change to "not is ready"
noTasks || maxCapacityReached || !isAnyChatPending(queuesStatusState.queuesStatus) || isWaitingNewTask;

return (
<AddTaskButton
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { connect, ConnectedProps } from 'react-redux';

import type { RootState } from '../../states';

type OwnProps = {
workerClient: import('@twilio/flex-ui').Manager['workerClient'];
};

const mapStateToProps = (state: RootState, ownProps: OwnProps) => {
const { worker } = state.flex;

return { worker };
};

const connector = connect(mapStateToProps);
type Props = OwnProps & ConnectedProps<typeof connector>;

const WorkerStatusHandler: React.FC<Props> = ({ worker, workerClient }) => {
const [availableActivity, offlineActivity] = React.useMemo(() => {
const activitiesArray = Array.from(workerClient.activities);
const [, available] = activitiesArray.find(([k, v]) => v.name === 'Available');
const [, offline] = activitiesArray.find(([k, v]) => v.name === 'Offline');
return [available, offline];
}, [workerClient.activities]);

const noTasks = worker.tasks.size === 0;
const { available } = worker.activity;
const { attributes } = worker;
const isSomeTaskPending = Array.from(worker.tasks).some(([, t]) => t.status === 'pending');

const shouldSetAvailable = noTasks && !available; // here we want to also consider a worker attribute to indicate if should stay offline or if it's "ready to work"
const shouldSetOffline = available && !noTasks && !worker.attributes.keepAvailable && !isSomeTaskPending;

console.log('>>>> shouldSetAvailable', shouldSetAvailable);
console.log('>>>> shouldSetOffline', shouldSetOffline);

React.useEffect(() => {
const handleActivities = async () => {
if (shouldSetAvailable) {
console.log('>>>> setting available');
await availableActivity.setAsCurrent();
} else if (shouldSetOffline) {
console.log('>>>> setting offline');
await offlineActivity.setAsCurrent();
}
};

try {
handleActivities();
} catch (err) {
console.log('>>>> err on handleActivities', err);
}
}, [availableActivity, offlineActivity, shouldSetAvailable, shouldSetOffline]);

return null;
};
WorkerStatusHandler.displayName = 'WorkerStatusHandler';

export default connector(WorkerStatusHandler);
14 changes: 14 additions & 0 deletions plugin-hrm-form/src/utils/setUpComponents.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as TransferHelpers from './transfer';
import CannedResponses from '../components/CannedResponses';
import QueuesStatusWriter from '../components/queuesStatus/QueuesStatusWriter';
import QueuesStatus from '../components/queuesStatus';
import WorkerStatusHandler from '../components/WorkerStatusHandler/WorkerStatusHandler';
import CustomCRMContainer from '../components/CustomCRMContainer';
import LocalizationContext from '../contexts/LocalizationContext';
import Translator from '../components/translator';
Expand Down Expand Up @@ -94,6 +95,19 @@ export const setUpQueuesStatusWriter = setupObject => {
);
};

/**
* Add an "invisible" component that tracks the state of the worker, setting the status activity accordingly
*/
export const setUpWorkerStatusHandler = () => {
Flex.MainContainer.Content.add(
<WorkerStatusHandler workerClient={Flex.Manager.getInstance().workerClient} key="worker-status-handler" />,
{
sortOrder: -1,
align: 'start',
},
);
};

// Re-renders UI if there is a new reservation created and no active tasks (avoid a visual bug with QueuesStatus when there are no tasks)
const setUpRerenderOnReservation = () => {
const manager = Flex.Manager.getInstance();
Expand Down