replace tag-operations with client preset (#1441)

This commit is contained in:
Laurin Quast 2023-02-24 15:39:44 +01:00 committed by GitHub
parent 1c7d6d9a1a
commit 1e2ab08f95
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
66 changed files with 1733 additions and 1083 deletions

View file

@ -1,5 +1,7 @@
/* eslint-env node */
// eslint-disable-next-line @typescript-eslint/no-var-requires
const guildConfig = require('@theguild/eslint-config/base');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { REACT_RESTRICTED_SYNTAX, RESTRICTED_SYNTAX } = require('@theguild/eslint-config/constants');
const rulesToExtends = Object.fromEntries(
@ -40,6 +42,8 @@ module.exports = {
'packages/web/app/src/graphql/index.ts',
'packages/libraries/cli/src/sdk.ts',
'packages/services/storage/src/db/types.ts',
'codegen.cjs',
'packages/web/app/src/gql/**/*',
],
parserOptions: {
ecmaVersion: 2020,

View file

@ -48,7 +48,7 @@ jobs:
- name: Operation Check
run: |
npx graphql-inspector validate \
"packages/web/app/**/*.graphql|packages/libraries/cli/**/*.graphql|packages/web/app/**/*.ts(x)" \
"packages/web/app/**/*.graphql|packages/libraries/cli/**/*.graphql|packages/web/app/**/*.tsx|packages/web/app/src/lib/**/*.ts" \
"packages/**/module.graphql.ts" \
--maxDepth=20 \
--maxAliasCount=20 \

View file

@ -9,8 +9,7 @@ __generated__/
/integration-tests/testkit/gql/
/packages/services/storage/src/db/types.ts
/packages/libraries/cli/src/sdk.ts
/packages/web/app/src/gql/gql.d.ts
/packages/web/app/src/gql/graphql.ts
/packages/web/app/src/gql/**
/packages/web/app/src/graphql/index.ts
/packages/web/app/next.config.mjs

View file

@ -133,14 +133,8 @@ const config = {
'./packages/web/app/src/graphql',
'!./packages/web/app/pages/api/github/setup-callback.ts',
],
preset: 'gql-tag-operations-preset',
preset: 'client',
plugins: [],
config: {
dedupeFragments: true,
},
presetConfig: {
augmentedModuleName: '@urql/core',
},
},
// CLI
'packages/libraries/cli/src/sdk.ts': {
@ -162,7 +156,7 @@ const config = {
// Integration tests
'./integration-tests/testkit/gql/': {
documents: './integration-tests/(testkit|tests)/**/*.ts',
preset: 'client-preset',
preset: 'client',
plugins: [],
},
},

View file

@ -39,7 +39,7 @@ export function waitFor(ms: number) {
export function createOrganization(input: CreateOrganizationInput, authToken: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation createOrganization($input: CreateOrganizationInput!) {
createOrganization(input: $input) {
ok {
@ -75,7 +75,7 @@ export function createOrganization(input: CreateOrganizationInput, authToken: st
export function getOrganization(organizationId: string, authToken: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
query getOrganization($organizationId: ID!) {
organization(selector: { organization: $organizationId }) {
organization {
@ -104,7 +104,7 @@ export function getOrganization(organizationId: string, authToken: string) {
export function inviteToOrganization(input: InviteToOrganizationByEmailInput, authToken: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation inviteToOrganization($input: InviteToOrganizationByEmailInput!) {
inviteToOrganizationByEmail(input: $input) {
ok {
@ -129,7 +129,7 @@ export function inviteToOrganization(input: InviteToOrganizationByEmailInput, au
export function renameOrganization(input: UpdateOrganizationNameInput, authToken: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation updateOrganizationName($input: UpdateOrganizationNameInput!) {
updateOrganizationName(input: $input) {
ok {
@ -159,7 +159,7 @@ export function renameOrganization(input: UpdateOrganizationNameInput, authToken
export function joinOrganization(code: string, authToken: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation joinOrganization($code: String!) {
joinOrganization(code: $code) {
__typename
@ -191,7 +191,7 @@ export function joinOrganization(code: string, authToken: string) {
export function getOrganizationMembers(selector: OrganizationSelectorInput, authToken: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
query getOrganizationMembers($selector: OrganizationSelectorInput!) {
organization(selector: $selector) {
organization {
@ -222,7 +222,7 @@ export function getOrganizationTransferRequest(
authToken: string,
) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
query getOrganizationTransferRequest($selector: OrganizationTransferRequestSelector!) {
organizationTransferRequest(selector: $selector) {
organization {
@ -243,7 +243,7 @@ export function requestOrganizationTransfer(
authToken: string,
) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation requestOrganizationTransfer($input: RequestOrganizationTransferInput!) {
requestOrganizationTransfer(input: $input) {
ok {
@ -268,7 +268,7 @@ export function answerOrganizationTransferRequest(
authToken: string,
) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation answerOrganizationTransferRequest($input: AnswerOrganizationTransferRequestInput!) {
answerOrganizationTransferRequest(input: $input) {
ok {
@ -289,7 +289,7 @@ export function answerOrganizationTransferRequest(
export function createProject(input: CreateProjectInput, authToken: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation createProject($input: CreateProjectInput!) {
createProject(input: $input) {
ok {
@ -315,7 +315,7 @@ export function createProject(input: CreateProjectInput, authToken: string) {
export function renameProject(input: UpdateProjectNameInput, authToken: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation updateProjectName($input: UpdateProjectNameInput!) {
updateProjectName(input: $input) {
ok {
@ -344,7 +344,7 @@ export function renameProject(input: UpdateProjectNameInput, authToken: string)
export function updateRegistryModel(input: UpdateProjectRegistryModelInput, authToken: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation updateRegistryModel($input: UpdateProjectRegistryModelInput!) {
updateProjectRegistryModel(input: $input) {
ok {
@ -367,7 +367,7 @@ export function updateRegistryModel(input: UpdateProjectRegistryModelInput, auth
export function createTarget(input: CreateTargetInput, authToken: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation createTarget($input: CreateTargetInput!) {
createTarget(input: $input) {
ok {
@ -391,7 +391,7 @@ export function createTarget(input: CreateTargetInput, authToken: string) {
export function renameTarget(input: UpdateTargetNameInput, authToken: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation updateTargetName($input: UpdateTargetNameInput!) {
updateTargetName(input: $input) {
ok {
@ -421,7 +421,7 @@ export function renameTarget(input: UpdateTargetNameInput, authToken: string) {
export function createToken(input: CreateTokenInput, authToken: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation createToken($input: CreateTokenInput!) {
createToken(input: $input) {
ok {
@ -442,7 +442,7 @@ export function createToken(input: CreateTokenInput, authToken: string) {
export function deleteTokens(input: DeleteTokensInput, authToken: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation deleteTokens($input: DeleteTokensInput!) {
deleteTokens(input: $input) {
deletedTokens
@ -458,7 +458,7 @@ export function deleteTokens(input: DeleteTokensInput, authToken: string) {
export function readTokenInfo(token: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
query readTokenInfo {
tokenInfo {
__typename
@ -494,7 +494,7 @@ export function readTokenInfo(token: string) {
export function updateMemberAccess(input: OrganizationMemberAccessInput, authToken: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation updateOrganizationMemberAccess($input: OrganizationMemberAccessInput!) {
updateOrganizationMemberAccess(input: $input) {
organization {
@ -527,7 +527,7 @@ export function publishSchema(
authHeader?: 'x-api-token' | 'authorization',
) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation schemaPublish($input: SchemaPublishInput!) {
schemaPublish(input: $input) {
__typename
@ -574,7 +574,7 @@ export function publishSchema(
export function checkSchema(input: SchemaCheckInput, token: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation schemaCheck($input: SchemaCheckInput!) {
schemaCheck(input: $input) {
... on SchemaCheckSuccess {
@ -621,7 +621,7 @@ export function deleteSchema(
authHeader?: 'x-api-token' | 'authorization',
) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation schemaDelete($input: SchemaDeleteInput!) {
schemaDelete(input: $input) {
__typename
@ -647,7 +647,7 @@ export function setTargetValidation(
},
) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation setTargetValidation($input: SetTargetValidationInput!) {
setTargetValidation(input: $input) {
enabled
@ -675,7 +675,7 @@ export function updateTargetValidationSettings(
},
) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation updateTargetValidationSettings($input: UpdateTargetValidationSettingsInput!) {
updateTargetValidationSettings(input: $input) {
ok {
@ -709,7 +709,7 @@ export function updateTargetValidationSettings(
export function updateBaseSchema(input: UpdateBaseSchemaInput, token: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation updateBaseSchema($input: UpdateBaseSchemaInput!) {
updateBaseSchema(input: $input) {
__typename
@ -725,7 +725,7 @@ export function updateBaseSchema(input: UpdateBaseSchemaInput, token: string) {
export function readOperationsStats(input: OperationsStatsSelectorInput, token: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
query readOperationsStats($input: OperationsStatsSelectorInput!) {
operationsStats(selector: $input) {
totalOperations
@ -757,7 +757,7 @@ export function readOperationsStats(input: OperationsStatsSelectorInput, token:
export function readOperationBody(selector: OperationBodyByHashInput, token: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
query readOperationBody($selector: OperationBodyByHashInput!) {
operationBodyByHash(selector: $selector)
}
@ -771,7 +771,7 @@ export function readOperationBody(selector: OperationBodyByHashInput, token: str
export function fetchLatestSchema(token: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
query latestVersion {
latestVersion {
baseSchema
@ -809,7 +809,7 @@ export function fetchLatestSchema(token: string) {
export function fetchLatestValidSchema(token: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
query latestValidVersion {
latestValidVersion {
id
@ -850,7 +850,7 @@ export function fetchLatestValidSchema(token: string) {
export function fetchVersions(selector: SchemaVersionsInput, limit: number, token: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
query schemaVersions($limit: Int!, $selector: SchemaVersionsInput!) {
schemaVersions(selector: $selector, limit: $limit) {
nodes {
@ -898,7 +898,7 @@ export function fetchVersions(selector: SchemaVersionsInput, limit: number, toke
export function publishPersistedOperations(input: PublishPersistedOperationInput[], token: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation publishPersistedOperations($input: [PublishPersistedOperationInput!]!) {
publishPersistedOperations(input: $input) {
summary {
@ -924,7 +924,7 @@ export function publishPersistedOperations(input: PublishPersistedOperationInput
export function updateSchemaVersionStatus(input: SchemaVersionUpdateInput, token: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation updateSchemaVersionStatus($input: SchemaVersionUpdateInput!) {
updateSchemaVersionStatus(input: $input) {
id
@ -953,7 +953,7 @@ export function updateSchemaVersionStatus(input: SchemaVersionUpdateInput, token
export function createCdnAccess(selector: TargetSelectorInput, token: string) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation createCdnAccessToken($input: CreateCdnAccessTokenInput!) {
createCdnAccessToken(input: $input) {
ok {
@ -1041,7 +1041,7 @@ export async function updateOrgRateLimit(
authToken: string,
) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation updateOrgRateLimit(
$selector: OrganizationSelectorInput!
$monthlyLimits: RateLimitInput!
@ -1064,7 +1064,7 @@ export async function enableExternalSchemaComposition(
token: string,
) {
return execute({
document: graphql(/* GraphQL */ `
document: graphql(`
mutation enableExternalSchemaComposition($input: EnableExternalSchemaCompositionInput!) {
enableExternalSchemaComposition(input: $input) {
ok {

View file

@ -53,7 +53,7 @@ export function initSeed() {
ownerToken,
async createPersonalProject(projectType: ProjectType) {
const orgs = await execute({
document: graphql(/* GraphQL */ `
document: graphql(`
query myOrganizations {
organizations {
total

View file

@ -446,7 +446,7 @@ runArtifactsCDNTests('API Mirror', { service: 'server', port: 8082, path: '/arti
// runArtifactsCDNTests('Local CDN Mock', 'http://127.0.0.1:3004/artifacts/v1/');
describe('CDN token', () => {
const TargetCDNAccessTokensQuery = graphql(/* GraphQL */ `
const TargetCDNAccessTokensQuery = graphql(`
query TargetCDNAccessTokens($selector: TargetSelectorInput!, $after: String, $first: Int = 2) {
target(selector: $selector) {
cdnAccessTokens(first: $first, after: $after) {
@ -468,7 +468,7 @@ describe('CDN token', () => {
}
`);
const DeleteCDNAccessTokenMutation = graphql(/* GraphQL */ `
const DeleteCDNAccessTokenMutation = graphql(`
mutation DeleteCDNAccessToken($input: DeleteCdnAccessTokenInput!) {
deleteCdnAccessToken(input: $input) {
error {

View file

@ -2,7 +2,7 @@ import { graphql } from '../../../testkit/gql';
import { execute } from '../../../testkit/graphql';
import { initSeed } from '../../../testkit/seed';
const OrganizationWithOIDCIntegration = graphql(/* GraphQL */ `
const OrganizationWithOIDCIntegration = graphql(`
query OrganizationWithOIDCIntegration($organizationId: ID!) {
organization(selector: { organization: $organizationId }) {
organization {
@ -15,7 +15,7 @@ const OrganizationWithOIDCIntegration = graphql(/* GraphQL */ `
}
`);
const CreateOIDCIntegrationMutation = graphql(/* GraphQL */ `
const CreateOIDCIntegrationMutation = graphql(`
mutation CreateOIDCIntegrationMutation($input: CreateOIDCIntegrationInput!) {
createOIDCIntegration(input: $input) {
ok {
@ -393,7 +393,7 @@ describe('create', () => {
});
});
const DeleteOIDCIntegrationMutation = graphql(/* GraphQL */ `
const DeleteOIDCIntegrationMutation = graphql(`
mutation DeleteOIDCIntegrationMutation($input: DeleteOIDCIntegrationInput!) {
deleteOIDCIntegration(input: $input) {
ok {
@ -560,7 +560,7 @@ describe('delete', () => {
const oidcIntegrationId = createResult.createOIDCIntegration.ok!.createdOIDCIntegration.id;
const MeQuery = graphql(/* GraphQL */ `
const MeQuery = graphql(`
query Me {
me {
id
@ -619,7 +619,7 @@ describe('delete', () => {
});
});
const UpdateOIDCIntegrationMutation = graphql(/* GraphQL */ `
const UpdateOIDCIntegrationMutation = graphql(`
mutation UpdateOIDCIntegrationMutation($input: UpdateOIDCIntegrationInput!) {
updateOIDCIntegration(input: $input) {
ok {

View file

@ -52,7 +52,6 @@
"@graphql-codegen/add": "4.0.1",
"@graphql-codegen/cli": "3.2.1",
"@graphql-codegen/client-preset": "2.1.0",
"@graphql-codegen/gql-tag-operations-preset": "2.1.0",
"@graphql-codegen/graphql-modules-preset": "3.1.0",
"@graphql-codegen/typed-document-node": "3.0.1",
"@graphql-codegen/typescript": "3.0.1",

View file

@ -1,5 +1,5 @@
import { ReactElement } from 'react';
import { gql, useQuery } from 'urql';
import { useQuery } from 'urql';
import { authenticated } from '@/components/authenticated-container';
import { TargetLayout } from '@/components/layouts';
import { SchemaExplorerFilter } from '@/components/target/explorer/filter';
@ -9,10 +9,10 @@ import {
useSchemaExplorerContext,
} from '@/components/target/explorer/provider';
import { DataWrapper, noSchema, Title } from '@/components/v2';
import { OrganizationFieldsFragment, ProjectFieldsFragment, TargetFieldsFragment } from '@/graphql';
import { graphql } from '@/gql';
import { withSessionProtection } from '@/lib/supertokens/guard';
const SchemaView_SchemaExplorer = gql(/* GraphQL */ `
const SchemaView_SchemaExplorer = graphql(`
query SchemaView_SchemaExplorer(
$organization: ID!
$project: ID!
@ -48,21 +48,21 @@ const SchemaView_SchemaExplorer = gql(/* GraphQL */ `
`);
function SchemaView({
organization,
project,
target,
organizationCleanId,
projectCleanId,
targetCleanId,
}: {
organization: OrganizationFieldsFragment;
project: ProjectFieldsFragment;
target: TargetFieldsFragment;
organizationCleanId: string;
projectCleanId: string;
targetCleanId: string;
}): ReactElement | null {
const { period } = useSchemaExplorerContext();
const [query] = useQuery({
query: SchemaView_SchemaExplorer,
variables: {
organization: organization.cleanId,
project: project.cleanId,
target: target.cleanId,
organization: organizationCleanId,
project: projectCleanId,
target: targetCleanId,
period,
},
requestPolicy: 'cache-first',
@ -85,9 +85,9 @@ function SchemaView({
</div>
<div className="flex flex-col gap-4">
<SchemaExplorerFilter
organization={organization}
project={project}
target={target}
organization={{ cleanId: organizationCleanId }}
project={{ cleanId: projectCleanId }}
target={{ cleanId: targetCleanId }}
period={period}
/>
{query ? (
@ -115,18 +115,48 @@ function SchemaView({
);
}
const TargetExplorerPageQuery = graphql(`
query TargetExplorerPageQuery($organizationId: ID!, $projectId: ID!, $targetId: ID!) {
organization(selector: { organization: $organizationId }) {
organization {
...TargetLayout_OrganizationFragment
rateLimit {
retentionInDays
}
cleanId
}
}
project(selector: { organization: $organizationId, project: $projectId }) {
...TargetLayout_ProjectFragment
cleanId
}
targets(selector: { organization: $organizationId, project: $projectId }) {
...TargetLayout_TargetConnectionFragment
}
target(selector: { organization: $organizationId, project: $projectId, target: $targetId }) {
cleanId
}
}
`);
function ExplorerPage(): ReactElement {
return (
<>
<Title title="Schema Explorer" />
<TargetLayout value="explorer">
{props => (
<SchemaExplorerProvider
dataRetentionInDays={props.organization.rateLimit.retentionInDays}
>
<SchemaView {...props} />
</SchemaExplorerProvider>
)}
<TargetLayout value="explorer" query={TargetExplorerPageQuery}>
{props =>
props.organization && props.project && props.target ? (
<SchemaExplorerProvider
dataRetentionInDays={props.organization.organization.rateLimit.retentionInDays}
>
<SchemaView
organizationCleanId={props.organization.organization.cleanId}
projectCleanId={props.project.cleanId}
targetCleanId={props.target.cleanId}
/>
</SchemaExplorerProvider>
) : null
}
</TargetLayout>
</>
);

View file

@ -1,42 +1,24 @@
import { ReactElement } from 'react';
import { DocumentType, gql, useQuery } from 'urql';
import { useQuery } from 'urql';
import { authenticated } from '@/components/authenticated-container';
import { TargetLayout } from '@/components/layouts';
import {
GraphQLEnumTypeComponent,
GraphQLEnumTypeComponent_TypeFragment,
} from '@/components/target/explorer/enum-type';
import { GraphQLEnumTypeComponent } from '@/components/target/explorer/enum-type';
import { SchemaExplorerFilter } from '@/components/target/explorer/filter';
import {
GraphQLInputObjectTypeComponent,
GraphQLInputObjectTypeComponent_TypeFragment,
} from '@/components/target/explorer/input-object-type';
import {
GraphQLInterfaceTypeComponent,
GraphQLInterfaceTypeComponent_TypeFragment,
} from '@/components/target/explorer/interface-type';
import {
GraphQLObjectTypeComponent,
GraphQLObjectTypeComponent_TypeFragment,
} from '@/components/target/explorer/object-type';
import { GraphQLInputObjectTypeComponent } from '@/components/target/explorer/input-object-type';
import { GraphQLInterfaceTypeComponent } from '@/components/target/explorer/interface-type';
import { GraphQLObjectTypeComponent } from '@/components/target/explorer/object-type';
import {
SchemaExplorerProvider,
useSchemaExplorerContext,
} from '@/components/target/explorer/provider';
import {
GraphQLScalarTypeComponent,
GraphQLScalarTypeComponent_TypeFragment,
} from '@/components/target/explorer/scalar-type';
import {
GraphQLUnionTypeComponent,
GraphQLUnionTypeComponent_TypeFragment,
} from '@/components/target/explorer/union-type';
import { GraphQLScalarTypeComponent } from '@/components/target/explorer/scalar-type';
import { GraphQLUnionTypeComponent } from '@/components/target/explorer/union-type';
import { DataWrapper, noSchema, Title } from '@/components/v2';
import { OrganizationFieldsFragment, ProjectFieldsFragment, TargetFieldsFragment } from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { useRouteSelector } from '@/lib/hooks/use-route-selector';
import { withSessionProtection } from '@/lib/supertokens/guard';
const SchemaTypeExplorer_Type = gql(/* GraphQL */ `
const SchemaTypeExplorer_Type = graphql(`
query SchemaTypeExplorer_Type(
$organization: ID!
$project: ID!
@ -53,13 +35,7 @@ const SchemaTypeExplorer_Type = gql(/* GraphQL */ `
valid
explorer(usage: { period: $period }) {
type(name: $typename) {
__typename
...GraphQLObjectTypeComponent_TypeFragment
...GraphQLInterfaceTypeComponent_TypeFragment
...GraphQLUnionTypeComponent_TypeFragment
...GraphQLEnumTypeComponent_TypeFragment
...GraphQLInputObjectTypeComponent_TypeFragment
...GraphQLScalarTypeComponent_TypeFragment
...TypeRenderFragment
}
}
}
@ -72,51 +48,59 @@ const SchemaTypeExplorer_Type = gql(/* GraphQL */ `
}
`);
type GraphQLNamedType =
| DocumentType<typeof GraphQLEnumTypeComponent_TypeFragment>
| DocumentType<typeof GraphQLInputObjectTypeComponent_TypeFragment>
| DocumentType<typeof GraphQLInterfaceTypeComponent_TypeFragment>
| DocumentType<typeof GraphQLObjectTypeComponent_TypeFragment>
| DocumentType<typeof GraphQLScalarTypeComponent_TypeFragment>
| DocumentType<typeof GraphQLUnionTypeComponent_TypeFragment>;
const TypeRenderFragment = graphql(`
fragment TypeRenderFragment on GraphQLNamedType {
__typename
...GraphQLObjectTypeComponent_TypeFragment
...GraphQLInterfaceTypeComponent_TypeFragment
...GraphQLUnionTypeComponent_TypeFragment
...GraphQLEnumTypeComponent_TypeFragment
...GraphQLInputObjectTypeComponent_TypeFragment
...GraphQLScalarTypeComponent_TypeFragment
}
`);
function TypeRenderer({ type, totalRequests }: { type: GraphQLNamedType; totalRequests: number }) {
switch (type.__typename) {
function TypeRenderer(props: {
type: FragmentType<typeof TypeRenderFragment>;
totalRequests: number;
}) {
const ttype = useFragment(TypeRenderFragment, props.type);
switch (ttype.__typename) {
case 'GraphQLObjectType':
return <GraphQLObjectTypeComponent type={type} totalRequests={totalRequests} />;
return <GraphQLObjectTypeComponent type={ttype} totalRequests={props.totalRequests} />;
case 'GraphQLInterfaceType':
return <GraphQLInterfaceTypeComponent type={type} totalRequests={totalRequests} />;
return <GraphQLInterfaceTypeComponent type={ttype} totalRequests={props.totalRequests} />;
case 'GraphQLUnionType':
return <GraphQLUnionTypeComponent type={type} totalRequests={totalRequests} />;
return <GraphQLUnionTypeComponent type={ttype} totalRequests={props.totalRequests} />;
case 'GraphQLEnumType':
return <GraphQLEnumTypeComponent type={type} totalRequests={totalRequests} />;
return <GraphQLEnumTypeComponent type={ttype} totalRequests={props.totalRequests} />;
case 'GraphQLInputObjectType':
return <GraphQLInputObjectTypeComponent type={type} totalRequests={totalRequests} />;
return <GraphQLInputObjectTypeComponent type={ttype} totalRequests={props.totalRequests} />;
case 'GraphQLScalarType':
return <GraphQLScalarTypeComponent type={type} totalRequests={totalRequests} />;
return <GraphQLScalarTypeComponent type={ttype} totalRequests={props.totalRequests} />;
default:
return <div>Unknown type: {type.__typename}</div>;
return <div>Unknown type: {(ttype as any).__typename}</div>;
}
}
function SchemaTypeExplorer({
organization,
project,
target,
organizationCleanId,
projectCleanId,
targetCleanId,
typename,
}: {
organization: OrganizationFieldsFragment;
project: ProjectFieldsFragment;
target: TargetFieldsFragment;
organizationCleanId: string;
projectCleanId: string;
targetCleanId: string;
typename: string;
}): ReactElement | null {
const { period } = useSchemaExplorerContext();
const [query] = useQuery({
query: SchemaTypeExplorer_Type,
variables: {
organization: organization.cleanId,
project: project.cleanId,
target: target.cleanId,
organization: organizationCleanId,
project: projectCleanId,
target: targetCleanId,
period,
typename,
},
@ -140,9 +124,9 @@ function SchemaTypeExplorer({
return (
<div className="space-y-4">
<SchemaExplorerFilter
organization={organization}
project={project}
target={target}
organization={{ cleanId: organizationCleanId }}
project={{ cleanId: projectCleanId }}
target={{ cleanId: targetCleanId }}
period={period}
typename={typename}
/>
@ -154,6 +138,30 @@ function SchemaTypeExplorer({
);
}
const TargetExplorerTypenamePageQuery = graphql(`
query TargetExplorerTypenamePageQuery($organizationId: ID!, $projectId: ID!, $targetId: ID!) {
organization(selector: { organization: $organizationId }) {
organization {
...TargetLayout_OrganizationFragment
cleanId
rateLimit {
retentionInDays
}
}
}
project(selector: { organization: $organizationId, project: $projectId }) {
...TargetLayout_ProjectFragment
cleanId
}
targets(selector: { organization: $organizationId, project: $projectId }) {
...TargetLayout_TargetConnectionFragment
}
target(selector: { organization: $organizationId, project: $projectId, target: $targetId }) {
cleanId
}
}
`);
function ExplorerPage(): ReactElement | null {
const router = useRouteSelector();
const { typename } = router.query;
@ -165,12 +173,17 @@ function ExplorerPage(): ReactElement | null {
return (
<>
<Title title={`Type ${typename}`} />
<TargetLayout value="explorer">
<TargetLayout value="explorer" query={TargetExplorerTypenamePageQuery}>
{props => (
<SchemaExplorerProvider
dataRetentionInDays={props.organization.rateLimit.retentionInDays}
dataRetentionInDays={props.organization?.organization.rateLimit.retentionInDays ?? 0}
>
<SchemaTypeExplorer {...props} typename={typename} />
<SchemaTypeExplorer
organizationCleanId={props.organization?.organization.cleanId ?? ''}
projectCleanId={props.project?.cleanId ?? ''}
targetCleanId={props.target?.cleanId ?? ''}
typename={typename}
/>
</SchemaExplorerProvider>
)}
</TargetLayout>

View file

@ -18,10 +18,10 @@ import {
ToggleGroupItem,
} from '@/components/v2';
import { DiffIcon } from '@/components/v2/icon';
import { graphql } from '@/gql';
import {
CompareDocument,
CriticalityLevel,
LatestSchemaDocument,
SchemaChangeFieldsFragment,
VersionsDocument,
} from '@/graphql';
@ -292,29 +292,43 @@ function Page({ versionId }: { versionId: string }) {
);
}
const TargetHistoryPageQuery = graphql(`
query TargetHistoryPageQuery($organizationId: ID!, $projectId: ID!, $targetId: ID!) {
organization(selector: { organization: $organizationId }) {
organization {
...TargetLayout_OrganizationFragment
}
}
project(selector: { organization: $organizationId, project: $projectId }) {
...TargetLayout_ProjectFragment
}
targets(selector: { organization: $organizationId, project: $projectId }) {
...TargetLayout_TargetConnectionFragment
}
target(selector: { organization: $organizationId, project: $projectId, target: $targetId }) {
id
latestSchemaVersion {
id
}
}
}
`);
function HistoryPage(): ReactElement {
const router = useRouteSelector();
const [latestSchemaQuery] = useQuery({
query: LatestSchemaDocument,
variables: {
selector: {
organization: router.organizationId,
project: router.projectId,
target: router.targetId,
},
},
requestPolicy: 'cache-and-network',
});
const versionId = router.versionId ?? latestSchemaQuery.data?.target?.latestSchemaVersion?.id;
return (
<>
<Title title="History" />
<TargetLayout
value="history"
className={versionId ? 'flex h-full items-stretch gap-x-5' : ''}
className={router.versionId ? 'flex h-full items-stretch gap-x-5' : ''}
query={TargetHistoryPageQuery}
>
{() => (versionId ? <Page versionId={versionId} /> : noSchema)}
{({ target }) => {
const versionId = router.versionId ?? target?.latestSchemaVersion?.id;
return versionId ? <Page versionId={versionId} /> : noSchema;
}}
</TargetLayout>
</>
);

View file

@ -5,15 +5,9 @@ import { authenticated } from '@/components/authenticated-container';
import { TargetLayout } from '@/components/layouts';
import { MarkAsValid } from '@/components/target/history/MarkAsValid';
import { DataWrapper, GraphQLBlock, Input, noSchema, Title } from '@/components/v2';
import { FragmentType, graphql, useFragment } from '@/gql';
import { CompositeSchemaFieldsFragment, SingleSchemaFieldsFragment } from '@/gql/graphql';
import {
LatestSchemaDocument,
OrganizationFieldsFragment,
ProjectFieldsFragment,
ProjectType,
RegistryModel,
TargetFieldsFragment,
} from '@/graphql';
import { LatestSchemaDocument, ProjectType, RegistryModel } from '@/graphql';
import { TargetAccessScope, useTargetAccess } from '@/lib/access/target';
import { withSessionProtection } from '@/lib/supertokens/guard';
@ -23,15 +17,22 @@ function isCompositeSchema(
return schema.__typename === 'CompositeSchema';
}
const Schemas_ProjectFragment = graphql(`
fragment Schemas_ProjectFragment on Project {
type
}
`);
function Schemas({
project,
filterService,
schemas = [],
...props
}: {
project: ProjectFieldsFragment;
project: FragmentType<typeof Schemas_ProjectFragment>;
schemas: Array<SingleSchemaFieldsFragment | CompositeSchemaFieldsFragment>;
filterService?: string;
}): ReactElement {
const project = useFragment(Schemas_ProjectFragment, props.project);
if (project.type === ProjectType.Single) {
const [schema] = schemas;
return (
@ -66,15 +67,45 @@ function Schemas({
);
}
function SchemaView({
organization,
project,
target,
}: {
organization: OrganizationFieldsFragment;
project: ProjectFieldsFragment;
target: TargetFieldsFragment;
const SchemaView_OrganizationFragment = graphql(`
fragment SchemaView_OrganizationFragment on Organization {
cleanId
me {
...CanAccessTarget_MemberFragment
}
}
`);
const SchemaView_ProjectFragment = graphql(`
fragment SchemaView_ProjectFragment on Project {
cleanId
type
registryModel
...Schemas_ProjectFragment
}
`);
const SchemaView_TargetFragment = graphql(`
fragment SchemaView_TargetFragment on Target {
cleanId
latestSchemaVersion {
schemas {
nodes {
__typename
}
}
}
}
`);
function SchemaView(props: {
organization: FragmentType<typeof SchemaView_OrganizationFragment>;
project: FragmentType<typeof SchemaView_ProjectFragment>;
target: FragmentType<typeof SchemaView_TargetFragment>;
}): ReactElement | null {
const organization = useFragment(SchemaView_OrganizationFragment, props.organization);
const project = useFragment(SchemaView_ProjectFragment, props.project);
const target = useFragment(SchemaView_TargetFragment, props.target);
const [filterService, setFilterService] = useState('');
const [term, setTerm] = useState('');
const debouncedFilter = useDebouncedCallback((value: string) => {
@ -153,11 +184,40 @@ function SchemaView({
);
}
const TargetSchemaPageQuery = graphql(`
query TargetSchemaPageQuery($organizationId: ID!, $projectId: ID!, $targetId: ID!) {
organization(selector: { organization: $organizationId }) {
organization {
...TargetLayout_OrganizationFragment
...SchemaView_OrganizationFragment
}
}
project(selector: { organization: $organizationId, project: $projectId }) {
...TargetLayout_ProjectFragment
...SchemaView_ProjectFragment
}
targets(selector: { organization: $organizationId, project: $projectId }) {
...TargetLayout_TargetConnectionFragment
}
target(selector: { organization: $organizationId, project: $projectId, target: $targetId }) {
...SchemaView_TargetFragment
}
}
`);
function SchemaPage(): ReactElement {
return (
<>
<Title title="Schema" />
<TargetLayout value="schema">{props => <SchemaView {...props} />}</TargetLayout>
<TargetLayout value="schema" query={TargetSchemaPageQuery}>
{props => (
<SchemaView
target={props.target!}
organization={props.organization!.organization}
project={props.project!}
/>
)}
</TargetLayout>
</>
);
}

View file

@ -5,6 +5,7 @@ import { TargetLayout } from '@/components/layouts';
import { Button, Title } from '@/components/v2';
import { HiveLogo, Link2Icon } from '@/components/v2/icon';
import { ConnectLabModal } from '@/components/v2/modals/connect-lab';
import { graphql } from '@/gql';
import { useRouteSelector, useToggle } from '@/lib/hooks';
import { withSessionProtection } from '@/lib/supertokens/guard';
import { createGraphiQLFetcher } from '@graphiql/toolkit';
@ -31,6 +32,25 @@ const Page = ({ endpoint }: { endpoint: string }): ReactElement => {
);
};
const TargetLaboratoryPageQuery = graphql(`
query TargetLaboratoryPageQuery($organizationId: ID!, $projectId: ID!, $targetId: ID!) {
organization(selector: { organization: $organizationId }) {
organization {
...TargetLayout_OrganizationFragment
}
}
project(selector: { organization: $organizationId, project: $projectId }) {
...TargetLayout_ProjectFragment
}
targets(selector: { organization: $organizationId, project: $projectId }) {
...TargetLayout_TargetConnectionFragment
}
target(selector: { organization: $organizationId, project: $projectId, target: $targetId }) {
id
}
}
`);
function LaboratoryPage(): ReactElement {
const [isModalOpen, toggleModalOpen] = useToggle();
const router = useRouteSelector();
@ -40,6 +60,7 @@ function LaboratoryPage(): ReactElement {
<>
<Title title="Schema laboratory" />
<TargetLayout
query={TargetLaboratoryPageQuery}
value="laboratory"
className="flex h-full flex-col"
connect={

View file

@ -1,19 +1,13 @@
import { ReactElement, useCallback, useMemo, useState } from 'react';
import { useRouter } from 'next/router';
import { formatISO, subDays, subHours, subMinutes } from 'date-fns';
import { useQuery } from 'urql';
import { authenticated } from '@/components/authenticated-container';
import { TargetLayout } from '@/components/layouts';
import { OperationsFilterTrigger } from '@/components/target/operations/Filters';
import { OperationsList } from '@/components/target/operations/List';
import { OperationsStats } from '@/components/target/operations/Stats';
import { DataWrapper, EmptyList, RadixSelect, Title } from '@/components/v2';
import {
HasCollectedOperationsDocument,
OrganizationFieldsFragment,
ProjectFieldsFragment,
TargetFieldsFragment,
} from '@/graphql';
import { EmptyList, RadixSelect, Title } from '@/components/v2';
import { graphql } from '@/gql';
import { getDocsUrl } from '@/lib/docs-url';
import { withSessionProtection } from '@/lib/supertokens/guard';
@ -33,13 +27,13 @@ const DateRange = {
type PeriodKey = keyof typeof DateRange;
function OperationsView({
organization,
project,
target,
organizationCleanId,
projectCleanId,
targetCleanId,
}: {
organization: OrganizationFieldsFragment;
project: ProjectFieldsFragment;
target: TargetFieldsFragment;
organizationCleanId: string;
projectCleanId: string;
targetCleanId: string;
}): ReactElement {
const router = useRouter();
const [href, periodParam] = router.asPath.split('?');
@ -86,74 +80,75 @@ function OperationsView({
/>
</div>
<OperationsStats
organization={organization.cleanId}
project={project.cleanId}
target={target.cleanId}
organization={organizationCleanId}
project={projectCleanId}
target={targetCleanId}
period={period}
operationsFilter={selectedOperations}
/>
<OperationsList
className="mt-12"
period={period}
organization={organization.cleanId}
project={project.cleanId}
target={target.cleanId}
organization={organizationCleanId}
project={projectCleanId}
target={targetCleanId}
operationsFilter={selectedOperations}
/>
</>
);
}
function OperationsViewGate({
organization,
project,
target,
}: {
organization: OrganizationFieldsFragment;
project: ProjectFieldsFragment;
target: TargetFieldsFragment;
}): ReactElement {
const [query] = useQuery({
query: HasCollectedOperationsDocument,
variables: {
selector: {
organization: organization.cleanId,
project: project.cleanId,
target: target.cleanId,
},
},
});
return (
<DataWrapper query={query}>
{result =>
result.data.hasCollectedOperations ? (
<OperationsView organization={organization} project={project} target={target} />
) : (
<EmptyList
title="Hive is waiting for your first collected operation"
description="You can collect usage of your GraphQL API with Hive Client"
docsUrl={getDocsUrl('/features/monitoring')}
/>
)
const TargetOperationsPageQuery = graphql(`
query TargetOperationsPageQuery($organizationId: ID!, $projectId: ID!, $targetId: ID!) {
organization(selector: { organization: $organizationId }) {
organization {
...TargetLayout_OrganizationFragment
cleanId
}
</DataWrapper>
);
}
}
project(selector: { organization: $organizationId, project: $projectId }) {
...TargetLayout_ProjectFragment
cleanId
}
targets(selector: { organization: $organizationId, project: $projectId }) {
...TargetLayout_TargetConnectionFragment
}
target(selector: { organization: $organizationId, project: $projectId, target: $targetId }) {
cleanId
}
hasCollectedOperations(
selector: { organization: $organizationId, project: $projectId, target: $targetId }
)
}
`);
function OperationsPage(): ReactElement {
return (
<>
<Title title="Operations" />
<TargetLayout value="operations">
{({ organization, project, target }) => (
<div className="relative">
<p className="mb-5 font-light text-gray-500">
Data collected based on operation executed against your GraphQL schema.
</p>
<OperationsViewGate organization={organization} project={project} target={target} />
</div>
)}
<TargetLayout value="operations" query={TargetOperationsPageQuery}>
{({ organization, project, target, hasCollectedOperations }) =>
organization && project && target ? (
<div className="relative">
<p className="mb-5 font-light text-gray-500">
Data collected based on operation executed against your GraphQL schema.
</p>
{hasCollectedOperations ? (
<OperationsView
organizationCleanId={organization.organization.cleanId}
projectCleanId={project.cleanId}
targetCleanId={target.cleanId}
/>
) : (
<EmptyList
title="Hive is waiting for your first collected operation"
description="You can collect usage of your GraphQL API with Hive Client"
docsUrl={getDocsUrl('/features/monitoring')}
/>
)}
</div>
) : null
}
</TargetLayout>
</>
);

View file

@ -2,7 +2,7 @@ import React, { ReactElement, useCallback, useState } from 'react';
import clsx from 'clsx';
import { formatISO, subDays } from 'date-fns';
import { useFormik } from 'formik';
import { gql, useMutation, useQuery } from 'urql';
import { useMutation, useQuery } from 'urql';
import * as Yup from 'yup';
import { authenticated } from '@/components/authenticated-container';
import { TargetLayout } from '@/components/layouts';
@ -27,19 +27,22 @@ import {
import { Combobox } from '@/components/v2/combobox';
import { AlertTriangleIcon } from '@/components/v2/icon';
import { CreateAccessTokenModal, DeleteTargetModal } from '@/components/v2/modals';
import {
DeleteTokensDocument,
MemberFieldsFragment,
OrganizationFieldsFragment,
SetTargetValidationDocument,
TargetFieldsFragment,
TokensDocument,
} from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { DeleteTokensDocument, SetTargetValidationDocument, TokensDocument } from '@/graphql';
import { canAccessTarget, TargetAccessScope } from '@/lib/access/target';
import { useRouteSelector, useToggle } from '@/lib/hooks';
import { withSessionProtection } from '@/lib/supertokens/guard';
function RegistryAccessTokens({ me }: { me: MemberFieldsFragment }): ReactElement {
const RegistryAccessTokens_MeFragment = graphql(`
fragment RegistryAccessTokens_MeFragment on Member {
...CanAccessTarget_MemberFragment
}
`);
function RegistryAccessTokens(props: {
me: FragmentType<typeof RegistryAccessTokens_MeFragment>;
}): ReactElement {
const me = useFragment(RegistryAccessTokens_MeFragment, props.me);
const router = useRouteSelector();
const [{ fetching: deleting }, mutate] = useMutation(DeleteTokensDocument);
const [checked, setChecked] = useState<string[]>([]);
@ -134,7 +137,7 @@ function RegistryAccessTokens({ me }: { me: MemberFieldsFragment }): ReactElemen
);
}
const Settings_UpdateBaseSchemaMutation = gql(/* GraphQL */ `
const Settings_UpdateBaseSchemaMutation = graphql(`
mutation Settings_UpdateBaseSchema($input: UpdateBaseSchemaInput!) {
updateBaseSchema(input: $input) {
ok {
@ -212,7 +215,7 @@ const ExtendBaseSchema = (props: { baseSchema: string }): ReactElement => {
);
};
const ClientExclusion_AvailableClientNamesQuery = gql(/* GraphQL */ `
const ClientExclusion_AvailableClientNamesQuery = graphql(`
query ClientExclusion_AvailableClientNamesQuery($selector: ClientStatsByTargetsInput!) {
clientStatsByTargets(selector: $selector) {
nodes {
@ -274,18 +277,27 @@ function ClientExclusion(
);
}
const Settings_TargetSettingsQuery = gql(/* GraphQL */ `
const Settings_TargetSettingsQuery = graphql(`
query Settings_TargetSettingsQuery(
$selector: TargetSelectorInput!
$targetsSelector: ProjectSelectorInput!
$organizationSelector: OrganizationSelectorInput!
) {
targetSettings(selector: $selector) {
...TargetSettingsFields
validation {
enabled
percentage
period
targets {
id
}
excludedClients
}
}
targets(selector: $targetsSelector) {
nodes {
...TargetEssentials
id
name
}
}
organization(selector: $organizationSelector) {
@ -299,7 +311,7 @@ const Settings_TargetSettingsQuery = gql(/* GraphQL */ `
}
`);
const Settings_UpdateTargetValidationSettingsMutation = gql(/* GraphQL */ `
const Settings_UpdateTargetValidationSettingsMutation = graphql(`
mutation Settings_UpdateTargetValidationSettings($input: UpdateTargetValidationSettingsInput!) {
updateTargetValidationSettings(input: $input) {
ok {
@ -554,7 +566,7 @@ const ConditionalBreakingChanges = (): ReactElement => {
);
};
const Settings_UpdateTargetNameMutation = gql(/* GraphQL */ `
const Settings_UpdateTargetNameMutation = graphql(`
mutation Settings_UpdateTargetName($input: UpdateTargetNameInput!) {
updateTargetName(input: $input) {
ok {
@ -565,6 +577,7 @@ const Settings_UpdateTargetNameMutation = gql(/* GraphQL */ `
}
updatedTarget {
...TargetFields
cleanId
}
}
error {
@ -577,13 +590,29 @@ const Settings_UpdateTargetNameMutation = gql(/* GraphQL */ `
}
`);
const Page = ({
target,
organization,
}: {
target: TargetFieldsFragment;
organization: OrganizationFieldsFragment;
const TargetSettingsPage_TargetFragment = graphql(`
fragment TargetSettingsPage_TargetFragment on Target {
name
baseSchema
}
`);
const TargetSettingsPage_OrganizationFragment = graphql(`
fragment TargetSettingsPage_OrganizationFragment on Organization {
me {
...CanAccessTarget_MemberFragment
...RegistryAccessTokens_MeFragment
...CDNAccessTokens_MeFragment
}
}
`);
const Page = (props: {
target: FragmentType<typeof TargetSettingsPage_TargetFragment>;
organization: FragmentType<typeof TargetSettingsPage_OrganizationFragment>;
}) => {
const target = useFragment(TargetSettingsPage_TargetFragment, props.target);
const organization = useFragment(TargetSettingsPage_OrganizationFragment, props.organization);
const router = useRouteSelector();
const [isModalOpen, toggleModalOpen] = useToggle();
@ -694,12 +723,41 @@ const Page = ({
);
};
const TargetSettingsPageQuery = graphql(`
query TargetSettingsPageQuery($organizationId: ID!, $projectId: ID!, $targetId: ID!) {
organization(selector: { organization: $organizationId }) {
organization {
...TargetLayout_OrganizationFragment
...TargetSettingsPage_OrganizationFragment
}
}
project(selector: { organization: $organizationId, project: $projectId }) {
...TargetLayout_ProjectFragment
}
targets(selector: { organization: $organizationId, project: $projectId }) {
...TargetLayout_TargetConnectionFragment
}
target(selector: { organization: $organizationId, project: $projectId, target: $targetId }) {
id
...TargetSettingsPage_TargetFragment
}
}
`);
function SettingsPage(): ReactElement {
return (
<>
<Title title="Settings" />
<TargetLayout value="settings" className="flex flex-col gap-16">
{props => <Page target={props.target} organization={props.organization} />}
<TargetLayout
value="settings"
className="flex flex-col gap-16"
query={TargetSettingsPageQuery}
>
{props =>
props.organization ? (
<Page target={props.target!} organization={props.organization.organization} />
) : null
}
</TargetLayout>
</>
);

View file

@ -21,6 +21,7 @@ import {
DropdownMenuTrigger,
} from '@/components/v2/dropdown';
import { LinkIcon, MoreIcon, SettingsIcon } from '@/components/v2/icon';
import { graphql } from '@/gql';
import { TargetQuery, TargetsDocument, VersionsDocument } from '@/graphql';
import { getDocsUrl } from '@/lib/docs-url';
import { useClipboard } from '@/lib/hooks/use-clipboard';
@ -140,11 +141,24 @@ const Page = () => {
);
};
const ProjectOverviewPageQuery = graphql(`
query ProjectOverviewPageQuery($organizationId: ID!, $projectId: ID!) {
organization(selector: { organization: $organizationId }) {
organization {
...ProjectLayout_OrganizationFragment
}
}
project(selector: { organization: $organizationId, project: $projectId }) {
...ProjectLayout_ProjectFragment
}
}
`);
function ProjectsPage(): ReactElement {
return (
<>
<Title title="Targets" />
<ProjectLayout value="targets" className="flex gap-x-5">
<ProjectLayout value="targets" className="flex gap-x-5" query={ProjectOverviewPageQuery}>
{() => <Page />}
</ProjectLayout>
</>

View file

@ -4,14 +4,13 @@ import { authenticated } from '@/components/authenticated-container';
import { ProjectLayout } from '@/components/layouts';
import { Button, Card, Checkbox, Heading, Table, Tag, TBody, Td, Title, Tr } from '@/components/v2';
import { CreateAlertModal, CreateChannelModal } from '@/components/v2/modals';
import { FragmentType, graphql, useFragment } from '@/gql';
import {
AlertChannelsDocument,
AlertChannelType,
AlertsDocument,
DeleteAlertChannelsDocument,
DeleteAlertsDocument,
OrganizationFieldsFragment,
ProjectFieldsFragment,
} from '@/graphql';
import { ProjectAccessScope, useProjectAccess } from '@/lib/access/project';
import { useRouteSelector, useToggle } from '@/lib/hooks';
@ -94,13 +93,30 @@ function Channels(): ReactElement {
);
}
const AlertsPage_OrganizationFragment = graphql(`
fragment AlertsPage_OrganizationFragment on Organization {
cleanId
me {
...CanAccessProject_MemberFragment
}
}
`);
const AlertsPage_ProjectFragment = graphql(`
fragment AlertsPage_ProjectFragment on Project {
cleanId
}
`);
const Page = (props: {
organization: OrganizationFieldsFragment;
project: ProjectFieldsFragment;
organization: FragmentType<typeof AlertsPage_OrganizationFragment>;
project: FragmentType<typeof AlertsPage_ProjectFragment>;
}) => {
const organization = useFragment(AlertsPage_OrganizationFragment, props.organization);
const project = useFragment(AlertsPage_ProjectFragment, props.project);
useProjectAccess({
scope: ProjectAccessScope.Alerts,
member: props.organization.me,
member: organization.me,
redirect: true,
});
const [checked, setChecked] = useState<string[]>([]);
@ -111,8 +127,8 @@ const Page = (props: {
query: AlertsDocument,
variables: {
selector: {
organization: props.organization.cleanId,
project: props.project.cleanId,
organization: organization.cleanId,
project: project.cleanId,
},
},
requestPolicy: 'cache-and-network',
@ -180,11 +196,30 @@ const Page = (props: {
);
};
const ProjectAlertsPageQuery = graphql(`
query ProjectAlertsPageQuery($organizationId: ID!, $projectId: ID!) {
organization(selector: { organization: $organizationId }) {
organization {
...ProjectLayout_OrganizationFragment
...AlertsPage_OrganizationFragment
}
}
project(selector: { organization: $organizationId, project: $projectId }) {
...ProjectLayout_ProjectFragment
...AlertsPage_ProjectFragment
}
}
`);
function AlertsPage(): ReactElement {
return (
<>
<Title title="Alerts" />
<ProjectLayout value="alerts" className="flex flex-col gap-y-10">
<ProjectLayout
value="alerts"
className="flex flex-col gap-y-10"
query={ProjectAlertsPageQuery}
>
{props => <Page organization={props.organization} project={props.project} />}
</ProjectLayout>
</>

View file

@ -1,6 +1,6 @@
import { ReactElement } from 'react';
import { useFormik } from 'formik';
import { gql, useMutation, useQuery } from 'urql';
import { useMutation, useQuery } from 'urql';
import * as Yup from 'yup';
import { authenticated } from '@/components/authenticated-container';
import { ProjectLayout } from '@/components/layouts';
@ -9,17 +9,13 @@ import { ModelMigrationSettings } from '@/components/project/settings/model-migr
import { Button, Card, Heading, Input, Link, Select, Tag, Title } from '@/components/v2';
import { AlertTriangleIcon } from '@/components/v2/icon';
import { DeleteProjectModal } from '@/components/v2/modals';
import {
GetGitHubIntegrationDetailsDocument,
OrganizationFieldsFragment,
ProjectFieldsFragment,
ProjectType,
} from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { GetGitHubIntegrationDetailsDocument, ProjectType } from '@/graphql';
import { canAccessProject, ProjectAccessScope, useProjectAccess } from '@/lib/access/project';
import { useRouteSelector, useToggle } from '@/lib/hooks';
import { withSessionProtection } from '@/lib/supertokens/guard';
const Settings_UpdateProjectGitRepositoryMutation = gql(/* GraphQL */ `
const Settings_UpdateProjectGitRepositoryMutation = graphql(`
mutation Settings_UpdateProjectGitRepository($input: UpdateProjectGitRepositoryInput!) {
updateProjectGitRepository(input: $input) {
ok {
@ -131,7 +127,7 @@ function GitHubIntegration({
);
}
const Settings_UpdateProjectNameMutation = gql(/* GraphQL */ `
const Settings_UpdateProjectNameMutation = graphql(`
mutation Settings_UpdateProjectName($input: UpdateProjectNameInput!) {
updateProjectName(input: $input) {
ok {
@ -141,6 +137,7 @@ const Settings_UpdateProjectNameMutation = gql(/* GraphQL */ `
}
updatedProject {
...ProjectFields
cleanId
}
}
error {
@ -150,13 +147,32 @@ const Settings_UpdateProjectNameMutation = gql(/* GraphQL */ `
}
`);
const Page = ({
organization,
project,
}: {
organization: OrganizationFieldsFragment;
project: ProjectFieldsFragment;
const SettingsPage_OrganizationFragment = graphql(`
fragment SettingsPage_OrganizationFragment on Organization {
cleanId
me {
...CanAccessProject_MemberFragment
}
...ExternalCompositionSettings_OrganizationFragment
}
`);
const SettingsPage_ProjectFragment = graphql(`
fragment SettingsPage_ProjectFragment on Project {
name
gitRepository
type
...ModelMigrationSettings_ProjectFragment
...ExternalCompositionSettings_ProjectFragment
}
`);
const Page = (props: {
organization: FragmentType<typeof SettingsPage_OrganizationFragment>;
project: FragmentType<typeof SettingsPage_ProjectFragment>;
}) => {
const organization = useFragment(SettingsPage_OrganizationFragment, props.organization);
const project = useFragment(SettingsPage_ProjectFragment, props.project);
useProjectAccess({
scope: ProjectAccessScope.Settings,
member: organization.me,
@ -193,9 +209,7 @@ const Page = ({
return (
<>
{/* {project.registryModel === 'LEGACY' ? ( */}
<ModelMigrationSettings project={project} organizationId={organization.cleanId} />
{/* ) : null} */}
<Card>
<Heading className="mb-2">Project Name</Heading>
<p className="mb-3 font-light text-gray-300">
@ -272,11 +286,30 @@ const Page = ({
);
};
const ProjectSettingsPageQuery = graphql(`
query ProjectSettingsPageQuery($organizationId: ID!, $projectId: ID!) {
organization(selector: { organization: $organizationId }) {
organization {
...SettingsPage_OrganizationFragment
...ProjectLayout_OrganizationFragment
}
}
project(selector: { organization: $organizationId, project: $projectId }) {
...ProjectLayout_ProjectFragment
...SettingsPage_ProjectFragment
}
}
`);
function SettingsPage(): ReactElement {
return (
<>
<Title title="Project settings" />
<ProjectLayout value="settings" className="flex flex-col gap-y-10">
<ProjectLayout
value="settings"
className="flex flex-col gap-y-10"
query={ProjectSettingsPageQuery}
>
{props => <Page {...props} />}
</ProjectLayout>
</>

View file

@ -22,11 +22,8 @@ import {
DropdownMenuTrigger,
} from '@/components/v2/dropdown';
import { LinkIcon, MoreIcon, SettingsIcon } from '@/components/v2/icon';
import {
ProjectActivitiesDocument,
ProjectsWithTargetsDocument,
ProjectsWithTargetsQuery,
} from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { ProjectActivitiesDocument } from '@/graphql';
import { writeLastVisitedOrganization } from '@/lib/cookies';
import { getDocsUrl } from '@/lib/docs-url';
import { fixDuplicatedFragments } from '@/lib/graphql';
@ -36,11 +33,19 @@ import { withSessionProtection } from '@/lib/supertokens/guard';
const projectActivitiesDocument = fixDuplicatedFragments(ProjectActivitiesDocument);
const ProjectCard = ({
project,
}: {
project: ProjectsWithTargetsQuery['projects']['nodes'][number];
const ProjectCard_ProjectFragment = graphql(`
fragment ProjectCard_ProjectFragment on Project {
cleanId
id
type
name
}
`);
const ProjectCard = (props: {
project: FragmentType<typeof ProjectCard_ProjectFragment>;
}): ReactElement => {
const project = useFragment(ProjectCard_ProjectFragment, props.project);
const copyToClipboard = useClipboard();
const router = useRouteSelector();
const [projectActivitiesQuery] = useQuery({
@ -109,27 +114,37 @@ const ProjectCard = ({
);
};
const OrganizationProjectsPageQuery = graphql(`
query OrganizationProjectsPageQuery($selector: OrganizationSelectorInput!) {
organization(selector: $selector) {
organization {
...OrganizationLayout_OrganizationFragment
}
}
projects(selector: $selector) {
total
nodes {
id
...ProjectCard_ProjectFragment
}
}
}
`);
function ProjectsPage(): ReactElement {
const router = useRouteSelector();
const [projectsWithTargetsQuery] = useQuery({
query: ProjectsWithTargetsDocument,
variables: {
selector: {
organization: router.organizationId,
},
},
});
return (
<>
<Title title="Projects" />
<OrganizationLayout value="overview" className="flex justify-between gap-5">
{() => (
<OrganizationLayout
value="overview"
className="flex justify-between gap-5"
query={OrganizationProjectsPageQuery}
>
{({ projects }) => (
<>
<div className="grow">
<Heading className="mb-4">Active Projects</Heading>
{projectsWithTargetsQuery.data?.projects.total === 0 ? (
{projects.total === 0 ? (
<EmptyList
title="Hive is waiting for your first project"
description='You can create a project by clicking the "Create Project" button'
@ -137,7 +152,8 @@ function ProjectsPage(): ReactElement {
/>
) : (
<div className="grid grid-cols-2 gap-5 items-stretch">
{projectsWithTargetsQuery.fetching
{/** TODO: use defer here :) */}
{projects === null
? [1, 2].map(key => (
<Card key={key}>
<div className="flex gap-x-2">
@ -151,7 +167,7 @@ function ProjectsPage(): ReactElement {
<Skeleton visible className="h-7" />
</Card>
))
: projectsWithTargetsQuery.data?.projects.nodes.map(project => (
: projects.nodes.map(project => (
<ProjectCard key={project.id} project={project} />
))}
</div>

View file

@ -1,6 +1,6 @@
import { ReactElement, useCallback, useEffect, useState } from 'react';
import { useFormik } from 'formik';
import { DocumentType, gql, useMutation, useQuery } from 'urql';
import { useMutation, useQuery } from 'urql';
import * as Yup from 'yup';
import { authenticated } from '@/components/authenticated-container';
import { OrganizationLayout } from '@/components/layouts';
@ -13,6 +13,7 @@ import {
} from '@/components/v2/dropdown';
import { CopyIcon, KeyIcon, MoreIcon, SettingsIcon, TrashIcon } from '@/components/v2/icon';
import { ChangePermissionsModal, DeleteMembersModal } from '@/components/v2/modals';
import { FragmentType, graphql, useFragment } from '@/gql';
import { MeDocument, OrganizationFieldsFragment, OrganizationType } from '@/graphql';
import { OrganizationAccessScope, useOrganizationAccess } from '@/lib/access/organization';
import { useClipboard } from '@/lib/hooks/use-clipboard';
@ -27,7 +28,7 @@ export const DateFormatter = Intl.DateTimeFormat('en', {
day: 'numeric',
});
const Members_Invitation = gql(/* GraphQL */ `
const Members_Invitation = graphql(`
fragment Members_Invitation on OrganizationInvitation {
id
createdAt
@ -37,11 +38,13 @@ const Members_Invitation = gql(/* GraphQL */ `
}
`);
export const MemberInvitationForm_InviteByEmail = gql(/* GraphQL */ `
const MemberInvitationForm_InviteByEmail = graphql(`
mutation MemberInvitationForm_InviteByEmail($input: InviteToOrganizationByEmailInput!) {
inviteToOrganizationByEmail(input: $input) {
ok {
...Members_Invitation
email
id
}
error {
message
@ -53,7 +56,7 @@ export const MemberInvitationForm_InviteByEmail = gql(/* GraphQL */ `
}
`);
export const InvitationDeleteButton_DeleteInvitation = gql(/* GraphQL */ `
const InvitationDeleteButton_DeleteInvitation = graphql(`
mutation InvitationDeleteButton_DeleteInvitation($input: DeleteOrganizationInvitationInput!) {
deleteOrganizationInvitation(input: $input) {
ok {
@ -99,7 +102,7 @@ const MemberInvitationForm = ({
if (result.data?.inviteToOrganizationByEmail?.ok?.email) {
notify(`Invited ${result.data.inviteToOrganizationByEmail.ok.email}`, 'success');
resetForm();
globalThis.window?.location.reload();
}
},
});
@ -160,6 +163,7 @@ function InvitationDeleteButton({
email,
},
});
globalThis.window?.location.reload();
}}
>
<TrashIcon /> Remove
@ -167,37 +171,11 @@ function InvitationDeleteButton({
);
}
export const Members_OrganizationMembers = gql(/* GraphQL */ `
query Members_OrganizationMembers($selector: OrganizationSelectorInput!) {
organization(selector: $selector) {
organization {
...OrganizationFields
owner {
...MemberFields
}
members {
nodes {
...MemberFields
}
total
}
invitations {
nodes {
...Members_Invitation
}
}
}
}
}
`);
const Invitation = ({
invitation,
organizationCleanId,
}: {
invitation: DocumentType<typeof Members_Invitation>;
const Invitation = (props: {
invitation: FragmentType<typeof Members_Invitation>;
organizationCleanId: string;
}) => {
}): ReactElement => {
const invitation = useFragment(Members_Invitation, props.invitation);
const copyToClipboard = useClipboard();
const copyLink = useCallback(async () => {
await copyToClipboard(`${window.location.origin}/join/${invitation.code}`);
@ -223,7 +201,7 @@ const Invitation = ({
Copy invite link
</DropdownMenuItem>
<InvitationDeleteButton
organizationCleanId={organizationCleanId}
organizationCleanId={props.organizationCleanId}
email={invitation.email}
/>
</DropdownMenuContent>
@ -232,7 +210,64 @@ const Invitation = ({
);
};
function Page({ organization }: { organization: OrganizationFieldsFragment }) {
const Page_OrganizationFragment = graphql(`
fragment Page_OrganizationFragment on Organization {
me {
...CanAccessOrganization_MemberFragment
...ChangePermissionsModal_MemberFragment
}
cleanId
type
owner {
id
}
members {
nodes {
id
...ChangePermissionsModal_MemberFragment
user {
provider
displayName
email
}
}
total
}
...OrganizationInvitations_OrganizationFragment
...ChangePermissionsModal_OrganizationFragment
}
`);
const OrganizationInvitations_OrganizationFragment = graphql(`
fragment OrganizationInvitations_OrganizationFragment on Organization {
cleanId
invitations {
nodes {
id
...Members_Invitation
}
}
}
`);
const OrganizationInvitations = (props: {
organization: FragmentType<typeof OrganizationInvitations_OrganizationFragment>;
}): ReactElement | null => {
const org = useFragment(OrganizationInvitations_OrganizationFragment, props.organization);
return org.invitations.nodes.length ? (
<div className="pt-3">
<div className="border-t-4 border-solid pb-6" />
{org.invitations.nodes.map(node => (
<Invitation key={node.id} invitation={node} organizationCleanId={org.cleanId} />
))}
</div>
) : null;
};
function Page(props: { organization: FragmentType<typeof Page_OrganizationFragment> }) {
const organization = useFragment(Page_OrganizationFragment, props.organization);
useOrganizationAccess({
scope: OrganizationAccessScope.Members,
redirect: true,
@ -246,19 +281,10 @@ function Page({ organization }: { organization: OrganizationFieldsFragment }) {
const [meQuery] = useQuery({ query: MeDocument });
const router = useRouteSelector();
const [organizationMembersQuery] = useQuery({
query: Members_OrganizationMembers,
variables: {
selector: {
organization: router.organizationId,
},
},
});
const org = organizationMembersQuery.data?.organization?.organization;
const org = organization;
const isPersonal = org?.type === OrganizationType.Personal;
const members = org?.members.nodes;
const invitations = org?.invitations.nodes;
useEffect(() => {
if (isPersonal) {
@ -360,24 +386,34 @@ function Page({ organization }: { organization: OrganizationFieldsFragment }) {
</Card>
);
})}
{invitations?.length ? (
<div className="pt-3">
<div className="border-t-4 border-solid pb-6" />
{invitations.map(node => (
<Invitation key={node.id} invitation={node} organizationCleanId={org.cleanId} />
))}
</div>
) : null}
<OrganizationInvitations organization={org} />
</>
);
}
function MembersPage(): ReactElement {
const OrganizationMembersPageQuery = graphql(`
query OrganizationMembersPageQuery($selector: OrganizationSelectorInput!) {
organization(selector: $selector) {
organization {
...OrganizationLayout_OrganizationFragment
...Page_OrganizationFragment
}
}
}
`);
function OrganizationMembersPage(): ReactElement {
return (
<>
<Title title="Members" />
<OrganizationLayout value="members" className="flex w-4/5 flex-col gap-4">
{({ organization }) => <Page organization={organization} />}
<OrganizationLayout
value="members"
className="flex w-4/5 flex-col gap-4"
query={OrganizationMembersPageQuery}
>
{({ organization }) =>
organization ? <Page organization={organization.organization} /> : null
}
</OrganizationLayout>
</>
);
@ -385,4 +421,4 @@ function MembersPage(): ReactElement {
export const getServerSideProps = withSessionProtection();
export default authenticated(MembersPage);
export default authenticated(OrganizationMembersPage);

View file

@ -1,6 +1,6 @@
import { ReactElement } from 'react';
import { useFormik } from 'formik';
import { gql, useMutation, useQuery } from 'urql';
import { useMutation, useQuery } from 'urql';
import * as Yup from 'yup';
import { authenticated } from '@/components/authenticated-container';
import { OrganizationLayout } from '@/components/layouts';
@ -12,11 +12,11 @@ import {
TransferOrganizationOwnershipModal,
} from '@/components/v2/modals';
import { env } from '@/env/frontend';
import { FragmentType, graphql, useFragment } from '@/gql';
import {
CheckIntegrationsDocument,
DeleteGitHubIntegrationDocument,
DeleteSlackIntegrationDocument,
OrganizationFieldsFragment,
OrganizationType,
} from '@/graphql';
import {
@ -122,7 +122,7 @@ function Integrations(): ReactElement | null {
);
}
const UpdateOrganizationNameMutation = gql(/* GraphQL */ `
const UpdateOrganizationNameMutation = graphql(`
mutation Settings_UpdateOrganizationName($input: UpdateOrganizationNameInput!) {
updateOrganizationName(input: $input) {
ok {
@ -142,7 +142,23 @@ const UpdateOrganizationNameMutation = gql(/* GraphQL */ `
}
`);
const Page = ({ organization }: { organization: OrganizationFieldsFragment }) => {
const SettingsPageRenderer_OrganizationFragment = graphql(`
fragment SettingsPageRenderer_OrganizationFragment on Organization {
type
name
me {
...CanAccessOrganization_MemberFragment
isOwner
}
...DeleteOrganizationModal_OrganizationFragment
...TransferOrganizationOwnershipModal_OrganizationFragment
}
`);
const SettingsPageRenderer = (props: {
organization: FragmentType<typeof SettingsPageRenderer_OrganizationFragment>;
}) => {
const organization = useFragment(SettingsPageRenderer_OrganizationFragment, props.organization);
useOrganizationAccess({
scope: OrganizationAccessScope.Settings,
member: organization.me,
@ -294,12 +310,31 @@ const Page = ({ organization }: { organization: OrganizationFieldsFragment }) =>
);
};
function SettingsPage(): ReactElement {
const OrganizationSettingsPageQuery = graphql(`
query OrganizationSettingsPageQuery($selector: OrganizationSelectorInput!) {
organization(selector: $selector) {
organization {
...OrganizationLayout_OrganizationFragment
...SettingsPageRenderer_OrganizationFragment
}
}
}
`);
function OrganizationSettingsPage(): ReactElement {
return (
<>
<Title title="Organization settings" />
<OrganizationLayout value="settings" className="flex flex-col gap-y-10">
{props => <Page {...props} />}
<OrganizationLayout
value="settings"
className="flex flex-col gap-y-10"
query={OrganizationSettingsPageQuery}
>
{props =>
props.organization ? (
<SettingsPageRenderer organization={props.organization.organization} />
) : null
}
</OrganizationLayout>
</>
);
@ -307,4 +342,4 @@ function SettingsPage(): ReactElement {
export const getServerSideProps = withSessionProtection();
export default authenticated(SettingsPage);
export default authenticated(OrganizationSettingsPage);

View file

@ -9,11 +9,7 @@ import { InvoicesList } from '@/components/organization/billing/InvoicesList';
import { RateLimitWarn } from '@/components/organization/billing/RateLimitWarn';
import { OrganizationUsageEstimationView } from '@/components/organization/Usage';
import { Card, Heading, Stat, Tabs, Title } from '@/components/v2';
import {
OrganizationFieldsFragment,
OrgBillingInfoFieldsFragment,
OrgRateLimitFieldsFragment,
} from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { OrganizationAccessScope, useOrganizationAccess } from '@/lib/access/organization';
import { getIsStripeEnabled } from '@/lib/billing/stripe-public-key';
import { withSessionProtection } from '@/lib/supertokens/guard';
@ -26,13 +22,39 @@ const DateFormatter = Intl.DateTimeFormat('en-US', {
const ManagePage = dynamic(() => import('./manage'));
function Page({
organization,
}: {
organization: OrganizationFieldsFragment &
OrgBillingInfoFieldsFragment &
OrgRateLimitFieldsFragment;
const SubscriptionPage_OrganizationFragment = graphql(`
fragment SubscriptionPage_OrganizationFragment on Organization {
me {
...CanAccessOrganization_MemberFragment
}
billingConfiguration {
invoices {
id
}
upcomingInvoice {
amount
date
}
}
...RateLimitWarn_OrganizationFragment
...OrganizationInvoicesList_OrganizationFragment
...BillingView_OrganizationFragment
...OrganizationUsageEstimationView_OrganizationFragment
}
`);
const SubscriptionPage_QueryFragment = graphql(`
fragment SubscriptionPage_QueryFragment on Query {
...BillingView_QueryFragment
}
`);
function Page(props: {
organization: FragmentType<typeof SubscriptionPage_OrganizationFragment>;
query: FragmentType<typeof SubscriptionPage_QueryFragment>;
}): ReactElement | null {
const organization = useFragment(SubscriptionPage_OrganizationFragment, props.organization);
const query = useFragment(SubscriptionPage_QueryFragment, props.query);
const canAccess = useOrganizationAccess({
scope: OrganizationAccessScope.Settings,
member: organization?.me,
@ -65,7 +87,7 @@ function Page({
<Card className="mt-8">
<Heading className="mb-2">Plan and Reserved Volume</Heading>
<div>
<BillingView organization={organization}>
<BillingView organization={organization} query={query}>
{organization.billingConfiguration?.upcomingInvoice && (
<Stat>
<Stat.Label>Next Invoice</Stat.Label>
@ -109,12 +131,28 @@ function Page({
);
}
const SubscriptionPageQuery = graphql(`
query SubscriptionPageQuery($selector: OrganizationSelectorInput!) {
organization(selector: $selector) {
organization {
...OrganizationLayout_OrganizationFragment
...SubscriptionPage_OrganizationFragment
}
}
...SubscriptionPage_QueryFragment
}
`);
function SubscriptionPage(): ReactElement {
return (
<>
<Title title="Subscription & Usage" />
<OrganizationLayout value="subscription" includeBilling includeRateLimit>
{({ organization }) => <Page organization={organization} />}
<OrganizationLayout value="subscription" query={SubscriptionPageQuery}>
{props =>
props.organization ? (
<Page organization={props.organization.organization} query={props} />
) : null
}
</OrganizationLayout>
</>
);

View file

@ -6,13 +6,12 @@ import { OrganizationLayout } from '@/components/layouts';
import { BillingPaymentMethod } from '@/components/organization/billing/BillingPaymentMethod';
import { BillingPlanPicker } from '@/components/organization/billing/BillingPlanPicker';
import { PlanSummary } from '@/components/organization/billing/PlanSummary';
import { Button, Card, DataWrapper, Heading, Input, Slider, Stat, Title } from '@/components/v2';
import { Button, Card, Heading, Input, Slider, Stat, Title } from '@/components/v2';
import { FragmentType, graphql, useFragment } from '@/gql';
import { BillingPlanType } from '@/gql/graphql';
import {
BillingPlansDocument,
DowngradeToHobbyDocument,
OrganizationFieldsFragment,
OrgBillingInfoFieldsFragment,
UpdateOrgRateLimitDocument,
UpgradeToProDocument,
} from '@/graphql';
@ -20,11 +19,42 @@ import { OrganizationAccessScope, useOrganizationAccess } from '@/lib/access/org
import { openChatSupport } from '@/utils';
import { CardElement, useElements, useStripe } from '@stripe/react-stripe-js';
function Inner({
organization,
}: {
organization: OrganizationFieldsFragment & OrgBillingInfoFieldsFragment;
const ManageSubscriptionInner_OrganizationFragment = graphql(`
fragment ManageSubscriptionInner_OrganizationFragment on Organization {
cleanId
me {
...CanAccessOrganization_MemberFragment
}
billingConfiguration {
paymentMethod {
__typename
}
}
plan
rateLimit {
operations
}
...BillingPaymentMethod_OrganizationFragment
}
`);
const ManageSubscriptionInner_BillingPlansFragment = graphql(`
fragment ManageSubscriptionInner_BillingPlansFragment on BillingPlan {
...BillingPlanPicker_PlanFragment
planType
retentionInDays
...PlanSummary_PlanFragment
}
`);
function Inner(props: {
organization: FragmentType<typeof ManageSubscriptionInner_OrganizationFragment>;
billingPlans: Array<FragmentType<typeof ManageSubscriptionInner_BillingPlansFragment>>;
}): ReactElement | null {
const organization = useFragment(
ManageSubscriptionInner_OrganizationFragment,
props.organization,
);
const stripe = useStripe();
const elements = useElements();
const canAccess = useOrganizationAccess({
@ -43,9 +73,7 @@ function Inner({
const updateOrgRateLimitMutation = useMutation(UpdateOrgRateLimitDocument);
const planSummaryRef = useRef<HTMLDivElement>(null);
const [plan, setPlan] = useState<BillingPlanType>(
(organization?.plan || 'HOBBY') as BillingPlanType,
);
const [plan, setPlan] = useState<BillingPlanType>(organization?.plan || 'HOBBY');
const onPlan = useCallback(
(plan: BillingPlanType) => {
setPlan(plan);
@ -192,116 +220,131 @@ function Inner({
downgradeToHobbyMutation[0].error ||
updateOrgRateLimitMutation[0].error;
const billingPlans = useFragment(
ManageSubscriptionInner_BillingPlansFragment,
props.billingPlans,
);
// TODO: this is also not safe as billingPlans might be an empty list.
const selectedPlan = billingPlans.find(v => v.planType === plan) ?? billingPlans[0];
return (
<DataWrapper query={query}>
{result => {
// TODO: this is also not safe as billingPlans might be an empty list.
const selectedPlan =
result.data.billingPlans.find(v => v.planType === plan) ?? result.data.billingPlans[0];
<div className="flex w-full flex-col gap-5">
<Card className="w-full">
<Heading className="mb-4">Choose Your Plan</Heading>
<BillingPlanPicker
activePlan={organization.plan}
value={plan}
plans={billingPlans}
onPlanChange={onPlan}
/>
</Card>
<Card className="w-full self-start" ref={planSummaryRef}>
<Heading className="mb-2">Plan Summary</Heading>
<div>
{error && <QueryError showError error={error} />}
return (
<div className="flex w-full flex-col gap-5">
<Card className="w-full">
<Heading className="mb-4">Choose Your Plan</Heading>
<BillingPlanPicker
activePlan={organization.plan}
value={plan}
plans={result.data.billingPlans}
onPlanChange={onPlan}
/>
</Card>
<Card className="w-full self-start" ref={planSummaryRef}>
<Heading className="mb-2">Plan Summary</Heading>
<div>
{error && <QueryError showError error={error} />}
<div className="flex flex-col">
<div>
<PlanSummary plan={selectedPlan} operationsRateLimit={operationsRateLimit}>
{selectedPlan.planType === BillingPlanType.Pro && (
<Stat>
<Stat.Label>Free Trial</Stat.Label>
<Stat.Number>30</Stat.Number>
<Stat.HelpText>days</Stat.HelpText>
</Stat>
)}
</PlanSummary>
</div>
<div className="flex flex-col">
<div>
<PlanSummary
plan={selectedPlan}
retentionInDays={selectedPlan.retentionInDays}
operationsRateLimit={operationsRateLimit}
>
{selectedPlan.planType === BillingPlanType.Pro && (
<Stat>
<Stat.Label>Free Trial</Stat.Label>
<Stat.Number>30</Stat.Number>
<Stat.HelpText>days</Stat.HelpText>
</Stat>
)}
</PlanSummary>
{plan === BillingPlanType.Pro && (
<div className="my-8 w-1/2">
<Heading>Define your reserved volume</Heading>
<p className="text-sm text-gray-500">
Pro plan requires to defined quota of reported operations.
</p>
<p className="text-sm text-gray-500">
Pick a volume a little higher than you think you'll need to avoid being rate
limited.
</p>
<p className="text-sm text-gray-500">
Don't worry, you can always adjust it later.
</p>
<div className="mt-5 pl-2.5">
<Slider
min={1}
max={300}
value={[operationsRateLimit]}
onValueChange={onOperationsRateLimitChange}
/>
<div className="flex justify-between">
<span>1M</span>
<span>100M</span>
<span>200M</span>
<span>300M</span>
</div>
{plan === BillingPlanType.Pro && (
<div className="my-8 w-1/2">
<Heading>Define your reserved volume</Heading>
<p className="text-sm text-gray-500">
Pro plan requires to defined quota of reported operations.
</p>
<p className="text-sm text-gray-500">
Pick a volume a little higher than you think you'll need to avoid being rate
limited.
</p>
<p className="text-sm text-gray-500">
Don't worry, you can always adjust it later.
</p>
<div className="mt-5 pl-2.5">
<Slider
min={1}
max={300}
value={[operationsRateLimit]}
onValueChange={onOperationsRateLimitChange}
/>
<div className="flex justify-between">
<span>1M</span>
<span>100M</span>
<span>200M</span>
<span>300M</span>
</div>
</div>
</div>
)}
<div className="my-8 flex flex-row gap-6">
<BillingPaymentMethod
className="w-1/2"
plan={selectedPlan.planType}
organizationBilling={organization}
onValidationChange={setPaymentDetailsValid}
/>
<div className="w-1/2">
{plan === BillingPlanType.Pro && plan !== organization.plan ? (
<div>
<Heading className="mb-3">Discount</Heading>
<Input
className="w-full"
size="medium"
value={couponCode ?? ''}
onChange={e => setCouponCode(e.target.value)}
placeholder="Code"
/>
</div>
) : null}
</div>
</div>
<div>{renderActions()}</div>
</div>
</div>
</Card>
)}
<div className="my-8 flex flex-row gap-6">
<BillingPaymentMethod
className="w-1/2"
plan={selectedPlan.planType}
organization={organization}
onValidationChange={setPaymentDetailsValid}
/>
<div className="w-1/2">
{plan === BillingPlanType.Pro && plan !== organization.plan ? (
<div>
<Heading className="mb-3">Discount</Heading>
<Input
className="w-full"
size="medium"
value={couponCode ?? ''}
onChange={e => setCouponCode(e.target.value)}
placeholder="Code"
/>
</div>
) : null}
</div>
</div>
<div>{renderActions()}</div>
</div>
);
}}
</DataWrapper>
</div>
</Card>
</div>
);
}
const ManageSubscriptionPageQuery = graphql(`
query ManageSubscriptionPageQuery($selector: OrganizationSelectorInput!) {
organization(selector: $selector) {
organization {
...OrganizationLayout_OrganizationFragment
...ManageSubscriptionInner_OrganizationFragment
}
}
billingPlans {
...ManageSubscriptionInner_BillingPlansFragment
}
}
`);
export default function ManageSubscriptionPage(): ReactElement {
return (
<>
<Title title="Manage Subscription" />
<OrganizationLayout includeBilling includeRateLimit>
{({ organization }) => <Inner organization={organization} />}
<OrganizationLayout query={ManageSubscriptionPageQuery}>
{props =>
props.organization ? (
<Inner
organization={props.organization.organization}
billingPlans={props.billingPlans}
/>
) : null
}
</OrganizationLayout>
</>
);

View file

@ -1,9 +1,10 @@
import { useCallback } from 'react';
import { clsx } from 'clsx';
import { gql, useMutation, useQuery } from 'urql';
import { useMutation, useQuery } from 'urql';
import { authenticated } from '@/components/authenticated-container';
import { Title } from '@/components/common';
import { Button, DataWrapper } from '@/components/v2';
import { graphql } from '@/gql';
import { useNotifications } from '@/lib/hooks/use-notifications';
import { useRouteSelector } from '@/lib/hooks/use-route-selector';
import { withSessionProtection } from '@/lib/supertokens/guard';
@ -14,7 +15,7 @@ const classes = {
actions: clsx('flex flex-row gap-2 items-center justify-center'),
};
const OrganizationTransferPage_GetRequest = gql(`
const OrganizationTransferPage_GetRequest = graphql(`
query OrganizationTransferPage_GetRequest($selector: OrganizationTransferRequestSelector!) {
organizationTransferRequest(selector: $selector) {
organization {
@ -33,7 +34,7 @@ const OrganizationTransferPage_GetRequest = gql(`
}
`);
const OrganizationTransferPage_AnswerRequest = gql(`
const OrganizationTransferPage_AnswerRequest = graphql(`
mutation OrganizationTransferPage_AnswerRequest($input: AnswerOrganizationTransferRequestInput!) {
answerOrganizationTransferRequest(input: $input) {
ok {

View file

@ -1,26 +1,21 @@
import { ReactElement } from 'react';
import { useFormik } from 'formik';
import { gql, useMutation, useQuery } from 'urql';
import { useMutation, useQuery } from 'urql';
import * as Yup from 'yup';
import { authenticated } from '@/components/authenticated-container';
import { Avatar, Button, Heading, Input, SubHeader, Tabs, Title } from '@/components/v2';
import { graphql } from '@/gql';
import { MeDocument } from '@/graphql';
import { withSessionProtection } from '@/lib/supertokens/guard';
gql(/* GraphQL */ `
fragment UpdateMeFragment on User {
id
fullName
displayName
}
`);
const UpdateMeMutation = gql(/* GraphQL */ `
const UpdateMeMutation = graphql(`
mutation updateMe($input: UpdateMeInput!) {
updateMe(input: $input) {
ok {
updatedUser {
...UpdateMeFragment
id
fullName
displayName
}
}
error {

View file

@ -11,10 +11,11 @@ import { clsx } from 'clsx';
import { formatISO } from 'date-fns';
import ReactECharts from 'echarts-for-react';
import AutoSizer from 'react-virtualized-auto-sizer';
import { DocumentType, gql, useQuery } from 'urql';
import { useQuery } from 'urql';
import { Button, DataWrapper, Stat, Table, TBody, Td, Th, THead, Tr } from '@/components/v2';
import { CHART_PRIMARY_COLOR } from '@/constants';
import { env } from '@/env/frontend';
import { DocumentType, FragmentType, graphql, useFragment } from '@/gql';
import { OrganizationType } from '@/graphql';
import { theme } from '@/lib/charts';
import { useChartStyles } from '@/utils';
@ -65,26 +66,25 @@ export type Filters = Partial<{
'with-collected': boolean;
}>;
const CollectedOperationsOverTime_OperationFragment = gql(/* GraphQL */ `
const CollectedOperationsOverTime_OperationFragment = graphql(`
fragment CollectedOperationsOverTime_OperationFragment on AdminOperationPoint {
count
date
}
`);
function CollectedOperationsOverTime({
dateRange,
operations,
}: {
function CollectedOperationsOverTime(props: {
dateRange: {
from: Date;
to: Date;
};
operations: DocumentType<typeof CollectedOperationsOverTime_OperationFragment>[];
operations: Array<FragmentType<typeof CollectedOperationsOverTime_OperationFragment>>;
}): ReactElement {
const operations = useFragment(CollectedOperationsOverTime_OperationFragment, props.operations);
const dataRef = useRef<[string, number][]>();
dataRef.current ||= operations.map(node => [node.date, node.count]);
const data = dataRef.current;
return (
<AutoSizer disableHeight>
{size => (
@ -107,8 +107,8 @@ function CollectedOperationsOverTime({
{
type: 'time',
boundaryGap: false,
min: dateRange.from,
max: dateRange.to,
min: props.dateRange.from,
max: props.dateRange.to,
},
],
yAxis: [{ type: 'value', min: 0 }],
@ -143,7 +143,7 @@ function OverallStat({ label, value }: { label: string; value: number }): ReactE
);
}
const AdminStatsQuery = gql(/* GraphQL */ `
const AdminStatsQuery = graphql(`
query adminStats($period: DateRangeInput!) {
admin {
stats(period: $period) {

View file

@ -1,13 +1,13 @@
import { ReactElement, ReactNode } from 'react';
import clsx from 'clsx';
import { DocumentType, gql } from 'urql';
import { Drawer } from '@/components/v2';
import { DocumentType, graphql } from '@/gql';
import { OrganizationType } from '@/graphql';
import { getDocsUrl } from '@/lib/docs-url';
import { useToggle } from '@/lib/hooks';
import { CheckCircledIcon } from '@radix-ui/react-icons';
const GetStartedWizard_GetStartedProgress = gql(/* GraphQL */ `
const GetStartedWizard_GetStartedProgress = graphql(`
fragment GetStartedWizard_GetStartedProgress on OrganizationGetStarted {
creatingProject
publishingSchema

View file

@ -2,18 +2,13 @@ import { ReactElement, ReactNode, useEffect } from 'react';
import NextLink from 'next/link';
import { useRouter } from 'next/router';
import cookies from 'js-cookie';
import { useQuery } from 'urql';
import { TypedDocumentNode, useQuery } from 'urql';
import { Button, Heading, SubHeader, Tabs } from '@/components/v2';
import { PlusIcon } from '@/components/v2/icon';
import { CreateProjectModal } from '@/components/v2/modals';
import { LAST_VISITED_ORG_KEY } from '@/constants';
import {
OrganizationDocument,
OrganizationFieldsFragment,
OrganizationType,
OrgBillingInfoFieldsFragment,
OrgRateLimitFieldsFragment,
} from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { Exact, OrganizationType } from '@/graphql';
import {
canAccessOrganization,
OrganizationAccessScope,
@ -29,38 +24,43 @@ enum TabValue {
Subscription = 'subscription',
}
type OrganizationLayout<T, P> = {
children(props: { organization: OrganizationFieldsFragment & T }): ReactNode;
value?: 'overview' | 'members' | 'settings' | 'subscription';
className?: string;
} & P;
const OrganizationLayout_OrganizationFragment = graphql(`
fragment OrganizationLayout_OrganizationFragment on Organization {
type
name
me {
...CanAccessOrganization_MemberFragment
}
}
`);
export function OrganizationLayout(
props: OrganizationLayout<OrgBillingInfoFieldsFragment, { includeBilling: true }>,
): ReactElement;
export function OrganizationLayout(
props: OrganizationLayout<OrgRateLimitFieldsFragment, { includeRateLimit: true }>,
): ReactElement;
export function OrganizationLayout(
props: OrganizationLayout<
OrgBillingInfoFieldsFragment & OrgRateLimitFieldsFragment,
{ includeBilling: true; includeRateLimit: true }
>,
): ReactElement;
export function OrganizationLayout(props: OrganizationLayout<{}, {}>): ReactElement;
export function OrganizationLayout({
export function OrganizationLayout<
TSatisfiesType extends {
organization?:
| {
organization?: FragmentType<typeof OrganizationLayout_OrganizationFragment> | null;
}
| null
| undefined;
},
>({
children,
value,
query,
className,
includeBilling,
includeRateLimit,
}: OrganizationLayout<
{},
{
includeBilling?: boolean;
includeRateLimit?: boolean;
}
>): ReactElement | null {
}: {
children(props: TSatisfiesType): ReactNode;
value?: 'overview' | 'members' | 'settings' | 'subscription';
className?: string;
query: TypedDocumentNode<
TSatisfiesType,
Exact<{
selector: {
organization: string;
};
}>
>;
}): ReactElement | null {
const router = useRouteSelector();
const { push } = useRouter();
const [isModalOpen, toggleModalOpen] = useToggle();
@ -68,16 +68,19 @@ export function OrganizationLayout({
const orgId = router.organizationId;
const [organizationQuery] = useQuery({
query: OrganizationDocument,
query,
variables: {
selector: {
organization: orgId,
},
includeBilling: includeBilling ?? false,
includeRateLimit: includeRateLimit ?? false,
},
});
const organization = useFragment(
OrganizationLayout_OrganizationFragment,
organizationQuery.data?.organization?.organization,
);
useEffect(() => {
if (organizationQuery.error) {
cookies.remove(LAST_VISITED_ORG_KEY);
@ -87,7 +90,7 @@ export function OrganizationLayout({
}, [organizationQuery.error, router]);
useOrganizationAccess({
member: organizationQuery.data?.organization?.organization?.me,
member: organization?.me ?? null,
scope: OrganizationAccessScope.Read,
redirect: true,
});
@ -96,7 +99,6 @@ export function OrganizationLayout({
return null;
}
const organization = organizationQuery.data?.organization?.organization;
const me = organization?.me;
const isRegularOrg = !organization || organization.type === OrganizationType.Regular;
@ -105,7 +107,7 @@ export function OrganizationLayout({
}
if (!value) {
return <>{children({ organization })}</>;
return <>{children(organizationQuery.data!)}</>;
}
return (
@ -148,7 +150,7 @@ export function OrganizationLayout({
)}
</Tabs.List>
<Tabs.Content value={value} className={className}>
{children({ organization })}
{children(organizationQuery.data!)}
</Tabs.Content>
</Tabs>
</>

View file

@ -1,6 +1,6 @@
import { ReactElement, ReactNode, useEffect } from 'react';
import NextLink from 'next/link';
import { useQuery } from 'urql';
import { TypedDocumentNode, useQuery } from 'urql';
import { Button, Heading, Link, SubHeader, Tabs } from '@/components/v2';
import {
DropdownMenu,
@ -10,12 +10,8 @@ import {
} from '@/components/v2/dropdown';
import { ArrowDownIcon, TargetIcon } from '@/components/v2/icon';
import { CreateTargetModal } from '@/components/v2/modals';
import {
OrganizationFieldsFragment,
ProjectDocument,
ProjectFieldsFragment,
ProjectsDocument,
} from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { Exact, ProjectsDocument } from '@/graphql';
import { canAccessProject, ProjectAccessScope, useProjectAccess } from '@/lib/access/project';
import { useRouteSelector, useToggle } from '@/lib/hooks';
import { ProjectMigrationToast } from '../project/migration-toast';
@ -26,25 +22,65 @@ enum TabValue {
Settings = 'settings',
}
export const ProjectLayout = ({
const ProjectLayout_OrganizationFragment = graphql(`
fragment ProjectLayout_OrganizationFragment on Organization {
name
me {
...CanAccessProject_MemberFragment
}
}
`);
const ProjectLayout_ProjectFragment = graphql(`
fragment ProjectLayout_ProjectFragment on Project {
name
type
registryModel
}
`);
export function ProjectLayout<
/**
* LOL fire me for this.
* Because of this kind of abstraction in place I invented this complicated generic satisfaction thing.
* I'm not sure if it's worth it, but it's the only way I could think of to make it work without removing this component.
*/
TSatisfiesType extends {
organization?:
| {
organization?: FragmentType<typeof ProjectLayout_OrganizationFragment> | null;
}
| null
| undefined;
project?: FragmentType<typeof ProjectLayout_ProjectFragment> | null;
},
>({
children,
value,
className,
query,
}: {
children(props: {
project: ProjectFieldsFragment;
organization: OrganizationFieldsFragment;
project: Exclude<TSatisfiesType['project'], null | undefined>;
organization: Exclude<TSatisfiesType['organization'], null | undefined>['organization'];
}): ReactNode;
value: 'targets' | 'alerts' | 'settings';
className?: string;
}): ReactElement | null => {
query: TypedDocumentNode<
TSatisfiesType,
Exact<{
organizationId: string;
projectId: string;
}>
>;
}): ReactElement | null {
const router = useRouteSelector();
const [isModalOpen, toggleModalOpen] = useToggle();
const { organizationId: orgId, projectId } = router;
const [projectQuery] = useQuery({
query: ProjectDocument,
query,
variables: {
organizationId: orgId,
projectId,
@ -67,9 +103,14 @@ export const ProjectLayout = ({
},
});
const organization = useFragment(
ProjectLayout_OrganizationFragment,
projectQuery.data?.organization?.organization,
);
useProjectAccess({
scope: ProjectAccessScope.Read,
member: projectQuery.data?.organization?.organization.me,
member: organization?.me ?? null,
redirect: true,
});
@ -77,11 +118,10 @@ export const ProjectLayout = ({
return null;
}
const project = projectQuery.data?.project;
const org = projectQuery.data?.organization?.organization;
const project = useFragment(ProjectLayout_ProjectFragment, projectQuery.data?.project);
const projects = projectsQuery.data?.projects;
if (!org || !project) {
if (!organization || !project) {
return null;
}
@ -90,12 +130,12 @@ export const ProjectLayout = ({
<SubHeader>
<div className="container flex items-center pb-4">
<div>
{org && (
{organization && (
<Link
href={`/${orgId}`}
className="line-clamp-1 flex max-w-[250px] items-center text-xs font-medium text-gray-500"
>
{org.name}
{organization.name}
</Link>
)}
<div className="flex items-center gap-2.5">
@ -150,21 +190,30 @@ export const ProjectLayout = ({
<Tabs.Trigger value={TabValue.Targets} asChild>
<NextLink href={`/${orgId}/${projectId}`}>Targets</NextLink>
</Tabs.Trigger>
{canAccessProject(ProjectAccessScope.Alerts, org.me) && (
{canAccessProject(ProjectAccessScope.Alerts, organization.me) && (
<Tabs.Trigger value={TabValue.Alerts} asChild>
<NextLink href={`/${orgId}/${projectId}/view/alerts`}>Alerts</NextLink>
</Tabs.Trigger>
)}
{canAccessProject(ProjectAccessScope.Settings, org.me) && (
{canAccessProject(ProjectAccessScope.Settings, organization.me) && (
<Tabs.Trigger value={TabValue.Settings} asChild>
<NextLink href={`/${orgId}/${projectId}/view/settings`}>Settings</NextLink>
</Tabs.Trigger>
)}
</Tabs.List>
<Tabs.Content value={value} className={className}>
{children({ project, organization: org })}
{children({
project: projectQuery.data?.project as Exclude<
TSatisfiesType['project'],
null | undefined
>,
organization: projectQuery.data?.organization as Exclude<
TSatisfiesType['organization'],
null | undefined
>['organization'],
})}
</Tabs.Content>
</Tabs>
</>
);
};
}

View file

@ -1,6 +1,6 @@
import { ReactElement, ReactNode, useEffect } from 'react';
import NextLink from 'next/link';
import { gql, useQuery } from 'urql';
import { TypedDocumentNode, useQuery } from 'urql';
import { Button, Heading, Link, SubHeader, Tabs } from '@/components/v2';
import {
DropdownMenu,
@ -10,13 +10,7 @@ import {
} from '@/components/v2/dropdown';
import { ArrowDownIcon, Link2Icon } from '@/components/v2/icon';
import { ConnectSchemaModal } from '@/components/v2/modals';
import {
OrganizationFieldsFragment,
ProjectDocument,
ProjectFieldsFragment,
TargetFieldsFragment,
TargetsDocument,
} from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { canAccessTarget, TargetAccessScope, useTargetAccess } from '@/lib/access/target';
import { useRouteSelector, useToggle } from '@/lib/hooks';
import { QueryError } from '../common/DataWrapper';
@ -31,100 +25,127 @@ enum TabValue {
Settings = 'settings',
}
const IsCDNEnabledQuery = gql(/* GraphQL */ `
query IsCDNEnabledQuery {
const TargetLayout_OrganizationFragment = graphql(`
fragment TargetLayout_OrganizationFragment on Organization {
me {
...CanAccessTarget_MemberFragment
}
name
}
`);
const TargetLayout_ProjectFragment = graphql(`
fragment TargetLayout_ProjectFragment on Project {
type
name
registryModel
}
`);
const TargetLayout_TargetConnectionFragment = graphql(`
fragment TargetLayout_TargetConnectionFragment on TargetConnection {
total
nodes {
cleanId
name
}
}
`);
const TargetLayout_IsCDNEnabledFragment = graphql(`
fragment TargetLayout_IsCDNEnabledFragment on Query {
isCDNEnabled
}
`);
export const TargetLayout = ({
export const TargetLayout = <
TSatisfies extends {
organization?:
| {
organization?: FragmentType<typeof TargetLayout_OrganizationFragment> | null;
}
| null
| undefined;
project?: FragmentType<typeof TargetLayout_ProjectFragment> | null;
targets: FragmentType<typeof TargetLayout_TargetConnectionFragment>;
} & FragmentType<typeof TargetLayout_IsCDNEnabledFragment>,
>({
children,
connect,
value,
className,
query,
}: {
children(props: {
target: TargetFieldsFragment;
project: ProjectFieldsFragment;
organization: OrganizationFieldsFragment;
}): ReactNode;
children(props: TSatisfies): ReactNode;
value: 'schema' | 'explorer' | 'history' | 'operations' | 'laboratory' | 'settings';
className?: string;
connect?: ReactNode;
query: TypedDocumentNode<
TSatisfies,
{
organizationId: string;
projectId: string;
targetId: string;
}
>;
}): ReactElement | null => {
const [isModalOpen, toggleModalOpen] = useToggle();
const router = useRouteSelector();
const { organizationId: orgId, projectId, targetId } = router;
const [targetsQuery] = useQuery({
query: TargetsDocument,
variables: {
selector: {
organization: orgId,
project: projectId,
},
},
requestPolicy: 'cache-and-network',
});
const [projectQuery] = useQuery({
query: ProjectDocument,
const [data] = useQuery({
query,
variables: {
organizationId: orgId,
projectId,
targetId,
},
requestPolicy: 'cache-and-network',
});
const [isCdnEnabledQuery] = useQuery({ query: IsCDNEnabledQuery });
const targets = targetsQuery.data?.targets;
const isCDNEnabled = useFragment(TargetLayout_IsCDNEnabledFragment, data.data);
const targets = useFragment(TargetLayout_TargetConnectionFragment, data.data?.targets);
const target = targets?.nodes.find(node => node.cleanId === targetId);
const org = projectQuery.data?.organization?.organization;
const project = projectQuery.data?.project;
const org = useFragment(TargetLayout_OrganizationFragment, data.data?.organization?.organization);
const project = useFragment(TargetLayout_ProjectFragment, data.data?.project);
const me = org?.me;
useEffect(() => {
if (!targetsQuery.fetching && !target) {
if (!data.fetching && !target) {
// url with # provoke error Maximum update depth exceeded
router.push('/404', router.asPath.replace(/#.*/, ''));
}
}, [router, target, targetsQuery.fetching]);
}, [router, target, data.fetching]);
useEffect(() => {
if (!projectQuery.fetching && !project) {
if (!data.fetching && !project) {
// url with # provoke error Maximum update depth exceeded
router.push('/404', router.asPath.replace(/#.*/, ''));
}
}, [router, project, projectQuery.fetching]);
}, [router, project, data.fetching]);
useTargetAccess({
scope: TargetAccessScope.Read,
member: me,
member: me ?? null,
redirect: true,
});
const canAccessSchema = canAccessTarget(TargetAccessScope.RegistryRead, me);
const canAccessSettings = canAccessTarget(TargetAccessScope.Settings, me);
const canAccessSchema = canAccessTarget(TargetAccessScope.RegistryRead, me ?? null);
const canAccessSettings = canAccessTarget(TargetAccessScope.Settings, me ?? null);
if (projectQuery.fetching || targetsQuery.fetching) {
if (data.fetching) {
return null;
}
if (projectQuery.error || targetsQuery.error) {
return <QueryError error={projectQuery.error || targetsQuery.error} />;
if (data.error) {
return <QueryError error={data.error} />;
}
if (!org || !project || !target) {
return null;
}
if (!isCdnEnabledQuery.data) {
return null;
}
return (
<>
<SubHeader>
@ -170,7 +191,7 @@ export const TargetLayout = ({
<div className="mb-10 text-xs font-bold text-[#34eab9]">{project?.type}</div>
</div>
{connect ??
(isCdnEnabledQuery.data.isCDNEnabled ? (
(isCDNEnabled?.isCDNEnabled ? (
<>
<Button
size="large"
@ -224,11 +245,7 @@ export const TargetLayout = ({
</Tabs.List>
<Tabs.Content value={value} className={className}>
{children({
target,
project,
organization: org,
})}
{children(data.data as TSatisfies)}
</Tabs.Content>
</Tabs>
</>

View file

@ -1,10 +1,9 @@
import { FormEventHandler, memo, ReactElement, useCallback, useState } from 'react';
import { useMutation } from 'urql';
import { Accordion, RadixSelect } from '@/components/v2';
import { FragmentType, graphql, useFragment } from '@/gql';
import {
MemberFieldsFragment,
OrganizationAccessScope,
OrganizationFieldsFragment,
ProjectAccessScope,
TargetAccessScope,
UpdateOrganizationMemberAccessDocument,
@ -149,17 +148,38 @@ export const PermissionsSpace = memo(
PermissionsSpaceInner,
) as unknown as typeof PermissionsSpaceInner;
const UsePermissionManager_OrganizationFragment = graphql(`
fragment UsePermissionManager_OrganizationFragment on Organization {
cleanId
me {
...CanAccessOrganization_MemberFragment
...CanAccessProject_MemberFragment
...CanAccessTarget_MemberFragment
}
}
`);
const UsePermissionManager_MemberFragment = graphql(`
fragment UsePermissionManager_MemberFragment on Member {
id
targetAccessScopes
projectAccessScopes
organizationAccessScopes
}
`);
export function usePermissionsManager({
organization,
member,
onSuccess,
passMemberScopes,
...props
}: {
organization: OrganizationFieldsFragment;
member: MemberFieldsFragment;
organization: FragmentType<typeof UsePermissionManager_OrganizationFragment>;
member: FragmentType<typeof UsePermissionManager_MemberFragment>;
passMemberScopes: boolean;
onSuccess(): void;
}) {
const member = useFragment(UsePermissionManager_MemberFragment, props.member);
const organization = useFragment(UsePermissionManager_OrganizationFragment, props.organization);
const [state, setState] = useState<'LOADING' | 'IDLE'>('IDLE');
const notify = useNotifications();
const [, mutate] = useMutation(UpdateOrganizationMemberAccessDocument);

View file

@ -1,21 +1,29 @@
import { ReactElement } from 'react';
import { useQuery } from 'urql';
import { DataWrapper, Table, TBody, Td, Th, THead, Tr } from '@/components/v2';
import {
OrganizationFieldsFragment,
OrgBillingInfoFieldsFragment,
UsageEstimationDocument,
} from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { UsageEstimationDocument } from '@/graphql';
import { Scale } from '../common';
import { calculatePeriod } from '../common/TimeFilter';
const NumericFormatter = Intl.NumberFormat('en', { notation: 'standard' });
export function OrganizationUsageEstimationView({
organization,
}: {
organization: OrganizationFieldsFragment & OrgBillingInfoFieldsFragment;
const OrganizationUsageEstimationView_OrganizationFragment = graphql(`
fragment OrganizationUsageEstimationView_OrganizationFragment on Organization {
cleanId
rateLimit {
operations
}
}
`);
export function OrganizationUsageEstimationView(props: {
organization: FragmentType<typeof OrganizationUsageEstimationView_OrganizationFragment>;
}): ReactElement {
const organization = useFragment(
OrganizationUsageEstimationView_OrganizationFragment,
props.organization,
);
const period = calculatePeriod('month');
const [query] = useQuery({

View file

@ -1,41 +1,45 @@
import { ReactNode } from 'react';
import { useQuery } from 'urql';
import { DataWrapper } from '@/components/v2';
import {
BillingPlansDocument,
OrganizationFieldsFragment,
OrgBillingInfoFieldsFragment,
} from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { PlanSummary } from './PlanSummary';
export function BillingView({
organization,
children,
}: {
const BillingView_OrganizationFragment = graphql(`
fragment BillingView_OrganizationFragment on Organization {
plan
rateLimit {
retentionInDays
operations
}
}
`);
const BillingView_QueryFragment = graphql(`
fragment BillingView_QueryFragment on Query {
billingPlans {
planType
...PlanSummary_PlanFragment
}
}
`);
export function BillingView(props: {
children: ReactNode;
organization: OrganizationFieldsFragment & OrgBillingInfoFieldsFragment;
organization: FragmentType<typeof BillingView_OrganizationFragment>;
query: FragmentType<typeof BillingView_QueryFragment>;
}) {
const [query] = useQuery({ query: BillingPlansDocument });
const organization = useFragment(BillingView_OrganizationFragment, props.organization);
const query = useFragment(BillingView_QueryFragment, props.query);
const plan = query.billingPlans.find(v => v.planType === organization.plan);
if (plan == null) {
return null;
}
return (
<DataWrapper query={query}>
{result => {
const plan = result.data.billingPlans.find(v => v.planType === organization.plan);
if (plan == null) {
return null;
}
return (
<PlanSummary
retentionInDays={organization.rateLimit.retentionInDays}
operationsRateLimit={Math.floor(organization.rateLimit.operations / 1_000_000)}
plan={plan}
>
{children}
</PlanSummary>
);
}}
</DataWrapper>
<PlanSummary
operationsRateLimit={Math.floor(organization.rateLimit.operations / 1_000_000)}
plan={plan}
>
{props.children}
</PlanSummary>
);
}

View file

@ -2,25 +2,41 @@ import { ReactElement } from 'react';
import clsx from 'clsx';
import { Section } from '@/components/common';
import { Heading, Link } from '@/components/v2';
import { BillingPlanType, OrgBillingInfoFieldsFragment } from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { BillingPlanType } from '@/graphql';
import { CardElement } from '@stripe/react-stripe-js';
const BillingPaymentMethod_OrganizationFragment = graphql(`
fragment BillingPaymentMethod_OrganizationFragment on Organization {
billingConfiguration {
paymentMethod {
brand
last4
expMonth
expYear
}
}
}
`);
export const BillingPaymentMethod = ({
plan,
organizationBilling,
onValidationChange,
className,
...props
}: {
plan: BillingPlanType;
className?: string;
organizationBilling: OrgBillingInfoFieldsFragment;
organization: FragmentType<typeof BillingPaymentMethod_OrganizationFragment>;
onValidationChange?: (isValid: boolean) => void;
}): ReactElement | null => {
if (plan !== 'PRO') {
return null;
}
if (!organizationBilling.billingConfiguration?.paymentMethod) {
const organization = useFragment(BillingPaymentMethod_OrganizationFragment, props.organization);
if (!organization.billingConfiguration?.paymentMethod) {
return (
<div className={clsx('flex flex-col gap-6', className)}>
<Heading>Payment Method</Heading>
@ -50,7 +66,7 @@ export const BillingPaymentMethod = ({
</div>
);
}
const info = organizationBilling.billingConfiguration.paymentMethod;
const info = organization.billingConfiguration.paymentMethod;
return (
<>

View file

@ -1,7 +1,8 @@
import { ReactElement, ReactNode } from 'react';
import { Label, Section } from '@/components/common';
import { Link, Radio, RadioGroup } from '@/components/v2';
import { BillingPlansQuery, BillingPlanType } from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { BillingPlanType } from '@/graphql';
import { CheckIcon } from '@radix-ui/react-icons';
const planCollection: {
@ -103,17 +104,27 @@ const billingPlanLookUpMap = {
[BillingPlanType.Hobby]: 'Free',
} as Record<BillingPlanType, string | undefined>;
const BillingPlanPicker_PlanFragment = graphql(`
fragment BillingPlanPicker_PlanFragment on BillingPlan {
planType
id
name
basePrice
}
`);
export function BillingPlanPicker({
value,
activePlan,
plans,
onPlanChange,
activePlan,
...props
}: {
value: BillingPlanType;
activePlan: BillingPlanType;
plans: BillingPlansQuery['billingPlans'];
plans: ReadonlyArray<FragmentType<typeof BillingPlanPicker_PlanFragment>>;
onPlanChange: (plan: BillingPlanType) => void;
}): ReactElement {
const plans = useFragment(BillingPlanPicker_PlanFragment, props.plans);
return (
<RadioGroup value={value} onValueChange={onPlanChange} className="flex gap-4 md:!flex-row">
{plans.map(plan => (

View file

@ -1,13 +1,30 @@
import { ReactElement } from 'react';
import { Link, Table, TBody, Td, Th, THead, Tr } from '@/components/v2';
import { OrganizationFieldsFragment, OrgBillingInfoFieldsFragment } from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { CurrencyFormatter, DateFormatter } from './helpers';
export function InvoicesList({
organization,
}: {
organization: OrganizationFieldsFragment & OrgBillingInfoFieldsFragment;
const OrganizationInvoicesList_OrganizationFragment = graphql(`
fragment OrganizationInvoicesList_OrganizationFragment on Organization {
billingConfiguration {
invoices {
id
date
amount
periodStart
periodEnd
pdfLink
}
}
}
`);
export function InvoicesList(props: {
organization: FragmentType<typeof OrganizationInvoicesList_OrganizationFragment>;
}): ReactElement | null {
const organization = useFragment(
OrganizationInvoicesList_OrganizationFragment,
props.organization,
);
if (!organization.billingConfiguration?.invoices?.length) {
return null;
}

View file

@ -1,17 +1,28 @@
import { ReactElement, ReactNode } from 'react';
import { Stat, Table, TBody, Td, TFoot, Th, THead, Tr } from '@/components/v2';
import { BillingPlansQuery, BillingPlanType } from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { BillingPlanType } from '@/graphql';
import { CurrencyFormatter } from './helpers';
function PriceEstimationTable({
plan,
operationsRateLimit,
}: {
plan: BillingPlansQuery['billingPlans'][number];
const PriceEstimationTable_PlanFragment = graphql(`
fragment PriceEstimationTable_PlanFragment on BillingPlan {
includedOperationsLimit
pricePerOperationsUnit
basePrice
planType
}
`);
function PriceEstimationTable(props: {
plan: FragmentType<typeof PriceEstimationTable_PlanFragment>;
operationsRateLimit: number;
}): ReactElement {
const plan = useFragment(PriceEstimationTable_PlanFragment, props.plan);
const includedOperationsInMillions = (plan.includedOperationsLimit ?? 0) / 1_000_000;
const additionalOperations = Math.max(0, operationsRateLimit - includedOperationsInMillions);
const additionalOperations = Math.max(
0,
props.operationsRateLimit - includedOperationsInMillions,
);
const operationsTotal = (plan.pricePerOperationsUnit ?? 0) * additionalOperations;
const total = (plan.basePrice ?? 0) + operationsTotal;
@ -59,17 +70,24 @@ function PriceEstimationTable({
);
}
const PlanSummary_PlanFragment = graphql(`
fragment PlanSummary_PlanFragment on BillingPlan {
planType
retentionInDays
...PriceEstimationTable_PlanFragment
}
`);
export function PlanSummary({
plan,
operationsRateLimit,
retentionInDays,
children,
...props
}: {
plan: BillingPlansQuery['billingPlans'][number];
plan: FragmentType<typeof PlanSummary_PlanFragment>;
operationsRateLimit: number;
retentionInDays: number;
children: ReactNode;
}): ReactElement {
const plan = useFragment(PlanSummary_PlanFragment, props.plan);
if (plan.planType === BillingPlanType.Enterprise) {
return (
<Stat>
@ -104,7 +122,7 @@ export function PlanSummary({
<Stat className="mb-8">
<Stat.Label>Retention</Stat.Label>
<Stat.HelpText>usage reports</Stat.HelpText>
<Stat.Number>{retentionInDays} days</Stat.Number>
<Stat.Number>{plan.retentionInDays} days</Stat.Number>
</Stat>
</div>
<PriceEstimationTable plan={plan} operationsRateLimit={operationsRateLimit} />

View file

@ -1,12 +1,20 @@
import { ReactElement } from 'react';
import { Callout } from '@/components/v2';
import { OrgRateLimitFieldsFragment } from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
export function RateLimitWarn({
organization,
}: {
organization: OrgRateLimitFieldsFragment;
const RateLimitWarn_OrganizationFragment = graphql(`
fragment RateLimitWarn_OrganizationFragment on Organization {
name
rateLimit {
limitedForOperations
}
}
`);
export function RateLimitWarn(props: {
organization: FragmentType<typeof RateLimitWarn_OrganizationFragment>;
}): ReactElement | null {
const organization = useFragment(RateLimitWarn_OrganizationFragment, props.organization);
if (!organization.rateLimit.limitedForOperations) {
return null;
}

View file

@ -2,23 +2,25 @@ import { ReactElement } from 'react';
import { useRouter } from 'next/router';
import { clsx } from 'clsx';
import { useFormik } from 'formik';
import { DocumentType, gql, useMutation } from 'urql';
import { useMutation } from 'urql';
import { Button, Heading, Input, Modal, Tag } from '@/components/v2';
import { AlertTriangleIcon, KeyIcon } from '@/components/v2/icon';
import { InlineCode } from '@/components/v2/inline-code';
import { env } from '@/env/frontend';
import { DocumentType, FragmentType, graphql, useFragment } from '@/gql';
const classes = {
container: clsx('flex flex-col items-stretch gap-5'),
modal: clsx('w-[550px]'),
};
const OIDCIntegrationSection_OrganizationFragment = gql(/* GraphQL */ `
const OIDCIntegrationSection_OrganizationFragment = graphql(`
fragment OIDCIntegrationSection_OrganizationFragment on Organization {
id
oidcIntegration {
id
...UpdateOIDCIntegration_OIDCIntegrationFragment
authorizationEndpoint
}
}
`);
@ -120,7 +122,7 @@ export function OIDCIntegrationSection(props: {
);
}
const CreateOIDCIntegrationModal_CreateOIDCIntegrationMutation = gql(/* GraphQL */ `
const CreateOIDCIntegrationModal_CreateOIDCIntegrationMutation = graphql(`
mutation CreateOIDCIntegrationModal_CreateOIDCIntegrationMutation(
$input: CreateOIDCIntegrationInput!
) {
@ -319,10 +321,15 @@ function CreateOIDCIntegrationForm(props: {
function ManageOIDCIntegrationModal(props: {
isOpen: boolean;
close: () => void;
oidcIntegration: DocumentType<typeof UpdateOIDCIntegration_OIDCIntegrationFragment> | null;
oidcIntegration: FragmentType<typeof UpdateOIDCIntegration_OIDCIntegrationFragment> | null;
openCreateModalLink: string;
}): ReactElement {
return props.oidcIntegration === null ? (
const oidcIntegration = useFragment(
UpdateOIDCIntegration_OIDCIntegrationFragment,
props.oidcIntegration,
);
return oidcIntegration == null ? (
<Modal open={props.isOpen} onOpenChange={props.close} className={classes.modal}>
<div className={classes.container}>
<Heading>Manage OpenID Connect Integration</Heading>
@ -350,13 +357,13 @@ function ManageOIDCIntegrationModal(props: {
<UpdateOIDCIntegrationForm
close={props.close}
isOpen={props.isOpen}
key={props.oidcIntegration.id}
oidcIntegration={props.oidcIntegration}
key={oidcIntegration.id}
oidcIntegration={oidcIntegration}
/>
);
}
const UpdateOIDCIntegration_OIDCIntegrationFragment = gql(/* GraphQL */ `
const UpdateOIDCIntegration_OIDCIntegrationFragment = graphql(`
fragment UpdateOIDCIntegration_OIDCIntegrationFragment on OIDCIntegration {
id
tokenEndpoint
@ -367,7 +374,7 @@ const UpdateOIDCIntegration_OIDCIntegrationFragment = gql(/* GraphQL */ `
}
`);
const UpdateOIDCIntegrationForm_UpdateOIDCIntegrationMutation = gql(/* GraphQL */ `
const UpdateOIDCIntegrationForm_UpdateOIDCIntegrationMutation = graphql(`
mutation UpdateOIDCIntegrationForm_UpdateOIDCIntegrationMutation(
$input: UpdateOIDCIntegrationInput!
) {
@ -549,7 +556,7 @@ function UpdateOIDCIntegrationForm(props: {
);
}
const RemoveOIDCIntegrationForm_DeleteOIDCIntegrationMutation = gql(/* GraphQL */ `
const RemoveOIDCIntegrationForm_DeleteOIDCIntegrationMutation = graphql(`
mutation RemoveOIDCIntegrationForm_DeleteOIDCIntegrationMutation(
$input: DeleteOIDCIntegrationInput!
) {

View file

@ -1,12 +1,12 @@
import { useCallback, useState } from 'react';
import { useFormik } from 'formik';
import { gql, useMutation, useQuery } from 'urql';
import { useMutation, useQuery } from 'urql';
import * as Yup from 'yup';
import { Button, Card, Heading, Input, Spinner, Switch } from '@/components/v2';
import { OrganizationFieldsFragment, ProjectFieldsFragment } from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { useNotifications } from '@/lib/hooks';
export const ExternalCompositionForm_EnableMutation = gql(`
export const ExternalCompositionForm_EnableMutation = graphql(`
mutation ExternalCompositionForm_EnableMutation($input: EnableExternalSchemaCompositionInput!) {
enableExternalSchemaComposition(input: $input) {
ok {
@ -23,15 +23,31 @@ export const ExternalCompositionForm_EnableMutation = gql(`
}
`);
const ExternalCompositionForm_OrganizationFragment = graphql(`
fragment ExternalCompositionForm_OrganizationFragment on Organization {
cleanId
}
`);
const ExternalCompositionForm_ProjectFragment = graphql(`
fragment ExternalCompositionForm_ProjectFragment on Project {
cleanId
}
`);
const ExternalCompositionForm = ({
project,
organization,
endpoint,
...props
}: {
project: ProjectFieldsFragment;
organization: OrganizationFieldsFragment;
project: FragmentType<typeof ExternalCompositionForm_ProjectFragment>;
organization: FragmentType<typeof ExternalCompositionForm_OrganizationFragment>;
endpoint?: string;
}) => {
const project = useFragment(ExternalCompositionForm_ProjectFragment, props.project);
const organization = useFragment(
ExternalCompositionForm_OrganizationFragment,
props.organization,
);
const notify = useNotifications();
const [mutation, enable] = useMutation(ExternalCompositionForm_EnableMutation);
const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } =
@ -122,7 +138,7 @@ const ExternalCompositionForm = ({
);
};
export const ExternalComposition_DisableMutation = gql(`
export const ExternalComposition_DisableMutation = graphql(`
mutation ExternalComposition_DisableMutation($input: DisableExternalSchemaCompositionInput!) {
disableExternalSchemaComposition(input: $input) {
ok
@ -131,7 +147,7 @@ export const ExternalComposition_DisableMutation = gql(`
}
`);
export const ExternalComposition_ProjectConfigurationQuery = gql(`
export const ExternalComposition_ProjectConfigurationQuery = graphql(`
query ExternalComposition_ProjectConfigurationQuery($selector: ProjectSelectorInput!) {
project(selector: $selector) {
id
@ -143,13 +159,29 @@ export const ExternalComposition_ProjectConfigurationQuery = gql(`
}
`);
export const ExternalCompositionSettings = ({
project,
organization,
}: {
project: ProjectFieldsFragment;
organization: OrganizationFieldsFragment;
const ExternalCompositionSettings_OrganizationFragment = graphql(`
fragment ExternalCompositionSettings_OrganizationFragment on Organization {
cleanId
...ExternalCompositionForm_OrganizationFragment
}
`);
const ExternalCompositionSettings_ProjectFragment = graphql(`
fragment ExternalCompositionSettings_ProjectFragment on Project {
cleanId
...ExternalCompositionForm_ProjectFragment
}
`);
export const ExternalCompositionSettings = (props: {
project: FragmentType<typeof ExternalCompositionSettings_ProjectFragment>;
organization: FragmentType<typeof ExternalCompositionSettings_OrganizationFragment>;
}) => {
const project = useFragment(ExternalCompositionSettings_ProjectFragment, props.project);
const organization = useFragment(
ExternalCompositionSettings_OrganizationFragment,
props.organization,
);
const [enabled, setEnabled] = useState<boolean>();
const [mutation, disableComposition] = useMutation(ExternalComposition_DisableMutation);
const notify = useNotifications();

View file

@ -1,7 +1,8 @@
import { ReactElement, ReactNode, useCallback } from 'react';
import { gql, useMutation } from 'urql';
import { useMutation } from 'urql';
import { Button, Card, Heading, Tooltip } from '@/components/v2';
import { ProjectFieldsFragment, RegistryModel } from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { RegistryModel } from '@/graphql';
import { useNotifications } from '@/lib/hooks';
import { openChatSupport } from '@/utils';
import { CheckIcon, Cross2Icon, QuestionMarkCircledIcon } from '@radix-ui/react-icons';
@ -106,7 +107,7 @@ const available = (
const notAvailable = <Tooltip content="Not available">{crossIcon}</Tooltip>;
const ModelMigrationSettings_upgradeProjectRegistryModelMutation = gql(/* GraphQL */ `
const ModelMigrationSettings_upgradeProjectRegistryModelMutation = graphql(`
mutation ModelMigrationSettings_upgradeProjectRegistryModelMutation(
$input: UpdateProjectRegistryModelInput!
) {
@ -121,13 +122,19 @@ const ModelMigrationSettings_upgradeProjectRegistryModelMutation = gql(/* GraphQ
}
`);
export function ModelMigrationSettings({
project,
organizationId,
}: {
project: ProjectFieldsFragment;
const ModelMigrationSettings_ProjectFragment = graphql(`
fragment ModelMigrationSettings_ProjectFragment on Project {
type
cleanId
registryModel
}
`);
export function ModelMigrationSettings(props: {
project: FragmentType<typeof ModelMigrationSettings_ProjectFragment>;
organizationId: string;
}): ReactElement | null {
const project = useFragment(ModelMigrationSettings_ProjectFragment, props.project);
const isStitching = project.type === 'STITCHING';
const isComposite = isStitching || project.type === 'FEDERATION';
const notify = useNotifications();
@ -140,7 +147,7 @@ export function ModelMigrationSettings({
const result = await upgradeMutation({
input: {
project: project.cleanId,
organization: organizationId,
organization: props.organizationId,
model: RegistryModel.Modern,
},
});

View file

@ -1,9 +1,9 @@
import React, { ReactElement, ReactNode } from 'react';
import { clsx } from 'clsx';
import { DocumentType, gql } from 'urql';
import { PulseIcon } from '@/components/v2/icon';
import { Link } from '@/components/v2/link';
import { Markdown } from '@/components/v2/markdown';
import { FragmentType, graphql, useFragment } from '@/gql';
import { formatNumber, useRouteSelector } from '@/lib/hooks';
import { ChatBubbleIcon } from '@radix-ui/react-icons';
import * as P from '@radix-ui/react-popover';
@ -11,7 +11,7 @@ import { useArgumentListToggle } from './provider';
const noop = () => {};
function useCollapsibleList<T>(list: T[], max: number, defaultValue: boolean) {
function useCollapsibleList<T>(list: ReadonlyArray<T>, max: number, defaultValue: boolean) {
const [collapsed, setCollapsed] = React.useState(defaultValue === true && list.length > max);
const expand = React.useCallback(() => {
setCollapsed(false);
@ -44,7 +44,7 @@ function Description(props: { description: string }) {
);
}
const SchemaExplorerUsageStats_UsageFragment = gql(/* GraphQL */ `
const SchemaExplorerUsageStats_UsageFragment = graphql(`
fragment SchemaExplorerUsageStats_UsageFragment on SchemaCoordinateUsage {
total
isUsed
@ -52,10 +52,11 @@ const SchemaExplorerUsageStats_UsageFragment = gql(/* GraphQL */ `
`);
export function SchemaExplorerUsageStats(props: {
usage: DocumentType<typeof SchemaExplorerUsageStats_UsageFragment>;
usage: FragmentType<typeof SchemaExplorerUsageStats_UsageFragment>;
totalRequests: number;
}) {
const percentage = props.totalRequests ? (props.usage.total / props.totalRequests) * 100 : 0;
const usage = useFragment(SchemaExplorerUsageStats_UsageFragment, props.usage);
const percentage = props.totalRequests ? (usage.total / props.totalRequests) * 100 : 0;
return (
<div className="flex flex-row items-center gap-2 text-xs">
@ -63,8 +64,8 @@ export function SchemaExplorerUsageStats(props: {
<PulseIcon className="h-6 w-auto" />
</div>
<div className="grow">
<div className="text-center" title={`${props.usage.total} requests`}>
{formatNumber(props.usage.total)}
<div className="text-center" title={`${usage.total} requests`}>
{formatNumber(usage.total)}
</div>
<div
title={`${percentage.toFixed(2)}% of all requests`}
@ -78,7 +79,7 @@ export function SchemaExplorerUsageStats(props: {
);
}
const GraphQLFields_FieldFragment = gql(/* GraphQL */ `
const GraphQLFields_FieldFragment = graphql(`
fragment GraphQLFields_FieldFragment on GraphQLField {
name
description
@ -94,7 +95,7 @@ const GraphQLFields_FieldFragment = gql(/* GraphQL */ `
}
`);
const GraphQLArguments_ArgumentFragment = gql(/* GraphQL */ `
const GraphQLArguments_ArgumentFragment = graphql(`
fragment GraphQLArguments_ArgumentFragment on GraphQLArgument {
name
description
@ -107,7 +108,7 @@ const GraphQLArguments_ArgumentFragment = gql(/* GraphQL */ `
}
`);
const GraphQLInputFields_InputFieldFragment = gql(/* GraphQL */ `
const GraphQLInputFields_InputFieldFragment = graphql(`
fragment GraphQLInputFields_InputFieldFragment on GraphQLInputField {
name
description
@ -127,7 +128,7 @@ export function GraphQLTypeCard(
description?: string | null;
implements?: string[];
totalRequests?: number;
usage?: DocumentType<typeof SchemaExplorerUsageStats_UsageFragment>;
usage?: FragmentType<typeof SchemaExplorerUsageStats_UsageFragment>;
}>,
): ReactElement {
return (
@ -160,9 +161,9 @@ export function GraphQLTypeCard(
}
function GraphQLArguments(props: {
args: DocumentType<typeof GraphQLArguments_ArgumentFragment>[];
args: FragmentType<typeof GraphQLArguments_ArgumentFragment>[];
}) {
const { args } = props;
const args = useFragment(GraphQLArguments_ArgumentFragment, props.args);
const [isCollapsedGlobally] = useArgumentListToggle();
const [collapsed, setCollapsed] = React.useState(isCollapsedGlobally);
const hasMoreThanTwo = args.length > 2;
@ -177,7 +178,7 @@ function GraphQLArguments(props: {
<span className="ml-1">
<span className="text-gray-400">(</span>
<div className="pl-4">
{props.args.map(arg => {
{args.map(arg => {
return (
<div key={arg.name}>
{arg.name}
@ -197,13 +198,15 @@ function GraphQLArguments(props: {
<span className="ml-1">
<span className="text-gray-400">(</span>
<span className="space-x-2">
{props.args.slice(0, 2).map(arg => (
<span key={arg.name}>
{arg.name}
{': '}
<GraphQLTypeAsLink type={arg.type} />
</span>
))}
{args.slice(0, 2).map(arg => {
return (
<span key={arg.name}>
{arg.name}
{': '}
<GraphQLTypeAsLink type={arg.type} />
</span>
);
})}
{hasMoreThanTwo ? (
<span
className="cursor-pointer rounded bg-gray-900 p-1 text-xs text-gray-300 hover:bg-gray-700 hover:text-white"
@ -239,12 +242,16 @@ export function GraphQLTypeCardListItem(props: {
}
export function GraphQLFields(props: {
fields: DocumentType<typeof GraphQLFields_FieldFragment>[];
fields: Array<FragmentType<typeof GraphQLFields_FieldFragment>>;
totalRequests: number;
collapsed?: boolean;
}) {
const { totalRequests } = props;
const [fields, collapsed, expand] = useCollapsibleList(props.fields, 5, props.collapsed ?? false);
const [fields, collapsed, expand] = useCollapsibleList(
useFragment(GraphQLFields_FieldFragment, props.fields),
5,
props.collapsed ?? false,
);
return (
<div className="flex flex-col">
@ -274,13 +281,11 @@ export function GraphQLFields(props: {
);
}
export function GraphQLInputFields({
fields,
totalRequests,
}: {
fields: DocumentType<typeof GraphQLInputFields_InputFieldFragment>[];
export function GraphQLInputFields(props: {
fields: FragmentType<typeof GraphQLInputFields_InputFieldFragment>[];
totalRequests: number;
}): ReactElement {
const fields = useFragment(GraphQLInputFields_InputFieldFragment, props.fields);
return (
<div className="flex flex-col">
{fields.map((field, i) => {
@ -291,7 +296,7 @@ export function GraphQLInputFields({
<span className="mr-1">:</span>
<GraphQLTypeAsLink type={field.type} />
</div>
<SchemaExplorerUsageStats totalRequests={totalRequests} usage={field.usage} />
<SchemaExplorerUsageStats totalRequests={props.totalRequests} usage={field.usage} />
</GraphQLTypeCardListItem>
);
})}

View file

@ -1,7 +1,7 @@
import { DocumentType, gql } from 'urql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { GraphQLTypeCard, GraphQLTypeCardListItem, SchemaExplorerUsageStats } from './common';
export const GraphQLEnumTypeComponent_TypeFragment = gql(/* GraphQL */ `
export const GraphQLEnumTypeComponent_TypeFragment = graphql(`
fragment GraphQLEnumTypeComponent_TypeFragment on GraphQLEnumType {
name
description
@ -21,13 +21,14 @@ export const GraphQLEnumTypeComponent_TypeFragment = gql(/* GraphQL */ `
`);
export function GraphQLEnumTypeComponent(props: {
type: DocumentType<typeof GraphQLEnumTypeComponent_TypeFragment>;
type: FragmentType<typeof GraphQLEnumTypeComponent_TypeFragment>;
totalRequests: number;
}) {
const ttype = useFragment(GraphQLEnumTypeComponent_TypeFragment, props.type);
return (
<GraphQLTypeCard name={props.type.name} kind="enum" description={props.type.description}>
<GraphQLTypeCard name={ttype.name} kind="enum" description={ttype.description}>
<div className="flex flex-col">
{props.type.values.map((value, i) => (
{ttype.values.map((value, i) => (
<GraphQLTypeCardListItem index={i}>
<div>{value.name}</div>
<SchemaExplorerUsageStats totalRequests={props.totalRequests} usage={value.usage} />

View file

@ -1,10 +1,11 @@
import { useMemo } from 'react';
import { useRouter } from 'next/router';
import { gql, useQuery } from 'urql';
import { useQuery } from 'urql';
import { Autocomplete, RadixSelect, SelectOption, Switch } from '@/components/v2';
import { graphql } from '@/gql';
import { useArgumentListToggle, usePeriodSelector } from './provider';
const SchemaExplorerFilter_AllTypes = gql(/* GraphQL */ `
const SchemaExplorerFilter_AllTypes = graphql(`
query SchemaExplorerFilter_AllTypes(
$organization: ID!
$project: ID!

View file

@ -1,7 +1,7 @@
import { DocumentType, gql } from 'urql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { GraphQLInputFields, GraphQLTypeCard } from './common';
export const GraphQLInputObjectTypeComponent_TypeFragment = gql(/* GraphQL */ `
export const GraphQLInputObjectTypeComponent_TypeFragment = graphql(`
fragment GraphQLInputObjectTypeComponent_TypeFragment on GraphQLInputObjectType {
name
description
@ -15,18 +15,19 @@ export const GraphQLInputObjectTypeComponent_TypeFragment = gql(/* GraphQL */ `
`);
export function GraphQLInputObjectTypeComponent(props: {
type: DocumentType<typeof GraphQLInputObjectTypeComponent_TypeFragment>;
type: FragmentType<typeof GraphQLInputObjectTypeComponent_TypeFragment>;
totalRequests: number;
}) {
const ttype = useFragment(GraphQLInputObjectTypeComponent_TypeFragment, props.type);
return (
<GraphQLTypeCard
kind="input"
name={props.type.name}
description={props.type.description}
name={ttype.name}
description={ttype.description}
totalRequests={props.totalRequests}
usage={props.type.usage}
usage={ttype.usage}
>
<GraphQLInputFields fields={props.type.fields} totalRequests={props.totalRequests} />
<GraphQLInputFields fields={ttype.fields} totalRequests={props.totalRequests} />
</GraphQLTypeCard>
);
}

View file

@ -1,7 +1,7 @@
import { DocumentType, gql } from 'urql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { GraphQLFields, GraphQLTypeCard } from './common';
export const GraphQLInterfaceTypeComponent_TypeFragment = gql(/* GraphQL */ `
export const GraphQLInterfaceTypeComponent_TypeFragment = graphql(`
fragment GraphQLInterfaceTypeComponent_TypeFragment on GraphQLInterfaceType {
name
description
@ -16,17 +16,18 @@ export const GraphQLInterfaceTypeComponent_TypeFragment = gql(/* GraphQL */ `
`);
export function GraphQLInterfaceTypeComponent(props: {
type: DocumentType<typeof GraphQLInterfaceTypeComponent_TypeFragment>;
type: FragmentType<typeof GraphQLInterfaceTypeComponent_TypeFragment>;
totalRequests: number;
}) {
const ttype = useFragment(GraphQLInterfaceTypeComponent_TypeFragment, props.type);
return (
<GraphQLTypeCard
kind="interface"
name={props.type.name}
description={props.type.description}
implements={props.type.interfaces}
name={ttype.name}
description={ttype.description}
implements={ttype.interfaces}
>
<GraphQLFields fields={props.type.fields} totalRequests={props.totalRequests} />
<GraphQLFields fields={ttype.fields} totalRequests={props.totalRequests} />
</GraphQLTypeCard>
);
}

View file

@ -1,7 +1,7 @@
import { DocumentType, gql } from 'urql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { GraphQLFields, GraphQLTypeCard } from './common';
export const GraphQLObjectTypeComponent_TypeFragment = gql(/* GraphQL */ `
export const GraphQLObjectTypeComponent_TypeFragment = graphql(`
fragment GraphQLObjectTypeComponent_TypeFragment on GraphQLObjectType {
name
description
@ -16,19 +16,20 @@ export const GraphQLObjectTypeComponent_TypeFragment = gql(/* GraphQL */ `
`);
export function GraphQLObjectTypeComponent(props: {
type: DocumentType<typeof GraphQLObjectTypeComponent_TypeFragment>;
type: FragmentType<typeof GraphQLObjectTypeComponent_TypeFragment>;
totalRequests: number;
collapsed?: boolean;
}) {
const ttype = useFragment(GraphQLObjectTypeComponent_TypeFragment, props.type);
return (
<GraphQLTypeCard
kind="type"
name={props.type.name}
description={props.type.description}
implements={props.type.interfaces}
name={ttype.name}
description={ttype.description}
implements={ttype.interfaces}
>
<GraphQLFields
fields={props.type.fields}
fields={ttype.fields}
totalRequests={props.totalRequests}
collapsed={props.collapsed}
/>

View file

@ -1,8 +1,8 @@
import { DocumentType, gql } from 'urql';
import { Markdown } from '@/components/v2/markdown';
import { FragmentType, graphql, useFragment } from '@/gql';
import { GraphQLTypeCard, SchemaExplorerUsageStats } from './common';
export const GraphQLScalarTypeComponent_TypeFragment = gql(/* GraphQL */ `
export const GraphQLScalarTypeComponent_TypeFragment = graphql(`
fragment GraphQLScalarTypeComponent_TypeFragment on GraphQLScalarType {
name
description
@ -13,18 +13,17 @@ export const GraphQLScalarTypeComponent_TypeFragment = gql(/* GraphQL */ `
`);
export function GraphQLScalarTypeComponent(props: {
type: DocumentType<typeof GraphQLScalarTypeComponent_TypeFragment>;
type: FragmentType<typeof GraphQLScalarTypeComponent_TypeFragment>;
totalRequests: number;
}) {
const ttype = useFragment(GraphQLScalarTypeComponent_TypeFragment, props.type);
return (
<GraphQLTypeCard name={props.type.name} kind="scalar">
<GraphQLTypeCard name={ttype.name} kind="scalar">
<div className="flex flex-row gap-4 p-4">
<div className="grow text-sm">
{typeof props.type.description === 'string' ? (
<Markdown content={props.type.description} />
) : null}
{typeof ttype.description === 'string' ? <Markdown content={ttype.description} /> : null}
</div>
<SchemaExplorerUsageStats totalRequests={props.totalRequests} usage={props.type.usage} />
<SchemaExplorerUsageStats totalRequests={props.totalRequests} usage={ttype.usage} />
</div>
</GraphQLTypeCard>
);

View file

@ -1,7 +1,7 @@
import { DocumentType, gql } from 'urql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { GraphQLTypeCard, GraphQLTypeCardListItem, SchemaExplorerUsageStats } from './common';
export const GraphQLUnionTypeComponent_TypeFragment = gql(/* GraphQL */ `
export const GraphQLUnionTypeComponent_TypeFragment = graphql(`
fragment GraphQLUnionTypeComponent_TypeFragment on GraphQLUnionType {
name
description
@ -18,13 +18,14 @@ export const GraphQLUnionTypeComponent_TypeFragment = gql(/* GraphQL */ `
`);
export function GraphQLUnionTypeComponent(props: {
type: DocumentType<typeof GraphQLUnionTypeComponent_TypeFragment>;
type: FragmentType<typeof GraphQLUnionTypeComponent_TypeFragment>;
totalRequests: number;
}) {
const ttype = useFragment(GraphQLUnionTypeComponent_TypeFragment, props.type);
return (
<GraphQLTypeCard name={props.type.name} kind="union" description={props.type.description}>
<GraphQLTypeCard name={ttype.name} kind="union" description={ttype.description}>
<div className="flex flex-col">
{props.type.members.map((member, i) => (
{ttype.members.map((member, i) => (
<GraphQLTypeCardListItem index={i}>
<div>{member.name}</div>
<SchemaExplorerUsageStats totalRequests={props.totalRequests} usage={member.usage} />

View file

@ -1,11 +1,12 @@
import { ReactElement, SetStateAction, useCallback, useMemo, useState } from 'react';
import { clsx } from 'clsx';
import { gql, useQuery } from 'urql';
import clsx from 'clsx';
import { useQuery } from 'urql';
import { useDebouncedCallback } from 'use-debounce';
import { Scale, Section } from '@/components/common';
import { GraphQLHighlight } from '@/components/common/GraphQLSDLBlock';
import { Button, Drawer, Input, Table, TBody, Td, Th, THead, Tooltip, Tr } from '@/components/v2';
import { env } from '@/env/frontend';
import { graphql } from '@/gql';
import { DateRangeInput, OperationsStatsDocument, OperationStatsFieldsFragment } from '@/graphql';
import { useDecimal, useFormattedDuration, useFormattedNumber, useToggle } from '@/lib/hooks';
import { ChevronUpIcon, ExclamationTriangleIcon } from '@radix-ui/react-icons';
@ -34,7 +35,7 @@ interface Operation {
hash: string;
}
const GraphQLOperationBody_GetOperationBodyQuery = gql(/* GraphQL */ `
const GraphQLOperationBody_GetOperationBodyQuery = graphql(`
query GraphQLOperationBody_GetOperationBodyQuery($selector: OperationBodyByHashInput!) {
operationBodyByHash(selector: $selector)
}

View file

@ -1,7 +1,7 @@
import { ReactElement, useEffect, useMemo, useState } from 'react';
import { useRouter } from 'next/router';
import { useFormik } from 'formik';
import { gql, useMutation, useQuery } from 'urql';
import { useMutation, useQuery } from 'urql';
import * as Yup from 'yup';
import {
Button,
@ -18,14 +18,12 @@ import {
} from '@/components/v2';
import { AlertTriangleIcon, TrashIcon } from '@/components/v2/icon';
import { InlineCode } from '@/components/v2/inline-code';
import { FragmentType, graphql, useFragment } from '@/gql';
import { TargetAccessScope } from '@/gql/graphql';
import { MemberFieldsFragment } from '@/graphql';
import { canAccessTarget } from '@/lib/access/target';
import { useRouteSelector } from '@/lib/hooks';
// Note: this will be used in the future :)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const CDNAccessTokeRowFragment = gql(/* GraphQL */ `
const CDNAccessTokeRowFragment = graphql(`
fragment CDNAccessTokens_CdnAccessTokenRowFragment on CdnAccessToken {
id
firstCharacters
@ -35,7 +33,7 @@ const CDNAccessTokeRowFragment = gql(/* GraphQL */ `
}
`);
const CDNAccessTokenCreateMutation = gql(/* GraphQL */ `
const CDNAccessTokenCreateMutation = graphql(`
mutation CDNAccessTokens_CDNAccessTokenCreateMutation($input: CreateCdnAccessTokenInput!) {
createCdnAccessToken(input: $input) {
error {
@ -43,6 +41,7 @@ const CDNAccessTokenCreateMutation = gql(/* GraphQL */ `
}
ok {
createdCdnAccessToken {
id
...CDNAccessTokens_CdnAccessTokenRowFragment
}
secretAccessToken
@ -180,7 +179,7 @@ function CreateCDNAccessTokenModal(props: {
);
}
const CDNAccessTokenDeleteMutation = gql(/* GraphQL */ `
const CDNAccessTokenDeleteMutation = graphql(`
mutation CDNAccessTokens_DeleteCDNAccessToken($input: DeleteCdnAccessTokenInput!) {
deleteCdnAccessToken(input: $input) {
error {
@ -300,7 +299,7 @@ function DeleteCDNAccessTokenModal(props: {
);
}
const CDNAccessTokensQuery = gql(/* GraphQL */ `
const CDNAccessTokensQuery = graphql(`
query CDNAccessTokensQuery($selector: TargetSelectorInput!, $first: Int!, $after: String) {
target(selector: $selector) {
id
@ -332,7 +331,16 @@ const isDeleteCDNAccessTokenModalPath = (path: string): null | string => {
return result[1];
};
export function CDNAccessTokens(props: { me: MemberFieldsFragment }): React.ReactElement {
const CDNAccessTokens_MeFragment = graphql(`
fragment CDNAccessTokens_MeFragment on Member {
...CanAccessTarget_MemberFragment
}
`);
export function CDNAccessTokens(props: {
me: FragmentType<typeof CDNAccessTokens_MeFragment>;
}): React.ReactElement {
const me = useFragment(CDNAccessTokens_MeFragment, props.me);
const routerSelector = useRouteSelector();
const router = useRouter();
@ -365,7 +373,7 @@ export function CDNAccessTokens(props: { me: MemberFieldsFragment }): React.Reac
requestPolicy: 'cache-and-network',
});
const canManage = canAccessTarget(TargetAccessScope.Settings, props.me);
const canManage = canAccessTarget(TargetAccessScope.Settings, me);
return (
<Card>
@ -394,31 +402,36 @@ export function CDNAccessTokens(props: { me: MemberFieldsFragment }): React.Reac
)}
<Table>
<TBody>
{target?.data?.target?.cdnAccessTokens.edges?.map(edge => (
<Tr key={edge.node.id}>
<Td>
{edge.node.firstCharacters +
new Array(10).fill('•').join('') +
edge.node.lastCharacters}
</Td>
<Td>{edge.node.alias}</Td>
<Td align="right">
created <TimeAgo date={edge.node.createdAt} />
</Td>
<Td align="right">
<Button
className="hover:text-red-500"
onClick={() => {
void router.push(`${router.asPath}#delete-cdn-access-token?id=${edge.node.id}`);
}}
>
<TrashIcon />
</Button>
</Td>
</Tr>
))}
{target?.data?.target?.cdnAccessTokens.edges?.map(edge => {
const node = useFragment(CDNAccessTokeRowFragment, edge.node);
return (
<Tr key={node.id}>
<Td>
{node.firstCharacters + new Array(10).fill('•').join('') + node.lastCharacters}
</Td>
<Td>{node.alias}</Td>
<Td align="right">
created <TimeAgo date={node.createdAt} />
</Td>
<Td align="right">
<Button
className="hover:text-red-500"
onClick={() => {
void router.push(
`${router.asPath}#delete-cdn-access-token?id=${edge.node.id}`,
);
}}
>
<TrashIcon />
</Button>
</Td>
</Tr>
);
})}
</TBody>
</Table>
<div className="my-3.5 flex justify-end">
{target.data?.target?.cdnAccessTokens.pageInfo.hasPreviousPage ? (
<Button

View file

@ -1,20 +1,33 @@
import { ReactElement } from 'react';
import { PermissionsSpace, usePermissionsManager } from '@/components/organization/Permissions';
import { Accordion, Button, Heading, Modal } from '@/components/v2';
import { MemberFieldsFragment, OrganizationFieldsFragment } from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { scopes } from '@/lib/access/common';
const ChangePermissionsModal_OrganizationFragment = graphql(`
fragment ChangePermissionsModal_OrganizationFragment on Organization {
...UsePermissionManager_OrganizationFragment
}
`);
const ChangePermissionsModal_MemberFragment = graphql(`
fragment ChangePermissionsModal_MemberFragment on Member {
...UsePermissionManager_MemberFragment
}
`);
export function ChangePermissionsModal({
isOpen,
toggleModalOpen,
organization,
member,
...props
}: {
isOpen: boolean;
toggleModalOpen: () => void;
organization: OrganizationFieldsFragment;
member: MemberFieldsFragment;
organization: FragmentType<typeof ChangePermissionsModal_OrganizationFragment>;
member: FragmentType<typeof ChangePermissionsModal_MemberFragment>;
}): ReactElement {
const organization = useFragment(ChangePermissionsModal_OrganizationFragment, props.organization);
const member = useFragment(ChangePermissionsModal_MemberFragment, props.member);
const manager = usePermissionsManager({
onSuccess: toggleModalOpen,
organization,

View file

@ -1,10 +1,11 @@
import { ReactElement } from 'react';
import { gql, useQuery } from 'urql';
import { useQuery } from 'urql';
import { Button, CopyValue, Heading, Link, Modal, Tag } from '@/components/v2';
import { graphql } from '@/gql';
import { getDocsUrl } from '@/lib/docs-url';
import { useRouteSelector } from '@/lib/hooks';
const ConnectSchemaModalQuery = gql(/* GraphQL */ `
const ConnectSchemaModalQuery = graphql(`
query ConnectSchemaModal($targetSelector: TargetSelectorInput!) {
target(selector: $targetSelector) {
id

View file

@ -1,14 +1,14 @@
import { ReactElement } from 'react';
import { useFormik } from 'formik';
import { gql, useMutation, useQuery } from 'urql';
import { useMutation, useQuery } from 'urql';
import * as Yup from 'yup';
import { PermissionsSpace, usePermissionsManager } from '@/components/organization/Permissions';
import { Accordion, Button, CopyValue, Heading, Input, Modal, Tag } from '@/components/v2';
import { OrganizationDocument, OrganizationQuery } from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { scopes } from '@/lib/access/common';
import { useRouteSelector } from '@/lib/hooks';
const CreateAccessToken_CreateTokenMutation = gql(/* GraphQL */ `
const CreateAccessToken_CreateTokenMutation = graphql(`
mutation CreateAccessToken_CreateToken($input: CreateTokenInput!) {
createToken(input: $input) {
ok {
@ -29,6 +29,16 @@ const CreateAccessToken_CreateTokenMutation = gql(/* GraphQL */ `
}
`);
const CreateAccessTokenModalQuery = graphql(`
query CreateAccessTokenModalQuery($organizationId: ID!) {
organization(selector: { organization: $organizationId }) {
organization {
...CreateAccessTokenModalContent_OrganizationFragment
}
}
}
`);
export function CreateAccessTokenModal({
isOpen,
toggleModalOpen,
@ -38,11 +48,9 @@ export function CreateAccessTokenModal({
}): ReactElement {
const router = useRouteSelector();
const [organizationQuery] = useQuery({
query: OrganizationDocument,
query: CreateAccessTokenModalQuery,
variables: {
selector: {
organization: router.organizationId,
},
organizationId: router.organizationId,
},
});
@ -63,13 +71,27 @@ export function CreateAccessTokenModal({
);
}
const CreateAccessTokenModalContent_OrganizationFragment = graphql(`
fragment CreateAccessTokenModalContent_OrganizationFragment on Organization {
id
...UsePermissionManager_OrganizationFragment
me {
...UsePermissionManager_MemberFragment
}
}
`);
function ModalContent(props: {
organization: Exclude<OrganizationQuery['organization'], null | undefined>['organization'];
organization: FragmentType<typeof CreateAccessTokenModalContent_OrganizationFragment>;
organizationId: string;
projectId: string;
targetId: string;
toggleModalOpen: () => void;
}): ReactElement {
const organization = useFragment(
CreateAccessTokenModalContent_OrganizationFragment,
props.organization,
);
const [mutation, mutate] = useMutation(CreateAccessToken_CreateTokenMutation);
const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } =
@ -95,8 +117,8 @@ function ModalContent(props: {
const manager = usePermissionsManager({
onSuccess() {},
organization: props.organization,
member: props.organization.me,
organization,
member: organization.me,
passMemberScopes: false,
});

View file

@ -1,12 +1,13 @@
import { ReactElement } from 'react';
import { useFormik } from 'formik';
import { gql, useMutation } from 'urql';
import { useMutation } from 'urql';
import * as Yup from 'yup';
import { Button, Heading, Input, Modal, Select, Tag } from '@/components/v2';
import { graphql } from '@/gql';
import { AlertChannelType } from '@/graphql';
import { useRouteSelector } from '@/lib/hooks';
const CreateChannel_AddAlertChannelMutation = gql(/* GraphQL */ `
const CreateChannel_AddAlertChannelMutation = graphql(`
mutation CreateChannel_AddAlertChannel($input: AddAlertChannelInput!) {
addAlertChannel(input: $input) {
ok {

View file

@ -3,9 +3,9 @@ import { useFormik } from 'formik';
import { useMutation } from 'urql';
import * as Yup from 'yup';
import { Button, Heading, Input } from '@/components/v2';
import { gql } from '@urql/core';
import { graphql } from '@/gql';
const CreateOrganizationMutation = gql(/* GraphQL */ `
const CreateOrganizationMutation = graphql(`
mutation CreateOrganizationMutation($input: CreateOrganizationInput!) {
createOrganization(input: $input) {
ok {
@ -14,6 +14,7 @@ const CreateOrganizationMutation = gql(/* GraphQL */ `
organization
}
organization {
cleanId
...OrganizationFields
}
}

View file

@ -1,13 +1,14 @@
import { ReactElement } from 'react';
import { useRouter } from 'next/router';
import { useFormik } from 'formik';
import { gql, useMutation } from 'urql';
import { useMutation } from 'urql';
import * as Yup from 'yup';
import { Button, Heading, Input, Modal, ProjectTypes } from '@/components/v2';
import { graphql } from '@/gql';
import { ProjectType } from '@/graphql';
import { useRouteSelector } from '@/lib/hooks';
const CreateProjectMutation = gql(/* GraphQL */ `
const CreateProjectMutation = graphql(`
mutation CreateProject_CreateProject($input: CreateProjectInput!) {
createProject(input: $input) {
ok {
@ -16,6 +17,7 @@ const CreateProjectMutation = gql(/* GraphQL */ `
project
}
createdProject {
cleanId
...ProjectFields
}
createdTargets {

View file

@ -1,12 +1,13 @@
import { ReactElement } from 'react';
import { useRouter } from 'next/router';
import { useFormik } from 'formik';
import { gql, useMutation } from 'urql';
import { useMutation } from 'urql';
import * as Yup from 'yup';
import { Button, Heading, Input, Modal } from '@/components/v2';
import { graphql } from '@/gql';
import { useRouteSelector } from '@/lib/hooks';
const CreateTarget_CreateTargetMutation = gql(/* GraphQL */ `
const CreateTarget_CreateTargetMutation = graphql(`
mutation CreateTarget_CreateTarget($input: CreateTargetInput!) {
createTarget(input: $input) {
ok {
@ -16,6 +17,7 @@ const CreateTarget_CreateTargetMutation = gql(/* GraphQL */ `
target
}
createdTarget {
cleanId
...TargetFields
}
}

View file

@ -3,18 +3,29 @@ import { useRouter } from 'next/router';
import { useMutation } from 'urql';
import { Button, Heading, Modal } from '@/components/v2';
import { TrashIcon } from '@/components/v2/icon';
import { DeleteOrganizationDocument, OrganizationFieldsFragment } from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { DeleteOrganizationDocument } from '@/graphql';
import { useRouteSelector } from '@/lib/hooks';
const DeleteOrganizationModal_OrganizationFragment = graphql(`
fragment DeleteOrganizationModal_OrganizationFragment on Organization {
cleanId
}
`);
export const DeleteOrganizationModal = ({
isOpen,
toggleModalOpen,
organization,
...props
}: {
isOpen: boolean;
toggleModalOpen: () => void;
organization: OrganizationFieldsFragment;
organization: FragmentType<typeof DeleteOrganizationModal_OrganizationFragment>;
}): ReactElement => {
const organization = useFragment(
DeleteOrganizationModal_OrganizationFragment,
props.organization,
);
const [, mutate] = useMutation(DeleteOrganizationDocument);
const router = useRouteSelector();
const { replace } = useRouter();

View file

@ -1,18 +1,19 @@
import { Fragment, ReactElement, useCallback, useState } from 'react';
import clsx from 'clsx';
import { useFormik } from 'formik';
import { gql, useMutation, useQuery } from 'urql';
import { useMutation, useQuery } from 'urql';
import * as Yup from 'yup';
import { Button, Heading, Input, Modal } from '@/components/v2';
import { ArrowDownIcon, CheckIcon } from '@/components/v2/icon';
import { MemberFieldsFragment, OrganizationFieldsFragment } from '@/graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { MemberFieldsFragment } from '@/graphql';
import { useNotifications } from '@/lib/hooks';
import { Combobox as HeadlessCombobox, Transition as HeadlessTransition } from '@headlessui/react';
const Combobox = HeadlessCombobox as any;
const Transition = HeadlessTransition as any;
const TransferOrganizationOwnership_Request = gql(/* GraphQL */ `
const TransferOrganizationOwnership_Request = graphql(`
mutation TransferOrganizationOwnership_Request($input: RequestOrganizationTransferInput!) {
requestOrganizationTransfer(input: $input) {
ok {
@ -25,7 +26,7 @@ const TransferOrganizationOwnership_Request = gql(/* GraphQL */ `
}
`);
const TransferOrganizationOwnership_Members = gql(/* GraphQL */ `
const TransferOrganizationOwnership_Members = graphql(`
query TransferOrganizationOwnership_Members($selector: OrganizationSelectorInput!) {
organization(selector: $selector) {
organization {
@ -35,7 +36,14 @@ const TransferOrganizationOwnership_Members = gql(/* GraphQL */ `
type
members {
nodes {
isOwner
...MemberFields
user {
id
fullName
displayName
email
}
}
total
}
@ -46,15 +54,25 @@ const TransferOrganizationOwnership_Members = gql(/* GraphQL */ `
type Member = MemberFieldsFragment;
const TransferOrganizationOwnershipModal_OrganizationFragment = graphql(`
fragment TransferOrganizationOwnershipModal_OrganizationFragment on Organization {
cleanId
}
`);
export const TransferOrganizationOwnershipModal = ({
isOpen,
toggleModalOpen,
organization,
...props
}: {
isOpen: boolean;
toggleModalOpen: () => void;
organization: OrganizationFieldsFragment;
organization: FragmentType<typeof TransferOrganizationOwnershipModal_OrganizationFragment>;
}): ReactElement => {
const organization = useFragment(
TransferOrganizationOwnershipModal_OrganizationFragment,
props.organization,
);
const notify = useNotifications();
const [, mutate] = useMutation(TransferOrganizationOwnership_Request);
const [query] = useQuery({

View file

@ -1,12 +1,20 @@
import { MemberFieldsFragment, OrganizationAccessScope } from '../../graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { OrganizationAccessScope } from '../../graphql';
import { useRedirect } from './common';
export { OrganizationAccessScope } from '../../graphql';
const CanAccessOrganization_MemberFragment = graphql(`
fragment CanAccessOrganization_MemberFragment on Member {
organizationAccessScopes
}
`);
export function canAccessOrganization(
scope: OrganizationAccessScope,
member: Pick<MemberFieldsFragment, 'organizationAccessScopes'> | null | undefined,
mmember: null | FragmentType<typeof CanAccessOrganization_MemberFragment>,
) {
const member = useFragment(CanAccessOrganization_MemberFragment, mmember);
if (!member) {
return false;
}
@ -16,14 +24,16 @@ export function canAccessOrganization(
export function useOrganizationAccess({
scope,
member,
member: mmember,
redirect = false,
}: {
scope: OrganizationAccessScope;
member: Pick<MemberFieldsFragment, 'organizationAccessScopes'> | null | undefined;
member: null | FragmentType<typeof CanAccessOrganization_MemberFragment>;
redirect?: boolean;
}) {
const canAccess = canAccessOrganization(scope, member);
const member = useFragment(CanAccessOrganization_MemberFragment, mmember);
const canAccess = canAccessOrganization(scope, mmember);
useRedirect({
canAccess,
redirectTo: redirect

View file

@ -1,12 +1,20 @@
import { MemberFieldsFragment, ProjectAccessScope } from '../../graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { ProjectAccessScope } from '../../graphql';
import { useRedirect } from './common';
export { ProjectAccessScope } from '../../graphql';
const CanAccessProject_MemberFragment = graphql(`
fragment CanAccessProject_MemberFragment on Member {
projectAccessScopes
}
`);
export function canAccessProject(
scope: ProjectAccessScope,
member: Pick<MemberFieldsFragment, 'projectAccessScopes'> | null | undefined,
mmember: null | FragmentType<typeof CanAccessProject_MemberFragment>,
) {
const member = useFragment(CanAccessProject_MemberFragment, mmember);
if (!member) {
return false;
}
@ -16,14 +24,16 @@ export function canAccessProject(
export function useProjectAccess({
scope,
member,
member: mmember,
redirect = false,
}: {
scope: ProjectAccessScope;
member: Pick<MemberFieldsFragment, 'projectAccessScopes'> | null | undefined;
member: null | FragmentType<typeof CanAccessProject_MemberFragment>;
redirect?: boolean;
}) {
const canAccess = canAccessProject(scope, member);
const member = useFragment(CanAccessProject_MemberFragment, mmember);
const canAccess = canAccessProject(scope, mmember);
useRedirect({
canAccess,
redirectTo: redirect

View file

@ -1,12 +1,21 @@
import { MemberFieldsFragment, TargetAccessScope } from '../../graphql';
import { FragmentType, graphql, useFragment } from '@/gql';
import { TargetAccessScope } from '../../graphql';
import { useRedirect } from './common';
export { TargetAccessScope } from '../../graphql';
const CanAccessTarget_MemberFragment = graphql(`
fragment CanAccessTarget_MemberFragment on Member {
targetAccessScopes
}
`);
export function canAccessTarget(
scope: TargetAccessScope,
member: Pick<MemberFieldsFragment, 'targetAccessScopes'> | null | undefined,
mmember: null | FragmentType<typeof CanAccessTarget_MemberFragment>,
) {
const member = useFragment(CanAccessTarget_MemberFragment, mmember);
if (!member) {
return false;
}
@ -16,14 +25,15 @@ export function canAccessTarget(
export function useTargetAccess({
scope,
member,
member: mmember,
redirect = false,
}: {
scope: TargetAccessScope;
member: Pick<MemberFieldsFragment, 'targetAccessScopes'> | null | undefined;
member: null | FragmentType<typeof CanAccessTarget_MemberFragment>;
redirect?: boolean;
}) {
const canAccess = canAccessTarget(scope, member);
const member = useFragment(CanAccessTarget_MemberFragment, mmember);
const canAccess = canAccessTarget(scope, mmember);
useRedirect({
canAccess,
redirectTo: redirect

View file

@ -8,11 +8,6 @@ import {
} from '@/components/project/settings/external-composition';
import { ResultOf, VariablesOf } from '@graphql-typed-document-node/core';
import { Cache, QueryInput, UpdateResolver } from '@urql/exchange-graphcache';
import {
InvitationDeleteButton_DeleteInvitation,
MemberInvitationForm_InviteByEmail,
Members_OrganizationMembers,
} from '../../pages/[orgId]/view/members';
import {
AddAlertChannelDocument,
AddAlertDocument,
@ -361,72 +356,6 @@ const deleteGitHubIntegration: TypedDocumentNodeUpdateResolver<
);
};
const inviteToOrganizationByEmail: TypedDocumentNodeUpdateResolver<
typeof MemberInvitationForm_InviteByEmail
> = ({ inviteToOrganizationByEmail }, args, cache) => {
if (inviteToOrganizationByEmail.ok) {
cache.updateQuery(
{
query: Members_OrganizationMembers,
variables: {
selector: {
organization: args.input.organization,
},
},
},
data => {
if (data === null) {
return null;
}
const invitation = inviteToOrganizationByEmail.ok;
if (invitation) {
data.organization?.organization?.invitations.nodes.push({
...invitation,
__typename: 'OrganizationInvitation',
});
}
return data;
},
);
}
};
const deleteOrganizationInvitation: TypedDocumentNodeUpdateResolver<
typeof InvitationDeleteButton_DeleteInvitation
> = ({ deleteOrganizationInvitation }, args, cache) => {
if (deleteOrganizationInvitation.ok) {
cache.updateQuery(
{
query: Members_OrganizationMembers,
variables: {
selector: {
organization: args.input.organization,
},
},
},
data => {
if (data === null) {
return null;
}
const invitation = deleteOrganizationInvitation.ok;
if (invitation && data.organization?.organization?.invitations.nodes) {
data.organization.organization.invitations.nodes =
data.organization.organization.invitations.nodes.filter(
node => node.id !== invitation.id,
);
}
return data;
},
);
}
};
const enableExternalSchemaComposition: TypedDocumentNodeUpdateResolver<
typeof ExternalCompositionForm_EnableMutation
> = ({ enableExternalSchemaComposition }, args, cache) => {
@ -509,8 +438,6 @@ export const Mutation = {
deleteAlertChannels,
addAlert,
deletePersistedOperation,
inviteToOrganizationByEmail,
deleteOrganizationInvitation,
enableExternalSchemaComposition,
disableExternalSchemaComposition,
};

View file

@ -5,21 +5,24 @@ overrides:
tsup: 6.5.0
patchedDependencies:
'@tgriesser/schemats@7.0.1':
hash: u3kbucfchakklx3sci2vh6wjau
path: patches/@tgriesser__schemats@7.0.1.patch
slonik@30.1.2:
hash: wg2hxbo7txnklmvja4aeqnygfi
path: patches/slonik@30.1.2.patch
'@oclif/core@1.23.0':
hash: zhte2hlj7lfobytjpalcwwo474
path: patches/@oclif__core@1.23.0.patch
'@theguild/buddy@0.1.0':
hash: ryylgra5xglhidfoiaxehn22hq
path: patches/@theguild__buddy@0.1.0.patch
slonik@30.1.2:
hash: wg2hxbo7txnklmvja4aeqnygfi
path: patches/slonik@30.1.2.patch
'@tgriesser/schemats@7.0.1':
hash: u3kbucfchakklx3sci2vh6wjau
path: patches/@tgriesser__schemats@7.0.1.patch
'@oclif/core@1.23.0':
hash: zhte2hlj7lfobytjpalcwwo474
path: patches/@oclif__core@1.23.0.patch
bullmq@3.7.2:
hash: xbmkiyyfti7h6orsfs6pdmi4s4
path: patches/bullmq@3.7.2.patch
mjml-core@4.13.0:
hash: zxxsxbqejjmcwuzpigutzzq6wa
path: patches/mjml-core@4.13.0.patch
oclif@3.7.0:
hash: rxmtqiusuyruv7tkwux5gvotbm
path: patches/oclif@3.7.0.patch
@ -29,15 +32,12 @@ patchedDependencies:
'@graphql-inspector/core@4.0.0':
hash: wf35oaq7brzyeva5aoncxac66a
path: patches/@graphql-inspector__core@4.0.0.patch
'@apollo/federation@0.38.1':
hash: rjgakkkphrejw6qrtph4ar24zq
path: patches/@apollo__federation@0.38.1.patch
mjml-core@4.13.0:
hash: zxxsxbqejjmcwuzpigutzzq6wa
path: patches/mjml-core@4.13.0.patch
'@octokit/webhooks-methods@3.0.1':
hash: ckboo4crezw7uvstxm24ozngtq
path: patches/@octokit__webhooks-methods@3.0.1.patch
'@apollo/federation@0.38.1':
hash: rjgakkkphrejw6qrtph4ar24zq
path: patches/@apollo__federation@0.38.1.patch
importers:
@ -48,7 +48,6 @@ importers:
'@graphql-codegen/add': 4.0.1
'@graphql-codegen/cli': 3.2.1
'@graphql-codegen/client-preset': 2.1.0
'@graphql-codegen/gql-tag-operations-preset': 2.1.0
'@graphql-codegen/graphql-modules-preset': 3.1.0
'@graphql-codegen/typed-document-node': 3.0.1
'@graphql-codegen/typescript': 3.0.1
@ -89,7 +88,6 @@ importers:
'@graphql-codegen/add': 4.0.1_graphql@16.6.0
'@graphql-codegen/cli': 3.2.1_3duxb27ep3oxt5ktrrpxolj5jm
'@graphql-codegen/client-preset': 2.1.0_graphql@16.6.0
'@graphql-codegen/gql-tag-operations-preset': 2.1.0_graphql@16.6.0
'@graphql-codegen/graphql-modules-preset': 3.1.0_graphql@16.6.0
'@graphql-codegen/typed-document-node': 3.0.1_graphql@16.6.0
'@graphql-codegen/typescript': 3.0.1_graphql@16.6.0
@ -3075,9 +3073,9 @@ packages:
'@babel/helper-annotate-as-pure': 7.18.6
'@babel/helper-environment-visitor': 7.18.9
'@babel/helper-function-name': 7.19.0
'@babel/helper-member-expression-to-functions': 7.18.9
'@babel/helper-member-expression-to-functions': 7.20.7
'@babel/helper-optimise-call-expression': 7.18.6
'@babel/helper-replace-supers': 7.19.1
'@babel/helper-replace-supers': 7.20.7
'@babel/helper-split-export-declaration': 7.18.6
transitivePeerDependencies:
- supports-color
@ -3100,13 +3098,6 @@ packages:
dependencies:
'@babel/types': 7.20.7
/@babel/helper-member-expression-to-functions/7.18.9:
resolution: {integrity: sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.20.7
dev: true
/@babel/helper-member-expression-to-functions/7.20.7:
resolution: {integrity: sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==}
engines: {node: '>=6.9.0'}
@ -3147,19 +3138,6 @@ packages:
resolution: {integrity: sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==}
engines: {node: '>=6.9.0'}
/@babel/helper-replace-supers/7.19.1:
resolution: {integrity: sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-environment-visitor': 7.18.9
'@babel/helper-member-expression-to-functions': 7.20.7
'@babel/helper-optimise-call-expression': 7.18.6
'@babel/traverse': 7.20.13
'@babel/types': 7.20.7
transitivePeerDependencies:
- supports-color
dev: true
/@babel/helper-replace-supers/7.20.7:
resolution: {integrity: sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==}
engines: {node: '>=6.9.0'}
@ -6544,10 +6522,6 @@ packages:
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
dev: true
/@iarna/toml/2.2.5:
resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==}
dev: false
/@ioredis/as-callback/3.0.0:
resolution: {integrity: sha512-Kqv1rZ3WbgOrS+hgzJ5xG5WQuhvzzSTRYvNeyPMLOAM78MHSnuKI20JeJGbpuAt//LCuP0vsexZcorqW7kWhJg==}
dev: true
@ -9565,13 +9539,12 @@ packages:
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
transitivePeerDependencies:
- '@swc/core'
- '@swc/wasm'
- '@types/node'
- bufferutil
- cosmiconfig-toml-loader
- cosmiconfig-typescript-loader
- encoding
- graphql
- typescript
- utf-8-validate
dev: false
@ -12264,25 +12237,6 @@ packages:
vary: 1.1.2
dev: false
/cosmiconfig-toml-loader/1.0.0:
resolution: {integrity: sha512-H/2gurFWVi7xXvCyvsWRLCMekl4tITJcX0QEsDMpzxtuxDyM59xLatYNg4s/k9AA/HdtCYfj2su8mgA0GSDLDA==}
dependencies:
'@iarna/toml': 2.2.5
dev: false
/cosmiconfig-typescript-loader/4.3.0_hguxidgmm6hcsx2ec4lzz7uqxu:
resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==}
engines: {node: '>=12', npm: '>=6'}
peerDependencies:
'@types/node': '*'
cosmiconfig: '>=7'
ts-node: '>=10'
typescript: '>=3'
dependencies:
cosmiconfig: 7.0.1
ts-node: 10.9.1
dev: false
/cosmiconfig-typescript-loader/4.3.0_tynoeqfilgwodzrdv3jpzpskvq:
resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==}
engines: {node: '>=12', npm: '>=6'}
@ -12316,7 +12270,6 @@ packages:
js-yaml: 4.1.0
parse-json: 5.2.0
path-type: 4.0.0
dev: true
/cpu-features/0.0.4:
resolution: {integrity: sha512-fKiZ/zp1mUwQbnzb9IghXtHtDoTMtNeb8oYGx6kX2SYfhnG0HNdBEBIzB9b5KlXu5DQPhfy3mInbBxFcgwAr3A==}
@ -15291,33 +15244,34 @@ packages:
- react-is
dev: false
/graphql-config/4.3.6_graphql@16.6.0:
resolution: {integrity: sha512-i7mAPwc0LAZPnYu2bI8B6yXU5820Wy/ArvmOseDLZIu0OU1UTULEuexHo6ZcHXeT9NvGGaUPQZm8NV3z79YydA==}
/graphql-config/4.4.1_graphql@16.6.0:
resolution: {integrity: sha512-B8wlvfBHZ5WnI4IiuQZRqql6s+CKz7S+xpUeTb28Z8nRBi8tH9ChEBgT5FnTyE05PUhHlrS2jK9ICJ4YBl9OtQ==}
engines: {node: '>= 10.0.0'}
peerDependencies:
cosmiconfig-toml-loader: ^1.0.0
cosmiconfig-typescript-loader: ^4.0.0
graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
peerDependenciesMeta:
cosmiconfig-toml-loader:
optional: true
cosmiconfig-typescript-loader:
optional: true
dependencies:
'@graphql-tools/graphql-file-loader': 7.5.5_graphql@16.6.0
'@graphql-tools/json-file-loader': 7.4.6_graphql@16.6.0
'@graphql-tools/load': 7.8.0_graphql@16.6.0
'@graphql-tools/merge': 8.3.18_graphql@16.6.0
'@graphql-tools/url-loader': 7.17.1_graphql@16.6.0
'@graphql-tools/utils': 8.13.1_graphql@16.6.0
cosmiconfig: 7.0.1
cosmiconfig-toml-loader: 1.0.0
cosmiconfig-typescript-loader: 4.3.0_hguxidgmm6hcsx2ec4lzz7uqxu
'@graphql-tools/utils': 9.2.1_graphql@16.6.0
cosmiconfig: 8.0.0
graphql: 16.6.0
minimatch: 4.2.1
string-env-interpolation: 1.0.1
ts-node: 10.9.1
tslib: 2.5.0
transitivePeerDependencies:
- '@swc/core'
- '@swc/wasm'
- '@types/node'
- bufferutil
- encoding
- typescript
- utf-8-validate
dev: false
@ -15359,18 +15313,17 @@ packages:
graphql: ^15.5.0 || ^16.0.0
dependencies:
graphql: 16.6.0
graphql-config: 4.3.6_graphql@16.6.0
graphql-config: 4.4.1_graphql@16.6.0
graphql-language-service-parser: 1.10.4_graphql@16.6.0
graphql-language-service-types: 1.8.7_graphql@16.6.0
graphql-language-service-utils: 2.7.1_graphql@16.6.0
vscode-languageserver-types: 3.17.2
transitivePeerDependencies:
- '@swc/core'
- '@swc/wasm'
- '@types/node'
- bufferutil
- cosmiconfig-toml-loader
- cosmiconfig-typescript-loader
- encoding
- typescript
- utf-8-validate
dev: false
@ -15382,12 +15335,11 @@ packages:
graphql: 16.6.0
graphql-language-service-types: 1.8.7_graphql@16.6.0
transitivePeerDependencies:
- '@swc/core'
- '@swc/wasm'
- '@types/node'
- bufferutil
- cosmiconfig-toml-loader
- cosmiconfig-typescript-loader
- encoding
- typescript
- utf-8-validate
dev: false
@ -15397,15 +15349,14 @@ packages:
graphql: ^15.5.0 || ^16.0.0
dependencies:
graphql: 16.6.0
graphql-config: 4.3.6_graphql@16.6.0
graphql-config: 4.4.1_graphql@16.6.0
vscode-languageserver-types: 3.17.2
transitivePeerDependencies:
- '@swc/core'
- '@swc/wasm'
- '@types/node'
- bufferutil
- cosmiconfig-toml-loader
- cosmiconfig-typescript-loader
- encoding
- typescript
- utf-8-validate
dev: false
@ -15419,12 +15370,11 @@ packages:
graphql-language-service-types: 1.8.7_graphql@16.6.0
nullthrows: 1.1.1
transitivePeerDependencies:
- '@swc/core'
- '@swc/wasm'
- '@types/node'
- bufferutil
- cosmiconfig-toml-loader
- cosmiconfig-typescript-loader
- encoding
- typescript
- utf-8-validate
dev: false
@ -15440,12 +15390,11 @@ packages:
graphql-language-service-types: 1.8.7_graphql@16.6.0
graphql-language-service-utils: 2.7.1_graphql@16.6.0
transitivePeerDependencies:
- '@swc/core'
- '@swc/wasm'
- '@types/node'
- bufferutil
- cosmiconfig-toml-loader
- cosmiconfig-typescript-loader
- encoding
- typescript
- utf-8-validate
dev: false