diff --git a/web/packages/design/src/StepSlider/StepSlider.tsx b/web/packages/design/src/StepSlider/StepSlider.tsx index 5dfcdb823750b..f9c406d9602c2 100644 --- a/web/packages/design/src/StepSlider/StepSlider.tsx +++ b/web/packages/design/src/StepSlider/StepSlider.tsx @@ -61,6 +61,7 @@ export function StepSlider(props: Props) { defaultStepIndex = 0, tDuration = 500, wrapping = false, + className, // extraProps are the props required by our step components defined in our flows. ...extraProps } = props; @@ -274,7 +275,7 @@ export function StepSlider(props: Props) { const transitionRef = keyToNodeRef.current.get(key); return ( - + {preMount && {$preContent}} @@ -420,6 +421,8 @@ type Props = * one and backwards from the first one to the last one. */ wrapping?: boolean; + /** Allows styling of the container element. */ + className?: string; } & ExtraProps // Extra props that are passed to each step component. Each step of each flow needs to accept the same set of extra props. : any; diff --git a/web/packages/teleterm/src/ui/AppUpdater/AppUpdater.story.tsx b/web/packages/teleterm/src/ui/AppUpdater/AppUpdater.story.tsx index 0839c366d5425..c8245a2a5b163 100644 --- a/web/packages/teleterm/src/ui/AppUpdater/AppUpdater.story.tsx +++ b/web/packages/teleterm/src/ui/AppUpdater/AppUpdater.story.tsx @@ -291,7 +291,7 @@ function WidgetAndDetails(storyProps: StoryProps) { return ( - +

Widget View

The component is rendered in the login form. diff --git a/web/packages/teleterm/src/ui/AppUpdater/WidgetView.tsx b/web/packages/teleterm/src/ui/AppUpdater/WidgetView.tsx index 90f1972cc3b31..7680745cc5f4f 100644 --- a/web/packages/teleterm/src/ui/AppUpdater/WidgetView.tsx +++ b/web/packages/teleterm/src/ui/AppUpdater/WidgetView.tsx @@ -18,12 +18,20 @@ import { ComponentType } from 'react'; -import { ButtonPrimary, ButtonSecondary, Flex, P3, Stack, Text } from 'design'; +import { + ButtonBorder, + ButtonPrimary, + ButtonSecondary, + Flex, + P3, + Stack, + Text, +} from 'design'; import { Alert } from 'design/Alert'; -import { Info, Warning } from 'design/Icon'; +import { Info } from 'design/Icon'; import { IconProps } from 'design/Icon/Icon'; +import { SpaceProps } from 'design/system'; import { UnreachableCluster } from 'gen-proto-ts/teleport/lib/teleterm/auto_update/v1/auto_update_service_pb'; -import { getErrorMessage } from 'shared/utils/error'; import { Platform } from 'teleterm/mainProcess/types'; import { @@ -50,34 +58,41 @@ import { * Hidden for `update-not-available` and `checking-for-update` events, * unless there's an issue that prevents autoupdates from working. */ -export function WidgetView(props: { +export function WidgetView({ + clusterGetter, + onDownload, + onInstall, + onMore, + platform, + updateEvent, + ...rest +}: { updateEvent: AppUpdateEvent; platform: Platform; clusterGetter: ClusterGetter; onMore(): void; onDownload(): void; onInstall(): void; -}) { - const getClusterName = clusterNameGetter(props.clusterGetter); - const { updateEvent } = props; +} & SpaceProps) { + const getClusterName = clusterNameGetter(clusterGetter); const { autoUpdatesStatus } = updateEvent; - const issueRequiringAttention = findAutoUpdatesIssuesRequiringAttention( - autoUpdatesStatus, - getClusterName - ); + const issueRequiringAttention = + autoUpdatesStatus && + findAutoUpdatesIssuesRequiringAttention(autoUpdatesStatus, getClusterName); if (issueRequiringAttention) { return ( + {issueRequiringAttention} + {/*TODO(gzdunek): Allow Alert to show buttons at the bottom. */} + Resolve +
+ } > App updates are disabled @@ -89,13 +104,15 @@ export function WidgetView(props: { return ( + {updateEvent.error.message} + {/*TODO(gzdunek): Allow Alert to show buttons at the bottom. */} + More +
+ } > Unable to check for app updates @@ -111,8 +128,8 @@ export function WidgetView(props: { const { description, button } = makeUpdaterContent({ updateEvent, - onDownload: props.onDownload, - onInstall: props.onInstall, + onDownload, + onInstall, }); const unreachableClusters = @@ -125,22 +142,32 @@ export function WidgetView(props: { return ( ); } -function AvailableUpdate(props: { +function AvailableUpdate({ + description, + downloadHost, + onMore, + platform, + primaryButton, + unreachableClusters, + version, + ...rest +}: { version: string; - description: string | { Icon: ComponentType; text: string }; + description: string; unreachableClusters: UnreachableCluster[]; downloadHost: string; platform: Platform; @@ -150,17 +177,16 @@ function AvailableUpdate(props: { name: string; onClick(): void; }; -}) { - const hasUnreachableClusters = !!props.unreachableClusters.length; +} & SpaceProps) { + const hasUnreachableClusters = !!unreachableClusters.length; const isNonTeleportServer = - props.downloadHost && !isTeleportDownloadHost(props.downloadHost); + downloadHost && !isTeleportDownloadHost(downloadHost); return ( // Mimics a neutral alert. props.theme.colors.text.disabled}; background: ${props => props.theme.colors.interactive.tonal.neutral[0]}; @@ -168,10 +194,11 @@ function AvailableUpdate(props: { borderRadius={3} px={3} py="12px" + {...rest} > - {props.platform === 'darwin' ? ( + {platform === 'darwin' ? ( App icon ) : ( )} - Teleport Connect {props.version} - {typeof props.description === 'object' ? ( - - - {props.description.text} - - ) : ( - {props.description} - )} + Teleport Connect {version} + {description} - {props.primaryButton && ( - - {props.primaryButton.name} + {primaryButton && ( + + {primaryButton.name} )} - + More @@ -208,14 +228,14 @@ function AvailableUpdate(props: { {hasUnreachableClusters && ( )} {isNonTeleportServer && ( )} @@ -248,7 +268,7 @@ function makeUpdaterContent({ onDownload(): void; onInstall(): void; }): { - description: string | { Icon: ComponentType; text: string }; + description: string; button?: { name: string; action(): void; @@ -283,10 +303,7 @@ function makeUpdaterContent({ }; case 'error': return { - description: { - Icon: Warning, - text: getErrorMessage(updateEvent.error), - }, + description: 'Update failed', }; } } diff --git a/web/packages/teleterm/src/ui/AppUpdater/common.ts b/web/packages/teleterm/src/ui/AppUpdater/common.ts index 1fa188c14ed42..1ed2790685bc6 100644 --- a/web/packages/teleterm/src/ui/AppUpdater/common.ts +++ b/web/packages/teleterm/src/ui/AppUpdater/common.ts @@ -38,7 +38,9 @@ export function getDownloadHost(event: AppUpdateEvent): string { case 'update-available': case 'download-progress': case 'update-downloaded': - return new URL(event.update.files.at(0).url).host; + case 'error': + const url = event.update?.files?.at(0)?.url; + return url && new URL(url).host; default: return ''; } diff --git a/web/packages/teleterm/src/ui/AppUpdater/index.ts b/web/packages/teleterm/src/ui/AppUpdater/index.ts index 25b472fb51672..60baf477f7e4f 100644 --- a/web/packages/teleterm/src/ui/AppUpdater/index.ts +++ b/web/packages/teleterm/src/ui/AppUpdater/index.ts @@ -19,3 +19,4 @@ export * from './DetailsView'; export * from './WidgetView'; export * from './AppUpdaterContext'; +export { type ClusterGetter } from './common'; diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.story.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.story.tsx index a6ec4f912b95a..a4db5113edee0 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.story.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.story.tsx @@ -32,6 +32,7 @@ const meta: Meta = { argTypes: compatibilityArgType, args: { compatibility: 'compatible', + showUpdate: true, }, }; export default meta; @@ -154,15 +155,19 @@ export const SsoError = (storyProps: StoryProps) => { }; export const LocalWithPasswordless = (storyProps: StoryProps) => { + const props = makeProps(storyProps); + props.initAttempt.data.allowPasswordless = true; + return ( - + ); }; export const LocalLoggedInUserWithPasswordless = (storyProps: StoryProps) => { const props = makeProps(storyProps); + props.initAttempt.data.allowPasswordless = true; props.loggedInUserName = 'llama'; return ( diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.test.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.test.tsx index c42974ed4cac6..46ee673d1adb3 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.test.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.test.tsx @@ -19,9 +19,11 @@ import userEvent from '@testing-library/user-event'; import { render, screen } from 'design/utils/testing'; +import { ClientVersionStatus } from 'gen-proto-ts/teleport/lib/teleterm/v1/auth_settings_pb'; import { MockedUnaryCall } from 'teleterm/services/tshd/cloneableClient'; import { makeRootCluster } from 'teleterm/services/tshd/testHelpers'; +import { AppUpdaterContextProvider } from 'teleterm/ui/AppUpdater'; import { MockAppContextProvider } from 'teleterm/ui/fixtures/MockAppContextProvider'; import { MockAppContext } from 'teleterm/ui/fixtures/mocks'; @@ -41,12 +43,14 @@ it('keeps the focus on the password field on submission error', async () => { render( - {}} - prefill={{ username: 'alice' }} - reason={undefined} - /> + + {}} + prefill={{ username: 'alice' }} + reason={undefined} + /> + ); @@ -59,3 +63,83 @@ it('keeps the focus on the password field on submission error', async () => { await screen.findByText('whoops something went wrong'); expect(passwordField).toHaveFocus(); }); + +it('shows go to updates button in compatibility warning if there are clusters providing updates', async () => { + const clusterFoo = makeRootCluster({ uri: '/clusters/foo' }); + const clusterBar = makeRootCluster({ uri: '/clusters/bar' }); + const appContext = new MockAppContext(); + appContext.addRootCluster(clusterFoo); + appContext.addRootCluster(clusterBar); + + jest.spyOn(appContext.tshd, 'getAuthSettings').mockResolvedValue( + new MockedUnaryCall({ + localAuthEnabled: true, + authProviders: [], + hasMessageOfTheDay: false, + authType: 'local', + allowPasswordless: false, + localConnectorName: '', + clientVersionStatus: ClientVersionStatus.TOO_NEW, + versions: { + minClient: '16.0.0-aa', + client: '17.0.0', + server: '17.0.0', + }, + }) + ); + + jest + .spyOn(appContext.mainProcessClient, 'subscribeToAppUpdateEvents') + .mockImplementation(callback => { + callback({ + kind: 'update-not-available', + autoUpdatesStatus: { + enabled: true, + source: 'managing-cluster', + version: '19.0.0', + options: { + highestCompatibleVersion: '', + managingClusterUri: clusterBar.uri, + unreachableClusters: [], + clusters: [ + { + clusterUri: clusterFoo.uri, + toolsAutoUpdate: true, + toolsVersion: '19.0.0', + minToolsVersion: '18.0.0-aa', + otherCompatibleClusters: [], + }, + { + clusterUri: clusterBar.uri, + toolsAutoUpdate: true, + toolsVersion: '17.0.0', + minToolsVersion: '16.0.0-aa', + otherCompatibleClusters: [], + }, + ], + }, + }, + }); + return { cleanup: () => {} }; + }); + + render( + + + {}} + prefill={{ username: 'alice' }} + reason={undefined} + /> + + + ); + + expect( + await screen.findByText('Detected potentially incompatible version') + ).toBeVisible(); + expect( + await screen.findByRole('button', { name: 'Go to Auto Updates' }) + ).toBeVisible(); +}); diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.tsx index 1dcc9ec99d0ea..5d378c28ee1ea 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/ClusterLogin.tsx @@ -25,16 +25,20 @@ import { Flex, H2, Indicator, + StepSlider, Text, } from 'design'; import * as Alerts from 'design/Alert'; import { DialogContent, DialogHeader } from 'design/Dialog'; import * as Icons from 'design/Icon'; +import { ArrowBack } from 'design/Icon'; +import type { StepComponentProps } from 'design/StepSlider'; import { AuthSettings } from 'gen-proto-ts/teleport/lib/teleterm/v1/auth_settings_pb'; import { PrimaryAuthType } from 'shared/services'; import { publicAddrWithTargetPort } from 'teleterm/services/tshd/app'; import { getTargetNameFromUri } from 'teleterm/services/tshd/gateway'; +import { DetailsView } from 'teleterm/ui/AppUpdater'; import { ClusterConnectReason } from 'teleterm/ui/services/modals'; import { outermostPadding } from '../spacing'; @@ -47,11 +51,28 @@ export function ClusterLogin(props: Props & { reason: ClusterConnectReason }) { return ; } +export const ClusterLoginPresentation = ( + props: ClusterLoginPresentationProps +) => { + return ( + + ); +}; + export type ClusterLoginPresentationProps = State & { reason: ClusterConnectReason; }; -export function ClusterLoginPresentation({ +function ClusterLoginForm({ title, initAttempt, init, @@ -69,9 +90,18 @@ export function ClusterLoginPresentation({ shouldSkipVersionCheck, disableVersionCheck, platform, -}: ClusterLoginPresentationProps) { + next, + refCallback, + clusterGetter, + changeAppUpdatesManagingCluster, + appUpdateEvent, + cancelAppUpdateDownload, + quitAndInstallAppUpdate, + downloadAppUpdate, + checkForAppUpdates, +}: ClusterLoginPresentationProps & StepComponentProps) { return ( - <> +

Log in to {title} @@ -125,13 +155,67 @@ export function ClusterLoginPresentation({ shouldSkipVersionCheck={shouldSkipVersionCheck} disableVersionCheck={disableVersionCheck} platform={platform} + clusterGetter={clusterGetter} + checkForAppUpdates={checkForAppUpdates} + changeAppUpdatesManagingCluster={changeAppUpdatesManagingCluster} + appUpdateEvent={appUpdateEvent} + cancelAppUpdateDownload={cancelAppUpdateDownload} + downloadAppUpdate={downloadAppUpdate} + quitAndInstallAppUpdate={quitAndInstallAppUpdate} + switchToAppUpdateDetails={next} /> )} - + ); } +const AppUpdateDetails = ({ + refCallback, + platform, + downloadAppUpdate, + checkForAppUpdates, + cancelAppUpdateDownload, + quitAndInstallAppUpdate, + changeAppUpdatesManagingCluster, + appUpdateEvent, + clusterGetter, + onCloseDialog, + prev, +}: ClusterLoginPresentationProps & StepComponentProps) => { + return ( + + + + + + +

App Updates

+
+ + + +
+ + quitAndInstallAppUpdate()} + platform={platform} + changeManagingCluster={clusterUri => + changeAppUpdatesManagingCluster(clusterUri) + } + updateEvent={appUpdateEvent} + onDownload={() => downloadAppUpdate()} + onCancelDownload={() => cancelAppUpdateDownload()} + clusterGetter={clusterGetter} + onCheckForUpdates={() => checkForAppUpdates()} + /> + +
+ ); +}; + +const loginViews = { default: [ClusterLoginForm, AppUpdateDetails] }; + function getPrimaryAuthType(auth: AuthSettings): PrimaryAuthType { if (auth.localConnectorName === 'passwordless') { return 'passwordless'; diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/CompatibilityWarning.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/CompatibilityWarning.tsx index 8fe44bae929d2..541c9ced7af9f 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/CompatibilityWarning.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/FormLogin/CompatibilityWarning.tsx @@ -27,10 +27,12 @@ import { import { Platform } from 'teleterm/mainProcess/types'; export function CompatibilityWarning(props: { + isAnyClusterProvidingUpdates: boolean; authSettings: AuthSettings; platform: Platform; shouldSkipVersionCheck: boolean; disableVersionCheck(): void; + onSwitchToAppUpdateDetails(): void; mx?: number; }) { if (props.shouldSkipVersionCheck) { @@ -56,15 +58,22 @@ export function CompatibilityWarning(props: { fill="border" intent="neutral" inputAlignment - action={{ - content: ( - <> - Download in Browser - - - ), - href: buildDownloadUrl(props.platform), - }} + action={ + props.isAnyClusterProvidingUpdates + ? { + content: 'Go to Auto Updates', + onClick: props.onSwitchToAppUpdateDetails, + } + : { + content: ( + <> + Download in Browser + + + ), + href: buildDownloadUrl(props.platform), + } + } /> c.toolsAutoUpdate + ), + onSwitchToAppUpdateDetails: props.switchToAppUpdateDetails, + }; + const appUpdateWidgetViewProps = { + updateEvent: props.appUpdateEvent, + onDownload: () => props.downloadAppUpdate(), + onInstall: () => props.quitAndInstallAppUpdate(), + platform: props.platform, + onMore: () => props.switchToAppUpdateDetails(), + clusterGetter: props.clusterGetter, }; const ssoEnabled = authProviders?.length > 0; @@ -82,6 +98,7 @@ export default function LoginForm(props: Props) { )} + ); @@ -97,6 +114,7 @@ export default function LoginForm(props: Props) { Login has not been enabled + ); } @@ -120,6 +138,7 @@ export default function LoginForm(props: Props) { mx={outermostPadding} {...compatibilityWarningProps} /> + flows={loginViews} currFlow={'default'} @@ -321,6 +340,14 @@ export type Props = { shouldSkipVersionCheck: boolean; disableVersionCheck(): void; platform: Platform; + switchToAppUpdateDetails(): void; + appUpdateEvent: AppUpdateEvent; + downloadAppUpdate(): Promise; + cancelAppUpdateDownload(): Promise; + checkForAppUpdates(): Promise; + quitAndInstallAppUpdate(): void; + changeAppUpdatesManagingCluster(clusterUri: RootClusterUri): Promise; + clusterGetter: ClusterGetter; }; const OutermostPadding = styled(Box).attrs({ px: outermostPadding })``; diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/storyHelpers.tsx b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/storyHelpers.tsx index ed40e03f43deb..ad1c39470cc66 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/storyHelpers.tsx +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/storyHelpers.tsx @@ -35,6 +35,7 @@ export const TestContainer: FC = ({ children }) => ( export interface StoryProps { compatibility: 'compatible' | 'client-too-old' | 'client-too-new'; + showUpdate: boolean; } export const compatibilityArgType: ArgTypes = { @@ -43,6 +44,10 @@ export const compatibilityArgType: ArgTypes = { options: ['compatible', 'client-too-old', 'client-too-new'], description: 'Client compatibility', }, + showUpdate: { + type: 'boolean', + description: 'Show app update', + }, }; export function makeProps( @@ -75,6 +80,27 @@ export function makeProps( shouldSkipVersionCheck: false, disableVersionCheck: () => {}, platform: 'darwin', + changeAppUpdatesManagingCluster: async () => {}, + checkForAppUpdates: async () => {}, + downloadAppUpdate: async () => {}, + clusterGetter: { + findCluster: () => undefined, + }, + quitAndInstallAppUpdate: async () => {}, + cancelAppUpdateDownload: async () => {}, + appUpdateEvent: { + kind: 'update-not-available', + autoUpdatesStatus: { + enabled: false, + reason: 'no-cluster-with-auto-update', + options: { + unreachableClusters: [], + clusters: [], + highestCompatibleVersion: '', + managingClusterUri: '', + }, + }, + }, }; switch (storyProps.compatibility) { @@ -99,5 +125,43 @@ export function makeProps( } } + if (storyProps.showUpdate) { + props.appUpdateEvent = { + kind: 'update-available', + update: { + version: '19.0.0', + files: [ + { + url: 'https://cdn.teleport.dev/connect-update', + sha512: '', + }, + ], + path: '', + releaseDate: '', + sha512: '', + }, + autoDownload: true, + autoUpdatesStatus: { + enabled: true, + source: 'highest-compatible', + version: '19.0.0', + options: { + unreachableClusters: [], + managingClusterUri: '', + clusters: [ + { + clusterUri: '/clusters/foo', + toolsAutoUpdate: true, + minToolsVersion: '18.0.0', + toolsVersion: '19.0.0', + otherCompatibleClusters: [], + }, + ], + highestCompatibleVersion: '19.0.0', + }, + }, + }; + } + return props; } diff --git a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/useClusterLogin.ts b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/useClusterLogin.ts index b19bbaa50648c..ea6ecb9df4810 100644 --- a/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/useClusterLogin.ts +++ b/web/packages/teleterm/src/ui/ClusterConnect/ClusterLogin/useClusterLogin.ts @@ -22,6 +22,7 @@ import { useAsync } from 'shared/hooks/useAsync'; import { cloneAbortSignal } from 'teleterm/services/tshd/cloneableClient'; import { useAppContext } from 'teleterm/ui/appContextProvider'; +import { useAppUpdaterContext } from 'teleterm/ui/AppUpdater'; import type * as types from 'teleterm/ui/services/clusters/types'; import { RootClusterUri } from 'teleterm/ui/uri'; import { assertUnreachable } from 'teleterm/ui/utils'; @@ -30,6 +31,7 @@ export default function useClusterLogin(props: Props) { const { onSuccess, clusterUri } = props; const { clustersService, tshd, configService, mainProcessClient } = useAppContext(); + const appUpdaterContext = useAppUpdaterContext(); const cluster = clustersService.findCluster(clusterUri); const refAbortCtrl = useRef(null); const loggedInUserName = @@ -38,9 +40,14 @@ export default function useClusterLogin(props: Props) { const [passwordlessLoginState, setPasswordlessLoginState] = useState(); - const [initAttempt, init] = useAsync(() => - tshd.getAuthSettings({ clusterUri }).then(({ response }) => response) - ); + const [initAttempt, init] = useAsync(() => { + return Promise.all([ + tshd.getAuthSettings({ clusterUri }).then(({ response }) => response), + // checkForAppUpdates doesn't return a rejected promise, errors are + // surfaced in app updates widget and details view. + mainProcessClient.checkForAppUpdates(), + ]).then(([authSettings]) => authSettings); + }); const [loginAttempt, login, setAttempt] = useAsync( (params: types.LoginParams) => { @@ -184,6 +191,17 @@ export default function useClusterLogin(props: Props) { shouldSkipVersionCheck, disableVersionCheck, platform, + appUpdateEvent: appUpdaterContext.updateEvent, + downloadAppUpdate: mainProcessClient.downloadAppUpdate, + cancelAppUpdateDownload: mainProcessClient.cancelAppUpdateDownload, + checkForAppUpdates: mainProcessClient.checkForAppUpdates, + quitAndInstallAppUpdate: mainProcessClient.quitAndInstallAppUpdate, + changeAppUpdatesManagingCluster: + mainProcessClient.changeAppUpdatesManagingCluster, + clusterGetter: { + findCluster: (clusterUri: RootClusterUri) => + clustersService.findCluster(clusterUri), + }, }; } diff --git a/web/packages/teleterm/src/ui/Search/SearchBar.test.tsx b/web/packages/teleterm/src/ui/Search/SearchBar.test.tsx index b7d7c4dcdb428..ed57077fdb8b4 100644 --- a/web/packages/teleterm/src/ui/Search/SearchBar.test.tsx +++ b/web/packages/teleterm/src/ui/Search/SearchBar.test.tsx @@ -26,6 +26,7 @@ import { makeRetryableError, makeRootCluster, } from 'teleterm/services/tshd/testHelpers'; +import { AppUpdaterContextProvider } from 'teleterm/ui/AppUpdater'; import { MockAppContextProvider } from 'teleterm/ui/fixtures/MockAppContextProvider'; import { MockAppContext } from 'teleterm/ui/fixtures/mocks'; import ModalsHost from 'teleterm/ui/ModalsHost'; @@ -321,12 +322,14 @@ it('shows a login modal when a request to a cluster from the current workspace f render( - - - - - - + + + + + + + + );