Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit 807b9df

Browse files
authored
Merge branch 'main' into ltwlf/skill-end-dialog
2 parents ff0f97d + e4c4f36 commit 807b9df

File tree

8 files changed

+84
-53
lines changed

8 files changed

+84
-53
lines changed

Composer/packages/client/src/components/ImportModal/ImportModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export const ImportModal: React.FC<RouteComponentProps> = (props) => {
109109
title: '',
110110
onRenderCardContent: ImportSuccessNotificationWrapper({
111111
importedToExisting: true,
112-
location: existingProject.location,
112+
location: path,
113113
}),
114114
});
115115
addNotification(notification);

Composer/packages/client/src/pages/publish/Publish.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ const LogDialog = (props) => {
499499
<Dialog
500500
dialogContentProps={logDialogProps}
501501
hidden={false}
502-
minWidth={450}
502+
minWidth={700}
503503
modalProps={{ isBlocking: true }}
504504
onDismiss={props.onDismiss}
505505
>

Composer/packages/client/src/pages/publish/publishStatusList.tsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky';
1515
import { TooltipHost } from 'office-ui-fabric-react/lib/Tooltip';
1616
import { Selection } from 'office-ui-fabric-react/lib/DetailsList';
1717
import { Icon } from 'office-ui-fabric-react/lib/Icon';
18+
import { Link } from 'office-ui-fabric-react/lib/Link';
1819
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
1920
import moment from 'moment';
2021
import { useMemo, useState, useEffect } from 'react';
@@ -35,6 +36,10 @@ export interface IStatus {
3536
status: number;
3637
message: string;
3738
comment: string;
39+
action?: {
40+
href: string;
41+
label: string;
42+
};
3843
}
3944

4045
function onRenderDetailsHeader(props, defaultRender) {
@@ -99,8 +104,8 @@ export const PublishStatusList: React.FC<IStatusListProps> = (props) => {
99104
name: formatMessage('Status'),
100105
className: 'publishstatus',
101106
fieldName: 'status',
102-
minWidth: 70,
103-
maxWidth: 90,
107+
minWidth: 40,
108+
maxWidth: 40,
104109
isResizable: true,
105110
data: 'string',
106111
onRender: (item: IStatus) => {
@@ -123,14 +128,29 @@ export const PublishStatusList: React.FC<IStatusListProps> = (props) => {
123128
name: formatMessage('Message'),
124129
className: 'publishmessage',
125130
fieldName: 'message',
126-
minWidth: 70,
127-
maxWidth: 90,
131+
minWidth: 150,
132+
maxWidth: 300,
128133
isResizable: true,
129134
isCollapsible: true,
130135
isMultiline: true,
131136
data: 'string',
132137
onRender: (item: IStatus) => {
133-
return <span>{item.message}</span>;
138+
return (
139+
<span>
140+
{item.message}
141+
{item.action && (
142+
<Link
143+
aria-label={item.action.label}
144+
href={item.action.href}
145+
rel="noopener noreferrer"
146+
style={{ marginLeft: '3px' }}
147+
target="_blank"
148+
>
149+
{item.action.label}
150+
</Link>
151+
)}
152+
</span>
153+
);
134154
},
135155
isPadded: true,
136156
},

Composer/packages/types/src/publish.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export type PullResponse = {
2828
error?: any;
2929
eTag?: string;
3030
status: number;
31+
/** Path to the pulled .zip containing updated bot content */
3132
zipPath?: string;
3233
};
3334

Composer/packages/types/src/server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export type IBotProject = {
88
fileStorage: any;
99
dir: string;
1010
dataDir: string;
11+
eTag?: string;
1112
id: string | undefined;
1213
name: string;
1314
builder: any;

Composer/packages/ui-plugins/cross-trained/src/index.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the MIT License.
33

44
import { PluginConfig } from '@bfc/extension-client';
5-
import { SDKKinds } from '@bfc/shared';
5+
import { SDKKinds, checkForPVASchema } from '@bfc/shared';
66
import formatMessage from 'format-message';
77

88
const config: PluginConfig = {
@@ -15,12 +15,16 @@ const config: PluginConfig = {
1515
},
1616
intentEditor: 'LuIntentEditor',
1717
seedNewRecognizer: (shellData) => {
18-
const { qnaFiles, luFiles, currentDialog, locale } = shellData;
18+
const { qnaFiles, luFiles, currentDialog, locale, schemas } = shellData;
1919
const qnaFile = qnaFiles.find((f) => f.id === `${currentDialog.id}.${locale}`);
2020
const luFile = luFiles.find((f) => f.id === `${currentDialog.id}.${locale}`);
2121

22-
if (!qnaFile || !luFile) {
23-
alert(formatMessage(`NO LU OR QNA FILE WITH NAME { id }`, { id: currentDialog.id }));
22+
if (!luFile) {
23+
alert(formatMessage(`NO LU FILE WITH NAME { id }`, { id: currentDialog.id }));
24+
}
25+
26+
if (!qnaFile && !checkForPVASchema(schemas.sdk)) {
27+
alert(formatMessage(`NO QNA FILE WITH NAME { id }`, { id: currentDialog.id }));
2428
}
2529

2630
return `${currentDialog.id}.lu.qna`;

extensions/pvaPublish/src/node/publish.ts

Lines changed: 45 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { IBotProject } from '@botframework-composer/types';
22
import { join } from 'path';
3-
import { createReadStream, createWriteStream } from 'fs';
4-
import { ensureDirSync, remove } from 'fs-extra';
3+
import { createWriteStream } from 'fs';
4+
import { ensureDirSync } from 'fs-extra';
55
import fetch, { RequestInit } from 'node-fetch';
6+
import stream from 'stream';
67

78
import {
89
PVAPublishJob,
@@ -42,47 +43,39 @@ export const publish = async (
4243
const { comment = '' } = metadata;
4344

4445
try {
46+
logger.log('Starting publish to Power Virtual Agents.');
4547
// authenticate with PVA
4648
const base = baseUrl || getBaseUrl();
4749
const creds = getAuthCredentials(base);
4850
const accessToken = await getAccessToken(creds);
4951

50-
// TODO: Investigate optimizing stream logic before enabling extension.
51-
// (https://github.com/microsoft/BotFramework-Composer/pull/4446#discussion_r510314378)
52-
53-
// where we will store the bot .zip
54-
const zipDir = join(process.env.COMPOSER_TEMP_DIR as string, 'pva-publish');
55-
ensureDirSync(zipDir);
56-
const zipPath = join(zipDir, 'bot.zip');
57-
58-
// write the .zip to disk
59-
const zipWriteStream = createWriteStream(zipPath);
52+
// write the .zip to a buffer in memory
53+
logger.log('Writing bot content to in-memory buffer.');
54+
const botContentWriter = new stream.Writable();
55+
const botContentData = [];
56+
botContentWriter._write = (chunk, encoding, callback) => {
57+
botContentData.push(chunk);
58+
callback(); // let the internal write() call know that the _write() was successful
59+
};
6060
await new Promise((resolve, reject) => {
61-
project.exportToZip((archive: NodeJS.ReadStream & { finalize: () => void; on: (ev, listener) => void }) => {
62-
archive.on('error', (err) => {
63-
console.error('Got error trying to export to zip: ', err);
64-
reject(err.message);
65-
});
66-
archive.pipe(zipWriteStream);
67-
archive.on('end', () => {
68-
archive.unpipe();
69-
zipWriteStream.end();
70-
resolve();
71-
});
72-
});
61+
project.exportToZip(
62+
{ files: ['*.botproject'], directories: ['/knowledge-base/'] },
63+
(archive: NodeJS.ReadStream & { finalize: () => void; on: (ev, listener) => void }) => {
64+
archive.on('error', (err) => {
65+
console.error('Got error trying to export to zip: ', err);
66+
reject(err.message);
67+
});
68+
archive.on('end', () => {
69+
archive.unpipe();
70+
logger.log('Done reading bot content.');
71+
resolve();
72+
});
73+
archive.pipe(botContentWriter);
74+
}
75+
);
7376
});
74-
75-
// open up the .zip for reading
76-
const zipReadStream = createReadStream(zipPath);
77-
await new Promise((resolve, reject) => {
78-
zipReadStream.on('error', (err) => {
79-
reject(err);
80-
});
81-
zipReadStream.once('readable', () => {
82-
resolve();
83-
});
84-
});
85-
const length = zipReadStream.readableLength;
77+
const botContent = Buffer.concat(botContentData);
78+
logger.log('In-memory buffer created from bot content.');
8679

8780
// initiate the publish job
8881
let url = `${base}api/botmanagement/${API_VERSION}/environments/${envId}/bots/${botId}/composer/publishoperations?deleteMissingDependencies=${deleteMissingDependencies}`;
@@ -91,15 +84,16 @@ export const publish = async (
9184
}
9285
const res = await fetch(url, {
9386
method: 'POST',
94-
body: zipReadStream,
87+
body: botContent,
9588
headers: {
9689
...getAuthHeaders(accessToken, tenantId),
9790
'Content-Type': 'application/zip',
98-
'Content-Length': length.toString(),
91+
'Content-Length': botContent.buffer.byteLength,
9992
'If-Match': project.eTag,
10093
},
10194
});
10295
const job: PVAPublishJob = await res.json();
96+
logger.log('Publish job started: %O', job);
10397

10498
// transform the PVA job to a publish response
10599
const result = xformJobToResult(job);
@@ -109,7 +103,7 @@ export const publish = async (
109103
ensurePublishProfileHistory(botProjectId, profileName);
110104
publishHistory[botProjectId][profileName].unshift(result);
111105

112-
remove(zipDir); // clean up zip -- fire and forget
106+
logger.log('Publish call successful.');
113107

114108
return {
115109
status: result.status,
@@ -173,6 +167,9 @@ export const getStatus = async (
173167
logger.log('Got updated status from publish job: %O', job);
174168

175169
// transform the PVA job to a publish response
170+
if (!job.lastUpdateTimeUtc) {
171+
job.lastUpdateTimeUtc = Date.now().toString(); // patch update time if server doesn't send one
172+
}
176173
const result = xformJobToResult(job);
177174

178175
// update publish history
@@ -291,13 +288,19 @@ const xformJobToResult = (job: PVAPublishJob): PublishResult => {
291288
eTag: job.importedContentEtag,
292289
id: job.operationId, // what is this used for in Composer?
293290
log: (job.diagnostics || []).map((diag) => `---\n${JSON.stringify(diag, null, 2)}\n---\n`).join('\n'),
294-
message: getUserFriendlyMessage(job.state),
291+
message: getUserFriendlyMessage(job),
295292
time: new Date(job.lastUpdateTimeUtc),
296293
status: getStatusFromJobState(job.state),
294+
action: getAction(job),
297295
};
298296
return result;
299297
};
300298

299+
const getAction = (job) => {
300+
if (job.state !== 'Done' || job.testUrl == null || job.testUrl == undefined) return null;
301+
return { href: job.testUrl, label: 'Test in Power Virtual Agents' };
302+
};
303+
301304
const getStatusFromJobState = (state: PublishState): number => {
302305
switch (state) {
303306
case 'Done':
@@ -337,8 +340,8 @@ const getOperationIdOfLastJob = (botProjectId: string, profileName: string): str
337340
return '';
338341
};
339342

340-
const getUserFriendlyMessage = (state: PublishState): string => {
341-
switch (state) {
343+
const getUserFriendlyMessage = (job: PVAPublishJob): string => {
344+
switch (job.state) {
342345
case 'Done':
343346
return 'Publish successful.';
344347

extensions/pvaPublish/src/node/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export type PVAPublishJob = {
66
operationId: string;
77
startTimeUtc: string;
88
state: PublishState;
9+
testUrl: string;
910
};
1011

1112
type DiagnosticInfo = {
@@ -52,6 +53,7 @@ export interface PublishResult {
5253
message: string;
5354
status?: number;
5455
time?: Date;
56+
action?: { href: string; label: string };
5557
}
5658

5759
/** Copied from @bfc/extension */

0 commit comments

Comments
 (0)