mirror of
https://github.com/lingble/twenty.git
synced 2025-11-02 13:47:55 +00:00
0.2.0 cleaning script (#2379)
* Move question to questions folder * Aggregate update result functions * Use lodash to compare list of objects * Remove favorites from tables * Add a workspace parameter * Move question after result log * Improve logging * Code review returns * Add only lodash.isequal
This commit is contained in:
@@ -87,6 +87,7 @@
|
|||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"lodash.camelcase": "^4.3.0",
|
"lodash.camelcase": "^4.3.0",
|
||||||
"lodash.isempty": "^4.4.0",
|
"lodash.isempty": "^4.4.0",
|
||||||
|
"lodash.isequal": "^4.5.0",
|
||||||
"lodash.isobject": "^3.0.2",
|
"lodash.isobject": "^3.0.2",
|
||||||
"lodash.kebabcase": "^4.1.1",
|
"lodash.kebabcase": "^4.1.1",
|
||||||
"lodash.merge": "^4.6.2",
|
"lodash.merge": "^4.6.2",
|
||||||
@@ -123,6 +124,7 @@
|
|||||||
"@types/graphql-upload": "^8.0.12",
|
"@types/graphql-upload": "^8.0.12",
|
||||||
"@types/jest": "28.1.8",
|
"@types/jest": "28.1.8",
|
||||||
"@types/lodash.isempty": "^4.4.7",
|
"@types/lodash.isempty": "^4.4.7",
|
||||||
|
"@types/lodash.isequal": "^4.5.7",
|
||||||
"@types/lodash.isobject": "^3.0.7",
|
"@types/lodash.isobject": "^3.0.7",
|
||||||
"@types/lodash.kebabcase": "^4.1.7",
|
"@types/lodash.kebabcase": "^4.1.7",
|
||||||
"@types/lodash.snakecase": "^4.1.7",
|
"@types/lodash.snakecase": "^4.1.7",
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import {
|
|||||||
InquirerService,
|
InquirerService,
|
||||||
Option,
|
Option,
|
||||||
} from 'nest-commander';
|
} from 'nest-commander';
|
||||||
|
import isEqual from 'lodash.isequal';
|
||||||
|
|
||||||
import { PrismaService } from 'src/database/prisma.service';
|
import { PrismaService } from 'src/database/prisma.service';
|
||||||
import peopleSeed from 'src/core/person/seed-data/people.json';
|
import peopleSeed from 'src/core/person/seed-data/people.json';
|
||||||
import companiesSeed from 'src/core/company/seed-data/companies.json';
|
import companiesSeed from 'src/core/company/seed-data/companies.json';
|
||||||
import pipelineStagesSeed from 'src/core/pipeline/seed-data/pipeline-stages.json';
|
import pipelineStagesSeed from 'src/core/pipeline/seed-data/pipeline-stages.json';
|
||||||
import pipelinesSeed from 'src/core/pipeline/seed-data/sales-pipeline.json';
|
import pipelinesSeed from 'src/core/pipeline/seed-data/sales-pipeline.json';
|
||||||
import { arraysEqual } from 'src/utils/equal';
|
|
||||||
import { WorkspaceService } from 'src/core/workspace/services/workspace.service';
|
import { WorkspaceService } from 'src/core/workspace/services/workspace.service';
|
||||||
|
|
||||||
interface DataCleanInactiveOptions {
|
interface DataCleanInactiveOptions {
|
||||||
@@ -18,6 +18,7 @@ interface DataCleanInactiveOptions {
|
|||||||
sameAsSeedDays?: number;
|
sameAsSeedDays?: number;
|
||||||
dryRun?: boolean;
|
dryRun?: boolean;
|
||||||
confirmation?: boolean;
|
confirmation?: boolean;
|
||||||
|
workspaceId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ActivityReport {
|
interface ActivityReport {
|
||||||
@@ -48,6 +49,14 @@ export class DataCleanInactiveCommand extends CommandRunner {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Option({
|
||||||
|
flags: '-w, --workspaceId [workspace id]',
|
||||||
|
description: 'Specific workspaceId to apply cleaning',
|
||||||
|
})
|
||||||
|
parseWorkspace(val: string): string {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
@Option({
|
@Option({
|
||||||
flags: '-d, --days [inactive days threshold]',
|
flags: '-d, --days [inactive days threshold]',
|
||||||
description: 'Inactive days threshold',
|
description: 'Inactive days threshold',
|
||||||
@@ -82,20 +91,20 @@ export class DataCleanInactiveCommand extends CommandRunner {
|
|||||||
!name.startsWith('$') &&
|
!name.startsWith('$') &&
|
||||||
!name.includes('user') &&
|
!name.includes('user') &&
|
||||||
!name.includes('refreshToken') &&
|
!name.includes('refreshToken') &&
|
||||||
!name.includes('workspace'),
|
!name.includes('workspace') &&
|
||||||
|
!name.includes('favorite'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTableMaxUpdatedAt(table, workspace) {
|
async getTableMaxUpdatedAt(table, workspace) {
|
||||||
try {
|
return await this.prismaService.client[table].aggregate({
|
||||||
return await this.prismaService.client[table].aggregate({
|
_max: { updatedAt: true },
|
||||||
_max: { updatedAt: true },
|
where: { workspaceId: { equals: workspace.id } },
|
||||||
where: { workspaceId: { equals: workspace.id } },
|
});
|
||||||
});
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateResult(result, workspace, newUpdatedAt) {
|
async addMaxUpdatedAtToWorkspaces(result, workspace, table) {
|
||||||
|
const newUpdatedAt = await this.getTableMaxUpdatedAt(table, workspace);
|
||||||
if (!result.activityReport[workspace.id]) {
|
if (!result.activityReport[workspace.id]) {
|
||||||
result.activityReport[workspace.id] = {
|
result.activityReport[workspace.id] = {
|
||||||
displayName: workspace.displayName,
|
displayName: workspace.displayName,
|
||||||
@@ -147,10 +156,10 @@ export class DataCleanInactiveCommand extends CommandRunner {
|
|||||||
where: { workspaceId: { equals: workspace.id } },
|
where: { workspaceId: { equals: workspace.id } },
|
||||||
});
|
});
|
||||||
if (
|
if (
|
||||||
arraysEqual(people, peopleSeed) &&
|
isEqual(people, peopleSeed) &&
|
||||||
arraysEqual(companies, companiesSeed) &&
|
isEqual(companies, companiesSeed) &&
|
||||||
arraysEqual(pipelineStages, pipelineStagesSeed) &&
|
isEqual(pipelineStages, pipelineStagesSeed) &&
|
||||||
arraysEqual(pipelines, [pipelinesSeed])
|
isEqual(pipelines, [pipelinesSeed])
|
||||||
) {
|
) {
|
||||||
result.sameAsSeedWorkspaces[workspace.id] = {
|
result.sameAsSeedWorkspaces[workspace.id] = {
|
||||||
displayName: workspace.displayName,
|
displayName: workspace.displayName,
|
||||||
@@ -158,14 +167,18 @@ export class DataCleanInactiveCommand extends CommandRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async findInactiveWorkspaces(result) {
|
async findInactiveWorkspaces(result, options) {
|
||||||
const workspaces = await this.prismaService.client.workspace.findMany();
|
const where = options.workspaceId
|
||||||
|
? { id: { equals: options.workspaceId } }
|
||||||
|
: {};
|
||||||
|
const workspaces = await this.prismaService.client.workspace.findMany({
|
||||||
|
where,
|
||||||
|
});
|
||||||
const tables = this.getRelevantTables();
|
const tables = this.getRelevantTables();
|
||||||
for (const workspace of workspaces) {
|
for (const workspace of workspaces) {
|
||||||
await this.detectWorkspacesWithSeedDataOnly(result, workspace);
|
await this.detectWorkspacesWithSeedDataOnly(result, workspace);
|
||||||
for (const table of tables) {
|
for (const table of tables) {
|
||||||
const maxUpdatedAt = await this.getTableMaxUpdatedAt(table, workspace);
|
await this.addMaxUpdatedAtToWorkspaces(result, workspace, table);
|
||||||
this.updateResult(result, workspace, maxUpdatedAt);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,26 +208,47 @@ export class DataCleanInactiveCommand extends CommandRunner {
|
|||||||
console.log('Deleting inactive workspaces');
|
console.log('Deleting inactive workspaces');
|
||||||
}
|
}
|
||||||
for (const workspaceId in result.activityReport) {
|
for (const workspaceId in result.activityReport) {
|
||||||
|
process.stdout.write(`- deleting ${workspaceId} ...`);
|
||||||
await this.workspaceService.deleteWorkspace({
|
await this.workspaceService.deleteWorkspace({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
});
|
});
|
||||||
console.log(`- ${workspaceId} deleted`);
|
console.log(' done!');
|
||||||
}
|
}
|
||||||
if (Object.keys(result.sameAsSeedWorkspaces).length) {
|
if (Object.keys(result.sameAsSeedWorkspaces).length) {
|
||||||
console.log('Deleting same as Seed workspaces');
|
console.log('Deleting same as Seed workspaces');
|
||||||
}
|
}
|
||||||
for (const workspaceId in result.sameAsSeedWorkspaces) {
|
for (const workspaceId in result.sameAsSeedWorkspaces) {
|
||||||
|
process.stdout.write(`- deleting ${workspaceId} ...`);
|
||||||
await this.workspaceService.deleteWorkspace({
|
await this.workspaceService.deleteWorkspace({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
});
|
});
|
||||||
console.log(`- ${workspaceId} deleted`);
|
console.log(' done!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
displayResults(result) {
|
||||||
|
const workspacesToDelete = new Set();
|
||||||
|
for (const workspaceId in result.activityReport) {
|
||||||
|
workspacesToDelete.add(workspaceId);
|
||||||
|
}
|
||||||
|
for (const workspaceId in result.sameAsSeedWorkspaces) {
|
||||||
|
workspacesToDelete.add(workspaceId);
|
||||||
|
}
|
||||||
|
console.log(`${workspacesToDelete.size} workspace(s) will be deleted:`);
|
||||||
|
console.log(result);
|
||||||
|
}
|
||||||
|
|
||||||
async run(
|
async run(
|
||||||
_passedParam: string[],
|
_passedParam: string[],
|
||||||
options: DataCleanInactiveOptions,
|
options: DataCleanInactiveOptions,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const result: DataCleanResults = {
|
||||||
|
activityReport: {},
|
||||||
|
sameAsSeedWorkspaces: {},
|
||||||
|
};
|
||||||
|
await this.findInactiveWorkspaces(result, options);
|
||||||
|
this.filterResults(result, options);
|
||||||
|
this.displayResults(result);
|
||||||
if (!options.dryRun) {
|
if (!options.dryRun) {
|
||||||
options = await this.inquiererService.ask('confirm', options);
|
options = await this.inquiererService.ask('confirm', options);
|
||||||
if (!options.confirmation) {
|
if (!options.confirmation) {
|
||||||
@@ -222,16 +256,8 @@ export class DataCleanInactiveCommand extends CommandRunner {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const result: DataCleanResults = {
|
|
||||||
activityReport: {},
|
|
||||||
sameAsSeedWorkspaces: {},
|
|
||||||
};
|
|
||||||
await this.findInactiveWorkspaces(result);
|
|
||||||
this.filterResults(result, options);
|
|
||||||
if (!options.dryRun) {
|
if (!options.dryRun) {
|
||||||
await this.delete(result);
|
await this.delete(result);
|
||||||
} else {
|
|
||||||
console.log(result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { DataCleanInactiveCommand } from 'src/database/commands/clean-inactive-workspaces.command';
|
import { DataCleanInactiveCommand } from 'src/database/commands/clean-inactive-workspaces.command';
|
||||||
import { ConfirmationQuestion } from 'src/database/commands/confirmation.question';
|
import { ConfirmationQuestion } from 'src/database/commands/questions/confirmation.question';
|
||||||
import { WorkspaceService } from 'src/core/workspace/services/workspace.service';
|
import { WorkspaceService } from 'src/core/workspace/services/workspace.service';
|
||||||
import { PipelineModule } from 'src/core/pipeline/pipeline.module';
|
import { PipelineModule } from 'src/core/pipeline/pipeline.module';
|
||||||
import { CompanyModule } from 'src/core/company/company.module';
|
import { CompanyModule } from 'src/core/company/company.module';
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
//https://stackoverflow.com/questions/27030/comparing-arrays-of-objects-in-javascript
|
|
||||||
export const objectsEqual = (o1, o2) => {
|
|
||||||
return (
|
|
||||||
Object.keys(o1).length === Object.keys(o2).length &&
|
|
||||||
Object.keys(o1).every((p) => o1[p] === o2[p])
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const arraysEqual = (a1, a2) => {
|
|
||||||
return (
|
|
||||||
a1.length === a2.length && a1.every((o, idx) => objectsEqual(o, a2[idx]))
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -3056,6 +3056,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/lodash" "*"
|
"@types/lodash" "*"
|
||||||
|
|
||||||
|
"@types/lodash.isequal@^4.5.7":
|
||||||
|
version "4.5.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash.isequal/-/lodash.isequal-4.5.7.tgz#8d95603728dc7a8070c37d12d98b27b4d665849a"
|
||||||
|
integrity sha512-UJQsb7aW8JU/h3fivQXVRDp9EKi98T9iQcVeTXBxcD4jApgGgbrET/0hVS6vH/YoYpqkcToMU5fSNPEiWVZgDg==
|
||||||
|
dependencies:
|
||||||
|
"@types/lodash" "*"
|
||||||
|
|
||||||
"@types/lodash.isobject@^3.0.7":
|
"@types/lodash.isobject@^3.0.7":
|
||||||
version "3.0.7"
|
version "3.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash.isobject/-/lodash.isobject-3.0.7.tgz#8a37beea56512f0ae86f8d48ea01e2ea9b79c185"
|
resolved "https://registry.yarnpkg.com/@types/lodash.isobject/-/lodash.isobject-3.0.7.tgz#8a37beea56512f0ae86f8d48ea01e2ea9b79c185"
|
||||||
@@ -5885,20 +5892,13 @@ graphql-subscriptions@2.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
iterall "^1.3.0"
|
iterall "^1.3.0"
|
||||||
|
|
||||||
graphql-tag@2.12.6, graphql-tag@^2.11.0:
|
graphql-tag@2.12.6, graphql-tag@^2.11.0, graphql-tag@^2.12.6:
|
||||||
version "2.12.6"
|
version "2.12.6"
|
||||||
resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz"
|
resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz"
|
||||||
integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==
|
integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^2.1.0"
|
tslib "^2.1.0"
|
||||||
|
|
||||||
graphql-tag@^2.12.6:
|
|
||||||
version "2.12.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1"
|
|
||||||
integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==
|
|
||||||
dependencies:
|
|
||||||
tslib "^2.1.0"
|
|
||||||
|
|
||||||
graphql-type-json@^0.3.2:
|
graphql-type-json@^0.3.2:
|
||||||
version "0.3.2"
|
version "0.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/graphql-type-json/-/graphql-type-json-0.3.2.tgz#f53a851dbfe07bd1c8157d24150064baab41e115"
|
resolved "https://registry.yarnpkg.com/graphql-type-json/-/graphql-type-json-0.3.2.tgz#f53a851dbfe07bd1c8157d24150064baab41e115"
|
||||||
@@ -7046,6 +7046,11 @@ lodash.isempty@^4.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e"
|
resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e"
|
||||||
integrity sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==
|
integrity sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==
|
||||||
|
|
||||||
|
lodash.isequal@^4.5.0:
|
||||||
|
version "4.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||||
|
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
|
||||||
|
|
||||||
lodash.isobject@^3.0.2:
|
lodash.isobject@^3.0.2:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d"
|
resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d"
|
||||||
|
|||||||
Reference in New Issue
Block a user