Skip to content

Commit

Permalink
Sample: Password input activity (#1569)
Browse files Browse the repository at this point in the history
* Move activity visibility filter to middleware

* Move activity visibility filter here

* Add password input sample

* Add password input sample

* Group timestamp using pure CSS

* Update CHANGELOG.md

* Rename 10 to 10.a

* Typo

* Use production version

* Use production token

* Typo

* Move comment

* Hide messageBack

* List out image snapshots

* Zip recursively

* Fix test for timestamp grouping

* Set pixelmatch threshold to 0.15

* Update pixelmatch threshold

* Typo and lower the threshold
  • Loading branch information
compulim committed Jan 18, 2019
1 parent 7a5cc3a commit 42e46ad
Show file tree
Hide file tree
Showing 17 changed files with 308 additions and 94 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ script:
- npm run coveralls

# Upload image snapshots to build artifact curator
- zip image-snapshots __tests__/__image_snapshots__/**/*
- zip -r image-snapshots __tests__/__image_snapshots__/
- curl -T image-snapshots.zip https://webchat-curator.azurewebsites.net/upload

before_deploy:
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `component`: Fix outgoing typing indicators are not sent and acknowledged properly, in PR [#1286](https://github.com/Microsoft/BotFramework-WebChat/pull/1286)
- Fix [#1402](https://github.com/Microsoft/BotFramework-WebChat/issues/1402). Add `messageBack` support, by [@corinagum](https://github.com/corinagum) in PR [#1581](https://github.com/Microsoft/BotFramework-WebChat/pull/1581)
- Fix [#1539], outgoing typing indicators are not sent and acknowledged properly, in PR [#1541](https://github.com/Microsoft/BotFramework-WebChat/pull/1541)
- `component`: Fix [#1547](https://github.com/Microsoft/BotFramework-WebChat/issues/1547). Fixed unhandled activity type should be forwarded to custom middleware, by [@compulim](https://github.com/compulim) in PR [#1569](https://github.com/Microsoft/BotFramework-WebChat/pull/1569)

### Removed
- `botAvatarImage` and `userAvatarImage` props, as they are moved inside `styleOptions`, in PR [#1486](https://github.com/Microsoft/BotFramework-WebChat/pull/1486)
Expand All @@ -62,6 +63,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `core`: [Incoming activity to JavaScript event](https://microsoft.github.io/BotFramework-WebChat/15.b.incoming-activity-event/), in [#1567](https://github.com/Microsoft/BotFramework-WebChat/pull/1567)
- `core`: [Send welcome event](https://microsoft.github.io/BotFramework-WebChat/15.b.backchannel-send-welcome-event/), in PR [#1286](https://github.com/Microsoft/BotFramework-WebChat/pull/1286)
- `core`: [Send typing indicator](https://microsoft.github.io/BotFramework-WebChat/07.b.customization-send-typing-indicator), in [#1541](https://github.com/Microsoft/BotFramework-WebChat/pull/1541)
- `component`: [Password input activity](https://microsoft.github.io/BotFramework-WebChat/10.b.customization-password-input/), in [#1569](https://github.com/Microsoft/BotFramework-WebChat/pull/1569)

## [4.2.0] - 2018-12-11
### Added
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,8 @@ npm run prepublishOnly
| [`07.b.customization-send-typing-indicator`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/07.customization-send-typing-indicator) | Demonstrates how to send typing activity when the user start typing on the send box. | [Demo](https://microsoft.github.io/BotFramework-WebChat/07.b.customization-send-typing-indicator) |
| [`08.customization-user-highlighting`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/08.customization-user-highlighting) | Demonstrates how to customize the styling of activities based whether the message is from the user or the bot. | [Demo](https://microsoft.github.io/BotFramework-WebChat/08.customization-user-highlighting) |
| [`09.customization-reaction-buttons`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/09.customization-reaction-buttons/) | Introduces the ability to create custom components for Web Chat that are unique to your bot's needs. This tutorial demonstrates the ability to add reaction emoji such as :thumbsup: and :thumbsdown: to conversational activities. | [Demo](https://microsoft.github.io/BotFramework-WebChat/09.customization-reaction-buttons) |
| [`10.customization-card-components`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/10.customization-card-components) | Demonstrates how to create custom activity card attachments, in this case GitHub repository cards. | [Demo](https://microsoft.github.io/BotFramework-WebChat/10.customization-card-components) |
| [`10.a.customization-card-components`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/10.a.customization-card-components) | Demonstrates how to create custom activity card attachments, in this case GitHub repository cards. | [Demo](https://microsoft.github.io/BotFramework-WebChat/10.a.customization-card-components) |
| [`10.b.customization-password-input`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/10.b.customization-password-input) | Demonstrates how to create custom activity for password input. | [Demo](https://microsoft.github.io/BotFramework-WebChat/10.b.customization-password-input) |
| [`11.customization-redux-actions`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/11.customization-redux-actions) | Advanced tutorial: Demonstrates how to incorporate redux middleware into your Web Chat app by sending redux actions through the bot. This example demonstrates manual styling based on activities between bot and user. | [Demo](https://microsoft.github.io/BotFramework-WebChat/11.customization-redux-actions) |
| [`12.customization-minimizable-web-chat`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/12.customization-minimizable-web-chat) | Advanced tutorial: Demonstrates how to add the Web Chat interface to your website as a minimizable show/hide chat box. | [Demo](https://microsoft.github.io/BotFramework-WebChat/12.customization-minimizable-web-chat) |
| [`13.customization-speech-ui`](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples/13.customization-speech-ui) | Advanced tutorial: Demonstrates how to fully customize key components of your bot, in this case speech, which entirely replaces the text-based transcript UI and instead shows a simple speech button with the bot's response. | [Demo](https://microsoft.github.io/BotFramework-WebChat/13.customization-speech-ui) |
Expand Down
5 changes: 3 additions & 2 deletions __tests__/constants.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"imageSnapshotOptions": {
"failureThreshold": 10,
"failureThresholdType": "pixel"
"customDiffConfig": {
"threshold": 0.14
}
}
}
9 changes: 6 additions & 3 deletions packages/component/src/Activity/CarouselFilmStrip.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ const ConnectedCarouselFilmStrip = connectCarouselFilmStrip(
language,
className,
filmContext,
showTimestamp,
styleSet,
timestampClassName
}) => {
const fromUser = activity.from.role === 'user';
const ariaLabel = localize('Bot said something', language, avatarInitials, activity.text, activity.timestamp)
Expand Down Expand Up @@ -168,8 +168,11 @@ const ConnectedCarouselFilmStrip = connectCarouselFilmStrip(
)
) ?
<SendStatus activity={ activity } />
: showTimestamp &&
<Timestamp activity={ activity } />
:
<Timestamp
activity={ activity }
className={ timestampClassName }
/>
}
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions packages/component/src/Activity/CarouselLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ const ROOT_CSS = css({

export default connectToWebChat(
({ language, styleSet }) => ({ language, styleSet })
)(({ activity, children, language, showTimestamp, styleSet }) => {
)(({ activity, children, language, styleSet, timestampClassName }) => {
const filmStyleSet = createBasicStyleSet();

return (
<Composer>
<FilmContext.Consumer>
{ ({ scrollBarWidth }) =>
<div className={ classNames(ROOT_CSS + '', filmStyleSet.carousel + '') }>
<CarouselFilmStrip activity={ activity } showTimestamp={ showTimestamp }>
<CarouselFilmStrip activity={ activity } timestampClassName={ timestampClassName }>
{ children }
</CarouselFilmStrip>
{ scrollBarWidth !== '100%' &&
Expand Down
29 changes: 13 additions & 16 deletions packages/component/src/Activity/StackedLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ export default connectStackedLayout(
avatarInitials,
children,
language,
showTimestamp,
styleSet
styleSet,
timestampClassName
}) => {
const fromUser = activity.from.role === 'user';
const { state } = activity.channelData || {};
Expand Down Expand Up @@ -161,20 +161,17 @@ export default connectStackedLayout(
</div>
)
}
{
(showSendStatus || showTimestamp) &&
<div
aria-hidden={ true }
className="row"
>
{ showSendStatus ?
<SendStatus activity={ activity } className="timestamp" />
:
<Timestamp activity={ activity } className="timestamp" />
}
<div className="filler" />
</div>
}
<div
aria-hidden={ true }
className="row"
>
{ showSendStatus ?
<SendStatus activity={ activity } className="timestamp" />
:
<Timestamp activity={ activity } className={ classNames('timestamp', timestampClassName) } />
}
<div className="filler" />
</div>
</div>
<div className="filler" />
</div>
Expand Down
7 changes: 6 additions & 1 deletion packages/component/src/Activity/Timestamp.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import classNames from 'classnames';
import React from 'react';

import connectToWebChat from '../connectToWebChat';
Expand All @@ -8,9 +9,13 @@ export default connectToWebChat(
)(
({
activity: { timestamp },
className,
styleSet
}) =>
<span className={ styleSet.timestamp }>
<span className={ classNames(
styleSet.timestamp + '',
(className || '') + ''
) }>
<TimeAgo value={ timestamp } />
</span>
)
101 changes: 50 additions & 51 deletions packages/component/src/BasicTranscript.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,50 +24,28 @@ const FILLER_CSS = css({
});

const LIST_CSS = css({
listStyleType: 'none'
});
listStyleType: 'none',

function shouldShowActivity(activity) {
if (activity) {
if (activity.type === 'message') {
const { attachments = [], text } = activity;

if (
// Do not show postback
!(activity.channelData && activity.channelData.postBack)
// Do not show messageBack if displayText is undefined
&& !(activity.channelData && activity.channelData.messageBack && !activity.channelData.messageBack.displayText)
// Do not show empty bubbles (no text and attachments, and not "typing")
&& (text || attachments.length)
) {
return true;
}
} else if (activity.type === 'typing') {
return true;
}
'& > li.hide-timestamp .transcript-timestamp': {
display: 'none'
}
});

return false;
}

function shouldShowTimestamp(activity, nextActivity, groupTimestamp) {
function sameTimestampGroup(activityX, activityY, groupTimestamp) {
if (groupTimestamp === false) {
return false;
} else {
return true;
} else if (activityX && activityY) {
groupTimestamp = typeof groupTimestamp === 'number' ? groupTimestamp : 5 * 60 * 1000;

if (activity.type !== 'message') {
// Hide timestamp for typing
return false;
} else if (nextActivity && activity.from.role === nextActivity.from.role) {
const time = new Date(activity.timestamp).getTime();
const nextTime = new Date(nextActivity.timestamp).getTime();
if (activityX.from.role === activityY.from.role) {
const timeX = new Date(activityX.timestamp).getTime();
const timeY = new Date(activityY.timestamp).getTime();

return (nextTime - time) > groupTimestamp;
} else {
return true;
return Math.abs(timeX - timeY) <= groupTimestamp;
}
}

return false;
}

const BasicTranscript = ({
Expand All @@ -80,7 +58,26 @@ const BasicTranscript = ({
webSpeechPonyfill
}) => {
const { speechSynthesis, SpeechSynthesisUtterance } = webSpeechPonyfill || {};
const visibleActivities = activities.filter(shouldShowActivity);

// We use 2-pass approach for rendering activities, for show/hide timestamp grouping.
// Until the activity pass thru middleware, we never know if it is going to show up.
// After we know which activities will show up, we can compute which activity will show timestamps.
// If the activity does not render, it will not be spoken if text-to-speech is enabled.
const activityElements = activities.reduce((activityElements, activity) => {
const element = activityRenderer({
activity,
timestampClassName: 'transcript-timestamp'
})(
({ attachment }) => attachmentRenderer({ activity, attachment })
);

element && activityElements.push({
activity,
element
});

return activityElements;
}, []);

return (
<div
Expand All @@ -102,23 +99,25 @@ const BasicTranscript = ({
role="list"
>
{
visibleActivities.map((activity, index) => {
const showTimestamp = shouldShowTimestamp(activity, visibleActivities[index + 1], groupTimestamp);

return (
<li
className={ styleSet.activity }
key={ (activity.channelData && activity.channelData.clientActivityID) || activity.id || index }
role="listitem"
>
{ activityRenderer({ activity, showTimestamp })(({ attachment }) => attachmentRenderer({ activity, attachment })) }
activityElements.map(({ activity, element }, index) =>
<li
className={ classNames(
styleSet.activity + '',
{
// TODO: [P2] We should use core/definitions/speakingActivity for this predicate instead
activity.channelData && activity.channelData.speak && <SpeakActivity activity={ activity } />
// Hide timestamp if same timestamp group with the next activity
'hide-timestamp': sameTimestampGroup(activity, (activityElements[index + 1] || {}).activity, groupTimestamp)
}
</li>
);
})
) }
key={ (activity.channelData && activity.channelData.clientActivityID) || activity.id || index }
role="listitem"
>
{ element }
{
// TODO: [P2] We should use core/definitions/speakingActivity for this predicate instead
activity.channelData && activity.channelData.speak && <SpeakActivity activity={ activity } />
}
</li>
)
}
</ul>
</SayComposer>
Expand Down
32 changes: 25 additions & 7 deletions packages/component/src/Middleware/Activity/createCoreMiddleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,44 @@ import StackedLayout from '../../Activity/StackedLayout';
const RETURN_FALSE = () => false;

export default function () {
return () => next => ({ activity, showTimestamp }) => {
return () => next => ({ activity, timestampClassName }) => {
// TODO: [P4] Can we simplify these if-statement to something more readable?

if (activity.type === 'typing' && activity.from.role === 'user') {
const { type } = activity;

// Filter out activities that should not be visible
if (type === 'event') {
return RETURN_FALSE;
} else if (type === 'message') {
const { attachments = [], text } = activity;

if (
// Do not show postback
(activity.channelData && activity.channelData.postBack)
// Do not show messageBack if displayText is undefined
|| (activity.channelData && activity.channelData.messageBack && !activity.channelData.messageBack.displayText)
// Do not show empty bubbles (no text and attachments, and not "typing")
|| !(text || attachments.length)
) {
return RETURN_FALSE;
}
} else if (type === 'typing' && activity.from.role === 'user') {
// Do not show typing by oneself
return RETURN_FALSE;
}

if (activity.type === 'message' || activity.type === 'typing') {
if (type === 'message' || type === 'typing') {
if (
activity.type === 'message'
type === 'message'
&& (activity.attachments || []).length > 1
&& activity.attachmentLayout === 'carousel'
) {
return children => <CarouselLayout activity={ activity } showTimestamp={ showTimestamp }>{ children }</CarouselLayout>;
return children => <CarouselLayout activity={ activity } timestampClassName={ timestampClassName }>{ children }</CarouselLayout>;
} else {
return children => <StackedLayout activity={ activity } showTimestamp={ showTimestamp }>{ children }</StackedLayout>;
return children => <StackedLayout activity={ activity } timestampClassName={ timestampClassName }>{ children }</StackedLayout>;
}
} else {
return next({ activity, showTimestamp });
return next({ activity, timestampClassName });
}
};
}
2 changes: 2 additions & 0 deletions packages/playground/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ export default class extends React.Component {
<option value="false">Don't show timestamp</option>
<option value="0">Don't group</option>
<option value="1000">1 second</option>
<option value="2000">2 seconds</option>
<option value="5000">5 seconds</option>
<option value="10000">10 seconds</option>
<option value="60000">One minute</option>
<option value="300000">5 minutes</option>
Expand Down
8 changes: 5 additions & 3 deletions packages/playground/src/createDevModeActivityMiddleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,10 @@ const ConnectedDevModeDecorator = connectToWebChat(

export default function () {
return () => next => card => {
return (children =>
<ConnectedDevModeDecorator card={ card }>{ next(card)(children) }</ConnectedDevModeDecorator>
);
return (children => {
const content = next(card)(children);

return !!content && <ConnectedDevModeDecorator card={ card }>{ content }</ConnectedDevModeDecorator>;
});
};
}
10 changes: 10 additions & 0 deletions samples/10.a.customization-card-components/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Sample

A simple web page with a maximized Web Chat and hosted using React.

# How to run

- Run `npx serve`
- Browse to [http://localhost:5000/](http://localhost:5000/)

# Things to try out
17 changes: 17 additions & 0 deletions samples/10.b.customization-password-input/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Sample - Password input activity

Show a custom activity that accepts password input and submit as a postback activity.

# Test out the hosted sample

- [Try out MockBot](https://microsoft.github.io/BotFramework-WebChat/10.b.customization-password-input)

# Things to try out

- Type `sample:password-input` to get the password input box

# Further reading

## Full list of Web Chat hosted samples

View the list of available samples by clicking [here](https://github.com/Microsoft/BotFramework-WebChat/tree/master/samples)
Loading

0 comments on commit 42e46ad

Please sign in to comment.