Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
ci:
name: Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 20
timeout-minutes: 30
defaults:
run:
working-directory: Composer
Expand Down
7 changes: 6 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,12 @@
"DEBUG": "composer*",
"COMPOSER_ENABLE_ONEAUTH": "false"
},
"outputCapture": "std"
"outputCapture": "std",
"outFiles": [
"${workspaceRoot}/Composer/packages/electron-server/build/**/*.js",
"${workspaceRoot}/Composer/packages/server/build/**/*.js",
"${workspaceRoot}/extensions/**/*.js"
]
},
{
"name": "Debug current jest test",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const state = {
publish: true,
status: true,
rollback: true,
pull: true,
},
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

/** @jsx jsx */
import { jsx } from '@emotion/core';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { DefaultButton, IconButton } from 'office-ui-fabric-react/lib/Button';
import { IContextualMenuItem } from 'office-ui-fabric-react/lib/ContextualMenu';
import { useRecoilValue } from 'recoil';
Expand Down Expand Up @@ -34,6 +34,13 @@ const iconSectionContainer = css`
}
`;

const disabledStyle = css`
&:before {
opacity: 0.4;
}
pointer-events: none;
`;

const startPanelsContainer = css`
display: flex;
flex-direction: 'row';
Expand All @@ -47,6 +54,7 @@ const BotController: React.FC = () => {
const [isControllerHidden, setControllerVisibility] = useState(true);
const { onboardingAddCoachMarkRef } = useRecoilValue(dispatcherState);
const onboardRef = useCallback((startBot) => onboardingAddCoachMarkRef({ startBot }), []);
const [disableStartBots, setDisableOnStartBotsWidget] = useState(false);

const target = useRef(null);
const botControllerMenuTarget = useRef(null);
Expand All @@ -55,6 +63,14 @@ const BotController: React.FC = () => {
setControllerVisibility(true);
});

useEffect(() => {
if (projectCollection.length === 0) {
setDisableOnStartBotsWidget(true);
return;
}
setDisableOnStartBotsWidget(false);
}, [projectCollection]);

const running = useMemo(() => !projectCollection.every(({ status }) => status === BotStatus.unConnected), [
projectCollection,
]);
Expand Down Expand Up @@ -93,11 +109,19 @@ const BotController: React.FC = () => {
{projectCollection.map(({ projectId }) => {
return <BotRuntimeStatus key={projectId} projectId={projectId} />;
})}
<div ref={target} css={startPanelsContainer}>
<div ref={target} css={[startPanelsContainer]}>
<DefaultButton
primary
aria-roledescription={formatMessage('bot controller')}
iconProps={{ iconName: running ? 'CircleStopSolid' : 'Play' }}
aria-roledescription={formatMessage('Bot Controller')}
disabled={disableStartBots}
iconProps={{
iconName: running ? 'CircleStopSolid' : 'Play',
styles: {
root: {
color: `${NeutralColors.white}`,
},
},
}}
menuAs={() => null}
styles={{
root: {
Expand All @@ -115,9 +139,10 @@ const BotController: React.FC = () => {
>
<span>{buttonText}</span>
</DefaultButton>
<div ref={onboardRef} css={iconSectionContainer}>
<div ref={onboardRef} css={[iconSectionContainer, disableStartBots ? disabledStyle : '']}>
<IconButton
ariaDescription={formatMessage('Open start bots panel')}
disabled={disableStartBots}
iconProps={{
iconName: 'ProductList',
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function useBotOperations() {
}
} else {
// Regex recognizer
await botRuntimeOperations?.startBot(projectId, config);
await botRuntimeOperations?.startBot(projectId);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Home from '../../pages/home/Home';
import { useProjectIdCache } from '../../utils/hooks';
import { useShell } from '../../shell';
import plugins from '../../plugins';
import { ImportModal } from '../ImportModal/ImportModal';

import { CreateOptions } from './CreateOptions';
import { OpenProject } from './OpenProject';
Expand Down Expand Up @@ -120,6 +121,11 @@ const CreationFlow: React.FC<CreationFlowProps> = () => {
schemaUrl: formData.schemaUrl,
appLocale,
qnaKbUrls,
templateDir: formData.templateDir,
eTag: formData.eTag,
urlSuffix: formData.urlSuffix,
alias: formData.alias,
preserveRoot: formData.preserveRoot,
};
createNewBot(newBotData);
};
Expand Down Expand Up @@ -199,6 +205,7 @@ const CreationFlow: React.FC<CreationFlowProps> = () => {
path="create/vaCore/*"
onDismiss={handleDismiss}
/>
<ImportModal path="import" />
</Router>
</EditorExtension>
</Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@ import { DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
import formatMessage from 'format-message';
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { Stack, StackItem } from 'office-ui-fabric-react/lib/Stack';
import React, { Fragment, useEffect, useCallback, useMemo } from 'react';
import React, { Fragment, useEffect, useCallback, useMemo, useState } from 'react';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { RouteComponentProps } from '@reach/router';
import querystring from 'query-string';
import { FontWeights } from '@uifabric/styling';
import { DialogWrapper, DialogTypes } from '@bfc/ui-shared';
import { useRecoilValue } from 'recoil';

import { DialogCreationCopy, QnABotTemplateId, nameRegex } 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';

Expand Down Expand Up @@ -66,6 +70,12 @@ interface DefineConversationFormData {
description: string;
schemaUrl: string;
location?: string;

templateDir?: string; // location of the imported template
eTag?: string; // e tag used for content sync between composer and imported bot content
urlSuffix?: string; // url to deep link to after creation
alias?: string; // identifier that is used to track bots between imports
preserveRoot?: boolean; // identifier that is used to determine ay project file renames upon creation
}

interface DefineConversationProps
Expand Down Expand Up @@ -109,18 +119,19 @@ const DefineConversation: React.FC<DefineConversationProps> = (props) => {
);
return defaultName;
};
const { addNotification } = useRecoilValue(dispatcherState);

const formConfig: FieldConfig<DefineConversationFormData> = {
name: {
required: true,
validate: (value) => {
if (!value || !nameRegex.test(value)) {
if (!value || !nameRegex.test(`${value}`)) {
return formatMessage('Spaces and special characters are not allowed. Use letters, numbers, -, or _.');
}

const newBotPath =
focusedStorageFolder !== null && Object.keys(focusedStorageFolder as Record<string, any>).length
? Path.join(focusedStorageFolder.parent, focusedStorageFolder.name, value)
? Path.join(focusedStorageFolder.parent, focusedStorageFolder.name, `${value}`)
: '';
if (
files.some((bot) => {
Expand All @@ -146,6 +157,14 @@ const DefineConversation: React.FC<DefineConversationProps> = (props) => {
},
};
const { formData, formErrors, hasErrors, updateField, updateForm } = useForm(formConfig);
const [isImported, setIsImported] = useState<boolean>(false);

useEffect(() => {
if (props.location?.state) {
const { imported } = props.location.state;
setIsImported(imported);
}
}, [props.location?.state]);

useEffect(() => {
const formData: DefineConversationFormData = {
Expand Down Expand Up @@ -189,9 +208,35 @@ const DefineConversation: React.FC<DefineConversationProps> = (props) => {
return;
}

// handle extra properties in the case of an imported bot project
const dataToSubmit = {
...formData,
};
if (props.location?.state) {
const { alias, eTag, imported, templateDir, urlSuffix } = props.location.state;

if (imported) {
dataToSubmit.templateDir = templateDir;
dataToSubmit.eTag = eTag;
dataToSubmit.urlSuffix = urlSuffix;
dataToSubmit.alias = alias;
dataToSubmit.preserveRoot = true;

// create a notification to indicate import success
const notification = createNotification({
type: 'success',
title: '',
onRenderCardContent: ImportSuccessNotificationWrapper({
importedToExisting: false,
}),
});
addNotification(notification);
}
}

onSubmit(
{
...formData,
...dataToSubmit,
},
templateId || ''
);
Expand Down Expand Up @@ -223,15 +268,11 @@ const DefineConversation: React.FC<DefineConversationProps> = (props) => {
/>
);
}, [focusedStorageFolder]);
const dialogCopy = isImported ? DialogCreationCopy.IMPORT_BOT_PROJECT : DialogCreationCopy.DEFINE_BOT_PROJECT;

return (
<Fragment>
<DialogWrapper
isOpen
{...DialogCreationCopy.DEFINE_BOT_PROJECT}
dialogType={DialogTypes.CreateFlow}
onDismiss={onDismiss}
>
<DialogWrapper isOpen {...dialogCopy} dialogType={DialogTypes.CreateFlow} onDismiss={onDismiss}>
<form onSubmit={handleSubmit}>
<input style={{ display: 'none' }} type="submit" />
<Stack horizontal styles={stackinput} tokens={{ childrenGap: '2rem' }}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/** @jsx jsx */
import { jsx } from '@emotion/core';
import { Dialog, DialogFooter, DialogType } from 'office-ui-fabric-react/lib/Dialog';
import { DefaultButton } from 'office-ui-fabric-react/lib/Button';
import React from 'react';
import formatMessage from 'format-message';
import { generateUniqueId } from '@bfc/shared';

import { boldText, boldBlueText, dialogContent } from './style';

type ImportFailedModalProps = {
botName: string;
error?: Error | string;
onDismiss: () => any;
};

const BoldBlue = ({ children }) => (
<span key={generateUniqueId()} css={boldBlueText}>
{children}
</span>
);

export const ImportFailedModal: React.FC<ImportFailedModalProps> = (props) => {
const { botName, error, onDismiss } = props;

return (
<Dialog
dialogContentProps={{
title: formatMessage('Something went wrong'),
type: DialogType.close,
}}
hidden={false}
minWidth={560}
onDismiss={onDismiss}
>
<p css={dialogContent}>
{formatMessage.rich('There was an unexpected error importing bot content to <b>{ botName }</b>', {
b: BoldBlue,
botName,
})}
</p>
<p css={boldText}>{typeof error === 'object' ? error.message : error}</p>
<DialogFooter>
<DefaultButton text={formatMessage('Cancel')} onClick={onDismiss} />
</DialogFooter>
</Dialog>
);
};
Loading