mirror of
https://github.com/graphql-hive/console
synced 2026-04-21 14:37:17 +00:00
replace tag-operations with client preset (#1441)
This commit is contained in:
parent
1c7d6d9a1a
commit
1e2ab08f95
66 changed files with 1733 additions and 1083 deletions
|
|
@ -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,
|
||||
|
|
|
|||
2
.github/workflows/lint.yaml
vendored
2
.github/workflows/lint.yaml
vendored
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
10
codegen.cjs
10
codegen.cjs
|
|
@ -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: [],
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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={
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -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 => (
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
135
pnpm-lock.yaml
135
pnpm-lock.yaml
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue