Skip to content

Commit

Permalink
[v10] Add k8s to recordings and active sessions list (#972) (#974)
Browse files Browse the repository at this point in the history
* Refactored design for active sessions list to accommodate
  different resource language
* Added altSortKey for table that allows you to define
  an alternate field to sort by
* Refactored makeSession and add k8s types
* Addded k8s type to Player
* Renamed existing session.ts to websession.ts and
   ssh.ts to session.ts

* Updated e-ref
  • Loading branch information
kimlisa authored Jul 13, 2022
1 parent 5116eb1 commit c77d16d
Show file tree
Hide file tree
Showing 39 changed files with 943 additions and 623 deletions.
137 changes: 137 additions & 0 deletions web/packages/design/src/DataTable/Table.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,140 @@ test('respects emptyText prop', () => {

expect(target.textContent).toEqual(targetText);
});

describe('sorting by field defined in key and altSortKey', () => {
const sample = [
{
hostname: 'host-a',
created: new Date('2022-07-15T15:34:33.256697813Z'),
durationText: '1 hour',
},
{
hostname: 'host-b',
created: new Date('2022-07-05T15:34:33.256697813Z'),
durationText: '1 second',
},
{
hostname: 'host-c',
created: new Date('2022-07-10T15:34:33.256697813Z'),
durationText: '1 minute',
},
];

// Sorted by string ASC.
const expectedSortedByKey = ['1 hour', '1 minute', '1 second'];
// Sorted by Date ASC.
const expectedSortedByAltKey = ['1 second', '1 minute', '1 hour'];

test('sort by key', () => {
const { container } = render(
<Table
data={sample}
columns={[
{
key: 'durationText',
headerText: 'duration',
isSortable: true,
},
]}
emptyText=""
/>
);

const cols = container.querySelectorAll('tbody > tr > td');
expect(cols).toHaveLength(sample.length);

const vals = [];
cols.forEach(c => vals.push(c.textContent));
expect(vals).toStrictEqual(expectedSortedByKey);
});

test('sort by key with initialSort', () => {
const { container } = render(
<Table
data={sample}
columns={[
// first column
{
key: 'hostname',
headerText: 'hostname',
isSortable: true,
},
// second column
{
key: 'durationText',
headerText: 'duration',
isSortable: true,
},
]}
emptyText=""
initialSort={{ key: 'durationText', dir: 'ASC' }}
/>
);

const cols = container.querySelectorAll('tbody > tr > td');
const vals = [];
// field durationText starts in the second column,
// which is every odd number per row.
cols.forEach((c, i) => i % 2 != 0 && vals.push(c.textContent));
expect(vals).toHaveLength(sample.length);
expect(vals).toStrictEqual(expectedSortedByKey);
});

test('sort by altSortKey', () => {
const { container } = render(
<Table
data={sample}
columns={[
{
key: 'durationText',
altSortKey: 'created',
headerText: 'duration',
isSortable: true,
},
]}
emptyText=""
/>
);

const cols = container.querySelectorAll('tbody > tr > td');
expect(cols).toHaveLength(sample.length);

const vals = [];
cols.forEach(c => vals.push(c.textContent));
expect(vals).toStrictEqual(expectedSortedByAltKey);
});

test('sort by altSortKey with initialSort', () => {
const { container } = render(
<Table
data={sample}
columns={[
// first column
{
key: 'hostname',
headerText: 'hostname',
isSortable: true,
},
// second column
{
key: 'durationText',
altSortKey: 'created',
headerText: 'duration',
isSortable: true,
},
]}
emptyText=""
initialSort={{ altSortKey: 'created', dir: 'ASC' }}
/>
);

const cols = container.querySelectorAll('tbody > tr > td');
const vals = [];
// field durationText starts in the second column,
// which is every odd number per row.
cols.forEach((c, i) => i % 2 != 0 && vals.push(c.textContent));
expect(vals).toHaveLength(sample.length);
expect(vals).toStrictEqual(expectedSortedByAltKey);
});
});
6 changes: 5 additions & 1 deletion web/packages/design/src/DataTable/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ export function Table<T>({
if (customSort) {
dir = customSort.fieldName == column.key ? customSort.dir : null;
} else {
dir = state.sort?.key === column.key ? state.sort?.dir : null;
dir =
state.sort?.key === column.key ||
state.sort?.key === column.altSortKey
? state.sort?.dir
: null;
}

const $cell = column.isSortable ? (
Expand Down
18 changes: 16 additions & 2 deletions web/packages/design/src/DataTable/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,32 @@ export type ServersideProps = {
// Makes it so either key or altKey is required
type TableColumnWithKey<T> = TableColumnBase<T> & {
key: Extract<keyof T, string>;
// altSortKey is the alternative field to sort column by,
// if provided. Otherwise it falls back to sorting by field
// "key".
altSortKey?: Extract<keyof T, string>;
altKey?: never;
};

type TableColumnWithAltKey<T> = TableColumnBase<T> & {
altKey: string;
key?: never;
altSortKey?: never;
};

// InitialSort defines the field (table column) that should be initiallly
// sorted on render. If not provided, it defaults to finding the first
// sortable column.

// Either "key" or "altSortKey" can be provided
// but not both. If "altSortKey" is provided, than that TableColumn
// should also define "altSortKey" (TableColumnWithAltKey).
type InitialSort<T> = {
key: Extract<keyof T, string>;
dir: SortDir;
};
} & (
| { key: Extract<keyof T, string>; altSortKey?: never }
| { altSortKey: Extract<keyof T, string>; key?: never }
);

export type SortType = {
fieldName: string;
Expand Down
14 changes: 9 additions & 5 deletions web/packages/design/src/DataTable/useTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,21 @@ export default function useTable<T>({
// Finds the first sortable column to use for the initial sorting
let col: TableColumn<T>;
if (!customSort) {
col = props.initialSort
? columns.find(column => column.key === props.initialSort.key)
: columns.find(column => column.isSortable);
if (props.initialSort) {
col = props.initialSort.altSortKey
? columns.find(col => col.altSortKey === props.initialSort.altSortKey)
: columns.find(col => col.key === props.initialSort.key);
} else {
col = columns.find(column => column.isSortable);
}
}

return {
data: serversideProps || disableFilter ? data : [],
searchValue: '',
sort: col
? {
key: col.key as string,
key: (col.altSortKey || col.key) as string,
onSort: col.onSort,
dir: props.initialSort?.dir || 'ASC',
}
Expand Down Expand Up @@ -108,7 +112,7 @@ export default function useTable<T>({

updateData(
{
key: column.key,
key: column.altSortKey || column.key,
onSort: column.onSort,
dir: state.sort?.dir === 'ASC' ? 'DESC' : 'ASC',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ limitations under the License.
*/

import React from 'react';
import type { Session } from 'teleport/services/session';
import DocumentSsh from './DocumentSsh';
import { TestLayout } from './../Console.story';
import ConsoleCtx from './../consoleContext';
Expand Down Expand Up @@ -86,14 +87,15 @@ const doc = {
created: new Date(),
} as const;

const session = {
const session: Session = {
kind: 'ssh',
login: '123',
sid: '',
namespace: '',
created: new Date(),
durationText: '',
serverId: '',
hostname: '',
resourceName: '',
clusterId: '',
parties: [],
addr: '1.1.1.1:1111',
Expand Down
12 changes: 8 additions & 4 deletions web/packages/teleport/src/Console/DocumentSsh/useSshSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ limitations under the License.

import React from 'react';
import cfg from 'teleport/config';
import { Session } from 'teleport/services/ssh';
import { Session } from 'teleport/services/session';
import { TermEventEnum } from 'teleport/lib/term/enums';
import Tty from 'teleport/lib/term/tty';
import ConsoleContext from 'teleport/Console/consoleContext';
Expand Down Expand Up @@ -98,13 +98,17 @@ function handleTtyConnect(
session: Session,
docId: number
) {
const { hostname, login, sid, clusterId } = session;
const { resourceName, login, sid, clusterId, serverId, created } = session;
const url = cfg.getSshSessionRoute({ sid, clusterId });
ctx.updateSshDocument(docId, {
title: `${login}@${hostname}`,
title: `${login}@${resourceName}`,
status: 'connected',
url,
...session,
serverId,
created,
login,
sid,
clusterId,
});

ctx.gotoTab({ url });
Expand Down
15 changes: 9 additions & 6 deletions web/packages/teleport/src/Console/consoleContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ limitations under the License.

import { StoreParties, StoreDocs, DocumentSsh, Document } from './stores';
import Logger from 'shared/libs/logger';
import session from 'teleport/services/session';
import webSession from 'teleport/services/websession';
import history from 'teleport/services/history';
import cfg, { UrlResourcesParams, UrlSshParams } from 'teleport/config';
import { getAccessToken, getHostName } from 'teleport/services/api';
import Tty from 'teleport/lib/term/tty';
import TtyAddressResolver from 'teleport/lib/term/ttyAddressResolver';
import serviceSsh, { Session, ParticipantList } from 'teleport/services/ssh';
import serviceSession, {
Session,
ParticipantList,
} from 'teleport/services/session';
import serviceNodes from 'teleport/services/nodes';
import serviceClusters from 'teleport/services/clusters';

Expand Down Expand Up @@ -122,7 +125,7 @@ export default class ConsoleContext {
const requests = unique.map(clusterId =>
// Fetch parties for a given cluster and in case of an error
// return an empty object.
serviceSsh.fetchParticipants({ clusterId }).catch(err => {
serviceSession.fetchParticipants({ clusterId }).catch(err => {
logger.error('failed to refresh participants', err);
const emptyResults: ParticipantList = {};
return emptyResults;
Expand Down Expand Up @@ -154,19 +157,19 @@ export default class ConsoleContext {
}

fetchSshSession(clusterId: string, sid: string) {
return serviceSsh.fetchSession({ clusterId, sid });
return serviceSession.fetchSession({ clusterId, sid });
}

createSshSession(clusterId: string, serverId: string, login: string) {
return serviceSsh.create({
return serviceSession.createSession({
serverId,
clusterId,
login,
});
}

logout() {
session.logout();
webSession.logout();
}

createTty(session: Session): Tty {
Expand Down
2 changes: 1 addition & 1 deletion web/packages/teleport/src/Console/stores/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { Participant } from 'teleport/services/ssh';
import { Participant } from 'teleport/services/session';

interface DocumentBase {
id?: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import renderHook from 'design/utils/renderHook';
import ConsoleContext from './consoleContext';
import useOnExitConfirmation from './useOnExitConfirmation';
import session from 'teleport/services/session';
import session from 'teleport/services/websession';

test('confirmation dialog before terminating an active ssh session', () => {
const ctx = new ConsoleContext();
Expand Down
2 changes: 1 addition & 1 deletion web/packages/teleport/src/Console/useOnExitConfirmation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import React from 'react';
import ConsoleContext from './consoleContext';
import * as stores from './stores/types';
import session from 'teleport/services/session';
import session from 'teleport/services/websession';

// TAB_MIN_AGE defines "active terminal" session in ms
const TAB_MIN_AGE = 30000;
Expand Down
14 changes: 7 additions & 7 deletions web/packages/teleport/src/Player/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import Tabs, { TabItem } from './PlayerTabs';
import SshPlayer from './SshPlayer';
import { DesktopPlayer } from './DesktopPlayer';
import ActionBar from './ActionBar';
import session from 'teleport/services/session';
import session from 'teleport/services/websession';
import { colors } from 'teleport/Console/colors';
import { UrlPlayerParams } from 'teleport/config';
import { getUrlParameter } from 'teleport/services/history';
Expand All @@ -40,7 +40,9 @@ export default function Player() {
const durationMs = Number(getUrlParameter('durationMs', search));

const validRecordingType =
recordingType === 'ssh' || recordingType === 'desktop';
recordingType === 'ssh' ||
recordingType === 'k8s' ||
recordingType === 'desktop';
const validDurationMs = Number.isInteger(durationMs) && durationMs > 0;

document.title = `${clusterId} • Play ${sid}`;
Expand Down Expand Up @@ -91,16 +93,14 @@ export default function Player() {
position: 'relative',
}}
>
{recordingType === 'ssh' && (
<SshPlayer sid={sid} clusterId={clusterId} />
)}

{recordingType === 'desktop' && (
{recordingType === 'desktop' ? (
<DesktopPlayer
sid={sid}
clusterId={clusterId}
durationMs={durationMs}
/>
) : (
<SshPlayer sid={sid} clusterId={clusterId} />
)}
</Flex>
</StyledPlayer>
Expand Down
Loading

0 comments on commit c77d16d

Please sign in to comment.