From e2e35e61e07293366fb5d5bbadcbdbfb1ab6a08c Mon Sep 17 00:00:00 2001 From: Srinaath Ravichandran Date: Mon, 3 May 2021 18:54:17 -0700 Subject: [PATCH] feat: Breaking change upgrade UX from 1.4 to 2.0 (#7388) * Added mechanism to show custom UX on breaking updates * Minor updates / l10n * Fixed some minor bugs and added l10n * Cleaned up types * Fixed "Not now" behavior * Reenabled nightly feed to composer Signed-off-by: Srinaath Ravichandran * Revert "Reenabled nightly feed to composer" This reverts commit f26216fb76065b760fbfecffb0e1f0f8036d3f78. Signed-off-by: Srinaath Ravichandran * Updated changelog and set correct feed URL Signed-off-by: Srinaath Ravichandran * revert updates Signed-off-by: Srinaath Ravichandran Revert import order Signed-off-by: Srinaath Ravichandran Removed logs Signed-off-by: Srinaath Ravichandran * Handle unit test upgrades Signed-off-by: Srinaath Ravichandran Co-authored-by: Tony Co-authored-by: Srinaath Ravichandran --- .../{ => AppUpdater}/AppUpdater.tsx | 117 ++++++++++----- .../breakingUpdates/breakingUpdatesMap.ts | 20 +++ .../AppUpdater/breakingUpdates/types.ts | 11 ++ .../breakingUpdates/version1To2.tsx | 142 ++++++++++++++++++ .../src/components/AppUpdater/index.tsx | 4 + Composer/packages/client/src/constants.ts | 1 + .../recoilModel/dispatchers/application.ts | 2 +- .../__tests__/appUpdater.test.ts | 10 +- .../src/{ => appUpdater}/appUpdater.ts | 46 ++++-- .../src/appUpdater/breakingUpdates/index.ts | 13 ++ .../src/appUpdater/breakingUpdates/types.ts | 7 + .../appUpdater/breakingUpdates/version1To2.ts | 11 ++ .../electron-server/src/appUpdater/index.ts | 4 + Composer/packages/electron-server/src/main.ts | 5 +- .../packages/server/src/locales/en-US.json | 15 ++ Composer/packages/types/src/appUpdates.ts | 5 + Composer/packages/types/src/index.ts | 1 + releases/1.4.1.md | 10 +- 18 files changed, 367 insertions(+), 57 deletions(-) rename Composer/packages/client/src/components/{ => AppUpdater}/AppUpdater.tsx (73%) create mode 100644 Composer/packages/client/src/components/AppUpdater/breakingUpdates/breakingUpdatesMap.ts create mode 100644 Composer/packages/client/src/components/AppUpdater/breakingUpdates/types.ts create mode 100644 Composer/packages/client/src/components/AppUpdater/breakingUpdates/version1To2.tsx create mode 100644 Composer/packages/client/src/components/AppUpdater/index.tsx rename Composer/packages/electron-server/src/{ => appUpdater}/appUpdater.ts (81%) create mode 100644 Composer/packages/electron-server/src/appUpdater/breakingUpdates/index.ts create mode 100644 Composer/packages/electron-server/src/appUpdater/breakingUpdates/types.ts create mode 100644 Composer/packages/electron-server/src/appUpdater/breakingUpdates/version1To2.ts create mode 100644 Composer/packages/electron-server/src/appUpdater/index.ts create mode 100644 Composer/packages/types/src/appUpdates.ts diff --git a/Composer/packages/client/src/components/AppUpdater.tsx b/Composer/packages/client/src/components/AppUpdater/AppUpdater.tsx similarity index 73% rename from Composer/packages/client/src/components/AppUpdater.tsx rename to Composer/packages/client/src/components/AppUpdater/AppUpdater.tsx index 9c4c4aa2b9..329d14b641 100644 --- a/Composer/packages/client/src/components/AppUpdater.tsx +++ b/Composer/packages/client/src/components/AppUpdater/AppUpdater.tsx @@ -20,8 +20,10 @@ import { useRecoilValue } from 'recoil'; import { SharedColors, NeutralColors } from '@uifabric/fluent-theme'; import { IpcRendererEvent } from 'electron'; -import { AppUpdaterStatus } from '../constants'; -import { appUpdateState, dispatcherState } from '../recoilModel'; +import { AppUpdaterStatus } from '../../constants'; +import { appUpdateState, dispatcherState } from '../../recoilModel'; + +import { breakingUpdatesMap } from './breakingUpdates/breakingUpdatesMap'; const updateAvailableDismissBtn: Partial = { root: { @@ -85,6 +87,12 @@ const downloadOptions = { installAndUpdate: 'installAndUpdate', }; +// TODO: factor this out into shared or types +type BreakingUpdateMetaData = { + explicitCheck: boolean; + uxId: string; +}; + // -------------------- AppUpdater -------------------- // export const AppUpdater: React.FC<{}> = () => { @@ -93,9 +101,11 @@ export const AppUpdater: React.FC<{}> = () => { ); const { downloadSizeInBytes, error, progressPercent, showing, status, version } = useRecoilValue(appUpdateState); const [downloadOption, setDownloadOption] = useState(downloadOptions.installAndUpdate); + const [breakingMetaData, setBreakingMetaData] = useState(undefined); const handleDismiss = useCallback(() => { setAppUpdateShowing(false); + setBreakingMetaData(undefined); if (status === AppUpdaterStatus.UPDATE_UNAVAILABLE || status === AppUpdaterStatus.UPDATE_FAILED) { setAppUpdateStatus(AppUpdaterStatus.IDLE, undefined); } @@ -118,53 +128,69 @@ export const AppUpdater: React.FC<{}> = () => { setDownloadOption(option); }, []); + // necessary? + const handleContinueFromBreakingUx = useCallback(() => { + handlePreDownloadOkay(); + }, [handlePreDownloadOkay]); + // listen for app updater events from main process useEffect(() => { - ipcRenderer.on('app-update', (_event: IpcRendererEvent, name: string, payload) => { - switch (name) { - case 'update-available': - setAppUpdateStatus(AppUpdaterStatus.UPDATE_AVAILABLE, payload.version); - setAppUpdateShowing(true); - break; - - case 'progress': { - const progress = +(payload.percent as number).toFixed(2); - setAppUpdateProgress(progress, payload.total); - break; - } + ipcRenderer.on( + 'app-update', + (_event: IpcRendererEvent, name: string, payload, breakingMetaData?: BreakingUpdateMetaData) => { + switch (name) { + case 'update-available': + setAppUpdateStatus(AppUpdaterStatus.UPDATE_AVAILABLE, payload.version); + setAppUpdateShowing(true); + break; - case 'update-in-progress': { - setAppUpdateStatus(AppUpdaterStatus.UPDATE_AVAILABLE, payload.version); - setAppUpdateShowing(true); - break; - } + case 'progress': { + const progress = +(payload.percent as number).toFixed(2); + setAppUpdateProgress(progress, payload.total); + break; + } - case 'update-not-available': { - const explicit = payload; - if (explicit) { - // the user has explicitly checked for an update via the Help menu; - // we should display some UI feedback if there are no updates available - setAppUpdateStatus(AppUpdaterStatus.UPDATE_UNAVAILABLE, undefined); + case 'update-in-progress': { + setAppUpdateStatus(AppUpdaterStatus.UPDATE_AVAILABLE, payload.version); setAppUpdateShowing(true); + break; } - break; - } - case 'update-downloaded': - setAppUpdateStatus(AppUpdaterStatus.UPDATE_SUCCEEDED, undefined); - setAppUpdateShowing(true); - break; + case 'update-not-available': { + const explicit = payload; + if (explicit) { + // the user has explicitly checked for an update via the Help menu; + // we should display some UI feedback if there are no updates available + setAppUpdateStatus(AppUpdaterStatus.UPDATE_UNAVAILABLE, undefined); + setAppUpdateShowing(true); + } + break; + } - case 'error': - setAppUpdateStatus(AppUpdaterStatus.UPDATE_FAILED, undefined); - setAppUpdateError(payload); - setAppUpdateShowing(true); - break; + case 'update-downloaded': + setAppUpdateStatus(AppUpdaterStatus.UPDATE_SUCCEEDED, undefined); + setAppUpdateShowing(true); + break; - default: - break; + case 'error': + setAppUpdateStatus(AppUpdaterStatus.UPDATE_FAILED, undefined); + setAppUpdateError(payload); + setAppUpdateShowing(true); + break; + + case 'breaking-update-available': + if (breakingMetaData) { + setBreakingMetaData(breakingMetaData); + setAppUpdateStatus(AppUpdaterStatus.BREAKING_UPDATE_AVAILABLE, payload.version); + setAppUpdateShowing(true); + } + break; + + default: + break; + } } - }); + ); }, []); const title = useMemo(() => { @@ -292,6 +318,19 @@ export const AppUpdater: React.FC<{}> = () => { const subText = status === AppUpdaterStatus.UPDATE_AVAILABLE ? `${formatMessage('Bot Framework Composer')} v${version}` : ''; + if (status === AppUpdaterStatus.BREAKING_UPDATE_AVAILABLE && showing && breakingMetaData) { + const BreakingUpdateUx = breakingUpdatesMap[breakingMetaData.uxId]; + // TODO: check if breaking update ux component is defined and handle undefined case + return ( + + ); + } + return showing ? ( > = { + 'Version1.x.xTo2.x.x': Version1To2Content, +}; diff --git a/Composer/packages/client/src/components/AppUpdater/breakingUpdates/types.ts b/Composer/packages/client/src/components/AppUpdater/breakingUpdates/types.ts new file mode 100644 index 0000000000..9c5fa3374c --- /dev/null +++ b/Composer/packages/client/src/components/AppUpdater/breakingUpdates/types.ts @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export type BreakingUpdateProps = { + explicitCheck: boolean; + /** Called when the user dismisses the breaking changes UX; stops the update flow completely. */ + onCancel: () => void; + /** Called when the breaking changes UX is ready to continue into the normal update flow. */ + onContinue: () => void; + version?: string; +}; diff --git a/Composer/packages/client/src/components/AppUpdater/breakingUpdates/version1To2.tsx b/Composer/packages/client/src/components/AppUpdater/breakingUpdates/version1To2.tsx new file mode 100644 index 0000000000..f78c3e54ec --- /dev/null +++ b/Composer/packages/client/src/components/AppUpdater/breakingUpdates/version1To2.tsx @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** @jsx jsx */ +import { jsx, css } from '@emotion/core'; +import React, { useCallback, useState } from 'react'; +import { DefaultButton, PrimaryButton, IButtonStyles } from 'office-ui-fabric-react/lib/Button'; +import { Dialog, DialogType, IDialogContentStyles } from 'office-ui-fabric-react/lib/Dialog'; +import { Link } from 'office-ui-fabric-react/lib/Link'; +import { NeutralColors } from '@uifabric/fluent-theme'; +import formatMessage from 'format-message'; +import { useRecoilValue } from 'recoil'; + +import { dispatcherState, userSettingsState } from '../../../recoilModel'; + +import { BreakingUpdateProps } from './types'; + +const dismissButton: Partial = { + root: { + marginRight: '6px;', + marginLeft: 'auto', + }, +}; + +const dialogContent: Partial = { + content: { color: NeutralColors.black }, +}; + +const dialogContentWithoutHeader: Partial = { + ...dialogContent, + header: { + display: 'none', + }, + inner: { + padding: '36px 24px 24px 24px', + }, +}; + +const buttonRow = css` + display: flex; + flex-flow: row nowrap; + justify-items: flex-end; +`; + +const gotItButton = css` + margin-left: auto; +`; + +const updateCancelledCopy = css` + margin-top: 0; + margin-bottom: 27px; +`; + +type ModalState = 'Default' | 'PressedNotNow'; + +export const Version1To2Content: React.FC = (props) => { + const { explicitCheck, onCancel, onContinue } = props; + const [currentState, setCurrentState] = useState('Default'); + const userSettings = useRecoilValue(userSettingsState); + const { updateUserSettings } = useRecoilValue(dispatcherState); + const onNotNow = useCallback(() => { + if (userSettings.appUpdater.autoDownload) { + // disable auto update and notify the user + updateUserSettings({ + appUpdater: { + autoDownload: false, + }, + }); + setCurrentState('PressedNotNow'); + } else { + onCancel(); + } + }, []); + + return currentState === 'Default' ? ( + + ) : ( + + ); +}; diff --git a/Composer/packages/client/src/components/AppUpdater/index.tsx b/Composer/packages/client/src/components/AppUpdater/index.tsx new file mode 100644 index 0000000000..57fef970fe --- /dev/null +++ b/Composer/packages/client/src/components/AppUpdater/index.tsx @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export { AppUpdater } from './AppUpdater'; diff --git a/Composer/packages/client/src/constants.ts b/Composer/packages/client/src/constants.ts index 2ede718606..8d67742cd8 100644 --- a/Composer/packages/client/src/constants.ts +++ b/Composer/packages/client/src/constants.ts @@ -346,6 +346,7 @@ export const SupportedFileTypes = [ export const USER_TOKEN_STORAGE_KEY = 'composer.userToken'; export enum AppUpdaterStatus { + BREAKING_UPDATE_AVAILABLE, IDLE, UPDATE_AVAILABLE, UPDATE_UNAVAILABLE, diff --git a/Composer/packages/client/src/recoilModel/dispatchers/application.ts b/Composer/packages/client/src/recoilModel/dispatchers/application.ts index 906f1d2a35..48831fc503 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/application.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/application.ts @@ -30,7 +30,7 @@ export const applicationDispatcher = () => { const newAppUpdateState = { ...currentAppUpdate, }; - if (status === AppUpdaterStatus.UPDATE_AVAILABLE) { + if (status === AppUpdaterStatus.UPDATE_AVAILABLE || status === AppUpdaterStatus.BREAKING_UPDATE_AVAILABLE) { newAppUpdateState.version = version; } if (status === AppUpdaterStatus.IDLE) { diff --git a/Composer/packages/electron-server/__tests__/appUpdater.test.ts b/Composer/packages/electron-server/__tests__/appUpdater.test.ts index 51fb95c86e..f6d7f9872e 100644 --- a/Composer/packages/electron-server/__tests__/appUpdater.test.ts +++ b/Composer/packages/electron-server/__tests__/appUpdater.test.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { AppUpdater } from '../src/appUpdater'; + const mockAutoUpdater = { allowDowngrade: false, autoDownload: false, @@ -23,14 +25,13 @@ jest.mock('electron', () => ({ }, })); -import { AppUpdater } from '../src/appUpdater'; - describe('App updater', () => { let appUpdater: AppUpdater; beforeEach(() => { mockAutoUpdater.allowDowngrade = false; mockAutoUpdater.autoDownload = false; appUpdater = AppUpdater.getInstance(); + (appUpdater as any).currentAppVersion = mockGetVersion(); (appUpdater as any).checkingForUpdate = false; (appUpdater as any).downloadingUpdate = false; (appUpdater as any)._downloadedUpdate = false; @@ -45,12 +46,10 @@ describe('App updater', () => { }); it('should check for updates from the nightly repo', () => { - (appUpdater as any).settings.autoDownload = true; (appUpdater as any).settings.useNightly = true; appUpdater.checkForUpdates(true); expect(mockAutoUpdater.checkForUpdates).toHaveBeenCalled(); - expect(mockAutoUpdater.autoDownload).toBe(true); expect((appUpdater as any).explicitCheck).toBe(true); expect(mockAutoUpdater.setFeedURL).toHaveBeenCalledWith({ provider: 'github', @@ -89,10 +88,9 @@ describe('App updater', () => { }); it('should not allow a downgrade when checking for updates from nightly to (stable or nightly)', () => { - mockGetVersion.mockReturnValueOnce('0.0.1-nightly.12345.abcdef'); + (appUpdater as any).currentAppVersion = '0.0.1-nightly.12345.abcdef'; mockAutoUpdater.allowDowngrade = true; appUpdater.checkForUpdates(); - expect(mockAutoUpdater.allowDowngrade).toBe(false); }); diff --git a/Composer/packages/electron-server/src/appUpdater.ts b/Composer/packages/electron-server/src/appUpdater/appUpdater.ts similarity index 81% rename from Composer/packages/electron-server/src/appUpdater.ts rename to Composer/packages/electron-server/src/appUpdater/appUpdater.ts index 70d8460467..dac0e4d9a9 100644 --- a/Composer/packages/electron-server/src/appUpdater.ts +++ b/Composer/packages/electron-server/src/appUpdater/appUpdater.ts @@ -8,15 +8,26 @@ import { app } from 'electron'; import { prerelease as isNightly } from 'semver'; import { AppUpdaterSettings } from '@bfc/shared'; -import logger from './utility/logger'; +import logger from '../utility/logger'; + +import { breakingUpdates } from './breakingUpdates'; + const log = logger.extend('app-updater'); +export type BreakingUpdateMetaData = { + explicitCheck: boolean; + uxId: string; +}; + let appUpdater: AppUpdater | undefined; export class AppUpdater extends EventEmitter { + private currentAppVersion = app.getVersion(); + private checkingForUpdate = false; private downloadingUpdate = false; private _downloadedUpdate = false; private explicitCheck = false; + private isBreakingUpdate = false; private updateInfo: UpdateInfo | undefined = undefined; private settings: AppUpdaterSettings = { autoDownload: false, useNightly: false }; @@ -25,6 +36,7 @@ export class AppUpdater extends EventEmitter { autoUpdater.allowDowngrade = false; autoUpdater.allowPrerelease = true; + autoUpdater.autoDownload = false; autoUpdater.autoInstallOnAppQuit = false; // we will explicitly call the install logic autoUpdater.on('error', this.onError.bind(this)); @@ -54,10 +66,8 @@ export class AppUpdater extends EventEmitter { this.emit('update-in-progress', this.updateInfo); return; } - this.setFeedURL(); this.determineUpdatePath(); - autoUpdater.autoDownload = this.settings.autoDownload; autoUpdater.checkForUpdates(); } @@ -98,8 +108,28 @@ export class AppUpdater extends EventEmitter { log('Update available: %O', updateInfo); this.checkingForUpdate = false; this.updateInfo = updateInfo; + + // check to see if the update will include breaking changes + const breakingUpdate = breakingUpdates + .map((predicate) => predicate(this.currentAppVersion, updateInfo.version)) + .find((result) => result.breaking); + if (breakingUpdate) { + this.isBreakingUpdate = true; + // show custom UX for the breaking changes + this.emit('breaking-update-available', updateInfo, { + uxId: breakingUpdate.uxId, + explicitCheck: this.explicitCheck, + }); + return; + } + + // show standard update UX if (this.explicitCheck || !this.settings.autoDownload) { + this.isBreakingUpdate = false; this.emit('update-available', updateInfo); + } else { + // silently download + autoUpdater.downloadUpdate(); } } @@ -115,7 +145,7 @@ export class AppUpdater extends EventEmitter { private onDownloadProgress(progress: any) { log('Got update progress: %O', progress); this.downloadingUpdate = true; - if (this.explicitCheck || !this.settings.autoDownload) { + if (this.explicitCheck || !this.settings.autoDownload || this.isBreakingUpdate) { this.emit('progress', progress); } } @@ -124,7 +154,7 @@ export class AppUpdater extends EventEmitter { log('Update downloaded: %O', updateInfo); this._downloadedUpdate = true; this.updateInfo = updateInfo; - if (this.explicitCheck || !this.settings.autoDownload) { + if (this.explicitCheck || !this.settings.autoDownload || this.isBreakingUpdate) { this.emit('update-downloaded', updateInfo); } this.resetToIdle(); @@ -158,12 +188,10 @@ export class AppUpdater extends EventEmitter { } private determineUpdatePath() { - const currentVersion = app.getVersion(); - // The following paths don't need to allow downgrade: // nightly -> stable (1.0.1-nightly.x.x -> 1.0.2) // nightly -> nightly (1.0.1-nightly.x.x -> 1.0.1-nightly.y.x) - if (isNightly(currentVersion)) { + if (isNightly(this.currentAppVersion)) { const targetChannel = this.settings.useNightly ? 'nightly' : 'stable'; log(`Updating from nightly to ${targetChannel}. Not allowing downgrade.`); autoUpdater.allowDowngrade = false; @@ -173,7 +201,7 @@ export class AppUpdater extends EventEmitter { // https://github.com/npm/node-semver/blob/v7.3.2/classes/semver.js#L127 // The following path needs to allow downgrade to work: // stable -> nightly (1.0.1 -> 1.0.1-nightly.x.x) - if (!isNightly(currentVersion) && this.settings.useNightly) { + if (!isNightly(this.currentAppVersion) && this.settings.useNightly) { log(`Updating from stable to nightly. Allowing downgrade.`); autoUpdater.allowDowngrade = true; return; diff --git a/Composer/packages/electron-server/src/appUpdater/breakingUpdates/index.ts b/Composer/packages/electron-server/src/appUpdater/breakingUpdates/index.ts new file mode 100644 index 0000000000..2af7b54690 --- /dev/null +++ b/Composer/packages/electron-server/src/appUpdater/breakingUpdates/index.ts @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { BreakingUpdatePredicate } from './types'; +import { version1To2 } from './version1To2'; + +/** + * Array of functions that will check the current version and latest version during an update to determine + * if the user is about to install a breaking update. If any of these checks is satisfied, the client will + * display custom UX for the offending update before allowing the user to proceed with the standard + * update flow. + */ +export const breakingUpdates: BreakingUpdatePredicate[] = [version1To2]; diff --git a/Composer/packages/electron-server/src/appUpdater/breakingUpdates/types.ts b/Composer/packages/electron-server/src/appUpdater/breakingUpdates/types.ts new file mode 100644 index 0000000000..f6574ef2ae --- /dev/null +++ b/Composer/packages/electron-server/src/appUpdater/breakingUpdates/types.ts @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { BreakingUpdateId } from '@botframework-composer/types'; + +type BreakingUpdateResult = { breaking: boolean; uxId: BreakingUpdateId }; +export type BreakingUpdatePredicate = (curVersion: string, newVersion: string) => BreakingUpdateResult; diff --git a/Composer/packages/electron-server/src/appUpdater/breakingUpdates/version1To2.ts b/Composer/packages/electron-server/src/appUpdater/breakingUpdates/version1To2.ts new file mode 100644 index 0000000000..0c24ed06a1 --- /dev/null +++ b/Composer/packages/electron-server/src/appUpdater/breakingUpdates/version1To2.ts @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { lt, satisfies } from 'semver'; + +import { BreakingUpdatePredicate } from './types'; + +export const version1To2: BreakingUpdatePredicate = (curVersion: string, newVersion: string) => { + const breaking = lt(curVersion, '2.0.0') && satisfies(newVersion, '>= 2.0.0 < 3.0.0'); + return { breaking, uxId: 'Version1.x.xTo2.x.x' }; +}; diff --git a/Composer/packages/electron-server/src/appUpdater/index.ts b/Composer/packages/electron-server/src/appUpdater/index.ts new file mode 100644 index 0000000000..a52cb14334 --- /dev/null +++ b/Composer/packages/electron-server/src/appUpdater/index.ts @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export * from './appUpdater'; diff --git a/Composer/packages/electron-server/src/main.ts b/Composer/packages/electron-server/src/main.ts index 553419e4be..94acb8cc8c 100644 --- a/Composer/packages/electron-server/src/main.ts +++ b/Composer/packages/electron-server/src/main.ts @@ -11,7 +11,7 @@ import formatMessage from 'format-message'; import { mkdirp } from 'fs-extra'; import { initAppMenu } from './appMenu'; -import { AppUpdater } from './appUpdater'; +import { AppUpdater, BreakingUpdateMetaData } from './appUpdater'; import { OneAuthService } from './auth/oneAuthService'; import { composerProtocol } from './constants'; import ElectronWindow from './electronWindow'; @@ -101,6 +101,9 @@ function initializeAppUpdater(settings: AppUpdaterSettings) { appUpdater.on('update-available', (updateInfo: UpdateInfo) => { mainWindow.webContents.send('app-update', 'update-available', updateInfo); }); + appUpdater.on('breaking-update-available', (updateInfo: UpdateInfo, breakingMetaData: BreakingUpdateMetaData) => { + mainWindow.webContents.send('app-update', 'breaking-update-available', updateInfo, breakingMetaData); + }); appUpdater.on('progress', (progress) => { mainWindow.webContents.send('app-update', 'progress', progress); }); diff --git a/Composer/packages/server/src/locales/en-US.json b/Composer/packages/server/src/locales/en-US.json index b4e03949ca..1a821918ba 100644 --- a/Composer/packages/server/src/locales/en-US.json +++ b/Composer/packages/server/src/locales/en-US.json @@ -449,6 +449,9 @@ "bot_files_created_986109df": { "message": "Bot files created" }, + "bot_framework_composer_2_0_provides_more_built_in__c6abf11c": { + "message": "Bot Framework Composer 2.0 provides more built-in capabilities so you can build complex bots quickly. Update to Composer 2.0 for advanced bot templates, prebuilt components, and a runtime that is fully extensible through packages." + }, "bot_framework_composer_enables_developers_and_mult_ce0e42a9": { "message": "Bot Framework Composer enables developers and multi-disciplinary teams to build all kinds of conversational experiences, using the latest components from the Bot Framework: SDK, LG, LU, and declarative file formats, all without writing code." }, @@ -632,6 +635,9 @@ "component_stacktrace_e24b1983": { "message": "Component Stacktrace:" }, + "composer_2_0_is_now_available_113ed532": { + "message": "Composer 2.0 is now available!" + }, "composer_cannot_yet_translate_your_bot_automatical_2d54081b": { "message": "Composer cannot yet translate your bot automatically.\nTo create a translation manually, Composer will create a copy of your bot’s content with the name of the additional language. This content can then be translated without affecting the original bot logic or flow and you can switch between languages to ensure the responses are correctly and appropriately translated." }, @@ -2276,6 +2282,9 @@ "not_yet_published_669e37b3": { "message": "Not yet published" }, + "note_if_your_bot_is_using_custom_actions_they_will_a500ed2": { + "message": "Note: If your bot is using custom actions, they will not be supported in Composer 2.0. Learn more about updating to Composer 2.0." + }, "notifications_cbfa7704": { "message": "Notifications" }, @@ -3446,9 +3455,15 @@ "update_activity_2b05e6c6": { "message": "Update activity" }, + "update_and_restart_b236a67": { + "message": "Update and restart" + }, "update_available_b637d767": { "message": "Update available" }, + "update_cancelled_auto_update_has_been_turned_off_f_7f7e08d7": { + "message": "Update cancelled. Auto-update has been turned off for this release. You can update at any time by selecting Help > Check for updates." + }, "update_complete_c5163fbf": { "message": "Update complete" }, diff --git a/Composer/packages/types/src/appUpdates.ts b/Composer/packages/types/src/appUpdates.ts new file mode 100644 index 0000000000..50effc11d4 --- /dev/null +++ b/Composer/packages/types/src/appUpdates.ts @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** Used to look up the corresponding UX to display for each breaking update */ +export type BreakingUpdateId = 'Version1.x.xTo2.x.x'; diff --git a/Composer/packages/types/src/index.ts b/Composer/packages/types/src/index.ts index 7c4deb0901..91b01a0243 100644 --- a/Composer/packages/types/src/index.ts +++ b/Composer/packages/types/src/index.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +export * from './appUpdates'; export * from './auth'; export * from './azure'; export * from './diagnostic'; diff --git a/releases/1.4.1.md b/releases/1.4.1.md index 77c20a962b..aeb84675b4 100644 --- a/releases/1.4.1.md +++ b/releases/1.4.1.md @@ -2,9 +2,17 @@ ## Changelog +## Summary + +This release mostly contains small bug fixes and performance improvements in addition to providing a UX for a breaking change upgrade. + +#### Added + +- feat: Breaking change upgrade UX from 1.4 to 2.0 ([#7388](https://github.com/microsoft/BotFramework-Composer/pull/7388)) ([@srinaath](https://github.com/srinaath)) + #### Fixed - fix: add retry logic for adding app password ([#6703](https://github.com/microsoft/BotFramework-Composer/pull/6703)) - fix: ARM token for provisioning is now passed via body. ([#6684](https://github.com/microsoft/BotFramework-Composer/pull/6684)) - fix: Publish page now populates publish types ([#6544](https://github.com/microsoft/BotFramework-Composer/pull/6544)) ([@GeoffCoxMSFT](https://github.com/GeoffCoxMSFT)) -- fix: unify runtime path from ejecting ([#6520](https://github.com/microsoft/BotFramework-Composer/pull/6520)) ([@lei9444](https://github.com/lei9444)) \ No newline at end of file +- fix: unify runtime path from ejecting ([#6520](https://github.com/microsoft/BotFramework-Composer/pull/6520)) ([@lei9444](https://github.com/lei9444))