Skip to content
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,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React from 'react';
import React, { useEffect, useState } from 'react';
import { MemoryRouter } from 'react-router';
import { rest } from 'msw';
import { mswLoader } from 'msw-storybook-addon';
Expand All @@ -27,11 +27,26 @@ import { ResourceKind } from 'teleport/Discover/Shared';
import { PingTeleportProvider } from 'teleport/Discover/Shared/PingTeleportContext';
import { ContextProvider } from 'teleport';

import {
INTERNAL_RESOURCE_ID_LABEL_KEY,
JoinToken,
} from 'teleport/services/joinToken';
import { clearCachedJoinTokenResult } from 'teleport/Discover/Shared/useJoinTokenSuspender';
import {
DiscoverContextState,
DiscoverProvider,
} from 'teleport/Discover/useDiscover';
import {
IntegrationKind,
IntegrationStatusCode,
} from 'teleport/services/integrations';
import { DiscoverEventResource } from 'teleport/services/userEvent';

import { generateCmd } from 'teleport/Discover/Kubernetes/HelmChart/HelmChart';

import { ManualHelmDialog } from './ManualHelmDialog';
import { AgentWaitingDialog } from './AgentWaitingDialog';
import { EnrollmentDialog } from './EnrollmentDialog';
import { AgentWaitingDialog } from './AgentWaitingDialog';
import { ManualHelmDialog } from './ManualHelmDialog';

export default {
title: 'Teleport/Discover/Kube/EnrollEksClusters/Dialogs',
Expand Down Expand Up @@ -110,7 +125,7 @@ AgentWaitingDialogSuccess.parameters = {
},
};

const helmCommand = generateCmd({
const helmCommandProps = {
namespace: 'teleport-agent',
clusterName: 'EKS1',
proxyAddr: 'teleport-proxy.example.com:1234',
Expand All @@ -126,15 +141,98 @@ const helmCommand = generateCmd({
{ name: 'region', value: 'us-east-1' },
{ name: 'account-id', value: '1234567789012' },
],
});
};

export const ManualHelmDialogStory = () => (
<MemoryRouter initialEntries={[{ state: { discover: {} } }]}>
<ManualHelmDialog
command={helmCommand}
confirmedCommands={() => {}}
cancel={() => {}}
/>
</MemoryRouter>
);
export const ManualHelmDialogStory = () => {
const discoverCtx: DiscoverContextState = {
agentMeta: {
resourceName: 'kube-name',
agentMatcherLabels: [],
kube: {
kind: 'kube_cluster',
name: '',
labels: [],
},
awsIntegration: {
kind: IntegrationKind.AwsOidc,
name: 'test-oidc',
resourceType: 'integration',
spec: {
roleArn: 'arn:aws:iam::123456789012:role/test-role-arn',
},
statusCode: IntegrationStatusCode.Running,
},
},
currentStep: 0,
nextStep: () => null,
prevStep: () => null,
onSelectResource: () => null,
resourceSpec: {
name: 'Eks',
kind: ResourceKind.Kubernetes,
icon: 'Eks',
keywords: '',
event: DiscoverEventResource.KubernetesEks,
},
exitFlow: () => null,
viewConfig: null,
indexedViews: [],
setResourceSpec: () => null,
updateAgentMeta: () => null,
emitErrorEvent: () => null,
emitEvent: () => null,
eventState: null,
};

useEffect(() => {
return () => {
clearCachedJoinTokenResult([
ResourceKind.Kubernetes,
ResourceKind.Application,
ResourceKind.Discovery,
]);
};
}, []);

const [, setToken] = useState<JoinToken>();

return (
<MemoryRouter
initialEntries={[
{ pathname: cfg.routes.discover, state: { entity: 'eks' } },
]}
>
<ContextProvider ctx={createTeleportContext()}>
<DiscoverProvider mockCtx={discoverCtx}>
<ManualHelmDialog
setJoinTokenAndGetCommand={token => {
// Emulate real usage of ManualHelmDialog where setJoinTokenAndGetCommand updates the
// state of a parent.
setToken(token);
return generateCmd(helmCommandProps);
}}
confirmedCommands={() => {}}
cancel={() => {}}
/>
</DiscoverProvider>
</ContextProvider>
</MemoryRouter>
);
};
ManualHelmDialogStory.storyName = 'ManualHelmDialog';
ManualHelmDialogStory.parameters = {
msw: {
handlers: [
rest.post(cfg.api.joinTokenPath, (req, res, ctx) => {
return res(
ctx.json({
id: 'token-id',
suggestedLabels: [
{ name: INTERNAL_RESOURCE_ID_LABEL_KEY, value: 'resource-id' },
],
})
);
}),
],
},
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I open the story for EnrollEksCluster and try to open ManualHelmDialog, I get "A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition."

I wanted to use the story to see how setJoinToken in ManualHelmDialog interacts with re-rendering EnrollEksCluster since I don't have a cluster with an AWS account set up to test this in a real UI.

storybook-error.mov

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added container to the dialog 52f6cf7

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React, { useState } from 'react';
import React, { useState, useCallback } from 'react';
import { Box, ButtonSecondary, ButtonText, Text, Toggle } from 'design';
import styled from 'styled-components';
import { FetchStatus } from 'design/DataTable/types';
Expand All @@ -39,14 +39,15 @@ import { isIamPermError } from 'teleport/Discover/Shared/Aws/error';
import { AgentStepProps } from 'teleport/Discover/types';
import useTeleport from 'teleport/useTeleport';

import { useJoinTokenSuspender } from 'teleport/Discover/Shared/useJoinTokenSuspender';
import { generateCmd } from 'teleport/Discover/Kubernetes/HelmChart/HelmChart';
import { Kube } from 'teleport/services/kube';

import { Header, ResourceKind } from '../../Shared';
import { JoinToken } from 'teleport/services/joinToken';

import { Header } from '../../Shared';

import { ClustersList } from './EksClustersList';
import { ManualHelmDialog } from './ManualHelmDialog';
import ManualHelmDialog from './ManualHelmDialog';
import { EnrollmentDialog } from './EnrollmentDialog';
import { AgentWaitingDialog } from './AgentWaitingDialog';

Expand Down Expand Up @@ -97,14 +98,11 @@ export function EnrollEksCluster(props: AgentStepProps) {
useState(false);
const [isManualHelmDialogShown, setIsManualHelmDialogShown] = useState(false);
const [waitingResourceId, setWaitingResourceId] = useState('');
// join token will be set only if user opens ManualHelmDialog,
// we delay it to avoid premature admin action MFA confirmation request.
const [joinToken, setJoinToken] = useState<JoinToken>(null);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's document the fact that we need a separate state for the join token because while the join token is used mostly by code in EnrollEksCluster, the join token needs to be generated only after ManualHelmDialog gets open.

If I saw this code without any context, my inclination would be to move useJoinTokenSuspender to EnrollEksCluster and pass the token down to ManualHelmDialog, because that's common practice in React – if you want to share a piece of state, you move it up. Here it wouldn't work of course because of the constraints you talk about in the PR description.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comment 👍 55f8214

const ctx = useTeleport();

const { joinToken } = useJoinTokenSuspender([
ResourceKind.Kubernetes,
ResourceKind.Application,
ResourceKind.Discovery,
]);

function fetchClustersWithNewRegion(region: Regions) {
setSelectedRegion(region);
// Clear table when fetching with new region.
Expand Down Expand Up @@ -205,8 +203,6 @@ export function EnrollEksCluster(props: AgentStepProps) {
{
region: selectedRegion,
enableAppDiscovery: isAppDiscoveryEnabled,
joinToken: joinToken.id,
resourceId: joinToken.internalResourceId,
Comment on lines -208 to -209
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did we remove it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not used anymore by the backend - we now create our own token in the backend enrollment code and return resourceId that frontend should look for while pinging resource.

clusterNames: [selectedCluster.name],
}
);
Expand Down Expand Up @@ -262,23 +258,34 @@ export function EnrollEksCluster(props: AgentStepProps) {
!selectedCluster ||
enrollmentState.status !== 'notStarted';

let command = '';
if (selectedCluster) {
command = generateCmd({
namespace: 'teleport-agent',
clusterName: selectedCluster.name,
proxyAddr: ctx.storeUser.state.cluster.publicURL,
tokenId: joinToken.id,
clusterVersion: ctx.storeUser.state.cluster.authVersion,
resourceId: joinToken.internalResourceId,
isEnterprise: ctx.isEnterprise,
isCloud: ctx.isCloud,
automaticUpgradesEnabled: ctx.automaticUpgradesEnabled,
automaticUpgradesTargetVersion: ctx.automaticUpgradesTargetVersion,
joinLabels: [...selectedCluster.labels, ...selectedCluster.joinLabels],
disableAppDiscovery: !isAppDiscoveryEnabled,
});
}
const setJoinTokenAndGetCommand = useCallback(
(token: JoinToken) => {
setJoinToken(token);
return generateCmd({
namespace: 'teleport-agent',
clusterName: selectedCluster.name,
proxyAddr: ctx.storeUser.state.cluster.publicURL,
clusterVersion: ctx.storeUser.state.cluster.authVersion,
tokenId: token.id,
resourceId: token.internalResourceId,
isEnterprise: ctx.isEnterprise,
isCloud: ctx.isCloud,
automaticUpgradesEnabled: ctx.automaticUpgradesEnabled,
automaticUpgradesTargetVersion: ctx.automaticUpgradesTargetVersion,
joinLabels: [...selectedCluster.labels, ...selectedCluster.joinLabels],
disableAppDiscovery: !isAppDiscoveryEnabled,
});
},
[
ctx.automaticUpgradesEnabled,
ctx.automaticUpgradesTargetVersion,
ctx.isCloud,
ctx.isEnterprise,
ctx.storeUser.state.cluster,
isAppDiscoveryEnabled,
selectedCluster,
]
);

return (
<Box maxWidth="1000px">
Expand Down Expand Up @@ -366,7 +373,7 @@ export function EnrollEksCluster(props: AgentStepProps) {
)}
{isManualHelmDialogShown && (
<ManualHelmDialog
command={command}
setJoinTokenAndGetCommand={setJoinTokenAndGetCommand}
cancel={() => setIsManualHelmDialogShown(false)}
confirmedCommands={() => {
setEnrollmentState({ status: 'awaitingAgent' });
Expand All @@ -377,7 +384,7 @@ export function EnrollEksCluster(props: AgentStepProps) {
)}
{isAgentWaitingDialogShown && (
<AgentWaitingDialog
joinResourceId={waitingResourceId || joinToken.internalResourceId}
joinResourceId={waitingResourceId || joinToken?.internalResourceId}
status={enrollmentState.status}
clusterName={selectedCluster.name}
updateWaitingResult={(result: Kube) => {
Expand Down
Loading