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 @@ -3,7 +3,7 @@

/** @jsx jsx */
import { jsx, css } from '@emotion/core';
import { useState, Fragment, useEffect, useMemo } from 'react';
import { useState, Fragment, useEffect, useMemo, SetStateAction, Dispatch } from 'react';
import find from 'lodash/find';
import formatMessage from 'format-message';
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
Expand Down Expand Up @@ -109,17 +109,18 @@ const optionKeys = {
// -------------------- CreateOptions -------------------- //
type CreateOptionsProps = {
templates: BotTemplate[];
selectedTemplateId: string;
setSelectedTemplateId: Dispatch<SetStateAction<string>>;
onDismiss: () => void;
onNext: (data: string) => void;
onNext: () => void;
fetchTemplates: (feedUrls?: string[]) => Promise<void>;
fetchReadMe: (moduleName: string) => {};
} & RouteComponentProps<{}>;

export function CreateOptionsV2(props: CreateOptionsProps) {
const [option] = useState(optionKeys.createFromTemplate);
const [disabled] = useState(false);
const { templates, onDismiss, onNext } = props;
const [currentTemplate, setCurrentTemplate] = useState('');
const { templates, selectedTemplateId, onDismiss, onNext, setSelectedTemplateId } = props;
const [emptyBotKey, setEmptyBotKey] = useState('');
const [selectedFeed, setSelectedFeed] = useState<{ props: IPivotItemProps }>({ props: { itemKey: csharpFeedKey } });
const readMe = useRecoilValue(selectedTemplateReadMeState);
Expand All @@ -129,29 +130,16 @@ export function CreateOptionsV2(props: CreateOptionsProps) {
onSelectionChanged: () => {
const t = selectedTemplate.getSelection()[0] as BotTemplate;
if (t) {
setCurrentTemplate(t.id);
setSelectedTemplateId(t.id);
}
},
});
}, []);

const handleJumpToNext = () => {
let routeToTemplate = emptyBotKey;
if (option === optionKeys.createFromTemplate) {
routeToTemplate = currentTemplate;
}

if (option === optionKeys.createFromQnA) {
routeToTemplate = QnABotTemplateId;
}
TelemetryClient.track('CreateNewBotProjectNextButton', { template: selectedTemplateId });

if (props.location && props.location.search) {
routeToTemplate += props.location.search;
}

TelemetryClient.track('CreateNewBotProjectNextButton', { template: routeToTemplate });

onNext(routeToTemplate);
onNext();
};

const tableColumns = [
Expand Down Expand Up @@ -185,7 +173,7 @@ export function CreateOptionsV2(props: CreateOptionsProps) {
if (templates.length > 1) {
const emptyBotTemplate = find(templates, ['id', EmptyBotTemplateId]);
if (emptyBotTemplate) {
setCurrentTemplate(emptyBotTemplate.id);
setSelectedTemplateId(emptyBotTemplate.id);
setEmptyBotKey(emptyBotTemplate.id);
}
}
Expand All @@ -198,10 +186,10 @@ export function CreateOptionsV2(props: CreateOptionsProps) {
}, [selectedFeed]);

useEffect(() => {
if (currentTemplate) {
props.fetchReadMe(currentTemplate);
if (selectedTemplateId) {
props.fetchReadMe(selectedTemplateId);
}
}, [currentTemplate]);
}, [selectedTemplateId]);

return (
<Fragment>
Expand Down Expand Up @@ -242,14 +230,16 @@ export function CreateOptionsV2(props: CreateOptionsProps) {
</ScrollablePane>
</div>
<div css={templateDetailContainer} data-is-scrollable="true">
<TemplateDetailView readMe={readMe} templateId={currentTemplate} />
<TemplateDetailView readMe={readMe} templateId={selectedTemplateId} />
</div>
</div>
<DialogFooter>
<DefaultButton text={formatMessage('Cancel')} onClick={onDismiss} />
<PrimaryButton
data-testid="NextStepButton"
disabled={option === optionKeys.createFromTemplate && (templates.length <= 0 || currentTemplate === null)}
disabled={
option === optionKeys.createFromTemplate && (templates.length <= 0 || selectedTemplateId === null)
}
text={formatMessage('Next')}
onClick={handleJumpToNext}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import Path from 'path';

import React, { useEffect, useRef, Fragment } from 'react';
import React, { useEffect, useRef, Fragment, useState } from 'react';
import { RouteComponentProps, Router, navigate } from '@reach/router';
import { useRecoilValue } from 'recoil';
import { BotTemplate } from '@bfc/shared';
Expand Down Expand Up @@ -54,6 +54,7 @@ const CreationFlowV2: React.FC<CreationFlowProps> = () => {
const focusedStorageFolder = useRecoilValue(focusedStorageFolderState);
const { appLocale } = useRecoilValue(userSettingsState);
const cachedProjectId = useProjectIdCache();
const [selectedTemplateId, setSelectedTemplateId] = useState('');
const currentStorageIndex = useRef(0);
const storage = storages[currentStorageIndex.current];
const currentStorageId = storage ? storage.id : 'default';
Expand Down Expand Up @@ -116,6 +117,7 @@ const CreationFlowV2: React.FC<CreationFlowProps> = () => {
description: formData.description,
location: formData.location,
schemaUrl: formData.schemaUrl,
runtimeChoice: formData.runtimeChoice,
appLocale,
qnaKbUrls,
templateDir: formData?.pvaData?.templateDir,
Expand Down Expand Up @@ -146,9 +148,9 @@ const CreationFlowV2: React.FC<CreationFlowProps> = () => {
}
};

const handleCreateNext = async (data: string) => {
const handleCreateNext = async () => {
setCreationFlowStatus(CreationFlowStatus.NEW_FROM_TEMPLATE);
navigate(`./create/${data}`);
navigate(`./create/template`);
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if some of this could have been avoided by doing an encodeURIcomponent on the name value...

Copy link
Contributor

Choose a reason for hiding this comment

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

I confirmed this was the case.

};

return (
Expand All @@ -158,7 +160,8 @@ const CreationFlowV2: React.FC<CreationFlowProps> = () => {
<DefineConversationV2
createFolder={createFolder}
focusedStorageFolder={focusedStorageFolder}
path="create/:templateId"
path="create/template"
selectedTemplateId={selectedTemplateId}
updateFolder={updateFolder}
onCurrentPathUpdate={updateCurrentPath}
onDismiss={handleDismiss}
Expand All @@ -168,14 +171,17 @@ const CreationFlowV2: React.FC<CreationFlowProps> = () => {
fetchReadMe={fetchReadMe}
fetchTemplates={fetchTemplatesV2}
path="create"
selectedTemplateId={selectedTemplateId}
setSelectedTemplateId={setSelectedTemplateId}
templates={templateProjects}
onDismiss={handleDismiss}
onNext={handleCreateNext}
/>
<DefineConversationV2
createFolder={createFolder}
focusedStorageFolder={focusedStorageFolder}
path=":projectId/:templateId/save"
path=":projectId/template/save"
selectedTemplateId={selectedTemplateId}
updateFolder={updateFolder}
onCurrentPathUpdate={updateCurrentPath}
onDismiss={handleDismiss}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ import {
mockLanguageOptions,
runtimeOptions,
defaultPrimaryLanguage,
defaultRuntime,
} from '../../../constants';
import { FieldConfig, useForm } from '../../../hooks/useForm';
import { StorageFolder } from '../../../recoilModel/types';
import { createNotification } from '../../../recoilModel/dispatchers/notification';
import { ImportSuccessNotificationWrapper } from '../../ImportModal/ImportSuccessNotification';
import { dispatcherState } from '../../../recoilModel';
import { LocationSelectContent } from '../LocationSelectContent';
import { RuntimeType, webAppRuntimeKey } from '@bfc/shared';

// -------------------- Styles -------------------- //

Expand Down Expand Up @@ -79,7 +79,7 @@ type DefineConversationFormData = {
description: string;
schemaUrl: string;
primaryLanguage: string;
runtimeChoice: string;
runtimeChoice: RuntimeType;
location?: string;
templateVersion?: string;

Expand All @@ -100,8 +100,8 @@ type DefineConversationProps = {
onCurrentPathUpdate: (newPath?: string, storageId?: string) => void;
onGetErrorMessage?: (text: string) => void;
focusedStorageFolder: StorageFolder;
selectedTemplateId: string;
} & RouteComponentProps<{
templateId: string;
location: string;
}>;

Expand All @@ -110,7 +110,7 @@ const DefineConversationV2: React.FC<DefineConversationProps> = (props) => {
onSubmit,
onDismiss,
onCurrentPathUpdate,
templateId,
selectedTemplateId,
focusedStorageFolder,
createFolder,
updateFolder,
Expand All @@ -127,7 +127,7 @@ const DefineConversationV2: React.FC<DefineConversationProps> = (props) => {

const getDefaultName = () => {
let i = -1;
const bot = normalizeTemplateId(templateId);
const bot = normalizeTemplateId(selectedTemplateId);
let defaultName = '';
do {
i++;
Expand Down Expand Up @@ -202,7 +202,7 @@ const DefineConversationV2: React.FC<DefineConversationProps> = (props) => {
const formData: DefineConversationFormData = {
name: getDefaultName(),
primaryLanguage: defaultPrimaryLanguage,
runtimeChoice: defaultRuntime,
runtimeChoice: webAppRuntimeKey,
description: '',
schemaUrl: '',
location:
Expand All @@ -228,7 +228,7 @@ const DefineConversationV2: React.FC<DefineConversationProps> = (props) => {
}
}
updateForm(formData);
}, [templateId]);
}, [selectedTemplateId]);

useEffect(() => {
validateForm();
Expand Down Expand Up @@ -269,7 +269,7 @@ const DefineConversationV2: React.FC<DefineConversationProps> = (props) => {
}
}

onSubmit({ ...dataToSubmit }, templateId || '');
onSubmit({ ...dataToSubmit }, selectedTemplateId || '');
},
[hasErrors, formData]
);
Expand Down Expand Up @@ -332,6 +332,7 @@ const DefineConversationV2: React.FC<DefineConversationProps> = (props) => {
<Stack horizontal styles={stackinput} tokens={{ childrenGap: '2rem' }}>
<StackItem grow={0} styles={halfstack}>
<Dropdown
disabled
data-testid="NewDialogPrimaryLanguage"
label={formatMessage('Primary Language')}
options={mockLanguageOptions}
Expand All @@ -355,7 +356,7 @@ const DefineConversationV2: React.FC<DefineConversationProps> = (props) => {
<PrimaryButton
data-testid="SubmitNewBotBtn"
disabled={hasErrors || !writable}
text={templateId === QnABotTemplateId ? formatMessage('Next') : formatMessage('OK')}
text={selectedTemplateId === QnABotTemplateId ? formatMessage('Next') : formatMessage('OK')}
onClick={handleSubmit}
/>
</DialogFooter>
Expand Down
8 changes: 3 additions & 5 deletions Composer/packages/client/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { FeedName } from '@botframework-composer/types/src';
import { FeedName, webAppRuntimeKey, functionsRuntimeKey } from '@botframework-composer/types/src';
import formatMessage from 'format-message';
import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';

Expand Down Expand Up @@ -403,9 +403,7 @@ export const mockLanguageOptions: IDropdownOption[] = [
{ key: 'spanish', text: 'Spanish' },
];

export const defaultRuntime = 'azureWebApp';

export const runtimeOptions: IDropdownOption[] = [
{ key: defaultRuntime, text: 'Azure Web App' },
{ key: 'azureFunctions', text: 'Azure Functions' },
{ key: webAppRuntimeKey, text: 'Azure Web App' },
{ key: functionsRuntimeKey, text: 'Azure Functions' },
];
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ export const projectDispatcher = () => {
urlSuffix,
alias,
preserveRoot,
runtimeChoice,
} = newProjectData;
// starts the creation process and stores the jobID in state for tracking
const response = await createNewBotFromTemplateV2(
Expand All @@ -352,6 +353,7 @@ export const projectDispatcher = () => {
name,
description,
location,
runtimeChoice,
schemaUrl,
locale,
templateDir,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
RootBotManagedProperties,
defaultPublishConfig,
LgFile,
RuntimeType,
QnABotTemplateId,
} from '@bfc/shared';
import formatMessage from 'format-message';
Expand Down Expand Up @@ -554,6 +555,7 @@ export const createNewBotFromTemplateV2 = async (
name: string,
description: string,
location: string,
runtimeChoice: RuntimeType,
schemaUrl?: string,
locale?: string,
templateDir?: string,
Expand All @@ -574,6 +576,7 @@ export const createNewBotFromTemplateV2 = async (
eTag,
alias,
preserveRoot,
runtimeChoice,
});
return jobId;
};
Expand Down
6 changes: 5 additions & 1 deletion Composer/packages/server/src/controllers/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ const getStatus = async (req, res) => {
if (jobId) {
const result = BackgroundProcessManager.getProcessStatus(jobId);
if (result) {
res.status(result.httpStatusCode).json(result);
res.status(result.httpStatusCode).json({
...result,
statusCode: result.httpStatusCode,
message: result.latestMessage,
});
} else {
res.status(404).json({
statusCode: '404',
Expand Down
16 changes: 10 additions & 6 deletions Composer/packages/server/src/models/asset/assetManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import fs from 'fs';
import path from 'path';

import find from 'lodash/find';
import { UserIdentity, FileExtensions, BotTemplateV2, FeedType } from '@bfc/extension';
import { UserIdentity, FileExtensions, BotTemplateV2, FeedType, RuntimeType } from '@bfc/extension';
import { mkdirSync, readFile } from 'fs-extra';
import yeoman from 'yeoman-environment';
import Environment from 'yeoman-environment';
Expand Down Expand Up @@ -103,6 +103,7 @@ export class AssetManager {
templateVersion: string,
projectName: string,
ref: LocationRef,
runtimeChoice: RuntimeType,
user?: UserIdentity
): Promise<LocationRef> {
try {
Expand All @@ -124,7 +125,7 @@ export class AssetManager {
const remoteTemplateAvailable = await this.installRemoteTemplate(generatorName, npmPackageName, templateVersion);

if (remoteTemplateAvailable) {
await this.instantiateRemoteTemplate(generatorName, dstDir, projectName);
await this.instantiateRemoteTemplate(generatorName, dstDir, projectName, runtimeChoice);
} else {
throw new Error(`error hit when installing remote template`);
}
Expand Down Expand Up @@ -159,13 +160,13 @@ export class AssetManager {
private async instantiateRemoteTemplate(
generatorName: string,
dstDir: string,
projectName: string
projectName: string,
runtimeChoice: RuntimeType
): Promise<boolean> {
log('About to instantiate a template!', dstDir, generatorName, projectName);
this.yeomanEnv.cwd = dstDir;
process.chdir(dstDir);

await this.yeomanEnv.run([generatorName, projectName], {}, () => {
await this.yeomanEnv.run([generatorName, projectName, runtimeChoice], {}, () => {
log('Template successfully instantiated', dstDir, generatorName, projectName);
});
return true;
Expand Down Expand Up @@ -282,8 +283,11 @@ export class AssetManager {
if (packageName) {
return packageName
.replace('generator-', '')
.replace('@microsoft/', '')
.replace('microsoft', '')
.split('-')
.reduce((a, b) => a.charAt(0).toUpperCase() + a.slice(1) + ' ' + b.charAt(0).toUpperCase() + b.slice(1));
.reduce((a, b) => a.charAt(0).toUpperCase() + a.slice(1) + ' ' + b.charAt(0).toUpperCase() + b.slice(1))
.trim();
} else {
return '';
}
Expand Down
Loading