Skip to content

Commit

Permalink
Merge branch 'main' into zhixzhan/file-api-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
zhixzhan committed May 6, 2021
2 parents 7580d6b + 1eefb2e commit fc3fa17
Show file tree
Hide file tree
Showing 28 changed files with 1,625 additions and 1,434 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
userSettingsState,
templateProjectsState,
} from '../../../recoilModel';
import { localBotsDataSelector } from '../../../recoilModel/selectors/project';
import Home from '../../../pages/home/Home';
import { useProjectIdCache } from '../../../utils/hooks';
import { ImportModal } from '../../ImportModal/ImportModal';
Expand All @@ -43,6 +44,7 @@ const CreationFlowV2: React.FC<CreationFlowProps> = () => {
fetchFeed,
openProject,
saveProjectAs,
migrateProjectTo,
fetchProjectById,
createNewBotV2,
fetchReadMe,
Expand All @@ -52,6 +54,8 @@ const CreationFlowV2: React.FC<CreationFlowProps> = () => {
const creationFlowStatus = useRecoilValue(creationFlowStatusState);
const projectId = useRecoilValue(currentProjectIdState);
const storages = useRecoilValue(storagesState);
const botProjects = useRecoilValue(localBotsDataSelector);
const botProject = botProjects.find((b) => b.projectId === projectId);
const focusedStorageFolder = useRecoilValue(focusedStorageFolderState);
const { appLocale } = useRecoilValue(userSettingsState);
const cachedProjectId = useProjectIdCache();
Expand Down Expand Up @@ -150,13 +154,28 @@ const CreationFlowV2: React.FC<CreationFlowProps> = () => {
saveProjectAs(projectId, formData.name, formData.description, formData.location);
};

const handleMigrate = (formData) => {
handleDismiss();
setCreationFlowStatus(CreationFlowStatus.MIGRATE);
migrateProjectTo(
projectId,
formData.name,
formData.description,
formData.location,
formData.runtimeLanguage,
formData.runtimeType
);
};

const handleSubmit = async (formData, templateId: string) => {
handleDismiss();
switch (creationFlowStatus) {
case CreationFlowStatus.SAVEAS:
handleSaveAs(formData);
break;

case CreationFlowStatus.MIGRATE:
handleMigrate(formData);
break;
default:
saveTemplateId(templateId);
await handleCreateNew(formData, templateId);
Expand Down Expand Up @@ -226,6 +245,16 @@ const CreationFlowV2: React.FC<CreationFlowProps> = () => {
onDismiss={handleDismiss}
onOpen={openBot}
/>
<DefineConversationV2
createFolder={createFolder}
focusedStorageFolder={focusedStorageFolder}
path="migrate/:projectId"
templateId={botProject?.name || 'migrated_project'} // templateId is used for default project name
updateFolder={updateFolder}
onCurrentPathUpdate={updateCurrentPath}
onDismiss={handleDismiss}
onSubmit={handleMigrate}
/>
<ImportModal path="import" />
</Router>
</Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import camelCase from 'lodash/camelCase';
import upperFirst from 'lodash/upperFirst';

import { DialogCreationCopy, nameRegexV2, nameRegex } from '../../../constants';
import { CreationFlowStatus, DialogCreationCopy, nameRegex, nameRegexV2 } 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, templateProjectsState } from '../../../recoilModel';
import { creationFlowStatusState, dispatcherState, templateProjectsState } from '../../../recoilModel';
import { LocationSelectContent } from '../LocationSelectContent';
import { getAliasFromPayload, Profile } from '../../../utils/electronUtil';
import TelemetryClient from '../../../telemetry/TelemetryClient';
Expand Down Expand Up @@ -100,6 +100,7 @@ type DefineConversationProps = {
focusedStorageFolder: StorageFolder;
} & RouteComponentProps<{
templateId: string;
defaultName: string;
runtimeLanguage: string;
location: string;
}>;
Expand All @@ -118,12 +119,16 @@ const DefineConversationV2: React.FC<DefineConversationProps> = (props) => {
const writable = focusedStorageFolder.writable;
const runtimeLanguage = props.runtimeLanguage ? props.runtimeLanguage : csharpFeedKey;
const templateProjects = useRecoilValue(templateProjectsState);
const creationFlowStatus = useRecoilValue(creationFlowStatusState);

const currentTemplate = templateProjects.find((t) => {
if (t?.id) {
return t.id === templateId;
}
});

const inBotMigration = creationFlowStatus === CreationFlowStatus.MIGRATE;

// template ID is populated by npm package name which needs to be formatted
const normalizeTemplateId = () => {
if (currentTemplate) {
Expand All @@ -135,6 +140,10 @@ const DefineConversationV2: React.FC<DefineConversationProps> = (props) => {
.replace(/-/g, ' ')
);
return upperFirst(camelCasedName);
} else if (templateId && inBotMigration) {
return templateId.trim().replace(/[-\s]/g, '_').toLocaleLowerCase();
} else {
return templateId;
}
};

Expand Down Expand Up @@ -311,8 +320,10 @@ const DefineConversationV2: React.FC<DefineConversationProps> = (props) => {

const getSupportedRuntimesForTemplate = (): IDropdownOption[] => {
const result: IDropdownOption[] = [];

if (currentTemplate) {
if (inBotMigration) {
result.push({ key: webAppRuntimeKey, text: formatMessage('Azure Web App') });
result.push({ key: functionsRuntimeKey, text: formatMessage('Azure Functions') });
} else if (currentTemplate) {
if (runtimeLanguage === csharpFeedKey) {
currentTemplate.dotnetSupport?.functionsSupported &&
result.push({ key: functionsRuntimeKey, text: formatMessage('Azure Functions') });
Expand All @@ -325,9 +336,17 @@ const DefineConversationV2: React.FC<DefineConversationProps> = (props) => {
result.push({ key: webAppRuntimeKey, text: formatMessage('Azure Web App') });
}
}

return result;
};

const getRuntimeLanguageOptions = (): IDropdownOption[] => {
return [
{ key: csharpFeedKey, text: 'C#' },
{ key: nodeFeedKey, text: formatMessage('Node (Preview)') },
];
};

useEffect(() => {
const location =
focusedStorageFolder !== null && Object.keys(focusedStorageFolder as Record<string, any>).length
Expand Down Expand Up @@ -368,14 +387,30 @@ const DefineConversationV2: React.FC<DefineConversationProps> = (props) => {
</StackItem>
{!isImported && (
<StackItem grow={0} styles={halfstack}>
<Dropdown
data-testid="NewDialogRuntimeType"
label={formatMessage('Runtime type')}
options={getSupportedRuntimesForTemplate()}
selectedKey={formData.runtimeType}
styles={{ root: { width: '420px' } }}
onChange={(_e, option) => updateField('runtimeType', option?.key.toString())}
/>
<Stack horizontal styles={stackinput} tokens={{ childrenGap: '2rem' }}>
<StackItem grow={0}>
<Dropdown
data-testid="NewDialogRuntimeType"
label={formatMessage('Runtime type')}
options={getSupportedRuntimesForTemplate()}
selectedKey={formData.runtimeType}
styles={{ root: { width: inBotMigration ? '200px' : '420px' } }}
onChange={(_e, option) => updateField('runtimeType', option?.key.toString())}
/>
</StackItem>
{inBotMigration && (
<StackItem grow={0}>
<Dropdown
data-testid="NewDialogRuntimeLanguage"
label={formatMessage('Runtime Language')}
options={getRuntimeLanguageOptions()}
selectedKey={formData.runtimeLanguage}
styles={{ root: { width: '200px' } }}
onChange={(_e, option) => updateField('runtimeLanguage', option?.key.toString())}
/>
</StackItem>
)}
</Stack>
</StackItem>
)}
</Stack>
Expand Down
1 change: 1 addition & 0 deletions Composer/packages/client/src/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export enum CreationFlowStatus {
NEW_FROM_SCRATCH = 'Scratch',
NEW_FROM_TEMPLATE = 'Template',
SAVEAS = 'Save as',
MIGRATE = 'Migrate',
OPEN = 'Open',
CLOSE = 'Close',
NEW_SKILL = 'New Skill',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
import { jsx, css } from '@emotion/core';
import formatMessage from 'format-message';

import { useAutoFix } from './useAutoFix';
import { useDiagnosticsStatistics } from './useDiagnostics';

export const DiagnosticsHeader = () => {
const { hasError, hasWarning } = useDiagnosticsStatistics();
useAutoFix();

return (
<div
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import get from 'lodash/get';
import set from 'lodash/set';
import cloneDeep from 'lodash/cloneDeep';
import { useRecoilValue } from 'recoil';
import { useEffect } from 'react';

import { DiagnosticType, SchemaDiagnostic } from '../../../../diagnostics/types';
import { botProjectSpaceSelector, dispatcherState } from '../../../../../recoilModel';

import { useDiagnosticsData } from './useDiagnostics';

export const useAutoFix = () => {
const diagnostics = useDiagnosticsData();
const botProjectSpace = useRecoilValue(botProjectSpaceSelector);
const { updateDialog } = useRecoilValue(dispatcherState);

// Auto fix schema absence by setting 'disabled' to true.
useEffect(() => {
const schemaDiagnostics = diagnostics.filter((d) => d.type === DiagnosticType.SCHEMA) as SchemaDiagnostic[];
/**
* Aggregated diagnostic paths where contains schema problem
*
* Example:
* {
* // projectId
* '2096.637': {
* // dialogId
* 'dialog-1': [
* 'triggers[0].actions[1]', // diagnostics.dialogPath
* 'triggers[2].actions[3]'
* ]
* }
* }
*/
const aggregatedPaths: { [projectId: string]: { [dialogId: string]: string[] } } = {};

// Aggregates schema diagnostics by projectId, dialogId
schemaDiagnostics.forEach((d) => {
const { projectId, id: dialogId, dialogPath } = d;
if (!dialogPath) return;
const currentPaths = get(aggregatedPaths, [projectId, dialogId]);
if (currentPaths) {
currentPaths.push(dialogPath);
} else {
set(aggregatedPaths, [projectId, dialogId], [dialogPath]);
}
});

// Consumes aggregatedPaths to update dialogs in recoil store
for (const [projectId, pathsByDialogId] of Object.entries(aggregatedPaths)) {
// Locates dialogs in current project
const dialogsInProject = botProjectSpace.find((bot) => bot.projectId === projectId)?.dialogs;
if (!Array.isArray(dialogsInProject)) continue;

for (const [dialogId, paths] of Object.entries(pathsByDialogId)) {
// Queries out current dialog data
const dialogData = dialogsInProject.find((dialog) => dialog.id === dialogId)?.content;
if (!dialogData) continue;

// Filters out those paths where action exists and action.disabled !== true
const pathsToUpdate = paths.filter((p) => {
const data = get(dialogData, p);
return data && !get(data, 'disabled');
});
if (!pathsToUpdate.length) continue;

// Manipulates the 'disabled' property and then submit to Recoil store.
const copy = cloneDeep(dialogData);
for (const p of pathsToUpdate) {
set(copy, `${p}.disabled`, true);
}
updateDialog({ id: dialogId, projectId, content: copy });
}
}
}, [diagnostics]);
};
5 changes: 5 additions & 0 deletions Composer/packages/client/src/pages/diagnostics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export enum DiagnosticType {
SKILL,
SETTING,
GENERAL,
SCHEMA,
}

export interface IDiagnosticInfo {
Expand Down Expand Up @@ -94,6 +95,10 @@ export class DialogDiagnostic extends DiagnosticInfo {
};
}

export class SchemaDiagnostic extends DialogDiagnostic {
type = DiagnosticType.SCHEMA;
}

export class SkillSettingDiagnostic extends DiagnosticInfo {
type = DiagnosticType.SKILL;
constructor(rootProjectId: string, projectId: string, id: string, location: string, diagnostic: Diagnostic) {
Expand Down
Loading

0 comments on commit fc3fa17

Please sign in to comment.