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,
|
setAccountAdmin,
|
||||||
setRole,
|
setRole,
|
||||||
UpgradeWorker,
|
UpgradeWorker,
|
||||||
upgradeWorkspace
|
upgradeWorkspace,
|
||||||
|
type Workspace
|
||||||
} from '@hcengineering/account'
|
} from '@hcengineering/account'
|
||||||
import { setMetadata } from '@hcengineering/platform'
|
import { setMetadata } from '@hcengineering/platform'
|
||||||
import {
|
import {
|
||||||
@ -86,7 +87,6 @@ import {
|
|||||||
restoreHrTaskTypesFromUpdates,
|
restoreHrTaskTypesFromUpdates,
|
||||||
restoreRecruitingTaskTypes
|
restoreRecruitingTaskTypes
|
||||||
} from './clean'
|
} from './clean'
|
||||||
import { checkOrphanWorkspaces } from './cleanOrphan'
|
|
||||||
import { changeConfiguration } from './configuration'
|
import { changeConfiguration } from './configuration'
|
||||||
import { fixJsonMarkup } from './markup'
|
import { fixJsonMarkup } from './markup'
|
||||||
import { fixMixinForeignAttributes, showMixinForeignAttributes } from './mixin'
|
import { fixMixinForeignAttributes, showMixinForeignAttributes } from './mixin'
|
||||||
@ -394,31 +394,60 @@ export function devTool (
|
|||||||
)
|
)
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('remove-unused-workspaces')
|
.command('list-unused-workspaces')
|
||||||
.description(
|
.description(
|
||||||
'remove unused workspaces, please pass --remove to really delete them. Without it will only mark them disabled'
|
'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('-r|--remove [remove]', 'Force remove', false)
|
||||||
.option('-d|--disable [disable]', 'Force disable', false)
|
.option('-t|--timeout [timeout]', 'Timeout in days', '7')
|
||||||
.option('-e|--exclude [exclude]', 'A comma separated list of workspaces to exclude', '')
|
.action(async (cmd: { remove: boolean, disable: boolean, exclude: string, timeout: string }) => {
|
||||||
.action(async (cmd: { remove: boolean, disable: boolean, exclude: string }) => {
|
|
||||||
const { mongodbUri } = prepareTools()
|
const { mongodbUri } = prepareTools()
|
||||||
await withDatabase(mongodbUri, async (db, client) => {
|
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) => {
|
await withStorage(mongodbUri, async (adapter) => {
|
||||||
// We need to update workspaces with missing workspaceUrl
|
// We need to update workspaces with missing workspaceUrl
|
||||||
await checkOrphanWorkspaces(
|
|
||||||
toolCtx,
|
for (const a of accounts) {
|
||||||
workspaces,
|
const authored = a.workspaces
|
||||||
transactorUrl,
|
.map((it) => workspaces.get(it.toString()))
|
||||||
productId,
|
.filter((it) => it !== undefined && it.createdBy?.trim() === a.email?.trim()) as Workspace[]
|
||||||
cmd,
|
authored.sort((a, b) => b.lastVisit - a.lastVisit)
|
||||||
db,
|
if (authored.length > 0) {
|
||||||
client,
|
const lastLoginDays = Math.floor((Date.now() - a.lastVisit) / 1000 / 3600 / 24)
|
||||||
adapter,
|
toolCtx.info(a.email, {
|
||||||
cmd.exclude.split(',')
|
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