diff --git a/Composer/packages/client/src/components/Notifications/NotificationCard.tsx b/Composer/packages/client/src/components/Notifications/NotificationCard.tsx index 698cc92edf..a39519b89f 100644 --- a/Composer/packages/client/src/components/Notifications/NotificationCard.tsx +++ b/Composer/packages/client/src/components/Notifications/NotificationCard.tsx @@ -10,6 +10,7 @@ import { FontSizes, SharedColors } from '@uifabric/fluent-theme'; import { Shimmer, ShimmerElementType } from 'office-ui-fabric-react/lib/Shimmer'; import { Icon } from 'office-ui-fabric-react/lib/Icon'; import formatMessage from 'format-message'; +import { Notification } from '@botframework-composer/types'; import { useInterval } from '../../utils/hooks'; @@ -121,24 +122,8 @@ const getShimmerStyles = { ], }; // -------------------- NotificationCard -------------------- // - -export type NotificationType = 'info' | 'warning' | 'error' | 'pending' | 'success'; - -export type Link = { - label: string; - onClick: () => void; -}; - -export type CardProps = { - type: NotificationType; - title: string; - description?: string; - retentionTime?: number; - link?: Link; - read?: boolean; - hidden?: boolean; - onRenderCardContent?: ((props: CardProps) => JSX.Element) | React.FC; -}; +export type CardProps = Notification; +export type NotificationType = Notification['type']; export type NotificationProps = { id: string; diff --git a/Composer/packages/client/src/plugins/api.ts b/Composer/packages/client/src/plugins/api.ts index 88bbdf2672..08b1d37511 100644 --- a/Composer/packages/client/src/plugins/api.ts +++ b/Composer/packages/client/src/plugins/api.ts @@ -20,7 +20,7 @@ interface AuthAPI { getAccessToken: (options: AuthParameters) => Promise; // returns an access token getARMTokenForTenant: (tenantId: string) => Promise; // returns an ARM access token for specified tenant getTenants: () => Promise; // signs a user in and returns available Azure tenants for the user - logOut: () => Promise; + logOut: () => Promise; } interface PublishAPI { diff --git a/Composer/packages/client/src/shell/useShell.ts b/Composer/packages/client/src/shell/useShell.ts index 5bd24d41f4..41113270da 100644 --- a/Composer/packages/client/src/shell/useShell.ts +++ b/Composer/packages/client/src/shell/useShell.ts @@ -11,6 +11,7 @@ import { BotInProject, FeatureFlagKey, SDKKinds, + Notification, } from '@botframework-composer/types'; import { useRecoilValue } from 'recoil'; import formatMessage from 'format-message'; @@ -48,6 +49,7 @@ import { navigateTo } from '../utils/navigation'; import TelemetryClient from '../telemetry/TelemetryClient'; import { lgFilesSelectorFamily } from '../recoilModel/selectors/lg'; import { getMemoryVariables } from '../recoilModel/dispatchers/utils/project'; +import { createNotification } from '../recoilModel/dispatchers/notification'; import { useLgApi } from './lgApi'; import { useLuApi } from './luApi'; @@ -127,6 +129,10 @@ export function useShell(source: EventSource, projectId: string): Shell { reloadProject, setApplicationLevelError, updateRecognizer, + addNotification, + deleteNotification, + hideNotification, + markNotificationAsRead, } = useRecoilValue(dispatcherState); const lgApi = useLgApi(projectId); @@ -279,6 +285,14 @@ export function useShell(source: EventSource, projectId: string): Shell { confirm: OpenConfirmModal, telemetryClient: TelemetryClient, getMemoryVariables, + addNotification: (notificationWithoutId: Notification): string => { + const notification = createNotification(notificationWithoutId); + addNotification(notification); + return notification.id; + }, + deleteNotification, + markNotificationAsRead, + hideNotification, }; const currentDialog = useMemo(() => { diff --git a/Composer/packages/client/src/utils/authClient.ts b/Composer/packages/client/src/utils/authClient.ts index 4b0c07ac45..f39228dc1b 100644 --- a/Composer/packages/client/src/utils/authClient.ts +++ b/Composer/packages/client/src/utils/authClient.ts @@ -96,18 +96,16 @@ async function logOut() { cleanTokenFromCache('accessToken'); cleanTokenFromCache('graphToken'); if (isElectron()) { - try { - const url = '/api/auth/logOut'; - await fetch(url, { method: 'GET' }); - } catch (e) { - // error handling - console.error('Can not log out'); - } + const url = '/api/auth/logOut'; + const result = await fetch(url, { method: 'GET' }); + return result.ok; } else if (authConfig.clientId) { // clean token cache in storage cleanTokenFromCache('idToken'); cleanTokenFromCache(authConfig.clientId); + return true; } + return true; } /** diff --git a/Composer/packages/extension-client/src/auth/index.ts b/Composer/packages/extension-client/src/auth/index.ts index bf9d25c4b1..9736ab684d 100644 --- a/Composer/packages/extension-client/src/auth/index.ts +++ b/Composer/packages/extension-client/src/auth/index.ts @@ -8,7 +8,7 @@ import { ComposerGlobalName } from '../common/constants'; /** * log out current user */ -export function logOut(): Promise { +export function logOut(): Promise { return window[ComposerGlobalName].logOut(); } diff --git a/Composer/packages/extension-client/src/hooks/useApplicationApi.ts b/Composer/packages/extension-client/src/hooks/useApplicationApi.ts index 239e97704f..7e67ea4c8d 100644 --- a/Composer/packages/extension-client/src/hooks/useApplicationApi.ts +++ b/Composer/packages/extension-client/src/hooks/useApplicationApi.ts @@ -29,6 +29,10 @@ const APPLICATION_KEYS = [ 'api.confirm', 'api.updateFlowZoomRate', 'api.telemetryClient', + 'api.addNotification', + 'api.deleteNotification', + 'api.markNotificationAsRead', + 'api.hideNotification', ]; export function useApplicationApi(): ApplicationContext & ApplicationContextApi { diff --git a/Composer/packages/lib/ui-shared/src/components/ConfirmDialog.tsx b/Composer/packages/lib/ui-shared/src/components/ConfirmDialog.tsx index f65a63dbbd..eae47b4e11 100644 --- a/Composer/packages/lib/ui-shared/src/components/ConfirmDialog.tsx +++ b/Composer/packages/lib/ui-shared/src/components/ConfirmDialog.tsx @@ -43,7 +43,7 @@ const builtInStyles = { const dialog = { title: { - fontWeight: FontWeights.bold, + fontWeight: FontWeights.semibold, }, }; diff --git a/Composer/packages/types/src/shell.ts b/Composer/packages/types/src/shell.ts index f3ef62cbe0..6b85af4375 100644 --- a/Composer/packages/types/src/shell.ts +++ b/Composer/packages/types/src/shell.ts @@ -64,6 +64,20 @@ export type DisabledMenuActions = { reason: string; }; +export type Notification = { + type: 'info' | 'warning' | 'error' | 'pending' | 'success'; + title: string; + description?: string; + retentionTime?: number; + link?: { + label: string; + onClick: () => void; + }; + read?: boolean; + hidden?: boolean; + onRenderCardContent?: ((props: Notification) => JSX.Element) | React.FC; +}; + export type ApplicationContextApi = { navigateTo: (to: string, opts?: { state?: any; replace?: boolean }) => void; updateUserSettings: (settings: Partial) => void; @@ -74,6 +88,10 @@ export type ApplicationContextApi = { confirm: (title: string, subTitle: string, settings?: any) => Promise; updateFlowZoomRate: (currentRate: number) => void; telemetryClient: TelemetryClient; + addNotification: (notification: Notification) => string; + deleteNotification: (id: string) => void; + markNotificationAsRead: (id: string) => void; + hideNotification: (id: string) => void; }; export type ApplicationContext = { diff --git a/extensions/azurePublish/src/components/ChooseProvisionAction.tsx b/extensions/azurePublish/src/components/ChooseProvisionAction.tsx index f77353983a..940f131d38 100644 --- a/extensions/azurePublish/src/components/ChooseProvisionAction.tsx +++ b/extensions/azurePublish/src/components/ChooseProvisionAction.tsx @@ -75,6 +75,7 @@ const LearnMoreLink = styled(Link)` const CreateActionContent = () => { return ( + {formatMessage('Create new resources')} {formatMessage( diff --git a/extensions/azurePublish/src/components/azureProvisionDialog.tsx b/extensions/azurePublish/src/components/azureProvisionDialog.tsx index e80d99207a..87609e4353 100644 --- a/extensions/azurePublish/src/components/azureProvisionDialog.tsx +++ b/extensions/azurePublish/src/components/azureProvisionDialog.tsx @@ -13,11 +13,13 @@ import { getARMTokenForTenant, useLocalStorage, useTelemetryClient, + TelemetryClient, + useApplicationApi, } from '@bfc/extension-client'; import { Subscription } from '@azure/arm-subscriptions/esm/models'; -import { DeployLocation, AzureTenant } from '@botframework-composer/types'; +import { DeployLocation, AzureTenant, Notification } from '@botframework-composer/types'; import { FluentTheme, NeutralColors } from '@uifabric/fluent-theme'; -import { LoadingSpinner, ProvisionHandoff } from '@bfc/ui-shared'; +import { dialogStyle, LoadingSpinner, OpenConfirmModal, ProvisionHandoff } from '@bfc/ui-shared'; import { ScrollablePane, ScrollbarVisibility, @@ -77,7 +79,6 @@ type ProvisionFormData = { type ProvisonActionsStylingProps = { showSignout: boolean; }; - const AddResourcesSectionName = styled(Text)` font-size: ${FluentTheme.fonts.mediumPlus.fontSize}; `; @@ -268,6 +269,17 @@ const getHostname = (config) => { } }; +const getLogoutNotificationSettings = (description: string, type: Notification['type']) => { + return { + title: '', + retentionTime: 5000, + description: description, + type, + onRenderCardContent: (props) => ( +
{props.description}
+ ), + }; +}; const getDefaultFormData = (currentProfile, defaults) => { return { creationType: defaults.creationType ?? 'create', @@ -346,6 +358,7 @@ export const AzureProvisionDialog: React.FC = () => { const [handoffInstructions, setHandoffInstructions] = useState(''); const [showHandoff, setShowHandoff] = useState(false); + const { addNotification } = useApplicationApi(); const updateHandoffInstructions = (resources) => { const createLuisResource = resources.filter((r) => r.key === 'luisPrediction').length > 0; const createLuisAuthoringResource = resources.filter((r) => r.key === 'luisAuthoring').length > 0; @@ -739,21 +752,53 @@ export const AzureProvisionDialog: React.FC = () => { closeDialog(); }, [importConfig]); + const signoutAndNotify = useCallback(async () => { + const isSignedOut = await logOut(); + if (isSignedOut) { + addNotification( + getLogoutNotificationSettings(formatMessage('You have successfully signed out of Azure'), 'info') + ); + closeDialog(); + } else { + addNotification( + getLogoutNotificationSettings( + formatMessage( + 'There was an error attempting to sign out of Azure. To complete sign out, you may need to restart Composer.' + ), + 'error' + ) + ); + } + }, [addNotification]); + const onRenderSecondaryText = useMemo( () => (props: IPersonaProps) => { return (
{ - closeDialog(); - logOut(); + onClick={async () => { + const confirmed = await OpenConfirmModal( + formatMessage('Sign out of Azure'), + formatMessage( + 'By signing out of Azure, your new publishing profile will be canceled and this dialog will close. Do you want to continue?' + ), + { + onRenderContent: (subtitle: string) =>
{subtitle}
, + confirmText: formatMessage('Sign out'), + cancelText: formatMessage('Cancel'), + } + ); + if (confirmed) { + await signoutAndNotify(); + } }} > {props.secondaryText}
); }, - [] + [signoutAndNotify] ); const isNextDisabled = useMemo(() => {