Enable new record board and messaging for all workspaces except demo (#4243)

* Enable new record board and messaging for all workspaces except demo

* Fix according to PR
This commit is contained in:
Charles Bochet 2024-02-29 18:22:32 +01:00 committed by GitHub
parent 773f698faf
commit fb439e3045
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 72 additions and 233 deletions

View File

@ -20,7 +20,6 @@ import { ImpersonateEffect } from '~/pages/impersonate/ImpersonateEffect';
import { NotFound } from '~/pages/not-found/NotFound';
import { RecordIndexPage } from '~/pages/object-record/RecordIndexPage';
import { RecordShowPage } from '~/pages/object-record/RecordShowPage';
import { Opportunities } from '~/pages/opportunities/Opportunities';
import { SettingsAccounts } from '~/pages/settings/accounts/SettingsAccounts';
import { SettingsAccountsCalendars } from '~/pages/settings/accounts/SettingsAccountsCalendars';
import { SettingsAccountsCalendarsSettings } from '~/pages/settings/accounts/SettingsAccountsCalendarsSettings';
@ -47,9 +46,6 @@ import { SettingsWorkspaceMembers } from '~/pages/settings/SettingsWorkspaceMemb
import { Tasks } from '~/pages/tasks/Tasks';
export const App = () => {
const isNewRecordBoardEnabled = useIsFeatureEnabled(
'IS_NEW_RECORD_BOARD_ENABLED',
);
const isSelfBillingEnabled = useIsFeatureEnabled('IS_SELF_BILLING_ENABLED');
return (
@ -79,13 +75,6 @@ export const App = () => {
<Route path={AppPath.Index} element={<DefaultHomePage />} />
<Route path={AppPath.TasksPage} element={<Tasks />} />
<Route path={AppPath.Impersonate} element={<ImpersonateEffect />} />
{!isNewRecordBoardEnabled && (
<Route
path={AppPath.OpportunitiesPage}
element={<Opportunities />}
/>
)}
<Route path={AppPath.RecordIndexPage} element={<RecordIndexPage />} />
<Route path={AppPath.RecordShowPage} element={<RecordShowPage />} />

View File

@ -34,7 +34,6 @@ export const SettingsNavigationDrawerItems = () => {
}, [signOut, navigate]);
const isCalendarEnabled = useIsFeatureEnabled('IS_CALENDAR_ENABLED');
const isMessagingEnabled = useIsFeatureEnabled('IS_MESSAGING_ENABLED');
return (
<>
@ -51,30 +50,28 @@ export const SettingsNavigationDrawerItems = () => {
Icon={IconColorSwatch}
/>
{isMessagingEnabled && (
<NavigationDrawerItemGroup>
<SettingsNavigationDrawerItem
label="Accounts"
path={SettingsPath.Accounts}
Icon={IconAt}
/>
<SettingsNavigationDrawerItem
level={2}
label="Emails"
path={SettingsPath.AccountsEmails}
Icon={IconMail}
matchSubPages
/>
<SettingsNavigationDrawerItem
level={2}
label="Calendars"
path={SettingsPath.AccountsCalendars}
Icon={IconCalendarEvent}
matchSubPages
soon={!isCalendarEnabled}
/>
</NavigationDrawerItemGroup>
)}
<NavigationDrawerItemGroup>
<SettingsNavigationDrawerItem
label="Accounts"
path={SettingsPath.Accounts}
Icon={IconAt}
/>
<SettingsNavigationDrawerItem
level={2}
label="Emails"
path={SettingsPath.AccountsEmails}
Icon={IconMail}
matchSubPages
/>
<SettingsNavigationDrawerItem
level={2}
label="Calendars"
path={SettingsPath.AccountsCalendars}
Icon={IconCalendarEvent}
matchSubPages
soon={!isCalendarEnabled}
/>
</NavigationDrawerItemGroup>
</NavigationDrawerSection>
<NavigationDrawerSection>

View File

@ -20,7 +20,6 @@ import {
import { TabList } from '@/ui/layout/tab/components/TabList';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
const StyledShowPageRightContainer = styled.div`
display: flex;
@ -60,8 +59,6 @@ export const ShowPageRightContainer = ({
notes,
emails,
}: ShowPageRightContainerProps) => {
const isMessagingEnabled = useIsFeatureEnabled('IS_MESSAGING_ENABLED');
const { getActiveTabIdState } = useTabList(TAB_LIST_COMPONENT_ID);
const activeTabId = useRecoilValue(getActiveTabIdState());
@ -107,7 +104,6 @@ export const ShowPageRightContainer = ({
title: 'Emails',
Icon: IconMail,
hide: !shouldDisplayEmailsTab,
disabled: !isMessagingEnabled,
hasBetaPill: true,
},
];

View File

@ -1,7 +1,5 @@
export type FeatureFlagKey =
| 'IS_BLOCKLIST_ENABLED'
| 'IS_CALENDAR_ENABLED'
| 'IS_MESSAGING_ENABLED'
| 'IS_NEW_RECORD_BOARD_ENABLED'
| 'IS_QUICK_ACTIONS_ENABLED'
| 'IS_SELF_BILLING_ENABLED';

View File

@ -37,14 +37,25 @@ export class GoogleGmailAuthController {
const { workspaceMemberId, workspaceId } =
await this.tokenService.verifyTransientToken(transientToken);
await this.googleGmailService.saveConnectedAccount({
handle: email,
workspaceMemberId: workspaceMemberId,
workspaceId: workspaceId,
provider: 'gmail',
accessToken,
refreshToken,
});
const demoWorkspaceIds = this.environmentService.getDemoWorkspaceIds();
if (demoWorkspaceIds.includes(workspaceId)) {
throw new Error('Cannot connect Gmail account to demo workspace');
}
if (!workspaceId) {
throw new Error('Workspace not found');
}
if (workspaceId)
await this.googleGmailService.saveConnectedAccount({
handle: email,
workspaceMemberId: workspaceMemberId,
workspaceId: workspaceId,
provider: 'gmail',
accessToken,
refreshToken,
});
return res.redirect(
`${this.environmentService.getFrontBaseUrl()}/settings/accounts`,

View File

@ -16,10 +16,7 @@ import { Workspace } from 'src/core/workspace/workspace.entity';
export enum FeatureFlagKeys {
IsBlocklistEnabled = 'IS_BLOCKLIST_ENABLED',
IsCalendarEnabled = 'IS_CALENDAR_ENABLED',
IsMessagingEnabled = 'IS_MESSAGING_ENABLED',
IsNewRecordBoardEnabled = 'IS_NEW_RECORD_BOARD_ENABLED',
IsSelfBillingEnabled = 'IS_SELF_BILLING_ENABLED',
IsWorkspaceCleanable = 'IS_WORKSPACE_CLEANABLE',
}
@Entity({ name: 'featureFlag', schema: 'core' })

View File

@ -1,28 +1,20 @@
import { DataSource } from 'typeorm';
import { FeatureFlagKeys } from 'src/core/feature-flag/feature-flag.entity';
const tableName = 'featureFlag';
export const seedFeatureFlags = async (
workspaceDataSource: DataSource,
schemaName: string,
workspaceId: string,
) => {
await workspaceDataSource
.createQueryBuilder()
.insert()
.into(`${schemaName}.${tableName}`, ['key', 'workspaceId', 'value'])
.orIgnore()
.values([
{
key: FeatureFlagKeys.IsNewRecordBoardEnabled,
workspaceId: workspaceId,
value: false,
},
])
.execute();
};
// export const seedFeatureFlags = async (
// workspaceDataSource: DataSource,
// schemaName: string,
// workspaceId: string,
// ) => {
// await workspaceDataSource
// .createQueryBuilder()
// .insert()
// .into(`${schemaName}.${tableName}`, ['key', 'workspaceId', 'value'])
// .orIgnore()
// .values([])
// .execute();
// };
export const deleteFeatureFlags = async (
workspaceDataSource: DataSource,

View File

@ -8,10 +8,7 @@ import {
seedWorkspaces,
deleteWorkspaces,
} from 'src/database/typeorm-seeds/core/demo/workspaces';
import {
seedFeatureFlags,
deleteFeatureFlags,
} from 'src/database/typeorm-seeds/core/demo/feature-flags';
import { deleteFeatureFlags } from 'src/database/typeorm-seeds/core/demo/feature-flags';
export const seedCoreSchema = async (
workspaceDataSource: DataSource,
@ -21,7 +18,6 @@ export const seedCoreSchema = async (
await seedWorkspaces(workspaceDataSource, schemaName, workspaceId);
await seedUsers(workspaceDataSource, schemaName, workspaceId);
await seedFeatureFlags(workspaceDataSource, schemaName, workspaceId);
};
export const deleteCoreSchema = async (

View File

@ -20,26 +20,11 @@ export const seedFeatureFlags = async (
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKeys.IsMessagingEnabled,
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKeys.IsBlocklistEnabled,
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKeys.IsWorkspaceCleanable,
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKeys.IsNewRecordBoardEnabled,
workspaceId: workspaceId,
value: true,
},
])
.execute();
};

View File

@ -16,7 +16,6 @@ import { GmailPartialSyncJob } from 'src/workspace/messaging/jobs/gmail-partial-
import { EmailSenderJob } from 'src/integrations/email/email-sender.job';
import { UserModule } from 'src/core/user/user.module';
import { EnvironmentModule } from 'src/integrations/environment/environment.module';
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
import { FetchAllWorkspacesMessagesJob } from 'src/workspace/messaging/commands/crons/fetch-all-workspaces-messages.job';
import { ConnectedAccountModule } from 'src/workspace/messaging/repositories/connected-account/connected-account.module';
import { MatchMessageParticipantJob } from 'src/workspace/messaging/jobs/match-message-participant.job';
@ -28,6 +27,7 @@ import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-dem
import { DataSeedDemoWorkspaceJob } from 'src/database/commands/data-seed-demo-workspace/jobs/data-seed-demo-workspace.job';
import { DeleteConnectedAccountAssociatedDataJob } from 'src/workspace/messaging/jobs/delete-connected-acount-associated-data.job';
import { ThreadCleanerModule } from 'src/workspace/messaging/services/thread-cleaner/thread-cleaner.module';
import { Workspace } from 'src/core/workspace/workspace.entity';
@Module({
imports: [
@ -40,7 +40,7 @@ import { ThreadCleanerModule } from 'src/workspace/messaging/services/thread-cle
UserModule,
EnvironmentModule,
TypeORMModule,
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
TypeOrmModule.forFeature([Workspace], 'core'),
ConnectedAccountModule,
MessageParticipantModule,
CreateCompaniesAndContactsModule,

View File

@ -5,10 +5,6 @@ import { Repository } from 'typeorm';
import { MessageQueueJob } from 'src/integrations/message-queue/interfaces/message-queue-job.interface';
import {
FeatureFlagEntity,
FeatureFlagKeys,
} from 'src/core/feature-flag/feature-flag.entity';
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
import { ConnectedAccountService } from 'src/workspace/messaging/repositories/connected-account/connected-account.service';
@ -16,29 +12,29 @@ import {
GmailPartialSyncJobData,
GmailPartialSyncJob,
} from 'src/workspace/messaging/jobs/gmail-partial-sync.job';
import { Workspace } from 'src/core/workspace/workspace.entity';
@Injectable()
export class FetchAllWorkspacesMessagesJob
implements MessageQueueJob<undefined>
{
constructor(
@InjectRepository(FeatureFlagEntity, 'core')
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
@InjectRepository(Workspace, 'core')
private readonly workspaceRepository: Repository<Workspace>,
@Inject(MessageQueue.messagingQueue)
private readonly messageQueueService: MessageQueueService,
private readonly connectedAccountService: ConnectedAccountService,
) {}
async handle(): Promise<void> {
const featureFlagsWithMessagingEnabled =
await this.featureFlagRepository.findBy({
key: FeatureFlagKeys.IsMessagingEnabled,
value: true,
});
const workspaceIds = featureFlagsWithMessagingEnabled.map(
(featureFlag) => featureFlag.workspaceId,
);
const workspaceIds = (
await this.workspaceRepository.find({
where: {
subscriptionStatus: 'active',
},
select: ['id'],
})
).map((workspace) => workspace.id);
for (const workspaceId of workspaceIds) {
await this.fetchWorkspaceMessages(workspaceId);

View File

@ -1,13 +1,7 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Inject } from '@nestjs/common';
import { Command, CommandRunner, Option } from 'nest-commander';
import { Repository } from 'typeorm';
import {
FeatureFlagEntity,
FeatureFlagKeys,
} from 'src/core/feature-flag/feature-flag.entity';
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
import {
@ -26,8 +20,6 @@ interface GmailFullSyncOptions {
})
export class GmailFullSyncCommand extends CommandRunner {
constructor(
@InjectRepository(FeatureFlagEntity, 'core')
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
@Inject(MessageQueue.messagingQueue)
private readonly messageQueueService: MessageQueueService,
private readonly connectedAccountService: ConnectedAccountService,
@ -39,16 +31,6 @@ export class GmailFullSyncCommand extends CommandRunner {
_passedParam: string[],
options: GmailFullSyncOptions,
): Promise<void> {
const isMessagingEnabled = await this.featureFlagRepository.findOneBy({
workspaceId: options.workspaceId,
key: FeatureFlagKeys.IsMessagingEnabled,
value: true,
});
if (!isMessagingEnabled) {
throw new Error('Messaging is not enabled for this workspace');
}
await this.fetchWorkspaceMessages(options.workspaceId);
return;

View File

@ -1,13 +1,7 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Inject } from '@nestjs/common';
import { Command, CommandRunner, Option } from 'nest-commander';
import { Repository } from 'typeorm';
import {
FeatureFlagEntity,
FeatureFlagKeys,
} from 'src/core/feature-flag/feature-flag.entity';
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
import {
@ -26,8 +20,6 @@ interface GmailPartialSyncOptions {
})
export class GmailPartialSyncCommand extends CommandRunner {
constructor(
@InjectRepository(FeatureFlagEntity, 'core')
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
@Inject(MessageQueue.messagingQueue)
private readonly messageQueueService: MessageQueueService,
private readonly connectedAccountService: ConnectedAccountService,
@ -39,16 +31,6 @@ export class GmailPartialSyncCommand extends CommandRunner {
_passedParam: string[],
options: GmailPartialSyncOptions,
): Promise<void> {
const isMessagingEnabled = await this.featureFlagRepository.findOneBy({
workspaceId: options.workspaceId,
key: FeatureFlagKeys.IsMessagingEnabled,
value: true,
});
if (!isMessagingEnabled) {
throw new Error('Messaging is not enabled for this workspace');
}
await this.fetchWorkspaceMessages(options.workspaceId);
return;

View File

@ -1,13 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import {
FeatureFlagEntity,
FeatureFlagKeys,
} from 'src/core/feature-flag/feature-flag.entity';
import { ObjectRecordCreateEvent } from 'src/integrations/event-emitter/types/object-record-create.event';
import { ObjectRecordUpdateEvent } from 'src/integrations/event-emitter/types/object-record-update.event';
import { objectRecordChangedProperties as objectRecordUpdateEventChangedProperties } from 'src/integrations/event-emitter/utils/object-record-changed-properties.util';
@ -24,8 +17,6 @@ export class MessagingPersonListener {
constructor(
@Inject(MessageQueue.messagingQueue)
private readonly messageQueueService: MessageQueueService,
@InjectRepository(FeatureFlagEntity, 'core')
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
) {}
@OnEvent('person.created')
@ -36,16 +27,6 @@ export class MessagingPersonListener {
return;
}
const messagingFeatureFlag = await this.featureFlagRepository.findOneBy({
key: FeatureFlagKeys.IsMessagingEnabled,
value: true,
workspaceId: payload.workspaceId,
});
if (!messagingFeatureFlag || !messagingFeatureFlag.value) {
return;
}
this.messageQueueService.add<MatchMessageParticipantsJobData>(
MatchMessageParticipantJob.name,
{
@ -60,21 +41,11 @@ export class MessagingPersonListener {
async handleUpdatedEvent(
payload: ObjectRecordUpdateEvent<PersonObjectMetadata>,
) {
const messagingFeatureFlag = await this.featureFlagRepository.findOneBy({
key: FeatureFlagKeys.IsMessagingEnabled,
value: true,
workspaceId: payload.workspaceId,
});
const isMessagingEnabled =
messagingFeatureFlag && messagingFeatureFlag.value;
if (
objectRecordUpdateEventChangedProperties(
payload.previousRecord,
payload.updatedRecord,
).includes('email') &&
isMessagingEnabled
).includes('email')
) {
this.messageQueueService.add<MatchMessageParticipantsJobData>(
MatchMessageParticipantJob.name,

View File

@ -4,10 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import {
FeatureFlagEntity,
FeatureFlagKeys,
} from 'src/core/feature-flag/feature-flag.entity';
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
import { ObjectRecordCreateEvent } from 'src/integrations/event-emitter/types/object-record-create.event';
import { ObjectRecordUpdateEvent } from 'src/integrations/event-emitter/types/object-record-update.event';
import { objectRecordChangedProperties as objectRecordUpdateEventChangedProperties } from 'src/integrations/event-emitter/utils/object-record-changed-properties.util';
@ -36,16 +33,6 @@ export class MessagingWorkspaceMemberListener {
return;
}
const messagingFeatureFlag = await this.featureFlagRepository.findOneBy({
key: FeatureFlagKeys.IsMessagingEnabled,
value: true,
workspaceId: payload.workspaceId,
});
if (!messagingFeatureFlag || !messagingFeatureFlag.value) {
return;
}
this.messageQueueService.add<MatchMessageParticipantsJobData>(
MatchMessageParticipantJob.name,
{
@ -60,21 +47,11 @@ export class MessagingWorkspaceMemberListener {
async handleUpdatedEvent(
payload: ObjectRecordUpdateEvent<WorkspaceMemberObjectMetadata>,
) {
const messagingFeatureFlag = await this.featureFlagRepository.findOneBy({
key: FeatureFlagKeys.IsMessagingEnabled,
value: true,
workspaceId: payload.workspaceId,
});
const isMessagingEnabled =
messagingFeatureFlag && messagingFeatureFlag.value;
if (
objectRecordUpdateEventChangedProperties(
payload.previousRecord,
payload.updatedRecord,
).includes('userEmail') &&
isMessagingEnabled
).includes('userEmail')
) {
this.messageQueueService.add<MatchMessageParticipantsJobData>(
MatchMessageParticipantJob.name,

View File

@ -1,8 +1,7 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { render } from '@react-email/render';
import { In, Repository } from 'typeorm';
import { In } from 'typeorm';
import {
CleanInactiveWorkspaceEmail,
DeleteInactiveWorkspaceEmail,
@ -17,10 +16,6 @@ import { DataSourceEntity } from 'src/metadata/data-source/data-source.entity';
import { UserService } from 'src/core/user/services/user.service';
import { EmailService } from 'src/integrations/email/email.service';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
import {
FeatureFlagEntity,
FeatureFlagKeys,
} from 'src/core/feature-flag/feature-flag.entity';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
import { CleanInactiveWorkspacesCommandOptions } from 'src/workspace/workspace-cleaner/commands/clean-inactive-workspaces.command';
@ -48,8 +43,6 @@ export class CleanInactiveWorkspaceJob
private readonly userService: UserService,
private readonly emailService: EmailService,
private readonly environmentService: EnvironmentService,
@InjectRepository(FeatureFlagEntity, 'core')
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
) {
this.inactiveDaysBeforeDelete =
this.environmentService.getInactiveDaysBeforeDelete();
@ -149,20 +142,6 @@ export class CleanInactiveWorkspaceJob
});
}
async isWorkspaceCleanable(dataSource: DataSourceEntity): Promise<boolean> {
const workspaceFeatureFlags = await this.featureFlagRepository.find({
where: { workspaceId: dataSource.workspaceId },
});
return (
workspaceFeatureFlags.filter(
(workspaceFeatureFlag) =>
workspaceFeatureFlag.key === FeatureFlagKeys.IsWorkspaceCleanable &&
workspaceFeatureFlag.value,
).length > 0
);
}
chunkArray(array: any[], chunkSize = 6): any[][] {
const chunkedArray: any[][] = [];
let index = 0;
@ -241,15 +220,6 @@ export class CleanInactiveWorkspaceJob
});
for (const dataSource of dataSourcesChunk) {
if (!(await this.isWorkspaceCleanable(dataSource))) {
this.logger.log(
`${getDryRunLogHeader(isDryRun)}Workspace ${
dataSource.workspaceId
} not cleanable`,
);
continue;
}
this.logger.log(
`${getDryRunLogHeader(isDryRun)}Cleaning Workspace ${
dataSource.workspaceId