Merge branch 'main' into appbuilder/sprint-14

This commit is contained in:
johnsoncherian 2025-07-01 10:01:13 +05:30
commit ca09b8df9c
20 changed files with 1276 additions and 308 deletions

View file

@ -139,6 +139,7 @@ jobs:
context: . context: .
build-args: | build-args: |
CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }} CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }}
BRANCH_NAME=main
file: docker/ee/ee-production.Dockerfile file: docker/ee/ee-production.Dockerfile
push: true push: true
tags: tooljet/tooljet-ee:${{ github.event.release.tag_name }},tooljet/tooljet-ee:ee-lts-latest,tooljet/tooljet:ee-lts-latest,tooljet/tooljet:${{ github.event.release.tag_name }} tags: tooljet/tooljet-ee:${{ github.event.release.tag_name }},tooljet/tooljet-ee:ee-lts-latest,tooljet/tooljet:ee-lts-latest,tooljet/tooljet:${{ github.event.release.tag_name }}
@ -155,6 +156,7 @@ jobs:
context: . context: .
build-args: | build-args: |
CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }} CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }}
BRANCH_NAME=lts
file: docker/ee/ee-production.Dockerfile file: docker/ee/ee-production.Dockerfile
push: true push: true
tags: tooljet/tooljet-ee:${{ github.event.release.tag_name }},tooljet/tooljet-ee:ee-lts-latest,tooljet/tooljet:ee-lts-latest,tooljet/tooljet:${{ github.event.release.tag_name }} tags: tooljet/tooljet-ee:${{ github.event.release.tag_name }},tooljet/tooljet-ee:ee-lts-latest,tooljet/tooljet:ee-lts-latest,tooljet/tooljet:${{ github.event.release.tag_name }}

View file

@ -0,0 +1,47 @@
name: Manual Docker Build and Push
on:
workflow_dispatch:
inputs:
branch_name:
description: 'Git branch to build from'
required: true
default: 'main'
dockerfile_path:
description: 'Path to Dockerfile'
required: true
default: './Dockerfile'
docker_tag:
description: 'Docker tag suffix (e.g., pre-release-14)'
required: true
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch_name }}
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push Docker image
uses: docker/build-push-action@v4
with:
context: .
file: ${{ github.event.inputs.dockerfile_path }}
push: true
tags: tooljet/tj-osv:${{ github.event.inputs.docker_tag }}
platforms: linux/amd64
build-args: |
CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }}

View file

@ -98,8 +98,10 @@ module.exports = defineConfig({
configFile: environment.configFile, configFile: environment.configFile,
specPattern: [ specPattern: [
"cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js", "cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js",
"cypress/e2e/happyPath/platform/ceTestcases/apps/appSlug.cy.js",
"cypress/e2e/happyPath/platform/ceTestcases/apps/!(*appSlug).cy.js",
"cypress/e2e/happyPath/platform/commonTestcases/userManagment/*.cy.js", "cypress/e2e/happyPath/platform/commonTestcases/userManagment/*.cy.js",
"cypress/e2e/happyPath/platform/eeTestcases/**/*.cy.js", "cypress/e2e/happyPath/platform/eeTestcases/workspace/*.cy.js",
], ],
numTestsKeptInMemory: 1, numTestsKeptInMemory: 1,
redirectionLimit: 15, redirectionLimit: 15,

View file

@ -22,7 +22,7 @@ RUN git checkout ${BRANCH_NAME}
RUN git submodule update --init --recursive RUN git submodule update --init --recursive
# Checkout the same branch in submodules if it exists, otherwise stay on default branch # Checkout the same branch in submodules if it exists, otherwise stay on default branch
RUN git submodule foreach 'git checkout ${BRANCH_NAME} || true' RUN git submodule foreach 'git checkout main'
# Scripts for building # Scripts for building
COPY ./package.json ./package.json COPY ./package.json ./package.json
@ -54,7 +54,7 @@ RUN npm install -g @nestjs/cli
RUN npm install -g copyfiles RUN npm install -g copyfiles
RUN npm --prefix server run build RUN npm --prefix server run build
FROM node:22.15.1 FROM node:22.15.1-bullseye
RUN apt-get update -yq \ RUN apt-get update -yq \
&& apt-get install curl wget gnupg zip -yq \ && apt-get install curl wget gnupg zip -yq \

View file

@ -479,24 +479,22 @@ Cypress.Commands.add("apiMakeAppPublic", (appId = Cypress.env("appId")) => {
}); });
}); });
Cypress.Commands.add("apiDeleteGranularPermission", (groupName) => { Cypress.Commands.add("apiDeleteGranularPermission", (groupName, typesToDelete = []) => {
cy.getAuthHeaders().then((headers) => { cy.getAuthHeaders().then((headers) => {
// Fetch group permissions // Step 1: Get the group by name
cy.request({ cy.request({
method: "GET", method: "GET",
url: `${Cypress.env("server_host")}/api/v2/group-permissions`, url: `${Cypress.env("server_host")}/api/v2/group-permissions`,
headers: headers, headers,
log: false, log: false,
}).then((response) => { }).then((response) => {
expect(response.status).to.equal(200); expect(response.status).to.equal(200);
const group = response.body.groupPermissions.find( const group = response.body.groupPermissions.find((g) => g.name === groupName);
(g) => g.name === groupName
);
if (!group) throw new Error(`Group with name ${groupName} not found`); if (!group) throw new Error(`Group with name ${groupName} not found`);
const groupId = group.id; const groupId = group.id;
// Fetch granular permissions for the specific group // Step 2: Get all granular permissions for the group
cy.request({ cy.request({
method: "GET", method: "GET",
url: `${Cypress.env("server_host")}/api/v2/group-permissions/${groupId}/granular-permissions`, url: `${Cypress.env("server_host")}/api/v2/group-permissions/${groupId}/granular-permissions`,
@ -504,22 +502,31 @@ Cypress.Commands.add("apiDeleteGranularPermission", (groupName) => {
log: false, log: false,
}).then((granularResponse) => { }).then((granularResponse) => {
expect(granularResponse.status).to.equal(200); expect(granularResponse.status).to.equal(200);
const granularPermissionId = granularResponse.body[0].id; const granularPermissions = granularResponse.body;
// Delete the granular permission // Step 3: Filter if typesToDelete is specified
cy.request({ const permissionsToDelete = typesToDelete.length
method: "DELETE", ? granularPermissions.filter((perm) => typesToDelete.includes(perm.type))
url: `${Cypress.env("server_host")}/api/v2/group-permissions/granular-permissions/app/${granularPermissionId}`, : granularPermissions;
headers,
log: false, // Step 4: Delete each granular permission
}).then((deleteResponse) => { permissionsToDelete.forEach((permission) => {
expect(deleteResponse.status).to.equal(200); cy.request({
method: "DELETE",
url: `${Cypress.env("server_host")}/api/v2/group-permissions/granular-permissions/app/${permission.id}`,
headers,
log: false,
}).then((deleteResponse) => {
expect(deleteResponse.status).to.equal(200);
cy.log(`Deleted granular permission: ${permission.name}`);
});
}); });
}); });
}); });
}); });
}); });
Cypress.Commands.add( Cypress.Commands.add(
"apiCreateGranularPermission", "apiCreateGranularPermission",
( (

View file

@ -34,7 +34,7 @@ describe("App Import Functionality", () => {
cy.apiLogin(); cy.apiLogin();
cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug); cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug);
cy.apiLogout(); cy.apiLogout();
cy.skipWalkthrough() cy.skipWalkthrough();
}); });
it("should verify app import functionality", () => { it("should verify app import functionality", () => {
@ -151,23 +151,49 @@ describe("App Import Functionality", () => {
cy.visit(`${data.workspaceSlug}/data-sources`); cy.visit(`${data.workspaceSlug}/data-sources`);
cy.get('[data-cy="postgresql-button"]').should("be.visible"); cy.get('[data-cy="postgresql-button"]').should("be.visible");
cy.apiUpdateDataSource("postgresql", "production", {
options: [ cy.ifEnv("Community", () => {
{ cy.apiUpdateDataSource("postgresql", "production", {
key: "password", options: [
value: `${Cypress.env("pg_password")}`, {
encrypted: true, key: "password",
}, value: `${Cypress.env("pg_password")}`,
], encrypted: true,
},
],
});
});
cy.ifEnv("Enterprise", () => {
cy.apiUpdateDataSource("postgresql", "development", {
options: [
{
key: "password",
value: `${Cypress.env("pg_password")}`,
encrypted: true,
},
],
});
}); });
cy.apiCreateWsConstant( cy.ifEnv("Community", () => {
"pageHeader", cy.apiCreateWsConstant(
"Import and Export", "pageHeader",
["Global"], "Import and Export",
["production"] ["Global"],
); ["production"]
cy.apiCreateWsConstant("db_name", "persons", ["Secret"], ["production"]); );
cy.apiCreateWsConstant("db_name", "persons", ["Secret"], ["production"]);
});
cy.ifEnv("Enterprise", () => {
cy.apiCreateWsConstant(
"pageHeader",
"Import and Export",
["Global"],
["development"]
);
cy.apiCreateWsConstant("db_name", "persons", ["Secret"], ["development"]);
});
// Verify app after setup // Verify app after setup
cy.wait("@importApp").then((interception) => { cy.wait("@importApp").then((interception) => {

View file

@ -7,6 +7,7 @@ import {
verifyURLs, verifyURLs,
resolveHost, resolveHost,
} from "Support/utils/apps"; } from "Support/utils/apps";
import { appPromote } from "Support/utils/platform/multiEnv";
describe("App Slug", () => { describe("App Slug", () => {
const data = {}; const data = {};
@ -153,6 +154,7 @@ describe("App Slug", () => {
cy.visit("/my-workspace"); cy.visit("/my-workspace");
cy.apiCreateApp(data.slug); cy.apiCreateApp(data.slug);
cy.openApp("my-workspace"); cy.openApp("my-workspace");
releaseApp(); releaseApp();
cy.get(commonWidgetSelector.shareAppButton).click(); cy.get(commonWidgetSelector.shareAppButton).click();
cy.clearAndType(commonWidgetSelector.appNameSlugInput, data.slug); cy.clearAndType(commonWidgetSelector.appNameSlugInput, data.slug);

View file

@ -12,6 +12,8 @@ import {
verifyRestrictedAccess, verifyRestrictedAccess,
onboardUserFromAppLink, onboardUserFromAppLink,
} from "Support/utils/apps"; } from "Support/utils/apps";
import { appPromote } from "Support/utils/platform/multiEnv";
import { InstanceSSO } from "Support/utils/platform/eeCommon";
describe( describe(
"Private and Public apps", "Private and Public apps",
@ -183,6 +185,9 @@ describe(
setupAppWithSlug(data.appName, data.slug); setupAppWithSlug(data.appName, data.slug);
cy.apiLogout(); cy.apiLogout();
cy.ifEnv("Enterprise", () => {
InstanceSSO(true, true, true);
});
userSignUp(data.firstName, data.email, data.workspaceName); userSignUp(data.firstName, data.email, data.workspaceName);
cy.wait(1000); cy.wait(1000);
cy.visitSlug({ cy.visitSlug({
@ -312,7 +317,9 @@ describe(
cy.apiLogout(); cy.apiLogout();
cy.apiLogin(); cy.apiLogin();
cy.visit(`${data.workspaceSlug}`); cy.visit(`${data.workspaceSlug}`);
cy.apiDeleteGranularPermission("end-user");
cy.apiDeleteGranularPermission("end-user", ["app", "workflow"]);
setSignupStatus(true, data.workspaceName); setSignupStatus(true, data.workspaceName);
setupAppWithSlug(data.appName, data.slug); setupAppWithSlug(data.appName, data.slug);

View file

@ -1,7 +1,6 @@
import { commonSelectors, commonWidgetSelector } from "Selectors/common"; import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { fake } from "Fixtures/fake"; import { fake } from "Fixtures/fake";
import { commonText } from "Texts/common"; import { commonText } from "Texts/common";
import { import {
editVersionAndVerify, editVersionAndVerify,
deleteVersionAndVerify, deleteVersionAndVerify,
@ -13,25 +12,20 @@ import {
navigateToEditVersionModal, navigateToEditVersionModal,
switchVersionAndVerify, switchVersionAndVerify,
} from "Support/utils/version"; } from "Support/utils/version";
import { appVersionSelectors } from "Selectors/exportImport"; import { appVersionSelectors } from "Selectors/exportImport";
import { editVersionSelectors } from "Selectors/version"; import { editVersionSelectors } from "Selectors/version";
import { editVersionText } from "Texts/version"; import { editVersionText } from "Texts/version";
import { createNewVersion } from "Support/utils/exportImport"; import { createNewVersion } from "Support/utils/exportImport";
import { verifyModal, closeModal } from "Support/utils/common"; import { verifyModal, closeModal } from "Support/utils/common";
import { import {
verifyComponent, verifyComponent,
verifyComponentinrightpannel, verifyComponentinrightpannel,
deleteComponentAndVerify, deleteComponentAndVerify,
} from "Support/utils/basicComponents"; } from "Support/utils/basicComponents";
import { deleteVersionText, onlydeleteVersionText } from "Texts/version"; import { deleteVersionText, onlydeleteVersionText } from "Texts/version";
import { createRestAPIQuery } from "Support/utils/dataSource"; import { createRestAPIQuery } from "Support/utils/dataSource";
import { deleteQuery } from "Support/utils/queries"; import { deleteQuery } from "Support/utils/queries";
import { selectEnv, appPromote } from "Support/utils/platform/multiEnv";
describe("App Version", () => { describe("App Version", () => {
let data; let data;
@ -120,7 +114,15 @@ describe("App Version", () => {
// Preview and release verification // Preview and release verification
cy.openInCurrentTab(commonWidgetSelector.previewButton); cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.url().should("include", "/home?version=v2");
cy.ifEnv("Community", () => {
cy.url().should("include", "/home?version=v2");
});
cy.ifEnv("Enterprise", () => {
cy.url().should("include", "/home?env=development&version=v2");
});
cy.openApp( cy.openApp(
"", "",
Cypress.env("workspaceId"), Cypress.env("workspaceId"),
@ -149,7 +151,11 @@ describe("App Version", () => {
createRestAPIQuery(data.query1, data.datasourceName, "", "", "/1", true); createRestAPIQuery(data.query1, data.datasourceName, "", "", "/1", true);
// Version v2 creation and verification cy.ifEnv("Enterprise", () => {
appPromote("development", "production");
});
// Version v2 creation and verification and v2 is created from v1 production environment
navigateToCreateNewVersionModal("v1"); navigateToCreateNewVersionModal("v1");
createNewVersion(["v2"], "v1"); createNewVersion(["v2"], "v1");
cy.get(commonWidgetSelector.draggableWidget("text1")).verifyVisibleElement( cy.get(commonWidgetSelector.draggableWidget("text1")).verifyVisibleElement(
@ -201,7 +207,8 @@ describe("App Version", () => {
versionChecks.forEach((check) => { versionChecks.forEach((check) => {
navigateToCreateNewVersionModal(check.create.from); navigateToCreateNewVersionModal(check.create.from);
createNewVersion([check.create.version], check.create.from); createNewVersion([check.create.version], check.create.from);
cy.waitForAutoSave();
cy.wait(1000);
if (check.verify.component.value) { if (check.verify.component.value) {
cy.get( cy.get(
commonWidgetSelector.draggableWidget(check.verify.component.selector) commonWidgetSelector.draggableWidget(check.verify.component.selector)
@ -224,6 +231,9 @@ describe("App Version", () => {
); );
// Version switching and component verification // Version switching and component verification
cy.ifEnv("Enterprise", () => {
selectEnv("development");
});
cy.get(appVersionSelectors.currentVersionField("v5")).click(); cy.get(appVersionSelectors.currentVersionField("v5")).click();
cy.contains(`[id*="react-select-"]`, "v4").click(); cy.contains(`[id*="react-select-"]`, "v4").click();
cy.get(appVersionSelectors.currentVersionField("v4")).should( cy.get(appVersionSelectors.currentVersionField("v4")).should(
@ -238,7 +248,14 @@ describe("App Version", () => {
// Preview and version switching verification // Preview and version switching verification
cy.openInCurrentTab(commonWidgetSelector.previewButton); cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.url().should("include", "/home?version=v4");
cy.ifEnv("Community", () => {
cy.url().should("include", "/home?version=v4");
});
cy.ifEnv("Enterprise", () => {
cy.url().should("include", "/home?env=development&version=v4");
});
cy.get(commonWidgetSelector.draggableWidget("text1")).verifyVisibleElement( cy.get(commonWidgetSelector.draggableWidget("text1")).verifyVisibleElement(
"have.text", "have.text",
"Leanne Graham" "Leanne Graham"
@ -250,8 +267,74 @@ describe("App Version", () => {
cy.get( cy.get(
commonWidgetSelector.draggableWidget("textInput") commonWidgetSelector.draggableWidget("textInput")
).verifyVisibleElement("have.value", "Ervin Howell"); ).verifyVisibleElement("have.value", "Ervin Howell");
//url validation should be added after bug fix
// cy.url().should("include", "/home?version=v5"); cy.ifEnv("Enterprise", () => {
cy.openApp(
"",
Cypress.env("workspaceId"),
Cypress.env("appId"),
commonWidgetSelector.draggableWidget("textInput")
);
navigateToCreateNewVersionModal("v5");
createNewVersion(["v6"], "v5");
cy.waitForAutoSave();
cy.wait(1000);
appPromote("development", "staging");
cy.get(
commonWidgetSelector.draggableWidget("textInput")
).verifyVisibleElement("have.value", "Ervin Howell");
cy.get(`[data-cy="list-query-${data.query2}"]`).should("be.visible");
appPromote("staging", "production");
cy.get(
commonWidgetSelector.draggableWidget("textInput")
).verifyVisibleElement("have.value", "Ervin Howell");
cy.get(`[data-cy="list-query-${data.query2}"]`).should("be.visible");
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.get(
commonWidgetSelector.draggableWidget("textInput")
).verifyVisibleElement("have.value", "Ervin Howell");
cy.url().should("include", "/home?env=production&version=v6");
cy.wait(1000);
cy.get('[data-cy="preview-settings"]').click();
switchVersionAndVerify("v6", "v1");
cy.get(
commonWidgetSelector.draggableWidget("text1")
).verifyVisibleElement("have.text", "Leanne Graham");
// url bug
// cy.url().should("include", "/home?env=production&version=v1");
cy.wait(1000);
cy.get('[data-cy="preview-settings"]').click();
switchVersionAndVerify("v1", "v6");
cy.wait(1000);
cy.get('[data-cy="preview-settings"]').click();
selectEnv("staging");
cy.get(
commonWidgetSelector.draggableWidget("textInput")
).verifyVisibleElement("have.value", "Ervin Howell");
// cy.url().should("include", "/home?env=staging&version=v6");
cy.wait(1000);
cy.get('[data-cy="preview-settings"]').click();
selectEnv("development");
cy.wait(1000);
cy.get('[data-cy="preview-settings"]').click();
switchVersionAndVerify("v6", "v1");
cy.get(
commonWidgetSelector.draggableWidget("text1")
).verifyVisibleElement("have.text", "Leanne Graham");
});
}); });
}); });

View file

@ -0,0 +1,662 @@
import { fake } from "Fixtures/fake";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { commonEeText, ssoEeText } from "Texts/eeCommon";
import { commonEeSelectors, multiEnvSelector } from "Selectors/eeCommon";
import {
verifyPromoteModalUI,
verifyTooltipDisabled,
} from "Support/utils/platform/eeCommon";
import { dataSourceSelector } from "Selectors/dataSource";
import {
navigateToAppEditor,
pinInspector,
verifyTooltip,
} from "Support/utils/common";
import { addQuery, selectDatasource } from "Support/utils/dataSource";
import {
appPromote,
createNewVersion,
selectVersion,
selectEnv,
} from "Support/utils/platform/multiEnv";
import { appVersionSelectors } from "Selectors/exportImport";
import { editAndVerifyWidgetName } from "Support/utils/commonWidget";
import { deleteVersionAndVerify } from "Support/utils/version";
import { deleteVersionText } from "Texts/version";
describe("Multi env", () => {
const data = {};
data.appName = `${fake.companyName} App`;
data.ds = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
data.constName = fake.firstName.toLowerCase().replaceAll("[^A-Za-z]", "");
const slug = data.appName.toLowerCase().replace(/\s+/g, "-");
let currentVersion = "";
let newVersion = [];
let versionFrom = "";
beforeEach(() => {
cy.apiLogin();
cy.viewport(1800, 1800);
cy.skipWalkthrough();
});
it.only("Verify the datasource configuration and data on each env", () => {
cy.apiCreateGDS(
`${Cypress.env("server_host")}/api/data-sources`,
data.ds,
"restapi",
[
{ key: "url", value: "" },
{ key: "auth_type", value: "none" },
{ key: "grant_type", value: "authorization_code" },
{ key: "add_token_to", value: "header" },
{ key: "header_prefix", value: "Bearer " },
{ key: "access_token_url", value: "" },
{ key: "client_ide", value: "" },
{ key: "client_secret", value: "", encrypted: true },
{ key: "scopes", value: "read, write" },
{ key: "username", value: "", encrypted: false },
{ key: "password", value: "", encrypted: true },
{ key: "bearer_token", value: "", encrypted: true },
{ key: "auth_url", value: "" },
{ key: "client_auth", value: "header" },
{ key: "headers", value: [["", ""]] },
{ key: "custom_query_params", value: [["", ""]], encrypted: false },
{ key: "custom_auth_params", value: [["", ""]] },
{
key: "access_token_custom_headers",
value: [["", ""]],
encrypted: false,
},
{ key: "multiple_auth_enabled", value: false, encrypted: false },
{ key: "ssl_certificate", value: "none", encrypted: false },
]
);
cy.apiCreateApp(data.appName);
cy.visit("/my-workspace");
cy.get(commonSelectors.globalDataSourceIcon).click();
selectDatasource(data.ds);
cy.get('[data-cy="development-label"]').click();
cy.clearAndType(
'[data-cy="base-url-text-field"]',
"https://reqres.in/api/users?page=1"
);
cy.get(dataSourceSelector.buttonSave).click();
cy.wait(2000);
cy.get(commonSelectors.dashboardIcon).click();
cy.openApp();
// cy.waitForAppLoad();
cy.wait(2000);
cy.get(`[data-cy="${data.ds}-add-query-card"] > .text-truncate`).click();
cy.wait(1000);
cy.get(dataSourceSelector.queryCreateAndRunButton).click();
cy.get('[data-cy="query-tab-settings"]').click();
cy.get(':nth-child(1) > .custom-toggle-switch > .switch > .slider').click();
cy.waitForAutoSave();
cy.dragAndDropWidget("Text Input", 550, 650);
editAndVerifyWidgetName(data.constName, []);
cy.waitForAutoSave();
cy.get(
'[data-cy="default-value-input-field"]'
).clearAndTypeOnCodeMirror(`{{queries.restapi1.data.data[0].email`);
cy.wait(1000);
cy.forceClickOnCanvas();
cy.waitForAutoSave();
cy.get(dataSourceSelector.queryCreateAndRunButton).click();
cy.get(
commonWidgetSelector.draggableWidget(data.constName)
).verifyVisibleElement("have.value", "george.bluth@reqres.in");
pinInspector();
cy.get(commonWidgetSelector.sidebarinspector).click();
cy.get(commonWidgetSelector.inspectorNodeComponents).click();
cy.get(commonWidgetSelector.nodeComponent(data.constName)).click();
cy.get('[data-cy="inspector-node-value"] > .mx-2').verifyVisibleElement(
"have.text",
`"george.bluth@reqres.in"`
);
cy.get('[style="height: 13px; width: 13px;"] > img').should("exist");
cy.get('[data-cy="inspector-node-globals"] > .node-key').click();
cy.get('[data-cy="inspector-node-environment"] > .node-key').click();
cy.get('[data-cy="inspector-node-name"] > .mx-2').verifyVisibleElement(
"have.text",
`"development"`
);
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.wait(4000);
cy.get(
commonWidgetSelector.draggableWidget(data.constName)
).verifyVisibleElement("have.value", "george.bluth@reqres.in");
cy.go("back");
cy.waitForAppLoad();
cy.wait(3000);
cy.get(commonEeSelectors.promoteButton).click();
cy.get(commonEeSelectors.promoteButton).eq(1).click();
cy.waitForAppLoad();
cy.wait(3000);
cy.get(dataSourceSelector.queryCreateAndRunButton, {
timeout: 20000,
}).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
"Query could not be completed"
);
cy.backToApps();
cy.get(commonSelectors.globalDataSourceIcon).click();
selectDatasource(data.ds);
cy.get('[data-cy="staging-label"]').click();
cy.clearAndType(
'[data-cy="base-url-text-field"]',
"https://reqres.in/api/users?page=2"
);
cy.get(dataSourceSelector.buttonSave).click();
cy.wait(2000);
cy.get(commonSelectors.dashboardIcon).click();
navigateToAppEditor(data.appName);
cy.get(dataSourceSelector.queryCreateAndRunButton).click();
cy.get(
commonWidgetSelector.draggableWidget(data.constName)
).verifyVisibleElement("have.value", "michael.lawson@reqres.in");
cy.get(commonWidgetSelector.sidebarinspector).click();
cy.get(commonWidgetSelector.inspectorNodeComponents).click();
cy.get(commonWidgetSelector.nodeComponent(data.constName)).click();
cy.get('[data-cy="inspector-node-value"] > .mx-2').verifyVisibleElement(
"have.text",
`"michael.lawson@reqres.in"`
);
cy.get('[style="height: 13px; width: 13px;"] > img').should("not.exist");
cy.get('[data-cy="inspector-node-globals"] > .node-key').click();
cy.get('[data-cy="inspector-node-environment"] > .node-key').click();
cy.get('[data-cy="inspector-node-name"] > .mx-2').verifyVisibleElement(
"have.text",
`"staging"`
);
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.wait(4000);
cy.get(
commonWidgetSelector.draggableWidget(data.constName)
).verifyVisibleElement("have.value", "michael.lawson@reqres.in");
cy.go("back");
cy.waitForAppLoad();
cy.wait(3000);
cy.get(commonEeSelectors.promoteButton).click();
cy.get(commonEeSelectors.promoteButton).eq(1).click();
cy.waitForAppLoad();
cy.wait(3000);
cy.get(dataSourceSelector.queryCreateAndRunButton, {
timeout: 20000,
}).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
"Query could not be completed"
);
cy.backToApps();
cy.get(commonSelectors.globalDataSourceIcon).click();
selectDatasource(data.ds);
cy.get('[data-cy="production-label"]').click();
cy.clearAndType(
'[data-cy="base-url-text-field"]',
"https://reqres.in/api/users?page=1"
);
cy.get(dataSourceSelector.buttonSave).click();
cy.wait(2000);
cy.get(commonSelectors.dashboardIcon).click();
navigateToAppEditor(data.appName);
cy.get(dataSourceSelector.queryCreateAndRunButton).click();
cy.get(
commonWidgetSelector.draggableWidget(data.constName)
).verifyVisibleElement("have.value", "george.bluth@reqres.in");
cy.get(commonWidgetSelector.sidebarinspector).click();
cy.get(commonWidgetSelector.inspectorNodeComponents).click();
cy.get(commonWidgetSelector.nodeComponent(data.constName)).click();
cy.get('[data-cy="inspector-node-value"] > .mx-2').verifyVisibleElement(
"have.text",
`"george.bluth@reqres.in"`
);
cy.get('[style="height: 13px; width: 13px;"] > img').should("not.exist");
cy.get('[data-cy="inspector-node-globals"] > .node-key').click();
cy.get('[data-cy="inspector-node-environment"] > .node-key').click();
cy.get('[data-cy="inspector-node-name"] > .mx-2').verifyVisibleElement(
"have.text",
`"production"`
);
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.wait(4000);
cy.get(
commonWidgetSelector.draggableWidget(data.constName)
).verifyVisibleElement("have.value", "george.bluth@reqres.in");
cy.go("back");
cy.waitForAppLoad();
cy.wait(3000);
cy.get(commonSelectors.releaseButton).click();
cy.get(commonSelectors.yesButton).click();
cy.verifyToastMessage(commonSelectors.toastMessage, "Version v1 released");
cy.wait(4000);
cy.get(commonWidgetSelector.shareAppButton).click();
cy.clearAndType(commonWidgetSelector.appNameSlugInput, `${slug}`);
cy.wait(2000);
cy.get(commonWidgetSelector.modalCloseButton).click();
cy.visit(`/applications/${slug}`);
cy.get(
commonWidgetSelector.draggableWidget(data.constName)
).verifyVisibleElement("have.value", "george.bluth@reqres.in");
});
it("should verify edit privilages of a promoted version", () => {
data.appName = `${fake.companyName} App`;
cy.apiCreateApp(data.appName);
cy.openApp();
cy.waitForAppLoad();
cy.dragAndDropWidget("Text", 550, 650);
appPromote("development", "production");
createNewVersion(
(currentVersion = "v1"),
(newVersion = ["v2"]),
(versionFrom = "v1")
);
appPromote("development", "release");
createNewVersion(
(currentVersion = "v2"),
(newVersion = ["v3"]),
(versionFrom = "v2")
);
appPromote("development", "staging");
selectVersion((currentVersion = "v3"), (newVersion = ["v1"]));
cy.get(commonSelectors.warningText).eq(0).verifyVisibleElement(
"have.text",
"App cannot be edited after promotion. Please create a new version from Development to make any changes."
);
cy.forceClickOnCanvas();
cy.get(".datasource-picker").should("have.class", "disabled");
cy.get(commonEeSelectors.AddQueryButton).should("be.disabled");
cy.get(".components-container").should("have.class", "disabled");
cy.wait(1000);
selectEnv("development");
cy.get(commonSelectors.warningText).eq(0).verifyVisibleElement(
"have.text",
"App cannot be edited after promotion. Please create a new version from Development to make any changes."
);
cy.get(".datasource-picker").should("have.class", "disabled");
cy.get(commonEeSelectors.AddQueryButton).should("be.disabled");
cy.get(".components-container").should("have.class", "disabled");
selectVersion((currentVersion = "v1"), (newVersion = ["v2"]));
cy.get(commonSelectors.warningText).eq(0).verifyVisibleElement(
"have.text",
"This version of the app is released. Please create a new version in development to make any changes."
);
cy.get(".datasource-picker").should("have.class", "disabled");
cy.get(commonEeSelectors.AddQueryButton).should("be.disabled");
cy.get(".components-container").should("have.class", "disabled");
cy.wait(1000);
selectEnv("staging");
cy.get(commonSelectors.warningText).eq(0).verifyVisibleElement(
"have.text",
"This version of the app is released. Please create a new version in development to make any changes."
);
cy.get(".datasource-picker").should("have.class", "disabled");
cy.get(commonEeSelectors.AddQueryButton).should("be.disabled");
cy.get(".components-container").should("have.class", "disabled");
cy.wait(1000);
selectEnv("production");
cy.get(commonSelectors.warningText).eq(0).verifyVisibleElement(
"have.text",
"This version of the app is released. Please create a new version in development to make any changes."
);
cy.get(".datasource-picker").should("have.class", "disabled");
cy.get(commonEeSelectors.AddQueryButton).should("be.disabled");
cy.get(".components-container").should("have.class", "disabled");
cy.get(commonSelectors.releaseButton).should("be.disabled");
});
it("Should verify last exisiting version", () => {
data.appName = `${fake.companyName} App`;
cy.apiCreateApp(data.appName);
cy.openApp();
cy.waitForAppLoad();
cy.dragAndDropWidget("Text", 550, 650);
appPromote("development", "staging");
createNewVersion(
(currentVersion = "v1"),
(newVersion = ["v2"]),
(versionFrom = "v1")
);
selectVersion((currentVersion = "v2"), (newVersion = ["v1"]));
cy.wait(1000);
selectEnv("staging");
cy.get(appVersionSelectors.currentVersionField(newVersion[0]))
.should("be.visible")
.and("have.text", "v1");
appPromote("staging", "production");
cy.wait(3000)
deleteVersionAndVerify(
(currentVersion = "v1"),
deleteVersionText.deleteToastMessage((currentVersion = "v1"))
);
cy.wait(2000);
cy.get('[data-cy="list-current-env-name"]').click();
verifyTooltip(
'[data-cy="env-name-dropdown"]:eq(1)',
"There are no versions in this environment"
);
verifyTooltip(
'[data-cy="env-name-dropdown"]:eq(2)',
"There are no versions in this environment"
);
});
it("Should verify version deletion", () => {
data.appName = `${fake.companyName} App`;
cy.apiCreateApp(data.appName);
cy.openApp();
cy.waitForAppLoad();
cy.dragAndDropWidget("Text", 550, 650);
appPromote("development", "staging");
createNewVersion(
(currentVersion = "v1"),
(newVersion = ["v2"]),
(versionFrom = "v1")
);
appPromote("development", "staging");
createNewVersion(
(currentVersion = "v2"),
(newVersion = ["v3"]),
(versionFrom = "v2")
);
appPromote("development", "production");
selectEnv("staging");
selectVersion((currentVersion = "v3"), (newVersion = ["v2"]));
deleteVersionAndVerify(
(currentVersion = "v2"),
deleteVersionText.deleteToastMessage((currentVersion = "v2"))
);
cy.get('[data-cy="v3-current-version-text"]')
.should("be.visible")
.and("have.text", "v3");
cy.get('[data-cy="list-current-env-name"]').should(
"have.text",
"Staging"
);
})
it("Verify the multi env components UI", () => {
data.appName = `${fake.companyName} App`;
cy.apiCreateApp(data.appName);
cy.openApp();
cy.waitForAppLoad();
cy.dragAndDropWidget("Text", 550, 650);
cy.get(multiEnvSelector.envContainer).should("be.visible");
cy.get(multiEnvSelector.currentEnvName)
.verifyVisibleElement("have.text", "Development")
.click();
cy.get(multiEnvSelector.envArrow).should("be.visible");
cy.get(multiEnvSelector.selectedEnvName).verifyVisibleElement(
"have.text",
" Development"
);
cy.get(multiEnvSelector.envNameList)
.eq(0)
.verifyVisibleElement("have.text", "Development");
cy.get(multiEnvSelector.envNameList)
.eq(1)
.verifyVisibleElement("have.text", "Staging");
cy.get(multiEnvSelector.envNameList)
.eq(2)
.verifyVisibleElement("have.text", "Production");
verifyTooltip(
'[data-cy="env-name-dropdown"]:eq(1)',
"There are no versions in this environment"
);
verifyTooltip(
'[data-cy="env-name-dropdown"]:eq(2)',
"There are no versions in this environment"
);
cy.get(multiEnvSelector.appVersionLabel).should("be.visible");
cy.get('[data-cy="v1-current-version-text"]')
.verifyVisibleElement("have.text", "v1")
.click();
cy.get(multiEnvSelector.currentVersion).verifyVisibleElement(
"have.text",
"v1"
);
cy.get(".col-10 > .app-version-name").verifyVisibleElement(
"have.text",
"v1"
);
cy.get(multiEnvSelector.createNewVersionButton).verifyVisibleElement(
"have.text",
"Create new version"
);
verifyPromoteModalUI("v1", "Development", "Staging");
cy.get('[data-cy="env-change-info-text"]').verifyVisibleElement(
"have.text",
"You wont be able to edit this version after promotion. Are you sure you want to continue?"
);
cy.get(commonSelectors.closeButton).click();
cy.get(multiEnvSelector.currentEnvName).verifyVisibleElement(
"have.text",
"Development"
);
cy.get(commonEeSelectors.promoteButton).click();
cy.get(commonSelectors.cancelButton).click();
cy.get(multiEnvSelector.currentEnvName).verifyVisibleElement(
"have.text",
"Development"
);
cy.get(commonEeSelectors.promoteButton).click();
cy.get(commonEeSelectors.promoteButton).eq(1).click();
cy.waitForAppLoad();
cy.wait(3000);
cy.get(commonSelectors.warningText).eq(0).verifyVisibleElement(
"have.text",
"App cannot be edited after promotion. Please create a new version from Development to make any changes."
);
cy.get(multiEnvSelector.envContainer).should("be.visible");
cy.get(multiEnvSelector.currentEnvName)
.verifyVisibleElement("have.text", "Staging")
.click();
cy.get(multiEnvSelector.envArrow).should("be.visible");
cy.get(multiEnvSelector.currentEnvName).verifyVisibleElement(
"have.text",
"Staging"
);
cy.get(multiEnvSelector.envNameList)
.eq(0)
.verifyVisibleElement("have.text", "Development");
cy.get(multiEnvSelector.envNameList)
.eq(1)
.verifyVisibleElement("have.text", "Staging");
cy.get(multiEnvSelector.envNameList)
.eq(2)
.verifyVisibleElement("have.text", "Production");
cy.wait(2000)
verifyTooltip(
'[data-cy="env-name-dropdown"]:eq(2)',
"There are no versions in this environment"
);
cy.get(multiEnvSelector.appVersionLabel).should("be.visible");
cy.get('[data-cy="v1-current-version-text"]')
.verifyVisibleElement("have.text", "v1")
.click();
cy.get(multiEnvSelector.currentVersion).verifyVisibleElement(
"have.text",
"v1"
);
cy.get(".col-10 > .app-version-name").verifyVisibleElement(
"have.text",
"v1"
);
cy.get(multiEnvSelector.createNewVersionButton).verifyVisibleElement(
"have.text",
"Create new version"
);
verifyTooltip(
multiEnvSelector.createNewVersionButton,
"New versions can only be created in development"
);
cy.forceClickOnCanvas();
cy.get(".datasource-picker").should("have.class", "disabled");
cy.get(commonEeSelectors.AddQueryButton).should("be.disabled");
cy.get(".components-container").should("have.class", "disabled");
verifyPromoteModalUI("v1", "Staging", "Production");
cy.get(commonSelectors.closeButton).click();
cy.get(multiEnvSelector.currentEnvName).verifyVisibleElement(
"have.text",
"Staging"
);
cy.get(commonEeSelectors.promoteButton).click();
cy.get(commonSelectors.cancelButton).click();
cy.get(multiEnvSelector.currentEnvName).verifyVisibleElement(
"have.text",
"Staging"
);
cy.get(commonEeSelectors.promoteButton).click();
cy.get(commonEeSelectors.promoteButton).eq(1).click();
cy.waitForAppLoad();
cy.wait(3000);
cy.get(commonSelectors.warningText).eq(0).verifyVisibleElement(
"have.text",
"App cannot be edited after promotion. Please create a new version from Development to make any changes."
);
cy.get(multiEnvSelector.envContainer).should("be.visible");
cy.get(multiEnvSelector.currentEnvName)
.verifyVisibleElement("have.text", "Production")
.click();
cy.get(multiEnvSelector.envArrow).should("be.visible");
cy.get(multiEnvSelector.currentEnvName).verifyVisibleElement(
"have.text",
"Production"
);
cy.get(multiEnvSelector.envNameList)
.eq(0)
.verifyVisibleElement("have.text", "Development");
cy.get(multiEnvSelector.envNameList)
.eq(1)
.verifyVisibleElement("have.text", "Staging");
cy.get(multiEnvSelector.envNameList)
.eq(2)
.verifyVisibleElement("have.text", "Production");
cy.get(multiEnvSelector.appVersionLabel).should("be.visible");
cy.get('[data-cy="v1-current-version-text"]')
.verifyVisibleElement("have.text", "v1")
.click();
cy.get(multiEnvSelector.currentVersion).verifyVisibleElement(
"have.text",
"v1"
);
cy.get(".col-10 > .app-version-name").verifyVisibleElement(
"have.text",
"v1"
);
cy.get(multiEnvSelector.createNewVersionButton).verifyVisibleElement(
"have.text",
"Create new version"
);
cy.get(commonSelectors.releaseButton)
.verifyVisibleElement("have.text", "Release")
.click();
cy.get('[data-cy="modal-title"]').verifyVisibleElement(
"have.text",
"Release Version"
);
cy.get(commonSelectors.closeButton).should("be.visible");
cy.get('[data-cy="confirm-dialogue-box-text"]').verifyVisibleElement(
"have.text",
"Are you sure you want to release this version?"
);
cy.get(commonSelectors.cancelButton).verifyVisibleElement(
"have.text",
"Cancel"
);
cy.get(commonSelectors.yesButton).verifyVisibleElement("have.text", "Yes");
cy.get(commonSelectors.closeButton).click();
cy.get(multiEnvSelector.currentEnvName).verifyVisibleElement(
"have.text",
"Production"
);
cy.get(commonSelectors.releaseButton).click();
cy.get(commonSelectors.cancelButton).click();
cy.get(multiEnvSelector.currentEnvName).verifyVisibleElement(
"have.text",
"Production"
);
cy.get(commonSelectors.releaseButton).click();
cy.get(commonSelectors.yesButton).click();
cy.verifyToastMessage(commonSelectors.toastMessage, "Version v1 released");
cy.wait(500);
cy.get(commonSelectors.warningText).eq(0).verifyVisibleElement(
"have.text",
"This version of the app is released. Please create a new version in development to make any changes."
);
cy.get('[data-cy="v1-current-version-text"]').click();
verifyTooltip(
multiEnvSelector.createNewVersionButton,
"New versions can only be created in development"
);
cy.get(".datasource-picker").should("have.class", "disabled");
cy.get(commonEeSelectors.AddQueryButton).should("be.disabled");
cy.get(".components-container").should("have.class", "disabled");
cy.get(commonSelectors.releaseButton).should("be.disabled");
});
});

View file

@ -1,112 +1,132 @@
import { commonSelectors, commonWidgetSelector } from "Selectors/common"; import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { appPromote } from "Support/utils/platform/multiEnv";
const slugValidations = [ const slugValidations = [
{ input: "", error: "App slug can't be empty" }, { input: "", error: "App slug can't be empty" },
{ input: "_2#", error: "Special characters are not accepted." }, { input: "_2#", error: "Special characters are not accepted." },
{ input: "t ", error: "Cannot contain spaces" }, { input: "t ", error: "Cannot contain spaces" },
{ input: "T", error: "Only lowercase letters are accepted." }, { input: "T", error: "Only lowercase letters are accepted." },
]; ];
export const verifySlugValidations = (inputSelector) => { export const verifySlugValidations = (inputSelector) => {
slugValidations.forEach(({ input, error }) => { slugValidations.forEach(({ input, error }) => {
cy.get(inputSelector).clear(); cy.get(inputSelector).clear();
if (input) cy.clearAndType(inputSelector, input); if (input) cy.clearAndType(inputSelector, input);
cy.wait(500); cy.wait(500);
cy.get(commonWidgetSelector.appSlugErrorLabel).verifyVisibleElement( cy.get(commonWidgetSelector.appSlugErrorLabel).verifyVisibleElement(
"have.text", "have.text",
error error
); );
}); });
}; };
export const verifySuccessfulSlugUpdate = (workspaceId, slug) => { export const verifySuccessfulSlugUpdate = (workspaceId, slug) => {
const host = resolveHost(); const host = resolveHost();
cy.get('[data-cy="app-slug-accepted-label"]').verifyVisibleElement( cy.get('[data-cy="app-slug-accepted-label"]').verifyVisibleElement(
"have.text", "have.text",
"Slug accepted!" "Slug accepted!"
); );
cy.wait(500); cy.wait(500);
// cy.get(commonWidgetSelector.appLinkSucessLabel).should('be.visible'); // cy.get(commonWidgetSelector.appLinkSucessLabel).should('be.visible');
cy.get(commonWidgetSelector.appLinkSucessLabel).should( cy.get(commonWidgetSelector.appLinkSucessLabel).should(
"have.text", "have.text",
"Link updated successfully!" "Link updated successfully!"
); );
cy.get(commonWidgetSelector.appLinkField).verifyVisibleElement( cy.get(commonWidgetSelector.appLinkField).verifyVisibleElement(
"have.text", "have.text",
`${host}/${workspaceId}/apps/${slug}` `${host}/${workspaceId}/apps/${slug}`
); );
}; };
export const verifyURLs = (workspaceId, slug, page) => { export const verifyURLs = (workspaceId, slug, page) => {
const baseUrl = Cypress.config("baseUrl"); const baseUrl = Cypress.config("baseUrl");
cy.url().should( cy.url().should(
"eq", "eq",
page page
? `${baseUrl}/${workspaceId}/apps/${slug}/home` ? `${baseUrl}/${workspaceId}/apps/${slug}/home`
: `${baseUrl}/${workspaceId}/apps/${slug}` : `${baseUrl}/${workspaceId}/apps/${slug}`
); );
cy.openInCurrentTab(commonWidgetSelector.previewButton); cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.ifEnv("Community", () => {
cy.url().should("eq", `${baseUrl}/applications/${slug}/home?version=v1`); cy.url().should("eq", `${baseUrl}/applications/${slug}/home?version=v1`);
});
cy.ifEnv("Enterprise", () => {
cy.url().should(
"eq",
`${baseUrl}/applications/${slug}/home?env=production&version=v1`
);
});
cy.visit("/my-workspace"); cy.visit("/my-workspace");
cy.visitSlug({ cy.visitSlug({
actualUrl: `${baseUrl}/applications/${slug}`, actualUrl: `${baseUrl}/applications/${slug}`,
}); });
cy.url().should("eq", `${baseUrl}/applications/${slug}`); cy.url().should("eq", `${baseUrl}/applications/${slug}`);
}; };
export const setUpSlug = (slug) => { export const setUpSlug = (slug) => {
cy.get(commonWidgetSelector.shareAppButton).click(); cy.get(commonWidgetSelector.shareAppButton).click();
cy.clearAndType(commonWidgetSelector.appNameSlugInput, slug); cy.clearAndType(commonWidgetSelector.appNameSlugInput, slug);
cy.get('[data-cy="app-slug-accepted-label"]') cy.get('[data-cy="app-slug-accepted-label"]')
.should("be.visible") .should("be.visible")
.and("have.text", "Slug accepted!"); .and("have.text", "Slug accepted!");
cy.get(commonWidgetSelector.modalCloseButton).click(); cy.get(commonWidgetSelector.modalCloseButton).click();
}; };
export const setupAppWithSlug = (appName, slug) => { export const setupAppWithSlug = (appName, slug) => {
cy.apiCreateApp(appName); cy.apiCreateApp(appName);
cy.apiAddComponentToApp(appName, "text1"); cy.apiAddComponentToApp(appName, "text1");
cy.apiReleaseApp(appName);
cy.apiAddAppSlug(appName, slug); cy.ifEnv("Enterprise", () => {
cy.openApp(
"",
Cypress.env("workspaceId"),
Cypress.env("appId"),
commonWidgetSelector.draggableWidget("text1")
);
appPromote("development", "production");
});
cy.apiReleaseApp(appName);
cy.apiAddAppSlug(appName, slug);
}; };
export const verifyRestrictedAccess = () => { export const verifyRestrictedAccess = () => {
cy.get('[data-cy="modal-header"]').should("have.text", "Restricted access"); cy.get('[data-cy="modal-header"]').should("have.text", "Restricted access");
cy.get('[data-cy="modal-description"]') cy.get('[data-cy="modal-description"]')
.invoke("text") .invoke("text")
.then((text) => { .then((text) => {
const normalizedText = text.replace(//g, "'"); const normalizedText = text.replace(//g, "'");
expect(normalizedText).to.equal( expect(normalizedText).to.equal(
"You don't have access to this app. Kindly contact admin to know more." "You don't have access to this app. Kindly contact admin to know more."
); );
}); });
cy.get('[data-cy="back-to-home-button"]').verifyVisibleElement( cy.get('[data-cy="back-to-home-button"]').verifyVisibleElement(
"have.text", "have.text",
"Back to home page" "Back to home page"
); );
}; };
export const onboardUserFromAppLink = ( export const onboardUserFromAppLink = (
email, email,
slug, slug,
workspaceName = "My workspace", workspaceName = "My workspace",
isNonExistingUser = true isNonExistingUser = true
) => { ) => {
const dbConfig = Cypress.env("app_db"); const dbConfig = Cypress.env("app_db");
const query = isNonExistingUser const query = isNonExistingUser
? ` ? `
SELECT u.invitation_token, o.id AS workspace_id, ou.invitation_token AS organization_token SELECT u.invitation_token, o.id AS workspace_id, ou.invitation_token AS organization_token
FROM users u FROM users u
JOIN organization_users ou ON u.id = ou.user_id JOIN organization_users ou ON u.id = ou.user_id
JOIN organizations o ON ou.organization_id = o.id JOIN organizations o ON ou.organization_id = o.id
WHERE u.email = '${email}' AND o.name = '${workspaceName}'; WHERE u.email = '${email}' AND o.name = '${workspaceName}';
` `
: ` : `
SELECT ou.invitation_token, o.id AS workspace_id SELECT ou.invitation_token, o.id AS workspace_id
FROM users u FROM users u
JOIN organization_users ou ON u.id = ou.user_id JOIN organization_users ou ON u.id = ou.user_id
@ -114,33 +134,33 @@ export const onboardUserFromAppLink = (
WHERE u.email = '${email}' AND o.name = '${workspaceName}'; WHERE u.email = '${email}' AND o.name = '${workspaceName}';
`; `;
cy.task("dbConnection", { dbconfig: dbConfig, sql: query }).then((resp) => { cy.task("dbConnection", { dbconfig: dbConfig, sql: query }).then((resp) => {
if (!resp.rows || resp.rows.length === 0) { if (!resp.rows || resp.rows.length === 0) {
throw new Error( throw new Error(
`No records found for email: ${email} and workspace: ${workspaceName}` `No records found for email: ${email} and workspace: ${workspaceName}`
); );
} }
const { invitation_token, workspace_id, organization_token } = resp.rows[0]; const { invitation_token, workspace_id, organization_token } = resp.rows[0];
const token = isNonExistingUser ? organization_token : invitation_token; const token = isNonExistingUser ? organization_token : invitation_token;
const url = isNonExistingUser const url = isNonExistingUser
? `${Cypress.config("baseUrl")}/invitations/${invitation_token}/workspaces/${organization_token}?oid=${workspace_id}&redirectTo=%2Fapplications%2F${slug}` ? `${Cypress.config("baseUrl")}/invitations/${invitation_token}/workspaces/${organization_token}?oid=${workspace_id}&redirectTo=%2Fapplications%2F${slug}`
: `${Cypress.config("baseUrl")}/organization-invitations/${token}?oid=${workspace_id}&redirectTo=%2Fapplications%2F${slug}`; : `${Cypress.config("baseUrl")}/organization-invitations/${token}?oid=${workspace_id}&redirectTo=%2Fapplications%2F${slug}`;
cy.visit(url); cy.visit(url);
}); });
}; };
export const resolveHost = () => { export const resolveHost = () => {
const baseUrl = Cypress.config("baseUrl"); const baseUrl = Cypress.config("baseUrl");
const urlMapping = { const urlMapping = {
"http://localhost:8082": "http://localhost:8082", "http://localhost:8082": "http://localhost:8082",
"http://localhost:3000": "http://localhost:3000", "http://localhost:3000": "http://localhost:3000",
"http://localhost:3000/apps": "http://localhost:3000/apps", "http://localhost:3000/apps": "http://localhost:3000/apps",
"http://localhost:4001": "http://localhost:3000", "http://localhost:4001": "http://localhost:3000",
"http://localhost:4001/apps": "http://localhost:3000/apps", "http://localhost:4001/apps": "http://localhost:3000/apps",
}; };
return urlMapping[baseUrl]; return urlMapping[baseUrl];
}; };

View file

@ -6,6 +6,7 @@ import moment from "moment";
import { dashboardSelector } from "Selectors/dashboard"; import { dashboardSelector } from "Selectors/dashboard";
import { groupsSelector } from "Selectors/manageGroups"; import { groupsSelector } from "Selectors/manageGroups";
import { groupsText } from "Texts/manageGroups"; import { groupsText } from "Texts/manageGroups";
import { appPromote } from "Support/utils/platform/multiEnv";
export const navigateToProfile = () => { export const navigateToProfile = () => {
cy.get(commonSelectors.settingsIcon).click(); cy.get(commonSelectors.settingsIcon).click();
@ -48,7 +49,7 @@ export const randomDateOrTime = (format = "DD/MM/YYYY") => {
let startDate = new Date(2018, 0, 1); let startDate = new Date(2018, 0, 1);
startDate = new Date( startDate = new Date(
startDate.getTime() + startDate.getTime() +
Math.random() * (endDate.getTime() - startDate.getTime()) Math.random() * (endDate.getTime() - startDate.getTime())
); );
return moment(startDate).format(format); return moment(startDate).format(format);
}; };
@ -104,7 +105,7 @@ export const viewAppCardOptions = (appName) => {
cy.get(commonSelectors.appCard(appName)) cy.get(commonSelectors.appCard(appName))
.realHover() .realHover()
.find(commonSelectors.appCardOptionsButton) .find(commonSelectors.appCardOptionsButton)
.realHover() .realHover();
cy.contains("div", appName) cy.contains("div", appName)
.parent() .parent()
.within(() => { .within(() => {
@ -230,6 +231,9 @@ export const navigateToworkspaceConstants = () => {
}; };
export const releaseApp = () => { export const releaseApp = () => {
cy.ifEnv("Enterprise", () => {
appPromote("development", "production");
});
cy.get(commonSelectors.releaseButton).click(); cy.get(commonSelectors.releaseButton).click();
cy.get(commonSelectors.yesButton).click(); cy.get(commonSelectors.yesButton).click();
cy.verifyToastMessage(commonSelectors.toastMessage, "Version v1 released"); cy.verifyToastMessage(commonSelectors.toastMessage, "Version v1 released");

View file

@ -92,7 +92,7 @@ export const userSignUp = (fullName, email, workspaceName = "test") => {
cy.visit(invitationLink); cy.visit(invitationLink);
cy.wait(2500); cy.wait(2500);
}); });
if (Cypress.env("environment") !== "Community") { if (Cypress.env("environment") == "Cloud") {
cy.clearAndType( cy.clearAndType(
'[data-cy="onboarding-workspace-name-input"]', '[data-cy="onboarding-workspace-name-input"]',
workspaceName workspaceName

View file

@ -0,0 +1,120 @@
import { multiEnvSelector, commonEeSelectors } from "Selectors/eeCommon";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { appVersionSelectors } from "Selectors/exportImport";
import { appVersionText } from "Texts/exportImport";
export const promoteApp = () => {
cy.get(commonEeSelectors.promoteButton).click();
cy.get(commonEeSelectors.promoteButton).eq(1).click();
cy.waitForAppLoad();
cy.wait(3000);
};
export const releaseApp = () => {
cy.get(commonSelectors.releaseButton).click();
cy.get(commonSelectors.yesButton).click();
cy.verifyToastMessage(commonSelectors.toastMessage, "Version v1 released");
cy.wait(500);
};
export const launchApp = () => {
cy.url().then((url) => {
const parts = url.split("/");
const value = parts[parts.length - 1];
cy.log(`Extracted value: ${value}`);
cy.visit(`/applications/${value}`);
cy.wait(3000);
});
};
export const appPromote = (fromEnv, toEnv) => {
const commonActions = () => {
cy.get(commonEeSelectors.promoteButton).click();
cy.get(commonEeSelectors.promoteButton).eq(1).click();
cy.waitForAppLoad();
cy.wait(2000);
};
const transitions = {
development: {
staging: commonActions,
production: () => {
commonActions();
appPromote("staging", "production");
},
release: () => {
commonActions();
commonActions();
cy.get(commonSelectors.releaseButton).click();
cy.get(commonSelectors.yesButton).click();
cy.wait(500);
},
},
staging: {
production: commonActions,
release: () => {
commonActions();
cy.get(commonSelectors.releaseButton).click();
cy.get(commonSelectors.yesButton).click();
cy.wait(500);
},
},
};
const transition = transitions[fromEnv]?.[toEnv];
transition();
};
export const createNewVersion = (value, newVersion = [], version) => {
cy.get('[data-cy="list-current-env-name"]').click();
cy.get(multiEnvSelector.envNameList).eq(0).click();
cy.get(appVersionSelectors.currentVersionField(value)).click();
cy.get(appVersionSelectors.createNewVersionButton).click();
cy.get(appVersionSelectors.createVersionInputField).click();
cy.contains(`[id*="react-select-"]`, version).click();
cy.get(appVersionSelectors.versionNameInputField).click().type(newVersion[0]);
cy.get(appVersionSelectors.createNewVersionButton).click();
cy.waitForAppLoad();
cy.verifyToastMessage(
commonSelectors.toastMessage,
appVersionText.createdToastMessage
);
cy.get(appVersionSelectors.currentVersionField(newVersion[0])).should(
"be.visible"
);
};
export const selectVersion = (value, newVersion = []) => {
cy.get(appVersionSelectors.currentVersionField(value)).click();
cy.get(".react-select__menu-list .app-version-name")
.contains(newVersion[0])
.click();
cy.waitForAppLoad();
};
export const selectEnv = (envName) => {
const envIndex = {
development: 0,
staging: 1,
production: 2,
}[envName];
const isValidEnvName = (envName) => {
return (
envName === "development" ||
envName === "staging" ||
envName === "production"
);
};
if (isValidEnvName(envName)) {
cy.get('[data-cy="list-current-env-name"]').click();
cy.wait(500)
const envSelector = `${multiEnvSelector.envNameList}:eq(${envIndex})`;
cy.get(envSelector).click();
cy.waitForAppLoad();
}
};

View file

@ -9,6 +9,7 @@ import {
} from "Selectors/version"; } from "Selectors/version";
import { deleteVersionText, releasedVersionText } from "Texts/version"; import { deleteVersionText, releasedVersionText } from "Texts/version";
import { verifyComponent } from "Support/utils/basicComponents"; import { verifyComponent } from "Support/utils/basicComponents";
import { appPromote } from "./platform/multiEnv";
export const navigateToCreateNewVersionModal = (value) => { export const navigateToCreateNewVersionModal = (value) => {
cy.get(appVersionSelectors.appVersionLabel).click(); cy.get(appVersionSelectors.appVersionLabel).click();
@ -121,6 +122,9 @@ export const verifyDuplicateVersion = (newVersion = [], version) => {
}; };
export const releasedVersionAndVerify = (currentVersion) => { export const releasedVersionAndVerify = (currentVersion) => {
cy.ifEnv("Enterprise", () => {
appPromote("development", "production");
});
cy.contains("Release").click(); cy.contains("Release").click();
cy.get(confirmVersionModalSelectors.yesButton).click(); cy.get(confirmVersionModalSelectors.yesButton).click();

View file

@ -3,15 +3,14 @@ FROM node:22.15.1 AS builder
# Fix for JS heap limit allocation issue # Fix for JS heap limit allocation issue
ENV NODE_OPTIONS="--max-old-space-size=4096" ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN npm i -g npm@10.9.2 RUN npm i -g npm@10.9.2 && npm cache clean --force
RUN mkdir -p /app
RUN npm cache clean --force
RUN mkdir -p /app
WORKDIR /app WORKDIR /app
# Set GitHub token and branch as build arguments # Set GitHub token and branch as build arguments
ARG CUSTOM_GITHUB_TOKEN ARG CUSTOM_GITHUB_TOKEN
ARG BRANCH_NAME=main ARG BRANCH_NAME
# Clone and checkout the frontend repository # Clone and checkout the frontend repository
RUN git config --global url."https://x-access-token:${CUSTOM_GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/" RUN git config --global url."https://x-access-token:${CUSTOM_GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/"
@ -21,7 +20,7 @@ RUN git config --global http.postBuffer 524288000
RUN git clone https://github.com/ToolJet/ToolJet.git . RUN git clone https://github.com/ToolJet/ToolJet.git .
# The branch name needs to be changed the branch with modularisation in CE repo # The branch name needs to be changed the branch with modularisation in CE repo
RUN git checkout main RUN git checkout ${BRANCH_NAME}
RUN git submodule update --init --recursive RUN git submodule update --init --recursive
@ -33,85 +32,61 @@ COPY ./package.json ./package.json
# Build plugins # Build plugins
COPY ./plugins/package.json ./plugins/package-lock.json ./plugins/ COPY ./plugins/package.json ./plugins/package-lock.json ./plugins/
RUN npm --prefix plugins install RUN npm --prefix plugins ci --omit=dev
COPY ./plugins/ ./plugins/ COPY ./plugins/ ./plugins/
RUN NODE_ENV=production npm --prefix plugins run build RUN NODE_ENV=production npm --prefix plugins run build
RUN npm --prefix plugins prune --production RUN npm --prefix plugins prune --omit=dev
ENV TOOLJET_EDITION=ee
# Build frontend # Build frontend
COPY ./frontend/package.json ./frontend/package-lock.json ./frontend/ COPY ./frontend/package.json ./frontend/package-lock.json ./frontend/
RUN npm --prefix frontend install RUN npm --prefix frontend install
COPY ./frontend/ ./frontend/ COPY ./frontend/ ./frontend/
RUN npm --prefix frontend run build --production RUN npm --prefix frontend run build --production && npm --prefix frontend prune --production
RUN npm --prefix frontend prune --production
ENV NODE_ENV=production ENV NODE_ENV=production
ENV TOOLJET_EDITION=ee
# Build server # Build server
COPY ./server/package.json ./server/package-lock.json ./server/ COPY ./server/package.json ./server/package-lock.json ./server/
RUN npm --prefix server install RUN npm --prefix server ci --omit=dev
COPY ./server/ ./server/ COPY ./server/ ./server/
RUN npm install -g @nestjs/cli RUN npm install -g @nestjs/cli
RUN npm install -g copyfiles RUN npm install -g copyfiles
RUN npm --prefix server run build RUN npm --prefix server run build
RUN npm prune --production --prefix server
FROM debian:12 # Install dependencies for PostgREST, curl, unzip, etc.
RUN apt-get update -yq \
&& apt-get install curl wget 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 \ RUN apt-get update && apt-get install -y \
curl tar xz-utils postgresql postgresql-contrib postgresql-client && \ curl ca-certificates unzip tar \
apt-get clean && rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Install PostgREST from official Docker image ENV POSTGREST_VERSION=v12.2.0
COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin
RUN apt-get update && apt-get install -y supervisor RUN curl -Lo postgrest.tar.xz https://github.com/PostgREST/postgrest/releases/download/${POSTGREST_VERSION}/postgrest-v12.2.0-linux-static-x64.tar.xz && \
tar -xf postgrest.tar.xz && \
mv postgrest /postgrest && \
rm postgrest.tar.xz && \
chmod +x /postgrest
# Create supervisord configuration file FROM debian:12-slim
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" \
"[program:neo4j]\n" \
"command=neo4j console\n" \
"autostart=true\n" \
"autorestart=unexpected\n" \
"startsecs=30\n" \
"startretries=999\n" \
"priority=90\n" \
"exitcodes=0,1,2\n" \
"stopsignal=SIGTERM\n" \
"stopasgroup=true\n" \
"killasgroup=true\n" \
"redirect_stderr=true\n" \
"stdout_logfile=/var/log/neo4j/neo4j.log\n" \
"stdout_logfile_backups=10\n" \
"stderr_capture_maxbytes=20MB\n" \
"\n" | sed 's/ //' > /etc/supervisor/conf.d/supervisord.conf
# Create a wrapper for PostgREST to prefix its logs RUN apt-get update && \
RUN mv /bin/postgrest /bin/postgrest-original && \ apt-get install -y --no-install-recommends \
echo '#!/bin/bash\n\ curl \
exec /bin/postgrest-original "$@" 2>&1 | sed "s/^/[PostgREST] /"\n\ wget \
' > /bin/postgrest && \ gnupg \
chmod +x /bin/postgrest unzip \
ca-certificates \
xz-utils \
tar \
zip \
postgresql-client \
redis \
libaio1 \
git \
freetds-dev \
&& apt-get upgrade -y -o Dpkg::Options::="--force-confold" \
&& apt-get autoremove -y \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
RUN curl -O https://nodejs.org/dist/v22.15.1/node-v22.15.1-linux-x64.tar.xz \ RUN curl -O https://nodejs.org/dist/v22.15.1/node-v22.15.1-linux-x64.tar.xz \
@ -125,53 +100,18 @@ ENV PATH=/usr/local/lib/nodejs/bin:$PATH
ENV NODE_ENV=production ENV NODE_ENV=production
ENV TOOLJET_EDITION=ee ENV TOOLJET_EDITION=ee
ENV NODE_OPTIONS="--max-old-space-size=4096" ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN apt-get update && \
apt-get install -y postgresql-client freetds-dev libaio1 wget && \
apt-get -o Dpkg::Options::="--force-confold" upgrade -q -y --force-yes && \
apt-get -y autoremove && \
apt-get -y autoclean
# Install Neo4j # Install Neo4j + APOC
RUN wget -O - https://debian.neo4j.com/neotechnology.gpg.key | apt-key add - && \ RUN wget -O - https://debian.neo4j.com/neotechnology.gpg.key | apt-key add - && \
echo "deb https://debian.neo4j.com stable 5" > /etc/apt/sources.list.d/neo4j.list && \ echo "deb https://debian.neo4j.com stable 5" > /etc/apt/sources.list.d/neo4j.list && \
apt-get update && \ apt-get update && apt-get install -y neo4j=1:5.26.6 && apt-mark hold neo4j && \
apt-get install -y neo4j=1:5.26.6 && \ mkdir -p /var/lib/neo4j/plugins && \
apt-mark hold neo4j && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Set the necessary Neo4j environment variables
ENV NEO4J_HOME=/opt/neo4j
ENV NEO4J_CONF=/etc/neo4j
ENV NEO4J_DATA=/var/lib/neo4j/data
ENV NEO4J_LOG=/var/log/neo4j
ENV NEO4J_PLUGIN=/var/lib/neo4j/plugins
ENV NEO4J_IMPORT=/var/lib/neo4j/import
# Create the necessary directories for Neo4j
RUN mkdir -p /data/db /data/logs /data/plugins
RUN mkdir -p /opt/neo4j/plugins
# Configure APOC plugin for Neo4j
ENV NEO4J_dbms_active_plugins=apoc
# Download and install APOC plugin for Neo4j 5.x (BEFORE creating user)
RUN mkdir -p /var/lib/neo4j/plugins && \
wget -P /var/lib/neo4j/plugins https://github.com/neo4j/apoc/releases/download/5.26.6/apoc-5.26.6-core.jar && \ wget -P /var/lib/neo4j/plugins https://github.com/neo4j/apoc/releases/download/5.26.6/apoc-5.26.6-core.jar && \
# Try to download extended version echo "dbms.security.procedures.unrestricted=apoc.*" >> /etc/neo4j/neo4j.conf && \
(wget -P /var/lib/neo4j/plugins https://github.com/neo4j/apoc/releases/download/5.26.6/apoc-5.26.6-extended.jar || \
wget -P /var/lib/neo4j/plugins https://neo4j-contrib.github.io/neo4j-apoc-procedures/5.26.6/apoc-5.26.6-extended.jar || \
echo "Extended JAR not available, continuing with core only")
# Configure Neo4j with APOC
RUN echo "dbms.security.procedures.unrestricted=apoc.*" >> /etc/neo4j/neo4j.conf && \
echo "dbms.security.procedures.allowlist=apoc.*,algo.*,gds.*" >> /etc/neo4j/neo4j.conf && \ echo "dbms.security.procedures.allowlist=apoc.*,algo.*,gds.*" >> /etc/neo4j/neo4j.conf && \
echo "dbms.directories.plugins=/var/lib/neo4j/plugins" >> /etc/neo4j/neo4j.conf echo "dbms.directories.plugins=/var/lib/neo4j/plugins" >> /etc/neo4j/neo4j.conf && \
echo "dbms.security.auth_enabled=true" >> /etc/neo4j/neo4j.conf && \
# Configure Neo4j to use authentication apt-get clean && rm -rf /var/lib/apt/lists/*
RUN if [ -f "/etc/neo4j/neo4j.conf" ]; then \
sed -i '/dbms.security.auth_enabled/d' /etc/neo4j/neo4j.conf && \
echo "dbms.security.auth_enabled=true" >> /etc/neo4j/neo4j.conf; \
fi
# Install Instantclient Basic Light Oracle and Dependencies # Install Instantclient Basic Light Oracle and Dependencies
WORKDIR /opt/oracle WORKDIR /opt/oracle
@ -186,40 +126,39 @@ RUN wget https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketpla
# Set the Instant Client library paths # Set the Instant Client library paths
ENV LD_LIBRARY_PATH="/opt/oracle/instantclient_11_2:/opt/oracle/instantclient_21_10:${LD_LIBRARY_PATH}" ENV LD_LIBRARY_PATH="/opt/oracle/instantclient_11_2:/opt/oracle/instantclient_21_10:${LD_LIBRARY_PATH}"
RUN rm -f *.zip *.key && apt-get clean && rm -rf /var/lib/apt/lists/*
WORKDIR / WORKDIR /
RUN mkdir -p /app RUN mkdir -p /app
# copy npm scripts
COPY --from=builder /app/package.json ./app/package.json
# copy plugins dependencies
COPY --from=builder /app/plugins/dist ./app/plugins/dist
COPY --from=builder /app/plugins/client.js ./app/plugins/client.js
COPY --from=builder /app/plugins/node_modules ./app/plugins/node_modules
COPY --from=builder /app/plugins/packages/common ./app/plugins/packages/common
COPY --from=builder /app/plugins/package.json ./app/plugins/package.json
# copy frontend build
COPY --from=builder /app/frontend/build ./app/frontend/build
# copy server build
COPY --from=builder /app/server/package.json ./app/server/package.json
COPY --from=builder /app/server/.version ./app/server/.version
COPY --from=builder /app/server/ee/keys ./app/server/ee/keys
COPY --from=builder /app/server/node_modules ./app/server/node_modules
COPY --from=builder /app/server/templates ./app/server/templates
COPY --from=builder /app/server/scripts ./app/server/scripts
COPY --from=builder /app/server/dist ./app/server/dist
COPY --from=builder /app/server/src/assets ./app/server/src/assets
COPY ./docker/ee/ee-entrypoint.sh ./app/server/ee-entrypoint.sh RUN useradd --create-home --home-dir /home/appuser appuser
# Define non-sudo user # Use the PostgREST binary from the builder stage
RUN useradd --create-home --home-dir /home/appuser appuser \ COPY --from=builder --chown=appuser:0 /postgrest /usr/local/bin/postgrest
&& chown -R appuser:0 /app \
&& chown -R appuser:0 /home \ RUN mv /usr/local/bin/postgrest /usr/local/bin/postgrest-original && \
&& chmod u+x /app \ echo '#!/bin/bash\nexec /usr/local/bin/postgrest-original "$@" 2>&1 | sed "s/^/[PostgREST] /"' > /usr/local/bin/postgrest && \
&& chmod u+x /home \ chmod +x /usr/local/bin/postgrest
&& chmod -R g=u /app \
&& chmod -R g=u /home
# Copy application with ownership set directly to avoid chown -R
COPY --from=builder --chown=appuser:0 /app/package.json ./app/package.json
COPY --from=builder --chown=appuser:0 /app/plugins/dist ./app/plugins/dist
COPY --from=builder --chown=appuser:0 /app/plugins/client.js ./app/plugins/client.js
COPY --from=builder --chown=appuser:0 /app/plugins/node_modules ./app/plugins/node_modules
COPY --from=builder --chown=appuser:0 /app/plugins/packages/common ./app/plugins/packages/common
COPY --from=builder --chown=appuser:0 /app/plugins/package.json ./app/plugins/package.json
COPY --from=builder --chown=appuser:0 /app/frontend/build ./app/frontend/build
COPY --from=builder --chown=appuser:0 /app/server/package.json ./app/server/package.json
COPY --from=builder --chown=appuser:0 /app/server/.version ./app/server/.version
COPY --from=builder --chown=appuser:0 /app/server/ee/keys ./app/server/ee/keys
COPY --from=builder --chown=appuser:0 /app/server/node_modules ./app/server/node_modules
COPY --from=builder --chown=appuser:0 /app/server/templates ./app/server/templates
COPY --from=builder --chown=appuser:0 /app/server/scripts ./app/server/scripts
COPY --from=builder --chown=appuser:0 /app/server/dist ./app/server/dist
COPY --from=builder --chown=appuser:0 /app/server/src/assets ./app/server/src/assets
COPY ./docker/ee/ee-entrypoint.sh ./app/server/ee-entrypoint.sh
RUN mkdir -p /var/lib/neo4j/data/databases /var/lib/neo4j/data/transactions /var/log/neo4j /opt/neo4j/run && \ RUN mkdir -p /var/lib/neo4j/data/databases /var/lib/neo4j/data/transactions /var/log/neo4j /opt/neo4j/run && \
chown -R appuser:0 /var/lib/neo4j /var/log/neo4j /etc/neo4j /opt/neo4j/run && \ chown -R appuser:0 /var/lib/neo4j /var/log/neo4j /etc/neo4j /opt/neo4j/run && \
@ -258,31 +197,11 @@ RUN mkdir -p /var/lib/redis /var/log/redis /etc/redis \
&& chmod g+s /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 && 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 ENV HOME=/home/appuser
# Installing git for simple git commands
RUN apt-get update && apt-get install -y git && apt-get clean
# Switch back to appuser # Switch back to appuser
USER appuser USER appuser
WORKDIR /app WORKDIR /app
# Dependencies for scripts outside nestjs
RUN npm install dotenv@10.0.0 joi@17.4.1
RUN npm cache clean --force RUN npm install --prefix server --no-save dotenv@10.0.0 joi@17.4.1 && npm cache clean --force
ENTRYPOINT ["./server/ee-entrypoint.sh"] ENTRYPOINT ["./server/ee-entrypoint.sh"]

View file

@ -1,15 +1,47 @@
#!/bin/bash #!/bin/bash
set -e set -e
# Install grpcurl if not already installed
if ! command -v grpcurl &> /dev/null; then
echo "grpcurl not found, installing..."
apt update && apt install -y curl \
&& curl -sSL https://github.com/fullstorydev/grpcurl/releases/download/v1.8.0/grpcurl_1.8.0_linux_x86_64.tar.gz | tar -xzv -C /usr/local/bin grpcurl
fi
# Start Redis # Start Redis
# service redis-server start service redis-server start
# redis-server /etc/redis/redis.conf
# Start Postgres # Start Postgres
service postgresql start service postgresql start
# Start Temporal Server (SQLite configuration)
echo "Starting Temporal Server..."
/usr/bin/temporal-server -r / -c /etc/temporal/ -e temporal-server start &
# Export the PORT variable to be used by the application # Export the PORT variable to be used by the application
export PORT=${PORT:-80} export PORT=${PORT:-80}
# Start Supervisor # Start Supervisor
exec supervisord -c /etc/supervisor/conf.d/supervisord.conf exec supervisord -c /etc/supervisor/conf.d/supervisord.conf &
# Wait for Temporal Server to be ready
echo "Waiting for Temporal Server to be ready..."
sleep 10
# Check if namespace already exists
echo "Checking if Temporal namespace exists..."
if grpcurl -plaintext localhost:7233 temporal.api.workflowservice.v1.WorkflowService/ListNamespaces | grep -q '"name": "default"'; then
echo "Namespace 'default' already exists."
else
# Register the namespace if it doesn't exist
echo "Registering Temporal namespace..."
grpcurl -plaintext -d '{
"namespace": "default",
"description": "Default namespace",
"workflowExecutionRetentionPeriod": "259200s"
}' localhost:7233 temporal.api.workflowservice.v1.WorkflowService/RegisterNamespace
fi
# Run the worker process (last step)
echo "Starting worker process..."
npm run worker:prod

View file

@ -1,20 +1,20 @@
FROM tooljet/tooljet:ee-lts-latest FROM tooljet/tooljet:ee-lts-latest
# Copy PostgREST executable # Copy postgrest executable
COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin
# Install PostgreSQL # Install Postgres
USER root USER root
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
RUN echo "deb http://deb.debian.org/debian"
RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor
USER postgres USER postgres
RUN service postgresql start && \ RUN service postgresql start && \
psql -c "create role tooljet with login superuser password 'postgres';" psql -c "create role tooljet with login superuser password 'postgres';"
USER root USER root
# Install Redis
RUN apt update && apt -y install redis RUN apt update && apt -y install redis
# Create appuser home & ensure permission for supervisord and services # Create appuser home & ensure permission for supervisord and services
@ -22,6 +22,28 @@ RUN mkdir -p /var/log/supervisor /var/run/postgresql /var/lib/postgresql /var/li
chown -R appuser:appuser /etc/supervisor /var/log/supervisor /var/lib/redis && \ chown -R appuser:appuser /etc/supervisor /var/log/supervisor /var/lib/redis && \
chown -R postgres:postgres /var/run/postgresql /var/lib/postgresql chown -R postgres:postgres /var/run/postgresql /var/lib/postgresql
# Install Temporal Server Binaries
RUN curl -OL https://github.com/temporalio/temporal/releases/download/v1.24.2/temporal_1.24.2_linux_amd64.tar.gz && \
tar -xzf temporal_1.24.2_linux_amd64.tar.gz && \
mv temporal-server /usr/bin/temporal-server && \
chmod +x /usr/bin/temporal-server && \
rm temporal_1.24.2_linux_amd64.tar.gz
# Install Temporal UI Server Binaries
RUN curl -OL https://github.com/temporalio/ui-server/releases/download/v2.28.0/ui-server_2.28.0_linux_amd64.tar.gz && \
tar -xzf ui-server_2.28.0_linux_amd64.tar.gz && \
mv ui-server /usr/bin/temporal-ui-server && \
chmod +x /usr/bin/temporal-ui-server && \
rm ui-server_2.28.0_linux_amd64.tar.gz
# Copy Temporal configuration files
COPY ./docker/ee/temporal-server.yaml /etc/temporal/temporal-server.yaml
COPY ./docker/ee/temporal-ui-server.yaml /etc/temporal/temporal-ui-server.yaml
# Install grpcurl
RUN apt update && apt install -y curl \
&& curl -sSL https://github.com/fullstorydev/grpcurl/releases/download/v1.8.0/grpcurl_1.8.0_linux_x86_64.tar.gz | tar -xzv -C /usr/local/bin grpcurl
# Configure Supervisor to manage PostgREST, ToolJet, and Redis # Configure Supervisor to manage PostgREST, ToolJet, and Redis
RUN echo "[supervisord] \n" \ RUN echo "[supervisord] \n" \
"nodaemon=true \n" \ "nodaemon=true \n" \
@ -52,8 +74,10 @@ RUN echo "[supervisord] \n" \
"stdout_logfile=/dev/stdout \n" \ "stdout_logfile=/dev/stdout \n" \
"stdout_logfile_maxbytes=0 \n" | sed 's/ //' > /etc/supervisor/conf.d/supervisord.conf "stdout_logfile_maxbytes=0 \n" | sed 's/ //' > /etc/supervisor/conf.d/supervisord.conf
# ENV defaults # ENV defaults
ENV TOOLJET_HOST=http://localhost \ ENV TOOLJET_HOST=http://localhost \
TOOLJET_SERVER_URL=http://localhost \
PORT=80 \ PORT=80 \
NODE_ENV=production \ NODE_ENV=production \
LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \ LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \
@ -78,7 +102,14 @@ ENV TOOLJET_HOST=http://localhost \
REDIS_PORT=6379 \ REDIS_PORT=6379 \
REDIS_USER=default \ REDIS_USER=default \
REDIS_PASS= \ REDIS_PASS= \
TERM=xterm ENABLE_MARKETPLACE_FEATURE=true \
TERM=xterm \
ENABLE_WORKFLOW_SCHEDULING=true \
TEMPORAL_SERVER_ADDRESS=localhost:7233 \
TEMPORAL_TASK_QUEUE_NAME_FOR_WORKFLOWS=tooljet-workflows \
TOOLJET_WORKFLOWS_TEMPORAL_NAMESPACE=default \
TEMPORAL_ADDRESS=localhost:7233 \
TEMPORAL_CORS_ORIGINS=http://localhost:8080
# Set the entrypoint # Set the entrypoint
COPY ./docker/ee/ee-try-entrypoint-lts.sh /ee-try-entrypoint-lts.sh COPY ./docker/ee/ee-try-entrypoint-lts.sh /ee-try-entrypoint-lts.sh