Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { BotStatus } from '../../constants';
import { isAbsHosted } from '../../utils/envUtil';
import useNotifications from '../../pages/notifications/useNotifications';
import { navigateTo, openInEmulator } from '../../utils/navigation';
import { useShell } from '../../shell';
import { crossTrainBuilder } from '../../utils/builders/crossTrainBuilder';

import { isBuildConfigComplete, needsBuild } from './../../utils/buildUtil';
import { PublishDialog } from './publishDialog';
Expand Down Expand Up @@ -60,6 +62,7 @@ export const TestController: React.FC<{ projectId: string }> = (props) => {
const botActionRef = useRef(null);
const notifications = useNotifications(projectId);

const shell = useShell('DesignPage', projectId);
const dialogs = useRecoilValue(validateDialogSelectorFamily(projectId));
const botStatus = useRecoilValue(botStatusState(projectId));
const botName = useRecoilValue(botDisplayNameState(projectId));
Expand Down Expand Up @@ -161,7 +164,10 @@ export const TestController: React.FC<{ projectId: string }> = (props) => {
luis: luis,
qna: Object.assign({}, settings.qna, qna),
});
await build(luis, qna, projectId);

// const builder = selectBuilder(pluginConfig.recognizer.builder);
// await build(projectId, config, shell.data, builder);
await build(projectId, config, shell.data, crossTrainBuilder);
Comment on lines +168 to +170
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here is the contribution point of a publisher plugin

}

async function handleLoadBot() {
Expand Down
73 changes: 14 additions & 59 deletions Composer/packages/client/src/recoilModel/dispatchers/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,71 +3,26 @@
/* eslint-disable react-hooks/rules-of-hooks */

import { useRecoilCallback, CallbackInterface } from 'recoil';
import { ILuisConfig, IQnAConfig } from '@bfc/shared';
import { IPublishConfig, ShellData } from '@bfc/types';

import * as luUtil from '../../utils/luUtil';
import * as buildUtil from '../../utils/buildUtil';
import { Text, BotStatus } from '../../constants';
import httpClient from '../../utils/httpUtil';
import luFileStatusStorage from '../../utils/luFileStatusStorage';
import qnaFileStatusStorage from '../../utils/qnaFileStatusStorage';
import { luFilesState, qnaFilesState, dialogsState, botStatusState, botLoadErrorState } from '../atoms';

const checkEmptyQuestionOrAnswerInQnAFile = (sections) => {
return sections.some((s) => !s.Answer || s.Questions.some((q) => !q.content));
};
import { botStatusState, botLoadErrorState } from '../atoms';
import { Builder } from '../../utils/builders/builderTypes';

export const builderDispatcher = () => {
const build = useRecoilCallback(
({ set, snapshot }: CallbackInterface) => async (
luisConfig: ILuisConfig,
qnaConfig: IQnAConfig,
projectId: string
({ set }: CallbackInterface) => (
projectId: string,
config: IPublishConfig,
shellData: ShellData,
builder: Builder<any>
) => {
const dialogs = await snapshot.getPromise(dialogsState(projectId));
const luFiles = await snapshot.getPromise(luFilesState(projectId));
const qnaFiles = await snapshot.getPromise(qnaFilesState(projectId));
const referredLuFiles = luUtil.checkLuisBuild(luFiles, dialogs);

const errorMsg = qnaFiles.reduce(
(result, file) => {
if (
file.qnaSections &&
file.qnaSections.length > 0 &&
checkEmptyQuestionOrAnswerInQnAFile(file.qnaSections)
) {
result.message = result.message + `${file.id}.qna file contains empty answer or questions`;
}
return result;
},
{ title: Text.LUISDEPLOYFAILURE, message: '' }
return builder(
projectId,
config,
shellData,
(status) => set(botStatusState(projectId), status),
(error) => set(botLoadErrorState(projectId), error)
);
if (errorMsg.message) {
set(botLoadErrorState(projectId), errorMsg);
set(botStatusState(projectId), BotStatus.failed);
return;
}
try {
//TODO crosstrain should add locale
const crossTrainConfig = buildUtil.createCrossTrainConfig(dialogs, referredLuFiles);
await httpClient.post(`/projects/${projectId}/build`, {
luisConfig,
qnaConfig,
projectId,
crossTrainConfig,
luFiles: referredLuFiles.map((file) => file.id),
qnaFiles: qnaFiles.map((file) => file.id),
});
luFileStatusStorage.publishAll(projectId);
qnaFileStatusStorage.publishAll(projectId);
set(botStatusState(projectId), BotStatus.published);
} catch (err) {
set(botStatusState(projectId), BotStatus.failed);
set(botLoadErrorState(projectId), {
title: Text.LUISDEPLOYFAILURE,
message: err.response?.data?.message || err.message,
});
}
}
);
return {
Expand Down
19 changes: 19 additions & 0 deletions Composer/packages/client/src/utils/builders/builderTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { ShellData } from '@bfc/types';

import { BotStatus } from '../../constants';
import { BotLoadError } from '../../recoilModel/types';

type SetStatusHandler = (status: BotStatus) => void;

type SetErrorHandler = (error: BotLoadError) => void;

export type Builder<BuilderConfig> = (
projectId: string,
config: BuilderConfig,
shellData: ShellData,
setStatus: SetStatusHandler,
setError: SetErrorHandler
) => void;
58 changes: 58 additions & 0 deletions Composer/packages/client/src/utils/builders/crossTrainBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { IPublishConfig } from '@bfc/types';

import { Text, BotStatus } from '../../constants';
import * as luUtil from '../luUtil';
import * as buildUtil from '../buildUtil';
import httpClient from '../httpUtil';
import luFileStatusStorage from '../luFileStatusStorage';
import qnaFileStatusStorage from '../qnaFileStatusStorage';

import { Builder } from './builderTypes';

const checkEmptyQuestionOrAnswerInQnAFile = (sections) => {
return sections.some((s) => !s.Answer || s.Questions.some((q) => !q.content));
};

export const crossTrainBuilder: Builder<IPublishConfig> = async (projectId, config, shellData, setStatus, setError) => {
const { dialogs, luFiles, qnaFiles } = shellData;
const referredLuFiles = luUtil.checkLuisBuild(luFiles, dialogs);

const errorMsg = qnaFiles.reduce(
(result, file) => {
if (file.qnaSections && file.qnaSections.length > 0 && checkEmptyQuestionOrAnswerInQnAFile(file.qnaSections)) {
result.message = result.message + `${file.id}.qna file contains empty answer or questions`;
}
return result;
},
{ title: Text.LUISDEPLOYFAILURE, message: '' }
);
if (errorMsg.message) {
setError(errorMsg);
setStatus(BotStatus.failed);
return;
}
try {
//TODO crosstrain should add locale
const crossTrainConfig = buildUtil.createCrossTrainConfig(dialogs, referredLuFiles);
await httpClient.post(`/projects/${projectId}/build`, {
luisConfig: config.luis,
qnaConfig: config.qna,
projectId,
crossTrainConfig,
luFiles: referredLuFiles.map((file) => file.id),
qnaFiles: qnaFiles.map((file) => file.id),
});
luFileStatusStorage.publishAll(projectId);
qnaFileStatusStorage.publishAll(projectId);
setStatus(BotStatus.published);
} catch (err) {
setStatus(BotStatus.failed);
setError({
title: Text.LUISDEPLOYFAILURE,
message: err.response?.data?.message || err.message,
});
}
};