Merge pull request #8406 from ToolJet/merge-dev/main

Merge main to develop
This commit is contained in:
Midhun G S 2023-12-26 18:02:29 +05:30 committed by GitHub
commit 3b79373f93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
107 changed files with 38919 additions and 152180 deletions

View file

@ -38,10 +38,10 @@ jobs:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
- name: Use Node.js 18.3.0
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.3.0
node-version: 18.18.2
- name: Cache node modules
uses: actions/cache@v3
@ -68,10 +68,10 @@ jobs:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
- name: Use Node.js 18.3.0
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.3.0
node-version: 18.18.2
- name: Cache node modules
uses: actions/cache@v3
@ -93,7 +93,7 @@ jobs:
unit-test:
runs-on: ubuntu-latest
needs: build
container: node:18.3.0-buster
container: node:18.18.2-buster
services:
postgres:
image: postgres
@ -131,7 +131,7 @@ jobs:
e2e-test:
runs-on: ubuntu-latest
needs: build
container: node:18.3.0-buster
container: node:18.18.2-buster
services:
postgres:
image: postgres

View file

@ -20,7 +20,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 18.3.0
node-version: 18.18.2
- name: Set up Docker
uses: docker-practice/actions-setup-docker@master

View file

@ -20,7 +20,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 18.3.0
node-version: 18.18.2
- name: Set up Docker
uses: docker-practice/actions-setup-docker@master

2
.nvmrc
View file

@ -1 +1 @@
v18.3.0
v18.18.2

View file

@ -1 +1 @@
2.26.2
2.27.0

12470
cli/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -70,7 +70,7 @@ Cypress.Commands.add("apiCreateApp", (appName = "testApp") => {
url: "http://localhost:3000/api/apps",
headers: {
"Tj-Workspace-Id": Cypress.env("workspaceId"),
Cookie: `tj_auth_token=${cookie.value}`,
Cookie: `tj_auth_token = ${cookie.value}`,
},
body: {
created_at: "",
@ -212,3 +212,27 @@ Cypress.Commands.add("userInviteApi", (userName, userEmail) => {
});
});
});
Cypress.Commands.add("addQueryApi", (queryName, query, dataQueryId) => {
cy.getCookie("tj_auth_token").then((cookie) => {
const headers = {
"Tj-Workspace-Id": Cypress.env("workspaceId"),
Cookie: `tj_auth_token=${cookie.value}`,
};
cy.request({
method: "PATCH",
url: `http://localhost:3000/api/data_queries/${dataQueryId}`,
headers: headers,
body: {
name: queryName,
options: {
mode: "sql",
transformationLanguage: "javascript",
enableTransformation: false,
query: query,
},
},
}).then((patchResponse) => {
expect(patchResponse.status).to.equal(200);
});
});
});

View file

@ -7,7 +7,7 @@ export const s3Text = {
alertRegionIsMissing: "Region is missing",
cypressAwsS3: "cypress-aws-s3",
placeholderEnterAccessKey: "Enter access key",
placeholderEnterSecretKey: "Enter secret key",
placeholderEnterSecretKey: "**************",
labelRegion: "Region",
region: "N. california",
alertInvalidUrl: "Invalid URL",

View file

@ -3,5 +3,5 @@ export const bigqueryText = {
cypressBigQuery: "cypress-bigquery",
errorInvalidEmailId:
"The incoming JSON object does not contain a client_email field",
placehlderPrivateKey: "Enter JSON private key for service account",
placehlderPrivateKey: "**************",
};

View file

@ -5,7 +5,7 @@ export const dataSourceText = {
allDataSources: "All data sources (41)",
allDatabase: "Databases (17)",
allApis: "APIs (20)",
allCloudStorage: "Cloud Storage (4)",
allCloudStorage: "Cloud Storages (4)",
pluginsLabelAndCount: "Plugins (0)",
postgreSQL: "PostgreSQL",

View file

@ -5,7 +5,7 @@ export const dynamoDbText = {
accessKey: "Access key",
placeHolderAccessKey: "Enter access key",
secretKey: "Secret key",
placeholderSecretKey: "Enter secret key",
placeholderSecretKey: "**************",
errorMissingRegion: "Missing region in config",
errorInvalidToken: "The security token included in the request is invalid.",

View file

@ -3,7 +3,7 @@ export const firestoreText = {
cypressFirestore: "cypress-firestore",
labelPrivateKey: "Private key",
privateKey: "Private key",
placeholderPrivateKey: "Enter private key",
placeholderPrivateKey: "**************",
errorGcpKeyCouldNotBeParsed:
"GCP key could not be parsed as a valid JSON object",

View file

@ -5,7 +5,7 @@ export const postgreSqlText = {
allDataSources: "All data sources (43)",
allDatabase: "Databases (19)",
allApis: "APIs (20)",
allCloudStorage: "Cloud Storage (4)",
allCloudStorage: "Cloud Storages (4)",
postgreSQL: "PostgreSQL",
labelHost: "Host",

View file

@ -4,6 +4,6 @@ export const redisText = {
errorMaxRetries:
'Reached the max retries per request limit (which is 1). Refer to "maxRetriesPerRequest" option for details.',
errorPort: "Port should be >= 0 and < 65536. Received 108299.",
errorPort: "Port should be >= 0 and < 65536. Received type number (108299).",
errorInvalidUserOrPassword: "WRONGPASS invalid username-password pair",
};

View file

@ -10,7 +10,6 @@ import {
closeDSModal,
deleteDatasource,
addQuery,
addQueryN,
verifyValueOnInspector,
} from "Support/utils/dataSource";
import { dataSourceSelector } from "Selectors/dataSource";
@ -38,12 +37,8 @@ describe("Global Datasource Manager", () => {
cy.viewport(1200, 1300);
});
before(() => {
cy.apiLogin();
cy.apiCreateApp();
cy.openApp();
cy.renameApp(data.appName);
cy.dragAndDropWidget("Table", 250, 250);
cy.get(commonSelectors.editorPageLogo).click();
cy.defaultWorkspaceLogin();
cy.apiCreateApp(data.appName);
addNewUserMW(data.firstName, data.email);
logout();
});
@ -72,7 +67,7 @@ describe("Global Datasource Manager", () => {
);
cy.get(dataSourceSelector.querySearchBar)
.invoke("attr", "placeholder")
.should("eq", "Search Databases");
.should("eq", "Search data sources");
cy.get(dataSourceSelector.apiLabelAndCount)
.verifyVisibleElement("have.text", dataSourceText.allApis)
@ -81,20 +76,14 @@ describe("Global Datasource Manager", () => {
"have.text",
" APIs"
);
cy.get(dataSourceSelector.querySearchBar)
.invoke("attr", "placeholder")
.should("eq", "Search APIs");
cy.get(dataSourceSelector.cloudStorageLabelAndCount)
.verifyVisibleElement("have.text", dataSourceText.allCloudStorage)
.click();
cy.get(commonSelectors.breadcrumbPageTitle).verifyVisibleElement(
"have.text",
" Cloud Storage"
" Cloud Storages"
);
cy.get(dataSourceSelector.querySearchBar)
.invoke("attr", "placeholder")
.should("eq", "Search Cloud Storage");
cy.get(dataSourceSelector.pluginsLabelAndCount)
.verifyVisibleElement("have.text", dataSourceText.pluginsLabelAndCount)
@ -103,9 +92,6 @@ describe("Global Datasource Manager", () => {
"have.text",
" Plugins"
);
cy.get(dataSourceSelector.querySearchBar)
.invoke("attr", "placeholder")
.should("eq", "Search Plugins");
cy.get('[data-cy="added-ds-label"]').should(($el) => {
expect($el.contents().first().text().trim()).to.eq("Data sources added");
@ -178,6 +164,7 @@ describe("Global Datasource Manager", () => {
deleteDatasource(`cypress-${data.dsName1}-postgresql1`);
});
it("Should verify the Datasource connection and query creation using global data source", () => {
selectAndAddDataSource(
"databases",
@ -198,10 +185,7 @@ describe("Global Datasource Manager", () => {
);
cy.wait("@datasource");
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(commonSelectors.dashboardIcon).click();
navigateToAppEditor(data.appName);
cy.openApp();
pinInspector();
addQuery(
@ -223,24 +207,22 @@ describe("Global Datasource Manager", () => {
.should("be.visible")
.and("have.text", "+ Add new Data source");
cy.get(".p-2 > .tj-base-btn").click();
cy.get('[data-cy="databases-datasource-button"]').should("be.visible");
selectAndAddDataSource(
"databases",
dataSourceText.postgreSQL,
data.dsName2
cy.apiCreateGDS(
"http://localhost:3000/api/v2/data_sources",
`cypress-${data.dsName2}-postgresql`,
"postgresql",
[
{ key: "host", value: Cypress.env("pg_host") },
{ key: "port", value: 5432 },
{ key: "database", value: Cypress.env("pg_user") },
{ key: "username", value: Cypress.env("pg_user") },
{ key: "password", value: Cypress.env("pg_password"), encrypted: true },
{ key: "ssl_enabled", value: false, encrypted: false },
{ key: "ssl_certificate", value: "none", encrypted: false },
]
);
cy.intercept("GET", "api/v2/data_sources").as("datasource");
fillConnectionForm(
{
Host: Cypress.env("pg_host"),
Port: "5432",
"Database Name": Cypress.env("pg_user"),
Username: Cypress.env("pg_user"),
Password: Cypress.env("pg_password"),
},
".form-switch"
);
cy.wait("@datasource");
navigateToManageGroups();
cy.get(groupsSelector.appSearchBox).click();
@ -265,7 +247,7 @@ describe("Global Datasource Manager", () => {
it("Should validate the user's global data source permissions on apps created by admin", () => {
logout();
cy.apiLogin(data.email, "password");
cy.visit('/my-workspace')
cy.visit("/my-workspace");
cy.get(commonSelectors.globalDataSourceIcon).should("not.exist");
@ -281,8 +263,7 @@ describe("Global Datasource Manager", () => {
cy.get(dataSourceSelector.queryCreateAndRunButton).click();
verifyValueOnInspector("table_preview", "7 items ");
cy.get('[data-cy="show-ds-popover-button"]').click();
addQueryN(
addQuery(
"student_data",
`SELECT * FROM student_data;`,
`cypress-${data.dsName2}-postgresql`
@ -293,16 +274,14 @@ describe("Global Datasource Manager", () => {
"student_data "
);
cy.get(dataSourceSelector.queryCreateAndRunButton).click();
verifyValueOnInspector("student_data", "8 items ");
verifyValueOnInspector("student_data", "4 items ");
});
it("Should verify the query creation and scope changing functionality.", () => {
data.appName = `${fake.companyName}-App`;
logout();
cy.apiLogin(data.email, "password");
cy.visit('/my-workspace')
cy.apiCreateApp(data.appName);
cy.openApp();
cy.dragAndDropWidget("Table", 250, 250);
addQuery(
"table_preview",

View file

@ -96,7 +96,7 @@ describe("Data sources", () => {
);
fillDataSourceTextField(
"Key",
"Enter your key",
"**************",
Cypress.env("cosmosdb_key")
);

View file

@ -133,7 +133,7 @@ describe("Data source Elasticsearch", () => {
fillDataSourceTextField(
postgreSqlText.labelPassword,
"Enter password",
"**************",
Cypress.env("elasticsearch_password")
);
@ -163,7 +163,7 @@ describe("Data source Elasticsearch", () => {
fillDataSourceTextField(
postgreSqlText.labelPassword,
"Enter password",
"**************",
"elasticsearch_password"
);
@ -173,7 +173,7 @@ describe("Data source Elasticsearch", () => {
);
fillDataSourceTextField(
postgreSqlText.labelPassword,
"Enter password",
"**************",
Cypress.env("elasticsearch_password")
);

View file

@ -136,7 +136,7 @@ describe("Data sources", () => {
);
fillDataSourceTextField(
postgreSqlText.labelPassword,
"Enter password",
"**************",
Cypress.env("mariadb_password")
);

View file

@ -129,7 +129,7 @@ describe("Data sources MySql", () => {
);
fillDataSourceTextField(
postgreSqlText.labelPassword,
"Enter password",
"**************",
Cypress.env("mysql_password")
);
@ -169,7 +169,7 @@ describe("Data sources MySql", () => {
fillDataSourceTextField(
postgreSqlText.labelPassword,
"Enter password",
"**************",
Cypress.env("mysql_password")
);

View file

@ -143,7 +143,7 @@ describe("Data sources", () => {
);
fillDataSourceTextField(
postgreSqlText.labelPassword,
"Enter password",
"**************",
Cypress.env("pg_password")
);

View file

@ -123,7 +123,7 @@ describe("Data source Redis", () => {
fillDataSourceTextField(
postgreSqlText.labelPassword,
"Enter password",
"**************",
Cypress.env("redis_password")
);
@ -156,7 +156,7 @@ describe("Data source Redis", () => {
fillDataSourceTextField(
postgreSqlText.labelPassword,
"Enter password",
"**************",
"redis_password"
);
@ -168,7 +168,7 @@ describe("Data source Redis", () => {
fillDataSourceTextField(
postgreSqlText.labelPassword,
"Enter password",
"**************",
Cypress.env("redis_password")
);

View file

@ -109,7 +109,7 @@ describe("Data source SMTP", () => {
fillDataSourceTextField(
postgreSqlText.labelPassword,
"Enter password",
"**************",
Cypress.env("smtp_password")
);

View file

@ -103,7 +103,7 @@ describe("Data sources", () => {
);
cy.get('[data-cy="connection-alert-text"]').should(
"have.text",
"Network error. Could not reach Snowflake."
"Invalid account. The specified value must be a valid subdomain string."
);
deleteDatasource(`cypress-${data.lastName}-snowflake`);
});
@ -124,7 +124,7 @@ describe("Data sources", () => {
);
fillDataSourceTextField(
"Password",
"Enter password",
"**************",
Cypress.env("snowflake_password")
);
fillDataSourceTextField(

View file

@ -141,7 +141,7 @@ describe("Data sources", () => {
fillDataSourceTextField(
postgreSqlText.labelPassword,
"Enter password",
"**************",
Cypress.env("sqlserver_password")
);

View file

@ -100,17 +100,20 @@ describe("Redirection error pages", () => {
cy.visit(`http://localhost:8082/applications/${data.slug}`);
cy.get(commonSelectors.modalHeader).verifyVisibleElement(
"have.text",
"URL unavailable"
"App URL Unavailable"
);
cy.get(commonSelectors.modalDescription).verifyVisibleElement(
"have.text",
"This URL is not accessible because it has not been released yet. Please either release it or contact admin for access."
'The app URL is currently unavailable because the app has not been released. Please either release it or contact admin for access.'
);
cy.get('[data-cy="open-app-button"]').verifyVisibleElement("have.text", "Open app")
cy.get(commonSelectors.backToHomeButton).verifyVisibleElement(
"have.text",
"Back to home page"
);
cy.url().should("eq", "http://localhost:8082/error/url-unavailable");
cy.url().should("eq", `http://localhost:8082/error/url-unavailable?appSlug=${data.slug}`);
cy.get(commonSelectors.backToHomeButton).click();
cy.get(commonSelectors.pageSectionHeader).should("be.visible");
@ -127,6 +130,8 @@ describe("Redirection error pages", () => {
"have.text",
"You dont have access to this app. Kindly contact admin to know more."
);
// cy.get('[data-cy="open-app-button"]').verifyVisibleElement("have.text", "Open app")
cy.get(commonSelectors.backToHomeButton).verifyVisibleElement(
"have.text",
"Back to home page"
@ -157,17 +162,17 @@ describe("Redirection error pages", () => {
cy.visit(`http://localhost:8082/applications/${data.slug}`);
cy.get(commonSelectors.modalHeader).verifyVisibleElement(
"have.text",
"URL unavailable"
"App URL Unavailable"
);
cy.get(commonSelectors.modalDescription).verifyVisibleElement(
"have.text",
"This URL is not accessible because it has not been released yet. Please either release it or contact admin for access."
'The app URL is currently unavailable because the app has not been released. Please either release it or contact admin for access.'
);
cy.get(commonSelectors.backToHomeButton).verifyVisibleElement(
"have.text",
"Back to home page"
);
cy.url().should("eq", "http://localhost:8082/error/url-unavailable");
cy.url().should("eq", `http://localhost:8082/error/url-unavailable?appSlug=${data.slug}`);
cy.get(commonSelectors.backToHomeButton).click();
cy.get(commonSelectors.pageSectionHeader).should("be.visible");
});

View file

@ -28,7 +28,7 @@ export const deleteComponentAndVerify = (widgetName) => {
});
cy.verifyToastMessage(
`[class=go3958317564]`,
"Component deleted! ( + Z to undo)"
"Component deleted! (ctrl + Z to undo)"
);
cy.notVisible(commonWidgetSelector.draggableWidget(widgetName));
};

View file

@ -72,33 +72,44 @@ export const closeDSModal = () => {
});
};
export const addQuery = (queryName, query, dbName) => {
export const addQueryN = (queryName, query, dbName) => {
cy.get("body").then(($body) => {
if ($body.find('[data-cy="gds-querymanager-search-bar"]').length > 0) {
cy.clearAndType('[data-cy="gds-querymanager-search-bar"]', `${dbName}`);
}
});
cy.intercept("POST", "http://localhost:3000/api/data_queries").as(
"createQuery"
);
cy.get(`[data-cy="${dbName}-add-query-card"] > .text-truncate`).click();
cy.get('[data-cy="query-rename-input"]').clear().type(queryName);
cy.forceClickOnCanvas();
cy.get(dataSourceSelector.queryInputField)
.realMouseDown({ position: "center" })
.realType(" ");
cy.get(dataSourceSelector.queryInputField).clearAndTypeOnCodeMirror(query);
cy.get(dataSourceSelector.queryCreateAndRunButton).click();
cy.wait("@createQuery").then((interception) => {
const dataQueryId = interception.response.body.id;
cy.visit("/my-workspace");
cy.addQueryApi(queryName, query, dataQueryId);
cy.openApp();
});
};
export const addQueryN = (queryName, query, dbName) => {
export const addQuery = (queryName, query, dbName) => {
cy.get('[data-cy="show-ds-popover-button"]').click();
cy.get(".css-1rrkggf-Input").type(`${dbName}`);
cy.intercept("POST", "http://localhost:3000/api/data_queries").as(
"createQuery"
);
cy.contains(`[id*="react-select-"]`, dbName).click();
cy.get('[data-cy="query-rename-input"]').clear().type(queryName);
cy.get(dataSourceSelector.queryInputField)
.realMouseDown({ position: "center" })
.realType(" ");
cy.get(dataSourceSelector.queryInputField).clearAndTypeOnCodeMirror(query);
cy.get(dataSourceSelector.queryCreateAndRunButton).click();
cy.wait("@createQuery").then((interception) => {
const dataQueryId = interception.response.body.id;
cy.visit("/my-workspace");
cy.addQueryApi(queryName, query, dataQueryId);
cy.openApp();
});
};
export const verifyValueOnInspector = (queryName, value) => {
@ -119,4 +130,4 @@ export const verifyValueOnInspector = (queryName, value) => {
);
}
});
};
};

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,7 @@ sudo apt-get -y install --no-install-recommends wget gnupg ca-certificates apt-u
curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
nvm install 18.3.0
nvm install 18.18.2
sudo ln -s "$(which node)" /usr/bin/node
sudo ln -s "$(which npm)" /usr/bin/npm
@ -74,7 +74,7 @@ mv /tmp/.env ~/app/.env
mv /tmp/setup_app ~/app/setup_app
sudo chmod +x ~/app/setup_app
npm install -g npm@8.11.0
npm install -g npm@9.8.1
# Building ToolJet app
npm install -g @nestjs/cli

View file

@ -1,9 +1,9 @@
# pull official base image
FROM node:18.3.0-buster
FROM node:18.18.2-buster
ENV NODE_ENV=development
RUN npm i -g npm@8.11.0
RUN npm i -g npm@9.8.1
# set working directory
WORKDIR /app

View file

@ -1,7 +1,7 @@
# pull official base image
FROM node:18.3.0-buster
FROM node:18.18.2-buster
RUN npm i -g npm@8.11.0
RUN npm i -g npm@9.8.1
# set working directory
WORKDIR /app

View file

@ -1,4 +1,4 @@
FROM node:18.3.0-buster AS builder
FROM node:18.18.2-buster AS builder
# Fix for JS heap limit allocation issue
ENV NODE_OPTIONS="--max-old-space-size=4096"
@ -32,7 +32,7 @@ COPY ./server/ ./server/
RUN npm install -g @nestjs/cli
RUN npm --prefix server run build
FROM node:18.3.0-buster
FROM node:18.18.2-buster
# copy postgrest executable
COPY --from=postgrest/postgrest:v10.1.1.20221215 /bin/postgrest /bin

View file

@ -1,9 +1,9 @@
FROM node:18.3.0-buster AS builder
FROM node:18.18.2-buster AS builder
# Fix for JS heap limit allocation issue
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN npm i -g npm@8.11.0
RUN npm i -g npm@9.8.1
RUN mkdir -p /app
WORKDIR /app
@ -42,12 +42,12 @@ RUN apt-get update -yq \
&& apt-get clean -y
RUN curl -O https://nodejs.org/dist/v18.3.0/node-v18.3.0-linux-x64.tar.xz \
&& tar -xf node-v18.3.0-linux-x64.tar.xz \
&& mv node-v18.3.0-linux-x64 /usr/local/lib/nodejs \
RUN curl -O https://nodejs.org/dist/v18.18.2/node-v18.18.2-linux-x64.tar.xz \
&& tar -xf node-v18.18.2-linux-x64.tar.xz \
&& mv node-v18.18.2-linux-x64 /usr/local/lib/nodejs \
&& echo 'export PATH="/usr/local/lib/nodejs/bin:$PATH"' >> /etc/profile.d/nodejs.sh \
&& /bin/bash -c "source /etc/profile.d/nodejs.sh" \
&& rm node-v18.3.0-linux-x64.tar.xz
&& rm node-v18.18.2-linux-x64.tar.xz
ENV PATH=/usr/local/lib/nodejs/bin:$PATH
ENV NODE_ENV=production

View file

@ -1,9 +1,9 @@
FROM node:18.3.0-buster as builder
FROM node:18.18.2-buster as builder
# Fix for JS heap limit allocation issue
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN npm i -g npm@8.11.0
RUN npm i -g npm@9.8.1
RUN npm install -g @nestjs/cli
RUN mkdir -p /app
@ -32,12 +32,12 @@ RUN apt-get update -yq \
&& apt-get install -yq build-essential \
&& apt-get clean -y
RUN curl -O https://nodejs.org/dist/v18.3.0/node-v18.3.0-linux-x64.tar.xz \
&& tar -xf node-v18.3.0-linux-x64.tar.xz \
&& mv node-v18.3.0-linux-x64 /usr/local/lib/nodejs \
RUN curl -O https://nodejs.org/dist/v18.18.2/node-v18.18.2-linux-x64.tar.xz \
&& tar -xf node-v18.18.2-linux-x64.tar.xz \
&& mv node-v18.18.2-linux-x64 /usr/local/lib/nodejs \
&& echo 'export PATH="/usr/local/lib/nodejs/bin:$PATH"' >> /etc/profile.d/nodejs.sh \
&& /bin/bash -c "source /etc/profile.d/nodejs.sh" \
&& rm node-v18.3.0-linux-x64.tar.xz
&& rm node-v18.18.2-linux-x64.tar.xz
ENV PATH=/usr/local/lib/nodejs/bin:$PATH
ENV NODE_ENV=production

View file

@ -1,5 +1,5 @@
# pull official base image
FROM node:18.3.0-buster
FROM node:18.18.2-buster
RUN apt-get update && apt-get install -y postgresql-client freetds-dev libaio1 wget
# Install Instantclient Basic Light Oracle and Dependencies
@ -19,7 +19,7 @@ WORKDIR /
ENV NODE_ENV=development
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN npm i -g npm@8.11.0
RUN npm i -g npm@9.8.1
RUN mkdir -p /app
WORKDIR /app

View file

@ -62,7 +62,7 @@ module.exports = {
'import/no-unresolved': [
'error',
{
ignore: ['^@/', 'react-hot-toast', 'react-i18next'],
ignore: ['^@/', 'react-hot-toast', 'react-i18next', 'react-loading-skeleton', 'react-spring'],
},
],
'react/no-unknown-property': 'off',

View file

@ -1 +1 @@
2.26.2
2.27.0

82863
frontend/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -31,6 +31,7 @@
"draft-js-export-html": "^1.4.1",
"driver.js": "^0.9.8",
"emoji-mart": "^5.5.2",
"file-loader": "^6.2.0",
"focus-trap-react": "^10.0.2",
"fuse.js": "^6.6.2",
"html-loader": "^4.2.0",
@ -97,6 +98,7 @@
"react-zoom-pan-pinch": "^2.6.1",
"rxjs": "^7.8.0",
"semver": "^7.3.8",
"string-hash": "^1.1.3",
"superstruct": "^1.0.3",
"tinycolor2": "^1.6.0",
"url-join": "^5.0.0",

View file

@ -191,7 +191,8 @@ class DataSourceManagerComponent extends React.Component {
};
createDataSource = () => {
const { appId, options, selectedDataSource, selectedDataSourcePluginId } = this.state;
const { appId, options, selectedDataSource, selectedDataSourcePluginId, dataSourceMeta, dataSourceSchema } =
this.state;
const name = selectedDataSource.name;
const kind = selectedDataSource.kind;
const pluginId = selectedDataSourcePluginId;
@ -200,7 +201,7 @@ class DataSourceManagerComponent extends React.Component {
const scope = this.state?.scope || selectedDataSource?.scope;
const parsedOptions = Object?.keys(options)?.map((key) => {
const keyMeta = selectedDataSource.options[key];
const keyMeta = dataSourceMeta.options[key];
return {
key: key,
value: options[key].value,

View file

@ -62,10 +62,12 @@ import { EMPTY_ARRAY, useEditorActions, useEditorStore } from '@/_stores/editorS
import { useAppDataActions, useAppInfo, useAppDataStore } from '@/_stores/appDataStore';
import { useMounted } from '@/_hooks/use-mount';
import EditorSelecto from './EditorSelecto';
import { useSocketOpen } from '@/_hooks/use-socket-open';
// eslint-disable-next-line import/no-unresolved
import { diff } from 'deep-object-diff';
import useDebouncedArrowKeyPress from '@/_hooks/useDebouncedArrowKeyPress';
import { getQueryParams } from '@/_helpers/routes';
import RightSidebarTabManager from './RightSidebarTabManager';
import { shallow } from 'zustand/shallow';
@ -80,6 +82,7 @@ const decimalToHex = (alpha) => (alpha === 0 ? '00' : Math.round(255 * alpha).to
const EditorComponent = (props) => {
const { socket } = createWebsocketConnection(props?.params?.id);
const isSocketOpen = useSocketOpen(socket);
const mounted = useMounted();
const {
@ -338,7 +341,14 @@ const EditorComponent = (props) => {
const fetchApps = async (page) => {
const { apps } = await appService.getAll(page);
updateState({ apps: apps.map((app) => ({ id: app.id, name: app.name, slug: app.slug })) });
updateState({
apps: apps.map((app) => ({
id: app.id,
name: app.name,
slug: app.slug,
current_version_id: app.current_version_id,
})),
});
};
const fetchOrgEnvironmentVariables = () => {
@ -613,12 +623,14 @@ const EditorComponent = (props) => {
const onVersionRelease = (versionId) => {
useAppVersionStore.getState().actions.updateReleasedVersionId(versionId);
socket.send(
JSON.stringify({
event: 'events',
data: { message: 'versionReleased', appId: appId },
})
);
if (socket instanceof WebSocket && socket?.readyState === WebSocket.OPEN) {
socket.send(
JSON.stringify({
event: 'events',
data: { message: 'versionReleased', appId: appId },
})
);
}
};
const computeCanvasBackgroundColor = () => {
@ -1367,11 +1379,21 @@ const EditorComponent = (props) => {
useCurrentStateStore.getState().actions.setCurrentState({ globals, page });
};
const navigateToPage = (queryParams = [], handle) => {
const appId = useAppDataStore.getState()?.appId;
const queryParamsString = queryParams.map(([key, value]) => `${key}=${value}`).join('&');
props?.navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${handle}?${queryParamsString}`, {
state: {
isSwitchingPage: true,
},
});
};
const switchPage = (pageId, queryParams = []) => {
// This are fetched from store to handle runQueriesOnAppLoad
const currentPageId = useEditorStore.getState().currentPageId;
const appDefinition = useEditorStore.getState().appDefinition;
const appId = useAppDataStore.getState()?.appId;
const pageHandle = getCurrentState().pageHandle;
if (currentPageId === pageId && pageHandle === appDefinition?.pages[pageId]?.handle) {
@ -1381,13 +1403,7 @@ const EditorComponent = (props) => {
if (!name || !handle) return;
const copyOfAppDefinition = JSON.parse(JSON.stringify(appDefinition));
const queryParamsString = queryParams.map(([key, value]) => `${key}=${value}`).join('&');
props?.navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${handle}?${queryParamsString}`, {
state: {
isSwitchingPage: true,
},
});
navigateToPage(queryParams, handle);
const page = {
id: pageId,
@ -1396,6 +1412,7 @@ const EditorComponent = (props) => {
variables: copyOfAppDefinition.pages[pageId]?.variables ?? {},
};
const queryParamsString = queryParams.map(([key, value]) => `${key}=${value}`).join('&');
const globals = {
...currentState.globals,
urlparams: JSON.parse(JSON.stringify(queryString.parse(queryParamsString))),
@ -1590,6 +1607,9 @@ const EditorComponent = (props) => {
appDefinitionChanged(newDefinition, {
pageDefinitionChanged: true,
});
const queryParams = getQueryParams();
navigateToPage(Object.entries(queryParams), newHandle);
};
const updateOnSortingPages = (newSortedPages) => {
@ -1698,6 +1718,7 @@ const EditorComponent = (props) => {
appName={appName}
appId={appId}
slug={slug}
isSocketOpen={isSocketOpen}
/>
<DndProvider backend={HTML5Backend}>
<div className="sub-section">

View file

@ -34,6 +34,7 @@ export default function EditorHeader({
onVersionDelete,
slug,
darkMode,
isSocketOpen,
}) {
const currentUser = useCurrentUser();
@ -193,14 +194,16 @@ export default function EditorHeader({
</div>
</div>
<div className="nav-item dropdown promote-release-btn">
<ReleaseVersionButton
appId={appId}
appName={appName}
onVersionRelease={onVersionRelease}
saveEditingVersion={saveEditingVersion}
/>
</div>
{isSocketOpen && (
<div className="nav-item dropdown promote-release-btn">
<ReleaseVersionButton
appId={appId}
appName={appName}
onVersionRelease={onVersionRelease}
saveEditingVersion={saveEditingVersion}
/>
</div>
)}
</div>
</div>
</div>

View file

@ -214,7 +214,7 @@ export const EventManager = ({
function getAllApps() {
let appsOptionsList = [];
apps
.filter((item) => item.slug !== undefined && item.id !== appId)
.filter((item) => item.slug !== undefined && item.id !== appId && item.current_version_id)
.forEach((item) => {
appsOptionsList.push({
name: item.name,

View file

@ -296,7 +296,7 @@ class ViewerComponent extends React.Component {
redirectToErrorPage(ERROR_TYPES.INVALID);
} else if (error?.statusCode === 403) {
redirectToErrorPage(ERROR_TYPES.RESTRICTED);
} else {
} else if (error?.statusCode !== 401) {
redirectToErrorPage(ERROR_TYPES.UNKNOWN);
}
});

View file

@ -1,13 +1,14 @@
import React, { useContext, useRef, useState, useEffect } from 'react';
import cx from 'classnames';
import toast from 'react-hot-toast';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { isEmpty } from 'lodash';
import { Sidebar } from '../Sidebar';
import { GlobalDataSourcesContext } from '..';
import { DataSourceManager } from '@/Editor/DataSourceManager';
import { DataBaseSources, ApiSources, CloudStorageSources } from '@/Editor/DataSourceManager/SourceComponents';
import { pluginsService, globalDatasourceService } from '@/_services';
import { pluginsService, globalDatasourceService, authenticationService } from '@/_services';
import { Card } from '@/_ui/Card';
import { SegregatedList } from '../SegregatedList';
import { SearchBox } from '@/_components';
@ -21,7 +22,11 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
const [filteredDataSources, setFilteredDataSources] = useState([]);
const [queryString, setQueryString] = useState('');
const [addingDataSource, setAddingDataSource] = useState(false);
const [suggestingDataSource, setSuggestingDataSource] = useState(false);
const { t } = useTranslation();
const navigate = useNavigate();
const { admin } = authenticationService.currentSessionValue;
const marketplaceEnabled = admin && window.public_config?.ENABLE_MARKETPLACE_FEATURE == 'true';
const [modalProps, setModalProps] = useState({
backdrop: false,
dialogClassName: `datasource-edit-modal`,
@ -98,15 +103,22 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
const searchQuery = e.target.value;
setQueryString(searchQuery);
const arr = [];
const filteredDatasources = datasourcesGroups().filter((group) => group.key === activeDatasourceList)[0].list;
let arr = [];
filteredDatasources.forEach((datasource) => {
if (datasource.name.toLowerCase().includes(searchQuery.toLowerCase())) {
arr.push(datasource);
}
const filtered = datasourcesGroups().map((datasourceGroup) => {
datasourceGroup.list.map((dataSource) => {
if (dataSource.name.toLowerCase().includes(searchQuery.toLowerCase())) {
arr.push({ ...dataSource, type: datasourceGroup.type });
}
});
datasourceGroup.list = [...arr];
(datasourceGroup.renderDatasources = () => renderCardGroup(datasourceGroup.list, datasourceGroup.type)),
(arr = []);
return datasourceGroup;
});
setFilteredDataSources([...arr]);
const filteredDsList = filtered.reduce((acc, filteredGroup) => [...acc, ...filteredGroup.list], []);
filteredDsList.length >= 1 ? setSuggestingDataSource(false) : setSuggestingDataSource(true);
setFilteredDataSources([...filtered]);
};
const createDataSource = (dataSource) => {
@ -165,8 +177,7 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
};
const segregateDataSources = () => {
const datasources = datasourcesGroups();
const searchPlaceholder = datasources.filter((ds) => ds.key === activeDatasourceList)[0];
const datasources = queryString && queryString.length > 0 ? filteredDataSources : datasourcesGroups();
return (
<div className="datasource-list-container">
@ -176,21 +187,40 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
dataCy={`home-page`}
className="border-0 homepage-search"
darkMode={darkMode}
placeholder={`Search ${searchPlaceholder?.type || 'datasources'}`}
placeholder={`Search data sources`}
initialValue={queryString}
width={'100%'}
callBack={handleSearch}
onClearCallback={() => setQueryString('')}
onClearCallback={() => {
setQueryString('');
setSuggestingDataSource(false);
}}
/>
<div className="liner mb-4"></div>
</div>
{datasources
.filter((ds) => ds.key === activeDatasourceList)
.map((dataSource) => {
{suggestingDataSource ? (
<center className="empty-ds-container">
<div>
<p className="mt-2 tj-text-lg font-weight-500 tj-text">{`No results for "${queryString}"`}</p>
</div>
<img src="assets/images/icons/no-results.svg" width="200" height="200" />
</center>
) : (
datasources.map((dataSource) => {
{
return dataSource.renderDatasources();
return (
(dataSource.list.length > 0 || (!queryString && dataSource.type === 'Plugins')) && (
<>
<div id={dataSource.key} className="tj-text-md font-weight-500 tj-text">
{dataSource.type}
</div>
{dataSource.renderDatasources()}
</>
)
);
}
})}
})
)}
</div>
</div>
);
@ -200,10 +230,17 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
const dataSourceList = datasourcesGroups().splice(0, 5);
const handleOnSelect = (activekey, type) => {
setQueryString('');
setSuggestingDataSource(false);
toggleDataSourceManagerModal(false);
setActiveDatasourceList(activekey);
updateSidebarNAV(type);
setSelectedDataSource(null);
setTimeout(() => {
const element = document.getElementById(activekey);
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
}
}, 100);
};
return (
<div>
@ -216,7 +253,35 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
);
};
const renderCardGroup = (source) => {
const renderCardGroup = (source, type) => {
if (type === 'Plugins' && source.length === 0) {
return (
<div className="add-plugins-container">
<div className="warning-container mb-2">
<SolidIcon name="warning" />
</div>
<div className="tj-text-sm font-weight-500 tj-text">No plugins added</div>
{admin && (
<>
<div className="tj-text-xsm font-weight-400 mt-2 mb-3">
Browse through plugins in marketplace to add them as a Data Source.{' '}
</div>
<ButtonSolid
onClick={() => {
marketplaceEnabled
? navigate('/integrations')
: toast.error('Please enable marketplace to add plugins');
}}
style={{ margin: 'auto' }}
variant="secondary"
>
Add plugins
</ButtonSolid>
</>
)}
</div>
);
}
const addDataSourceBtn = (item) => (
<ButtonSolid
disabled={addingDataSource}
@ -230,39 +295,6 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
</ButtonSolid>
);
if (queryString && queryString.length > 0) {
const filteredDatasources = filteredDataSources?.map((datasource) => {
const src = datasource?.iconFile?.data
? `data:image/svg+xml;base64,${datasource.iconFile?.data}`
: datasource.kind.toLowerCase();
return {
...datasource,
src,
title: datasource.name,
};
});
return (
<>
<div className="row row-deck mt-4 ">
{filteredDatasources?.map((item) => (
<Card
key={item.key}
title={item.title}
src={item.src}
usePluginIcon={isEmpty(item?.iconFile?.data)}
height="35px"
width="35px"
actionButton={addDataSourceBtn(item)}
className="datasource-card"
titleClassName={'datasource-card-title'}
/>
))}
</div>
</>
);
}
const datasources = source.map((datasource) => {
const src = datasource?.iconFile?.data
? `data:image/svg+xml;base64,${datasource.iconFile?.data}`
@ -277,7 +309,7 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
return (
<>
<div className="row row-deck mt-4">
<div className="row row-deck mt-3">
{datasources.map((item) => (
<Card
key={item.key}
@ -305,17 +337,6 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
filteredDatasources: filteredDataSources,
};
const dataSourceList = [
{
type: 'All Datasources',
key: '#alldatasources',
list: [
...allDataSourcesList.databases,
...allDataSourcesList.apis,
...allDataSourcesList.cloudStorages,
...allDataSourcesList.plugins,
],
renderDatasources: () => renderCardGroup(allDataSourcesList, 'All Datasources'),
},
{
type: 'Databases',
key: '#databases',
@ -329,7 +350,7 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
renderDatasources: () => renderCardGroup(allDataSourcesList.apis, 'APIs'),
},
{
type: 'Cloud Storage',
type: 'Cloud Storages',
key: '#cloudstorage',
list: allDataSourcesList.cloudStorages,
renderDatasources: () => renderCardGroup(allDataSourcesList.cloudStorages, 'Cloud Storages'),
@ -340,12 +361,6 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
list: allDataSourcesList.plugins,
renderDatasources: () => renderCardGroup(allDataSourcesList.plugins, 'Plugins'),
},
{
type: 'Filtered Datasources',
key: '#filtereddatasources',
list: allDataSourcesList.filteredDatasources,
renderDatasources: () => renderCardGroup(filteredDataSources, activeDatasourceList),
},
];
return dataSourceList;

View file

@ -4,33 +4,36 @@ import useGlobalDatasourceUnsavedChanges from '@/_hooks/useGlobalDatasourceUnsav
export const SegregatedList = ({ dataSources, activeDatasourceList, handleOnSelect }) => {
const { handleActions } = useGlobalDatasourceUnsavedChanges();
const totalDataSources = dataSources.reduce((acc, filteredGroup) => [...acc, ...filteredGroup.list], []).length;
return (
<>
<div className="datasources-info tj-text-xsm datasource-list-header" data-cy="datasource-list-header">
All data sources {dataSources[0].list.length > 0 && `(${dataSources[0].list.length})`}
All data sources {totalDataSources > 0 && `(${totalDataSources})`}
</div>
{dataSources.slice(1, 5).map((dataSource, index) => (
<div
key={index}
className={cx('mx-3 rounded-3 datasources-list', {
'datasources-list-item': activeDatasourceList === dataSource.key,
})}
>
{dataSources
.filter((ds) => ds.list.length > 0 || ds.type === 'Plugins')
.map((dataSource, index) => (
<div
role="button"
onClick={() => handleActions(() => handleOnSelect(dataSource.key, dataSource.type))}
className="col d-flex align-items-center overflow-hidden"
data-cy={`${dataSource.key
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/[^a-zA-Z0-9-]/g, '')}-datasource-button`}
key={index}
className={cx('mx-3 rounded-3 datasources-list', {
'datasources-list-item': activeDatasourceList === dataSource.key,
})}
>
<div className="font-400 tj-text-xsm text-truncate" style={{ paddingLeft: '6px' }}>
{`${dataSource.type} (${dataSource.list.length})`}
<div
role="button"
onClick={() => handleActions(() => handleOnSelect(dataSource.key, dataSource.type))}
className="col d-flex align-items-center overflow-hidden"
data-cy={`${dataSource.key
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/[^a-zA-Z0-9-]/g, '')}-datasource-button`}
>
<div className="font-400 tj-text-xsm text-truncate" style={{ paddingLeft: '6px' }}>
{`${dataSource.type} (${dataSource.list.length})`}
</div>
</div>
</div>
</div>
))}
))}
</>
);
};

View file

@ -56,10 +56,22 @@ export const GlobalDatasources = (props) => {
globalDatasourceService
.getAll()
.then((data) => {
const orderedDataSources = data.data_sources.sort((a, b) => a.name.localeCompare(b.name));
const orderedDataSources = data.data_sources
.map((ds) => {
if (ds.options && ds.options.connection_limit) {
return {
...ds,
options: {
...ds.options,
connectionLimit: ds.options.connection_limit,
},
};
}
return ds;
})
.sort((a, b) => a.name.localeCompare(b.name));
setDataSources([...(orderedDataSources ?? [])]);
const ds = dataSource && orderedDataSources.find((ds) => ds.id === dataSource.id);
if (!resetSelection && ds) {
setEditing(true);
setSelectedDataSource(ds);

View file

@ -156,7 +156,7 @@ class HomePageComponent extends React.Component {
_self.setState({ renamingApp: true });
try {
await appsService.saveApp(appId, { name: newAppName });
await this.fetchApps();
await this.fetchApps(this.state.currentPage, this.state.currentFolder.id);
toast.success('App name has been updated!');
_self.setState({ renamingApp: false });
return true;

View file

@ -139,9 +139,11 @@ class LoginPageComponent extends React.Component {
setRedirectUrlToCookie() {
// Page is loaded inside an iframe
const iframe = window !== window.top;
const redirectPath = getRedirectTo(
iframe ? new URL(window.location.href).searchParams : new URL(location.href).searchParams
);
if (iframe) {
const redirectPath = getRedirectTo();
window.parent.postMessage(
{
type: 'redirectTo',
@ -153,9 +155,6 @@ class LoginPageComponent extends React.Component {
);
}
const params = iframe ? new URL(window.location.href).searchParams : new URL(location.href).searchParams;
const redirectPath = params.get('redirectTo');
authenticationService.saveLoginOrganizationId(this.organizationId);
authenticationService.saveLoginOrganizationSlug(this.organizationSlug);
redirectPath && setCookie('redirectPath', redirectPath, iframe);

View file

@ -108,10 +108,10 @@ const DynamicForm = ({
Object.keys(fields).length > 0 &&
Object.keys(fields).map((key) => {
const { type, encrypted } = fields[key];
if ((type === 'password' || encrypted) && !(key in computedProps)) {
const { type, encrypted, key: propertyKey } = fields[key];
if ((type === 'password' || encrypted) && !(propertyKey in computedProps)) {
//Editable encrypted fields only if datasource doesn't exists
encrpytedFieldsProps[key] = {
encrpytedFieldsProps[propertyKey] = {
disabled: !!selectedDataSource?.id,
};
}
@ -182,6 +182,7 @@ const DynamicForm = ({
ignoreBraces = false,
className,
controller,
encrypted,
}) => {
const source = schema?.source?.kind;
const darkMode = localStorage.getItem('darkMode') === 'true';
@ -191,12 +192,14 @@ const DynamicForm = ({
switch (type) {
case 'password':
case 'text':
case 'textarea':
case 'textarea': {
const useEncrypted =
(options?.[key]?.encrypted !== undefined ? options?.[key].encrypted : encrypted) || type === 'password';
return {
type,
placeholder: options?.[key]?.encrypted ? '**************' : description,
placeholder: useEncrypted ? '**************' : description,
className: `form-control${handleToggle(controller)}`,
value: options?.[key]?.value,
value: options?.[key]?.value || '',
...(type === 'textarea' && { rows: rows }),
...(helpText && { helpText }),
onChange: (e) => optionchanged(key, e.target.value, true), //shouldNotAutoSave is true because autosave should occur during onBlur, not after each character change (in optionchanged).
@ -204,7 +207,9 @@ const DynamicForm = ({
isGDS,
workspaceVariables,
workspaceConstants: currentOrgEnvironmentConstants,
encrypted: useEncrypted,
};
}
case 'toggle':
return {
defaultChecked: options?.[key],
@ -221,6 +226,7 @@ const DynamicForm = ({
useMenuPortal: queryName ? true : false,
styles: computeSelectStyles ? computeSelectStyles('100%') : {},
useCustomStyles: computeSelectStyles ? true : false,
encrypted: options?.[key]?.encrypted,
};
case 'checkbox-group':
@ -248,6 +254,7 @@ const DynamicForm = ({
currentState,
isRenderedAsQueryEditor,
workspaceConstants: currentOrgEnvironmentConstants,
encrypted: options?.[key]?.encrypted,
};
}
case 'react-component-oauth-authentication':
@ -275,6 +282,9 @@ const DynamicForm = ({
multiple_auth_enabled: options?.multiple_auth_enabled?.value,
optionchanged,
workspaceConstants: currentOrgEnvironmentConstants,
options,
optionsChanged,
selectedDataSource,
};
case 'react-component-google-sheets':
case 'react-component-slack':
@ -286,6 +296,7 @@ const DynamicForm = ({
isSaving,
selectedDataSource,
workspaceConstants: currentOrgEnvironmentConstants,
optionsChanged,
};
case 'tooljetdb-operations':
return {
@ -364,7 +375,12 @@ const DynamicForm = ({
//Send old field value if editing mode disabled for encrypted fields
const newOptions = { ...options };
const oldFieldValue = selectedDataSource?.['options']?.[field];
optionsChanged({ ...newOptions, [field]: oldFieldValue });
if (oldFieldValue) {
optionsChanged({ ...newOptions, [field]: oldFieldValue });
} else {
delete newOptions[field];
optionsChanged({ ...newOptions });
}
}
setComputedProps({
...computedProps,
@ -378,7 +394,7 @@ const DynamicForm = ({
return (
<div className={`${isHorizontalLayout ? '' : 'row'}`}>
{Object.keys(obj).map((key) => {
const { label, type, encrypted, className } = obj[key];
const { label, type, encrypted, className, key: propertyKey } = obj[key];
const Element = getElement(type);
const isSpecificComponent = ['tooljetdb-operations'].includes(type);
@ -415,9 +431,9 @@ const DynamicForm = ({
variant="tertiary"
target="_blank"
rel="noreferrer"
onClick={(event) => handleEncryptedFieldsToggle(event, key)}
onClick={(event) => handleEncryptedFieldsToggle(event, propertyKey)}
>
{computedProps?.[key]?.['disabled'] ? 'Edit' : 'Cancel'}
{computedProps?.[propertyKey]?.['disabled'] ? 'Edit' : 'Cancel'}
</ButtonSolid>
</div>
)}
@ -444,7 +460,7 @@ const DynamicForm = ({
>
<Element
{...getElementProps(obj[key])}
{...computedProps[key]}
{...computedProps[propertyKey]}
data-cy={`${String(label).toLocaleLowerCase().replace(/\s+/g, '-')}-text-field`}
//to be removed after whole ui is same
isHorizontalLayout={isHorizontalLayout}

View file

@ -0,0 +1,55 @@
import React, { useState } from 'react';
import { ButtonSolid } from './AppButton';
export default function EncryptedFieldWrapper({
children,
options,
selectedDataSource,
optionchanged,
optionsChanged,
name,
label,
encrypted = true,
}) {
const [disabled, setDisabled] = useState(true);
const handleEncryptedFieldsToggle = (field) => {
const isEditing = disabled;
if (isEditing) {
optionchanged(field, '');
} else {
//Send old field value if editing mode disabled for encrypted fields
const newOptions = { ...options };
const oldFieldValue = selectedDataSource?.['options']?.[field];
optionsChanged({ ...newOptions, [field]: oldFieldValue });
}
setDisabled(!isEditing);
};
return (
<>
<div className="d-flex align-items-center mt-3">
<label className="form-label text-muted">{label}</label>
<div className="mx-1 col">
<ButtonSolid
className="datasource-edit-btn mb-2"
type="a"
variant="tertiary"
target="_blank"
rel="noreferrer"
onClick={() => handleEncryptedFieldsToggle(name)}
>
{disabled ? 'Edit' : 'Cancel'}
</ButtonSolid>
</div>
<div className="col-auto mb-2">
<small className="text-green mx-2">
<img className="mx-2 encrypted-icon" src="assets/images/icons/padlock.svg" width="12" height="12" />
Encrypted
</small>
</div>
</div>
{React.cloneElement(children, { encrypted, disabled, placeholder: '**************' })}
</>
);
}

View file

@ -1,5 +1,5 @@
import { ERROR_MESSAGES } from '@/_helpers/constants';
import { redirectToDashboard } from '@/_helpers/routes';
import { redirectToDashboard, getPrivateRoute, getSubpath } from '@/_helpers/routes';
import React from 'react';
import { Modal } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
@ -13,16 +13,28 @@ export default function ErrorPage({ darkMode }) {
if (!errorMsg) redirectToDashboard();
const searchParams = new URLSearchParams(location.search);
const appSlug = searchParams.get('appSlug');
return (
<div style={{ display: 'flex', justifyContent: 'center' }}>
<ErrorModal errorMsg={errorMsg} show={true} darkMode={darkMode} />
<ErrorModal errorMsg={errorMsg} appSlug={appSlug} show={true} darkMode={darkMode} />
</div>
);
}
export const ErrorModal = ({ errorMsg, ...props }) => {
export const ErrorModal = ({ errorMsg, appSlug, ...props }) => {
const { t } = useTranslation();
// Redirect to edit app URL in a new tab
const openAppEditorInNewTab = () => {
const subpath = getSubpath();
const path = subpath
? `${subpath}${getPrivateRoute('editor', { slug: appSlug })}`
: getPrivateRoute('editor', { slug: appSlug });
window.open(path, '_blank');
};
return (
<div className="custom-backdrop">
<Modal
@ -86,8 +98,17 @@ export const ErrorModal = ({ errorMsg, ...props }) => {
{t('globals.workspace-modal.continue-btn', 'Retry')}
</button>
)}
{appSlug && (
<button
className={'btn btn-primary action-btn'}
onClick={() => openAppEditorInNewTab()}
data-cy="open-app-button"
>
{t('globals.workspace-modal.continue-btn', 'Open app')}
</button>
)}
<button
className={errorMsg?.retry ? 'btn btn-primary' : 'btn btn-primary action-btn'}
className={errorMsg?.retry || appSlug ? 'btn btn-primary' : 'btn btn-primary action-btn'}
onClick={() => redirectToDashboard()}
data-cy="back-to-home-button"
>

View file

@ -9,32 +9,40 @@ import _ from 'lodash';
export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
const [isCreating, setIsCreating] = useState(false);
const [fields, setFields] = useState({ name: { value: '', error: '' }, slug: { value: null, error: '' } });
const [name, setName] = useState({ value: null, error: '' });
const [slug, setSlug] = useState({ value: null, error: '' });
const [slugProgress, setSlugProgress] = useState(false);
const [workspaceNameProgress, setWorkspaceNameProgress] = useState(false);
const [isDisabled, setDisabled] = useState(true);
const [isNameDisabled, setNameDisabled] = useState(true);
const [isSlugDisabled, setSlugDisabled] = useState(true);
const darkMode = localStorage.getItem('darkMode') === 'true';
const { t } = useTranslation();
const createOrganization = () => {
let emptyError = false;
const fieldsTemp = fields;
Object.keys(fields).map((key) => {
if (!fields?.[key]?.value?.trim()) {
fieldsTemp[key] = {
error: `Workspace ${key} can't be empty`,
};
[name, slug].map((field, index) => {
if (!field?.value?.trim()) {
index === 0
? setName({
...name,
error: {
error: `Workspace name can't be empty`,
},
})
: setSlug({ ...slug, error: `Workspace slug can't be empty` });
emptyError = true;
}
});
setFields({ ...fields, ...fieldsTemp });
const errorFound = !_.isEmpty(name.error) || !_.isEmpty(slug.error);
if (!emptyError && !Object.keys(fields).find((key) => !_.isEmpty(fields[key].error))) {
if (!emptyError && !errorFound) {
const slugValue = slug.value;
setIsCreating(true);
organizationService.createOrganization({ name: fields['name'].value, slug: fields['slug'].value }).then(
organizationService.createOrganization({ name: name.value, slug: slugValue }).then(
() => {
setIsCreating(false);
const newPath = appendWorkspaceId(fields['slug'].value, location.pathname, true);
const newPath = appendWorkspaceId(slugValue, location.pathname, true);
window.history.replaceState(null, null, newPath);
window.location.reload();
},
@ -47,13 +55,18 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
};
const handleInputChange = async (value, field) => {
setFields({
...fields,
[field]: {
...fields[field],
if (field === 'slug') {
setSlug({
...slug,
error: null,
},
});
});
}
if (field === 'name') {
setName({
...name,
error: null,
});
}
let error = validateName(
value,
`Workspace ${field}`,
@ -79,20 +92,22 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
}
}
setFields({
...fields,
[field]: {
value,
error: error?.errorMsg,
},
});
const disabled = !error?.status;
const updatedValue = {
value,
error: error?.errorMsg,
};
const otherInputErrors = Object.keys(fields).find(
(key) => (key !== field && !_.isEmpty(fields[key].error)) || (key !== field && _.isEmpty(fields[key].value))
);
setDisabled(!error?.status || otherInputErrors);
field === 'slug' && setSlugProgress(false);
field === 'name' && setWorkspaceNameProgress(false);
if (field === 'slug') {
setSlug(updatedValue);
setSlugDisabled(disabled);
setSlugProgress(false);
}
if (field === 'name') {
setName(updatedValue);
setNameDisabled(disabled);
setWorkspaceNameProgress(false);
}
return;
};
@ -104,16 +119,25 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
};
const closeModal = () => {
setFields({ name: { value: '', error: '' }, slug: { value: null, error: '' } });
/* reseting states */
setName({ value: null, error: '' });
setSlug({ value: null, error: '' });
setShowCreateOrg(false);
setDisabled(true);
setNameDisabled(true);
setSlugDisabled(true);
};
const delayedFieldChange = _.debounce(async (value, field) => {
field === 'name' && setWorkspaceNameProgress(true);
field === 'slug' && setSlugProgress(true);
await handleInputChange(value, field);
}, 500);
const delayedSlugChange = _.debounce(async (value) => {
setSlugProgress(true);
await handleInputChange(value, 'slug');
}, 300);
const delayedNameChange = _.debounce(async (value) => {
setWorkspaceNameProgress(true);
await handleInputChange(value, 'name');
}, 300);
const isDisabled = isCreating || isNameDisabled || isSlugDisabled || slugProgress || workspaceNameProgress;
return (
<AlertDialog
@ -129,9 +153,9 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
type="text"
onChange={async (e) => {
e.persist();
await delayedFieldChange(e.target.value, 'name');
await delayedNameChange(e.target.value);
}}
className={`form-control ${fields['name']?.error ? 'is-invalid' : 'is-valid'}`}
className={`form-control ${name?.error ? 'is-invalid' : 'is-valid'}`}
placeholder={t('header.organization.workspaceName', 'Workspace name')}
disabled={isCreating}
onKeyDown={handleKeyDown}
@ -139,9 +163,9 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
data-cy="workspace-name-input-field"
autoFocus
/>
{fields['name']?.error ? (
{name?.error ? (
<label className="label tj-input-error" data-cy="workspace-error-label">
{fields['name']?.error || ''}
{name?.error || ''}
</label>
) : (
<label className="label label-info" data-cy="workspace-name-info-label">
@ -155,18 +179,18 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
<label data-cy="slug-input-label">Unique workspace slug</label>
<input
type="text"
className={`form-control ${fields['slug']?.error ? 'is-invalid' : 'is-valid'}`}
className={`form-control ${slug?.error ? 'is-invalid' : 'is-valid'}`}
placeholder={t('header.organization.workspaceSlug', 'Unique workspace slug')}
disabled={isCreating}
maxLength={50}
onChange={async (e) => {
e.persist();
await delayedFieldChange(e.target.value, 'slug');
await delayedSlugChange(e.target.value);
}}
data-cy="workspace-slug-input-field"
autoFocusfields
/>
{!slugProgress && fields['slug'].value !== null && !fields['slug'].error && (
{!slugProgress && slug.value !== null && !slug.error && (
<div className="icon-container">
<svg width="15" height="10" viewBox="0 0 15 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -178,11 +202,11 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
</svg>
</div>
)}
{fields['slug']?.error ? (
{slug?.error ? (
<label className="label tj-input-error" data-cy="input-label-error">
{fields['slug']?.error || ''}
{slug?.error || ''}
</label>
) : fields['slug'].value && !slugProgress ? (
) : slug.value && !slugProgress ? (
<label className="label label-success" data-cy="slug-sucess-label">{`Slug accepted!`}</label>
) : (
<label
@ -197,7 +221,7 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
<label data-cy="workspace-link-label">Workspace link</label>
<div className={`tj-text-input break-all ${darkMode ? 'dark' : ''}`} data-cy="slug-field">
{!slugProgress ? (
`${getHostURL()}/${fields['slug']?.value || '<workspace-slug>'}`
`${getHostURL()}/${slug?.value || '<workspace-slug>'}`
) : (
<div className="d-flex gap-2">
<div class="spinner-border text-secondary workspace-spinner" role="status">
@ -208,7 +232,7 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
)}
</div>
<label className="label label-success label-updated" data-cy="slug-error-label">
{fields['slug'].value && !fields['slug'].error && !slugProgress ? `Link updated successfully!` : ''}
{slug.value && !slug.error && !slugProgress ? `Link updated successfully!` : ''}
</label>
</div>
</div>
@ -218,7 +242,7 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
{t('globals.cancel', 'Cancel')}
</ButtonSolid>
<ButtonSolid
disabled={isCreating || isDisabled || slugProgress || workspaceNameProgress}
disabled={isDisabled}
onClick={createOrganization}
data-cy="create-workspace-button"
isLoading={isCreating}

View file

@ -10,52 +10,56 @@ import _ from 'lodash';
export const EditOrganization = ({ showEditOrg, setShowEditOrg, currentValue }) => {
const [isCreating, setIsCreating] = useState(false);
const [fields, setFields] = useState({ name: { value: '', error: '' }, slug: { value: null, error: '' } });
const [name, setName] = useState({ value: '', error: '' });
const [slug, setSlug] = useState({ value: '', error: '' });
const [slugProgress, setSlugProgress] = useState(false);
const [workspaceNameProgress, setWorkspaceNameProgress] = useState(false);
const [isDisabled, setDisabled] = useState(true);
const [isNameDisabled, setNameDisabled] = useState(true);
const [isSlugDisabled, setSlugDisabled] = useState(true);
const { t } = useTranslation();
const darkMode = localStorage.getItem('darkMode') === 'true';
useEffect(
() =>
setFields({
name: {
value: currentValue?.name,
},
slug: {
value: currentValue?.slug,
},
}),
() => {
setName({
value: currentValue?.name,
});
setSlug({
value: currentValue?.slug,
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[currentValue]
);
const editOrganization = () => {
let emptyError = false;
const fieldsTemp = fields;
Object.keys(fields).map((key) => {
if (!fields?.[key]?.value?.trim()) {
fieldsTemp[key] = {
error: `Workspace ${key} can't be empty`,
};
[name, slug].map((field, index) => {
if (!field?.value?.trim()) {
index === 0
? setName({
...name,
error: `Workspace name can't be empty`,
})
: setSlug({ ...slug, error: `Workspace slug can't be empty` });
emptyError = true;
}
});
setFields({ ...fields, ...fieldsTemp });
const errorFound = !_.isEmpty(name.error) || !_.isEmpty(slug.error);
if (!emptyError && !Object.keys(fields).find((key) => !_.isEmpty(fields[key].error))) {
if (!emptyError && !errorFound) {
setIsCreating(true);
const data = {
...(fields?.name?.value && fields?.name?.value !== currentValue?.name && { name: fields.name.value.trim() }),
...(fields?.slug?.value && fields?.slug?.value !== currentValue?.slug && { slug: fields.slug.value.trim() }),
...(name?.value && name?.value !== currentValue?.name && { name: name.value.trim() }),
...(slug?.value && slug?.value !== currentValue?.slug && { slug: slug.value.trim() }),
};
organizationService.editOrganization(data).then(
() => {
toast.success('Workspace updated');
setIsCreating(false);
setShowEditOrg(false);
const newPath = appendWorkspaceId(fields['slug'].value, location.pathname, true);
const newPath = appendWorkspaceId(slug.value, location.pathname, true);
window.history.replaceState(null, null, newPath);
window.location.reload();
},
@ -71,13 +75,18 @@ export const EditOrganization = ({ showEditOrg, setShowEditOrg, currentValue })
const trimmedValue = value?.trim();
const prevValue = field === 'name' ? currentValue?.name : currentValue?.slug;
//reset fields
setFields({
...fields,
[field]: {
...fields[field],
if (field === 'slug') {
setSlug({
...slug,
error: null,
},
});
});
}
if (field === 'name') {
setName({
...name,
error: null,
});
}
let error = validateName(
value,
`Workspace ${field}`,
@ -103,32 +112,32 @@ export const EditOrganization = ({ showEditOrg, setShowEditOrg, currentValue })
}
}
setFields({
...fields,
[field]: {
value,
error: error?.errorMsg,
},
});
/* Checking for if the user entered the same value or not */
let isValueTheSame = false;
if (error?.status) {
if (field === 'name') {
isValueTheSame = trimmedValue === currentValue?.name && fields?.slug?.value === currentValue?.slug;
isValueTheSame = trimmedValue === currentValue?.name && slug?.value === currentValue?.slug;
} else {
isValueTheSame = trimmedValue === currentValue?.slug && fields?.name?.value === currentValue?.name;
isValueTheSame = trimmedValue === currentValue?.slug && name?.value === currentValue?.name;
}
}
/* recheck if the rest of fields are valid or not */
const otherInputErrors = Object.keys(fields).find(
(key) => (key !== field && !_.isEmpty(fields[key].error)) || (key !== field && _.isEmpty(fields[key].value))
);
const disabled = isValueTheSame || !error?.status;
const updatedValue = {
value,
error: error?.errorMsg,
};
setDisabled(isValueTheSame || !error?.status || otherInputErrors);
field === 'slug' && setSlugProgress(false);
field === 'name' && setWorkspaceNameProgress(false);
if (field === 'slug') {
setSlug(updatedValue);
setSlugDisabled(disabled);
setSlugProgress(false);
}
if (field === 'name') {
setName(updatedValue);
setNameDisabled(disabled);
setWorkspaceNameProgress(false);
}
return;
};
@ -140,17 +149,25 @@ export const EditOrganization = ({ showEditOrg, setShowEditOrg, currentValue })
};
const closeModal = () => {
setFields({ name: { value: currentValue?.name, error: '' }, slug: { value: currentValue?.slug, error: '' } });
setName({ value: currentValue?.name, error: '' });
setSlug({ value: currentValue?.slug, error: '' });
setShowEditOrg(false);
setDisabled(true);
setSlugDisabled(true);
setNameDisabled(true);
};
const delayedFieldChange = _.debounce(async (value, field) => {
field === 'name' && setWorkspaceNameProgress(true);
field === 'slug' && setSlugProgress(true);
await handleInputChange(value, field);
const delayedSlugChange = _.debounce(async (value) => {
setSlugProgress(true);
await handleInputChange(value, 'slug');
}, 500);
const delayedNameChange = _.debounce(async (value) => {
setWorkspaceNameProgress(true);
await handleInputChange(value, 'name');
}, 500);
const isDisabled = isCreating || isNameDisabled || isSlugDisabled || slugProgress || workspaceNameProgress;
return (
<AlertDialog
show={showEditOrg}
@ -165,20 +182,20 @@ export const EditOrganization = ({ showEditOrg, setShowEditOrg, currentValue })
type="text"
onChange={async (e) => {
e.persist();
await delayedFieldChange(e.target.value, 'name');
await delayedNameChange(e.target.value);
}}
onKeyDown={handleKeyDown}
className={`form-control ${fields['name']?.error ? 'is-invalid' : 'is-valid'}`}
className={`form-control ${name?.error ? 'is-invalid' : 'is-valid'}`}
placeholder={t('header.organization.workspaceName', 'Workspace name')}
disabled={isCreating}
maxLength={50}
defaultValue={fields['name']?.value}
defaultValue={name?.value}
data-cy="workspace-name-input-field"
autoFocus
/>
{fields['name']?.error ? (
{name?.error ? (
<label className="label tj-input-error" data-cy="workspace-error-label">
{fields['name']?.error || ''}
{name?.error || ''}
</label>
) : (
<label className="label label-info" data-cy="workspace-name-info-label">
@ -192,20 +209,20 @@ export const EditOrganization = ({ showEditOrg, setShowEditOrg, currentValue })
<label data-cy="slug-input-label">Unique workspace slug</label>
<input
type="text"
className={`form-control ${fields['slug']?.error ? 'is-invalid' : 'is-valid'}`}
className={`form-control ${slug?.error ? 'is-invalid' : 'is-valid'}`}
placeholder={t('header.organization.workspaceSlug', 'Unique workspace slug')}
disabled={isCreating}
maxLength={50}
onChange={async (e) => {
e.persist();
await delayedFieldChange(e.target.value, 'slug');
await delayedSlugChange(e.target.value);
}}
onKeyDown={handleKeyDown}
defaultValue={fields['slug']?.value}
defaultValue={slug?.value}
data-cy="workspace-slug-input-field"
autoFocusfields
/>
{!slugProgress && fields?.['slug']?.value !== currentValue?.slug && !fields['slug'].error && (
{!slugProgress && slug?.value !== currentValue?.slug && !slug.error && (
<div className="icon-container">
<svg width="15" height="10" viewBox="0 0 15 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -217,11 +234,11 @@ export const EditOrganization = ({ showEditOrg, setShowEditOrg, currentValue })
</svg>
</div>
)}
{fields['slug']?.error ? (
{slug?.error ? (
<label className="label tj-input-error" data-cy="input-label-error">
{fields['slug']?.error || ''}
{slug?.error || ''}
</label>
) : fields?.['slug']?.value !== currentValue?.slug && !slugProgress ? (
) : slug?.value !== currentValue?.slug && !slugProgress ? (
<label className="label label-success" data-cy="sucess-label">{`Slug accepted!`}</label>
) : (
<label
@ -236,7 +253,7 @@ export const EditOrganization = ({ showEditOrg, setShowEditOrg, currentValue })
<label data-cy="workspace-link-label">Workspace link</label>
<div className={`tj-text-input break-all ${darkMode ? 'dark' : ''}`} data-cy="slug-field">
{!slugProgress ? (
`${getHostURL()}/${fields['slug']?.value || '<workspace-slug>'}`
`${getHostURL()}/${slug?.value || '<workspace-slug>'}`
) : (
<div className="d-flex gap-2">
<div class="spinner-border text-secondary workspace-spinner" role="status">
@ -247,10 +264,7 @@ export const EditOrganization = ({ showEditOrg, setShowEditOrg, currentValue })
)}
</div>
<label className="label label-success label-updated" data-cy="slug-success-label">
{!slugProgress &&
fields['slug'].value &&
!fields['slug'].error &&
fields?.['slug']?.value !== currentValue?.slug
{!slugProgress && slug.value && !slug.error && slug?.value !== currentValue?.slug
? `Link updated successfully!`
: ''}
</label>
@ -261,12 +275,7 @@ export const EditOrganization = ({ showEditOrg, setShowEditOrg, currentValue })
<ButtonSolid variant="secondary" onClick={closeModal} className="cancel-btn" data-cy="cancel-button">
{t('globals.cancel', 'Cancel')}
</ButtonSolid>
<ButtonSolid
isLoading={isCreating}
disabled={isCreating || isDisabled || slugProgress || workspaceNameProgress}
onClick={editOrganization}
data-cy="save-button"
>
<ButtonSolid isLoading={isCreating} disabled={isDisabled} onClick={editOrganization} data-cy="save-button">
{t('globals.save', 'Save')}
</ButtonSolid>
</div>

View file

@ -90,16 +90,15 @@ export const PrivateRoute = ({ children }) => {
(session?.authentication_status === false || session?.authentication_failed) &&
!location.pathname.startsWith('/applications/')
) {
// not logged in so redirect to login page with the return url'
return (
<Navigate
to={{
pathname: `/login${getWorkspaceId() ? `/${getWorkspaceId()}` : ''}`,
search: `?redirectTo=${excludeWorkspaceIdFromURL(location.pathname)}`,
state: { from: location },
}}
replace
/>
const redirectTo = `${excludeWorkspaceIdFromURL(location.pathname)}${location.search}`;
const workspaceId = getWorkspaceId();
return navigate(
{
pathname: `/login${workspaceId ? `/${workspaceId}` : ''}`,
search: `?redirectTo=${redirectTo}`,
state: { from: location },
},
{ replace: true }
);
}
@ -110,6 +109,8 @@ export const PrivateRoute = ({ children }) => {
export const AdminRoute = ({ children }) => {
const [session, setSession] = React.useState(authenticationService.currentSessionValue);
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
const subject = authenticationService.currentSession.subscribe((newSession) => {
setSession(newSession);
@ -120,17 +121,16 @@ export const AdminRoute = ({ children }) => {
// authorised so return component
if (session?.group_permissions) {
// TODO-check: do we really need this route while we are having the integration menu item.?
//check: [Marketplace route]
if (!session?.admin) {
return (
<Navigate
to={{
pathname: '/',
search: `?redirectTo=${location.pathname}`,
state: { from: location },
}}
replace
/>
return navigate(
{
pathname: '/',
search: `?redirectTo=${location.pathname}`,
state: { from: location },
},
{ replace: true }
);
}
@ -138,15 +138,14 @@ export const AdminRoute = ({ children }) => {
} else {
if (session?.authentication_status === false && !location.pathname.startsWith('/applications/')) {
// not logged in so redirect to login page with the return url'
return (
<Navigate
to={{
pathname: `/login${getWorkspaceId() ? `/${getWorkspaceId()}` : ''}`,
search: `?redirectTo=${location.pathname}`,
state: { from: location },
}}
replace
/>
const workspaceId = getWorkspaceId();
return navigate(
{
pathname: `/login${workspaceId ? `/${workspaceId}` : ''}`,
search: `?redirectTo=${location.pathname}`,
state: { from: location },
},
{ replace: true }
);
}

View file

@ -3,8 +3,17 @@ import { toast } from 'react-hot-toast';
import Input from '@/_ui/Input';
import Radio from '@/_ui/Radio';
import Button from '@/_ui/Button';
import EncryptedFieldWrapper from './EncyrptedFieldWrapper';
const Zendesk = ({ optionchanged, createDataSource, options, isSaving, selectedDataSource, workspaceConstants }) => {
const Zendesk = ({
optionchanged,
createDataSource,
options,
isSaving,
selectedDataSource,
workspaceConstants,
optionsChanged,
}) => {
const [authStatus, setAuthStatus] = useState(null);
function authZendesk() {
@ -60,20 +69,22 @@ const Zendesk = ({ optionchanged, createDataSource, options, isSaving, selectedD
/>
</div>
<div className="col-md-12 mb-2">
<label className="form-label text-muted mt-3">
Client Secret
<small className="text-green mx-2">
<img className="mx-2 encrypted-icon" src="assets/images/icons/padlock.svg" width="12" height="12" />
Encrypted
</small>
</label>
<Input
type="password"
className="form-control"
onChange={(e) => optionchanged('client_secret', e.target.value)}
value={options?.client_secret?.value}
workspaceConstants={workspaceConstants}
/>
<EncryptedFieldWrapper
options={options}
selectedDataSource={selectedDataSource}
optionchanged={optionchanged}
optionsChanged={optionsChanged}
name="client_secret"
label="Client Secret"
>
<Input
type="password"
className="form-control"
onChange={(e) => optionchanged('client_secret', e.target.value)}
value={options?.client_secret?.value}
workspaceConstants={workspaceConstants}
/>
</EncryptedFieldWrapper>
</div>
<div className="col-md-12">

View file

@ -37,9 +37,9 @@ export const ERROR_TYPES = {
export const ERROR_MESSAGES = {
'url-unavailable': {
title: 'URL unavailable',
title: 'App URL Unavailable',
message:
'This URL is not accessible because it has not been released yet. Please either release it or contact admin for access.',
'The app URL is currently unavailable because the app has not been released. Please either release it or contact admin for access.',
cta: 'Back to home page',
queryParams: [],
},

View file

@ -6,8 +6,8 @@ import _ from 'lodash';
import queryString from 'query-string';
import { ERROR_TYPES } from './constants';
/* appId, versionId are olny for old preview URLs */
export const handleAppAccess = (componentType, slug, version_id) => {
/* appId, versionId are only for old preview URLs */
export const handleAppAccess = async (componentType, slug, version_id) => {
const previewQueryParams = getPreviewQueryParams();
const isOldLocalPreview = version_id ? true : false;
const isLocalPreview = !_.isEmpty(previewQueryParams);
@ -26,9 +26,12 @@ export const handleAppAccess = (componentType, slug, version_id) => {
});
} else {
/* Released app link [launch/sharable link] */
return appsService.validateReleasedApp(slug).catch((error) => {
handleError(componentType, error, redirectPath);
});
try {
return await appsService.validateReleasedApp(slug);
} catch (errorResponse) {
const editPermission = errorResponse?.error?.editPermission;
handleError(componentType, errorResponse, redirectPath, editPermission, slug);
}
}
};
@ -45,7 +48,7 @@ const switchOrganization = (componentType, orgId, redirectPath) => {
);
};
const handleError = (componentType, error, redirectPath) => {
const handleError = (componentType, error, redirectPath, editPermission, appSlug = null) => {
try {
if (error?.data) {
const statusCode = error.data?.statusCode;
@ -70,7 +73,11 @@ const handleError = (componentType, error, redirectPath) => {
}
case 501: {
/* Restrict the users from accessing the sharable app url if the app is not released */
redirectToErrorPage(ERROR_TYPES.URL_UNAVAILABLE, {});
if (editPermission === true && appSlug) {
redirectToErrorPage(ERROR_TYPES.URL_UNAVAILABLE, { appSlug });
} else {
redirectToErrorPage(ERROR_TYPES.URL_UNAVAILABLE, {});
}
return;
}
case 404: {

View file

@ -157,9 +157,12 @@ export const getRedirectURL = (path) => {
return redirectLoc;
};
export const getRedirectTo = () => {
const params = new URL(window.location.href).searchParams;
return params.get('redirectTo') || '/';
export const getRedirectTo = (paramObj) => {
const params = paramObj || new URL(window.location.href).searchParams;
let combined = Array.from(params.entries())
.map((param) => param.join('='))
.join('&');
return params.get('redirectTo') ? combined.replace('redirectTo=', '') : '/';
};
export const getPreviewQueryParams = () => {
@ -169,14 +172,21 @@ export const getPreviewQueryParams = () => {
};
};
export const getRedirectToWithParams = () => {
export const getRedirectToWithParams = (shouldAddCustomParams = false) => {
const pathname = getPathname(null, true);
const queryParams = pathname.includes('/applications/') ? getPreviewQueryParams() : {};
const query = !_.isEmpty(queryParams) ? queryString.stringify(queryParams) : '';
return `${pathname}${!_.isEmpty(query) ? `?${query}` : ''}`;
let query = pathname.includes('/applications/') ? constructQueryParamsInOrder(shouldAddCustomParams) : '';
return `${pathname}${query}`;
};
export const redirectToErrorPage = (errType, queryParams) => {
const query = !_.isEmpty(queryParams) ? queryString.stringify(queryParams) : '';
window.location = `${getHostURL()}/error/${errType}${!_.isEmpty(query) ? `?${query}` : ''}`;
};
/* TODO-reuse: Somewhere in the code we used same logic to construct preview params */
const constructQueryParamsInOrder = (shouldAddCustomParams = false) => {
const { version, ...rest } = getQueryParams();
const queryStr = shouldAddCustomParams && !_.isEmpty(rest) ? queryString.stringify(rest) : '';
const previewParams = `${version ? `?version=${version}` : ''}`;
return `${previewParams}${queryStr ? `${previewParams ? '&' : '?'}${queryStr}` : ''}`;
};

View file

@ -0,0 +1,13 @@
import React from 'react';
/* Created to avoid more useEffect inside the editor */
export const useSocketOpen = (socket) => {
const [status, setStatus] = React.useState(false);
React.useEffect(() => {
if (socket instanceof WebSocket) {
socket.addEventListener('open', () => {
setStatus(true);
});
}
}, [socket]);
return status;
};

View file

@ -262,7 +262,7 @@ function logout(avoidRedirection = false) {
if (avoidRedirection) {
window.location.href = loginPath;
} else {
const pathname = getRedirectToWithParams();
const pathname = getRedirectToWithParams(true);
window.location.href = loginPath + `?redirectTo=${`${pathname.indexOf('/') === 0 ? '' : '/'}${pathname}`}`;
}
};

View file

@ -184,6 +184,13 @@
}
}
}
.empty-ds-container {
position: absolute;
left: 50%;
transform: translate(-50%, 0);
top: 30%;
}
}
.datasources-info {
@ -220,7 +227,7 @@
overflow: hidden;
text-overflow: ellipsis;
}
br:first-of-type {
display: none !important;
}
@ -257,3 +264,23 @@
.delete-modal {
z-index: 1060 !important;
}
.add-plugins-container {
padding: 10px 0px;
margin: auto;
width: 28%;
height: 300px;
text-align: center;
.warning-container {
padding: 5px;
background-color: var(--slate3);
width: 50px;
height: 50px;
align-items: center;
display: flex;
justify-content: center;
border-radius: 6px;
margin: auto;
}
}

View file

@ -10206,6 +10206,7 @@ tbody {
flex-direction: column;
font-family: 'IBM Plex Sans';
font-style: normal;
position: relative;
.text-danger {
font-weight: 400 !important;
@ -10275,6 +10276,21 @@ tbody {
}
.tj-app-input-wrapper {
display: flex;
.eye-icon {
position: absolute;
right: 8px;
top: 5px;
cursor: pointer;
}
.form-control {
padding-right: 2.2rem;
}
}
.tj-sub-helper-text {

View file

@ -1,5 +1,5 @@
import React, { useContext } from 'react';
import { Link } from 'react-router-dom';
import { Link, useLocation } from 'react-router-dom';
import SolidIcon from '../Icon/SolidIcons';
import { BreadCrumbContext } from '../../App/App';
import useBreadcrumbs from 'use-react-router-breadcrumbs';
@ -7,6 +7,8 @@ import useBreadcrumbs from 'use-react-router-breadcrumbs';
export const Breadcrumbs = ({ darkMode, dataCy }) => {
const { sidebarNav } = useContext(BreadCrumbContext);
const breadcrumbs = useBreadcrumbs(routes, { excludePaths: ['/'] });
const location = useLocation();
const search = location.search || '';
return (
<ol className="breadcrumb breadcrumb-arrows">
@ -17,7 +19,7 @@ export const Breadcrumbs = ({ darkMode, dataCy }) => {
<p className=" tj-text-xsm ">{breadcrumb}</p>
{sidebarNav?.length > 0 && <SolidIcon name="cheveronright" fill={darkMode ? '#FDFDFE' : '#131620'} />}
<li className="breadcrumb-item font-weight-500">
<Link to={breadcrumb.key} data-cy="breadcrumb-page-title">
<Link to={`${breadcrumb.key}${search}`} data-cy="breadcrumb-page-title">
{' '}
{sidebarNav}
</Link>

View file

@ -42,7 +42,7 @@ export default ({ options, addNewKeyValuePair, removeKeyValuePair, keyValuePairV
</div>
);
})}
<ButtonSolid variant="ghostBlue" size="sm" onClick={addNewKeyValuePair}>
<ButtonSolid variant="ghostBlue" size="sm" onClick={() => addNewKeyValuePair(options)}>
<AddRectangle width="15" fill="#3E63DD" opacity="1" secondaryFill="#ffffff" />
&nbsp;&nbsp; Add header
</ButtonSolid>

View file

@ -52,7 +52,7 @@ export default ({ options, addNewKeyValuePair, removeKeyValuePair, keyValuePairV
)}
{index === 0 && (
<td>
<button className="btn btn-sm btn-primary" onClick={addNewKeyValuePair}>
<button className="btn btn-sm btn-primary" onClick={() => addNewKeyValuePair(options)}>
Add
</button>
</td>

View file

@ -1,4 +1,5 @@
import React from 'react';
import _ from 'lodash';
import QueryEditor from './QueryEditor';
import SourceEditor from './SourceEditor';
@ -10,7 +11,7 @@ export default ({
isRenderedAsQueryEditor,
workspaceConstants,
}) => {
function addNewKeyValuePair() {
function addNewKeyValuePair(options) {
const newPairs = [...options, ['', '']];
optionchanged(getter, newPairs);
}
@ -21,13 +22,14 @@ export default ({
}
function keyValuePairValueChanged(value, keyIndex, index) {
if (!isRenderedAsQueryEditor && options.length - 1 === index) {
setTimeout(() => {
addNewKeyValuePair();
}, 100);
if (!isRenderedAsQueryEditor) {
const newOptions = _.cloneDeep(options);
newOptions[index][keyIndex] = value;
options.length - 1 === index ? addNewKeyValuePair(newOptions) : optionchanged(getter, newOptions);
} else {
options[index][keyIndex] = value;
optionchanged(getter, options);
}
options[index][keyIndex] = value;
optionchanged(getter, options);
}
const commonProps = {

View file

@ -0,0 +1,21 @@
import React from 'react';
const EyeClosed = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 25 25' }) => (
<svg
width={width}
height={width}
viewBox={viewBox}
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M2.99767 3.77544C2.7536 3.53136 2.7536 3.13563 2.99767 2.89155C3.24175 2.64748 3.63748 2.64748 3.88156 2.89155L17.2149 16.2249C17.459 16.469 17.459 16.8647 17.2149 17.1088C16.9708 17.3528 16.5751 17.3528 16.331 17.1088L13.9754 14.7531C12.801 15.3926 11.4896 15.8335 10.1063 15.8335C6.92669 15.8335 4.12753 13.5041 2.4977 11.7892C1.53136 10.7725 1.53136 9.2278 2.4977 8.21108C3.18922 7.4835 4.09125 6.64528 5.14352 5.92128L2.99767 3.77544ZM7.95242 8.73018C7.73249 9.10238 7.60628 9.53653 7.60628 10.0002C7.60628 11.3809 8.72557 12.5002 10.1063 12.5002C10.5699 12.5002 11.0041 12.374 11.3763 12.154L7.95242 8.73018ZM10.1063 4.16683C13.2859 4.16683 16.085 6.49627 17.7149 8.21108C18.6812 9.22781 18.6812 10.7725 17.7149 11.7892C17.3481 12.1751 16.9222 12.592 16.4461 13.0066L7.9502 4.5107C8.64143 4.29396 9.36347 4.16683 10.1063 4.16683Z"
fill={fill}
/>
</svg>
);
export default EyeClosed;

View file

@ -1,13 +1,41 @@
import React, { useState } from 'react';
import OrgConstantVariablesPreviewBox from '../../_components/OrgConstantsVariablesResolver';
import React, { useEffect, useState } from 'react';
import cx from 'classnames';
import OrgConstantVariablesPreviewBox from '@/_components/OrgConstantsVariablesResolver';
import SolidIcon from '../Icon/SolidIcons';
const Input = ({ helpText, ...props }) => {
const { workspaceVariables, workspaceConstants, value } = props;
const { workspaceVariables, workspaceConstants, value, type, disabled, encrypted } = props;
const [isFocused, setIsFocused] = useState(false);
const [showPasswordProps, setShowPasswordProps] = useState({
inputType: type,
iconType: 'eyedisable',
});
const toggleShowPassword = () => {
if (inputType !== 'text') {
setShowPasswordProps({ inputType: 'text', iconType: 'eye' });
} else {
setShowPasswordProps({ inputType: 'password', iconType: 'eyedisable' });
}
};
useEffect(() => {
if (disabled && encrypted) setShowPasswordProps({ inputType: 'password', iconType: 'eyedisable' });
}, [disabled]);
const { inputType, iconType } = showPasswordProps;
return (
<div className="tj-app-input">
<input {...props} onFocus={() => setIsFocused(true)} onBlur={() => setIsFocused(false)} />
<div className={cx('', { 'tj-app-input-wrapper': type === 'password' || encrypted })}>
<input {...props} type={inputType} onFocus={() => setIsFocused(true)} onBlur={() => setIsFocused(false)} />
{(type === 'password' || encrypted) && (
<div onClick={!disabled && toggleShowPassword}>
{' '}
<SolidIcon className="eye-icon" name={iconType} />
</div>
)}
</div>
<OrgConstantVariablesPreviewBox
workspaceVariables={workspaceVariables}
workspaceConstants={workspaceConstants}

View file

@ -113,7 +113,7 @@ function Layout({ children, switchDarkMode, darkMode }) {
<ToolTip message="Marketplace (Beta)" placement="right">
<Link
to="/integrations"
onClick={(event) => checkForUnsavedChanges(getPrivateRoute('integrations'), event)}
onClick={(event) => checkForUnsavedChanges('/integrations', event)}
className={`tj-leftsidebar-icon-items ${
router.pathname === '/integrations' && `current-seleted-route`
}`}

View file

@ -2,6 +2,7 @@ import React from 'react';
import Input from '@/_ui/Input';
import Select from '@/_ui/Select';
import Headers from '@/_ui/HttpHeaders';
import EncryptedFieldWrapper from '@/_components/EncyrptedFieldWrapper';
const Authentication = ({
auth_type,
@ -23,6 +24,9 @@ const Authentication = ({
multiple_auth_enabled,
optionchanged,
workspaceConstants,
optionsChanged,
selectedDataSource,
options,
}) => {
if (auth_type === 'oauth2') {
return (
@ -97,20 +101,22 @@ const Authentication = ({
</div>
<div className="col-md-12">
<label className="form-label text-muted mt-3">
Client Secret
<small className="text-green mx-2">
<img className="mx-2 encrypted-icon" src="assets/images/icons/padlock.svg" width="12" height="12" />
Encrypted
</small>
</label>
<Input
type="password"
className="form-control"
onChange={(e) => optionchanged('client_secret', e.target.value)}
value={client_secret}
workspaceConstants={workspaceConstants}
/>
<EncryptedFieldWrapper
options={options}
selectedDataSource={selectedDataSource}
optionchanged={optionchanged}
optionsChanged={optionsChanged}
name="client_secret"
label="Client Secret"
>
<Input
type="password"
className="form-control"
onChange={(e) => optionchanged('client_secret', e.target.value)}
value={client_secret}
workspaceConstants={workspaceConstants}
/>
</EncryptedFieldWrapper>
</div>
<div className="col-md-12">
@ -199,20 +205,22 @@ const Authentication = ({
/>
</div>
<div className="col-md-12">
<label className="form-label text-muted mt-3">
Password
<small className="text-green mx-2">
<img className="mx-2 encrypted-icon" src="assets/images/icons/padlock.svg" width="12" height="12" />
Encrypted
</small>
</label>
<Input
type="password"
className="form-control"
onChange={(e) => optionchanged('password', e.target.value)}
value={password}
workspaceConstants={workspaceConstants}
/>
<EncryptedFieldWrapper
options={options}
selectedDataSource={selectedDataSource}
optionchanged={optionchanged}
optionsChanged={optionsChanged}
name="password"
label="Password"
>
<Input
type="password"
className="form-control"
onChange={(e) => optionchanged('password', e.target.value)}
value={password}
workspaceConstants={workspaceConstants}
/>
</EncryptedFieldWrapper>
</div>
</div>
);
@ -220,20 +228,22 @@ const Authentication = ({
return (
<div>
<div className="col-md-12">
<label className="form-label text-muted mt-3">
Token
<small className="text-green mx-2">
<img className="mx-2 encrypted-icon" src="assets/images/icons/padlock.svg" width="12" height="12" />
Encrypted
</small>
</label>
<Input
type="password"
className="form-control"
onChange={(e) => optionchanged('bearer_token', e.target.value)}
value={bearer_token}
workspaceConstants={workspaceConstants}
/>
<EncryptedFieldWrapper
options={options}
selectedDataSource={selectedDataSource}
optionchanged={optionchanged}
optionsChanged={optionsChanged}
name="bearer_token"
label="Token"
>
<Input
type="password"
className="form-control"
onChange={(e) => optionchanged('bearer_token', e.target.value)}
value={bearer_token}
workspaceConstants={workspaceConstants}
/>
</EncryptedFieldWrapper>
</div>
</div>
);

View file

@ -26,6 +26,9 @@ const OAuth = ({
multiple_auth_enabled,
optionchanged,
workspaceConstants,
options,
optionsChanged,
selectedDataSource,
}) => {
const authOptions = (isGrpc = false) => {
const options = [
@ -78,6 +81,9 @@ const OAuth = ({
bearer_token={bearer_token}
auth_url={auth_url}
workspaceConstants={workspaceConstants}
options={options}
optionsChanged={optionsChanged}
selectedDataSource={selectedDataSource}
/>
</>
);

File diff suppressed because it is too large Load diff

View file

@ -30,4 +30,4 @@
"lint": "eslint . '**/*.ts'",
"format": "eslint . --fix '**/*.ts'"
}
}
}

3517
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,8 @@
"version": "1.18.0",
"description": "ToolJet is an open-source low-code framework to build and deploy internal tools.",
"engines": {
"node": "18.3.0",
"npm": "8.11.0"
"node": "18.18.2",
"npm": "9.8.1"
},
"lint-staged": {
"./frontend/src/**/*.{js,jsx}": [
@ -53,4 +53,4 @@
"prepare": "husky install",
"update-version": "node update-version.js"
}
}
}

34729
plugins/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -65,7 +65,8 @@
"@tooljet-plugins/zendesk": "file:packages/zendesk"
},
"peerDependencies": {
"typescript": "^4.9.5"
"typescript": "^4.9.5",
"form-data": "^4.0.0"
},
"devDependencies": {
"@types/jest": "^27.5.2",
@ -78,9 +79,9 @@
"eslint-plugin-jest": "^24.7.0",
"eslint-plugin-prettier": "^3.4.1",
"jest": "^27.5.1",
"lerna": "^4.0.0",
"lerna": "^5.5.2",
"prettier": "^2.8.3",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.5"
}
}
}

View file

@ -8,9 +8,9 @@
"name": "@tooljet-plugins/bigquery",
"version": "1.0.0",
"dependencies": {
"@google-cloud/bigquery": "^5.10.0",
"@google-cloud/bigquery": "^5.12.0",
"@tooljet-plugins/common": "file:../common",
"json5": "^2.2.0",
"json5": "^2.2.3",
"react": "^17.0.2"
}
},
@ -19,15 +19,19 @@
"version": "1.0.0",
"dependencies": {
"react": "^17.0.2",
"rimraf": "^3.0.2"
"rimraf": "^3.0.2",
"tough-cookie": "^4.1.3"
},
"devDependencies": {
"@types/tough-cookie": "^4.0.2"
}
},
"node_modules/@google-cloud/bigquery": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/@google-cloud/bigquery/-/bigquery-5.10.0.tgz",
"integrity": "sha512-kHwPT3O5pihjlhZ4wpNElovv/RY2hyz5MdgON1UlwFM9bVA8kXqdUWS09owjVhHKaHqBxliUpG0DAwjrKHqY7Q==",
"version": "5.12.0",
"resolved": "https://registry.npmjs.org/@google-cloud/bigquery/-/bigquery-5.12.0.tgz",
"integrity": "sha512-UaIvvuKpyJhCRBkxEJXnJwvxOxkGoZHvSs9IsS0MNUS4YphcbWYOyzRMufV5gxdsr7XNSd+36Nj/n/7vyZiCqQ==",
"dependencies": {
"@google-cloud/common": "^3.1.0",
"@google-cloud/common": "^3.9.0",
"@google-cloud/paginator": "^3.0.0",
"@google-cloud/promisify": "^2.0.0",
"arrify": "^2.0.1",
@ -36,6 +40,7 @@
"extend": "^3.0.2",
"is": "^3.3.0",
"p-event": "^4.1.0",
"readable-stream": "^3.6.0",
"stream-events": "^1.0.5",
"uuid": "^8.0.0"
},
@ -378,12 +383,9 @@
}
},
"node_modules/json5": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
"dependencies": {
"minimist": "^1.2.5"
},
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"bin": {
"json5": "lib/cli.js"
},
@ -432,11 +434,6 @@
"node": ">=10"
}
},
"node_modules/minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@ -660,11 +657,11 @@
},
"dependencies": {
"@google-cloud/bigquery": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/@google-cloud/bigquery/-/bigquery-5.10.0.tgz",
"integrity": "sha512-kHwPT3O5pihjlhZ4wpNElovv/RY2hyz5MdgON1UlwFM9bVA8kXqdUWS09owjVhHKaHqBxliUpG0DAwjrKHqY7Q==",
"version": "5.12.0",
"resolved": "https://registry.npmjs.org/@google-cloud/bigquery/-/bigquery-5.12.0.tgz",
"integrity": "sha512-UaIvvuKpyJhCRBkxEJXnJwvxOxkGoZHvSs9IsS0MNUS4YphcbWYOyzRMufV5gxdsr7XNSd+36Nj/n/7vyZiCqQ==",
"requires": {
"@google-cloud/common": "^3.1.0",
"@google-cloud/common": "^3.9.0",
"@google-cloud/paginator": "^3.0.0",
"@google-cloud/promisify": "^2.0.0",
"arrify": "^2.0.1",
@ -673,6 +670,7 @@
"extend": "^3.0.2",
"is": "^3.3.0",
"p-event": "^4.1.0",
"readable-stream": "^3.6.0",
"stream-events": "^1.0.5",
"uuid": "^8.0.0"
}
@ -715,8 +713,10 @@
"@tooljet-plugins/common": {
"version": "file:../common",
"requires": {
"@types/tough-cookie": "^4.0.2",
"react": "^17.0.2",
"rimraf": "^3.0.2"
"rimraf": "^3.0.2",
"tough-cookie": "^4.1.3"
}
},
"@tootallnate/once": {
@ -923,12 +923,9 @@
}
},
"json5": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
"requires": {
"minimist": "^1.2.5"
}
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
},
"jwa": {
"version": "2.0.0",
@ -965,11 +962,6 @@
"yallist": "^4.0.0"
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",

View file

@ -21,5 +21,8 @@
"@tooljet-plugins/common": "file:../common",
"json5": "^2.2.3",
"react": "^17.0.2"
},
"overrides": {
"minimist": "^1.2.6"
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -16,9 +16,9 @@
"clean": "rimraf ./dist && rimraf tsconfig.tsbuildinfo"
},
"dependencies": {
"@google-cloud/firestore": "^5.0.2",
"@google-cloud/firestore": "^7.1.0",
"@tooljet-plugins/common": "file:../common",
"react": "^17.0.2",
"rimraf": "^3.0.2"
}
}
}

View file

@ -1,7 +1,7 @@
{
"name": "@tooljet-plugins/grpc",
"version": "1.0.0",
"lockfileVersion": 2,
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
@ -18,15 +18,19 @@
"version": "1.0.0",
"dependencies": {
"react": "^17.0.2",
"rimraf": "^3.0.2"
"rimraf": "^3.0.2",
"tough-cookie": "^4.1.3"
},
"devDependencies": {
"@types/tough-cookie": "^4.0.2"
}
},
"node_modules/@grpc/grpc-js": {
"version": "1.8.14",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.14.tgz",
"integrity": "sha512-w84maJ6CKl5aApCMzFll0hxtFNT6or9WwMslobKaqWUEf1K+zhlL43bSQhFreyYWIWR+Z0xnVFC1KtLm4ZpM/A==",
"version": "1.9.12",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.12.tgz",
"integrity": "sha512-Um5MBuge32TS3lAKX02PGCnFM4xPT996yLgZNb5H03pn6NyJ4Iwn5YcPq6Jj9yxGRk7WOgaZFtVRH5iTdYBeUg==",
"dependencies": {
"@grpc/proto-loader": "^0.7.0",
"@grpc/proto-loader": "^0.7.8",
"@types/node": ">=12.12.47"
},
"engines": {
@ -34,15 +38,14 @@
}
},
"node_modules/@grpc/proto-loader": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.6.tgz",
"integrity": "sha512-QyAXR8Hyh7uMDmveWxDSUcJr9NAWaZ2I6IXgAYvQmfflwouTM+rArE2eEaCtLlRqO81j7pRLCt81IefUei6Zbw==",
"version": "0.7.10",
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.10.tgz",
"integrity": "sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ==",
"dependencies": {
"@types/long": "^4.0.1",
"lodash.camelcase": "^4.3.0",
"long": "^4.0.0",
"protobufjs": "^7.0.0",
"yargs": "^16.2.0"
"long": "^5.0.0",
"protobufjs": "^7.2.4",
"yargs": "^17.7.2"
},
"bin": {
"proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
@ -109,15 +112,13 @@
"resolved": "../common",
"link": true
},
"node_modules/@types/long": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
"integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="
},
"node_modules/@types/node": {
"version": "18.16.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz",
"integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q=="
"version": "20.10.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.2.tgz",
"integrity": "sha512-37MXfxkb0vuIlRKHNxwCkb60PNBpR94u4efQuN4JgIAm66zfCDXGSAFCef9XUWFovX2R1ok6Z7MHhtdVXXkkIw==",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
@ -142,13 +143,16 @@
}
},
"node_modules/cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/color-convert": {
@ -207,9 +211,9 @@
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
},
"node_modules/long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q=="
},
"node_modules/loose-envify": {
"version": "1.4.0",
@ -231,9 +235,9 @@
}
},
"node_modules/protobufjs": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz",
"integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==",
"version": "7.2.5",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz",
"integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==",
"hasInstallScript": true,
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
@ -253,11 +257,6 @@
"node": ">=12.0.0"
}
},
"node_modules/protobufjs/node_modules/long": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q=="
},
"node_modules/react": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
@ -302,6 +301,11 @@
"node": ">=8"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@ -327,299 +331,29 @@
}
},
"node_modules/yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dependencies": {
"cliui": "^7.0.2",
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.0",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^20.2.2"
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=10"
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "20.2.9",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"engines": {
"node": ">=10"
"node": ">=12"
}
}
},
"dependencies": {
"@grpc/grpc-js": {
"version": "1.8.14",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.14.tgz",
"integrity": "sha512-w84maJ6CKl5aApCMzFll0hxtFNT6or9WwMslobKaqWUEf1K+zhlL43bSQhFreyYWIWR+Z0xnVFC1KtLm4ZpM/A==",
"requires": {
"@grpc/proto-loader": "^0.7.0",
"@types/node": ">=12.12.47"
}
},
"@grpc/proto-loader": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.6.tgz",
"integrity": "sha512-QyAXR8Hyh7uMDmveWxDSUcJr9NAWaZ2I6IXgAYvQmfflwouTM+rArE2eEaCtLlRqO81j7pRLCt81IefUei6Zbw==",
"requires": {
"@types/long": "^4.0.1",
"lodash.camelcase": "^4.3.0",
"long": "^4.0.0",
"protobufjs": "^7.0.0",
"yargs": "^16.2.0"
}
},
"@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
},
"@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
},
"@protobufjs/codegen": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
},
"@protobufjs/eventemitter": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
},
"@protobufjs/fetch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
"requires": {
"@protobufjs/aspromise": "^1.1.1",
"@protobufjs/inquire": "^1.1.0"
}
},
"@protobufjs/float": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
},
"@protobufjs/inquire": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
},
"@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
},
"@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
},
"@protobufjs/utf8": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
},
"@tooljet-plugins/common": {
"version": "file:../common",
"requires": {
"react": "^17.0.2",
"rimraf": "^3.0.2"
}
},
"@types/long": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
"integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="
},
"@types/node": {
"version": "18.16.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz",
"integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q=="
},
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^7.0.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
},
"long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
},
"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": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
},
"protobufjs": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz",
"integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==",
"requires": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/node": ">=13.7.0",
"long": "^5.0.0"
},
"dependencies": {
"long": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q=="
}
}
},
"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"
}
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
},
"string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
}
},
"strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"requires": {
"ansi-regex": "^5.0.1"
}
},
"wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
}
},
"y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
},
"yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
"requires": {
"cliui": "^7.0.2",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.0",
"y18n": "^5.0.5",
"yargs-parser": "^20.2.2"
}
},
"yargs-parser": {
"version": "20.2.9",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="
}
}
}

View file

@ -21,5 +21,8 @@
"@grpc/proto-loader": "^0.7.6",
"@tooljet-plugins/common": "file:../common",
"react": "^17.0.2"
},
"overrides": {
"minimist": "^1.2.6"
}
}
}

View file

@ -29,11 +29,11 @@
"value": ""
},
"protocol": {
"type": "HTTP"
"value": "http"
}
},
"properties": {
"api_key": {
"api_token": {
"label": "API token",
"key": "api_token",
"type": "password",
@ -79,4 +79,4 @@
"host",
"protocol"
]
}
}

View file

@ -2,6 +2,7 @@ import { QueryError, QueryResult, QueryService, ConnectionTestResult } from '@to
import { SourceOptions, QueryOptions } from './types';
const mariadb = require('mariadb');
export default class Mariadb implements QueryService {
private defaultConnectionLimit = '5';
async run(sourceOptions: SourceOptions, queryOptions: QueryOptions): Promise<QueryResult> {
let result = {};
const mariadbClient = await this.getConnection(sourceOptions);
@ -18,23 +19,31 @@ export default class Mariadb implements QueryService {
}
async testConnection(sourceOptions: SourceOptions): Promise<ConnectionTestResult> {
const conn = await this.getConnection(sourceOptions);
const rows = await conn.query('SELECT 1 as val');
try {
const conn = await this.getConnection(sourceOptions);
const rows = await conn.query('SELECT 1 as val');
if (!rows) {
throw new Error('Error');
if (!rows) {
throw new Error('Connection test returned no results');
}
return {
status: 'ok',
};
} catch (error) {
throw new QueryError('Connection test failed', error.message, {});
}
return {
status: 'ok',
};
}
async getConnection(sourceOptions: SourceOptions): Promise<any> {
const connectionLimit =
sourceOptions.connectionLimit && sourceOptions.connectionLimit !== ''
? sourceOptions.connectionLimit
: this.defaultConnectionLimit;
const poolConfig = {
host: sourceOptions.host,
user: sourceOptions.user,
password: sourceOptions.password,
connectionLimit: sourceOptions.connectionLimit,
connectionLimit: connectionLimit,
port: sourceOptions.port,
database: sourceOptions.database,
};
@ -51,9 +60,17 @@ export default class Mariadb implements QueryService {
if (sourceOptions.ssl_enabled) poolConfig['ssl'] = sslObject;
const mariadbPool = mariadb.createPool(poolConfig);
try {
const mariadbPool = await mariadb.createPool(poolConfig);
mariadbPool.on('error', (error) => {
console.error(error);
});
return mariadbPool.getConnection();
return mariadbPool.getConnection();
} catch (error) {
console.error('Error while establishing database connection:', error.message);
throw new QueryError('Database connection failed', error.message, {});
}
}
private toJson(data) {

View file

@ -61,8 +61,8 @@ export default class MinioService implements QueryService {
async getConnection(sourceOptions: SourceOptions, options?: object): Promise<any> {
const credentials: ClientOptions = {
endPoint: sourceOptions['host'],
port: sourceOptions['port'],
useSSL: sourceOptions['ssl_enabled'],
port: +sourceOptions['port'],
useSSL: !!sourceOptions['ssl_enabled'],
accessKey: sourceOptions['access_key'],
secretKey: sourceOptions['secret_key'],
};

View file

@ -61,5 +61,6 @@ export async function signedUrlForPut(minioClient: MinioClient, queryOptions: ob
}
export async function removeObject(minioClient: MinioClient, queryOptions: object): Promise<object> {
return await minioClient.removeObject(queryOptions['bucket'], queryOptions['objectName']);
await minioClient.removeObject(queryOptions['bucket'], queryOptions['objectName']);
return {};
}

File diff suppressed because it is too large Load diff

View file

@ -17,5 +17,8 @@
"@tooljet-plugins/common": "file:../common",
"minio": "^7.0.32",
"react": "^17.0.2"
},
"overrides": {
"minimist": "^1.2.6"
}
}
}

View file

@ -111,6 +111,7 @@
"label": "Connection string",
"key": "connection_string",
"type": "text",
"encrypted": true,
"description": "mongodb+srv://tooljet:<password>@cluster0.i1vq4.mongodb.net/mydb?retryWrites=true&w=majority"
}
}

View file

@ -61,7 +61,7 @@
"label": "Password",
"key": "password",
"type": "password",
"description": "Enter username"
"description": "Enter password"
}
},
"header":{

File diff suppressed because it is too large Load diff

View file

@ -22,4 +22,4 @@
"react": "^17.0.2",
"snowflake-sdk": "^1.9.1"
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -20,5 +20,8 @@
"react": "^17.0.2",
"rimraf": "^3.0.2",
"twilio": "^3.84.1"
},
"overrides": {
"url-parse": ">=1.5.9"
}
}
}

View file

@ -12,7 +12,12 @@
"data": {},
"rawData": {}
},
"options": {},
"options": {
"client_secret": {
"type": "string",
"encrypted": true
}
},
"customTesting": true,
"hideSave": true
},

View file

@ -1 +1 @@
2.26.2
2.27.0

16120
server/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -44,14 +44,14 @@
"@nestjs/platform-ws": "^8.0.10",
"@nestjs/schedule": "^2.2.0",
"@nestjs/serve-static": "^2.2.2",
"@nestjs/typeorm": "^8.0.0",
"@nestjs/typeorm": "8.0.0",
"@nestjs/websockets": "^8.0.10",
"@sentry/node": "6.17.6",
"@sentry/tracing": "6.17.6",
"@tooljet/plugins": "../plugins",
"bcrypt": "^5.0.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.1",
"class-validator": "^0.14.0",
"compression": "^1.7.4",
"cookie-parser": "^1.4.6",
"dotenv": "^10.0.0",
@ -88,7 +88,7 @@
"y-websocket": "^1.4.0"
},
"peerDependencies": {
"@nestjs/cli": "^8.0.0"
"@nestjs/cli": "^9.0.0"
},
"devDependencies": {
"@golevelup/ts-jest": "^0.3.2",
@ -96,12 +96,11 @@
"@nestjs/testing": "^8.0.0",
"@types/compression": "^1.7.2",
"@types/cookie-parser": "^1.4.3",
"@types/cron": "^2.0.0",
"@types/express": "^4.17.13",
"@types/express-http-proxy": "^1.6.3",
"@types/got": "^9.6.12",
"@types/humps": "^2.0.1",
"@types/jest": "^26.0.24",
"@types/jest": "^27.0.0",
"@types/multer": "^1.4.7",
"@types/node": "^16.0.0",
"@types/nodemailer": "^6.4.4",
@ -110,8 +109,8 @@
"@types/sanitize-html": "^2.6.2",
"@types/supertest": "^2.0.11",
"@types/ws": "^8.2.2",
"@typescript-eslint/eslint-plugin": "^4.31.1",
"@typescript-eslint/parser": "^4.31.1",
"@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.2",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-cypress": "^2.12.1",
@ -127,7 +126,7 @@
"typescript": "^4.3.5"
},
"engines": {
"node": "18.3.0",
"npm": "8.11.0"
"node": "18.18.2",
"npm": "9.8.1"
}
}

Some files were not shown because too many files have changed in this diff Show more