Use enums as types in the GraphQL codegen

This commit is contained in:
Quentin Gliech
2024-10-23 17:26:25 +02:00
parent a320a16b13
commit 95644b76fe
14 changed files with 133 additions and 165 deletions

View File

@@ -15,6 +15,7 @@ const config: CodegenConfig = {
preset: "client",
config: {
useTypeImports: true,
enumsAsTypes: true,
// By default, unknown scalars are generated as `any`. This is not ideal for catching potential bugs.
defaultScalarType: "unknown",
scalars: {

View File

@@ -14,7 +14,7 @@ import { useTranslation } from "react-i18next";
import { useMutation } from "urql";
import { type FragmentType, graphql, useFragment } from "../gql";
import { DeviceType } from "../gql/graphql";
import type { DeviceType } from "../gql/graphql";
import DateTime from "./DateTime";
import EndSessionButton from "./Session/EndSessionButton";
@@ -97,7 +97,7 @@ const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => {
const onSessionEnd = useEndBrowserSession(data.id, isCurrent);
const deviceType = data.userAgent?.deviceType ?? DeviceType.Unknown;
const deviceType = data.userAgent?.deviceType ?? "UNKNOWN";
let deviceName: string | null = null;
let clientName: string | null = null;

View File

@@ -86,7 +86,7 @@ const CompatSession: React.FC<{
? simplifyUrl(data.ssoLogin.redirectUri)
: undefined;
const deviceType = data.userAgent?.deviceType ?? DeviceType.Unknown;
const deviceType = data.userAgent?.deviceType ?? "UNKNOWN";
const deviceName =
data.userAgent?.model ??

View File

@@ -12,7 +12,7 @@ import { beforeAll, describe, expect, it } from "vitest";
import { never } from "wonka";
import { makeFragmentData } from "../gql";
import { Oauth2ApplicationType } from "../gql/graphql";
import type { Oauth2ApplicationType } from "../gql/graphql";
import { mockLocale } from "../test-utils/mockLocale";
import { DummyRouter } from "../test-utils/router";
@@ -34,7 +34,7 @@ describe("<OAuth2Session />", () => {
clientId: "test-client-id",
clientName: "Element",
clientUri: "https://element.io",
applicationType: Oauth2ApplicationType.Web,
applicationType: "WEB" as Oauth2ApplicationType,
},
};
@@ -80,7 +80,7 @@ describe("<OAuth2Session />", () => {
finishedAt,
client: {
...defaultSession.client,
applicationType: Oauth2ApplicationType.Native,
applicationType: "NATIVE",
},
},
FRAGMENT,

View File

@@ -9,7 +9,7 @@ import { useTranslation } from "react-i18next";
import { useMutation } from "urql";
import { type FragmentType, graphql, useFragment } from "../gql";
import { DeviceType, Oauth2ApplicationType } from "../gql/graphql";
import type { DeviceType, Oauth2ApplicationType } from "../gql/graphql";
import { getDeviceIdFromScope } from "../utils/deviceIdFromScope";
import DateTime from "./DateTime";
@@ -58,13 +58,13 @@ export const END_SESSION_MUTATION = graphql(/* GraphQL */ `
const getDeviceTypeFromClientAppType = (
appType?: Oauth2ApplicationType | null,
): DeviceType => {
if (appType === Oauth2ApplicationType.Web) {
return DeviceType.Pc;
if (appType === "WEB") {
return "PC";
}
if (appType === Oauth2ApplicationType.Native) {
return DeviceType.Mobile;
if (appType === "NATIVE") {
return "MOBILE";
}
return DeviceType.Unknown;
return "UNKNOWN";
};
type Props = {
@@ -88,7 +88,7 @@ const OAuth2Session: React.FC<Props> = ({ session }) => {
: undefined;
const deviceType =
(data.userAgent?.deviceType === DeviceType.Unknown
(data.userAgent?.deviceType === "UNKNOWN"
? null
: data.userAgent?.deviceType) ??
getDeviceTypeFromClientAppType(data.client.applicationType);

View File

@@ -15,17 +15,12 @@ const meta = {
component: DeviceTypeIcon,
tags: ["autodocs"],
args: {
deviceType: DeviceType.Unknown,
deviceType: "UNKNOWN",
},
argTypes: {
deviceType: {
control: "select",
options: [
DeviceType.Unknown,
DeviceType.Pc,
DeviceType.Mobile,
DeviceType.Tablet,
],
options: ["UNKNOWN", "PC", "MOBILE", "TABLET"],
},
},
} satisfies Meta<typeof DeviceTypeIcon>;
@@ -37,16 +32,16 @@ export const Unknown: Story = {};
export const Pc: Story = {
args: {
deviceType: DeviceType.Pc,
deviceType: "PC",
},
};
export const Mobile: Story = {
args: {
deviceType: DeviceType.Mobile,
deviceType: "MOBILE",
},
};
export const Tablet: Story = {
args: {
deviceType: DeviceType.Tablet,
deviceType: "TABLET",
},
};

View File

@@ -11,7 +11,7 @@ import IconBrowser from "@vector-im/compound-design-tokens/assets/web/icons/web-
import type { FunctionComponent, SVGProps } from "react";
import { useTranslation } from "react-i18next";
import { DeviceType } from "../../gql/graphql";
import type { DeviceType } from "../../gql/graphql";
import styles from "./DeviceTypeIcon.module.css";
@@ -19,10 +19,10 @@ const deviceTypeToIcon: Record<
DeviceType,
FunctionComponent<SVGProps<SVGSVGElement> & { title?: string | undefined }>
> = {
[DeviceType.Unknown]: IconUnknown,
[DeviceType.Pc]: IconComputer,
[DeviceType.Mobile]: IconMobile,
[DeviceType.Tablet]: IconBrowser,
UNKNOWN: IconUnknown,
PC: IconComputer,
MOBILE: IconMobile,
TABLET: IconBrowser,
};
const DeviceTypeIcon: React.FC<{ deviceType: DeviceType }> = ({
@@ -33,10 +33,10 @@ const DeviceTypeIcon: React.FC<{ deviceType: DeviceType }> = ({
const Icon = deviceTypeToIcon[deviceType];
const deviceTypeToLabel: Record<DeviceType, string> = {
[DeviceType.Unknown]: t("frontend.device_type_icon_label.unknown"),
[DeviceType.Pc]: t("frontend.device_type_icon_label.pc"),
[DeviceType.Mobile]: t("frontend.device_type_icon_label.mobile"),
[DeviceType.Tablet]: t("frontend.device_type_icon_label.tablet"),
UNKNOWN: t("frontend.device_type_icon_label.unknown"),
PC: t("frontend.device_type_icon_label.pc"),
MOBILE: t("frontend.device_type_icon_label.mobile"),
TABLET: t("frontend.device_type_icon_label.tablet"),
};
const label = deviceTypeToLabel[deviceType];

View File

@@ -9,7 +9,7 @@ import IconSignOut from "@vector-im/compound-design-tokens/assets/web/icons/sign
import { Button } from "@vector-im/compound-web";
import { useTranslation } from "react-i18next";
import { DeviceType } from "../../gql/graphql";
import type { DeviceType } from "../../gql/graphql";
import * as Card from "./SessionCard";
@@ -51,10 +51,13 @@ const meta = {
disabled: false,
deviceName: "MacBook Pro 16",
clientName: "Firefox",
deviceType: DeviceType.Pc,
deviceType: "PC",
},
argTypes: {
deviceType: { control: "select", options: Object.values(DeviceType) },
deviceType: {
control: "select",
options: ["PC", "MOBILE", "TABLET", "UNKNOWN"],
},
disabled: { control: "boolean" },
deviceName: { control: "text" },
clientName: { control: "text" },

View File

@@ -9,10 +9,7 @@ import { Provider } from "urql";
import { delay, fromValue, pipe } from "wonka";
import { makeFragmentData } from "../../gql";
import {
type SetDisplayNameMutation,
SetDisplayNameStatus,
} from "../../gql/graphql";
import type { SetDisplayNameMutation } from "../../gql/graphql";
import UserGreeting, { CONFIG_FRAGMENT, FRAGMENT } from "./UserGreeting";
@@ -31,7 +28,7 @@ const Template: React.FC<{
fromValue({
data: {
setDisplayName: {
status: SetDisplayNameStatus.Set,
status: "SET",
user: { id: userId, matrix: { displayName } },
},
},

View File

@@ -107,7 +107,7 @@ const UserGreeting: React.FC<Props> = ({ user, siteConfig }) => {
const result = await setDisplayName({ displayName, userId: data.id });
if (result.data?.setDisplayName.status === SetDisplayNameStatus.Set) {
if (result.data?.setDisplayName.status === "SET") {
setOpen(false);
}
};
@@ -163,8 +163,7 @@ const UserGreeting: React.FC<Props> = ({ user, siteConfig }) => {
<Form.Field
name="displayname"
serverInvalid={
setDisplayNameResult.data?.setDisplayName.status ===
SetDisplayNameStatus.Invalid
setDisplayNameResult.data?.setDisplayName.status === "INVALID"
}
>
<Form.Label>

View File

@@ -37,16 +37,15 @@ export type AddEmailInput = {
};
/** The status of the `addEmail` mutation */
export enum AddEmailStatus {
export type AddEmailStatus =
/** The email address was added */
Added = 'ADDED',
| 'ADDED'
/** The email address is not allowed by the policy */
Denied = 'DENIED',
| 'DENIED'
/** The email address already exists */
Exists = 'EXISTS',
| 'EXISTS'
/** The email address is invalid */
Invalid = 'INVALID'
}
| 'INVALID';
/** The input for the `addUser` mutation. */
export type AddUserInput = {
@@ -63,16 +62,15 @@ export type AddUserInput = {
};
/** The status of the `addUser` mutation. */
export enum AddUserStatus {
export type AddUserStatus =
/** The user was added. */
Added = 'ADDED',
| 'ADDED'
/** The user already exists. */
Exists = 'EXISTS',
| 'EXISTS'
/** The username is invalid. */
Invalid = 'INVALID',
| 'INVALID'
/** The username is reserved. */
Reserved = 'RESERVED'
}
| 'RESERVED';
/** The input for the `allowUserCrossSigningReset` mutation. */
export type AllowUserCrossSigningResetInput = {
@@ -81,19 +79,17 @@ export type AllowUserCrossSigningResetInput = {
};
/** Which Captcha service is being used */
export enum CaptchaService {
CloudflareTurnstile = 'CLOUDFLARE_TURNSTILE',
HCaptcha = 'H_CAPTCHA',
RecaptchaV2 = 'RECAPTCHA_V2'
}
export type CaptchaService =
| 'CLOUDFLARE_TURNSTILE'
| 'H_CAPTCHA'
| 'RECAPTCHA_V2';
/** The type of a compatibility session. */
export enum CompatSessionType {
export type CompatSessionType =
/** The session was created by a SSO login. */
SsoLogin = 'SSO_LOGIN',
| 'SSO_LOGIN'
/** The session was created by an unknown method. */
Unknown = 'UNKNOWN'
}
| 'UNKNOWN';
/** The input of the `createOauth2Session` mutation. */
export type CreateOAuth2SessionInput = {
@@ -114,16 +110,15 @@ export type DateFilter = {
};
/** The type of a user agent */
export enum DeviceType {
export type DeviceType =
/** A mobile phone. Can also sometimes be a tablet. */
Mobile = 'MOBILE',
| 'MOBILE'
/** A personal computer, laptop or desktop */
Pc = 'PC',
| 'PC'
/** A tablet */
Tablet = 'TABLET',
| 'TABLET'
/** Unknown device type */
Unknown = 'UNKNOWN'
}
| 'UNKNOWN';
/** The input of the `endBrowserSession` mutation. */
export type EndBrowserSessionInput = {
@@ -132,12 +127,11 @@ export type EndBrowserSessionInput = {
};
/** The status of the `endBrowserSession` mutation. */
export enum EndBrowserSessionStatus {
export type EndBrowserSessionStatus =
/** The session was ended. */
Ended = 'ENDED',
| 'ENDED'
/** The session was not found. */
NotFound = 'NOT_FOUND'
}
| 'NOT_FOUND';
/** The input of the `endCompatSession` mutation. */
export type EndCompatSessionInput = {
@@ -146,12 +140,11 @@ export type EndCompatSessionInput = {
};
/** The status of the `endCompatSession` mutation. */
export enum EndCompatSessionStatus {
export type EndCompatSessionStatus =
/** The session was ended. */
Ended = 'ENDED',
| 'ENDED'
/** The session was not found. */
NotFound = 'NOT_FOUND'
}
| 'NOT_FOUND';
/** The input of the `endOauth2Session` mutation. */
export type EndOAuth2SessionInput = {
@@ -160,12 +153,11 @@ export type EndOAuth2SessionInput = {
};
/** The status of the `endOauth2Session` mutation. */
export enum EndOAuth2SessionStatus {
export type EndOAuth2SessionStatus =
/** The session was ended. */
Ended = 'ENDED',
| 'ENDED'
/** The session was not found. */
NotFound = 'NOT_FOUND'
}
| 'NOT_FOUND';
/** The input for the `lockUser` mutation. */
export type LockUserInput = {
@@ -176,20 +168,18 @@ export type LockUserInput = {
};
/** The status of the `lockUser` mutation. */
export enum LockUserStatus {
export type LockUserStatus =
/** The user was locked. */
Locked = 'LOCKED',
| 'LOCKED'
/** The user was not found. */
NotFound = 'NOT_FOUND'
}
| 'NOT_FOUND';
/** The application type advertised by the client. */
export enum Oauth2ApplicationType {
export type Oauth2ApplicationType =
/** Client is a native application. */
Native = 'NATIVE',
| 'NATIVE'
/** Client is a web application. */
Web = 'WEB'
}
| 'WEB';
/** The input for the `removeEmail` mutation */
export type RemoveEmailInput = {
@@ -198,14 +188,13 @@ export type RemoveEmailInput = {
};
/** The status of the `removeEmail` mutation */
export enum RemoveEmailStatus {
export type RemoveEmailStatus =
/** The email address was not found */
NotFound = 'NOT_FOUND',
| 'NOT_FOUND'
/** Can't remove the primary email address */
Primary = 'PRIMARY',
| 'PRIMARY'
/** The email address was removed */
Removed = 'REMOVED'
}
| 'REMOVED';
/** The input for the `sendVerificationEmail` mutation */
export type SendVerificationEmailInput = {
@@ -214,20 +203,18 @@ export type SendVerificationEmailInput = {
};
/** The status of the `sendVerificationEmail` mutation */
export enum SendVerificationEmailStatus {
export type SendVerificationEmailStatus =
/** The email address is already verified */
AlreadyVerified = 'ALREADY_VERIFIED',
| 'ALREADY_VERIFIED'
/** The verification email was sent */
Sent = 'SENT'
}
| 'SENT';
/** The state of a session */
export enum SessionState {
export type SessionState =
/** The session is active. */
Active = 'ACTIVE',
| 'ACTIVE'
/** The session is no longer active. */
Finished = 'FINISHED'
}
| 'FINISHED';
/** The input for the `setCanRequestAdmin` mutation. */
export type SetCanRequestAdminInput = {
@@ -246,12 +233,11 @@ export type SetDisplayNameInput = {
};
/** The status of the `setDisplayName` mutation */
export enum SetDisplayNameStatus {
export type SetDisplayNameStatus =
/** The display name is invalid */
Invalid = 'INVALID',
| 'INVALID'
/** The display name was set */
Set = 'SET'
}
| 'SET';
/** The input for the `setPasswordByRecovery` mutation. */
export type SetPasswordByRecoveryInput = {
@@ -283,44 +269,43 @@ export type SetPasswordInput = {
};
/** The status of the `setPassword` mutation. */
export enum SetPasswordStatus {
export type SetPasswordStatus =
/** Your account is locked and you can't change its password. */
AccountLocked = 'ACCOUNT_LOCKED',
| 'ACCOUNT_LOCKED'
/** The password was updated. */
Allowed = 'ALLOWED',
| 'ALLOWED'
/** The specified recovery ticket has expired. */
ExpiredRecoveryTicket = 'EXPIRED_RECOVERY_TICKET',
| 'EXPIRED_RECOVERY_TICKET'
/**
* The new password is invalid. For example, it may not meet configured
* security requirements.
*/
InvalidNewPassword = 'INVALID_NEW_PASSWORD',
| 'INVALID_NEW_PASSWORD'
/**
* You aren't allowed to set the password for that user.
* This happens if you aren't setting your own password and you aren't a
* server administrator.
*/
NotAllowed = 'NOT_ALLOWED',
| 'NOT_ALLOWED'
/** The user was not found. */
NotFound = 'NOT_FOUND',
| 'NOT_FOUND'
/** The user doesn't have a current password to attempt to match against. */
NoCurrentPassword = 'NO_CURRENT_PASSWORD',
| 'NO_CURRENT_PASSWORD'
/** The specified recovery ticket does not exist. */
NoSuchRecoveryTicket = 'NO_SUCH_RECOVERY_TICKET',
| 'NO_SUCH_RECOVERY_TICKET'
/**
* Password support has been disabled.
* This usually means that login is handled by an upstream identity
* provider.
*/
PasswordChangesDisabled = 'PASSWORD_CHANGES_DISABLED',
| 'PASSWORD_CHANGES_DISABLED'
/**
* The specified recovery ticket has already been used and cannot be used
* again.
*/
RecoveryTicketAlreadyUsed = 'RECOVERY_TICKET_ALREADY_USED',
| 'RECOVERY_TICKET_ALREADY_USED'
/** The supplied current password was wrong. */
WrongPassword = 'WRONG_PASSWORD'
}
| 'WRONG_PASSWORD';
/** The input for the `setPrimaryEmail` mutation */
export type SetPrimaryEmailInput = {
@@ -329,14 +314,13 @@ export type SetPrimaryEmailInput = {
};
/** The status of the `setPrimaryEmail` mutation */
export enum SetPrimaryEmailStatus {
export type SetPrimaryEmailStatus =
/** The email address was not found */
NotFound = 'NOT_FOUND',
| 'NOT_FOUND'
/** The email address was set as primary */
Set = 'SET',
| 'SET'
/** Can't make an unverified email address primary */
Unverified = 'UNVERIFIED'
}
| 'UNVERIFIED';
/** The input for the `unlockUser` mutation. */
export type UnlockUserInput = {
@@ -345,28 +329,25 @@ export type UnlockUserInput = {
};
/** The status of the `unlockUser` mutation. */
export enum UnlockUserStatus {
export type UnlockUserStatus =
/** The user was not found. */
NotFound = 'NOT_FOUND',
| 'NOT_FOUND'
/** The user was unlocked. */
Unlocked = 'UNLOCKED'
}
| 'UNLOCKED';
/** The state of a compatibility session. */
export enum UserEmailState {
export type UserEmailState =
/** The email address has been confirmed. */
Confirmed = 'CONFIRMED',
| 'CONFIRMED'
/** The email address is pending confirmation. */
Pending = 'PENDING'
}
| 'PENDING';
/** The state of a user. */
export enum UserState {
export type UserState =
/** The user is active. */
Active = 'ACTIVE',
| 'ACTIVE'
/** The user is locked. */
Locked = 'LOCKED'
}
| 'LOCKED';
/** The input for the `verifyEmail` mutation */
export type VerifyEmailInput = {
@@ -377,14 +358,13 @@ export type VerifyEmailInput = {
};
/** The status of the `verifyEmail` mutation */
export enum VerifyEmailStatus {
export type VerifyEmailStatus =
/** The email address was already verified before */
AlreadyVerified = 'ALREADY_VERIFIED',
| 'ALREADY_VERIFIED'
/** The verification code is invalid */
InvalidCode = 'INVALID_CODE',
| 'INVALID_CODE'
/** The email address was just verified */
Verified = 'VERIFIED'
}
| 'VERIFIED';
export type PasswordChange_SiteConfigFragment = { __typename?: 'SiteConfig', id: string, passwordChangeAllowed: boolean } & { ' $fragmentName'?: 'PasswordChange_SiteConfigFragment' };

View File

@@ -6,7 +6,7 @@
import type { TFunction } from "i18next";
import { SetPasswordStatus } from "../gql/graphql";
import type { SetPasswordStatus } from "../gql/graphql";
/**
* Provides a translated string representing a `SetPasswordStatus`.
@@ -25,35 +25,35 @@ export function translateSetPasswordError(
status: SetPasswordStatus | undefined,
): string | undefined {
switch (status) {
case SetPasswordStatus.NoCurrentPassword:
case "NO_CURRENT_PASSWORD":
return t(
"frontend.password_change.failure.description.no_current_password",
);
case SetPasswordStatus.PasswordChangesDisabled:
case "PASSWORD_CHANGES_DISABLED":
return t(
"frontend.password_change.failure.description.password_changes_disabled",
);
case SetPasswordStatus.AccountLocked:
case "ACCOUNT_LOCKED":
return t("frontend.password_change.failure.description.account_locked");
case SetPasswordStatus.ExpiredRecoveryTicket:
case "EXPIRED_RECOVERY_TICKET":
return t(
"frontend.password_change.failure.description.expired_recovery_ticket",
);
case SetPasswordStatus.NoSuchRecoveryTicket:
case "NO_SUCH_RECOVERY_TICKET":
return t(
"frontend.password_change.failure.description.no_such_recovery_ticket",
);
case SetPasswordStatus.RecoveryTicketAlreadyUsed:
case "RECOVERY_TICKET_ALREADY_USED":
return t(
"frontend.password_change.failure.description.recovery_ticket_already_used",
);
case SetPasswordStatus.WrongPassword:
case SetPasswordStatus.InvalidNewPassword:
case "WRONG_PASSWORD":
case "INVALID_NEW_PASSWORD":
// These cases are shown as inline errors in the form itself.
return undefined;
case SetPasswordStatus.Allowed:
case "ALLOWED":
case undefined:
return undefined;

View File

@@ -78,7 +78,7 @@ function ChangePassword(): React.ReactNode {
const response = await changePassword({ userId, oldPassword, newPassword });
if (response.data?.setPassword.status === SetPasswordStatus.Allowed) {
if (response.data?.setPassword.status === "ALLOWED") {
router.navigate({ to: "/password/change/success" });
}
};
@@ -125,10 +125,7 @@ function ChangePassword(): React.ReactNode {
<Form.Field
name="current_password"
serverInvalid={
result.data?.setPassword.status ===
SetPasswordStatus.WrongPassword
}
serverInvalid={result.data?.setPassword.status === "WRONG_PASSWORD"}
>
<Form.Label>
{t("frontend.password_change.current_password_label")}
@@ -145,8 +142,7 @@ function ChangePassword(): React.ReactNode {
</Form.ErrorMessage>
{result.data &&
result.data.setPassword.status ===
SetPasswordStatus.WrongPassword && (
result.data.setPassword.status === "WRONG_PASSWORD" && (
<Form.ErrorMessage>
{t(
"frontend.password_change.failure.description.wrong_password",
@@ -161,8 +157,7 @@ function ChangePassword(): React.ReactNode {
siteConfig={siteConfig}
forceShowNewPasswordInvalid={
(result.data &&
result.data.setPassword.status ===
SetPasswordStatus.InvalidNewPassword) ||
result.data.setPassword.status === "INVALID_NEW_PASSWORD") ||
false
}
/>

View File

@@ -67,9 +67,7 @@ function RecoverPassword(): React.ReactNode {
const response = await changePassword({ ticket, newPassword });
if (
response.data?.setPasswordByRecovery.status === SetPasswordStatus.Allowed
) {
if (response.data?.setPasswordByRecovery.status === "ALLOWED") {
// Redirect to the application root using a full page load
// The MAS backend will then redirect to the login page
// Unfortunately this won't work in dev mode (`npm run dev`)
@@ -124,7 +122,7 @@ function RecoverPassword(): React.ReactNode {
forceShowNewPasswordInvalid={
(result.data &&
result.data.setPasswordByRecovery.status ===
SetPasswordStatus.InvalidNewPassword) ||
"INVALID_NEW_PASSWORD") ||
false
}
/>