mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-22 16:38:21 +00:00
Merge branch 'main' into appbuilder/sprint-14
This commit is contained in:
commit
42f81d3e79
33 changed files with 893 additions and 5126 deletions
|
|
@ -5,213 +5,214 @@ import { commonText } from "Texts/common";
|
|||
|
||||
import { exportAppModalText } from "Texts/exportImport";
|
||||
import {
|
||||
clickOnExportButtonAndVerify,
|
||||
exportAllVersionsAndVerify,
|
||||
verifyElementsOfExportModal,
|
||||
clickOnExportButtonAndVerify,
|
||||
exportAllVersionsAndVerify,
|
||||
verifyElementsOfExportModal,
|
||||
} from "Support/utils/exportImport";
|
||||
import { selectAppCardOption, closeModal } from "Support/utils/common";
|
||||
|
||||
describe("App Export", () => {
|
||||
const TEST_DATA = {
|
||||
appFiles: {
|
||||
multiVersion: "cypress/fixtures/templates/three-versions.json",
|
||||
singleVersion: "cypress/fixtures/templates/one_version.json",
|
||||
},
|
||||
};
|
||||
const TEST_DATA = {
|
||||
appFiles: {
|
||||
multiVersion: "cypress/fixtures/templates/three-versions.json",
|
||||
singleVersion: "cypress/fixtures/templates/one_version.json",
|
||||
},
|
||||
};
|
||||
|
||||
let data;
|
||||
let data;
|
||||
|
||||
data = {
|
||||
workspaceName: fake.firstName,
|
||||
workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"),
|
||||
appName: `${fake.companyName}-IE-App`,
|
||||
appReName: `${fake.companyName}-${fake.companyName}-IE-App`,
|
||||
dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
data = {
|
||||
workspaceName: fake.firstName,
|
||||
workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"),
|
||||
appName: `${fake.companyName}-IE-App`,
|
||||
appReName: `${fake.companyName}-${fake.companyName}-IE-App`,
|
||||
dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""),
|
||||
workspaceName: fake.firstName,
|
||||
workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"),
|
||||
appName: `${fake.companyName}-IE-App`,
|
||||
appReName: `${fake.companyName}-${fake.companyName}-IE-App`,
|
||||
dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""),
|
||||
};
|
||||
cy.exec("mkdir -p ./cypress/downloads/");
|
||||
cy.exec("cd ./cypress/downloads/ && rm -rf *");
|
||||
cy.exec("mkdir -p ./cypress/downloads/");
|
||||
cy.wait(3000);
|
||||
|
||||
beforeEach(() => {
|
||||
data = {
|
||||
workspaceName: fake.firstName,
|
||||
workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"),
|
||||
appName: `${fake.companyName}-IE-App`,
|
||||
appReName: `${fake.companyName}-${fake.companyName}-IE-App`,
|
||||
dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""),
|
||||
};
|
||||
cy.exec("mkdir -p ./cypress/downloads/");
|
||||
cy.wait(3000);
|
||||
cy.apiLogin();
|
||||
cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug);
|
||||
cy.apiLogout();
|
||||
});
|
||||
|
||||
cy.apiLogin();
|
||||
cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug);
|
||||
cy.apiLogout();
|
||||
});
|
||||
it("Verify the elements of export dialog box", () => {
|
||||
cy.skipWalkthrough();
|
||||
|
||||
it("Verify the elements of export dialog box", () => {
|
||||
cy.skipWalkthrough()
|
||||
cy.apiLogin();
|
||||
cy.visit(`${data.workspaceSlug}`);
|
||||
cy.get(importSelectors.importOptionInput)
|
||||
.eq(0)
|
||||
.selectFile(TEST_DATA.appFiles.multiVersion, { force: true });
|
||||
cy.wait(2000);
|
||||
cy.clearAndType(commonSelectors.appNameInput, data.appName);
|
||||
cy.get(importSelectors.importAppButton).click();
|
||||
cy.wait(3000);
|
||||
cy.backToApps();
|
||||
|
||||
cy.apiLogin();
|
||||
cy.visit(`${data.workspaceSlug}`);
|
||||
cy.get(importSelectors.importOptionInput)
|
||||
.eq(0)
|
||||
.selectFile(TEST_DATA.appFiles.multiVersion, {
|
||||
force: true,
|
||||
});
|
||||
cy.wait(1500);
|
||||
cy.clearAndType(commonSelectors.appNameInput, data.appName);
|
||||
cy.get(importSelectors.importAppButton).click();
|
||||
cy.wait(3000);
|
||||
cy.backToApps();
|
||||
// Select the app card option to export the app
|
||||
selectAppCardOption(
|
||||
data.appName,
|
||||
commonSelectors.appCardOptions(commonText.exportAppOption)
|
||||
);
|
||||
|
||||
// Select the app card option to export the app
|
||||
selectAppCardOption(
|
||||
data.appName,
|
||||
commonSelectors.appCardOptions(commonText.exportAppOption)
|
||||
// Verify the elements of the export modal
|
||||
verifyElementsOfExportModal("v3", ["v2", "v1"], [true, false, false]);
|
||||
|
||||
// Close the modal
|
||||
closeModal(exportAppModalText.modalCloseButton);
|
||||
|
||||
// Ensure the modal title is no longer visible
|
||||
cy.get(
|
||||
commonSelectors.modalTitle(exportAppModalText.selectVersionTitle)
|
||||
).should("not.exist");
|
||||
|
||||
// Re-open the export modal and click the export button
|
||||
cy.wait(2000);
|
||||
selectAppCardOption(
|
||||
data.appName,
|
||||
commonSelectors.appCardOptions(commonText.exportAppOption)
|
||||
);
|
||||
clickOnExportButtonAndVerify(exportAppModalText.exportAll, data.appName);
|
||||
|
||||
cy.exec("ls ./cypress/downloads/").then((result) => {
|
||||
const downloadedAppExportFileName = result.stdout.split("\n")[0];
|
||||
const filePath = `./cypress/downloads/${downloadedAppExportFileName}`;
|
||||
|
||||
// Ensure the file name contains the expected app export name
|
||||
expect(downloadedAppExportFileName).to.contain(
|
||||
data.appName.toLowerCase()
|
||||
);
|
||||
|
||||
// Read and validate the exported JSON file
|
||||
cy.readFile(filePath).then((appData) => {
|
||||
// Validate the app name
|
||||
const appNameFromFile = appData.app[0].definition.appV2.name;
|
||||
expect(appNameFromFile).to.equal(data.appName);
|
||||
|
||||
// Validate the schema for the student table in tooljetdb
|
||||
const tooljetDatabase = appData.tooljet_database.find(
|
||||
(db) => db.table_name === "student"
|
||||
);
|
||||
expect(tooljetDatabase).to.exist;
|
||||
expect(tooljetDatabase.schema).to.exist;
|
||||
|
||||
// Validate components and queries
|
||||
const components = appData.app[0].definition.appV2.components;
|
||||
|
||||
const text2Component = components.find(
|
||||
(component) => component.name === "text2"
|
||||
);
|
||||
expect(text2Component).to.exist;
|
||||
expect(text2Component.properties.text.value).to.equal(
|
||||
"{{constants.pageHeader}}"
|
||||
);
|
||||
|
||||
// Verify the elements of the export modal
|
||||
verifyElementsOfExportModal("v3", ["v2", "v1"], [true, false, false]);
|
||||
|
||||
// Close the modal
|
||||
closeModal(exportAppModalText.modalCloseButton);
|
||||
|
||||
// Ensure the modal title is no longer visible
|
||||
cy.get(
|
||||
commonSelectors.modalTitle(exportAppModalText.selectVersionTitle)
|
||||
).should("not.exist");
|
||||
|
||||
// Re-open the export modal and click the export button
|
||||
selectAppCardOption(
|
||||
data.appName,
|
||||
commonSelectors.appCardOptions(commonText.exportAppOption)
|
||||
const textinput1 = components.find(
|
||||
(component) => component.name === "textinput1"
|
||||
);
|
||||
clickOnExportButtonAndVerify(exportAppModalText.exportAll, data.appName);
|
||||
expect(textinput1).to.exist;
|
||||
expect(textinput1.properties.value.value).to.include("queries");
|
||||
|
||||
cy.exec("ls ./cypress/downloads/").then((result) => {
|
||||
const downloadedAppExportFileName = result.stdout.split("\n")[0];
|
||||
const filePath = `./cypress/downloads/${downloadedAppExportFileName}`;
|
||||
|
||||
// Ensure the file name contains the expected app export name
|
||||
expect(downloadedAppExportFileName).to.contain(
|
||||
data.appName.toLowerCase()
|
||||
);
|
||||
|
||||
// Read and validate the exported JSON file
|
||||
cy.readFile(filePath).then((appData) => {
|
||||
// Validate the app name
|
||||
const appNameFromFile = appData.app[0].definition.appV2.name;
|
||||
expect(appNameFromFile).to.equal(data.appName);
|
||||
|
||||
// Validate the schema for the student table in tooljetdb
|
||||
const tooljetDatabase = appData.tooljet_database.find(
|
||||
(db) => db.table_name === "student"
|
||||
);
|
||||
expect(tooljetDatabase).to.exist;
|
||||
expect(tooljetDatabase.schema).to.exist;
|
||||
|
||||
// Validate components and queries
|
||||
const components = appData.app[0].definition.appV2.components;
|
||||
|
||||
const text2Component = components.find(
|
||||
(component) => component.name === "text2"
|
||||
);
|
||||
expect(text2Component).to.exist;
|
||||
expect(text2Component.properties.text.value).to.equal(
|
||||
"{{constants.pageHeader}}"
|
||||
);
|
||||
|
||||
const textinput1 = components.find(
|
||||
(component) => component.name === "textinput1"
|
||||
);
|
||||
expect(textinput1).to.exist;
|
||||
expect(textinput1.properties.value.value).to.include("queries");
|
||||
|
||||
const textinput2 = components.find(
|
||||
(component) => component.name === "textinput2"
|
||||
);
|
||||
expect(textinput2).to.exist;
|
||||
expect(textinput2.properties.value.value).to.include("queries");
|
||||
|
||||
const textinput3 = components.find(
|
||||
(component) => component.name === "textinput3"
|
||||
);
|
||||
expect(textinput3).to.exist;
|
||||
expect(textinput3.properties.value.value).to.include("queries");
|
||||
|
||||
// Validate the data queries
|
||||
const dataQueries = appData.app[0].definition.appV2.dataQueries;
|
||||
|
||||
const postgresqlQuery = dataQueries.find(
|
||||
(query) => query.name === "postgresql1"
|
||||
);
|
||||
expect(postgresqlQuery).to.exist;
|
||||
expect(postgresqlQuery.options.query).to.include(
|
||||
"Select * from {{secrets.db_name}}"
|
||||
);
|
||||
|
||||
const restapiQuery = dataQueries.find(
|
||||
(query) => query.name === "restapi1"
|
||||
);
|
||||
expect(restapiQuery).to.exist;
|
||||
expect(restapiQuery.options.url).to.equal(
|
||||
"https://jsonplaceholder.typicode.com/users/1"
|
||||
);
|
||||
|
||||
const tooljetdbQuery = dataQueries.find(
|
||||
(query) => query.name === "tooljetdb1"
|
||||
);
|
||||
expect(tooljetdbQuery).to.exist;
|
||||
expect(tooljetdbQuery.options.operation).to.equal("list_rows");
|
||||
|
||||
// Ensure appVersions exists
|
||||
const appVersions = appData.app[0].definition.appV2.appVersions;
|
||||
expect(appVersions).to.exist;
|
||||
|
||||
// Map and verify app version names
|
||||
const versionNames = appVersions.map((version) => version.name);
|
||||
expect(versionNames).to.include.members(["v1", "v2", "v3"]);
|
||||
});
|
||||
});
|
||||
|
||||
cy.exec("cd ./cypress/downloads/ && rm -rf *");
|
||||
|
||||
selectAppCardOption(
|
||||
data.appName,
|
||||
commonSelectors.appCardOptions(commonText.exportAppOption)
|
||||
const textinput2 = components.find(
|
||||
(component) => component.name === "textinput2"
|
||||
);
|
||||
cy.get(`[data-cy="v1-radio-button"]`).check();
|
||||
cy.get(
|
||||
commonSelectors.buttonSelector(exportAppModalText.exportSelectedVersion)
|
||||
).click();
|
||||
expect(textinput2).to.exist;
|
||||
expect(textinput2.properties.value.value).to.include("queries");
|
||||
|
||||
cy.exec("ls ./cypress/downloads/").then((result) => {
|
||||
const downloadedAppExportFileName = result.stdout.split("\n")[0];
|
||||
const filePath = `./cypress/downloads/${downloadedAppExportFileName}`;
|
||||
const textinput3 = components.find(
|
||||
(component) => component.name === "textinput3"
|
||||
);
|
||||
expect(textinput3).to.exist;
|
||||
expect(textinput3.properties.value.value).to.include("queries");
|
||||
|
||||
// Ensure the file name contains the expected app export name
|
||||
expect(downloadedAppExportFileName).to.contain(
|
||||
data.appName.toLowerCase()
|
||||
);
|
||||
// Validate the data queries
|
||||
const dataQueries = appData.app[0].definition.appV2.dataQueries;
|
||||
|
||||
// Read and validate the exported JSON file
|
||||
cy.readFile(filePath).then((appData) => {
|
||||
// Validate the app name
|
||||
const appNameFromFile = appData.app[0].definition.appV2.name;
|
||||
expect(appNameFromFile).to.equal(data.appName);
|
||||
});
|
||||
});
|
||||
const postgresqlQuery = dataQueries.find(
|
||||
(query) => query.name === "postgresql1"
|
||||
);
|
||||
expect(postgresqlQuery).to.exist;
|
||||
expect(postgresqlQuery.options.query).to.include(
|
||||
"Select * from {{secrets.db_name}}"
|
||||
);
|
||||
|
||||
const restapiQuery = dataQueries.find(
|
||||
(query) => query.name === "restapi1"
|
||||
);
|
||||
expect(restapiQuery).to.exist;
|
||||
expect(restapiQuery.options.url).to.equal(
|
||||
"https://jsonplaceholder.typicode.com/users/1"
|
||||
);
|
||||
|
||||
const tooljetdbQuery = dataQueries.find(
|
||||
(query) => query.name === "tooljetdb1"
|
||||
);
|
||||
expect(tooljetdbQuery).to.exist;
|
||||
expect(tooljetdbQuery.options.operation).to.equal("list_rows");
|
||||
|
||||
// Ensure appVersions exists
|
||||
const appVersions = appData.app[0].definition.appV2.appVersions;
|
||||
expect(appVersions).to.exist;
|
||||
|
||||
// Map and verify app version names
|
||||
const versionNames = appVersions.map((version) => version.name);
|
||||
expect(versionNames).to.include.members(["v1", "v2", "v3"]);
|
||||
});
|
||||
});
|
||||
|
||||
it.skip("Verify 'Export app' functionality of an application inside app editor", () => {
|
||||
data.appName2 = `${fake.companyName}-App`;
|
||||
cy.apiCreateApp(data.appName2);
|
||||
cy.openApp(data.appName2);
|
||||
cy.exec("cd ./cypress/downloads/ && rm -rf *");
|
||||
|
||||
cy.dragAndDropWidget("Text Input", 50, 50);
|
||||
selectAppCardOption(
|
||||
data.appName,
|
||||
commonSelectors.appCardOptions(commonText.exportAppOption)
|
||||
);
|
||||
cy.get(`[data-cy="v1-radio-button"]`).check();
|
||||
cy.get(
|
||||
commonSelectors.buttonSelector(exportAppModalText.exportSelectedVersion)
|
||||
).click();
|
||||
|
||||
cy.get('[data-cy="left-sidebar-settings-button"]').click();
|
||||
cy.get('[data-cy="button-user-status-change"]').click();
|
||||
cy.exec("ls ./cypress/downloads/").then((result) => {
|
||||
const downloadedAppExportFileName = result.stdout.split("\n")[0];
|
||||
const filePath = `./cypress/downloads/${downloadedAppExportFileName}`;
|
||||
|
||||
verifyElementsOfExportModal("v1");
|
||||
// Ensure the file name contains the expected app export name
|
||||
expect(downloadedAppExportFileName).to.contain(
|
||||
data.appName.toLowerCase()
|
||||
);
|
||||
|
||||
exportAllVersionsAndVerify(data.appName1, "v1");
|
||||
// Read and validate the exported JSON file
|
||||
cy.readFile(filePath).then((appData) => {
|
||||
// Validate the app name
|
||||
const appNameFromFile = appData.app[0].definition.appV2.name;
|
||||
expect(appNameFromFile).to.equal(data.appName);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it.skip("Verify 'Export app' functionality of an application inside app editor", () => {
|
||||
data.appName2 = `${fake.companyName}-App`;
|
||||
cy.apiCreateApp(data.appName2);
|
||||
cy.openApp(data.appName2);
|
||||
|
||||
cy.dragAndDropWidget("Text Input", 50, 50);
|
||||
|
||||
cy.get('[data-cy="left-sidebar-settings-button"]').click();
|
||||
cy.get('[data-cy="button-user-status-change"]').click();
|
||||
|
||||
verifyElementsOfExportModal("v1");
|
||||
|
||||
exportAllVersionsAndVerify(data.appName1, "v1");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class DeprecateLocalStaticDataSourcesb1745318714733 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// Get all organization IDs
|
||||
const organizationsResult = await queryRunner.query(`SELECT id FROM organizations`);
|
||||
const organizationIds = organizationsResult.map((row) => row.id);
|
||||
|
||||
console.log(`Found ${organizationIds.length} organizations to process`);
|
||||
|
||||
// Process each organization
|
||||
for (const orgId of organizationIds) {
|
||||
console.log(`Processing organization: ${orgId}`);
|
||||
|
||||
// Get all app_version_ids under this organization
|
||||
const appVersionsResult = await queryRunner.query(
|
||||
`
|
||||
SELECT av.id as app_version_id
|
||||
FROM app_versions av
|
||||
JOIN apps a ON av.app_id = a.id
|
||||
WHERE a.organization_id = $1
|
||||
`,
|
||||
[orgId]
|
||||
);
|
||||
|
||||
const appVersionIds = appVersionsResult.map((row) => row.app_version_id);
|
||||
|
||||
if (appVersionIds.length === 0) {
|
||||
console.log(`No app versions found for organization: ${orgId}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`Found ${appVersionIds.length} app versions for organization: ${orgId}`);
|
||||
|
||||
// Get all distinct kinds for static global data sources associated with these app versions
|
||||
const kindsResult = await queryRunner.query(
|
||||
`
|
||||
SELECT DISTINCT kind
|
||||
FROM data_sources
|
||||
WHERE type = 'static' AND scope = 'local' AND app_version_id = ANY($1::uuid[])
|
||||
`,
|
||||
[appVersionIds]
|
||||
);
|
||||
|
||||
const kinds = kindsResult.map((row) => row.kind);
|
||||
|
||||
if (kinds.length === 0) {
|
||||
console.log(`No global static data sources found for app versions under organization: ${orgId}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`Found ${kinds.length} different kinds of data sources`);
|
||||
|
||||
// Process each kind of data source
|
||||
for (const kind of kinds) {
|
||||
console.log(`Processing kind: ${kind}`);
|
||||
|
||||
// Get first data source ID of this kind to keep
|
||||
const primaryResult = await queryRunner.query(
|
||||
`
|
||||
SELECT id
|
||||
FROM data_sources
|
||||
WHERE type = 'static' AND scope = 'local' AND app_version_id = ANY($1::uuid[]) AND kind = $2
|
||||
LIMIT 1
|
||||
`,
|
||||
[appVersionIds, kind]
|
||||
);
|
||||
|
||||
if (primaryResult.length === 0) {
|
||||
console.log(`No data sources found for kind: ${kind}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const primaryDataSourceId = primaryResult[0].id;
|
||||
|
||||
// Ensure the primary data source has scope set to 'global', app_version_id set to null,
|
||||
// and organization_id set to the current organization
|
||||
await queryRunner.query(
|
||||
`
|
||||
UPDATE data_sources
|
||||
SET scope = 'global', app_version_id = NULL, organization_id = $1
|
||||
WHERE id = $2
|
||||
`,
|
||||
[orgId, primaryDataSourceId]
|
||||
);
|
||||
|
||||
console.log(
|
||||
`Updated primary data source ${primaryDataSourceId} scope to 'global', app_version_id to NULL, and organization_id to ${orgId}`
|
||||
);
|
||||
|
||||
// Get all other data source IDs of the same kind to be replaced
|
||||
const duplicatesResult = await queryRunner.query(
|
||||
`
|
||||
SELECT id
|
||||
FROM data_sources
|
||||
WHERE type = 'static' AND scope = 'local' AND
|
||||
app_version_id = ANY($1::uuid[]) AND
|
||||
kind = $2 AND
|
||||
id != $3
|
||||
`,
|
||||
[appVersionIds, kind, primaryDataSourceId]
|
||||
);
|
||||
|
||||
const duplicateDataSourceIds = duplicatesResult.map((row) => row.id);
|
||||
|
||||
if (duplicateDataSourceIds.length === 0) {
|
||||
console.log(`No duplicates found for kind: ${kind}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`Found ${duplicateDataSourceIds.length} duplicate data sources for kind: ${kind}`);
|
||||
console.log(`Primary ID: ${primaryDataSourceId}, Duplicates: ${duplicateDataSourceIds.join(', ')}`);
|
||||
|
||||
// Update data_queries to use the primary data source
|
||||
const updateResult = await queryRunner.query(
|
||||
`
|
||||
UPDATE data_queries
|
||||
SET data_source_id = $1
|
||||
WHERE data_source_id = ANY($2::uuid[])
|
||||
`,
|
||||
[primaryDataSourceId, duplicateDataSourceIds]
|
||||
);
|
||||
|
||||
console.log(
|
||||
`Updated ${updateResult.length || 'multiple'} data_queries to use primary data source: ${primaryDataSourceId}`
|
||||
);
|
||||
|
||||
// Delete the duplicate data sources
|
||||
const deleteResult = await queryRunner.query(
|
||||
`
|
||||
DELETE FROM data_sources
|
||||
WHERE id = ANY($1::uuid[])
|
||||
`,
|
||||
[duplicateDataSourceIds]
|
||||
);
|
||||
|
||||
console.log(`Deleted ${deleteResult.length || duplicateDataSourceIds.length} duplicate data sources`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Data source consolidation complete.');
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
// This is a data consolidation migration and cannot be easily reverted
|
||||
console.log(`
|
||||
WARNING: No down migration available for data consolidation.
|
||||
This migration consolidates duplicate data sources and cannot be automatically reverted.
|
||||
Please restore from a backup if needed.
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddMissingStaticDataSources1745409452884 implements MigrationInterface {
|
||||
private requiredDataSourceKinds = ['restapi', 'runjs', 'runpy', 'tooljetdb', 'workflows'];
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// Get all organization IDs
|
||||
const organizationsResult = await queryRunner.query(`SELECT id FROM organizations`);
|
||||
const organizationIds = organizationsResult.map((row) => row.id);
|
||||
|
||||
console.log(`Found ${organizationIds.length} organizations to validate`);
|
||||
|
||||
// Process each organization
|
||||
for (const orgId of organizationIds) {
|
||||
console.log(`Processing organization: ${orgId}`);
|
||||
|
||||
// Get existing static data source kinds for this organization
|
||||
const existingDataSourcesResult = await queryRunner.query(
|
||||
`
|
||||
SELECT DISTINCT kind
|
||||
FROM data_sources
|
||||
WHERE type = 'static' AND scope = 'global' AND organization_id = $1
|
||||
`,
|
||||
[orgId]
|
||||
);
|
||||
|
||||
const existingKinds = existingDataSourcesResult.map((row) => row.kind);
|
||||
console.log(`Found existing data source kinds: ${existingKinds.join(', ') || 'none'}`);
|
||||
|
||||
// Find missing kinds
|
||||
const missingKinds = this.requiredDataSourceKinds.filter((kind) => !existingKinds.includes(kind));
|
||||
|
||||
if (missingKinds.length === 0) {
|
||||
console.log(`Organization ${orgId} has all required data source kinds`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`Missing data source kinds for organization ${orgId}: ${missingKinds.join(', ')}`);
|
||||
|
||||
// Add missing data sources
|
||||
for (const kind of missingKinds) {
|
||||
const name = this.getDefaultNameForKind(kind);
|
||||
|
||||
await queryRunner.query(
|
||||
`
|
||||
INSERT INTO data_sources (
|
||||
name, kind, type, scope, organization_id, created_at, updated_at
|
||||
) VALUES (
|
||||
$1, $2, 'static', 'global', $3, NOW(), NOW()
|
||||
)
|
||||
`,
|
||||
[name, kind, orgId]
|
||||
);
|
||||
|
||||
console.log(`Added new data source: ${kind} for organization ${orgId}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Data source validation and addition complete.');
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
// This is a data addition migration that adds missing required data sources
|
||||
console.log(`
|
||||
NOTE: Down migration is not implemented for data source validation.
|
||||
This migration adds missing required data sources to organizations.
|
||||
If needed, you could delete data sources by kind and organization manually.
|
||||
`);
|
||||
}
|
||||
|
||||
// Helper function to get default names for data source kinds
|
||||
private getDefaultNameForKind(kind: string): string {
|
||||
const nameMap: Record<string, string> = {
|
||||
restapi: 'restapidefault',
|
||||
runjs: 'runjsdefault',
|
||||
runpy: 'runpydefault',
|
||||
tooljetdb: 'tooljetdbdefault',
|
||||
workflows: 'workflowsdefault',
|
||||
};
|
||||
|
||||
return nameMap[kind] || `${kind}default`;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddDataSourceConstraintsForStatic1745409631920 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
console.log('Starting migration to add constraints to data_sources table');
|
||||
|
||||
// Add check constraint to ensure static type data sources have global scope
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE data_sources
|
||||
ADD CONSTRAINT chk_static_type_global_scope
|
||||
CHECK (type != 'static' OR scope = 'global');
|
||||
`);
|
||||
console.log('Added constraint: static type data sources must have global scope');
|
||||
|
||||
// Add unique constraint for combination of kind, type, and organization_id
|
||||
await queryRunner.query(`
|
||||
CREATE UNIQUE INDEX idx_unique_static_kind_org
|
||||
ON public.data_sources (kind, type, organization_id)
|
||||
WHERE type = 'static';
|
||||
`);
|
||||
console.log('Added unique constraint on kind, type, and organization_id');
|
||||
|
||||
console.log('Migration completed successfully');
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
console.log('Starting rollback of data_sources constraints');
|
||||
|
||||
// Drop the unique constraint
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE public.data_sources
|
||||
DROP CONSTRAINT IF EXISTS idx_unique_static_kind_org;
|
||||
`);
|
||||
console.log('Dropped unique constraint on kind, type, and organization_id');
|
||||
|
||||
// Drop the check constraint
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE public.data_sources
|
||||
DROP CONSTRAINT IF EXISTS chk_static_type_global_scope;
|
||||
`);
|
||||
console.log('Dropped constraint: static type data sources must have global scope');
|
||||
|
||||
console.log('Rollback completed successfully');
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ import { GuardValidatorModule } from './validators/feature-guard.validator';
|
|||
import { SentryModule } from '@modules/observability/sentry/module';
|
||||
|
||||
export class AppModuleLoader {
|
||||
static async loadModules(configs: { IS_GET_CONTEXT: boolean }): Promise<(DynamicModule | Type<any>)[]> {
|
||||
static async loadModules(configs: { IS_GET_CONTEXT: boolean }): Promise<(DynamicModule | typeof GuardValidatorModule)[]> {
|
||||
// Static imports that are always loaded
|
||||
const staticModules = [
|
||||
EventEmitterModule.forRoot({
|
||||
|
|
@ -33,7 +33,7 @@ export class AppModuleLoader {
|
|||
port: parseInt(process.env.REDIS_PORT) || 6379,
|
||||
},
|
||||
}),
|
||||
ConfigModule.forRoot({
|
||||
await ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
envFilePath: [`../.env.${process.env.NODE_ENV}`, '../.env'],
|
||||
load: [() => getEnvVars()],
|
||||
|
|
@ -111,17 +111,17 @@ export class AppModuleLoader {
|
|||
* █ █
|
||||
* ███████████████████████████████████████████████████████████████████████████████
|
||||
*/
|
||||
const dynamicModules: Promise<DynamicModule>[] = [];
|
||||
const dynamicModules: DynamicModule[] = [];
|
||||
|
||||
try {
|
||||
const { LogToFileModule } = await import(`${await getImportPath(configs.IS_GET_CONTEXT)}/log-to-file/module`);
|
||||
const { AuditLogsModule } = await import(`${await getImportPath(configs.IS_GET_CONTEXT)}/audit-logs/module`);
|
||||
dynamicModules.push(LogToFileModule.register(configs));
|
||||
dynamicModules.push(AuditLogsModule.register(configs));
|
||||
dynamicModules.push(await LogToFileModule.register(configs));
|
||||
dynamicModules.push(await AuditLogsModule.register(configs));
|
||||
} catch (error) {
|
||||
console.error('Error loading dynamic modules:', error);
|
||||
}
|
||||
|
||||
return [...staticModules, ...dynamicModules] as (Type<any> | DynamicModule)[];
|
||||
return [...staticModules, ...dynamicModules];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ interface AppResourceMappings {
|
|||
componentsMapping: Record<string, string>;
|
||||
}
|
||||
|
||||
type DefaultDataSourceKind = 'restapi' | 'runjs' | 'runpy' | 'tooljetdb' | 'workflows';
|
||||
type DefaultDataSourceName =
|
||||
| 'restapidefault'
|
||||
| 'runjsdefault'
|
||||
|
|
@ -76,7 +75,6 @@ const DefaultDataSourceNames: DefaultDataSourceName[] = [
|
|||
'tooljetdbdefault',
|
||||
'workflowsdefault',
|
||||
];
|
||||
const DefaultDataSourceKinds: DefaultDataSourceKind[] = ['restapi', 'runjs', 'runpy', 'tooljetdb', 'workflows'];
|
||||
const NewRevampedComponents: NewRevampedComponent[] = [
|
||||
'Text',
|
||||
'TextInput',
|
||||
|
|
@ -97,7 +95,6 @@ export class AppImportExportService {
|
|||
protected dataSourcesRepository: DataSourcesRepository,
|
||||
protected appEnvironmentUtilService: AppEnvironmentUtilService,
|
||||
protected usersUtilService: UsersUtilService,
|
||||
protected readonly entityManager: EntityManager,
|
||||
protected componentsService: ComponentsService
|
||||
) {}
|
||||
|
||||
|
|
@ -261,54 +258,65 @@ export class AppImportExportService {
|
|||
externalResourceMappings = {},
|
||||
isGitApp = false,
|
||||
tooljetVersion = '',
|
||||
cloning = false
|
||||
cloning = false,
|
||||
manager?: EntityManager
|
||||
): Promise<App> {
|
||||
if (typeof appParamsObj !== 'object') {
|
||||
throw new BadRequestException('Invalid params for app import');
|
||||
}
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
if (typeof appParamsObj !== 'object') {
|
||||
throw new BadRequestException('Invalid params for app import');
|
||||
}
|
||||
|
||||
let appParams = appParamsObj;
|
||||
let appParams = appParamsObj;
|
||||
|
||||
if (appParams?.appV2) {
|
||||
appParams = { ...appParams.appV2 };
|
||||
}
|
||||
if (appParams?.appV2) {
|
||||
appParams = { ...appParams.appV2 };
|
||||
}
|
||||
|
||||
if (!appParams?.name) {
|
||||
throw new BadRequestException('Invalid params for app import');
|
||||
}
|
||||
if (!appParams?.name) {
|
||||
throw new BadRequestException('Invalid params for app import');
|
||||
}
|
||||
|
||||
const schemaUnifiedAppParams = appParams?.schemaDetails?.multiPages
|
||||
? appParams
|
||||
: convertSinglePageSchemaToMultiPageSchema(appParams);
|
||||
schemaUnifiedAppParams.name = appName;
|
||||
const schemaUnifiedAppParams = appParams?.schemaDetails?.multiPages
|
||||
? appParams
|
||||
: convertSinglePageSchemaToMultiPageSchema(appParams);
|
||||
schemaUnifiedAppParams.name = appName;
|
||||
|
||||
const importedAppTooljetVersion = !cloning && extractMajorVersion(tooljetVersion);
|
||||
const isNormalizedAppDefinitionSchema = cloning
|
||||
? true
|
||||
: isTooljetVersionWithNormalizedAppDefinitionSchem(importedAppTooljetVersion);
|
||||
const importedAppTooljetVersion = !cloning && extractMajorVersion(tooljetVersion);
|
||||
const isNormalizedAppDefinitionSchema = cloning
|
||||
? true
|
||||
: isTooljetVersionWithNormalizedAppDefinitionSchem(importedAppTooljetVersion);
|
||||
|
||||
const currentTooljetVersion = !cloning ? tooljetVersion : null;
|
||||
const currentTooljetVersion = !cloning ? tooljetVersion : null;
|
||||
|
||||
const importedApp = await this.createImportedAppForUser(this.entityManager, schemaUnifiedAppParams, user, isGitApp);
|
||||
const importedApp = await this.createImportedAppForUser(manager, schemaUnifiedAppParams, user, isGitApp);
|
||||
|
||||
const resourceMapping = await this.setupImportedAppAssociations(
|
||||
this.entityManager,
|
||||
importedApp,
|
||||
schemaUnifiedAppParams,
|
||||
user,
|
||||
externalResourceMappings,
|
||||
isNormalizedAppDefinitionSchema,
|
||||
currentTooljetVersion
|
||||
);
|
||||
await this.updateEntityReferencesForImportedApp(this.entityManager, resourceMapping);
|
||||
const resourceMapping = await this.setupImportedAppAssociations(
|
||||
manager,
|
||||
importedApp,
|
||||
schemaUnifiedAppParams,
|
||||
user,
|
||||
externalResourceMappings,
|
||||
isNormalizedAppDefinitionSchema,
|
||||
currentTooljetVersion
|
||||
);
|
||||
await this.updateEntityReferencesForImportedApp(manager, resourceMapping);
|
||||
|
||||
// NOTE: App slug updation callback doesn't work while wrapped in transaction
|
||||
// hence updating slug explicitly
|
||||
await importedApp.reload();
|
||||
importedApp.slug = importedApp.id;
|
||||
await this.entityManager.save(importedApp);
|
||||
// Update latest version as editing version
|
||||
const {
|
||||
importingAppVersions,
|
||||
} = this.extractImportDataFromAppParams(appParams)
|
||||
|
||||
return importedApp;
|
||||
await this.setEditingVersionAsLatestVersion(manager, resourceMapping.appVersionMapping, importingAppVersions)
|
||||
|
||||
// NOTE: App slug updation callback doesn't work while wrapped in transaction
|
||||
// hence updating slug explicitly
|
||||
//await importedApp.reload(); -> this will not work as we are using transaction
|
||||
const newApp = await manager.findOne(App, { where: { id: importedApp.id } });
|
||||
newApp.slug = importedApp.id;
|
||||
await manager.save(newApp);
|
||||
|
||||
return newApp;
|
||||
}, manager);
|
||||
}
|
||||
|
||||
async updateEntityReferencesForImportedApp(manager: EntityManager, resourceMapping: AppResourceMappings) {
|
||||
|
|
@ -487,196 +495,194 @@ export class AppImportExportService {
|
|||
* If an error occurs during the function execution, the transaction will rolled back.
|
||||
*/
|
||||
|
||||
await manager.transaction(async (transactionalEntityManager) => {
|
||||
appResourceMappings = await this.setupAppVersionAssociations(
|
||||
transactionalEntityManager,
|
||||
importingAppVersions,
|
||||
user,
|
||||
appResourceMappings,
|
||||
externalResourceMappings,
|
||||
importingAppEnvironments,
|
||||
importingDataSources,
|
||||
importingDataSourceOptions,
|
||||
importingDataQueries,
|
||||
importingDefaultAppEnvironmentId,
|
||||
importingPages,
|
||||
importingComponents,
|
||||
importingEvents,
|
||||
tooljetVersion
|
||||
);
|
||||
appResourceMappings = await this.setupAppVersionAssociations(
|
||||
manager,
|
||||
importingAppVersions,
|
||||
user,
|
||||
appResourceMappings,
|
||||
externalResourceMappings,
|
||||
importingAppEnvironments,
|
||||
importingDataSources,
|
||||
importingDataSourceOptions,
|
||||
importingDataQueries,
|
||||
importingDefaultAppEnvironmentId,
|
||||
importingPages,
|
||||
importingComponents,
|
||||
importingEvents,
|
||||
tooljetVersion
|
||||
);
|
||||
|
||||
if (!isNormalizedAppDefinitionSchema) {
|
||||
for (const importingAppVersion of importingAppVersions) {
|
||||
const updatedDefinition: DeepPartial<any> = this.replaceDataQueryIdWithinDefinitions(
|
||||
importingAppVersion.definition,
|
||||
appResourceMappings.dataQueryMapping
|
||||
);
|
||||
if (!isNormalizedAppDefinitionSchema) {
|
||||
for (const importingAppVersion of importingAppVersions) {
|
||||
const updatedDefinition: DeepPartial<any> = this.replaceDataQueryIdWithinDefinitions(
|
||||
importingAppVersion.definition,
|
||||
appResourceMappings.dataQueryMapping
|
||||
);
|
||||
|
||||
let updateHomepageId = null;
|
||||
let updateHomepageId = null;
|
||||
|
||||
if (updatedDefinition?.pages) {
|
||||
for (const pageId of Object.keys(updatedDefinition?.pages)) {
|
||||
const page = updatedDefinition.pages[pageId];
|
||||
if (updatedDefinition?.pages) {
|
||||
for (const pageId of Object.keys(updatedDefinition?.pages)) {
|
||||
const page = updatedDefinition.pages[pageId];
|
||||
|
||||
const pageEvents = page.events || [];
|
||||
const componentEvents = [];
|
||||
const pageEvents = page.events || [];
|
||||
const componentEvents = [];
|
||||
|
||||
const pagePostionIntheList = Object.keys(updatedDefinition?.pages).indexOf(pageId);
|
||||
const pagePostionIntheList = Object.keys(updatedDefinition?.pages).indexOf(pageId);
|
||||
|
||||
const isHompage = (updatedDefinition['homePageId'] as any) === pageId;
|
||||
const isHompage = (updatedDefinition['homePageId'] as any) === pageId;
|
||||
|
||||
const pageComponents = page.components;
|
||||
const pageComponents = page.components;
|
||||
|
||||
const mappedComponents = transformComponentData(
|
||||
pageComponents,
|
||||
componentEvents,
|
||||
appResourceMappings.componentsMapping,
|
||||
isNormalizedAppDefinitionSchema,
|
||||
tooljetVersion
|
||||
);
|
||||
const mappedComponents = transformComponentData(
|
||||
pageComponents,
|
||||
componentEvents,
|
||||
appResourceMappings.componentsMapping,
|
||||
isNormalizedAppDefinitionSchema,
|
||||
tooljetVersion
|
||||
);
|
||||
|
||||
const componentLayouts = [];
|
||||
const componentLayouts = [];
|
||||
|
||||
const newPage = transactionalEntityManager.create(Page, {
|
||||
name: page.name,
|
||||
handle: page.handle,
|
||||
appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id],
|
||||
index: pagePostionIntheList,
|
||||
disabled: page.disabled || false,
|
||||
hidden: page.hidden || false,
|
||||
autoComputeLayout: page.autoComputeLayout || false,
|
||||
isPageGroup: page.isPageGroup,
|
||||
pageGroupIndex: page.pageGroupIndex || null,
|
||||
icon: page.icon || null,
|
||||
});
|
||||
const pageCreated = await transactionalEntityManager.save(newPage);
|
||||
const newPage = manager.create(Page, {
|
||||
name: page.name,
|
||||
handle: page.handle,
|
||||
appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id],
|
||||
index: pagePostionIntheList,
|
||||
disabled: page.disabled || false,
|
||||
hidden: page.hidden || false,
|
||||
autoComputeLayout: page.autoComputeLayout || false,
|
||||
isPageGroup: page.isPageGroup,
|
||||
pageGroupIndex: page.pageGroupIndex || null,
|
||||
icon: page.icon || null,
|
||||
});
|
||||
const pageCreated = await manager.save(newPage);
|
||||
|
||||
appResourceMappings.pagesMapping[pageId] = pageCreated.id;
|
||||
appResourceMappings.pagesMapping[pageId] = pageCreated.id;
|
||||
|
||||
mappedComponents.forEach((component) => {
|
||||
component.page = pageCreated;
|
||||
});
|
||||
mappedComponents.forEach((component) => {
|
||||
component.page = pageCreated;
|
||||
});
|
||||
|
||||
const savedComponents = await transactionalEntityManager.save(Component, mappedComponents);
|
||||
const savedComponents = await manager.save(Component, mappedComponents);
|
||||
|
||||
for (const componentId in pageComponents) {
|
||||
const componentLayout = pageComponents[componentId]['layouts'];
|
||||
for (const componentId in pageComponents) {
|
||||
const componentLayout = pageComponents[componentId]['layouts'];
|
||||
|
||||
if (componentLayout && appResourceMappings.componentsMapping[componentId]) {
|
||||
for (const type in componentLayout) {
|
||||
const layout = componentLayout[type];
|
||||
const newLayout = new Layout();
|
||||
newLayout.type = type;
|
||||
newLayout.top = layout.top;
|
||||
newLayout.left =
|
||||
layout.dimensionUnit !== LayoutDimensionUnits.COUNT
|
||||
? this.componentsService.resolveGridPositionForComponent(layout.left, type)
|
||||
: layout.left;
|
||||
newLayout.dimensionUnit = LayoutDimensionUnits.COUNT;
|
||||
newLayout.width = layout.width;
|
||||
newLayout.height = layout.height;
|
||||
newLayout.componentId = appResourceMappings.componentsMapping[componentId];
|
||||
if (componentLayout && appResourceMappings.componentsMapping[componentId]) {
|
||||
for (const type in componentLayout) {
|
||||
const layout = componentLayout[type];
|
||||
const newLayout = new Layout();
|
||||
newLayout.type = type;
|
||||
newLayout.top = layout.top;
|
||||
newLayout.left =
|
||||
layout.dimensionUnit !== LayoutDimensionUnits.COUNT
|
||||
? this.componentsService.resolveGridPositionForComponent(layout.left, type)
|
||||
: layout.left;
|
||||
newLayout.dimensionUnit = LayoutDimensionUnits.COUNT;
|
||||
newLayout.width = layout.width;
|
||||
newLayout.height = layout.height;
|
||||
newLayout.componentId = appResourceMappings.componentsMapping[componentId];
|
||||
|
||||
componentLayouts.push(newLayout);
|
||||
}
|
||||
componentLayouts.push(newLayout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await transactionalEntityManager.save(Layout, componentLayouts);
|
||||
await manager.save(Layout, componentLayouts);
|
||||
|
||||
//Event handlers
|
||||
//Event handlers
|
||||
|
||||
if (pageEvents.length > 0) {
|
||||
pageEvents.forEach(async (event, index) => {
|
||||
const newEvent = {
|
||||
name: event.eventId,
|
||||
sourceId: pageCreated.id,
|
||||
target: Target.page,
|
||||
event: event,
|
||||
index: pageEvents.index || index,
|
||||
appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id],
|
||||
};
|
||||
if (pageEvents.length > 0) {
|
||||
await Promise.all(pageEvents.map(async (event, index) => {
|
||||
const newEvent = {
|
||||
name: event.eventId,
|
||||
sourceId: pageCreated.id,
|
||||
target: Target.page,
|
||||
event: event,
|
||||
index: pageEvents.index || index,
|
||||
appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id],
|
||||
};
|
||||
|
||||
await transactionalEntityManager.save(EventHandler, newEvent);
|
||||
await manager.save(EventHandler, newEvent);
|
||||
}));
|
||||
}
|
||||
|
||||
await Promise.all(componentEvents.map(async (eventObj) => {
|
||||
if (eventObj.event?.length === 0) return;
|
||||
|
||||
await Promise.all(eventObj.event.map(async (event, index) => {
|
||||
const newEvent = manager.create(EventHandler, {
|
||||
name: event.eventId,
|
||||
sourceId: appResourceMappings.componentsMapping[eventObj.componentId],
|
||||
target: Target.component,
|
||||
event: event,
|
||||
index: eventObj.index || index,
|
||||
appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id],
|
||||
});
|
||||
}
|
||||
|
||||
componentEvents.forEach((eventObj) => {
|
||||
if (eventObj.event?.length === 0) return;
|
||||
await manager.save(EventHandler, newEvent);
|
||||
}));
|
||||
}));
|
||||
|
||||
eventObj.event.forEach(async (event, index) => {
|
||||
const newEvent = transactionalEntityManager.create(EventHandler, {
|
||||
name: event.eventId,
|
||||
sourceId: appResourceMappings.componentsMapping[eventObj.componentId],
|
||||
target: Target.component,
|
||||
event: event,
|
||||
index: eventObj.index || index,
|
||||
appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id],
|
||||
});
|
||||
await Promise.all(savedComponents.map(async (component) => {
|
||||
if (component.type === 'Table') {
|
||||
const tableActions = component.properties?.actions?.value || [];
|
||||
const tableColumns = component.properties?.columns?.value || [];
|
||||
|
||||
await transactionalEntityManager.save(EventHandler, newEvent);
|
||||
});
|
||||
});
|
||||
const tableActionAndColumnEvents = [];
|
||||
|
||||
savedComponents.forEach(async (component) => {
|
||||
if (component.type === 'Table') {
|
||||
const tableActions = component.properties?.actions?.value || [];
|
||||
const tableColumns = component.properties?.columns?.value || [];
|
||||
tableActions.forEach((action) => {
|
||||
const actionEvents = action.events || [];
|
||||
|
||||
const tableActionAndColumnEvents = [];
|
||||
|
||||
tableActions.forEach((action) => {
|
||||
const actionEvents = action.events || [];
|
||||
|
||||
actionEvents.forEach((event, index) => {
|
||||
tableActionAndColumnEvents.push({
|
||||
name: event.eventId,
|
||||
sourceId: component.id,
|
||||
target: Target.tableAction,
|
||||
event: { ...event, ref: action.name },
|
||||
index: event.index ?? index,
|
||||
appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id],
|
||||
});
|
||||
actionEvents.forEach((event, index) => {
|
||||
tableActionAndColumnEvents.push({
|
||||
name: event.eventId,
|
||||
sourceId: component.id,
|
||||
target: Target.tableAction,
|
||||
event: { ...event, ref: action.name },
|
||||
index: event.index ?? index,
|
||||
appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
tableColumns.forEach((column) => {
|
||||
if (column?.columnType !== 'toggle') return;
|
||||
const columnEvents = column.events || [];
|
||||
tableColumns.forEach((column) => {
|
||||
if (column?.columnType !== 'toggle') return;
|
||||
const columnEvents = column.events || [];
|
||||
|
||||
columnEvents.forEach((event, index) => {
|
||||
tableActionAndColumnEvents.push({
|
||||
name: event.eventId,
|
||||
sourceId: component.id,
|
||||
target: Target.tableColumn,
|
||||
event: { ...event, ref: column.name },
|
||||
index: event.index ?? index,
|
||||
appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id],
|
||||
});
|
||||
columnEvents.forEach((event, index) => {
|
||||
tableActionAndColumnEvents.push({
|
||||
name: event.eventId,
|
||||
sourceId: component.id,
|
||||
target: Target.tableColumn,
|
||||
event: { ...event, ref: column.name },
|
||||
index: event.index ?? index,
|
||||
appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await transactionalEntityManager.save(EventHandler, tableActionAndColumnEvents);
|
||||
}
|
||||
});
|
||||
|
||||
if (isHompage) {
|
||||
updateHomepageId = pageCreated.id;
|
||||
await manager.save(EventHandler, tableActionAndColumnEvents);
|
||||
}
|
||||
}));
|
||||
|
||||
if (isHompage) {
|
||||
updateHomepageId = pageCreated.id;
|
||||
}
|
||||
}
|
||||
|
||||
await transactionalEntityManager.update(
|
||||
AppVersion,
|
||||
{ id: appResourceMappings.appVersionMapping[importingAppVersion.id] },
|
||||
{
|
||||
definition: updatedDefinition,
|
||||
homePageId: updateHomepageId,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
await manager.update(
|
||||
AppVersion,
|
||||
{ id: appResourceMappings.appVersionMapping[importingAppVersion.id] },
|
||||
{
|
||||
definition: updatedDefinition,
|
||||
homePageId: updateHomepageId,
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const appVersionIds = Object.values(appResourceMappings.appVersionMapping);
|
||||
|
||||
|
|
@ -690,8 +696,6 @@ export class AppImportExportService {
|
|||
);
|
||||
}
|
||||
|
||||
await this.setEditingVersionAsLatestVersion(manager, appResourceMappings.appVersionMapping, importingAppVersions);
|
||||
|
||||
return appResourceMappings;
|
||||
}
|
||||
|
||||
|
|
@ -748,7 +752,6 @@ export class AppImportExportService {
|
|||
const dataSourceForAppVersion = await this.findOrCreateDataSourceForAppVersion(
|
||||
manager,
|
||||
importingDataSource,
|
||||
appResourceMappings.appVersionMapping[importingAppVersion.id],
|
||||
user
|
||||
);
|
||||
|
||||
|
|
@ -914,7 +917,7 @@ export class AppImportExportService {
|
|||
appResourceMappings.componentsMapping[component.id] = savedComponent.id;
|
||||
const componentLayout = component.layouts;
|
||||
|
||||
componentLayout.forEach(async (layout) => {
|
||||
await Promise.all(componentLayout.map(async (layout) => {
|
||||
const newLayout = new Layout();
|
||||
newLayout.type = layout.type;
|
||||
newLayout.top = layout.top;
|
||||
|
|
@ -928,12 +931,12 @@ export class AppImportExportService {
|
|||
newLayout.component = savedComponent;
|
||||
|
||||
await manager.save(newLayout);
|
||||
});
|
||||
}));
|
||||
|
||||
const componentEvents = importingEvents.filter((event) => event.sourceId === component.id);
|
||||
|
||||
if (componentEvents.length > 0) {
|
||||
componentEvents.forEach(async (componentEvent) => {
|
||||
await Promise.all(componentEvents.map(async (componentEvent) => {
|
||||
const newEvent = await manager.create(EventHandler, {
|
||||
name: componentEvent.name,
|
||||
sourceId: savedComponent.id,
|
||||
|
|
@ -944,7 +947,7 @@ export class AppImportExportService {
|
|||
});
|
||||
|
||||
await manager.save(EventHandler, newEvent);
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -952,7 +955,7 @@ export class AppImportExportService {
|
|||
const pageEvents = importingEvents.filter((event) => event.sourceId === page.id);
|
||||
|
||||
if (pageEvents.length > 0) {
|
||||
pageEvents.forEach(async (pageEvent) => {
|
||||
await Promise.all(pageEvents.map(async (pageEvent) => {
|
||||
const newEvent = await manager.create(EventHandler, {
|
||||
name: pageEvent.name,
|
||||
sourceId: pageCreated.id,
|
||||
|
|
@ -963,7 +966,7 @@ export class AppImportExportService {
|
|||
});
|
||||
|
||||
await manager.save(EventHandler, newEvent);
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -990,7 +993,7 @@ export class AppImportExportService {
|
|||
);
|
||||
|
||||
if (importingQueryEvents.length > 0) {
|
||||
importingQueryEvents.forEach(async (dataQueryEvent) => {
|
||||
await Promise.all(importingQueryEvents.map(async (dataQueryEvent) => {
|
||||
const newEvent = await manager.create(EventHandler, {
|
||||
name: dataQueryEvent.name,
|
||||
sourceId: mappedNewDataQuery.id,
|
||||
|
|
@ -1001,7 +1004,7 @@ export class AppImportExportService {
|
|||
});
|
||||
|
||||
await manager.save(EventHandler, newEvent);
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
this.replaceDataQueryOptionsWithNewDataQueryIds(
|
||||
mappedNewDataQuery?.options,
|
||||
|
|
@ -1012,7 +1015,7 @@ export class AppImportExportService {
|
|||
delete mappedNewDataQuery?.options?.events;
|
||||
|
||||
if (queryEvents.length > 0) {
|
||||
queryEvents.forEach(async (event, index) => {
|
||||
await Promise.all(queryEvents.map(async (event, index) => {
|
||||
const newEvent = await manager.create(EventHandler, {
|
||||
name: event.eventId,
|
||||
sourceId: mappedNewDataQuery.id,
|
||||
|
|
@ -1023,7 +1026,7 @@ export class AppImportExportService {
|
|||
});
|
||||
|
||||
await manager.save(EventHandler, newEvent);
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1195,12 +1198,7 @@ export class AppImportExportService {
|
|||
user: User,
|
||||
appResourceMappings: AppResourceMappings
|
||||
) {
|
||||
const defaultDataSourceIds = await this.createDefaultDataSourceForVersion(
|
||||
user?.organizationId,
|
||||
appResourceMappings.appVersionMapping[appVersion.id],
|
||||
DefaultDataSourceKinds,
|
||||
manager
|
||||
);
|
||||
const defaultDataSourceIds = await this.createDefaultDataSourceForVersion(user.organizationId, manager);
|
||||
appResourceMappings.defaultDataSourceIdMapping[appVersion.id] = defaultDataSourceIds;
|
||||
|
||||
return appResourceMappings;
|
||||
|
|
@ -1209,7 +1207,6 @@ export class AppImportExportService {
|
|||
async findOrCreateDataSourceForAppVersion(
|
||||
manager: EntityManager,
|
||||
dataSource: DataSource,
|
||||
appVersionId: string,
|
||||
user: User
|
||||
): Promise<DataSource> {
|
||||
const isDefaultDatasource = DefaultDataSourceNames.includes(dataSource.name as DefaultDataSourceName);
|
||||
|
|
@ -1218,10 +1215,10 @@ export class AppImportExportService {
|
|||
if (isDefaultDatasource) {
|
||||
const createdDefaultDatasource = await manager.findOne(DataSource, {
|
||||
where: {
|
||||
appVersionId,
|
||||
organizationId: user.organizationId,
|
||||
kind: dataSource.kind,
|
||||
type: DataSourceTypes.STATIC,
|
||||
scope: 'local',
|
||||
scope: DataSourceScopes.GLOBAL,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -1234,8 +1231,8 @@ export class AppImportExportService {
|
|||
id: dataSource.id,
|
||||
kind: dataSource.kind,
|
||||
type: DataSourceTypes.DEFAULT,
|
||||
scope: 'global',
|
||||
organizationId: user?.organizationId,
|
||||
scope: DataSourceScopes.GLOBAL,
|
||||
organizationId: user.organizationId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -1245,8 +1242,8 @@ export class AppImportExportService {
|
|||
name: dataSource.name,
|
||||
kind: dataSource.kind,
|
||||
type: In([DataSourceTypes.DEFAULT, DataSourceTypes.SAMPLE]),
|
||||
scope: 'global',
|
||||
organizationId: user?.organizationId,
|
||||
scope: DataSourceScopes.GLOBAL,
|
||||
organizationId: user.organizationId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -1268,7 +1265,7 @@ export class AppImportExportService {
|
|||
name: dataSource.name,
|
||||
kind: dataSource.kind,
|
||||
type: DataSourceTypes.DEFAULT,
|
||||
scope: 'global', // No appVersionId for global data sources
|
||||
scope: DataSourceScopes.GLOBAL, // No appVersionId for global data sources
|
||||
pluginId: plugin.id,
|
||||
});
|
||||
await manager.save(newDataSource);
|
||||
|
|
@ -1283,7 +1280,7 @@ export class AppImportExportService {
|
|||
name: dataSource.name,
|
||||
kind: dataSource.kind,
|
||||
type: DataSourceTypes.DEFAULT,
|
||||
scope: 'global', // No appVersionId for global data sources
|
||||
scope: DataSourceScopes.GLOBAL, // No appVersionId for global data sources
|
||||
pluginId: null,
|
||||
});
|
||||
await manager.save(newDataSource);
|
||||
|
|
@ -1499,19 +1496,12 @@ export class AppImportExportService {
|
|||
return appResourceMappings;
|
||||
}
|
||||
|
||||
async createDefaultDataSourceForVersion(
|
||||
organizationId: string,
|
||||
versionId: string,
|
||||
kinds: DefaultDataSourceKind[],
|
||||
manager: EntityManager
|
||||
): Promise<any> {
|
||||
const response = {};
|
||||
for (const defaultSource of kinds) {
|
||||
const dataSource = await this.dataSourcesRepository.createDefaultDataSource(defaultSource, versionId, manager);
|
||||
response[defaultSource] = dataSource.id;
|
||||
await this.dataSourcesUtilService.createDataSourceInAllEnvironments(organizationId, dataSource.id, manager);
|
||||
}
|
||||
return response;
|
||||
async createDefaultDataSourceForVersion(organizationId: string, manager: EntityManager): Promise<any> {
|
||||
const dataSources = await this.dataSourcesRepository.getStaticDataSources(organizationId, manager);
|
||||
return dataSources?.reduce<Record<string, string>>((acc, source) => {
|
||||
acc[source.kind] = source.id;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
async setEditingVersionAsLatestVersion(manager: EntityManager, appVersionMapping: any, appVersions: Array<any>) {
|
||||
|
|
@ -1657,12 +1647,7 @@ export class AppImportExportService {
|
|||
await manager.save(version);
|
||||
|
||||
// Create default data sources
|
||||
const defaultDataSourceIds = await this.createDefaultDataSourceForVersion(
|
||||
user?.organizationId,
|
||||
version.id,
|
||||
DefaultDataSourceKinds,
|
||||
manager
|
||||
);
|
||||
const defaultDataSourceIds = await this.createDefaultDataSourceForVersion(user.organizationId, manager);
|
||||
let envIdArray: string[] = [];
|
||||
|
||||
const organization: Organization = await manager.findOne(Organization, {
|
||||
|
|
@ -1759,7 +1744,7 @@ export class AppImportExportService {
|
|||
newQuery.options = newOptions;
|
||||
await manager.save(newQuery);
|
||||
|
||||
queryEvents.forEach(async (event, index) => {
|
||||
await Promise.all(queryEvents.map(async (event, index) => {
|
||||
const newEvent = {
|
||||
name: event.eventId,
|
||||
sourceId: newQuery.id,
|
||||
|
|
@ -1770,7 +1755,7 @@ export class AppImportExportService {
|
|||
};
|
||||
|
||||
await manager.save(EventHandler, newEvent);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
await manager.update(
|
||||
|
|
|
|||
|
|
@ -34,9 +34,7 @@ import { mergeWith } from 'lodash';
|
|||
import { isArray } from 'lodash';
|
||||
import { UserAppsPermissions, UserWorkflowPermissions } from '@modules/ability/types';
|
||||
import { AbilityService } from '@modules/ability/interfaces/IService';
|
||||
import { DataSourcesRepository } from '@modules/data-sources/repository';
|
||||
import { IAppsUtilService } from './interfaces/IUtilService';
|
||||
import { DataSourcesUtilService } from '@modules/data-sources/util.service';
|
||||
import { AppVersionUpdateDto } from '@dto/app-version-update.dto';
|
||||
import { APP_TYPES } from './constants';
|
||||
import { Component } from 'src/entities/component.entity';
|
||||
|
|
@ -52,10 +50,8 @@ export class AppsUtilService implements IAppsUtilService {
|
|||
protected readonly versionRepository: VersionRepository,
|
||||
protected readonly licenseTermsService: LicenseTermsService,
|
||||
protected readonly organizationRepository: OrganizationRepository,
|
||||
protected readonly abilityService: AbilityService,
|
||||
protected readonly dataSourceRepository: DataSourcesRepository,
|
||||
protected readonly dataSourceUtilService: DataSourcesUtilService
|
||||
) { }
|
||||
protected readonly abilityService: AbilityService
|
||||
) {}
|
||||
async create(name: string, user: User, type: APP_TYPES, manager: EntityManager): Promise<App> {
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const app = await catchDbException(() => {
|
||||
|
|
@ -77,14 +73,6 @@ export class AppsUtilService implements IAppsUtilService {
|
|||
const firstPriorityEnv = await this.appEnvironmentUtilService.get(user.organizationId, null, true, manager);
|
||||
const appVersion = await this.versionRepository.createOne('v1', app.id, firstPriorityEnv.id, null, manager);
|
||||
|
||||
for (const defaultSource of ['restapi', 'runjs', 'runpy', 'tooljetdb', 'workflows']) {
|
||||
const dataSource = await this.dataSourceRepository.createDefaultDataSource(
|
||||
defaultSource,
|
||||
appVersion.id,
|
||||
manager
|
||||
);
|
||||
await this.dataSourceUtilService.createDataSourceInAllEnvironments(user.organizationId, dataSource.id, manager);
|
||||
}
|
||||
const defaultHomePage = await manager.save(
|
||||
manager.create(Page, {
|
||||
name: 'Home',
|
||||
|
|
@ -157,43 +145,6 @@ export class AppsUtilService implements IAppsUtilService {
|
|||
}, manager);
|
||||
}
|
||||
|
||||
// async createVersion(
|
||||
// user: User,
|
||||
// app: App,
|
||||
// versionName: string,
|
||||
// versionFromId: string,
|
||||
// manager?: EntityManager
|
||||
// ): Promise<AppVersion> {
|
||||
// return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
// let versionFrom: AppVersion;
|
||||
// const { organizationId } = user;
|
||||
|
||||
// if (versionFromId) {
|
||||
// versionFrom = await manager.findOneOrFail(AppVersion, {
|
||||
// where: {
|
||||
// id: versionFromId,
|
||||
// app: {
|
||||
// id: app.id,
|
||||
// organizationId,
|
||||
// },
|
||||
// },
|
||||
// relations: ['app', 'dataSources', 'dataSources.dataQueries', 'dataSources.dataSourceOptions'],
|
||||
// });
|
||||
// }
|
||||
|
||||
// const noOfVersions = await manager.count(AppVersion, { where: { appId: app?.id } });
|
||||
|
||||
// if (noOfVersions && !versionFrom) {
|
||||
// throw new BadRequestException('Version from should not be empty');
|
||||
// }
|
||||
|
||||
// if (versionFrom) {
|
||||
// }
|
||||
|
||||
// return appVersion;
|
||||
// }, manager);
|
||||
// }
|
||||
|
||||
async findAppWithIdOrSlug(slug: string, organizationId: string): Promise<App> {
|
||||
let app: App;
|
||||
try {
|
||||
|
|
@ -675,7 +626,7 @@ export class AppsUtilService implements IAppsUtilService {
|
|||
const tooljetDbDataQueries = await manager
|
||||
.createQueryBuilder(DataQuery, 'data_queries')
|
||||
.innerJoin(DataSource, 'data_sources', 'data_queries.data_source_id = data_sources.id')
|
||||
.innerJoin(AppVersion, 'app_versions', 'app_versions.id = data_sources.app_version_id')
|
||||
.innerJoin(AppVersion, 'app_versions', 'app_versions.id = data_queries.app_version_id')
|
||||
.where('app_versions.app_id = :appId', { appId })
|
||||
.andWhere('data_sources.kind = :kind', { kind: 'tooljetdb' })
|
||||
.getMany();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { DefaultDataSourceKind } from '../types';
|
||||
|
||||
export enum FEATURE_KEY {
|
||||
GET = 'GET',
|
||||
GET_FOR_APP = 'GET_FOR_APP',
|
||||
|
|
@ -23,3 +25,5 @@ export enum DataSourceScopes {
|
|||
LOCAL = 'local',
|
||||
GLOBAL = 'global',
|
||||
}
|
||||
|
||||
export const DefaultDataSourceKinds: DefaultDataSourceKind[] = ['restapi', 'runjs', 'runpy', 'tooljetdb', 'workflows'];
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export class DataSourcesRepository extends Repository<DataSource> {
|
|||
organizationId: string,
|
||||
queryVars: GetQueryVariables
|
||||
): Promise<DataSource[]> {
|
||||
const { appVersionId, environmentId } = queryVars;
|
||||
const { appVersionId, environmentId, types } = queryVars;
|
||||
// Data source options are attached only if selectedEnvironmentId is passed
|
||||
// Returns global data sources + sample data sources
|
||||
// If version Id is passed, then data queries under each are also returned
|
||||
|
|
@ -67,6 +67,9 @@ export class DataSourcesRepository extends Repository<DataSource> {
|
|||
.andWhere('data_source.organization_id = :organizationId', { organizationId })
|
||||
.andWhere('data_source.scope = :scope', { scope: DataSourceScopes.GLOBAL });
|
||||
|
||||
if (types && types.length > 0) {
|
||||
query.andWhere('data_source.type IN (:...types)', { types });
|
||||
}
|
||||
if (environmentId) {
|
||||
query.andWhere('data_source_options.environmentId = :environmentId', { environmentId });
|
||||
}
|
||||
|
|
@ -140,11 +143,12 @@ export class DataSourcesRepository extends Repository<DataSource> {
|
|||
}, manager || this.manager);
|
||||
}
|
||||
|
||||
async createDefaultDataSource(kind: string, appVersionId: string, manager?: EntityManager): Promise<DataSource> {
|
||||
async createDefaultDataSource(kind: string, organizationId: string, manager?: EntityManager): Promise<DataSource> {
|
||||
const newDataSource = manager.create(DataSource, {
|
||||
name: `${kind}default`,
|
||||
kind,
|
||||
appVersionId,
|
||||
scope: DataSourceScopes.GLOBAL,
|
||||
organizationId,
|
||||
type: DataSourceTypes.STATIC,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
|
|
@ -152,6 +156,12 @@ export class DataSourcesRepository extends Repository<DataSource> {
|
|||
return await manager.save(newDataSource);
|
||||
}
|
||||
|
||||
async getStaticDataSources(organizationId: string, manager?: EntityManager): Promise<DataSource[]> {
|
||||
return await manager.find(DataSource, {
|
||||
where: { organizationId, type: DataSourceTypes.STATIC },
|
||||
});
|
||||
}
|
||||
|
||||
findByQuery(dataQueryId: string, organizationId: string, dataSourceId?: string, manager?: EntityManager) {
|
||||
return dbTransactionWrap((manager: EntityManager) => {
|
||||
return manager.findOne(DataSource, {
|
||||
|
|
@ -161,14 +171,6 @@ export class DataSourcesRepository extends Repository<DataSource> {
|
|||
}, manager || this.manager);
|
||||
}
|
||||
|
||||
getAllStaticDataSources(versionId: string, manager?: EntityManager): Promise<DataSource[]> {
|
||||
return dbTransactionWrap((manager: EntityManager) => {
|
||||
return manager.find(DataSource, {
|
||||
where: { appVersionId: versionId, type: DataSourceTypes.STATIC },
|
||||
});
|
||||
}, manager || this.manager);
|
||||
}
|
||||
|
||||
getDatasourceByPluginId(pluginId: string) {
|
||||
return dbTransactionWrap((manager: EntityManager) => {
|
||||
return manager.find(DataSource, {
|
||||
|
|
|
|||
|
|
@ -43,14 +43,13 @@ export class DataSourcesService implements IDataSourcesService {
|
|||
});
|
||||
const shouldIncludeWorkflows = query.shouldIncludeWorkflows ?? true;
|
||||
|
||||
const dataSources = await this.dataSourcesRepository.allGlobalDS(userPermissions, user.organizationId, query ?? {});
|
||||
let staticDataSources = await this.dataSourcesRepository.getAllStaticDataSources(query.appVersionId);
|
||||
let dataSources = await this.dataSourcesRepository.allGlobalDS(userPermissions, user.organizationId, query ?? {});
|
||||
|
||||
if (!shouldIncludeWorkflows) {
|
||||
// remove workflowsdefault data source from static data sources
|
||||
staticDataSources = staticDataSources.filter((dataSource) => dataSource.kind !== 'workflows');
|
||||
dataSources = dataSources.filter((dataSource) => dataSource.kind !== 'workflows');
|
||||
}
|
||||
const decamelizedDatasources = decamelizeKeys([...staticDataSources, ...dataSources]);
|
||||
const decamelizedDatasources = decamelizeKeys(dataSources);
|
||||
return { data_sources: decamelizedDatasources };
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +65,7 @@ export class DataSourcesService implements IDataSourcesService {
|
|||
const dataSources = await this.dataSourcesRepository.allGlobalDS(userPermissions, user.organizationId, {
|
||||
appVersionId: query.appVersionId,
|
||||
environmentId: selectedEnvironmentId,
|
||||
types: [DataSourceTypes.DEFAULT, DataSourceTypes.SAMPLE],
|
||||
});
|
||||
for (const dataSource of dataSources) {
|
||||
const parseIfNeeded = (data: any) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { FEATURE_KEY } from '../constants';
|
||||
import { DataSourceTypes, FEATURE_KEY } from '../constants';
|
||||
import { FeatureConfig } from '@modules/app/types';
|
||||
import { MODULES } from '@modules/app/constants/modules';
|
||||
import { QueryError, OAuthUnauthorizedClientError } from '@tooljet/plugins/dist/server';
|
||||
|
|
@ -50,6 +50,7 @@ export { QueryError, OAuthUnauthorizedClientError };
|
|||
export interface GetQueryVariables {
|
||||
appVersionId?: string;
|
||||
environmentId?: string;
|
||||
types?: DataSourceTypes[];
|
||||
shouldIncludeWorkflows?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -57,3 +58,5 @@ export interface UpdateOptions {
|
|||
dataSourceId: string;
|
||||
environmentId: string;
|
||||
}
|
||||
|
||||
export type DefaultDataSourceKind = 'restapi' | 'runjs' | 'runpy' | 'tooljetdb' | 'workflows';
|
||||
|
|
|
|||
|
|
@ -712,24 +712,6 @@ export class DataSourcesUtilService implements IDataSourcesUtilService {
|
|||
}
|
||||
}
|
||||
|
||||
async findDefaultDataSource(
|
||||
kind: string,
|
||||
appVersionId: string,
|
||||
organizationId: string,
|
||||
manager: EntityManager
|
||||
): Promise<DataSource> {
|
||||
const defaultDataSource = await manager.findOne(DataSource, {
|
||||
where: { kind, appVersionId, type: DataSourceTypes.STATIC },
|
||||
});
|
||||
|
||||
if (defaultDataSource) {
|
||||
return defaultDataSource;
|
||||
}
|
||||
const dataSource = await this.dataSourceRepository.createDefaultDataSource(kind, appVersionId, manager);
|
||||
await this.createDataSourceInAllEnvironments(organizationId, dataSource.id, manager);
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
async getAuthUrl(getDataSourceOauthUrlDto: GetDataSourceOauthUrlDto): Promise<{ url: string }> {
|
||||
const { provider, source_options = {}, plugin_id = null } = getDataSourceOauthUrlDto;
|
||||
const service = await this.pluginsServiceSelector.getService(plugin_id, provider);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { isEmpty } from 'lodash';
|
|||
import { InternalTableRepository } from '@modules/tooljet-db/repository';
|
||||
import { RequestContext } from '@modules/request-context/service';
|
||||
import { AUDIT_LOGS_REQUEST_CONTEXT_KEY } from '@modules/app/constants';
|
||||
import { dbTransactionWrap } from '@helpers/database.helper';
|
||||
|
||||
@Injectable()
|
||||
export class ImportExportResourcesService {
|
||||
|
|
@ -82,40 +83,43 @@ export class ImportExportResourcesService {
|
|||
}
|
||||
}
|
||||
|
||||
if (!isEmpty(importResourcesDto.tooljet_database)) {
|
||||
const res = await this.tooljetDbImportExportService.bulkImport(importResourcesDto, importingVersion, cloning);
|
||||
tableNameMapping = res.tableNameMapping;
|
||||
imports.tooljet_database = res.tooljet_database;
|
||||
imports.tableNameMapping = tableNameMapping;
|
||||
}
|
||||
|
||||
if (!isEmpty(importResourcesDto.app)) {
|
||||
for (const appImportDto of importResourcesDto.app) {
|
||||
user.organizationId = importResourcesDto.organization_id;
|
||||
const createdApp = await this.appImportExportService.import(
|
||||
user,
|
||||
appImportDto.definition,
|
||||
appImportDto.appName,
|
||||
{
|
||||
tooljet_database: tableNameMapping,
|
||||
},
|
||||
isGitApp,
|
||||
importResourcesDto.tooljet_version,
|
||||
cloning
|
||||
);
|
||||
|
||||
imports.app.push({ id: createdApp.id, name: createdApp.name });
|
||||
|
||||
RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, {
|
||||
userId: user.id,
|
||||
organizationId: user.organizationId,
|
||||
resourceId: createdApp.id,
|
||||
resourceName: createdApp.name,
|
||||
});
|
||||
return await dbTransactionWrap(async (manager) => {
|
||||
if (!isEmpty(importResourcesDto.tooljet_database)) {
|
||||
const res = await this.tooljetDbImportExportService.bulkImport(importResourcesDto, importingVersion, cloning);
|
||||
tableNameMapping = res.tableNameMapping;
|
||||
imports.tooljet_database = res.tooljet_database;
|
||||
imports.tableNameMapping = tableNameMapping;
|
||||
}
|
||||
}
|
||||
|
||||
return imports;
|
||||
if (!isEmpty(importResourcesDto.app)) {
|
||||
for (const appImportDto of importResourcesDto.app) {
|
||||
user.organizationId = importResourcesDto.organization_id;
|
||||
const createdApp = await this.appImportExportService.import(
|
||||
user,
|
||||
appImportDto.definition,
|
||||
appImportDto.appName,
|
||||
{
|
||||
tooljet_database: tableNameMapping,
|
||||
},
|
||||
isGitApp,
|
||||
importResourcesDto.tooljet_version,
|
||||
cloning,
|
||||
manager
|
||||
);
|
||||
|
||||
imports.app.push({ id: createdApp.id, name: createdApp.name });
|
||||
|
||||
RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, {
|
||||
userId: user.id,
|
||||
organizationId: user.organizationId,
|
||||
resourceId: createdApp.id,
|
||||
resourceName: createdApp.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return imports;
|
||||
});
|
||||
}
|
||||
|
||||
async legacyImport(user: User, templateDefinition: any, appName: string) {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { ThemesModule } from '@modules/organization-themes/module';
|
|||
import { SessionModule } from '@modules/session/module';
|
||||
import { InstanceSettingsModule } from '@modules/instance-settings/module';
|
||||
import { TooljetDbModule } from '@modules/tooljet-db/module';
|
||||
import { DataSourcesRepository } from '@modules/data-sources/repository';
|
||||
|
||||
export class SetupOrganizationsModule {
|
||||
static async register(configs?: { IS_GET_CONTEXT: boolean }): Promise<DynamicModule> {
|
||||
|
|
@ -38,7 +39,7 @@ export class SetupOrganizationsModule {
|
|||
OrganizationRepository,
|
||||
OrganizationUsersRepository,
|
||||
FeatureAbilityFactory,
|
||||
TooljetDbModule,
|
||||
DataSourcesRepository,
|
||||
],
|
||||
exports: [SetupOrganizationsUtilService],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ import { OrganizationUsersRepository } from '@modules/organization-users/reposit
|
|||
import { SampleDataSourceService } from '@modules/data-sources/services/sample-ds.service';
|
||||
import { ISetupOrganizationsUtilService } from './interfaces/IUtilService';
|
||||
import { TooljetDbTableOperationsService } from '@modules/tooljet-db/services/tooljet-db-table-operations.service';
|
||||
import { DataSourcesUtilService } from '@modules/data-sources/util.service';
|
||||
import { DataSourcesRepository } from '@modules/data-sources/repository';
|
||||
import { DefaultDataSourceKinds } from '@modules/data-sources/constants';
|
||||
import { OrganizationInputs } from './types/organization-inputs';
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -29,7 +32,9 @@ export class SetupOrganizationsUtilService implements ISetupOrganizationsUtilSer
|
|||
protected readonly sampleDBService: SampleDataSourceService,
|
||||
protected readonly licenseOrganizationService: LicenseOrganizationService,
|
||||
protected readonly licenseUserService: LicenseUserService,
|
||||
protected readonly organizationUserRepository: OrganizationUsersRepository
|
||||
protected readonly organizationUserRepository: OrganizationUsersRepository,
|
||||
protected readonly dataSourceUtilService: DataSourcesUtilService,
|
||||
protected readonly dataSourcesRepository: DataSourcesRepository
|
||||
) {}
|
||||
|
||||
async create(organizationInputs: OrganizationInputs, user?: User, manager?: EntityManager): Promise<Organization> {
|
||||
|
|
@ -48,6 +53,17 @@ export class SetupOrganizationsUtilService implements ISetupOrganizationsUtilSer
|
|||
//create default theme for this organization
|
||||
await this.organizationThemesUtilService.createDefaultTheme(manager, organization.id);
|
||||
await this.tooljetDbTableOperationsService.createTooljetDbTenantSchemaAndRole(organization.id, manager);
|
||||
|
||||
// Create static data sources for the organization
|
||||
for (const defaultSource of DefaultDataSourceKinds) {
|
||||
const dataSource = await this.dataSourcesRepository.createDefaultDataSource(
|
||||
defaultSource,
|
||||
organization.id,
|
||||
manager
|
||||
);
|
||||
await this.dataSourceUtilService.createDataSourceInAllEnvironments(organization.id, dataSource.id, manager);
|
||||
}
|
||||
|
||||
return organization;
|
||||
}, manager);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
|
||||
# ToolJet DB Postgrest
|
||||
|
||||
This module is required to setup ToolJet database
|
||||
|
||||
## Install postgrest
|
||||
https://docs.postgrest.org/en/v12/explanations/install.html
|
||||
|
||||
## PostgREST configuration file - postgrest.conf
|
||||
```
|
||||
db-uri = "postgres://postgres:postgres@localhost:5432/tooljet_new_db"
|
||||
db-pre-config = "postgrest.pre_config"
|
||||
server-port = "3001"
|
||||
jwt-secret = <add secret>
|
||||
```
|
||||
|
||||
### ToolJet .env settings
|
||||
```
|
||||
PGRST_JWT_SECRET=Same value configured as jwt-secret
|
||||
TOOLJET_DB=tooljet_new_db (Same value configured as db-uri db name)
|
||||
PGRST_HOST=localhost:3001
|
||||
TOOLJET_DB_USER=postgres
|
||||
TOOLJET_DB_PASS=postgres
|
||||
PGRST_DB_PRE_CONFIG=postgrest.pre_config
|
||||
```
|
||||
|
||||
### Start Postgrest
|
||||
```
|
||||
postgrest postgrest.conf
|
||||
```
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
db-uri = "postgres://postgres:postgres@localhost:5432/tooljet_new_db"
|
||||
db-pre-config = "postgrest.pre_config"
|
||||
server-port = "3001"
|
||||
jwt-secret = "cba75674be5b6305a18d18e3761d82bb031fe36b45719744a308209db3d6d805"
|
||||
|
|
@ -91,87 +91,44 @@ export class VersionsCreateService implements IVersionsCreateService {
|
|||
manager
|
||||
);
|
||||
|
||||
if (!versionFrom) {
|
||||
//create default data sources
|
||||
for (const defaultSource of ['restapi', 'runjs', 'tooljetdb', 'workflows']) {
|
||||
const dataSource = await this.dataSourceRepository.createDefaultDataSource(
|
||||
defaultSource,
|
||||
appVersion.id,
|
||||
manager
|
||||
);
|
||||
await this.dataSourceUtilService.createDataSourceInAllEnvironments(organizationId, dataSource.id, manager);
|
||||
}
|
||||
} else {
|
||||
const globalQueries: DataQuery[] = await this.dataQueryRepository.getQueriesByVersionId(
|
||||
versionFrom.id,
|
||||
DataSourceScopes.GLOBAL,
|
||||
manager
|
||||
);
|
||||
const dataSources = versionFrom?.dataSources.filter((ds) => ds.scope == DataSourceScopes.LOCAL); //Local data sources
|
||||
const globalDataSources = [...new Map(globalQueries.map((gq) => [gq.dataSource.id, gq.dataSource])).values()];
|
||||
const globalQueries: DataQuery[] = await this.dataQueryRepository.getQueriesByVersionId(
|
||||
versionFrom.id,
|
||||
DataSourceScopes.GLOBAL,
|
||||
manager
|
||||
);
|
||||
const dataSources = versionFrom?.dataSources.filter((ds) => ds.scope == DataSourceScopes.LOCAL); //Local data sources
|
||||
const globalDataSources = [...new Map(globalQueries.map((gq) => [gq.dataSource.id, gq.dataSource])).values()];
|
||||
|
||||
const dataSourceMapping = {};
|
||||
const newDataQueries = [];
|
||||
const allEvents = await manager.find(EventHandler, {
|
||||
where: { appVersionId: versionFrom?.id, target: Target.dataQuery },
|
||||
});
|
||||
const dataSourceMapping = {};
|
||||
const newDataQueries = [];
|
||||
const allEvents = await manager.find(EventHandler, {
|
||||
where: { appVersionId: versionFrom?.id, target: Target.dataQuery },
|
||||
});
|
||||
|
||||
if (dataSources?.length > 0 || globalDataSources?.length > 0) {
|
||||
if (dataSources?.length > 0) {
|
||||
for (const dataSource of dataSources) {
|
||||
const dataSourceParams: Partial<DataSource> = {
|
||||
name: dataSource.name,
|
||||
kind: dataSource.kind,
|
||||
type: dataSource.type,
|
||||
appVersionId: appVersion.id,
|
||||
};
|
||||
const newDataSource = await manager.save(manager.create(DataSource, dataSourceParams));
|
||||
dataSourceMapping[dataSource.id] = newDataSource.id;
|
||||
if (dataSources?.length > 0 || globalDataSources?.length > 0) {
|
||||
if (dataSources?.length > 0) {
|
||||
for (const dataSource of dataSources) {
|
||||
const dataSourceParams: Partial<DataSource> = {
|
||||
name: dataSource.name,
|
||||
kind: dataSource.kind,
|
||||
type: dataSource.type,
|
||||
appVersionId: appVersion.id,
|
||||
};
|
||||
const newDataSource = await manager.save(manager.create(DataSource, dataSourceParams));
|
||||
dataSourceMapping[dataSource.id] = newDataSource.id;
|
||||
|
||||
const dataQueries = versionFrom?.dataSources?.find((ds) => ds.id === dataSource.id).dataQueries;
|
||||
const dataQueries = versionFrom?.dataSources?.find((ds) => ds.id === dataSource.id).dataQueries;
|
||||
|
||||
for (const dataQuery of dataQueries) {
|
||||
const dataQueryParams = {
|
||||
name: dataQuery.name,
|
||||
options: dataQuery.options,
|
||||
dataSourceId: newDataSource.id,
|
||||
appVersionId: appVersion.id,
|
||||
};
|
||||
const newQuery = await manager.save(manager.create(DataQuery, dataQueryParams));
|
||||
|
||||
const dataQueryEvents = allEvents.filter((event) => event.sourceId === dataQuery.id);
|
||||
|
||||
dataQueryEvents.forEach(async (event, index) => {
|
||||
const newEvent = new EventHandler();
|
||||
|
||||
newEvent.id = uuid.v4();
|
||||
newEvent.name = event.name;
|
||||
newEvent.sourceId = newQuery.id;
|
||||
newEvent.target = event.target;
|
||||
newEvent.event = event.event;
|
||||
newEvent.index = event.index ?? index;
|
||||
newEvent.appVersionId = appVersion.id;
|
||||
|
||||
await manager.save(newEvent);
|
||||
});
|
||||
|
||||
oldDataQueryToNewMapping[dataQuery.id] = newQuery.id;
|
||||
newDataQueries.push(newQuery);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (globalQueries?.length > 0) {
|
||||
for (const globalQuery of globalQueries) {
|
||||
for (const dataQuery of dataQueries) {
|
||||
const dataQueryParams = {
|
||||
name: globalQuery.name,
|
||||
options: globalQuery.options,
|
||||
dataSourceId: globalQuery.dataSourceId,
|
||||
name: dataQuery.name,
|
||||
options: dataQuery.options,
|
||||
dataSourceId: newDataSource.id,
|
||||
appVersionId: appVersion.id,
|
||||
};
|
||||
|
||||
const newQuery = await manager.save(manager.create(DataQuery, dataQueryParams));
|
||||
const dataQueryEvents = allEvents.filter((event) => event.sourceId === globalQuery.id);
|
||||
|
||||
const dataQueryEvents = allEvents.filter((event) => event.sourceId === dataQuery.id);
|
||||
|
||||
dataQueryEvents.forEach(async (event, index) => {
|
||||
const newEvent = new EventHandler();
|
||||
|
|
@ -186,45 +143,70 @@ export class VersionsCreateService implements IVersionsCreateService {
|
|||
|
||||
await manager.save(newEvent);
|
||||
});
|
||||
oldDataQueryToNewMapping[globalQuery.id] = newQuery.id;
|
||||
|
||||
oldDataQueryToNewMapping[dataQuery.id] = newQuery.id;
|
||||
newDataQueries.push(newQuery);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const newQuery of newDataQueries) {
|
||||
const newOptions = this.replaceDataQueryOptionsWithNewDataQueryIds(
|
||||
newQuery.options,
|
||||
oldDataQueryToNewMapping
|
||||
);
|
||||
newQuery.options = newOptions;
|
||||
if (globalQueries?.length > 0) {
|
||||
for (const globalQuery of globalQueries) {
|
||||
const dataQueryParams = {
|
||||
name: globalQuery.name,
|
||||
options: globalQuery.options,
|
||||
dataSourceId: globalQuery.dataSourceId,
|
||||
appVersionId: appVersion.id,
|
||||
};
|
||||
|
||||
await manager.save(newQuery);
|
||||
const newQuery = await manager.save(manager.create(DataQuery, dataQueryParams));
|
||||
const dataQueryEvents = allEvents.filter((event) => event.sourceId === globalQuery.id);
|
||||
|
||||
dataQueryEvents.forEach(async (event, index) => {
|
||||
const newEvent = new EventHandler();
|
||||
|
||||
newEvent.id = uuid.v4();
|
||||
newEvent.name = event.name;
|
||||
newEvent.sourceId = newQuery.id;
|
||||
newEvent.target = event.target;
|
||||
newEvent.event = event.event;
|
||||
newEvent.index = event.index ?? index;
|
||||
newEvent.appVersionId = appVersion.id;
|
||||
|
||||
await manager.save(newEvent);
|
||||
});
|
||||
oldDataQueryToNewMapping[globalQuery.id] = newQuery.id;
|
||||
newDataQueries.push(newQuery);
|
||||
}
|
||||
}
|
||||
|
||||
appVersion.definition = this.replaceDataQueryIdWithinDefinitions(
|
||||
appVersion.definition,
|
||||
oldDataQueryToNewMapping
|
||||
);
|
||||
await manager.save(appVersion);
|
||||
for (const newQuery of newDataQueries) {
|
||||
const newOptions = this.replaceDataQueryOptionsWithNewDataQueryIds(newQuery.options, oldDataQueryToNewMapping);
|
||||
newQuery.options = newOptions;
|
||||
|
||||
for (const appEnvironment of appEnvironments) {
|
||||
for (const dataSource of dataSources) {
|
||||
const dataSourceOption = await manager.findOneOrFail(DataSourceOptions, {
|
||||
where: { dataSourceId: dataSource.id, environmentId: appEnvironment.id },
|
||||
});
|
||||
await manager.save(newQuery);
|
||||
}
|
||||
|
||||
const convertedOptions = this.convertToArrayOfKeyValuePairs(dataSourceOption.options);
|
||||
const newOptions = await this.dataSourceUtilService.parseOptionsForCreate(convertedOptions, false, manager);
|
||||
await this.setNewCredentialValueFromOldValue(newOptions, convertedOptions, manager);
|
||||
appVersion.definition = this.replaceDataQueryIdWithinDefinitions(appVersion.definition, oldDataQueryToNewMapping);
|
||||
await manager.save(appVersion);
|
||||
|
||||
await manager.save(
|
||||
manager.create(DataSourceOptions, {
|
||||
options: newOptions,
|
||||
dataSourceId: dataSourceMapping[dataSource.id],
|
||||
environmentId: appEnvironment.id,
|
||||
})
|
||||
);
|
||||
}
|
||||
for (const appEnvironment of appEnvironments) {
|
||||
for (const dataSource of dataSources) {
|
||||
const dataSourceOption = await manager.findOneOrFail(DataSourceOptions, {
|
||||
where: { dataSourceId: dataSource.id, environmentId: appEnvironment.id },
|
||||
});
|
||||
|
||||
const convertedOptions = this.convertToArrayOfKeyValuePairs(dataSourceOption.options);
|
||||
const newOptions = await this.dataSourceUtilService.parseOptionsForCreate(convertedOptions, false, manager);
|
||||
await this.setNewCredentialValueFromOldValue(newOptions, convertedOptions, manager);
|
||||
|
||||
await manager.save(
|
||||
manager.create(DataSourceOptions, {
|
||||
options: newOptions,
|
||||
dataSourceId: dataSourceMapping[dataSource.id],
|
||||
environmentId: appEnvironment.id,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,31 +0,0 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { User } from 'src/entities/user.entity';
|
||||
import { AppUser } from 'src/entities/app_user.entity';
|
||||
import { OrganizationUser } from 'src/entities/organization_user.entity';
|
||||
|
||||
@Injectable()
|
||||
export class AppUsersService {
|
||||
constructor(
|
||||
@InjectRepository(AppUser)
|
||||
private appUsersRepository: Repository<AppUser>,
|
||||
@InjectRepository(OrganizationUser)
|
||||
private organizationUsersRepository: Repository<AppUser>
|
||||
) {}
|
||||
|
||||
// TODO: remove deprecated
|
||||
async create(user: User, appId: string, organizationUserId: string, role: string): Promise<AppUser> {
|
||||
const organizationUser = await this.organizationUsersRepository.findOne({ where: { id: organizationUserId } });
|
||||
|
||||
return await this.appUsersRepository.save(
|
||||
this.appUsersRepository.create({
|
||||
appId,
|
||||
userId: organizationUser.userId,
|
||||
role,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Comment } from '../entities/comment.entity';
|
||||
import { CommentRepository } from '../repositories/comment.repository';
|
||||
import { CreateCommentDto, UpdateCommentDto } from '../dto/comment.dto';
|
||||
import { groupBy, head } from 'lodash';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { AppVersion } from 'src/entities/app_version.entity';
|
||||
import { User } from 'src/entities/user.entity';
|
||||
import { CommentUsers } from 'src/entities/comment_user.entity';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { EMAIL_EVENTS } from '@modules/email/constants';
|
||||
|
||||
@Injectable()
|
||||
export class CommentService {
|
||||
constructor(
|
||||
private readonly commentRepository: CommentRepository,
|
||||
@InjectRepository(AppVersion)
|
||||
private appVersionsRepository: Repository<AppVersion>,
|
||||
@InjectRepository(User)
|
||||
private usersRepository: Repository<User>,
|
||||
@InjectRepository(CommentUsers)
|
||||
private commentUsersRepository: Repository<CommentUsers>,
|
||||
private readonly _dataSource: DataSource,
|
||||
private eventEmitter: EventEmitter2
|
||||
) {}
|
||||
|
||||
public async createComment(createCommentDto: CreateCommentDto, user: User): Promise<Comment> {
|
||||
try {
|
||||
const comment = await this.commentRepository.createComment(createCommentDto, user.id, user.organizationId);
|
||||
|
||||
// todo: move mentioned user emails to a queue service
|
||||
const [appLink, commentLink, appName] = await this.getAppLinks(createCommentDto.appVersionsId, comment);
|
||||
|
||||
for (const userId of createCommentDto.mentionedUsers) {
|
||||
const mentionedUser = await this.usersRepository.findOne({ where: { id: userId }, relations: ['avatar'] });
|
||||
if (!mentionedUser) return null; // todo: invite user
|
||||
this.eventEmitter.emit('emailEvent', {
|
||||
type: EMAIL_EVENTS.SEND_COMMENT_MENTION_EMAIL,
|
||||
payload: {
|
||||
to: mentionedUser.email,
|
||||
from: user.firstName,
|
||||
appName: appName,
|
||||
appLink: appLink,
|
||||
commentLink: commentLink,
|
||||
timestamp: comment.createdAt.toUTCString(),
|
||||
comment: comment.comment,
|
||||
fromAvatar: mentionedUser.avatar?.data.toString('base64'),
|
||||
},
|
||||
});
|
||||
void this.commentUsersRepository.save(
|
||||
this.commentUsersRepository.create({ commentId: comment.id, userId: mentionedUser.id })
|
||||
);
|
||||
}
|
||||
return comment;
|
||||
} catch (error) {
|
||||
throw new InternalServerErrorException(error);
|
||||
}
|
||||
}
|
||||
|
||||
private async getAppLinks(appVersionsId: string, comment: Comment) {
|
||||
const appVersion = await this.appVersionsRepository.findOne({ where: { id: appVersionsId }, relations: ['app'] });
|
||||
const appLink = `${process.env.TOOLJET_HOST}/apps/${appVersion.app.id}`;
|
||||
const commentLink = `${appLink}?threadId=${comment.threadId}&commentId=${comment.id}`;
|
||||
|
||||
return [appLink, commentLink, appVersion.app.name];
|
||||
}
|
||||
|
||||
public async getComments(threadId: string, appVersionsId: string): Promise<Comment[]> {
|
||||
return await this._dataSource
|
||||
.createQueryBuilder(Comment, 'comment')
|
||||
.innerJoin('comment.user', 'user')
|
||||
.addSelect(['user.id', 'user.firstName', 'user.lastName'])
|
||||
.andWhere('comment.threadId = :threadId', {
|
||||
threadId,
|
||||
})
|
||||
.andWhere('comment.appVersionsId = :appVersionsId', {
|
||||
appVersionsId,
|
||||
})
|
||||
.orderBy('comment.createdAt', 'ASC')
|
||||
.getMany();
|
||||
}
|
||||
|
||||
public async getOrganizationComments(organizationId: string, appVersionsId: string): Promise<Comment[]> {
|
||||
return await this.commentRepository.find({
|
||||
where: {
|
||||
organizationId,
|
||||
appVersionsId,
|
||||
},
|
||||
order: {
|
||||
createdAt: 'ASC',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async getNotifications(
|
||||
appId: string,
|
||||
userId: string,
|
||||
isResolved = false,
|
||||
appVersionsId: string,
|
||||
pageId: string
|
||||
): Promise<Comment[]> {
|
||||
const comments = await this._dataSource
|
||||
.createQueryBuilder(Comment, 'comment')
|
||||
.innerJoin('comment.user', 'user')
|
||||
.addSelect(['user.id', 'user.firstName', 'user.lastName'])
|
||||
.innerJoin('comment.thread', 'thread')
|
||||
.addSelect(['thread.id'])
|
||||
.andWhere('thread.appId = :appId', {
|
||||
appId,
|
||||
})
|
||||
.andWhere('thread.pageId = :pageId', {
|
||||
pageId,
|
||||
})
|
||||
.andWhere('thread.isResolved = :isResolved', {
|
||||
isResolved,
|
||||
})
|
||||
.andWhere('comment.appVersionsId = :appVersionsId', {
|
||||
appVersionsId,
|
||||
})
|
||||
.orderBy('comment.createdAt', 'DESC')
|
||||
.getMany();
|
||||
|
||||
const groupedComments = groupBy(comments, 'threadId');
|
||||
|
||||
const _comments = [];
|
||||
|
||||
Object.keys(groupedComments).map((k) => {
|
||||
_comments.push({ comment: head(groupedComments[k]), count: groupedComments[k].length });
|
||||
});
|
||||
|
||||
return _comments;
|
||||
}
|
||||
|
||||
public async getComment(commentId: string): Promise<Comment> {
|
||||
const foundComment = await this.commentRepository.findOne({ where: { id: commentId } });
|
||||
if (!foundComment) {
|
||||
throw new NotFoundException('Comment not found');
|
||||
}
|
||||
return foundComment;
|
||||
}
|
||||
|
||||
public async editComment(commentId: string, updateCommentDto: UpdateCommentDto): Promise<Comment> {
|
||||
const editedComment = await this.commentRepository.findOne({ where: { id: commentId } });
|
||||
if (!editedComment) {
|
||||
throw new NotFoundException('Comment not found');
|
||||
}
|
||||
return this.commentRepository.editComment(updateCommentDto, editedComment);
|
||||
}
|
||||
|
||||
public async deleteComment(commentId: string): Promise<void> {
|
||||
await this.commentRepository.delete(commentId);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Comment } from '../entities/comment.entity';
|
||||
import { Repository } from 'typeorm';
|
||||
import { AppVersion } from 'src/entities/app_version.entity';
|
||||
import { User } from 'src/entities/user.entity';
|
||||
import { CommentUsers } from 'src/entities/comment_user.entity';
|
||||
import { UpdateCommentUserDto } from '@dto/comment-user.dto';
|
||||
|
||||
@Injectable()
|
||||
export class CommentUsersService {
|
||||
constructor(
|
||||
@InjectRepository(AppVersion)
|
||||
private appVersionsRepository: Repository<AppVersion>,
|
||||
@InjectRepository(User)
|
||||
private usersRepository: Repository<User>,
|
||||
@InjectRepository(CommentUsers)
|
||||
private commentUsersRepository: Repository<CommentUsers>
|
||||
) {}
|
||||
|
||||
private async getAppLinks(appVersionsId: string, comment: Comment) {
|
||||
const appVersion = await this.appVersionsRepository.findOne({ where: { id: appVersionsId }, relations: ['app'] });
|
||||
const appLink = `${process.env.TOOLJET_HOST}/apps/${appVersion.app.id}`;
|
||||
const commentLink = `${appLink}?threadId=${comment.threadId}&commentId=${comment.id}`;
|
||||
|
||||
return [appLink, commentLink, appVersion.app.name];
|
||||
}
|
||||
|
||||
public async findAll(userId: string, isRead = false) {
|
||||
const notifications = await this.commentUsersRepository.find({
|
||||
where: {
|
||||
userId,
|
||||
isRead,
|
||||
},
|
||||
order: {
|
||||
createdAt: 'DESC',
|
||||
},
|
||||
relations: ['comment'],
|
||||
});
|
||||
|
||||
if (!notifications) {
|
||||
throw new NotFoundException('User notifications not found');
|
||||
}
|
||||
|
||||
try {
|
||||
const _notifications = notifications.map(async (notification) => {
|
||||
const [, commentLink] = await this.getAppLinks(notification.comment.appVersionsId, notification.comment);
|
||||
const user = await this.usersRepository.findOne({
|
||||
where: { id: notification.comment.userId },
|
||||
relations: ['avatar'],
|
||||
});
|
||||
const creator = {
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
avatar: user.avatar?.data.toString('base64'),
|
||||
};
|
||||
return {
|
||||
id: notification.id,
|
||||
creator,
|
||||
comment: notification.comment.comment,
|
||||
createdAt: notification.comment.createdAt,
|
||||
updatedAt: notification.comment.updatedAt,
|
||||
commentLink,
|
||||
isRead: notification.isRead,
|
||||
};
|
||||
});
|
||||
return Promise.all(_notifications);
|
||||
} catch (error) {
|
||||
throw new InternalServerErrorException(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async update(commentUserId: string, body: UpdateCommentUserDto) {
|
||||
const item = await this.commentUsersRepository.update(commentUserId, { isRead: body.isRead });
|
||||
return item;
|
||||
}
|
||||
|
||||
public async updateAll(userId: string, body: UpdateCommentUserDto) {
|
||||
const { isRead } = body;
|
||||
const item = await this.commentUsersRepository.update({ userId }, { isRead });
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,343 +0,0 @@
|
|||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { User } from 'src/entities/user.entity';
|
||||
import { OrganizationUser } from 'src/entities/organization_user.entity';
|
||||
import { Organization } from 'src/entities/organization.entity';
|
||||
import { GroupPermission } from 'src/entities/group_permission.entity';
|
||||
import { UserGroupPermission } from 'src/entities/user_group_permission.entity';
|
||||
import { dbTransactionWrap } from 'src/helpers/database.helper';
|
||||
import { EntityManager } from 'typeorm';
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { CreateUserDto, Status, UpdateGivenWorkspaceDto, UpdateUserDto, WorkspaceDto } from '@dto/external_apis.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ExternalApisService {
|
||||
constructor() {}
|
||||
|
||||
private generateRandomPassword(length: number = 8): string {
|
||||
const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
let password = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * charset.length);
|
||||
password += charset[randomIndex];
|
||||
}
|
||||
return password;
|
||||
}
|
||||
|
||||
async getAllUsers(id?: string, manager?: EntityManager) {
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const query = manager
|
||||
.createQueryBuilder(User, 'user')
|
||||
.leftJoinAndSelect('user.organizationUsers', 'organizationUser')
|
||||
.leftJoinAndSelect('organizationUser.organization', 'organization', 'organization.status=:activeStatus', {
|
||||
activeStatus: 'active',
|
||||
})
|
||||
.leftJoinAndSelect('user.userGroupPermissions', 'userGroupPermissions')
|
||||
.leftJoinAndSelect('userGroupPermissions.groupPermission', 'groupPermissions');
|
||||
|
||||
if (id) {
|
||||
query.andWhere('user.id=:id', { id });
|
||||
}
|
||||
const users: User[] = await query.getMany();
|
||||
|
||||
const userResponses = users?.map((user) => {
|
||||
const userResponse = {
|
||||
id: user.id,
|
||||
name: `${user.firstName} ${user.lastName}`,
|
||||
email: user.email,
|
||||
status: user.status,
|
||||
workspaces: [],
|
||||
};
|
||||
const workspaces = user?.organizationUsers?.map((ou) => {
|
||||
const workspaceResponse = {
|
||||
id: ou.organization?.id,
|
||||
name: ou.organization?.name,
|
||||
status: ou.organization?.status,
|
||||
groups: [],
|
||||
};
|
||||
const groups = user?.userGroupPermissions
|
||||
?.filter((ugp) => ugp.groupPermission.organizationId === workspaceResponse.id)
|
||||
?.map((ugp) => {
|
||||
return {
|
||||
id: ugp?.groupPermission?.id,
|
||||
name: ugp?.groupPermission.group,
|
||||
};
|
||||
});
|
||||
workspaceResponse.groups = groups || [];
|
||||
return workspaceResponse;
|
||||
});
|
||||
userResponse.workspaces = workspaces || [];
|
||||
return userResponse;
|
||||
});
|
||||
return !id ? userResponses || [] : userResponses && userResponses.length > 0 ? userResponses[0] : [];
|
||||
}, manager);
|
||||
}
|
||||
|
||||
async createUser(userDto: CreateUserDto) {
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const { name, email, password, status, workspaces } = userDto;
|
||||
|
||||
const [firstName, lastName] = name.split(' ');
|
||||
|
||||
// Generate a password if not provided
|
||||
const userPassword = password || this.generateRandomPassword();
|
||||
|
||||
const newUser = manager.create(User, {
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
password: userPassword,
|
||||
status: status || Status.ARCHIVED,
|
||||
});
|
||||
|
||||
await manager.save(User, newUser);
|
||||
|
||||
for (const workspace of workspaces) {
|
||||
let organization = null;
|
||||
|
||||
if (workspace.id) {
|
||||
organization = await manager.findOne(Organization, { where: { id: workspace.id } });
|
||||
} else if (workspace.name) {
|
||||
organization = await manager.findOne(Organization, { where: { name: workspace.name } });
|
||||
}
|
||||
|
||||
if (!organization) {
|
||||
throw new BadRequestException(
|
||||
`The workspaces id or name do not exist: id ${workspace.id}, name ${workspace.name}`
|
||||
);
|
||||
}
|
||||
|
||||
const organizationUser = manager.create(OrganizationUser, {
|
||||
userId: newUser.id,
|
||||
organizationId: organization.id,
|
||||
status: workspace?.status || Status.ARCHIVED,
|
||||
role: 'all-users',
|
||||
});
|
||||
|
||||
await manager.save(OrganizationUser, organizationUser);
|
||||
|
||||
let groups = workspace.groups;
|
||||
|
||||
if (!groups || groups.length === 0) {
|
||||
groups = [{ name: 'all_users' }];
|
||||
}
|
||||
|
||||
for (const group of groups) {
|
||||
let groupPermission = null;
|
||||
|
||||
if (group.id) {
|
||||
groupPermission = await manager.findOne(GroupPermission, { where: { id: group.id } });
|
||||
} else if (group.name) {
|
||||
groupPermission = await manager.findOne(GroupPermission, {
|
||||
where: { group: group.name, organizationId: organization.id },
|
||||
});
|
||||
}
|
||||
|
||||
if (!groupPermission) {
|
||||
throw new BadRequestException(`Group permission id or name not found: id ${group.id}, name ${group.name}`);
|
||||
}
|
||||
|
||||
// Associate user with group permission
|
||||
await manager.save(UserGroupPermission, {
|
||||
userId: newUser.id,
|
||||
groupPermissionId: groupPermission.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return await this.getAllUsers(newUser.id, manager);
|
||||
});
|
||||
}
|
||||
|
||||
async updateUser(id: string, updateDto: UpdateUserDto) {
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const user = await manager.findOne(User, { where: { id } });
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException('User not found');
|
||||
}
|
||||
|
||||
const { name, email, password, status } = updateDto;
|
||||
|
||||
const userUpdateParams: Partial<User> = {};
|
||||
|
||||
if (name) {
|
||||
const [firstName, lastName] = name.split(' ');
|
||||
userUpdateParams.firstName = firstName;
|
||||
userUpdateParams.lastName = lastName;
|
||||
}
|
||||
|
||||
if (email) {
|
||||
userUpdateParams.email = email;
|
||||
}
|
||||
|
||||
if (password) {
|
||||
userUpdateParams.password = password;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
userUpdateParams.status = status;
|
||||
}
|
||||
|
||||
await manager.update(User, { id: user.id }, userUpdateParams);
|
||||
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
async replaceUserAllWorkspacesRelations(userId: string, workspacesDto: WorkspaceDto[]) {
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const user = await manager.findOne(User, { where: { id: userId } });
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException('User not found');
|
||||
}
|
||||
|
||||
// Remove existing group permissions for the user from all workspaces
|
||||
await manager.delete(UserGroupPermission, { userId });
|
||||
|
||||
// Remove existing organization user records for the user
|
||||
await manager.delete(OrganizationUser, { userId });
|
||||
|
||||
for (const workspace of workspacesDto) {
|
||||
let organization = null;
|
||||
|
||||
if (workspace.id) {
|
||||
organization = await manager.findOne(Organization, { where: { id: workspace.id } });
|
||||
} else if (workspace.name) {
|
||||
organization = await manager.findOne(Organization, { where: { name: workspace.name } });
|
||||
}
|
||||
|
||||
if (!organization) {
|
||||
throw new BadRequestException(
|
||||
`The workspaces id or name do not exist: id ${workspace.id}, name ${workspace.name}`
|
||||
);
|
||||
}
|
||||
|
||||
const organizationUser = manager.create(OrganizationUser, {
|
||||
userId: userId,
|
||||
organizationId: organization.id,
|
||||
status: workspace.status || Status.ARCHIVED,
|
||||
role: 'all-users',
|
||||
});
|
||||
|
||||
await manager.save(OrganizationUser, organizationUser);
|
||||
|
||||
const groups = !workspace.groups || workspace.groups.length === 0 ? [{ name: 'all_users' }] : workspace.groups;
|
||||
|
||||
for (const group of groups) {
|
||||
let groupPermission = null;
|
||||
|
||||
if (group.id) {
|
||||
groupPermission = await manager.findOne(GroupPermission, {
|
||||
where: { id: group.id, organizationId: organization.id },
|
||||
});
|
||||
} else if (group.name) {
|
||||
groupPermission = await manager.findOne(GroupPermission, {
|
||||
where: { group: group.name, organizationId: organization.id },
|
||||
});
|
||||
}
|
||||
|
||||
if (!groupPermission) {
|
||||
throw new BadRequestException(`Group permission id or name not found: id ${group.id}, name ${group.name}`);
|
||||
}
|
||||
|
||||
// Associate user with group permission
|
||||
await manager.save(UserGroupPermission, {
|
||||
userId: userId,
|
||||
groupPermissionId: groupPermission.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
async replaceUserWorkspaceRelations(userId: string, workspaceId: string, workspaceDto: UpdateGivenWorkspaceDto) {
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const query = manager
|
||||
.createQueryBuilder(OrganizationUser, 'organizationUser')
|
||||
.innerJoin('organizationUser.organization', 'organization', 'organization.status = :active', {
|
||||
active: 'active',
|
||||
})
|
||||
.where('organizationUser.userId = :userId', { userId })
|
||||
.andWhere('organizationUser.organizationId = :workspaceId', { workspaceId });
|
||||
|
||||
const organizationUser = await query.getOne();
|
||||
|
||||
if (!organizationUser) {
|
||||
throw new NotFoundException('User not found');
|
||||
}
|
||||
|
||||
if (workspaceDto.status && organizationUser.status !== workspaceDto.status) {
|
||||
await manager.update(OrganizationUser, { status: workspaceDto.status }, { id: organizationUser.id });
|
||||
}
|
||||
|
||||
if (workspaceDto.groups && workspaceDto.groups.length > 0) {
|
||||
// Remove existing group permissions for the user in this workspace
|
||||
await manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(UserGroupPermission)
|
||||
.where('userId = :userId', { userId })
|
||||
.andWhere('groupPermissionId IN (SELECT id FROM group_permissions WHERE organization_id = :organizationId)', {
|
||||
organizationId: workspaceId,
|
||||
})
|
||||
.execute();
|
||||
|
||||
// Add to groups
|
||||
for (const group of workspaceDto.groups) {
|
||||
let groupPermission = null;
|
||||
|
||||
if (group.id) {
|
||||
groupPermission = await manager.findOne(GroupPermission, {
|
||||
where: { id: group.id, organizationId: workspaceId },
|
||||
});
|
||||
} else if (group.name) {
|
||||
groupPermission = await manager.findOne(GroupPermission, {
|
||||
where: { group: group.name, organizationId: workspaceId },
|
||||
});
|
||||
}
|
||||
|
||||
if (!groupPermission) {
|
||||
throw new BadRequestException(`Group permission id or name not found: id ${group.id}, name ${group.name}`);
|
||||
}
|
||||
|
||||
// Associate user with group permission
|
||||
await manager.save(UserGroupPermission, {
|
||||
userId: userId,
|
||||
groupPermissionId: groupPermission.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
async getAllWorkspaces() {
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const workspaces: Organization[] = await manager.find(Organization, {
|
||||
where: { status: 'active' },
|
||||
relations: ['groupPermissions'],
|
||||
});
|
||||
|
||||
const workspaceResponses = workspaces.map((workspace: Organization) => {
|
||||
return {
|
||||
id: workspace.id,
|
||||
name: workspace.name,
|
||||
status: workspace.status,
|
||||
groups:
|
||||
workspace?.groupPermissions?.map((groupPermission: GroupPermission) => {
|
||||
return {
|
||||
id: groupPermission.id,
|
||||
name: groupPermission.group,
|
||||
};
|
||||
}) || [],
|
||||
};
|
||||
});
|
||||
|
||||
return workspaceResponses;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
// import { Injectable } from '@nestjs/common';
|
||||
// import { User } from 'src/entities/user.entity';
|
||||
// import { ExportResourcesDto } from '@dto/export-resources.dto';
|
||||
// import { AppImportExportService } from './app_import_export.service';
|
||||
// import { TooljetDbImportExportService } from './tooljet_db_import_export_service';
|
||||
// import { ImportResourcesDto, ImportTooljetDatabaseDto } from '@dto/import-resources.dto';
|
||||
// import { AppsService } from './apps.service';
|
||||
// import { CloneResourcesDto } from '@dto/clone-resources.dto';
|
||||
// import { isEmpty } from 'lodash';
|
||||
// import { ActionTypes, ResourceTypes } from 'src/entities/audit_log.entity';
|
||||
// import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
|
||||
// @Injectable()
|
||||
// export class ImportExportResourcesService {
|
||||
// constructor(
|
||||
// private readonly appImportExportService: AppImportExportService,
|
||||
// private readonly appsService: AppsService,
|
||||
// private readonly tooljetDbImportExportService: TooljetDbImportExportService,
|
||||
// private eventEmitter: EventEmitter2
|
||||
// ) {}
|
||||
|
||||
// async export(
|
||||
// user: User,
|
||||
// exportResourcesDto: ExportResourcesDto
|
||||
// ): Promise<{
|
||||
// tooljet_database?: Array<ImportTooljetDatabaseDto>;
|
||||
// app?: Array<Record<string, any>>; // TODO: Define the type for app
|
||||
// }> {
|
||||
// const resourcesExport: {
|
||||
// tooljet_database?: Array<ImportTooljetDatabaseDto>;
|
||||
// app?: Array<Record<string, unknown>>;
|
||||
// } = {};
|
||||
|
||||
// if (exportResourcesDto.tooljet_database?.length) {
|
||||
// const exportedDbs: ImportTooljetDatabaseDto[] = [];
|
||||
// for (const tjdb of exportResourcesDto.tooljet_database) {
|
||||
// const exportedDb = await this.tooljetDbImportExportService.export(
|
||||
// exportResourcesDto.organization_id,
|
||||
// tjdb,
|
||||
// exportResourcesDto.tooljet_database
|
||||
// );
|
||||
// exportedDbs.push(exportedDb);
|
||||
// }
|
||||
|
||||
// if (exportedDbs.length > 0) resourcesExport.tooljet_database = exportedDbs;
|
||||
// }
|
||||
|
||||
// if (exportResourcesDto.app?.length) {
|
||||
// const exportedApps: Record<string, unknown>[] = [];
|
||||
// for (const app of exportResourcesDto.app) {
|
||||
// const exportedApp = {
|
||||
// definition: await this.appImportExportService.export(user, app.id, app.search_params),
|
||||
// };
|
||||
// exportedApps.push(exportedApp);
|
||||
// }
|
||||
|
||||
// if (exportedApps.length > 0) resourcesExport.app = exportedApps;
|
||||
// }
|
||||
|
||||
// return resourcesExport;
|
||||
// }
|
||||
|
||||
// async import(
|
||||
// user: User,
|
||||
// importResourcesDto: ImportResourcesDto,
|
||||
// cloning = false,
|
||||
// isGitApp = false,
|
||||
// isTemplateApp = false
|
||||
// ) {
|
||||
// let tableNameMapping = {};
|
||||
// const imports = { app: [], tooljet_database: [], tableNameMapping: {} };
|
||||
// const importingVersion = importResourcesDto.tooljet_version;
|
||||
|
||||
// if (!isEmpty(importResourcesDto.tooljet_database)) {
|
||||
// const res = await this.tooljetDbImportExportService.bulkImport(importResourcesDto, importingVersion, cloning);
|
||||
// tableNameMapping = res.tableNameMapping;
|
||||
// imports.tooljet_database = res.tooljet_database;
|
||||
// imports.tableNameMapping = tableNameMapping;
|
||||
// }
|
||||
|
||||
// if (!isEmpty(importResourcesDto.app)) {
|
||||
// for (const appImportDto of importResourcesDto.app) {
|
||||
// user.organizationId = importResourcesDto.organization_id;
|
||||
// const createdApp = await this.appImportExportService.import(
|
||||
// user,
|
||||
// appImportDto.definition,
|
||||
// appImportDto.appName,
|
||||
// {
|
||||
// tooljet_database: tableNameMapping,
|
||||
// },
|
||||
// isGitApp,
|
||||
// importResourcesDto.tooljet_version,
|
||||
// cloning
|
||||
// );
|
||||
|
||||
// imports.app.push({ id: createdApp.id, name: createdApp.name });
|
||||
|
||||
// this.eventEmitter.emit('auditLogEntry', {
|
||||
// userId: user.id,
|
||||
// organizationId: user.organizationId,
|
||||
// resourceId: createdApp.id,
|
||||
// resourceType: ResourceTypes.APP,
|
||||
// resourceName: createdApp.name,
|
||||
// actionType: ActionTypes.APP_CREATE,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// return imports;
|
||||
// }
|
||||
|
||||
// async clone(user: User, { organization_id, app: [{ id: appId, name: newAppName }] }: CloneResourcesDto) {
|
||||
// const tablesForApp = await this.appsService.findTooljetDbTables(appId);
|
||||
// const exportResourcesDto: ExportResourcesDto = {
|
||||
// organization_id,
|
||||
// app: [{ id: appId, search_params: null }],
|
||||
// tooljet_database: tablesForApp,
|
||||
// };
|
||||
|
||||
// const resourceExport = await this.export(user, exportResourcesDto);
|
||||
// // TODO: Verify if this is required as we always pass name on imports
|
||||
// // Without this appImportExportService.import will throw an error
|
||||
// resourceExport.app[0].definition.appV2.name = newAppName;
|
||||
|
||||
// const importResourcesDto: ImportResourcesDto = {
|
||||
// organization_id,
|
||||
// tooljet_version: globalThis.TOOLJET_VERSION,
|
||||
// app: [
|
||||
// {
|
||||
// appName: newAppName,
|
||||
// definition: resourceExport.app[0].definition,
|
||||
// },
|
||||
// ],
|
||||
// tooljet_database: resourceExport.tooljet_database,
|
||||
// };
|
||||
|
||||
// return this.import(user, importResourcesDto, true);
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { User } from '../entities/user.entity';
|
||||
import { readFileSync } from 'fs';
|
||||
import { Logger } from 'nestjs-pino';
|
||||
import { ImportExportResourcesService } from './import_export_resources.service';
|
||||
import { ImportResourcesDto } from '@dto/import-resources.dto';
|
||||
import { AppImportExportService } from './app_import_export.service';
|
||||
import { isVersionGreaterThanOrEqual } from 'src/helpers/utils.helper';
|
||||
import { AppsService } from './apps.service';
|
||||
import { getMaxCopyNumber } from 'src/helpers/utils.helper';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { TooljetDbBulkUploadService } from '@services/tooljet_db_bulk_upload.service';
|
||||
import { PluginsService } from './plugins.service';
|
||||
|
||||
@Injectable()
|
||||
export class LibraryAppCreationService {
|
||||
constructor(
|
||||
private readonly importExportResourcesService: ImportExportResourcesService,
|
||||
private readonly appImportExportService: AppImportExportService,
|
||||
private readonly appsService: AppsService,
|
||||
private readonly logger: Logger,
|
||||
private readonly tooljetDbBulkUploadService: TooljetDbBulkUploadService,
|
||||
private readonly pluginsService: PluginsService
|
||||
) {}
|
||||
|
||||
async perform(
|
||||
currentUser: User,
|
||||
identifier: string,
|
||||
appName: string,
|
||||
dependentPluginsForTemplate: Array<string>,
|
||||
shouldAutoImportPlugin: boolean
|
||||
) {
|
||||
const templateDefinition = this.findTemplateDefinition(identifier);
|
||||
if (dependentPluginsForTemplate.length)
|
||||
await this.pluginsService.autoInstallPluginsForTemplates(dependentPluginsForTemplate, shouldAutoImportPlugin);
|
||||
return this.importTemplate(currentUser, templateDefinition, appName, identifier);
|
||||
}
|
||||
|
||||
async createSampleApp(currentUser: User) {
|
||||
let name = 'Sample app ';
|
||||
const allSampleApps = await this.appsService.findAll(currentUser?.organizationId, { name });
|
||||
const existNameList = allSampleApps.map((app) => app.name);
|
||||
const maxNumber = getMaxCopyNumber(existNameList, ' ');
|
||||
name = `${name} ${maxNumber}`;
|
||||
const sampleAppDef = JSON.parse(readFileSync(`templates/sample_app_def.json`, 'utf-8'));
|
||||
return this.importTemplate(currentUser, sampleAppDef, name);
|
||||
}
|
||||
|
||||
async createSampleOnboardApp(currentUser: User) {
|
||||
const name = 'Product inventory';
|
||||
const sampleAppDef = JSON.parse(readFileSync(`templates/onboard_sample_app.json`, 'utf-8'));
|
||||
return this.importTemplate(currentUser, sampleAppDef, name);
|
||||
}
|
||||
|
||||
async importTemplate(currentUser: User, templateDefinition: any, appName: string, identifier?: string) {
|
||||
const importDto = new ImportResourcesDto();
|
||||
importDto.organization_id = currentUser.organizationId;
|
||||
importDto.app = templateDefinition.app || templateDefinition.appV2;
|
||||
importDto.tooljet_database = templateDefinition.tooljet_database;
|
||||
importDto.tooljet_version = templateDefinition.tooljet_version;
|
||||
|
||||
if (isVersionGreaterThanOrEqual(templateDefinition.tooljet_version, '2.16.0')) {
|
||||
importDto.app[0].appName = appName;
|
||||
const importedResources = await this.importExportResourcesService.import(
|
||||
currentUser,
|
||||
importDto,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
const tableNameMapping: { [key: string]: { id: string; table_name: string } } =
|
||||
importedResources.tableNameMapping;
|
||||
const entries = Object.entries(tableNameMapping);
|
||||
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const [key, { id: tableId }] = entries[i];
|
||||
const tableIdFromDefinition = key;
|
||||
const newTableid = tableId;
|
||||
|
||||
const tableDetails = templateDefinition.tooljet_database.find(
|
||||
(table: Record<string, any>) => table.id === tableIdFromDefinition
|
||||
);
|
||||
|
||||
if (tableDetails) {
|
||||
const tableNameAsPerDefinition = tableDetails.table_name;
|
||||
this.processCsvFile(identifier, tableNameAsPerDefinition, newTableid, currentUser.organizationId);
|
||||
}
|
||||
}
|
||||
|
||||
return importedResources;
|
||||
} else {
|
||||
const importedApp = await this.appImportExportService.import(currentUser, templateDefinition, appName);
|
||||
return {
|
||||
app: [importedApp],
|
||||
tooljet_database: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
findTemplateDefinition(identifier: string) {
|
||||
try {
|
||||
return JSON.parse(readFileSync(`templates/${identifier}/definition.json`, 'utf-8'));
|
||||
} catch (err) {
|
||||
this.logger.error(err);
|
||||
throw new BadRequestException('App definition not found');
|
||||
}
|
||||
}
|
||||
async processCsvFile(identifier: string, tableName: string, tableId: string, organizationId: string) {
|
||||
try {
|
||||
const csvFilePath = path.join('templates', `${identifier}/data/${tableName}/data.csv`);
|
||||
|
||||
// Read the CSV file and convert it into a buffer
|
||||
const fileBuffer = fs.readFileSync(csvFilePath);
|
||||
|
||||
return await this.tooljetDbBulkUploadService.bulkUploadCsv(tableId, fileBuffer, organizationId);
|
||||
} catch (error) {
|
||||
console.error('Error processing CSV file:', error);
|
||||
throw new BadRequestException('Failed to process CSV file');
|
||||
}
|
||||
}
|
||||
|
||||
async findDepedentPluginsFromTemplateDefinition(identifier: string) {
|
||||
const templateDefinition = this.findTemplateDefinition(identifier);
|
||||
const importDto = new ImportResourcesDto();
|
||||
importDto.app = templateDefinition.app || templateDefinition.appV2;
|
||||
|
||||
const dataSourcesUsedInApps = [];
|
||||
importDto.app.forEach((appDefinition) => {
|
||||
appDefinition.definition?.appV2.dataSources.forEach((dataSource) => {
|
||||
dataSourcesUsedInApps.push(dataSource);
|
||||
});
|
||||
});
|
||||
const { pluginsToBeInstalled, pluginsListIdToDetailsMap } = await this.pluginsService.checkIfPluginsToBeInstalled(
|
||||
dataSourcesUsedInApps
|
||||
);
|
||||
return { pluginsToBeInstalled, pluginsListIdToDetailsMap };
|
||||
}
|
||||
}
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
import { CreateEnvironmentVariableDto, UpdateEnvironmentVariableDto } from '@dto/environment-variable.dto';
|
||||
import { ConflictException, Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { OrgEnvironmentVariable } from 'src/entities/org_envirnoment_variable.entity';
|
||||
import { Repository } from 'typeorm';
|
||||
import { User } from '../entities/user.entity';
|
||||
import { cleanObject } from 'src/helpers/utils.helper';
|
||||
import { EncryptionService } from '@modules/encryption/service';
|
||||
|
||||
@Injectable()
|
||||
export class OrgEnvironmentVariablesService {
|
||||
constructor(
|
||||
@InjectRepository(OrgEnvironmentVariable)
|
||||
private orgEnvironmentVariablesRepository: Repository<OrgEnvironmentVariable>,
|
||||
private encryptionService: EncryptionService
|
||||
) {}
|
||||
|
||||
async fetchVariables(organizationId: string): Promise<OrgEnvironmentVariable[]> {
|
||||
const variables: OrgEnvironmentVariable[] = await this.orgEnvironmentVariablesRepository.find({
|
||||
where: { organizationId },
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
variables.map(async (variable: OrgEnvironmentVariable) => {
|
||||
if (variable.variableType === 'server') {
|
||||
delete variable.value;
|
||||
} else {
|
||||
if (variable.encrypted) variable['value'] = await this.decryptSecret(organizationId, variable.value);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
async create(
|
||||
currentUser: User,
|
||||
environmentVariableDto: CreateEnvironmentVariableDto
|
||||
): Promise<OrgEnvironmentVariable> {
|
||||
const variableToFind = await this.orgEnvironmentVariablesRepository.findOne({
|
||||
where: {
|
||||
variableName: environmentVariableDto.variable_name,
|
||||
variableType: environmentVariableDto.variable_type,
|
||||
},
|
||||
});
|
||||
|
||||
if (variableToFind) {
|
||||
throw new ConflictException(
|
||||
`Variable name already exists in ${environmentVariableDto.variable_type ?? 'environment'} variables`
|
||||
);
|
||||
}
|
||||
|
||||
const encrypted = environmentVariableDto.variable_type === 'server' ? true : environmentVariableDto.encrypted;
|
||||
let value: string;
|
||||
if (encrypted && environmentVariableDto.value) {
|
||||
value = await this.encryptSecret(currentUser.organizationId, environmentVariableDto.value);
|
||||
} else {
|
||||
value = environmentVariableDto.value;
|
||||
}
|
||||
return await this.orgEnvironmentVariablesRepository.save(
|
||||
this.orgEnvironmentVariablesRepository.create({
|
||||
variableName: environmentVariableDto.variable_name,
|
||||
value,
|
||||
variableType: environmentVariableDto.variable_type,
|
||||
encrypted,
|
||||
organizationId: currentUser.organizationId,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async fetch(organizationId: string, variableId: string) {
|
||||
return await this.orgEnvironmentVariablesRepository.findOne({
|
||||
where: {
|
||||
organizationId,
|
||||
id: variableId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async update(organizationId: string, variableId: string, params: UpdateEnvironmentVariableDto) {
|
||||
const { variable_name } = params;
|
||||
let value = params.value;
|
||||
const variable = await this.fetch(organizationId, variableId);
|
||||
|
||||
if (variable_name) {
|
||||
const variableToFind = await this.orgEnvironmentVariablesRepository.findOne({
|
||||
where: {
|
||||
variableName: variable_name,
|
||||
variableType: variable.variableType,
|
||||
},
|
||||
});
|
||||
|
||||
if (variableToFind && variableToFind.id !== variableId) {
|
||||
throw new ConflictException(`Variable name already exists in ${variable.variableType} variables`);
|
||||
}
|
||||
}
|
||||
|
||||
if (variable.encrypted && value) {
|
||||
value = await this.encryptSecret(organizationId, value);
|
||||
}
|
||||
|
||||
const updateableParams = {
|
||||
variableName: variable_name,
|
||||
value,
|
||||
};
|
||||
|
||||
// removing keys with undefined values
|
||||
cleanObject(updateableParams);
|
||||
|
||||
return await this.orgEnvironmentVariablesRepository.update({ organizationId, id: variableId }, updateableParams);
|
||||
}
|
||||
|
||||
async delete(organizationId: string, variableId: string) {
|
||||
return await this.orgEnvironmentVariablesRepository.delete({ organizationId, id: variableId });
|
||||
}
|
||||
|
||||
private async encryptSecret(workspaceId: string, value: string) {
|
||||
return await this.encryptionService.encryptColumnValue('org_environment_variables', workspaceId, value);
|
||||
}
|
||||
|
||||
private async decryptSecret(workspaceId: string, value: string) {
|
||||
return await this.encryptionService.decryptColumnValue('org_environment_variables', workspaceId, value);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,254 +0,0 @@
|
|||
import { Injectable, OnModuleInit, OnApplicationShutdown } from '@nestjs/common';
|
||||
import { NativeConnection, Worker } from '@temporalio/worker';
|
||||
import { Connection, Client, ScheduleOptions, ScheduleOverlapPolicy } from '@temporalio/client';
|
||||
import * as moment from 'moment';
|
||||
import { CreateWorkflowExecutionDto } from '@dto/create-workflow-execution.dto';
|
||||
import { WorkflowSchedule } from '@entities/workflow_schedule.entity';
|
||||
import { isValidCron } from 'cron-validator';
|
||||
|
||||
@Injectable()
|
||||
export class TemporalService implements OnModuleInit, OnApplicationShutdown {
|
||||
client: Client;
|
||||
temporalConnection: Connection = undefined;
|
||||
workerNativeConnection: NativeConnection = undefined;
|
||||
worker: Worker = undefined;
|
||||
|
||||
async onModuleInit() {
|
||||
this.#connectToTemporal();
|
||||
}
|
||||
|
||||
async onApplicationShutdown() {
|
||||
await this.#closeConnection();
|
||||
await this.#closeWokerNativeConnection();
|
||||
}
|
||||
|
||||
async isTemporalConnected(): Promise<boolean> {
|
||||
try {
|
||||
await this.temporalConnection.healthService.check({});
|
||||
return true;
|
||||
} catch (exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async runWorker() {
|
||||
try {
|
||||
console.log(`\x1b[1;33m[INFO] Starting worker connection to Temporal\x1b[0m`);
|
||||
this.workerNativeConnection = await NativeConnection.connect({
|
||||
address: process.env.TEMPORAL_SERVER_ADDRESS,
|
||||
});
|
||||
this.worker = await Worker.create({
|
||||
connection: this.workerNativeConnection,
|
||||
namespace: process.env?.TOOLJET_WORKFLOWS_TEMPORAL_NAMESPACE ?? 'default',
|
||||
taskQueue: process.env?.TEMPORAL_TASK_QUEUE_NAME_FOR_WORKFLOWS,
|
||||
workflowsPath: require.resolve('../temporal/workflows'),
|
||||
activities: require('../temporal/activities'),
|
||||
});
|
||||
|
||||
console.log(`\x1b[1;33m[INFO] Worker connection to Temporal established.\x1b[0m`);
|
||||
await this.worker.run();
|
||||
} catch (error) {
|
||||
console.log(`\x1b[1;31m[ERROR] Temporal server could not be reached, exiting.\x1b[0m Reason: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async createScheduleInTemporal(
|
||||
workflowScheduleId: string,
|
||||
settings: any,
|
||||
schedule: WorkflowSchedule,
|
||||
environmentId,
|
||||
timezone,
|
||||
userId,
|
||||
paused = true
|
||||
) {
|
||||
const workflowExecution = new CreateWorkflowExecutionDto();
|
||||
workflowExecution.executeUsing = 'version';
|
||||
workflowExecution.appVersionId = schedule.workflowId;
|
||||
workflowExecution.environmentId = environmentId;
|
||||
|
||||
workflowExecution.params = {};
|
||||
workflowExecution.userId = userId;
|
||||
workflowExecution.app = schedule.workflow.appId;
|
||||
|
||||
const interval: string =
|
||||
schedule.type === 'cron'
|
||||
? `${settings.minute} ${settings.hours} ${settings.dayOfMonth} ${settings.month} ${settings.dayOfWeek}`
|
||||
: this.#convertWorkflowScheduleSettingsToCronString(settings);
|
||||
|
||||
if (isValidCron(interval, { seconds: false }) == false) {
|
||||
throw Error('Invalid interval configuration ' + interval);
|
||||
}
|
||||
|
||||
const scheduleOptions: ScheduleOptions = {
|
||||
scheduleId: workflowScheduleId,
|
||||
action: {
|
||||
taskQueue: process.env?.TEMPORAL_TASK_QUEUE_NAME_FOR_WORKFLOWS ?? 'tooljet-workflows',
|
||||
type: 'startWorkflow',
|
||||
workflowType: 'execute',
|
||||
workflowId: `schedule-${workflowScheduleId}`,
|
||||
args: [JSON.parse(JSON.stringify(workflowExecution))],
|
||||
retry: {
|
||||
maximumAttempts: 1,
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
cronExpressions: [interval],
|
||||
timezone,
|
||||
},
|
||||
policies: {
|
||||
overlap: ScheduleOverlapPolicy.SKIP,
|
||||
},
|
||||
state: {
|
||||
paused,
|
||||
},
|
||||
};
|
||||
|
||||
await this.client.schedule.create(scheduleOptions);
|
||||
}
|
||||
|
||||
async setScheduleState(scheduleId: string, paused: boolean) {
|
||||
const handle = await this.client.schedule.getHandle(scheduleId);
|
||||
|
||||
if (paused) await handle.pause('Paused from ToolJet');
|
||||
else await handle.unpause('Unpaused from ToolJet');
|
||||
}
|
||||
|
||||
async removeSchedule(scheduleId: string) {
|
||||
const handle = await this.client.schedule.getHandle(scheduleId);
|
||||
|
||||
await handle.delete();
|
||||
}
|
||||
|
||||
async updateSchedule(updatedSchedule: WorkflowSchedule, settings: any, timezone, existingSchedule, userId) {
|
||||
const handle = this.client.schedule.getHandle(updatedSchedule.id);
|
||||
|
||||
await handle.delete();
|
||||
|
||||
try {
|
||||
await this.createScheduleInTemporal(
|
||||
updatedSchedule.id,
|
||||
updatedSchedule.details,
|
||||
updatedSchedule,
|
||||
updatedSchedule.environmentId,
|
||||
timezone,
|
||||
userId,
|
||||
!existingSchedule.active
|
||||
);
|
||||
} catch (error) {
|
||||
console.log({ error });
|
||||
await this.createScheduleInTemporal(
|
||||
existingSchedule.id,
|
||||
existingSchedule.details,
|
||||
existingSchedule,
|
||||
existingSchedule.environmentId,
|
||||
existingSchedule.timezone,
|
||||
userId,
|
||||
!existingSchedule.active
|
||||
);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
#convertWorkflowScheduleSettingsToCronString(settings: any): string {
|
||||
switch (settings.frequency) {
|
||||
case 'minute': {
|
||||
return '* * * * *';
|
||||
}
|
||||
|
||||
case 'hour': {
|
||||
const { minutes } = settings;
|
||||
return `${minutes} * * * *`;
|
||||
}
|
||||
|
||||
case 'day': {
|
||||
const { hour } = settings;
|
||||
const hourOfTheDay = this.#convertToHourOffset(hour);
|
||||
return `0 ${hourOfTheDay} * * *`;
|
||||
}
|
||||
|
||||
case 'week': {
|
||||
const { day, hour } = settings;
|
||||
|
||||
const dayOfTheWeek = moment().day(day).day();
|
||||
const hourOfTheDay = this.#convertToHourOffset(hour);
|
||||
|
||||
return `0 ${hourOfTheDay} * * ${dayOfTheWeek}`;
|
||||
}
|
||||
|
||||
case 'month': {
|
||||
const { date: dayOfMonth, hour } = settings;
|
||||
|
||||
const hourOfTheDay = this.#convertToHourOffset(hour);
|
||||
|
||||
return `0 ${hourOfTheDay} ${dayOfMonth} * *`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#convertToHourOffset(timeString) {
|
||||
const time = moment(timeString, 'h:mm A');
|
||||
return time.hours() + time.minutes() / 60;
|
||||
}
|
||||
|
||||
#connectToTemporal() {
|
||||
if (!process.env.WORKER) {
|
||||
if (process.env.ENABLE_WORKFLOW_SCHEDULING === 'true') {
|
||||
console.log(`\x1b[1;33m[INFO] Connecting to Temporal server\x1b[0m`);
|
||||
Connection.connect({
|
||||
address: process.env.TEMPORAL_SERVER_ADDRESS,
|
||||
})
|
||||
.then((connection) => {
|
||||
this.temporalConnection = connection;
|
||||
this.client = new Client({
|
||||
connection,
|
||||
namespace: process.env?.TOOLJET_WORKFLOWS_TEMPORAL_NAMESPACE ?? 'default',
|
||||
});
|
||||
console.log(`\x1b[1;32m[INFO] Connected to Temporal server\x1b[0m`);
|
||||
})
|
||||
.catch((reason) => {
|
||||
console.log(
|
||||
`\x1b[1;33m [WARNING] Temporal server could not be reached, workflow schedules cannot be created, deleted or updated.\x1b[0m Reason: ${reason.message}`
|
||||
);
|
||||
});
|
||||
} else {
|
||||
console.log(
|
||||
`\x1b[1;33m[INFO] Not connecting to temporal as ENABLE_WORKFLOW_SCHEDULING is not set to 'true'. \x1b[0m`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async #closeConnection() {
|
||||
if (this.temporalConnection) {
|
||||
console.log(`\x1b[1;33m[INFO] Closing Temporal connection\x1b[0m`);
|
||||
this.temporalConnection
|
||||
.close()
|
||||
.then(() => {
|
||||
console.log(`\x1b[1;32m[INFO] Temporal connection closed\x1b[0m`);
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(`\x1b[1;31m[ERROR] Could not close Temporal connection\x1b[0m`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async #closeWokerNativeConnection() {
|
||||
if (process.env.WORKER) {
|
||||
console.log(`\x1b[1;33m[INFO] Closing worker connection to Temporal\x1b[0m`);
|
||||
try {
|
||||
await this.workerNativeConnection.close();
|
||||
console.log(`\x1b[1;32m[INFO] Worker connection to temporal closed\x1b[0m`);
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`\x1b[1;31m[ERROR] Worker connection to temporal failed to close. Reason: ${error.message} \x1b[0m`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shutDownWorker() {
|
||||
this.worker.shutdown();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Thread } from '../entities/thread.entity';
|
||||
import { CreateThreadDto, UpdateThreadDto } from '../dto/thread.dto';
|
||||
import { ThreadRepository } from '../repositories/thread.repository';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class ThreadService {
|
||||
constructor(
|
||||
@InjectRepository(ThreadRepository)
|
||||
private threadRepository: ThreadRepository,
|
||||
private readonly _dataSource: DataSource
|
||||
) {}
|
||||
|
||||
public async createThread(createThreadDto: CreateThreadDto, userId: string, orgId: string): Promise<Thread> {
|
||||
const thread: Thread = await this.threadRepository.createThread(createThreadDto, userId, orgId);
|
||||
return (await this.getThreads(thread.appId, thread.organizationId, thread.appVersionsId, thread.id))?.[0];
|
||||
}
|
||||
|
||||
public async getThreads(
|
||||
appId: string,
|
||||
organizationId: string,
|
||||
appVersionsId: string,
|
||||
threadId?: string
|
||||
): Promise<Thread[]> {
|
||||
const query = this._dataSource
|
||||
.createQueryBuilder(Thread, 'thread')
|
||||
.innerJoin('thread.user', 'user')
|
||||
.addSelect(['user.id', 'user.firstName', 'user.lastName'])
|
||||
.andWhere('thread.appId = :appId', {
|
||||
appId,
|
||||
})
|
||||
.andWhere('thread.organizationId = :organizationId', {
|
||||
organizationId,
|
||||
})
|
||||
.andWhere('thread.appVersionsId = :appVersionsId', {
|
||||
appVersionsId,
|
||||
});
|
||||
|
||||
if (threadId) {
|
||||
query.andWhere('thread.id = :threadId', {
|
||||
threadId,
|
||||
});
|
||||
}
|
||||
return await query.getMany();
|
||||
}
|
||||
|
||||
public async getOrganizationThreads(organizationId: string): Promise<Thread[]> {
|
||||
return await this.threadRepository.find({
|
||||
where: {
|
||||
organizationId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async getThread(threadId: string): Promise<Thread> {
|
||||
const foundThread = await this.threadRepository.findOne({ where: { id: threadId } });
|
||||
if (!foundThread) {
|
||||
throw new NotFoundException('Thread not found');
|
||||
}
|
||||
return foundThread;
|
||||
}
|
||||
|
||||
public async editThread(threadId: string, updateThreadDto: UpdateThreadDto): Promise<Thread> {
|
||||
const editedThread = await this.threadRepository.findOne({ where: { id: threadId } });
|
||||
if (!editedThread) {
|
||||
throw new NotFoundException('Thread not found');
|
||||
}
|
||||
return this.threadRepository.editThread(updateThreadDto, editedThread);
|
||||
}
|
||||
|
||||
public async deleteThread(threadId: string): Promise<void> {
|
||||
await this.threadRepository.delete(threadId);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { User } from '../entities/user.entity';
|
||||
|
||||
@Injectable()
|
||||
export class UserCommonService {
|
||||
constructor(
|
||||
@InjectRepository(User)
|
||||
private userRepository: Repository<User>
|
||||
) {}
|
||||
|
||||
@OnEvent('user.update')
|
||||
async handleUserUpdate(event: { userId: string; details: Partial<User> }) {
|
||||
const { userId, details } = event;
|
||||
return await this.userRepository.update(userId, details);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class WorkerService implements OnModuleInit {
|
||||
onModuleInit() {
|
||||
console.log(`The module has been initialized.`);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,882 +0,0 @@
|
|||
// import { CreateWorkflowExecutionDto } from '@dto/create-workflow-execution.dto';
|
||||
// import { Injectable } from '@nestjs/common';
|
||||
// import { InjectRepository } from '@nestjs/typeorm';
|
||||
// import { AppVersion } from 'src/entities/app_version.entity';
|
||||
// import { App } from 'src/entities/app.entity';
|
||||
// import { WorkflowExecution } from 'src/entities/workflow_execution.entity';
|
||||
// import { WorkflowExecutionNode } from 'src/entities/workflow_execution_node.entity';
|
||||
// import { WorkflowExecutionEdge } from 'src/entities/workflow_execution_edge.entity';
|
||||
// import { dbTransactionWrap } from 'src/helpers/database.helper';
|
||||
// import { EntityManager, Repository } from 'typeorm';
|
||||
// import { find } from 'lodash';
|
||||
// import { DataQueriesService } from '../modules/data-queries/service';
|
||||
// import { User } from 'src/entities/user.entity';
|
||||
// import { getQueryVariables, resolveCode } from '../../lib/utils';
|
||||
// import { Graph, alg } from '@dagrejs/graphlib';
|
||||
// import * as moment from 'moment';
|
||||
// import { stringify } from 'flatted';
|
||||
// import { OrganizationConstantsService } from '../modules/organization-constants/service';
|
||||
// // import { Organization } from 'src/entities/organization.entity';
|
||||
// import { Response } from 'express';
|
||||
// import { cloneDeep } from 'lodash';
|
||||
|
||||
// const STATIC_NODE_TYPE_TO_HANDLE_MAPPING = {
|
||||
// input: 'startTrigger',
|
||||
// output: 'responseNode',
|
||||
// 'if-condition': 'ifCondition',
|
||||
// };
|
||||
// import { OrganizationConstantType } from 'src/entities/organization_constants.entity';
|
||||
// import { LicenseTermsService } from '@modules/licensing/interfaces/IService';
|
||||
// import { LICENSE_FIELD, LICENSE_LIMIT } from '@modules/licensing/constants';
|
||||
|
||||
// @Injectable()
|
||||
// export class WorkflowExecutionsService {
|
||||
// constructor(
|
||||
// @InjectRepository(AppVersion)
|
||||
// private appVersionsRepository: Repository<AppVersion>,
|
||||
|
||||
// @InjectRepository(WorkflowExecution)
|
||||
// private workflowExecutionRepository: Repository<WorkflowExecution>,
|
||||
|
||||
// @InjectRepository(WorkflowExecutionEdge)
|
||||
// private workflowExecutionEdgeRepository: Repository<WorkflowExecutionEdge>,
|
||||
|
||||
// @InjectRepository(WorkflowExecutionNode)
|
||||
// private workflowExecutionNodeRepository: Repository<WorkflowExecutionNode>,
|
||||
|
||||
// @InjectRepository(User)
|
||||
// private userRepository: Repository<User>,
|
||||
|
||||
// private dataQueriesService: DataQueriesService,
|
||||
// private licenseTermsService: LicenseTermsService,
|
||||
// private organizationConstantsService: OrganizationConstantsService
|
||||
// ) {}
|
||||
|
||||
// workflowExecutionTimeout = parseInt(process.env.WORKFLOW_TIMEOUT_SECONDS);
|
||||
|
||||
// async create(createWorkflowExecutionDto: CreateWorkflowExecutionDto): Promise<WorkflowExecution> {
|
||||
// const workflowExecution = await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
// const appVersionId =
|
||||
// createWorkflowExecutionDto?.appVersionId ??
|
||||
// (
|
||||
// await manager.findOne(App, {
|
||||
// where: { id: createWorkflowExecutionDto.appId },
|
||||
// })
|
||||
// ).editingVersion.id;
|
||||
|
||||
// const workflowExecution = await manager.save(
|
||||
// WorkflowExecution,
|
||||
// manager.create(WorkflowExecution, {
|
||||
// appVersionId: appVersionId,
|
||||
// createdAt: new Date(),
|
||||
// updatedAt: new Date(),
|
||||
// })
|
||||
// );
|
||||
|
||||
// const appVersion = await this.appVersionsRepository.findOne({ where: { id: workflowExecution.appVersionId } });
|
||||
// const definition = appVersion.definition;
|
||||
|
||||
// const queryIdsOnDefinitionToActualQueryIdMapping = Object.fromEntries(
|
||||
// definition.nodes
|
||||
// .filter((node) => node.type === 'query')
|
||||
// .map((node) => node.data.idOnDefinition)
|
||||
// .map((idOnDefinition) => [
|
||||
// idOnDefinition,
|
||||
// find(appVersion.definition.queries, {
|
||||
// idOnDefinition,
|
||||
// }).id,
|
||||
// ])
|
||||
// );
|
||||
|
||||
// const queries = await this.dataQueriesService.findByIds(
|
||||
// Object.values(queryIdsOnDefinitionToActualQueryIdMapping)
|
||||
// );
|
||||
|
||||
// const nodes = [];
|
||||
// for (const nodeData of definition.nodes) {
|
||||
// nodeData.data.handle = this.computeNodeHandle(nodeData, queries, queryIdsOnDefinitionToActualQueryIdMapping);
|
||||
|
||||
// const node = await manager.save(
|
||||
// WorkflowExecutionNode,
|
||||
// manager.create(WorkflowExecutionNode, {
|
||||
// type: nodeData.type,
|
||||
// workflowExecutionId: workflowExecution.id,
|
||||
// idOnWorkflowDefinition: nodeData.id,
|
||||
// definition: nodeData.data,
|
||||
// createdAt: new Date(),
|
||||
// updatedAt: new Date(),
|
||||
// })
|
||||
// );
|
||||
|
||||
// nodes.push(node);
|
||||
// }
|
||||
|
||||
// const startNode = find(nodes, (node) => node.definition.nodeType === 'start');
|
||||
// workflowExecution.startNodeId = startNode.id;
|
||||
|
||||
// await manager.update(WorkflowExecution, workflowExecution.id, { startNode });
|
||||
|
||||
// const edges = [];
|
||||
// for (const edgeData of definition.edges) {
|
||||
// // const sourceNode = find(nodes, (node) => node.idOnWorkflowDefinition === edgeData.source);
|
||||
// // const targetNode = find(nodes, (node) => node.idOnWorkflowDefinition === edgeData.target);
|
||||
|
||||
// const edge = await manager.save(
|
||||
// WorkflowExecutionEdge,
|
||||
// manager.create(WorkflowExecutionEdge, {
|
||||
// workflowExecutionId: workflowExecution.id,
|
||||
// idOnWorkflowDefinition: edgeData.id,
|
||||
// sourceWorkflowExecutionNodeId: find(nodes, (node) => node.idOnWorkflowDefinition === edgeData.source).id,
|
||||
// targetWorkflowExecutionNodeId: find(nodes, (node) => node.idOnWorkflowDefinition === edgeData.target).id,
|
||||
// sourceHandle: edgeData.sourceHandle,
|
||||
// createdAt: new Date(),
|
||||
// updatedAt: new Date(),
|
||||
// })
|
||||
// );
|
||||
|
||||
// edges.push(edge);
|
||||
// }
|
||||
|
||||
// return workflowExecution;
|
||||
// });
|
||||
|
||||
// return workflowExecution;
|
||||
// }
|
||||
|
||||
// async getStatus(workflowExecutionId: string) {
|
||||
// const workflowExecution = await this.workflowExecutionRepository.findOne({
|
||||
// where: { id: workflowExecutionId },
|
||||
// });
|
||||
// const workflowExecutionNodes = await this.workflowExecutionNodeRepository.find({
|
||||
// where: {
|
||||
// workflowExecutionId: workflowExecution.id,
|
||||
// },
|
||||
// });
|
||||
|
||||
// const nodes = workflowExecutionNodes.map((node) => ({
|
||||
// id: node.id,
|
||||
// idOnDefinition: node.idOnWorkflowDefinition,
|
||||
// executed: node.executed,
|
||||
// result: node.result,
|
||||
// }));
|
||||
|
||||
// return {
|
||||
// logs: workflowExecution.logs,
|
||||
// status: workflowExecution.executed,
|
||||
// nodes,
|
||||
// };
|
||||
// }
|
||||
|
||||
// async getWorkflowExecution(workflowExecutionId: string) {
|
||||
// const workflowExecution = await this.workflowExecutionRepository.findOne({
|
||||
// where: { id: workflowExecutionId },
|
||||
// });
|
||||
// const nodes = await this.workflowExecutionNodeRepository.find({
|
||||
// where: { id: workflowExecutionId },
|
||||
// });
|
||||
// const appVersion = await this.appVersionsRepository.findOne({
|
||||
// where: { id: workflowExecution.appVersionId },
|
||||
// });
|
||||
// const queries = await this.dataQueriesService.all({ app_version_id: appVersion.id });
|
||||
|
||||
// const nodesWithHandles = nodes.map((node) => {
|
||||
// const queryId = find(appVersion.definition.queries, {
|
||||
// idOnDefinition: node.definition.idOnDefinition,
|
||||
// })?.id;
|
||||
|
||||
// const query = find(queries, { id: queryId });
|
||||
// if (query) {
|
||||
// node.definition.handle = query.name;
|
||||
// } else {
|
||||
// node.definition.handle = STATIC_NODE_TYPE_TO_HANDLE_MAPPING[node.type];
|
||||
// }
|
||||
|
||||
// return node;
|
||||
// });
|
||||
|
||||
// workflowExecution.nodes = nodesWithHandles;
|
||||
|
||||
// return workflowExecution;
|
||||
// }
|
||||
|
||||
// async listWorkflowExecutions(appVersionId: string) {
|
||||
// const workflowExecutions = await this.workflowExecutionRepository.find({
|
||||
// where: {
|
||||
// appVersionId,
|
||||
// },
|
||||
// order: {
|
||||
// createdAt: 'DESC',
|
||||
// },
|
||||
// relations: ['nodes'],
|
||||
// take: 10,
|
||||
// });
|
||||
|
||||
// return workflowExecutions;
|
||||
// }
|
||||
|
||||
// async execute(
|
||||
// workflowExecution: WorkflowExecution,
|
||||
// params: object = {},
|
||||
// envId = '',
|
||||
// response: Response
|
||||
// ): Promise<object> {
|
||||
// const organization: any = await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
// return manager
|
||||
// .createQueryBuilder('organizations', 'organization')
|
||||
// .innerJoin('apps', 'app', 'app.organization_id = organization.id')
|
||||
// .innerJoin('app_versions', 'av', 'av.app_id = app.id')
|
||||
// .innerJoin('workflow_executions', 'we', 'we.app_version_id = av.id')
|
||||
// .where('we.id = :workflowExecutionId', { workflowExecutionId: workflowExecution.id })
|
||||
// .getOne();
|
||||
// });
|
||||
|
||||
// const constants = await this.organizationConstantsService.getConstantsForEnvironment(
|
||||
// organization.id,
|
||||
// envId,
|
||||
// OrganizationConstantType.GLOBAL
|
||||
// );
|
||||
// const constantsObject = Object.fromEntries(constants.map((constant) => [constant.name, constant.value]));
|
||||
// const appVersion = await this.appVersionsRepository.findOne({
|
||||
// where: {
|
||||
// id: workflowExecution.appVersionId,
|
||||
// },
|
||||
// relations: ['app'],
|
||||
// });
|
||||
|
||||
// this.workflowExecutionTimeout =
|
||||
// (await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.WORKFLOWS))?.execution_timeout ??
|
||||
// parseInt(process.env.WORKFLOW_TIMEOUT_SECONDS);
|
||||
|
||||
// if (envId) appVersion.currentEnvironmentId = envId;
|
||||
|
||||
// workflowExecution = await this.workflowExecutionRepository.findOne({
|
||||
// where: {
|
||||
// id: workflowExecution.id,
|
||||
// },
|
||||
// relations: ['startNode', 'user', 'nodes', 'edges'],
|
||||
// });
|
||||
|
||||
// let finalResult = {};
|
||||
// const logs = [];
|
||||
// const queue = [];
|
||||
|
||||
// const addLog = (message: string, status = 'normal') =>
|
||||
// logs.push({ createdAt: moment().utc().format('YYYY-MM-DD HH:mm:ss.SSS'), message, nodeId: undefined, status });
|
||||
|
||||
// // FIXME: isMaintenanceOn - Column is used to check whether workflow is enabled or not.
|
||||
// if (appVersion.app.isMaintenanceOn) {
|
||||
// queue.push(
|
||||
// ...this.computeNodesToBeExecuted(workflowExecution.startNode, workflowExecution.nodes, workflowExecution.edges)
|
||||
// );
|
||||
// } else {
|
||||
// addLog('Workflow is disabled', 'failure');
|
||||
// }
|
||||
|
||||
// let executionFailed = false;
|
||||
// while (queue.length != 0 && executionFailed === false) {
|
||||
// const nodeToBeExecuted = queue.shift();
|
||||
// const currentNode = await this.workflowExecutionNodeRepository.findOne({ where: { id: nodeToBeExecuted.id } });
|
||||
|
||||
// const addLog = (message: string, queryName: string = undefined, status = 'normal') =>
|
||||
// logs.push({
|
||||
// createdAt: moment().utc().format('YYYY-MM-DD HH:mm:ss.SSS'),
|
||||
// message,
|
||||
// nodeId: currentNode.idOnWorkflowDefinition,
|
||||
// kind: currentNode?.definition?.kind ?? STATIC_NODE_TYPE_TO_HANDLE_MAPPING[currentNode.type],
|
||||
// handle: queryName ? queryName : STATIC_NODE_TYPE_TO_HANDLE_MAPPING[currentNode.type],
|
||||
// status,
|
||||
// });
|
||||
|
||||
// const currentTime = moment();
|
||||
// const timeTaken = currentTime.diff(moment(workflowExecution.createdAt));
|
||||
// if (timeTaken / 1000 > this.workflowExecutionTimeout) {
|
||||
// addLog('Execution stopped due to timeout', undefined, 'failure');
|
||||
// break;
|
||||
// }
|
||||
|
||||
// let { state } = await this.getStateAndPreviousNodesExecutionCompletionStatus(currentNode);
|
||||
|
||||
// state = { constants: constantsObject, ...state };
|
||||
|
||||
// // eslint-disable-next-line no-empty
|
||||
// if (currentNode.executed) {
|
||||
// } else {
|
||||
// switch (currentNode.type) {
|
||||
// case 'input': {
|
||||
// const { result } = await this.processStartNode(currentNode, params, addLog);
|
||||
|
||||
// if (result?.status === 'failed') executionFailed = true;
|
||||
// break;
|
||||
// }
|
||||
|
||||
// case 'query': {
|
||||
// const { result } = await this.processQueryNode(
|
||||
// currentNode,
|
||||
// workflowExecution,
|
||||
// appVersion,
|
||||
// state,
|
||||
// addLog,
|
||||
// response,
|
||||
// queue
|
||||
// );
|
||||
|
||||
// if (result?.status === 'failed' && !currentNode.definition.errorHandler) executionFailed = true;
|
||||
// break;
|
||||
// }
|
||||
|
||||
// case 'if-condition': {
|
||||
// const { result } = await this.processIfConditionNode(
|
||||
// currentNode,
|
||||
// workflowExecution,
|
||||
// appVersion,
|
||||
// state,
|
||||
// addLog,
|
||||
// response,
|
||||
// queue
|
||||
// );
|
||||
|
||||
// if (result?.status === 'failed') executionFailed = true;
|
||||
// break;
|
||||
// }
|
||||
|
||||
// case 'output': {
|
||||
// const { result } = await this.processResponseNode(
|
||||
// currentNode,
|
||||
// workflowExecution,
|
||||
// appVersion,
|
||||
// state,
|
||||
// addLog,
|
||||
// response,
|
||||
// queue
|
||||
// );
|
||||
|
||||
// finalResult = result?.data ?? {};
|
||||
|
||||
// if (result?.status === 'failed') executionFailed = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// await this.saveExecutionStatus({ workflowExecution, logs, executionFailed });
|
||||
// await this.markWorkflowAsExecuted(workflowExecution);
|
||||
// await this.saveWorkflowLogs(workflowExecution, logs);
|
||||
|
||||
// return finalResult;
|
||||
// }
|
||||
|
||||
// async processStartNode(node: WorkflowExecutionNode, params: object, addLog) {
|
||||
// addLog('Execution started', undefined, 'success');
|
||||
// await this.completeNodeExecution(node, '', { startTrigger: { params } });
|
||||
|
||||
// const result: any = { status: 'ok' };
|
||||
|
||||
// return { result };
|
||||
// }
|
||||
|
||||
// async processQueryNode(
|
||||
// node: WorkflowExecutionNode,
|
||||
// execution: WorkflowExecution,
|
||||
// appVersion: AppVersion,
|
||||
// state: object,
|
||||
// basicAddLog: any,
|
||||
// response: Response,
|
||||
// queue: WorkflowExecutionNode[]
|
||||
// ) {
|
||||
// const queryId = find(appVersion.definition.queries, {
|
||||
// idOnDefinition: node.definition.idOnDefinition,
|
||||
// }).id;
|
||||
|
||||
// const query = await this.dataQueriesService.findOne(queryId);
|
||||
|
||||
// const addLog = (message, status = 'normal') => basicAddLog(message, query.name, status);
|
||||
|
||||
// //* beta: workflow execution's environment is "development" by default
|
||||
// const currentEnvironmentId = appVersion.currentEnvironmentId;
|
||||
|
||||
// const user = await this.userRepository.findOne({
|
||||
// where: {
|
||||
// id: execution.executingUserId,
|
||||
// },
|
||||
// relations: ['organization'],
|
||||
// });
|
||||
// user.organizationId = user.organization.id;
|
||||
|
||||
// let result: any = {};
|
||||
// let handleToSkip = 'failure';
|
||||
|
||||
// try {
|
||||
// addLog(`Started execution`);
|
||||
// if (node.definition.looped) {
|
||||
// const iterationValues = resolveCode({ code: node.definition?.iterationValuesCode, state, addLog });
|
||||
|
||||
// if (!Array.isArray(iterationValues)) throw new Error('Loop array did not resolve into an array');
|
||||
|
||||
// let index = 0;
|
||||
// result = [];
|
||||
// for (const value of iterationValues) {
|
||||
// const currentTime = moment();
|
||||
// const timeTaken = currentTime.diff(moment(execution.createdAt));
|
||||
// if (timeTaken / 1000 > this.workflowExecutionTimeout) {
|
||||
// throw new Error('Execution stopped due to timeout');
|
||||
// }
|
||||
// const modifiedState = { ...state, value, index };
|
||||
// const options = getQueryVariables(query.options, modifiedState, addLog);
|
||||
// result.push(
|
||||
// query.kind === 'runjs'
|
||||
// ? resolveCode({ code: query.options?.code, state: modifiedState, addLog })
|
||||
// : (
|
||||
// await this.dataQueriesService.runQuery(
|
||||
// user,
|
||||
// cloneDeep(query),
|
||||
// options,
|
||||
// response,
|
||||
// currentEnvironmentId
|
||||
// )
|
||||
// )['data']
|
||||
// );
|
||||
|
||||
// index++;
|
||||
// }
|
||||
// } else {
|
||||
// const options = getQueryVariables(query.options, state, addLog);
|
||||
// result =
|
||||
// query.kind === 'runjs'
|
||||
// ? resolveCode({ code: query.options?.code, state, addLog })
|
||||
// : (await this.dataQueriesService.runQuery(user, query, options, response, currentEnvironmentId))['data'];
|
||||
// }
|
||||
|
||||
// const decoratedResult = { status: 'ok', data: result };
|
||||
|
||||
// const newState = {
|
||||
// ...state,
|
||||
// [query.name]: decoratedResult,
|
||||
// };
|
||||
|
||||
// addLog(`Execution succeeded`, 'success');
|
||||
// await this.completeNodeExecution(node, stringify(decoratedResult), newState);
|
||||
// } catch (exception) {
|
||||
// // if (exception instanceof TypeError) throw exception;
|
||||
// addLog(`Execution failed: ${exception.message}`, 'failure');
|
||||
// result = { status: 'failed', exception };
|
||||
|
||||
// const newState = {
|
||||
// ...state,
|
||||
// [query.name]: result,
|
||||
// };
|
||||
|
||||
// handleToSkip = 'success';
|
||||
|
||||
// await this.completeNodeExecution(node, stringify(result), newState);
|
||||
// }
|
||||
|
||||
// execution.edges
|
||||
// .filter((edge) => edge.sourceWorkflowExecutionNodeId === node.id && edge.sourceHandle == handleToSkip)
|
||||
// .forEach((edge) => (edge.skipped = true));
|
||||
|
||||
// queue.length = 0;
|
||||
// queue.push(...this.computeNodesToBeExecuted(execution.startNode, execution.nodes, execution.edges));
|
||||
|
||||
// return { result };
|
||||
// }
|
||||
|
||||
// async processIfConditionNode(
|
||||
// currentNode: WorkflowExecutionNode,
|
||||
// workflowExecution: WorkflowExecution,
|
||||
// appVersion: AppVersion,
|
||||
// state: object,
|
||||
// addLog: any,
|
||||
// response: Response,
|
||||
// queue: WorkflowExecutionNode[]
|
||||
// ) {
|
||||
// const code = currentNode.definition?.code ?? '';
|
||||
// let sourceHandleToBeSkipped = 'false';
|
||||
// let result: any = {};
|
||||
|
||||
// try {
|
||||
// result = { status: 'ok', data: resolveCode({ code, state, isIfCondition: true, addLog }) };
|
||||
// addLog('If condition evaluated to ' + result);
|
||||
// sourceHandleToBeSkipped = result.data ? 'false' : 'true';
|
||||
|
||||
// await this.completeNodeExecution(currentNode, stringify(result), { ...state });
|
||||
// } catch (exception) {
|
||||
// addLog(`Code within if condition failed: ${exception.message}`);
|
||||
// result = { status: 'failed' };
|
||||
// await this.completeNodeExecution(currentNode, stringify(result), { ...state });
|
||||
// }
|
||||
|
||||
// workflowExecution.edges
|
||||
// .filter(
|
||||
// (edge) => edge.sourceWorkflowExecutionNodeId === currentNode.id && edge.sourceHandle === sourceHandleToBeSkipped
|
||||
// )
|
||||
// .forEach((edge) => (edge.skipped = true));
|
||||
|
||||
// queue.length = 0;
|
||||
// queue.push(
|
||||
// ...this.computeNodesToBeExecuted(workflowExecution.startNode, workflowExecution.nodes, workflowExecution.edges)
|
||||
// );
|
||||
|
||||
// return { result };
|
||||
// }
|
||||
|
||||
// async processResponseNode(
|
||||
// currentNode: WorkflowExecutionNode,
|
||||
// workflowExecution: WorkflowExecution,
|
||||
// appVersion: AppVersion,
|
||||
// state: object,
|
||||
// addLog: any,
|
||||
// response: Response,
|
||||
// queue: WorkflowExecutionNode[]
|
||||
// ) {
|
||||
// const code = currentNode.definition?.code ?? '';
|
||||
// let result: any = {};
|
||||
|
||||
// try {
|
||||
// result = {
|
||||
// data: resolveCode({
|
||||
// code,
|
||||
// state,
|
||||
// isIfCondition: false,
|
||||
// addLog,
|
||||
// }),
|
||||
// status: 'ok',
|
||||
// };
|
||||
// await this.completeNodeExecution(currentNode, stringify(result), state);
|
||||
// } catch (exception) {
|
||||
// result = { status: 'failed' };
|
||||
// await this.completeNodeExecution(currentNode, stringify(result), state);
|
||||
// }
|
||||
|
||||
// return { result };
|
||||
// }
|
||||
|
||||
// computeNodesToBeExecuted(
|
||||
// currentNode: WorkflowExecutionNode,
|
||||
// nodes: WorkflowExecutionNode[],
|
||||
// edges: WorkflowExecutionEdge[]
|
||||
// ) {
|
||||
// const nodeIds = nodes.map((node) => node.id);
|
||||
// const dag = new Graph({ directed: true });
|
||||
|
||||
// nodeIds.forEach((nodeId) => dag.setNode(nodeId));
|
||||
|
||||
// edges.forEach((edge) => {
|
||||
// if (!edge.skipped) {
|
||||
// dag.setEdge(edge.sourceWorkflowExecutionNodeId, edge.targetWorkflowExecutionNodeId);
|
||||
// }
|
||||
// });
|
||||
|
||||
// const sortedNodeIds = alg.topsort(dag);
|
||||
// const traversedNodeIds = alg.postorder(dag, [currentNode.id]);
|
||||
|
||||
// const orderedNodes = sortedNodeIds
|
||||
// .filter((nodeId) => traversedNodeIds.includes(nodeId))
|
||||
// .map((id) => {
|
||||
// return find(nodes, { id });
|
||||
// });
|
||||
// return orderedNodes;
|
||||
// }
|
||||
|
||||
// async completeNodeExecution(node: WorkflowExecutionNode, result: any, state: object) {
|
||||
// await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
// await manager.update(WorkflowExecutionNode, node.id, { executed: true, result, state });
|
||||
// });
|
||||
// }
|
||||
|
||||
// async markWorkflowAsExecuted(workflow: WorkflowExecution) {
|
||||
// await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
// await manager.update(WorkflowExecution, workflow.id, { executed: true });
|
||||
// });
|
||||
// }
|
||||
|
||||
// async saveWorkflowLogs(workflow: WorkflowExecution, logs: any[]) {
|
||||
// await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
// await manager.update(WorkflowExecution, workflow.id, { logs });
|
||||
// });
|
||||
// }
|
||||
|
||||
// async saveExecutionStatus({ workflowExecution, logs, executionFailed }) {
|
||||
// await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
// const status = executionFailed ? 'failure' : 'success';
|
||||
// await manager.update(WorkflowExecution, workflowExecution.id, { logs, status });
|
||||
// });
|
||||
// }
|
||||
|
||||
// async getStateAndPreviousNodesExecutionCompletionStatus(node: WorkflowExecutionNode) {
|
||||
// const incomingEdges = await this.workflowExecutionEdgeRepository.find({
|
||||
// where: {
|
||||
// targetWorkflowExecutionNodeId: node.id,
|
||||
// },
|
||||
// relations: ['sourceWorkflowExecutionNode'],
|
||||
// });
|
||||
|
||||
// const incomingNodes = await Promise.all(incomingEdges.map((edge) => edge.sourceWorkflowExecutionNode));
|
||||
|
||||
// const previousNodesExecutionCompletionStatus = !incomingNodes.map((node) => node.executed).includes(false);
|
||||
|
||||
// const state = incomingNodes.reduce((existingState, node) => {
|
||||
// const nodeState = node.state ?? {};
|
||||
// return { ...existingState, ...nodeState };
|
||||
// }, {});
|
||||
|
||||
// return { state, previousNodesExecutionCompletionStatus };
|
||||
// }
|
||||
|
||||
// async forwardNodes(
|
||||
// startNode: WorkflowExecutionNode,
|
||||
// sourceHandle: string = undefined
|
||||
// ): Promise<WorkflowExecutionNode[]> {
|
||||
// const forwardEdges = await this.workflowExecutionEdgeRepository.find({
|
||||
// where: {
|
||||
// sourceWorkflowExecutionNode: startNode,
|
||||
// ...(sourceHandle ? { sourceHandle } : {}),
|
||||
// },
|
||||
// });
|
||||
|
||||
// const forwardNodeIds = forwardEdges.map((edge) => edge.targetWorkflowExecutionNodeId);
|
||||
|
||||
// const forwardNodes = Promise.all(
|
||||
// forwardNodeIds.map((id) =>
|
||||
// this.workflowExecutionNodeRepository.findOne({
|
||||
// where: {
|
||||
// id,
|
||||
// },
|
||||
// })
|
||||
// )
|
||||
// );
|
||||
|
||||
// return forwardNodes;
|
||||
// }
|
||||
|
||||
// async incomingNodes(startNode: WorkflowExecutionNode): Promise<WorkflowExecutionNode[]> {
|
||||
// const incomingEdges = await this.workflowExecutionEdgeRepository.find({
|
||||
// where: {
|
||||
// targetWorkflowExecutionNode: startNode,
|
||||
// },
|
||||
// });
|
||||
|
||||
// const incomingNodeIds = incomingEdges.map((edge) => edge.sourceWorkflowExecutionNodeId);
|
||||
|
||||
// const receivedNodes = Promise.all(
|
||||
// incomingNodeIds.map((id) =>
|
||||
// this.workflowExecutionNodeRepository.findOne({
|
||||
// where: {
|
||||
// id,
|
||||
// },
|
||||
// })
|
||||
// )
|
||||
// );
|
||||
|
||||
// return receivedNodes;
|
||||
// }
|
||||
|
||||
// async previewQueryNode(
|
||||
// queryId: string,
|
||||
// nodeId: string,
|
||||
// state: object,
|
||||
// appVersion: AppVersion,
|
||||
// user: User,
|
||||
// response: Response
|
||||
// ): Promise<any> {
|
||||
// const query = await this.dataQueriesService.findOne(queryId);
|
||||
// const node = find(appVersion.definition.nodes, { id: nodeId });
|
||||
// //* beta: workflow execution's environment is "development" by default
|
||||
// const currentEnvironmentId = appVersion.currentEnvironmentId;
|
||||
|
||||
// const organization: any = await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
// return manager
|
||||
// .createQueryBuilder('organizations', 'organization')
|
||||
// .innerJoin('apps', 'app', 'app.organization_id = organization.id')
|
||||
// .innerJoin('app_versions', 'av', 'av.app_id = app.id')
|
||||
// .where('av.id = :appVersionId', { appVersionId: appVersion.id })
|
||||
// .getOne();
|
||||
// });
|
||||
|
||||
// const constants = await this.organizationConstantsService.getConstantsForEnvironment(
|
||||
// organization.id,
|
||||
// currentEnvironmentId,
|
||||
// OrganizationConstantType.GLOBAL
|
||||
// );
|
||||
|
||||
// const constantsObject = Object.fromEntries(constants.map((constant) => [constant.name, constant.value]));
|
||||
|
||||
// state = { ...state, constants: constantsObject };
|
||||
|
||||
// // const user = await this.userRepository.findOne(execution.executingUserId, {
|
||||
// // relations: ['organization'],
|
||||
// // });
|
||||
// // user.organizationId = user.organization.id;
|
||||
// try {
|
||||
// void getQueryVariables(query.options, state);
|
||||
// } catch (e) {
|
||||
// console.log({ e });
|
||||
// }
|
||||
|
||||
// const startingTime = moment();
|
||||
// let result: any;
|
||||
// const addLog = () => {};
|
||||
// try {
|
||||
// if (node.data.looped) {
|
||||
// const iterationValues = resolveCode({ code: node.data?.iterationValuesCode, state, addLog });
|
||||
|
||||
// if (!Array.isArray(iterationValues)) throw new Error('Loop array did not resolve into an array');
|
||||
|
||||
// let index = 0;
|
||||
// result = [];
|
||||
|
||||
// for (const value of iterationValues) {
|
||||
// const currentTime = moment();
|
||||
// const timeTaken = currentTime.diff(startingTime);
|
||||
// if (timeTaken / 1000 > this.workflowExecutionTimeout) {
|
||||
// throw new Error('Execution stopped due to timeout');
|
||||
// }
|
||||
// const modifiedState = { ...state, value, index };
|
||||
// const options = getQueryVariables(query.options, modifiedState, addLog);
|
||||
// result.push(
|
||||
// query.kind === 'runjs'
|
||||
// ? resolveCode({ code: query.options?.code, state: modifiedState, addLog })
|
||||
// : (
|
||||
// await this.dataQueriesService.runQuery(
|
||||
// user,
|
||||
// cloneDeep(query),
|
||||
// options,
|
||||
// response,
|
||||
// currentEnvironmentId
|
||||
// )
|
||||
// )['data']
|
||||
// );
|
||||
|
||||
// index++;
|
||||
// }
|
||||
|
||||
// result = { status: 'ok', data: result };
|
||||
// } else {
|
||||
// const options = getQueryVariables(query.options, state, addLog);
|
||||
// result =
|
||||
// query.kind === 'runjs'
|
||||
// ? { status: 'ok', data: resolveCode({ code: query.options?.code, state, addLog }) }
|
||||
// : await this.dataQueriesService.runQuery(user, query, options, response, currentEnvironmentId);
|
||||
// }
|
||||
// } catch (exception) {
|
||||
// const result = { status: 'failed', exception };
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// computeNodeHandle(nodeData, queries, queryIdsOnDefinitionToActualQueryIdMapping): string {
|
||||
// switch (nodeData.type) {
|
||||
// case 'query': {
|
||||
// return find(queries, { id: queryIdsOnDefinitionToActualQueryIdMapping[nodeData.data.idOnDefinition] }).name;
|
||||
// }
|
||||
// default:
|
||||
// return STATIC_NODE_TYPE_TO_HANDLE_MAPPING[nodeData.type];
|
||||
// }
|
||||
// }
|
||||
|
||||
// async canExecuteWorkflow(organizationId: string): Promise<{ allowed: boolean; message: string }> {
|
||||
// if (!organizationId) {
|
||||
// return { allowed: false, message: 'WorkspaceId is missing' };
|
||||
// }
|
||||
|
||||
// const workflowsLimit = await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.WORKFLOWS);
|
||||
// if (!workflowsLimit?.workspace || !workflowsLimit?.instance) {
|
||||
// return { allowed: false, message: 'Workflow is not enabled in the license, contact admin' };
|
||||
// }
|
||||
|
||||
// return await dbTransactionWrap(async (manager) => {
|
||||
// const dailyWorkspaceCount = (
|
||||
// await manager.query(
|
||||
// `SELECT COUNT(*)
|
||||
// FROM apps a
|
||||
// INNER JOIN app_versions av on av.app_id = a.id
|
||||
// INNER JOIN workflow_executions we on we.app_version_id = av.id
|
||||
// WHERE a.organization_id = $1
|
||||
// AND extract (year from we.created_at) = extract (year from current_date)
|
||||
// AND extract (month from we.created_at) = extract (month from current_date)
|
||||
// AND DATE(we.created_at) = current_date`,
|
||||
// [organizationId]
|
||||
// )
|
||||
// )[0].count;
|
||||
|
||||
// if (
|
||||
// workflowsLimit.workspace.daily_executions !== LICENSE_LIMIT.UNLIMITED &&
|
||||
// dailyWorkspaceCount >= workflowsLimit.workspace.daily_executions
|
||||
// ) {
|
||||
// return {
|
||||
// allowed: false,
|
||||
// message: 'Maximum daily limit for workflow execution has been reached for this workspace',
|
||||
// };
|
||||
// }
|
||||
|
||||
// // Workspace Level - Monthly Limit
|
||||
// const monthlyWorkspaceCount = (
|
||||
// await manager.query(
|
||||
// `SELECT COUNT(*)
|
||||
// FROM apps a
|
||||
// INNER JOIN app_versions av on av.app_id = a.id
|
||||
// INNER JOIN workflow_executions we on we.app_version_id = av.id
|
||||
// WHERE a.organization_id = $1
|
||||
// AND extract (year from we.created_at) = extract (year from current_date)
|
||||
// AND extract (month from we.created_at) = extract (month from current_date)`,
|
||||
// [organizationId]
|
||||
// )
|
||||
// )[0].count;
|
||||
|
||||
// if (
|
||||
// workflowsLimit.workspace.monthly_executions !== LICENSE_LIMIT.UNLIMITED &&
|
||||
// monthlyWorkspaceCount >= workflowsLimit.workspace.monthly_executions
|
||||
// ) {
|
||||
// return {
|
||||
// allowed: false,
|
||||
// message: 'Maximum monthly limit for workflow execution has been reached for this workspace',
|
||||
// };
|
||||
// }
|
||||
|
||||
// // Instance Level - Daily Limit
|
||||
// const dailyInstanceCount = (
|
||||
// await manager.query(
|
||||
// `SELECT COUNT(*)
|
||||
// FROM apps a
|
||||
// INNER JOIN app_versions av on av.app_id = a.id
|
||||
// INNER JOIN workflow_executions we on we.app_version_id = av.id
|
||||
// WHERE extract (year from we.created_at) = extract (year from current_date)
|
||||
// AND extract (month from we.created_at) = extract (month from current_date)
|
||||
// AND DATE(we.created_at) = current_date`
|
||||
// )
|
||||
// )[0].count;
|
||||
|
||||
// if (
|
||||
// workflowsLimit.instance.daily_executions !== LICENSE_LIMIT.UNLIMITED &&
|
||||
// dailyInstanceCount >= workflowsLimit.instance.daily_executions
|
||||
// ) {
|
||||
// return { allowed: false, message: 'Maximum daily limit for workflow execution has been reached' };
|
||||
// }
|
||||
|
||||
// // Instance Level - Monthly Limit
|
||||
// const monthlyInstanceCount = (
|
||||
// await manager.query(
|
||||
// `SELECT COUNT(*)
|
||||
// FROM apps a
|
||||
// INNER JOIN app_versions av on av.app_id = a.id
|
||||
// INNER JOIN workflow_executions we on we.app_version_id = av.id
|
||||
// WHERE extract (year from we.created_at) = extract (year from current_date)
|
||||
// AND extract (month from we.created_at) = extract (month from current_date)`
|
||||
// )
|
||||
// )[0].count;
|
||||
|
||||
// if (
|
||||
// workflowsLimit.instance.monthly_executions !== LICENSE_LIMIT.UNLIMITED &&
|
||||
// monthlyInstanceCount >= workflowsLimit.instance.monthly_executions
|
||||
// ) {
|
||||
// return { allowed: false, message: 'Maximum monthly limit for workflow execution has been reached' };
|
||||
// }
|
||||
|
||||
// return { allowed: true, message: 'Workflow execution allowed' };
|
||||
// });
|
||||
// }
|
||||
// // Workspace Level - Daily Limit
|
||||
// }
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { WorkflowSchedule } from '../entities/workflow_schedule.entity';
|
||||
import { AppVersion } from '../entities/app_version.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowSchedulesService {
|
||||
constructor(
|
||||
@InjectRepository(WorkflowSchedule)
|
||||
private workflowSchedulesRepository: Repository<WorkflowSchedule>
|
||||
) {}
|
||||
|
||||
async create(createWorkflowScheduleDto: {
|
||||
workflowId: string;
|
||||
active: boolean;
|
||||
environmentId: string;
|
||||
type: string;
|
||||
timezone: string;
|
||||
details: any;
|
||||
}): Promise<WorkflowSchedule> {
|
||||
const { workflowId, active, environmentId, type, timezone, details } = createWorkflowScheduleDto;
|
||||
const scheduleOptions = this.workflowSchedulesRepository.create({
|
||||
workflow: { id: workflowId } as AppVersion,
|
||||
active,
|
||||
environmentId,
|
||||
type,
|
||||
timezone,
|
||||
details,
|
||||
});
|
||||
const workflowSchedule = await this.workflowSchedulesRepository.save(scheduleOptions);
|
||||
|
||||
return workflowSchedule;
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<WorkflowSchedule> {
|
||||
const workflowSchedule = await this.workflowSchedulesRepository.findOne({
|
||||
where: { id },
|
||||
relations: ['workflow'],
|
||||
});
|
||||
if (!workflowSchedule) {
|
||||
throw new NotFoundException(`WorkflowSchedule with ID ${id} not found`);
|
||||
}
|
||||
return workflowSchedule;
|
||||
}
|
||||
|
||||
async findAll(appVersionId: string): Promise<WorkflowSchedule[]> {
|
||||
return await this.workflowSchedulesRepository.find({
|
||||
where: { workflowId: appVersionId },
|
||||
});
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
updateWorkflowScheduleDto: Partial<{
|
||||
active: boolean;
|
||||
environmentId: string;
|
||||
type: string;
|
||||
timezone: string;
|
||||
details: any;
|
||||
}>
|
||||
): Promise<WorkflowSchedule> {
|
||||
const workflowSchedule = await this.findOne(id);
|
||||
Object.assign(workflowSchedule, updateWorkflowScheduleDto);
|
||||
return await this.workflowSchedulesRepository.save(workflowSchedule);
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
const result = await this.workflowSchedulesRepository.delete(id);
|
||||
if (result.affected === 0) {
|
||||
throw new NotFoundException(`WorkflowSchedule with ID ${id} not found`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
// import { BadRequestException, HttpException, HttpStatus, Injectable, NotFoundException } from '@nestjs/common';
|
||||
// import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
// import { DataSource, EntityManager } from 'typeorm';
|
||||
// import { App } from 'src/entities/app.entity';
|
||||
// import { AppEnvironment } from 'src/entities/app_environments.entity';
|
||||
// import { AppVersion } from 'src/entities/app_version.entity';
|
||||
// import { v4 as uuidv4 } from 'uuid';
|
||||
// import { WorkflowExecutionsService } from '@services/workflow_executions.service';
|
||||
|
||||
// @Injectable()
|
||||
// export class WorkflowWebhooksService {
|
||||
// constructor(
|
||||
// private readonly manager: EntityManager,
|
||||
// private eventEmitter: EventEmitter2,
|
||||
// private workflowExecutionsService: WorkflowExecutionsService,
|
||||
// private readonly _dataSource: DataSource
|
||||
// ) {}
|
||||
|
||||
// async triggerWorkflow(workflowApps, workflowParams, environment, response) {
|
||||
// // When workflow version is introduced - Query needs to be tweaked
|
||||
// const appVersion = await this.manager
|
||||
// .createQueryBuilder(AppVersion, 'av')
|
||||
// .select(['av.definition'])
|
||||
// .innerJoinAndSelect(App, 'a', 'av.appId = a.id')
|
||||
// .where('av.appId = :id', { id: workflowApps.appId })
|
||||
// .getOne();
|
||||
|
||||
// const app = await this.manager
|
||||
// .createQueryBuilder(App, 'app')
|
||||
// .where('app.id = :id', { id: workflowApps.appId })
|
||||
// .getOne();
|
||||
// const enabled = app.isMaintenanceOn;
|
||||
|
||||
// if (!enabled) throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
|
||||
|
||||
// // Type validation for input values passed.
|
||||
// const inputValidators = appVersion?.definition?.webhookParams ?? [];
|
||||
// if (inputValidators.length) {
|
||||
// const inputParamsSet = new Set();
|
||||
// Object.entries(workflowParams).forEach(([key, _value]) => {
|
||||
// inputParamsSet.add(key);
|
||||
// });
|
||||
|
||||
// inputValidators.forEach((validator: { key: string; dataType: string }) => {
|
||||
// if (!inputParamsSet.has(validator.key)) throw new BadRequestException(`Params - ${validator.key} is missing`);
|
||||
// });
|
||||
// }
|
||||
|
||||
// const sanitisedWorkflowParams = {};
|
||||
// inputValidators.length &&
|
||||
// Object.entries(workflowParams).forEach(([key, value]) => {
|
||||
// const condition = inputValidators.find((input) => input.key == key);
|
||||
// if (condition) {
|
||||
// const isValidType = this.isValidateInputTypes(value, condition.dataType);
|
||||
// if (!isValidType) throw new BadRequestException(`${key} has incorrect datatype`);
|
||||
// if (isValidType) sanitisedWorkflowParams[key] = value;
|
||||
// }
|
||||
// });
|
||||
|
||||
// const environmentDetails = await this.manager
|
||||
// .createQueryBuilder(App, 'apps')
|
||||
// .leftJoinAndSelect(AppEnvironment, 'ae', 'ae.organizationId = apps.organizationId')
|
||||
// .where('apps.id = :id and ae.name = :envName', { id: workflowApps.appId, envName: environment })
|
||||
// .select(['apps.id', 'ae.id'])
|
||||
// .execute();
|
||||
|
||||
// if (!environmentDetails.length) throw new HttpException('Invalid environment', 404);
|
||||
// const webhookEnvironmentId = environmentDetails[0]?.ae_id ?? '';
|
||||
|
||||
// const workflowExecution = await this.workflowExecutionsService.create(workflowApps);
|
||||
// const result = await this.workflowExecutionsService.execute(
|
||||
// workflowExecution,
|
||||
// sanitisedWorkflowParams,
|
||||
// webhookEnvironmentId,
|
||||
// response
|
||||
// );
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// async updateWorkflow(workflowId, workflowValuesToUpdate) {
|
||||
// if (Object.keys(workflowValuesToUpdate).length === 0) throw new BadRequestException('Values to update is empty');
|
||||
// if (!workflowId) throw new BadRequestException('Invalid workflowId');
|
||||
// const { isEnable } = workflowValuesToUpdate;
|
||||
// const workflowApps = await this._dataSource
|
||||
// .getRepository(App)
|
||||
// .createQueryBuilder('apps')
|
||||
// .where('apps.id = :id', { id: workflowId })
|
||||
// .getOne();
|
||||
|
||||
// if (!workflowApps) throw new NotFoundException("Workflow doesn't exist");
|
||||
|
||||
// return this._dataSource
|
||||
// .createQueryBuilder()
|
||||
// .update(App)
|
||||
// .set({
|
||||
// workflowEnabled: isEnable === 'endPointTrigger',
|
||||
// ...(!workflowApps?.workflowApiToken && { workflowApiToken: uuidv4() }),
|
||||
// })
|
||||
// .where('id = :id', { id: workflowId })
|
||||
// .execute();
|
||||
// }
|
||||
|
||||
// private isValidateInputTypes(value, type) {
|
||||
// switch (type) {
|
||||
// case 'string':
|
||||
// return typeof value == 'string';
|
||||
// case 'number':
|
||||
// return typeof value == 'number';
|
||||
// case 'array':
|
||||
// return Array.isArray(value);
|
||||
// case 'object':
|
||||
// return typeof value == 'object';
|
||||
// case 'boolean':
|
||||
// return typeof value == 'boolean';
|
||||
// case 'null':
|
||||
// return value == null;
|
||||
// default:
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
Loading…
Reference in a new issue