Skip to content

Commit

Permalink
Add more information to session detail page (#1659)
Browse files Browse the repository at this point in the history
* rename `session` route to `browser-sessions`

* add session detail route

* stubbed route with userid

* get session and display as session tile on session detail page

* improve error message

* useMemo instead of ref

* oauth session detail page

* compat session detail

* link to session detail from compat and oauth sessions
  • Loading branch information
Kerry committed Aug 31, 2023
1 parent 21d3d3a commit 0c267c0
Show file tree
Hide file tree
Showing 10 changed files with 349 additions and 16 deletions.
11 changes: 8 additions & 3 deletions frontend/src/components/CompatSession.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { atomFamily } from "jotai/utils";
import { atomWithMutation } from "jotai-urql";
import { useTransition } from "react";

import { Link } from "../Router";
import { FragmentType, graphql, useFragment } from "../gql";

import { Session } from "./Session";
Expand Down Expand Up @@ -47,7 +48,7 @@ const END_SESSION_MUTATION = graphql(/* GraphQL */ `
}
`);

const endCompatSessionFamily = atomFamily((id: string) => {
export const endCompatSessionFamily = atomFamily((id: string) => {
const endCompatSession = atomWithMutation(END_SESSION_MUTATION);

// A proxy atom which pre-sets the id variable in the mutation
Expand All @@ -59,7 +60,7 @@ const endCompatSessionFamily = atomFamily((id: string) => {
return endCompatSessionAtom;
});

const simplifyUrl = (url: string): string => {
export const simplifyUrl = (url: string): string => {
let parsed;
try {
parsed = new URL(url);
Expand Down Expand Up @@ -93,14 +94,18 @@ const CompatSession: React.FC<{
});
};

const sessionName = (
<Link route={{ type: "session", id: data.deviceId }}>{data.deviceId}</Link>
);

const clientName = data.ssoLogin?.redirectUri
? simplifyUrl(data.ssoLogin.redirectUri)
: undefined;

return (
<Session
id={data.id}
name={data.deviceId}
name={sessionName}
createdAt={data.createdAt}
finishedAt={data.finishedAt || undefined}
clientName={clientName}
Expand Down
13 changes: 9 additions & 4 deletions frontend/src/components/OAuth2Session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { atomFamily } from "jotai/utils";
import { atomWithMutation } from "jotai-urql";
import { useTransition } from "react";

import { Link } from "../Router";
import { FragmentType, graphql, useFragment } from "../gql";
import { getDeviceIdFromScope } from "../utils/deviceIdFromScope";

Expand All @@ -39,7 +40,7 @@ export const OAUTH2_SESSION_FRAGMENT = graphql(/* GraphQL */ `
}
`);

type Oauth2SessionType = {
export type Oauth2SessionType = {
id: string;
scope: string;
createdAt: string;
Expand All @@ -64,7 +65,7 @@ const END_SESSION_MUTATION = graphql(/* GraphQL */ `
}
`);

const endSessionFamily = atomFamily((id: string) => {
export const endSessionFamily = atomFamily((id: string) => {
const endSession = atomWithMutation(END_SESSION_MUTATION);

// A proxy atom which pre-sets the id variable in the mutation
Expand Down Expand Up @@ -96,12 +97,16 @@ const OAuth2Session: React.FC<Props> = ({ session }) => {
});
};

const sessionName = getDeviceIdFromScope(data.scope);
const deviceId = getDeviceIdFromScope(data.scope);

const name = deviceId && (
<Link route={{ type: "session", id: deviceId }}>{deviceId}</Link>
);

return (
<Session
id={data.id}
name={sessionName}
name={name}
createdAt={data.createdAt}
finishedAt={data.finishedAt || undefined}
clientName={data.client.clientName}
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/Session/Session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

import { H6, Body } from "@vector-im/compound-web";
import { ReactNode } from "react";

import Block from "../Block";
import DateTime from "../DateTime";
Expand All @@ -25,7 +26,7 @@ const SessionMetadata: React.FC<React.ComponentProps<typeof Body>> = (

export type SessionProps = {
id: string;
name?: string;
name?: string | ReactNode;
createdAt: string;
finishedAt?: string;
clientName?: string;
Expand Down
96 changes: 96 additions & 0 deletions frontend/src/components/SessionDetail/CompatSessionDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { H3, Button } from "@vector-im/compound-web";
import { useSetAtom } from "jotai";
import { useTransition } from "react";

import { FragmentType, useFragment } from "../../gql";
import BlockList from "../BlockList/BlockList";
import {
COMPAT_SESSION_FRAGMENT,
endCompatSessionFamily,
simplifyUrl,
} from "../CompatSession";
import DateTime from "../DateTime";

import SessionDetails from "./SessionDetails";

type Props = {
session: FragmentType<typeof COMPAT_SESSION_FRAGMENT>;
};

const CompatSessionDetail: React.FC<Props> = ({ session }) => {
const [pending, startTransition] = useTransition();
const data = useFragment(COMPAT_SESSION_FRAGMENT, session);
const endSession = useSetAtom(endCompatSessionFamily(data.id));

// @TODO(kerrya) make this wait for session refresh properly
// https://github.com/matrix-org/matrix-authentication-service/issues/1533
const onSessionEnd = (): void => {
startTransition(() => {
endSession();
});
};

const finishedAt = data.finishedAt
? [{ label: "Finished", value: <DateTime datetime={data.createdAt} /> }]
: [];
const sessionDetails = [
{ label: "ID", value: <code>{data.id}</code> },
{ label: "Device ID", value: <code>{data.deviceId}</code> },
{ label: "Signed in", value: <DateTime datetime={data.createdAt} /> },
...finishedAt,
];

const clientName = data.ssoLogin?.redirectUri
? simplifyUrl(data.ssoLogin.redirectUri)
: undefined;

const clientDetails = [
{ label: "Name", value: clientName },
{
label: "Uri",
value: (
<a target="_blank" href={data.ssoLogin?.redirectUri}>
{data.ssoLogin?.redirectUri}
</a>
),
},
];

return (
<div>
<BlockList>
<H3>{data.deviceId || data.id}</H3>
<SessionDetails title="Session" details={sessionDetails} />
<SessionDetails title="Client" details={clientDetails} />
{!data.finishedAt && (
<Button
kind="destructive"
size="sm"
onClick={onSessionEnd}
disabled={pending}
>
{/* @TODO(kerrya) put this back after pending state works properly */}
{/* { pending && <LoadingSpinner />} */}
End session
</Button>
)}
</BlockList>
</div>
);
};

export default CompatSessionDetail;
113 changes: 113 additions & 0 deletions frontend/src/components/SessionDetail/OAuth2SessionDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { H3, Button } from "@vector-im/compound-web";
import { useSetAtom } from "jotai";
import { useTransition } from "react";

import { FragmentType, useFragment } from "../../gql";
import { getDeviceIdFromScope } from "../../utils/deviceIdFromScope";
import BlockList from "../BlockList/BlockList";
import DateTime from "../DateTime";
import {
OAUTH2_SESSION_FRAGMENT,
Oauth2SessionType,
endSessionFamily,
} from "../OAuth2Session";

import SessionDetails from "./SessionDetails";

type Props = {
session: FragmentType<typeof OAUTH2_SESSION_FRAGMENT>;
};

const OAuth2SessionDetail: React.FC<Props> = ({ session }) => {
const [pending, startTransition] = useTransition();
const data = useFragment(
OAUTH2_SESSION_FRAGMENT,
session,
) as Oauth2SessionType;
const endSession = useSetAtom(endSessionFamily(data.id));

// @TODO(kerrya) make this wait for session refresh properly
// https://github.com/matrix-org/matrix-authentication-service/issues/1533
const onSessionEnd = (): void => {
startTransition(() => {
endSession();
});
};

const deviceId = getDeviceIdFromScope(data.scope);

const scopes = data.scope.split(" ");

const finishedAt = data.finishedAt
? [{ label: "Finished", value: <DateTime datetime={data.createdAt} /> }]
: [];
const sessionDetails = [
{ label: "ID", value: <code>{data.id}</code> },
{ label: "Device ID", value: <code>{deviceId}</code> },
{ label: "Signed in", value: <DateTime datetime={data.createdAt} /> },
...finishedAt,
{
label: "Scopes",
value: (
<>
{scopes.map((scope) => (
<p>
<code key={scope}>{scope}</code>
</p>
))}
</>
),
},
];

const clientDetails = [
{ label: "Name", value: data.client.clientName },
{ label: "ID", value: <code>{data.client.clientId}</code> },
{
label: "Uri",
value: (
<a target="_blank" href={data.client.clientUri}>
{data.client.clientUri}
</a>
),
},
];

return (
<div>
<BlockList>
<H3>{deviceId || data.id}</H3>
<SessionDetails title="Session" details={sessionDetails} />
<SessionDetails title="Client" details={clientDetails} />
{!data.finishedAt && (
<Button
kind="destructive"
size="sm"
onClick={onSessionEnd}
disabled={pending}
>
{/* @TODO(kerrya) put this back after pending state works properly */}
{/* { pending && <LoadingSpinner />} */}
End session
</Button>
)}
</BlockList>
</div>
);
};

export default OAuth2SessionDetail;
9 changes: 5 additions & 4 deletions frontend/src/components/SessionDetail/SessionDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ import { useMemo } from "react";

import { Link } from "../../Router";
import { graphql } from "../../gql/gql";
import CompatSession from "../CompatSession";
import OAuth2Session from "../OAuth2Session";

import CompatSessionDetail from "./CompatSessionDetail";
import OAuth2SessionDetail from "./OAuth2SessionDetail";

const QUERY = graphql(/* GraphQL */ `
query SessionQuery($userId: ID!, $deviceId: String!) {
Expand Down Expand Up @@ -70,9 +71,9 @@ const SessionDetail: React.FC<{
const sessionType = session.__typename;

if (sessionType === "Oauth2Session") {
return <OAuth2Session session={session} />;
return <OAuth2SessionDetail session={session} />;
} else {
return <CompatSession session={session} />;
return <CompatSessionDetail session={session} />;
}
};

Expand Down
36 changes: 36 additions & 0 deletions frontend/src/components/SessionDetail/SessionDetails.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* Copyright 2023 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

.list {
display: flex;
flex-direction: column;
margin-top: var(--cpd-space-1x);
gap: var(--cpd-space-1x);
}

.detail-row {
display: flex;
flex-direction: row;
gap: var(--cpd-space-4x);
}

.detail-label {
flex: 0 0 20%;
color: var(--cpd-color-text-secondary);
}

.detail-value {
overflow-wrap: anywhere;
}
Loading

0 comments on commit 0c267c0

Please sign in to comment.