mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 11:31:57 +03:00
List unused workspaces (#6073)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
6d2f17b2cb
commit
f11bc4c89d
@ -1,81 +0,0 @@
|
||||
import { dropWorkspaceFull, setWorkspaceDisabled, type Workspace } from '@hcengineering/account'
|
||||
import core, { AccountRole, MeasureMetricsContext, SortingOrder, type MeasureContext } from '@hcengineering/core'
|
||||
import contact from '@hcengineering/model-contact'
|
||||
import { type StorageAdapter } from '@hcengineering/server-core'
|
||||
import { connect } from '@hcengineering/server-tool'
|
||||
import { type Db, type MongoClient } from 'mongodb'
|
||||
|
||||
export async function checkOrphanWorkspaces (
|
||||
ctx: MeasureContext,
|
||||
workspaces: Workspace[],
|
||||
transactorUrl: string,
|
||||
productId: string,
|
||||
cmd: { remove: boolean, disable: boolean },
|
||||
db: Db,
|
||||
client: MongoClient,
|
||||
storageAdapter: StorageAdapter,
|
||||
excludes: string[]
|
||||
): Promise<void> {
|
||||
for (const ws of workspaces) {
|
||||
if (excludes.includes(ws.workspace) || (ws.workspaceUrl != null && excludes.includes(ws.workspaceUrl))) {
|
||||
continue
|
||||
}
|
||||
if ((ws.accounts ?? []).length === 0) {
|
||||
// Potential orhpan workspace
|
||||
// Let's connect and check activity.
|
||||
const connection = await connect(transactorUrl, { name: ws.workspace, productId }, undefined, { admin: 'true' })
|
||||
|
||||
const accounts = await connection.findAll(contact.class.PersonAccount, {})
|
||||
const employees = await connection.findAll(contact.mixin.Employee, {})
|
||||
let activeOwners = 0
|
||||
for (const person of employees) {
|
||||
const account = accounts.find((it) => it.person === person._id)
|
||||
if (account !== undefined) {
|
||||
if (account.role === AccountRole.Owner && person.active) {
|
||||
activeOwners++
|
||||
}
|
||||
// console.log('-----------', person.name, person.active, account.email, account.role)
|
||||
}
|
||||
}
|
||||
|
||||
// Find last transaction index:
|
||||
const wspace = { name: ws.workspace, productId }
|
||||
const hasBucket = await storageAdapter.exists(ctx, wspace)
|
||||
const [lastTx] = await connection.findAll(
|
||||
core.class.Tx,
|
||||
{
|
||||
objectSpace: { $ne: core.space.Model },
|
||||
createdBy: { $nin: [core.account.System, core.account.ConfigUser] },
|
||||
modifiedBy: { $ne: core.account.System }
|
||||
},
|
||||
{ limit: 1, sort: { modifiedOn: SortingOrder.Descending } }
|
||||
)
|
||||
|
||||
await connection.close()
|
||||
const lastTxHours = Math.floor((Date.now() - (lastTx?.modifiedOn ?? 0)) / 1000 / 60 / 60)
|
||||
if (((activeOwners === 0 || lastTx == null) && lastTxHours > 1000) || !hasBucket) {
|
||||
const createdOn = (ws.createdOn ?? 0) !== 0 ? new Date(ws.createdOn).toDateString() : ''
|
||||
console.log(
|
||||
'Found orhpan workspace',
|
||||
`'${ws.workspaceName}' id: '${ws.workspace}' url:${ws.workspaceUrl} by: ${ws.createdBy ?? ''} on: '${createdOn}'`,
|
||||
lastTxHours + ' hours without modifications',
|
||||
hasBucket
|
||||
)
|
||||
if (cmd.disable) {
|
||||
await setWorkspaceDisabled(db, ws._id, true)
|
||||
}
|
||||
if (cmd.remove) {
|
||||
await dropWorkspaceFull(
|
||||
new MeasureMetricsContext('tool', {}),
|
||||
db,
|
||||
client,
|
||||
productId,
|
||||
null,
|
||||
ws.workspace,
|
||||
storageAdapter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -33,7 +33,8 @@ import {
|
||||
setAccountAdmin,
|
||||
setRole,
|
||||
UpgradeWorker,
|
||||
upgradeWorkspace
|
||||
upgradeWorkspace,
|
||||
type Workspace
|
||||
} from '@hcengineering/account'
|
||||
import { setMetadata } from '@hcengineering/platform'
|
||||
import {
|
||||
@ -86,7 +87,6 @@ import {
|
||||
restoreHrTaskTypesFromUpdates,
|
||||
restoreRecruitingTaskTypes
|
||||
} from './clean'
|
||||
import { checkOrphanWorkspaces } from './cleanOrphan'
|
||||
import { changeConfiguration } from './configuration'
|
||||
import { fixJsonMarkup } from './markup'
|
||||
import { fixMixinForeignAttributes, showMixinForeignAttributes } from './mixin'
|
||||
@ -394,31 +394,60 @@ export function devTool (
|
||||
)
|
||||
|
||||
program
|
||||
.command('remove-unused-workspaces')
|
||||
.command('list-unused-workspaces')
|
||||
.description(
|
||||
'remove unused workspaces, please pass --remove to really delete them. Without it will only mark them disabled'
|
||||
)
|
||||
.option('-r|--remove [remove]', 'Force remove', false)
|
||||
.option('-d|--disable [disable]', 'Force disable', false)
|
||||
.option('-e|--exclude [exclude]', 'A comma separated list of workspaces to exclude', '')
|
||||
.action(async (cmd: { remove: boolean, disable: boolean, exclude: string }) => {
|
||||
.option('-t|--timeout [timeout]', 'Timeout in days', '7')
|
||||
.action(async (cmd: { remove: boolean, disable: boolean, exclude: string, timeout: string }) => {
|
||||
const { mongodbUri } = prepareTools()
|
||||
await withDatabase(mongodbUri, async (db, client) => {
|
||||
const workspaces = await listWorkspacesPure(db, productId)
|
||||
const workspaces = new Map((await listWorkspacesPure(db, productId)).map((p) => [p._id.toString(), p]))
|
||||
|
||||
const accounts = await listAccounts(db)
|
||||
|
||||
const _timeout = parseInt(cmd.timeout) ?? 7
|
||||
|
||||
await withStorage(mongodbUri, async (adapter) => {
|
||||
// We need to update workspaces with missing workspaceUrl
|
||||
await checkOrphanWorkspaces(
|
||||
toolCtx,
|
||||
workspaces,
|
||||
transactorUrl,
|
||||
productId,
|
||||
cmd,
|
||||
db,
|
||||
client,
|
||||
adapter,
|
||||
cmd.exclude.split(',')
|
||||
)
|
||||
|
||||
for (const a of accounts) {
|
||||
const authored = a.workspaces
|
||||
.map((it) => workspaces.get(it.toString()))
|
||||
.filter((it) => it !== undefined && it.createdBy?.trim() === a.email?.trim()) as Workspace[]
|
||||
authored.sort((a, b) => b.lastVisit - a.lastVisit)
|
||||
if (authored.length > 0) {
|
||||
const lastLoginDays = Math.floor((Date.now() - a.lastVisit) / 1000 / 3600 / 24)
|
||||
toolCtx.info(a.email, {
|
||||
workspaces: a.workspaces.length,
|
||||
firstName: a.first,
|
||||
lastName: a.last,
|
||||
lastLoginDays
|
||||
})
|
||||
for (const ws of authored) {
|
||||
const lastVisitDays = Math.floor((Date.now() - ws.lastVisit) / 1000 / 3600 / 24)
|
||||
|
||||
if (lastVisitDays > _timeout) {
|
||||
toolCtx.warn(' --- unused', {
|
||||
url: ws.workspaceUrl,
|
||||
id: ws.workspace,
|
||||
lastVisitDays
|
||||
})
|
||||
if (cmd.remove) {
|
||||
await dropWorkspaceFull(toolCtx, db, client, productId, null, ws.workspace, adapter)
|
||||
}
|
||||
} else {
|
||||
toolCtx.warn(' +++ used', {
|
||||
url: ws.workspaceUrl,
|
||||
id: ws.workspace,
|
||||
createdBy: ws.createdBy,
|
||||
lastVisitDays
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user