mirror of https://github.com/nocodb/nocodb
github-actions[bot]
3 years ago
committed by
GitHub
10 changed files with 411 additions and 196 deletions
@ -0,0 +1,252 @@ |
|||||||
|
import { AirtableBase } from 'airtable/lib/airtable_base'; |
||||||
|
import { Api, RelationTypes, TableType, UITypes } from 'nocodb-sdk'; |
||||||
|
|
||||||
|
const BULK_DATA_BATCH_SIZE = 2000; |
||||||
|
const ASSOC_BULK_DATA_BATCH_SIZE = 5000; |
||||||
|
|
||||||
|
async function readAllData({ |
||||||
|
table, |
||||||
|
fields, |
||||||
|
base, |
||||||
|
logBasic = _str => {}, |
||||||
|
triggerThreshold = BULK_DATA_BATCH_SIZE, |
||||||
|
onThreshold = async _rec => {} |
||||||
|
}: { |
||||||
|
table: { title?: string }; |
||||||
|
fields?; |
||||||
|
base: AirtableBase; |
||||||
|
logBasic?: (string) => void; |
||||||
|
logDetailed?: (string) => void; |
||||||
|
triggerThreshold?: number; |
||||||
|
onThreshold?: ( |
||||||
|
records: Array<{ fields: any; id: string }>, |
||||||
|
allRecords?: Array<{ fields: any; id: string }> |
||||||
|
) => Promise<void>; |
||||||
|
}): Promise<Array<any>> { |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
const data = []; |
||||||
|
let thresholdCbkData = []; |
||||||
|
|
||||||
|
const selectParams: any = { |
||||||
|
pageSize: 100 |
||||||
|
}; |
||||||
|
|
||||||
|
if (fields) selectParams.fields = fields; |
||||||
|
const insertJobs: Promise<any>[] = []; |
||||||
|
|
||||||
|
base(table.title) |
||||||
|
.select(selectParams) |
||||||
|
.eachPage( |
||||||
|
async function page(records, fetchNextPage) { |
||||||
|
data.push(...records); |
||||||
|
thresholdCbkData.push(...records); |
||||||
|
|
||||||
|
logBasic( |
||||||
|
`:: Reading '${table.title}' data :: ${Math.max( |
||||||
|
1, |
||||||
|
data.length - records.length |
||||||
|
)} - ${data.length}` |
||||||
|
); |
||||||
|
|
||||||
|
if (thresholdCbkData.length >= triggerThreshold) { |
||||||
|
await Promise.all(insertJobs); |
||||||
|
insertJobs.push(onThreshold(thresholdCbkData, data)); |
||||||
|
thresholdCbkData = []; |
||||||
|
} |
||||||
|
|
||||||
|
// To fetch the next page of records, call `fetchNextPage`.
|
||||||
|
// If there are more records, `page` will get called again.
|
||||||
|
// If there are no more records, `done` will get called.
|
||||||
|
fetchNextPage(); |
||||||
|
}, |
||||||
|
async function done(err) { |
||||||
|
if (err) { |
||||||
|
console.error(err); |
||||||
|
return reject(err); |
||||||
|
} |
||||||
|
if (thresholdCbkData.length) { |
||||||
|
await Promise.all(insertJobs); |
||||||
|
await onThreshold(thresholdCbkData, data); |
||||||
|
thresholdCbkData = []; |
||||||
|
} |
||||||
|
resolve(data); |
||||||
|
} |
||||||
|
); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
export async function importData({ |
||||||
|
projectName, |
||||||
|
table, |
||||||
|
base, |
||||||
|
api, |
||||||
|
nocoBaseDataProcessing_v2, |
||||||
|
sDB, |
||||||
|
logDetailed = _str => {}, |
||||||
|
logBasic = _str => {} |
||||||
|
}: { |
||||||
|
projectName: string; |
||||||
|
table: { title?: string; id?: string }; |
||||||
|
fields?; |
||||||
|
base: AirtableBase; |
||||||
|
logBasic: (string) => void; |
||||||
|
logDetailed: (string) => void; |
||||||
|
api: Api<any>; |
||||||
|
nocoBaseDataProcessing_v2; |
||||||
|
sDB; |
||||||
|
}): Promise<any> { |
||||||
|
try { |
||||||
|
// @ts-ignore
|
||||||
|
const records = await readAllData({ |
||||||
|
table, |
||||||
|
base, |
||||||
|
logDetailed, |
||||||
|
logBasic, |
||||||
|
async onThreshold(records, allRecords) { |
||||||
|
const allData = []; |
||||||
|
for (let i = 0; i < records.length; i++) { |
||||||
|
const r = await nocoBaseDataProcessing_v2(sDB, table, records[i]); |
||||||
|
allData.push(r); |
||||||
|
} |
||||||
|
|
||||||
|
logBasic( |
||||||
|
`:: Importing '${table.title}' data :: ${allRecords.length - |
||||||
|
records.length + |
||||||
|
1} - ${allRecords.length}` |
||||||
|
); |
||||||
|
await api.dbTableRow.bulkCreate('nc', projectName, table.id, allData); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
return records; |
||||||
|
} catch (e) { |
||||||
|
console.log(e); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export async function importLTARData({ |
||||||
|
table, |
||||||
|
fields, |
||||||
|
base, |
||||||
|
api, |
||||||
|
projectName, |
||||||
|
insertedAssocRef = {}, |
||||||
|
logDetailed = _str => {}, |
||||||
|
logBasic = _str => {}, |
||||||
|
records, |
||||||
|
atNcAliasRef |
||||||
|
}: { |
||||||
|
projectName: string; |
||||||
|
table: { title?: string; id?: string }; |
||||||
|
fields; |
||||||
|
base: AirtableBase; |
||||||
|
logDetailed: (string) => void; |
||||||
|
logBasic: (string) => void; |
||||||
|
api: Api<any>; |
||||||
|
insertedAssocRef: { [assocTableId: string]: boolean }; |
||||||
|
records?: Array<{ fields: any; id: string }>; |
||||||
|
atNcAliasRef: { |
||||||
|
[ncTableId: string]: { |
||||||
|
[ncTitle: string]: string; |
||||||
|
}; |
||||||
|
}; |
||||||
|
}) { |
||||||
|
const assocTableMetas: Array<{ |
||||||
|
modelMeta: { id?: string; title?: string }; |
||||||
|
colMeta: { title?: string }; |
||||||
|
curCol: { title?: string }; |
||||||
|
refCol: { title?: string }; |
||||||
|
}> = []; |
||||||
|
const allData = |
||||||
|
records || |
||||||
|
(await readAllData({ |
||||||
|
table, |
||||||
|
fields, |
||||||
|
base, |
||||||
|
logDetailed, |
||||||
|
logBasic |
||||||
|
})); |
||||||
|
|
||||||
|
const modelMeta: any = await api.dbTable.read(table.id); |
||||||
|
|
||||||
|
for (const colMeta of modelMeta.columns) { |
||||||
|
// skip columns which are not LTAR and Many to many
|
||||||
|
if ( |
||||||
|
colMeta.uidt !== UITypes.LinkToAnotherRecord || |
||||||
|
colMeta.colOptions.type !== RelationTypes.MANY_TO_MANY |
||||||
|
) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
// skip if already inserted
|
||||||
|
if (colMeta.colOptions.fk_mm_model_id in insertedAssocRef) continue; |
||||||
|
|
||||||
|
// mark as inserted
|
||||||
|
insertedAssocRef[colMeta.colOptions.fk_mm_model_id] = true; |
||||||
|
|
||||||
|
const assocModelMeta: TableType = (await api.dbTable.read( |
||||||
|
colMeta.colOptions.fk_mm_model_id |
||||||
|
)) as any; |
||||||
|
|
||||||
|
// extract associative table and columns meta
|
||||||
|
assocTableMetas.push({ |
||||||
|
modelMeta: assocModelMeta, |
||||||
|
colMeta, |
||||||
|
curCol: assocModelMeta.columns.find( |
||||||
|
c => c.id === colMeta.colOptions.fk_mm_child_column_id |
||||||
|
), |
||||||
|
refCol: assocModelMeta.columns.find( |
||||||
|
c => c.id === colMeta.colOptions.fk_mm_parent_column_id |
||||||
|
) |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
let nestedLinkCnt = 0; |
||||||
|
// Iterate over all related M2M associative table
|
||||||
|
for (const assocMeta of assocTableMetas) { |
||||||
|
const assocTableData = []; |
||||||
|
|
||||||
|
// extract insert data from records
|
||||||
|
for (const record of allData) { |
||||||
|
const rec = record.fields; |
||||||
|
|
||||||
|
// todo: use actual alias instead of sanitized
|
||||||
|
assocTableData.push( |
||||||
|
...(rec?.[atNcAliasRef[table.id][assocMeta.colMeta.title]] || []).map( |
||||||
|
id => ({ |
||||||
|
[assocMeta.curCol.title]: record.id, |
||||||
|
[assocMeta.refCol.title]: id |
||||||
|
}) |
||||||
|
) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
nestedLinkCnt += assocTableData.length; |
||||||
|
// Insert datas as chunks of size `ASSOC_BULK_DATA_BATCH_SIZE`
|
||||||
|
for ( |
||||||
|
let i = 0; |
||||||
|
i < assocTableData.length; |
||||||
|
i += ASSOC_BULK_DATA_BATCH_SIZE |
||||||
|
) { |
||||||
|
logBasic( |
||||||
|
`:: Importing '${table.title}' LTAR data :: ${i + 1} - ${Math.min( |
||||||
|
i + ASSOC_BULK_DATA_BATCH_SIZE, |
||||||
|
assocTableData.length |
||||||
|
)}` |
||||||
|
); |
||||||
|
|
||||||
|
console.log( |
||||||
|
assocTableData.slice(i, i + ASSOC_BULK_DATA_BATCH_SIZE).length |
||||||
|
); |
||||||
|
|
||||||
|
await api.dbTableRow.bulkCreate( |
||||||
|
'nc', |
||||||
|
projectName, |
||||||
|
assocMeta.modelMeta.id, |
||||||
|
assocTableData.slice(i, i + ASSOC_BULK_DATA_BATCH_SIZE) |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
return nestedLinkCnt; |
||||||
|
} |
Loading…
Reference in new issue