Skip to content
This repository was archived by the owner on Feb 8, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ limitations under the License.

import React, { useEffect, useRef } from 'react';
import styled from 'styled-components';
import { Box, Flex, Label } from 'design';
import { Box, Flex, Label, Text } from 'design';
import { makeLabelTag } from 'teleport/components/formatters';
import * as types from 'teleterm/ui/services/quickInput/types';
import { Cli, Server, Person } from 'design/Icon';
import { Cli, Server, Person, Database } from 'design/Icon';

const QuickInputList = React.forwardRef<HTMLElement, Props>((props, ref) => {
const activeItemRef = useRef<HTMLDivElement>();
Expand Down Expand Up @@ -114,6 +114,34 @@ function ServerItem(props: { item: types.SuggestionServer }) {
);
}

function DatabaseItem(props: { item: types.SuggestionDatabase }) {
const db = props.item.data;
const $labels = db.labelsList.map((label, index) => (
<Label mr="1" key={index} kind="secondary">
{makeLabelTag(label)}
</Label>
));

return (
<Flex alignItems="center" p={1} minWidth="300px">
<SquareIconBackground color="#4DB2F0">
<Database fontSize="10px" />
</SquareIconBackground>
<Flex flexDirection="column" ml={1} flex={1}>
<Flex justifyContent="space-between" alignItems="center">
<Box mr={2}>{db.name}</Box>
<Box mr={2}>
<Text typography="body2" fontSize={0}>
{db.type}/{db.protocol}
</Text>
</Box>
</Flex>
<Box>{$labels}</Box>
</Flex>
</Flex>
);
}

function UnknownItem(props: { item: types.Suggestion }) {
const { kind } = props.item;
return <div>unknown kind: {kind} </div>;
Expand Down Expand Up @@ -163,6 +191,7 @@ const ComponentMap: Record<
['suggestion.cmd']: CmdItem,
['suggestion.ssh-login']: SshLoginItem,
['suggestion.server']: ServerItem,
['suggestion.database']: DatabaseItem,
};

type Props = {
Expand Down
7 changes: 2 additions & 5 deletions packages/teleterm/src/ui/QuickInput/useQuickInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,8 @@ import {
} from 'teleterm/ui/services/quickInput/types';

export default function useQuickInput() {
const {
quickInputService,
workspacesService,
commandLauncher,
} = useAppContext();
const { quickInputService, workspacesService, commandLauncher } =
useAppContext();
workspacesService.useState();
const documentsService =
workspacesService.getActiveWorkspaceDocumentService();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,54 @@ test('getAutocompleteResult returns correct result for an SSH login suggestion w
});
});

test('getAutocompleteResult returns correct result for a database name suggestion', () => {
mockCommandLauncherAutocompleteCommands(CommandLauncherMock, [
{
name: 'autocomplete.tsh-proxy-db',
displayName: 'tsh proxy db',
description: '',
run: () => {},
},
]);
jest
.spyOn(WorkspacesServiceMock.prototype, 'getActiveWorkspace')
.mockImplementation(() => ({
localClusterUri: 'test_uri',
documents: [],
location: '',
}));
jest
.spyOn(ClustersServiceMock.prototype, 'searchDbs')
.mockImplementation(() => {
return [
{
hostname: 'foobar',
uri: '',
name: '',
desc: '',
protocol: '',
type: '',
addr: '',
labelsList: null,
},
];
});
const quickInputService = new QuickInputService(
new CommandLauncherMock(undefined),
new ClustersServiceMock(undefined),
new WorkspacesServiceMock(undefined, undefined, undefined)
);

const autocompleteResult =
quickInputService.getAutocompleteResult('tsh proxy db foo');
expect(autocompleteResult.kind).toBe('autocomplete.partial-match');
expect((autocompleteResult as AutocompletePartialMatch).targetToken).toEqual({
value: 'foo',
startIndex: 13,
});
expect(autocompleteResult.command).toEqual({ kind: 'command.unknown' });
});

test("getAutocompleteResult doesn't return any suggestions if the only suggestion completely matches the target token", () => {
jest.mock('./quickPickers');
const QuickCommandPickerMock = pickers.QuickCommandPicker as jest.MockedClass<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,18 @@ export class QuickInputService extends Store<State> {
workspacesService,
clustersService
);
const databasePicker = new pickers.QuickDatabasePicker(
workspacesService,
clustersService
);

this.quickCommandPicker.registerPickerForCommand(
'tsh ssh',
new pickers.QuickTshSshPicker(sshLoginPicker, serverPicker)
);
this.quickCommandPicker.registerPickerForCommand(
'tsh proxy db',
new pickers.QuickTshProxyDbPicker()
new pickers.QuickTshProxyDbPicker(databasePicker)
);
}

Expand Down
79 changes: 76 additions & 3 deletions packages/teleterm/src/ui/services/quickInput/quickPickers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
SuggestionCmd,
SuggestionServer,
SuggestionSshLogin,
SuggestionDatabase,
AutocompleteResult,
} from './types';

Expand Down Expand Up @@ -238,11 +239,46 @@ export class QuickTshSshPicker implements QuickInputPicker {
}
}

// TODO: Implement the rest of this class.
export class QuickTshProxyDbPicker implements QuickInputPicker {
constructor() {}
private totalDbNameRegex = /^\S+$/i;

constructor(private databasePicker: QuickDatabasePicker) {}

getAutocompleteResult(
rawInput: string,
startIndex: number
): AutocompleteResult {
// We can safely ignore any whitespace at the start. However, `startIndex` needs to account for
// any removed whitespace.
const input = rawInput.trimStart();
if (input === '') {
// input is empty, so rawInput must include only whitespace.
// Add length of the whitespace to startIndex.
startIndex += rawInput.length;
} else {
startIndex += rawInput.indexOf(input);
}

// Show autocomplete only after at least one space after `tsh proxy db`.
if (rawInput !== '' && input === '') {
return {
...this.databasePicker.getAutocompleteResult('', startIndex),
command: { kind: 'command.unknown' },
};
}

const dbNameMatch = input.match(this.totalDbNameRegex);

if (dbNameMatch) {
return {
...this.databasePicker.getAutocompleteResult(
dbNameMatch[0],
startIndex
),
command: { kind: 'command.unknown' },
};
}

getAutocompleteResult(input: string): AutocompleteResult {
return {
kind: 'autocomplete.no-match',
command: { kind: 'command.unknown' },
Expand Down Expand Up @@ -335,3 +371,40 @@ export class QuickServerPicker implements QuickInputPicker {
};
}
}

export class QuickDatabasePicker implements QuickInputPicker {
constructor(
private workspacesService: WorkspacesService,
private clustersService: ClustersService
) {}

private filterDatabases(input: string): SuggestionDatabase[] {
const localClusterUri =
this.workspacesService.getActiveWorkspace()?.localClusterUri;
if (!localClusterUri) {
return [];
}
const databases = this.clustersService.searchDbs(localClusterUri, {
search: input,
});

return databases.map(database => ({
kind: 'suggestion.database' as const,
token: database.name,
data: database,
}));
}

getAutocompleteResult(input: string, startIndex: number): AutocompleteResult {
const suggestions = this.filterDatabases(input);
return {
kind: 'autocomplete.partial-match',
suggestions,
command: { kind: 'command.unknown' },
targetToken: {
startIndex,
value: input,
},
};
}
}
11 changes: 10 additions & 1 deletion packages/teleterm/src/ui/services/quickInput/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,16 @@ export type SuggestionSshLogin = SuggestionBase<

export type SuggestionServer = SuggestionBase<'suggestion.server', tsh.Server>;

export type Suggestion = SuggestionCmd | SuggestionSshLogin | SuggestionServer;
export type SuggestionDatabase = SuggestionBase<
'suggestion.database',
tsh.Database
>;

export type Suggestion =
| SuggestionCmd
| SuggestionSshLogin
| SuggestionServer
| SuggestionDatabase;

export type QuickInputPicker = {
getAutocompleteResult(input: string, startIndex: number): AutocompleteResult;
Expand Down