From 951322cdef56c31247c08b7cd8c4d9c140bd0b15 Mon Sep 17 00:00:00 2001 From: Kamil Kisiela Date: Fri, 29 Sep 2023 12:34:11 +0200 Subject: [PATCH] Replace "Operations" with "Insights" (#2982) --- .../[projectId]/[targetId]/checks.tsx | 4 +- .../[projectId]/[targetId]/explorer.tsx | 4 +- .../[targetId]/explorer/[typename].tsx | 4 +- .../[projectId]/[targetId]/history.tsx | 4 +- .../[projectId]/[targetId]/index.tsx | 8 +- .../{operations.tsx => insights.tsx} | 19 +- .../[operationName]/[operationHash].tsx | 25 +- .../client/[name].tsx | 20 +- .../schema-coordinate/[coordinate].tsx | 10 +- .../[projectId]/[targetId]/laboratory.tsx | 4 +- .../[projectId]/[targetId]/settings.tsx | 4 +- .../[organizationId]/[projectId]/index.tsx | 10 +- .../[projectId]/view/alerts.tsx | 4 +- .../[projectId]/view/policy.tsx | 4 +- .../[projectId]/view/settings.tsx | 4 +- .../web/app/pages/[organizationId]/index.tsx | 6 +- .../view/manage-subscription.tsx | 4 +- .../pages/[organizationId]/view/members.tsx | 8 +- .../pages/[organizationId]/view/policy.tsx | 4 +- .../pages/[organizationId]/view/settings.tsx | 4 +- .../[organizationId]/view/subscription.tsx | 4 +- .../pages/[organizationId]/view/support.tsx | 4 +- .../view/support/[ticketId].tsx | 12 +- .../src/components/layouts/organization.tsx | 30 +-- .../app/src/components/layouts/project.tsx | 24 +- .../web/app/src/components/layouts/target.tsx | 28 +-- .../src/components/target/explorer/common.tsx | 6 +- .../{operations => insights}/Fallback.tsx | 0 .../{operations => insights}/Filters.tsx | 0 .../target/{operations => insights}/List.tsx | 85 +++++-- .../target/{operations => insights}/Stats.tsx | 223 ++++++++++++------ .../target/{operations => insights}/utils.ts | 0 .../web/app/src/components/v2/sortable.tsx | 15 +- packages/web/app/src/components/v2/table.tsx | 12 +- .../pages/docs/features/usage-reporting.mdx | 8 +- 35 files changed, 365 insertions(+), 240 deletions(-) rename packages/web/app/pages/[organizationId]/[projectId]/[targetId]/{operations.tsx => insights.tsx} (91%) rename packages/web/app/pages/[organizationId]/[projectId]/[targetId]/{operations => insights}/[operationName]/[operationHash].tsx (89%) rename packages/web/app/pages/[organizationId]/[projectId]/[targetId]/{operations => insights}/client/[name].tsx (96%) rename packages/web/app/pages/[organizationId]/[projectId]/[targetId]/{operations => insights}/schema-coordinate/[coordinate].tsx (98%) rename packages/web/app/src/components/target/{operations => insights}/Fallback.tsx (100%) rename packages/web/app/src/components/target/{operations => insights}/Filters.tsx (100%) rename packages/web/app/src/components/target/{operations => insights}/List.tsx (88%) rename packages/web/app/src/components/target/{operations => insights}/Stats.tsx (82%) rename packages/web/app/src/components/target/{operations => insights}/utils.ts (100%) diff --git a/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/checks.tsx b/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/checks.tsx index bf77dda4b..89ed83dad 100644 --- a/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/checks.tsx +++ b/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/checks.tsx @@ -3,7 +3,7 @@ import NextLink from 'next/link'; import clsx from 'clsx'; import { useMutation, useQuery } from 'urql'; import { authenticated } from '@/components/authenticated-container'; -import { TargetLayout } from '@/components/layouts/target'; +import { Page, TargetLayout } from '@/components/layouts/target'; import { SchemaEditor } from '@/components/schema-editor'; import { ChangesBlock, labelize } from '@/components/target/history/errors-and-changes'; import { Subtitle, Title } from '@/components/ui/page'; @@ -766,7 +766,7 @@ function ChecksPageContent() { return ( <> - + ); } diff --git a/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/operations.tsx b/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/insights.tsx similarity index 91% rename from packages/web/app/pages/[organizationId]/[projectId]/[targetId]/operations.tsx rename to packages/web/app/pages/[organizationId]/[projectId]/[targetId]/insights.tsx index d9f51974d..70045d866 100644 --- a/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/operations.tsx +++ b/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/insights.tsx @@ -1,13 +1,13 @@ import { ReactElement, useState } from 'react'; import { useQuery } from 'urql'; import { authenticated } from '@/components/authenticated-container'; -import { TargetLayout } from '@/components/layouts/target'; +import { Page, TargetLayout } from '@/components/layouts/target'; import { ClientsFilterTrigger, OperationsFilterTrigger, -} from '@/components/target/operations/Filters'; -import { OperationsList } from '@/components/target/operations/List'; -import { OperationsStats } from '@/components/target/operations/Stats'; +} from '@/components/target/insights/Filters'; +import { OperationsList } from '@/components/target/insights/List'; +import { OperationsStats } from '@/components/target/insights/Stats'; import { Subtitle, Title } from '@/components/ui/page'; import { QueryError } from '@/components/ui/query-error'; import { EmptyList, MetaTitle, RadixSelect } from '@/components/v2'; @@ -44,7 +44,7 @@ function OperationsView({ <>
- Operations + Insights Observe GraphQL requests and see how the API is consumed.
@@ -76,6 +76,7 @@ function OperationsView({ operationsFilter={selectedOperations} clientNamesFilter={selectedClients} resolution={resolution} + dateRangeText={displayDateRangeLabel(dateRangeKey)} mode="operation-list" /> - + ); @@ -188,4 +189,4 @@ function OperationsPage(): ReactElement { export const getServerSideProps = withSessionProtection(); -export default authenticated(OperationsPage); +export default authenticated(InsightsPage); diff --git a/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/operations/[operationName]/[operationHash].tsx b/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/insights/[operationName]/[operationHash].tsx similarity index 89% rename from packages/web/app/pages/[organizationId]/[projectId]/[targetId]/operations/[operationName]/[operationHash].tsx rename to packages/web/app/pages/[organizationId]/[projectId]/[targetId]/insights/[operationName]/[operationHash].tsx index d96ef1108..3658b76f0 100644 --- a/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/operations/[operationName]/[operationHash].tsx +++ b/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/insights/[operationName]/[operationHash].tsx @@ -4,9 +4,9 @@ import { useQuery } from 'urql'; import { authenticated } from '@/components/authenticated-container'; import { Section } from '@/components/common'; import { GraphQLHighlight } from '@/components/common/GraphQLSDLBlock'; -import { TargetLayout } from '@/components/layouts/target'; -import { ClientsFilterTrigger } from '@/components/target/operations/Filters'; -import { OperationsStats } from '@/components/target/operations/Stats'; +import { Page, TargetLayout } from '@/components/layouts/target'; +import { ClientsFilterTrigger } from '@/components/target/insights/Filters'; +import { OperationsStats } from '@/components/target/insights/Stats'; import { Subtitle, Title } from '@/components/ui/page'; import { QueryError } from '@/components/ui/query-error'; import { EmptyList, MetaTitle, RadixSelect } from '@/components/v2'; @@ -87,7 +87,7 @@ function OperationView({
{operationName} - Performance of individual GraphQL operation + Insights of individual GraphQL operation
- + ); } export const getServerSideProps = withSessionProtection(); -export default authenticated(OperationPage); +export default authenticated(OperationInsightsPage); diff --git a/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/operations/client/[name].tsx b/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/insights/client/[name].tsx similarity index 96% rename from packages/web/app/pages/[organizationId]/[projectId]/[targetId]/operations/client/[name].tsx rename to packages/web/app/pages/[organizationId]/[projectId]/[targetId]/insights/client/[name].tsx index 43f682cd2..32829c0b4 100644 --- a/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/operations/client/[name].tsx +++ b/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/insights/client/[name].tsx @@ -7,7 +7,7 @@ import { ActivityIcon, BookIcon, GlobeIcon, HistoryIcon } from 'lucide-react'; import AutoSizer from 'react-virtualized-auto-sizer'; import { useQuery } from 'urql'; import { authenticated } from '@/components/authenticated-container'; -import { TargetLayout } from '@/components/layouts/target'; +import { Page, TargetLayout } from '@/components/layouts/target'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Subtitle, Title } from '@/components/ui/page'; import { QueryError } from '@/components/ui/query-error'; @@ -289,7 +289,7 @@ function ClientView(props: { className="text-orange-500 hover:underline hover:underline-offset-2 hover:text-orange-500" href={{ pathname: - '/[organizationId]/[projectId]/[targetId]/operations/[operationName]/[operationHash]', + '/[organizationId]/[projectId]/[targetId]/insights/[operationName]/[operationHash]', query: { organizationId: props.organizationCleanId, projectId: props.projectCleanId, @@ -350,8 +350,8 @@ function ClientView(props: { ); } -const ClientOperationsPageQuery = graphql(` - query ClientOperationsPageQuery($organizationId: ID!, $projectId: ID!, $targetId: ID!) { +const ClientInsightsPageQuery = graphql(` + query ClientInsightsPageQuery($organizationId: ID!, $projectId: ID!, $targetId: ID!) { organizations { ...TargetLayout_OrganizationConnectionFragment } @@ -381,10 +381,10 @@ const ClientOperationsPageQuery = graphql(` } `); -function ClientOperationsPageContent({ clientName }: { clientName: string }) { +function ClientInsightsPageContent({ clientName }: { clientName: string }) { const router = useRouteSelector(); const [query] = useQuery({ - query: ClientOperationsPageQuery, + query: ClientInsightsPageQuery, variables: { organizationId: router.organizationId, projectId: router.projectId, @@ -406,7 +406,7 @@ function ClientOperationsPageContent({ clientName }: { clientName: string }) { return ( - + ); } export const getServerSideProps = withSessionProtection(); -export default authenticated(ClientOperationsPage); +export default authenticated(ClientInsightsPage); diff --git a/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/operations/schema-coordinate/[coordinate].tsx b/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/insights/schema-coordinate/[coordinate].tsx similarity index 98% rename from packages/web/app/pages/[organizationId]/[projectId]/[targetId]/operations/schema-coordinate/[coordinate].tsx rename to packages/web/app/pages/[organizationId]/[projectId]/[targetId]/insights/schema-coordinate/[coordinate].tsx index 1aad65d49..be3f36cb1 100644 --- a/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/operations/schema-coordinate/[coordinate].tsx +++ b/packages/web/app/pages/[organizationId]/[projectId]/[targetId]/insights/schema-coordinate/[coordinate].tsx @@ -7,7 +7,7 @@ import { ActivityIcon, BookIcon, GlobeIcon, TabletSmartphoneIcon } from 'lucide- import AutoSizer from 'react-virtualized-auto-sizer'; import { useQuery } from 'urql'; import { authenticated } from '@/components/authenticated-container'; -import { TargetLayout } from '@/components/layouts/target'; +import { Page, TargetLayout } from '@/components/layouts/target'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Subtitle, Title } from '@/components/ui/page'; import { QueryError } from '@/components/ui/query-error'; @@ -111,7 +111,7 @@ function SchemaCoordinateView(props: {
{props.coordinate} - Detailed view of schema coordinate usage + Schema coordinate insights
- Go to: - - } id="page" - size="medium" + className="w-16" type="number" defaultValue={tableInstance.getState().pagination.pageIndex + 1} onChange={e => { diff --git a/packages/web/app/src/components/target/operations/Stats.tsx b/packages/web/app/src/components/target/insights/Stats.tsx similarity index 82% rename from packages/web/app/src/components/target/operations/Stats.tsx rename to packages/web/app/src/components/target/insights/Stats.tsx index 27cf95d4d..51c0aed0d 100644 --- a/packages/web/app/src/components/target/operations/Stats.tsx +++ b/packages/web/app/src/components/target/insights/Stats.tsx @@ -1,11 +1,21 @@ import { ReactElement, useCallback, useMemo, useState } from 'react'; +import { differenceInMilliseconds } from 'date-fns'; import ReactECharts from 'echarts-for-react'; -import { ChevronUp } from 'lucide-react'; +import { + ActivityIcon, + BookIcon, + ChevronUp, + FrownIcon, + GaugeIcon, + GlobeIcon, + PercentIcon, + SmileIcon, +} from 'lucide-react'; import AutoSizer from 'react-virtualized-auto-sizer'; import { useQuery } from 'urql'; import { Section } from '@/components/common'; import { Button } from '@/components/ui/button'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { CHART_PRIMARY_COLOR } from '@/constants'; import { FragmentType, graphql, useFragment } from '@/gql'; import { DateRangeInput } from '@/graphql'; @@ -19,7 +29,6 @@ import { useFormattedThroughput, useRouteSelector, } from '@/lib/hooks'; -import { cn } from '@/lib/utils'; import { useChartStyles } from '@/utils'; import { OperationsFallback } from './Fallback'; import { createEmptySeries, resolutionToMilliseconds } from './utils'; @@ -53,41 +62,50 @@ const Stats_GeneralOperationsStatsQuery = graphql(` } `); -const classes = { - root: cn('text-center'), - value: cn('font-normal text-3xl text-gray-900 dark:text-white'), - title: cn('text-sm leading-relaxed'), -}; - -function RequestsStats({ requests = 0 }: { requests?: number }): ReactElement { +function RequestsStats({ + requests = 0, +}: { + requests?: number; + dateRangeText: string; +}): ReactElement { const value = useFormattedNumber(requests); return ( -
-

{value}

-

Requests

-
+ + + Requests + + + +
{value}
+

Total requests served

+
+
); } -function UniqueOperationsStats({ operations = 0 }: { operations?: number }): ReactElement { +function UniqueOperationsStats({ + operations = 0, + dateRangeText, +}: { + operations?: number; + dateRangeText: string; +}): ReactElement { const value = useFormattedNumber(operations); return ( - -
- - -

{value}

-

Unique Operations

-
- - Count of unique operations that have been requested, taking into account applied - filters. - -
-
-
+ + + Operations + + + +
{value}
+

+ Distinct GraphQL operations in {dateRangeText} +

+
+
); } @@ -97,45 +115,60 @@ function OperationRelativeFrequency({ }: { allOperationRequests: number; operationRequests: number; + dateRangeText: string; }): ReactElement { const rate = allOperationRequests ? `${toDecimal((operationRequests * 100) / allOperationRequests)}%` : '-'; return ( - -
- - -

{rate}

-

Total traffic ratio

-
- - The proportion of traffic accounted for by this operation, taking into account applied - filters, in relation to the total target traffic. - -
-
-
+ + + Relative Request Frequency + + + +
{rate}
+

The impact on the overall API traffic

+
+
); } -function PercentileStats({ value, title }: { value?: number; title: string }): ReactElement { +function PercentileStats({ + value, + percentile, + dateRangeText, +}: { + value?: number; + percentile: number; + dateRangeText: string; +}): ReactElement { const formatted = useFormattedDuration(value); return ( -
-

{formatted}

-

{title}

-
+ + + p{percentile} + + + +
{formatted}
+

+ Latency p{percentile} in {dateRangeText} +

+
+
); } function RPM({ period, + dateRangeText, requests = 0, }: { requests?: number; + dateRangeText: string; period: { from: string; to: string; @@ -143,22 +176,31 @@ function RPM({ }): ReactElement { const throughput = useFormattedThroughput({ requests, - window: new Date(period.to).getTime() - new Date(period.from).getTime(), + window: differenceInMilliseconds(new Date(period.to), new Date(period.from)), }); + return ( -
-

{throughput}

-

RPM

-
+ + + Requests per minute + + + +
{throughput}
+

Throughput in {dateRangeText}

+
+
); } function SuccessRateStats({ requests = 0, totalFailures = 0, + dateRangeText, }: { requests?: number; totalFailures?: number; + dateRangeText: string; }): ReactElement { const rate = requests || totalFailures @@ -166,27 +208,45 @@ function SuccessRateStats({ : '-'; return ( -
-

{rate}

-

Success rate

-
+ + + + Success rate + + + + +
{rate}
+

Successful requests in {dateRangeText}

+
+
); } function FailureRateStats({ requests = 0, totalFailures = 0, + dateRangeText, }: { requests?: number; totalFailures?: number; + dateRangeText: string; }): ReactElement { const rate = requests || totalFailures ? `${toDecimal((totalFailures * 100) / requests)}%` : '-'; return ( -
-

{rate}

-

Failure rate

-
+ + + + Failure rate + + + + +
{rate}
+

Failed requests in {dateRangeText}

+
+
); } @@ -497,7 +557,7 @@ function ClientsStats(props: { (ev: { componentType: string; targetType: string; value: string }) => { if (ev.componentType === 'yAxis' && ev.targetType === 'axisLabel') { void router.push({ - pathname: '/[organizationId]/[projectId]/[targetId]/operations/client/[name]', + pathname: '/[organizationId]/[projectId]/[targetId]/insights/client/[name]', query: { organizationId: router.organizationId, projectId: router.projectId, @@ -957,6 +1017,7 @@ export function OperationsStats({ clientNamesFilter, resolution, mode, + dateRangeText, }: { organization: string; project: string; @@ -965,6 +1026,7 @@ export function OperationsStats({ from: string; to: string; }; + dateRangeText: string; resolution: number; operationsFilter: string[]; clientNamesFilter: Array; @@ -1002,31 +1064,54 @@ export function OperationsStats({ const operationsStats = query.data?.operationsStats; const allOperationsStats = query.data?.allOperations; + dateRangeText = dateRangeText.toLowerCase(); return (
-
- - +
+ + {mode === 'operation-list' ? ( - + ) : ( )} + + + - - -
diff --git a/packages/web/app/src/components/target/operations/utils.ts b/packages/web/app/src/components/target/insights/utils.ts similarity index 100% rename from packages/web/app/src/components/target/operations/utils.ts rename to packages/web/app/src/components/target/insights/utils.ts diff --git a/packages/web/app/src/components/v2/sortable.tsx b/packages/web/app/src/components/v2/sortable.tsx index 14792ba79..657d82721 100644 --- a/packages/web/app/src/components/v2/sortable.tsx +++ b/packages/web/app/src/components/v2/sortable.tsx @@ -1,5 +1,4 @@ import { ComponentProps, ReactElement, ReactNode } from 'react'; -import { clsx } from 'clsx'; import { Tooltip } from '@/components/v2/tooltip'; import { TriangleUpIcon } from '@radix-ui/react-icons'; import { SortDirection } from '@tanstack/react-table'; @@ -23,15 +22,13 @@ export function Sortable({ return ( - ); diff --git a/packages/web/app/src/components/v2/table.tsx b/packages/web/app/src/components/v2/table.tsx index f456a6701..b3d74e66b 100644 --- a/packages/web/app/src/components/v2/table.tsx +++ b/packages/web/app/src/components/v2/table.tsx @@ -1,9 +1,9 @@ import { ComponentProps, ReactElement } from 'react'; -import clsx from 'clsx'; +import { cn } from '@/lib/utils'; function Table({ children, className, ...props }: ComponentProps<'table'>): ReactElement { return ( - +
{children}
); @@ -31,7 +31,7 @@ function TFoot({ children, ...props }: ComponentProps<'tfoot'>): ReactElement { function Th({ children, className, align = 'left', ...props }: ComponentProps<'th'>): ReactElement { return ( - + {children} ); @@ -40,7 +40,7 @@ function Th({ children, className, align = 'left', ...props }: ComponentProps<'t function Tr({ children, className, ...props }: ComponentProps<'tr'>): ReactElement { return ( {children} @@ -51,8 +51,8 @@ function Tr({ children, className, ...props }: ComponentProps<'tr'>): ReactEleme function Td({ children, className, ...props }: ComponentProps<'td'>): ReactElement { return ( -### Operations Overview +### Insights A list of all the GraphQL operations executed by your consumers, their performance metrics and total count. By clicking on a specific query, you'll be able to see the full list of fields and arguments used in the operation. - +