Enable roles on api keys (#13334)

This commit is contained in:
nitin
2025-08-03 03:24:10 +05:30
committed by GitHub
parent 2bda929984
commit 4cd2a87833
143 changed files with 3355 additions and 671 deletions
@@ -4,66 +4,80 @@ import styled from '@emotion/styled';
import { formatExpiration } from '@/settings/developers/utils/formatExpiration';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import {
IconChevronRight,
OverflowingTextWithTooltip,
} from 'twenty-ui/display';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { IconChevronRight } from 'twenty-ui/display';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { ApiKey } from '~/generated-metadata/graphql';
import { ApiKey, FeatureFlagKey } from '~/generated-metadata/graphql';
export const StyledApisFieldTableRow = styled(TableRow)`
grid-template-columns: 312px auto 28px;
@media (max-width: ${MOBILE_VIEWPORT}px) {
width: 100%;
grid-template-columns: 12fr 4fr;
}
`;
const StyledNameTableCell = styled(TableCell)`
color: ${({ theme }) => theme.font.color.primary};
gap: ${({ theme }) => theme.spacing(2)};
const StyledTruncatedCell = styled(TableCell)`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
`;
const StyledIconTableCell = styled(TableCell)`
justify-content: center;
padding-right: ${({ theme }) => theme.spacing(1)};
padding-left: 0;
const StyledEllipsisLabel = styled.div`
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
`;
const StyledIconChevronRight = styled(IconChevronRight)`
color: ${({ theme }) => theme.font.color.tertiary};
`;
type ApiKeyType = Pick<ApiKey, 'id' | 'name' | 'expiresAt' | 'revokedAt'> & {
role?: { id: string; label: string; icon?: string | null } | null;
};
type SettingsApiKeysFieldItemTableRowProps = {
apiKey: ApiKeyType;
to: string;
};
export const SettingsApiKeysFieldItemTableRow = ({
apiKey,
to,
}: {
apiKey: Pick<ApiKey, 'id' | 'name' | 'expiresAt' | 'revokedAt'>;
to: string;
}) => {
}: SettingsApiKeysFieldItemTableRowProps) => {
const theme = useTheme();
const formattedExpiration = formatExpiration(apiKey.expiresAt || null);
const isApiKeyRolesEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_API_KEY_ROLES_ENABLED,
);
const gridColumns = isApiKeyRolesEnabled ? '5fr 2fr 3fr 1fr' : '5fr 3fr 1fr';
return (
<StyledApisFieldTableRow to={to}>
<StyledNameTableCell>
<OverflowingTextWithTooltip text={apiKey.name} />
</StyledNameTableCell>
<TableCell
<StyledApisFieldTableRow gridAutoColumns={gridColumns} to={to}>
<StyledTruncatedCell color={theme.font.color.primary}>
<StyledEllipsisLabel>{apiKey.name}</StyledEllipsisLabel>
</StyledTruncatedCell>
{isApiKeyRolesEnabled && (
<StyledTruncatedCell color={theme.font.color.tertiary}>
<StyledEllipsisLabel>{apiKey.role?.label || '-'}</StyledEllipsisLabel>
</StyledTruncatedCell>
)}
<StyledTruncatedCell
color={
formattedExpiration === 'Expired'
? theme.font.color.danger
: theme.font.color.tertiary
}
>
{formattedExpiration}
</TableCell>
<StyledIconTableCell>
<StyledIconChevronRight
<StyledEllipsisLabel>{formattedExpiration}</StyledEllipsisLabel>
</StyledTruncatedCell>
<TableCell align="right">
<IconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
color={theme.font.color.tertiary}
/>
</StyledIconTableCell>
</TableCell>
</StyledApisFieldTableRow>
);
};
@@ -4,46 +4,48 @@ import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import styled from '@emotion/styled';
import { Trans } from '@lingui/react/macro';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { useGetApiKeysQuery } from '~/generated-metadata/graphql';
import {
FeatureFlagKey,
useGetApiKeysQuery,
} from '~/generated-metadata/graphql';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
const StyledTableBody = styled(TableBody)`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
@media (max-width: ${MOBILE_VIEWPORT}px) {
padding-top: ${({ theme }) => theme.spacing(3)};
display: flex;
justify-content: space-between;
scroll-behavior: smooth;
}
`;
const StyledTableRow = styled(TableRow)`
grid-template-columns: 312px auto 28px;
@media (max-width: ${MOBILE_VIEWPORT}px) {
width: 95%;
grid-template-columns: 20fr 2fr;
}
`;
export const SettingsApiKeysTable = () => {
const isApiKeyRolesEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_API_KEY_ROLES_ENABLED,
);
const { data: apiKeysData } = useGetApiKeysQuery();
const apiKeys = apiKeysData?.apiKeys;
const gridAutoColumns = isApiKeyRolesEnabled
? '5fr 2fr 3fr 1fr'
: '5fr 3fr 1fr';
return (
<Table>
<StyledTableRow>
<TableRow gridAutoColumns={gridAutoColumns}>
<TableHeader>
<Trans>Name</Trans>
</TableHeader>
{isApiKeyRolesEnabled && (
<TableHeader>
<Trans>Role</Trans>
</TableHeader>
)}
<TableHeader>
<Trans>Expiration</Trans>
</TableHeader>
<TableHeader></TableHeader>
</StyledTableRow>
</TableRow>
{!!apiKeys?.length && (
<StyledTableBody>
{apiKeys.map((apiKey) => (
@@ -0,0 +1,64 @@
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { Select } from '@/ui/input/components/Select';
import { useRecoilValue } from 'recoil';
import { useIcons } from 'twenty-ui/display';
import { Role } from '~/generated-metadata/graphql';
type SettingsDevelopersRoleSelectorProps = {
value?: string | null | undefined;
onChange: (roleId: string) => void;
label?: string;
description?: string;
roles: Role[];
};
export const SettingsDevelopersRoleSelector = ({
value,
onChange,
label,
description,
roles,
}: SettingsDevelopersRoleSelectorProps) => {
const { getIcon } = useIcons();
const currentWorkspace = useRecoilValue(currentWorkspaceState);
let doesDefaultRoleExistInRoles = false;
const defaultRole = currentWorkspace?.defaultRole;
try {
doesDefaultRoleExistInRoles = roles.some(
(role) => role.id === defaultRole?.id,
);
} catch {
throw new Error(
'Default role does not exist in roles, this should not happen',
);
}
if (roles.length === 0) {
return null;
}
const options = roles.map((role) => ({
label: role.label,
value: role.id,
Icon: getIcon(role.icon) ?? undefined,
}));
const selectValue =
value || (doesDefaultRoleExistInRoles ? defaultRole?.id : roles[0].id);
return (
<Select
dropdownId="role-selector"
options={options}
value={selectValue}
onChange={onChange}
label={label}
description={description}
withSearchInput
fullWidth
/>
);
};
@@ -13,6 +13,11 @@ const meta: Meta<typeof SettingsApiKeysFieldItemTableRow> = {
name: 'Zapier Api Key',
expiresAt: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000).toISOString(), // 3 days from now
revokedAt: null,
role: {
id: '1',
label: 'Admin',
icon: 'admin',
},
},
to: '/settings/developers/api-keys/3f4a42e8-b81f-4f8c-9c20-1602e6b34791',
},