Skip to content

Commit

Permalink
feat: add end call summary
Browse files Browse the repository at this point in the history
  • Loading branch information
zwaardje committed Jan 9, 2025
1 parent d4d0346 commit 02a3c2d
Show file tree
Hide file tree
Showing 23 changed files with 893 additions and 110 deletions.
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import { Icon } from '@stream-io/video-react-sdk';
import { useState, useCallback, useEffect } from 'react';
import { Feedback } from '../Feedback/Feedback';
import { useState, useCallback, useEffect, useMemo } from 'react';
import { Feedback } from './Feedback';
import clsx from 'clsx';
import Link from 'next/link';

import { Card } from './Card';
import { Badge, LinkBadge, Status } from './Badge';
import { NetworkStatus } from './NetworkStatus';
import { Rating } from './Rating';
import { Header } from './Header';
import { Recordings } from './Recordings';
import {
useStreamVideoClient,
useCall,
useCallStateHooks,
EdgeResponse,
useCall,
} from '@stream-io/video-react-sdk';

import { Notification } from './Notification';
import { CallRecordings } from '../CallRecordings';

export interface EndCallSummaryViewProps {
rejoin: () => void;
startNewCall: () => void;
joinTime?: Date;
}

const toStatus = (config: {
Expand All @@ -39,23 +42,63 @@ const toCountryName = (isoCountryCode: string) => {
return regionNames.of(isoCountryCode);
};

const toEdgeStatus = (edge: EdgeResponse) => {
if (edge.green === 1) return 'green';
if (edge.yellow === 1) return 'yellow';

return 'red';
};

const toNetworkStatus = (quality: number) => {
if (quality >= 80) return 'green';
if (quality >= 40) return 'yellow';

return 'red';
};

const toNetworkNotification = (
quality: number,
): {
message: string;
type: 'success' | 'info' | 'error' | 'caution';
} => {
if (quality >= 80)
return {
message: 'Your Network is Stable.',
type: 'success',
};
if (quality >= 40)
return {
message: 'Your Network is Average.',
type: 'caution',
};

return {
message: 'Your Network is Poor.',
type: 'error',
};
};

export function EndCallSummaryView({
rejoin,
startNewCall,
joinTime,
}: EndCallSummaryViewProps) {
const [rating, setRating] = useState<{
current: number;
maxAmount: number;
success: boolean;
}>({ current: 0, maxAmount: 5, success: false });
const [callStats, setCallStats] = useState<any | undefined>(undefined);
const [showRecordings, setShowRecordings] = useState(false);
const [edges, setEdges] = useState<EdgeResponse[] | undefined>(undefined);
const client = useStreamVideoClient();
const call = useCall();

const { useCallStatsReport } = useCallStateHooks();
const { useCallStatsReport, useCallStartedAt } = useCallStateHooks();
const callStatsReport = useCallStatsReport();
const startedAt = useCallStartedAt();
const call = useCall();

const client = useStreamVideoClient();
const { publisherStats } = callStatsReport || {};

const handleSetRating = useCallback(
Expand All @@ -75,15 +118,40 @@ export function EndCallSummaryView({
});
}, [client]);

const latencyComparison = {
lowBound: 75,
highBound: 400,
};
useEffect(() => {
if (!client || !call) return;

async function fetchCallStats() {
const res = await client?.queryCallStats({
filter_conditions: {
call_cid: call?.cid,
},
limit: 1,
});
return res;
}

const response = fetchCallStats();
response.then((res) => {
setCallStats(res?.reports[0]);
});
}, [client, call]);

const timeToConnect = useMemo(() => {
if (!joinTime || !startedAt) return null;
const timeDifference = startedAt.getTime() - joinTime.getTime();
const differenceDate = new Date(timeDifference);
return differenceDate.getMilliseconds();
}, [joinTime, startedAt]);

const handleSubmitSuccess = useCallback(() => {
setRating({ current: 0, maxAmount: 5, success: true });
}, []);

const networkNotification = toNetworkNotification(
callStats?.quality_score || 0,
);

return (
<div className="rd__leave">
<Header
Expand All @@ -93,41 +161,46 @@ export function EndCallSummaryView({
/>
<div className="rd__leave--row rd__leave--row-first">
<Card
className="rd__leave--row-quality"
title="Call Quality"
tooltip="Combines real-time call quality metrics like latency, packet loss, and jitter to help diagnose and improve video call "
>
<Badge
status={toStatus({
...latencyComparison,
value: publisherStats?.averageJitterInMs || 0,
lowBound: 100,
highBound: 0,
value: callStats?.quality_score || 0,
})}
variant="large"
>
{publisherStats?.averageJitterInMs || 0}ms
{callStats?.quality_score || 0}%
</Badge>
</Card>
<Card
className="rd__leave--row-time-to-connect"
title="Time to connect"
tooltip="The time it takes to establish a connection after a request."
>
<Badge
status={toStatus({
lowBound: 0.5,
highBound: 30,
value: 0.4,
lowBound: 1000,
highBound: 3000,
value: timeToConnect || 0,
})}
variant="large"
>
0.4s
{timeToConnect}ms
</Badge>
</Card>
<Card
className="rd__leave--row-latency-average"
title="Latency average"
tooltip="The time it takes for data to travel between the sender and receiver."
>
<Badge
status={toStatus({
...latencyComparison,
lowBound: 75,
highBound: 400,
value: publisherStats?.averageRoundTripTimeInMs || 0,
})}
variant="large"
Expand All @@ -136,24 +209,39 @@ export function EndCallSummaryView({
</Badge>
</Card>
<Card
className="rd__leave--row-video-codex"
title="Video Codex"
tooltip="Format used to compress and encode video for efficient storage and playback."
contentVariant="row"
>
<Badge variant="large">{publisherStats?.codec || 'Unknown'}</Badge>
<Badge variant="large">
{publisherStats?.codec?.replace('video/', '') || 'Unknown'}
</Badge>
</Card>
</div>

<div className="rd__leave--row rd__leave--row-second">
<Card
className="rd__leave--row-user-network"
title="User Network"
tooltip="The network and device a user is using, often the most common cause of poor audio or video quality."
>
<Notification message="Your Network is Stable." variant="success" />
<NetworkStatus status={80}>Network</NetworkStatus>
<NetworkStatus status={20}>Device</NetworkStatus>
<Notification
message={networkNotification.message}
variant={networkNotification.type}
/>
<NetworkStatus
status={toNetworkStatus(callStats?.quality_score || 0)}
>
Network
</NetworkStatus>
<NetworkStatus status={'green'}>Device</NetworkStatus>
</Card>
<Card title="Video & Audio products" link="https://getstream.io/video/">
<Card
className="rd__leave--row-rooms"
title="Video & Audio products"
link="https://getstream.io/video/"
>
<div className="rd__leave--rooms">
<LinkBadge
className="rd__leave--rooms-link"
Expand Down Expand Up @@ -195,7 +283,7 @@ export function EndCallSummaryView({
</Card>
<Card
className={clsx(
'rd__leave--review-container',
'rd__leave--review-container rd__leave--row-review',
rating.success && 'rd__leave--review-container-success',
)}
>
Expand Down Expand Up @@ -223,41 +311,48 @@ export function EndCallSummaryView({
</div>

<div className="rd__leave--row rd__leave--row-third">
<Card variant="parent">
<Card className="rd__leave--row-edge-networks" variant="parent">
<Card
title="Edge networks used"
tooltip="Displays the quality of the edge network being used for this call."
>
<NetworkStatus status={80}>
<NetworkStatus status={'green'}>
<Badge variant="small">
<Icon icon="language" />
Amsterdam
</Badge>
</NetworkStatus>
<NetworkStatus status={100}>
<NetworkStatus status={'green'}>
<Badge variant="small">
<Icon icon="language" />
Boston
</Badge>
</NetworkStatus>
</Card>
<Card
className="rd__leave--row-edge-servers"
title="Edge servers available"
tooltip="Shows available edge servers on Stream’s network that aren’t currently in use."
>
<div className="rd__edge-server">
{edges?.map((edge) => (
<NetworkStatus status={90}>
<NetworkStatus key={edge.id} status={toEdgeStatus(edge)}>
<Badge variant="small">
<Icon icon="language" />
{toCountryName(edge.country_iso_code)}
<span className="rd__edge-server--country">
{toCountryName(edge.country_iso_code)}
</span>
</Badge>
</NetworkStatus>
))}
</div>
</Card>
</Card>
<Card title="SDK's" link="https://getstream.io/video/">
<Card
className="rd__leave---row-sdks"
title="SDK's"
link="https://getstream.io/video/"
>
<div className="rd__sdks">
<LinkBadge
className="rd__sdks--link"
Expand Down Expand Up @@ -309,7 +404,7 @@ export function EndCallSummaryView({
</LinkBadge>
</div>
</Card>
<Card className="rd__build-and-ship">
<Card className="rd__build-and-ship rd__leave--row-build-and-ship">
<div className="rd__build-and-ship--content">
<div className="rd__build-and-ship--content-text">
<h2 className="rd__build-and-ship--title">
Expand All @@ -322,7 +417,9 @@ export function EndCallSummaryView({
</div>
</div>
<div className="rd__build-and-ship--button-container">
<button className="rd__build-and-ship--button">Contact Us</button>
<Link href="/contact/" className="rd__build-and-ship--button">
Contact Us
</Link>
</div>
</Card>
</div>
Expand All @@ -336,7 +433,12 @@ export function EndCallSummaryView({
/>
<Feedback
className="rd__modal--content"
// submitSuccess={handleSubmitSuccess}
submitSuccess={handleSubmitSuccess}
rating={rating.current}
callData={JSON.stringify({ callStats, publisherStats })}
onClose={() =>
setRating({ current: 0, maxAmount: 5, success: false })
}
/>
</div>
)}
Expand All @@ -347,7 +449,7 @@ export function EndCallSummaryView({
onClick={() => setShowRecordings(false)}
/>
<div className="rd__modal--content">
<CallRecordings />
<Recordings onClose={() => setShowRecordings(false)} />
</div>
</div>
)}
Expand Down
Loading

0 comments on commit 02a3c2d

Please sign in to comment.