Merge branch 'main' into appbuilder/sprint-9

This commit is contained in:
johnsoncherian 2025-03-21 12:13:13 +05:30
commit 39f0bf2ed4
41 changed files with 2226 additions and 86 deletions

View file

@ -79,7 +79,7 @@ jobs:
- name: Set up environment variables
run: |
echo "TOOLJET_EDITION=${{ matrix.edition == 'ee' && 'EE' || 'CE' }}" >> .env
echo "TOOLJET_EDITION=${{ matrix.edition == 'ee' && 'ee' || 'ce' }}" >> .env
echo "TOOLJET_HOST=http://localhost:8082" >> .env
echo "LOCKBOX_MASTER_KEY=cd97331a419c09387bef49787f7da8d2a81d30733f0de6bed23ad8356d2068b2" >> .env
echo "SECRET_KEY_BASE=7073b9a35a15dd20914ae17e36a693093f25b74b96517a5fec461fc901c51e011cd142c731bee48c5081ec8bac321c1f259ef097ef2a16f25df17a3798c03426" >> .env

View file

@ -116,8 +116,10 @@ Cypress.Commands.add(
});
const splitIntoFlatArray = (value) => {
const regex = /(\{|\}|\(|\)|\[|\]|,|:|;|=>|'[^']*'|[a-zA-Z0-9._]+|\s+)/g;
const regex =
/(\{|\}|\(|\)|\[|\]|,|:|;|=>|'[^']*'|"[^"]*"|[a-zA-Z0-9._-]+|\s+)/g;
let prefix = "";
return (
value.match(regex)?.reduce((acc, part) => {
if (part === "{{" || part === "((") {
@ -132,6 +134,10 @@ Cypress.Commands.add(
acc.push(prefix + " ");
} else if (part === ":") {
acc.push(prefix + ":");
} else if (part === '"') {
acc.push(prefix + '"');
} else if (part.includes("-")) {
acc.push(prefix + part); // Ensure hyphen is included
} else {
acc.push(prefix + part);
prefix = "";
@ -142,13 +148,11 @@ Cypress.Commands.add(
};
if (Array.isArray(value)) {
cy.wrap(subject)
.last()
.realType(value, {
parseSpecialCharSequences: false,
delay: 0,
force: true,
});
cy.wrap(subject).last().realType(value, {
parseSpecialCharSequences: false,
delay: 0,
force: true,
});
} else {
splitIntoFlatArray(value).forEach((i) => {
cy.wrap(subject)
@ -228,9 +232,9 @@ Cypress.Commands.add(
.invoke("text")
.then((text) => {
cy.wrap(subject).realType(createBackspaceText(text)),
{
delay: 0,
};
{
delay: 0,
};
});
}
);

View file

@ -0,0 +1,40 @@
export const pluginSelectors = {
regionField: '[data-cy="region-section"] .react-select__control',
regionFieldValue: '[data-cy="region-section"] .react-select__single-value',
amazonsesAccesKey: '[data-cy="access-key-text-field"]',
operationDropdown: '[data-cy="operation-select-dropdown"]',
sendEmailInputField: '[data-cy="send-mail-to-input-field"]',
ccEmailInputField: '[data-cy="cc-to-input-field"]',
bccEmailInputField: '[data-cy="bcc-to-input-field"]',
sendEmailFromInputField: '[data-cy="send-mail-from-input-field"]',
emailSubjetInputField: '[data-cy="subject-input-field"]',
emailbodyInputField: '[data-cy="body-input-field"]',
amazonAthenaDbName: '[data-cy="database-text-field"]',
};
export const baserowSelectors = {
hostField: '[data-cy="host-select-dropdown"]',
baserowApiKey: '[data-cy="api-token-text-field"]',
table: '[data-cy="table-id-input-field"]',
rowIdinputfield: '[data-cy="row-id-input-field"]',
};
export const appWriteSelectors = {
projectID: '[data-cy="project-id-text-field"]',
collectionId: '[data-cy="collectionid-input-field"]',
documentId: '[data-cy="documentid-input-field"]',
bodyInput: '[data-cy="body-input-field"]',
};
export const twilioSelectors = {
toNumberInputField: '[data-cy="to-number-input-field"]',
bodyInput: '[data-cy="body-input-field"]',
};
export const minioSelectors = {
sslToggle: 'data-cy="ssl-enabled-toggle-input"',
bucketNameInputField: '[data-cy="bucket-input-field"]',
objectNameInputField: '[data-cy="objectname-input-field"]',
contentTypeInputField: '[data-cy="contenttype-input-field"]',
dataInput: '[data-cy="data-input-field"]',
};

View file

@ -5,5 +5,5 @@ export const s3Selector = {
regionLabel: '[data-cy="label-region"]',
customEndpointLabel: '[data-cy="label-custom-endpoint"]',
customEndpointInput: '[data-cy="undefined-text-field"]',
dataSourceNameInput: '[data-cy="data-source-name-input-filed"]',
dataSourceNameInput: '[data-cy="data-source-name-input-field"]',
};

View file

@ -14,7 +14,7 @@ export const dataSourceSelector = {
dataSourceSearchInputField: '[data-cy="home-page-search-bar"]',
postgresDataSource: "[data-cy='data-source-postgresql']",
dataSourceNameInputField: '[data-cy="data-source-name-input-filed"]',
dataSourceNameInputField: '[data-cy="data-source-name-input-field"]',
labelHost: '[data-cy="label-host"]',
labelPort: '[data-cy="label-port"]',
labelSsl: '[data-cy="label-ssl"]',
@ -28,7 +28,7 @@ export const dataSourceSelector = {
buttonTestConnection: '[data-cy="test-connection-button"]',
connectionFailedText: '[data-cy="test-connection-failed-text"]',
buttonSave: '[data-cy="db-connection-save-button"] > .tj-base-btn',
dangerAlertNotSupportSSL: '.go3958317564',
dangerAlertNotSupportSSL: ".go3958317564",
passwordTextField: '[data-cy="password-text-field"]',
textConnectionVerified: '[data-cy="test-connection-verified-text"]',
@ -97,11 +97,11 @@ export const dataSourceSelector = {
eventQuerySelectionField: '[data-cy="query-selection-field"]',
addedDsSearchIcon: '[data-cy="added-ds-search-icon"]',
AddedDsSearchBar: '[data-cy="added-ds-search-bar"]',
dsNameInputField: '[data-cy="data-source-name-input-filed"]',
dsNameInputField: '[data-cy="data-source-name-input-field"]',
unSavedModalTitle: '[data-cy="unsaved-changes-title"]',
eventQuerySelectionField: '[data-cy="query-selection-field"]',
connectionAlertText: '[data-cy="connection-alert-text"]',
deleteDSButton: (datasourceName) => {
return `[data-cy="${cyParamName(datasourceName)}-delete-button"]`
return `[data-cy="${cyParamName(datasourceName)}-delete-button"]`;
},
};

View file

@ -12,7 +12,7 @@ export const postgreSqlSelector = {
dataSourceSearchInputField: '[data-cy="home-page-search-bar"]',
postgresDataSource: "[data-cy='data-source-postgresql']",
dataSourceNameInputField: '[data-cy="data-source-name-input-filed"]',
dataSourceNameInputField: '[data-cy="data-source-name-input-field"]',
labelHost: '[data-cy="label-host"]',
labelPort: '[data-cy="label-port"]',
labelSsl: '[data-cy="label-ssl"]',
@ -88,3 +88,11 @@ export const postgreSqlSelector = {
eventQuerySelectionField: '[data-cy="query-selection-field"]',
};
export const airTableSelector = {
operationSelectDropdown: '[data-cy="operation-select-dropdown"]',
baseIdInputField: '[data-cy="base-id-input-field"]',
tableNameInputField: '[data-cy="table-name-input-field"]',
recordIdInputField: '[data-cy="record-id-input-field"]',
bodyInputField: '[data-cy="body-input-field"]',
};

View file

@ -0,0 +1,6 @@
export const airtableText = {
airtable: "Airtable",
cypressairtable: "cypress-Airtable",
ApiKey: "Personal access token",
apikeyPlaceholder: "**************",
};

View file

@ -0,0 +1,8 @@
export const amazonAthenaText = {
AmazonAthena: "Amazon Athena",
cypressAmazonAthena: "cypress-Amazon Athena",
labelAccesskey: "Access key",
labelSecretKey: "Secret key",
placeholderEnteraAccessKey: "Enter access key",
placeholderSecretKey:"**************",
};

View file

@ -0,0 +1,8 @@
export const amazonSesText = {
AmazonSES: "Amazon SES",
cypressAmazonSES: "cypress-Amazon SES",
labelAccesskey: "Access key",
labelSecretKey: "Secret key",
placeholderAccessKey: "Enter access key",
placeholderSecretKey:"**************",
};

View file

@ -0,0 +1,12 @@
export const appwriteText = {
appwrite: "Appwrite",
cypressAppwrite: "cypress-Appwrite",
host: "Host",
ProjectID: "Project ID",
DatabaseID: "Database ID",
SecretKey: "Secret Key",
SecretKeyPlaceholder: "**************",
hostPlaceholder: "Appwrite database host/endpoint",
projectIdPlaceholder: "Appwrite project id",
databaseIdPlaceholder: "Appwrite Database id",
};

View file

@ -0,0 +1,6 @@
export const baseRowText = {
baserow: "baserow",
cypressBaseRow: "cypress-baserow",
lableApiToken: "API token",
placeholderApiToken:"**************",
};

View file

@ -0,0 +1,14 @@
export const minioText = {
minio: "Minio",
cypressMinio: "cypressMinio",
hostLabel: "Host",
hostInputPlaceholder: "Enter host",
portLabel: "Port",
portPlaceholder: "Enter port",
labelAccesskey: "Access key",
labelSecretKey: "Secret key",
placeholderAccessKey: "Enter access key",
placeholderSecretKey: "**************",
bucketName: `my-second-bucket`,
objectName: `mybucket`,
};

View file

@ -0,0 +1,11 @@
export const twilioText = {
twilio: "Twilio",
cypresstwilio: "cypress-Twilio",
authTokenLabel: "Auth Token",
authTokenPlaceholder: "**************",
accountSidLabel: "Account SID",
accountSidPlaceholder: "Account SID for Twilio",
messagingSIDLabel: "Messaging Service SID",
messagingSIDPalceholder: "Messaging Service SID for Twilio",
messageText: "Sending test message to check twilio",
};

View file

@ -0,0 +1,210 @@
import { fake } from "Fixtures/fake";
import { postgreSqlSelector } from "Selectors/postgreSql";
import { postgreSqlText } from "Texts/postgreSql";
import { commonSelectors } from "Selectors/common";
import { selectAndAddDataSource } from "Support/utils/postgreSql";
import { closeDSModal } from "Support/utils/dataSource";
const data = {};
data.dsNamefake = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
data.dsNamefake1 = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
const cyParamName = (name) => name.toLowerCase().replace(/[^a-z0-9]/g, "-");
const dataSources = [
"BigQuery",
"ClickHouse",
"CosmosDB",
"CouchDB",
"Databricks",
"DynamoDB",
"Elasticsearch",
"Firestore",
"InfluxDB",
"MariaDB",
"MongoDB",
"SQL Server",
"MySQL",
"Oracle DB",
"PostgreSQL",
"Redis",
"RethinkDB",
"SAP HANA",
"Snowflake",
"TypeSense",
"Airtable",
"Amazon SES",
"Appwrite",
"Amazon Athena",
"Baserow",
// "Google Sheets", need to remove
"GraphQL",
// "gRPC", need to remove
"Mailgun",
"n8n",
"Notion",
"OpenAPI",
"REST API",
"SendGrid",
// "Slack", need to remove
"SMTP",
"Stripe",
"Twilio",
"Woocommerce",
//"Zendesk", need to remove
"Azure Blob Storage",
"GCS",
"Minio",
"AWS S3",
];
describe("Add all Data sources to app", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
});
it("Should verify global data source page", () => {
cy.get(commonSelectors.globalDataSourceIcon).click();
closeDSModal();
cy.get(postgreSqlSelector.allDatasourceLabelAndCount).should(
"have.text",
postgreSqlText.allDataSources()
);
cy.get(postgreSqlSelector.commonlyUsedLabelAndCount).should(
"have.text",
postgreSqlText.commonlyUsed
);
cy.get(postgreSqlSelector.cloudStorageLabelAndCount).should(
"have.text",
postgreSqlText.allCloudStorage
);
});
it("Should add all data sources in data source page", () => {
dataSources.forEach((dsName) => {
cy.get(commonSelectors.globalDataSourceIcon).click();
selectAndAddDataSource("databases", dsName, dsName); // Using the correct fake name
// Test connection
// cy.get(postgreSqlSelector.buttonTestConnection).click();
// cy.get(postgreSqlSelector.textConnectionVerified, {
// timeout: 10000,
// }).should("have.text", postgreSqlText.labelConnectionVerified);
// // Save data source
// cy.get(postgreSqlSelector.buttonSave).click();
// cy.verifyToastMessage(
// commonSelectors.toastMessage,
// `Data Source ${dsName} saved.`
// );
});
});
it("Should add all data sources in the app", () => {
cy.get(commonSelectors.dashboardIcon).click();
cy.get(commonSelectors.appCreateButton).click();
cy.get(commonSelectors.appNameInput).click().type(data.dsNamefake);
cy.get(commonSelectors.createAppButton).click();
cy.skipWalkthrough();
cy.wrap(dataSources).each((dsName) => {
cy.get('[data-cy="show-ds-popover-button"]').click();
cy.get(".css-4e90k9").type(
`cypress-${cyParamName(dsName)}-${cyParamName(dsName)}`
);
cy.wait(500);
cy.contains(
`[id*="react-select-"]`,
`cypress-${cyParamName(dsName)}-${cyParamName(dsName)}`
)
.should("be.visible")
.click();
cy.wait(500);
});
});
it("Should install all makretplace plugins and add them into the app", () => {
const dataSourcesMarketplace = [
"Plivo",
"GitHub",
"OpenAI",
"AWS Textract",
"HarperDB",
"AWS Redshift",
"PocketBase",
"AWS Lambda",
"Supabase",
"Engagespot",
// "Salesforce", need to remove
"Presto",
"Jira",
// "Sharepoint", need to remove
"Portkey",
"Pinecone",
"Hugging Face",
"Cohere",
"Gemini",
"Mistral",
"Anthropic",
"Qdrant",
"Weaviate DB",
];
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.window().then((win) => {
cy.stub(win, "open").callsFake((url) => {
win.location.href = url;
});
});
cy.get('[data-cy="data-source-add-plugin"]').click();
cy.get(".marketplace-install").each(($el) => {
cy.wrap($el).click();
cy.wait(500);
cy.get(commonSelectors.toastMessage).should("include.text", "installed");
});
cy.wait(1000);
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(commonSelectors.pageSectionHeader).should(
"have.text",
"Data sources"
);
cy.wrap(dataSourcesMarketplace).each((dsName) => {
cy.get(commonSelectors.globalDataSourceIcon).click();
selectAndAddDataSource("databases", dsName, dsName);
cy.wait(500);
});
cy.get(commonSelectors.dashboardIcon).click();
cy.get(commonSelectors.appCreateButton).click();
cy.get(commonSelectors.appNameInput).click().type(data.dsNamefake1);
cy.get(commonSelectors.createAppButton).click();
cy.skipWalkthrough();
cy.wrap(dataSourcesMarketplace).each((dsName) => {
cy.get('[data-cy="show-ds-popover-button"]').click();
cy.get(".css-4e90k9").type(
`cypress-${cyParamName(dsName)}-${cyParamName(dsName)}`
);
cy.wait(500);
cy.contains(
`[id*="react-select-"]`,
`cypress-${cyParamName(dsName)}-${cyParamName(dsName)}`
)
.should("be.visible")
.click();
cy.wait(500);
});
});
});

View file

@ -0,0 +1,289 @@
import { fake } from "Fixtures/fake";
import { postgreSqlSelector, airTableSelector } from "Selectors/postgreSql";
import { postgreSqlText } from "Texts/postgreSql";
import { airtableText } from "Texts/airTable";
import { commonSelectors } from "Selectors/common";
import { commonText } from "Texts/common";
import {
fillDataSourceTextField,
selectAndAddDataSource,
} from "Support/utils/postgreSql";
import { redisText } from "Texts/redis";
import {
verifyCouldnotConnectWithAlert,
deleteDatasource,
closeDSModal,
addQuery,
addDsAndAddQuery,
selectDatasource,
} from "Support/utils/dataSource";
import { openQueryEditor } from "Support/utils/dataSource";
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
data.dsName1 = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source Airtable", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
});
it("Should verify elements on connection AirTable form", () => {
cy.get(commonSelectors.globalDataSourceIcon).click();
closeDSModal();
cy.get(postgreSqlSelector.allDatasourceLabelAndCount).should(
"have.text",
postgreSqlText.allDataSources()
);
cy.get(postgreSqlSelector.commonlyUsedLabelAndCount).should(
"have.text",
postgreSqlText.commonlyUsed
);
cy.get(postgreSqlSelector.databaseLabelAndCount).should(
"have.text",
postgreSqlText.allDatabase()
);
cy.get(postgreSqlSelector.apiLabelAndCount).should(
"have.text",
postgreSqlText.allApis
);
cy.get(postgreSqlSelector.cloudStorageLabelAndCount).should(
"have.text",
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", airtableText.airtable, data.dsName);
cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement(
"have.text",
postgreSqlText.buttonTextSave
);
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
deleteDatasource(`cypress-${data.dsName}-airtable`);
});
it("Should verify the functionality of AirTable connection form.", () => {
selectAndAddDataSource("databases", airtableText.airtable, data.dsName);
fillDataSourceTextField(
airtableText.ApiKey,
airtableText.apikeyPlaceholder,
Cypress.env("airTable_apikey")
);
cy.get(postgreSqlSelector.buttonSave).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.dsName}-airtable-button"]`
).verifyVisibleElement("have.text", `cypress-${data.dsName}-airtable`);
deleteDatasource(`cypress-${data.dsName}-airtable`);
});
it("Should able to run the query with valid conection", () => {
const airTable_apiKey = Cypress.env("airTable_apikey");
const airTable_baseId = Cypress.env("airtabelbaseId");
const airTable_tableName = Cypress.env("airtable_tableName");
const airTable_recordID = Cypress.env("airtable_recordId");
selectAndAddDataSource("databases", airtableText.airtable, data.dsName);
fillDataSourceTextField(
airtableText.ApiKey,
airtableText.apikeyPlaceholder,
airTable_apiKey
);
cy.wait(1000);
cy.get(postgreSqlSelector.buttonSave).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.dsName}-airtable-button"]`
).verifyVisibleElement("have.text", `cypress-${data.dsName}-airtable`);
cy.get(commonSelectors.dashboardIcon).click();
cy.get(commonSelectors.appCreateButton).click();
cy.get(commonSelectors.appNameInput).click().type(data.dsName);
cy.get(commonSelectors.createAppButton).click();
cy.skipWalkthrough();
cy.get('[data-cy="show-ds-popover-button"]').click();
cy.get(".css-4e90k9").type(`${data.dsName}`);
cy.contains(`[id*="react-select-"]`, data.dsName).click();
cy.get('[data-cy="query-rename-input"]').clear().type(data.dsName);
// Verfiy List Recored operation
cy.get(airTableSelector.operationSelectDropdown)
.click()
.type("List records{enter}");
cy.get(airTableSelector.baseIdInputField).clearAndTypeOnCodeMirror(
airTable_baseId
);
cy.get(airTableSelector.tableNameInputField).clearAndTypeOnCodeMirror(
airTable_tableName
);
cy.get(dataSourceSelector.queryPreviewButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
`Query (${data.dsName}) completed.`
);
// Verfiy Retrieve record operation
cy.get(airTableSelector.operationSelectDropdown)
.click()
.type("Retrieve record{enter}");
cy.get(airTableSelector.baseIdInputField).clearAndTypeOnCodeMirror(
airTable_baseId
);
cy.get(airTableSelector.tableNameInputField).clearAndTypeOnCodeMirror(
airTable_tableName
);
cy.get(airTableSelector.recordIdInputField).clearAndTypeOnCodeMirror(
airTable_recordID
);
cy.get(dataSourceSelector.queryPreviewButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
`Query (${data.dsName}) completed.`
);
// Verfiy Create record operation
cy.get(airTableSelector.operationSelectDropdown)
.click()
.type("Create record{enter}");
cy.get(airTableSelector.baseIdInputField).clearAndTypeOnCodeMirror(
airTable_baseId
);
cy.get(airTableSelector.tableNameInputField).clearAndTypeOnCodeMirror(
airTable_tableName
);
cy.get(airTableSelector.bodyInputField)
.realClick()
.realType('[{"', { force: true, delay: 0 })
.realType("fields", { force: true, delay: 0 })
.realType('": {}', { force: true, delay: 0 });
cy.get(dataSourceSelector.queryPreviewButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
`Query (${data.dsName}) completed.`
);
// Verfiy Update record operation
cy.get('[data-cy="show-ds-popover-button"]').click();
cy.get(".css-4e90k9").type(`${data.dsName}`);
cy.contains(`[id*="react-select-"]`, data.dsName).click();
cy.get('[data-cy="query-rename-input"]').clear().type(data.dsName1);
cy.get(airTableSelector.operationSelectDropdown)
.click()
.type("Update record{enter}");
cy.get(airTableSelector.baseIdInputField).clearAndTypeOnCodeMirror(
airTable_baseId
);
cy.get(airTableSelector.tableNameInputField).clearAndTypeOnCodeMirror(
airTable_tableName
);
cy.get(airTableSelector.recordIdInputField).clearAndTypeOnCodeMirror(
airTable_recordID
);
cy.get(airTableSelector.bodyInputField)
.realClick()
.realType("{", { force: true, delay: 0 })
.realType("{enter}", { force: true, delay: 0 })
.realType('"Phone Number": "555_98"', { force: true, delay: 0 });
cy.get(dataSourceSelector.queryPreviewButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
`Query (${data.dsName1}) completed.`
);
// Verify Delete record operation
cy.get(airTableSelector.operationSelectDropdown)
.click()
.type("Delete record{enter}");
const recordId = Cypress._.uniqueId("recDummy_");
cy.request({
method: "POST",
url: `https://api.airtable.com/v0/${airTable_baseId}/${airTable_tableName}`,
headers: {
Authorization: `Bearer ${Cypress.env("airTable_apikey")}`,
"Content-Type": "application/json",
},
body: {
records: [
{
fields: {
"Employee ID": "E005",
"First Name": "test",
"Last Name": "abc",
Email: "doe@example.com",
"Phone Number": "555-12",
},
},
],
},
}).then((createResponse) => {
const newRecordId = createResponse.body.records[0].id;
cy.get(airTableSelector.operationSelectDropdown)
.click()
.type("Delete record{enter}");
cy.get(airTableSelector.baseIdInputField).clearAndTypeOnCodeMirror(
airTable_baseId
);
cy.get(airTableSelector.tableNameInputField).clearAndTypeOnCodeMirror(
airTable_tableName
);
cy.get(airTableSelector.recordIdInputField).clearAndTypeOnCodeMirror(
newRecordId
);
cy.get(dataSourceSelector.queryPreviewButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
`Query (${data.dsName1}) completed.`
);
});
});
});

View file

@ -0,0 +1,207 @@
import { fake } from "Fixtures/fake";
import { postgreSqlSelector } from "Selectors/postgreSql";
import { pluginSelectors } from "Selectors/plugins";
import { postgreSqlText } from "Texts/postgreSql";
import { amazonSesText } from "Texts/amazonSes";
import { amazonAthenaText } from "Texts/amazonAthena";
import { commonSelectors } from "Selectors/common";
import { commonText } from "Texts/common";
import {
fillDataSourceTextField,
selectAndAddDataSource,
} from "Support/utils/postgreSql";
import {
verifyCouldnotConnectWithAlert,
deleteDatasource,
closeDSModal,
addQuery,
addDsAndAddQuery,
} from "Support/utils/dataSource";
import { openQueryEditor } from "Support/utils/dataSource";
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source amazon athena", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.intercept("POST", "/api/data_queries").as("createQuery");
});
it("Should verify elements on amazon athena connection form", () => {
const Accesskey = Cypress.env("amazonathena_accessKey");
const Secretkey = Cypress.env("amazonathena_secretKey");
const DbName = Cypress.env("amazonathena_DbName");
cy.get(commonSelectors.globalDataSourceIcon).click();
closeDSModal();
cy.get(postgreSqlSelector.allDatasourceLabelAndCount).should(
"have.text",
postgreSqlText.allDataSources()
);
cy.get(postgreSqlSelector.commonlyUsedLabelAndCount).should(
"have.text",
postgreSqlText.commonlyUsed
);
cy.get(postgreSqlSelector.databaseLabelAndCount).should(
"have.text",
postgreSqlText.allDatabase()
);
cy.get(postgreSqlSelector.apiLabelAndCount).should(
"have.text",
postgreSqlText.allApis
);
cy.get(postgreSqlSelector.cloudStorageLabelAndCount).should(
"have.text",
postgreSqlText.allCloudStorage
);
selectAndAddDataSource(
"databases",
amazonAthenaText.AmazonAthena,
data.dsName
);
cy.get(pluginSelectors.amazonAthenaDbName).click().type(DbName);
cy.get(pluginSelectors.amazonsesAccesKey).click().type(" ");
fillDataSourceTextField(
amazonSesText.labelSecretKey,
amazonAthenaText.placeholderSecretKey,
Secretkey
);
cy.get(".react-select__dropdown-indicator").eq(1).click();
cy.get(".react-select__option").contains("US West (N. California)").click();
cy.get(postgreSqlSelector.buttonTestConnection)
.verifyVisibleElement(
"have.text",
postgreSqlText.buttonTextTestConnection
)
.click();
cy.get(postgreSqlSelector.connectionFailedText).verifyVisibleElement(
"have.text",
postgreSqlText.couldNotConnect
);
cy.get(postgreSqlSelector.buttonSave)
.verifyVisibleElement("have.text", postgreSqlText.buttonTextSave)
.click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
deleteDatasource(`cypress-${data.dsName}-Amazon-Athena`);
});
it("Should verify the functionality of amazon athena connection form.", () => {
const Accesskey = Cypress.env("amazonathena_accessKey");
const Secretkey = Cypress.env("amazonathena_secretKey");
const DbName = Cypress.env("amazonathena_DbName");
selectAndAddDataSource(
"databases",
amazonAthenaText.AmazonAthena,
data.dsName
);
cy.get(pluginSelectors.amazonAthenaDbName).click().type(DbName);
cy.get(pluginSelectors.amazonsesAccesKey).click().type(Accesskey);
fillDataSourceTextField(
amazonSesText.labelSecretKey,
amazonAthenaText.placeholderSecretKey,
Secretkey
);
cy.get(".react-select__dropdown-indicator").eq(1).click();
cy.get(".react-select__option").contains("US West (N. California)").click();
cy.get(postgreSqlSelector.buttonTestConnection).click();
cy.get(postgreSqlSelector.textConnectionVerified, {
timeout: 10000,
}).should("have.text", postgreSqlText.labelConnectionVerified);
cy.get(postgreSqlSelector.buttonSave).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
deleteDatasource(`cypress-${data.dsName}-amazon-Athena`);
});
it("Should able to run the query with valid conection", () => {
const Accesskey = Cypress.env("amazonathena_accessKey");
const Secretkey = Cypress.env("amazonathena_secretKey");
const DbName = Cypress.env("amazonathena_DbName");
selectAndAddDataSource(
"databases",
amazonAthenaText.AmazonAthena,
data.dsName
);
cy.get(pluginSelectors.amazonAthenaDbName).click().type(DbName);
fillDataSourceTextField(
amazonAthenaText.labelAccesskey,
amazonAthenaText.placeholderEnteraAccessKey,
Cypress.env("amazonathena_accessKey")
);
fillDataSourceTextField(
amazonAthenaText.labelSecretKey,
amazonAthenaText.placeholderSecretKey,
Cypress.env("amazonathena_secretKey")
);
cy.get(".react-select__dropdown-indicator").eq(1).click();
cy.get(".react-select__option").contains("US West (N. California)").click();
cy.get(postgreSqlSelector.buttonTestConnection).click();
cy.get(postgreSqlSelector.textConnectionVerified, {
timeout: 10000,
}).should("have.text", postgreSqlText.labelConnectionVerified);
cy.get(postgreSqlSelector.buttonSave).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.dsName}-amazon-athena-button"]`
).verifyVisibleElement("have.text", `cypress-${data.dsName}-amazon-athena`);
cy.wait(1000);
cy.get(commonSelectors.dashboardIcon).click();
cy.get(commonSelectors.appCreateButton).click();
cy.get(commonSelectors.appNameInput).click().type(data.dsName);
cy.get(commonSelectors.createAppButton).click();
cy.skipWalkthrough();
cy.get('[data-cy="show-ds-popover-button"]').click();
cy.get(".css-4e90k9").type(`${data.dsName}`);
cy.contains(`[id*="react-select-"]`, data.dsName).click();
cy.get('[data-cy="query-rename-input"]').clear().type(data.dsName);
cy.get('[data-cy="query-input-field"]').clearAndTypeOnCodeMirror(
"SHOW DATABASES;"
);
cy.get(dataSourceSelector.queryPreviewButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
`Query (${data.dsName}) completed.`
);
});
});

View file

@ -0,0 +1,204 @@
import { fake } from "Fixtures/fake";
import { postgreSqlSelector } from "Selectors/postgreSql";
import { pluginSelectors } from "Selectors/plugins";
import { postgreSqlText } from "Texts/postgreSql";
import { amazonSesText } from "Texts/amazonSes";
import { commonSelectors } from "Selectors/common";
import { commonText } from "Texts/common";
import {
fillDataSourceTextField,
selectAndAddDataSource,
} from "Support/utils/postgreSql";
import {
verifyCouldnotConnectWithAlert,
deleteDatasource,
closeDSModal,
addQuery,
addDsAndAddQuery,
} from "Support/utils/dataSource";
import { openQueryEditor } from "Support/utils/dataSource";
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source amazon ses", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.intercept("POST", "/api/data_queries").as("createQuery");
});
it("Should verify elements on amazonses connection form", () => {
const Accesskey = Cypress.env("amazonSes_accessKey");
const Secretkey = Cypress.env("amazonSes_secretKey");
cy.get(commonSelectors.globalDataSourceIcon).click();
closeDSModal();
cy.get(postgreSqlSelector.allDatasourceLabelAndCount).should(
"have.text",
postgreSqlText.allDataSources()
);
cy.get(postgreSqlSelector.commonlyUsedLabelAndCount).should(
"have.text",
postgreSqlText.commonlyUsed
);
cy.get(postgreSqlSelector.databaseLabelAndCount).should(
"have.text",
postgreSqlText.allDatabase()
);
cy.get(postgreSqlSelector.apiLabelAndCount).should(
"have.text",
postgreSqlText.allApis
);
cy.get(postgreSqlSelector.cloudStorageLabelAndCount).should(
"have.text",
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", amazonSesText.AmazonSES, data.dsName);
cy.get(".react-select__dropdown-indicator").eq(1).click();
cy.get(".react-select__option").contains("US West (N. California)").click();
cy.get(pluginSelectors.amazonsesAccesKey).click().type(Accesskey);
fillDataSourceTextField(
amazonSesText.labelSecretKey,
"**************",
Secretkey
);
cy.get(postgreSqlSelector.buttonSave)
.verifyVisibleElement("have.text", postgreSqlText.buttonTextSave)
.click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
deleteDatasource(`cypress-${data.dsName}-Amazon-ses`);
});
it("Should verify the functionality of amazonses connection form.", () => {
selectAndAddDataSource("databases", amazonSesText.AmazonSES, data.dsName);
cy.get(".react-select__dropdown-indicator").eq(1).click();
cy.get(".react-select__option").contains("US West (N. California)").click();
fillDataSourceTextField(
amazonSesText.labelAccesskey,
amazonSesText.placeholderAccessKey,
Cypress.env("amazonSes_accessKey")
);
fillDataSourceTextField(
amazonSesText.labelSecretKey,
amazonSesText.placeholderSecretKey,
Cypress.env("amazonSes_secretKey")
);
cy.get(postgreSqlSelector.buttonSave).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.dsName}-amazon-ses-button"]`
).verifyVisibleElement("have.text", `cypress-${data.dsName}-amazon-ses`);
deleteDatasource(`cypress-${data.dsName}-amazon-ses`);
});
it("Should able to run the query with valid conection", () => {
const email = "adish" + "@" + "tooljet.com";
selectAndAddDataSource("databases", amazonSesText.AmazonSES, data.dsName);
cy.get(".react-select__dropdown-indicator").eq(1).click();
cy.get(".react-select__option").contains("US West (N. California)").click();
fillDataSourceTextField(
amazonSesText.labelAccesskey,
amazonSesText.placeholderAccessKey,
Cypress.env("amazonSes_accessKey")
);
fillDataSourceTextField(
amazonSesText.labelSecretKey,
amazonSesText.placeholderSecretKey,
Cypress.env("amazonSes_secretKey")
);
cy.get(postgreSqlSelector.buttonSave).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.dsName}-amazon-ses-button"]`
).verifyVisibleElement("have.text", `cypress-${data.dsName}-amazon-ses`);
cy.wait(1000);
cy.get(commonSelectors.dashboardIcon).click();
cy.get(commonSelectors.appCreateButton).click();
cy.get(commonSelectors.appNameInput).click().type(data.dsName);
cy.get(commonSelectors.createAppButton).click();
cy.skipWalkthrough();
cy.get('[data-cy="show-ds-popover-button"]').click();
cy.get(".css-4e90k9").type(`${data.dsName}`);
cy.contains(`[id*="react-select-"]`, data.dsName).click();
cy.get('[data-cy="query-rename-input"]').clear().type(data.dsName);
cy.get(pluginSelectors.operationDropdown)
.click()
.type("Email service{enter}");
cy.wait(500);
cy.get(pluginSelectors.sendEmailInputField)
.realClick()
.realType('{{["', { force: true, delay: 0 })
.realType("mekhla@tooljet.com", { force: true, delay: 0 });
cy.get(pluginSelectors.ccEmailInputField)
.realClick()
.realType('{{["', { force: true, delay: 0 })
.realType("mani@tooljet.com", { force: true, delay: 0 });
cy.get(pluginSelectors.bccEmailInputField)
.realClick()
.realType('{{["', { force: true, delay: 0 })
.realType("midhun@tooljet.com", { force: true, delay: 0 });
cy.get(pluginSelectors.sendEmailFromInputField)
.realClick()
.realType("adish", { force: true, delay: 0 })
.realType("@", { force: true, delay: 0 })
.realType("tooljet.com", { force: true, delay: 0 });
cy.get(pluginSelectors.emailSubjetInputField).clearAndTypeOnCodeMirror(
"Testmail for amazon ses"
);
cy.get(pluginSelectors.emailbodyInputField).clearAndTypeOnCodeMirror(
"Body text for amazon ses"
);
cy.wait(1000);
cy.get(dataSourceSelector.queryPreviewButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
`Query (${data.dsName}) completed.`
);
});
});

View file

@ -0,0 +1,308 @@
import { fake } from "Fixtures/fake";
import { postgreSqlSelector } from "Selectors/postgreSql";
import { postgreSqlText } from "Texts/postgreSql";
import { appwriteText } from "Texts/appWrite";
import { appWriteSelectors } from "Selectors/Plugins";
import { commonSelectors } from "Selectors/common";
import { commonText } from "Texts/common";
import {
fillDataSourceTextField,
selectAndAddDataSource,
} from "Support/utils/postgreSql";
import {
verifyCouldnotConnectWithAlert,
deleteDatasource,
closeDSModal,
addQuery,
addDsAndAddQuery,
} from "Support/utils/dataSource";
import { openQueryEditor } from "Support/utils/dataSource";
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source AppWrite", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.intercept("POST", "/api/data_queries").as("createQuery");
});
it("Should verify elements on appwrite connection form", () => {
const Host = Cypress.env("appwrite_host");
const ProjectID = Cypress.env("appwrite_projectID");
const DatabaseID = Cypress.env("appwrite_databaseID");
const SecretKey = Cypress.env("appwrite_secretkey");
cy.get(commonSelectors.globalDataSourceIcon).click();
closeDSModal();
cy.get(postgreSqlSelector.commonlyUsedLabelAndCount).should(
"have.text",
postgreSqlText.commonlyUsed
);
cy.get(postgreSqlSelector.apiLabelAndCount).should(
"have.text",
postgreSqlText.allApis
);
cy.get(postgreSqlSelector.cloudStorageLabelAndCount).should(
"have.text",
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", appwriteText.appwrite, data.dsName);
fillDataSourceTextField(
appwriteText.host,
appwriteText.hostPlaceholder,
Host
);
fillDataSourceTextField(
appwriteText.ProjectID,
appwriteText.projectIdPlaceholder,
ProjectID
);
fillDataSourceTextField(
appwriteText.DatabaseID,
appwriteText.databaseIdPlaceholder,
DatabaseID
);
fillDataSourceTextField(
appwriteText.SecretKey,
appwriteText.SecretKeyPlaceholder,
SecretKey
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
cy.get(postgreSqlSelector.textConnectionVerified, {
timeout: 10000,
}).should("have.text", postgreSqlText.labelConnectionVerified);
cy.get(postgreSqlSelector.buttonSave)
.verifyVisibleElement("have.text", postgreSqlText.buttonTextSave)
.click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
deleteDatasource(`cypress-${data.dsName}-Appwrite`);
});
it("Should verify the functionality of appwrite connection form.", () => {
const Host = Cypress.env("appwrite_host");
const ProjectID = Cypress.env("appwrite_projectID");
const DatabaseID = Cypress.env("appwrite_databaseID");
const SecretKey = Cypress.env("appwrite_secretkey");
cy.get(commonSelectors.globalDataSourceIcon).click();
closeDSModal();
selectAndAddDataSource("databases", appwriteText.appwrite, data.dsName);
fillDataSourceTextField(
appwriteText.host,
appwriteText.hostPlaceholder,
Host
);
fillDataSourceTextField(
appwriteText.ProjectID,
appwriteText.projectIdPlaceholder,
ProjectID
);
fillDataSourceTextField(
appwriteText.DatabaseID,
appwriteText.databaseIdPlaceholder,
DatabaseID
);
fillDataSourceTextField(
appwriteText.SecretKey,
appwriteText.SecretKeyPlaceholder,
SecretKey
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
cy.get(postgreSqlSelector.textConnectionVerified, {
timeout: 10000,
}).should("have.text", postgreSqlText.labelConnectionVerified);
cy.get(postgreSqlSelector.buttonSave)
.verifyVisibleElement("have.text", postgreSqlText.buttonTextSave)
.click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
deleteDatasource(`cypress-${data.dsName}-Appwrite`);
});
it("Should be able to run the query with a valid connection", () => {
const Host = Cypress.env("appwrite_host");
const ProjectID = Cypress.env("appwrite_projectID");
const DatabaseID = Cypress.env("appwrite_databaseID");
const SecretKey = Cypress.env("appwrite_secretkey");
const CollectionID = Cypress.env("appwrite_collectionID");
cy.get(commonSelectors.globalDataSourceIcon).click();
closeDSModal();
selectAndAddDataSource("databases", appwriteText.appwrite, data.dsName);
fillDataSourceTextField(
appwriteText.host,
appwriteText.hostPlaceholder,
Host
);
fillDataSourceTextField(
appwriteText.ProjectID,
appwriteText.projectIdPlaceholder,
ProjectID
);
fillDataSourceTextField(
appwriteText.DatabaseID,
appwriteText.databaseIdPlaceholder,
DatabaseID
);
fillDataSourceTextField(
appwriteText.SecretKey,
appwriteText.SecretKeyPlaceholder,
SecretKey
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
cy.get(postgreSqlSelector.textConnectionVerified, {
timeout: 10000,
}).should("have.text", postgreSqlText.labelConnectionVerified);
cy.get(postgreSqlSelector.buttonSave)
.verifyVisibleElement("have.text", postgreSqlText.buttonTextSave)
.click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.dsName}-appwrite-button"]`
).verifyVisibleElement("have.text", `cypress-${data.dsName}-appwrite`);
cy.wait(1000);
cy.get(commonSelectors.dashboardIcon).click();
cy.get(commonSelectors.appCreateButton).click();
cy.get(commonSelectors.appNameInput).click().type(data.dsName);
cy.get(commonSelectors.createAppButton).click();
cy.skipWalkthrough();
cy.get('[data-cy="show-ds-popover-button"]').click();
cy.get(".css-4e90k9").type(`${data.dsName}`);
cy.contains(`[id*="react-select-"]`, data.dsName).click();
cy.get('[data-cy="query-rename-input"]').clear().type(data.dsName);
// Create API document for delete operation
cy.request({
method: "POST",
url: `https://cloud.appwrite.io/v1/databases/${DatabaseID}/collections/${CollectionID}/documents`,
headers: {
"X-Appwrite-Project": ProjectID,
"X-Appwrite-Key": SecretKey,
"Content-Type": "application/json",
},
body: {
documentId: "unique()",
data: {
User_name: "test",
User_ID: 30,
},
permissions: ['read("any")'],
},
}).then((response) => {
expect(response.status).to.eq(201);
cy.wrap(response.body.$id).as("documentId");
});
// Verify all operations
const operations = [
"List documents",
"Get document",
"Add Document to Collection",
"Update document",
"Delete document",
];
cy.get("@documentId").then((documentId) => {
operations.forEach((operation) => {
cy.get(".react-select__input")
.eq(1)
.type(`${operation}{enter}`, { force: true });
if (operation === "Get document") {
cy.get(appWriteSelectors.collectionId).clearAndTypeOnCodeMirror(
CollectionID
);
cy.get(appWriteSelectors.documentId).clearAndTypeOnCodeMirror(
Cypress.env("appwrite_documentID")
);
}
if (operation === "Add Document to Collection") {
cy.get(appWriteSelectors.collectionId).clearAndTypeOnCodeMirror(
CollectionID
);
cy.get(appWriteSelectors.bodyInput).clearAndTypeOnCodeMirror(
'{"User_name": "John Updated", "User_ID": 35}'
);
}
if (operation === "Update document") {
cy.get(appWriteSelectors.collectionId).clearAndTypeOnCodeMirror(
CollectionID
);
cy.get(appWriteSelectors.documentId).clearAndTypeOnCodeMirror(
Cypress.env("appwrite_documentID")
);
cy.get(appWriteSelectors.bodyInput).clearAndTypeOnCodeMirror(
'{"User_name": "John Updated", "User_ID": 35}'
);
}
if (operation === "List documents") {
cy.get(appWriteSelectors.collectionId).clearAndTypeOnCodeMirror(
CollectionID
);
}
if (operation === "Delete document") {
cy.get(appWriteSelectors.collectionId).clearAndTypeOnCodeMirror(
CollectionID
);
cy.get(appWriteSelectors.documentId).clearAndTypeOnCodeMirror(
documentId
);
}
cy.get(dataSourceSelector.queryPreviewButton).click();
// Verify toast message
cy.verifyToastMessage(
commonSelectors.toastMessage,
`Query (${data.dsName}) completed.`
);
});
});
});
});

View file

@ -0,0 +1,219 @@
import { fake } from "Fixtures/fake";
import { postgreSqlSelector } from "Selectors/postgreSql";
import { pluginSelectors, baserowSelectors } from "Selectors/plugins";
import { postgreSqlText } from "Texts/postgreSql";
import { amazonSesText } from "Texts/amazonSes";
import { baseRowText } from "Texts/baseRow";
import { amazonAthenaText } from "Texts/amazonAthena";
import { commonSelectors } from "Selectors/common";
import { commonText } from "Texts/common";
import {
fillDataSourceTextField,
selectAndAddDataSource,
} from "Support/utils/postgreSql";
import {
verifyCouldnotConnectWithAlert,
deleteDatasource,
closeDSModal,
addQuery,
addDsAndAddQuery,
} from "Support/utils/dataSource";
import { openQueryEditor } from "Support/utils/dataSource";
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source baserow", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.intercept("POST", "/api/data_queries").as("createQuery");
});
it("Should verify elements on baserow connection form", () => {
const Apikey = Cypress.env("baserow_apikey");
cy.get(commonSelectors.globalDataSourceIcon).click();
closeDSModal();
cy.get(postgreSqlSelector.allDatasourceLabelAndCount).should(
"have.text",
postgreSqlText.allDataSources()
);
cy.get(postgreSqlSelector.commonlyUsedLabelAndCount).should(
"have.text",
postgreSqlText.commonlyUsed
);
cy.get(postgreSqlSelector.databaseLabelAndCount).should(
"have.text",
postgreSqlText.allDatabase()
);
cy.get(postgreSqlSelector.apiLabelAndCount).should(
"have.text",
postgreSqlText.allApis
);
cy.get(postgreSqlSelector.cloudStorageLabelAndCount).should(
"have.text",
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", baseRowText.baserow, data.dsName);
fillDataSourceTextField(
baseRowText.lableApiToken,
baseRowText.placeholderApiToken,
Apikey
);
cy.get(".react-select__control").eq(1).click();
cy.get(".react-select__option").contains("Baserow Cloud").click();
cy.get(postgreSqlSelector.buttonSave)
.verifyVisibleElement("have.text", postgreSqlText.buttonTextSave)
.click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
deleteDatasource(`cypress-${data.dsName}-baserow`);
});
it("Should verify the functionality of baserow connection form.", () => {
const Apikey = Cypress.env("baserow_apikey");
selectAndAddDataSource("databases", baseRowText.baserow, data.dsName);
fillDataSourceTextField(
baseRowText.lableApiToken,
baseRowText.placeholderApiToken,
Apikey
);
cy.get(".react-select__control").eq(1).click();
cy.get(".react-select__option").contains("Baserow Cloud").click();
cy.get(postgreSqlSelector.buttonSave)
.verifyVisibleElement("have.text", postgreSqlText.buttonTextSave)
.click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
deleteDatasource(`cypress-${data.dsName}-baserow`);
});
it("Should be able to run the query with a valid connection", () => {
const baserowTableID = Cypress.env("baserow_tableid");
const baserowRowID = Cypress.env("baserow_rowid");
const Apikey = Cypress.env("baserow_apikey");
selectAndAddDataSource("databases", baseRowText.baserow, data.dsName);
fillDataSourceTextField(
baseRowText.lableApiToken,
baseRowText.placeholderApiToken,
Apikey
);
cy.get(".react-select__control").eq(1).click();
cy.get(".react-select__option").contains("Baserow Cloud").click();
cy.get(postgreSqlSelector.buttonSave)
.verifyVisibleElement("have.text", postgreSqlText.buttonTextSave)
.click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.dsName}-baserow-button"]`
).verifyVisibleElement("have.text", `cypress-${data.dsName}-baserow`);
cy.wait(1000);
cy.log("Baserow Table ID:", baserowTableID);
cy.log("Row ID:", baserowRowID);
cy.log("API Key:", Apikey);
if (!baserowTableID || !Apikey) {
throw new Error("Missing required environment variables!");
}
cy.request({
method: "POST",
url: `https://api.baserow.io/api/database/rows/table/${baserowTableID}/`,
headers: { Authorization: `Token ${Apikey}` },
body: {
field_1: "Sample Data",
field_2: "Another Value",
},
}).then((response) => {
expect(response.status).to.eq(200);
const rowId = response.body.id;
cy.get(commonSelectors.dashboardIcon).click();
cy.get(commonSelectors.appCreateButton).click();
cy.get(commonSelectors.appNameInput).click().type(data.dsName);
cy.get(commonSelectors.createAppButton).click();
cy.skipWalkthrough();
cy.get('[data-cy="show-ds-popover-button"]').click();
cy.get(".css-4e90k9").type(`${data.dsName}`);
cy.contains(`[id*="react-select-"]`, data.dsName).click();
cy.get('[data-cy="query-rename-input"]').clear().type(data.dsName);
// Verify delete operation (Need to uncomment after bug fixes)
// cy.get('[data-cy="operation-select-dropdown"]').click();
// cy.get(".react-select__option").contains("Delete row").click();
// cy.get(baserowSelectors.baserowTabelId).clearAndTypeOnCodeMirror(baserowTableID);
// cy.get(baserowSelectors.rowIdinputfield).clearAndTypeOnCodeMirror(rowId.toString());
// cy.get(dataSourceSelector.queryPreviewButton).click();
// cy.verifyToastMessage(commonSelectors.toastMessage, `Query (${data.dsName}) completed.`);
});
// Verify other operations
const operations = [
"List fields",
"List rows",
"Get row",
"Create row",
"Update row",
"Move row",
];
operations.forEach((operation) => {
cy.get(pluginSelectors.operationDropdown).click();
cy.get(".react-select__option").contains(operation).click();
cy.get(baserowSelectors.table).clearAndTypeOnCodeMirror(baserowTableID);
if (operation === "Get row") {
cy.get(baserowSelectors.rowIdinputfield).clearAndTypeOnCodeMirror(
baserowRowID
);
}
if (operation === "Move row") {
cy.get('[data-cy="before-id-input-field"]').clearAndTypeOnCodeMirror(
"1"
);
}
cy.get(dataSourceSelector.queryPreviewButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
`Query (${data.dsName}) completed.`
);
});
});
});

View file

@ -0,0 +1,278 @@
import { fake } from "Fixtures/fake";
import { postgreSqlSelector } from "Selectors/postgreSql";
import { postgreSqlText } from "Texts/postgreSql";
import { minioText } from "Texts/minio";
import { minioSelectors } from "Selectors/Plugins";
import { commonSelectors } from "Selectors/common";
import { commonText } from "Texts/common";
import {
fillDataSourceTextField,
selectAndAddDataSource,
} from "Support/utils/postgreSql";
import {
verifyCouldnotConnectWithAlert,
deleteDatasource,
closeDSModal,
addQuery,
addDsAndAddQuery,
} from "Support/utils/dataSource";
import { openQueryEditor } from "Support/utils/dataSource";
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source minio", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
});
it("Should verify elements on minio connection form", () => {
const Host = Cypress.env("minio_host");
const Port = Cypress.env("minio_port");
const AccessKey = Cypress.env("minio_accesskey");
const SecretKey = Cypress.env("minio_secretkey");
cy.get(commonSelectors.globalDataSourceIcon).click();
closeDSModal();
cy.get(postgreSqlSelector.commonlyUsedLabelAndCount).should(
"have.text",
postgreSqlText.commonlyUsed
);
cy.get(postgreSqlSelector.apiLabelAndCount).should(
"have.text",
postgreSqlText.allApis
);
cy.get(postgreSqlSelector.cloudStorageLabelAndCount).should(
"have.text",
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", minioText.minio, data.dsName);
fillDataSourceTextField(
minioText.hostLabel,
minioText.hostInputPlaceholder,
Host
);
fillDataSourceTextField(
minioText.portLabel,
minioText.portPlaceholder,
Port
);
cy.get(`[${minioSelectors.sslToggle}]`).click();
fillDataSourceTextField(
minioText.labelAccesskey,
minioText.placeholderAccessKey,
AccessKey
);
fillDataSourceTextField(
minioText.labelSecretKey,
minioText.placeholderSecretKey,
SecretKey
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
cy.get(postgreSqlSelector.textConnectionVerified, {
timeout: 10000,
}).should("have.text", postgreSqlText.labelConnectionVerified);
cy.get(postgreSqlSelector.buttonSave)
.verifyVisibleElement("have.text", postgreSqlText.buttonTextSave)
.click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
deleteDatasource(`cypress-${data.dsName}-minio`);
});
it("Should verify functionality of minio connection form", () => {
const Host = Cypress.env("minio_host");
const Port = Cypress.env("minio_port");
const AccessKey = Cypress.env("minio_accesskey");
const SecretKey = Cypress.env("minio_secretkey");
cy.get(commonSelectors.globalDataSourceIcon).click();
closeDSModal();
selectAndAddDataSource("databases", minioText.minio, data.dsName);
fillDataSourceTextField(
minioText.hostLabel,
minioText.hostInputPlaceholder,
Host
);
fillDataSourceTextField(
minioText.portLabel,
minioText.portPlaceholder,
Port
);
cy.get(`[${minioSelectors.sslToggle}]`).click();
fillDataSourceTextField(
minioText.labelAccesskey,
minioText.placeholderAccessKey,
AccessKey
);
fillDataSourceTextField(
minioText.labelSecretKey,
minioText.placeholderSecretKey,
SecretKey
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
cy.get(postgreSqlSelector.textConnectionVerified, {
timeout: 10000,
}).should("have.text", postgreSqlText.labelConnectionVerified);
cy.get(postgreSqlSelector.buttonSave)
.verifyVisibleElement("have.text", postgreSqlText.buttonTextSave)
.click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
deleteDatasource(`cypress-${data.dsName}-minio`);
});
it("Should be able to run the query with a valid connection", () => {
const Host = Cypress.env("minio_host");
const Port = Cypress.env("minio_port");
const AccessKey = Cypress.env("minio_accesskey");
const SecretKey = Cypress.env("minio_secretkey");
cy.get(commonSelectors.globalDataSourceIcon).click();
closeDSModal();
selectAndAddDataSource("databases", minioText.minio, data.dsName);
fillDataSourceTextField(
minioText.hostLabel,
minioText.hostInputPlaceholder,
Host
);
fillDataSourceTextField(
minioText.portLabel,
minioText.portPlaceholder,
Port
);
cy.get(`[${minioSelectors.sslToggle}]`).click();
fillDataSourceTextField(
minioText.labelAccesskey,
minioText.placeholderAccessKey,
AccessKey
);
fillDataSourceTextField(
minioText.labelSecretKey,
minioText.placeholderSecretKey,
SecretKey
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
cy.get(postgreSqlSelector.textConnectionVerified, {
timeout: 10000,
}).should("have.text", postgreSqlText.labelConnectionVerified);
cy.get(postgreSqlSelector.buttonSave)
.verifyVisibleElement("have.text", postgreSqlText.buttonTextSave)
.click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
cy.get(commonSelectors.dashboardIcon).click();
cy.get(commonSelectors.appCreateButton).click();
cy.get(commonSelectors.appNameInput).click().type(data.dsName);
cy.get(commonSelectors.createAppButton).click();
cy.skipWalkthrough();
cy.get('[data-cy="show-ds-popover-button"]').click();
cy.get(".css-4e90k9").type(`${data.dsName}`);
cy.contains(`[id*="react-select-"]`, data.dsName).click();
cy.get('[data-cy="query-rename-input"]').clear().type(data.dsName);
const operationsMinio = [
"List buckets",
"Put object",
"List objects in a bucket",
"Read object",
"Presigned url for download",
"Presigned url for upload",
"Remove object",
];
operationsMinio.forEach((operation) => {
cy.get(".react-select__input")
.eq(1)
.type(`${operation}{enter}`, { force: true });
if (operation === "List objects in a bucket") {
cy.get(minioSelectors.bucketNameInputField).clearAndTypeOnCodeMirror(
minioText.bucketName
);
}
if (operation === "Read object" || operation === "Remove object") {
cy.get(minioSelectors.bucketNameInputField).clearAndTypeOnCodeMirror(
minioText.bucketName
);
cy.get(minioSelectors.objectNameInputField).clearAndTypeOnCodeMirror(
minioText.objectName
);
}
if (operation === "Put object") {
cy.get(minioSelectors.bucketNameInputField).clearAndTypeOnCodeMirror(
minioText.bucketName
);
cy.get(minioSelectors.objectNameInputField).clearAndTypeOnCodeMirror(
minioText.objectName
);
cy.get(minioSelectors.contentTypeInputField).clearAndTypeOnCodeMirror(
'"string"'
);
cy.get(minioSelectors.dataInput).clearAndTypeOnCodeMirror(`test`);
}
if (
operation === "Presigned url for download" ||
operation === "Presigned url for upload"
) {
cy.get(minioSelectors.bucketNameInputField).clearAndTypeOnCodeMirror(
minioText.bucketName
);
cy.get(minioSelectors.objectNameInputField).clearAndTypeOnCodeMirror(
minioText.objectName
);
}
cy.get(dataSourceSelector.queryPreviewButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
`Query (${data.dsName}) completed.`
);
});
});
});

View file

@ -123,7 +123,7 @@ describe("Data sources", () => {
);
cy.clearAndType(
'[data-cy="data-source-name-input-filed"]',
'[data-cy="data-source-name-input-field"]',
postgreSqlText.psqlName
);

View file

@ -19,7 +19,8 @@ const data = {};
describe("Data sources AWS S3", () => {
beforeEach(() => {
cy.appUILogin();
cy.apiLogin();
cy.defaultWorkspaceLogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -108,7 +108,7 @@ describe("Data sources", () => {
selectAndAddDataSource(postgreSqlText.postgreSQL);
cy.clearAndType(
'[data-cy="data-source-name-input-filed"]',
'[data-cy="data-source-name-input-field"]',
postgreSqlText.psqlName
);

View file

@ -0,0 +1,189 @@
import { fake } from "Fixtures/fake";
import { postgreSqlSelector } from "Selectors/postgreSql";
import { postgreSqlText } from "Texts/postgreSql";
import { twilioText } from "Texts/twilio";
import { twilioSelectors } from "Selectors/Plugins";
import { commonSelectors } from "Selectors/common";
import { commonText } from "Texts/common";
import {
fillDataSourceTextField,
selectAndAddDataSource,
} from "Support/utils/postgreSql";
import {
verifyCouldnotConnectWithAlert,
deleteDatasource,
closeDSModal,
addQuery,
addDsAndAddQuery,
} from "Support/utils/dataSource";
import { openQueryEditor } from "Support/utils/dataSource";
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
import { pluginSelectors } from "Selectors/plugins";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source Twilio", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
});
it("Should verify elements on Twilio connection form", () => {
const AuthToken = Cypress.env("twilio_auth_token");
const AccountSID = Cypress.env("twilio_account_SID");
const MessageSID = Cypress.env("twilio_messaging_service_SID");
cy.get(commonSelectors.globalDataSourceIcon).click();
closeDSModal();
cy.get(postgreSqlSelector.commonlyUsedLabelAndCount).should(
"have.text",
postgreSqlText.commonlyUsed
);
cy.get(postgreSqlSelector.apiLabelAndCount).should(
"have.text",
postgreSqlText.allApis
);
cy.get(postgreSqlSelector.cloudStorageLabelAndCount).should(
"have.text",
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", twilioText.twilio, data.dsName);
fillDataSourceTextField(
twilioText.authTokenLabel,
twilioText.authTokenPlaceholder,
AuthToken
);
fillDataSourceTextField(
twilioText.accountSidLabel,
twilioText.accountSidPlaceholder,
AccountSID
);
fillDataSourceTextField(
twilioText.messagingSIDLabel,
twilioText.messagingSIDPalceholder,
MessageSID
);
cy.get(postgreSqlSelector.buttonSave)
.verifyVisibleElement("have.text", postgreSqlText.buttonTextSave)
.click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
deleteDatasource(`cypress-${data.dsName}-twilio`);
});
it("Should verify functionality of Twilio connection form", () => {
const AuthToken = Cypress.env("twilio_auth_token");
const AccountSID = Cypress.env("twilio_account_SID");
const MessageSID = Cypress.env("twilio_messaging_service_SID");
cy.get(commonSelectors.globalDataSourceIcon).click();
closeDSModal();
selectAndAddDataSource("databases", twilioText.twilio, data.dsName);
fillDataSourceTextField(
twilioText.authTokenLabel,
twilioText.authTokenPlaceholder,
AuthToken
);
fillDataSourceTextField(
twilioText.accountSidLabel,
twilioText.accountSidPlaceholder,
AccountSID
);
fillDataSourceTextField(
twilioText.messagingSIDLabel,
twilioText.messagingSIDPalceholder,
MessageSID
);
cy.get(postgreSqlSelector.buttonSave)
.verifyVisibleElement("have.text", postgreSqlText.buttonTextSave)
.click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
deleteDatasource(`cypress-${data.dsName}-twilio`);
});
it("Should be able to run the query with a valid connection", () => {
const AuthToken = Cypress.env("twilio_auth_token");
const AccountSID = Cypress.env("twilio_account_SID");
const MessageSID = Cypress.env("twilio_messaging_service_SID");
const MessageNumber = Cypress.env("twilio_message_number");
cy.get(commonSelectors.globalDataSourceIcon).click();
closeDSModal();
selectAndAddDataSource("databases", twilioText.twilio, data.dsName);
fillDataSourceTextField(
twilioText.authTokenLabel,
twilioText.authTokenPlaceholder,
AuthToken
);
fillDataSourceTextField(
twilioText.accountSidLabel,
twilioText.accountSidPlaceholder,
AccountSID
);
fillDataSourceTextField(
twilioText.messagingSIDLabel,
twilioText.messagingSIDPalceholder,
MessageSID
);
cy.get(postgreSqlSelector.buttonSave)
.verifyVisibleElement("have.text", postgreSqlText.buttonTextSave)
.click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
cy.get(commonSelectors.dashboardIcon).click();
cy.get(commonSelectors.appCreateButton).click();
cy.get(commonSelectors.appNameInput).click().type(data.dsName);
cy.get(commonSelectors.createAppButton).click();
cy.skipWalkthrough();
cy.get('[data-cy="show-ds-popover-button"]').click();
cy.get(".css-4e90k9").type(`${data.dsName}`);
cy.contains(`[id*="react-select-"]`, data.dsName).click();
cy.get('[data-cy="query-rename-input"]').clear().type(data.dsName);
cy.get(pluginSelectors.operationDropdown).click().type("Send SMS{enter}");
cy.get(twilioSelectors.toNumberInputField).clearAndTypeOnCodeMirror(
MessageNumber
);
cy.get(twilioSelectors.bodyInput).clearAndTypeOnCodeMirror(
twilioText.messageText
);
cy.get(dataSourceSelector.queryPreviewButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
`Query (${data.dsName}) completed.`
);
});
});

View file

@ -10,7 +10,7 @@ export const connectMongo = () => {
selectAndAddDataSource(mongoDbText.mongoDb);
cy.clearAndType(
'[data-cy="data-source-name-input-filed"]',
'[data-cy="data-source-name-input-field"]',
mongoDbText.cypressMongoDb
);

View file

@ -3,7 +3,7 @@ import { commonSelectors } from "../../constants/selectors/common";
export const searchPage = (pageName) => {
cy.get('[title="Search"]').click();
cy.get('[data-cy="search-input-filed"]').type(pageName);
cy.get('[data-cy="search-input-field"]').type(pageName);
};
export const clearSearch = () => {

View file

@ -48,27 +48,25 @@ export const selectAndAddDataSource = (
dataSourceName
) => {
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.wait(1000)
cy.wait(1000);
cy.get(`[data-cy="${cyParamName(dscategory)}-datasource-button"]`).click();
cy.wait(500)
cy.wait(500);
cy.get(postgreSqlSelector.dataSourceSearchInputField).type(dataSource);
cy.get(`[data-cy="data-source-${(dataSource).toLowerCase()}"]`)
cy.get(`[data-cy="data-source-${dataSource.toLowerCase()}"]`)
.parent()
.within(() => {
cy.get(
`[data-cy="data-source-${(
dataSource
).toLowerCase()}"]>>>.datasource-card-title`
`[data-cy="data-source-${dataSource.toLowerCase()}"]>>>.datasource-card-title`
).realHover("mouse");
cy.get(
`[data-cy="${cyParamName(dataSource).toLowerCase()}-add-button"]`
).click();
});
cy.wait(1000)
cy.get(postgreSqlSelector.buttonSave).should("be.disabled")
cy.wait(1000);
cy.get(postgreSqlSelector.buttonSave).should("be.disabled");
cy.clearAndType(
'[data-cy="data-source-name-input-filed"]',
'[data-cy="data-source-name-input-field"]',
cyParamName(`cypress-${dataSourceName}-${dataSource}`)
);
cy.get(postgreSqlSelector.buttonSave).click();
@ -184,4 +182,4 @@ export const addWidgetsToAddUser = () => {
addEventHandlerToRunQuery("add_data_using_widgets");
};
export const addListviewToVerifyData = () => { };
export const addListviewToVerifyData = () => {};

View file

@ -62,8 +62,40 @@ FROM debian:11
RUN apt-get update -yq \
&& apt-get install curl gnupg zip -yq \
&& apt-get install -yq build-essential \
&& apt -y install redis \
&& apt-get clean -y
# Install required dependencies for downloading and extracting files
RUN apt-get update && apt-get install -y \
curl tar xz-utils postgresql postgresql-contrib postgresql-client && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Install PostgREST from official Docker image
COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin
RUN apt-get update && apt-get install -y supervisor
# Create supervisord configuration file
RUN echo "[supervisord]\n" \
"nodaemon=true\n" \
"\n" \
"[program:postgrest]\n" \
"command=/bin/postgrest \n" \
"autostart=true\n" \
"autorestart=true\n" \
"stdout_logfile=/dev/stdout\n" \
"stderr_logfile=/dev/stderr\n" \
"stdout_logfile_maxbytes=0\n" \
"stderr_logfile_maxbytes=0\n" \
"\n" | sed 's/ //' > /etc/supervisor/conf.d/supervisord.conf
# Create a wrapper for PostgREST to prefix its logs
RUN mv /bin/postgrest /bin/postgrest-original && \
echo '#!/bin/bash\n\
exec /bin/postgrest-original "$@" 2>&1 | sed "s/^/[PostgREST] /"\n\
' > /bin/postgrest && \
chmod +x /bin/postgrest
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 \
@ -152,6 +184,25 @@ RUN mkdir -p /tmp/.npm/npm-cache/_logs \
&& chmod g+s /tmp/.npm/npm-cache/_logs \
&& chmod -R g=u /tmp/.npm/npm-cache/_logs
# Create Redis data, log, and configuration directories
RUN mkdir -p /var/lib/redis /var/log/redis /etc/redis \
&& chown -R appuser:0 /var/lib/redis /var/log/redis /etc/redis \
&& chmod g+s /var/lib/redis /var/log/redis /etc/redis \
&& chmod -R g=u /var/lib/redis /var/log/redis /etc/redis
# Set permissions for PostgREST binary
RUN chown appuser:0 /bin/postgrest && chmod u+x /bin/postgrest && chmod g=u /bin/postgrest
RUN touch /tmp/postgrest.conf \
&& chown appuser:0 /tmp/postgrest.conf \
&& chmod 640 /tmp/postgrest.conf
# Create PostgREST data, log, and configuration directories
RUN mkdir -p /var/lib/postgrest /var/log/postgrest /etc/postgrest \
&& chown -R appuser:0 /var/lib/postgrest /var/log/postgrest /etc/postgrest \
&& chmod g+s /var/lib/postgrest /var/log/postgrest /etc/postgrest \
&& chmod -R g=u /var/lib/postgrest /var/log/postgrest /etc/postgrest
ENV HOME=/home/appuser
# Switch back to appuser

@ -1 +1 @@
Subproject commit d93ee7e1318f044ef2327671b8b257648071453d
Subproject commit 715a830c7a8d75efc7f77106292d9e4499005b69

View file

@ -336,7 +336,7 @@ const EditorInput = ({
<div
ref={currentEditorHeightRef}
className={`cm-codehinter ${darkMode && 'cm-codehinter-dark-themed'} ${disabled ? 'disabled-cursor' : ''}`}
data-cy={`${cyLabel}-input-field`}
data-cy={`${cyLabel.replace(/_/g, '-')}-input-field`}
>
{/* sticky element to position the preview box correctly on top without flowing out of container */}
<div

View file

@ -572,6 +572,7 @@ const DynamicForm = ({
'd-flex': isHorizontalLayout,
'dynamic-form-row': isHorizontalLayout,
})}
data-cy={`${key.replace(/_/g, '-')}-section`}
key={key}
>
{!isSpecificComponent && (
@ -628,6 +629,7 @@ const DynamicForm = ({
{...getElementProps(obj[key])}
{...computedProps[propertyKey]}
data-cy={`${String(label).toLocaleLowerCase().replace(/\s+/g, '-')}-text-field`}
dataCy={obj[key].key.replace(/_/g, '-')}
//to be removed after whole ui is same
isHorizontalLayout={isHorizontalLayout}
/>
@ -669,7 +671,7 @@ const DynamicForm = ({
</label>
)}
<div data-cy={'query-select-dropdown'} className={cx({ 'flex-grow-1': isHorizontalLayout })}>
<div data-cy={`${String(flipComponentDropdown.label).toLocaleLowerCase().replace(/\s+/g, '-')}-select-dropdown`} className={cx({ 'flex-grow-1': isHorizontalLayout })}>
<Select
{...getElementProps(flipComponentDropdown)}
styles={computeSelectStyles ? computeSelectStyles('100%') : {}}

View file

@ -29,7 +29,7 @@ export default function EncryptedFieldWrapper({
return (
<>
<div className="d-flex align-items-center mt-3">
<label className="form-label">{label}</label>
<label className="form-label" data-cy={`label-${String(label).toLowerCase().replace(/\s+/g, '-')}`}>{label}</label>
<div className="mx-1 col">
<ButtonSolid
className="datasource-edit-btn mb-2"
@ -38,6 +38,7 @@ export default function EncryptedFieldWrapper({
target="_blank"
rel="noreferrer"
onClick={() => handleEncryptedFieldsToggle(name)}
data-cy={`button-${(disabled ? 'Edit' : 'Cancel').toLowerCase().replace(/\s+/g, '-')}`}
>
{disabled ? 'Edit' : 'Cancel'}
</ButtonSolid>

View file

@ -1,5 +1,5 @@
import React, { useEffect } from 'react';
import cx from 'classnames';
import React, { useEffect } from "react";
import cx from "classnames";
const AccordionItem = ({ open = true, index, title, children }) => {
const [show, setShow] = React.useState(open);
@ -13,7 +13,7 @@ const AccordionItem = ({ open = true, index, title, children }) => {
function isNotEmpty(item) {
if (Array.isArray(item)) {
return item.some(isNotEmpty); // Check if any element in the array is not empty
} else if (typeof item === 'object') {
} else if (typeof item === "object") {
return Object.keys(item).some((key) => isNotEmpty(item[key])); // Check if any key in the object is not empty
} else {
return Boolean(item); // Check if the item itself is truthy
@ -23,7 +23,7 @@ const AccordionItem = ({ open = true, index, title, children }) => {
function removeEmptyItems(input) {
if (Array.isArray(input)) {
return input.filter(isNotEmpty);
} else if (typeof input === 'object') {
} else if (typeof input === "object") {
return isNotEmpty(input) ? input : null;
} else {
return input;
@ -31,22 +31,38 @@ const AccordionItem = ({ open = true, index, title, children }) => {
}
return (
<div className="accordion-item">
<h2 className="accordion-header" id={`heading-${index}`} data-cy={`widget-accordion-${title.toLowerCase()}`}>
<div className={cx('accordion-button inspector')}>
<span className="text-capitalize accordion-title-text">{title}</span>
<h2
className="accordion-header"
id={`heading-${index}`}
data-cy={`widget-accordion-${String(title)
.toLowerCase()
.replace(/\s+/g, "-")}`}
>
<div className={cx("accordion-button inspector")}>
<span
className="text-capitalize accordion-title-text"
data-cy={`label-${String(title)
.toLowerCase()
.replace(/\s+/g, "-")}`}
>
{title}
</span>
<div
data-cy={`${String(title)
.toLowerCase()
.replace(/\s+/g, "-")}-collapse-button`}
type="button"
data-bs-toggle="collapse"
data-bs-target={`collapse-${index}`}
aria-expanded="false"
className={cx('accordion-item-trigger', { collapsed: !show })}
className={cx("accordion-item-trigger", { collapsed: !show })}
onClick={() => setShow((prev) => !prev)}
></div>
</div>
</h2>
<div
id={`collapse-${index}`}
className={cx('accordion-collapse collapse', { show })}
className={cx("accordion-collapse collapse", { show })}
data-bs-parent="#accordion-example"
>
<div className="accordion-body">{newChildren}</div>

View file

@ -14,13 +14,15 @@ export default ({
workspaceConstants,
isDisabled,
width,
dataCy,
}) => {
const darkMode = localStorage.getItem('darkMode') === 'true';
return (
<div className="table-content-wrapper">
{options.length === 0 && (
<div className="empty-key-value">
<div className="empty-key-value"
data-cy="label-empty-key-value">
<InfoIcon style={{ width: '16px', marginRight: '5px' }} />
<span>There are no key value pairs added</span>
</div>
@ -29,6 +31,7 @@ export default ({
{options.map((option, index) => (
<div className="d-flex align-items-top row-container query-manager-border-color" key={index}>
<Input
data-cy={`${dataCy}-key-input-field-${index}`}
type="text"
className="input-control"
onChange={(e) => keyValuePairValueChanged(e.target.value, 0, index)}
@ -47,6 +50,7 @@ export default ({
/>
<Input
data-cy={`${dataCy}-value-input-field-${index}`}
type="text"
value={option[1]}
placeholder="Value"
@ -66,6 +70,7 @@ export default ({
/>
<button
data-cy={`${dataCy}-delete-button-${index}`}
className={`d-flex justify-content-center align-items-center delete-field-option bg-transparent border-0 rounded-0 border-top border-bottom border-end rounded-end ${
darkMode ? 'delete-field-option-dark' : ''
}`}
@ -81,6 +86,7 @@ export default ({
<div className="d-flex mb-2" style={{ height: '16px' }}>
<ButtonSolid
data-cy={`${dataCy}-add-more-button`}
variant="ghostBlue"
size="sm"
onClick={() => addNewKeyValuePair(options)}

View file

@ -1,21 +1,22 @@
import React from 'react';
import _ from 'lodash';
import QueryEditor from './QueryEditor';
import SourceEditor from './SourceEditor';
import { deepClone } from '@/_helpers/utilities/utils.helpers';
import React from "react";
import _ from "lodash";
import QueryEditor from "./QueryEditor";
import SourceEditor from "./SourceEditor";
import { deepClone } from "@/_helpers/utilities/utils.helpers";
export default ({
getter,
options = [['', '']],
options = [["", ""]],
optionchanged,
isRenderedAsQueryEditor,
workspaceConstants,
isDisabled,
buttonText,
width,
dataCy,
}) => {
function addNewKeyValuePair(options) {
const newPairs = [...options, ['', '']];
const newPairs = [...options, ["", ""]];
optionchanged(getter, newPairs);
}
@ -29,7 +30,9 @@ export default ({
if (!isRenderedAsQueryEditor) {
const newOptions = deepClone(options);
newOptions[index][keyIndex] = value;
options.length - 1 === index ? addNewKeyValuePair(newOptions) : optionchanged(getter, newOptions);
options.length - 1 === index
? addNewKeyValuePair(newOptions)
: optionchanged(getter, newOptions);
} else {
let newOptions = deepClone(options);
newOptions[index][keyIndex] = value;
@ -44,11 +47,16 @@ export default ({
keyValuePairValueChanged,
isDisabled,
buttonText,
dataCy,
};
return isRenderedAsQueryEditor ? (
<QueryEditor {...commonProps} />
) : (
<SourceEditor {...commonProps} workspaceConstants={workspaceConstants} width={width} />
<SourceEditor
{...commonProps}
workspaceConstants={workspaceConstants}
width={width}
/>
);
};

View file

@ -70,8 +70,9 @@ const Authentication = ({
return (
<div>
<div className="col-md-12">
<label className="form-label text-muted mt-3">Username</label>
<label className="form-label text-muted mt-3" data-cy="label-username">Username</label>
<Input
data-cy="username-input-field"
type="text"
className="form-control"
onChange={(e) => optionchanged('username', e.target.value)}
@ -90,6 +91,7 @@ const Authentication = ({
label="Password"
>
<Input
data-cy="password-input-field"
type="password"
className="form-control"
onChange={(e) => optionchanged('password', e.target.value)}
@ -113,6 +115,7 @@ const Authentication = ({
label="Token"
>
<Input
data-cy="token-input-field"
type="password"
className="form-control"
onChange={(e) => optionchanged('bearer_token', e.target.value)}

View file

@ -14,8 +14,9 @@ const CommonOAuthFields = ({ clientConfig, tokenConfig, authConfig, workspaceCon
return (
<>
<div className="col-md-12">
<label className="form-label mt-3">Access token URL</label>
<label className="form-label mt-3" data-cy="label-access-token-url">Access token URL</label>
<Input
data-cy="access-token-url-input-field"
type="text"
placeholder="https://api.example.com/oauth/token"
className="form-control"
@ -26,7 +27,7 @@ const CommonOAuthFields = ({ clientConfig, tokenConfig, authConfig, workspaceCon
</div>
<div className="row mt-3">
<div className="col">
<label className="form-label pt-2">Access token URL custom headers</label>
<label className="form-label pt-2" data-cy="label-access-token-url-custom-headers">Access token URL custom headers</label>
</div>
</div>
<Headers
@ -34,10 +35,12 @@ const CommonOAuthFields = ({ clientConfig, tokenConfig, authConfig, workspaceCon
options={access_token_custom_headers}
optionchanged={optionchanged}
workspaceConstants={workspaceConstants}
dataCy={'access-token-url-custom-headers'}
/>
<div className="col-md-12">
<label className="form-label mt-3">Client ID</label>
<label className="form-label mt-3" data-cy="label-client-id">Client ID</label>
<Input
data-cy="client-id-input-field"
type="text"
className="form-control"
onChange={(e) => optionchanged('client_id', e.target.value)}
@ -56,6 +59,7 @@ const CommonOAuthFields = ({ clientConfig, tokenConfig, authConfig, workspaceCon
label="Client secret"
>
<Input
data-cy="client-secret-input-field"
type="password"
className="form-control"
onChange={(e) => optionchanged('client_secret', e.target.value)}
@ -65,8 +69,9 @@ const CommonOAuthFields = ({ clientConfig, tokenConfig, authConfig, workspaceCon
</EncryptedFieldWrapper>
</div>
<div className="col-md-12">
<label className="form-label mt-3">Scope(s)</label>
<label className="form-label mt-3" data-cy="label-scope">Scope(s)</label>
<Input
data-cy="scope-input-field"
type="text"
className="form-control"
onChange={(e) => optionchanged('scopes', e.target.value)}
@ -85,8 +90,9 @@ const ClientCredentialsFields = ({ authConfig, workspaceConfig, handlers }) => {
return (
<div className="col-md-12">
<label className="form-label mt-3">Audience</label>
<label className="form-label mt-3" data-cy="label-audience">Audience</label>
<Input
data-cy="audience-input-field"
type="text"
className="form-control"
onChange={(e) => optionchanged('audience', e.target.value)}
@ -107,7 +113,7 @@ const AuthorizationCode = ({ authConfig, clientConfig, tokenConfig, workspaceCon
return (
<>
<div className="col-md-12">
<label className="form-label mt-3">Add access token to</label>
<label className="form-label mt-3" data-cy="label-add-access-token-to">Add access token to</label>
<Select
options={[{ name: 'Request header', value: 'header' }]}
value={add_token_to}
@ -118,8 +124,9 @@ const AuthorizationCode = ({ authConfig, clientConfig, tokenConfig, workspaceCon
</div>
{add_token_to === 'header' && (
<div className="col-md-12">
<label className="form-label mt-3">Header prefix</label>
<label className="form-label mt-3" data-cy="label-header-prefix">Header prefix</label>
<Input
data-cy="header-prefix-input-field"
type="text"
className="form-control"
onChange={(e) => optionchanged('header_prefix', e.target.value)}
@ -129,8 +136,9 @@ const AuthorizationCode = ({ authConfig, clientConfig, tokenConfig, workspaceCon
</div>
)}
<div className="col-md-12">
<label className="form-label mt-3">Authorization URL</label>
<label className="form-label mt-3" data-cy="label-authorization-url">Authorization URL</label>
<Input
data-cy="authorization-url-input-field"
type="text"
placeholder="https://api.example.com/oauth/authorize"
className="form-control"
@ -141,7 +149,7 @@ const AuthorizationCode = ({ authConfig, clientConfig, tokenConfig, workspaceCon
</div>
<div className="row mt-3">
<div className="col">
<label className="form-label pt-2">Custom authentication parameters</label>
<label className="form-label pt-2" data-cy="label-custom-authentication-parameters">Custom authentication parameters</label>
</div>
</div>
<Headers
@ -149,9 +157,10 @@ const AuthorizationCode = ({ authConfig, clientConfig, tokenConfig, workspaceCon
options={custom_auth_params}
optionchanged={optionchanged}
workspaceConstants={workspaceConstants}
dataCy={'custom-authentication-parameters'}
/>
<div className="col-md-12">
<label className="form-label mt-3">Client authentication</label>
<label className="form-label mt-3" data-cy="label-client-authentication">Client authentication</label>
<Select
options={[
{ name: 'Send as basic auth header', value: 'header' },
@ -166,17 +175,18 @@ const AuthorizationCode = ({ authConfig, clientConfig, tokenConfig, workspaceCon
<div>
<label className="form-check form-switch my-4">
<input
data-cy="authentication-required-for-all-users-toggle-switch"
className="form-check-input"
type="checkbox"
checked={multiple_auth_enabled}
onChange={() => optionchanged('multiple_auth_enabled', !multiple_auth_enabled)}
/>
<span className="form-check-label">Authentication required for all users</span>
<span className="form-check-label" data-cy="label-authentication-required-for-all-users">Authentication required for all users</span>
</label>
</div>
<div className="row mt-3">
<div className="col">
<label className="form-label pt-2">Custom query parameters</label>
<label className="form-label pt-2" data-cy="label-custom-query-parameters">Custom query parameters</label>
</div>
</div>
<Headers
@ -184,6 +194,7 @@ const AuthorizationCode = ({ authConfig, clientConfig, tokenConfig, workspaceCon
options={custom_query_params}
optionchanged={optionchanged}
workspaceConstants={workspaceConstants}
dataCy={'custom-query-parameters'}
/>
</>
);
@ -195,7 +206,7 @@ const OAuthConfiguration = ({ authConfig, clientConfig, tokenConfig, workspaceCo
return (
<div>
<div className="row mt-3">
<label className="form-label">Grant type</label>
<label className="form-label" data-cy="label-grant-type">Grant type</label>
<Select
options={[
{ name: 'Authorization code', value: 'authorization_code' },

View file

@ -95,7 +95,7 @@ export const SearchBox = ({ onChange, ...restProps }) => {
onChange={handleChange}
className="form-control animate-width-change"
placeholder={placeholder ?? t(`globals.search`, 'Search')}
data-cy={`search-input-filed`}
data-cy={`search-input-field`}
/>
</div>
</div>

View file

@ -911,7 +911,7 @@ class DataSourceManagerComponent extends React.Component {
className="form-control-plaintext form-control-plaintext-sm color-slate12"
value={decodeEntities(selectedDataSource.name)}
style={{ width: '160px' }}
data-cy="data-source-name-input-filed"
data-cy="data-source-name-input-field"
autoFocus
autoComplete="off"
disabled={!canUpdateDataSource(selectedDataSource.id)}
@ -1107,6 +1107,7 @@ class DataSourceManagerComponent extends React.Component {
<SolidIcon name="logs" fill="#3E63DD" width="20" style={{ marginRight: '8px' }} />
<a
className="color-primary tj-docs-link tj-text-sm"
data-cy="link-read-documentation"
href={
selectedDataSource?.pluginId && selectedDataSource.pluginId.trim() !== ''
? `https://docs.tooljet.com/docs/marketplace/plugins/marketplace-plugin-${selectedDataSource.kind}/`

@ -1 +1 @@
Subproject commit 1da04eef696345ce9f35d42af92e5d6de992cd85
Subproject commit 0eefbb71a1d5288f49641af5efaaab25970f27d1

View file

@ -8,6 +8,40 @@ if [ -f "./.env" ]; then
export $(grep -v '^#' ./.env | xargs -d '\n') || true
fi
# Start Redis server only if REDIS_HOST is localhost or not set
if [ -z "$REDIS_HOST" ] || [ "$REDIS_HOST" = "localhost" ]; then
echo "Starting Redis server locally..."
redis-server /etc/redis/redis.conf &
elif [ -n "$REDIS_URL" ]; then
echo "REDIS_URL connection is set: $REDIS_URL"
else
echo "Using external Redis at $REDIS_HOST:$REDIS_PORT."
# Validate external Redis connection
if ! ./server/scripts/wait-for-it.sh "$REDIS_HOST:${REDIS_PORT:-6379}" --strict --timeout=300 -- echo "Redis is up"; then
echo "Error: Unable to connect to Redis at $REDIS_HOST:$REDIS_PORT."
exit 1
fi
fi
# Check if PGRST_HOST starts with "localhost"
if [[ "$PGRST_HOST" == localhost:* ]]; then
echo "Starting PostgREST server locally..."
# Generate PostgREST configuration in a writable directory
POSTGREST_CONFIG_PATH="/tmp/postgrest.conf"
echo "db-uri = \"${PGRST_DB_URI}\"" > "$POSTGREST_CONFIG_PATH"
echo "db-pre-config = \"postgrest.pre_config\"" >> "$POSTGREST_CONFIG_PATH"
echo "server-port = \"${PGRST_SERVER_PORT}\"" >> "$POSTGREST_CONFIG_PATH"
# Starting PostgREST
echo "Starting PostgREST..."
postgrest "$POSTGREST_CONFIG_PATH" &
else
echo "Using external PostgREST at $PGRST_HOST."
fi
# Check WORKLOW_WORKER and skip SETUP_CMD if true
if [ "${WORKFLOW_WORKER}" == "true" ]; then
echo "WORKFLOW_WORKER is set to true. Running worker process."
@ -31,19 +65,6 @@ else
./server/scripts/wait-for-it.sh "$PG_HOST:$PG_PORT" --strict --timeout=300 -- echo "PostgreSQL is up"
fi
# Note: This Redis connection check changes are only for EE repo
# Check Redis connection
if [ -z "$REDIS_URL" ]; then
if [ -z "$REDIS_HOST" ] || [ -z "$REDIS_PORT" ]; then
echo "Waiting for Redis connection..."
fi
./server/scripts/wait-for-it.sh $REDIS_HOST:${REDIS_PORT:-6379} --strict --timeout=300 -- echo "Redis is up"
else
echo "REDIS_URL connection is set"
fi
# Run setup command if defined
if [ -n "$SETUP_CMD" ]; then
$SETUP_CMD