From b94ee7dee6a8f45aa1b136dbdfbf9c03bc698ab2 Mon Sep 17 00:00:00 2001 From: Steven Davelaar Date: Wed, 22 Nov 2023 12:26:08 +0100 Subject: [PATCH] Version 2.7.2 (#51) - Removed logging that might disclose sensitive info - Fixed name of bag item disambiguateMessage event in TypeScript - Added new CMM message types and properties - Typescript bots-node-sdk did not allow creating sub-type items for DateTime bag item. --- RELEASE_NOTES.md | 12 ++ lib/dataquery/utils.js | 7 +- lib/entity/utils.js | 4 +- package-lock.json | 2 +- package.json | 2 +- ts/lib.ts | 2 +- ts/lib/component/kinds.ts | 5 +- ts/lib/dataquery/utils.ts | 7 +- ts/lib/entity/utils.ts | 5 +- ts/lib2/llmcomponent/utils.ts | 6 +- ts/lib2/llmtransformation/utils.ts | 4 +- ts/lib2/messagev2/action/action.ts | 28 +++ ts/lib2/messagev2/action/index.ts | 1 + ts/lib2/messagev2/action/popupAction.ts | 53 ++++++ ts/lib2/messagev2/common/messageUtil.ts | 11 +- ts/lib2/messagev2/internal.ts | 3 + ts/lib2/messagev2/messageFactory.ts | 74 +++++--- .../messagePayload/commandMessage.ts | 7 +- .../executeApplicationActionCommandMessage.ts | 81 +++++++++ ts/lib2/messagev2/messagePayload/index.ts | 2 + .../updateApplicationContextCommandMessage.ts | 159 ++++++++++++++++++ .../json/executeApplicationActionCommand.json | 8 + ts/spec/json/textWithActions.json | 2 + ts/spec/json/textWithPopupAction.json | 79 +++++++++ .../json/updateApplicationContextCommand.json | 10 ++ .../lib/message/messageDeserializer.spec.ts | 43 ++++- ts/spec/lib/message/messageFactory.spec.ts | 4 +- 27 files changed, 563 insertions(+), 58 deletions(-) create mode 100644 ts/lib2/messagev2/action/popupAction.ts create mode 100644 ts/lib2/messagev2/messagePayload/executeApplicationActionCommandMessage.ts create mode 100644 ts/lib2/messagev2/messagePayload/updateApplicationContextCommandMessage.ts create mode 100644 ts/spec/json/executeApplicationActionCommand.json create mode 100644 ts/spec/json/textWithPopupAction.json create mode 100644 ts/spec/json/updateApplicationContextCommand.json diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 1bc2a7b1..9ecb785d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,6 @@ # Release Notes +- [Version 2.7.2](#v272) - [Version 2.7.1](#v271) - [Version 2.7.0](#v270) - [Version 2.6.8](#v268) @@ -20,6 +21,17 @@ - [Version 2.4.3](#v243) - [Version 2.4.2](#v242) +## Version 2.7.2 + +### New Features +- Added new CMM message types and properties + +### Fixed Issues + +- Removed logging that might disclose sensitive info +- Fixed name of bag item disambiguateMessage event in TypeScript +- Fixed issue with nested items not supported in entity item event in TypeScript + ## Version 2.7.1 ### Fixed Issues diff --git a/lib/dataquery/utils.js b/lib/dataquery/utils.js index 25f1d160..12108252 100644 --- a/lib/dataquery/utils.js +++ b/lib/dataquery/utils.js @@ -31,7 +31,6 @@ async function invokeDataQueryEventHandlers(component, context) { // update row with formatted value if attribute is included in the row and has a value if (row[attributeName]) { event.properties = {'attributeValue': row[attributeName]}; - logger.debug(`Invoking event handler ${handlerPath} with event: ${JSON.stringify(event)}`); let returnValue = await Promise.resolve(handler(event.properties, context)); if (returnValue) { row[attributeName] = returnValue @@ -43,7 +42,6 @@ async function invokeDataQueryEventHandlers(component, context) { let metadata = context.getAttributeUISettings(attributeName); if (metadata) { event.properties = {'settings': metadata}; - logger.debug(`Invoking event handler ${handlerPath} with event: ${JSON.stringify(event)}`); let returnValue = await Promise.resolve(handler(event.properties, context)); if (returnValue) { context.setAttributeUISettings(attributeName, returnValue); @@ -54,7 +52,6 @@ async function invokeDataQueryEventHandlers(component, context) { let settings = context.getUISettings(); if (settings) { event.properties = {'settings': settings}; - logger.debug(`Invoking event handler ${handlerPath} with event: ${JSON.stringify(event)}`); let returnValue = await Promise.resolve(handler(event.properties, context)); if (returnValue) { context.setUISettings(returnValue); @@ -63,21 +60,19 @@ async function invokeDataQueryEventHandlers(component, context) { } else if (handlerPath === `entity.changeResponseData`) { let data = context.getQueryResult() || []; event.properties = {'responseData': data}; - logger.debug(`Invoking event handler ${handlerPath} with event: ${JSON.stringify(event)}`); let returnValue = await Promise.resolve(handler(event.properties, context)); if (returnValue) { context.setQueryResult(returnValue); } } else if (handlerPath === `entity.changeBotMessages`) { event.properties = {'messages': context.getRequest().candidateMessages}; - logger.debug(`Invoking event handler ${handlerPath} with event: ${JSON.stringify(event)}`); let returnValue = await Promise.resolve(handler(event.properties, context)); if (returnValue) { context.getResponse().messages = returnValue; } } } else { - logger.debug(`No handler found for event: ${handlerPath}`); + logger.error(`No handler found for event: ${handlerPath}`); break; } } diff --git a/lib/entity/utils.js b/lib/entity/utils.js index 2ebdfd2a..9ccaeb18 100644 --- a/lib/entity/utils.js +++ b/lib/entity/utils.js @@ -35,7 +35,6 @@ async function invokeResolveEntitiesEventHandlers(component, context) { handlerPath = `entity.${eventName}`; } if (handler) { - logger.debug(`Invoking event handler ${handlerPath} with event: ${JSON.stringify(event)}`); // event handlers can be async (returning a promise), but we dont want to enforce // every event handler is async, hence Promise.resolve wrapping of invocation let returnValue = await Promise.resolve(handler(event.properties || {}, context)); @@ -43,7 +42,6 @@ async function invokeResolveEntitiesEventHandlers(component, context) { let retValue = returnValue === undefined ? true : (returnValue+''==='true') logger.debug(`${eventName} returned ${retValue}`); if (eventName==='shouldPrompt') { - logger.debug(`Adding ${itemName} to shouldPrompt cache with value ${retValue}`); context._getShouldPromptCache()[itemName] = retValue; if (retValue) { // only invoke next shouldPrompt handler when current handler returned false @@ -62,7 +60,7 @@ async function invokeResolveEntitiesEventHandlers(component, context) { } } } else { - logger.debug(`No handler found for event: ${handlerPath}`); + logger.error(`No handler found for event: ${handlerPath}`); break; } } diff --git a/package-lock.json b/package-lock.json index 41a54af9..7da28d19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@oracle/bots-node-sdk", - "version": "2.7.1", + "version": "2.7.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d591a47c..212c8a0f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@oracle/bots-node-sdk", - "version": "2.7.1", + "version": "2.7.2", "description": "Oracle Digital Assistant SDK for custom component development and webhook integrations", "main": "index.js", "browser": "index-browser.js", diff --git a/ts/lib.ts b/ts/lib.ts index cd7f5663..ba71b6ef 100644 --- a/ts/lib.ts +++ b/ts/lib.ts @@ -75,7 +75,7 @@ export namespace Lib { export type EntityItemPublishPromptMessageEvent = lib.EntityItemPublishPromptMessageEvent - export type EntityItemPublishDisambiguationMessageEvent = lib.EntityItemPublishDisambiguationMessageEvent + export type EntityItemPublishDisambiguateMessageEvent = lib.EntityItemPublishDisambiguateMessageEvent export type EntityItemMaxPromptsReachedEvent = lib.EntityItemMaxPromptsReachedEvent diff --git a/ts/lib/component/kinds.ts b/ts/lib/component/kinds.ts index 4a6db5a6..26050d6b 100644 --- a/ts/lib/component/kinds.ts +++ b/ts/lib/component/kinds.ts @@ -67,10 +67,11 @@ export interface EntityEvent { } export interface EntityItemEvents { + items?: EntityItems; shouldPrompt?(event: EntityValidateEvent, context: EntityResolutionContext): void validate?(event: EntityItemValidateEvent, context: EntityResolutionContext): void publishPromptMessage?(event: EntityItemPublishPromptMessageEvent, context: EntityResolutionContext): void - publishDisambiguationMessage?(event: EntityItemPublishDisambiguationMessageEvent, context: EntityResolutionContext): void + publishDisambiguateMessage?(event: EntityItemPublishDisambiguateMessageEvent, context: EntityResolutionContext): void maxPromptsReached?(event: EntityItemMaxPromptsReachedEvent, context: EntityResolutionContext): void } @@ -139,7 +140,7 @@ export interface EntityItemPublishPromptMessageEvent extends EntityBaseEvent { promptCount: number; } -export interface EntityItemPublishDisambiguationMessageEvent extends EntityBaseEvent { +export interface EntityItemPublishDisambiguateMessageEvent extends EntityBaseEvent { disambiguationValues: object[]; } diff --git a/ts/lib/dataquery/utils.ts b/ts/lib/dataquery/utils.ts index 7da918b0..6aa1005a 100644 --- a/ts/lib/dataquery/utils.ts +++ b/ts/lib/dataquery/utils.ts @@ -34,7 +34,6 @@ export async function invokeDataQueryEventHandlers(component: DataQueryEventHand // update row with formatted value if attribute is included in the row and has a value if (row[attributeName]) { event.properties = {'attributeValue': row[attributeName]}; - logger.debug(`Invoking event handler ${handlerPath} with event: ${JSON.stringify(event)}`); let returnValue = await Promise.resolve(handler(event.properties, context)); if (returnValue) { row[attributeName] = returnValue @@ -46,7 +45,6 @@ export async function invokeDataQueryEventHandlers(component: DataQueryEventHand let metadata = context.getAttributeUISettings(attributeName); if (metadata) { event.properties = {'settings': metadata}; - logger.debug(`Invoking event handler ${handlerPath} with event: ${JSON.stringify(event)}`); let returnValue = await Promise.resolve(handler(event.properties, context)); if (returnValue) { context.setAttributeUISettings(attributeName, returnValue); @@ -57,7 +55,6 @@ export async function invokeDataQueryEventHandlers(component: DataQueryEventHand let settings = context.getUISettings(); if (settings) { event.properties = {'settings': settings}; - logger.debug(`Invoking event handler ${handlerPath} with event: ${JSON.stringify(event)}`); let returnValue = await Promise.resolve(handler(event.properties, context)); if (returnValue) { context.setUISettings(returnValue); @@ -66,21 +63,19 @@ export async function invokeDataQueryEventHandlers(component: DataQueryEventHand } else if (handlerPath === `entity.changeResponseData`) { let data = context.getQueryResult() || []; event.properties = {'responseData': data}; - logger.debug(`Invoking event handler ${handlerPath} with event: ${JSON.stringify(event)}`); let returnValue = await Promise.resolve(handler(event.properties, context)); if (returnValue) { context.setQueryResult(returnValue); } } else if (handlerPath === `entity.changeBotMessages`) { event.properties = {'messages': context.getRequest().candidateMessages}; - logger.debug(`Invoking event handler ${handlerPath} with event: ${JSON.stringify(event)}`); let returnValue = await Promise.resolve(handler(event.properties, context)); if (returnValue) { context.getResponse().messages = returnValue; } } } else { - logger.debug(`No handler found for event: ${handlerPath}`); + logger.error(`No handler found for event: ${handlerPath}`); break; } } diff --git a/ts/lib/entity/utils.ts b/ts/lib/entity/utils.ts index 26ac4c31..514e7de1 100644 --- a/ts/lib/entity/utils.ts +++ b/ts/lib/entity/utils.ts @@ -33,15 +33,12 @@ export async function invokeResolveEntitiesEventHandlers( handlerPath = `entity.${eventName}`; } if (handler) { - logger.debug(`Invoking event handler ${handlerPath} with event: ${JSON.stringify(event)}`); // event handlers can be async (returning a promise), but we dont want to enforce // every event handler is async, hence Promise.resolve wrapping of invocation let returnValue = await Promise.resolve(handler(event.properties || {}, context)); // make sure return value is a boolean let retValue = returnValue === undefined ? true : (returnValue + '' === 'true') - logger.debug(`${eventName} returned ${retValue}`); if (eventName === 'shouldPrompt') { - logger.debug(`Adding ${itemName} to shouldPrompt cache with value ${retValue}`); context._getShouldPromptCache()[itemName] = retValue; if (retValue) { // only invoke next shouldPrompt handler when current handler returned false @@ -60,7 +57,7 @@ export async function invokeResolveEntitiesEventHandlers( } } } else { - logger.debug(`No handler found for event: ${handlerPath}`); + logger.error(`No handler found for event: ${handlerPath}`); break; } } diff --git a/ts/lib2/llmcomponent/utils.ts b/ts/lib2/llmcomponent/utils.ts index d6e24b53..c93d3bcf 100644 --- a/ts/lib2/llmcomponent/utils.ts +++ b/ts/lib2/llmcomponent/utils.ts @@ -24,14 +24,11 @@ export async function invokeLlmComponentHandlers(component: LlmComponentHandler, // event handlers can be async (returning a promise), but we dont want to enforce // every event handler is async, hence Promise.resolve wrapping of invocation if (eventName === `validateResponsePayload` || eventName === `validateRequestPayload`) { - logger.debug(`Invoking event handler ${eventName}`); let returnValue = await Promise.resolve(handler(event.properties, context)); // make sure return value is a boolean let retValue = returnValue === undefined ? true : (returnValue + '' === 'true') - logger.debug(`${eventName} returned ${retValue}`); context.getResponse().valid = retValue; } else if (eventName === `changeBotMessages` ) { - logger.debug(`Invoking event handler ${eventName}`); // convert json messages to message class const mf = context.getMessageFactory(); let messages = event.properties.messages.map(msg => mf.messageFromJson(msg)); @@ -48,11 +45,10 @@ export async function invokeLlmComponentHandlers(component: LlmComponentHandler, let currMessages = context.getResponse().messages || []; context.getResponse().messages = currMessages.concat(messages); } else { - logger.debug(`Invoking ${event.custom ? 'custom ' : ''}event handler ${eventName}`); await Promise.resolve(handler(event.properties, context)); } } else { - logger.debug(`No handler found for event: ${eventName}`); + logger.error(`No handler found for event: ${eventName}`); } } diff --git a/ts/lib2/llmtransformation/utils.ts b/ts/lib2/llmtransformation/utils.ts index e745bebb..84c3a103 100644 --- a/ts/lib2/llmtransformation/utils.ts +++ b/ts/lib2/llmtransformation/utils.ts @@ -18,20 +18,18 @@ export async function invokeLlmTransformationHandlers(component: LlmTransformati // event handlers can be async (returning a promise), but we dont want to enforce // every event handler is async, hence Promise.resolve wrapping of invocation if (eventName === `transformRequestPayload`) { - logger.debug(`Invoking event handler ${eventName}`); let returnValue = await Promise.resolve(handler(event.properties, context)); if (returnValue) { context.setRequestPayload(returnValue); } } else if (eventName === `transformResponsePayload` || eventName === `transformErrorResponsePayload`) { - logger.debug(`Invoking event handler ${eventName}`); let returnValue = await Promise.resolve(handler(event.properties, context)); if (returnValue) { context.setResponsePayload(returnValue); } } } else { - logger.debug(`No handler found for event: ${eventName}`); + logger.error(`No handler found for event: ${eventName}`); } } diff --git a/ts/lib2/messagev2/action/action.ts b/ts/lib2/messagev2/action/action.ts index 1fc2b30a..5ec7a00b 100644 --- a/ts/lib2/messagev2/action/action.ts +++ b/ts/lib2/messagev2/action/action.ts @@ -10,6 +10,7 @@ export class Action extends ChannelCustomizable { private voice?: Voice; private imageUrl?: string; private style?: ActionStyle; + private displayType?: DisplayType; /** * Deserialize nested object properties into corresponding class instances @@ -119,6 +120,24 @@ export class Action extends ChannelCustomizable { return this; } + /** + * Gets the style of the action. + * @returns {DisplayType} The display type of the action. + */ + public getDisplayType(): DisplayType { + return this.displayType; + } + + /** + * Sets the display type of the action. + * @param {DisplayType} displayType - The display type to set. + * @returns The current instance of the Action class. + */ + public setDisplayType(displayType: DisplayType): this { + this.displayType = displayType; + return this; + } + } /** @@ -129,3 +148,12 @@ export enum ActionStyle { danger = 'danger', } +/** + * Represents the display type of an action. + */ +export enum DisplayType { + button = 'button', + link = 'link', + icon = 'icon', +} + diff --git a/ts/lib2/messagev2/action/index.ts b/ts/lib2/messagev2/action/index.ts index 8cf5906e..1c8ad174 100644 --- a/ts/lib2/messagev2/action/index.ts +++ b/ts/lib2/messagev2/action/index.ts @@ -1,4 +1,5 @@ export * from './callAction'; +export * from './popupAction'; export * from './action'; export * from './keyword'; export * from './locationAction'; diff --git a/ts/lib2/messagev2/action/popupAction.ts b/ts/lib2/messagev2/action/popupAction.ts new file mode 100644 index 00000000..fd02c20d --- /dev/null +++ b/ts/lib2/messagev2/action/popupAction.ts @@ -0,0 +1,53 @@ +import { Action, NonRawMessage, MessageUtil } from '../internal'; + +/** + * Represents a popup action. When clicked a popup dialog opens that shows the popup content + * @extends Action + */ +export class PopupAction extends Action { + public readonly type: string = 'popup'; + private popupContent: NonRawMessage; + + /** + * Creates an instance of PopupAction. + * @param label - The label of the popup action. + * @param popupContent - The popup content associated with the popup action. + */ + constructor(label: string, popupContent: NonRawMessage) { + super(label); + this.popupContent = popupContent; + } + + /** + * Deserialize nested object properties into corresponding class instances + */ + public deserializeNestedProperties(): void { + super.deserializeNestedProperties(); + if (this.popupContent) { + this.popupContent = MessageUtil.deserializeMessage(this.popupContent); + } + } + + /** + * Gets the popup content associated with the popup action. + * @returns The popup content associated with the popup action. + */ + public getPopupContent(): NonRawMessage { + return this.popupContent; + } + + /** + * Sets the popup content associated with the popup action. + * @param popupContent - The popup content to set. + * @returns The current instance of the PopupAction class. + */ + public setPopupContent(popupContent: NonRawMessage): this { + this.popupContent = popupContent; + return this; + } + +} + + + + diff --git a/ts/lib2/messagev2/common/messageUtil.ts b/ts/lib2/messagev2/common/messageUtil.ts index 4bee7e58..5bb6a45b 100644 --- a/ts/lib2/messagev2/common/messageUtil.ts +++ b/ts/lib2/messagev2/common/messageUtil.ts @@ -4,7 +4,7 @@ import { , TimePickerField, ToggleField, LinkField, Field, TextMessage, Attachment, AttachmentMessage, Card, CardMessage , CommandMessage, EditFormMessage, FormMessage, FormSubmissionMessage, NonRawMessage, TableMessage, TableFormMessage, ReadOnlyForm , PaginationInfo, ChannelExtensions, Row, TableHeading, Location, LocationMessage, PostbackMessage, TextStreamMessage - , Column, FormRow, ActionField, MediaField + , Column, FormRow, ActionField, MediaField, PopupAction, UpdateApplicationContextCommandMessage, ExecuteApplicationActionCommandMessage } from '../internal'; @@ -55,6 +55,12 @@ export class MessageUtil { case 'textStream': msg = Object.assign(new TextStreamMessage(null, null, null, null), json); break; + case 'updateApplicationContextCommand': + msg = Object.assign(new UpdateApplicationContextCommandMessage(null), json); + break; + case 'executeApplicationActionCommand': + msg = Object.assign(new ExecuteApplicationActionCommandMessage(null, null), json); + break; default: throw new Error(`Error deserializing message, unknown message type: ${type}`); } @@ -87,6 +93,9 @@ export class MessageUtil { case 'submitForm': action = Object.assign(new SubmitFormAction(null), json); break; + case 'popup': + action = Object.assign(new PopupAction(null, null), json); + break; case 'url': action = Object.assign(new UrlAction(null, null), json); break; diff --git a/ts/lib2/messagev2/internal.ts b/ts/lib2/messagev2/internal.ts index 66413ea6..34c11eee 100644 --- a/ts/lib2/messagev2/internal.ts +++ b/ts/lib2/messagev2/internal.ts @@ -17,6 +17,7 @@ export * from './common/formRow'; export * from './action/action'; export * from './action/keyword'; export * from './action/callAction'; +export * from './action/popupAction'; export * from './action/locationAction'; export * from './action/postbackAction'; export * from './action/customEventAction'; @@ -48,6 +49,8 @@ export * from './messagePayload/textStreamMessage'; export * from './messagePayload/cardMessage'; export * from './messagePayload/attachmentMessage'; export * from './messagePayload/commandMessage'; +export * from './messagePayload/executeApplicationActionCommandMessage'; +export * from './messagePayload/updateApplicationContextCommandMessage'; export * from './messagePayload/editFormMessage'; export * from './messagePayload/formMessage'; export * from './messagePayload/rawMessage'; diff --git a/ts/lib2/messagev2/messageFactory.ts b/ts/lib2/messagev2/messageFactory.ts index cd63750d..f7c7400a 100644 --- a/ts/lib2/messagev2/messageFactory.ts +++ b/ts/lib2/messagev2/messageFactory.ts @@ -3,8 +3,10 @@ import { , Voice, MessageUtil, Row, TableHeading, DatePickerField, MultiSelectField, SelectFieldOption, NumberInputField, SingleSelectField , TextField, TextInputField, TimePickerField, ToggleField, LinkField, Field, ReadOnlyField, TextMessage, Attachment, AttachmentType , AttachmentMessage, Card, CardMessage, CommandMessage, CommandType, EditFormMessage, FormMessage, NonRawMessage, RawMessage - , TableMessage, TableFormMessage, TextStreamMessage, StreamState, CustomEventAction + , TableMessage, TableFormMessage, TextStreamMessage, StreamState, CustomEventAction, PopupAction, ActionField, MediaField, MediaType + , Action } from './internal'; +import { ExecuteApplicationActionCommandMessage, UpdateApplicationContextCommandMessage } from './messagePayload'; /** * Factory class to create the various message types of Oracle Digital Assistant Conversation Message Model (CMM) @@ -174,6 +176,26 @@ export class MessageFactory { return new RawMessage(payload); } + /** + * Creates an instance of the ExecuteApplicationActionCommandMessage class. + * @param {string} applicationName The name of the application + * @param {string} action The action to execute + * @returns {ExecuteApplicationActionCommandMessage} The created instance of the ExecuteApplicationActionCommandMessage. + */ + public static createExecuteApplicationActionCommandMessage(applicationName: string, action: string): + ExecuteApplicationActionCommandMessage { + return new ExecuteApplicationActionCommandMessage(applicationName, action); + } + + /** + * Creates an instance of the UpdateApplicationContextCommandMessage class. + * @param {string} applicationName The name of the application + * @returns {UpdateApplicationContextCommandMessage} The created instance of the UpdateApplicationContextCommandMessage. + */ + public static createUpdateApplicationContextCommandMessage(applicationName: string): UpdateApplicationContextCommandMessage { + return new UpdateApplicationContextCommandMessage(applicationName); + } + /** * Creates an instance of the PaginationInfo class. * @param {number} totalCount The total count. @@ -243,6 +265,16 @@ export class MessageFactory { return new UrlAction(label, url); } + /** + * Create a new Popup action + * @param {string} label The label of the Popup action. + * @param {NonRawMessage} popupContent The content shown in the popup. + * @returns {PopupAction} A new instance of the PopupAction class. + */ + public static createPopupAction(label: string, popupContent: NonRawMessage): PopupAction { + return new PopupAction(label, popupContent); + } + /** * Create a new Call action * @param {string} label The label of the Call action. @@ -368,26 +400,24 @@ export class MessageFactory { return new LinkField(label, value, linkLabel); } - // fields below will be added in 23.08 - - // /** - // * Creates an instance of the ActionField class. - // * @param action The action of the action field. - // * @returns The created instance of the ActionField. - // */ - // public static createActionField(action: Action) { - // return new ActionField(action); - // } - - // /** - // * Creates an instance of the MediaField class. - // * @param label The label of the field. - // * @param value The URL value of the field - // * @param mediaType The media type for the field. - // * @returns The created instance of the MediaField. - // */ - // public static createMediaField(label: string, value: string, mediaType: MediaType): MediaField { - // return new MediaField(label, value, mediaType); - // } + /** + * Creates an instance of the ActionField class. + * @param action The action of the action field. + * @returns The created instance of the ActionField. + */ + public static createActionField(action: Action) { + return new ActionField(action); + } + + /** + * Creates an instance of the MediaField class. + * @param label The label of the field. + * @param value The URL value of the field + * @param mediaType The media type for the field. + * @returns The created instance of the MediaField. + */ + public static createMediaField(label: string, value: string, mediaType: MediaType): MediaField { + return new MediaField(label, value, mediaType); + } } diff --git a/ts/lib2/messagev2/messagePayload/commandMessage.ts b/ts/lib2/messagev2/messagePayload/commandMessage.ts index a39055a0..eee42af9 100644 --- a/ts/lib2/messagev2/messagePayload/commandMessage.ts +++ b/ts/lib2/messagev2/messagePayload/commandMessage.ts @@ -63,5 +63,10 @@ export enum CommandType { home = 'home', exit = 'exit', startDoNotDisturbMode = 'startDoNotDisturbMode', - stopDoNotDisturbMode = 'stopDoNotDisturbMode' + stopDoNotDisturbMode = 'stopDoNotDisturbMode', + invokeFlow = 'invokeFlow', + updateApplicationContext = 'updateApplicationContext', + custom = 'custom', + replayRequest = 'replayRequest', + executeApplicationAction = 'executeApplicationAction' } diff --git a/ts/lib2/messagev2/messagePayload/executeApplicationActionCommandMessage.ts b/ts/lib2/messagev2/messagePayload/executeApplicationActionCommandMessage.ts new file mode 100644 index 00000000..3158b769 --- /dev/null +++ b/ts/lib2/messagev2/messagePayload/executeApplicationActionCommandMessage.ts @@ -0,0 +1,81 @@ +import { CommandMessage, CommandType } from '../internal'; + +/** + * Represents an execute application action command message. + * This message is used in co-pilot where this message is sent from the skill to the client to trigger a specific action in a + * page, e.g. updateing fields. + * + * @extends CommandMessage + */ +export class ExecuteApplicationActionCommandMessage extends CommandMessage { + public readonly type: string = 'executeApplicationActionCommand'; + private applicationName: string; + private pageName?: string; + private action: string; + + /** + * Creates an instance of the ExecuteApplicationActionCommandMessage class. + * @param {string} applicationName The name of the application + */ + constructor(applicationName: string, action: string) { + super(CommandType.executeApplicationAction); + this.applicationName = applicationName; + this.action = action; + } + + + /** + * Gets the application name + * @returns {string} The name of the application + */ + public getApplicationName(): string { + return this.applicationName; + } + + /** + * Sets the application name + * @param {string} The name of the application + * @returns {this} The updated instance of the ExecuteApplicationActionCommandMessage. + */ + public setApplicationName(applicationName: string): this { + this.applicationName = applicationName; + return this; + } + + /** + * Gets the page name + * @returns {string} The name of the page + */ + public getPageName(): string { + return this.pageName; + } + + /** + * Sets the page name + * @param {string} The name of the page + * @returns {this} The updated instance of the ExecuteApplicationActionCommandMessage. + */ + public setPageName(pageName: string): this { + this.pageName = pageName; + return this; + } + + /** + * Gets the action + * @returns {string} The action + */ + public getAction(): string { + return this.action; + } + + /** + * Sets the action + * @param {string} The action + * @returns {this} The updated instance of the ExecuteApplicationActionCommandMessage. + */ + public setAction(action: string): this { + this.action = action; + return this; + } + +} diff --git a/ts/lib2/messagev2/messagePayload/index.ts b/ts/lib2/messagev2/messagePayload/index.ts index 9ab06b89..3142641a 100644 --- a/ts/lib2/messagev2/messagePayload/index.ts +++ b/ts/lib2/messagev2/messagePayload/index.ts @@ -3,6 +3,8 @@ export * from './textStreamMessage'; export * from './cardMessage'; export * from './attachmentMessage'; export * from './commandMessage'; +export * from './updateApplicationContextCommandMessage'; +export * from './executeApplicationActionCommandMessage'; export * from './editFormMessage'; export * from './formMessage'; export * from './rawMessage'; diff --git a/ts/lib2/messagev2/messagePayload/updateApplicationContextCommandMessage.ts b/ts/lib2/messagev2/messagePayload/updateApplicationContextCommandMessage.ts new file mode 100644 index 00000000..d6d73fc2 --- /dev/null +++ b/ts/lib2/messagev2/messagePayload/updateApplicationContextCommandMessage.ts @@ -0,0 +1,159 @@ +import { CommandMessage, CommandType } from '../internal'; + +/** + * Represents the context source that initiates the application context update + */ +export enum ContextSource { + chatWindow = 'chatWindow', + UIWidget = 'UIWidget', + skill = 'skill' +} + +/** + * Represents an update context command message. + * This message is used in co-pilot where the application context can be sent from the client to the skill to + * invoke or resume a flow, or it is sent from the skill to the client to trigger application navigation. + * + * @extends CommandMessage + */ +export class UpdateApplicationContextCommandMessage extends CommandMessage { + public readonly type: string = 'updateApplicationContextCommand'; + private applicationName: string; + private pageName?: string; + private fieldName?: string; + private parameters?: Map; + private contextSource: ContextSource; + private reset?: boolean; + + /** + * Creates an instance of the UpdateContextCommandMessage class. + * @param {string} applicationName The command type. + */ + constructor(applicationName: string) { + super(CommandType.updateApplicationContext); + this.contextSource = ContextSource.skill; + this.applicationName = applicationName; + } + + /** + * Gets the context source + * @returns {ContextSource} The context source + */ + public getContextSource(): ContextSource { + return this.contextSource; + } + + /** + * Gets the application name + * @returns {string} The name of the application + */ + public getApplicationName(): string { + return this.applicationName; + } + + /** + * Sets the application name + * @param {string} The name of the application + * @returns {this} The updated instance of the UpdateContextCommandMessage. + */ + public setApplicationName(applicationName: string): this { + this.applicationName = applicationName; + return this; + } + + /** + * Gets the page name + * @returns {string} The name of the page + */ + public getPageName(): string { + return this.pageName; + } + + /** + * Sets the page name + * @param {string} The name of the page + * @returns {this} The updated instance of the UpdateContextCommandMessage. + */ + public setPageName(pageName: string): this { + this.pageName = pageName; + return this; + } + + /** + * Gets the field name + * @returns {string} The name of the field + */ + public getFieldName(): string { + return this.fieldName; + } + + /** + * Sets the field name + * @param {string} The name of the field + * @returns {this} The updated instance of the UpdateContextCommandMessage. + */ + public setFieldName(fieldName: string): this { + this.fieldName = fieldName; + return this; + } + + /** + * Gets the parameters of the message. + * @returns {Map} The parameters of the message. + */ + public getParameters(): Map { + return this.parameters; + } + + /** + * Gets the value of a parameter. + * @param {string} parameterName The name of the parameter. + * @returns {any} The parameter value. + */ + public getParameterValue(parameterName: string): any { + return this.parameters ? this.parameters[parameterName] : undefined; + } + + /** + * Sets the parameters of the message. + * @param {Map} parameters The parameters to set. + * @returns {this} The current instance of the UpdateContextCommandMessage class. + */ + public setParameters(parameters: Map): this { + this.parameters = parameters; + return this; + } + + /** + * Add a parameter to the message. + * @param {string} name The name of the parameter. + * @param {any} value The value of the parameter. + * @returns {this} The current instance of the UpdateContextCommandMessage class. + */ + public addParameter(name: string, value: any): this { + if (!this.parameters) { + this.parameters = new Map(); + } + this.parameters[name] = value; + return this; + } + + /** + * Returns the flow reset flag + * @returns {boolean} flow reset flag + */ + public getReset(): boolean { + return this.reset; + } + + /** + * Set the flow reset flag + * @param {boolean} reset The reset flag + * @returns {this} The current instance of the UpdateContextCommandMessage class. + */ + public setReset(reset: boolean): this { + this.reset = reset; + return this; + } + +} diff --git a/ts/spec/json/executeApplicationActionCommand.json b/ts/spec/json/executeApplicationActionCommand.json new file mode 100644 index 00000000..457750f7 --- /dev/null +++ b/ts/spec/json/executeApplicationActionCommand.json @@ -0,0 +1,8 @@ +{ + "type": "executeApplicationActionCommand", + "command": "executeApplicationAction", + "applicationName": "HCM", + "pageName": "JobRequisition", + "action" : "updateFields", + "properties": {"foo": "bar"} +} \ No newline at end of file diff --git a/ts/spec/json/textWithActions.json b/ts/spec/json/textWithActions.json index f202a3c2..4e554963 100644 --- a/ts/spec/json/textWithActions.json +++ b/ts/spec/json/textWithActions.json @@ -19,6 +19,8 @@ "word1", "word2" ], + "displayType": "icon", + "style": "danger", "channelExtensions": { "facebook": { "myProp": "myValue", diff --git a/ts/spec/json/textWithPopupAction.json b/ts/spec/json/textWithPopupAction.json new file mode 100644 index 00000000..fc3e0cf8 --- /dev/null +++ b/ts/spec/json/textWithPopupAction.json @@ -0,0 +1,79 @@ +{ + "text": "I'm sorry the answer wasn't helpful. Can you please provide feedback to help me improve?", + "type": "text", + "actions": [ + { + "popupContent": { + "type": "editForm", + "title": "Give your feedback", + "fields": [ + { + "displayType": "text", + "labelFontWeight": "bold", + "label": "What was the issue with this response?" + }, + { + "displayType": "multiSelect", + "options": [ + { + "label": "Inaccurate", + "value": "inaccurate" + }, + { + "label": "Inappropriate", + "value": "inappropriate" + }, + { + "label": "Irrelevant", + "value": "irrelevant" + }, + { + "label": "Other", + "value": "other" + } + ], + "layoutStyle": "checkboxes", + "id": "system_feedback_reasons", + "required": true + }, + { + "displayType": "textInput", + "multiLine": true, + "id": "system_feedback_comments", + "placeholder": "Additional feedback" + } + ], + "formColumns": 1, + "actions": [ + { + "postback": { + "rating": "negative", + "system.botId": "D92910F5-16F9-4F24-BF2B-8284B356AC61", + "action": "cancel", + "system.flow": "UnresolvedIntent", + "feedbackType": "llm", + "system.state": "invokeLLM" + }, + "label": "Cancel", + "type": "postback" + }, + { + "postback": { + "rating": "negative", + "system.botId": "D92910F5-16F9-4F24-BF2B-8284B356AC61", + "action": "cancel", + "system.flow": "UnresolvedIntent", + "feedbackType": "llm", + "system.state": "invokeLLM" + }, + "label": "Submit Feedback", + "type": "submitForm" + } + ], + "channelExtensions": {} + }, + "displayType": "link", + "label": "Give Feedback", + "type": "popup" + } + ]} \ No newline at end of file diff --git a/ts/spec/json/updateApplicationContextCommand.json b/ts/spec/json/updateApplicationContextCommand.json new file mode 100644 index 00000000..c38c809a --- /dev/null +++ b/ts/spec/json/updateApplicationContextCommand.json @@ -0,0 +1,10 @@ +{ + "type": "updateApplicationContextCommand", + "command": "updateApplicationContext", + "applicationName": "HCM", + "source" : "skill", + "reset": true, + "pageName": "JobRequisition", + "fieldName": "JobDescription", + "parameters": {"foo": "bar"} +} \ No newline at end of file diff --git a/ts/spec/lib/message/messageDeserializer.spec.ts b/ts/spec/lib/message/messageDeserializer.spec.ts index 05c35679..d9779898 100644 --- a/ts/spec/lib/message/messageDeserializer.spec.ts +++ b/ts/spec/lib/message/messageDeserializer.spec.ts @@ -3,7 +3,7 @@ import { , MultiSelectLayoutStyle, CardLayout, Card, AttachmentType, FieldAlignment, PostbackAction, MessageUtil, ChannelExtensions, CardMessage, LocationAction, TextInputField, TextField, LinkField, EditFormMessage, SingleSelectField, SubmitFormAction, FormMessage, TableMessage , TableFormMessage, CommandMessage, CommandType, LocationMessage, PostbackMessage, FormSubmissionMessage, TextStreamMessage, StreamState - , ColumnWidth, ActionField, VerticalAlignment, Column, FormRow + , ColumnWidth, ActionField, VerticalAlignment, Column, FormRow, PopupAction, DisplayType, ExecuteApplicationActionCommandMessage, UpdateApplicationContextCommandMessage, ContextSource } from '../../../lib2'; import * as fs from 'fs'; import * as path from 'path'; @@ -44,6 +44,20 @@ describe('MessageDeserializer', () => { }); + it('Deserialize Text Message with PopupAction', function () { + + const file = path.resolve('ts/spec/json/', 'textWithPopupAction.json'); + let expected: string = fs.readFileSync(file, 'utf-8'); + + let msg = MF.messageFromJson(JSON.parse(expected)) as TextMessage; + expect(msg.getActions()[0] instanceof PopupAction).toBeTruthy; + let action = msg.getActions()[0] as PopupAction; + expect(action.getPopupContent() instanceof EditFormMessage).toBeTruthy; + expect(action.getDisplayType() === DisplayType.link).toBeTrue; + + }); + + it('Deserialize Text Stream Message', function () { const file = path.resolve('ts/spec/json/', 'textStream.json'); @@ -172,6 +186,33 @@ describe('MessageDeserializer', () => { }); + it('Deserialize ExecuteApplicationActionCommand', function () { + + const file = path.resolve('ts/spec/json/', 'executeApplicationActionCommand.json'); + let expected: string = fs.readFileSync(file, 'utf-8'); + let msg = MF.messageFromJson(JSON.parse(expected)) as ExecuteApplicationActionCommandMessage; + expect(msg.getCommand()).toEqual(CommandType.executeApplicationAction); + expect(msg.getAction()).toEqual('updateFields'); + expect(msg.getApplicationName()).toEqual('HCM'); + expect(msg.getPageName()).toEqual('JobRequisition'); + expect(msg.getPropertyValue('foo')).toEqual('bar'); + + }); + + it('Deserialize UpdateApplicationContextCommand', function () { + + const file = path.resolve('ts/spec/json/', 'updateApplicationContextCommand.json'); + let expected: string = fs.readFileSync(file, 'utf-8'); + let msg = MF.messageFromJson(JSON.parse(expected)) as UpdateApplicationContextCommandMessage; + expect(msg.getCommand()).toEqual(CommandType.updateApplicationContext); + expect(msg.getContextSource()).toEqual(ContextSource.skill); + expect(msg.getReset()).toBeTruthy; + expect(msg.getApplicationName()).toEqual('HCM'); + expect(msg.getPageName()).toEqual('JobRequisition'); + expect(msg.getParameterValue('foo')).toEqual('bar'); + + }); + it('Deserialize Location', function () { const file = path.resolve('ts/spec/json/', 'location.json'); diff --git a/ts/spec/lib/message/messageFactory.spec.ts b/ts/spec/lib/message/messageFactory.spec.ts index e80367de..c5ddc36f 100644 --- a/ts/spec/lib/message/messageFactory.spec.ts +++ b/ts/spec/lib/message/messageFactory.spec.ts @@ -1,5 +1,5 @@ import { MessageFactory as MF, TextMessage, Voice, ChannelType, InputStyle, SingleSelectLayoutStyle, MultiSelectLayoutStyle, CardLayout - , AttachmentType, FieldAlignment, CommandType, StreamState, TextStreamMessage} from '../../../lib2'; + , AttachmentType, FieldAlignment, CommandType, StreamState, TextStreamMessage, DisplayType, ActionStyle} from '../../../lib2'; import * as fs from 'fs'; import * as path from 'path'; @@ -19,6 +19,8 @@ describe('MessageFactory', () => { .setSkipAutoNumber(true) .addKeyword('word1') .addKeyword('word2') + .setDisplayType(DisplayType.icon) + .setStyle(ActionStyle.danger) .setChannelExtensionProperty(ChannelType.facebook, 'myProp', 'myValue') .setChannelExtensionProperty(ChannelType.facebook, 'myProp2', 'myValue2') .setChannelExtensionProperty(ChannelType.slack, 'myProp', 'myValue')