Skip to content

Commit

Permalink
feat: Polling creation process (#4804)
Browse files Browse the repository at this point in the history
* Added status controller for front end process status queries
Added backgroundprocessmanager as a service used for process tracking
Added routing to the controller

* Cleaning up unused aspects of ported polling pattern

* creating new 'create bot' server endpoint for new creation experinece back end
Refactoring exisitng endpoint for code sharing

* scaffolding new creation backend flow

* Implemented new backend creation flow with polling

* minor bug fix

* Stronger typing and minor clean up

* minor changes for clean 'yarn typecheck'

* Fixing server response status and nit clean up

* foxing return statement

* Update project.ts

Removed unnecessary check

Co-authored-by: Patrick Volum <pavolum@microsoft.com>
Co-authored-by: Soroush <hatpick@gmail.com>
  • Loading branch information
3 people authored and EricDahlvang committed Nov 27, 2020
1 parent 02e393c commit 7aec61e
Show file tree
Hide file tree
Showing 14 changed files with 437 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const CreationFlow: React.FC<CreationFlowProps> = () => {
createNewBot,
saveProjectAs,
fetchProjectById,
createNewBotV2,
} = useRecoilValue(dispatcherState);

const templateProjects = useRecoilValue(filteredTemplatesSelector);
Expand Down Expand Up @@ -127,7 +128,11 @@ const CreationFlow: React.FC<CreationFlowProps> = () => {
alias: formData.alias,
preserveRoot: formData.preserveRoot,
};
createNewBot(newBotData);
if (templateId === 'conversationalcore') {
createNewBotV2(newBotData);
} else {
createNewBot(newBotData);
}
};

const handleSaveAs = (formData) => {
Expand Down
6 changes: 6 additions & 0 deletions Composer/packages/client/src/recoilModel/atoms/appState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { atom, atomFamily } from 'recoil';
import { FormDialogSchemaTemplate, FeatureFlagMap, BotTemplate, UserSettings } from '@bfc/shared';
import { ExtensionMetadata } from '@bfc/extension-client';
import formatMessage from 'format-message';

import {
StorageFolder,
Expand Down Expand Up @@ -212,6 +213,11 @@ export const botOpeningState = atom<boolean>({
default: false,
});

export const botOpeningMessage = atom({
key: getFullyQualifiedKey('botOpeningMessage'),
default: formatMessage('Loading'),
});

export const formDialogLibraryTemplatesState = atom<FormDialogSchemaTemplate[]>({
key: getFullyQualifiedKey('formDialogLibraryTemplates'),
default: [],
Expand Down
107 changes: 102 additions & 5 deletions Composer/packages/client/src/recoilModel/dispatchers/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { BotProjectFile } from '@bfc/shared';
import formatMessage from 'format-message';
import findIndex from 'lodash/findIndex';
import { CallbackInterface, useRecoilCallback } from 'recoil';
Expand All @@ -17,7 +18,9 @@ import qnaFileStatusStorage from '../../utils/qnaFileStatusStorage';
import {
botErrorState,
botNameIdentifierState,
botOpeningMessage,
botOpeningState,
botProjectFileState,
botProjectIdsState,
botProjectSpaceLoadedState,
botStatusState,
Expand All @@ -32,6 +35,7 @@ import { logMessage, setError } from './../dispatchers/shared';
import {
checkIfBotExistsInBotProjectFile,
createNewBotFromTemplate,
createNewBotFromTemplateV2,
fetchProjectDataById,
flushExistingTasks,
getSkillNameIdentifier,
Expand Down Expand Up @@ -144,7 +148,7 @@ export const projectDispatcher = () => {
const { set, snapshot } = callbackHelpers;
const dispatcher = await snapshot.getPromise(dispatcherState);
try {
const { templateId, name, description, location, schemaUrl, locale, qnaKbUrls } = newProjectData;
const { templateId, name, description, location, schemaUrl, locale } = newProjectData;
set(botOpeningState, true);

const { projectId, mainDialog } = await createNewBotFromTemplate(
Expand All @@ -164,7 +168,7 @@ export const projectDispatcher = () => {
});
set(botProjectIdsState, (current) => [...current, projectId]);
await dispatcher.addLocalSkillToBotProjectFile(projectId);
navigateToBot(callbackHelpers, projectId, mainDialog, qnaKbUrls, templateId);
navigateToBot(callbackHelpers, projectId, mainDialog);
return projectId;
} catch (ex) {
handleProjectFailure(callbackHelpers, ex);
Expand Down Expand Up @@ -236,7 +240,6 @@ export const projectDispatcher = () => {
location,
schemaUrl,
locale,
qnaKbUrls,
templateDir,
eTag,
urlSuffix,
Expand Down Expand Up @@ -264,7 +267,7 @@ export const projectDispatcher = () => {
isRemote: false,
});
projectIdCache.set(projectId);
navigateToBot(callbackHelpers, projectId, mainDialog, qnaKbUrls, templateId, urlSuffix);
navigateToBot(callbackHelpers, projectId, mainDialog, urlSuffix);
} catch (ex) {
set(botProjectIdsState, []);
handleProjectFailure(callbackHelpers, ex);
Expand All @@ -274,6 +277,49 @@ export const projectDispatcher = () => {
}
});

const createNewBotV2 = useRecoilCallback((callbackHelpers: CallbackInterface) => async (newProjectData: any) => {
const { set, snapshot } = callbackHelpers;
try {
await flushExistingTasks(callbackHelpers);
const dispatcher = await snapshot.getPromise(dispatcherState);
set(botOpeningState, true);
const {
templateId,
name,
description,
location,
schemaUrl,
locale,
templateDir,
eTag,
urlSuffix,
alias,
preserveRoot,
} = newProjectData;
// starts the creation process and stores the jobID in state for tracking
const response = await createNewBotFromTemplateV2(
callbackHelpers,
templateId,
name,
description,
location,
schemaUrl,
locale,
templateDir,
eTag,
alias,
preserveRoot
);
if (response.data.jobId) {
dispatcher.updateCreationMessage(response.data.jobId, templateId, urlSuffix);
}
} catch (ex) {
set(botProjectIdsState, []);
handleProjectFailure(callbackHelpers, ex);
navigateTo('/home');
}
});

const saveProjectAs = useRecoilCallback(
(callbackHelpers: CallbackInterface) => async (oldProjectId, name, description, location) => {
const { set } = callbackHelpers;
Expand Down Expand Up @@ -365,7 +411,7 @@ export const projectDispatcher = () => {

const reloadProject = async (callbackHelpers: CallbackInterface, response: any) => {
callbackHelpers.reset(filePersistenceState(response.data.id));
const { projectData, botFiles } = loadProjectData(response);
const { projectData, botFiles } = loadProjectData(response.data);

await initBotState(callbackHelpers, projectData, botFiles);
};
Expand All @@ -377,9 +423,59 @@ export const projectDispatcher = () => {
await initBotState(callbackHelpers, projectData, botFiles);
});

const updateCreationMessage = useRecoilCallback(
(callbackHelpers: CallbackInterface) => async (jobId: string, templateId: string, urlSuffix: string) => {
const timer = setInterval(async () => {
try {
const response = await httpClient.get(`/status/${jobId}`);
if (response.data?.httpStatusCode === 200 && response.data.result) {
// Bot creation successful
clearInterval(timer);
callbackHelpers.set(botOpeningMessage, response.data.latestMessage);
const { botFiles, projectData } = loadProjectData(response.data.result);
const projectId = response.data.result.id;
if (settingStorage.get(projectId)) {
settingStorage.remove(projectId);
}
const currentBotProjectFileIndexed: BotProjectFile = botFiles.botProjectSpaceFiles[0];
callbackHelpers.set(botProjectFileState(projectId), currentBotProjectFileIndexed);

const mainDialog = await initBotState(callbackHelpers, projectData, botFiles);
callbackHelpers.set(botProjectIdsState, [projectId]);

// Post project creation
callbackHelpers.set(projectMetaDataState(projectId), {
isRootBot: true,
isRemote: false,
});
projectIdCache.set(projectId);
navigateToBot(callbackHelpers, projectId, mainDialog, urlSuffix);
callbackHelpers.set(botOpeningMessage, '');
callbackHelpers.set(botOpeningState, false);
} else {
if (response.data.httpStatusCode !== 500) {
// pending
callbackHelpers.set(botOpeningMessage, response.data.latestMessage);
} else {
// failure
callbackHelpers.set(botOpeningMessage, response.data.latestMessage);
clearInterval(timer);
}
}
} catch (err) {
clearInterval(timer);
callbackHelpers.set(botProjectIdsState, []);
handleProjectFailure(callbackHelpers, err);
navigateTo('/home');
}
}, 5000);
}
);

return {
openProject,
createNewBot,
createNewBotV2,
deleteBot,
saveProjectAs,
fetchProjectById,
Expand All @@ -395,5 +491,6 @@ export const projectDispatcher = () => {
replaceSkillInBotProject,
reloadProject,
reloadExistingProject,
updateCreationMessage,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,6 @@ export const navigateToBot = (
callbackHelpers: CallbackInterface,
projectId: string,
mainDialog: string,
qnaKbUrls?: string[],
templateId?: string,
urlSuffix?: string
) => {
if (projectId) {
Expand All @@ -161,8 +159,8 @@ export const navigateToBot = (
}
};

export const loadProjectData = (response) => {
const { files, botName, settings, skills: skillContent, id: projectId } = response.data;
export const loadProjectData = (data) => {
const { files, botName, settings, skills: skillContent, id: projectId } = data;
const mergedSettings = getMergedSettings(projectId, settings);
const storedLocale = languageStorage.get(botName)?.locale;
const locale = settings.languages.includes(storedLocale) ? storedLocale : settings.defaultLanguage;
Expand All @@ -174,7 +172,7 @@ export const loadProjectData = (response) => {

return {
botFiles: { ...indexedFiles, qnaFiles: updateQnAFiles, mergedSettings },
projectData: response.data,
projectData: data,
error: undefined,
};
};
Expand All @@ -185,7 +183,7 @@ export const fetchProjectDataByPath = async (
): Promise<{ botFiles: any; projectData: any; error: any }> => {
try {
const response = await httpClient.put(`/projects/open`, { path, storageId });
const projectData = loadProjectData(response);
const projectData = loadProjectData(response.data);
return projectData;
} catch (ex) {
return {
Expand All @@ -199,7 +197,7 @@ export const fetchProjectDataByPath = async (
export const fetchProjectDataById = async (projectId): Promise<{ botFiles: any; projectData: any; error: any }> => {
try {
const response = await httpClient.get(`/projects/${projectId}`);
const projectData = loadProjectData(response);
const projectData = loadProjectData(response.data);
return projectData;
} catch (ex) {
return {
Expand Down Expand Up @@ -434,7 +432,7 @@ export const createNewBotFromTemplate = async (
alias,
preserveRoot,
});
const { botFiles, projectData } = loadProjectData(response);
const { botFiles, projectData } = loadProjectData(response.data);
const projectId = response.data.id;
if (settingStorage.get(projectId)) {
settingStorage.remove(projectId);
Expand All @@ -451,6 +449,35 @@ export const createNewBotFromTemplate = async (
return { projectId, mainDialog };
};

export const createNewBotFromTemplateV2 = async (
callbackHelpers,
templateId: string,
name: string,
description: string,
location: string,
schemaUrl?: string,
locale?: string,
templateDir?: string,
eTag?: string,
alias?: string,
preserveRoot?: boolean
) => {
const jobId = await httpClient.post(`/v2/projects`, {
storageId: 'default',
templateId,
name,
description,
location,
schemaUrl,
locale,
templateDir,
eTag,
alias,
preserveRoot,
});
return jobId;
};

const addProjectToBotProjectSpace = (set, projectId: string, skillCt: number) => {
let isBotProjectLoaded = false;
set(botProjectIdsState, (current: string[]) => {
Expand Down Expand Up @@ -559,7 +586,7 @@ export const saveProject = async (callbackHelpers, oldProjectData) => {
description,
location,
});
const data = loadProjectData(response);
const data = loadProjectData(response.data);
if (data.error) {
throw data.error;
}
Expand Down
12 changes: 10 additions & 2 deletions Composer/packages/client/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ import { resolveToBasePath } from './utils/fileUtil';
import { data } from './styles';
import { NotFound } from './components/NotFound';
import { BASEPATH } from './constants';
import { dispatcherState, schemasState, botProjectIdsState, botOpeningState, pluginPagesSelector } from './recoilModel';
import {
dispatcherState,
schemasState,
botProjectIdsState,
botOpeningState,
pluginPagesSelector,
botOpeningMessage,
} from './recoilModel';
import { openAlertModal } from './components/Modal/AlertDialog';
import { dialogStyle } from './components/Modal/dialogStyle';
import { LoadingSpinner } from './components/LoadingSpinner';
Expand All @@ -32,6 +39,7 @@ const FormDialogPage = React.lazy(() => import('./pages/form-dialog/FormDialogPa
const Routes = (props) => {
const botOpening = useRecoilValue(botOpeningState);
const pluginPages = useRecoilValue(pluginPagesSelector);
const spinnerText = useRecoilValue(botOpeningMessage);

return (
<div css={data}>
Expand Down Expand Up @@ -87,7 +95,7 @@ const Routes = (props) => {
<div
css={{ position: 'absolute', top: 0, left: 0, bottom: 0, right: 0, background: 'rgba(255, 255, 255, 0.6)' }}
>
<LoadingSpinner />
<LoadingSpinner message={spinnerText} />
</div>
)}
</div>
Expand Down
Loading

0 comments on commit 7aec61e

Please sign in to comment.