Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const BotController: React.FC<BotControllerProps> = ({ onHideController, isContr
const [startAllBotsOperationQueued, queueStartAllBots] = useState(false);

const [botsStartOperationCompleted, setBotsStartOperationCompleted] = useState(false);
const [areBotsStarting, setBotsStarting] = useState(false);
const [areBotsProcessing, setBotsProcessing] = useState(false);
const [startPanelButtonText, setStartPanelButtonText] = useState('');
const { startAllBots, stopAllBots } = useBotOperations();
const builderEssentials = useRecoilValue(buildConfigurationSelector);
Expand All @@ -99,7 +99,7 @@ const BotController: React.FC<BotControllerProps> = ({ onHideController, isContr
}, [projectCollection, errors]);

useEffect(() => {
const botsStarting =
const botsProcessing =
startAllBotsOperationQueued ||
projectCollection.some(({ status }) => {
return (
Expand All @@ -111,25 +111,39 @@ const BotController: React.FC<BotControllerProps> = ({ onHideController, isContr
status == BotStatus.stopping
);
});
setBotsStarting(botsStarting);
setBotsProcessing(botsProcessing);

const botOperationsCompleted = projectCollection.some(
({ status }) => status === BotStatus.connected || status === BotStatus.failed
);
setBotsStartOperationCompleted(botOperationsCompleted);

if (botsStarting) {
if (botsProcessing) {
setStatusIconClass(undefined);
setStartPanelButtonText(
formatMessage(
`{
total, plural,
=1 {Starting bot..}
other {Starting bots.. ({running}/{total} running)}
}`,
{ running: runningBots.projectIds.length, total: runningBots.totalBots }
)
);
const botsStopping = projectCollection.some(({ status }) => status == BotStatus.stopping);
if (botsStopping) {
setStartPanelButtonText(
formatMessage(
`{
total, plural,
=1 {Stopping bot..}
other {Stopping bots.. ({running}/{total} running)}
}`,
{ running: runningBots.projectIds.length, total: runningBots.totalBots }
)
);
} else {
setStartPanelButtonText(
formatMessage(
`{
total, plural,
=1 {Starting bot..}
other {Starting bots.. ({running}/{total} running)}
}`,
{ running: runningBots.projectIds.length, total: runningBots.totalBots }
)
);
}
return;
}

Expand Down Expand Up @@ -223,7 +237,7 @@ const BotController: React.FC<BotControllerProps> = ({ onHideController, isContr
aria-roledescription={formatMessage('Bot Controller')}
ariaDescription={startPanelButtonText}
data-testid={'startBotButton'}
disabled={disableStartBots || areBotsStarting}
disabled={disableStartBots || areBotsProcessing}
iconProps={{
iconName: statusIconClass,
styles: {
Expand Down Expand Up @@ -260,7 +274,7 @@ const BotController: React.FC<BotControllerProps> = ({ onHideController, isContr
title={startPanelButtonText}
onClick={handleClick}
>
{areBotsStarting && (
{areBotsProcessing && (
<Spinner
size={SpinnerSize.small}
styles={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,20 @@ describe('<BotController />', () => {
);
await findByText('Starting bots.. (1/4 running)');
});

it('should show bots are stopping', async () => {
const initRecoilState = ({ set }) => {
const projectIds = ['123a.234', '456a.234', '789a.234', '1323.sdf'];
set(botProjectIdsState, projectIds);
set(botStatusState(projectIds[0]), BotStatus.published);
set(botStatusState(projectIds[1]), BotStatus.publishing);
set(botStatusState(projectIds[2]), BotStatus.connected);
set(botStatusState(projectIds[3]), BotStatus.stopping);
};
const { findByText } = renderWithRecoil(
<BotController isControllerHidden={false} onHideController={jest.fn()} />,
initRecoilState
);
await findByText('Stopping bots.. (2/4 running)');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,8 @@ export const publisherDispatcher = () => {
const { set, snapshot } = callbackHelpers;
try {
const currentBotStatus = await snapshot.getPromise(botStatusState(projectId));
if (currentBotStatus !== BotStatus.failed) {
// Change to "Stopping" status only if the Bot is not in a failed state or inactive state
if (currentBotStatus !== BotStatus.failed && currentBotStatus !== BotStatus.inactive) {
set(botStatusState(projectId), BotStatus.stopping);
}

Expand Down
5 changes: 5 additions & 0 deletions Composer/packages/client/src/shell/useShell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import TelemetryClient from '../telemetry/TelemetryClient';
import { lgFilesSelectorFamily } from '../recoilModel/selectors/lg';
import { getMemoryVariables } from '../recoilModel/dispatchers/utils/project';
import { createNotification } from '../recoilModel/dispatchers/notification';
import { useBotOperations } from '../components/BotRuntimeController/useBotOperations';

import { useLgApi } from './lgApi';
import { useLuApi } from './luApi';
Expand Down Expand Up @@ -141,6 +142,7 @@ export function useShell(source: EventSource, projectId: string): Shell {
const triggerApi = useTriggerApi(projectId);
const actionApi = useActionApi(projectId);
const { dialogId, selected, focused, promptTab } = designPageLocation;
const { stopSingleBot } = useBotOperations();

const dialogsMap = useMemo(() => {
return dialogs.reduce((result, dialog) => {
Expand Down Expand Up @@ -270,6 +272,9 @@ export function useShell(source: EventSource, projectId: string): Shell {
},
updateFlowZoomRate,
reloadProject: () => reloadProject(projectId),
stopBot: async (targetProjectId?: string) => {
stopSingleBot(targetProjectId ?? projectId);
},
...lgApi,
...luApi,
...qnaApi,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const PROJECT_KEYS = [
'api.updateDialogSchema',
'api.createTrigger',
'api.createQnATrigger',
'api.stopBot',
'api.updateSkill',
'api.updateRecognizer',
];
Expand Down
1 change: 1 addition & 0 deletions Composer/packages/types/src/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export type ProjectContextApi = {
updateDialogSchema: (_: DialogSchemaFile) => Promise<void>;
createTrigger: (id: string, formData, autoSelected?: boolean) => void;
createQnATrigger: (id: string) => void;
stopBot: (projectId?: string) => void;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove the conditional projectId since we are passing currentProjectID to the function @tonyanziano ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure

updateSkill: (skillId: string, skillsData: { skill: Skill; selectedEndpointIndex: number }) => Promise<void>;
updateRecognizer: (projectId: string, dialogId: string, kind: LuProviderType) => void;
};
Expand Down
5 changes: 3 additions & 2 deletions extensions/packageManager/src/pages/Library.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export interface PackageSourceFeed extends IDropdownOption {

const Library: React.FC = () => {
const [items, setItems] = useState<LibraryRef[]>([]);
const { projectId, reloadProject, projectCollection: allProjectCollection } = useProjectApi();
const { projectId, reloadProject, projectCollection: allProjectCollection, stopBot } = useProjectApi();
const { setApplicationLevelError, navigateTo, confirm } = useApplicationApi();
const telemetryClient: TelemetryClient = useTelemetryClient();

Expand Down Expand Up @@ -391,6 +391,7 @@ const Library: React.FC = () => {

const importComponent = async (packageName, version, isUpdating, source) => {
try {
stopBot(currentProjectId);
const results = await installComponentAPI(currentProjectId, packageName, version, isUpdating, source);

// check to see if there was a conflict that requires confirmation
Expand Down Expand Up @@ -424,7 +425,6 @@ const Library: React.FC = () => {
setReadmeHidden(false);
}

// reload modified content
await reloadProject();
}
} catch (err) {
Expand Down Expand Up @@ -515,6 +515,7 @@ const Library: React.FC = () => {
closeDialog();
setWorking(strings.uninstallProgress);
try {
stopBot(currentProjectId);
const results = await uninstallComponentAPI(currentProjectId, selectedItem.name);

if (results.data.success) {
Expand Down