Browse Source

feat: improved duplicate logic

Signed-off-by: mertmit <mertmit99@gmail.com>
feat/export-nest
mertmit 1 year ago
parent
commit
63a7b9a5c3
  1. 38
      packages/nc-gui/pages/index/index/index.vue
  2. 36
      packages/nocodb-nest/src/modules/jobs/export-import/duplicate.controller.ts
  3. 60
      packages/nocodb-nest/src/modules/jobs/export-import/duplicate.processor.ts

38
packages/nc-gui/pages/index/index/index.vue

@ -39,9 +39,17 @@ const filterQuery = ref('')
const projects = ref<ProjectType[]>() const projects = ref<ProjectType[]>()
const activePage = ref(1)
const pageChange = (p: number) => {
activePage.value = p
}
const loadProjects = async () => { const loadProjects = async () => {
const lastPage = activePage.value
const response = await api.project.list({}) const response = await api.project.list({})
projects.value = response.list projects.value = response.list
activePage.value = lastPage
} }
const filteredProjects = computed( const filteredProjects = computed(
@ -85,12 +93,14 @@ const duplicateProject = (project: ProjectType) => {
try { try {
const jobData = await api.project.duplicate(project.id as string) const jobData = await api.project.duplicate(project.id as string)
await loadProjects()
$events.subscribe(jobData.name, jobData.id, async (data: { status: string }) => { $events.subscribe(jobData.name, jobData.id, async (data: { status: string }) => {
console.log('dataCB', data) if (data.status === 'completed') {
if (data.status === 'completed' || data.status === 'refresh') {
await loadProjects() await loadProjects()
} else if (data.status === 'failed') { } else if (data.status === 'failed') {
message.error('Failed to duplicate project') message.error('Failed to duplicate project')
await loadProjects()
} }
}) })
@ -224,7 +234,7 @@ const copyProjectMeta = async () => {
v-else v-else
:custom-row="customRow" :custom-row="customRow"
:data-source="filteredProjects" :data-source="filteredProjects"
:pagination="{ position: ['bottomCenter'] }" :pagination="{ 'position': ['bottomCenter'], 'current': activePage, 'onUpdate:current': pageChange }"
:table-layout="md ? 'auto' : 'fixed'" :table-layout="md ? 'auto' : 'fixed'"
> >
<template #emptyText> <template #emptyText>
@ -301,19 +311,27 @@ const copyProjectMeta = async () => {
@click.stop="navigateTo(`/${text}`)" @click.stop="navigateTo(`/${text}`)"
/> />
<component
:is="iconMap.copy"
v-e="['c:project:duplicate']"
class="nc-action-btn"
@click.stop="duplicateProject(record)"
/>
<component <component
:is="iconMap.delete" :is="iconMap.delete"
class="nc-action-btn" class="nc-action-btn"
:data-testid="`delete-project-${record.title}`" :data-testid="`delete-project-${record.title}`"
@click.stop="deleteProject(record)" @click.stop="deleteProject(record)"
/> />
<a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-import-menu" @click.stop>
<GeneralIcon icon="threeDotVertical" class="nc-import-menu outline-0" />
<template #overlay>
<a-menu class="!py-0 rounded text-sm">
<a-menu-item key="duplicate" v-e="['c:project:duplicate']" @click.stop="duplicateProject(record)">
<div class="color-transition nc-project-menu-item group">
<GeneralIcon icon="copy" class="group-hover:text-accent" />
Duplicate
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div> </div>
</template> </template>
</a-table-column> </a-table-column>

36
packages/nocodb-nest/src/modules/jobs/export-import/duplicate.controller.ts

@ -14,6 +14,9 @@ import {
Acl, Acl,
ExtractProjectIdMiddleware, ExtractProjectIdMiddleware,
} from 'src/middlewares/extract-project-id/extract-project-id.middleware'; } from 'src/middlewares/extract-project-id/extract-project-id.middleware';
import { ProjectsService } from 'src/services/projects.service';
import { Base, Project } from 'src/models';
import { generateUniqueName } from 'src/helpers/exportImportHelpers';
import { QueueService } from '../fallback-queue.service'; import { QueueService } from '../fallback-queue.service';
@Controller() @Controller()
@ -23,6 +26,7 @@ export class DuplicateController {
constructor( constructor(
@InjectQueue('jobs') private readonly jobsQueue: Queue, @InjectQueue('jobs') private readonly jobsQueue: Queue,
private readonly fallbackQueueService: QueueService, private readonly fallbackQueueService: QueueService,
private readonly projectsService: ProjectsService,
) { ) {
this.activeQueue = process.env.NC_REDIS_URL this.activeQueue = process.env.NC_REDIS_URL
? this.jobsQueue ? this.jobsQueue
@ -37,14 +41,42 @@ export class DuplicateController {
@Param('projectId') projectId: string, @Param('projectId') projectId: string,
@Param('baseId') baseId?: string, @Param('baseId') baseId?: string,
) { ) {
const project = await Project.get(projectId);
if (!project) {
throw new Error(`Project not found for id '${projectId}'`);
}
const base = baseId
? await Base.get(baseId)
: (await project.getBases())[0];
if (!base) {
throw new Error(`Base not found!`);
}
const projects = await Project.list({});
const uniqueTitle = generateUniqueName(
`${project.title} copy`,
projects.map((p) => p.title),
);
const dupProject = await this.projectsService.projectCreate({
project: { title: uniqueTitle, status: 'job' },
user: { id: req.user.id },
});
const job = await this.activeQueue.add('duplicate', { const job = await this.activeQueue.add('duplicate', {
projectId, project,
baseId, base,
dupProject,
req: { req: {
user: req.user, user: req.user,
clientIp: req.clientIp, clientIp: req.clientIp,
}, },
}); });
return { id: job.id, name: job.name }; return { id: job.id, name: job.name };
} }
} }

60
packages/nocodb-nest/src/modules/jobs/export-import/duplicate.processor.ts

@ -6,15 +6,12 @@ import {
Process, Process,
Processor, Processor,
} from '@nestjs/bull'; } from '@nestjs/bull';
import { Base, Column, Model, Project } from 'src/models'; import { Column, Model } from 'src/models';
import { Job } from 'bull'; import { Job } from 'bull';
import { ProjectsService } from 'src/services/projects.service'; import { ProjectsService } from 'src/services/projects.service';
import boxen from 'boxen'; import boxen from 'boxen';
import papaparse from 'papaparse'; import papaparse from 'papaparse';
import { import { findWithIdentifier } from 'src/helpers/exportImportHelpers';
findWithIdentifier,
generateUniqueName,
} from 'src/helpers/exportImportHelpers';
import { BulkDataAliasService } from 'src/services/bulk-data-alias.service'; import { BulkDataAliasService } from 'src/services/bulk-data-alias.service';
import { UITypes } from 'nocodb-sdk'; import { UITypes } from 'nocodb-sdk';
import { forwardRef, Inject } from '@nestjs/common'; import { forwardRef, Inject } from '@nestjs/common';
@ -76,6 +73,9 @@ export class DuplicateProcessor {
@Process('duplicate') @Process('duplicate')
async duplicateBase(job: Job) { async duplicateBase(job: Job) {
const { project, base, dupProject, req } = job.data;
try {
let start = process.hrtime(); let start = process.hrtime();
const debugLog = function (...args: any[]) { const debugLog = function (...args: any[]) {
@ -91,23 +91,7 @@ export class DuplicateProcessor {
start = process.hrtime(); start = process.hrtime();
}; };
const param: { projectId: string; baseId?: string; req: any } = job.data; const user = (req as any).user;
const user = (param.req as any).user;
const project = await Project.get(param.projectId);
if (!project) {
throw new Error(`Base not found for id '${param.baseId}'`);
}
const base = param?.baseId
? await Base.get(param.baseId)
: (await project.getBases())[0];
if (!base) {
throw new Error(`Base not found!`);
}
const models = (await base.getModels()).filter( const models = (await base.getModels()).filter(
(m) => !m.mm && m.type === 'table', (m) => !m.mm && m.type === 'table',
@ -123,24 +107,6 @@ export class DuplicateProcessor {
throw new Error(`Export failed for base '${base.id}'`); throw new Error(`Export failed for base '${base.id}'`);
} }
const projects = await Project.list({});
const uniqueTitle = generateUniqueName(
`${project.title} copy`,
projects.map((p) => p.title),
);
const dupProject = await this.projectsService.projectCreate({
project: { title: uniqueTitle, status: 'job' },
user: { id: user.id },
});
this.jobsGateway.jobStatus({
name: job.name,
id: job.id.toString(),
status: 'refresh',
});
const dupBaseId = dupProject.bases[0].id; const dupBaseId = dupProject.bases[0].id;
elapsedTime('projectCreate'); elapsedTime('projectCreate');
@ -150,7 +116,7 @@ export class DuplicateProcessor {
projectId: dupProject.id, projectId: dupProject.id,
baseId: dupBaseId, baseId: dupBaseId,
data: exportedModels, data: exportedModels,
req: param.req, req: req,
}); });
elapsedTime('importModels'); elapsedTime('importModels');
@ -181,7 +147,9 @@ export class DuplicateProcessor {
const headers: string[] = []; const headers: string[] = [];
let chunk = []; let chunk = [];
const model = await Model.get(findWithIdentifier(idMap, sourceModel.id)); const model = await Model.get(
findWithIdentifier(idMap, sourceModel.id),
);
await new Promise((resolve) => { await new Promise((resolve) => {
papaparse.parse(dataStream, { papaparse.parse(dataStream, {
@ -366,5 +334,13 @@ export class DuplicateProcessor {
status: null, status: null,
}, },
}); });
} catch (e) {
if (dupProject?.id) {
await this.projectsService.projectSoftDelete({
projectId: dupProject.id,
});
}
throw e;
}
} }
} }

Loading…
Cancel
Save