datahub/datahub-web-react/src/app/settingsV2/CreateTokenModal.tsx

225 lines
8.7 KiB
TypeScript

import { red } from '@ant-design/colors';
import { Form, Input, Modal, Select, Typography, message } from 'antd';
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import analytics, { EventType } from '@app/analytics';
import { AccessTokenModal } from '@app/settingsV2/AccessTokenModal';
import { ACCESS_TOKEN_DURATIONS, getTokenExpireDate } from '@app/settingsV2/utils';
import { ModalButtonContainer } from '@app/shared/button/styledComponents';
import { useEnterKeyListener } from '@app/shared/useEnterKeyListener';
import { Button } from '@src/alchemy-components';
import { useCreateAccessTokenMutation } from '@graphql/auth.generated';
import { AccessTokenDuration, AccessTokenType, CreateAccessTokenInput } from '@types';
type Props = {
currentUserUrn: string;
visible: boolean;
forRemoteExecutor?: boolean;
onClose: () => void;
onCreateToken: () => void;
};
type FormProps = {
name: string;
description?: string;
duration: AccessTokenDuration;
};
const ExpirationSelectContainer = styled.div`
padding: 1px;
`;
const ExpirationDurationSelect = styled(Select)`
&& {
width: 100%;
margin-top: 1em;
margin-bottom: 1em;
}
`;
const OptionText = styled.span<{ isRed: boolean }>`
${(props) => props.isRed && `color: ${red[5]}`}
`;
export default function CreateTokenModal({
currentUserUrn,
forRemoteExecutor,
visible,
onClose,
onCreateToken,
}: Props) {
const [selectedTokenDuration, setSelectedTokenDuration] = useState<AccessTokenDuration | null>(null);
const [showModal, setShowModal] = useState(false);
const [createButtonEnabled, setCreateButtonEnabled] = useState(true);
const [createAccessToken, { data }] = useCreateAccessTokenMutation();
const [form] = Form.useForm<FormProps>();
// For remote executors they default to never
useEffect(() => {
if (forRemoteExecutor) {
form.setFieldValue('duration', AccessTokenDuration.NoExpiry);
}
}, [forRemoteExecutor, form]);
// Check and show the modal once the data for createAccessToken will generate
useEffect(() => {
if (data && data.createAccessToken?.accessToken) {
setShowModal(true);
}
}, [data, setShowModal]);
// Function to handle the close or cross button of Access Token Modal
const onDetailModalClose = () => {
setSelectedTokenDuration(null);
setShowModal(false);
onClose();
};
// Function to handle the close or cross button of Create Token Modal
const onModalClose = () => {
form.resetFields();
onClose();
};
const onCreateNewToken = () => {
const { duration, name, description } = form.getFieldsValue();
const input: CreateAccessTokenInput = {
actorUrn: currentUserUrn,
type: AccessTokenType.Personal,
duration,
name,
description,
};
createAccessToken({ variables: { input } })
.then(({ errors }) => {
if (!errors) {
setSelectedTokenDuration(duration);
analytics.event({
type: EventType.CreateAccessTokenEvent,
accessTokenType: AccessTokenType.Personal,
duration,
});
}
})
.catch((e) => {
message.destroy();
message.error({ content: `Failed to create Token!: \n ${e.message || ''}`, duration: 3 });
})
.finally(() => {
onCreateToken();
});
onModalClose();
};
const accessToken = data && data.createAccessToken?.accessToken;
const selectedExpiresInText = selectedTokenDuration && getTokenExpireDate(selectedTokenDuration);
// Handle the Enter press
useEnterKeyListener({
querySelectorToExecuteClick: '#createTokenButton',
});
const hasSelectedNoExpiration = selectedTokenDuration === AccessTokenDuration.NoExpiry;
return (
<>
<Modal
title={forRemoteExecutor ? 'Create new Token for Remote Executor' : 'Create new Token'}
visible={visible}
onCancel={onModalClose}
footer={
<ModalButtonContainer>
<Button
onClick={onModalClose}
variant="text"
color="gray"
data-testid="cancel-create-access-token-button"
>
Cancel
</Button>
<Button
id="createTokenButton"
onClick={onCreateNewToken}
disabled={createButtonEnabled}
data-testid="create-access-token-button"
>
Create
</Button>
</ModalButtonContainer>
}
>
<Form
form={form}
initialValues={{ duration: ACCESS_TOKEN_DURATIONS[2].duration }}
layout="vertical"
onFieldsChange={() =>
setCreateButtonEnabled(form.getFieldsError().some((field) => field.errors.length > 0))
}
>
<Form.Item label={<Typography.Text strong>Name</Typography.Text>}>
<Typography.Paragraph>Give your new token a name. </Typography.Paragraph>
<Form.Item
name="name"
rules={[
{
required: true,
message: 'Enter a token name.',
},
{ whitespace: true },
{ min: 1, max: 50 },
]}
hasFeedback
>
<Input placeholder="A name for your token" data-testid="create-access-token-name" />
</Form.Item>
</Form.Item>
<Form.Item label={<Typography.Text strong>Description</Typography.Text>}>
<Typography.Paragraph>An optional description for your new token.</Typography.Paragraph>
<Form.Item name="description" rules={[{ whitespace: true }, { min: 1, max: 500 }]} hasFeedback>
<Input
placeholder="A description for your token"
data-testid="create-access-token-description"
/>
</Form.Item>
</Form.Item>
<ExpirationSelectContainer>
<Typography.Text strong>Expires in</Typography.Text>
<Form.Item name="duration" data-testid="create-access-token-duration" noStyle>
<ExpirationDurationSelect disabled={forRemoteExecutor}>
{ACCESS_TOKEN_DURATIONS.map((duration) => (
<Select.Option key={duration.text} value={duration.duration}>
<OptionText isRed={duration.duration === AccessTokenDuration.NoExpiry}>
{duration.text}
</OptionText>
</Select.Option>
))}
</ExpirationDurationSelect>
</Form.Item>
<Form.Item shouldUpdate={(prev, cur) => prev.duration !== cur.duration} noStyle>
{({ getFieldValue }) => (
<Typography.Text
type="secondary"
style={hasSelectedNoExpiration ? { color: `${red[5]}` } : {}}
>
{getFieldValue('duration') && getTokenExpireDate(getFieldValue('duration'))}
</Typography.Text>
)}
</Form.Item>
</ExpirationSelectContainer>
</Form>
</Modal>
<AccessTokenModal
visible={showModal}
onClose={onDetailModalClose}
accessToken={accessToken || ''}
expiresInText={selectedExpiresInText || ''}
/>
</>
);
}