use more client preset (#2661)

This commit is contained in:
Laurin Quast 2023-07-27 14:14:18 +02:00 committed by GitHub
parent 32ab26c928
commit da27a1606b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 392 additions and 363 deletions

View file

@ -23,6 +23,6 @@ jobs:
uses: SimenB/github-actions-cpu-cores@v1
- name: typecheck
run: pnpm typecheck --concurrency ${{ steps.cpu-cores.outputs.count }}
run: pnpm run --workspace-concurrency ${{ steps.cpu-cores.outputs.count }} -r typecheck
env:
NODE_OPTIONS: '--max-old-space-size=4096'

View file

@ -45,7 +45,7 @@
"test": "vitest",
"test:e2e": "CYPRESS_BASE_URL=$HIVE_APP_BASE_URL cypress run",
"test:integration": "cd integration-tests && pnpm test:integration",
"typecheck": "pnpm turbo typecheck --color",
"typecheck": "pnpm run -r typecheck",
"upload-sourcemaps": "./scripts/upload-sourcemaps.sh",
"workspace": "pnpm run --filter $1 $2"
},

View file

@ -99,7 +99,6 @@
"@graphql-codegen/client-preset-swc-plugin": "0.2.0",
"@graphql-typed-document-node/core": "3.2.0",
"@hive/emails": "workspace:*",
"@hive/server": "workspace:*",
"@next/bundle-analyzer": "13.4.11",
"@storybook/addon-essentials": "7.0.23",
"@storybook/addon-interactions": "7.0.23",

View file

@ -149,13 +149,13 @@ const SchemaView_TargetFragment = graphql(`
cleanId
latestSchemaVersion {
id
valid
schemas {
nodes {
__typename
...SchemaView_SchemaFragment
}
}
...MarkAsValid_SchemaVersionFragment
}
}
`);

View file

@ -30,11 +30,21 @@ import {
import { Combobox } from '@/components/v2/combobox';
import { CreateAccessTokenModal, DeleteTargetModal } from '@/components/v2/modals';
import { FragmentType, graphql, useFragment } from '@/gql';
import { SetTargetValidationDocument } from '@/graphql';
import { canAccessTarget, TargetAccessScope } from '@/lib/access/target';
import { useRouteSelector, useToggle } from '@/lib/hooks';
import { withSessionProtection } from '@/lib/supertokens/guard';
const SetTargetValidationMutation = graphql(`
mutation Settings_SetTargetValidation($input: SetTargetValidationInput!) {
setTargetValidation(input: $input) {
id
validationSettings {
...TargetValidationSettingsFields
}
}
}
`);
const RegistryAccessTokens_MeFragment = graphql(`
fragment RegistryAccessTokens_MeFragment on Member {
...CanAccessTarget_MemberFragment
@ -379,7 +389,7 @@ function floorDate(date: Date): Date {
const ConditionalBreakingChanges = (): ReactElement => {
const router = useRouteSelector();
const [targetValidation, setValidation] = useMutation(SetTargetValidationDocument);
const [targetValidation, setValidation] = useMutation(SetTargetValidationMutation);
const [mutation, updateValidation] = useMutation(
TargetSettingsPage_UpdateTargetValidationSettingsMutation,
);

View file

@ -8,12 +8,12 @@ import { Title } from '@/components/common';
import { DataWrapper } from '@/components/v2';
import { LAST_VISITED_ORG_KEY } from '@/constants';
import { env } from '@/env/backend';
import { OrganizationsDocument } from '@/graphql';
import { graphql } from '@/gql';
import { writeLastVisitedOrganization } from '@/lib/cookies';
import { useRouteSelector } from '@/lib/hooks/use-route-selector';
import { withSessionProtection } from '@/lib/supertokens/guard';
// eslint-disable-next-line import/no-extraneous-dependencies -- TODO: should we move to "dependencies"?
import { InternalApi } from '@hive/server';
import { type InternalApi } from '@hive/server';
import { createTRPCProxyClient, httpLink } from '@trpc/client';
async function getSuperTokensUserIdFromRequest(
@ -67,8 +67,20 @@ export const getServerSideProps = withSessionProtection(async ({ req, res }) =>
};
});
export const OrganizationsQuery = graphql(`
query organizations {
organizations {
nodes {
...OrganizationFields
cleanId
}
total
}
}
`);
function Home(): ReactElement {
const [query] = useQuery({ query: OrganizationsDocument });
const [query] = useQuery({ query: OrganizationsQuery });
const router = useRouteSelector();
useEffect(() => {

View file

@ -1,10 +1,3 @@
import {
OrganizationActivitiesQuery,
ProjectActivitiesQuery,
TargetActivitiesQuery,
} from '@/graphql';
import { OrganizationActivitiesQuery } from '@/graphql';
export type ActivityNode =
| OrganizationActivitiesQuery['organizationActivities']['nodes'][0]
| ProjectActivitiesQuery['projectActivities']['nodes'][0]
| TargetActivitiesQuery['targetActivities']['nodes'][0];
export type ActivityNode = OrganizationActivitiesQuery['organizationActivities']['nodes'][0];

View file

@ -2,7 +2,6 @@ import { ReactElement } from 'react';
import { useQuery } from 'urql';
import { DataWrapper, Table, TBody, Td, Th, THead, Tr } from '@/components/v2';
import { FragmentType, graphql, useFragment } from '@/gql';
import { UsageEstimationDocument } from '@/graphql';
import { Scale } from '../common';
import { calculatePeriod } from '../common/TimeFilter';
@ -17,6 +16,16 @@ const OrganizationUsageEstimationView_OrganizationFragment = graphql(`
}
`);
const Usage_UsageEstimationQuery = graphql(`
query Usage_UsageEstimationQuery($range: DateRangeInput!, $organization: ID!) {
usageEstimation(range: $range) {
org(selector: { organization: $organization }) {
operations
}
}
}
`);
export function OrganizationUsageEstimationView(props: {
organization: FragmentType<typeof OrganizationUsageEstimationView_OrganizationFragment>;
}): ReactElement {
@ -27,7 +36,7 @@ export function OrganizationUsageEstimationView(props: {
const period = calculatePeriod('month');
const [query] = useQuery({
query: UsageEstimationDocument,
query: Usage_UsageEstimationQuery,
variables: {
organization: organization.cleanId,
range: period,

View file

@ -1,16 +1,30 @@
import { ReactElement, useCallback } from 'react';
import { useMutation } from 'urql';
import { Button, Tooltip } from '@/components/v2';
import { SchemaVersionFieldsFragment, UpdateSchemaVersionStatusDocument } from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { useRouteSelector } from '@/lib/hooks';
export function MarkAsValid({
version,
}: {
version: Pick<SchemaVersionFieldsFragment, 'id' | 'valid'>;
const UpdateSchemaVersionStatusMutation = graphql(`
mutation updateSchemaVersionStatus($input: SchemaVersionUpdateInput!) {
updateSchemaVersionStatus(input: $input) {
...SchemaVersionFields
}
}
`);
const MarkAsValid_SchemaVersionFragment = graphql(`
fragment MarkAsValid_SchemaVersionFragment on SchemaVersion {
id
valid
}
`);
export function MarkAsValid(props: {
version: FragmentType<typeof MarkAsValid_SchemaVersionFragment>;
}): ReactElement | null {
const router = useRouteSelector();
const [mutation, mutate] = useMutation(UpdateSchemaVersionStatusDocument);
const version = useFragment(MarkAsValid_SchemaVersionFragment, props.version);
const [mutation, mutate] = useMutation(UpdateSchemaVersionStatusMutation);
const markAsValid = useCallback(async () => {
await mutate({
input: {

View file

@ -4,26 +4,43 @@ import { FixedSizeList, ListChildComponentProps } from 'react-window';
import { useQuery } from 'urql';
import { useDebouncedCallback } from 'use-debounce';
import { Button, Checkbox, Drawer, Input, Spinner } from '@/components/v2';
import { DateRangeInput, OperationsStatsDocument, OperationStatsFieldsFragment } from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { DateRangeInput } from '@/graphql';
import { useFormattedNumber, useRouteSelector, useToggle } from '@/lib/hooks';
import { ChevronUpIcon } from '@radix-ui/react-icons';
const OperationsFilter_OperationStatsConnectionFragment = graphql(`
fragment OperationsFilter_OperationStatsConnectionFragment on OperationStatsConnection {
nodes {
id
operationHash
name
...OperationRow_OperationStatsFragment
}
}
`);
function OperationsFilter({
onClose,
isOpen,
onFilter,
operations,
operationStatsConnection,
selected,
}: {
onClose(): void;
onFilter(keys: string[]): void;
isOpen: boolean;
operations: readonly OperationStatsFieldsFragment[];
operationStatsConnection: FragmentType<typeof OperationsFilter_OperationStatsConnectionFragment>;
selected?: string[];
}): ReactElement {
const operations = useFragment(
OperationsFilter_OperationStatsConnectionFragment,
operationStatsConnection,
);
function getOperationHashes() {
const items: string[] = [];
for (const op of operations) {
for (const op of operations.nodes) {
if (op.operationHash) {
items.push(op.operationHash);
}
@ -51,7 +68,9 @@ function OperationsFilter({
const [searchTerm, setSearchTerm] = useState('');
const debouncedFilter = useDebouncedCallback((value: string) => {
setVisibleOperations(
operations.filter(op => op.name.toLocaleLowerCase().includes(value.toLocaleLowerCase())),
operations.nodes.filter(op =>
op.name.toLocaleLowerCase().includes(value.toLocaleLowerCase()),
),
);
}, 500);
@ -65,7 +84,7 @@ function OperationsFilter({
[setSearchTerm, debouncedFilter],
);
const [visibleOperations, setVisibleOperations] = useState(operations);
const [visibleOperations, setVisibleOperations] = useState(operations.nodes);
const selectAll = useCallback(() => {
setSelectedItems(getOperationHashes());
@ -82,7 +101,7 @@ function OperationsFilter({
<OperationRow
style={style}
key={operation.id}
operation={operation}
operationStats={operation}
selected={selectedItems.includes(operation.operationHash || '')}
onSelect={onSelect}
/>
@ -103,7 +122,7 @@ function OperationsFilter({
value={searchTerm}
onClear={() => {
setSearchTerm('');
setVisibleOperations(operations);
setVisibleOperations(operations.nodes);
}}
/>
<div className="flex gap-2 items-center w-full">
@ -151,6 +170,17 @@ function OperationsFilter({
);
}
const OperationsFilterContainer_OperationStatsQuery = graphql(`
query OperationsFilterContainer_OperationStatsQuery($selector: OperationsStatsSelectorInput!) {
operationsStats(selector: $selector) {
operations {
...OperationsFilter_OperationStatsConnectionFragment
total
}
}
}
`);
function OperationsFilterContainer({
period,
isOpen,
@ -166,7 +196,7 @@ function OperationsFilterContainer({
}): ReactElement | null {
const router = useRouteSelector();
const [query] = useQuery({
query: OperationsStatsDocument,
query: OperationsFilterContainer_OperationStatsQuery,
variables: {
selector: {
organization: router.organizationId,
@ -186,32 +216,40 @@ function OperationsFilterContainer({
return <Spinner />;
}
const allOperations = query.data.operationsStats?.operations?.nodes ?? [];
return (
<OperationsFilter
operations={allOperations}
operationStatsConnection={query.data.operationsStats?.operations}
selected={selected}
isOpen={isOpen}
onClose={onClose}
onFilter={hashes => {
onFilter(hashes.length === allOperations.length ? [] : hashes);
onFilter(hashes.length === query.data?.operationsStats.operations.total ? [] : hashes);
}}
/>
);
}
const OperationRow_OperationStatsFragment = graphql(`
fragment OperationRow_OperationStatsFragment on OperationStats {
id
name
operationHash
count
}
`);
function OperationRow({
operation,
operationStats,
selected,
onSelect,
style,
}: {
operation: OperationStatsFieldsFragment;
operationStats: FragmentType<typeof OperationRow_OperationStatsFragment>;
selected: boolean;
onSelect(id: string, selected: boolean): void;
style: any;
}): ReactElement {
const operation = useFragment(OperationRow_OperationStatsFragment, operationStats);
const requests = useFormattedNumber(operation.count);
const hash = operation.operationHash || '';
const change = useCallback(() => {
@ -261,17 +299,25 @@ export function OperationsFilterTrigger({
);
}
const ClientRow_ClientStatsFragment = graphql(`
fragment ClientRow_ClientStatsFragment on ClientStats {
name
count
}
`);
function ClientRow({
client,
selected,
onSelect,
style,
...props
}: {
client: { name: string; count: number };
client: FragmentType<typeof ClientRow_ClientStatsFragment>;
selected: boolean;
onSelect(id: string, selected: boolean): void;
style: any;
}): ReactElement {
const client = useFragment(ClientRow_ClientStatsFragment, props.client);
const requests = useFormattedNumber(client.count);
const hash = client.name;
const change = useCallback(() => {
@ -294,21 +340,34 @@ function ClientRow({
);
}
const ClientsFilter_ClientStatsConnectionFragment = graphql(`
fragment ClientsFilter_ClientStatsConnectionFragment on ClientStatsConnection {
nodes {
name
...ClientRow_ClientStatsFragment
}
}
`);
function ClientsFilter({
onClose,
isOpen,
onFilter,
clients,
clientStatsConnection,
selected,
}: {
onClose(): void;
onFilter(keys: string[]): void;
isOpen: boolean;
clients: readonly { name: string; count: number }[];
clientStatsConnection: FragmentType<typeof ClientsFilter_ClientStatsConnectionFragment>;
selected?: string[];
}): ReactElement {
const clientConnection = useFragment(
ClientsFilter_ClientStatsConnectionFragment,
clientStatsConnection,
);
function getClientNames() {
return clients.map(client => client.name);
return clientConnection.nodes.map(client => client.name);
}
const [selectedItems, setSelectedItems] = useState<string[]>(() =>
@ -331,7 +390,9 @@ function ClientsFilter({
const [searchTerm, setSearchTerm] = useState('');
const debouncedFilter = useDebouncedCallback((value: string) => {
setVisibleOperations(
clients.filter(client => client.name.toLocaleLowerCase().includes(value.toLocaleLowerCase())),
clientConnection.nodes.filter(client =>
client.name.toLocaleLowerCase().includes(value.toLocaleLowerCase()),
),
);
}, 500);
@ -345,11 +406,11 @@ function ClientsFilter({
[setSearchTerm, debouncedFilter],
);
const [visibleOperations, setVisibleOperations] = useState(clients);
const [visibleOperations, setVisibleOperations] = useState(clientConnection.nodes);
const selectAll = useCallback(() => {
setSelectedItems(getClientNames());
}, [clients]);
}, [clientConnection.nodes]);
const selectNone = useCallback(() => {
setSelectedItems([]);
}, [setSelectedItems]);
@ -383,7 +444,7 @@ function ClientsFilter({
value={searchTerm}
onClear={() => {
setSearchTerm('');
setVisibleOperations(clients);
setVisibleOperations(clientConnection.nodes);
}}
/>
<div className="flex gap-2 items-center w-full">
@ -431,6 +492,19 @@ function ClientsFilter({
);
}
const ClientsFilterContainer_ClientStatsQuery = graphql(`
query ClientsFilterContainer_ClientStats($selector: OperationsStatsSelectorInput!) {
operationsStats(selector: $selector) {
clients {
...ClientsFilter_ClientStatsConnectionFragment
nodes {
__typename
}
}
}
}
`);
function ClientsFilterContainer({
period,
isOpen,
@ -446,7 +520,7 @@ function ClientsFilterContainer({
}): ReactElement | null {
const router = useRouteSelector();
const [query] = useQuery({
query: OperationsStatsDocument,
query: ClientsFilterContainer_ClientStatsQuery,
variables: {
selector: {
organization: router.organizationId,
@ -466,11 +540,11 @@ function ClientsFilterContainer({
return <Spinner />;
}
const allClients = query.data.operationsStats?.clients?.nodes ?? [];
const allClients = query.data.operationsStats?.clients.nodes ?? [];
return (
<ClientsFilter
clients={allClients}
clientStatsConnection={query.data.operationsStats.clients}
selected={selected}
isOpen={isOpen}
onClose={onClose}

View file

@ -18,8 +18,8 @@ import {
Tr,
} from '@/components/v2';
import { env } from '@/env/frontend';
import { graphql } from '@/gql';
import { DateRangeInput, OperationsStatsDocument, OperationStatsFieldsFragment } from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { DateRangeInput } from '@/graphql';
import { useDecimal, useFormattedDuration, useFormattedNumber, useToggle } from '@/lib/hooks';
import { ChevronUpIcon, ExclamationTriangleIcon } from '@radix-ui/react-icons';
import {
@ -203,7 +203,7 @@ function OperationsTable({
organization: string;
project: string;
target: string;
clients: readonly { name: string }[];
clients: readonly { name: string }[] | null;
clientFilter: string | null;
setClientFilter: (filter: string) => void;
}): ReactElement {
@ -322,53 +322,83 @@ function OperationsTable({
);
}
const OperationsTableContainer_OperationsStatsFragment = graphql(`
fragment OperationsTableContainer_OperationsStatsFragment on OperationsStats {
clients {
nodes {
name
}
}
operations {
nodes {
id
name
operationHash
kind
duration {
p90
p95
p99
}
countOk
count
percentage
}
}
}
`);
function OperationsTableContainer({
operations,
operationsFilter,
organization,
project,
target,
clients,
clientFilter,
setClientFilter,
className,
...props
}: {
operations: readonly OperationStatsFieldsFragment[];
operationStats: FragmentType<typeof OperationsTableContainer_OperationsStatsFragment> | null;
operationsFilter: readonly string[];
organization: string;
project: string;
target: string;
clients: readonly { name: string }[];
clientFilter: string | null;
setClientFilter: (client: string) => void;
className?: string;
}): ReactElement {
const operationStats = useFragment(
OperationsTableContainer_OperationsStatsFragment,
props.operationStats,
);
const data = useMemo(() => {
const records: Operation[] = [];
for (const op of operations) {
if (
operationsFilter.length > 0 &&
op.operationHash &&
!operationsFilter.includes(op.operationHash)
) {
continue;
if (operationStats) {
for (const op of operationStats.operations.nodes) {
if (
operationsFilter.length > 0 &&
op.operationHash &&
!operationsFilter.includes(op.operationHash)
) {
continue;
}
records.push({
id: op.id,
name: op.name,
kind: op.kind,
p90: op.duration.p90,
p95: op.duration.p95,
p99: op.duration.p99,
failureRate: 1 - op.countOk / op.count,
requests: op.count,
percentage: op.percentage,
hash: op.operationHash!,
});
}
records.push({
id: op.id,
name: op.name,
kind: op.kind,
p90: op.duration.p90,
p95: op.duration.p95,
p99: op.duration.p99,
failureRate: 1 - op.countOk / op.count,
requests: op.count,
percentage: op.percentage,
hash: op.operationHash!,
});
}
return records;
}, [operations, operationsFilter]);
}, [operationStats?.operations.nodes, operationsFilter]);
const [pagination, setPagination] = useState<PaginationState>({ pageIndex: 0, pageSize: 20 });
const [sorting, setSorting] = useState<SortingState>([]);
@ -403,13 +433,31 @@ function OperationsTableContainer({
organization={organization}
project={project}
target={target}
clients={clients}
clients={operationStats?.clients.nodes ?? null}
clientFilter={clientFilter}
setClientFilter={setClientFilter}
/>
);
}
const OperationsList_OperationsStatsQuery = graphql(`
query OperationsList_OperationsStats($selector: OperationsStatsSelectorInput!) {
operationsStats(selector: $selector) {
clients {
nodes {
__typename
}
}
operations {
nodes {
__typename
}
}
...OperationsTableContainer_OperationsStatsFragment
}
}
`);
export function OperationsList({
className,
organization,
@ -429,7 +477,7 @@ export function OperationsList({
}): ReactElement {
const [clientFilter, setClientFilter] = useState<string | null>(null);
const [query, refetch] = useQuery({
query: OperationsStatsDocument,
query: OperationsList_OperationsStatsQuery,
variables: {
selector: {
organization,
@ -441,8 +489,6 @@ export function OperationsList({
},
},
});
const operations = query.data?.operationsStats?.operations?.nodes ?? [];
const clients = query.data?.operationsStats?.clients?.nodes ?? [];
return (
<OperationsFallback
@ -451,10 +497,9 @@ export function OperationsList({
refetch={() => refetch({ requestPolicy: 'cache-and-network' })}
>
<OperationsTableContainer
operations={operations}
operationStats={query.data?.operationsStats ?? null}
operationsFilter={operationsFilter}
className={className}
clients={clients}
setClientFilter={setClientFilter}
clientFilter={clientFilter}
organization={organization}

View file

@ -4,11 +4,8 @@ import AutoSizer from 'react-virtualized-auto-sizer';
import { useQuery } from 'urql';
import { Section } from '@/components/common';
import { CHART_PRIMARY_COLOR } from '@/constants';
import {
DateRangeInput,
GeneralOperationsStatsDocument,
GeneralOperationsStatsQuery,
} from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { DateRangeInput } from '@/graphql';
import {
formatDuration,
formatNumber,
@ -23,6 +20,28 @@ import { useChartStyles } from '@/utils';
import { OperationsFallback } from './Fallback';
import { createEmptySeries, fullSeries, resolutionToMilliseconds } from './utils';
const Stats_GeneralOperationsStatsQuery = graphql(`
query Stats_GeneralOperationsStats($selector: OperationsStatsSelectorInput!, $resolution: Int!) {
operationsStats(selector: $selector) {
... on OperationsStats {
totalRequests
totalFailures
totalOperations
duration {
p75
p90
p95
p99
}
}
...OverTimeStats_OperationsStatsFragment
...RpmOverTimeStats_OperationStatsFragment
...LatencyOverTimeStats_OperationStatsFragment
...ClientsStats_OperationsStatsFragment
}
}
`);
const classes = {
root: cn('text-center'),
value: cn('font-normal text-3xl text-gray-900 dark:text-white'),
@ -121,17 +140,31 @@ function FailureRateStats({
);
}
const OverTimeStats_OperationsStatsFragment = graphql(`
fragment OverTimeStats_OperationsStatsFragment on OperationsStats {
failuresOverTime(resolution: $resolution) {
date
value
}
requestsOverTime(resolution: $resolution) {
date
value
}
}
`);
function OverTimeStats({
period,
resolution,
requestsOverTime = [],
failuresOverTime = [],
operationStats,
}: {
period: DateRangeInput;
resolution: number;
requestsOverTime?: GeneralOperationsStatsQuery['operationsStats']['requestsOverTime'];
failuresOverTime?: GeneralOperationsStatsQuery['operationsStats']['failuresOverTime'];
operationStats: FragmentType<typeof OverTimeStats_OperationsStatsFragment> | null;
}): ReactElement {
const { failuresOverTime = [], requestsOverTime = [] } =
useFragment(OverTimeStats_OperationsStatsFragment, operationStats) ?? {};
const styles = useChartStyles();
const interval = resolutionToMilliseconds(resolution, period);
const requests = useMemo(() => {
@ -250,15 +283,34 @@ function OverTimeStats({
);
}
function ClientsStats({
clients = [],
}: {
clients?: GeneralOperationsStatsQuery['operationsStats']['clients']['nodes'];
const ClientsStats_OperationsStatsFragment = graphql(`
fragment ClientsStats_OperationsStatsFragment on OperationsStats {
clients {
nodes {
name
count
percentage
versions {
version
count
percentage
}
}
total
}
}
`);
function ClientsStats(props: {
operationStats: FragmentType<typeof ClientsStats_OperationsStatsFragment> | null;
}): ReactElement {
const styles = useChartStyles();
const operationStats = useFragment(ClientsStats_OperationsStatsFragment, props.operationStats);
const sortedClients = useMemo(() => {
return clients?.length ? clients.slice().sort((a, b) => b.count - a.count) : [];
}, [clients]);
return operationStats?.clients.nodes?.length
? operationStats.clients.nodes.slice().sort((a, b) => b.count - a.count)
: [];
}, [operationStats?.clients.nodes]);
const byClient = useMemo(() => {
let values: string[] = [];
const labels: string[] = [];
@ -438,20 +490,36 @@ function ClientsStats({
);
}
const LatencyOverTimeStats_OperationStatsFragment = graphql(`
fragment LatencyOverTimeStats_OperationStatsFragment on OperationsStats {
durationOverTime(resolution: $resolution) {
date
duration {
p75
p90
p95
p99
}
}
}
`);
function LatencyOverTimeStats({
period,
resolution,
duration = [],
operationStats,
}: {
period: {
from: string;
to: string;
};
resolution: number;
duration?: GeneralOperationsStatsQuery['operationsStats']['durationOverTime'];
operationStats?: FragmentType<typeof LatencyOverTimeStats_OperationStatsFragment> | null;
}): ReactElement {
const styles = useChartStyles();
const interval = resolutionToMilliseconds(resolution, period);
const { durationOverTime: duration = [] } =
useFragment(LatencyOverTimeStats_OperationStatsFragment, operationStats) ?? {};
const p75 = useMemo(() => {
if (duration?.length) {
return fullSeries(
@ -576,20 +644,30 @@ function LatencyOverTimeStats({
);
}
const RpmOverTimeStats_OperationStatsFragment = graphql(`
fragment RpmOverTimeStats_OperationStatsFragment on OperationsStats {
requestsOverTime(resolution: $resolution) {
date
value
}
}
`);
function RpmOverTimeStats({
period,
resolution,
requestsOverTime = [],
operationStats,
}: {
period: {
from: string;
to: string;
};
resolution: number;
requestsOverTime?: GeneralOperationsStatsQuery['operationsStats']['requestsOverTime'];
operationStats: FragmentType<typeof RpmOverTimeStats_OperationStatsFragment> | null;
}): ReactElement {
const styles = useChartStyles();
const requests = requestsOverTime ?? [];
const { requestsOverTime: requests = [] } =
useFragment(RpmOverTimeStats_OperationStatsFragment, operationStats) ?? {};
const interval = resolutionToMilliseconds(resolution, period);
const windowInM = interval / (60 * 1000);
@ -703,7 +781,7 @@ export function OperationsStats({
}): ReactElement {
const resolution = 90;
const [query, refetchQuery] = useQuery({
query: GeneralOperationsStatsDocument,
query: Stats_GeneralOperationsStatsQuery,
variables: {
selector: {
organization,
@ -749,15 +827,14 @@ export function OperationsStats({
</div>
</OperationsFallback>
<div>
<ClientsStats clients={operationsStats?.clients?.nodes} />
<ClientsStats operationStats={operationsStats ?? null} />
</div>
<div>
<OperationsFallback isError={isError} refetch={refetch} isFetching={isFetching}>
<OverTimeStats
period={period}
resolution={resolution}
requestsOverTime={operationsStats?.requestsOverTime}
failuresOverTime={operationsStats?.failuresOverTime}
operationStats={operationsStats ?? null}
/>
</OperationsFallback>
</div>
@ -766,7 +843,7 @@ export function OperationsStats({
<RpmOverTimeStats
period={period}
resolution={resolution}
requestsOverTime={operationsStats?.requestsOverTime}
operationStats={operationsStats ?? null}
/>
</OperationsFallback>
</div>
@ -774,7 +851,7 @@ export function OperationsStats({
<OperationsFallback isError={isError} refetch={refetch}>
<LatencyOverTimeStats
period={period}
duration={operationsStats?.durationOverTime}
operationStats={operationsStats ?? null}
resolution={resolution}
/>
</OperationsFallback>

View file

@ -2,10 +2,25 @@ import { ReactElement } from 'react';
import { useRouter } from 'next/router';
import { useMutation } from 'urql';
import { Button, Heading, Modal } from '@/components/v2';
import { DeleteProjectDocument } from '@/graphql';
import { graphql } from '@/gql';
import { useRouteSelector } from '@/lib/hooks';
import { TrashIcon } from '@radix-ui/react-icons';
export const DeleteProjectMutation = graphql(`
mutation deleteProject($selector: ProjectSelectorInput!) {
deleteProject(selector: $selector) {
selector {
organization
project
}
deletedProject {
__typename
id
}
}
}
`);
export const DeleteProjectModal = ({
isOpen,
toggleModalOpen,
@ -13,7 +28,7 @@ export const DeleteProjectModal = ({
isOpen: boolean;
toggleModalOpen: () => void;
}): ReactElement => {
const [, mutate] = useMutation(DeleteProjectDocument);
const [, mutate] = useMutation(DeleteProjectMutation);
const router = useRouteSelector();
const { replace } = useRouter();

View file

@ -2,9 +2,25 @@ import { ReactElement } from 'react';
import { useRouter } from 'next/router';
import { useMutation } from 'urql';
import { Button, Heading, Modal } from '@/components/v2';
import { DeleteTargetDocument } from '@/graphql';
import { graphql } from '@/gql';
import { TrashIcon } from '@radix-ui/react-icons';
export const DeleteTargetMutation = graphql(`
mutation deleteTarget($selector: TargetSelectorInput!) {
deleteTarget(selector: $selector) {
selector {
organization
project
target
}
deletedTarget {
__typename
id
}
}
}
`);
export const DeleteTargetModal = ({
isOpen,
toggleModalOpen,
@ -18,7 +34,7 @@ export const DeleteTargetModal = ({
projectId: string;
targetId: string;
}): ReactElement => {
const [, mutate] = useMutation(DeleteTargetDocument);
const [, mutate] = useMutation(DeleteTargetMutation);
const { replace } = useRouter();
return (

View file

@ -17,7 +17,7 @@ import { createThirdPartyEmailPasswordNodeOktaProvider } from '@/lib/supertokens
// eslint-disable-next-line import/no-extraneous-dependencies -- TODO: should we move to "dependencies"?
import { EmailsApi } from '@hive/emails';
// eslint-disable-next-line import/no-extraneous-dependencies -- TODO: should we move to "dependencies"?
import { InternalApi } from '@hive/server';
import { type InternalApi } from '@hive/server';
import { createTRPCProxyClient, CreateTRPCProxyClient, httpLink } from '@trpc/client';
import { fetch } from '@whatwg-node/fetch';

View file

@ -1,9 +0,0 @@
mutation deletePersistedOperation($input: PersistedOperationSelectorInput!) {
deletePersistedOperation(selector: $input) {
deletedPersistedOperation {
__typename
id
operationHash
}
}
}

View file

@ -1,12 +0,0 @@
mutation deleteProject($selector: ProjectSelectorInput!) {
deleteProject(selector: $selector) {
selector {
organization
project
}
deletedProject {
__typename
id
}
}
}

View file

@ -1,13 +0,0 @@
mutation deleteTarget($selector: TargetSelectorInput!) {
deleteTarget(selector: $selector) {
selector {
organization
project
target
}
deletedTarget {
__typename
id
}
}
}

View file

@ -1,3 +0,0 @@
mutation sendFeedback($feedback: String!) {
sendFeedback(feedback: $feedback)
}

View file

@ -1,8 +0,0 @@
mutation setTargetValidation($input: SetTargetValidationInput!) {
setTargetValidation(input: $input) {
id
validationSettings {
...TargetValidationSettingsFields
}
}
}

View file

@ -1,10 +0,0 @@
mutation updateOrganizationMemberAccess($input: OrganizationMemberAccessInput!) {
updateOrganizationMemberAccess(input: $input) {
selector {
organization
}
organization {
...OrganizationFields
}
}
}

View file

@ -1,5 +0,0 @@
mutation updateSchemaVersionStatus($input: SchemaVersionUpdateInput!) {
updateSchemaVersionStatus(input: $input) {
...SchemaVersionFields
}
}

View file

@ -1,14 +0,0 @@
mutation updateTargetName($input: UpdateTargetNameInput!) {
updateTargetName(input: $input) {
ok {
selector {
organization
project
target
}
updatedTarget {
...TargetFields
}
}
}
}

View file

@ -1,49 +0,0 @@
query generalOperationsStats($selector: OperationsStatsSelectorInput!, $resolution: Int!) {
operationsStats(selector: $selector) {
... on OperationsStats {
totalRequests
totalFailures
totalOperations
duration {
p75
p90
p95
p99
}
}
... on OperationsStats {
failuresOverTime(resolution: $resolution) {
date
value
}
requestsOverTime(resolution: $resolution) {
date
value
}
durationOverTime(resolution: $resolution) {
date
duration {
p75
p90
p95
p99
}
}
}
... on OperationsStats {
clients {
nodes {
name
count
percentage
versions {
version
count
percentage
}
}
total
}
}
}
}

View file

@ -1,3 +0,0 @@
query hasCollectedOperations($selector: TargetSelectorInput!) {
hasCollectedOperations(selector: $selector)
}

View file

@ -1,16 +0,0 @@
query operationsStats($selector: OperationsStatsSelectorInput!) {
operationsStats(selector: $selector) {
operations {
nodes {
...OperationStatsFields
}
total
}
clients {
nodes {
name
count
}
}
}
}

View file

@ -1,7 +0,0 @@
query usageEstimation($range: DateRangeInput!, $organization: ID!) {
usageEstimation(range: $range) {
org(selector: { organization: $organization }) {
operations
}
}
}

View file

@ -1,8 +0,0 @@
query organizations {
organizations {
nodes {
...OrganizationFields
}
total
}
}

View file

@ -1,18 +0,0 @@
query projectActivities($selector: ProjectActivitiesSelector!) {
projectActivities(selector: $selector) {
nodes {
__typename
id
createdAt
...ProjectCreated
...ProjectDeleted
...ProjectNameUpdated
...ProjectIdUpdated
...TargetCreated
...TargetDeleted
...TargetNameUpdated
...TargetIdUpdated
}
total
}
}

View file

@ -1,14 +0,0 @@
query projectsWithTargets($selector: OrganizationSelectorInput!) {
projects(selector: $selector) {
nodes {
...ProjectFields
targets {
nodes {
...TargetFields
}
total
}
}
total
}
}

View file

@ -1,8 +0,0 @@
query projects($selector: OrganizationSelectorInput!) {
projects(selector: $selector) {
nodes {
...ProjectFields
}
total
}
}

View file

@ -1,14 +0,0 @@
query targetActivities($selector: TargetActivitiesSelector!) {
targetActivities(selector: $selector) {
nodes {
__typename
id
createdAt
...TargetCreated
...TargetDeleted
...TargetNameUpdated
...TargetIdUpdated
}
total
}
}

View file

@ -1,8 +0,0 @@
query targets($selector: ProjectSelectorInput!) {
targets(selector: $selector) {
total
nodes {
...TargetFields
}
}
}

View file

@ -4,7 +4,7 @@ import { TypeInput as ThirdPartEmailPasswordTypeInput } from 'supertokens-node/r
import zod from 'zod';
import { env } from '@/env/backend';
// eslint-disable-next-line import/no-extraneous-dependencies -- TODO: should we move to "dependencies"?
import { InternalApi } from '@hive/server';
import { type InternalApi } from '@hive/server';
import { CreateTRPCProxyClient } from '@trpc/client';
const OIDCProfileInfoSchema = zod.object({

View file

@ -14,20 +14,17 @@ import type { CreateOrganizationMutation } from '@/components/v2/modals/create-o
import type { CreateProjectMutation } from '@/components/v2/modals/create-project';
import type { CreateTarget_CreateTargetMutation } from '@/components/v2/modals/create-target';
import type { DeleteOrganizationDocument } from '@/components/v2/modals/delete-organization';
import { type DeleteProjectMutation } from '@/components/v2/modals/delete-project';
import { type DeleteTargetMutation } from '@/components/v2/modals/delete-target';
import { graphql } from '@/gql';
import { ResultOf, VariablesOf } from '@graphql-typed-document-node/core';
import { Cache, QueryInput, UpdateResolver } from '@urql/exchange-graphcache';
import { OrganizationsQuery } from '../../pages';
import { CollectionsQuery } from '../../pages/[orgId]/[projectId]/[targetId]/laboratory';
import {
TokensDocument,
type DeleteTokensDocument,
} from '../../pages/[orgId]/[projectId]/[targetId]/settings';
import {
DeletePersistedOperationDocument,
DeleteProjectDocument,
DeleteTargetDocument,
OrganizationsDocument,
} from '../graphql';
const TargetsDocument = graphql(`
query targets($selector: ProjectSelectorInput!) {
@ -85,7 +82,7 @@ const createOrganization: TypedDocumentNodeUpdateResolver<typeof CreateOrganizat
updateQuery(
cache,
{
query: OrganizationsDocument,
query: OrganizationsQuery,
},
data => {
if (createOrganization.ok) {
@ -127,7 +124,7 @@ const createProject: TypedDocumentNodeUpdateResolver<typeof CreateProjectMutatio
});
};
const deleteProject: TypedDocumentNodeUpdateResolver<typeof DeleteProjectDocument> = (
const deleteProject: TypedDocumentNodeUpdateResolver<typeof DeleteProjectMutation> = (
{ deleteProject },
_args,
cache,
@ -171,7 +168,7 @@ const createTarget: TypedDocumentNodeUpdateResolver<typeof CreateTarget_CreateTa
);
};
const deleteTarget: TypedDocumentNodeUpdateResolver<typeof DeleteTargetDocument> = (
const deleteTarget: TypedDocumentNodeUpdateResolver<typeof DeleteTargetMutation> = (
{ deleteTarget },
_args,
cache,
@ -279,16 +276,6 @@ const addAlert: TypedDocumentNodeUpdateResolver<typeof CreateAlertModal_AddAlert
id: updatedProject.id,
});
};
const deletePersistedOperation: TypedDocumentNodeUpdateResolver<
typeof DeletePersistedOperationDocument
> = ({ deletePersistedOperation }, _args, cache) => {
const operation = deletePersistedOperation.deletedPersistedOperation;
cache.invalidate({
__typename: operation.__typename,
id: operation.id,
});
};
const deleteDocumentCollection: TypedDocumentNodeUpdateResolver<DeleteCollectionMutationType> = (
mutation,
@ -387,7 +374,6 @@ export const Mutation = {
addAlertChannel,
deleteAlertChannels,
addAlert,
deletePersistedOperation,
deleteDocumentCollection,
deleteOperationInDocumentCollection,
createOperationInDocumentCollection,

View file

@ -1,4 +1,4 @@
lockfileVersion: '6.1'
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
@ -1703,9 +1703,6 @@ importers:
'@hive/emails':
specifier: workspace:*
version: link:../../services/emails
'@hive/server':
specifier: workspace:*
version: link:../../services/server
'@next/bundle-analyzer':
specifier: 13.4.11
version: 13.4.11

View file

@ -39,8 +39,9 @@
"dependsOn": ["^build", "@graphql-hive/client#build"],
"outputs": ["dist/**"]
},
"typecheck": {
"outputs": [""]
"@hive/server#build": {
"dependsOn": ["^build", "@hive/app#build"],
"outputs": ["dist/**"]
},
"check:build": {
"outputs": [""]