diff --git a/Composer/packages/client/src/components/Adapters/TeamsManifestGeneratorModal.tsx b/Composer/packages/client/src/components/Adapters/TeamsManifestGeneratorModal.tsx new file mode 100644 index 0000000000..1f5c04b4a9 --- /dev/null +++ b/Composer/packages/client/src/components/Adapters/TeamsManifestGeneratorModal.tsx @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** @jsx jsx */ +import { v4 as uuidv4 } from 'uuid'; +import { jsx } from '@emotion/core'; +import { DialogFooter } from 'office-ui-fabric-react/lib/Dialog'; +import { PrimaryButton } from 'office-ui-fabric-react/lib/Button'; +import formatMessage from 'format-message'; +import { Text } from 'office-ui-fabric-react/lib/Text'; +import { IButtonStyles, IconButton } from 'office-ui-fabric-react/lib/components/Button'; +import { FontSizes, NeutralColors } from '@uifabric/fluent-theme/lib/fluent'; +import { useRef } from 'react'; +import { ITextField, TextField } from 'office-ui-fabric-react/lib/components/TextField'; +import { DialogTypes, DialogWrapper } from '@bfc/ui-shared/lib/components/DialogWrapper'; +import { DefaultButton } from 'office-ui-fabric-react/lib/Button'; +import { useRecoilValue } from 'recoil'; + +import { defaultTeamsManifest } from '../../constants'; +import { dispatcherState } from '../../recoilModel'; + +const iconButtonStyle: IButtonStyles = { + root: { + height: 'unset', + float: 'right', + marginRight: '10px', + }, + menuIcon: { + backgroundColor: NeutralColors.white, + color: NeutralColors.gray130, + fontSize: FontSizes.size16, + }, + rootDisabled: { + backgroundColor: NeutralColors.white, + }, + rootHovered: { + backgroundColor: 'unset', + }, + rootPressed: { + backgroundColor: 'unset', + }, +}; + +const teamsAppStudioDeepLink = 'https://aka.ms/AppStudioTeamsLink'; + +type TeamsManifestGeneratorModalProps = { + hidden: boolean; + botAppId: string; + botDisplayName: string; + onDismiss: () => void; +}; + +export const TeamsManifestGeneratorModal = (props: TeamsManifestGeneratorModalProps) => { + const textFieldRef = useRef(null); + const { setApplicationLevelError } = useRecoilValue(dispatcherState); + + const copyCodeToClipboard = () => { + try { + if (textFieldRef.current) { + textFieldRef.current.select(); + document.execCommand('copy'); + textFieldRef.current.setSelectionRange(0, 0); + textFieldRef.current.blur(); + } + } catch (e) { + setApplicationLevelError(e); + } + }; + + const generateTeamsManifest = () => { + const appId = props.botAppId ? props.botAppId : '{AddBotAppId}'; + const botName = props.botDisplayName ? props.botDisplayName : '{AddBotDisplayName}'; + const result = defaultTeamsManifest; + result.id = uuidv4().toString(); + result.description.short = `${formatMessage('short description for')} ${botName}`; + result.description.full = `${formatMessage('full description for')} ${botName}`; + result.packageName = botName; + result.name.short = botName; + result.name.full = botName; + result.bots[0].botId = appId; + return JSON.stringify(defaultTeamsManifest, null, 2); + }; + + return ( + +
+ {formatMessage('Teams manifest for your bot:')} + + { + copyCodeToClipboard(); + }} + /> +
+ + + { + props.onDismiss(); + }} + /> + + +
+ ); +}; diff --git a/Composer/packages/client/src/constants.ts b/Composer/packages/client/src/constants.ts index 8ad0611317..8e9084c98f 100644 --- a/Composer/packages/client/src/constants.ts +++ b/Composer/packages/client/src/constants.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { FeedName } from '@botframework-composer/types/src'; +import { FeedName, TeamsManifest } from '@botframework-composer/types/src'; import formatMessage from 'format-message'; import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; @@ -421,3 +421,38 @@ export const runtimeOptions: IDropdownOption[] = [ ]; export const onboardingDisabled = false; + +export const defaultTeamsManifest: TeamsManifest = { + $schema: 'https://developer.microsoft.com/en-us/json-schemas/teams/v1.9/MicrosoftTeams.schema.json', + manifestVersion: '1.9', + version: '1.0.0', + id: '', + packageName: '', + developer: { + name: 'contoso', + websiteUrl: 'https://contoso.com', + privacyUrl: 'https://cotoso.com/privacy', + termsOfUseUrl: 'https://contoso.com/terms', + }, + icons: { + color: '', + outline: '', + }, + name: { + short: '', + full: '', + }, + description: { + short: '', + full: '', + }, + accentColor: '#FFFFFF', + bots: [ + { + botId: '', + scopes: ['personal'], + }, + ], + permissions: ['identity', 'messageTeamMembers'], + validDomains: ['token.botframework.com'], +}; diff --git a/Composer/packages/client/src/pages/botProject/adapters/ABSChannels.tsx b/Composer/packages/client/src/pages/botProject/adapters/ABSChannels.tsx index 09deab0e7f..6e2500e049 100644 --- a/Composer/packages/client/src/pages/botProject/adapters/ABSChannels.tsx +++ b/Composer/packages/client/src/pages/botProject/adapters/ABSChannels.tsx @@ -21,7 +21,7 @@ import { OpenConfirmModal } from '@bfc/ui-shared'; import TelemetryClient from '../../../telemetry/TelemetryClient'; import { LoadingSpinner } from '../../../components/LoadingSpinner'; import { navigateTo } from '../../../utils/navigation'; -import { settingsState } from '../../../recoilModel'; +import { botDisplayNameState, settingsState } from '../../../recoilModel'; import { AuthClient } from '../../../utils/authClient'; import { AuthDialog } from '../../../components/Auth/AuthDialog'; import { armScopes } from '../../../constants'; @@ -40,6 +40,7 @@ import { errorTextStyle, columnSizes, } from '../styles'; +import { TeamsManifestGeneratorModal } from '../../../components/Adapters/TeamsManifestGeneratorModal'; import ABSChannelSpeechModal from './ABSChannelSpeechModal'; @@ -62,6 +63,7 @@ type AzureResourcePointer = { alternateSubscriptionId?: string | undefined; resourceName: string; resourceGroupName: string; + microsoftAppId: string; }; type AzureChannelStatus = { @@ -86,12 +88,14 @@ export const ABSChannels: React.FC = (props) => { const [currentResource, setCurrentResource] = useState(); const [channelStatus, setChannelStatus] = useState(); const { publishTargets } = useRecoilValue(settingsState(projectId)); + const botDisplayName = useRecoilValue(botDisplayNameState(projectId)); const [token, setToken] = useState(); const [availableSubscriptions, setAvailableSubscriptions] = useState([]); const [publishTargetOptions, setPublishTargetOptions] = useState([]); const [isLoading, setLoadingStatus] = useState(false); const [errorMessage, setErrorMessage] = useState(undefined); const [showSpeechModal, setShowSpeechModal] = useState(false); + const [showTeamsManifestModal, setShowTeamsManifestModal] = useState(false); const { setApplicationLevelError } = useRecoilValue(dispatcherState); /* Copied from Azure Publishing extension */ const getSubscriptions = async (token: string): Promise> => { @@ -134,6 +138,7 @@ export const ABSChannels: React.FC = (props) => { if (profile) { const config = JSON.parse(profile.configuration); setCurrentResource({ + microsoftAppId: config?.settings?.MicrosoftAppId, resourceName: config.name, resourceGroupName: config.name, subscriptionId: config.subscriptionId, @@ -241,7 +246,9 @@ export const ABSChannels: React.FC = (props) => { }; } await httpClient.put(url, data, { headers: { Authorization: `Bearer ${token}` } }); - + if (channelId === CHANNELS.TEAMS) { + setShowTeamsManifestModal(true); + } // success!! setChannelStatus({ ...channelStatus, @@ -494,6 +501,18 @@ export const ABSChannels: React.FC = (props) => { )} + {key === CHANNELS.TEAMS && channelStatus?.[key].enabled && !channelStatus?.[key].loading && ( + + { + setShowTeamsManifestModal(true); + }} + > + {formatMessage('Open Manifest')} + + + )} ); @@ -581,6 +600,14 @@ export const ABSChannels: React.FC = (props) => { {absTableRow(CHANNELS.SPEECH, formatMessage('Speech'), speechHelpLink)} )} +