Skip to content

Commit

Permalink
Several PVA integration improvements / bug fixes (#4776)
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyanziano committed Nov 12, 2020
1 parent 2b23ff6 commit e4c4f36
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export const ImportModal: React.FC<RouteComponentProps> = (props) => {
title: '',
onRenderCardContent: ImportSuccessNotificationWrapper({
importedToExisting: true,
location: existingProject.location,
location: path,
}),
});
addNotification(notification);
Expand Down
2 changes: 1 addition & 1 deletion Composer/packages/client/src/pages/publish/Publish.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ const LogDialog = (props) => {
<Dialog
dialogContentProps={logDialogProps}
hidden={false}
minWidth={450}
minWidth={700}
modalProps={{ isBlocking: true }}
onDismiss={props.onDismiss}
>
Expand Down
30 changes: 25 additions & 5 deletions Composer/packages/client/src/pages/publish/publishStatusList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky';
import { TooltipHost } from 'office-ui-fabric-react/lib/Tooltip';
import { Selection } from 'office-ui-fabric-react/lib/DetailsList';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { Link } from 'office-ui-fabric-react/lib/Link';
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
import moment from 'moment';
import { useMemo, useState, useEffect } from 'react';
Expand All @@ -35,6 +36,10 @@ export interface IStatus {
status: number;
message: string;
comment: string;
action?: {
href: string;
label: string;
};
}

function onRenderDetailsHeader(props, defaultRender) {
Expand Down Expand Up @@ -99,8 +104,8 @@ export const PublishStatusList: React.FC<IStatusListProps> = (props) => {
name: formatMessage('Status'),
className: 'publishstatus',
fieldName: 'status',
minWidth: 70,
maxWidth: 90,
minWidth: 40,
maxWidth: 40,
isResizable: true,
data: 'string',
onRender: (item: IStatus) => {
Expand All @@ -123,14 +128,29 @@ export const PublishStatusList: React.FC<IStatusListProps> = (props) => {
name: formatMessage('Message'),
className: 'publishmessage',
fieldName: 'message',
minWidth: 70,
maxWidth: 90,
minWidth: 150,
maxWidth: 300,
isResizable: true,
isCollapsible: true,
isMultiline: true,
data: 'string',
onRender: (item: IStatus) => {
return <span>{item.message}</span>;
return (
<span>
{item.message}
{item.action && (
<Link
aria-label={item.action.label}
href={item.action.href}
rel="noopener noreferrer"
style={{ marginLeft: '3px' }}
target="_blank"
>
{item.action.label}
</Link>
)}
</span>
);
},
isPadded: true,
},
Expand Down
1 change: 1 addition & 0 deletions Composer/packages/types/src/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type PullResponse = {
error?: any;
eTag?: string;
status: number;
/** Path to the pulled .zip containing updated bot content */
zipPath?: string;
};

Expand Down
1 change: 1 addition & 0 deletions Composer/packages/types/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type IBotProject = {
fileStorage: any;
dir: string;
dataDir: string;
eTag?: string;
id: string | undefined;
name: string;
builder: any;
Expand Down
12 changes: 8 additions & 4 deletions Composer/packages/ui-plugins/cross-trained/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License.

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

const config: PluginConfig = {
Expand All @@ -15,12 +15,16 @@ const config: PluginConfig = {
},
intentEditor: 'LuIntentEditor',
seedNewRecognizer: (shellData) => {
const { qnaFiles, luFiles, currentDialog, locale } = shellData;
const { qnaFiles, luFiles, currentDialog, locale, schemas } = shellData;
const qnaFile = qnaFiles.find((f) => f.id === `${currentDialog.id}.${locale}`);
const luFile = luFiles.find((f) => f.id === `${currentDialog.id}.${locale}`);

if (!qnaFile || !luFile) {
alert(formatMessage(`NO LU OR QNA FILE WITH NAME { id }`, { id: currentDialog.id }));
if (!luFile) {
alert(formatMessage(`NO LU FILE WITH NAME { id }`, { id: currentDialog.id }));
}

if (!qnaFile && !checkForPVASchema(schemas.sdk)) {
alert(formatMessage(`NO QNA FILE WITH NAME { id }`, { id: currentDialog.id }));
}

return `${currentDialog.id}.lu.qna`;
Expand Down
87 changes: 45 additions & 42 deletions extensions/pvaPublish/src/node/publish.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { IBotProject } from '@botframework-composer/types';
import { join } from 'path';
import { createReadStream, createWriteStream } from 'fs';
import { ensureDirSync, remove } from 'fs-extra';
import { createWriteStream } from 'fs';
import { ensureDirSync } from 'fs-extra';
import fetch, { RequestInit } from 'node-fetch';
import stream from 'stream';

import {
PVAPublishJob,
Expand Down Expand Up @@ -42,47 +43,39 @@ export const publish = async (
const { comment = '' } = metadata;

try {
logger.log('Starting publish to Power Virtual Agents.');
// authenticate with PVA
const base = baseUrl || getBaseUrl();
const creds = getAuthCredentials(base);
const accessToken = await getAccessToken(creds);

// TODO: Investigate optimizing stream logic before enabling extension.
// (https://github.com/microsoft/BotFramework-Composer/pull/4446#discussion_r510314378)

// where we will store the bot .zip
const zipDir = join(process.env.COMPOSER_TEMP_DIR as string, 'pva-publish');
ensureDirSync(zipDir);
const zipPath = join(zipDir, 'bot.zip');

// write the .zip to disk
const zipWriteStream = createWriteStream(zipPath);
// write the .zip to a buffer in memory
logger.log('Writing bot content to in-memory buffer.');
const botContentWriter = new stream.Writable();
const botContentData = [];
botContentWriter._write = (chunk, encoding, callback) => {
botContentData.push(chunk);
callback(); // let the internal write() call know that the _write() was successful
};
await new Promise((resolve, reject) => {
project.exportToZip((archive: NodeJS.ReadStream & { finalize: () => void; on: (ev, listener) => void }) => {
archive.on('error', (err) => {
console.error('Got error trying to export to zip: ', err);
reject(err.message);
});
archive.pipe(zipWriteStream);
archive.on('end', () => {
archive.unpipe();
zipWriteStream.end();
resolve();
});
});
project.exportToZip(
{ files: ['*.botproject'], directories: ['/knowledge-base/'] },
(archive: NodeJS.ReadStream & { finalize: () => void; on: (ev, listener) => void }) => {
archive.on('error', (err) => {
console.error('Got error trying to export to zip: ', err);
reject(err.message);
});
archive.on('end', () => {
archive.unpipe();
logger.log('Done reading bot content.');
resolve();
});
archive.pipe(botContentWriter);
}
);
});

// open up the .zip for reading
const zipReadStream = createReadStream(zipPath);
await new Promise((resolve, reject) => {
zipReadStream.on('error', (err) => {
reject(err);
});
zipReadStream.once('readable', () => {
resolve();
});
});
const length = zipReadStream.readableLength;
const botContent = Buffer.concat(botContentData);
logger.log('In-memory buffer created from bot content.');

// initiate the publish job
let url = `${base}api/botmanagement/${API_VERSION}/environments/${envId}/bots/${botId}/composer/publishoperations?deleteMissingDependencies=${deleteMissingDependencies}`;
Expand All @@ -91,15 +84,16 @@ export const publish = async (
}
const res = await fetch(url, {
method: 'POST',
body: zipReadStream,
body: botContent,
headers: {
...getAuthHeaders(accessToken, tenantId),
'Content-Type': 'application/zip',
'Content-Length': length.toString(),
'Content-Length': botContent.buffer.byteLength,
'If-Match': project.eTag,
},
});
const job: PVAPublishJob = await res.json();
logger.log('Publish job started: %O', job);

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

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

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

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

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

const getAction = (job) => {
if (job.state !== 'Done' || job.testUrl == null || job.testUrl == undefined) return null;
return { href: job.testUrl, label: 'Test in Power Virtual Agents' };
};

const getStatusFromJobState = (state: PublishState): number => {
switch (state) {
case 'Done':
Expand Down Expand Up @@ -337,8 +340,8 @@ const getOperationIdOfLastJob = (botProjectId: string, profileName: string): str
return '';
};

const getUserFriendlyMessage = (state: PublishState): string => {
switch (state) {
const getUserFriendlyMessage = (job: PVAPublishJob): string => {
switch (job.state) {
case 'Done':
return 'Publish successful.';

Expand Down
2 changes: 2 additions & 0 deletions extensions/pvaPublish/src/node/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type PVAPublishJob = {
operationId: string;
startTimeUtc: string;
state: PublishState;
testUrl: string;
};

type DiagnosticInfo = {
Expand Down Expand Up @@ -52,6 +53,7 @@ export interface PublishResult {
message: string;
status?: number;
time?: Date;
action?: { href: string; label: string };
}

/** Copied from @bfc/extension */
Expand Down

0 comments on commit e4c4f36

Please sign in to comment.