mirror of
https://github.com/graphql-hive/console
synced 2026-05-24 01:28:32 +00:00
Replace "Operations" with "Insights" (#2982)
This commit is contained in:
parent
be37a9807e
commit
951322cdef
35 changed files with 365 additions and 240 deletions
|
|
@ -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 (
|
||||
<>
|
||||
<TargetLayout
|
||||
value="checks"
|
||||
page={Page.Checks}
|
||||
className="h-full"
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
currentProject={currentProject ?? null}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ReactElement } 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 { SchemaExplorerFilter } from '@/components/target/explorer/filter';
|
||||
import { GraphQLObjectTypeComponent } from '@/components/target/explorer/object-type';
|
||||
import {
|
||||
|
|
@ -163,7 +163,7 @@ function ExplorerPageContent() {
|
|||
|
||||
return (
|
||||
<TargetLayout
|
||||
value="explorer"
|
||||
page={Page.Explorer}
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
currentProject={currentProject ?? null}
|
||||
me={me ?? null}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useQuery } from 'urql';
|
||||
import { authenticated } from '@/components/authenticated-container';
|
||||
import { TargetLayout } from '@/components/layouts/target';
|
||||
import { Page, TargetLayout } from '@/components/layouts/target';
|
||||
import { GraphQLEnumTypeComponent } from '@/components/target/explorer/enum-type';
|
||||
import { SchemaExplorerFilter } from '@/components/target/explorer/filter';
|
||||
import { GraphQLInputObjectTypeComponent } from '@/components/target/explorer/input-object-type';
|
||||
|
|
@ -194,7 +194,7 @@ function TypeExplorerPageContent({ typename }: { typename: string }) {
|
|||
|
||||
return (
|
||||
<TargetLayout
|
||||
value="explorer"
|
||||
page={Page.Explorer}
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
currentProject={currentProject ?? null}
|
||||
me={me ?? null}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { ReactElement, useCallback, useState } from 'react';
|
|||
import NextLink from 'next/link';
|
||||
import { useQuery } from 'urql';
|
||||
import { authenticated } from '@/components/authenticated-container';
|
||||
import { TargetLayout } from '@/components/layouts/target';
|
||||
import { Page, TargetLayout } from '@/components/layouts/target';
|
||||
import { VersionErrorsAndChanges } from '@/components/target/history/errors-and-changes';
|
||||
import { Subtitle, Title } from '@/components/ui/page';
|
||||
import { QueryError } from '@/components/ui/query-error';
|
||||
|
|
@ -392,7 +392,7 @@ function HistoryPageContent() {
|
|||
|
||||
return (
|
||||
<TargetLayout
|
||||
value="history"
|
||||
page={Page.History}
|
||||
className="h-full"
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
currentProject={currentProject ?? null}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { ChangeEventHandler, ReactElement, useCallback, useEffect, useRef, useSt
|
|||
import { useQuery } from 'urql';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { authenticated } from '@/components/authenticated-container';
|
||||
import { TargetLayout } from '@/components/layouts/target';
|
||||
import { Page, TargetLayout } from '@/components/layouts/target';
|
||||
import { MarkAsValid } from '@/components/target/history/MarkAsValid';
|
||||
import { Subtitle, Title } from '@/components/ui/page';
|
||||
import { QueryError } from '@/components/ui/query-error';
|
||||
|
|
@ -281,7 +281,7 @@ const TargetSchemaPageQuery = graphql(`
|
|||
}
|
||||
`);
|
||||
|
||||
function Page() {
|
||||
function TargetSchemaPage() {
|
||||
const router = useRouteSelector();
|
||||
const [query] = useQuery({
|
||||
query: TargetSchemaPageQuery,
|
||||
|
|
@ -306,7 +306,7 @@ function Page() {
|
|||
|
||||
return (
|
||||
<TargetLayout
|
||||
value="schema"
|
||||
page={Page.Schema}
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
currentProject={currentProject ?? null}
|
||||
me={me ?? null}
|
||||
|
|
@ -335,7 +335,7 @@ function SchemaPage(): ReactElement {
|
|||
return (
|
||||
<>
|
||||
<MetaTitle title="Schema" />
|
||||
<Page />
|
||||
<TargetSchemaPage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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({
|
|||
<>
|
||||
<div className="py-6 flex flex-row items-center justify-between">
|
||||
<div>
|
||||
<Title>Operations</Title>
|
||||
<Title>Insights</Title>
|
||||
<Subtitle>Observe GraphQL requests and see how the API is consumed.</Subtitle>
|
||||
</div>
|
||||
<div className="flex justify-end gap-x-2">
|
||||
|
|
@ -76,6 +76,7 @@ function OperationsView({
|
|||
operationsFilter={selectedOperations}
|
||||
clientNamesFilter={selectedClients}
|
||||
resolution={resolution}
|
||||
dateRangeText={displayDateRangeLabel(dateRangeKey)}
|
||||
mode="operation-list"
|
||||
/>
|
||||
<OperationsList
|
||||
|
|
@ -148,7 +149,7 @@ function TargetOperationsPageContent() {
|
|||
|
||||
return (
|
||||
<TargetLayout
|
||||
value="operations"
|
||||
page={Page.Insights}
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
currentProject={currentProject ?? null}
|
||||
me={me ?? null}
|
||||
|
|
@ -177,10 +178,10 @@ function TargetOperationsPageContent() {
|
|||
);
|
||||
}
|
||||
|
||||
function OperationsPage(): ReactElement {
|
||||
function InsightsPage(): ReactElement {
|
||||
return (
|
||||
<>
|
||||
<MetaTitle title="Operations" />
|
||||
<MetaTitle title="Insights" />
|
||||
<TargetOperationsPageContent />
|
||||
</>
|
||||
);
|
||||
|
|
@ -188,4 +189,4 @@ function OperationsPage(): ReactElement {
|
|||
|
||||
export const getServerSideProps = withSessionProtection();
|
||||
|
||||
export default authenticated(OperationsPage);
|
||||
export default authenticated(InsightsPage);
|
||||
|
|
@ -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({
|
|||
<div className="py-6 flex flex-row items-center justify-between">
|
||||
<div>
|
||||
<Title>{operationName}</Title>
|
||||
<Subtitle>Performance of individual GraphQL operation</Subtitle>
|
||||
<Subtitle>Insights of individual GraphQL operation</Subtitle>
|
||||
</div>
|
||||
<div className="flex justify-end gap-x-2">
|
||||
<ClientsFilterTrigger
|
||||
|
|
@ -110,6 +110,7 @@ function OperationView({
|
|||
project={projectCleanId}
|
||||
target={targetCleanId}
|
||||
period={dateRange}
|
||||
dateRangeText={displayDateRangeLabel(dateRangeKey)}
|
||||
operationsFilter={operationsList}
|
||||
clientNamesFilter={selectedClients}
|
||||
resolution={resolution}
|
||||
|
|
@ -128,8 +129,8 @@ function OperationView({
|
|||
);
|
||||
}
|
||||
|
||||
const TargetOperationPageQuery = graphql(`
|
||||
query TargetOperationPageQuery($organizationId: ID!, $projectId: ID!, $targetId: ID!) {
|
||||
const OperationInsightsPageQuery = graphql(`
|
||||
query OperationInsightsPageQuery($organizationId: ID!, $projectId: ID!, $targetId: ID!) {
|
||||
organizations {
|
||||
...TargetLayout_OrganizationConnectionFragment
|
||||
}
|
||||
|
|
@ -159,7 +160,7 @@ const TargetOperationPageQuery = graphql(`
|
|||
}
|
||||
`);
|
||||
|
||||
function TargetOperationPageContent({
|
||||
function OperationInsightsContent({
|
||||
operationHash,
|
||||
operationName,
|
||||
}: {
|
||||
|
|
@ -168,7 +169,7 @@ function TargetOperationPageContent({
|
|||
}) {
|
||||
const router = useRouteSelector();
|
||||
const [query] = useQuery({
|
||||
query: TargetOperationPageQuery,
|
||||
query: OperationInsightsPageQuery,
|
||||
variables: {
|
||||
organizationId: router.organizationId,
|
||||
projectId: router.projectId,
|
||||
|
|
@ -190,7 +191,7 @@ function TargetOperationPageContent({
|
|||
|
||||
return (
|
||||
<TargetLayout
|
||||
value="operations"
|
||||
page={Page.Insights}
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
currentProject={currentProject ?? null}
|
||||
me={me ?? null}
|
||||
|
|
@ -221,7 +222,7 @@ function TargetOperationPageContent({
|
|||
);
|
||||
}
|
||||
|
||||
function OperationPage(): ReactElement {
|
||||
function OperationInsightsPage(): ReactElement {
|
||||
const router = useRouter();
|
||||
const { operationHash, operationName } = router.query;
|
||||
|
||||
|
|
@ -236,11 +237,11 @@ function OperationPage(): ReactElement {
|
|||
return (
|
||||
<>
|
||||
<MetaTitle title={`Operation ${operationName}`} />
|
||||
<TargetOperationPageContent operationHash={operationHash} operationName={operationName} />
|
||||
<OperationInsightsContent operationHash={operationHash} operationName={operationName} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const getServerSideProps = withSessionProtection();
|
||||
|
||||
export default authenticated(OperationPage);
|
||||
export default authenticated(OperationInsightsPage);
|
||||
|
|
@ -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 (
|
||||
<TargetLayout
|
||||
value="operations"
|
||||
page={Page.Insights}
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
currentProject={currentProject ?? null}
|
||||
me={me ?? null}
|
||||
|
|
@ -436,7 +436,7 @@ function ClientOperationsPageContent({ clientName }: { clientName: string }) {
|
|||
);
|
||||
}
|
||||
|
||||
function ClientOperationsPage(): ReactElement {
|
||||
function ClientInsightsPage(): ReactElement {
|
||||
const router = useRouter();
|
||||
const { name } = router.query;
|
||||
|
||||
|
|
@ -447,11 +447,11 @@ function ClientOperationsPage(): ReactElement {
|
|||
return (
|
||||
<>
|
||||
<MetaTitle title={`${name} - client`} />
|
||||
<ClientOperationsPageContent clientName={name} />
|
||||
<ClientInsightsPageContent clientName={name} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const getServerSideProps = withSessionProtection();
|
||||
|
||||
export default authenticated(ClientOperationsPage);
|
||||
export default authenticated(ClientInsightsPage);
|
||||
|
|
@ -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: {
|
|||
<div className="py-6 flex flex-row items-center justify-between">
|
||||
<div>
|
||||
<Title>{props.coordinate}</Title>
|
||||
<Subtitle>Detailed view of schema coordinate usage</Subtitle>
|
||||
<Subtitle>Schema coordinate insights</Subtitle>
|
||||
</div>
|
||||
<div className="flex justify-end gap-x-2">
|
||||
<Select
|
||||
|
|
@ -293,7 +293,7 @@ function SchemaCoordinateView(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,
|
||||
|
|
@ -338,7 +338,7 @@ function SchemaCoordinateView(props: {
|
|||
className="text-orange-500 hover:underline hover:underline-offset-2 hover:text-orange-500"
|
||||
href={{
|
||||
pathname:
|
||||
'/[organizationId]/[projectId]/[targetId]/operations/client/[name]',
|
||||
'/[organizationId]/[projectId]/[targetId]/insights/client/[name]',
|
||||
query: {
|
||||
organizationId: props.organizationCleanId,
|
||||
projectId: props.projectCleanId,
|
||||
|
|
@ -423,7 +423,7 @@ function TargetSchemaCoordinatePageContent({ coordinate }: { coordinate: string
|
|||
|
||||
return (
|
||||
<TargetLayout
|
||||
value="operations"
|
||||
page={Page.Insights}
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
currentProject={currentProject ?? null}
|
||||
me={me ?? null}
|
||||
|
|
@ -4,7 +4,7 @@ import { GraphiQL } from 'graphiql';
|
|||
import { buildSchema } from 'graphql';
|
||||
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 { ConnectLabModal } from '@/components/target/laboratory/connect-lab-modal';
|
||||
import { CreateCollectionModal } from '@/components/target/laboratory/create-collection-modal';
|
||||
import { CreateOperationModal } from '@/components/target/laboratory/create-operation-modal';
|
||||
|
|
@ -751,7 +751,7 @@ function LaboratoryPageContent() {
|
|||
|
||||
return (
|
||||
<TargetLayout
|
||||
value="laboratory"
|
||||
page={Page.Laboratory}
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
currentProject={currentProject ?? null}
|
||||
me={me ?? null}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { useFormik } from 'formik';
|
|||
import { useMutation, useQuery } from 'urql';
|
||||
import * as Yup from 'yup';
|
||||
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 { CDNAccessTokens } from '@/components/target/settings/cdn-access-tokens';
|
||||
import { Subtitle, Title } from '@/components/ui/page';
|
||||
|
|
@ -963,7 +963,7 @@ function TargetSettingsContent() {
|
|||
|
||||
return (
|
||||
<TargetLayout
|
||||
value="settings"
|
||||
page={Page.Settings}
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
currentProject={currentProject ?? null}
|
||||
me={me ?? null}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ import { Globe, History } from 'lucide-react';
|
|||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { useQuery } from 'urql';
|
||||
import { authenticated } from '@/components/authenticated-container';
|
||||
import { ProjectLayout } from '@/components/layouts/project';
|
||||
import { Page, ProjectLayout } from '@/components/layouts/project';
|
||||
import {
|
||||
createEmptySeries,
|
||||
fullSeries,
|
||||
resolutionToMilliseconds,
|
||||
} from '@/components/target/operations/utils';
|
||||
} from '@/components/target/insights/utils';
|
||||
import { Subtitle, Title } from '@/components/ui/page';
|
||||
import { QueryError } from '@/components/ui/query-error';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
|
|
@ -208,7 +208,7 @@ const TargetCard = (props: {
|
|||
);
|
||||
};
|
||||
|
||||
const Page = () => {
|
||||
const ProjectsPageContent = () => {
|
||||
const router = useRouteSelector();
|
||||
const period = useRef<{
|
||||
from: string;
|
||||
|
|
@ -260,7 +260,7 @@ const Page = () => {
|
|||
|
||||
return (
|
||||
<ProjectLayout
|
||||
value="targets"
|
||||
page={Page.Targets}
|
||||
className="flex justify-between gap-12"
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
currentProject={currentProject ?? null}
|
||||
|
|
@ -371,7 +371,7 @@ function ProjectsPage(): ReactElement {
|
|||
return (
|
||||
<>
|
||||
<MetaTitle title="Targets" />
|
||||
<Page />
|
||||
<ProjectsPageContent />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ReactElement, useState } from 'react';
|
||||
import { useQuery } from 'urql';
|
||||
import { authenticated } from '@/components/authenticated-container';
|
||||
import { ProjectLayout } from '@/components/layouts/project';
|
||||
import { Page, ProjectLayout } from '@/components/layouts/project';
|
||||
import { AlertsTable, AlertsTable_AlertFragment } from '@/components/project/alerts/alerts-table';
|
||||
import {
|
||||
ChannelsTable,
|
||||
|
|
@ -234,7 +234,7 @@ function AlertsPageContent() {
|
|||
currentProject={currentProject ?? null}
|
||||
organizations={organizationConnection ?? null}
|
||||
me={me ?? null}
|
||||
value="alerts"
|
||||
page={Page.Alerts}
|
||||
className="flex flex-col gap-y-10"
|
||||
>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ReactElement } from 'react';
|
||||
import { useMutation, useQuery } from 'urql';
|
||||
import { authenticated } from '@/components/authenticated-container';
|
||||
import { ProjectLayout } from '@/components/layouts/project';
|
||||
import { Page, ProjectLayout } from '@/components/layouts/project';
|
||||
import { PolicySettings } from '@/components/policy/policy-settings';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Subtitle, Title } from '@/components/ui/page';
|
||||
|
|
@ -113,7 +113,7 @@ function ProjectPolicyContent() {
|
|||
currentProject={currentProject ?? null}
|
||||
organizations={organizationConnection ?? null}
|
||||
me={me ?? null}
|
||||
value="policy"
|
||||
page={Page.Policy}
|
||||
className="flex flex-col gap-y-10"
|
||||
>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { useFormik } from 'formik';
|
|||
import { useMutation, useQuery } from 'urql';
|
||||
import * as Yup from 'yup';
|
||||
import { authenticated } from '@/components/authenticated-container';
|
||||
import { ProjectLayout } from '@/components/layouts/project';
|
||||
import { Page, ProjectLayout } from '@/components/layouts/project';
|
||||
import { ExternalCompositionSettings } from '@/components/project/settings/external-composition';
|
||||
import { ModelMigrationSettings } from '@/components/project/settings/model-migration';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
|
@ -309,7 +309,7 @@ function ProjectSettingsContent() {
|
|||
currentProject={currentProject ?? null}
|
||||
organizations={organizationConnection ?? null}
|
||||
me={me ?? null}
|
||||
value="settings"
|
||||
page={Page.Settings}
|
||||
className="flex flex-col gap-y-10"
|
||||
>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ import { Globe, History } from 'lucide-react';
|
|||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { useQuery } from 'urql';
|
||||
import { authenticated } from '@/components/authenticated-container';
|
||||
import { OrganizationLayout } from '@/components/layouts/organization';
|
||||
import { OrganizationLayout, Page } from '@/components/layouts/organization';
|
||||
import {
|
||||
createEmptySeries,
|
||||
fullSeries,
|
||||
resolutionToMilliseconds,
|
||||
} from '@/components/target/operations/utils';
|
||||
} from '@/components/target/insights/utils';
|
||||
import { Subtitle, Title } from '@/components/ui/page';
|
||||
import { QueryError } from '@/components/ui/query-error';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
|
|
@ -319,7 +319,7 @@ function OrganizationPageContent() {
|
|||
|
||||
return (
|
||||
<OrganizationLayout
|
||||
value="overview"
|
||||
page={Page.Overview}
|
||||
className="flex justify-between gap-12"
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
organizations={organizationConnection ?? null}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import NextLink from 'next/link';
|
|||
import { useMutation, useQuery } from 'urql';
|
||||
import { authenticated } from '@/components/authenticated-container';
|
||||
import { Section } from '@/components/common';
|
||||
import { OrganizationLayout } from '@/components/layouts/organization';
|
||||
import { OrganizationLayout, Page } from '@/components/layouts/organization';
|
||||
import { BillingPaymentMethod } from '@/components/organization/billing/BillingPaymentMethod';
|
||||
import { BillingPlanPicker } from '@/components/organization/billing/BillingPlanPicker';
|
||||
import { PlanSummary } from '@/components/organization/billing/PlanSummary';
|
||||
|
|
@ -471,7 +471,7 @@ function ManageSubscriptionPageContent() {
|
|||
|
||||
return (
|
||||
<OrganizationLayout
|
||||
value="subscription"
|
||||
page={Page.Subscription}
|
||||
className="flex flex-col gap-y-10"
|
||||
currentOrganization={currentOrganization}
|
||||
organizations={organizationConnection}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { useFormik } from 'formik';
|
|||
import { useMutation, useQuery } from 'urql';
|
||||
import * as Yup from 'yup';
|
||||
import { authenticated } from '@/components/authenticated-container';
|
||||
import { OrganizationLayout } from '@/components/layouts/organization';
|
||||
import { OrganizationLayout, Page } from '@/components/layouts/organization';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Subtitle, Title } from '@/components/ui/page';
|
||||
|
|
@ -270,7 +270,7 @@ const OrganizationInvitations = (props: {
|
|||
) : null;
|
||||
};
|
||||
|
||||
function Page(props: {
|
||||
function PageContent(props: {
|
||||
organization: FragmentType<typeof Page_OrganizationFragment>;
|
||||
me?: FragmentType<typeof OrganizationMembersPage_MeFragment>;
|
||||
}) {
|
||||
|
|
@ -441,13 +441,13 @@ function SettingsPageContent() {
|
|||
|
||||
return (
|
||||
<OrganizationLayout
|
||||
value="members"
|
||||
page={Page.Members}
|
||||
className="flex flex-col gap-y-10"
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
organizations={organizationConnection ?? null}
|
||||
me={me ?? null}
|
||||
>
|
||||
{currentOrganization ? <Page organization={currentOrganization} me={me} /> : null}
|
||||
{currentOrganization ? <PageContent organization={currentOrganization} me={me} /> : null}
|
||||
</OrganizationLayout>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ReactElement } from 'react';
|
||||
import { useMutation, useQuery } from 'urql';
|
||||
import { authenticated } from '@/components/authenticated-container';
|
||||
import { OrganizationLayout } from '@/components/layouts/organization';
|
||||
import { OrganizationLayout, Page } from '@/components/layouts/organization';
|
||||
import { PolicySettings } from '@/components/policy/policy-settings';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
|
|
@ -98,7 +98,7 @@ function PolicyPageContent() {
|
|||
|
||||
return (
|
||||
<OrganizationLayout
|
||||
value="policy"
|
||||
page={Page.Policy}
|
||||
className="flex flex-col gap-y-10"
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
organizations={organizationConnection ?? null}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { useFormik } from 'formik';
|
|||
import { useMutation, useQuery } from 'urql';
|
||||
import * as Yup from 'yup';
|
||||
import { authenticated } from '@/components/authenticated-container';
|
||||
import { OrganizationLayout } from '@/components/layouts/organization';
|
||||
import { OrganizationLayout, Page } from '@/components/layouts/organization';
|
||||
import { OIDCIntegrationSection } from '@/components/organization/settings/oidc-integration-section';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
|
|
@ -450,7 +450,7 @@ function SettingsPageContent() {
|
|||
|
||||
return (
|
||||
<OrganizationLayout
|
||||
value="settings"
|
||||
page={Page.Settings}
|
||||
className="flex flex-col gap-y-10"
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
organizations={organizationConnection ?? null}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import NextLink from 'next/link';
|
|||
import { endOfMonth, startOfMonth } from 'date-fns';
|
||||
import { useQuery } from 'urql';
|
||||
import { authenticated } from '@/components/authenticated-container';
|
||||
import { OrganizationLayout } from '@/components/layouts/organization';
|
||||
import { OrganizationLayout, Page } from '@/components/layouts/organization';
|
||||
import { BillingView } from '@/components/organization/billing/Billing';
|
||||
import { CurrencyFormatter } from '@/components/organization/billing/helpers';
|
||||
import { InvoicesList } from '@/components/organization/billing/InvoicesList';
|
||||
|
|
@ -116,7 +116,7 @@ function SubscriptionPageContent() {
|
|||
|
||||
return (
|
||||
<OrganizationLayout
|
||||
value="subscription"
|
||||
page={Page.Subscription}
|
||||
className="flex flex-col gap-y-10"
|
||||
currentOrganization={currentOrganization}
|
||||
organizations={organizationConnection}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useForm } from 'react-hook-form';
|
|||
import { useMutation, useQuery } from 'urql';
|
||||
import { z } from 'zod';
|
||||
import { authenticated } from '@/components/authenticated-container';
|
||||
import { OrganizationLayout } from '@/components/layouts/organization';
|
||||
import { OrganizationLayout, Page } from '@/components/layouts/organization';
|
||||
import { Priority, priorityDescription, Status } from '@/components/organization/support';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
|
|
@ -435,7 +435,7 @@ function SupportPageContent() {
|
|||
|
||||
return (
|
||||
<OrganizationLayout
|
||||
value="support"
|
||||
page={Page.Support}
|
||||
className="flex flex-col gap-y-10"
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
organizations={organizationConnection ?? null}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useForm } from 'react-hook-form';
|
|||
import { useMutation, useQuery } from 'urql';
|
||||
import { z } from 'zod';
|
||||
import { authenticated } from '@/components/authenticated-container';
|
||||
import { OrganizationLayout } from '@/components/layouts/organization';
|
||||
import { OrganizationLayout, Page } from '@/components/layouts/organization';
|
||||
import { priorityDescription, statusDescription } from '@/components/organization/support';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';
|
||||
|
|
@ -301,7 +301,7 @@ const SupportTicketPageQuery = graphql(`
|
|||
}
|
||||
`);
|
||||
|
||||
function SupportTicketPage() {
|
||||
function SupportTicketPageContent() {
|
||||
const router = useRouteSelector();
|
||||
const ticketId = router.query.ticketId as string;
|
||||
const [query, refetchQuery] = useQuery({
|
||||
|
|
@ -330,7 +330,7 @@ function SupportTicketPage() {
|
|||
|
||||
return (
|
||||
<OrganizationLayout
|
||||
value="support"
|
||||
page={Page.Support}
|
||||
className="flex flex-col gap-y-10"
|
||||
currentOrganization={currentOrganization ?? null}
|
||||
organizations={organizationConnection ?? null}
|
||||
|
|
@ -352,18 +352,18 @@ function SupportTicketPage() {
|
|||
);
|
||||
}
|
||||
|
||||
function Page() {
|
||||
function SupportTicketPage() {
|
||||
const router = useRouteSelector();
|
||||
const ticketId = router.query.ticketId as string;
|
||||
|
||||
return (
|
||||
<>
|
||||
<MetaTitle title={`Support Ticket #${ticketId}`} />
|
||||
<SupportTicketPage />
|
||||
<SupportTicketPageContent />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const getServerSideProps = withSessionProtection();
|
||||
|
||||
export default authenticated(Page);
|
||||
export default authenticated(SupportTicketPage);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { useRouteSelector, useToggle } from '@/lib/hooks';
|
|||
import { ProPlanBilling } from '../organization/billing/ProPlanBillingWarm';
|
||||
import { RateLimitWarn } from '../organization/billing/RateLimitWarn';
|
||||
|
||||
enum TabValue {
|
||||
export enum Page {
|
||||
Overview = 'overview',
|
||||
Members = 'members',
|
||||
Settings = 'settings',
|
||||
|
|
@ -61,11 +61,11 @@ const OrganizationLayout_OrganizationConnectionFragment = graphql(`
|
|||
|
||||
export function OrganizationLayout({
|
||||
children,
|
||||
value,
|
||||
page,
|
||||
className,
|
||||
...props
|
||||
}: {
|
||||
value?: 'overview' | 'members' | 'settings' | 'subscription' | 'policy' | 'support';
|
||||
page?: Page;
|
||||
className?: string;
|
||||
me: FragmentType<typeof OrganizationLayout_MeFragment> | null;
|
||||
currentOrganization: FragmentType<typeof OrganizationLayout_CurrentOrganizationFragment> | null;
|
||||
|
|
@ -138,9 +138,9 @@ export function OrganizationLayout({
|
|||
<div className="relative border-b border-gray-800">
|
||||
<div className="container flex justify-between items-center">
|
||||
{currentOrganization && meInCurrentOrg ? (
|
||||
<Tabs value={value}>
|
||||
<Tabs value={page}>
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value={TabValue.Overview} asChild>
|
||||
<Tabs.Trigger value={Page.Overview} asChild>
|
||||
<NextLink
|
||||
href={{
|
||||
pathname: '/[organizationId]',
|
||||
|
|
@ -151,13 +151,13 @@ export function OrganizationLayout({
|
|||
</NextLink>
|
||||
</Tabs.Trigger>
|
||||
{canAccessOrganization(OrganizationAccessScope.Members, meInCurrentOrg) && (
|
||||
<Tabs.Trigger value={TabValue.Members} asChild>
|
||||
<Tabs.Trigger value={Page.Members} asChild>
|
||||
<NextLink
|
||||
href={{
|
||||
pathname: '/[organizationId]/view/[tab]',
|
||||
query: {
|
||||
organizationId: currentOrganization.cleanId,
|
||||
tab: TabValue.Members,
|
||||
tab: Page.Members,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
|
@ -167,26 +167,26 @@ export function OrganizationLayout({
|
|||
)}
|
||||
{canAccessOrganization(OrganizationAccessScope.Settings, meInCurrentOrg) && (
|
||||
<>
|
||||
<Tabs.Trigger value={TabValue.Policy} asChild>
|
||||
<Tabs.Trigger value={Page.Policy} asChild>
|
||||
<NextLink
|
||||
href={{
|
||||
pathname: '/[organizationId]/view/[tab]',
|
||||
query: {
|
||||
organizationId: currentOrganization.cleanId,
|
||||
tab: TabValue.Policy,
|
||||
tab: Page.Policy,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Policy
|
||||
</NextLink>
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value={TabValue.Settings} asChild>
|
||||
<Tabs.Trigger value={Page.Settings} asChild>
|
||||
<NextLink
|
||||
href={{
|
||||
pathname: '/[organizationId]/view/[tab]',
|
||||
query: {
|
||||
organizationId: currentOrganization.cleanId,
|
||||
tab: TabValue.Settings,
|
||||
tab: Page.Settings,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
|
@ -197,13 +197,13 @@ export function OrganizationLayout({
|
|||
)}
|
||||
{canAccessOrganization(OrganizationAccessScope.Read, meInCurrentOrg) &&
|
||||
env.zendeskSupport && (
|
||||
<Tabs.Trigger value={TabValue.Support} asChild>
|
||||
<Tabs.Trigger value={Page.Support} asChild>
|
||||
<NextLink
|
||||
href={{
|
||||
pathname: '/[organizationId]/view/[tab]',
|
||||
query: {
|
||||
organizationId: currentOrganization.cleanId,
|
||||
tab: TabValue.Support,
|
||||
tab: Page.Support,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
|
@ -213,13 +213,13 @@ export function OrganizationLayout({
|
|||
)}
|
||||
{getIsStripeEnabled() &&
|
||||
canAccessOrganization(OrganizationAccessScope.Settings, meInCurrentOrg) && (
|
||||
<Tabs.Trigger value={TabValue.Subscription} asChild>
|
||||
<Tabs.Trigger value={Page.Subscription} asChild>
|
||||
<NextLink
|
||||
href={{
|
||||
pathname: '/[organizationId]/view/[tab]',
|
||||
query: {
|
||||
organizationId: currentOrganization.cleanId,
|
||||
tab: TabValue.Subscription,
|
||||
tab: Page.Subscription,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { canAccessProject, ProjectAccessScope, useProjectAccess } from '@/lib/ac
|
|||
import { useRouteSelector, useToggle } from '@/lib/hooks';
|
||||
import { ProjectMigrationToast } from '../project/migration-toast';
|
||||
|
||||
enum TabValue {
|
||||
export enum Page {
|
||||
Targets = 'targets',
|
||||
Alerts = 'alerts',
|
||||
Policy = 'policy',
|
||||
|
|
@ -72,11 +72,11 @@ const ProjectLayout_ProjectConnectionFragment = graphql(`
|
|||
|
||||
export function ProjectLayout({
|
||||
children,
|
||||
value,
|
||||
page,
|
||||
className,
|
||||
...props
|
||||
}: {
|
||||
value: 'targets' | 'alerts' | 'settings' | 'policy';
|
||||
page: Page;
|
||||
className?: string;
|
||||
me: FragmentType<typeof ProjectLayout_MeFragment> | null;
|
||||
currentOrganization: FragmentType<typeof ProjectLayout_CurrentOrganizationFragment> | null;
|
||||
|
|
@ -169,16 +169,16 @@ export function ProjectLayout({
|
|||
</div>
|
||||
</header>
|
||||
|
||||
{value === 'settings' || currentProject?.registryModel !== 'LEGACY' ? null : (
|
||||
{page === Page.Settings || currentProject?.registryModel !== 'LEGACY' ? null : (
|
||||
<ProjectMigrationToast orgId={orgId} projectId={currentProject.cleanId} />
|
||||
)}
|
||||
|
||||
<div className="relative border-b border-gray-800">
|
||||
<div className="container flex justify-between items-center">
|
||||
{currentOrganization && currentProject ? (
|
||||
<Tabs value={value}>
|
||||
<Tabs value={page}>
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value={TabValue.Targets} asChild>
|
||||
<Tabs.Trigger value={Page.Targets} asChild>
|
||||
<NextLink
|
||||
href={{
|
||||
pathname: '/[organizationId]/[projectId]',
|
||||
|
|
@ -192,14 +192,14 @@ export function ProjectLayout({
|
|||
</NextLink>
|
||||
</Tabs.Trigger>
|
||||
{canAccessProject(ProjectAccessScope.Alerts, currentOrganization.me) && (
|
||||
<Tabs.Trigger value={TabValue.Alerts} asChild>
|
||||
<Tabs.Trigger value={Page.Alerts} asChild>
|
||||
<NextLink
|
||||
href={{
|
||||
pathname: '/[organizationId]/[projectId]/view/[tab]',
|
||||
query: {
|
||||
organizationId: currentOrganization.cleanId,
|
||||
projectId: currentProject.cleanId,
|
||||
tab: TabValue.Alerts,
|
||||
tab: Page.Alerts,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
|
@ -209,28 +209,28 @@ export function ProjectLayout({
|
|||
)}
|
||||
{canAccessProject(ProjectAccessScope.Settings, currentOrganization.me) && (
|
||||
<>
|
||||
<Tabs.Trigger value={TabValue.Policy} asChild>
|
||||
<Tabs.Trigger value={Page.Policy} asChild>
|
||||
<NextLink
|
||||
href={{
|
||||
pathname: '/[organizationId]/[projectId]/view/[tab]',
|
||||
query: {
|
||||
organizationId: currentOrganization.cleanId,
|
||||
projectId: currentProject.cleanId,
|
||||
tab: TabValue.Policy,
|
||||
tab: Page.Policy,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Policy
|
||||
</NextLink>
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value={TabValue.Settings} asChild>
|
||||
<Tabs.Trigger value={Page.Settings} asChild>
|
||||
<NextLink
|
||||
href={{
|
||||
pathname: '/[organizationId]/[projectId]/view/[tab]',
|
||||
query: {
|
||||
organizationId: currentOrganization.cleanId,
|
||||
projectId: currentProject.cleanId,
|
||||
tab: TabValue.Settings,
|
||||
tab: Page.Settings,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ import { useRouteSelector, useToggle } from '@/lib/hooks';
|
|||
import { cn } from '@/lib/utils';
|
||||
import { ProjectMigrationToast } from '../project/migration-toast';
|
||||
|
||||
enum TabValue {
|
||||
export enum Page {
|
||||
Schema = 'schema',
|
||||
Explorer = 'explorer',
|
||||
Checks = 'checks',
|
||||
History = 'history',
|
||||
Operations = 'operations',
|
||||
Insights = 'insights',
|
||||
Laboratory = 'laboratory',
|
||||
Settings = 'settings',
|
||||
}
|
||||
|
|
@ -86,11 +86,11 @@ const TargetLayout_IsCDNEnabledFragment = graphql(`
|
|||
export const TargetLayout = ({
|
||||
children,
|
||||
connect,
|
||||
value,
|
||||
page,
|
||||
className,
|
||||
...props
|
||||
}: {
|
||||
value: 'schema' | 'explorer' | 'checks' | 'history' | 'operations' | 'laboratory' | 'settings';
|
||||
page: Page;
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
connect?: ReactNode;
|
||||
|
|
@ -223,11 +223,11 @@ export const TargetLayout = ({
|
|||
<div className="relative border-b border-gray-800">
|
||||
<div className="container flex justify-between items-center">
|
||||
{currentOrganization && currentProject && currentTarget ? (
|
||||
<Tabs className="flex h-full grow flex-col" value={value}>
|
||||
<Tabs className="flex h-full grow flex-col" value={page}>
|
||||
<Tabs.List>
|
||||
{canAccessSchema && (
|
||||
<>
|
||||
<Tabs.Trigger value={TabValue.Schema} asChild>
|
||||
<Tabs.Trigger value={Page.Schema} asChild>
|
||||
<NextLink
|
||||
href={{
|
||||
pathname: '/[organizationId]/[projectId]/[targetId]',
|
||||
|
|
@ -241,7 +241,7 @@ export const TargetLayout = ({
|
|||
Schema
|
||||
</NextLink>
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value={TabValue.Checks} asChild>
|
||||
<Tabs.Trigger value={Page.Checks} asChild>
|
||||
<NextLink
|
||||
href={{
|
||||
pathname: '/[organizationId]/[projectId]/[targetId]/checks',
|
||||
|
|
@ -255,7 +255,7 @@ export const TargetLayout = ({
|
|||
Checks
|
||||
</NextLink>
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value={TabValue.Explorer} asChild>
|
||||
<Tabs.Trigger value={Page.Explorer} asChild>
|
||||
<NextLink
|
||||
href={{
|
||||
pathname: '/[organizationId]/[projectId]/[targetId]/explorer',
|
||||
|
|
@ -269,7 +269,7 @@ export const TargetLayout = ({
|
|||
Explorer
|
||||
</NextLink>
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value={TabValue.History} asChild>
|
||||
<Tabs.Trigger value={Page.History} asChild>
|
||||
<NextLink
|
||||
href={{
|
||||
pathname: '/[organizationId]/[projectId]/[targetId]/history',
|
||||
|
|
@ -283,10 +283,10 @@ export const TargetLayout = ({
|
|||
History
|
||||
</NextLink>
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value={TabValue.Operations} asChild>
|
||||
<Tabs.Trigger value={Page.Insights} asChild>
|
||||
<NextLink
|
||||
href={{
|
||||
pathname: '/[organizationId]/[projectId]/[targetId]/operations',
|
||||
pathname: '/[organizationId]/[projectId]/[targetId]/insights',
|
||||
query: {
|
||||
organizationId: currentOrganization.cleanId,
|
||||
projectId: currentProject.cleanId,
|
||||
|
|
@ -294,10 +294,10 @@ export const TargetLayout = ({
|
|||
},
|
||||
}}
|
||||
>
|
||||
Operations
|
||||
Insights
|
||||
</NextLink>
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value={TabValue.Laboratory} asChild>
|
||||
<Tabs.Trigger value={Page.Laboratory} asChild>
|
||||
<NextLink
|
||||
href={{
|
||||
pathname: '/[organizationId]/[projectId]/[targetId]/laboratory',
|
||||
|
|
@ -314,7 +314,7 @@ export const TargetLayout = ({
|
|||
</>
|
||||
)}
|
||||
{canAccessSettings && (
|
||||
<Tabs.Trigger value={TabValue.Settings} asChild>
|
||||
<Tabs.Trigger value={Page.Settings} asChild>
|
||||
<NextLink
|
||||
href={{
|
||||
pathname: '/[organizationId]/[projectId]/[targetId]/settings',
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ export function SchemaExplorerUsageStats(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,
|
||||
|
|
@ -171,7 +171,7 @@ export function SchemaExplorerUsageStats(props: {
|
|||
className="text-orange-500 hover:underline hover:underline-offset-2 hover:text-orange-500"
|
||||
href={{
|
||||
pathname:
|
||||
'/[organizationId]/[projectId]/[targetId]/operations/client/[name]',
|
||||
'/[organizationId]/[projectId]/[targetId]/insights/client/[name]',
|
||||
query: {
|
||||
organizationId: props.organizationCleanId,
|
||||
projectId: props.projectCleanId,
|
||||
|
|
@ -527,7 +527,7 @@ export function LinkToCoordinatePage(props: {
|
|||
className="text-orange-500"
|
||||
href={{
|
||||
pathname:
|
||||
'/[organizationId]/[projectId]/[targetId]/operations/schema-coordinate/[coordinate]',
|
||||
'/[organizationId]/[projectId]/[targetId]/insights/schema-coordinate/[coordinate]',
|
||||
query: {
|
||||
organizationId: router.organizationId,
|
||||
projectId: router.projectId,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,19 @@ import clsx from 'clsx';
|
|||
import { useQuery } from 'urql';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { Scale, Section } from '@/components/common';
|
||||
import { Button, Input, Sortable, Table, TBody, Td, Th, THead, Tooltip, Tr } from '@/components/v2';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Button as OldButton,
|
||||
Sortable,
|
||||
Table,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tooltip,
|
||||
Tr,
|
||||
} from '@/components/v2';
|
||||
import { env } from '@/env/frontend';
|
||||
import { FragmentType, graphql, useFragment } from '@/gql';
|
||||
import { DateRangeInput } from '@/graphql';
|
||||
|
|
@ -58,12 +70,12 @@ function OperationRow({
|
|||
return (
|
||||
<>
|
||||
<Tr>
|
||||
<Td className="font-medium truncate">
|
||||
<Td className="font-medium">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Link
|
||||
href={{
|
||||
pathname:
|
||||
'/[organizationId]/[projectId]/[targetId]/operations/[operationName]/[operationHash]',
|
||||
'/[organizationId]/[projectId]/[targetId]/insights/[operationName]/[operationHash]',
|
||||
query: {
|
||||
organizationId: organization,
|
||||
projectId: project,
|
||||
|
|
@ -75,9 +87,9 @@ function OperationRow({
|
|||
}}
|
||||
passHref
|
||||
>
|
||||
<Button variant="link" as="a">
|
||||
<OldButton variant="link" as="a" className="block truncate max-w-[300px]">
|
||||
{operation.name}
|
||||
</Button>
|
||||
</OldButton>
|
||||
</Link>
|
||||
{operation.name === 'anonymous' && (
|
||||
<Tooltip.Provider delayDuration={200}>
|
||||
|
|
@ -88,16 +100,16 @@ function OperationRow({
|
|||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td align="center">{operation.kind}</Td>
|
||||
<Td align="center" className="text-xs">
|
||||
{operation.kind}
|
||||
</Td>
|
||||
<Td align="center">{p90}</Td>
|
||||
<Td align="center">{p95}</Td>
|
||||
<Td align="center">{p99}</Td>
|
||||
<Td align="center">{failureRate}%</Td>
|
||||
<Td align="center">{count}</Td>
|
||||
<Td align="right" width="1">
|
||||
{percentage}%
|
||||
</Td>
|
||||
<Td width="1">
|
||||
<Td align="right">{percentage}%</Td>
|
||||
<Td>
|
||||
<Scale value={operation.percentage} size={10} max={100} className="justify-end" />
|
||||
</Td>
|
||||
</Tr>
|
||||
|
|
@ -105,34 +117,62 @@ function OperationRow({
|
|||
);
|
||||
}
|
||||
|
||||
const table = createTable().setRowType<Operation>();
|
||||
const table = createTable()
|
||||
.setTableMetaType<{
|
||||
align: 'left' | 'center' | 'right';
|
||||
}>()
|
||||
.setRowType<Operation>();
|
||||
|
||||
const columns = [
|
||||
table.createDataColumn('name', {
|
||||
header: 'Operations',
|
||||
enableSorting: false,
|
||||
meta: {
|
||||
align: 'left',
|
||||
},
|
||||
}),
|
||||
table.createDataColumn('kind', {
|
||||
header: 'Kind',
|
||||
enableSorting: false,
|
||||
meta: {
|
||||
align: 'center',
|
||||
},
|
||||
}),
|
||||
table.createDataColumn('p90', {
|
||||
header: 'p90',
|
||||
meta: {
|
||||
align: 'center',
|
||||
},
|
||||
}),
|
||||
table.createDataColumn('p95', {
|
||||
header: 'p95',
|
||||
meta: {
|
||||
align: 'center',
|
||||
},
|
||||
}),
|
||||
table.createDataColumn('p99', {
|
||||
header: 'p99',
|
||||
meta: {
|
||||
align: 'center',
|
||||
},
|
||||
}),
|
||||
table.createDataColumn('failureRate', {
|
||||
header: 'Failure Rate',
|
||||
meta: {
|
||||
align: 'center',
|
||||
},
|
||||
}),
|
||||
table.createDataColumn('requests', {
|
||||
header: 'Requests',
|
||||
meta: {
|
||||
align: 'center',
|
||||
},
|
||||
}),
|
||||
table.createDataColumn('percentage', {
|
||||
header: 'Traffic',
|
||||
meta: {
|
||||
align: 'right',
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
|
|
@ -191,6 +231,7 @@ function OperationsTable({
|
|||
}, 500);
|
||||
|
||||
const { headers } = tableInstance.getHeaderGroups()[0];
|
||||
|
||||
return (
|
||||
<div className={clsx('rounded-md p-5 border border-gray-800 bg-gray-900/50', className)}>
|
||||
<Section.Title>Operations</Section.Title>
|
||||
|
|
@ -201,9 +242,11 @@ function OperationsTable({
|
|||
<Tooltip.Provider>
|
||||
{headers.map(header => {
|
||||
const canSort = header.column.getCanSort();
|
||||
const align: 'center' | 'left' | 'right' =
|
||||
(header.column.columnDef.meta as any)?.align || 'left';
|
||||
const name = header.renderHeader();
|
||||
return (
|
||||
<Th key={header.id}>
|
||||
<Th key={header.id} className="text-sm font-semibold" align={align}>
|
||||
{canSort ? (
|
||||
<Sortable
|
||||
sortOrder={header.column.getIsSorted()}
|
||||
|
|
@ -238,11 +281,16 @@ function OperationsTable({
|
|||
</TBody>
|
||||
</Table>
|
||||
<div className="flex items-center gap-2 mt-6">
|
||||
<Button onClick={firstPage} disabled={!tableInstance.getCanPreviousPage()}>
|
||||
<Button
|
||||
onClick={firstPage}
|
||||
variant="outline"
|
||||
disabled={!tableInstance.getCanPreviousPage()}
|
||||
>
|
||||
First
|
||||
</Button>
|
||||
<Button
|
||||
aria-label="Go to previous page"
|
||||
variant="outline"
|
||||
onClick={tableInstance.previousPage}
|
||||
disabled={!tableInstance.getCanPreviousPage()}
|
||||
>
|
||||
|
|
@ -253,22 +301,19 @@ function OperationsTable({
|
|||
</span>
|
||||
<Button
|
||||
aria-label="Go to next page"
|
||||
variant="outline"
|
||||
onClick={tableInstance.nextPage}
|
||||
disabled={!tableInstance.getCanNextPage()}
|
||||
>
|
||||
<ChevronUpIcon className="rotate-90 h-5 w-auto" />
|
||||
</Button>
|
||||
<Button onClick={lastPage} disabled={!tableInstance.getCanNextPage()}>
|
||||
<Button variant="outline" onClick={lastPage} disabled={!tableInstance.getCanNextPage()}>
|
||||
Last
|
||||
</Button>
|
||||
<div className="ml-6">Go to:</div>
|
||||
<Input
|
||||
prefix={
|
||||
<label htmlFor="page" className="shrink-0">
|
||||
Go to:
|
||||
</label>
|
||||
}
|
||||
id="page"
|
||||
size="medium"
|
||||
className="w-16"
|
||||
type="number"
|
||||
defaultValue={tableInstance.getState().pagination.pageIndex + 1}
|
||||
onChange={e => {
|
||||
|
|
@ -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 (
|
||||
<div className={classes.root}>
|
||||
<h2 className={classes.value}>{value}</h2>
|
||||
<p className={classes.title}>Requests</p>
|
||||
</div>
|
||||
<Card className="bg-gray-900/50">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Requests</CardTitle>
|
||||
<GlobeIcon className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{value}</div>
|
||||
<p className="text-xs text-muted-foreground">Total requests served</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function UniqueOperationsStats({ operations = 0 }: { operations?: number }): ReactElement {
|
||||
function UniqueOperationsStats({
|
||||
operations = 0,
|
||||
dateRangeText,
|
||||
}: {
|
||||
operations?: number;
|
||||
dateRangeText: string;
|
||||
}): ReactElement {
|
||||
const value = useFormattedNumber(operations);
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<div className={classes.root}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<h2 className={classes.value}>{value}</h2>
|
||||
<p className={classes.title}>Unique Operations</p>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Count of unique operations that have been requested, taking into account applied
|
||||
filters.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
<Card className="bg-gray-900/50">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Operations</CardTitle>
|
||||
<BookIcon className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{value}</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Distinct GraphQL operations in {dateRangeText}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -97,45 +115,60 @@ function OperationRelativeFrequency({
|
|||
}: {
|
||||
allOperationRequests: number;
|
||||
operationRequests: number;
|
||||
dateRangeText: string;
|
||||
}): ReactElement {
|
||||
const rate = allOperationRequests
|
||||
? `${toDecimal((operationRequests * 100) / allOperationRequests)}%`
|
||||
: '-';
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<div className={classes.root}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<h2 className={classes.value}>{rate}</h2>
|
||||
<p className={classes.title}>Total traffic ratio</p>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
The proportion of traffic accounted for by this operation, taking into account applied
|
||||
filters, in relation to the total target traffic.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
<Card className="bg-gray-900/50">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Relative Request Frequency</CardTitle>
|
||||
<PercentIcon className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{rate}</div>
|
||||
<p className="text-xs text-muted-foreground">The impact on the overall API traffic</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className={classes.root}>
|
||||
<h2 className={classes.value}>{formatted}</h2>
|
||||
<p className={classes.title}>{title}</p>
|
||||
</div>
|
||||
<Card className="bg-gray-900/50">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">p{percentile}</CardTitle>
|
||||
<GaugeIcon className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{formatted}</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Latency p{percentile} in {dateRangeText}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className={classes.root}>
|
||||
<h2 className={classes.value}>{throughput}</h2>
|
||||
<p className={classes.title}>RPM</p>
|
||||
</div>
|
||||
<Card className="bg-gray-900/50">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Requests per minute</CardTitle>
|
||||
<ActivityIcon className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{throughput}</div>
|
||||
<p className="text-xs text-muted-foreground">Throughput in {dateRangeText}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className={classes.root}>
|
||||
<h2 className={cn(classes.value, 'text-emerald-500 dark:text-emerald-500')}>{rate}</h2>
|
||||
<p className={classes.title}>Success rate</p>
|
||||
</div>
|
||||
<Card className="bg-gray-900/50">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium text-emerald-500 dark:text-emerald-500">
|
||||
Success rate
|
||||
</CardTitle>
|
||||
<SmileIcon className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{rate}</div>
|
||||
<p className="text-xs text-muted-foreground">Successful requests in {dateRangeText}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function FailureRateStats({
|
||||
requests = 0,
|
||||
totalFailures = 0,
|
||||
dateRangeText,
|
||||
}: {
|
||||
requests?: number;
|
||||
totalFailures?: number;
|
||||
dateRangeText: string;
|
||||
}): ReactElement {
|
||||
const rate = requests || totalFailures ? `${toDecimal((totalFailures * 100) / requests)}%` : '-';
|
||||
|
||||
return (
|
||||
<div className={cn(classes.root)}>
|
||||
<h2 className={cn(classes.value, 'text-red-500 dark:text-red-500')}>{rate}</h2>
|
||||
<p className={classes.title}>Failure rate</p>
|
||||
</div>
|
||||
<Card className="bg-gray-900/50">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium text-red-500 dark:text-red-500">
|
||||
Failure rate
|
||||
</CardTitle>
|
||||
<FrownIcon className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{rate}</div>
|
||||
<p className="text-xs text-muted-foreground">Failed requests in {dateRangeText}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -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<string>;
|
||||
|
|
@ -1002,31 +1064,54 @@ export function OperationsStats({
|
|||
|
||||
const operationsStats = query.data?.operationsStats;
|
||||
const allOperationsStats = query.data?.allOperations;
|
||||
dateRangeText = dateRangeText.toLowerCase();
|
||||
|
||||
return (
|
||||
<section className="text-gray-600 dark:text-gray-400 space-y-12 transition-opacity ease-in-out duration-700">
|
||||
<OperationsFallback isError={isError} refetch={refetch} isFetching={isFetching}>
|
||||
<div className="grid gap-y-4 grid-cols-4 rounded-md p-5 border border-gray-800 bg-gray-900/50">
|
||||
<RequestsStats requests={operationsStats?.totalRequests} />
|
||||
<RPM requests={operationsStats?.totalRequests} period={period} />
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<RequestsStats requests={operationsStats?.totalRequests} dateRangeText={dateRangeText} />
|
||||
<RPM
|
||||
requests={operationsStats?.totalRequests}
|
||||
period={period}
|
||||
dateRangeText={dateRangeText}
|
||||
/>
|
||||
{mode === 'operation-list' ? (
|
||||
<UniqueOperationsStats operations={operationsStats?.totalOperations} />
|
||||
<UniqueOperationsStats
|
||||
operations={operationsStats?.totalOperations}
|
||||
dateRangeText={dateRangeText}
|
||||
/>
|
||||
) : (
|
||||
<OperationRelativeFrequency
|
||||
allOperationRequests={allOperationsStats?.totalRequests ?? 0}
|
||||
operationRequests={operationsStats?.totalRequests ?? 0}
|
||||
dateRangeText={dateRangeText}
|
||||
/>
|
||||
)}
|
||||
<SuccessRateStats
|
||||
requests={operationsStats?.totalRequests}
|
||||
totalFailures={operationsStats?.totalFailures}
|
||||
dateRangeText={dateRangeText}
|
||||
/>
|
||||
<PercentileStats
|
||||
value={operationsStats?.duration?.p99}
|
||||
percentile={99}
|
||||
dateRangeText={dateRangeText}
|
||||
/>
|
||||
<PercentileStats
|
||||
value={operationsStats?.duration?.p95}
|
||||
percentile={95}
|
||||
dateRangeText={dateRangeText}
|
||||
/>
|
||||
<PercentileStats
|
||||
value={operationsStats?.duration?.p90}
|
||||
percentile={90}
|
||||
dateRangeText={dateRangeText}
|
||||
/>
|
||||
<PercentileStats value={operationsStats?.duration?.p99} title="Latency p99" />
|
||||
<PercentileStats value={operationsStats?.duration?.p95} title="Latency p95" />
|
||||
<PercentileStats value={operationsStats?.duration?.p90} title="Latency p90" />
|
||||
<FailureRateStats
|
||||
requests={operationsStats?.totalRequests}
|
||||
totalFailures={operationsStats?.totalFailures}
|
||||
dateRangeText={dateRangeText}
|
||||
/>
|
||||
</div>
|
||||
</OperationsFallback>
|
||||
|
|
@ -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 (
|
||||
<Tooltip content={tooltipText}>
|
||||
<button className="flex gap-2 items-center justify-center" onClick={onClick}>
|
||||
{children}
|
||||
<button className="flex items-center justify-center" onClick={onClick}>
|
||||
<div>{children}</div>
|
||||
|
||||
<span>
|
||||
<TriangleUpIcon className={clsx(sortOrder === 'asc' && 'text-orange-500')} />
|
||||
<TriangleUpIcon
|
||||
className={clsx('rotate-180', sortOrder === 'desc' && 'text-orange-500')}
|
||||
/>
|
||||
</span>
|
||||
{sortOrder === 'asc' ? <TriangleUpIcon className="text-orange-500 ml-2" /> : null}
|
||||
{sortOrder === 'desc' ? (
|
||||
<TriangleUpIcon className="rotate-180 text-orange-500 ml-2" />
|
||||
) : null}
|
||||
</button>
|
||||
</Tooltip>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<table className={clsx('w-full', className)} {...props}>
|
||||
<table className={cn('w-full', className)} {...props}>
|
||||
{children}
|
||||
</table>
|
||||
);
|
||||
|
|
@ -31,7 +31,7 @@ function TFoot({ children, ...props }: ComponentProps<'tfoot'>): ReactElement {
|
|||
|
||||
function Th({ children, className, align = 'left', ...props }: ComponentProps<'th'>): ReactElement {
|
||||
return (
|
||||
<th className={clsx('px-5 py-4', className)} align={align} {...props}>
|
||||
<th className={cn('px-5 py-4', className)} align={align} {...props}>
|
||||
{children}
|
||||
</th>
|
||||
);
|
||||
|
|
@ -40,7 +40,7 @@ function Th({ children, className, align = 'left', ...props }: ComponentProps<'t
|
|||
function Tr({ children, className, ...props }: ComponentProps<'tr'>): ReactElement {
|
||||
return (
|
||||
<tr
|
||||
className={clsx('border border-gray-600/10 text-xs odd:bg-gray-600/10', className)}
|
||||
className={cn('border border-gray-600/10 text-xs odd:bg-gray-600/10', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
|
@ -51,8 +51,8 @@ function Tr({ children, className, ...props }: ComponentProps<'tr'>): ReactEleme
|
|||
function Td({ children, className, ...props }: ComponentProps<'td'>): ReactElement {
|
||||
return (
|
||||
<td
|
||||
className={clsx(
|
||||
'break-all px-5 py-4 text-sm',
|
||||
className={cn(
|
||||
'break-all px-4 py-2 text-sm',
|
||||
className,
|
||||
// column.align === 'right' && 'text-right',
|
||||
// column.width === 'auto' && 'w-1',
|
||||
|
|
|
|||
|
|
@ -116,14 +116,10 @@ performance:
|
|||
className="mt-10 max-w-2xl drop-shadow-md"
|
||||
/>
|
||||
|
||||
### 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.
|
||||
|
||||
<NextImage
|
||||
alt="Operations Overview"
|
||||
src={usageOperationsImage}
|
||||
className="mt-10 max-w-2xl drop-shadow-md"
|
||||
/>
|
||||
<NextImage alt="Insights" src={usageOperationsImage} className="mt-10 max-w-2xl drop-shadow-md" />
|
||||
|
|
|
|||
Loading…
Reference in a new issue