Skip to content

Commit 4ab4d87

Browse files
srinaathSrinaath Ravichandran
and
Srinaath Ravichandran
authored
4.8 blockers (#2098)
* 4.8 blockers Copy to clipboard fixed Making sure restart conversation does not show up in Transcript or Debug mode Signed-off-by: Srinaath Ravichandran <[email protected]> * Changelog updated Signed-off-by: Srinaath Ravichandran <[email protected]> Co-authored-by: Srinaath Ravichandran <[email protected]>
1 parent 47c8349 commit 4ab4d87

File tree

9 files changed

+127
-30
lines changed

9 files changed

+127
-30
lines changed

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2828
- [client] Upload and download attachments bubble texts and background in webchat were hidden. The adjustments have been made to override FileContent class in PR [2088](https://github.com/microsoft/BotFramework-Emulator/pull/2088)
2929
- [client] Fixed an issue that was causing adaptive card focus to be blurred when clicking on an activity in PR [2090](https://github.com/microsoft/BotFramework-Emulator/pull/2090)
3030
- [client] Fixed an accessibility issue with the recent bots list remove button in PR [2091](https://github.com/microsoft/BotFramework-Emulator/pull/2091)
31-
31+
- [client] Copy secret key button now copies the secret key to the clipboar when creating a new bot file in PR [2098](https://github.com/microsoft/BotFramework-Emulator/pull/2098)
3232

3333
## Removed
3434
- [client/main] Removed legacy payments code in PR [2058](https://github.com/microsoft/BotFramework-Emulator/pull/2058)

packages/app/client/src/ui/dialogs/botCreationDialog/botCreationDialog.spec.tsx

+5-16
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,14 @@ import { ariaAlertService } from '../../a11y';
4242
import { BotCreationDialog, BotCreationDialogState } from './botCreationDialog';
4343
import { BotCreationDialogContainer } from './botCreationDialogContainer';
4444

45+
const mockCopyToClipboard = jest.fn(args => true);
4546
jest.mock('../index', () => null);
4647
jest.mock('../../../utils', () => ({
4748
debounce: (func: () => any) => func,
4849
generateBotSecret: () => {
4950
return Math.random() + '';
5051
},
52+
copyTextToClipboard: args => mockCopyToClipboard(args),
5153
}));
5254

5355
jest.mock('electron', () => ({
@@ -140,26 +142,13 @@ describe('BotCreationDialog tests', () => {
140142

141143
it('should execute a window copy command when copy is clicked', () => {
142144
testWrapper.instance().setState({ encryptKey: true });
143-
// mock window functions
144-
const backupExec = window.document.execCommand;
145-
const mockExec = jest.fn((_command: string) => null);
146-
const backupGetElementById = window.document.getElementById;
147-
const mockGetElementById = _selector => ({
148-
removeAttribute: () => null,
149-
select: () => null,
150-
setAttribute: () => null,
151-
});
152-
(window.document.getElementById as any) = mockGetElementById;
153-
window.document.execCommand = mockExec;
154145
const alertServiceSpy = jest.spyOn(ariaAlertService, 'alert').mockReturnValueOnce(undefined);
155146

156147
(testWrapper.instance() as any).onCopyClick();
157-
expect(mockExec).toHaveBeenCalledWith('copy');
158148
expect(alertServiceSpy).toHaveBeenCalledWith('Secret copied to clipboard.');
159-
160-
// restore window functions
161-
window.document.execCommand = backupExec;
162-
window.document.getElementById = backupGetElementById;
149+
mockCopyToClipboard.mockReturnValueOnce(false);
150+
(testWrapper.instance() as any).onCopyClick();
151+
expect(alertServiceSpy).toHaveBeenCalledWith('Failed to copy secret to clipboard.');
163152
});
164153

165154
it('should set state via input change handlers', () => {

packages/app/client/src/ui/dialogs/botCreationDialog/botCreationDialog.tsx

+9-8
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ import * as React from 'react';
5252
import { CommandServiceImpl, CommandServiceInstance } from '@bfemulator/sdk-shared';
5353

5454
import { store } from '../../../state/store';
55-
import { generateBotSecret, debounce } from '../../../utils';
55+
import { generateBotSecret, debounce, copyTextToClipboard } from '../../../utils';
5656
import { ActiveBotHelper } from '../../helpers/activeBotHelper';
5757
import { DialogService } from '../service';
5858
import { ariaAlertService } from '../../a11y';
@@ -295,13 +295,14 @@ export class BotCreationDialog extends React.Component<BotCreationDialogProps, B
295295
if (!this.state.encryptKey) {
296296
return null;
297297
}
298-
const { secretInputRef } = this;
299-
const { type } = secretInputRef;
300-
secretInputRef.type = 'text';
301-
secretInputRef.select();
302-
window.document.execCommand('copy');
303-
secretInputRef.type = type;
304-
ariaAlertService.alert('Secret copied to clipboard.');
298+
if (copyTextToClipboard(this.secretInputRef.value)) {
299+
ariaAlertService.alert('Secret copied to clipboard.');
300+
} else {
301+
const err = 'Failed to copy secret to clipboard.';
302+
ariaAlertService.alert(err);
303+
// eslint-disable-next-line no-console
304+
console.error(err);
305+
}
305306
};
306307

307308
// TODO: Re-enable ability to re-generate secret after 4.1

packages/app/client/src/ui/editor/emulator/parts/chat/activityWrapper.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ interface ActivityWrapperProps extends HTMLAttributes<HTMLDivElement> {
4242
activity: Activity;
4343
children: ReactNode;
4444
isSelected: boolean;
45-
isUserActivity: boolean;
4645
onRestartConversationFromActivityClick: () => void;
46+
showRestartBubble: boolean;
4747
}
4848

4949
// Returns false if the event target is normally an interactive element.
@@ -65,10 +65,10 @@ function shouldSelectActivity(e: React.SyntheticEvent): boolean {
6565

6666
export class ActivityWrapper extends Component<ActivityWrapperProps> {
6767
render() {
68-
const { activity: _, children, isSelected, isUserActivity, ...divProps } = this.props;
68+
const { activity: _, children, isSelected, showRestartBubble, ...divProps } = this.props;
6969
let classes = styles.chatActivity;
7070
const restartConversationBubble = (
71-
<div className={[styles.replayBubble, isUserActivity && isSelected ? '' : styles.hidden].join(' ')}>
71+
<div className={[styles.replayBubble, showRestartBubble ? '' : styles.hidden].join(' ')}>
7272
<LinkButton ariaLabel="Restart from activity." linkRole={false} onClick={this.replayConversation}>
7373
Restart conversation from here
7474
</LinkButton>

packages/app/client/src/ui/editor/emulator/parts/chat/chat.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ export class Chat extends PureComponent<ChatProps, ChatState> {
139139
}
140140

141141
private activityWrapper(next, card, children): ReactNode {
142+
const { mode, restartStatus } = this.props;
143+
const isWebChatDisabled =
144+
mode === 'transcript' || mode === 'debug' || restartStatus === RestartConversationStatus.Started;
142145
return (
143146
<OuterActivityWrapperContainer
144147
card={card}
@@ -147,6 +150,7 @@ export class Chat extends PureComponent<ChatProps, ChatState> {
147150
onItemRendererClick={this.onItemRendererClick}
148151
onItemRendererKeyDown={this.onItemRendererKeyDown}
149152
restartStatusForActivity={this.props.restartStatus}
153+
isWebChatDisabled={isWebChatDisabled}
150154
>
151155
{next(card)(children)}
152156
</OuterActivityWrapperContainer>

packages/app/client/src/ui/editor/emulator/parts/chat/outerActivityWrapper.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,16 @@ export interface OuterActivityWrapperProps {
5454
restartOption: RestartConversationOptions
5555
) => void;
5656
currentRestartConversationOption: RestartConversationOptions;
57+
isWebChatDisabled: boolean;
5758
}
5859

5960
export class OuterActivityWrapper extends React.Component<OuterActivityWrapperProps, {}> {
6061
public render() {
61-
const { card, children, onContextMenu, onItemRendererClick, onItemRendererKeyDown } = this.props;
62+
const { card, children, onContextMenu, onItemRendererClick, onItemRendererKeyDown, isWebChatDisabled } = this.props;
6263

6364
const isSelected = this.shouldBeSelected(card.activity);
6465
const isUserActivity = this.isUserActivity(card.activity);
66+
const showRestartBubble = isUserActivity && isSelected && !isWebChatDisabled;
6567

6668
return (
6769
<ActivityWrapper
@@ -71,8 +73,8 @@ export class OuterActivityWrapper extends React.Component<OuterActivityWrapperPr
7173
onKeyDown={onItemRendererKeyDown}
7274
onContextMenu={onContextMenu}
7375
isSelected={isSelected}
74-
isUserActivity={isUserActivity}
7576
onRestartConversationFromActivityClick={this.onRestartConversationFromActivityClick}
77+
showRestartBubble={showRestartBubble}
7678
>
7779
{children}
7880
</ActivityWrapper>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license.
4+
//
5+
// Microsoft Bot Framework: http://botframework.com
6+
//
7+
// Bot Framework Emulator Github:
8+
// https://github.com/Microsoft/BotFramwork-Emulator
9+
//
10+
// Copyright (c) Microsoft Corporation
11+
// All rights reserved.
12+
//
13+
// MIT License:
14+
// Permission is hereby granted, free of charge, to any person obtaining
15+
// a copy of this software and associated documentation files (the
16+
// "Software"), to deal in the Software without restriction, including
17+
// without limitation the rights to use, copy, modify, merge, publish,
18+
// distribute, sublicense, and/or sell copies of the Software, and to
19+
// permit persons to whom the Software is furnished to do so, subject to
20+
// the following conditions:
21+
//
22+
// The above copyright notice and this permission notice shall be
23+
// included in all copies or substantial portions of the Software.
24+
//
25+
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
26+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29+
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31+
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32+
//
33+
34+
import { copyTextToClipboard } from './copyToClipboard';
35+
36+
const mockWrite = jest.fn(args => true);
37+
38+
jest.mock('electron', () => ({
39+
clipboard: {
40+
writeText: args => {
41+
mockWrite(args);
42+
},
43+
},
44+
}));
45+
46+
describe('Copy To Clipboard', () => {
47+
beforeEach(() => {
48+
mockWrite.mockReset();
49+
});
50+
51+
it('should copy text to clipboard', async () => {
52+
let expected = 'Hello';
53+
copyTextToClipboard(expected);
54+
expect(mockWrite).toHaveBeenCalledWith(expected);
55+
expected = 'Hello Check Again';
56+
copyTextToClipboard(expected);
57+
expect(mockWrite).toHaveBeenCalledWith(expected);
58+
});
59+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license.
4+
//
5+
// Microsoft Bot Framework: http://botframework.com
6+
//
7+
// Bot Framework Emulator Github:
8+
// https://github.com/Microsoft/BotFramwork-Emulator
9+
//
10+
// Copyright (c) Microsoft Corporation
11+
// All rights reserved.
12+
//
13+
// MIT License:
14+
// Permission is hereby granted, free of charge, to any person obtaining
15+
// a copy of this software and associated documentation files (the
16+
// "Software"), to deal in the Software without restriction, including
17+
// without limitation the rights to use, copy, modify, merge, publish,
18+
// distribute, sublicense, and/or sell copies of the Software, and to
19+
// permit persons to whom the Software is furnished to do so, subject to
20+
// the following conditions:
21+
//
22+
// The above copyright notice and this permission notice shall be
23+
// included in all copies or substantial portions of the Software.
24+
//
25+
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
26+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29+
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31+
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32+
import { clipboard } from 'electron';
33+
34+
export const copyTextToClipboard = (textValue: string): boolean => {
35+
try {
36+
clipboard.writeText(textValue, 'selection');
37+
return true;
38+
} catch (ex) {
39+
return false;
40+
}
41+
};

packages/app/client/src/utils/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ export * from './getGlobal';
3737
export * from './getSettingsDelta';
3838
export * from './generateBotSecret';
3939
export * from './chatUtils';
40+
export * from './copyToClipboard';

0 commit comments

Comments
 (0)