feat(custom-domain): remove domainName + add migration for custom dom… (#9872)

…ain + feature flag

Blocked by #9849
This commit is contained in:
Antoine Moreaux
2025-01-28 15:28:18 +01:00
committed by GitHub
parent 0e981bae0a
commit eb88f6f584
14 changed files with 95 additions and 50 deletions

View File

@ -458,6 +458,7 @@ export enum FeatureFlagKey {
IsBillingPlansEnabled = 'IsBillingPlansEnabled',
IsCommandMenuV2Enabled = 'IsCommandMenuV2Enabled',
IsCopilotEnabled = 'IsCopilotEnabled',
IsCustomDomainEnabled = 'IsCustomDomainEnabled',
IsEventObjectEnabled = 'IsEventObjectEnabled',
IsJsonFilterEnabled = 'IsJsonFilterEnabled',
IsLocalizationEnabled = 'IsLocalizationEnabled',
@ -1981,9 +1982,9 @@ export type Workspace = {
databaseUrl: Scalars['String']['output'];
deletedAt?: Maybe<Scalars['DateTime']['output']>;
displayName?: Maybe<Scalars['String']['output']>;
domainName?: Maybe<Scalars['String']['output']>;
featureFlags?: Maybe<Array<FeatureFlag>>;
hasValidEntrepriseKey: Scalars['Boolean']['output'];
hostname?: Maybe<Scalars['String']['output']>;
id: Scalars['UUID']['output'];
inviteHash?: Maybe<Scalars['String']['output']>;
isGoogleAuthEnabled: Scalars['Boolean']['output'];

View File

@ -390,6 +390,7 @@ export enum FeatureFlagKey {
IsBillingPlansEnabled = 'IsBillingPlansEnabled',
IsCommandMenuV2Enabled = 'IsCommandMenuV2Enabled',
IsCopilotEnabled = 'IsCopilotEnabled',
IsCustomDomainEnabled = 'IsCustomDomainEnabled',
IsEventObjectEnabled = 'IsEventObjectEnabled',
IsJsonFilterEnabled = 'IsJsonFilterEnabled',
IsLocalizationEnabled = 'IsLocalizationEnabled',
@ -1760,9 +1761,9 @@ export type Workspace = {
databaseUrl: Scalars['String'];
deletedAt?: Maybe<Scalars['DateTime']>;
displayName?: Maybe<Scalars['String']>;
domainName?: Maybe<Scalars['String']>;
featureFlags?: Maybe<Array<FeatureFlag>>;
hasValidEntrepriseKey: Scalars['Boolean'];
hostname?: Maybe<Scalars['String']>;
id: Scalars['UUID'];
inviteHash?: Maybe<Scalars['String']>;
isGoogleAuthEnabled: Scalars['Boolean'];
@ -2149,7 +2150,7 @@ export type ListSsoIdentityProvidersByWorkspaceIdQueryVariables = Exact<{ [key:
export type ListSsoIdentityProvidersByWorkspaceIdQuery = { __typename?: 'Query', listSSOIdentityProvidersByWorkspaceId: Array<{ __typename?: 'FindAvailableSSOIDPOutput', type: IdentityProviderType, id: string, name: string, issuer: string, status: SsoIdentityProviderStatus }> };
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null, subdomain: string } | null }> };
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string } | null }> };
export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>;
@ -2166,7 +2167,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null, subdomain: string } | null }> } };
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string } | null }> } };
export type ActivateWorkflowVersionMutationVariables = Exact<{
workflowVersionId: Scalars['String'];
@ -2269,7 +2270,7 @@ export type UpdateWorkspaceMutationVariables = Exact<{
}>;
export type UpdateWorkspaceMutation = { __typename?: 'Mutation', updateWorkspace: { __typename?: 'Workspace', id: any, domainName?: string | null, subdomain: string, displayName?: string | null, logo?: string | null, allowImpersonation: boolean, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean } };
export type UpdateWorkspaceMutation = { __typename?: 'Mutation', updateWorkspace: { __typename?: 'Workspace', id: any, subdomain: string, displayName?: string | null, logo?: string | null, allowImpersonation: boolean, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean } };
export type UploadWorkspaceLogoMutationVariables = Exact<{
file: Scalars['Upload'];
@ -2426,7 +2427,6 @@ export const UserQueryFragmentFragmentDoc = gql`
id
displayName
logo
domainName
inviteHash
allowImpersonation
activationStatus
@ -2459,7 +2459,6 @@ export const UserQueryFragmentFragmentDoc = gql`
id
logo
displayName
domainName
subdomain
}
}
@ -4513,7 +4512,6 @@ export const UpdateWorkspaceDocument = gql`
mutation UpdateWorkspace($input: UpdateWorkspaceInput!) {
updateWorkspace(data: $input) {
id
domainName
subdomain
displayName
logo

View File

@ -63,7 +63,6 @@ export const results = {
id: 'id',
displayName: 'displayName',
logo: 'logo',
domainName: 'domainName',
inviteHash: 'inviteHash',
allowImpersonation: true,
subscriptionStatus: 'subscriptionStatus',

View File

@ -26,36 +26,47 @@ export const queries = {
}
`,
findManyViewsQuery: gql`
query FindManyViews($filter: ViewFilterInput, $orderBy: [ViewOrderByInput], $lastCursor: String, $limit: Int) {
views(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor) {
edges {
node {
__typename
id
viewGroups {
edges {
node {
__typename
fieldMetadataId
fieldValue
id
isVisible
position
}
query FindManyViews(
$filter: ViewFilterInput
$orderBy: [ViewOrderByInput]
$lastCursor: String
$limit: Int
) {
views(
filter: $filter
orderBy: $orderBy
first: $limit
after: $lastCursor
) {
edges {
node {
__typename
id
viewGroups {
edges {
node {
__typename
fieldMetadataId
fieldValue
id
isVisible
position
}
}
}
cursor
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
cursor
}
}`,
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
}
}
`,
deleteMetadataFieldRelation: gql`
mutation DeleteOneRelationMetadataItem($idToDelete: UUID!) {
deleteOneRelation(input: { id: $idToDelete }) {
@ -138,7 +149,6 @@ export const queries = {
id
displayName
logo
domainName
inviteHash
allowImpersonation
activationStatus
@ -171,7 +181,6 @@ export const queries = {
id
logo
displayName
domainName
subdomain
}
}
@ -289,7 +298,6 @@ export const responseData = {
id: 'test-workspace-id',
displayName: 'Test Workspace',
logo: null,
domainName: 'test',
inviteHash: 'test-hash',
allowImpersonation: false,
activationStatus: 'active',

View File

@ -28,7 +28,6 @@ export const USER_QUERY_FRAGMENT = gql`
id
displayName
logo
domainName
inviteHash
allowImpersonation
activationStatus
@ -61,7 +60,6 @@ export const USER_QUERY_FRAGMENT = gql`
id
logo
displayName
domainName
subdomain
}
}

View File

@ -4,7 +4,6 @@ export const UPDATE_WORKSPACE = gql`
mutation UpdateWorkspace($input: UpdateWorkspaceInput!) {
updateWorkspace(data: $input) {
id
domainName
subdomain
displayName
logo

View File

@ -40,7 +40,6 @@ export const mockCurrentWorkspace: Workspace = {
subdomain: 'acme.twenty.com',
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6w',
displayName: 'Twenty',
domainName: 'twenty.com',
inviteHash: 'twenty.com-invite-hash',
logo: workspaceLogoUrl,
isPublicInviteLinkEnabled: true,

View File

@ -45,6 +45,11 @@ export const seedFeatureFlags = async (
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKey.IsCustomDomainEnabled,
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKey.IsBillingPlansEnabled,
workspaceId: workspaceId,

View File

@ -18,7 +18,6 @@ export const seedWorkspaces = async (
Workspace,
| 'id'
| 'displayName'
| 'domainName'
| 'inviteHash'
| 'logo'
| 'subdomain'
@ -28,7 +27,6 @@ export const seedWorkspaces = async (
[SEED_APPLE_WORKSPACE_ID]: {
id: workspaceId,
displayName: 'Apple',
domainName: 'apple.dev',
subdomain: 'apple',
inviteHash: 'apple.dev-invite-hash',
logo: 'https://twentyhq.github.io/placeholder-images/workspaces/apple-logo.png',
@ -37,7 +35,6 @@ export const seedWorkspaces = async (
[SEED_ACME_WORKSPACE_ID]: {
id: workspaceId,
displayName: 'Acme',
domainName: 'acme.dev',
subdomain: 'acme',
inviteHash: 'acme.dev-invite-hash',
logo: 'https://logos-world.net/wp-content/uploads/2022/05/Acme-Logo-700x394.png',
@ -51,7 +48,6 @@ export const seedWorkspaces = async (
.into(`${schemaName}.${tableName}`, [
'id',
'displayName',
'domainName',
'subdomain',
'inviteHash',
'logo',

View File

@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class RemoveDomainNameFromWorkspace1737996712702
implements MigrationInterface
{
name = 'RemoveDomainNameFromWorkspace1737996712702';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."workspace" DROP COLUMN "domainName"`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."workspace" ADD "domainName" character varying`,
);
}
}

View File

@ -0,0 +1,23 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddHostnameToWorkspace1737997028359 implements MigrationInterface {
name = 'AddHostnameToWorkspace1737997028359';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."workspace" ADD "hostname" character varying`,
);
await queryRunner.query(
`ALTER TABLE "core"."workspace" ADD CONSTRAINT "UQ_e6fa363bdaf45cbf8ce97bcebf0" UNIQUE ("hostname")`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."workspace" DROP CONSTRAINT "UQ_e6fa363bdaf45cbf8ce97bcebf0"`,
);
await queryRunner.query(
`ALTER TABLE "core"."workspace" DROP COLUMN "hostname"`,
);
}
}

View File

@ -311,7 +311,6 @@ export class SignInUpService {
const workspaceToCreate = this.workspaceRepository.create({
subdomain: await this.domainManagerService.generateSubdomain(),
displayName: '',
domainName: '',
inviteHash: v4(),
activationStatus: WorkspaceActivationStatus.PENDING_CREATION,
logo,

View File

@ -11,6 +11,7 @@ export enum FeatureFlagKey {
IsAdvancedFiltersEnabled = 'IS_ADVANCED_FILTERS_ENABLED',
IsCommandMenuV2Enabled = 'IS_COMMAND_MENU_V2_ENABLED',
IsJsonFilterEnabled = 'IS_JSON_FILTER_ENABLED',
IsCustomDomainEnabled = 'IS_CUSTOM_DOMAIN_ENABLED',
IsLocalizationEnabled = 'IS_LOCALIZATION_ENABLED',
IsBillingPlansEnabled = 'IS_BILLING_PLANS_ENABLED',
IsRichTextV2Enabled = 'IS_RICH_TEXT_V2_ENABLED',

View File

@ -32,10 +32,6 @@ export class Workspace {
@PrimaryGeneratedColumn('uuid')
id: string;
@Field({ nullable: true })
@Column({ nullable: true })
domainName?: string;
@Field({ nullable: true })
@Column({ nullable: true })
displayName?: string;
@ -126,6 +122,10 @@ export class Workspace {
@Column({ unique: true })
subdomain: string;
@Field({ nullable: true })
@Column({ unique: true, nullable: true })
hostname?: string;
@Field()
@Column({ default: true })
isGoogleAuthEnabled: boolean;