Fix the default retention period of personal plans in PG (#949)

This commit is contained in:
Kamil Kisiela 2023-01-05 11:27:42 +01:00 committed by GitHub
parent 7c84107e9f
commit 21a135b9d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 117 additions and 11 deletions

View file

@ -20,3 +20,4 @@ RATE_LIMIT_ENDPOINT=http://rate-limit:3009
CLICKHOUSE_ASYNC_INSERT_BUSY_TIMEOUT_MS=500
CLICKHOUSE_ASYNC_INSERT_MAX_DATA_SIZE=1000
EXTERNAL_COMPOSITION_SECRET=secretsecret
LIMIT_CACHE_UPDATE_INTERVAL_MS=2000

View file

@ -139,7 +139,7 @@ services:
environment:
NODE_ENV: production
LOG_LEVEL: debug
LIMIT_CACHE_UPDATE_INTERVAL_MS: 2000
LIMIT_CACHE_UPDATE_INTERVAL_MS: '${LIMIT_CACHE_UPDATE_INTERVAL_MS}'
POSTGRES_HOST: db
POSTGRES_PORT: 5432
POSTGRES_DB: '${POSTGRES_DB}'

View file

@ -20,7 +20,7 @@ export default {
}),
'^(\\.{1,2}/.*)\\.js$': '$1',
},
testTimeout: 200000,
testTimeout: 30_000,
setupFilesAfterEnv: ['dotenv/config'],
collectCoverage: false,
};

View file

@ -28,6 +28,7 @@
"zod": "3.20.2"
},
"devDependencies": {
"@hive/rate-limit": "workspace:*",
"@hive/server": "workspace:*",
"@types/dockerode": "3.3.14",
"@types/ioredis": "4.28.10",

View file

@ -1,5 +1,7 @@
import { gql } from '@app/gql';
import {
OrganizationAccessScope,
OrganizationType,
ProjectAccessScope,
ProjectType,
TargetAccessScope,
@ -31,6 +33,7 @@ import {
updateMemberAccess,
updateSchemaVersionStatus,
} from './flow';
import { execute } from './graphql';
import { collect, CollectedOperation } from './usage';
import { generateUnique } from './utils';
@ -45,6 +48,48 @@ export function initSeed() {
return {
ownerEmail,
ownerToken,
async createPersonalProject(projectType: ProjectType) {
const orgs = await execute({
document: gql(/* GraphQL */ `
query myOrganizations {
organizations {
total
nodes {
id
cleanId
name
type
}
}
}
`),
authToken: ownerToken,
}).then(r => r.expectNoGraphQLErrors());
const personalOrg = orgs.organizations.nodes.find(
o => o.type === OrganizationType.Personal,
);
if (!personalOrg) {
throw new Error('Personal organization should exist');
}
const projectResult = await createProject(
{
organization: personalOrg.cleanId,
type: projectType,
name: generateUnique(),
},
ownerToken,
).then(r => r.expectNoGraphQLErrors());
const targets = projectResult.createProject.ok!.createdTargets;
const target = targets[0];
return {
target,
};
},
async createOrg() {
const orgName = generateUnique();
const orgResult = await createOrganization({ name: orgName }, ownerToken).then(r =>

View file

@ -1,3 +1,4 @@
import { ProjectType, TargetAccessScope } from '@app/gql/graphql';
import {
DeleteObjectsCommand,
GetObjectCommand,
@ -5,7 +6,6 @@ import {
S3Client,
} from '@aws-sdk/client-s3';
import { fetch } from '@whatwg-node/fetch';
import { ProjectType, TargetAccessScope } from '../../testkit/gql/graphql';
import { initSeed } from '../../testkit/seed';
import { getServiceHost } from '../../testkit/utils';

View file

@ -1,13 +1,9 @@
import { OrganizationAccessScope, ProjectAccessScope, TargetAccessScope } from '@app/gql/graphql';
import {
answerOrganizationTransferRequest,
getOrganizationTransferRequest,
requestOrganizationTransfer,
} from '../../../testkit/flow';
import {
OrganizationAccessScope,
ProjectAccessScope,
TargetAccessScope,
} from '../../../testkit/gql/graphql';
import { initSeed } from '../../../testkit/seed';
test.concurrent(

View file

@ -1,6 +1,17 @@
import { gql } from '@app/gql';
import { ProjectType } from '@app/gql/graphql';
import type { RateLimitApi } from '@hive/rate-limit';
import { createTRPCProxyClient, httpLink } from '@trpc/client';
import { createFetch } from '@whatwg-node/fetch';
import { ensureEnv } from '../../testkit/env';
import { waitFor } from '../../testkit/flow';
import { execute } from '../../testkit/graphql';
import { initSeed } from '../../testkit/seed';
import { getServiceHost } from '../../testkit/utils';
const { fetch } = createFetch({
useNodeFetch: true,
});
test.concurrent('should auto-create an organization for freshly signed-up user', async () => {
const { ownerToken } = await initSeed().createOwner();
@ -22,6 +33,49 @@ test.concurrent('should auto-create an organization for freshly signed-up user',
expect(result.organizations.total).toBe(1);
});
test.concurrent(
'freshly signed-up user should have a Hobby plan with 7 days of retention',
async () => {
const { ownerToken, createPersonalProject } = await initSeed().createOwner();
const result = await execute({
document: gql(/* GraphQL */ `
query organizations {
organizations {
total
nodes {
id
name
}
}
}
`),
authToken: ownerToken,
}).then(r => r.expectNoGraphQLErrors());
expect(result.organizations.total).toBe(1);
const { target } = await createPersonalProject(ProjectType.Single);
await waitFor(ensureEnv('LIMIT_CACHE_UPDATE_INTERVAL_MS', 'number') + 1_000); // wait for rate-limit to update
const rateLimit = createTRPCProxyClient<RateLimitApi>({
links: [
httpLink({
url: `http://${await getServiceHost('rate-limit', 3009)}/trpc`,
fetch,
}),
],
});
// Expect the default retention for a Hobby plan to be 7 days
await expect(
rateLimit.getRetention.query({
targetId: target.id,
}),
).resolves.toEqual(7);
},
);
test.concurrent(
'should auto-create an organization for freshly signed-up user with no race-conditions',
async () => {

View file

@ -1,7 +1,7 @@
/* eslint-disable no-process-env */
import { createHash } from 'node:crypto';
import { ProjectType } from '@app/gql/graphql';
import { schemaCheck, schemaPublish } from '../../testkit/cli';
import { ProjectType } from '../../testkit/gql/graphql';
import { initSeed } from '../../testkit/seed';
test.concurrent('can publish and check a schema with target:registry:read access', async () => {

View file

@ -6,7 +6,8 @@
"esModuleInterop": true,
"paths": {
"@hive/server": ["../packages/services/server/src/api.ts"],
"@hive/storage": ["../packages/services/storage/src/index.ts"]
"@hive/storage": ["../packages/services/storage/src/index.ts"],
"@hive/rate-limit": ["../packages/services/rate-limit/src/api.ts"]
}
},
"include": ["testkit", "tests"]

View file

@ -9,7 +9,7 @@ export type RateLimitInput = z.infer<typeof VALIDATION>;
const VALIDATION = z
.object({
id: z.string().nonempty(),
id: z.string().min(1),
entityType: z.enum(['organization', 'target']),
type: z.enum(['operations-reporting']),
/**

View file

@ -0,0 +1,5 @@
-- Update Hobby with 3d to 7d - personal orgs were created with the default value of 3d
UPDATE public.organizations SET limit_retention_days = 7 WHERE plan_name = 'HOBBY' AND limit_retention_days = 3;
-- Update limit_retention_days default value to 7
ALTER table public.organizations ALTER COLUMN limit_retention_days SET DEFAULT 7;

View file

@ -157,6 +157,7 @@ importers:
'@esm2cjs/execa': 6.1.1-cjs.1
'@graphql-hive/core': 0.2.3
'@graphql-typed-document-node/core': 3.1.1
'@hive/rate-limit': workspace:*
'@hive/server': workspace:*
'@trpc/client': 10.7.0
'@trpc/server': 10.7.0
@ -196,6 +197,7 @@ importers:
slonik: 30.1.2_wg2hxbo7txnklmvja4aeqnygfi
zod: 3.20.2
devDependencies:
'@hive/rate-limit': link:../packages/services/rate-limit
'@hive/server': link:../packages/services/server
'@types/dockerode': 3.3.14
'@types/ioredis': 4.28.10