mirror of https://github.com/nocodb/nocodb
Wing-Kam Wong
3 years ago
15 changed files with 1162 additions and 24 deletions
@ -0,0 +1,167 @@
|
||||
name: "Release : Executables" |
||||
|
||||
on: |
||||
# Triggered manually |
||||
workflow_dispatch: |
||||
inputs: |
||||
tag: |
||||
description: "Timely version" |
||||
required: true |
||||
# Triggered by release-nightly-dev.yml / release-pr.yml |
||||
workflow_call: |
||||
inputs: |
||||
tag: |
||||
description: "Timely version" |
||||
required: true |
||||
type: string |
||||
secrets: |
||||
NC_GITHUB_TOKEN: |
||||
required: true |
||||
jobs: |
||||
build-executables: |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: actions/checkout@v3 |
||||
with: |
||||
token: ${{ secrets.NC_GITHUB_TOKEN }} |
||||
repository: 'nocodb/nocodb-timely' |
||||
- name: Cache node modules |
||||
id: cache-npm |
||||
uses: actions/cache@v3 |
||||
env: |
||||
cache-name: cache-node-modules |
||||
with: |
||||
# npm cache files are stored in `~/.npm` on Linux/macOS |
||||
path: ~/.npm |
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} |
||||
restore-keys: | |
||||
${{ runner.os }}-build-${{ env.cache-name }}- |
||||
${{ runner.os }}-build- |
||||
${{ runner.os }}- |
||||
- name: Cache pkg modules |
||||
id: cache-pkg |
||||
uses: actions/cache@v3 |
||||
env: |
||||
cache-name: cache-pkg |
||||
with: |
||||
# pkg cache files are stored in `~/.pkg-cache` |
||||
path: ~/.pkg-cache |
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} |
||||
restore-keys: | |
||||
${{ runner.os }}-build-${{ env.cache-name }}- |
||||
${{ runner.os }}-build- |
||||
${{ runner.os }}- |
||||
- name: Install QEMU and ldid |
||||
run: | |
||||
# Install qemu |
||||
sudo apt install qemu binfmt-support qemu-user-static |
||||
# install ldid |
||||
git clone https://github.com/daeken/ldid.git |
||||
cd ./ldid |
||||
./make.sh |
||||
sudo cp ./ldid /usr/local/bin |
||||
|
||||
- name: Update nocodb-timely |
||||
env: |
||||
TAG: ${{ github.event.inputs.tag || inputs.tag }} |
||||
run: | |
||||
npm i -E nocodb-daily@$TAG |
||||
|
||||
git config user.name 'github-actions[bot]' |
||||
git config user.email 'github-actions[bot]@users.noreply.github.com' |
||||
|
||||
git commit package.json -m "Update to $TAG" |
||||
git tag $TAG |
||||
git push --tags |
||||
|
||||
|
||||
- uses: actions/setup-node@v3 |
||||
with: |
||||
node-version: 16 |
||||
|
||||
- name : Install dependencies and build executables |
||||
run: | |
||||
# install npm dependendencies |
||||
npm i |
||||
|
||||
# Copy sqlite binaries |
||||
rsync -rvzhP ./binaries/binding/ ./node_modules/sqlite3/lib/binding/ |
||||
|
||||
# clean up code to optimize size |
||||
npx modclean --patterns="default:*" --ignore="nc-lib-gui-daily/**,dayjs/**,express-status-monitor/**,sqlite3/**" --run |
||||
|
||||
# build executables |
||||
npm run build |
||||
|
||||
mkdir ./mac-dist |
||||
mv ./dist/Noco-macos-arm64 ./mac-dist/ |
||||
mv ./dist/Noco-macos-x64 ./mac-dist/ |
||||
|
||||
# Compress executables |
||||
GZIP=-9 tar -czvf ./dist/Noco-linux-x64.tar.gz ./dist/Noco-linux-x64 |
||||
GZIP=-9 tar -czvf ./dist/Noco-win-x64.tar.gz ./dist/Noco-win-x64.exe |
||||
GZIP=-9 tar -czvf ./dist/Noco-linux-arm64.tar.gz ./dist/Noco-linux-arm64 |
||||
GZIP=-9 tar -czvf ./dist/Noco-win-arm64.tar.gz ./dist/Noco-win-arm64.exe |
||||
|
||||
- name: Upload executables(except mac executables) to release |
||||
uses: svenstaro/upload-release-action@v2 |
||||
with: |
||||
repo_token: ${{ secrets.NC_GITHUB_TOKEN }} |
||||
file: dist/** |
||||
tag: ${{ github.event.inputs.tag || inputs.tag }} |
||||
overwrite: true |
||||
file_glob: true |
||||
repo_name: nocodb/nocodb-timely |
||||
|
||||
- uses: actions/upload-artifact@master |
||||
with: |
||||
name: ${{ github.event.inputs.tag || inputs.tag }} |
||||
path: mac-dist |
||||
retention-days: 1 |
||||
|
||||
sign-mac-executables: |
||||
runs-on: macos-latest |
||||
needs: build-executables |
||||
steps: |
||||
|
||||
- uses: actions/download-artifact@master |
||||
with: |
||||
name: ${{ github.event.inputs.tag || inputs.tag }} |
||||
path: mac-dist |
||||
|
||||
- name: Sign macOS executables |
||||
run: | |
||||
/usr/bin/codesign --force -s - ./mac-dist/Noco-macos-arm64 -v |
||||
/usr/bin/codesign --force -s - ./mac-dist/Noco-macos-x64 -v |
||||
|
||||
- uses: actions/upload-artifact@master |
||||
with: |
||||
name: ${{ github.event.inputs.tag || inputs.tag }} |
||||
path: mac-dist |
||||
retention-days: 1 |
||||
|
||||
|
||||
publish-mac-executables: |
||||
needs: sign-mac-executables |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: actions/download-artifact@master |
||||
with: |
||||
name: ${{ github.event.inputs.tag || inputs.tag }} |
||||
path: mac-dist |
||||
- name: Compress files |
||||
run: | |
||||
GZIP=-9 tar -czvf ./mac-dist/Noco-macos-x64.tar.gz ./mac-dist/Noco-macos-x64 |
||||
GZIP=-9 tar -czvf ./mac-dist/Noco-macos-arm64.tar.gz ./mac-dist/Noco-macos-arm64 |
||||
|
||||
- name: Upload mac executables to release |
||||
uses: svenstaro/upload-release-action@v2 |
||||
with: |
||||
repo_token: ${{ secrets.NC_GITHUB_TOKEN }} |
||||
file: mac-dist/** |
||||
tag: ${{ github.event.inputs.tag || inputs.tag }} |
||||
overwrite: true |
||||
file_glob: true |
||||
repo_name: nocodb/nocodb-timely |
||||
|
||||
|
@ -0,0 +1,23 @@
|
||||
## config.json |
||||
{ |
||||
"srcProject": "sample", |
||||
"dstProject": "sample-copy", |
||||
"baseURL": "http://localhost:8080", |
||||
"xc-auth": "Copy Auth Token" |
||||
} |
||||
- baseURL & xc-auth are common configurations for both import & export |
||||
|
||||
## Export |
||||
- `srcProject`: specify source project name to be exported. |
||||
- Export JSON file will be created as `srcProject.json` |
||||
- execute |
||||
`cd packages/nocodb/tests/export-import` |
||||
`node exportSchema.js` |
||||
|
||||
## Import |
||||
- `srcProject`: specify JSON file name to be imported (sans .JSON suffix) |
||||
- `dstProject`: new project name to be imported as |
||||
- Data will also be imported if `srcProject` exists in NocoDB. Note that, data import isn't via exported JSON |
||||
- execute |
||||
`cd packages/nocodb/tests/export-import` |
||||
`node importSchema.js` |
@ -0,0 +1,6 @@
|
||||
{ |
||||
"srcProject": "sample", |
||||
"dstProject": "sample-copy", |
||||
"baseURL": "http://localhost:8080", |
||||
"xc-auth": "Copy Auth Token" |
||||
} |
@ -0,0 +1,297 @@
|
||||
const Api = require('nocodb-sdk').Api; |
||||
const { UITypes } = require('nocodb-sdk'); |
||||
const jsonfile = require('jsonfile'); |
||||
|
||||
const GRID = 3, GALLERY = 2, FORM = 1; |
||||
|
||||
let ncMap = { /* id: name <string> */ }; |
||||
let tblSchema = []; |
||||
let api = {}; |
||||
let viewStore = { columns: {}, sort: {}, filter: {} }; |
||||
|
||||
let inputConfig = jsonfile.readFileSync(`config.json`) |
||||
let ncConfig = { |
||||
projectName: inputConfig.srcProject, |
||||
baseURL: inputConfig.baseURL, |
||||
headers: { |
||||
'xc-auth': `${inputConfig["xc-auth"]}` |
||||
} |
||||
}; |
||||
|
||||
|
||||
// helper routines
|
||||
// remove objects containing 0/ false/ null
|
||||
// fixme: how to handle when cdf (default value) is configured as 0/ null/ false
|
||||
function removeEmpty(obj) { |
||||
return Object.fromEntries( |
||||
Object.entries(obj) |
||||
.filter(([_, v]) => v != null && v != 0 && v != false) |
||||
.map(([k, v]) => [k, v === Object(v) ? removeEmpty(v) : v]) |
||||
); |
||||
} |
||||
|
||||
function addColumnSpecificData(c) { |
||||
// pick required fields to proceed further
|
||||
let col = removeEmpty( |
||||
(({ id, title, column_name, uidt, dt, pk, pv, rqd, dtxp, system }) => ({ |
||||
id, |
||||
title, |
||||
column_name, |
||||
uidt, |
||||
dt, |
||||
pk, |
||||
pv, |
||||
rqd, |
||||
dtxp, |
||||
system |
||||
}))(c) |
||||
); |
||||
|
||||
switch (c.uidt) { |
||||
case UITypes.Formula: |
||||
col.formula = c.colOptions.formula; |
||||
col.formula_raw = c.colOptions.formula_raw; |
||||
break; |
||||
case UITypes.LinkToAnotherRecord: |
||||
col[`colOptions`] = { |
||||
fk_model_id: c.fk_model_id, |
||||
fk_related_model_id: c.colOptions.fk_related_model_id, |
||||
fk_child_column_id: c.colOptions.fk_child_column_id, |
||||
fk_parent_column_id: c.colOptions.fk_parent_column_id, |
||||
type: c.colOptions.type |
||||
}; |
||||
break; |
||||
case UITypes.Lookup: |
||||
col[`colOptions`] = { |
||||
fk_model_id: c.fk_model_id, |
||||
fk_relation_column_id: c.colOptions.fk_relation_column_id, |
||||
fk_lookup_column_id: c.colOptions.fk_lookup_column_id |
||||
}; |
||||
break; |
||||
case UITypes.Rollup: |
||||
col[`colOptions`] = { |
||||
fk_model_id: c.fk_model_id, |
||||
fk_relation_column_id: c.colOptions.fk_relation_column_id, |
||||
fk_rollup_column_id: c.colOptions.fk_rollup_column_id, |
||||
rollup_function: c.colOptions.rollup_function |
||||
}; |
||||
break; |
||||
} |
||||
|
||||
return col; |
||||
} |
||||
|
||||
function addViewDetails(v) { |
||||
// pick required fields to proceed further
|
||||
let view = (({ id, title, type, show_system_fields, lock_type, order }) => ({ |
||||
id, |
||||
title, |
||||
type, |
||||
show_system_fields, |
||||
lock_type, |
||||
order |
||||
}))(v); |
||||
|
||||
// form view
|
||||
if (v.type === FORM) { |
||||
view.property = (({ |
||||
heading, |
||||
subheading, |
||||
success_msg, |
||||
redirect_after_secs, |
||||
email, |
||||
submit_another_form, |
||||
show_blank_form |
||||
}) => ({ |
||||
heading, |
||||
subheading, |
||||
success_msg, |
||||
redirect_after_secs, |
||||
email, |
||||
submit_another_form, |
||||
show_blank_form |
||||
}))(v.view); |
||||
} |
||||
|
||||
// gallery view
|
||||
else if (v.type === GALLERY) { |
||||
view.property = { |
||||
fk_cover_image_col_id: ncMap[v.view.fk_cover_image_col_id] |
||||
}; |
||||
} |
||||
|
||||
// gallery view doesn't share column information in api yet
|
||||
if (v.type !== GALLERY) { |
||||
if (v.type === GRID) |
||||
view.columns = viewStore.columns[v.id].map(a => |
||||
(({ id, width, order, show }) => ({ id, width, order, show }))(a) |
||||
); |
||||
if (v.type === FORM) |
||||
view.columns = viewStore.columns[v.id].map(a => |
||||
(({ id, order, show, label, help, description, required }) => ({ |
||||
id, |
||||
order, |
||||
show, |
||||
label, |
||||
help, |
||||
description, |
||||
required |
||||
}))(a) |
||||
); |
||||
|
||||
for (let i = 0; i < view.columns?.length; i++) |
||||
view.columns[i].title = ncMap[viewStore.columns[v.id][i].id]; |
||||
|
||||
// skip hm & mm columns
|
||||
view.columns = view.columns |
||||
?.filter(a => a.title?.includes('_nc_m2m_') === false) |
||||
.filter(a => a.title?.includes('nc_') === false); |
||||
} |
||||
|
||||
// filter & sort configurations
|
||||
if (v.type !== FORM) { |
||||
view.sort = viewStore.sort[v.id].map(a => |
||||
(({ fk_column_id, direction, order }) => ({ |
||||
fk_column_id, |
||||
direction, |
||||
order |
||||
}))(a) |
||||
); |
||||
view.filter = viewStore.filter[v.id].map(a => |
||||
(({ fk_column_id, logical_op, comparison_op, value, order }) => ({ |
||||
fk_column_id, |
||||
logical_op, |
||||
comparison_op, |
||||
value, |
||||
order |
||||
}))(a) |
||||
); |
||||
} |
||||
return view; |
||||
} |
||||
|
||||
// view data stored as is for quick access
|
||||
async function storeViewDetails(tableId) { |
||||
// read view data for each table
|
||||
let viewList = await api.dbView.list(tableId); |
||||
for (let j = 0; j < viewList.list.length; j++) { |
||||
let v = viewList.list[j]; |
||||
let viewDetails = []; |
||||
|
||||
// invoke view specific read to populate columns information
|
||||
if (v.type === FORM) viewDetails = (await api.dbView.formRead(v.id)).columns; |
||||
else if (v.type === GALLERY) viewDetails = await api.dbView.galleryRead(v.id); |
||||
else if (v.type === GRID) viewDetails = await api.dbView.gridColumnsList(v.id); |
||||
viewStore.columns[v.id] = viewDetails; |
||||
|
||||
// populate sort information
|
||||
let vSort = await api.dbTableSort.list(v.id); |
||||
viewStore.sort[v.id] = vSort.sorts.list; |
||||
|
||||
let vFilter = await api.dbTableFilter.read(v.id); |
||||
viewStore.filter[v.id] = vFilter; |
||||
} |
||||
} |
||||
|
||||
// mapping table for quick information access
|
||||
// store maps for tableId, columnId, viewColumnId & viewId to their names
|
||||
async function generateMapTbl(pId) { |
||||
const tblList = await api.dbTable.list(pId); |
||||
|
||||
for (let i = 0; i < tblList.list.length; i++) { |
||||
let tblId = tblList.list[i].id; |
||||
let tbl = await api.dbTable.read(tblId); |
||||
|
||||
// table ID <> name
|
||||
ncMap[tblId] = tbl.title; |
||||
|
||||
// column ID <> name
|
||||
tbl.columns.map(x => (ncMap[x.id] = x.title)); |
||||
|
||||
// view ID <> name
|
||||
tbl.views.map(x => (ncMap[x.id] = x.tn)); |
||||
|
||||
for (let i = 0; i < tbl.views.length; i++) { |
||||
let x = tbl.views[i]; |
||||
let viewColumns = []; |
||||
if (x.type === FORM) viewColumns = (await api.dbView.formRead(x.id)).columns; |
||||
else if (x.type === GALLERY) |
||||
viewColumns = (await api.dbView.galleryRead(x.id)).columns; |
||||
else if (x.type === GRID) viewColumns = await api.dbView.gridColumnsList(x.id); |
||||
|
||||
// view column ID <> name
|
||||
viewColumns?.map(a => (ncMap[a.id] = ncMap[a.fk_column_id])); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// main
|
||||
//
|
||||
async function exportSchema() { |
||||
api = new Api(ncConfig); |
||||
|
||||
// fetch project details (id et.al)
|
||||
const x = await api.project.list(); |
||||
const p = x.list.find(a => a.title === ncConfig.projectName); |
||||
|
||||
await generateMapTbl(p.id); |
||||
|
||||
// read project
|
||||
const tblList = await api.dbTable.list(p.id); |
||||
|
||||
// for each table
|
||||
for (let i = 0; i < tblList.list.length; i++) { |
||||
let tblId = tblList.list[i].id; |
||||
await storeViewDetails(tblId); |
||||
|
||||
let tbl = await api.dbTable.read(tblId); |
||||
|
||||
// prepare schema
|
||||
let tSchema = { |
||||
id: tbl.id, |
||||
title: tbl.title, |
||||
table_name: tbl?.table_name, |
||||
columns: [...tbl.columns.map(c => addColumnSpecificData(c))] |
||||
.filter(a => a.title.includes('_nc_m2m_') === false) // mm
|
||||
.filter(a => a.title.includes(p.prefix) === false) // hm
|
||||
.filter( |
||||
a => !(a?.system === 1 && a.uidt === UITypes.LinkToAnotherRecord) |
||||
), |
||||
views: [...tbl.views.map(v => addViewDetails(v))] |
||||
}; |
||||
tblSchema.push(tSchema); |
||||
} |
||||
} |
||||
|
||||
(async () => { |
||||
await exportSchema(); |
||||
jsonfile.writeFileSync( |
||||
`${ncConfig.projectName.replace(/ /g, '_')}.json`, |
||||
tblSchema, |
||||
{ spaces: 2 } |
||||
); |
||||
})().catch(e => { |
||||
console.log(e); |
||||
}); |
||||
|
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Raju Udava <sivadstala@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
@ -0,0 +1,537 @@
|
||||
// tbd
|
||||
// - formula dependency list
|
||||
// - nested lookup/ rollup
|
||||
|
||||
const Api = require('nocodb-sdk').Api; |
||||
const { UITypes } = require('nocodb-sdk'); |
||||
const jsonfile = require('jsonfile'); |
||||
|
||||
let inputConfig = jsonfile.readFileSync(`config.json`) |
||||
let ncConfig = { |
||||
srcProject: inputConfig.srcProject, |
||||
projectName: inputConfig.dstProject, |
||||
baseURL: inputConfig.baseURL, |
||||
headers: { |
||||
'xc-auth': `${inputConfig["xc-auth"]}` |
||||
} |
||||
}; |
||||
let ncIn = jsonfile.readFileSync(`${ncConfig.srcProject}.json`); |
||||
|
||||
let api = {}; |
||||
let ncProject = {}; |
||||
let link = []; |
||||
let lookup = []; |
||||
let rollup = []; |
||||
let formula = []; |
||||
|
||||
let rootLinks = []; |
||||
|
||||
// maps v1 table ID, v2 table ID & table title to table schema
|
||||
let ncTables = {}; |
||||
|
||||
|
||||
async function createBaseTables() { |
||||
console.log(`createBaseTables`); |
||||
for (let i = 0; i < ncIn.length; i++) { |
||||
let tblSchema = ncIn[i]; |
||||
let reducedColumnSet = tblSchema.columns.filter( |
||||
a => |
||||
a.uidt !== UITypes.LinkToAnotherRecord && |
||||
a.uidt !== UITypes.Lookup && |
||||
a.uidt !== UITypes.Rollup && |
||||
a.uidt !== UITypes.Formula |
||||
); |
||||
link.push( |
||||
...tblSchema.columns.filter(a => a.uidt === UITypes.LinkToAnotherRecord) |
||||
); |
||||
lookup.push(...tblSchema.columns.filter(a => a.uidt === UITypes.Lookup)); |
||||
rollup.push(...tblSchema.columns.filter(a => a.uidt === UITypes.Rollup)); |
||||
formula.push(...tblSchema.columns.filter(a => a.uidt === UITypes.Formula)); |
||||
formula.map(a => (a['table_id'] = tblSchema.id)); |
||||
|
||||
let tbl = await api.dbTable.create(ncProject.id, { |
||||
title: tblSchema.title, |
||||
table_name: tblSchema.title, |
||||
columns: reducedColumnSet.map(({ id, ...rest }) => ({ ...rest })) |
||||
}); |
||||
ncTables[tbl.title] = tbl; |
||||
ncTables[tbl.id] = tbl; |
||||
ncTables[tblSchema.id] = tbl; |
||||
} |
||||
} |
||||
|
||||
let linksCreated = []; |
||||
function isLinkCreated(pId, cId) { |
||||
let idx = linksCreated.findIndex(a => a.cId === pId && a.pId === cId); |
||||
if (idx === -1) { |
||||
linksCreated.push({ pId: pId, cId: cId }); |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
// retrieve nc-view column ID from corresponding nc-column ID
|
||||
async function nc_getViewColumnId(viewId, viewType, ncColumnId) { |
||||
// retrieve view Info
|
||||
let viewDetails; |
||||
|
||||
if (viewType === 'form') |
||||
viewDetails = (await api.dbView.formRead(viewId)).columns; |
||||
else if (viewType === 'gallery') |
||||
viewDetails = (await api.dbView.galleryRead(viewId)).columns; |
||||
else viewDetails = await api.dbView.gridColumnsList(viewId); |
||||
|
||||
return viewDetails.find(x => x.fk_column_id === ncColumnId)?.id; |
||||
} |
||||
|
||||
async function createFormula() { |
||||
for (let i = 0; i < formula.length; i++) { |
||||
let tbl = await api.dbTableColumn.create(ncTables[formula[i].table_id].id, { |
||||
uidt: UITypes.Formula, |
||||
title: formula[i].title, |
||||
formula_raw: formula[i].formula_raw |
||||
}); |
||||
} |
||||
} |
||||
|
||||
async function createLinks() { |
||||
console.log(`createLinks`); |
||||
|
||||
for (let i = 0; i < link.length; i++) { |
||||
if ( |
||||
(link[i].colOptions.type === 'mm' && |
||||
false === |
||||
isLinkCreated( |
||||
link[i].colOptions.fk_parent_column_id, |
||||
link[i].colOptions.fk_child_column_id |
||||
)) || |
||||
link[i].colOptions.type === 'hm' |
||||
) { |
||||
let srcTbl = ncTables[link[i].colOptions.fk_model_id]; |
||||
let dstTbl = ncTables[link[i].colOptions.fk_related_model_id]; |
||||
|
||||
// create link
|
||||
let tbl = await api.dbTableColumn.create(srcTbl.id, { |
||||
uidt: UITypes.LinkToAnotherRecord, |
||||
title: link[i].title, |
||||
parentId: srcTbl.id, |
||||
childId: dstTbl.id, |
||||
type: link[i].colOptions.type |
||||
}); |
||||
ncTables[tbl.title] = tbl; |
||||
ncTables[tbl.id] = tbl; |
||||
ncTables[link[i].colOptions.fk_model_id] = tbl; |
||||
|
||||
// for data-link procedure later
|
||||
rootLinks.push({ linkColumn: link[i], linkSrcTbl: srcTbl }); |
||||
|
||||
// symmetry field update
|
||||
//
|
||||
let v2ColSchema = tbl.columns.find(x => x.title === link[i].title); |
||||
// read related table again after link is created
|
||||
dstTbl = await api.dbTable.read(dstTbl.id); |
||||
let v2SymmetricColumn = |
||||
link[i].colOptions.type === 'mm' |
||||
? dstTbl.columns.find( |
||||
x => |
||||
x.uidt === UITypes.LinkToAnotherRecord && |
||||
x?.colOptions.fk_parent_column_id === |
||||
v2ColSchema.colOptions.fk_child_column_id && |
||||
x?.colOptions.fk_child_column_id === |
||||
v2ColSchema.colOptions.fk_parent_column_id |
||||
) |
||||
: dstTbl.columns.find( |
||||
x => |
||||
x.uidt === UITypes.LinkToAnotherRecord && |
||||
x?.colOptions.fk_parent_column_id === |
||||
v2ColSchema.colOptions.fk_parent_column_id && |
||||
x?.colOptions.fk_child_column_id === |
||||
v2ColSchema.colOptions.fk_child_column_id |
||||
); |
||||
let v1SymmetricColumn = |
||||
link[i].colOptions.type === 'mm' |
||||
? link.find( |
||||
x => |
||||
x.colOptions.fk_parent_column_id === |
||||
link[i].colOptions.fk_child_column_id && |
||||
x.colOptions.fk_child_column_id === |
||||
link[i].colOptions.fk_parent_column_id && |
||||
x.colOptions.type === 'mm' |
||||
) |
||||
: link.find( |
||||
x => |
||||
x.colOptions.fk_parent_column_id === |
||||
link[i].colOptions.fk_parent_column_id && |
||||
x.colOptions.fk_child_column_id === |
||||
link[i].colOptions.fk_child_column_id && |
||||
x.colOptions.type === 'bt' |
||||
); |
||||
|
||||
tbl = await api.dbTableColumn.update(v2SymmetricColumn.id, { |
||||
...v2SymmetricColumn, |
||||
title: v1SymmetricColumn.title, |
||||
column_name: null |
||||
}); |
||||
ncTables[tbl.title] = tbl; |
||||
ncTables[tbl.id] = tbl; |
||||
ncTables[v1SymmetricColumn.colOptions.fk_model_id] = tbl; |
||||
} |
||||
} |
||||
} |
||||
|
||||
function get_v2Id(v1ColId) { |
||||
for (let i = 0; i < ncIn.length; i++) { |
||||
let tblSchema = ncIn[i]; |
||||
let colSchema = {}; |
||||
if ( |
||||
undefined !== (colSchema = tblSchema.columns.find(x => x.id === v1ColId)) |
||||
) { |
||||
let colName = colSchema.title; |
||||
let v2Tbl = ncTables[tblSchema.id]; |
||||
return v2Tbl.columns.find(y => y.title === colName)?.id; |
||||
} |
||||
} |
||||
} |
||||
|
||||
async function createLookup() { |
||||
console.log(`createLookup`); |
||||
|
||||
for (let i = 0; i < lookup.length; i++) { |
||||
let srcTbl = ncTables[lookup[i].colOptions.fk_model_id]; |
||||
let v2_fk_relation_column_id = get_v2Id( |
||||
lookup[i].colOptions.fk_relation_column_id |
||||
); |
||||
let v2_lookup_column_id = get_v2Id( |
||||
lookup[i].colOptions.fk_lookup_column_id |
||||
); |
||||
|
||||
if (v2_lookup_column_id) { |
||||
let tbl = await api.dbTableColumn.create(srcTbl.id, { |
||||
uidt: UITypes.Lookup, |
||||
title: lookup[i].title, |
||||
fk_relation_column_id: v2_fk_relation_column_id, |
||||
fk_lookup_column_id: v2_lookup_column_id |
||||
}); |
||||
ncTables[tbl.title] = tbl; |
||||
ncTables[tbl.id] = tbl; |
||||
ncTables[lookup[i].colOptions.fk_model_id] = tbl; |
||||
} |
||||
} |
||||
} |
||||
|
||||
async function createRollup() { |
||||
console.log(`createRollup`); |
||||
|
||||
for (let i = 0; i < rollup.length; i++) { |
||||
let srcTbl = ncTables[rollup[i].colOptions.fk_model_id]; |
||||
let v2_fk_relation_column_id = get_v2Id( |
||||
rollup[i].colOptions.fk_relation_column_id |
||||
); |
||||
let v2_rollup_column_id = get_v2Id( |
||||
rollup[i].colOptions.fk_rollup_column_id |
||||
); |
||||
|
||||
if (v2_rollup_column_id) { |
||||
let tbl = await api.dbTableColumn.create(srcTbl.id, { |
||||
uidt: UITypes.Rollup, |
||||
title: rollup[i].title, |
||||
column_name: rollup[i].title, |
||||
fk_relation_column_id: v2_fk_relation_column_id, |
||||
fk_rollup_column_id: v2_rollup_column_id, |
||||
rollup_function: rollup[i].colOptions.rollup_function |
||||
}); |
||||
ncTables[tbl.title] = tbl; |
||||
ncTables[tbl.id] = tbl; |
||||
ncTables[rollup[i].colOptions.fk_model_id] = tbl; |
||||
} |
||||
} |
||||
} |
||||
|
||||
async function configureGrid() { |
||||
console.log(`configureGrid`); |
||||
|
||||
for (let i = 0; i < ncIn.length; i++) { |
||||
let tblSchema = ncIn[i]; |
||||
let tblId = ncTables[tblSchema.id].id; |
||||
let gridList = tblSchema.views.filter(a => a.type === 3); |
||||
let srcTbl = await api.dbTable.read(tblId); |
||||
|
||||
const view = await api.dbView.list(tblId); |
||||
|
||||
// create / rename view
|
||||
for (let gridCnt = 0; gridCnt < gridList.length; gridCnt++) { |
||||
let viewCreated = {}; |
||||
// rename first view; default view already created
|
||||
if (gridCnt === 0) { |
||||
viewCreated = await api.dbView.update(view.list[0].id, { |
||||
title: gridList[gridCnt].title |
||||
}); |
||||
} |
||||
// create new views
|
||||
else { |
||||
viewCreated = await api.dbView.gridCreate(tblId, { |
||||
title: gridList[gridCnt].title |
||||
}); |
||||
} |
||||
|
||||
// retrieve view Info
|
||||
let viewId = viewCreated.id; |
||||
let viewDetails = await api.dbView.gridColumnsList(viewId); |
||||
|
||||
// column visibility
|
||||
for ( |
||||
let colCnt = 0; |
||||
colCnt < gridList[gridCnt].columns.length; |
||||
colCnt++ |
||||
) { |
||||
let ncColumnId = srcTbl.columns.find( |
||||
a => a.title === gridList[gridCnt].columns[colCnt].title |
||||
)?.id; |
||||
// let ncViewColumnId = await nc_getViewColumnId( viewCreated.id, "grid", ncColumnId )
|
||||
let ncViewColumnId = viewDetails.find( |
||||
x => x.fk_column_id === ncColumnId |
||||
)?.id; |
||||
// column order & visibility
|
||||
await api.dbViewColumn.update(viewCreated.id, ncViewColumnId, { |
||||
show: gridList[gridCnt].columns[colCnt].show, |
||||
order: gridList[gridCnt].columns[colCnt].order |
||||
}); |
||||
await api.dbView.gridColumnUpdate(ncViewColumnId, { |
||||
width: gridList[gridCnt].columns[colCnt].width |
||||
}); |
||||
} |
||||
|
||||
// sort
|
||||
for (let sCnt = 0; sCnt < gridList[gridCnt].sort.length; sCnt++) { |
||||
let sColName = tblSchema.columns.find( |
||||
a => gridList[gridCnt].sort[sCnt].fk_column_id === a.id |
||||
).title; |
||||
await api.dbTableSort.create(viewId, { |
||||
fk_column_id: srcTbl.columns.find(a => a.title === sColName)?.id, |
||||
direction: gridList[gridCnt].sort[sCnt].direction |
||||
}); |
||||
} |
||||
|
||||
// filter
|
||||
for (let fCnt = 0; fCnt < gridList[gridCnt].filter.length; fCnt++) { |
||||
let fColName = tblSchema.columns.find( |
||||
a => gridList[gridCnt].sort[fCnt].fk_column_id === a.id |
||||
).title; |
||||
await api.dbTableFilter.create(viewId, { |
||||
...gridList[gridCnt].filter[fCnt], |
||||
fk_column_id: srcTbl.columns.find(a => a.title === fColName)?.id |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
async function configureGallery() { |
||||
console.log(`configureGallery`); |
||||
|
||||
for (let i = 0; i < ncIn.length; i++) { |
||||
let tblSchema = ncIn[i]; |
||||
let tblId = ncTables[tblSchema.id].id; |
||||
let galleryList = tblSchema.views.filter(a => a.type === 2); |
||||
for (let cnt = 0; cnt < galleryList.length; cnt++) { |
||||
const viewCreated = await api.dbView.galleryCreate(tblId, { |
||||
title: galleryList[cnt].title |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
|
||||
async function configureForm() { |
||||
console.log(`configureForm`); |
||||
|
||||
for (let i = 0; i < ncIn.length; i++) { |
||||
let tblSchema = ncIn[i]; |
||||
let tblId = ncTables[tblSchema.id].id; |
||||
let formList = tblSchema.views.filter(a => a.type === 1); |
||||
let srcTbl = await api.dbTable.read(tblId); |
||||
|
||||
for (let formCnt = 0; formCnt < formList.length; formCnt++) { |
||||
const formData = { |
||||
title: formList[formCnt].title, |
||||
...formList[formCnt].property |
||||
}; |
||||
const viewCreated = await api.dbView.formCreate(tblId, formData); |
||||
|
||||
// column visibility
|
||||
for ( |
||||
let colCnt = 0; |
||||
colCnt < formList[formCnt].columns.length; |
||||
colCnt++ |
||||
) { |
||||
let ncColumnId = srcTbl.columns.find( |
||||
a => a.title === formList[formCnt].columns[colCnt].title |
||||
)?.id; |
||||
let ncViewColumnId = await nc_getViewColumnId( |
||||
viewCreated.id, |
||||
'form', |
||||
ncColumnId |
||||
); |
||||
// column order & visibility
|
||||
await api.dbView.formColumnUpdate(ncViewColumnId, { |
||||
show: formList[formCnt].columns[colCnt].show, |
||||
order: formList[formCnt].columns[colCnt].order, |
||||
label: formList[formCnt].columns[colCnt].label, |
||||
description: formList[formCnt].columns[colCnt].description, |
||||
required: formList[formCnt].columns[colCnt].required |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
async function restoreBaseData() { |
||||
console.log(`restoreBaseData`); |
||||
|
||||
for (let i = 0; i < ncIn.length; i++) { |
||||
let tblSchema = ncIn[i]; |
||||
let tblId = ncTables[tblSchema.id].id; |
||||
let pk = tblSchema.columns.find(a => a.pk).title; |
||||
|
||||
let moreRecords = true; |
||||
let offset = 0, |
||||
limit = 25; |
||||
|
||||
while (moreRecords) { |
||||
let recList = await api.dbTableRow.list( |
||||
'nc', |
||||
ncConfig.srcProject, |
||||
tblSchema.title, |
||||
{}, |
||||
{ |
||||
query: { limit: limit, offset: offset } |
||||
} |
||||
); |
||||
moreRecords = !recList.pageInfo.isLastPage; |
||||
offset += limit; |
||||
|
||||
for (let recCnt = 0; recCnt < recList.list.length; recCnt++) { |
||||
let record = await api.dbTableRow.read( |
||||
'nc', |
||||
ncConfig.srcProject, |
||||
tblSchema.title, |
||||
recList.list[recCnt][pk] |
||||
); |
||||
|
||||
// post-processing on the record
|
||||
for (const [key, value] of Object.entries(record)) { |
||||
let table = ncTables[tblId]; |
||||
// retrieve datatype
|
||||
const dt = table.columns.find(x => x.title === key)?.uidt; |
||||
if (dt === UITypes.LinkToAnotherRecord) delete record[key]; |
||||
if (dt === UITypes.Lookup) delete record[key]; |
||||
if (dt === UITypes.Rollup) delete record[key]; |
||||
} |
||||
await api.dbTableRow.create( |
||||
'nc', |
||||
ncConfig.projectName, |
||||
tblSchema.title, |
||||
record |
||||
); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
async function restoreLinks() { |
||||
console.log(`restoreLinks`); |
||||
|
||||
for (let i = 0; i < rootLinks.length; i++) { |
||||
let pk = rootLinks[i].linkSrcTbl.columns.find(a => a.pk).title; |
||||
let moreRecords = true; |
||||
let offset = 0, |
||||
limit = 25; |
||||
|
||||
while (moreRecords) { |
||||
let recList = await api.dbTableRow.list( |
||||
'nc', |
||||
ncConfig.srcProject, |
||||
rootLinks[i].linkSrcTbl.title, |
||||
{}, |
||||
{ |
||||
query: { limit: limit, offset: offset } |
||||
} |
||||
); |
||||
moreRecords = !recList.pageInfo.isLastPage; |
||||
offset += limit; |
||||
|
||||
for (let recCnt = 0; recCnt < recList.list.length; recCnt++) { |
||||
let record = await api.dbTableRow.read( |
||||
'nc', |
||||
ncConfig.srcProject, |
||||
rootLinks[i].linkSrcTbl.title, |
||||
recList.list[recCnt][pk] |
||||
); |
||||
let linkField = record[rootLinks[i].linkColumn.title]; |
||||
if (linkField.length) { |
||||
await api.dbTableRow.nestedAdd( |
||||
'nc', |
||||
ncConfig.projectName, |
||||
rootLinks[i].linkSrcTbl.title, |
||||
record[pk], |
||||
rootLinks[i].linkColumn.colOptions.type, |
||||
encodeURIComponent(rootLinks[i].linkColumn.title), |
||||
linkField[0][pk] |
||||
); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
async function importSchema() { |
||||
api = new Api(ncConfig); |
||||
|
||||
const x = await api.project.list(); |
||||
const p = x.list.find(a => a.title === ncConfig.projectName); |
||||
if (p) await api.project.delete(p.id); |
||||
ncProject = await api.project.create({ title: ncConfig.projectName }); |
||||
|
||||
await createBaseTables(); |
||||
await createLinks(); |
||||
await createLookup(); |
||||
await createRollup(); |
||||
await createFormula(); |
||||
|
||||
// configure views
|
||||
await configureGrid(); |
||||
await configureGallery(); |
||||
await configureForm(); |
||||
|
||||
// restore data only if source project exists
|
||||
const p2 = x.list.find(a => a.title === ncConfig.srcProject); |
||||
if (p2 !== undefined) { |
||||
await restoreBaseData(); |
||||
await restoreLinks(); |
||||
} |
||||
} |
||||
(async () => { |
||||
await importSchema(); |
||||
console.log('completed'); |
||||
})().catch(e => console.log(e)); |
||||
|
||||
/** |
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||
* |
||||
* @author Raju Udava <sivadstala@gmail.com> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
Loading…
Reference in new issue