mirror of
https://github.com/graphql-hive/console
synced 2026-04-21 14:37:17 +00:00
Make Member.id unique (#4794)
This commit is contained in:
parent
c4dcb47534
commit
7c476f941d
71 changed files with 158 additions and 378 deletions
|
|
@ -78,14 +78,9 @@ module.exports = {
|
|||
},
|
||||
{
|
||||
files: ['packages/web/app/**/*.graphql'],
|
||||
plugins: ['@graphql-eslint', 'hive'],
|
||||
plugins: ['@graphql-eslint'],
|
||||
rules: {
|
||||
'@graphql-eslint/require-id-when-available': 'error',
|
||||
// Require temporaryFixId field when available.
|
||||
// We need it to be able to cache Member objects in the frontend properly.
|
||||
// Member.id is not unique, so we need to use Member.temporaryFixId instead.
|
||||
// Once we have a better solution, we can remove this rule.
|
||||
'hive/graphql-require-selection': ['error'],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -194,6 +194,13 @@ const config: CodegenConfig = {
|
|||
documents: ['./integration-tests/(testkit|tests)/**/*.ts'],
|
||||
preset: 'client',
|
||||
plugins: [],
|
||||
config: {
|
||||
scalars: {
|
||||
DateTime: 'string',
|
||||
Date: 'string',
|
||||
SafeInt: 'number',
|
||||
},
|
||||
},
|
||||
},
|
||||
'./schema.graphql': {
|
||||
plugins: ['schema-ast'],
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
"private": true,
|
||||
"scripts": {
|
||||
"prepare:env": "cd ../ && pnpm build:libraries && pnpm build:services",
|
||||
"test:integration": "vitest"
|
||||
"test:integration": "vitest",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apollo/gateway": "2.7.8",
|
||||
|
|
|
|||
|
|
@ -173,6 +173,9 @@ export function joinOrganization(code: string, authToken: string) {
|
|||
cleanId
|
||||
me {
|
||||
id
|
||||
user {
|
||||
id
|
||||
}
|
||||
organizationAccessScopes
|
||||
projectAccessScopes
|
||||
targetAccessScopes
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectType, RegistryModel, TargetAccessScope } from '@app/gql/graphql';
|
||||
import { ProjectType, RegistryModel, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { initSeed } from './seed';
|
||||
|
||||
export async function prepareProject(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { RuleInstanceSeverityLevel, SchemaPolicyInput } from '@app/gql/graphql';
|
||||
import { RuleInstanceSeverityLevel, SchemaPolicyInput } from 'testkit/gql/graphql';
|
||||
import { graphql } from './gql';
|
||||
|
||||
export const TargetCalculatedPolicy = graphql(`
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { UUID } from 'node:crypto';
|
||||
import { humanId } from 'human-id';
|
||||
import { createPool, sql } from 'slonik';
|
||||
import {
|
||||
|
|
@ -7,7 +8,7 @@ import {
|
|||
RegistryModel,
|
||||
SchemaPolicyInput,
|
||||
TargetAccessScope,
|
||||
} from '@app/gql/graphql';
|
||||
} from 'testkit/gql/graphql';
|
||||
import { authenticate, userEmail } from './auth';
|
||||
import {
|
||||
CreateCollectionMutation,
|
||||
|
|
@ -720,7 +721,7 @@ export function initSeed() {
|
|||
async assignMemberRole(
|
||||
input: {
|
||||
roleId: string;
|
||||
memberId: string;
|
||||
userId: string;
|
||||
},
|
||||
options: { useMemberToken?: boolean } = {
|
||||
useMemberToken: false,
|
||||
|
|
@ -729,7 +730,7 @@ export function initSeed() {
|
|||
const memberRoleAssignmentResult = await assignMemberRole(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
member: input.memberId,
|
||||
user: input.userId,
|
||||
role: input.roleId,
|
||||
},
|
||||
options.useMemberToken ? memberToken : ownerToken,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import crypto from 'node:crypto';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { ApolloGateway } from '@apollo/gateway';
|
||||
import { ApolloServer } from '@apollo/server';
|
||||
import { startStandaloneServer } from '@apollo/server/standalone';
|
||||
import { ProjectType, TargetAccessScope } from '@app/gql/graphql';
|
||||
import {
|
||||
DeleteObjectsCommand,
|
||||
GetObjectCommand,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
|
||||
import { ProjectType, TargetAccessScope } from '@app/gql/graphql';
|
||||
import { ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
||||
describe('Document Collections', () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { formatISO } from 'date-fns/formatISO';
|
||||
import { subHours } from 'date-fns/subHours';
|
||||
import { ProjectType } from '@app/gql/graphql';
|
||||
import { ProjectType } from 'testkit/gql/graphql';
|
||||
import { waitFor } from '../../testkit/flow';
|
||||
import { initSeed } from '../../testkit/seed';
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {
|
|||
ProjectAccessScope,
|
||||
ProjectType,
|
||||
TargetAccessScope,
|
||||
} from '@app/gql/graphql';
|
||||
} from 'testkit/gql/graphql';
|
||||
import { waitFor } from '../../../testkit/flow';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import { OrganizationAccessScope, ProjectAccessScope, TargetAccessScope } from '@app/gql/graphql';
|
||||
import {
|
||||
OrganizationAccessScope,
|
||||
ProjectAccessScope,
|
||||
TargetAccessScope,
|
||||
} from 'testkit/gql/graphql';
|
||||
import { history } from '../../../testkit/emails';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
||||
|
|
@ -56,7 +60,7 @@ test.concurrent(
|
|||
});
|
||||
await assignMemberRole({
|
||||
roleId: membersManagerRole.id,
|
||||
memberId: member.id,
|
||||
userId: member.user.id,
|
||||
});
|
||||
|
||||
await expect(
|
||||
|
|
@ -91,7 +95,7 @@ test.concurrent(
|
|||
});
|
||||
await assignMemberRole({
|
||||
roleId: membersManagerRole.id,
|
||||
memberId: member.id,
|
||||
userId: member.user.id,
|
||||
});
|
||||
|
||||
const adminRoleId = organization.memberRoles.find(r => r.name === 'Admin')?.id;
|
||||
|
|
@ -104,7 +108,7 @@ test.concurrent(
|
|||
assignMemberRole(
|
||||
{
|
||||
roleId: adminRoleId,
|
||||
memberId: viewerRoleMember.id,
|
||||
userId: viewerRoleMember.user.id,
|
||||
},
|
||||
{
|
||||
useMemberToken: true,
|
||||
|
|
@ -166,11 +170,11 @@ test.concurrent('cannot downgrade a member when assigning a new role', async ()
|
|||
});
|
||||
await assignMemberRole({
|
||||
roleId: managerRole.id,
|
||||
memberId: member.id,
|
||||
userId: member.user.id,
|
||||
});
|
||||
await assignMemberRole({
|
||||
roleId: originalRole.id,
|
||||
memberId: viewerRoleMember.id,
|
||||
userId: viewerRoleMember.user.id,
|
||||
});
|
||||
|
||||
// non-admin member cannot downgrade another member
|
||||
|
|
@ -178,7 +182,7 @@ test.concurrent('cannot downgrade a member when assigning a new role', async ()
|
|||
assignMemberRole(
|
||||
{
|
||||
roleId: roleWithLessAccess.id,
|
||||
memberId: viewerRoleMember.id,
|
||||
userId: viewerRoleMember.user.id,
|
||||
},
|
||||
{
|
||||
useMemberToken: true,
|
||||
|
|
@ -188,7 +192,7 @@ test.concurrent('cannot downgrade a member when assigning a new role', async ()
|
|||
// admin can downgrade another member
|
||||
await assignMemberRole({
|
||||
roleId: roleWithLessAccess.id,
|
||||
memberId: viewerRoleMember.id,
|
||||
userId: viewerRoleMember.user.id,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -219,11 +223,11 @@ test.concurrent('cannot downgrade a member when modifying a role', async () => {
|
|||
});
|
||||
await assignMemberRole({
|
||||
roleId: managerRole.id,
|
||||
memberId: member.id,
|
||||
userId: member.user.id,
|
||||
});
|
||||
await assignMemberRole({
|
||||
roleId: roleToBeUpdated.id,
|
||||
memberId: viewerRoleMember.id,
|
||||
userId: viewerRoleMember.user.id,
|
||||
});
|
||||
|
||||
// non-admin member cannot downgrade another member
|
||||
|
|
@ -267,11 +271,11 @@ test.concurrent('cannot delete a role with members', async () => {
|
|||
});
|
||||
await assignMemberRole({
|
||||
roleId: membersManagerRole.id,
|
||||
memberId: member.id,
|
||||
userId: member.user.id,
|
||||
});
|
||||
await assignMemberRole({
|
||||
roleId: readOnlyRole.id,
|
||||
memberId: viewerRoleMember.id,
|
||||
userId: viewerRoleMember.user.id,
|
||||
});
|
||||
|
||||
// delete the role as the owner
|
||||
|
|
@ -316,7 +320,7 @@ test.concurrent('cannot invite a member with more access than the inviter', asyn
|
|||
// give the inviting member a role with enough access to invite other members
|
||||
await assignMemberRole({
|
||||
roleId: membersManagerRole.id,
|
||||
memberId: invitingMember.id,
|
||||
userId: invitingMember.user.id,
|
||||
});
|
||||
|
||||
const inviteEmail = seed.generateEmail();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import { OrganizationAccessScope, ProjectAccessScope, TargetAccessScope } from '@app/gql/graphql';
|
||||
import {
|
||||
OrganizationAccessScope,
|
||||
ProjectAccessScope,
|
||||
TargetAccessScope,
|
||||
} from 'testkit/gql/graphql';
|
||||
import {
|
||||
answerOrganizationTransferRequest,
|
||||
getOrganizationTransferRequest,
|
||||
|
|
@ -32,7 +36,7 @@ test.concurrent('owner should be able to request the ownership transfer to a mem
|
|||
const transferRequestResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.id,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
|
@ -49,7 +53,7 @@ test.concurrent('non-owner should not be able to request the ownership transfer'
|
|||
const errors = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: orgMembers.find(u => u.user.email === ownerEmail)!.id,
|
||||
user: orgMembers.find(u => u.user.email === ownerEmail)!.user.id,
|
||||
},
|
||||
memberToken,
|
||||
).then(r => r.expectGraphQLErrors());
|
||||
|
|
@ -68,7 +72,7 @@ test.concurrent(
|
|||
const transferRequestResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.id,
|
||||
user: member.user.id,
|
||||
},
|
||||
memberToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
|
@ -85,7 +89,7 @@ test.concurrent('non-member should not be able to access the transfer request',
|
|||
const requestTransferResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.id,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
|
@ -119,7 +123,7 @@ test.concurrent('non-recipient should not be able to access the transfer request
|
|||
const requestTransferResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.id,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
|
@ -148,7 +152,7 @@ test.concurrent('recipient should be able to access the transfer request', async
|
|||
const requestTransferResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.id,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
|
@ -178,7 +182,7 @@ test.concurrent('recipient should be able to answer the ownership transfer', asy
|
|||
const requestTransferResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.id,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
|
@ -210,7 +214,7 @@ test.concurrent('non-member should not be able to answer the ownership transfer'
|
|||
const requestTransferResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.id,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
|
@ -241,7 +245,7 @@ test.concurrent('owner should not be able to answer the ownership transfer', asy
|
|||
const requestTransferResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.id,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
|
@ -272,7 +276,7 @@ test.concurrent('non-member should not be able to answer the ownership transfer'
|
|||
const requestTransferResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.id,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
|
@ -306,7 +310,7 @@ test.concurrent(
|
|||
const requestTransferResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.id,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
|
@ -355,7 +359,7 @@ test.concurrent(
|
|||
const requestTransferResult = await requestOrganizationTransfer(
|
||||
{
|
||||
organization: organization.cleanId,
|
||||
user: member.id,
|
||||
user: member.user.id,
|
||||
},
|
||||
ownerToken,
|
||||
).then(r => r.expectNoGraphQLErrors());
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectType } from '@app/gql/graphql';
|
||||
import { ProjectType } from 'testkit/gql/graphql';
|
||||
import { graphql } from '../../../testkit/gql';
|
||||
import { execute } from '../../../testkit/graphql';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
|
@ -32,7 +32,7 @@ describe('Policy Access', () => {
|
|||
const { member, memberToken, assignMemberRole } = await inviteAndJoinMember();
|
||||
await assignMemberRole({
|
||||
roleId: adminRole.id,
|
||||
memberId: member.id,
|
||||
userId: member.user.id,
|
||||
});
|
||||
|
||||
const result = await execute({
|
||||
|
|
@ -100,7 +100,7 @@ describe('Policy Access', () => {
|
|||
const { member, memberToken, assignMemberRole } = await inviteAndJoinMember();
|
||||
await assignMemberRole({
|
||||
roleId: adminRole.id,
|
||||
memberId: member.id,
|
||||
userId: member.user.id,
|
||||
});
|
||||
|
||||
const result = await execute({
|
||||
|
|
@ -166,7 +166,7 @@ describe('Policy Access', () => {
|
|||
const { member, memberToken, assignMemberRole } = await inviteAndJoinMember();
|
||||
await assignMemberRole({
|
||||
roleId: adminRole.id,
|
||||
memberId: member.id,
|
||||
userId: member.user.id,
|
||||
});
|
||||
|
||||
const result = await execute({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import stripAnsi from 'strip-ansi';
|
||||
import { ProjectType, RuleInstanceSeverityLevel, SchemaPolicyInput } from 'testkit/gql/graphql';
|
||||
import { prepareProject } from 'testkit/registry-models';
|
||||
import { ProjectType, RuleInstanceSeverityLevel, SchemaPolicyInput } from '@app/gql/graphql';
|
||||
import { createCLI } from '../../../testkit/cli';
|
||||
|
||||
export const createPolicy = (level: RuleInstanceSeverityLevel): SchemaPolicyInput => ({
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectType } from '@app/gql/graphql';
|
||||
import { ProjectType } from 'testkit/gql/graphql';
|
||||
import { execute } from '../../../testkit/graphql';
|
||||
import {
|
||||
DESCRIPTION_RULE,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectType } from '@app/gql/graphql';
|
||||
import { ProjectType } from 'testkit/gql/graphql';
|
||||
import { renameProject } from '../../../testkit/flow';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {
|
|||
ProjectAccessScope,
|
||||
ProjectType,
|
||||
TargetAccessScope,
|
||||
} from '@app/gql/graphql';
|
||||
} from 'testkit/gql/graphql';
|
||||
import * as emails from '../../../testkit/emails';
|
||||
import { updateOrgRateLimit, waitFor } from '../../../testkit/flow';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectType, RuleInstanceSeverityLevel, TargetAccessScope } from '@app/gql/graphql';
|
||||
import { ProjectType, RuleInstanceSeverityLevel, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { graphql } from '../../../testkit/gql';
|
||||
import { execute } from '../../../testkit/graphql';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectType, TargetAccessScope } from '@app/gql/graphql';
|
||||
import { ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
||||
describe.each`
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectAccessScope, ProjectType, TargetAccessScope } from '@app/gql/graphql';
|
||||
import { ProjectAccessScope, ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { enableExternalSchemaComposition } from '../../../testkit/flow';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
import { generateUnique } from '../../../testkit/utils';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectType, TargetAccessScope } from '@app/gql/graphql';
|
||||
import { ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { graphql } from '../../../testkit/gql';
|
||||
import { execute } from '../../../testkit/graphql';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectType, TargetAccessScope } from '@app/gql/graphql';
|
||||
import { ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { GetObjectCommand, NoSuchKey, S3Client } from '@aws-sdk/client-s3';
|
||||
import { graphql } from '../../../testkit/gql';
|
||||
import { execute } from '../../../testkit/graphql';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import 'reflect-metadata';
|
||||
import { parse, print } from 'graphql';
|
||||
import { enableExternalSchemaComposition } from 'testkit/flow';
|
||||
import { ProjectAccessScope, ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { initSeed } from 'testkit/seed';
|
||||
import { ProjectAccessScope, ProjectType, TargetAccessScope } from '@app/gql/graphql';
|
||||
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
||||
import { createStorage } from '@hive/storage';
|
||||
import { sortSDL } from '@theguild/federation-composition';
|
||||
|
|
@ -131,7 +131,7 @@ test.concurrent(
|
|||
test.concurrent(
|
||||
'the changes and schema sdl is persisted in the database when the super schema schema is composable',
|
||||
async ({ expect }) => {
|
||||
let storage: Awaited<ReturnType<typeof createStorage>>;
|
||||
let storage: Awaited<ReturnType<typeof createStorage>> | undefined = undefined;
|
||||
|
||||
try {
|
||||
storage = await createStorage(connectionString(), 1);
|
||||
|
|
@ -228,7 +228,7 @@ test.concurrent(
|
|||
test.concurrent(
|
||||
'composition error is persisted in the database when the super schema schema is not composable',
|
||||
async ({ expect }) => {
|
||||
let storage: Awaited<ReturnType<typeof createStorage>>;
|
||||
let storage: Awaited<ReturnType<typeof createStorage>> | undefined = undefined;
|
||||
|
||||
try {
|
||||
storage = await createStorage(connectionString(), 1);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectAccessScope, ProjectType, TargetAccessScope } from '@app/gql/graphql';
|
||||
import { ProjectAccessScope, ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { history, serviceName, servicePort } from '../../../testkit/external-composition';
|
||||
import { enableExternalSchemaComposition } from '../../../testkit/flow';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import 'reflect-metadata';
|
||||
import { createPool, sql } from 'slonik';
|
||||
import { graphql } from 'testkit/gql';
|
||||
import { execute } from 'testkit/graphql';
|
||||
/* eslint-disable no-process-env */
|
||||
import { ProjectAccessScope, ProjectType, TargetAccessScope } from '@app/gql/graphql';
|
||||
import { ProjectAccessScope, ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { execute } from 'testkit/graphql';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { createStorage } from '@hive/storage';
|
||||
import {
|
||||
|
|
@ -691,6 +691,10 @@ describe('schema publishing changes are persisted', () => {
|
|||
versionId: latestVersion.id,
|
||||
});
|
||||
|
||||
if (!Array.isArray(changes)) {
|
||||
throw new Error('Expected changes to be an array');
|
||||
}
|
||||
|
||||
expect(changes[0]['meta']).toEqual(args.equalsObject['meta']);
|
||||
expect(changes[0]['type']).toEqual(args.equalsObject['type']);
|
||||
});
|
||||
|
|
@ -2778,6 +2782,7 @@ test('Target.schemaVersion: result is read from the database', async () => {
|
|||
const latestVersion = await storage.getLatestVersion({
|
||||
target: target.id,
|
||||
project: project.id,
|
||||
organization: organization.id,
|
||||
});
|
||||
|
||||
const result = await execute({
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { graphql } from 'testkit/gql';
|
||||
import { ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { execute } from 'testkit/graphql';
|
||||
import { initSeed } from 'testkit/seed';
|
||||
import { graphql } from '@app/gql';
|
||||
import { ProjectType, TargetAccessScope } from '@app/gql/graphql';
|
||||
|
||||
const CreateContractMutation = graphql(`
|
||||
mutation CreateContractMutation($input: CreateContractInput!) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectType } from '@app/gql/graphql';
|
||||
import { ProjectType } from 'testkit/gql/graphql';
|
||||
import { renameTarget } from '../../../testkit/flow';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectType, TargetAccessScope } from '@app/gql/graphql';
|
||||
import { ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { initSeed } from '../../../testkit/seed';
|
||||
|
||||
test.concurrent(
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@ import { formatISO } from 'date-fns/formatISO';
|
|||
import { subHours } from 'date-fns/subHours';
|
||||
import { buildASTSchema, parse, print } from 'graphql';
|
||||
import { createLogger } from 'graphql-yoga';
|
||||
import { execute } from 'testkit/graphql';
|
||||
import { getServiceHost } from 'testkit/utils';
|
||||
import { graphql } from '@app/gql';
|
||||
import { graphql } from 'testkit/gql';
|
||||
import {
|
||||
OrganizationAccessScope,
|
||||
ProjectAccessScope,
|
||||
ProjectType,
|
||||
TargetAccessScope,
|
||||
} from '@app/gql/graphql';
|
||||
} from 'testkit/gql/graphql';
|
||||
import { execute } from 'testkit/graphql';
|
||||
import { getServiceHost } from 'testkit/utils';
|
||||
// eslint-disable-next-line hive/enforce-deps-in-dev
|
||||
import { normalizeOperation } from '@graphql-hive/core';
|
||||
import { createHive } from '../../../../packages/libraries/core/src';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { readTokenInfo, waitFor } from 'testkit/flow';
|
||||
import { ProjectType, TargetAccessScope } from '@app/gql/graphql';
|
||||
import { ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { initSeed } from '../../testkit/seed';
|
||||
|
||||
test.concurrent('deleting a token should clear the cache', async () => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { randomUUID } from 'node:crypto';
|
|||
import { readFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { ProjectType } from '@app/gql/graphql';
|
||||
import { ProjectType } from 'testkit/gql/graphql';
|
||||
import { createCLI } from '../../testkit/cli';
|
||||
import { initSeed } from '../../testkit/seed';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable no-process-env */
|
||||
import { createHash } from 'node:crypto';
|
||||
import { ProjectType } from '@app/gql/graphql';
|
||||
import { ProjectType } from 'testkit/gql/graphql';
|
||||
import { createCLI, schemaCheck, schemaPublish } from '../../testkit/cli';
|
||||
import { initSeed } from '../../testkit/seed';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectType, RegistryModel } from '@app/gql/graphql';
|
||||
import { ProjectType, RegistryModel } from 'testkit/gql/graphql';
|
||||
import { createCLI, schemaPublish } from '../../testkit/cli';
|
||||
import { prepareProject } from '../../testkit/registry-models';
|
||||
import { initSeed } from '../../testkit/seed';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectType, TargetAccessScope } from '@app/gql/graphql';
|
||||
import { ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { normalizeCliOutput } from '../../../scripts/serializers/cli-output';
|
||||
import { createCLI, schemaPublish } from '../../testkit/cli';
|
||||
import { prepareProject } from '../../testkit/registry-models';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectType, RegistryModel, TargetAccessScope } from '@app/gql/graphql';
|
||||
import { ProjectType, RegistryModel, TargetAccessScope } from 'testkit/gql/graphql';
|
||||
import { createCLI } from '../../testkit/cli';
|
||||
import { prepareProject } from '../../testkit/registry-models';
|
||||
import { initSeed } from '../../testkit/seed';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectType } from '@app/gql/graphql';
|
||||
import { ProjectType } from 'testkit/gql/graphql';
|
||||
import { normalizeCliOutput } from '../../../scripts/serializers/cli-output';
|
||||
import { createCLI } from '../../testkit/cli';
|
||||
import { prepareProject } from '../../testkit/registry-models';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectType, RegistryModel } from '@app/gql/graphql';
|
||||
import { ProjectType, RegistryModel } from 'testkit/gql/graphql';
|
||||
import { createCLI } from '../../testkit/cli';
|
||||
import { prepareProject } from '../../testkit/registry-models';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ProjectType } from '@app/gql/graphql';
|
||||
import { ProjectType } from 'testkit/gql/graphql';
|
||||
import { normalizeCliOutput } from '../../../scripts/serializers/cli-output';
|
||||
import { createCLI } from '../../testkit/cli';
|
||||
import { prepareProject } from '../../testkit/registry-models';
|
||||
|
|
|
|||
|
|
@ -1,18 +1,7 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "..",
|
||||
"baseUrl": ".",
|
||||
"esModuleInterop": true,
|
||||
"paths": {
|
||||
"@hive/service-common": ["../packages/services/service-common/src/index.ts"],
|
||||
"@hive/server": ["../packages/services/server/src/api.ts"],
|
||||
"@hive/storage": ["../packages/services/storage/src/index.ts"],
|
||||
"@hive/rate-limit": ["../packages/services/rate-limit/src/api.ts"],
|
||||
"@app/gql/graphql": ["./testkit/gql/graphql.ts"],
|
||||
"@app/gql": ["./testkit/gql/index.ts"],
|
||||
"@hive/schema": ["../packages/services/schema/src/api.ts"]
|
||||
}
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["testkit", "tests", "./expect.ts"]
|
||||
"include": ["./testkit", "./tests", "./expect.ts"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ export default defineConfig({
|
|||
test: {
|
||||
globals: true,
|
||||
alias: {
|
||||
'@app/gql/graphql': new URL('./testkit/gql/graphql.ts', import.meta.url).pathname,
|
||||
'@app/gql': new URL('./testkit/gql/index.ts', import.meta.url).pathname,
|
||||
'testkit/gql/graphql': new URL('./testkit/gql/graphql.ts', import.meta.url).pathname,
|
||||
'testkit/gql': new URL('./testkit/gql/index.ts', import.meta.url).pathname,
|
||||
'@hive/service-common': new URL(
|
||||
'../packages/services/service-common/src/index.ts',
|
||||
import.meta.url,
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export const version = '0.32.0';
|
||||
export const version = '0.33.0';
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export const version = '0.2.4';
|
||||
export const version = '0.3.0';
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export const version = '0.32.0';
|
||||
export const version = '0.33.0';
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export const version = '0.32.0';
|
||||
export const version = '0.33.0';
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ export default gql`
|
|||
|
||||
type Member {
|
||||
id: ID!
|
||||
temporaryFixId: ID!
|
||||
user: User!
|
||||
isOwner: Boolean!
|
||||
organizationAccessScopes: [OrganizationAccessScope!]!
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import Dataloader from 'dataloader';
|
||||
import DataLoader from 'dataloader';
|
||||
import { forwardRef, Inject, Injectable, Scope } from 'graphql-modules';
|
||||
import { Token } from '../../../shared/entities';
|
||||
|
|
@ -45,8 +44,8 @@ export function isOrganizationScope(scope: any): scope is OrganizationAccessScop
|
|||
})
|
||||
export class OrganizationAccess {
|
||||
private logger: Logger;
|
||||
private userAccess: Dataloader<OrganizationUserAccessSelector, boolean, string>;
|
||||
private tokenAccess: Dataloader<OrganizationTokenAccessSelector, boolean, string>;
|
||||
private userAccess: DataLoader<OrganizationUserAccessSelector, boolean, string>;
|
||||
private tokenAccess: DataLoader<OrganizationTokenAccessSelector, boolean, string>;
|
||||
private allScopes: DataLoader<
|
||||
OrganizationUserScopesSelector,
|
||||
ReadonlyArray<OrganizationAccessScope | ProjectAccessScope | TargetAccessScope>,
|
||||
|
|
@ -74,7 +73,7 @@ export class OrganizationAccess {
|
|||
this.logger = logger.child({
|
||||
source: 'OrganizationAccess',
|
||||
});
|
||||
this.userAccess = new Dataloader(
|
||||
this.userAccess = new DataLoader(
|
||||
async selectors => {
|
||||
const scopes = await this.scopes.loadMany(selectors);
|
||||
|
||||
|
|
@ -104,7 +103,7 @@ export class OrganizationAccess {
|
|||
},
|
||||
},
|
||||
);
|
||||
this.tokenAccess = new Dataloader(
|
||||
this.tokenAccess = new DataLoader(
|
||||
selectors =>
|
||||
Promise.all(
|
||||
selectors.map(async selector => {
|
||||
|
|
@ -128,7 +127,7 @@ export class OrganizationAccess {
|
|||
},
|
||||
},
|
||||
);
|
||||
this.allScopes = new Dataloader(
|
||||
this.allScopes = new DataLoader(
|
||||
async selectors => {
|
||||
const scopesPerSelector = await this.storage.getOrganizationMemberAccessPairs(selectors);
|
||||
|
||||
|
|
@ -144,7 +143,7 @@ export class OrganizationAccess {
|
|||
},
|
||||
},
|
||||
);
|
||||
this.scopes = new Dataloader(
|
||||
this.scopes = new DataLoader(
|
||||
async selectors => {
|
||||
const scopesPerSelector = await this.allScopes.loadMany(selectors);
|
||||
|
||||
|
|
@ -174,7 +173,7 @@ export class OrganizationAccess {
|
|||
},
|
||||
);
|
||||
|
||||
this.ownership = new Dataloader(
|
||||
this.ownership = new DataLoader(
|
||||
async selectors => {
|
||||
const ownerPerSelector = await Promise.all(
|
||||
selectors.map(selector => this.storage.getOrganizationOwnerId(selector)),
|
||||
|
|
@ -192,7 +191,7 @@ export class OrganizationAccess {
|
|||
},
|
||||
);
|
||||
|
||||
this.tokenInfo = new Dataloader(
|
||||
this.tokenInfo = new DataLoader(
|
||||
selectors => Promise.all(selectors.map(selector => this.tokenStorage.getToken(selector))),
|
||||
{
|
||||
cacheKeyFn(selector) {
|
||||
|
|
|
|||
|
|
@ -78,24 +78,21 @@ export const resolvers: AuthModule.Resolvers & {
|
|||
TOKENS_WRITE: TargetAccessScope.TOKENS_WRITE,
|
||||
},
|
||||
Member: {
|
||||
temporaryFixId(member) {
|
||||
return `${member.organization}:${member.id}`;
|
||||
},
|
||||
organizationAccessScopes(member, _, { injector }) {
|
||||
return injector.get(AuthManager).getMemberOrganizationScopes({
|
||||
user: member.id,
|
||||
user: member.user.id,
|
||||
organization: member.organization,
|
||||
});
|
||||
},
|
||||
projectAccessScopes(member, _, { injector }) {
|
||||
return injector.get(AuthManager).getMemberProjectScopes({
|
||||
user: member.id,
|
||||
user: member.user.id,
|
||||
organization: member.organization,
|
||||
});
|
||||
},
|
||||
targetAccessScopes(member, _, { injector }) {
|
||||
return injector.get(AuthManager).getMemberTargetScopes({
|
||||
user: member.id,
|
||||
user: member.user.id,
|
||||
organization: member.organization,
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -404,7 +404,7 @@ export default gql`
|
|||
|
||||
input AssignMemberRoleInput {
|
||||
organization: ID!
|
||||
member: ID!
|
||||
user: ID!
|
||||
role: ID!
|
||||
}
|
||||
|
||||
|
|
@ -444,7 +444,7 @@ export default gql`
|
|||
input AssignMemberRoleMigrationInput {
|
||||
organization: ID!
|
||||
role: ID!
|
||||
members: [ID!]!
|
||||
users: [ID!]!
|
||||
}
|
||||
|
||||
input CreateMemberRoleMigrationInput {
|
||||
|
|
@ -454,7 +454,7 @@ export default gql`
|
|||
organizationScopes: [OrganizationAccessScope!]!
|
||||
projectScopes: [ProjectAccessScope!]!
|
||||
targetScopes: [TargetAccessScope!]!
|
||||
members: [ID!]!
|
||||
users: [ID!]!
|
||||
}
|
||||
|
||||
type MigrateUnassignedMembersResult {
|
||||
|
|
|
|||
|
|
@ -642,7 +642,7 @@ export class OrganizationManager {
|
|||
|
||||
const { code } = await this.storage.createOrganizationTransferRequest({
|
||||
organization: organization.id,
|
||||
user: member.id,
|
||||
user: member.user.id,
|
||||
});
|
||||
|
||||
await this.emails.schedule({
|
||||
|
|
@ -985,7 +985,7 @@ export class OrganizationManager {
|
|||
};
|
||||
}
|
||||
|
||||
async assignMemberRole(input: { organizationId: string; memberId: string; roleId: string }) {
|
||||
async assignMemberRole(input: { organizationId: string; userId: string; roleId: string }) {
|
||||
await this.authManager.ensureOrganizationAccess({
|
||||
organization: input.organizationId,
|
||||
scope: OrganizationAccessScope.MEMBERS,
|
||||
|
|
@ -994,7 +994,7 @@ export class OrganizationManager {
|
|||
// Ensure selected member is part of the organization
|
||||
const member = await this.storage.getOrganizationMember({
|
||||
organization: input.organizationId,
|
||||
user: input.memberId,
|
||||
user: input.userId,
|
||||
});
|
||||
|
||||
if (!member) {
|
||||
|
|
@ -1073,7 +1073,7 @@ export class OrganizationManager {
|
|||
// Assign the role to the member
|
||||
await this.storage.assignOrganizationMemberRole({
|
||||
organizationId: input.organizationId,
|
||||
userId: input.memberId,
|
||||
userId: input.userId,
|
||||
roleId: input.roleId,
|
||||
});
|
||||
|
||||
|
|
@ -1084,7 +1084,7 @@ export class OrganizationManager {
|
|||
ok: {
|
||||
updatedMember: await this.getOrganizationMember({
|
||||
organization: input.organizationId,
|
||||
user: input.memberId,
|
||||
user: input.userId,
|
||||
}),
|
||||
previousMemberRole: member.role,
|
||||
},
|
||||
|
|
@ -1409,7 +1409,7 @@ export class OrganizationManager {
|
|||
organizationId: string;
|
||||
assignRole?: {
|
||||
role: string;
|
||||
members: readonly string[];
|
||||
users: readonly string[];
|
||||
} | null;
|
||||
createRole?: {
|
||||
name: string;
|
||||
|
|
@ -1417,7 +1417,7 @@ export class OrganizationManager {
|
|||
organizationScopes: readonly OrganizationAccessScope[];
|
||||
projectScopes: readonly ProjectAccessScope[];
|
||||
targetScopes: readonly TargetAccessScope[];
|
||||
members: readonly string[];
|
||||
users: readonly string[];
|
||||
} | null;
|
||||
}) {
|
||||
const currentUser = await this.authManager.getCurrentUser();
|
||||
|
|
@ -1438,7 +1438,7 @@ export class OrganizationManager {
|
|||
return this.assignRoleToMembersMigration({
|
||||
organizationId,
|
||||
roleId: assignRole.role,
|
||||
members: assignRole.members,
|
||||
users: assignRole.users,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1459,7 +1459,7 @@ export class OrganizationManager {
|
|||
organizationScopes: readonly OrganizationAccessScope[];
|
||||
projectScopes: readonly ProjectAccessScope[];
|
||||
targetScopes: readonly TargetAccessScope[];
|
||||
members: readonly string[];
|
||||
users: readonly string[];
|
||||
}) {
|
||||
const result = await this.createMemberRole({
|
||||
organizationId: input.organizationId,
|
||||
|
|
@ -1474,7 +1474,7 @@ export class OrganizationManager {
|
|||
return this.assignRoleToMembersMigration({
|
||||
roleId: result.ok.createdRole.id,
|
||||
organizationId: input.organizationId,
|
||||
members: input.members,
|
||||
users: input.users,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1484,12 +1484,12 @@ export class OrganizationManager {
|
|||
private async assignRoleToMembersMigration(input: {
|
||||
organizationId: string;
|
||||
roleId: string;
|
||||
members: readonly string[];
|
||||
users: readonly string[];
|
||||
}) {
|
||||
await this.storage.assignOrganizationMemberRoleToMany({
|
||||
organizationId: input.organizationId,
|
||||
roleId: input.roleId,
|
||||
userIds: input.members,
|
||||
userIds: input.users,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -448,7 +448,7 @@ export const resolvers: OrganizationModule.Resolvers = {
|
|||
|
||||
return injector.get(OrganizationManager).assignMemberRole({
|
||||
organizationId,
|
||||
memberId: input.member,
|
||||
userId: input.user,
|
||||
roleId: input.role,
|
||||
});
|
||||
},
|
||||
|
|
@ -591,7 +591,7 @@ export const resolvers: OrganizationModule.Resolvers = {
|
|||
async canLeaveOrganization(member, _, { injector }) {
|
||||
const { result } = await injector.get(OrganizationManager).canLeaveOrganization({
|
||||
organizationId: member.organization,
|
||||
userId: member.id,
|
||||
userId: member.user.id,
|
||||
});
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -810,7 +810,7 @@ function createSchemaChangeId(change: { type: string; meta: Record<string, unkno
|
|||
}
|
||||
|
||||
const ApprovalMetadataModel = z.object({
|
||||
userId: z.string(),
|
||||
userId: z.string().uuid(),
|
||||
schemaCheckId: z.string(),
|
||||
date: z.string(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -169,7 +169,6 @@ const AdminStatsQuery = graphql(`
|
|||
name
|
||||
owner {
|
||||
id
|
||||
temporaryFixId
|
||||
user {
|
||||
id
|
||||
email
|
||||
|
|
@ -178,7 +177,6 @@ const AdminStatsQuery = graphql(`
|
|||
members {
|
||||
nodes {
|
||||
id
|
||||
temporaryFixId
|
||||
user {
|
||||
id
|
||||
email
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ const ProjectLayoutQuery = graphql(`
|
|||
name
|
||||
me {
|
||||
id
|
||||
temporaryFixId
|
||||
...CanAccessProject_MemberFragment
|
||||
}
|
||||
projects {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ const TargetLayoutQuery = graphql(`
|
|||
name
|
||||
me {
|
||||
id
|
||||
temporaryFixId
|
||||
...CanAccessTarget_MemberFragment
|
||||
}
|
||||
projects {
|
||||
|
|
|
|||
|
|
@ -264,7 +264,9 @@ const UsePermissionManager_OrganizationFragment = graphql(`
|
|||
const UsePermissionManager_MemberFragment = graphql(`
|
||||
fragment UsePermissionManager_MemberFragment on Member {
|
||||
id
|
||||
temporaryFixId
|
||||
user {
|
||||
id
|
||||
}
|
||||
targetAccessScopes
|
||||
projectAccessScopes
|
||||
organizationAccessScopes
|
||||
|
|
@ -312,7 +314,7 @@ export function usePermissionsManager({
|
|||
const result = await mutate({
|
||||
input: {
|
||||
organization: organization.cleanId,
|
||||
user: member.id,
|
||||
user: member.user.id,
|
||||
targetScopes,
|
||||
projectScopes,
|
||||
organizationScopes,
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ const OrganizationMemberRoleSwitcher_AssignRoleMutation = graphql(`
|
|||
ok {
|
||||
updatedMember {
|
||||
id
|
||||
temporaryFixId
|
||||
user {
|
||||
id
|
||||
displayName
|
||||
|
|
@ -66,7 +65,6 @@ const OrganizationMemberRoleSwitcher_OrganizationFragment = graphql(`
|
|||
cleanId
|
||||
me {
|
||||
id
|
||||
temporaryFixId
|
||||
isAdmin
|
||||
organizationAccessScopes
|
||||
projectAccessScopes
|
||||
|
|
@ -74,7 +72,6 @@ const OrganizationMemberRoleSwitcher_OrganizationFragment = graphql(`
|
|||
}
|
||||
owner {
|
||||
id
|
||||
temporaryFixId
|
||||
}
|
||||
memberRoles {
|
||||
id
|
||||
|
|
@ -98,10 +95,12 @@ const OrganizationMemberRoleSwitcher_OrganizationFragment = graphql(`
|
|||
const OrganizationMemberRoleSwitcher_MemberFragment = graphql(`
|
||||
fragment OrganizationMemberRoleSwitcher_MemberFragment on Member {
|
||||
id
|
||||
temporaryFixId
|
||||
organizationAccessScopes
|
||||
projectAccessScopes
|
||||
targetAccessScopes
|
||||
user {
|
||||
id
|
||||
}
|
||||
...ChangePermissionsModal_MemberFragment
|
||||
}
|
||||
`);
|
||||
|
|
@ -131,7 +130,7 @@ function OrganizationMemberRoleSwitcher(props: {
|
|||
const [isPermissionsModalOpen, togglePermissionsModalOpen] = useToggle(false);
|
||||
const memberRole = roles.find(role => role.id === props.memberRoleId);
|
||||
|
||||
if (!memberRole && !member) {
|
||||
if (!memberRole || !member) {
|
||||
console.error('No role or member provided to OrganizationMemberRoleSwitcher');
|
||||
return null;
|
||||
}
|
||||
|
|
@ -150,7 +149,7 @@ function OrganizationMemberRoleSwitcher(props: {
|
|||
input: {
|
||||
organization: organization.cleanId,
|
||||
role: role.id,
|
||||
member: props.memberId,
|
||||
user: member.user.id,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -321,7 +320,6 @@ const OrganizationMemberRow_DeleteMember = graphql(`
|
|||
const OrganizationMemberRow_MemberFragment = graphql(`
|
||||
fragment OrganizationMemberRow_MemberFragment on Member {
|
||||
id
|
||||
temporaryFixId
|
||||
user {
|
||||
id
|
||||
provider
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ function SimilarRoles(props: {
|
|||
|
||||
const migrationFormSchema = z.intersection(
|
||||
z.object({
|
||||
members: z.array(z.string()).min(1),
|
||||
users: z.array(z.string()).min(1),
|
||||
}),
|
||||
z.union([
|
||||
roleFormSchema,
|
||||
|
|
@ -363,7 +363,9 @@ const OrganizationMemberRolesMigrationGroup_Migrate = graphql(`
|
|||
members {
|
||||
nodes {
|
||||
id
|
||||
temporaryFixId
|
||||
user {
|
||||
id
|
||||
}
|
||||
organizationAccessScopes
|
||||
projectAccessScopes
|
||||
targetAccessScopes
|
||||
|
|
@ -413,7 +415,7 @@ function OrganizationMemberRolesMigrationGroup(props: {
|
|||
mode: 'onChange',
|
||||
resolver: zodResolver(migrationFormSchema),
|
||||
defaultValues: {
|
||||
members: memberGroup.members.map(m => m.id),
|
||||
users: memberGroup.members.map(m => m.user.id),
|
||||
roleId: '',
|
||||
name: '',
|
||||
description: '',
|
||||
|
|
@ -458,7 +460,7 @@ function OrganizationMemberRolesMigrationGroup(props: {
|
|||
assignRole: {
|
||||
organization: props.organizationCleanId,
|
||||
role: data.roleId,
|
||||
members: data.members,
|
||||
users: data.users,
|
||||
},
|
||||
}
|
||||
: {
|
||||
|
|
@ -476,7 +478,7 @@ function OrganizationMemberRolesMigrationGroup(props: {
|
|||
targetScopes: data.targetScopes.filter((s): s is TargetAccessScope =>
|
||||
Object.values(TargetAccessScope).includes(s as TargetAccessScope),
|
||||
),
|
||||
members: data.members,
|
||||
users: data.users,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const ChangePermissionsModal_OrganizationFragment = graphql(`
|
|||
|
||||
export const ChangePermissionsModal_MemberFragment = graphql(`
|
||||
fragment ChangePermissionsModal_MemberFragment on Member {
|
||||
temporaryFixId
|
||||
id
|
||||
...UsePermissionManager_MemberFragment
|
||||
}
|
||||
`);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ const TransferOrganizationOwnership_Members = graphql(`
|
|||
name
|
||||
members {
|
||||
nodes {
|
||||
temporaryFixId
|
||||
id
|
||||
isOwner
|
||||
...MemberFields
|
||||
user {
|
||||
|
|
@ -54,7 +54,6 @@ const TransferOrganizationOwnership_Members = graphql(`
|
|||
const MemberFields = graphql(`
|
||||
fragment MemberFields on Member {
|
||||
id
|
||||
temporaryFixId
|
||||
user {
|
||||
id
|
||||
fullName
|
||||
|
|
@ -159,7 +158,7 @@ export const TransferOrganizationOwnershipModal = ({
|
|||
const onSelect = useCallback(
|
||||
(member: Member) => {
|
||||
setSelected(member);
|
||||
void setFieldValue('newOwner', member.id, true);
|
||||
void setFieldValue('newOwner', member.user.id, true);
|
||||
},
|
||||
[setSelected, setFieldValue],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ export { OrganizationAccessScope };
|
|||
const CanAccessOrganization_MemberFragment = graphql(`
|
||||
fragment CanAccessOrganization_MemberFragment on Member {
|
||||
id
|
||||
temporaryFixId
|
||||
organizationAccessScopes
|
||||
}
|
||||
`);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ export { TargetAccessScope };
|
|||
export const CanAccessTarget_MemberFragment = graphql(`
|
||||
fragment CanAccessTarget_MemberFragment on Member {
|
||||
id
|
||||
temporaryFixId
|
||||
targetAccessScopes
|
||||
}
|
||||
`);
|
||||
|
|
|
|||
|
|
@ -35,10 +35,6 @@ export const urqlClient = createClient({
|
|||
Mutation,
|
||||
},
|
||||
keys: {
|
||||
// Member.id is not globally unique, it's really User.id
|
||||
// In order to avoid conflicts or cache issues, let's use Member.temporaryFixId.
|
||||
// This is a temporary solution until we have a better way to handle this.
|
||||
Member: ({ temporaryFixId }) => `Member:${temporaryFixId}`,
|
||||
RequestsOverTime: noKey,
|
||||
FailuresOverTime: noKey,
|
||||
DurationOverTime: noKey,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const OrganizationPolicyPageQuery = graphql(`
|
|||
organization {
|
||||
id
|
||||
me {
|
||||
temporaryFixId
|
||||
id
|
||||
...CanAccessOrganization_MemberFragment
|
||||
}
|
||||
projects {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ const OrganizationTransferPage_GetRequest = graphql(`
|
|||
name
|
||||
owner {
|
||||
id
|
||||
temporaryFixId
|
||||
user {
|
||||
id
|
||||
displayName
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ const ProjectPolicyPageQuery = graphql(`
|
|||
id
|
||||
me {
|
||||
id
|
||||
temporaryFixId
|
||||
...CanAccessProject_MemberFragment
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -827,7 +827,6 @@ const TargetLaboratoryPageQuery = graphql(`
|
|||
id
|
||||
me {
|
||||
id
|
||||
temporaryFixId
|
||||
...CanAccessTarget_MemberFragment
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1033,7 +1033,6 @@ const TargetSettingsPageQuery = graphql(`
|
|||
cleanId
|
||||
...TargetSettingsPage_OrganizationFragment
|
||||
me {
|
||||
temporaryFixId
|
||||
...CDNAccessTokens_MeFragment
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,211 +0,0 @@
|
|||
/// @ts-check
|
||||
const {
|
||||
requireGraphQLSchemaFromContext,
|
||||
requireSiblingsOperations,
|
||||
} = require('@graphql-eslint/eslint-plugin');
|
||||
|
||||
const {
|
||||
Kind,
|
||||
TypeInfo,
|
||||
visit,
|
||||
visitWithTypeInfo,
|
||||
GraphQLObjectType,
|
||||
GraphQLInterfaceType,
|
||||
GraphQLUnionType,
|
||||
isListType,
|
||||
isNonNullType,
|
||||
} = require('graphql');
|
||||
|
||||
function getBaseType(type) {
|
||||
if (isNonNullType(type) || isListType(type)) {
|
||||
return getBaseType(type.ofType);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
const RULE_ID = 'graphql-require-selections';
|
||||
const idNames = ['temporaryFixId'];
|
||||
|
||||
/// Ported https://github.com/dimaMachina/graphql-eslint/blob/3c1020888472eb6579ffddc1e8e5ec16df8fad74/packages/plugin/src/rules/require-selections.ts
|
||||
|
||||
/**
|
||||
* @type {import('@graphql-eslint/eslint-plugin').GraphQLESLintRule}
|
||||
*/
|
||||
const rule = {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
hasSuggestions: true,
|
||||
messages: {
|
||||
[RULE_ID]:
|
||||
"Field{{ pluralSuffix }} {{ fieldName }} must be selected when it's available on a type.\nInclude it in your selection set{{ addition }}.",
|
||||
},
|
||||
docs: {
|
||||
category: 'Operations',
|
||||
description: 'Enforce selecting specific fields when they are available on the GraphQL type.',
|
||||
requiresSchema: true,
|
||||
requiresSiblings: true,
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
|
||||
const siblings = requireSiblingsOperations(RULE_ID, context);
|
||||
|
||||
// Check selections only in OperationDefinition,
|
||||
// skip selections of OperationDefinition and InlineFragment
|
||||
const selector =
|
||||
'OperationDefinition SelectionSet[parent.kind!=/(^OperationDefinition|InlineFragment)$/]';
|
||||
const typeInfo = new TypeInfo(schema);
|
||||
|
||||
function checkFragments(node) {
|
||||
for (const selection of node.selections) {
|
||||
if (selection.kind !== Kind.FRAGMENT_SPREAD) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const [foundSpread] = siblings.getFragment(selection.name.value);
|
||||
if (!foundSpread) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const checkedFragmentSpreads = new Set();
|
||||
const visitor = visitWithTypeInfo(typeInfo, {
|
||||
SelectionSet(node, key, _parent) {
|
||||
const parent = _parent;
|
||||
if (parent.kind === Kind.FRAGMENT_DEFINITION) {
|
||||
checkedFragmentSpreads.add(parent.name.value);
|
||||
} else if (parent.kind !== Kind.INLINE_FRAGMENT) {
|
||||
checkSelections(
|
||||
node,
|
||||
typeInfo.getType(),
|
||||
selection.loc.start,
|
||||
parent,
|
||||
checkedFragmentSpreads,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
visit(foundSpread.document, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
function checkSelections(
|
||||
node,
|
||||
type,
|
||||
// Fragment can be placed in separate file
|
||||
// Provide actual fragment spread location instead of location in fragment
|
||||
loc,
|
||||
// Can't access to node.parent in GraphQL AST.Node, so pass as argument
|
||||
parent,
|
||||
checkedFragmentSpreads = new Set(),
|
||||
) {
|
||||
const rawType = getBaseType(type);
|
||||
|
||||
if (rawType instanceof GraphQLObjectType || rawType instanceof GraphQLInterfaceType) {
|
||||
checkFields(rawType);
|
||||
} else if (rawType instanceof GraphQLUnionType) {
|
||||
for (const selection of node.selections) {
|
||||
if (selection.kind === Kind.INLINE_FRAGMENT) {
|
||||
const types = rawType.getTypes();
|
||||
const t = types.find(t => t.name === selection.typeCondition.name.value);
|
||||
if (t) {
|
||||
checkFields(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkFields(rawType) {
|
||||
const fields = rawType.getFields();
|
||||
const hasIdFieldInType = idNames.some(name => fields[name]);
|
||||
|
||||
if (!hasIdFieldInType) {
|
||||
return;
|
||||
}
|
||||
|
||||
function hasIdField({ selections }) {
|
||||
return selections.some(selection => {
|
||||
if (selection.kind === Kind.FIELD) {
|
||||
if (selection.alias && idNames.includes(selection.alias.value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return idNames.includes(selection.name.value);
|
||||
}
|
||||
|
||||
if (selection.kind === Kind.INLINE_FRAGMENT) {
|
||||
return hasIdField(selection.selectionSet);
|
||||
}
|
||||
|
||||
if (selection.kind === Kind.FRAGMENT_SPREAD) {
|
||||
const [foundSpread] = siblings.getFragment(selection.name.value);
|
||||
if (foundSpread) {
|
||||
const fragmentSpread = foundSpread.document;
|
||||
checkedFragmentSpreads.add(fragmentSpread.name.value);
|
||||
return hasIdField(fragmentSpread.selectionSet);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
const hasId = hasIdField(node);
|
||||
|
||||
checkFragments(node);
|
||||
|
||||
if (hasId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pluralSuffix = idNames.length > 1 ? 's' : '';
|
||||
const fieldName = idNames.join(',');
|
||||
|
||||
const addition =
|
||||
checkedFragmentSpreads.size === 0
|
||||
? ''
|
||||
: ` or add to used fragment${
|
||||
checkedFragmentSpreads.size > 1 ? 's' : ''
|
||||
} ${Array.from(checkedFragmentSpreads).join(', ')}`;
|
||||
|
||||
const problem = {
|
||||
loc,
|
||||
messageId: RULE_ID,
|
||||
data: {
|
||||
pluralSuffix,
|
||||
fieldName,
|
||||
addition,
|
||||
},
|
||||
};
|
||||
|
||||
// Don't provide suggestions for selections in fragments as fragment can be in a separate file
|
||||
if ('type' in node) {
|
||||
problem.suggest = idNames.map(idName => ({
|
||||
desc: `Add \`${idName}\` selection`,
|
||||
fix: fixer => {
|
||||
let insertNode = node.selections[0];
|
||||
insertNode =
|
||||
insertNode.kind === Kind.INLINE_FRAGMENT
|
||||
? insertNode.selectionSet.selections[0]
|
||||
: insertNode;
|
||||
return fixer.insertTextBefore(insertNode, `${idName} `);
|
||||
},
|
||||
}));
|
||||
}
|
||||
context.report(problem);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
[selector](node) {
|
||||
const typeInfo = node.typeInfo();
|
||||
if (typeInfo.gqlType) {
|
||||
checkSelections(node, typeInfo.gqlType, node.loc.start, node.parent);
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = rule;
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
module.exports = {
|
||||
rules: {
|
||||
'enforce-deps-in-dev': require('./enforce-deps-in-dev.cjs'),
|
||||
'graphql-require-selection': require('./graphql-require-selection.cjs'),
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"types": ["vitest/globals"],
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist",
|
||||
"rootDir": "packages",
|
||||
"rootDir": ".",
|
||||
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
|
|
@ -66,7 +66,8 @@
|
|||
"./packages/libraries/external-composition/src/index.ts"
|
||||
],
|
||||
"@graphql-hive/core": ["./packages/libraries/core/src/index.ts"],
|
||||
"@/*": ["./packages/web/app/src/*"]
|
||||
"@/*": ["./packages/web/app/src/*"],
|
||||
"testkit/*": ["./integration-tests/testkit/*"]
|
||||
}
|
||||
},
|
||||
"include": ["packages", "tsup.config.node.ts"],
|
||||
|
|
|
|||
Loading…
Reference in a new issue