2062 view edit an api key (#2231)
* Add query to get api keys * Add a link to apiKey detail page * Reset generatedApiKey when leaving page * Simplify stuff * Regenerate key when clicking on button * Simplify * Fix test * Refetch apiKeys when delete or create one * Add test for utils * Create utils function * Enable null expiration dates * Update formatExpiration * Fix display * Fix noteCard * Fix errors * Fix reset * Fix display * Fix renaming * Fix tests * Fix ci * Fix mocked data * Fix test * Update coverage requiremeents * Rename folder * Code review returns * Symplify sht code
This commit is contained in:
@ -5,7 +5,6 @@ import { IconCopy } from '@/ui/display/icon';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { beautifyDateDiff } from '~/utils/date-utils';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
@ -17,22 +16,16 @@ const StyledLinkContainer = styled.div`
|
||||
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
type ApiKeyInputProps = { expiresAt?: string | null; apiKey: string };
|
||||
type ApiKeyInputProps = { apiKey: string };
|
||||
|
||||
export const ApiKeyInput = ({ expiresAt, apiKey }: ApiKeyInputProps) => {
|
||||
export const ApiKeyInput = ({ apiKey }: ApiKeyInputProps) => {
|
||||
const theme = useTheme();
|
||||
const computeInfo = () => {
|
||||
if (!expiresAt) {
|
||||
return '';
|
||||
}
|
||||
return `This key will expire in ${beautifyDateDiff(expiresAt)}`;
|
||||
};
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledLinkContainer>
|
||||
<TextInput info={computeInfo()} value={apiKey} fullWidth />
|
||||
<TextInput value={apiKey} fullWidth />
|
||||
</StyledLinkContainer>
|
||||
<Button
|
||||
Icon={IconCopy}
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { ApiFieldItem } from '@/settings/developers/types/ApiFieldItem';
|
||||
import { IconChevronRight } from '@/ui/display/icon';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
|
||||
import { ApisFiedlItem } from '../types/ApisFieldItem';
|
||||
|
||||
export const StyledApisFieldTableRow = styled(TableRow)`
|
||||
grid-template-columns: 180px 148px 148px 36px;
|
||||
`;
|
||||
@ -27,13 +26,15 @@ const StyledIconChevronRight = styled(IconChevronRight)`
|
||||
|
||||
export const SettingsApiKeysFieldItemTableRow = ({
|
||||
fieldItem,
|
||||
onClick,
|
||||
}: {
|
||||
fieldItem: ApisFiedlItem;
|
||||
fieldItem: ApiFieldItem;
|
||||
onClick: () => void;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledApisFieldTableRow onClick={() => {}}>
|
||||
<StyledApisFieldTableRow onClick={() => onClick()}>
|
||||
<StyledNameTableCell>{fieldItem.name}</StyledNameTableCell>
|
||||
<TableCell color={theme.font.color.tertiary}>Internal</TableCell>{' '}
|
||||
<TableCell
|
||||
|
||||
@ -8,7 +8,6 @@ const meta: Meta<typeof ApiKeyInput> = {
|
||||
component: ApiKeyInput,
|
||||
decorators: [ComponentDecorator],
|
||||
args: {
|
||||
expiresAt: '2123-11-06T23:59:59.825Z',
|
||||
apiKey:
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0d2VudHktN2VkOWQyMTItMWMyNS00ZDAyLWJmMjUtNmFlY2NmN2VhNDE5IiwiaWF0IjoxNjk4MTQyODgyLCJleHAiOjE2OTk0MDE1OTksImp0aSI6ImMyMmFiNjQxLTVhOGYtNGQwMC1iMDkzLTk3MzUwYTM2YzZkOSJ9.JIe2TX5IXrdNl3n-kRFp3jyfNUE7unzXZLAzm2Gxl98',
|
||||
},
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export const ExpirationDates: {
|
||||
value: number;
|
||||
value: number | null;
|
||||
label: string;
|
||||
}[] = [
|
||||
{ label: '15 days', value: 15 },
|
||||
@ -7,5 +7,5 @@ export const ExpirationDates: {
|
||||
{ label: '90 days', value: 90 },
|
||||
{ label: '1 year', value: 365 },
|
||||
{ label: '2 years', value: 2 * 365 },
|
||||
{ label: 'Never', value: 10 * 365 },
|
||||
{ label: 'Never', value: null },
|
||||
];
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ApisFiedlItem } from '../types/ApisFieldItem';
|
||||
|
||||
export const activeApiKeyItems: ApisFiedlItem[] = [
|
||||
{
|
||||
id: v4(),
|
||||
name: 'Zapier key',
|
||||
type: 'internal',
|
||||
expiration: 'In 80 days',
|
||||
},
|
||||
{
|
||||
id: v4(),
|
||||
name: 'Notion',
|
||||
type: 'internal',
|
||||
expiration: 'Expired',
|
||||
},
|
||||
{
|
||||
id: v4(),
|
||||
name: 'Trello',
|
||||
type: 'internal',
|
||||
expiration: 'In 1 year',
|
||||
},
|
||||
{
|
||||
id: v4(),
|
||||
name: 'Cargo',
|
||||
type: 'published',
|
||||
expiration: 'Never',
|
||||
},
|
||||
{
|
||||
id: v4(),
|
||||
name: 'Backoffice',
|
||||
type: 'published',
|
||||
expiration: 'In 32 days',
|
||||
},
|
||||
];
|
||||
@ -6,6 +6,7 @@ export const GET_API_KEY = gql`
|
||||
id
|
||||
name
|
||||
expiresAt
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_API_KEYS = gql`
|
||||
query GetApiKeys {
|
||||
findManyApiKey {
|
||||
id
|
||||
name
|
||||
expiresAt
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,12 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { generatedApiKeyFamilyState } from '@/settings/developers/states/generatedApiKeyFamilyState';
|
||||
|
||||
export const useGeneratedApiKeys = () => {
|
||||
return useRecoilCallback(
|
||||
({ set }) =>
|
||||
(apiKeyId: string, apiKey: string | null) => {
|
||||
set(generatedApiKeyFamilyState(apiKeyId), apiKey);
|
||||
},
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,9 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const generatedApiKeyFamilyState = atomFamily<
|
||||
string | null | undefined,
|
||||
string
|
||||
>({
|
||||
key: 'generatedApiKeyFamilyState',
|
||||
default: null,
|
||||
});
|
||||
@ -1,6 +0,0 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const generatedApiKeyState = atom<string | null | undefined>({
|
||||
key: 'generatedApiKeyState',
|
||||
default: null,
|
||||
});
|
||||
@ -1,4 +1,4 @@
|
||||
export type ApisFiedlItem = {
|
||||
export type ApiFieldItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'internal' | 'published';
|
||||
@ -0,0 +1,23 @@
|
||||
import { computeNewExpirationDate } from '@/settings/developers/utils/compute-new-expiration-date';
|
||||
|
||||
jest.useFakeTimers().setSystemTime(new Date('2024-01-01T00:00:00.000Z'));
|
||||
|
||||
describe('computeNewExpirationDate', () => {
|
||||
it('should compute properly', () => {
|
||||
const expiresAt = '2023-01-10T00:00:00.000Z';
|
||||
const createdAt = '2023-01-01T00:00:00.000Z';
|
||||
const result = computeNewExpirationDate(expiresAt, createdAt);
|
||||
expect(result).toEqual('2024-01-10T00:00:00.000Z');
|
||||
});
|
||||
it('should compute properly with same values', () => {
|
||||
const expiresAt = '2023-01-01T10:00:00.000Z';
|
||||
const createdAt = '2023-01-01T10:00:00.000Z';
|
||||
const result = computeNewExpirationDate(expiresAt, createdAt);
|
||||
expect(result).toEqual('2024-01-01T00:00:00.000Z');
|
||||
});
|
||||
it('should compute properly with no expiration', () => {
|
||||
const createdAt = '2023-01-01T10:00:00.000Z';
|
||||
const result = computeNewExpirationDate(undefined, createdAt);
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,34 @@
|
||||
import { formatExpiration } from '@/settings/developers/utils/format-expiration';
|
||||
|
||||
jest.useFakeTimers().setSystemTime(new Date('2024-01-01T00:00:00.000Z'));
|
||||
|
||||
describe('formatExpiration', () => {
|
||||
it('should format properly when expiresAt is null', () => {
|
||||
const expiresAt = null;
|
||||
const result = formatExpiration(expiresAt);
|
||||
expect(result).toEqual('Never');
|
||||
const resultWithExpiresMention = formatExpiration(expiresAt, true);
|
||||
expect(resultWithExpiresMention).toEqual('Never expires');
|
||||
});
|
||||
it('should format properly when expiresAt is not null', () => {
|
||||
const expiresAt = '2024-01-10T00:00:00.000Z';
|
||||
const result = formatExpiration(expiresAt);
|
||||
expect(result).toEqual('In 9 days');
|
||||
const resultWithExpiresMention = formatExpiration(expiresAt, true);
|
||||
expect(resultWithExpiresMention).toEqual('Expires in 9 days');
|
||||
});
|
||||
it('should format properly when expiresAt is large', () => {
|
||||
const expiresAt = '2034-01-10T00:00:00.000Z';
|
||||
const result = formatExpiration(expiresAt);
|
||||
expect(result).toEqual('In 10 years');
|
||||
const resultWithExpiresMention = formatExpiration(expiresAt, true);
|
||||
expect(resultWithExpiresMention).toEqual('Expires in 10 years');
|
||||
});
|
||||
it('should format properly when expiresAt is large and long version', () => {
|
||||
const expiresAt = '2034-01-10T00:00:00.000Z';
|
||||
const result = formatExpiration(expiresAt, undefined, false);
|
||||
expect(result).toEqual('In 10 years and 9 days');
|
||||
const resultWithExpiresMention = formatExpiration(expiresAt, true, false);
|
||||
expect(resultWithExpiresMention).toEqual('Expires in 10 years and 9 days');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,14 @@
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export const computeNewExpirationDate = (
|
||||
expiresAt: string | null | undefined,
|
||||
createdAt: string,
|
||||
): string | null => {
|
||||
if (!expiresAt) {
|
||||
return null;
|
||||
}
|
||||
const days = DateTime.fromISO(expiresAt).diff(DateTime.fromISO(createdAt), [
|
||||
'days',
|
||||
]).days;
|
||||
return DateTime.utc().plus({ days }).toISO();
|
||||
};
|
||||
@ -0,0 +1,31 @@
|
||||
import { ApiFieldItem } from '@/settings/developers/types/ApiFieldItem';
|
||||
import { GetApiKeysQuery } from '~/generated/graphql';
|
||||
import { beautifyDateDiff } from '~/utils/date-utils';
|
||||
|
||||
export const formatExpiration = (
|
||||
expiresAt: string | null,
|
||||
withExpiresMention: boolean = false,
|
||||
short: boolean = true,
|
||||
) => {
|
||||
if (expiresAt) {
|
||||
const dateDiff = beautifyDateDiff(expiresAt, undefined, short);
|
||||
if (dateDiff.includes('-')) {
|
||||
return 'Expired';
|
||||
}
|
||||
return withExpiresMention ? `Expires in ${dateDiff}` : `In ${dateDiff}`;
|
||||
}
|
||||
return withExpiresMention ? 'Never expires' : 'Never';
|
||||
};
|
||||
|
||||
export const formatExpirations = (
|
||||
apiKeysQuery: GetApiKeysQuery,
|
||||
): ApiFieldItem[] => {
|
||||
return apiKeysQuery.findManyApiKey.map(({ id, name, expiresAt }) => {
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
expiration: formatExpiration(expiresAt || null),
|
||||
type: 'internal',
|
||||
};
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user