Browse Source

feat(nocodb): copy paste link cell api

pull/7558/head
Ramesh Mane 10 months ago
parent
commit
16da34f7fc
  1. 4
      packages/nocodb/src/controllers/data-table.controller.ts
  2. 5
      packages/nocodb/src/db/BaseModelSqlv2.ts
  3. 7
      packages/nocodb/src/schema/swagger.json
  4. 146
      packages/nocodb/src/services/data-table.service.ts

4
packages/nocodb/src/controllers/data-table.controller.ts

@ -194,6 +194,7 @@ export class DataTableController {
});
}
// todo: naming
@Post(['/api/v2/tables/:modelId/links/:columnId/records'])
@Acl('nestedDataLinkUnlink')
async nestedLinkUnlink(
@ -205,10 +206,9 @@ export class DataTableController {
data: {
operation: 'copy' | 'paste';
rowId: string;
fk_related_model_id: string;
}[],
) {
console.log('data', data);
return await this.dataTableService.nestedLinkUnlink({
modelId,
query: req.query,

5
packages/nocodb/src/db/BaseModelSqlv2.ts

@ -1330,6 +1330,7 @@ class BaseModelSqlv2 {
public async mmList(
{ colId, parentId },
args: { limit?; offset?; fieldsSet?: Set<string> } = {},
selectAllRecords = false
) {
const { where, sort, ...rest } = this._getListArgs(args as any);
const relColumn = (await this.model.getColumns()).find(
@ -1375,7 +1376,9 @@ class BaseModelSqlv2 {
await this.applySortAndFilter({ table: childTable, where, qb, sort });
// todo: sanitize
qb.limit(+rest?.limit || 25);
if (!selectAllRecords) {
qb.limit(+rest?.limit || 25);
}
qb.offset(+rest?.offset || 0);
const children = await this.execAndParse(qb, await childTable.getColumns());

7
packages/nocodb/src/schema/swagger.json

@ -23237,6 +23237,8 @@
},
"NestedLinkUnlinkReq": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {
"type": "object",
"properties": {
@ -23246,9 +23248,12 @@
},
"rowId": {
"type": "string"
},
"fk_related_model_id":{
"type": "string"
}
},
"required": ["operation", "rowId"]
"required": ["operation", "rowId", "fk_related_model_id"]
}
}
},

146
packages/nocodb/src/services/data-table.service.ts

@ -444,6 +444,7 @@ export class DataTableService {
return true;
}
// todo: naming & optimizing
async nestedLinkUnlink(param: {
cookie: any;
viewId: string;
@ -453,6 +454,7 @@ export class DataTableService {
data: {
operation: 'copy' | 'paste';
rowId: string;
fk_related_model_id: string;
}[];
}) {
validatePayload(
@ -460,7 +462,124 @@ export class DataTableService {
param.data,
);
return true;
if (
param.data[0]?.fk_related_model_id !== param.data[1]?.fk_related_model_id
) {
throw new Error(
'The operation is not supported on different fk_related_model_id',
);
}
const operationMap = param.data.reduce(
(map, p) => {
map[p.operation] = p;
return map;
},
{} as Record<
'copy' | 'paste',
{
operation: 'copy' | 'paste';
rowId: string;
fk_related_model_id: string;
}
>,
);
const { model, view } = await this.getModelAndView(param);
const source = await Source.get(model.source_id);
const baseModel = await Model.getBaseModelSQL({
id: model.id,
viewId: view?.id,
dbDriver: await NcConnectionMgrv2.get(source),
});
const existsCopyRow = await baseModel.exist(operationMap.copy.rowId);
const existsPasteRow = await baseModel.exist(operationMap.paste.rowId);
if (!existsCopyRow && !existsPasteRow) {
NcError.notFound(
`Record with id '${operationMap.copy.rowId}' and '${operationMap.paste.rowId}' not found`,
);
} else if (!existsCopyRow) {
NcError.notFound(`Record with id '${operationMap.copy.rowId}' not found`);
} else if (!existsPasteRow) {
NcError.notFound(
`Record with id '${operationMap.paste.rowId}' not found`,
);
}
const column = await this.getColumn(param);
const colOptions = await column.getColOptions<LinkToAnotherRecordColumn>();
const relatedModel = await colOptions.getRelatedTable();
await relatedModel.getColumns();
if (colOptions.type !== RelationTypes.MANY_TO_MANY) return;
const { ast, dependencyFields } = await getAst({
model: relatedModel,
query: param.query,
extractOnlyPrimaries: !(param.query?.f || param.query?.fields),
});
const listArgs: any = dependencyFields;
try {
listArgs.filterArr = JSON.parse(listArgs.filterArrJson);
} catch (e) {}
try {
listArgs.sortArr = JSON.parse(listArgs.sortArrJson);
} catch (e) {}
const [copiedCellNestedList, pasteCellNestedList] = await Promise.all([
baseModel.mmList(
{
colId: column.id,
parentId: operationMap.copy.rowId,
},
listArgs as any,
true,
),
baseModel.mmList(
{
colId: column.id,
parentId: operationMap.paste.rowId,
},
listArgs as any,
true,
),
]);
const filteredRowsToLink = this.filterAndMapRows(
copiedCellNestedList,
pasteCellNestedList,
relatedModel.primaryKeys,
);
const filteredRowsToUnlink = this.filterAndMapRows(
pasteCellNestedList,
copiedCellNestedList,
relatedModel.primaryKeys,
);
await Promise.all([
baseModel.addLinks({
colId: column.id,
childIds: filteredRowsToLink,
rowId: operationMap.paste.rowId,
cookie: param.cookie,
}),
baseModel.removeLinks({
colId: column.id,
childIds: filteredRowsToUnlink,
rowId: operationMap.paste.rowId,
cookie: param.cookie,
}),
]);
return { link: filteredRowsToLink, unlink: filteredRowsToUnlink };
}
private validateIds(rowIds: any[] | any) {
@ -485,4 +604,29 @@ export class DataTableService {
NcError.unprocessableEntity('Invalid row id ' + rowIds);
}
}
private filterAndMapRows(
sourceList: Record<string, any>[],
targetList: Record<string, any>[],
primaryKeys: Column<any>[],
): Record<string, any>[] {
return sourceList
.filter(
(sourceRow: Record<string, any>) =>
!targetList.some((targetRow: Record<string, any>) =>
primaryKeys.every(
(key) =>
sourceRow[key.title || key.column_name] ===
targetRow[key.title || key.column_name],
),
),
)
.map((item: Record<string, any>) =>
primaryKeys.reduce((acc, key) => {
acc[key.title || key.column_name] =
item[key.title || key.column_name];
return acc;
}, {} as Record<string, any>),
);
}
}

Loading…
Cancel
Save