Skip to content

Commit

Permalink
Refactor DocumentGateway and DocumentGatewayApp (#50497)
Browse files Browse the repository at this point in the history
* Add controls to DocumentGatewayApp story

* Drop "tcp://" from address of TCP apps

It really is mostly just a Teleport convention. The tcp:// part was
not useful in any 3rd-party app.

* PortFieldInput: Remove CSS responsible for hiding stepper

It doesn't seem to work anyway in the current Chromium version.

* Refactor DocumentGateway away from container pattern

* Add controls to DocumentGateway story

* Inline useDocumentGateway

* Fix how AppGateway displays disconnect error

* Show disconnect errors in DocumentGateway
  • Loading branch information
ravicious authored Dec 31, 2024
1 parent 8738464 commit b228096
Show file tree
Hide file tree
Showing 12 changed files with 378 additions and 340 deletions.
4 changes: 3 additions & 1 deletion web/packages/shared/hooks/useAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export function useAsync<Args extends unknown[], AttemptData>(
const isMounted = useIsMounted();
const asyncTask = useRef<Promise<AttemptData>>();

const run = useCallback(
const run: (...args: Args) => RunFuncReturnValue<AttemptData> = useCallback(
(...args: Args) => {
setState(prevState => ({
status: 'processing',
Expand Down Expand Up @@ -311,3 +311,5 @@ export function useDelayedRepeatedAttempt<Data>(

return currentAttempt;
}

export type RunFuncReturnValue<AttemptData> = Promise<[AttemptData, Error]>;
276 changes: 146 additions & 130 deletions web/packages/teleterm/src/ui/DocumentGateway/DocumentGateway.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,157 +16,173 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { Meta } from '@storybook/react';
import { ComponentProps } from 'react';
import {
makeEmptyAttempt,
makeProcessingAttempt,
makeErrorAttemptWithStatusText,
makeSuccessAttempt,
makeErrorAttempt,
} from 'shared/hooks/useAsync';

import { makeDatabaseGateway } from 'teleterm/services/tshd/testHelpers';

import { DocumentGateway, DocumentGatewayProps } from './DocumentGateway';
import { OfflineGateway } from '../components/OfflineGateway';

export default {
title: 'Teleterm/DocumentGateway',
};
import { OnlineDocumentGateway } from './OnlineDocumentGateway';

const gateway = makeDatabaseGateway({
uri: '/gateways/bar',
targetName: 'sales-production',
targetUri: '/clusters/bar/dbs/foo',
targetUser: 'alice',
localAddress: 'localhost',
localPort: '1337',
protocol: 'postgres',
targetSubresourceName: 'bar',
});
gateway.gatewayCliCommand.preview = 'connect-me-to-db-please';

const onlineDocumentGatewayProps: DocumentGatewayProps = {
gateway: gateway,
targetName: gateway.targetName,
defaultPort: gateway.localPort,
disconnect: async () => [undefined, null],
connected: true,
reconnect: async () => [undefined, null],
connectAttempt: makeSuccessAttempt(undefined),
disconnectAttempt: makeEmptyAttempt(),
runCliCommand: () => {},
changeDbName: async () => [undefined, null],
changeDbNameAttempt: makeEmptyAttempt(),
changePort: async () => [undefined, null],
changePortAttempt: makeEmptyAttempt(),
type StoryProps = {
online: boolean;
// Online props.
longValues: boolean;
dbNameAttempt: 'not-started' | 'processing' | 'error';
portAttempt: 'not-started' | 'processing' | 'error';
disconnectAttempt: 'not-started' | 'error';
// Offline props.
connectAttempt: 'not-started' | 'processing' | 'error';
};

export function Online() {
return <DocumentGateway {...onlineDocumentGatewayProps} />;
}
const meta: Meta<StoryProps> = {
title: 'Teleterm/DocumentGateway',
component: Story,
argTypes: {
// Online props.
longValues: { if: { arg: 'online' } },
dbNameAttempt: {
if: { arg: 'online' },
control: { type: 'radio' },
options: ['not-started', 'processing', 'error'],
},
portAttempt: {
if: { arg: 'online' },
control: { type: 'radio' },
options: ['not-started', 'processing', 'error'],
},
disconnectAttempt: {
if: { arg: 'online' },
control: { type: 'radio' },
options: ['not-started', 'error'],
},
// Offline props.
connectAttempt: {
if: { arg: 'online', truthy: false },
control: { type: 'radio' },
options: ['not-started', 'processing', 'error'],
},
},
args: {
online: true,
// Online props.
longValues: false,
dbNameAttempt: 'not-started',
portAttempt: 'not-started',
disconnectAttempt: 'not-started',
// Offline props.
connectAttempt: 'not-started',
},
};
export default meta;

export function OnlineWithLongValues() {
const gateway = makeDatabaseGateway({
export function Story(props: StoryProps) {
let gateway = makeDatabaseGateway({
uri: '/gateways/bar',
targetName: 'sales-production',
targetUri: '/clusters/bar/dbs/foo',
targetUser:
'quux-quuz-foo-bar-quux-quuz-foo-bar-quux-quuz-foo-bar-quux-quuz-foo-bar',
targetUser: 'alice',
localAddress: 'localhost',
localPort: '13337',
localPort: '1337',
protocol: 'postgres',
targetSubresourceName:
'foo-bar-baz-quux-quuz-foo-bar-baz-quux-quuz-foo-bar-baz-quux-quuz',
targetSubresourceName: 'bar',
});
gateway.gatewayCliCommand.preview =
'connect-me-to-db-please-baz-quux-quuz-foo-baz-quux-quuz-foo-baz-quux-quuz-foo';

return (
<DocumentGateway
{...onlineDocumentGatewayProps}
gateway={gateway}
defaultPort={gateway.localPort}
/>
);
}

export function OnlineWithFailedDbNameAttempt() {
return (
<DocumentGateway
{...onlineDocumentGatewayProps}
changeDbNameAttempt={makeErrorAttemptWithStatusText<void>(
'Something went wrong with setting database name.'
)}
/>
);
}

export function OnlineWithFailedPortAttempt() {
return (
<DocumentGateway
{...onlineDocumentGatewayProps}
changePortAttempt={makeErrorAttemptWithStatusText<void>(
'Something went wrong with setting port.'
)}
/>
);
}

export function OnlineWithFailedDbNameAndPortAttempts() {
return (
<DocumentGateway
{...onlineDocumentGatewayProps}
changeDbNameAttempt={makeErrorAttemptWithStatusText<void>(
'Something went wrong with setting database name.'
)}
changePortAttempt={makeErrorAttemptWithStatusText<void>(
'Something went wrong with setting port.'
)}
/>
);
}

export function Offline() {
return (
<DocumentGateway
{...onlineDocumentGatewayProps}
gateway={undefined}
defaultPort="1337"
connected={false}
connectAttempt={makeEmptyAttempt()}
/>
);
}

export function OfflineWithFailedConnectAttempt() {
return (
<DocumentGateway
{...onlineDocumentGatewayProps}
gateway={undefined}
defaultPort="62414"
connected={false}
connectAttempt={makeErrorAttemptWithStatusText<void>(
'listen tcp 127.0.0.1:62414: bind: address already in use'
)}
/>
);
}

export function Processing() {
return (
<DocumentGateway
{...onlineDocumentGatewayProps}
gateway={undefined}
defaultPort="1337"
connected={false}
connectAttempt={makeProcessingAttempt()}
/>
);
}
gateway.gatewayCliCommand.preview = 'connect-me-to-db-please';

if (!props.online) {
const offlineGatewayProps: ComponentProps<typeof OfflineGateway> = {
connectAttempt: makeEmptyAttempt(),
reconnect: () => {},
gatewayPort: { isSupported: true, defaultPort: '1337' },
targetName: gateway.targetName,
gatewayKind: 'database',
};

if (props.connectAttempt === 'error') {
offlineGatewayProps.connectAttempt = makeErrorAttempt(
new Error('listen tcp 127.0.0.1:62414: bind: address already in use')
);
}

if (props.connectAttempt === 'processing') {
offlineGatewayProps.connectAttempt = makeProcessingAttempt();
}

return (
<OfflineGateway
// Completely re-mount all components on props change.
key={JSON.stringify(props)}
{...offlineGatewayProps}
/>
);
}

if (props.longValues) {
gateway = makeDatabaseGateway({
uri: '/gateways/bar',
targetName: 'sales-production',
targetUri: '/clusters/bar/dbs/foo',
targetUser:
'quux-quuz-foo-bar-quux-quuz-foo-bar-quux-quuz-foo-bar-quux-quuz-foo-bar',
localAddress: 'localhost',
localPort: '13337',
protocol: 'postgres',
targetSubresourceName:
'foo-bar-baz-quux-quuz-foo-bar-baz-quux-quuz-foo-bar-baz-quux-quuz',
});
gateway.gatewayCliCommand.preview =
'connect-me-to-db-please-baz-quux-quuz-foo-baz-quux-quuz-foo-baz-quux-quuz-foo';
}

const onlineDocumentGatewayProps: ComponentProps<
typeof OnlineDocumentGateway
> = {
gateway: gateway,
disconnect: async () => [undefined, null],
runCliCommand: () => {},
changeDbName: async () => [undefined, null],
changeDbNameAttempt: makeEmptyAttempt(),
changePort: async () => [undefined, null],
changePortAttempt: makeEmptyAttempt(),
disconnectAttempt: makeEmptyAttempt(),
};

if (props.dbNameAttempt === 'error') {
onlineDocumentGatewayProps.changeDbNameAttempt = makeErrorAttempt(
new Error('Something went wrong with setting database name.')
);
}
if (props.dbNameAttempt === 'processing') {
onlineDocumentGatewayProps.changeDbNameAttempt = makeProcessingAttempt();
}

if (props.portAttempt === 'error') {
onlineDocumentGatewayProps.changePortAttempt = makeErrorAttempt(
new Error('Something went wrong with setting port.')
);
}
if (props.portAttempt === 'processing') {
onlineDocumentGatewayProps.changePortAttempt = makeProcessingAttempt();
}

if (props.disconnectAttempt === 'error') {
onlineDocumentGatewayProps.disconnectAttempt = makeErrorAttempt(
new Error('Something went wrong with closing connection.')
);
}

export function PortProcessing() {
return (
<DocumentGateway
<OnlineDocumentGateway
// Completely re-mount all components on props change. This impacts the default values of
// inputs.
key={JSON.stringify(props)}
{...onlineDocumentGatewayProps}
changePortAttempt={makeProcessingAttempt()}
/>
);
}
Loading

0 comments on commit b228096

Please sign in to comment.