Merge branch 'main' into feat/steps-v2-alignment-style-improvement

This commit is contained in:
johnsoncherian 2025-04-29 13:25:47 +05:30
commit 6e0d9b8073
16 changed files with 3110 additions and 155 deletions

View file

@ -2,7 +2,7 @@ name: Cypress App-Builder
on:
pull_request_target:
types: [labeled, unlabeled, closed]
types: [labeled]
workflow_dispatch:
env:
@ -12,22 +12,18 @@ env:
jobs:
Cypress-App-Builder:
runs-on: ubuntu-22.04
if: |
github.event.action == 'labeled' &&
(
github.event.label.name == 'run-cypress' ||
github.event.label.name == 'run-ce-app-builder' ||
github.event.label.name == 'run-ee-app-builder'
)
contains(github.event.pull_request.labels.*.name, 'run-ce-app-builder') ||
contains(github.event.pull_request.labels.*.name, 'run-ee-app-builder') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress')
strategy:
matrix:
edition: >-
${{
contains(github.event.pull_request.labels.*.name, 'run-cypress') && fromJson('["ce", "ee"]') ||
contains(github.event.pull_request.labels.*.name, 'run-ce-app-builder') && fromJson('["ce"]') ||
contains(github.event.pull_request.labels.*.name, 'run-ee-app-builder') && fromJson('["ee"]') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress') && fromJson('["ce", "ee"]') ||
fromJson('[]')
}}
@ -164,8 +160,8 @@ jobs:
Cypress-App-builder-Subpath:
runs-on: ubuntu-22.04
if: ${{ github.event.action == 'labeled' && github.event.label.name == 'run-cypress-app-builder-subpath' }}
if: contains(github.event.pull_request.labels.*.name, 'run-cypress') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-subpath')
steps:
- name: Checkout
@ -173,82 +169,6 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.ref }}
# Create Docker Buildx builder with platform configuration
- name: Set up Docker Buildx
run: |
mkdir -p ~/.docker/cli-plugins
curl -SL https://github.com/docker/buildx/releases/download/v0.11.0/buildx-v0.11.0.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx
chmod a+x ~/.docker/cli-plugins/docker-buildx
docker buildx create --name mybuilder --platform linux/arm64,linux/amd64,linux/amd64/v2,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6
docker buildx use mybuilder
- name: Set DOCKER_CLI_EXPERIMENTAL
run: echo "DOCKER_CLI_EXPERIMENTAL=enabled" >> $GITHUB_ENV
- name: use mybuilder buildx
run: docker buildx use mybuilder
- name: Build docker image
run: docker buildx build --platform=linux/amd64 -f docker/production.Dockerfile . -t tooljet/tj-osv:cypressplaform
- name: Set up environment variables
run: |
echo "TOOLJET_HOST=http://localhost:3000" >> .env
echo "LOCKBOX_MASTER_KEY=cd97331a419c09387bef49787f7da8d2a81d30733f0de6bed23ad8356d2068b2" >> .env
echo "SECRET_KEY_BASE=7073b9a35a15dd20914ae17e36a693093f25b74b96517a5fec461fc901c51e011cd142c731bee48c5081ec8bac321c1f259ef097ef2a16f25df17a3798c03426" >> .env
echo "PG_DB=tooljet_development" >> .env
echo "PG_USER=postgres" >> .env
echo "PG_HOST=postgres" >> .env
echo "PG_PASS=postgres" >> .env
echo "PG_PORT=5432" >> .env
echo "ENABLE_TOOLJET_DB=true" >> .env
echo "TOOLJET_DB=tooljet_db" >> .env
echo "TOOLJET_DB_USER=postgres" >> .env
echo "TOOLJET_DB_HOST=postgres" >> .env
echo "TOOLJET_DB_PASS=postgres" >> .env
echo "PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj" >> .env
echo "PGRST_HOST=postgrest" >> .env
echo "PGRST_DB_URI=postgres://postgres:postgres@postgres/tooljet_db" >> .env
echo "SSO_GIT_OAUTH2_CLIENT_ID=dummy" >> .env
echo "SSO_GIT_OAUTH2_CLIENT_SECRET=dummy" >> .env
echo "SSO_GIT_OAUTH2_HOST=dummy" >> .env
echo "SSO_GOOGLE_OAUTH2_CLIENT_ID=dummy" >> .env
echo "SUB_PATH=/apps/tooljet/" >> .env
echo "NODE_ENV=production" >> .env
echo "SERVE_CLIENT=true" >> .env
echo "ENABLE_PRIVATE_APP_EMBED=true" >> .env
- name: Pulling the docker-compose file
run: curl -LO https://tooljet-test.s3.us-west-1.amazonaws.com/docker-compose.yaml && mkdir postgres_data
- name: Run docker-compose file
run: docker-compose up -d
- name: Checking containers
run: docker ps -a
- name: docker logs
run: sudo docker logs Tooljet-app
- name: Wait for the server to be ready
run: |
timeout 1500 bash -c '
until curl --silent --fail http://localhost:80/apps/tooljet/; do
sleep 5
done'
- name: Seeding (Setup Super Admin)
run: |
curl 'http://localhost:3000/api/onboarding/setup-super-admin' \
-H 'Content-Type: application/json' \
--data-raw '{
"companyName": "ToolJet",,
"name": "The Developer",
"workspaceName": "Tooljet'\''s workspace",
"email": "dev@tooljet.io",
"password": "password"
}'
- name: Create Cypress environment file
id: create-json
uses: jsdaniell/create-json@1.1.2

View file

@ -2,7 +2,7 @@ name: Cypress Marketplace
on:
pull_request_target:
types: [labeled, unlabeled, closed]
types: [labeled]
workflow_dispatch:
@ -14,13 +14,9 @@ jobs:
Cypress-Marketplace:
runs-on: ubuntu-22.04
if: |
github.event.action == 'labeled' &&
(
github.event.label.name == 'run-cypress' ||
github.event.label.name == 'run-ce-cypress-marketplace' ||
github.event.label.name == 'run-ee-cypress-marketplace'
)
if: contains(github.event.pull_request.labels.*.name, 'run-cypress') ||
contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-marketplace') ||
contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-marketplace')
strategy:
matrix:
@ -44,7 +40,7 @@ jobs:
mkdir -p ~/.docker/cli-plugins
curl -SL https://github.com/docker/buildx/releases/download/v0.11.0/buildx-v0.11.0.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx
chmod a+x ~/.docker/cli-plugins/docker-buildx
docker buildx create --name mybuilder --platform linux/arm64,linux/amd64,linux/amd64/v2,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6
docker buildx create --name mybuilder --platform linux/arm64,linux/amd64
docker buildx use mybuilder
- name: Set DOCKER_CLI_EXPERIMENTAL
@ -189,7 +185,8 @@ jobs:
Cypress-Marketplace-Subpath:
runs-on: ubuntu-22.04
if: ${{ github.event.action == 'labeled' && github.event.label.name == 'run-cypress-marketplace-subpath' }}
if: contains(github.event.pull_request.labels.*.name, 'run-cypress') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-subpath')
steps:
- name: Checkout

View file

@ -2,7 +2,7 @@ name: Cypress Platform
on:
pull_request_target:
types: [labeled, unlabeled, closed]
types: [labeled]
workflow_dispatch:
env:
@ -12,14 +12,9 @@ env:
jobs:
Cypress-Platform:
runs-on: ubuntu-22.04
if: |
github.event.action == 'labeled' &&
(
github.event.label.name == 'run-cypress' ||
github.event.label.name == 'run-ce-cypress-platform' ||
github.event.label.name == 'run-ee-cypress-platform'
)
if: contains(github.event.pull_request.labels.*.name, 'run-cypress') ||
contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-platform') ||
contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-platform')
strategy:
matrix:
edition: >-

View file

@ -12,7 +12,7 @@ permissions:
jobs:
# Community Edition
# Community Edition CE
create-ce-review-app:
if: ${{ github.event.action == 'labeled' && (github.event.label.name == 'create-ce-review-app' || github.event.label.name == 'review-app') }}
runs-on: ubuntu-latest
@ -72,7 +72,7 @@ jobs:
"envVars": [
{
"key": "PG_HOST",
"value": "${{ secrets.RENDER_PG_HOST }}"
"value": "localhost"
},
{
"key": "PG_PORT",
@ -80,11 +80,11 @@ jobs:
},
{
"key": "PG_USER",
"value": "${{ secrets.RENDER_PG_USER }}"
"value": "tooljet"
},
{
"key": "PG_PASS",
"value": "${{ secrets.RENDER_PG_PASS }}"
"value": "postgres"
},
{
"key": "PG_DB",
@ -96,15 +96,15 @@ jobs:
},
{
"key": "TOOLJET_DB_HOST",
"value": "${{ secrets.RENDER_PG_HOST }}"
"value": "localhost"
},
{
"key": "TOOLJET_DB_USER",
"value": "${{ secrets.RENDER_PG_USER }}"
"value": "tooljet"
},
{
"key": "TOOLJET_DB_PASS",
"value": "${{ secrets.RENDER_PG_PASS }}"
"value": "postgres"
},
{
"key": "TOOLJET_DB_PORT",
@ -116,7 +116,7 @@ jobs:
},
{
"key": "PGRST_DB_URI",
"value": "postgres://${{ secrets.RENDER_PG_USER }}:${{ secrets.RENDER_PG_PASS }}@${{ secrets.RENDER_PG_HOST }}/${{ env.PR_NUMBER }}-ce-tjdb"
"value": "postgres://tooljet:postgres@localhost/${{ env.PR_NUMBER }}-ce-tjdb"
},
{
"key": "PGRST_HOST",
@ -162,18 +162,6 @@ jobs:
"key": "SMTP_PASSWORD",
"value": "${{ secrets.RENDER_SMTP_PASSWORD }}"
},
{
"key": "TEMPORAL_SERVER_ADDRESS",
"value": "https://auto-setup-1-25-1.onrender.com"
},
{
"key": "TEMPORAL_TASK_QUEUE_NAME_FOR_WORKFLOWS",
"value": "tooljet-ce-pr-${{ env.PR_NUMBER }}"
},
{
"key": "TOOLJET_WORKFLOWS_TEMPORAL_NAMESPACE",
"value": "default"
},
{
"key": "TOOLJET_MARKETPLACE_URL",
"value": "${{ secrets.MARKETPLACE_BUCKET }}"
@ -424,7 +412,7 @@ jobs:
"envVars": [
{
"key": "PG_HOST",
"value": "${{ secrets.RENDER_PG_HOST }}"
"value": "localhost"
},
{
"key": "PG_PORT",
@ -432,11 +420,11 @@ jobs:
},
{
"key": "PG_USER",
"value": "${{ secrets.RENDER_PG_USER }}"
"value": "tooljet"
},
{
"key": "PG_PASS",
"value": "${{ secrets.RENDER_PG_PASS }}"
"value": "postgres"
},
{
"key": "PG_DB",
@ -448,15 +436,15 @@ jobs:
},
{
"key": "TOOLJET_DB_HOST",
"value": "${{ secrets.RENDER_PG_HOST }}"
"value": "localhost"
},
{
"key": "TOOLJET_DB_USER",
"value": "${{ secrets.RENDER_PG_USER }}"
"value": "tooljet"
},
{
"key": "TOOLJET_DB_PASS",
"value": "${{ secrets.RENDER_PG_PASS }}"
"value": "postgres"
},
{
"key": "TOOLJET_DB_PORT",
@ -468,7 +456,7 @@ jobs:
},
{
"key": "PGRST_DB_URI",
"value": "postgres://${{ secrets.RENDER_PG_USER }}:${{ secrets.RENDER_PG_PASS }}@${{ secrets.RENDER_PG_HOST }}/${{ env.PR_NUMBER }}-ee-tjdb"
"value": "postgres://tooljet:postgres@localhost/${{ env.PR_NUMBER }}-ee-tjdb"
},
{
"key": "PGRST_HOST",
@ -1124,4 +1112,3 @@ jobs:
# } catch (e) {
# console.log(e)
# }

View file

@ -192,6 +192,7 @@ Cypress.Commands.add("apiCreateWorkspace", (workspaceName, workspaceSlug) => {
{ log: false }
).then((response) => {
expect(response.status).to.equal(201);
return response;
});
});
});

View file

@ -522,10 +522,8 @@ describe("Manage Groups", () => {
commonSelectors.buttonSelector(exportAppModalText.exportSelectedVersion)
).click();
cy.exec("ls ./cypress/downloads/").then((result) => {
cy.log(result);
const downloadedAppExportFileName = result.stdout.split("\n")[0];
exportedFilePath = `cypress/downloads/${downloadedAppExportFileName}`;
cy.log(exportedFilePath);
cy.get(importSelectors.dropDownMenu).should("be.visible").click();
cy.get(importSelectors.importOptionInput).selectFile(exportedFilePath, {
force: true,

View file

@ -0,0 +1,382 @@
import { fake } from "Fixtures/fake";
import {
createUser, getAllUsers, getUser, updateUser, createGroup, validateUserInGroup, updateUserRole,
getAllWorkspaces, replaceUserWorkspace, replaceUserWorkspacesRelations
} from 'Support/utils/api';
import { groupsSelector } from "Selectors/manageGroups";
import { commonSelectors } from 'Selectors/common';
import { searchUser, navigateToManageUsers, logout, navigateToManageGroups } from 'Support/utils/common';
describe("API Test", () => {
const sanitize = (str) => str.toLowerCase().replace(/[^A-Za-z]/g, "");
let userId;
let workspaceId;
const data = {
firstName: fake.firstName,
lastName: fake.lastName,
firstName1: fake.firstName,
lastName1: fake.lastName,
firstName2: fake.firstName,
lastName2: fake.lastName,
email: fake.email.toLowerCase().replaceAll("[^A-Za-z]", ""),
email1: fake.email.toLowerCase().replaceAll("[^A-Za-z]", ""),
email2: fake.email.toLowerCase().replaceAll("[^A-Za-z]", ""),
workspaceName: sanitize(fake.lastName),
workspaceSlug: sanitize(fake.lastName),
workspaceName1: sanitize(fake.firstName),
workspaceSlug1: sanitize(fake.firstName),
group1: sanitize(fake.firstName),
group2: sanitize(fake.firstName),
group3: sanitize(fake.firstName),
group4: sanitize(fake.firstName),
group5: sanitize(fake.firstName),
appName: fake.companyName
};
//user with all valid details
const userData = {
name: `${data.firstName} ${data.lastName}`,
email: data.email,
password: "password",
status: "active",
workspaces: [
{
name: "My workspace",
status: "active",
groups: [
{ name: data.group1 },
{ name: data.group2 }
]
},
{
name: data.workspaceName,
status: "active",
role: "builder",
groups: [{ name: data.group3 }]
},
{
name: data.workspaceName1,
status: "archived",
role: "admin",
groups: [{ name: data.group4 }]
}
]
};
beforeEach(() => {
cy.defaultWorkspaceLogin();
});
it("Create user with valid details", () => {
// create multiple groups in different workspaces
navigateToManageGroups();
[data.group1, data.group2, data.group5].forEach(createGroup);
//builder group
cy.get(groupsSelector.groupLink(data.group5)).click();
cy.get(groupsSelector.permissionsLink).click();
cy.get(groupsSelector.appsCreateCheck).check();
[
{ name: data.workspaceName, slug: data.workspaceSlug, group: data.group3 },
{ name: data.workspaceName1, slug: data.workspaceSlug1, group: data.group4 }
].forEach(({ name, slug, group }) => {
cy.apiCreateWorkspace(name, slug);
cy.visit(slug);
navigateToManageGroups();
createGroup(group);
});
// Added valid user and logged-in in the workpsace
cy.visit("/my-workspace");
cy.wait(500);
createUser(userData).then((response) => {
expect(response.status).to.eq(201);
userId = response.body.id;
workspaceId = response.body.workspaces[0].id;
navigateToManageUsers();
searchUser(data.email);
cy.contains("td", data.email)
.parent()
.within(() => {
cy.get("td small").should("have.text", "active");
});
validateUserInGroup(data.email, "my-workspace", "end-user");
validateUserInGroup(data.email, data.workspaceSlug, "builder");
validateUserInGroup(data.email, data.workspaceSlug1, "admin", false);
cy.apiLogout();
cy.apiLogin(data.email, "password");
cy.visit("/my-workspace");
cy.get(commonSelectors.workspaceName).should("have.text", "My workspace");
logout();
//Retrieve all users, a specific user by ID, and all workspaces
cy.defaultWorkspaceLogin();
navigateToManageUsers();
let number = 0;
cy.get('[data-cy="title-users-page"]').invoke('text').then((text) => {
number = parseInt(text.match(/\d+/)[0], 10);
});
getAllUsers().then((response) => {
expect(response.status).to.eq(200);
//expect(response.body.length).to.eq(number); //error due to removal of user from instance
});
getUser(userId).then((response) => {
expect(response.status).to.eq(200);
expect(response.body.name).to.eq(`${data.firstName} ${data.lastName}`);
});
getAllWorkspaces().then((response) => {
expect(response.status).to.eq(200);
});
});
});
it('Handles user creation errors', () => {
const invalidUserData = [
{ // Duplicate user
data: { ...userData },
expectedStatus: 422,
expectedMessage: 'Already exists!'
},
{ // Invalid email and long password
data: {
...userData,
name: `${data.firstName1} ${data.lastName1}`,
email: 'invalid-email',
password: 'a'.repeat(101)
},
expectedStatus: 400,
expectedMessages: ['email must be an email', 'password must be shorter than or equal to 100 characters']
},
{ // Non-existing group
data: {
...userData,
name: `${data.firstName1} ${data.lastName1}`,
email: `${data.email1}`,
workspaces: [{ name: 'My workspace', status: 'active', groups: [{ name: 'NonExistingGroup' }] }]
},
expectedStatus: 400,
expectedMessage: 'Group permission id or name not found:'
},
{ // Non-existing workspace
data: {
...userData,
name: `${data.firstName1} ${data.lastName1}`,
email: `${data.email1}`,
workspaces: [{ name: 'NonExistingWorkspace', status: 'active' }]
},
expectedStatus: 400,
expectedMessage: 'The workspaces id or name do not exist:'
}
];
invalidUserData.forEach(({ data, expectedStatus, expectedMessages, expectedMessage }) => {
createUser(data).then((response) => {
expect(response.status).to.eq(expectedStatus);
if (expectedMessages) {
expectedMessages.forEach(msg => expect(response.body.message).to.include(msg));
} else {
expect(response.body.message).to.include(expectedMessage);
}
});
});
//Conflict permission
const enduserData = {
...userData,
name: `${data.firstName1} ${data.lastName1}`,
email: `${data.email1}`,
workspaces: [{ name: 'My workspace', status: 'active', groups: [{ name: data.group5 }] }]
}
createUser(enduserData).then((response) => {
expect(response.status).to.eq(400);
expect(response.body.message.title).to.include("Conflicting permissions");
})
});
it("Update user details and workspaces relations", () => {
const updatedUserData = {
name: `${data.firstName1} ${data.lastName1}`,
email: data.email1,
password: "updatedpassword"
}
updateUser(userId, updatedUserData).then((response) => {
expect(response.status).to.eq(200);
})
cy.apiLogout();
cy.apiLogin(updatedUserData.email, updatedUserData.password);
cy.apiLogout();
// Replace user workspaces relations
cy.apiLogin();
validateUserInGroup(updatedUserData.email, "my-workspace", data.group2);
validateUserInGroup(updatedUserData.email, data.workspaceSlug, data.group3);
cy.visit(data.workspaceSlug1);
navigateToManageUsers();
searchUser(updatedUserData.email);
cy.contains("td", updatedUserData.email);
replaceUserWorkspacesRelations(userId, [
{ name: "My workspace", status: "active", role: "end-user", groups: [{ name: data.group1 }] },
{ name: data.workspaceName, status: "active", role: "builder", groups: [] }
]).then((response) => {
expect(response.status).to.eq(200);
});
navigateToManageUsers();
validateUserInGroup(updatedUserData.email, "my-workspace", data.group2, false);
validateUserInGroup(updatedUserData.email, data.workspaceSlug, data.group3, false);
cy.visit(data.workspaceSlug1);
navigateToManageUsers();
searchUser(updatedUserData.email);
cy.get('[data-cy="text-no-result-found"]').contains("No result found");
replaceUserWorkspacesRelations(userId, []).then((response) => {
expect(response.status).to.eq(200);
});
cy.visit("my-workspace");
navigateToManageUsers();
searchUser(updatedUserData.email);
cy.get('[data-cy="text-no-result-found"]').contains("No result found");
});
it("update user role", () => {
const userData2 = {
name: `${data.firstName} ${data.lastName}`,
email: data.email,
password: "password",
status: "active",
workspaces: [
{
name: "My workspace",
status: "active"
}
]
}
let userId1;
let workspaceId1;
createUser(userData2).then((response) => {
expect(response.status).to.eq(201);
userId1 = response.body.id;
workspaceId1 = response.body.workspaces[0].id;
//update role to builder and validate user in builder's group
updateUserRole(workspaceId1, { newRole: "builder", userId: userId1 })
.then((response) => {
expect(response.status).to.eq(200);
});
validateUserInGroup(userData2.email, "my-workspace", "builder");
//update role to end-user and validate user is removed from builder's group
updateUserRole(workspaceId1, { newRole: "end-user", userId: userId1 })
.then((response) => {
expect(response.status).to.eq(200);
});
validateUserInGroup(userData2.email, "my-workspace", data.group5, false);
// update role to builders and validate app's owner role can't be updated
updateUserRole(workspaceId1, { newRole: "builder", userId: userId1 })
.then((response) => {
expect(response.status).to.eq(200);
});
cy.apiLogout();
cy.apiLogin(userData2.email, userData2.password);
cy.apiCreateApp(data.appName);
cy.apiLogout();
cy.defaultWorkspaceLogin();
updateUserRole(workspaceId1, { newRole: "end-user", userId: userId1 })
.then((response) => {
expect(response.status).to.eq(400);
expect(response.body.message.title).to.include("Can not change user role");
});
});
});
const userData3 = {
name: `${data.firstName2} ${data.lastName2}`,
email: data.email2,
password: "password",
status: "active",
workspaces: [
{
name: "My workspace",
status: "active",
groups: [
{ name: data.group1 },
{ name: data.group2 }
]
},
{
name: data.workspaceName,
status: "active",
role: "builder",
groups: [{ name: data.group3 }]
},
{
name: data.workspaceName1,
status: "archived",
role: "admin",
groups: [{ name: data.group4 }]
}
]
};
it("Replace user workspace", () => {
let userId1, workspaceId1;
createUser(userData3).then((response) => {
expect(response.status).to.eq(201);
userId1 = response.body.id;
workspaceId1 = response.body.workspaces[0].id;
// Helper function to replace user workspace and validate response
const replaceAndValidate = (payload, expectedStatus = 200) => {
return replaceUserWorkspace(userId1, workspaceId1, payload).then((response) => {
expect(response.status).to.eq(expectedStatus);
});
};
// No change if empty request body
replaceAndValidate({}).then(() => {
validateUserInGroup(userData3.email, "my-workspace", data.group1);
validateUserInGroup(userData3.email, "my-workspace", data.group2);
});
// Archive the user and verify status
replaceAndValidate({ status: "archived" }).then(() => {
navigateToManageUsers();
searchUser(userData3.email);
cy.contains("td", userData3.email)
.parent()
.within(() => {
cy.get("td small").should("have.text", "archived");
});
});
// Reactivate user and validate groups
replaceAndValidate({ status: "active" }).then(() => {
validateUserInGroup(userData3.email, "my-workspace", data.group1);
validateUserInGroup(userData3.email, "my-workspace", data.group2);
});
// Update groups and validate removal
replaceAndValidate({ groups: [{ name: data.group1 }] }).then(() => {
validateUserInGroup(userData3.email, "my-workspace", data.group2, false);
});
//Empty group array, user removed from groups
replaceAndValidate({ groups: [] }).then(() => {
validateUserInGroup(userData3.email, "my-workspace", data.group1, false);
});
//Conflict permission
replaceAndValidate({ groups: [{ name: data.group5 }] }, 400);
//Add user in groups and validate
replaceAndValidate({ groups: [{ name: data.group1 }, { name: data.group2 }] });
validateUserInGroup(userData3.email, "my-workspace", data.group1);
validateUserInGroup(userData3.email, "my-workspace", data.group2);
});
});
});

View file

@ -0,0 +1,160 @@
import { importApp, exportApp, allAppsDetails } from 'Support/utils/api';
import { fake } from "Fixtures/fake";
describe("Export and Import API ", () => {
const sanitize = (str) => str.toLowerCase().replace(/[^A-Za-z]/g, "");
const data = {
workspaceName: sanitize(fake.lastName),
workspaceSlug: sanitize(fake.lastName),
}
const fixtureFiles = {
requestData: "templates/import_unnamed_file.json",
requestData2: "templates/import_named_file.json",
requestData3: "templates/three-versions.json",
};
let requestData, requestData2, requestData3;
beforeEach(() => {
cy.defaultWorkspaceLogin();
const fixturePromises = Object.entries(fixtureFiles).map(([key, file]) =>
cy.fixture(file).then((data) => ({ key, data }))
);
// Assign loaded data to respective variables
return Promise.all(fixturePromises).then((results) => {
results.forEach(({ key, data }) => {
({ requestData, requestData2, requestData3 }[key] = data);
});
});
});
it("Import App API", () => {
const workspaceId = Cypress.env("workspaceId");
importApp(workspaceId, requestData).then((response) => {
expect(response.status).to.eq(201);
expect(response.body.message).to.include("App imported successfully into workspace");
});
//Invalid access token and workspace
importApp(workspaceId, requestData, {
Authorization: "Basic xyz",
"Content-Type": "application/json"
}).then((response) => {
expect(response.status).to.eq(403);
});
importApp(workspaceId, requestData, {
Authorization: "",
"Content-Type": "application/json"
}).then((response) => {
expect(response.status).to.eq(403);
});
importApp(`${workspaceId}ee`, requestData).then((response) => {
expect(response.status).to.eq(400);
});
//Import named file
importApp(workspaceId, requestData2).then((response) => {
expect(response.status).to.eq(201);
expect(response.body.message).to.include("App imported successfully into workspace");
});
cy.reload();
cy.get('[data-cy="app_json-title"]').should("exist");
//duplicate app
importApp(workspaceId, requestData2).then((response) => {
expect(response.status).to.eq(409);
expect(response.body.message).to.include("App with app_json already exists in the workspace");
});
cy.deleteApp("app_json");
cy.get('[data-cy="app_json-title"]').should("not.exist");
//Import app in another workpsace
let newWorkspaceId;
cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug).then((res) => {
newWorkspaceId = res.body.organization_id;
cy.visit(data.workspaceSlug);
importApp(newWorkspaceId, requestData).then((response) => {
expect(response.status).to.eq(201);
expect(response.body.message).to.include("App imported successfully into workspace");
});
});
});
it("Export App API", () => {
const workspaceId = Cypress.env("workspaceId");
let appId;
importApp(workspaceId, requestData3).then((response) => {
expect(response.status).to.eq(201);
expect(response.body.message).to.include("App imported successfully into workspace");
}).then(() => {
cy.get('[data-cy^="import-export-app"]')
.first()
.find('[data-cy="edit-button"]')
.click({ force: true });
cy.skipWalkthrough();
});
cy.get('[data-cy="left-sidebar-settings-button"]').click();
cy.get('[data-cy="app-slug-input-field"]').invoke('val').then((value) => {
appId = value;
//export last created version
exportApp(workspaceId, appId, "").then((response) => {
expect(response.status).to.eq(201);
expect(response.body.app[0].definition.appV2.appVersions.length).to.eq(1);
expect(response.body.app[0].definition.appV2.appVersions[0].name).to.eq("v3");
});
//export specific versions
exportApp(workspaceId, appId, "?appVersion=v2").then((response) => {
expect(response.status).to.eq(201);
expect(response.body.app[0].definition.appV2.appVersions.length).to.eq(1);
expect(response.body.app[0].definition.appV2.appVersions[0].name).to.eq("v2");
});
//export all versions
exportApp(workspaceId, appId, "?exportAllVersions=true").then((response) => {
expect(response.status).to.eq(201);
expect(response.body.app[0].definition.appV2.appVersions.length).to.eq(3);
});
//Invalid access token and workspace
/* exportApp(workspaceId, appId, "", {
Authorization: "",
"Content-Type": "application/json"
}).then((response) => {
expect(response.status).to.eq(403);
});
exportApp(workspaceId, appId, "", {
Authorization: "",
"Content-Type": "application/json"
}).then((response) => {
expect(response.status).to.eq(403);
});
exportApp(`${workspaceId}ee`, appId, "").then((response) => {
expect(response.status).to.eq(400);
});
*/
//with and without TJDB -x.tooljet_database
exportApp(workspaceId, appId, "?exportTJDB=false").then((response) => {
expect(response.status).to.eq(201);
expect(response.body).not.to.have.property("tooljet_database");
});
exportApp(workspaceId, appId, "?exportTJDB=true").then((response) => {
expect(response.status).to.eq(201);
expect(response.body).to.have.property("tooljet_database");
});
});
//All Apps details
allAppsDetails(workspaceId).then((response) => {
expect(response.status).to.eq(200);
});
});
});

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,72 @@
import { groupsSelector } from "Selectors/manageGroups";
import { navigateToManageGroups } from 'Support/utils/common';
export const apiRequest = (method, url, body = {}, headers = {}) => {
return cy.request({
method,
url,
body,
headers: {
Authorization: Cypress.env('AUTH_TOKEN'),
"Content-Type": "application/json",
...headers,
},
failOnStatusCode: false
});
};
export const createUser = (userData) => {
return apiRequest("POST", `${Cypress.env('API_URL')}/ext/users`, userData);
};
export const getUser = (userId) => {
return apiRequest("GET", `${Cypress.env('API_URL')}/ext/user/${userId}`);
};
export const getAllUsers = () => {
return apiRequest("GET", `${Cypress.env('API_URL')}/ext/users`);
};
export const updateUser = (userId, userData) => {
return apiRequest("PATCH", `${Cypress.env('API_URL')}/ext/user/${userId}`, userData);
};
export const updateUserRole = (workspaceId, userData) => {
return apiRequest("PUT", `${Cypress.env('API_URL')}/ext/update-user-role/workspace/${workspaceId}`, userData);
}
export const replaceUserWorkspace = (userId, workspaceId, userData) => {
return apiRequest("PATCH", `${Cypress.env('API_URL')}/ext/user/${userId}/workspace/${workspaceId}`, userData);
}
export const replaceUserWorkspacesRelations = (userId, userData) => {
return apiRequest("PUT", `${Cypress.env('API_URL')}/ext/user/${userId}/workspaces`, userData);
}
export const getAllWorkspaces = () => {
return apiRequest("GET", `${Cypress.env('API_URL')}/ext/workspaces`);
}
export const importApp = (workspaceId, appData, headers) => {
return apiRequest("POST", `${Cypress.env('API_URL')}/ext/import/workspace/${workspaceId}/apps`, appData, headers);
}
export const exportApp = (workspaceId, appId, endpoint, headers) => {
return apiRequest("POST", `${Cypress.env('API_URL')}/ext/export/workspace/${workspaceId}/apps/${appId}${endpoint}`, headers);
}
export const allAppsDetails = (workspaceIds) => {
return apiRequest("GET", `${Cypress.env('API_URL')}/ext/workspace/${workspaceIds}/apps`);
}
export const createGroup = (groupName) => {
cy.get(groupsSelector.createNewGroupButton).click();
cy.clearAndType(groupsSelector.groupNameInput, groupName);
cy.get(groupsSelector.createGroupButton).click();
}
export const validateUserInGroup = (email, workspaceSlug, groupName, shouldExist = true) => {
if (workspaceSlug) cy.visit(workspaceSlug);
navigateToManageGroups();
cy.get(groupsSelector.groupLink(groupName)).click();
cy.get(groupsSelector.usersLink).click();
const userRow = `[data-cy="${email}-user-row"]`;
cy.get(userRow).should(shouldExist ? "exist" : "not.exist");
};

View file

@ -853,6 +853,9 @@ export const createGroupsAndAddUserInGroup = (groupName, email) => {
commonSelectors.toastMessage,
groupsText.groupCreatedToast
);
addUserInGroup(groupName, email);
};
export const addUserInGroup = (groupName, email) => {
cy.get(groupsSelector.groupLink(groupName)).click();
cy.clearAndType(groupsSelector.multiSelectSearchInput, email);
cy.wait(2000);
@ -862,7 +865,7 @@ export const createGroupsAndAddUserInGroup = (groupName, email) => {
commonSelectors.toastMessage,
groupsText.userAddedToast
);
};
}
export const inviteUserBasedOnRole = (firstName, email, role = "end-user") => {
fillUserInviteForm(firstName, email);

View file

@ -1,6 +1,10 @@
#!/bin/bash
set -e
if [ -f "./.env" ]; then
export $(grep -v '^#' ./.env | xargs -d '\n') || true
fi
if [ -d "./server/dist" ]; then
SETUP_CMD='npm run db:setup:prod'
else

View file

@ -75,20 +75,39 @@ 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 ./docker/ce-entrypoint.sh ./app/server/entrypoint.sh
WORKDIR /app
USER root
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://deb.debian.org/debian"
RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor
USER postgres
RUN service postgresql start && \
psql -c "create role tooljet with login superuser password 'postgres';"
USER root
# ENV defaults
ENV TOOLJET_HOST=http://localhost:80 \
PGRST_HOST=http://localhost:3000 \
PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \
TOOLJET_DB=tooljet_db \
ENABLE_TOOLJET_DB=true \
PORT=80 \
ENV TOOLJET_HOST=http://localhost \
NODE_ENV=production \
LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \
SECRET_KEY_BASE=replace_with_secret_key_base \
ORM_LOGGING=all \
PG_DB=tooljet_production \
PG_USER=tooljet \
PG_PASS=postgres \
PG_HOST=localhost \
ENABLE_TOOLJET_DB=true \
TOOLJET_DB_HOST=localhost \
TOOLJET_DB_USER=tooljet \
TOOLJET_DB_PASS=postgres \
TOOLJET_DB=tooljet_db \
PGRST_HOST=http://localhost:3000 \
PGRST_DB_URI=postgres://tooljet:postgres@localhost/tooljet_db \
PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \
PGRST_DB_PRE_CONFIG=postgrest.pre_config \
ORM_LOGGING=true \
DEPLOYMENT_PLATFORM=docker:local \
HOME=/home/appuser \
TERM=xterm
CMD ["/usr/bin/supervisord"]

View file

@ -104,20 +104,40 @@ 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 ./docker/ee/ee-entrypoint.sh ./app/server/ee-entrypoint.sh
WORKDIR /app
# ENV defaults
ENV TOOLJET_HOST=http://localhost:80 \
PGRST_HOST=http://localhost:3000 \
PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \
TOOLJET_DB=tooljet_db \
ENABLE_TOOLJET_DB=true \
PORT=80 \
USER root
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://deb.debian.org/debian"
RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor
USER postgres
RUN service postgresql start && \
psql -c "create role tooljet with login superuser password 'postgres';"
USER root
# ENV defaults
ENV TOOLJET_HOST=http://localhost \
NODE_ENV=production \
LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \
SECRET_KEY_BASE=replace_with_secret_key_base \
ORM_LOGGING=all \
PG_DB=tooljet_production \
PG_USER=tooljet \
PG_PASS=postgres \
PG_HOST=localhost \
ENABLE_TOOLJET_DB=true \
TOOLJET_DB_HOST=localhost \
TOOLJET_DB_USER=tooljet \
TOOLJET_DB_PASS=postgres \
TOOLJET_DB=tooljet_db \
PGRST_HOST=http://localhost:3000 \
PGRST_DB_URI=postgres://tooljet:postgres@localhost/tooljet_db \
PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \
PGRST_DB_PRE_CONFIG=postgrest.pre_config \
ORM_LOGGING=true \
DEPLOYMENT_PLATFORM=docker:local \
REDIS_PASS= \
TERM=xterm
CMD ["/usr/bin/supervisord"]

View file

@ -1,6 +1,8 @@
#!/bin/bash
set -e
service postgresql start
echo "
_____ _ ___ _
|_ _| | | |_ | | |