Merge branch 'release/v1.6.0'

This commit is contained in:
Sherfin Shamsudeen 2022-03-17 22:32:57 +05:30
commit be50abc076
89 changed files with 6088 additions and 9157 deletions

View file

@ -1 +1 @@
1.5.1
1.6.0

6
Aptfile Normal file
View file

@ -0,0 +1,6 @@
# you can list packages
libaio1
# or include links to specific .deb files
# or add custom apt repos (only required if using packages outside of the standard Ubuntu APT repositories)

View file

@ -46,6 +46,12 @@
"buildpacks": [
{
"url": "heroku/nodejs"
},
{
"url": "heroku-community/apt"
},
{
"url": "https://github.com/featurist/oracle-client-buildpack.git"
}
],
"environments": {

View file

@ -11,7 +11,7 @@ $ npm install -g @tooljet/cli
$ tooljet COMMAND
running command...
$ tooljet (--version)
@tooljet/cli/0.0.6 darwin-arm64 node-v16.13.1
@tooljet/cli/0.0.7 darwin-arm64 node-v14.17.3
$ tooljet --help [COMMAND]
USAGE
$ tooljet COMMAND

12747
cli/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,14 @@
{
"name": "@tooljet/cli",
"description": "tooljet cli tool",
"version": "0.0.6",
"version": "0.0.7",
"bin": {
"tooljet": "./bin/run"
},
"bugs": "https://github.com/tooljet/tooljet/issues",
"dependencies": {
"@oclif/core": "^1",
"@oclif/plugin-help": "^5",
"@oclif/core": "^1.6.0",
"@oclif/plugin-help": "^5.1.12",
"@oclif/plugin-plugins": "^2.1.0",
"@types/inquirer": "^8.1.3",
"hygen": "^6.1.0",

View file

@ -25,6 +25,19 @@ sudo luarocks install lua-resty-auto-ssl
sudo mkdir /etc/resty-auto-ssl /var/log/openresty /etc/fallback-certs
sudo chown -R www-data:www-data /etc/resty-auto-ssl
# Oracle db client library setup
sudo apt install -y libaio1
curl -o instantclient-basiclite.zip https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linuxx64.zip -SL && \
unzip instantclient-basiclite.zip && \
sudo mv instantclient*/ /usr/lib/instantclient && \
rm instantclient-basiclite.zip && \
sudo ln -s /usr/lib/instantclient/libclntsh.so.19.1 /usr/lib/libclntsh.so && \
sudo ln -s /usr/lib/instantclient/libocci.so.19.1 /usr/lib/libocci.so && \
sudo ln -s /lib/libc.so.6 /usr/lib/libresolv.so.2 && \
sudo ln -s /lib64/ld-linux-x86-64.so.2 /usr/lib/ld-linux-x86-64.so.2
export LD_LIBRARY_PATH="/usr/lib/instantclient"
# Gen fallback certs
sudo openssl rand -out /home/ubuntu/.rnd -hex 256
sudo chown www-data:www-data /home/ubuntu/.rnd
@ -52,6 +65,7 @@ mv /tmp/setup_app ~/app/setup_app
sudo chmod +x ~/app/setup_app
sudo npm install -g npm@7.20.0
sudo chown -R 1000:1000 "/home/ubuntu/.npm"
# Building ToolJet app
sudo npm install -g @nestjs/cli

View file

@ -37,6 +37,21 @@ FROM node:14.17.3-alpine
ENV NODE_ENV=production
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN apk add postgresql-client freetds
# Install Instantclient Basic Light Oracle and Dependencies
RUN apk --no-cache add libaio libnsl libc6-compat curl && \
cd /tmp && \
curl -o instantclient-basiclite.zip https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linuxx64.zip -SL && \
unzip instantclient-basiclite.zip && \
mv instantclient*/ /usr/lib/instantclient && \
rm instantclient-basiclite.zip && \
ln -s /usr/lib/instantclient/libclntsh.so.19.1 /usr/lib/libclntsh.so && \
ln -s /usr/lib/instantclient/libocci.so.19.1 /usr/lib/libocci.so && \
ln -s /lib/libc.so.6 /usr/lib/libresolv.so.2 && \
ln -s /lib64/ld-linux-x86-64.so.2 /usr/lib/ld-linux-x86-64.so.2
ENV LD_LIBRARY_PATH /usr/lib/instantclient
RUN mkdir -p /app
# copy npm scripts

View file

@ -30,6 +30,20 @@ ENV NODE_ENV=production
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN apk add postgresql-client freetds
# Install Instantclient Basic Light Oracle and Dependencies
RUN apk --no-cache add libaio libnsl libc6-compat curl && \
cd /tmp && \
curl -o instantclient-basiclite.zip https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linuxx64.zip -SL && \
unzip instantclient-basiclite.zip && \
mv instantclient*/ /usr/lib/instantclient && \
rm instantclient-basiclite.zip && \
ln -s /usr/lib/instantclient/libclntsh.so.19.1 /usr/lib/libclntsh.so && \
ln -s /usr/lib/instantclient/libocci.so.19.1 /usr/lib/libocci.so && \
ln -s /lib/libc.so.6 /usr/lib/libresolv.so.2 && \
ln -s /lib64/ld-linux-x86-64.so.2 /usr/lib/ld-linux-x86-64.so.2
ENV LD_LIBRARY_PATH /usr/lib/instantclient
RUN mkdir -p /app
# copy npm scripts

View file

@ -1,6 +1,20 @@
# pull official base image
FROM node:14.17.3-alpine
RUN apk add postgresql-client freetds
RUN apk add postgresql-client
# Install Instantclient Basic Light Oracle and Dependencies
RUN apk --no-cache add libaio libnsl libc6-compat curl && \
cd /tmp && \
curl -o instantclient-basiclite.zip https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linuxx64.zip -SL && \
unzip instantclient-basiclite.zip && \
mv instantclient*/ /usr/lib/instantclient && \
rm instantclient-basiclite.zip && \
ln -s /usr/lib/instantclient/libclntsh.so.19.1 /usr/lib/libclntsh.so && \
ln -s /usr/lib/instantclient/libocci.so.19.1 /usr/lib/libocci.so && \
ln -s /lib/libc.so.6 /usr/lib/libresolv.so.2 && \
ln -s /lib64/ld-linux-x86-64.so.2 /usr/lib/ld-linux-x86-64.so.2
ENV LD_LIBRARY_PATH /usr/lib/instantclient
ENV NODE_ENV=development
ENV NODE_OPTIONS="--max-old-space-size=4096"

View file

@ -0,0 +1,47 @@
# Oracle DB
ToolJet can connect to Oracle databases to read and write data.
## Connection
A Oracle DB can be connected with the following credentails:
- **Host**
- **Port**
- **SID / Service Name** ( Database name must be a SID / Service Name )
- **Database Name**
- **SSL**
- **Username**
- **Password**
- **Client Library Path** ( Only need for local setup )
:::info
You can also test your connection before saving the configuration by clicking on `Test Connection` button.
:::
Click on **Test connection** button to verify if the credentials are correct and that the database is accessible to ToolJet server. Click on **Save** button to save the data source.
## Querying Oracle DB
Once you have added a Oracle DB data source, click on `+` button of the query manager to create a new query. There are two modes by which you can query SQL:
1. **[SQL mode](/docs/data-sources/oracledb#sql-mode)**
2. **[GUI mode](/docs/data-sources/oracledb#gui-mode)**
#### SQL mode
SQL mode can be used to write raw SQL queries. Select SQL mode from the dropdown and enter the SQL query in the editor. Click on the `run` button to run the query.
**NOTE**: Query should be saved before running.
#### GUI mode
GUI mode can be used to query Oracle database without writing queries. Select GUI mode from the dropdown and then choose the operation **Bulk update using primary key**. Enter the **Table** name and **Primary key column** name. Now, in the editor enter the records in the form of an array of objects.
**Example**: `{{ [ {id: 1, channel: 33}, {id:2, channel:24} ] }}`
Click on the **run** button to run the query. **NOTE**: Query should be saved before running.
:::tip
Query results can be transformed using transformations. Read our transformations documentation to see how: **[link](/docs/tutorial/transformations)**
:::

View file

@ -187,6 +187,15 @@ This is required when client is built separately.
| TOOLJET_SERVER_URL | the URL of ToolJet server ( eg: https://server.tooljet.com ) |
#### Server Port ( optional)
This could be used to for local development, it will set the server url like so: `http://localhost:<TOOLJET_SERVER_PORT>`
| variable | description |
|---------------------|-----------------------------------------|
| TOOLJET_SERVER_PORT | the port of ToolJet server ( eg: 3000 ) |
#### Asset path ( optionally required )
This is required when the assets for the client are to be loaded from elsewhere (eg: CDN).

View file

@ -156,5 +156,5 @@ strong {
}
.menu:hover {
overflow-y: overlay;
overflow-y: auto;
}

View file

@ -124,6 +124,7 @@
"@tooljet-plugins/mssql": "file:packages/mssql",
"@tooljet-plugins/mysql": "file:packages/mysql",
"@tooljet-plugins/n8n": "file:packages/n8n",
"@tooljet-plugins/oracledb": "file:packages/oracledb",
"@tooljet-plugins/postgresql": "file:packages/postgresql",
"@tooljet-plugins/redis": "file:packages/redis",
"@tooljet-plugins/restapi": "file:packages/restapi",
@ -22627,6 +22628,7 @@
"@tooljet-plugins/mssql": "file:packages/mssql",
"@tooljet-plugins/mysql": "file:packages/mysql",
"@tooljet-plugins/n8n": "file:packages/n8n",
"@tooljet-plugins/oracledb": "file:packages/oracledb",
"@tooljet-plugins/postgresql": "file:packages/postgresql",
"@tooljet-plugins/redis": "file:packages/redis",
"@tooljet-plugins/restapi": "file:packages/restapi",

View file

@ -193,7 +193,7 @@ export function CodeHinter({
className={`row${height === '150px' || height === '300px' ? ' tablr-gutter-x-0' : ''}`}
style={{ width: width, display: codeShow ? 'flex' : 'none' }}
>
<div className={`col`} style={{ marginBottom: '0.5rem' }}>
<div className={`col code-hinter-col`} style={{ marginBottom: '0.5rem' }}>
<div className="code-hinter-wrapper" style={{ width: '100%', backgroundColor: darkMode && '#272822' }}>
<div
className={`${defaultClassName} ${className || 'codehinter-default-input'}`}

View file

@ -2,7 +2,11 @@ import React from 'react';
export default function FxButton({ active, onPress }) {
return (
<div className={`fx-button ${active ? 'active' : ''} unselectable`} onClick={onPress}>
<div
title="Use fx for property to have a programmatically determined value instead of a fixed value"
className={`fx-button ${active ? 'active' : ''} unselectable`}
onClick={onPress}
>
Fx
</div>
);

View file

@ -27,10 +27,10 @@ export const Map = function Map({
}
const addNewMarkersProp = component.definition.properties.addNewMarkers;
const canAddNewMarkers = addNewMarkersProp ? addNewMarkersProp.value : false;
const canAddNewMarkers = addNewMarkersProp ? resolveReferences(addNewMarkersProp.value, currentState) : false;
const canSearchProp = component.definition.properties.canSearch;
const canSearch = canSearchProp ? canSearchProp.value : false;
const canSearch = canSearchProp ? resolveReferences(canSearchProp.value, currentState) : false;
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
const disabledState = component.definition.styles?.disabledState?.value ?? false;

View file

@ -352,12 +352,13 @@ export function Table({
Cell: function (cell) {
const rowChangeSet = changeSet ? changeSet[cell.row.index] : null;
const cellValue = rowChangeSet ? rowChangeSet[column.name] || cell.value : cell.value;
const rowData = tableData[cell.row.index];
switch (columnType) {
case 'string':
case undefined:
case 'default': {
const textColor = resolveReferences(column.textColor, currentState, '', { cellValue });
const textColor = resolveReferences(column.textColor, currentState, '', { cellValue, rowData });
const cellStyles = {
color: textColor ?? '',

View file

@ -666,7 +666,8 @@ export const componentTypes = [
borderRadius: { type: 'code', displayName: 'Border radius' },
},
exposedVariables: {
value: {},
value:
'ToolJet is an open-source low-code platform for building and deploying internal tools with minimal engineering efforts 🚀',
},
definition: {
others: {

View file

@ -189,7 +189,10 @@ export const DraggableBox = function DraggableBox({
>
{inCanvas ? (
<div
className={cx(`draggable-box widget-${id}`, { [className]: !!className })}
className={cx(`draggable-box widget-${id}`, {
[className]: !!className,
'draggable-box-in-editor': mode === 'edit',
})}
onMouseEnter={(e) => {
if (e.currentTarget.className.includes(`widget-${id}`)) {
onComponentHover(id);

View file

@ -1145,7 +1145,7 @@ class Editor extends React.Component {
alignItems: 'center',
}}
>
<h5 className="mb-0 common-sidebar-popover-margin">QUERIES</h5>
<h5 className="mb-0">QUERIES</h5>
<span onClick={this.toggleQueryEditor} className="cursor-pointer m-1" data-tip="Show query editor">
<svg
style={{ transform: 'rotate(180deg)' }}
@ -1178,9 +1178,9 @@ class Editor extends React.Component {
}}
>
<div className="row main-row">
<div className="col-3 data-pane">
<div className="data-pane">
<div className="queries-container">
<div className="queries-header row">
<div className="queries-header row" style={{ marginLeft: '1.5px' }}>
{showQuerySearchField && (
<div className="col-12 p-1">
<div className="queries-search px-1">
@ -1197,7 +1197,7 @@ class Editor extends React.Component {
<>
<div className="col">
<h5
style={{ fontSize: '14px', marginLeft: ' 32px' }}
style={{ fontSize: '14px', marginLeft: ' 6px' }}
className="py-1 px-3 mt-2 text-muted"
>
Queries
@ -1241,7 +1241,7 @@ class Editor extends React.Component {
</center>
</div>
) : (
<div className="query-list p-1 mt-1" style={{ marginLeft: '32px' }}>
<div className="query-list p-1 mt-1">
<div>{dataQueries.map((query) => this.renderDataQuery(query))}</div>
{dataQueries.length === 0 && (
<div className="mt-5">
@ -1268,7 +1268,7 @@ class Editor extends React.Component {
)}
</div>
</div>
<div className="col-9 query-definition-pane-wrapper">
<div className="query-definition-pane-wrapper">
{!loadingDataSources && (
<div className="query-definition-pane">
<div>

View file

@ -40,10 +40,7 @@ export const LeftSidebarDataSources = ({ appId, editingVersionId, darkMode, data
className={`left-sidebar-item sidebar-datasources left-sidebar-layout ${open && 'active'}`}
text={'Sources'}
/>
<div
{...content}
className={`card popover common-sidebar-popover-margin datasources-popover ${open ? 'show' : 'hide'}`}
>
<div {...content} className={`card popover datasources-popover ${open ? 'show' : 'hide'}`}>
<LeftSidebarDataSources.Container
renderDataSource={renderDataSource}
dataSources={dataSources}

View file

@ -122,9 +122,7 @@ export const LeftSidebarDebugger = ({ darkMode, errors }) => {
/>
<div
{...content}
className={`card popover common-sidebar-popover-margin debugger-popover ${
open || popoverPinned ? 'show' : 'hide'
}`}
className={`card popover debugger-popover ${open || popoverPinned ? 'show' : 'hide'}`}
style={{ minWidth: '350px', minHeight: '108px', resize: 'horizontal', maxWidth: '50%' }}
>
<div className="row-header">

View file

@ -24,10 +24,7 @@ export const LeftSidebarGlobalSettings = ({ globalSettings, globalSettingsChange
className={`left-sidebar-item left-sidebar-layout ${open && 'active'}`}
text={'Settings'}
/>
<div
{...content}
className={`card popover global-settings-popover common-sidebar-popover-margin ${open ? 'show' : 'hide'}`}
>
<div {...content} className={`card popover global-settings-popover ${open ? 'show' : 'hide'}`}>
<div style={{ marginTop: '1rem' }} className="card-body">
<div>
<div className="d-flex mb-3">

View file

@ -18,7 +18,7 @@ export const LeftSidebarInspector = ({ darkMode, currentState }) => {
/>
<div
{...content}
className={`card popover common-sidebar-popover-margin ${open || popoverPinned ? 'show' : 'hide'}`}
className={`card popover ${open || popoverPinned ? 'show' : 'hide'}`}
style={{ resize: 'horizontal', maxWidth: '50%' }}
>
<SidebarPinnedButton

View file

@ -1,16 +1,28 @@
import React from 'react';
const GroupHeader = ({ darkMode }) => {
const GroupHeader = ({ addNewKeyValuePair, paramType, descText }) => {
return (
<div className={`group-header row py-2 mb-1 mx-0 ${darkMode && 'bg-dark'}`}>
<div className="col-5">
<span className="text-uppercase small strong" style={{ fontSize: '10px' }}>
Key
</span>
</div>
<div className="col-6">
<span className="text-uppercase small strong" style={{ fontSize: '10px' }}>
Value
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div className="content-title">{descText}</div>
<div>
<span onClick={() => addNewKeyValuePair(paramType)} role="button">
<svg
xmlns="http://www.w3.org/2000/svg"
className="icon icon-tabler icon-tabler-square-plus svg-plus"
width="36"
height="36"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="#737373"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<rect x="4" y="4" width="16" height="16" rx="2" />
<line x1="9" y1="12" x2="15" y2="12" />
<line x1="12" y1="9" x2="12" y2="15" />
</svg>
</span>
</div>
</div>

View file

@ -1,6 +1,6 @@
import React from 'react';
import { CodeHinter } from '../../../CodeBuilder/CodeHinter';
import GroupHeader from './GroupHeader';
import TabContent from './TabContent';
export default ({
options = [],
@ -9,72 +9,22 @@ export default ({
removeKeyValuePair,
addNewKeyValuePair,
onChange,
darkMode,
componentName,
}) => {
return (
<>
<div className="row">
<div className="col px-3">
<GroupHeader darkMode={darkMode} />
</div>
<div className="col-auto px-2">
<span onClick={() => addNewKeyValuePair('body')} className="btn-sm col-2 mt-1 color-primary" role="button">
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M13 0.75C9.76323 0.789051 6.67003 2.09221 4.38112 4.38112C2.09221 6.67003 0.789051 9.76323 0.75 13C0.789051 16.2368 2.09221 19.33 4.38112 21.6189C6.67003 23.9078 9.76323 25.2109 13 25.25C16.2368 25.2109 19.33 23.9078 21.6189 21.6189C23.9078 19.33 25.2109 16.2368 25.25 13C25.2109 9.76323 23.9078 6.67003 21.6189 4.38112C19.33 2.09221 16.2368 0.789051 13 0.75ZM20 13.875H13.875V20H12.125V13.875H6V12.125H12.125V6H13.875V12.125H20V13.875Z"
fill="#4D72FA"
/>
</svg>
</span>
</div>
</div>
<GroupHeader addNewKeyValuePair={addNewKeyValuePair} paramType={'body'} descText={'Body Parameters'} />
<div className="row px-2 pb-3">
{options.map((option, index) => {
return (
<div className="row input-group my-1" key={index}>
<div className="col-5 field">
<CodeHinter
currentState={currentState}
initialValue={option[0]}
theme={theme}
height={'32px'}
width="80%"
placeholder="key"
onChange={onChange('body', 0, index)}
componentName={`${componentName}/body::key::${index}`}
/>
</div>
<div className="col-5 field tab-pane-body">
<CodeHinter
currentState={currentState}
initialValue={option[1]}
theme={theme}
height={'32px'}
width="80%"
placeholder="value"
onChange={onChange('body', 1, index)}
componentName={`${componentName}/body::value::${index}`}
/>
</div>
{index > 0 && (
<span
style={{ marginLeft: '-5%' }}
className="btn-sm col-2 mt-1 color-primary"
role="button"
onClick={() => {
removeKeyValuePair('body', index);
}}
>
Remove
</span>
)}
</div>
);
})}
</div>
<TabContent
options={options}
currentState={currentState}
theme={theme}
removeKeyValuePair={removeKeyValuePair}
onChange={onChange}
componentName={componentName}
tabType={'body'}
paramType={'body'}
/>
</>
);
};

View file

@ -0,0 +1,77 @@
import React from 'react';
import { CodeHinter } from '../../../CodeBuilder/CodeHinter';
export default ({
options = [],
currentState,
theme,
onChange,
componentName,
removeKeyValuePair,
paramType,
tabType,
}) => {
return (
<>
<div className="tab-content-wrapper">
{options.map((option, index) => {
return (
<div className="mt-1 row-container" key={index}>
<div className="fields-container">
<div className="field" style={{ width: '100%' }}>
<CodeHinter
currentState={currentState}
initialValue={option[0]}
theme={theme}
height={'32px'}
placeholder="key"
onChange={onChange(paramType, 0, index)}
componentName={`${componentName}/${tabType}::key::${index}`}
/>
</div>
<div className="field" style={{ width: '100%' }}>
<CodeHinter
currentState={currentState}
initialValue={option[1]}
theme={theme}
height={'32px'}
placeholder="value"
onChange={onChange(paramType, 1, index)}
componentName={`${componentName}/${tabType}::value::${index}`}
/>
</div>
</div>
<span
className="color-primary delete-btn-wrapper"
role="button"
onClick={() => {
removeKeyValuePair(paramType, index);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="icon icon-tabler icon-tabler-trash"
width="36"
height="36"
viewBox="0 0 25 25"
strokeWidth="1.5"
stroke="#ff2825"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<line x1="4" y1="7" x2="20" y2="7" />
<line x1="10" y1="11" x2="10" y2="17" />
<line x1="14" y1="11" x2="14" y2="17" />
<path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12" />
<path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3" />
</svg>
</span>
</div>
);
})}
</div>
</>
);
};

View file

@ -1,5 +1,5 @@
import React from 'react';
import { CodeHinter } from '../../../CodeBuilder/CodeHinter';
import TabContent from './TabContent';
import GroupHeader from './GroupHeader';
export default ({
@ -9,72 +9,21 @@ export default ({
removeKeyValuePair,
addNewKeyValuePair,
onChange,
darkMode,
componentName,
}) => {
return (
<>
<div className="row">
<div className="col px-3">
<GroupHeader darkMode={darkMode} />
</div>
<div className="col-auto px-2">
<span onClick={() => addNewKeyValuePair('headers')} className="btn-sm col-2 mt-1 color-primary" role="button">
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M13 0.75C9.76323 0.789051 6.67003 2.09221 4.38112 4.38112C2.09221 6.67003 0.789051 9.76323 0.75 13C0.789051 16.2368 2.09221 19.33 4.38112 21.6189C6.67003 23.9078 9.76323 25.2109 13 25.25C16.2368 25.2109 19.33 23.9078 21.6189 21.6189C23.9078 19.33 25.2109 16.2368 25.25 13C25.2109 9.76323 23.9078 6.67003 21.6189 4.38112C19.33 2.09221 16.2368 0.789051 13 0.75ZM20 13.875H13.875V20H12.125V13.875H6V12.125H12.125V6H13.875V12.125H20V13.875Z"
fill="#4D72FA"
/>
</svg>
</span>
</div>
</div>
<div className="row px-2 pb-3">
{options.map((option, index) => {
return (
<div className="row input-group my-1" key={index}>
<div className="col-5 field">
<CodeHinter
currentState={currentState}
initialValue={option[0]}
theme={theme}
height={'32px'}
width="80%"
placeholder="key"
onChange={onChange('headers', 0, index)}
componentName={`${componentName}/headers::key::${index}`}
/>
</div>
<div className="col-5 field tab-pane-body">
<CodeHinter
currentState={currentState}
initialValue={option[1]}
theme={theme}
height={'32px'}
width="80%"
placeholder="value"
onChange={onChange('headers', 1, index)}
componentName={`${componentName}/headers::value::${index}`}
/>
</div>
{index > 0 && (
<span
style={{ marginLeft: '-5%' }}
className="btn-sm col-auto mt-1 color-primary"
role="button"
onClick={() => {
removeKeyValuePair('headers', index);
}}
>
Remove
</span>
)}
</div>
);
})}
</div>
<GroupHeader addNewKeyValuePair={addNewKeyValuePair} paramType={'headers'} descText="Request Headers" />
<TabContent
options={options}
currentState={currentState}
theme={theme}
removeKeyValuePair={removeKeyValuePair}
onChange={onChange}
componentName={componentName}
tabType={'headers'}
paramType={'headers'}
/>
</>
);
};

View file

@ -1,6 +1,6 @@
import React from 'react';
import { CodeHinter } from '../../../CodeBuilder/CodeHinter';
import GroupHeader from './GroupHeader';
import TabContent from './TabContent';
export default ({
options = [],
@ -9,76 +9,21 @@ export default ({
removeKeyValuePair,
addNewKeyValuePair,
onChange,
darkMode,
componentName,
}) => {
return (
<>
<div className="row">
<div className="col px-3">
<GroupHeader darkMode={darkMode} />
</div>
<div className="col-auto px-2">
<span
onClick={() => addNewKeyValuePair('url_params')}
className="btn-sm col-2 mt-1 color-primary"
role="button"
>
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M13 0.75C9.76323 0.789051 6.67003 2.09221 4.38112 4.38112C2.09221 6.67003 0.789051 9.76323 0.75 13C0.789051 16.2368 2.09221 19.33 4.38112 21.6189C6.67003 23.9078 9.76323 25.2109 13 25.25C16.2368 25.2109 19.33 23.9078 21.6189 21.6189C23.9078 19.33 25.2109 16.2368 25.25 13C25.2109 9.76323 23.9078 6.67003 21.6189 4.38112C19.33 2.09221 16.2368 0.789051 13 0.75ZM20 13.875H13.875V20H12.125V13.875H6V12.125H12.125V6H13.875V12.125H20V13.875Z"
fill="#4D72FA"
/>
</svg>
</span>
</div>
</div>
<div className="row px-2 pb-3">
{options.map((option, index) => {
return (
<div className="row input-group my-1" key={index}>
<div className="col-5 field">
<CodeHinter
currentState={currentState}
initialValue={option[0]}
theme={theme}
height={'32px'}
width="80%"
placeholder="key"
onChange={onChange('url_params', 0, index)}
componentName={`${componentName}/params::key::${index}`}
/>
</div>
<div className="col-5 field tab-pane-body">
<CodeHinter
currentState={currentState}
initialValue={option[1]}
theme={theme}
height={'32px'}
width="80%"
placeholder="value"
onChange={onChange('url_params', 1, index)}
componentName={`${componentName}/params::value::${index}`}
/>
</div>
{index > 0 && (
<span
style={{ marginLeft: '-5%' }}
className="btn-sm col-2 mt-1 color-primary"
role="button"
onClick={() => {
removeKeyValuePair('url_params', index);
}}
>
Remove
</span>
)}
</div>
);
})}
</div>
<GroupHeader addNewKeyValuePair={addNewKeyValuePair} paramType={'url_params'} descText={'Query parameters'} />
<TabContent
options={options}
currentState={currentState}
theme={theme}
removeKeyValuePair={removeKeyValuePair}
onChange={onChange}
componentName={componentName}
tabType={'params'}
paramType={'url_params'}
/>
</>
);
};

View file

@ -19,7 +19,7 @@ function ControlledTabs({
return (
<Tab.Container activeKey={key} onSelect={(k) => setKey(k)} defaultActiveKey="headers">
<Row>
<div className={`col-auto keys ${darkMode ? 'dark' : ''}`}>
<div className="keys">
<ListGroup className="query-pane-rest-api-keys-list-group" variant="flush">
{tabs.map((tab) => (
<ListGroup.Item key={tab} eventKey={tab.toLowerCase()}>

View file

@ -152,10 +152,11 @@ class Restapi extends React.Component {
defaultValue={{ label: 'GET', value: 'get' }}
placeholder="Method"
styles={selectStyles}
isSearchable={false}
/>
</div>
<div className="col field mx-3 w-100" style={{ display: 'flex', maxWidth: '700px' }}>
<div className="col field w-100" style={{ display: 'flex', marginLeft: 16 }}>
{dataSourceURL && (
<BaseUrl theme={this.props.darkMode ? 'monokai' : 'default'} dataSourceURL={dataSourceURL} />
)}
@ -177,7 +178,7 @@ class Restapi extends React.Component {
</div>
</div>
<div className={`query-pane-restapi-tabs mt-3 px-2 ${this.props.darkMode ? 'dark' : ''}`}>
<div className={`query-pane-restapi-tabs mt-3 ${this.props.darkMode ? 'dark' : ''}`}>
<Tabs
theme={this.props.darkMode ? 'monokai' : 'default'}
options={this.state.options}

View file

@ -375,7 +375,7 @@ let QueryManager = class QueryManager extends React.Component {
<ReactTooltip type="dark" effect="solid" delayShow={250} />
<div className="row header">
<div className="col">
{(addingQuery || editingQuery) && (
{(addingQuery || editingQuery) && selectedDataSource && (
<div className="nav-header">
<ul className="nav nav-tabs query-manager-header" data-bs-toggle="tabs">
<li className="nav-item">

View file

@ -74,6 +74,7 @@ export const WidgetManager = function WidgetManager({ componentTypes, zoomLevel,
const formSection = { title: 'forms', items: [] };
const integrationSection = { title: 'integrations', items: [] };
const otherSection = { title: 'others', items: [] };
const allWidgets = [];
const commonItems = ['Table', 'Chart', 'Button', 'Text', 'Datepicker'];
const formItems = [
@ -96,6 +97,7 @@ export const WidgetManager = function WidgetManager({ componentTypes, zoomLevel,
const layoutItems = ['Container', 'Listview', 'Tabs', 'Modal'];
filteredComponents.forEach((f) => {
if (searchQuery) allWidgets.push(f);
if (commonItems.includes(f.name)) commonSection.items.push(f);
if (formItems.includes(f.name)) formSection.items.push(f);
else if (integrationItems.includes(f.name)) integrationSection.items.push(f);
@ -103,15 +105,19 @@ export const WidgetManager = function WidgetManager({ componentTypes, zoomLevel,
else otherSection.items.push(f);
});
return (
<>
{renderList(commonSection.title, commonSection.items)}
{renderList(layoutsSection.title, layoutsSection.items)}
{renderList(formSection.title, formSection.items)}
{renderList(otherSection.title, otherSection.items)}
{renderList(integrationSection.title, integrationSection.items)}
</>
);
if (allWidgets.length > 0) {
return <>{renderList(undefined, allWidgets)}</>;
} else {
return (
<>
{renderList(commonSection.title, commonSection.items)}
{renderList(layoutsSection.title, layoutsSection.items)}
{renderList(formSection.title, formSection.items)}
{renderList(otherSection.title, otherSection.items)}
{renderList(integrationSection.title, integrationSection.items)}
</>
);
}
}
return (

View file

@ -32,7 +32,7 @@
}
.popover {
position: fixed;
left: 3%;
left: 76px;
top: 55px;
overflow: auto;
max-height: 60%;

View file

@ -167,10 +167,6 @@ button {
font-weight: 600;
}
}
.common-sidebar-popover-margin{
margin-left: 33px;
}
.left-sidebar {
scrollbar-width: none;
}
@ -457,11 +453,10 @@ button {
width: 0;
background: transparent;
}
.query-pane {
height: 350px;
position: fixed;
left: 3%;
left: 76px; //sidebar is 76px
right: 300px;
bottom: 0;
overflow-x: hidden;
@ -524,6 +519,7 @@ button {
}
.query-definition-pane-wrapper {
width: 72%;
overflow-x: hidden;
overflow-y: scroll;
height: 100%;
@ -557,6 +553,7 @@ button {
}
.data-pane {
width: 28%;
border: solid rgba(0, 0, 0, 0.125);
border-width: 0px 1px 0px 0px;
overflow-x: hidden;
@ -2359,6 +2356,11 @@ hr {
.CodeMirror {
font-family: "Roboto", sans-serif;
}
.CodeMirror-placeholder {
height: inherit !important;
position: absolute !important;
margin-top: 3px !important;
}
}
.codehinter-query-editor-input {
@ -2422,7 +2424,7 @@ hr {
}
}
.draggable-box:hover {
.draggable-box-in-editor:hover {
z-index: 3 !important;
}
@ -3919,6 +3921,9 @@ input[type="text"] {
width: 200px;
border-radius: 5px !important;
}
input:focus {
width: 300px;
}
.input-icon .input-icon-addon {
display: flex;
}
@ -4202,13 +4207,11 @@ input[type="text"] {
}
.query-pane-restapi-tabs.dark {
background: $bg-dark !important;
border-color: $border-grey-dark !important;
.list-group-item {
color: $disabled !important;
&:hover {
background-color: #262b33 !important;
color: #ffffff !important;
}
}
.list-group-item.active {
@ -4216,28 +4219,17 @@ input[type="text"] {
}
}
.query-pane-restapi-tabs {
background: #f7f9fc;
border: 1px solid #d2ddec;
box-sizing: border-box;
border-radius: 4px;
margin: 0px;
height: fit-content;
min-height: 160px;
width: 100%;
.row {
height: inherit;
.keys {
border-right: 1px solid #d2ddec !important;
min-height: 90px;
min-height: 30px;
}
.keys.dark {
border-color: $border-grey-dark !important;
}
.rest-api-tab-content {
margin-top: 1rem;
min-height: 160px;
.rest-api-tabpanes {
display: none;
@ -4246,40 +4238,92 @@ input[type="text"] {
.rest-api-tabpanes.active {
display: block;
}
.svg-plus {
stroke: $primary;
}
.delete-btn-wrapper {
display: flex;
align-items: center;
padding-top: 2px;
padding-bottom: 2px;
height: 32px;
}
.code-hinter-col {
margin-bottom: 0px !important;
}
.tab-content-wrapper {
display: flex;
flex-direction: column;
gap: .3rem;
}
.row-container{
display: flex;
width: 100%;
justify-content: space-between;
gap: 10px;
}
.fields-container {
display: flex;
justify-content: space-between;
gap: 10px;
width: 100%;
}
}
}
}
.query-pane-rest-api-keys-list-group {
margin-top: 1.25rem;
width: 100%;
display: flex;
flex-direction: row;
.list-group-item {
border: none !important;
cursor: pointer;
margin-bottom: 11px;
font-weight: 600;
font-size: 12px;
line-height: 17px;
padding: 3px !important;
padding: 0px !important;
margin-right: 20px;
height: 22px;
width: 72px;
color: #737373;
span {
display: flex;
justify-content: left;
margin-left: 7px;
}
&:hover {
border-radius: 4px !important;
background-color: $bg-light;
color: #000;
}
}
.list-group-item.active {
border-radius: 4px !important;
background-color: $primary !important;
z-index: inherit !important;
.list-group-item + .list-group-item.active {
margin-top: 0;
}
.list-group-item.active {
background-color: transparent !important;
color: #000;
z-index: inherit !important;
border-bottom: 2px solid $primary !important;
}
}
.query-pane-rest-api-keys-list-group.dark + .list-group-item {
&:hover{
color: #ffffff;
}
}
.content-title {
font-size: 12px;
color: #a3a3a3;
font-weight: 600;
}
// ** Query Panel: REST API Tabs **

View file

@ -6,7 +6,7 @@ const environment = process.env.NODE_ENV === 'production' ? 'production' : 'deve
const API_URL = {
production: process.env.TOOLJET_SERVER_URL || '',
development: 'http://localhost:3000',
development: `http://localhost:${process.env.TOOLJET_SERVER_PORT || 3000}`,
};
const ASSET_PATH = process.env.ASSET_PATH || '/';

View file

@ -7,7 +7,6 @@ module.exports = {
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
'plugin:cypress/recommended',
],
ignorePatterns: ['.eslintrc.js'],
parser: '@typescript-eslint/parser',

View file

@ -24,6 +24,7 @@
"@tooljet-plugins/mssql": "file:packages/mssql",
"@tooljet-plugins/mysql": "file:packages/mysql",
"@tooljet-plugins/n8n": "file:packages/n8n",
"@tooljet-plugins/oracledb": "file:packages/oracledb",
"@tooljet-plugins/postgresql": "file:packages/postgresql",
"@tooljet-plugins/redis": "file:packages/redis",
"@tooljet-plugins/restapi": "file:packages/restapi",
@ -42,7 +43,6 @@
"@typescript-eslint/parser": "^4.31.1",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-jest": "^24.4.2",
"eslint-plugin-prettier": "^3.4.1",
"jest": "^27.4.5",
@ -4551,6 +4551,10 @@
"resolved": "packages/n8n",
"link": true
},
"node_modules/@tooljet-plugins/oracledb": {
"resolved": "packages/oracledb",
"link": true
},
"node_modules/@tooljet-plugins/postgresql": {
"resolved": "packages/postgresql",
"link": true
@ -4746,6 +4750,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/oracledb": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/oracledb/-/oracledb-5.2.3.tgz",
"integrity": "sha512-Mw1/LjO8WSCdVOIGopLwxoUi2pNb9NJ2NK06U/mpQhR738FNeVWXjNbla2LAZy3nTCqPHsg3phUQNqL3qhHj8w==",
"dev": true,
"dependencies": {
"@types/node": "*",
"dotenv": "^8.2.0"
}
},
"node_modules/@types/parse-json": {
"version": "4.0.0",
"dev": true,
@ -7376,6 +7390,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/dotenv": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
"integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/duplexer": {
"version": "0.1.2",
"dev": true,
@ -7715,18 +7738,6 @@
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-plugin-cypress": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.12.1.tgz",
"integrity": "sha512-c2W/uPADl5kospNDihgiLc7n87t5XhUbFDoTl6CfVkmG+kDAb5Ux10V9PoLPu9N+r7znpc+iQlcmAqT1A/89HA==",
"dev": true,
"dependencies": {
"globals": "^11.12.0"
},
"peerDependencies": {
"eslint": ">= 3.2.1"
}
},
"node_modules/eslint-plugin-jest": {
"version": "24.7.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.7.0.tgz",
@ -12812,6 +12823,15 @@
"node": ">= 0.8.0"
}
},
"node_modules/oracledb": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/oracledb/-/oracledb-5.3.0.tgz",
"integrity": "sha512-HMJzQ6lCf287ztvvehTEmjCWA21FQ3RMvM+mgoqd4i8pkREuqFWO+y3ovsGR9moJUg4T0xjcwS8rl4mggWPxmg==",
"hasInstallScript": true,
"engines": {
"node": ">=10.16"
}
},
"node_modules/os-homedir": {
"version": "1.0.2",
"dev": true,
@ -16588,6 +16608,19 @@
"react": "^17.0.2"
}
},
"packages/oracledb": {
"name": "@tooljet-plugins/oracledb",
"version": "1.0.0",
"dependencies": {
"@tooljet-plugins/common": "file:../common",
"knex": "^0.95.14",
"oracledb": "^5.3.0",
"react": "^17.0.2"
},
"devDependencies": {
"@types/oracledb": "^5.2.2"
}
},
"packages/postgresql": {
"name": "@tooljet-plugins/postgresql",
"version": "1.0.0",
@ -20291,6 +20324,16 @@
"react": "^17.0.2"
}
},
"@tooljet-plugins/oracledb": {
"version": "file:packages/oracledb",
"requires": {
"@tooljet-plugins/common": "file:../common",
"@types/oracledb": "^5.2.2",
"knex": "^0.95.14",
"oracledb": "^5.3.0",
"react": "^17.0.2"
}
},
"@tooljet-plugins/postgresql": {
"version": "file:packages/postgresql",
"requires": {
@ -20523,6 +20566,16 @@
"version": "2.4.1",
"dev": true
},
"@types/oracledb": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/oracledb/-/oracledb-5.2.3.tgz",
"integrity": "sha512-Mw1/LjO8WSCdVOIGopLwxoUi2pNb9NJ2NK06U/mpQhR738FNeVWXjNbla2LAZy3nTCqPHsg3phUQNqL3qhHj8w==",
"dev": true,
"requires": {
"@types/node": "*",
"dotenv": "^8.2.0"
}
},
"@types/parse-json": {
"version": "4.0.0",
"dev": true
@ -22335,6 +22388,12 @@
"is-obj": "^2.0.0"
}
},
"dotenv": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
"integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==",
"dev": true
},
"duplexer": {
"version": "0.1.2",
"dev": true
@ -22680,15 +22739,6 @@
"dev": true,
"requires": {}
},
"eslint-plugin-cypress": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.12.1.tgz",
"integrity": "sha512-c2W/uPADl5kospNDihgiLc7n87t5XhUbFDoTl6CfVkmG+kDAb5Ux10V9PoLPu9N+r7znpc+iQlcmAqT1A/89HA==",
"dev": true,
"requires": {
"globals": "^11.12.0"
}
},
"eslint-plugin-jest": {
"version": "24.7.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.7.0.tgz",
@ -26033,6 +26083,11 @@
"word-wrap": "~1.2.3"
}
},
"oracledb": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/oracledb/-/oracledb-5.3.0.tgz",
"integrity": "sha512-HMJzQ6lCf287ztvvehTEmjCWA21FQ3RMvM+mgoqd4i8pkREuqFWO+y3ovsGR9moJUg4T0xjcwS8rl4mggWPxmg=="
},
"os-homedir": {
"version": "1.0.2",
"dev": true

View file

@ -34,6 +34,7 @@
"@tooljet-plugins/mssql": "file:packages/mssql",
"@tooljet-plugins/mysql": "file:packages/mysql",
"@tooljet-plugins/n8n": "file:packages/n8n",
"@tooljet-plugins/oracledb": "file:packages/oracledb",
"@tooljet-plugins/postgresql": "file:packages/postgresql",
"@tooljet-plugins/redis": "file:packages/redis",
"@tooljet-plugins/restapi": "file:packages/restapi",
@ -52,7 +53,6 @@
"@typescript-eslint/parser": "^4.31.1",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-jest": "^24.4.2",
"eslint-plugin-prettier": "^3.4.1",
"jest": "^27.4.5",
@ -62,4 +62,4 @@
"ts-jest": "^27.1.2",
"typescript": "^4.5.4"
}
}
}

View file

@ -4,36 +4,38 @@ const airtable = require('../lib');
const nock = require('nock');
describe('airtable', () => {
const _airtable = new airtable.default()
describe('airtable operations', () => {
const sourceOptions = { api_key: '123456' };
test('#list_records', async () => {
nock(`https://api.airtable.com`).get('/v0/1/consumer/').query({ pageSize: '', offset: '' })
const _airtable = new airtable.default();
describe('airtable operations', () => {
const sourceOptions = { api_key: '123456' };
test('#list_records', async () => {
nock(`https://api.airtable.com`)
.get('/v0/1/consumer/')
.query({ pageSize: '', offset: '' })
.reply(200, { message: 'ok' });
const queryOptions = { operation: 'list_records', base_id: 1, table_name: 'consumer' }
const response = await _airtable.run(sourceOptions, queryOptions);
expect(response.status).toEqual('ok');
});
test('#retrieve_record', async () => {
nock(`https://api.airtable.com`).get('/v0/1/consumer/1').reply(200, { message: 'ok' });
const queryOptions = { operation: 'retrieve_record', base_id: 1, table_name: 'consumer', record_id: 1 }
const response = await _airtable.run(sourceOptions, queryOptions);
expect(response.status).toEqual('ok');
});
test('#update_record', async () => {
nock(`https://api.airtable.com`).intercept('/v0/1/consumer', 'patch').reply(200, { message: 'ok' });
const queryOptions = { operation: 'update_record', base_id: 1, table_name: 'consumer', record_id: 1, body: "{}" }
const response = await _airtable.run(sourceOptions, queryOptions);
expect(response.status).toEqual('ok');
});
test('#delete_record', async () => {
nock(`https://api.airtable.com`).intercept('/v0/1/consumer/1', 'delete').reply(200, { message: 'ok' });
const queryOptions = { operation: 'delete_record', base_id: 1, table_name: 'consumer', record_id: 1 }
const response = await _airtable.run(sourceOptions, queryOptions);
expect(response.status).toEqual('ok');
});
const queryOptions = { operation: 'list_records', base_id: 1, table_name: 'consumer' };
const response = await _airtable.run(sourceOptions, queryOptions);
expect(response.status).toEqual('ok');
});
test('#retrieve_record', async () => {
nock(`https://api.airtable.com`).get('/v0/1/consumer/1').reply(200, { message: 'ok' });
const queryOptions = { operation: 'retrieve_record', base_id: 1, table_name: 'consumer', record_id: 1 };
const response = await _airtable.run(sourceOptions, queryOptions);
expect(response.status).toEqual('ok');
});
test('#update_record', async () => {
nock(`https://api.airtable.com`).intercept('/v0/1/consumer', 'patch').reply(200, { message: 'ok' });
const queryOptions = { operation: 'update_record', base_id: 1, table_name: 'consumer', record_id: 1, body: '{}' };
const response = await _airtable.run(sourceOptions, queryOptions);
expect(response.status).toEqual('ok');
});
test('#delete_record', async () => {
nock(`https://api.airtable.com`).intercept('/v0/1/consumer/1', 'delete').reply(200, { message: 'ok' });
const queryOptions = { operation: 'delete_record', base_id: 1, table_name: 'consumer', record_id: 1 };
const response = await _airtable.run(sourceOptions, queryOptions);
expect(response.status).toEqual('ok');
});
});
});

View file

@ -1,6 +1,6 @@
'use strict';
const bigquery = require('../lib');
// const bigquery = require('../lib');
describe('bigquery', () => {
it.todo('needs tests');

View file

@ -1,7 +1,7 @@
'use strict';
const common = require('../lib/utils.helper');
// const common = require('../lib/utils.helper');
describe('common', () => {
it.todo('needs tests');
it.todo('needs tests');
});

View file

@ -1,7 +1,7 @@
'use strict';
const couchdb = require('../lib');
// const couchdb = require('../lib');
describe('couchdb', () => {
it.todo('needs tests');
});
it.todo('needs tests');
});

View file

@ -1,75 +1,50 @@
import {
QueryError,
QueryResult,
QueryService,
ConnectionTestResult,
} from "@tooljet-plugins/common";
import { SourceOptions, QueryOptions } from "./types";
import got from "got";
const JSON5 = require("json5");
import { QueryError, QueryResult, QueryService, ConnectionTestResult } from '@tooljet-plugins/common';
import { SourceOptions, QueryOptions } from './types';
import got from 'got';
const JSON5 = require('json5');
export default class Couchdb implements QueryService {
async run(
sourceOptions: SourceOptions,
queryOptions: QueryOptions
): Promise<QueryResult> {
async run(sourceOptions: SourceOptions, queryOptions: QueryOptions): Promise<QueryResult> {
let result = {};
let response = null;
const {
operation,
record_id,
limit,
view_url,
start_key,
end_key,
skip,
descending,
include_docs
} = queryOptions;
const { username, password, port, host, database, protocol } =
sourceOptions;
const { operation, record_id, limit, view_url, start_key, end_key, skip, descending, include_docs } = queryOptions;
const { username, password, port, host, database, protocol } = sourceOptions;
const revision_id = queryOptions.rev_id;
const authHeader = () => {
const combined = `${username}:${password}`;
const key = Buffer.from(combined).toString("base64");
const key = Buffer.from(combined).toString('base64');
return { Authorization: `Basic ${key}` };
};
try {
switch (operation) {
case "list_records": {
response = await got(
`${protocol}://${host}:${port}/${database}/_all_docs`,
{
method: "get",
headers: authHeader(),
searchParams: {
...(limit?.length > 0 && { limit }),
...(skip?.length > 0 && { skip }),
...(descending && { descending }),
...(include_docs && { include_docs }),
},
}
);
case 'list_records': {
response = await got(`${protocol}://${host}:${port}/${database}/_all_docs`, {
method: 'get',
headers: authHeader(),
searchParams: {
...(limit?.length > 0 && { limit }),
...(skip?.length > 0 && { skip }),
...(descending && { descending }),
...(include_docs && { include_docs }),
},
});
result = this.parseJSON(response.body);
break;
}
case "retrieve_record": {
response = await got(
`${protocol}://${host}:${port}/${database}/${record_id}`,
{
headers: authHeader(),
method: "get",
}
);
case 'retrieve_record': {
response = await got(`${protocol}://${host}:${port}/${database}/${record_id}`, {
headers: authHeader(),
method: 'get',
});
result = this.parseJSON(response.body);
break;
}
case "create_record": {
case 'create_record': {
response = await got(`${protocol}://${host}:${port}/${database}`, {
method: "post",
method: 'post',
headers: authHeader(),
json: {
records: this.parseJSON(queryOptions.body),
@ -79,53 +54,44 @@ export default class Couchdb implements QueryService {
break;
}
case "update_record": {
response = await got(
`${protocol}://${host}:${port}/${database}/${record_id}`,
{
method: "put",
headers: authHeader(),
json: {
_rev: revision_id,
records: this.parseJSON(queryOptions.body),
},
}
);
case 'update_record': {
response = await got(`${protocol}://${host}:${port}/${database}/${record_id}`, {
method: 'put',
headers: authHeader(),
json: {
_rev: revision_id,
records: this.parseJSON(queryOptions.body),
},
});
result = this.parseJSON(response.body);
break;
}
case "delete_record": {
response = await got(
`${protocol}://${host}:${port}/${database}/${record_id}`,
{
method: "delete",
headers: authHeader(),
searchParams: {
rev: revision_id,
},
}
);
case 'delete_record': {
response = await got(`${protocol}://${host}:${port}/${database}/${record_id}`, {
method: 'delete',
headers: authHeader(),
searchParams: {
rev: revision_id,
},
});
result = this.parseJSON(response.body);
break;
}
case "find": {
response = await got(
`${protocol}://${host}:${port}/${database}/_find`,
{
method: "post",
headers: authHeader(),
json: this.parseJSON(queryOptions.body),
}
);
case 'find': {
response = await got(`${protocol}://${host}:${port}/${database}/_find`, {
method: 'post',
headers: authHeader(),
json: this.parseJSON(queryOptions.body),
});
result = this.parseJSON(response.body);
break;
}
case "get_view": {
case 'get_view': {
response = await got(`${view_url}`, {
method: "get",
method: 'get',
headers: authHeader(),
searchParams: {
...(limit?.length > 0 && { limit }),
@ -141,32 +107,30 @@ export default class Couchdb implements QueryService {
}
} catch (error) {
console.log(error);
throw new QueryError("Query could not be completed", error.message, {});
throw new QueryError('Query could not be completed', error.message, {});
}
return {
status: "ok",
status: 'ok',
data: result,
};
}
async testConnection(
sourceOptions: SourceOptions
): Promise<ConnectionTestResult> {
const { username, password, port, host, database, protocol } =
sourceOptions;
async testConnection(sourceOptions: SourceOptions): Promise<ConnectionTestResult> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { username, password, port, host, database, protocol } = sourceOptions;
const combined = `${username}:${password}`;
const key = Buffer.from(combined).toString("base64");
const key = Buffer.from(combined).toString('base64');
const client = await got(`${protocol}://${host}:${port}/_all_dbs`, {
method: "get",
method: 'get',
headers: { Authorization: `Basic ${key}` },
});
if (!client) {
throw new Error("Error");
throw new Error('Error');
}
return {
status: "ok",
status: 'ok',
};
}

View file

@ -1,22 +1,21 @@
export type SourceOptions = {
username: string;
password: string;
database: string;
database: string;
port: string;
host:string;
protocol: string;
host: string;
protocol: string;
};
export type QueryOptions = {
operation: string;
record_id: string;
body: string;
rev_id:string;
view_url:string;
start_key?:string;
end_key?:string;
limit?:string;
skip?:string;
descending?:boolean;
include_docs?:boolean;
rev_id: string;
view_url: string;
start_key?: string;
end_key?: string;
limit?: string;
skip?: string;
descending?: boolean;
include_docs?: boolean;
};

View file

@ -0,0 +1,99 @@
{
"name": "@tooljet-plugins/couchdb",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@tooljet-plugins/couchdb",
"version": "1.0.0",
"dependencies": {
"@tooljet-plugins/common": "file:../common",
"react": "^17.0.2"
}
},
"../common": {
"version": "1.0.0",
"dependencies": {
"react": "^17.0.2",
"rimraf": "^3.0.2"
}
},
"node_modules/@tooljet-plugins/common": {
"resolved": "../common",
"link": true
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
},
"engines": {
"node": ">=0.10.0"
}
}
},
"dependencies": {
"@tooljet-plugins/common": {
"version": "file:../common",
"requires": {
"react": "^17.0.2",
"rimraf": "^3.0.2"
}
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"react": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
}
}
}

View file

@ -1,7 +1,7 @@
'use strict';
const dynamodb = require('../lib');
// const dynamodb = require('../lib');
describe('dynamodb', () => {
it.todo('needs tests');
it.todo('needs tests');
});

View file

@ -1,7 +1,7 @@
'use strict';
const elasticsearch = require('../lib');
// const elasticsearch = require('../lib');
describe('elasticsearch', () => {
it.todo('needs tests');
it.todo('needs tests');
});

View file

@ -1,7 +1,7 @@
'use strict';
const firestore = require('../lib');
// const firestore = require('../lib');
describe('firestore', () => {
it.todo('needs tests');
it.todo('needs tests');
});

View file

@ -1,7 +1,7 @@
'use strict';
const gcs = require('../lib');
// const gcs = require('../lib');
describe('gcs', () => {
it.todo('needs tests');
it.todo('needs tests');
});

View file

@ -1,30 +1,74 @@
'use strict';
const { makeRequestBodyToBatchUpdate } =require('../lib/operations');
const { makeRequestBodyToBatchUpdate } = require('../lib/operations');
describe('googlesheets', () => {
it('should generate the request body for update operation' ,() => {
const requestBody = {
caseOne: { Gender: 'Female' },
caseTwo: { extra: '0 points' },
caseThree: { Gender: 'Female', extra: '0 points' }
}
const filterCondition = { key: 'Student Name', value: 'Anna' }
const filterOperator = '==='
const data = [
[ 'ID', '1', '2' ],[ 'Student Name', 'John', 'Anna' ],[ 'Major', 'Science', 'English' ],[],[ 'Gender', 'Male', 'Female' ],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[ 'extra', 'extra-update', '0 points' ]
]
it('should generate the request body for update operation', () => {
const requestBody = {
caseOne: { Gender: 'Female' },
caseTwo: { extra: '0 points' },
caseThree: { Gender: 'Female', extra: '0 points' },
};
const filterCondition = { key: 'Student Name', value: 'Anna' };
const filterOperator = '===';
const data = [
['ID', '1', '2'],
['Student Name', 'John', 'Anna'],
['Major', 'Science', 'English'],
[],
['Gender', 'Male', 'Female'],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
['extra', 'extra-update', '0 points'],
];
const queryOptionsOne = { requestBody: requestBody.caseOne, filterCondition, filterOperator, data }
const queryOptionsTwo = { requestBody: requestBody.caseTwo, filterCondition, filterOperator, data }
const queryOptionsThree = { requestBody: requestBody.caseThree, filterCondition, filterOperator, data }
const queryOptionsOne = { requestBody: requestBody.caseOne, filterCondition, filterOperator, data };
const queryOptionsTwo = { requestBody: requestBody.caseTwo, filterCondition, filterOperator, data };
const queryOptionsThree = { requestBody: requestBody.caseThree, filterCondition, filterOperator, data };
const expectedBodyForCaseOne = makeRequestBodyToBatchUpdate(queryOptionsOne.requestBody, queryOptionsOne.filterCondition, queryOptionsOne.filterOperator, queryOptionsOne.data)
const expectedBodyForCaseTwo = makeRequestBodyToBatchUpdate(queryOptionsTwo.requestBody, queryOptionsOne.filterCondition, queryOptionsOne.filterOperator, queryOptionsOne.data)
const expectedBodyForCaseThree = makeRequestBodyToBatchUpdate(queryOptionsThree.requestBody, queryOptionsOne.filterCondition, queryOptionsOne.filterOperator, queryOptionsOne.data)
const expectedBodyForCaseOne = makeRequestBodyToBatchUpdate(
queryOptionsOne.requestBody,
queryOptionsOne.filterCondition,
queryOptionsOne.filterOperator,
queryOptionsOne.data
);
const expectedBodyForCaseTwo = makeRequestBodyToBatchUpdate(
queryOptionsTwo.requestBody,
queryOptionsOne.filterCondition,
queryOptionsOne.filterOperator,
queryOptionsOne.data
);
const expectedBodyForCaseThree = makeRequestBodyToBatchUpdate(
queryOptionsThree.requestBody,
queryOptionsOne.filterCondition,
queryOptionsOne.filterOperator,
queryOptionsOne.data
);
expect(expectedBodyForCaseOne).toEqual([{ cellValue: 'Female', cellIndex: 'E3' }]);
expect(expectedBodyForCaseTwo).toEqual([{ cellValue: '0 points', cellIndex: 'AB3' }]);
expect(expectedBodyForCaseThree).toEqual([{ cellValue: 'Female', cellIndex: 'E3' }, { cellValue: '0 points', cellIndex: 'AB3' }]);
});
expect(expectedBodyForCaseOne).toEqual([{ cellValue: 'Female', cellIndex: 'E3' }]);
expect(expectedBodyForCaseTwo).toEqual([{ cellValue: '0 points', cellIndex: 'AB3' }]);
expect(expectedBodyForCaseThree).toEqual([
{ cellValue: 'Female', cellIndex: 'E3' },
{ cellValue: '0 points', cellIndex: 'AB3' },
]);
});
});

View file

@ -3,19 +3,19 @@
const graphql = require('../lib');
describe('graphql', () => {
it('should query graphql datasources', async () => {
const sourceOptions = {
url: 'https://api.spacex.land/graphql/',
headers: [],
url_params: [],
};
const queryOptions = { query: '{ launchesPast(limit: 10) { mission_name } }' };
it('should query graphql datasources', async () => {
const sourceOptions = {
url: 'https://api.spacex.land/graphql/',
headers: [],
url_params: [],
};
const _graphql = new graphql.default()
const result = await _graphql.run(sourceOptions, queryOptions, 'no-datasource-id');
expect(result.data['data']['launchesPast'].length).toBe(10);
});
const queryOptions = { query: '{ launchesPast(limit: 10) { mission_name } }' };
const _graphql = new graphql.default();
const result = await _graphql.run(sourceOptions, queryOptions, 'no-datasource-id');
expect(result.data['data']['launchesPast'].length).toBe(10);
});
});

View file

@ -1,7 +1,7 @@
'use strict';
const mailgun = require('../lib');
// const mailgun = require('../lib');
describe('mailgun', () => {
it.todo('needs tests');
});
it.todo('needs tests');
});

View file

@ -1,5 +1,5 @@
import { QueryError, QueryResult, QueryService, ConnectionTestResult } from '@tooljet-plugins/common';
import {SourceOptions, QueryOptions, EmailOptions} from "./types";
import { QueryError, QueryResult, QueryService } from '@tooljet-plugins/common';
import { SourceOptions, QueryOptions, EmailOptions } from './types';
import MailgunSdk from 'mailgun.js';
import FormData from 'form-data';
@ -10,7 +10,7 @@ export default class Mailgun implements QueryService {
}
const sdk = new MailgunSdk(FormData);
const mailgunOptions = {username: 'api', key: sourceOptions.api_key, url: null}
const mailgunOptions = { username: 'api', key: sourceOptions.api_key, url: null };
if (sourceOptions.eu_hosted) {
mailgunOptions.url = 'https://api.eu.mailgun.net';
}
@ -21,8 +21,8 @@ export default class Mailgun implements QueryService {
to: queryOptions.send_mail_to,
from: queryOptions.send_mail_from,
subject: queryOptions.subject,
text: queryOptions.text
}
text: queryOptions.text,
};
if (queryOptions.html && queryOptions.html.length > 0) {
emailOptions.html = queryOptions.html;

View file

@ -1,7 +1,7 @@
'use strict';
const minioapi = require('../lib');
// const minioapi = require('../lib');
describe('minioapi', () => {
it.todo('needs tests');
it.todo('needs tests');
});

View file

@ -1,7 +1,7 @@
"use strict";
'use strict';
const mongodb = require("../lib");
// const mongodb = require('../lib');
describe("mongodb", () => {
it.todo("needs tests");
describe('mongodb', () => {
it.todo('needs tests');
});

View file

@ -1,7 +1,7 @@
'use strict';
const mssql = require('../lib');
// const mssql = require('../lib');
describe('mssql', () => {
it.todo('needs tests');
it.todo('needs tests');
});

View file

@ -60,9 +60,9 @@ export default class MssqlQueryService implements QueryService {
database: sourceOptions.database,
port: +sourceOptions.port,
options: {
encrypt: sourceOptions.azure ?? false
}
}
encrypt: sourceOptions.azure ?? false,
},
},
};
return knex(config);

View file

@ -21,8 +21,8 @@ describe('mysql', () => {
],
};
const _mysql = new mysql.default()
const _mysql = new mysql.default();
const builtQuery = await _mysql.buildBulkUpdateQuery(queryOptions);
const expectedQuery =
"UPDATE customers SET name = 'sam', email = 'sam@example.com' WHERE id = 1; UPDATE customers SET name = 'jon', email = 'jon@example.com' WHERE id = 2;";

View file

@ -1,7 +1,7 @@
'use strict';
const n8n = require('../lib');
// const n8n = require('../lib');
describe('n8n', () => {
it.todo('needs tests');
it.todo('needs tests');
});

4
plugins/packages/oracledb/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
node_modules
lib/*.d.*
lib/*.js
lib/*.js.map

View file

@ -0,0 +1,4 @@
# Oracle DB
Documentation on: https://docs.tooljet.com/docs/data-sources/oracledb

View file

@ -0,0 +1,7 @@
'use strict';
// const oracledb = require('../lib');
describe('oracledb', () => {
it.todo('needs tests');
});

View file

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 231 30' preserveAspectRatio='xMinYMid'><path d='M99.61,19.52h15.24l-8.05-13L92,30H85.27l18-28.17a4.29,4.29,0,0,1,7-.05L128.32,30h-6.73l-3.17-5.25H103l-3.36-5.23m69.93,5.23V0.28h-5.72V27.16a2.76,2.76,0,0,0,.85,2,2.89,2.89,0,0,0,2.08.87h26l3.39-5.25H169.54M75,20.38A10,10,0,0,0,75,.28H50V30h5.71V5.54H74.65a4.81,4.81,0,0,1,0,9.62H58.54L75.6,30h8.29L72.43,20.38H75M14.88,30H32.15a14.86,14.86,0,0,0,0-29.71H14.88a14.86,14.86,0,1,0,0,29.71m16.88-5.23H15.26a9.62,9.62,0,0,1,0-19.23h16.5a9.62,9.62,0,1,1,0,19.23M140.25,30h17.63l3.34-5.23H140.64a9.62,9.62,0,1,1,0-19.23h16.75l3.38-5.25H140.25a14.86,14.86,0,1,0,0,29.71m69.87-5.23a9.62,9.62,0,0,1-9.26-7h24.42l3.36-5.24H200.86a9.61,9.61,0,0,1,9.26-7h16.76l3.35-5.25h-20.5a14.86,14.86,0,0,0,0,29.71h17.63l3.35-5.23h-20.6' transform='translate(-0.02 0)' style='fill:#C74634'/></svg>

After

Width:  |  Height:  |  Size: 874 B

View file

@ -0,0 +1,154 @@
import { Knex, knex } from 'knex';
import oracledb from 'oracledb';
import {
cacheConnection,
getCachedConnection,
ConnectionTestResult,
QueryService,
QueryResult,
} from '@tooljet-plugins/common';
import { SourceOptions, QueryOptions } from './types';
export default class OracledbQueryService implements QueryService {
private static _instance: OracledbQueryService;
constructor() {
if (OracledbQueryService._instance) {
return OracledbQueryService._instance;
}
OracledbQueryService._instance = this;
return OracledbQueryService._instance;
}
async run(
sourceOptions: SourceOptions,
queryOptions: QueryOptions,
dataSourceId: string,
dataSourceUpdatedAt: string
): Promise<QueryResult> {
let result = {
rows: [],
};
let query = '';
if (queryOptions.mode === 'gui') {
if (queryOptions.operation === 'bulk_update_pkey') {
query = await this.buildBulkUpdateQuery(queryOptions);
}
} else {
query = queryOptions.query;
}
const knexInstance = await this.getConnection(sourceOptions, {}, true, dataSourceId, dataSourceUpdatedAt);
// eslint-disable-next-line no-useless-catch
try {
result = await knexInstance.raw(query);
return {
status: 'ok',
data: result,
};
} catch (err) {
throw err;
}
}
async testConnection(sourceOptions: SourceOptions): Promise<ConnectionTestResult> {
const knexInstance = await this.getConnection(sourceOptions, {}, false);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const result = await knexInstance.raw('SELECT * FROM v$version');
return {
status: 'ok',
};
}
initOracleClient(clientPathType: string, customPath: string) {
try {
if (clientPathType === 'custom') {
if (process.platform === 'darwin') {
oracledb.initOracleClient({ libDir: process.env.HOME + customPath });
} else if (process.platform === 'win32') {
oracledb.initOracleClient({
libDir: customPath,
}); // note the double backslashes
}
} else {
oracledb.initOracleClient();
}
} catch (err) {
console.error(err);
throw err;
}
}
async buildConnection(sourceOptions: SourceOptions) {
// we should add this to our datasource documentation
try {
oracledb.oracleClientVersion;
} catch (err) {
console.log('Oracle client is not initailized');
this.initOracleClient(sourceOptions.client_path_type, sourceOptions.path);
}
const config: Knex.Config = {
client: 'oracledb',
connection: {
user: sourceOptions.username,
password: sourceOptions.password,
connectString: `(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=${sourceOptions.host})(PORT=${sourceOptions.port}))(CONNECT_DATA=(SERVER=DEDICATED)(${sourceOptions.database_type}=${sourceOptions.database})))`,
multipleStatements: true,
ssl: sourceOptions.ssl_enabled, // Disabling by default for backward compatibility
},
};
return knex(config);
}
async getConnection(
sourceOptions: SourceOptions,
options: any,
checkCache: boolean,
dataSourceId?: string,
dataSourceUpdatedAt?: string
): Promise<any> {
if (checkCache) {
let connection = await getCachedConnection(dataSourceId, dataSourceUpdatedAt);
if (connection) {
return connection;
} else {
connection = await this.buildConnection(sourceOptions);
dataSourceId && cacheConnection(dataSourceId, connection);
return connection;
}
} else {
return await this.buildConnection(sourceOptions);
}
}
async buildBulkUpdateQuery(queryOptions: any): Promise<string> {
let queryText = '';
const tableName = queryOptions['table'];
const primaryKey = queryOptions['primary_key_column'];
const records = queryOptions['records'];
for (const record of records) {
queryText = `${queryText} UPDATE ${tableName} SET`;
for (const key of Object.keys(record)) {
if (key !== primaryKey) {
queryText = ` ${queryText} ${key} = '${record[key]}',`;
}
}
queryText = queryText.slice(0, -1);
queryText = `begin ${queryText} WHERE ${primaryKey} = ${record[primaryKey]}; end;`;
}
return queryText.trim();
}
}

View file

@ -0,0 +1,133 @@
{
"$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/manifest.schema.json",
"title": "Oracle DB datasource",
"description": "A schema defining Oracle DB datasource",
"type": "database",
"source": {
"name": "Oracle DB",
"kind": "oracledb",
"exposedVariables": {
"isLoading": false,
"data": {},
"rawData": {}
},
"options": {
"password": {
"encrypted": true
}
}
},
"defaults": {
"host": {
"value": "localhost"
},
"port": {
"value": 1521
},
"database": {
"value": ""
},
"database_type": {
"value": "SID"
},
"username": {
"value": ""
},
"password": {
"value": ""
},
"ssl_enabled": {
"value": true
},
"client_path_type":{
"value": "default"
}
},
"properties": {
"client_path_type": {
"label": "Client Library Location",
"key": "client_path_type",
"type": "dropdown-component-flip",
"description": "Single select dropdown for client library",
"list": [
{
"value": "default",
"name": "Default"
},
{
"value": "custom",
"name": "Custom"
}
],
"commonFields":{
"host": {
"label": "Host",
"key": "host",
"type": "text",
"description": "Enter host"
},
"port": {
"label": "Port",
"key": "port",
"type": "text",
"description": "Enter port"
},
"database_type": {
"label": "SID / Service Name",
"key": "database_type",
"type": "dropdown",
"description": "Type of the database",
"list": [
{
"name": "SID",
"value": "SID"
},
{
"name": "Service name",
"value": "SERVICE_NAME"
}
]
},
"database": {
"label": "Database Name",
"key": "database",
"type": "text",
"description": "Name of the database"
},
"ssl_enabled": {
"label": "SSL",
"key": "ssl_enabled",
"type": "toggle",
"description": "Toggle for ssl_enabled"
},
"username": {
"label": "Username",
"key": "username",
"type": "text",
"description": "Enter username"
},
"password": {
"label": "Password",
"key": "password",
"type": "password",
"description": "Enter password"
}
}
},
"custom":{
"path": {
"label": "Path",
"key": "path",
"type": "text",
"description": "Enter path"
}
}
},
"required": [
"host",
"port",
"username",
"password"
]
}

View file

@ -0,0 +1,81 @@
{
"$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/operations.schema.json",
"title": "Oracle DB datasource",
"description": "A schema defining Oracle DB datasource",
"type": "database",
"defaults": {},
"properties": {
"mode": {
"label": "",
"key": "mode",
"type": "dropdown-component-flip",
"description": "Single select dropdown for mode",
"list": [
{
"name": "SQL mode",
"value": "sql"
},
{
"name": "GUI mode",
"value": "gui"
}
]
},
"sql": {
"query": {
"key": "query",
"type": "codehinter",
"description": "Enter query",
"placeholder": "SELECT * FROM customers",
"height": "150px"
}
},
"gui": {
"operation": {
"label": "Operation",
"key": "operation",
"type": "dropdown-component-flip",
"description": "Single select dropdown for mode",
"list": [
{
"name": "Bulk update using primary key",
"value": "bulk_update_pkey"
}
]
},
"bulk_update_pkey": {
"table": {
"label": "Table",
"key": "table",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter table",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins",
"placeholder": "Enter table"
},
"primary_key_column": {
"label": "Primary key column",
"key": "primary_key_column",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter primary key column",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins",
"placeholder": "Enter primary key column"
},
"records": {
"label": "Records",
"key": "records",
"type": "codehinter",
"description": "Enter records",
"height": "150px"
}
}
}
}
}

View file

@ -0,0 +1,16 @@
export type SourceOptions = {
database: string;
database_type: string;
host: string;
port: string;
username: string;
password: string;
ssl_enabled: boolean;
client_path_type: string;
path: string;
};
export type QueryOptions = {
operation: string;
query: string;
mode: string;
};

View file

@ -0,0 +1,516 @@
{
"name": "@tooljet-plugins/oracledb",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@tooljet-plugins/oracledb",
"version": "1.0.0",
"dependencies": {
"@tooljet-plugins/common": "file:../common",
"knex": "^0.95.14",
"oracledb": "^5.3.0",
"react": "^17.0.2"
},
"devDependencies": {
"@types/oracledb": "^5.2.2"
}
},
"../common": {
"name": "@tooljet-plugins/common",
"version": "1.0.0",
"dependencies": {
"react": "^17.0.2",
"rimraf": "^3.0.2"
}
},
"node_modules/@tooljet-plugins/common": {
"resolved": "../common",
"link": true
},
"node_modules/@types/node": {
"version": "17.0.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
"integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==",
"dev": true
},
"node_modules/@types/oracledb": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@types/oracledb/-/oracledb-5.2.2.tgz",
"integrity": "sha512-aYb2DdZOQVIgSCSXjXNikQuyiHAY09SkRA4cjwoj+F/mhLJDahdjNeBmvQvfFojyChCKLuupSJHqoAXPExgV5w==",
"dev": true,
"dependencies": {
"@types/node": "*",
"dotenv": "^8.2.0"
}
},
"node_modules/colorette": {
"version": "2.0.16",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz",
"integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g=="
},
"node_modules/commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"engines": {
"node": ">= 10"
}
},
"node_modules/debug": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/dotenv": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
"integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
"engines": {
"node": ">=6"
}
},
"node_modules/esm": {
"version": "3.2.25",
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
"engines": {
"node": ">=6"
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/getopts": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/getopts/-/getopts-2.2.5.tgz",
"integrity": "sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA=="
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/interpret": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
"integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-core-module": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz",
"integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==",
"dependencies": {
"has": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/knex": {
"version": "0.95.15",
"resolved": "https://registry.npmjs.org/knex/-/knex-0.95.15.tgz",
"integrity": "sha512-Loq6WgHaWlmL2bfZGWPsy4l8xw4pOE+tmLGkPG0auBppxpI0UcK+GYCycJcqz9W54f2LiGewkCVLBm3Wq4ur/w==",
"dependencies": {
"colorette": "2.0.16",
"commander": "^7.1.0",
"debug": "4.3.2",
"escalade": "^3.1.1",
"esm": "^3.2.25",
"getopts": "2.2.5",
"interpret": "^2.2.0",
"lodash": "^4.17.21",
"pg-connection-string": "2.5.0",
"rechoir": "0.7.0",
"resolve-from": "^5.0.0",
"tarn": "^3.0.1",
"tildify": "2.0.0"
},
"bin": {
"knex": "bin/cli.js"
},
"engines": {
"node": ">=10"
},
"peerDependenciesMeta": {
"mysql": {
"optional": true
},
"mysql2": {
"optional": true
},
"pg": {
"optional": true
},
"pg-native": {
"optional": true
},
"sqlite3": {
"optional": true
},
"tedious": {
"optional": true
}
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/oracledb": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/oracledb/-/oracledb-5.3.0.tgz",
"integrity": "sha512-HMJzQ6lCf287ztvvehTEmjCWA21FQ3RMvM+mgoqd4i8pkREuqFWO+y3ovsGR9moJUg4T0xjcwS8rl4mggWPxmg==",
"hasInstallScript": true,
"engines": {
"node": ">=10.16"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"node_modules/pg-connection-string": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
"integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
},
"node_modules/react": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/rechoir": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz",
"integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==",
"dependencies": {
"resolve": "^1.9.0"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/resolve": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
"integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
"dependencies": {
"is-core-module": "^2.8.1",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
"engines": {
"node": ">=8"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tarn": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz",
"integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/tildify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz",
"integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==",
"engines": {
"node": ">=8"
}
}
},
"dependencies": {
"@tooljet-plugins/common": {
"version": "file:../common",
"requires": {
"react": "^17.0.2",
"rimraf": "^3.0.2"
}
},
"@types/node": {
"version": "17.0.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
"integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==",
"dev": true
},
"@types/oracledb": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@types/oracledb/-/oracledb-5.2.2.tgz",
"integrity": "sha512-aYb2DdZOQVIgSCSXjXNikQuyiHAY09SkRA4cjwoj+F/mhLJDahdjNeBmvQvfFojyChCKLuupSJHqoAXPExgV5w==",
"dev": true,
"requires": {
"@types/node": "*",
"dotenv": "^8.2.0"
}
},
"colorette": {
"version": "2.0.16",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz",
"integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g=="
},
"commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="
},
"debug": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
"requires": {
"ms": "2.1.2"
}
},
"dotenv": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
"integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==",
"dev": true
},
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
},
"esm": {
"version": "3.2.25",
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"getopts": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/getopts/-/getopts-2.2.5.tgz",
"integrity": "sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA=="
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"requires": {
"function-bind": "^1.1.1"
}
},
"interpret": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
"integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw=="
},
"is-core-module": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz",
"integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==",
"requires": {
"has": "^1.0.3"
}
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"knex": {
"version": "0.95.15",
"resolved": "https://registry.npmjs.org/knex/-/knex-0.95.15.tgz",
"integrity": "sha512-Loq6WgHaWlmL2bfZGWPsy4l8xw4pOE+tmLGkPG0auBppxpI0UcK+GYCycJcqz9W54f2LiGewkCVLBm3Wq4ur/w==",
"requires": {
"colorette": "2.0.16",
"commander": "^7.1.0",
"debug": "4.3.2",
"escalade": "^3.1.1",
"esm": "^3.2.25",
"getopts": "2.2.5",
"interpret": "^2.2.0",
"lodash": "^4.17.21",
"pg-connection-string": "2.5.0",
"rechoir": "0.7.0",
"resolve-from": "^5.0.0",
"tarn": "^3.0.1",
"tildify": "2.0.0"
}
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"oracledb": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/oracledb/-/oracledb-5.3.0.tgz",
"integrity": "sha512-HMJzQ6lCf287ztvvehTEmjCWA21FQ3RMvM+mgoqd4i8pkREuqFWO+y3ovsGR9moJUg4T0xjcwS8rl4mggWPxmg=="
},
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"pg-connection-string": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
"integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
},
"react": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
},
"rechoir": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz",
"integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==",
"requires": {
"resolve": "^1.9.0"
}
},
"resolve": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
"integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
"requires": {
"is-core-module": "^2.8.1",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
}
},
"resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="
},
"supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
},
"tarn": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz",
"integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ=="
},
"tildify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz",
"integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw=="
}
}
}

View file

@ -0,0 +1,28 @@
{
"name": "@tooljet-plugins/oracledb",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib"
],
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1",
"build": "tsc -b",
"clean": "rimraf ./dist && rimraf tsconfig.tsbuildinfo"
},
"homepage": "https://github.com/tooljet/tooljet#readme",
"dependencies": {
"@tooljet-plugins/common": "file:../common",
"knex": "^0.95.14",
"oracledb": "^5.3.0",
"react": "^17.0.2"
},
"devDependencies": {
"@types/oracledb": "^5.2.2"
}
}

View file

@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "lib"
},
"exclude": [
"node_modules",
"dist"
]
}

View file

@ -3,30 +3,30 @@
const postgresql = require('../lib');
describe('postgresql', () => {
it('should generate the query for bulk update operation', async () => {
const queryOptions = {
table: 'customers',
primary_key_column: 'id',
records: [
{
id: 1,
name: 'sam',
email: 'sam@example.com',
},
{
id: 2,
name: 'jon',
email: 'jon@example.com',
},
],
};
it('should generate the query for bulk update operation', async () => {
const queryOptions = {
table: 'customers',
primary_key_column: 'id',
records: [
{
id: 1,
name: 'sam',
email: 'sam@example.com',
},
{
id: 2,
name: 'jon',
email: 'jon@example.com',
},
],
};
const _postgresql = new postgresql.default()
const builtQuery = await _postgresql.buildBulkUpdateQuery(queryOptions);
const expectedQuery =
"UPDATE customers SET name = 'sam', email = 'sam@example.com' WHERE id = 1; UPDATE customers SET name = 'jon', email = 'jon@example.com' WHERE id = 2;";
expect(builtQuery).toBe(expectedQuery);
});
const _postgresql = new postgresql.default();
const builtQuery = await _postgresql.buildBulkUpdateQuery(queryOptions);
const expectedQuery =
"UPDATE customers SET name = 'sam', email = 'sam@example.com' WHERE id = 1; UPDATE customers SET name = 'jon', email = 'jon@example.com' WHERE id = 2;";
expect(builtQuery).toBe(expectedQuery);
});
});

View file

@ -1,7 +1,7 @@
'use strict';
const redis = require('../lib');
// const redis = require('../lib');
describe('redis', () => {
it.todo('needs tests');
it.todo('needs tests');
});

View file

@ -1,7 +1,7 @@
'use strict';
const restapi = require('../lib');
// const restapi = require('../lib');
describe('restapi', () => {
it.todo('needs tests');
it.todo('needs tests');
});

View file

@ -1,7 +1,7 @@
'use strict';
const s3 = require('../lib');
// const s3 = require('../lib');
describe('s3', () => {
it.todo('needs tests');
it.todo('needs tests');
});

View file

@ -1,7 +1,7 @@
'use strict';
const sendgrid = require('../lib');
// const sendgrid = require('../lib');
describe('sendgrid', () => {
it.todo('needs tests');
it.todo('needs tests');
});

View file

@ -1,7 +1,7 @@
'use strict';
const slack = require('../lib');
// const slack = require('../lib');
describe('slack', () => {
it.todo('needs tests');
it.todo('needs tests');
});

View file

@ -1,6 +1,6 @@
'use strict';
const smtp = require('../lib');
// const smtp = require('../lib');
describe('smtp', () => {
it.todo('needs tests');

View file

@ -1,6 +1,6 @@
'use strict';
const snowflake = require('../lib');
// const snowflake = require('../lib');
describe('snowflake', () => {
it.todo('needs tests');

View file

@ -1,7 +1,7 @@
'use strict';
const stripe = require('../lib');
// const stripe = require('../lib');
describe('stripe', () => {
it.todo('needs tests');
it.todo('needs tests');
});

View file

@ -1,7 +1,7 @@
'use strict';
const twilioapi = require('../lib');
// const twilioapi = require('../lib');
describe('twilioapi', () => {
it.todo('needs tests');
it.todo('needs tests');
});

View file

@ -1,7 +1,7 @@
'use strict';
const typesenseapi = require('../lib');
// const typesenseapi = require('../lib');
describe('typesenseapi', () => {
it.todo('needs tests');
it.todo('needs tests');
});

View file

@ -1 +1 @@
1.5.1
1.6.0

View file

@ -105,6 +105,7 @@
"@tooljet-plugins/mssql": "file:packages/mssql",
"@tooljet-plugins/mysql": "file:packages/mysql",
"@tooljet-plugins/n8n": "file:packages/n8n",
"@tooljet-plugins/oracledb": "file:packages/oracledb",
"@tooljet-plugins/postgresql": "file:packages/postgresql",
"@tooljet-plugins/redis": "file:packages/redis",
"@tooljet-plugins/restapi": "file:packages/restapi",
@ -14313,6 +14314,7 @@
"@tooljet-plugins/mssql": "file:packages/mssql",
"@tooljet-plugins/mysql": "file:packages/mysql",
"@tooljet-plugins/n8n": "file:packages/n8n",
"@tooljet-plugins/oracledb": "file:packages/oracledb",
"@tooljet-plugins/postgresql": "file:packages/postgresql",
"@tooljet-plugins/redis": "file:packages/redis",
"@tooljet-plugins/restapi": "file:packages/restapi",

View file

@ -288,7 +288,7 @@ export class AppsService {
}
async createVersion(user: User, app: App, versionName: string, versionFromId: string): Promise<AppVersion> {
const lastVersion = await this.appVersionsRepository.findOne({
const versionFrom = await this.appVersionsRepository.findOne({
where: { id: versionFromId },
});
@ -299,12 +299,12 @@ export class AppsService {
manager.create(AppVersion, {
name: versionName,
app,
definition: lastVersion?.definition,
definition: versionFrom?.definition,
createdAt: new Date(),
updatedAt: new Date(),
})
);
await this.setupDataSourcesAndQueriesForVersion(manager, appVersion, lastVersion);
await this.setupDataSourcesAndQueriesForVersion(manager, appVersion, versionFrom);
});
return appVersion;
@ -320,24 +320,34 @@ export class AppsService {
await getManager().transaction(async (manager) => {
await manager.delete(DataSource, { appVersionId: version.id });
await manager.delete(DataQuery, { appVersionId: version.id });
result = await manager.delete(AppVersion, { id: version.id, appId: app.id });
result = await manager.delete(AppVersion, {
id: version.id,
appId: app.id,
});
});
return result;
}
async setupDataSourcesAndQueriesForVersion(manager: EntityManager, appVersion: AppVersion, lastVersion: AppVersion) {
if (lastVersion) {
await this.createNewDataSourcesAndQueriesForVersion(manager, appVersion, lastVersion);
async setupDataSourcesAndQueriesForVersion(manager: EntityManager, appVersion: AppVersion, versionFrom: AppVersion) {
if (versionFrom) {
await this.createNewDataSourcesAndQueriesForVersion(manager, appVersion, versionFrom);
} else {
// TODO: Remove this when default version will be create when app creation is done
const totalVersions = await manager.count(AppVersion, {
where: { appId: appVersion.appId },
});
if (totalVersions > 1) {
throw new BadRequestException('More than one version found. Version to create from not specified.');
}
await this.associateExistingDataSourceAndQueriesToVersion(manager, appVersion);
}
}
async associateExistingDataSourceAndQueriesToVersion(manager: EntityManager, appVersion: AppVersion) {
const dataSources = await manager.find(DataSource, {
where: { appId: appVersion.appId },
where: { appId: appVersion.appId, appVersionId: null },
});
for await (const dataSource of dataSources) {
await manager.update(DataSource, dataSource.id, {
@ -346,7 +356,7 @@ export class AppsService {
}
const dataQueries = await manager.find(DataQuery, {
where: { appId: appVersion.appId },
where: { appId: appVersion.appId, appVersionId: null },
});
for await (const dataQuery of dataQueries) {
await manager.update(DataQuery, dataQuery.id, {
@ -358,13 +368,13 @@ export class AppsService {
async createNewDataSourcesAndQueriesForVersion(
manager: EntityManager,
appVersion: AppVersion,
lastVersion: AppVersion
versionFrom: AppVersion
) {
const oldDataSourceToNewMapping = {};
const oldDataQueryToNewMapping = {};
const dataSources = await manager.find(DataSource, {
where: { appVersionId: lastVersion.id },
where: { appVersionId: versionFrom.id },
});
for await (const dataSource of dataSources) {
@ -384,7 +394,7 @@ export class AppsService {
}
const dataQueries = await manager.find(DataQuery, {
where: { appVersionId: lastVersion.id },
where: { appVersionId: versionFrom.id },
});
const newDataQueries = [];
for await (const dataQuery of dataQueries) {

View file

@ -7,7 +7,7 @@ import { DataQuery } from '../../src/entities/data_query.entity';
import { CredentialsService } from './credentials.service';
import { DataSource } from 'src/entities/data_source.entity';
import { DataSourcesService } from './data_sources.service';
const got = require('got');
import got from 'got';
@Injectable()
export class DataQueriesService {
@ -115,24 +115,37 @@ export class DataQueriesService {
return result;
}
checkIfContentTypeIsURLenc(headers: []) {
const objectHeaders = Object.fromEntries(headers);
const contentType = objectHeaders['content-type'] ?? objectHeaders['Content-Type'];
return contentType === 'application/x-www-form-urlencoded';
}
/* This function fetches the access token from the token url set in REST API (oauth) datasource */
async fetchOAuthToken(sourceOptions: any, code: string): Promise<any> {
const tooljetHost = process.env.TOOLJET_HOST;
const isUrlEncoded = this.checkIfContentTypeIsURLenc(sourceOptions['headers']);
const accessTokenUrl = sourceOptions['access_token_url'];
const customParams = Object.fromEntries(sourceOptions['custom_auth_params']);
Object.keys(customParams).forEach((key) => (customParams[key] === '' ? delete customParams[key] : {}));
const bodyData = {
code,
client_id: sourceOptions['client_id'],
client_secret: sourceOptions['client_secret'],
grant_type: sourceOptions['grant_type'],
redirect_uri: `${tooljetHost}/oauth2/authorize`,
...customParams,
};
const response = await got(accessTokenUrl, {
method: 'post',
json: {
code,
client_id: sourceOptions['client_id'],
client_secret: sourceOptions['client_secret'],
grant_type: sourceOptions['grant_type'],
redirect_uri: `${tooljetHost}/oauth2/authorize`,
...customParams,
headers: isUrlEncoded && {
'content-type': 'application/x-www-form-urlencoded',
},
form: isUrlEncoded ? bodyData : undefined,
json: !isUrlEncoded ? bodyData : undefined,
});
const result = JSON.parse(response.body);

View file

@ -121,7 +121,10 @@ describe('apps controller', () => {
});
const organization = adminUserData.organization;
const allUserGroup = await getManager().findOne(GroupPermission, {
where: { group: 'all_users', organization: adminUserData.organization },
where: {
group: 'all_users',
organization: adminUserData.organization,
},
});
const developerUserData = await createUser(app, {
email: 'developer@tooljet.io',
@ -765,7 +768,7 @@ describe('apps controller', () => {
const application = await createApplication(app, {
user: adminUserData.user,
});
const version = await createApplicationVersion(app, application);
// setup app permissions for developer
const developerUserGroup = await getRepository(GroupPermission).findOne({
where: {
@ -784,6 +787,7 @@ describe('apps controller', () => {
.set('Authorization', authHeaderForUser(userData.user))
.send({
versionName: 'v1',
versionFromId: version.id,
});
expect(response.statusCode).toBe(201);
@ -812,7 +816,9 @@ describe('apps controller', () => {
expect(response.statusCode).toBe(201);
const v2 = await getManager().findOne(AppVersion, { where: { name: 'v2' } });
const v2 = await getManager().findOne(AppVersion, {
where: { name: 'v2' },
});
expect(v2.definition).toEqual(v1.definition);
});
@ -947,6 +953,31 @@ describe('apps controller', () => {
expect(dataQueries).toHaveLength(3);
expect(dataSources.map((s) => s.appVersionId).includes(response.body.id)).toBeTruthy();
expect(dataQueries.map((q) => q.appVersionId).includes(response.body.id)).toBeTruthy();
// creating a new version from a non existing version id will throw error when more than 1 versions exist
await createDataSource(app, {
name: 'name',
kind: 'postgres',
application: application,
user: adminUserData.user,
});
await createDataQuery(app, {
application,
dataSource,
kind: 'restapi',
options: { method: 'get' },
});
response = await request(app.getHttpServer())
.post(`/api/apps/${application.id}/versions`)
.set('Authorization', authHeaderForUser(adminUserData.user))
.send({
versionName: 'v3',
versionFromId: 'a77b051a-dd48-4633-a01f-089a845d5f88',
});
expect(response.statusCode).toBe(400);
expect(response.body.message).toBe('More than one version found. Version to create from not specified.');
});
it('creates new credentials and copies cipher text on data source', async () => {
@ -954,33 +985,54 @@ describe('apps controller', () => {
email: 'admin@tooljet.io',
});
const application = await importAppFromTemplates(app, adminUserData.user, 'customer-dashboard');
const dataSource = await getManager().findOne(DataSource, { where: { appId: application } });
const dataSource = await getManager().findOne(DataSource, {
where: { appId: application },
});
let dataSources = await getManager().find(DataSource);
let dataQueries = await getManager().find(DataQuery);
const credential = await getManager().findOne(Credential, {
where: { id: dataSource.options['password']['credential_id'] },
});
credential.valueCiphertext = 'strongPassword';
await getManager().save(credential);
const response = await request(app.getHttpServer())
let response = await request(app.getHttpServer())
.post(`/api/apps/${application.id}/versions`)
.set('Authorization', authHeaderForUser(adminUserData.user))
.send({
versionName: 'v1',
});
await request(app.getHttpServer())
expect(response.statusCode).toBe(400);
expect(response.body.message).toBe('More than one version found. Version to create from not specified.');
const initialVersion = await getManager().findOne(AppVersion, {
where: { appId: application.id, name: 'v0' },
});
response = await request(app.getHttpServer())
.post(`/api/apps/${application.id}/versions`)
.set('Authorization', authHeaderForUser(adminUserData.user))
.send({
versionName: 'v1',
versionFromId: initialVersion.id,
});
expect(response.statusCode).toBe(201);
response = await request(app.getHttpServer())
.post(`/api/apps/${application.id}/versions`)
.set('Authorization', authHeaderForUser(adminUserData.user))
.send({
versionName: 'v2',
versionFromId: response.body.id,
});
dataSources = await getManager().find(DataSource);
dataQueries = await getManager().find(DataQuery);
const dataSources = await getManager().find(DataSource);
const dataQueries = await getManager().find(DataQuery);
expect(dataSources).toHaveLength(2);
expect(dataQueries).toHaveLength(4);
expect(dataSources).toHaveLength(3);
expect(dataQueries).toHaveLength(6);
const credentials = await getManager().find(Credential);
expect([...new Set(credentials.map((c) => c.valueCiphertext))]).toEqual(['strongPassword']);