frontend: allow setting custom names to sessions

This commit is contained in:
Quentin Gliech
2025-04-25 15:35:41 +02:00
parent cf9d4599f9
commit b38b55a805
10 changed files with 427 additions and 30 deletions

View File

@@ -258,6 +258,11 @@
"last_active_label": "Last Active",
"name_for_platform": "{{name}} for {{platform}}",
"scopes_label": "Scopes",
"set_device_name": {
"help": "Set a name that will help you identify this device.",
"label": "Device name",
"title": "Edit device name"
},
"signed_in_label": "Signed in",
"title": "Device details",
"unknown_browser": "Unknown browser",

View File

@@ -6,6 +6,7 @@
// @vitest-environment happy-dom
import { TooltipProvider } from "@vector-im/compound-web";
import { beforeAll, describe, expect, it } from "vitest";
import { makeFragmentData } from "../../gql";
import { mockLocale } from "../../test-utils/mockLocale";
@@ -33,7 +34,9 @@ describe("<CompatSessionDetail>", () => {
const data = makeFragmentData({ ...baseSession }, FRAGMENT);
const { container, getByText, queryByText } = render(
<CompatSessionDetail session={data} />,
<TooltipProvider>
<CompatSessionDetail session={data} />
</TooltipProvider>,
);
expect(container).toMatchSnapshot();
@@ -51,7 +54,9 @@ describe("<CompatSessionDetail>", () => {
);
const { container, getByText, queryByText } = render(
<CompatSessionDetail session={data} />,
<TooltipProvider>
<CompatSessionDetail session={data} />
</TooltipProvider>,
);
expect(container).toMatchSnapshot();
@@ -69,7 +74,9 @@ describe("<CompatSessionDetail>", () => {
);
const { container, getByText, queryByText } = render(
<CompatSessionDetail session={data} />,
<TooltipProvider>
<CompatSessionDetail session={data} />
</TooltipProvider>,
);
expect(container).toMatchSnapshot();

View File

@@ -4,17 +4,28 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { VisualList } from "@vector-im/compound-web";
import { parseISO } from "date-fns";
import { useTranslation } from "react-i18next";
import { type FragmentType, graphql, useFragment } from "../../gql";
import { graphqlRequest } from "../../graphql";
import simplifyUrl from "../../utils/simplifyUrl";
import DateTime from "../DateTime";
import EndCompatSessionButton from "../Session/EndCompatSessionButton";
import LastActive from "../Session/LastActive";
import EditSessionName from "./EditSessionName";
import SessionHeader from "./SessionHeader";
import * as Info from "./SessionInfo";
const SET_SESSION_NAME_MUTATION = graphql(/* GraphQL */ `
mutation SetCompatSessionName($sessionId: ID!, $displayName: String!) {
setCompatSessionName(input: { compatSessionId: $sessionId, humanName: $displayName }) {
status
}
}
`);
export const FRAGMENT = graphql(/* GraphQL */ `
fragment CompatSession_detail on CompatSession {
id
@@ -47,6 +58,19 @@ type Props = {
const CompatSessionDetail: React.FC<Props> = ({ session }) => {
const data = useFragment(FRAGMENT, session);
const { t } = useTranslation();
const queryClient = useQueryClient();
const setDisplayName = useMutation({
mutationFn: (displayName: string) =>
graphqlRequest({
query: SET_SESSION_NAME_MUTATION,
variables: { sessionId: data.id, displayName },
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["sessionDetail", data.id] });
queryClient.invalidateQueries({ queryKey: ["sessionsOverview"] });
},
});
const deviceName =
data.userAgent?.model ??
@@ -67,7 +91,10 @@ const CompatSessionDetail: React.FC<Props> = ({ session }) => {
return (
<div className="flex flex-col gap-10">
<SessionHeader to="/sessions">{sessionName}</SessionHeader>
<SessionHeader to="/sessions">
{sessionName}
<EditSessionName mutation={setDisplayName} deviceName={sessionName} />
</SessionHeader>
<Info.DataSection>
<Info.DataSectionHeader>
{t("frontend.session.title")}

View File

@@ -0,0 +1,100 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import IconEdit from "@vector-im/compound-design-tokens/assets/web/icons/edit";
import { Button, Form, IconButton, Tooltip } from "@vector-im/compound-web";
import {
type ComponentPropsWithoutRef,
forwardRef,
useRef,
useState,
} from "react";
import * as Dialog from "../Dialog";
import LoadingSpinner from "../LoadingSpinner";
import type { UseMutationResult } from "@tanstack/react-query";
import { useTranslation } from "react-i18next";
// This needs to be its own component because else props and refs aren't passed properly in the trigger
const EditButton = forwardRef<
HTMLButtonElement,
{ label: string } & ComponentPropsWithoutRef<"button">
>(({ label, ...props }, ref) => (
<Tooltip label={label}>
<IconButton
ref={ref}
type="button"
size="var(--cpd-space-6x)"
style={{ marginInline: "var(--cpd-space-2x)" }}
{...props}
>
<IconEdit />
</IconButton>
</Tooltip>
));
type Props = {
mutation: UseMutationResult<unknown, unknown, string, unknown>;
deviceName: string;
};
const EditSessionName: React.FC<Props> = ({ mutation, deviceName }) => {
const { t } = useTranslation();
const fieldRef = useRef<HTMLInputElement>(null);
const [open, setOpen] = useState(false);
const onSubmit = async (
event: React.FormEvent<HTMLFormElement>,
): Promise<void> => {
event.preventDefault();
const form = event.currentTarget;
const formData = new FormData(form);
const displayName = formData.get("name") as string;
await mutation.mutateAsync(displayName);
setOpen(false);
};
return (
<Dialog.Dialog
trigger={<EditButton label={t("action.edit")} />}
open={open}
onOpenChange={(open) => {
// Reset the form when the dialog is opened or closed
fieldRef.current?.form?.reset();
setOpen(open);
}}
>
<Dialog.Title>{t("frontend.session.set_device_name.title")}</Dialog.Title>
<Form.Root onSubmit={onSubmit}>
<Form.Field name="name">
<Form.Label>{t("frontend.session.set_device_name.label")}</Form.Label>
<Form.TextControl
type="text"
required
defaultValue={deviceName}
ref={fieldRef}
/>
<Form.HelpMessage>
{t("frontend.session.set_device_name.help")}
</Form.HelpMessage>
</Form.Field>
<Form.Submit disabled={mutation.isPending}>
{mutation.isPending && <LoadingSpinner inline />}
{t("action.save")}
</Form.Submit>
</Form.Root>
<Dialog.Close asChild>
<Button kind="tertiary">{t("action.cancel")}</Button>
</Dialog.Close>
</Dialog.Dialog>
);
};
export default EditSessionName;

View File

@@ -11,6 +11,7 @@ import { beforeAll, describe, expect, it } from "vitest";
import { makeFragmentData } from "../../gql";
import { mockLocale } from "../../test-utils/mockLocale";
import { TooltipProvider } from "@vector-im/compound-web";
import render from "../../test-utils/render";
import OAuth2SessionDetail, { FRAGMENT } from "./OAuth2SessionDetail";
@@ -39,7 +40,9 @@ describe("<OAuth2SessionDetail>", () => {
const data = makeFragmentData(baseSession, FRAGMENT);
const { asFragment, getByText, queryByText } = render(
<OAuth2SessionDetail session={data} />,
<TooltipProvider>
<OAuth2SessionDetail session={data} />
</TooltipProvider>,
);
expect(asFragment()).toMatchSnapshot();
@@ -57,7 +60,9 @@ describe("<OAuth2SessionDetail>", () => {
);
const { asFragment, getByText, queryByText } = render(
<OAuth2SessionDetail session={data} />,
<TooltipProvider>
<OAuth2SessionDetail session={data} />
</TooltipProvider>,
);
expect(asFragment()).toMatchSnapshot();

View File

@@ -4,17 +4,28 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { parseISO } from "date-fns";
import { useTranslation } from "react-i18next";
import { type FragmentType, graphql, useFragment } from "../../gql";
import { graphqlRequest } from "../../graphql";
import { getDeviceIdFromScope } from "../../utils/deviceIdFromScope";
import DateTime from "../DateTime";
import ClientAvatar from "../Session/ClientAvatar";
import EndOAuth2SessionButton from "../Session/EndOAuth2SessionButton";
import LastActive from "../Session/LastActive";
import EditSessionName from "./EditSessionName";
import SessionHeader from "./SessionHeader";
import * as Info from "./SessionInfo";
const SET_SESSION_NAME_MUTATION = graphql(/* GraphQL */ `
mutation SetOAuth2SessionName($sessionId: ID!, $displayName: String!) {
setOauth2SessionName(input: { oauth2SessionId: $sessionId, humanName: $displayName }) {
status
}
}
`);
export const FRAGMENT = graphql(/* GraphQL */ `
fragment OAuth2Session_detail on Oauth2Session {
id
@@ -50,6 +61,19 @@ type Props = {
const OAuth2SessionDetail: React.FC<Props> = ({ session }) => {
const data = useFragment(FRAGMENT, session);
const { t } = useTranslation();
const queryClient = useQueryClient();
const setDisplayName = useMutation({
mutationFn: (displayName: string) =>
graphqlRequest({
query: SET_SESSION_NAME_MUTATION,
variables: { sessionId: data.id, displayName },
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["sessionDetail", data.id] });
queryClient.invalidateQueries({ queryKey: ["sessionsOverview"] });
},
});
const deviceId = getDeviceIdFromScope(data.scope);
const clientName = data.client.clientName || data.client.clientId;
@@ -70,7 +94,9 @@ const OAuth2SessionDetail: React.FC<Props> = ({ session }) => {
<div className="flex flex-col gap-10">
<SessionHeader to="/sessions">
{clientName}: {deviceName}
<EditSessionName mutation={setDisplayName} deviceName={deviceName} />
</SessionHeader>
<Info.DataSection>
<Info.DataSectionHeader>
{t("frontend.session.title")}

View File

@@ -27,9 +27,38 @@ exports[`<CompatSessionDetail> > renders a compatability session details 1`] = `
<h3
class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
>
element.io
:
Unknown device
element.io: Unknown device
<button
aria-controls="radix-«r0»"
aria-expanded="false"
aria-haspopup="dialog"
aria-labelledby="«r3»"
class="_icon-button_m2erp_8"
data-state="closed"
role="button"
style="--cpd-icon-button-size: var(--cpd-space-6x);"
tabindex="0"
type="button"
>
<div
class="_indicator-icon_zr2a0_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
clip-rule="evenodd"
d="M15.706 2.637a2 2 0 0 1 2.829 0l2.828 2.828a2 2 0 0 1 0 2.829L9.605 20.052a1 1 0 0 1-.465.263L3.483 21.73a1 1 0 0 1-1.212-1.213l1.414-5.657a1 1 0 0 1 .263-.465zm1.224 7.262L14.102 7.07l-8.544 8.544-.943 3.771 3.771-.943z"
fill-rule="evenodd"
/>
</svg>
</div>
</button>
</h3>
</header>
<section
@@ -238,7 +267,7 @@ exports[`<CompatSessionDetail> > renders a compatability session details 1`] = `
</ul>
</section>
<button
aria-controls="radix-«r0»"
aria-controls="radix-«r8»"
aria-expanded="false"
aria-haspopup="dialog"
class="_button_vczzf_8 _has-icon_vczzf_57 _destructive_vczzf_107"
@@ -294,9 +323,38 @@ exports[`<CompatSessionDetail> > renders a compatability session without an ssoL
<h3
class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
>
abcd1234
:
Unknown device
abcd1234: Unknown device
<button
aria-controls="radix-«rc»"
aria-expanded="false"
aria-haspopup="dialog"
aria-labelledby="«rf»"
class="_icon-button_m2erp_8"
data-state="closed"
role="button"
style="--cpd-icon-button-size: var(--cpd-space-6x);"
tabindex="0"
type="button"
>
<div
class="_indicator-icon_zr2a0_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
clip-rule="evenodd"
d="M15.706 2.637a2 2 0 0 1 2.829 0l2.828 2.828a2 2 0 0 1 0 2.829L9.605 20.052a1 1 0 0 1-.465.263L3.483 21.73a1 1 0 0 1-1.212-1.213l1.414-5.657a1 1 0 0 1 .263-.465zm1.224 7.262L14.102 7.07l-8.544 8.544-.943 3.771 3.771-.943z"
fill-rule="evenodd"
/>
</svg>
</div>
</button>
</h3>
</header>
<section
@@ -488,22 +546,10 @@ exports[`<CompatSessionDetail> > renders a compatability session without an ssoL
Unknown device
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31 text-secondary"
>
Uri
</h5>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 text-ellipsis overflow-hidden"
/>
</li>
</ul>
</section>
<button
aria-controls="radix-«r3»"
aria-controls="radix-«rk»"
aria-expanded="false"
aria-haspopup="dialog"
class="_button_vczzf_8 _has-icon_vczzf_57 _destructive_vczzf_107"
@@ -559,9 +605,38 @@ exports[`<CompatSessionDetail> > renders a finished compatability session detail
<h3
class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
>
element.io
:
Unknown device
element.io: Unknown device
<button
aria-controls="radix-«ro»"
aria-expanded="false"
aria-haspopup="dialog"
aria-labelledby="«rr»"
class="_icon-button_m2erp_8"
data-state="closed"
role="button"
style="--cpd-icon-button-size: var(--cpd-space-6x);"
tabindex="0"
type="button"
>
<div
class="_indicator-icon_zr2a0_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
clip-rule="evenodd"
d="M15.706 2.637a2 2 0 0 1 2.829 0l2.828 2.828a2 2 0 0 1 0 2.829L9.605 20.052a1 1 0 0 1-.465.263L3.483 21.73a1 1 0 0 1-1.212-1.213l1.414-5.657a1 1 0 0 1 .263-.465zm1.224 7.262L14.102 7.07l-8.544 8.544-.943 3.771 3.771-.943z"
fill-rule="evenodd"
/>
</svg>
</div>
</button>
</h3>
</header>
<section

View File

@@ -28,6 +28,37 @@ exports[`<OAuth2SessionDetail> > renders a finished session details 1`] = `
class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
>
Element: Unknown device
<button
aria-controls="radix-«rc»"
aria-expanded="false"
aria-haspopup="dialog"
aria-labelledby="«rf»"
class="_icon-button_m2erp_8"
data-state="closed"
role="button"
style="--cpd-icon-button-size: var(--cpd-space-6x);"
tabindex="0"
type="button"
>
<div
class="_indicator-icon_zr2a0_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
clip-rule="evenodd"
d="M15.706 2.637a2 2 0 0 1 2.829 0l2.828 2.828a2 2 0 0 1 0 2.829L9.605 20.052a1 1 0 0 1-.465.263L3.483 21.73a1 1 0 0 1-1.212-1.213l1.414-5.657a1 1 0 0 1 .263-.465zm1.224 7.262L14.102 7.07l-8.544 8.544-.943 3.771 3.771-.943z"
fill-rule="evenodd"
/>
</svg>
</div>
</button>
</h3>
</header>
<section
@@ -307,6 +338,37 @@ exports[`<OAuth2SessionDetail> > renders session details 1`] = `
class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
>
Element: Unknown device
<button
aria-controls="radix-«r0»"
aria-expanded="false"
aria-haspopup="dialog"
aria-labelledby="«r3»"
class="_icon-button_m2erp_8"
data-state="closed"
role="button"
style="--cpd-icon-button-size: var(--cpd-space-6x);"
tabindex="0"
type="button"
>
<div
class="_indicator-icon_zr2a0_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
clip-rule="evenodd"
d="M15.706 2.637a2 2 0 0 1 2.829 0l2.828 2.828a2 2 0 0 1 0 2.829L9.605 20.052a1 1 0 0 1-.465.263L3.483 21.73a1 1 0 0 1-1.212-1.213l1.414-5.657a1 1 0 0 1 .263-.465zm1.224 7.262L14.102 7.07l-8.544 8.544-.943 3.771 3.771-.943z"
fill-rule="evenodd"
/>
</svg>
</div>
</button>
</h3>
</header>
<section
@@ -537,7 +599,7 @@ exports[`<OAuth2SessionDetail> > renders session details 1`] = `
</ul>
</section>
<button
aria-controls="radix-«r0»"
aria-controls="radix-«r8»"
aria-expanded="false"
aria-haspopup="dialog"
class="_button_vczzf_8 _has-icon_vczzf_57 _destructive_vczzf_107"

View File

@@ -33,7 +33,9 @@ type Documents = {
"\n fragment EndOAuth2SessionButton_session on Oauth2Session {\n id\n\n userAgent {\n name\n model\n os\n deviceType\n }\n\n client {\n clientId\n clientName\n applicationType\n logoUri\n }\n }\n": typeof types.EndOAuth2SessionButton_SessionFragmentDoc,
"\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n }\n }\n }\n": typeof types.EndOAuth2SessionDocument,
"\n fragment BrowserSession_detail on BrowserSession {\n id\n createdAt\n finishedAt\n ...EndBrowserSessionButton_session\n userAgent {\n name\n model\n os\n }\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n": typeof types.BrowserSession_DetailFragmentDoc,
"\n mutation SetCompatSessionName($sessionId: ID!, $displayName: String!) {\n setCompatSessionName(input: { compatSessionId: $sessionId, humanName: $displayName }) {\n status\n }\n }\n": typeof types.SetCompatSessionNameDocument,
"\n fragment CompatSession_detail on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n humanName\n\n ...EndCompatSessionButton_session\n\n userAgent {\n name\n os\n model\n }\n\n ssoLogin {\n id\n redirectUri\n }\n }\n": typeof types.CompatSession_DetailFragmentDoc,
"\n mutation SetOAuth2SessionName($sessionId: ID!, $displayName: String!) {\n setOauth2SessionName(input: { oauth2SessionId: $sessionId, humanName: $displayName }) {\n status\n }\n }\n": typeof types.SetOAuth2SessionNameDocument,
"\n fragment OAuth2Session_detail on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n humanName\n\n ...EndOAuth2SessionButton_session\n\n userAgent {\n name\n model\n os\n }\n\n client {\n id\n clientId\n clientName\n clientUri\n logoUri\n }\n }\n": typeof types.OAuth2Session_DetailFragmentDoc,
"\n fragment UserEmail_email on UserEmail {\n id\n email\n }\n": typeof types.UserEmail_EmailFragmentDoc,
"\n mutation RemoveEmail($id: ID!, $password: String) {\n removeEmail(input: { userEmailId: $id, password: $password }) {\n status\n\n user {\n id\n }\n }\n }\n": typeof types.RemoveEmailDocument,
@@ -87,7 +89,9 @@ const documents: Documents = {
"\n fragment EndOAuth2SessionButton_session on Oauth2Session {\n id\n\n userAgent {\n name\n model\n os\n deviceType\n }\n\n client {\n clientId\n clientName\n applicationType\n logoUri\n }\n }\n": types.EndOAuth2SessionButton_SessionFragmentDoc,
"\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n }\n }\n }\n": types.EndOAuth2SessionDocument,
"\n fragment BrowserSession_detail on BrowserSession {\n id\n createdAt\n finishedAt\n ...EndBrowserSessionButton_session\n userAgent {\n name\n model\n os\n }\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n": types.BrowserSession_DetailFragmentDoc,
"\n mutation SetCompatSessionName($sessionId: ID!, $displayName: String!) {\n setCompatSessionName(input: { compatSessionId: $sessionId, humanName: $displayName }) {\n status\n }\n }\n": types.SetCompatSessionNameDocument,
"\n fragment CompatSession_detail on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n humanName\n\n ...EndCompatSessionButton_session\n\n userAgent {\n name\n os\n model\n }\n\n ssoLogin {\n id\n redirectUri\n }\n }\n": types.CompatSession_DetailFragmentDoc,
"\n mutation SetOAuth2SessionName($sessionId: ID!, $displayName: String!) {\n setOauth2SessionName(input: { oauth2SessionId: $sessionId, humanName: $displayName }) {\n status\n }\n }\n": types.SetOAuth2SessionNameDocument,
"\n fragment OAuth2Session_detail on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n humanName\n\n ...EndOAuth2SessionButton_session\n\n userAgent {\n name\n model\n os\n }\n\n client {\n id\n clientId\n clientName\n clientUri\n logoUri\n }\n }\n": types.OAuth2Session_DetailFragmentDoc,
"\n fragment UserEmail_email on UserEmail {\n id\n email\n }\n": types.UserEmail_EmailFragmentDoc,
"\n mutation RemoveEmail($id: ID!, $password: String) {\n removeEmail(input: { userEmailId: $id, password: $password }) {\n status\n\n user {\n id\n }\n }\n }\n": types.RemoveEmailDocument,
@@ -195,10 +199,18 @@ export function graphql(source: "\n mutation EndOAuth2Session($id: ID!) {\n
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment BrowserSession_detail on BrowserSession {\n id\n createdAt\n finishedAt\n ...EndBrowserSessionButton_session\n userAgent {\n name\n model\n os\n }\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n"): typeof import('./graphql').BrowserSession_DetailFragmentDoc;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation SetCompatSessionName($sessionId: ID!, $displayName: String!) {\n setCompatSessionName(input: { compatSessionId: $sessionId, humanName: $displayName }) {\n status\n }\n }\n"): typeof import('./graphql').SetCompatSessionNameDocument;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment CompatSession_detail on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n humanName\n\n ...EndCompatSessionButton_session\n\n userAgent {\n name\n os\n model\n }\n\n ssoLogin {\n id\n redirectUri\n }\n }\n"): typeof import('./graphql').CompatSession_DetailFragmentDoc;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation SetOAuth2SessionName($sessionId: ID!, $displayName: String!) {\n setOauth2SessionName(input: { oauth2SessionId: $sessionId, humanName: $displayName }) {\n status\n }\n }\n"): typeof import('./graphql').SetOAuth2SessionNameDocument;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/

View File

@@ -1767,11 +1767,27 @@ export type BrowserSession_DetailFragment = (
& { ' $fragmentRefs'?: { 'EndBrowserSessionButton_SessionFragment': EndBrowserSessionButton_SessionFragment } }
) & { ' $fragmentName'?: 'BrowserSession_DetailFragment' };
export type SetCompatSessionNameMutationVariables = Exact<{
sessionId: Scalars['ID']['input'];
displayName: Scalars['String']['input'];
}>;
export type SetCompatSessionNameMutation = { __typename?: 'Mutation', setCompatSessionName: { __typename?: 'SetCompatSessionNamePayload', status: SetCompatSessionNameStatus } };
export type CompatSession_DetailFragment = (
{ __typename?: 'CompatSession', id: string, createdAt: string, deviceId?: string | null, finishedAt?: string | null, lastActiveIp?: string | null, lastActiveAt?: string | null, humanName?: string | null, userAgent?: { __typename?: 'UserAgent', name?: string | null, os?: string | null, model?: string | null } | null, ssoLogin?: { __typename?: 'CompatSsoLogin', id: string, redirectUri: string } | null }
& { ' $fragmentRefs'?: { 'EndCompatSessionButton_SessionFragment': EndCompatSessionButton_SessionFragment } }
) & { ' $fragmentName'?: 'CompatSession_DetailFragment' };
export type SetOAuth2SessionNameMutationVariables = Exact<{
sessionId: Scalars['ID']['input'];
displayName: Scalars['String']['input'];
}>;
export type SetOAuth2SessionNameMutation = { __typename?: 'Mutation', setOauth2SessionName: { __typename?: 'SetOAuth2SessionNamePayload', status: SetOAuth2SessionNameStatus } };
export type OAuth2Session_DetailFragment = (
{ __typename?: 'Oauth2Session', id: string, scope: string, createdAt: string, finishedAt?: string | null, lastActiveIp?: string | null, lastActiveAt?: string | null, humanName?: string | null, userAgent?: { __typename?: 'UserAgent', name?: string | null, model?: string | null, os?: string | null } | null, client: { __typename?: 'Oauth2Client', id: string, clientId: string, clientName?: string | null, clientUri?: string | null, logoUri?: string | null } }
& { ' $fragmentRefs'?: { 'EndOAuth2SessionButton_SessionFragment': EndOAuth2SessionButton_SessionFragment } }
@@ -2431,6 +2447,24 @@ export const EndOAuth2SessionDocument = new TypedDocumentString(`
}
}
`) as unknown as TypedDocumentString<EndOAuth2SessionMutation, EndOAuth2SessionMutationVariables>;
export const SetCompatSessionNameDocument = new TypedDocumentString(`
mutation SetCompatSessionName($sessionId: ID!, $displayName: String!) {
setCompatSessionName(
input: {compatSessionId: $sessionId, humanName: $displayName}
) {
status
}
}
`) as unknown as TypedDocumentString<SetCompatSessionNameMutation, SetCompatSessionNameMutationVariables>;
export const SetOAuth2SessionNameDocument = new TypedDocumentString(`
mutation SetOAuth2SessionName($sessionId: ID!, $displayName: String!) {
setOauth2SessionName(
input: {oauth2SessionId: $sessionId, humanName: $displayName}
) {
status
}
}
`) as unknown as TypedDocumentString<SetOAuth2SessionNameMutation, SetOAuth2SessionNameMutationVariables>;
export const RemoveEmailDocument = new TypedDocumentString(`
mutation RemoveEmail($id: ID!, $password: String) {
removeEmail(input: {userEmailId: $id, password: $password}) {
@@ -3094,6 +3128,50 @@ export const mockEndOAuth2SessionMutation = (resolver: GraphQLResponseResolver<E
options
)
/**
* @param resolver A function that accepts [resolver arguments](https://mswjs.io/docs/api/graphql#resolver-argument) and must always return the instruction on what to do with the intercepted request. ([see more](https://mswjs.io/docs/concepts/response-resolver#resolver-instructions))
* @param options Options object to customize the behavior of the mock. ([see more](https://mswjs.io/docs/api/graphql#handler-options))
* @see https://mswjs.io/docs/basics/response-resolver
* @example
* mockSetCompatSessionNameMutation(
* ({ query, variables }) => {
* const { sessionId, displayName } = variables;
* return HttpResponse.json({
* data: { setCompatSessionName }
* })
* },
* requestOptions
* )
*/
export const mockSetCompatSessionNameMutation = (resolver: GraphQLResponseResolver<SetCompatSessionNameMutation, SetCompatSessionNameMutationVariables>, options?: RequestHandlerOptions) =>
graphql.mutation<SetCompatSessionNameMutation, SetCompatSessionNameMutationVariables>(
'SetCompatSessionName',
resolver,
options
)
/**
* @param resolver A function that accepts [resolver arguments](https://mswjs.io/docs/api/graphql#resolver-argument) and must always return the instruction on what to do with the intercepted request. ([see more](https://mswjs.io/docs/concepts/response-resolver#resolver-instructions))
* @param options Options object to customize the behavior of the mock. ([see more](https://mswjs.io/docs/api/graphql#handler-options))
* @see https://mswjs.io/docs/basics/response-resolver
* @example
* mockSetOAuth2SessionNameMutation(
* ({ query, variables }) => {
* const { sessionId, displayName } = variables;
* return HttpResponse.json({
* data: { setOauth2SessionName }
* })
* },
* requestOptions
* )
*/
export const mockSetOAuth2SessionNameMutation = (resolver: GraphQLResponseResolver<SetOAuth2SessionNameMutation, SetOAuth2SessionNameMutationVariables>, options?: RequestHandlerOptions) =>
graphql.mutation<SetOAuth2SessionNameMutation, SetOAuth2SessionNameMutationVariables>(
'SetOAuth2SessionName',
resolver,
options
)
/**
* @param resolver A function that accepts [resolver arguments](https://mswjs.io/docs/api/graphql#resolver-argument) and must always return the instruction on what to do with the intercepted request. ([see more](https://mswjs.io/docs/concepts/response-resolver#resolver-instructions))
* @param options Options object to customize the behavior of the mock. ([see more](https://mswjs.io/docs/api/graphql#handler-options))