-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add option to synchronize all active workspaces at once #6221
Changes from 2 commits
0b5c3d2
3596881
31a998e
9dd72cf
abcf8e6
4084507
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { Repository } from 'typeorm'; | ||
|
||
import { | ||
BillingSubscription, | ||
SubscriptionStatus, | ||
} from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; | ||
import { | ||
FeatureFlagEntity, | ||
FeatureFlagKeys, | ||
} from 'src/engine/core-modules/feature-flag/feature-flag.entity'; | ||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; | ||
|
||
export const isWorkspaceActive = async ({ | ||
workspace, | ||
billingSubscriptionRepository, | ||
featureFlagRepository, | ||
}: { | ||
workspace: Workspace; | ||
billingSubscriptionRepository: Repository<BillingSubscription>; | ||
featureFlagRepository: Repository<FeatureFlagEntity>; | ||
}) => { | ||
const billingSubscriptionForWorkspace = | ||
await billingSubscriptionRepository.findOne({ | ||
where: { workspaceId: workspace.id }, | ||
}); | ||
|
||
if ( | ||
billingSubscriptionForWorkspace?.status && | ||
[ | ||
SubscriptionStatus.PastDue, | ||
SubscriptionStatus.Active, | ||
SubscriptionStatus.Trialing, | ||
].includes(billingSubscriptionForWorkspace.status as SubscriptionStatus) | ||
) { | ||
return true; | ||
} | ||
|
||
const freeAccessEnabledFeatureFlagForWorkspace = | ||
await featureFlagRepository.findOne({ | ||
where: { | ||
workspaceId: workspace.id, | ||
key: FeatureFlagKeys.IsFreeAccessEnabled, | ||
value: true, | ||
}, | ||
}); | ||
|
||
return !!freeAccessEnabledFeatureFlagForWorkspace; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,40 @@ | ||
import { Logger } from '@nestjs/common'; | ||
import { InjectRepository } from '@nestjs/typeorm'; | ||
|
||
import isEmpty from 'lodash.isempty'; | ||
import { Command, CommandRunner, Option } from 'nest-commander'; | ||
import { Repository } from 'typeorm'; | ||
|
||
import { isWorkspaceActive } from 'src/database/commands/utils/is-workspace-active.utils'; | ||
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; | ||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; | ||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; | ||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; | ||
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service'; | ||
import { WorkspaceHealthService } from 'src/engine/workspace-manager/workspace-health/workspace-health.service'; | ||
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service'; | ||
|
||
import { SyncWorkspaceLoggerService } from './services/sync-workspace-logger.service'; | ||
|
||
// TODO: implement dry-run | ||
interface RunWorkspaceMigrationsOptions { | ||
workspaceId?: string; | ||
interface BaseOptions { | ||
dryRun?: boolean; | ||
force?: boolean; | ||
} | ||
|
||
interface WorkspaceIdOption extends BaseOptions { | ||
workspaceId: string; | ||
syncAllActiveWorkspaces?: never; | ||
} | ||
|
||
interface SyncAllActiveWorkspacesOption extends BaseOptions { | ||
workspaceId?: never; | ||
syncAllActiveWorkspaces: boolean; | ||
} | ||
|
||
type RunWorkspaceMigrationsOptions = | ||
| WorkspaceIdOption | ||
| SyncAllActiveWorkspacesOption; | ||
|
||
@Command({ | ||
name: 'workspace:sync-metadata', | ||
description: 'Sync metadata', | ||
|
@@ -27,6 +47,12 @@ export class SyncWorkspaceMetadataCommand extends CommandRunner { | |
private readonly workspaceHealthService: WorkspaceHealthService, | ||
private readonly dataSourceService: DataSourceService, | ||
private readonly syncWorkspaceLoggerService: SyncWorkspaceLoggerService, | ||
@InjectRepository(Workspace, 'core') | ||
private readonly workspaceRepository: Repository<Workspace>, | ||
@InjectRepository(BillingSubscription, 'core') | ||
private readonly billingSubscriptionRepository: Repository<BillingSubscription>, | ||
@InjectRepository(FeatureFlagEntity, 'core') | ||
private readonly featureFlagRepository: Repository<FeatureFlagEntity>, | ||
) { | ||
super(); | ||
} | ||
|
@@ -36,7 +62,34 @@ export class SyncWorkspaceMetadataCommand extends CommandRunner { | |
options: RunWorkspaceMigrationsOptions, | ||
): Promise<void> { | ||
// TODO: re-implement load index from workspaceService, this is breaking the logger | ||
const workspaceIds = options.workspaceId ? [options.workspaceId] : []; | ||
let workspaceIds = options.workspaceId ? [options.workspaceId] : []; | ||
|
||
if (options.syncAllActiveWorkspaces && isEmpty(workspaceIds)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no need for onlyActiveWorkspaces |
||
const workspaces = await this.workspaceRepository.find(); | ||
|
||
const activeWorkspaceIds = ( | ||
await Promise.all( | ||
workspaces.map(async (workspace) => { | ||
const isActive = await isWorkspaceActive({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add areWorkspaceActive query |
||
workspace: workspace, | ||
billingSubscriptionRepository: this.billingSubscriptionRepository, | ||
featureFlagRepository: this.featureFlagRepository, | ||
}); | ||
|
||
return { workspace, isActive }; | ||
}), | ||
) | ||
) | ||
.filter((result) => result.isActive) | ||
.map((result) => result.workspace.id); | ||
|
||
workspaceIds = activeWorkspaceIds; | ||
this.logger.log( | ||
`Attempting to sync ${activeWorkspaceIds.length} workspaces.`, | ||
); | ||
} | ||
|
||
const errorsDuringAllActiveWorkspaceSync: string[] = []; | ||
|
||
for (const workspaceId of workspaceIds) { | ||
try { | ||
|
@@ -75,34 +128,58 @@ export class SyncWorkspaceMetadataCommand extends CommandRunner { | |
); | ||
} | ||
|
||
const dataSourceMetadata = | ||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( | ||
workspaceId, | ||
); | ||
try { | ||
const dataSourceMetadata = | ||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( | ||
workspaceId, | ||
); | ||
|
||
const { storage, workspaceMigrations } = | ||
await this.workspaceSyncMetadataService.synchronize( | ||
{ | ||
workspaceId, | ||
dataSourceId: dataSourceMetadata.id, | ||
}, | ||
{ applyChanges: !options.dryRun }, | ||
); | ||
|
||
const { storage, workspaceMigrations } = | ||
await this.workspaceSyncMetadataService.synchronize( | ||
{ | ||
if (options.dryRun) { | ||
await this.syncWorkspaceLoggerService.saveLogs( | ||
workspaceId, | ||
dataSourceId: dataSourceMetadata.id, | ||
}, | ||
{ applyChanges: !options.dryRun }, | ||
); | ||
storage, | ||
workspaceMigrations, | ||
); | ||
} | ||
} catch (error) { | ||
if (options.syncAllActiveWorkspaces) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if (options.throwOnFirstFail) |
||
errorsDuringAllActiveWorkspaceSync.push( | ||
`Failed to synchronize workspace ${workspaceId}: ${error.message}`, | ||
); | ||
|
||
if (options.dryRun) { | ||
await this.syncWorkspaceLoggerService.saveLogs( | ||
workspaceId, | ||
storage, | ||
workspaceMigrations, | ||
); | ||
continue; | ||
} | ||
throw error; | ||
} | ||
} | ||
|
||
if (options.syncAllActiveWorkspaces) { | ||
this.logger.log( | ||
`Finished synchronizing all active workspaces (${ | ||
workspaceIds.length | ||
} workspaces). ${ | ||
errorsDuringAllActiveWorkspaceSync.length > 0 | ||
? 'Errors during sync:\n' + | ||
errorsDuringAllActiveWorkspaceSync.join('.\n') | ||
: '' | ||
}`, | ||
); | ||
} | ||
} | ||
|
||
@Option({ | ||
flags: '-w, --workspace-id [workspace_id]', | ||
description: 'workspace id', | ||
required: true, | ||
required: false, | ||
}) | ||
parseWorkspaceId(value: string): string { | ||
return value; | ||
|
@@ -125,4 +202,13 @@ export class SyncWorkspaceMetadataCommand extends CommandRunner { | |
force(): boolean { | ||
return true; | ||
} | ||
|
||
@Option({ | ||
flags: '-s, --sync-all-active-workspaces', | ||
description: 'Sync all active workspaces', | ||
required: false, | ||
}) | ||
syncAllActiveWorkspaces(): boolean { | ||
return true; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move to workspace.service.ts