mirror of https://github.com/nocodb/nocodb
38 changed files with 6486 additions and 104 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,5 @@ |
|||||||
|
import crypto from 'crypto'; |
||||||
|
|
||||||
|
export function randomTokenString(): string { |
||||||
|
return crypto.randomBytes(40).toString('hex'); |
||||||
|
} |
@ -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/>.
|
||||||
|
* |
||||||
|
*/ |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,17 @@ |
|||||||
|
version: "2.1" |
||||||
|
|
||||||
|
services: |
||||||
|
pg96: |
||||||
|
image: postgres:9.6 |
||||||
|
restart: always |
||||||
|
environment: |
||||||
|
POSTGRES_PASSWORD: password |
||||||
|
ports: |
||||||
|
- 5432:5432 |
||||||
|
volumes: |
||||||
|
- ../../packages/nocodb/tests/pg-cy-quick:/docker-entrypoint-initdb.d |
||||||
|
healthcheck: |
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"] |
||||||
|
interval: 10s |
||||||
|
timeout: 5s |
||||||
|
retries: 5 |
Binary file not shown.
@ -0,0 +1,358 @@ |
|||||||
|
import { |
||||||
|
isTestSuiteActive, |
||||||
|
roles, |
||||||
|
} from "../../support/page_objects/projectConstants"; |
||||||
|
import { loginPage, projectsPage } from "../../support/page_objects/navigation"; |
||||||
|
import { mainPage } from "../../support/page_objects/mainPage"; |
||||||
|
|
||||||
|
// normal fields
|
||||||
|
let records = { |
||||||
|
Name: "Movie-1", |
||||||
|
Notes: "Good", |
||||||
|
Status: "Todo", |
||||||
|
Tags: "Jan", |
||||||
|
Phone: "123123123", |
||||||
|
Email: "a@b.com", |
||||||
|
URL: "www.a.com", |
||||||
|
Number: "1", |
||||||
|
Value: "$1.00", |
||||||
|
Percent: "0.01", |
||||||
|
Duration: "60", |
||||||
|
}; |
||||||
|
|
||||||
|
// links/ computed fields
|
||||||
|
let records2 = { |
||||||
|
Done: true, |
||||||
|
Date: "2022-05-31", |
||||||
|
Rating: "1", |
||||||
|
Actor: ["Actor1", "Actor2"], |
||||||
|
"Status (from Actor)": ["Todo", "In progress"], |
||||||
|
RollUp: "128", |
||||||
|
Computation: "4.04", |
||||||
|
Producer: ["P1", "P2"] |
||||||
|
}; |
||||||
|
|
||||||
|
function openWebhook(index) { |
||||||
|
cy.get(".nc-btn-webhook").should("exist").click(); |
||||||
|
cy.get(".nc-hook").eq(index).click({ force: true }); |
||||||
|
} |
||||||
|
|
||||||
|
// to be invoked after open
|
||||||
|
function verifyWebhook(config) { |
||||||
|
cy.get(".nc-text-field-hook-title") |
||||||
|
.find('input').then(($element) => { |
||||||
|
expect($element[0].value).to.have.string(config.title) |
||||||
|
}) |
||||||
|
cy.get(".nc-text-field-hook-event") |
||||||
|
.find('.v-select__selection') |
||||||
|
.contains(config.event) |
||||||
|
.should('exist') |
||||||
|
cy.get(".nc-text-field-hook-notification-type") |
||||||
|
.find('.v-select__selection') |
||||||
|
.contains(config.notification) |
||||||
|
.should('exist') |
||||||
|
cy.get('.nc-select-hook-url-method') |
||||||
|
.find('.v-select__selection') |
||||||
|
.contains(config.type) |
||||||
|
.should('exist') |
||||||
|
cy.get(".nc-text-field-hook-url-path") |
||||||
|
.find('input').then(($element) => { |
||||||
|
expect($element[0].value).to.have.string(config.url) |
||||||
|
}) |
||||||
|
cy.get(".nc-icon-hook-navigate-left").click({force:true}) |
||||||
|
} |
||||||
|
|
||||||
|
export const genTest = (apiType, dbType) => { |
||||||
|
if (!isTestSuiteActive(apiType, dbType)) return; |
||||||
|
describe(`Webhook`, () => { |
||||||
|
before(() => { |
||||||
|
cy.task("copyFile") |
||||||
|
loginPage.signIn(roles.owner.credentials); |
||||||
|
projectsPage.openProject("sample"); |
||||||
|
}); |
||||||
|
|
||||||
|
after(() => {}); |
||||||
|
|
||||||
|
it("Verify Data types", () => { |
||||||
|
cy.openTableTab("Film", 3); |
||||||
|
|
||||||
|
// normal cells
|
||||||
|
for (let [key, value] of Object.entries(records)) { |
||||||
|
mainPage.getCell(key, 1).contains(value).should("exist"); |
||||||
|
} |
||||||
|
|
||||||
|
// checkbox
|
||||||
|
mainPage |
||||||
|
.getCell("Done", 1) |
||||||
|
.find(".mdi-check-circle-outline") |
||||||
|
.should(records2.Done ? "exist" : "not.exist"); |
||||||
|
|
||||||
|
// date
|
||||||
|
|
||||||
|
// rating
|
||||||
|
mainPage |
||||||
|
.getCell("Rating", 1) |
||||||
|
.find("button.mdi-star") |
||||||
|
.should("have.length", records2.Rating); |
||||||
|
|
||||||
|
// LinkToAnotherRecord
|
||||||
|
mainPage.getCell("Actor", 1).scrollIntoView(); |
||||||
|
cy.get( |
||||||
|
':nth-child(1) > [data-col="Actor"] > .nc-virtual-cell > .v-lazy > .d-100 > .chips > :nth-child(1) > .v-chip__content > .name' |
||||||
|
) |
||||||
|
.contains(records2.Actor[0]) |
||||||
|
.should("exist"); |
||||||
|
cy.get( |
||||||
|
':nth-child(1) > [data-col="Actor"] > .nc-virtual-cell > .v-lazy > .d-100 > .chips > :nth-child(2) > .v-chip__content > .name' |
||||||
|
) |
||||||
|
.contains(records2.Actor[1]) |
||||||
|
.should("exist"); |
||||||
|
// mainPage.getCell("Actor", 1).find(".nc-virtual-cell > .v-lazy > .d-100 > .chips").eq(0).contains("Actor1").should('exist')
|
||||||
|
// mainPage.getCell("Actor", 1).find(".nc-virtual-cell > .v-lazy > .d-100 > .chips").eq(1).contains("Actor2").should('exist')
|
||||||
|
|
||||||
|
// lookup
|
||||||
|
mainPage.getCell("Status (from Actor)", 1).scrollIntoView(); |
||||||
|
cy.get( |
||||||
|
':nth-child(1) > [data-col="Status (from Actor)"] > .nc-virtual-cell > .v-lazy > .d-flex > :nth-child(1) > .v-chip__content > div > .set-item' |
||||||
|
) |
||||||
|
.contains(records2["Status (from Actor)"][0]) |
||||||
|
.should("exist"); |
||||||
|
cy.get( |
||||||
|
':nth-child(1) > [data-col="Status (from Actor)"] > .nc-virtual-cell > .v-lazy > .d-flex > :nth-child(2) > .v-chip__content > div > .set-item' |
||||||
|
) |
||||||
|
.contains(records2["Status (from Actor)"][1]) |
||||||
|
.should("exist"); |
||||||
|
|
||||||
|
// rollup
|
||||||
|
mainPage.getCell("RollUp", 1).scrollIntoView(); |
||||||
|
// cy.get(':nth-child(1) > [data-col="RollUp"] > .nc-virtual-cell > .v-lazy > span').contains(records2.RollUp).should('exist')
|
||||||
|
cy.get(`:nth-child(1) > [data-col="RollUp"] > .nc-virtual-cell`) |
||||||
|
.contains(records2.RollUp) |
||||||
|
.should("exist"); |
||||||
|
|
||||||
|
// formula
|
||||||
|
mainPage.getCell("Computation", 1).scrollIntoView(); |
||||||
|
cy.get( |
||||||
|
`:nth-child(1) > [data-col="Computation"] > .nc-virtual-cell` |
||||||
|
) |
||||||
|
.contains(records2.Computation) |
||||||
|
.should("exist"); |
||||||
|
|
||||||
|
// ltar hm relation
|
||||||
|
mainPage.getCell("Producer", 1).scrollIntoView(); |
||||||
|
cy.get( |
||||||
|
':nth-child(1) > [data-col="Producer"] > .nc-virtual-cell > .v-lazy > .d-100 > .chips > :nth-child(1) > .v-chip__content > .name' |
||||||
|
) |
||||||
|
.contains(records2.Producer[0]) |
||||||
|
.should("exist"); |
||||||
|
cy.get( |
||||||
|
':nth-child(1) > [data-col="Producer"] > .nc-virtual-cell > .v-lazy > .d-100 > .chips > :nth-child(2) > .v-chip__content > .name' |
||||||
|
) |
||||||
|
.contains(records2.Producer[1]) |
||||||
|
.should("exist"); |
||||||
|
|
||||||
|
cy.closeTableTab("Film"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Verify Views & Shared base", () => { |
||||||
|
cy.openTableTab("Film", 3); |
||||||
|
cy.get('.nc-form-view-item').eq(0) |
||||||
|
.click({ force: true }) |
||||||
|
|
||||||
|
// Header & description should exist
|
||||||
|
cy.get(".nc-form") |
||||||
|
.find('[placeholder="Form Title"]') |
||||||
|
.contains("FormTitle") |
||||||
|
.should("exist"); |
||||||
|
cy.get(".nc-form") |
||||||
|
.find('[placeholder="Add form description"]') |
||||||
|
.contains("FormDescription") |
||||||
|
.should("exist"); |
||||||
|
|
||||||
|
// modified column name & help text
|
||||||
|
cy.get(".nc-field-wrapper").eq(0) |
||||||
|
.find('.nc-field-labels') |
||||||
|
.contains("DisplayName") |
||||||
|
.should('exist') |
||||||
|
cy.get(".nc-field-wrapper").eq(0) |
||||||
|
.find('.nc-hint') |
||||||
|
.contains('HelpText') |
||||||
|
.should('exist') |
||||||
|
|
||||||
|
cy.get(".nc-field-wrapper").eq(1) |
||||||
|
.find('.nc-field-labels') |
||||||
|
.contains("Email") |
||||||
|
.should('exist') |
||||||
|
|
||||||
|
// add message
|
||||||
|
cy.get(".nc-form > .mx-auto") |
||||||
|
.find("textarea").then(($element) => { |
||||||
|
expect($element[0].value).to.have.string("Thank you for submitting the form!") |
||||||
|
}) |
||||||
|
|
||||||
|
// submit another form button
|
||||||
|
cy.get(".nc-form > .mx-auto") |
||||||
|
.find('[type="checkbox"]') |
||||||
|
.eq(0) |
||||||
|
.should('be.checked') |
||||||
|
// "New form after 5 seconds" button
|
||||||
|
cy.get(".nc-form > .mx-auto") |
||||||
|
.find('[type="checkbox"]') |
||||||
|
.eq(1) |
||||||
|
.should('be.checked') |
||||||
|
// email me
|
||||||
|
cy.get(".nc-form > .mx-auto") |
||||||
|
.find('[type="checkbox"]') |
||||||
|
.eq(2) |
||||||
|
.should('not.be.checked') |
||||||
|
|
||||||
|
cy.closeTableTab("Film"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Verify Webhooks", () => { |
||||||
|
cy.openTableTab("Actor", 25); |
||||||
|
openWebhook(0) |
||||||
|
verifyWebhook({ |
||||||
|
title: "Webhook-1", |
||||||
|
event: "After Insert", |
||||||
|
notification: "URL", |
||||||
|
type: "POST", |
||||||
|
url: "http://localhost:9090/hook", |
||||||
|
condition: false |
||||||
|
}) |
||||||
|
cy.get("body").type("{esc}"); |
||||||
|
|
||||||
|
openWebhook(1) |
||||||
|
verifyWebhook({ |
||||||
|
title: "Webhook-2", |
||||||
|
event: "After Update", |
||||||
|
notification: "URL", |
||||||
|
type: "POST", |
||||||
|
url: "http://localhost:9090/hook", |
||||||
|
condition: false |
||||||
|
}) |
||||||
|
cy.get("body").type("{esc}"); |
||||||
|
|
||||||
|
openWebhook(2) |
||||||
|
verifyWebhook({ |
||||||
|
title: "Webhook-3", |
||||||
|
event: "After Delete", |
||||||
|
notification: "URL", |
||||||
|
type: "POST", |
||||||
|
url: "http://localhost:9090/hook", |
||||||
|
condition: false |
||||||
|
}) |
||||||
|
cy.get("body").type("{esc}"); |
||||||
|
|
||||||
|
cy.closeTableTab("Actor"); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
it("Pagination", () => { |
||||||
|
cy.openTableTab("Actor", 25); |
||||||
|
|
||||||
|
cy.get(".nc-pagination").should("exist"); |
||||||
|
|
||||||
|
// verify > pagination option
|
||||||
|
mainPage.getPagination(">").click(); |
||||||
|
mainPage |
||||||
|
.getPagination(2) |
||||||
|
.should("have.class", "v-pagination__item--active"); |
||||||
|
|
||||||
|
// verify < pagination option
|
||||||
|
mainPage.getPagination("<").click(); |
||||||
|
mainPage |
||||||
|
.getPagination(1) |
||||||
|
.should("have.class", "v-pagination__item--active"); |
||||||
|
|
||||||
|
cy.closeTableTab("Actor"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Verify Fields, Filter & Sort", () => { |
||||||
|
cy.openTableTab("Actor", 25); |
||||||
|
cy.get(".nc-grid-view-item").eq(1).click() |
||||||
|
|
||||||
|
cy.get(".nc-grid-header-cell").contains('Name').should("be.visible"); |
||||||
|
cy.get(".nc-grid-header-cell").contains('Notes').should("be.visible"); |
||||||
|
cy.get(".nc-grid-header-cell").contains('Attachments').should("not.be.visible"); |
||||||
|
cy.get(".nc-grid-header-cell").contains('Status').should("be.visible"); |
||||||
|
cy.get(".nc-grid-header-cell").contains('Film').should("be.visible"); |
||||||
|
|
||||||
|
cy.get(".nc-fields-menu-btn").click(); |
||||||
|
cy.getActiveMenu().find(`[type="checkbox"]`).eq(0).should('be.checked') |
||||||
|
cy.getActiveMenu().find(`[type="checkbox"]`).eq(1).should('be.checked') |
||||||
|
cy.getActiveMenu().find(`[type="checkbox"]`).eq(2).should('not.be.checked') |
||||||
|
cy.getActiveMenu().find(`[type="checkbox"]`).eq(3).should('be.checked') |
||||||
|
cy.getActiveMenu().find(`[type="checkbox"]`).eq(4).should('be.checked') |
||||||
|
cy.get(".nc-fields-menu-btn").click(); |
||||||
|
|
||||||
|
cy.get(".nc-sort-menu-btn").click(); |
||||||
|
cy.get(".nc-sort-field-select").eq(0) |
||||||
|
.contains('Name') |
||||||
|
.should("exist"); |
||||||
|
cy.get(".nc-sort-dir-select").eq(0) |
||||||
|
.contains('A -> Z') |
||||||
|
.should("exist"); |
||||||
|
cy.get(".nc-sort-menu-btn").click(); |
||||||
|
|
||||||
|
cy.get(".nc-filter-menu-btn").click(); |
||||||
|
cy.get(".nc-filter-field-select").eq(0) |
||||||
|
.contains('Name') |
||||||
|
.should("exist"); |
||||||
|
cy.get(".nc-filter-operation-select").eq(0) |
||||||
|
.contains('is like') |
||||||
|
.should("exist"); |
||||||
|
|
||||||
|
cy.get(".nc-filter-field-select").eq(1) |
||||||
|
.contains('Name') |
||||||
|
.should("exist"); |
||||||
|
cy.get(".nc-filter-operation-select").eq(1) |
||||||
|
.contains('is like') |
||||||
|
.should("exist"); |
||||||
|
cy.get(".nc-filter-menu-btn").click(); |
||||||
|
|
||||||
|
cy.closeTableTab("Actor"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Views, bt relation", () => { |
||||||
|
cy.openTableTab("Producer", 3) |
||||||
|
cy.get('.nc-grid-view-item').should('have.length', 4) |
||||||
|
cy.get('.nc-form-view-item').should('have.length', 4) |
||||||
|
cy.get('.nc-gallery-view-item').should('have.length', 3) |
||||||
|
|
||||||
|
// LinkToAnotherRecord hm relation
|
||||||
|
mainPage.getCell("FilmRead", 1).scrollIntoView(); |
||||||
|
cy.get( |
||||||
|
':nth-child(1) > [data-col="FilmRead"] > .nc-virtual-cell > .v-lazy > .d-100 > .chips > :nth-child(1) > .v-chip__content > .name' |
||||||
|
) |
||||||
|
.contains('Movie-1') |
||||||
|
.should("exist"); |
||||||
|
|
||||||
|
cy.closeTableTab("Producer") |
||||||
|
}) |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
genTest("rest", "xcdb"); |
||||||
|
|
||||||
|
/** |
||||||
|
* @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,36 @@ |
|||||||
|
let t9a = require("../common/9a_QuickTest"); |
||||||
|
const { |
||||||
|
setCurrentMode, |
||||||
|
} = require("../../support/page_objects/projectConstants"); |
||||||
|
|
||||||
|
// use 0 as mode to execute individual files (debug mode, skip pre-configs)
|
||||||
|
// use 1 mode if noco.db doesnt contain user credentials (full run over GIT)
|
||||||
|
|
||||||
|
const nocoTestSuite = (apiType, dbType) => { |
||||||
|
setCurrentMode(apiType, dbType); |
||||||
|
t9a.genTest(apiType, dbType); |
||||||
|
}; |
||||||
|
|
||||||
|
nocoTestSuite("rest", "xcdb"); |
||||||
|
|
||||||
|
/** |
||||||
|
* @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