Merge branch 'release/marketplace-sprint-2' into community/marketplace

This commit is contained in:
Akshay 2024-06-26 13:01:25 +05:30 committed by GitHub
commit a29f025012
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 615 additions and 298 deletions

View file

@ -4,15 +4,11 @@ on:
push:
branches: [develop, main]
pull_request:
types: [labeled, opened, synchronize, reopened]
types: [labeled, unlabeled, closed]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main') && github.run_number || github.ref }}
cancel-in-progress: true
env:
FORCE_COLOR: true
NODE_OPTIONS: "--max-old-space-size=4096"
@ -25,75 +21,287 @@ env:
PG_PASS: postgres
PG_DB: tooljet_test
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
build:
runs-on: ubuntu-latest
if: |
contains(github.event.pull_request.labels.*.name, 'run-ci') ||
github.ref == 'refs/heads/main' ||
github.ref == 'refs/heads/develop'
if: ${{ github.event.action == 'labeled' && github.event.label.name == 'run-ci' }}
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
- name: Checkout repository
uses: actions/checkout@v3
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
- name: Cache node modules
# Cache server node modules to speed up subsequent builds
- name: Cache server node modules
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') }}
path: server/node_modules
key: ${{ runner.os }}-node-server-${{ hashFiles('server/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
${{ runner.os }}-node-server-
- run: npm run build
# Cache frontend node modules to speed up subsequent builds
- name: Cache frontend node modules
uses: actions/cache@v3
with:
path: frontend/node_modules
key: ${{ runner.os }}-node-frontend-${{ hashFiles('frontend/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-frontend-
lint:
# Cache plugins node modules to speed up subsequent builds
- name: Cache plugins node modules
uses: actions/cache@v3
with:
path: plugins/node_modules
key: ${{ runner.os }}-node-plugins-${{ hashFiles('plugins/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-plugins-
- name: Setup Python 3
uses: actions/setup-python@v2
with:
python-version: 3.x
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools
- name: Install Node.js dependencies
run: npm ci
- name: Build
run: npm run build
- name: Frontend ci
run: |
npm --prefix frontend ci
- name: Server ci
run: npm --prefix server ci
- name: plugins ci
run: npm --prefix plugins ci
# Upload plugins build artifacts
- name: Archive specific plugins files and folders
uses: actions/upload-artifact@v4
with:
name: plugins-files
path: |
plugins/dist
plugins/client.js
plugins/node_modules
plugins/packages/common
plugins/package.json
# Upload server build artifacts
- name: Archive specific server files and folders
uses: actions/upload-artifact@v4
with:
name: server-files
path: |
server/dist
# Upload frontend build artifacts
- name: Archive specific frontend files and folders
uses: actions/upload-artifact@v4
with:
name: frontend-files
path: |
frontend/build
lint-for-plugins:
runs-on: ubuntu-latest
if: |
contains(github.event.pull_request.labels.*.name, 'run-ci') ||
github.ref == 'refs/heads/main' ||
github.ref == 'refs/heads/develop'
needs: build
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
- name: Checkout repository
uses: actions/checkout@v3
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
- name: Cache node modules
# Cache server node modules to speed up subsequent builds
- name: Cache server node modules
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') }}
path: server/node_modules
key: ${{ runner.os }}-node-server-${{ hashFiles('server/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
${{ runner.os }}-node-server-
- run: npm run build:plugins
- run: npm --prefix frontend ci && npm --prefix server ci && npm --prefix plugins ci
- run: npm --prefix server run lint && npm --prefix frontend run lint && npm --prefix plugins run lint
# Cache frontend node modules to speed up subsequent builds
- name: Cache frontend node modules
uses: actions/cache@v3
with:
path: frontend/node_modules
key: ${{ runner.os }}-node-frontend-${{ hashFiles('frontend/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-frontend-
# Cache plugins node modules to speed up subsequent builds
- name: Cache plugins node modules
uses: actions/cache@v3
with:
path: plugins/node_modules
key: ${{ runner.os }}-node-plugins-${{ hashFiles('plugins/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-plugins-
- name: Setup Python 3
uses: actions/setup-python@v2
with:
python-version: 3.x
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools
# Download plugins build artifacts
- name: Download plugins files and folders
uses: actions/download-artifact@v4
with:
name: plugins-files
- name: Running for plugins
run: |
npm --prefix plugins run lint
lint-for-frontend:
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
# Cache server node modules to speed up subsequent builds
- name: Cache server node modules
uses: actions/cache@v3
with:
path: server/node_modules
key: ${{ runner.os }}-node-server-${{ hashFiles('server/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-server-
# Cache frontend node modules to speed up subsequent builds
- name: Cache frontend node modules
uses: actions/cache@v3
with:
path: frontend/node_modules
key: ${{ runner.os }}-node-frontend-${{ hashFiles('frontend/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-frontend-
# Cache plugins node modules to speed up subsequent builds
- name: Cache plugins node modules
uses: actions/cache@v3
with:
path: plugins/node_modules
key: ${{ runner.os }}-node-plugins-${{ hashFiles('plugins/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-plugins-
- name: Setup Python 3
uses: actions/setup-python@v2
with:
python-version: 3.x
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools
# Download frontend build artifacts
- name: Download frontend files and folders
uses: actions/download-artifact@v4
with:
name: frontend-files
- name: Running for frontend
run: |
npm --prefix frontend run lint
lint-for-server:
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
# Cache server node modules to speed up subsequent builds
- name: Cache server node modules
uses: actions/cache@v3
with:
path: server/node_modules
key: ${{ runner.os }}-node-server-${{ hashFiles('server/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-server-
# Cache frontend node modules to speed up subsequent builds
- name: Cache frontend node modules
uses: actions/cache@v3
with:
path: frontend/node_modules
key: ${{ runner.os }}-node-frontend-${{ hashFiles('frontend/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-frontend-
# Cache plugins node modules to speed up subsequent builds
- name: Cache plugins node modules
uses: actions/cache@v3
with:
path: plugins/node_modules
key: ${{ runner.os }}-node-plugins-${{ hashFiles('plugins/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-plugins-
- name: Setup Python 3
uses: actions/setup-python@v2
with:
python-version: 3.x
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools
# Download server build artifacts
- name: Download server files and folders
uses: actions/download-artifact@v4
with:
name: server-files
- name: Running for server
run: |
npm --prefix server run lint
unit-test:
runs-on: ubuntu-latest
timeout-minutes: 30 # Set a timeout of 30 minutes
needs: build
container: node:18.18.2-buster
container: node:18.18.2-bullseye
services:
postgres:
image: postgres
@ -119,7 +327,7 @@ jobs:
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- run: apt update && apt install -y postgresql
- run: apt update && apt install -y postgresql-client
- run: npm --prefix plugins ci
- run: npm --prefix plugins run create:client && npm --prefix plugins run create:server
- run: npm --prefix plugins run build:packages && npm --prefix plugins run build:server
@ -130,8 +338,9 @@ jobs:
e2e-test:
runs-on: ubuntu-latest
timeout-minutes: 30 # Set a timeout of 30 minutes
needs: build
container: node:18.18.2-buster
container: node:18.18.2-bullseye
services:
postgres:
image: postgres
@ -157,11 +366,11 @@ jobs:
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- run: apt update && apt install -y postgresql
- run: apt update && apt install -y postgresql-client
- run: npm --prefix plugins ci
- run: npm --prefix plugins run create:client && npm --prefix plugins run create:server
- run: npm --prefix plugins run build:packages && npm --prefix plugins run build:server
- run: npm --prefix server ci
- run: npm --prefix server run db:create
- run: npm --prefix server run db:migrate
- run: NODE_OPTIONS=--max_old_space_size=8096 npm --prefix server run test:e2e -- --silent --testTimeout=20000
- run: NODE_OPTIONS=--max_old_space_size=8096 npm --prefix server run test:e2e -- --silent --testTimeout=20000

View file

@ -97,11 +97,11 @@ jobs:
# Remove the existing tooljet/* images
sudo docker images -a | grep 'tooljet/' | awk '{print $3}' | xargs sudo docker rmi -f
#checking images
# Check remaining images
sudo docker images
# Update docker-compose.yml with the new image
sed -i '/^[[:space:]]*tooljet:/,/^$/ s|^\([[:space:]]*image:[[:space:]]*\).*|\1tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}|' docker-compose.yaml
# Update docker-compose.yml with the new image for tooljet service
sed -i '/^[[:space:]]*tooljet:/,/^[[:space:]]*[^:]*$/ { /^[[:space:]]*image:[[:space:]]*tooljet\/tj-osv/s|\(image:[[:space:]]*\).*|\1tooljet/tj-osv:'"${{ env.SAFE_BRANCH_NAME }}"'| }' docker-compose.yml
# Start the Docker containers
cat docker-compose.yaml

View file

@ -1 +1 @@
2.61.1
2.61.3

View file

@ -71,13 +71,13 @@ describe("App Version Functionality", () => {
cy.openApp()
cy.get('[data-cy="widget-list-box-table"]').should("be.visible");
cy.dragAndDropWidget("Toggle Switch", 50, 50);
verifyComponent("toggleswitch1");
cy.dragAndDropWidget("Text", 50, 50);
verifyComponent("text1");
navigateToCreateNewVersionModal((currentVersion = "v1"));
createNewVersion((newVersion = ["v2"]), (versionFrom = "v1"));
verifyComponent("toggleswitch1");
deleteComponentAndVerify("toggleswitch1");
verifyComponent("text1");
deleteComponentAndVerify("text1");
cy.dragAndDropWidget(buttonText.defaultWidgetText);
verifyComponent("button1");
@ -87,7 +87,7 @@ describe("App Version Functionality", () => {
navigateToCreateNewVersionModal((currentVersion = "v3"));
createNewVersion((newVersion = ["v4"]), (versionFrom = "v1"));
verifyComponent("toggleswitch1");
verifyComponent("text1");
editVersionAndVerify(
(currentVersion = "v4"),

View file

@ -12,9 +12,13 @@ describe("Manage SSO for multi workspace", () => {
beforeEach(() => {
cy.defaultWorkspaceLogin();
SSO.deleteOrganisationSSO("My workspace", ["google", "git"]);
SSO.resetDomain();
});
after(() => {
cy.defaultWorkspaceLogin();
SSO.resetDomain();
});
it("Should verify General settings page elements", () => {
SSO.resetDomain();
SSO.setSignupStatus(false);
SSO.defaultSSO(true);

View file

@ -290,54 +290,38 @@ describe("Workspace constants", () => {
cy.apiCreateApp(data.appName);
cy.getCookie("tj_auth_token").then((cookie) => {
const headers = {
"Tj-Workspace-Id": Cypress.env("workspaceId"),
Cookie: `tj_auth_token=${cookie.value}`,
};
cy.request({
method: "GET",
url: `http://localhost:3000/api/app-environments/versions?app_id=${Cypress.env(
"appId"
)}`,
headers: headers,
}).then((response) => {
const appVersions = response.body.appVersions;
const appVersionId = appVersions[0].id;
createDataQuery(
appVersionId,
data.restapilink,
data.restapiHeaderKey,
data.restapiHeaderValue
);
});
});
cy.wait(1000);
createDataQuery(
data.appName,
data.restapilink,
data.restapiHeaderKey,
data.restapiHeaderValue
);
cy.openApp();
cy.get(".custom-toggle-switch>.switch>").eq(3).click();
cy.waitForAutoSave();
cy.dragAndDropWidget("Text", 550, 650);
cy.dragAndDropWidget("Text Input", 550, 650);
editAndVerifyWidgetName(data.widgetName, []);
cy.waitForAutoSave();
cy.get(
'[data-cy="textcomponenttextinput-input-field"]'
'[data-cy="default-value-input-field"]'
).clearAndTypeOnCodeMirror(`{{queries.restapi1.data.message`);
cy.forceClickOnCanvas();
cy.waitForAutoSave();
cy.get(dataSourceSelector.queryCreateAndRunButton).click();
cy.get(
commonWidgetSelector.draggableWidget(data.widgetName)
).verifyVisibleElement("have.text", "Production environment testing");
).verifyVisibleElement("have.value", "Production environment testing");
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.wait(4000);
cy.get(
commonWidgetSelector.draggableWidget(data.widgetName)
).verifyVisibleElement("have.text", "Production environment testing");
).verifyVisibleElement("have.value", "Production environment testing");
});
it("should verify the constants resolving in datasource connection form", () => {
data.ds = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");

View file

@ -143,10 +143,6 @@ describe("Bulk user upload", () => {
});
cy.get(usersSelector.buttonUploadUsers).click();
cy.wait(10000);
cy.get(".go2072408551")
.should("be.visible")
.and("have.text", "250 users are being added");
cy.wait(1000);
common.searchUser("test12@gmail.com");
cy.contains("td", "test12@gmail.com")
.parent()

View file

@ -159,36 +159,52 @@ export const selectDatasource = (datasourceName) => {
cy.get(`[data-cy="${cyParamName(datasourceName)}-button"]`).click();
};
export const createDataQuery = (versionId, url, key, value) => {
cy.getCookie("tj_auth_token").then((cookie) => {
const headers = {
"Tj-Workspace-Id": Cypress.env("workspaceId"),
Cookie: `tj_auth_token=${cookie.value}`,
};
cy.request({
method: "POST",
url: "http://localhost:3000/api/data_queries",
headers: headers,
body: {
app_id: Cypress.env("appId"),
app_version_id: versionId,
name: "restapi1",
kind: "restapi",
options: {
method: "get",
url: `{{constants.${url}}}`,
url_params: [["", ""]],
headers: [[`{{constants.${key}}}`, `{{constants.${value}}}`]],
body: [["", ""]],
json_body: null,
body_toggle: false,
transformationLanguage: "javascript",
enableTransformation: false,
},
data_source_id: null,
},
}).then((response) => {
expect(response.status).to.equal(201);
export const createDataQuery = (appName, url, key, value) => {
let appId, versionId;
cy.task("updateId", {
dbconfig: Cypress.env("app_db"),
sql: `select id from apps where name='${appName}';`,
}).then((resp) => {
appId = resp.rows[0].id;
cy.task("updateId", {
dbconfig: Cypress.env("app_db"),
sql: `select id from app_versions where app_id='${appId}';`,
}).then((resp) => {
versionId = resp.rows[0].id;
cy.getCookie("tj_auth_token").then((cookie) => {
const headers = {
"Tj-Workspace-Id": Cypress.env("workspaceId"),
Cookie: `tj_auth_token=${cookie.value}`,
};
cy.request({
method: "POST",
url: "http://localhost:3000/api/data_queries",
headers: headers,
body: {
app_id: appId,
app_version_id: versionId,
name: "restapi1",
kind: "restapi",
options: {
method: "get",
url: `{{constants.${url}}}`,
url_params: [["", ""]],
headers: [[`{{constants.${key}}}`, `{{constants.${value}}}`]],
body: [["", ""]],
json_body: null,
body_toggle: false,
transformationLanguage: "javascript",
enableTransformation: false,
},
data_source_id: null,
},
}).then((response) => {
expect(response.status).to.equal(201);
});
});
});
});
};

View file

@ -1 +1 @@
2.61.1
2.61.3

View file

@ -87,7 +87,7 @@ export const Modal = function Modal({
return;
}
const canShowModal = exposedVariables.show ?? false;
fireEvent(canShowModal ? 'onOpen' : 'onClose');
fireEvent(!canShowModal && 'onClose');
setShowModal(exposedVariables.show ?? false);
const inputRef = document?.getElementsByClassName('tj-text-input-widget')?.[0];
inputRef?.blur();
@ -223,6 +223,7 @@ export const Modal = function Modal({
event.stopPropagation();
setShowModal(true);
setExposedVariable('show', true);
fireEvent('onOpen');
}}
data-cy={`${dataCy}-launch-button`}
>

View file

@ -242,7 +242,7 @@ const DropDownSelect = ({
style={{
position: 'relative',
left: '-10px',
top: selected.label === null || selected.label === undefined ? '0px' : '2px',
top: selected.label === null || selected.label === undefined || isCellEdit ? '0px' : '2px',
paddingLeft: '10px',
paddingBottom: '4px',
}}
@ -251,10 +251,11 @@ const DropDownSelect = ({
setShowMenu(true);
}}
>
<span
<p
className={cx({
'd-flex align-items-center justify-content-center':
'd-flex align-items-center justify-content-center m-0':
selected.label === null || selected.label === undefined,
'cell-menu-text m-0': selected.label !== null || selected.label !== undefined,
})}
style={{
color: darkMode ? '#fff' : '',
@ -263,7 +264,7 @@ const DropDownSelect = ({
}}
>
{selected.label === null || selected.label === undefined ? 'Null' : selected.label}
</span>
</p>
</div>
) : (
<div className={`col-auto ${buttonClasses}`} id={popoverBtnId.current}>

View file

@ -171,15 +171,15 @@
div {
padding-bottom: 0px !important;
span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 85%;
.cell-menu-text {
white-space: nowrap ;
overflow: hidden ;
text-overflow: ellipsis ;
width: 85% ;
color: var(--slate11) !important;
cursor: default;
border-radius: 6px;
height: 20px;
// height: 20px;
font-size: 12px;
}
}
@ -190,7 +190,7 @@
.tjdb-td-wrapper {
div {
padding-bottom: 0px !important;
span {
.cell-menu-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@ -198,7 +198,7 @@
color: var(--slate11) !important;
cursor: default;
border-radius: 6px;
height: 20px;
// height: 20px;
font-size: 12px;
}
}

View file

@ -7,7 +7,7 @@
justify-content: center;
align-items: center;
padding: 10px 20px;
gap: 6px;
gap: 8px;
border-radius: 6px;
font-weight: 600;
text-decoration: none;
@ -51,7 +51,7 @@
.tj-small-btn {
height: 28px;
border-radius: 6px;
padding: 4px 0px;
padding: 4px 16px 4px 16px;
font-size: 12px;
}
@ -198,7 +198,6 @@
color: var(--indigo9);
border: none;
background: transparent;
box-shadow: none !important;
&:hover {
color: var(--indigo10);
@ -214,6 +213,15 @@
background: var(--base);
border: none;
outline: none;
box-shadow: 0px 0px 0px 4px var(--indigo6);
}
&:focus {
box-shadow: 0px 0px 0px 4px var(--indigo6);
}
&:focus:not(:focus-visible) {
box-shadow: none;
}
}

View file

@ -9573,14 +9573,15 @@
"license": "MIT"
},
"node_modules/follow-redirects": {
"version": "1.15.3",
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
@ -13874,11 +13875,11 @@
}
},
"node_modules/node-appwrite": {
"version": "5.1.0",
"license": "BSD-3-Clause",
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-13.0.0.tgz",
"integrity": "sha512-O1g1RWcRYcvnVvR0Dykjoe/4UcL6ELVvZosb4B8/hhNvleuWfN2fOFxzeCaU8amGW6UtVtZqvNaUT3IF1mPBdA==",
"dependencies": {
"axios": "^0.26.1",
"form-data": "^4.0.0"
"node-fetch-native-with-agent": "1.7.2"
}
},
"node_modules/node-fetch": {
@ -13899,6 +13900,11 @@
}
}
},
"node_modules/node-fetch-native-with-agent": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/node-fetch-native-with-agent/-/node-fetch-native-with-agent-1.7.2.tgz",
"integrity": "sha512-5MaOOCuJEvcckoz7/tjdx1M6OusOY6Xc5f459IaruGStWnKzlI1qpNgaAwmn4LmFYcsSlj+jBMk84wmmRxfk5g=="
},
"node_modules/node-fetch-npm": {
"version": "2.0.4",
"license": "MIT",
@ -19944,7 +19950,7 @@
"version": "1.0.0",
"dependencies": {
"@tooljet-plugins/common": "file:../common",
"node-appwrite": "^5.1.0",
"node-appwrite": "^13.0.0",
"react": "^17.0.2"
}
},

View file

@ -1,21 +1,27 @@
import { QueryError, QueryResult, QueryService, ConnectionTestResult } from '@tooljet-plugins/common';
import { SourceOptions, QueryOptions } from './types';
import { SourceOptions, QueryOptions, ReturnObject } from './types';
import sdk from 'node-appwrite';
import { bulkUpdate, createDocument, deleteDocument, getDocument, queryCollection, updateDocument } from './operations';
import { createDocument, deleteDocument, getDocument, queryCollection, updateDocument } from './operations';
const JSON5 = require('json5');
export default class Appwrite implements QueryService {
async run(sourceOptions: SourceOptions, queryOptions: QueryOptions, dataSourceId: string): Promise<QueryResult> {
const database = await this.getConnection(sourceOptions);
const { database_id } = sourceOptions;
const operation = queryOptions.operation;
const body = this.returnObject(queryOptions.body);
const isBodyEmpty = !Object.keys(body).length;
let result = {};
try {
if (!queryOptions.collectionId) {
throw new Error('Collection id is required.');
}
switch (operation) {
case 'list_docs':
result = await queryCollection(
database,
database_id,
queryOptions.collectionId,
queryOptions.limit,
queryOptions.order_fields,
@ -26,24 +32,27 @@ export default class Appwrite implements QueryService {
);
break;
case 'get_document':
result = await getDocument(database, queryOptions.collectionId, queryOptions.documentId);
if (!queryOptions.documentId) throw new Error('Document id is required');
result = await getDocument(database, database_id, queryOptions.collectionId, queryOptions.documentId);
break;
case 'add_document':
result = await createDocument(database, queryOptions.collectionId, body);
if (isBodyEmpty) throw new Error('Body is required');
result = await createDocument(database, database_id, queryOptions.collectionId, body as object);
break;
case 'update_document':
result = await updateDocument(database, queryOptions.collectionId, queryOptions.documentId, body);
if (!queryOptions.documentId) throw new Error('Document id is required');
if (isBodyEmpty) throw new Error('Body is required');
result = await updateDocument(
database,
database_id,
queryOptions.collectionId,
queryOptions.documentId,
body as object
);
break;
case 'delete_document':
result = await deleteDocument(database, queryOptions.collectionId, queryOptions.documentId);
break;
case 'bulk_update':
result = await bulkUpdate(
database,
queryOptions.collectionId,
this.returnObject(queryOptions.records),
queryOptions['document_id_key']
);
if (!queryOptions.documentId) throw new Error('Document id is required');
result = await deleteDocument(database, database_id, queryOptions.collectionId, queryOptions.documentId);
break;
}
} catch (error) {
@ -56,14 +65,14 @@ export default class Appwrite implements QueryService {
};
}
private returnObject(data: any) {
returnObject(data: ReturnObject | string): ReturnObject {
if (!data) {
return {};
}
return typeof data === 'string' ? JSON5.parse(data) : data;
}
async getConnection(sourceOptions: SourceOptions, _options?: object): Promise<sdk.Database> {
async getConnection(sourceOptions: SourceOptions, _options?: object): Promise<sdk.Databases> {
const { host, secret_key, project_id } = sourceOptions;
const client = new sdk.Client();
@ -72,20 +81,20 @@ export default class Appwrite implements QueryService {
.setProject(project_id) // Your project ID
.setKey(secret_key); // Your secret API key;
return new sdk.Database(client);
return new sdk.Databases(client);
}
async testConnection(sourceOptions: SourceOptions): Promise<ConnectionTestResult> {
const databaseClient = await this.getConnection(sourceOptions);
if (!databaseClient) {
throw new Error('Invalid credentials');
try {
await databaseClient.listCollections(sourceOptions.database_id);
return {
status: 'ok',
};
} catch (err) {
throw new Error(`Connection test failed - ${err.message}`);
}
await databaseClient.listCollections();
return {
status: 'ok',
};
}
}

View file

@ -32,6 +32,12 @@
"type": "text",
"description": "Appwrite project id"
},
"database_id": {
"label": "Database ID",
"key": "database_id",
"type": "text",
"description": "Appwrite Database id"
},
"secret_key": {
"label": "Secret Key",
"key": "secret_key",

View file

@ -27,10 +27,6 @@
"value": "update_document",
"name": "Update Document"
},
{
"value": "bulk_update",
"name": "Bulk update using document id"
},
{
"value": "delete_document",
"name": "Delete Document"
@ -121,6 +117,22 @@
"value": ">=",
"name": ">="
},
{
"value": "is",
"name": "Is"
},
{
"value": "startsWith",
"name": "Starts With"
},
{
"value": "endsWith",
"name": "Ends With"
},
{
"value": "search",
"name": "Search"
},
{
"value": "",
"name": "None"
@ -177,7 +189,8 @@
"type": "codehinter",
"description": "Enter document body",
"height": "150px",
"editorType": "extendedSingleLine"
"editorType": "extendedSingleLine",
"placeholder": "{'name': 'John', 'age': 30}"
}
},
"update_document": {
@ -207,7 +220,8 @@
"type": "codehinter",
"description": "Enter document body",
"height": "150px",
"editorType": "extendedSingleLine"
"editorType": "extendedSingleLine",
"placeholder": "{'name': 'John', 'age': 30}"
}
},
"delete_document": {
@ -231,39 +245,6 @@
"className": "codehinter-plugins col-6",
"placeholder": "Enter document id"
}
},
"bulk_update": {
"collectionId": {
"label": "Collection ID",
"key": "collectionId",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter collection id",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins",
"placeholder": "Enter collection id"
},
"document_id_key": {
"label": "Key for document Id",
"key": "document_id_key",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter key for document Id",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins",
"placeholder": "Enter key for document Id"
},
"records": {
"label": "Records",
"key": "records",
"type": "codehinter",
"mode": "javascript",
"description": "Enter records",
"height": "150px",
"editorType": "extendedSingleLine"
}
}
}
}
}

View file

@ -1,88 +1,150 @@
import { Database, Query } from 'node-appwrite';
import { Databases, Query } from 'node-appwrite';
import { ParsedObject, AwModelDocument, AwModelDocumentList, AwQueryTypes } from './types';
import { isEmpty } from 'lodash';
function computeValue(value: string) {
const numConverted = Number.parseInt(value);
return isNaN(numConverted) ? value : numConverted;
function parseValue(value: ParsedObject) {
try {
return typeof value === 'string' ? JSON.parse(value) : value;
} catch (err) {
return value;
}
}
function computeValue(value: AwQueryTypes | ParsedObject) {
const numConverted = Number.parseInt(value as string);
return isNaN(numConverted) ? parseValue(value as ParsedObject) : numConverted;
}
export async function queryCollection(
db: Database,
db: Databases,
databaseId: string,
collection: string,
limit: number,
order_fields: string[],
order_types: string[],
limit: string,
order_fields: string | string[],
order_types: string | string[],
where_field: string,
where_operation: string,
where_value: any
): Promise<object> {
const limitProvided = isNaN(limit) !== true;
where_value: AwQueryTypes
): Promise<AwModelDocumentList> {
const limitProvided = isNaN(Number.parseInt(limit));
let queryString: string;
where_value = computeValue(where_value);
if (where_field || where_operation || where_value) {
let filterErrorStr: string | string[] = [];
if (!where_field) filterErrorStr.push('Field');
if (!where_operation) filterErrorStr.push('Operator');
if (!where_value) filterErrorStr.push('Value');
if (filterErrorStr.length) {
const suffix = ` ${filterErrorStr.length > 1 ? 'fields are' : 'field is'} required`;
filterErrorStr = filterErrorStr.join(' & ');
filterErrorStr += suffix;
throw new Error(filterErrorStr);
}
where_value = computeValue(where_value);
switch (where_operation) {
case '==':
queryString = Query.equal(where_field, where_value);
break;
case '!=':
queryString = Query.notEqual(where_field, where_value);
break;
case '<':
queryString = Query.lesser(where_field, where_value);
break;
case '>':
queryString = Query.greater(where_field, where_value);
break;
case '>=':
queryString = Query.greaterEqual(where_field, where_value);
break;
case '<=':
queryString = Query.lesserEqual(where_field, where_value);
break;
switch (where_operation) {
case '==':
queryString = Query.equal(where_field, where_value);
break;
case '!=':
queryString = Query.notEqual(where_field, where_value);
break;
case '<':
queryString = Query.lessThan(where_field, where_value);
break;
case '>':
queryString = Query.greaterThan(where_field, where_value);
break;
case '>=':
queryString = Query.greaterThanEqual(where_field, where_value);
break;
case '<=':
queryString = Query.lessThanEqual(where_field, where_value);
break;
case 'is':
if (String(where_value).toLowerCase() === 'not null') {
queryString = Query.isNotNull(where_field);
} else if (String(where_value).toLowerCase() === 'null') {
queryString = Query.isNull(where_field);
}
break;
case 'startsWith':
queryString = Query.startsWith(where_field, where_value as string);
break;
case 'endsWith':
queryString = Query.endsWith(where_field, where_value as string);
break;
case 'search':
queryString = Query.search(where_field, where_value as string);
break;
default:
break;
}
}
return await db.listDocuments(
collection,
queryString ? [queryString] : [],
limitProvided ? limit : 25,
0,
null,
null,
order_fields,
order_types
);
const queries = [Query.limit(!limitProvided ? Number(limit) : 25)];
if (!isEmpty(queryString)) {
queries.push(queryString);
}
if (order_fields || order_types) {
if (!order_fields) throw new Error('Order fields field is required.');
if (!order_types) throw new Error('Order types field is required.');
order_fields = parseValue(order_fields);
order_types = parseValue(order_types);
if (typeof order_fields === 'string' || typeof order_types === 'string') {
throw new Error('Stringify order fields & order types values if not passing as {{["VALUE"]}}');
}
if (order_types.length !== order_fields.length) {
throw new Error('Size of order types & order fields should be same');
}
for (let loop = 0; loop < order_fields.length; loop++) {
if (order_types[loop].toLowerCase() === 'asc') {
queries.push(Query.orderAsc(order_fields[loop]));
}
if (order_types[loop].toLowerCase() === 'desc') {
queries.push(Query.orderDesc(order_fields[loop]));
}
}
}
return await db.listDocuments(databaseId, collection, queries);
}
export async function getDocument(db: Database, collectionId: string, documentId: string): Promise<object> {
return await db.getDocument(collectionId, documentId);
export async function getDocument(
db: Databases,
databaseId: string,
collectionId: string,
documentId: string
): Promise<AwModelDocument> {
return await db.getDocument(databaseId, collectionId, documentId);
}
export async function createDocument(db: Database, collectionId: string, body: object): Promise<object> {
return await db.createDocument(collectionId, 'unique()', body);
export async function createDocument(
db: Databases,
databaseId: string,
collectionId: string,
body: object
): Promise<AwModelDocument> {
return await db.createDocument(databaseId, collectionId, 'unique()', body);
}
export async function updateDocument(
db: Database,
db: Databases,
databaseId: string,
collectionId: string,
documentId: string,
body: object
): Promise<object> {
return await db.updateDocument(collectionId, documentId, body);
): Promise<AwModelDocument> {
return await db.updateDocument(databaseId, collectionId, documentId, body);
}
export async function deleteDocument(db: Database, collectionId: string, documentId: string): Promise<object> {
return await db.deleteDocument(collectionId, documentId);
}
export async function bulkUpdate(
db: Database,
export async function deleteDocument(
db: Databases,
databaseId: string,
collectionId: string,
records: Array<object>,
documentIdKey: string
documentId: string
): Promise<object> {
for (const record of records) {
const documentId = record[documentIdKey];
await db.updateDocument(collectionId, documentId, record);
}
return { message: 'Docs are being updated' };
await db.deleteDocument(databaseId, collectionId, documentId);
return { deleted: true };
}

View file

@ -1,12 +1,15 @@
import { QueryTypes, Models } from 'node-appwrite';
export type SourceOptions = {
host: string;
project_id: string;
secret_key: string;
database_id: string;
};
export type QueryOptions = {
operation: string;
collectionId: string;
limit: number;
limit: string;
documentId: string;
body: any;
document_id_key: string;
@ -17,3 +20,13 @@ export type QueryOptions = {
where_operation: string;
where_value: string;
};
export type ParsedObject = string | number | object | [];
export type ReturnObject = object[] | object;
export type AwQueryTypes = QueryTypes;
export type AwModelDocumentList = Models.DocumentList<Models.Document>;
export type AwModelDocument = Models.Document;

View file

@ -9,15 +9,20 @@
"version": "1.0.0",
"dependencies": {
"@tooljet-plugins/common": "file:../common",
"node-appwrite": "^5.0.0",
"node-appwrite": "^11.1.1",
"react": "^17.0.2"
}
},
"../common": {
"name": "@tooljet-plugins/common",
"version": "1.0.0",
"dependencies": {
"react": "^17.0.2",
"rimraf": "^3.0.2"
"rimraf": "^3.0.2",
"tough-cookie": "^4.1.3"
},
"devDependencies": {
"@types/tough-cookie": "^4.0.2"
}
},
"node_modules/@tooljet-plugins/common": {
@ -30,11 +35,13 @@
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"node_modules/axios": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
"integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
"integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
"dependencies": {
"follow-redirects": "^1.14.7"
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/combined-stream": {
@ -57,9 +64,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.14.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [
{
"type": "individual",
@ -124,11 +131,11 @@
}
},
"node_modules/node-appwrite": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-5.0.0.tgz",
"integrity": "sha512-VJ9e5+ra+ycQS17C0aJMbVXK4Gcja6at+f2EzlRlsjxAzTMetb79QJBOO6ktMtmVrUkAieMnaMZcV1hPppERmg==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-11.1.1.tgz",
"integrity": "sha512-j9QTHfsDx/RoLq9shVtpDWofCG8u/izy6uQrlzet7+UV14OMikaPUNZpQhtZGi5KjLy5R+JvJfx6HHXrfdcIvQ==",
"dependencies": {
"axios": "^0.25.0",
"axios": "^1.4.0",
"form-data": "^4.0.0"
}
},
@ -140,6 +147,11 @@
"node": ">=0.10.0"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/react": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
@ -157,8 +169,10 @@
"@tooljet-plugins/common": {
"version": "file:../common",
"requires": {
"@types/tough-cookie": "^4.0.2",
"react": "^17.0.2",
"rimraf": "^3.0.2"
"rimraf": "^3.0.2",
"tough-cookie": "^4.1.3"
}
},
"asynckit": {
@ -167,11 +181,13 @@
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"axios": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
"integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
"integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
"requires": {
"follow-redirects": "^1.14.7"
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"combined-stream": {
@ -188,9 +204,9 @@
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"follow-redirects": {
"version": "1.14.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w=="
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
},
"form-data": {
"version": "4.0.0",
@ -229,11 +245,11 @@
}
},
"node-appwrite": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-5.0.0.tgz",
"integrity": "sha512-VJ9e5+ra+ycQS17C0aJMbVXK4Gcja6at+f2EzlRlsjxAzTMetb79QJBOO6ktMtmVrUkAieMnaMZcV1hPppERmg==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-11.1.1.tgz",
"integrity": "sha512-j9QTHfsDx/RoLq9shVtpDWofCG8u/izy6uQrlzet7+UV14OMikaPUNZpQhtZGi5KjLy5R+JvJfx6HHXrfdcIvQ==",
"requires": {
"axios": "^0.25.0",
"axios": "^1.4.0",
"form-data": "^4.0.0"
}
},
@ -242,6 +258,11 @@
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"react": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",

View file

@ -18,7 +18,7 @@
"homepage": "https://github.com/tooljet/tooljet#readme",
"dependencies": {
"@tooljet-plugins/common": "file:../common",
"node-appwrite": "^5.1.0",
"node-appwrite": "^13.0.0",
"react": "^17.0.2"
}
}

View file

@ -1 +1 @@
2.61.1
2.61.3

View file

@ -55,13 +55,13 @@ export class ImportExportResourcesService {
const importingVersion = importResourcesDto.tooljet_version;
const isTJDBEnabled = process.env.ENABLE_TOOLJET_DB === 'true';
if (isTJDBEnabled && importResourcesDto.tooljet_database) {
if (isTJDBEnabled && !isEmpty(importResourcesDto.tooljet_database)) {
const res = await this.tooljetDbImportExportService.bulkImport(importResourcesDto, importingVersion, cloning);
tableNameMapping = res.tableNameMapping;
imports.tooljet_database = res.tooljet_database;
}
if (importResourcesDto.app) {
if (!isEmpty(importResourcesDto.app)) {
for (const appImportDto of importResourcesDto.app) {
user.organizationId = importResourcesDto.organization_id;
const createdApp = await this.appImportExportService.import(