mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-20 07:28:20 +00:00
* importing service: updated * import service, categorize and update events with associations * fix: deleting events associated with pages on page delete * handle app version: creation, updates, switching versions from app builder * on version switch: no updates should be triggeted to server * versioning for query events * fixes: new components db transaction fails for newly created pages * fixes: query chaining with events * map older query ids to new for event action: run query * fixes: multi-editor support * fixes/multi-editor: users should be able to edit different version of the app at real time without sync * minor fixes * fixes: undo/redo savings with latest app def updates * fixes: execution of page switch action * fixes: csa events * fixes: csa selection dropdown * fixes: on csa action changed, the action params should also be updated correctly * fixes: event rendering actions * fixes: table event - row hovered * fixes: table event - on search * fixes: table event - onNewRowsAdded * fixes: table event - onBulkUpdate * fixes: table column updates * fixes: table column updates to component definition * re-order events * handle adding widgets to sub containers * fixes: csa for modals * fixes: deletes children components on deleting parent * fixes: components with default children * fixed events for imported app * gs- crash fix * fixes: global settings UI * fixes: header and user * fixes: page switch event * fixes: adapts to new event manager ui * import app * add event index for creating app versions * fixes: table rendering on viewer * fixes: event execution for viewer * fixes: loading app with slug * fixes: Page side bar is not rendered in viewer * fixes: version manager ui for released versions * fixes: tabs default children saving * fixes: app resource mapping for parent-child components * fixes: duplicate pages * fixes: page load events for viewer * fixes: enable and disable pages * fixes: hide and show pages * fixes: on maintaince toggle button * fixes: new version child components are not tied to its parent * fixes: redo breaks- on deleting a component and undo then redo (editor) * new export schema and handling apps impport with new and older schemas * table events: column and actions events * fixes: query confirmations popup * fixes: copy/paste * fixes: cut/paste * fixes: event mapping for newer versions for new components, pages and queries id * fixes: app resource mapping for imported apps * fixes: cascade events for table actions and colulmns * Migrates the existing JSON-based app definition schema to a structured table-based architecture. This enhancement introduces component and page-specific permissions, improves data organization, and enables fine-grained access control. Additionally, it adds the 'globalSettings' column to the 'appVersion' table. * cleanup * fixes: enable and disable pages * fixes: hide/show page and set saving state for cloning pages * cleanup * fixes: page disable menu * fixes: migrations for data query events * fixes: switching app version from version creation modal results in editor loading state * fixes: setting up the page title * fixes: Page duplication has same page handler name. * fixes: updating general styles of a component * fixes: delete component should trigger confirmations box for one widget and for multiple should process deletion * fixes: CSA for button(component) does not work for page event handler. * fixes: component name update [calendar] * fixes: Duplicating pages do not create child components * improves copy-paste mechanism of widgets * fix: calendar subcontainer components comes out of the parent container on copy/paste * fix:Form properties, no option for selecting submit button. * fixes: Dark mode issue with event handler. * fixes: display preferences for components * fixes:have to select the selected version again to create a new * fixes: Pages menu is not getting disabled when enabled and vice-versa * fixes: correct naming of duplicating pages * fixes: 2 action button even with no event attached to one, it gets attached to both. * fixes: event deletion for action btn removal * fixes: Keyboard action to move component is not saving * reduce outbound calls when widget re-positioned with keyboard * fixes: Not able to delete component from Inspector * fixes: cloning of widgets * fixes: Request confirmation before query run toggle is not visible on viewer mode. (can't run query if confirmation toggle is on) * fixes: event sorting * fixes: events mapping for versioning: queries and components * fixes: importing app bug - query running issue when importing apps * [appdef-2.0] fixes: event action linking for imported apps (#7627) * fixes: event action linking for imported apps * cleanup * fixes: Toggling display preferences is not saving for components. (#7629) * fixes: dnd issue for mobile view (#7632) * default page menu settings should be true * [appdef-2] event manager selector bug (#7631) * fixes: on selecting query - 3 outbound calls are done to the server, and event manager re-renders 3times resulting in flikering ui * reduces outbound calls for updating csa actions to 1 * [appdef] - copy associated events for cloned components (#7634) * fixes: Copying component is not coping the events associated with the component. * cleanup * [appdef-2] : Fixes frontend issues (#7636) * Fix UI issues * Fix Scrollbar is not available after we pin the inspector. * Fix button jumping places if switched from extended monitor to laptop. * Fix white background around canvas * fixes: Component inspector go blank after switching to different pages after dropping components (#7637) * fixes: general properties of widgets are not getting saved (#7638) * fixes: selecting the components via selecto (#7653) * fixes: multiple undo-redo simlut. (#7656) * fixes: copy associated events for cloned queries (#7657) * Fixes not able toggle of Listview pagination toggle (#7701) * Fix UI issues * Fix Scrollbar is not available after we pin the inspector. * Fix button jumping places if switched from extended monitor to laptop. * fix enable pagination not getting toggled in listview * Fix form children not being displayed * fixes: dnd fix for widgets dropped inside subcontainer (#7691) * [Appdef-2] copy-paste, cut and clone fix for widgets (#7687) * fixes: copy/cut/paste and cloning of widgets * cleanup * can copy/paste-clone in listview * fixes: on mulit-widget selected via mouse area selection: widget manager should be rendered (#7688) * fixes: on deleting tabs widgets should delete its children (#7692) * fixes: column data generated from restapi does not render correct columns in viewer (#7695) * [appdef-2] fixes: multiple query confirmations trigger (#7704) * fixes: multiple query confirmations trigger * fixes: multiple outbound calls in the inital load, run queries on app load with confirmations:editor&viewer * fixes: correct confirmations list to the stote * [appdef-2] fixes:Event handler are running twice for page load (#7705) * fixes:Event handler are running twice for page load(eg- add 2 show alert and change the page). * fixes: for viewer page events * fixes: container widget is not getting saved on drop (#7718) * fixes: Create app version from is empty if we delete another version. (#7720) * [appdef-2] fixes: on versioned app (switching or creating) version, the componet layout is wrongly updated to the container dnd (#7721) * fixes: on versioned app (switching or creating) version, the component layout is wrongly updated to the container dnd * fixes: container widget is not saving * fixes: triggering confirmation box for every query with on load trigger (#7728) * Fixes canvas background and go to app crashing (#7725) * Fix UI issues * Fix Scrollbar is not available after we pin the inspector. * Fix button jumping places if switched from extended monitor to laptop. * fix enable pagination not getting toggled in listview * Fix form children not being displayed * Fix Go to app is crashing the application. * Fix fx for canvas background color is not working. * fixes: cloned/copied table with actions (#7758) * fixes: calendar and form widgets (#7735) * fixes: rendering of components in viewer for mobile (#7759) * fixes: toggling, resizing, dropping widgets in both display preferences (#7760) * fixes: page switch action via runjs actions (#7762) * fixes: component validations do not get saved (#7766) * [appdef-2] subcontainer dnd height outbound fix (#7767) * fixes: listview children can be dragged outside its outbound limit * cleanup * fixes: widget inspector going to empty component (#7768) * fixes: goToApp not running from runjs in viewer mode (#7770) * fixes: multi-components cloning or copy/paste have same name (#7761) * Fix disabled page is being displayed on switch page event dropdown (#7769) * Fix kanban rendering leading to infinite look * Fix disabled page is being displayed on switch page event dropdown * Fix Kanban widget getting into infinite loop (#7808) * Fix kanban rendering leading to infinite look * Fix disabled page is being displayed on switch page event dropdown * Fix kanban getting into infinite loop * adds support of constants to current state of the ediotr (#7821) * removes loader added for testing (#7822) * [appdef] fixes - dnd container cloning edge cases (#7820) * fixes: copy/pasting components updating wrong display preferences * fixes: copy/pasting tabs and cloning components inside tabs * fixes: duplication of calendar component bug * if components in subcontainer(children) are selected via selecto along with its parent, children should not be going through duplication * if components in subcontainer(children) are selected via selecto along with its parent, children should not be going through duplication * fixes: Resolving App Version and Timestamp Update Challenges (#7863) * Fixes query confirmation issue on viewer (#7862) * [appdef ]fixes: components copied from template app to a new page or app do not render in canvas (#7867) * fixes: components copied from template app to a new page or app does not render in canvas * fixes: table crash on coping from other pages with columns * adds the column exists check * fixes: tables crash for imported apps with auto generated cols * appdefinition refactor/cleanup (#7872) * cleanup controllers and request calls from frontend * removing unwanted console logs and unused variables * revering v1 apis og * adding length validation for page dto * adding dtos for components * updated dtos for components and pages * added dto for event handlers * fixes event handler dto * fixes: page dto * adds/fixes event handlers creating dtp * fixes: event handler service and dtos * [appdef] fixes: Creating page not changing the slug (#7873) * fixes: Creating page not changing the slug * removes extra whitespace * [appdef] fixes: on importing a exported app child components are not present in the parent component (#7864) * fixes: on importing a exported app child components are not present in the parent component * handles parent component mapping for tabs and calendar component * handles parent component mapping for tabs and calendar component for new versions * [appdef] api endpoint fixes (#7888) * fixes: moved fetching app version to v2 api * fixes: app slug api * Fixes CurrentUser & Mode not present in globals in inspector (#7812) * Fix current user not being present in inspector * Add Mode in globas in inspector * Fix creating page not changing the slug. * Revert "Fix creating page not changing the slug." This reverts commit0ff9c18ab8. * Fixes on adding query params in event handler, breaking the app (#7889) * Fix on version change if left sidebar is open canvas not scrolling right * Fix on adding query params in event handler, breaking the app * Fix * Fix on version change if left sidebar is open canvas not scrolling right (#7884) * fixes: fixes on on app load switch page action via run queires (#7858) * fixes: fixes on on app load switch page action via run queires * Fix * refactor * Fix on load event not appearing on viewer --------- Co-authored-by: Nakul Nagargade <nakul@tooljet.com> * [appdef] fixes: event actions mapping for import-export (#7895) * fixes: event actions mapping for import-export * fixes: updates organisation id * fixes: templates event mapping * do not app again for not normalized apps * [appdef]migrations fix (#7910) * fixes: page attributes * fixes: table action and column events for imported apps (prev) and app migrations * adds processDataInBatches * fixes: app data migrations * create a new queryBuilder instance for each batch to ensure that there's no interference between batches * fix: app migration * cleanup * cleanup * fixes: table column data not updated on boxes changes in container (#7919) * fixes: creating all pages from all versions (#7905) * Fix state not changing in chart (#7900) * Fix in chart, toggles are not working * Update Chart.jsx --------- Co-authored-by: Arpit <arpitnath42@gmail.com> * fix event param not updating (#7902) * [appdef] Pages attributes are missing on versioning or imported app (#7904) * fixes: on creating new version pages attributes are not copied * fixes: on importing apps with pages attributes are not copied * fixes: component double duplication issues * fixes: deleting children components via selecto (#7915) * fixes: component deletion fixes * fixes: cloning components to a new version should also create associated events * fixes: creating components on cloning with general styles or properties * fixes: creatinng general properties on version * fixes: imported app * fixes added to app migrations * fixes: mobile view * fixes: Created a new version with multiple pages from second page, the new version shows the homepage with second page URL * fixes: table crash due to columnDeletionHistory saved as an object instead of an array * fixes: on creating new version, data_queries should be created (#7975) * [appdef] fixes: migrations (#7951) * refactor migrations with batching * event actions: switch page should be mapped to correct new page id * fixes: importing json-schema app with multiple version: same components do not get render in the canvas * fixes: import/export of legecy apps * event actions mapping to correct page ids: migrations * fixes: migrations children not rendered in subcontainer components * adapts to main/viewer changes * fixes: viewer with #6698 * fixes: viewer route * fixes: page switch via validateRoutes * fixes: on delete version fetchApp fails * handle error on saving changes * skip name opts * typo fix * Instead of relying on the schema, we choose to use the Tooljet version as the determining factor for decoupling import flows * fixes: slug updates from global settings * fixes: slug app link (#8008) * fixes: on version changed the preview link should also update (#8009) * fixes: on cut and paste events should not cascade (#8010) * fixes: query options to new mapped ids * [appdef] fixes: cloning apps (#8012) * fixes: cloning apps * fixes: slug status from share modal * fixes: query confirmations list on viewer (#8017) * undo-fix * fixes: updates current state with page data on creating new page * Fix failing specs (#8031) * [appdef] fixes : ghost child components are being created on imported/cloned apps and while migrating (#8026) * fixes: ignore ghost components while importing * added the fix in migrations * fixes: adding other components * fixes: table column resizes * updates layout dto * update component dto * fixes: tabs children are not rendered as the are not in their repsective parent container (#8036) * moving editorFunc to Editor.jsx * cleanup * fixes: e2e test for clone * cleanup * fixes: toggle maintaince * bumping version * multi-edit: ymap-fix-1 * Revert "multi-edit: ymap-fix-1" This reverts commit8b799c3c51. * [appdef] fixes: viewer route: keeps on reloading for private apps (#8051) * fixes: viewer route: keeps on reloading for pribate apps * should return the response * test: ymap updates-1 * fixes: v1 apps with dq queries resuts in app crash * Updated import spec * Revert "Updated import spec" This reverts commit802136cdc3. * Fixed failed platform test cases for app desinition re-design (#8053) * Fix failed platform test cases * Modify user permission test cases * fixes: trial-5: fixing vanishing of components * Revert "fixes: trial-5: fixing vanishing of components" This reverts commita22aec12c7. * fixes: trail-6: fix * fixes: trail-7: fix * Revert "fixes: trail-7: fix" This reverts commit08f373c415. * Revert "fixes: trail-6: fix" This reverts commitc4e19b5d05. * multi-edit: ymap-fix * Revert "multi-edit: ymap-fix" This reverts commit92f49c0cde. * fixes: multi-user updates: adding or removing * event handlers sycned for multi-user * should take slug instead of appid if slug is present * updating adding ymap logic * versioning with multi-user * fixes: saving issue * dont skip ymap * adds delay to ymap * ymap-update-order-fixed * ymap-update-order-fixed-1.1 * ymap-update-order-fixed-1.2 * test=fix * Updated import spec (#8061) * cleanup * lint fixed * fixes: cloning apps with tabs * veiwer on event should return * fixes: event should map with show/hide modal component id * Fix failing appbuilder specs (#8117) * cherrypicked ee/00195c064 * bumping version to v2.24.0 * fixed modal actionid typo * fixes: slugs issues for released an public apps (#8119) * Fix failed test cases (#8121) * reverting global slug input * fixes: versioning with cloned page and ghost components (#8122) --------- Co-authored-by: Nakul Nagargade <133095394+nakulnagargade@users.noreply.github.com> Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> Co-authored-by: Nakul Nagargade <nakul@tooljet.com> Co-authored-by: Midhun Kumar E <midhun752@gmail.com> Co-authored-by: nandinisaha13 <nandinisaha13@gmail.com> Co-authored-by: Mekhla Asopa <dadhichmekhla@gmail.com> Co-authored-by: Ajith KV <ajith.jaban@gmail.com> Co-authored-by: Mekhla Asopa <59684099+Mekhla-Asopa@users.noreply.github.com>
793 lines
24 KiB
TypeScript
793 lines
24 KiB
TypeScript
/* eslint-disable prefer-const */
|
|
import { JwtService } from '@nestjs/jwt';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { getConnection, getManager, Repository } from 'typeorm';
|
|
import { OrganizationUser } from 'src/entities/organization_user.entity';
|
|
import { Organization } from 'src/entities/organization.entity';
|
|
import { User } from 'src/entities/user.entity';
|
|
import { App } from 'src/entities/app.entity';
|
|
import { File } from 'src/entities/file.entity';
|
|
import { Plugin } from 'src/entities/plugin.entity';
|
|
import { INestApplication, ValidationPipe, VersioningType, VERSION_NEUTRAL } from '@nestjs/common';
|
|
import { Test } from '@nestjs/testing';
|
|
import { AppModule } from 'src/app.module';
|
|
import { AppVersion } from 'src/entities/app_version.entity';
|
|
import { DataQuery } from 'src/entities/data_query.entity';
|
|
import { DataSource } from 'src/entities/data_source.entity';
|
|
import { PluginsService } from 'src/services/plugins.service';
|
|
import { DataSourcesService } from 'src/services/data_sources.service';
|
|
import { PluginsModule } from 'src/modules/plugins/plugins.module';
|
|
import { DataSourcesModule } from 'src/modules/data_sources/data_sources.module';
|
|
import { ThreadRepository } from 'src/repositories/thread.repository';
|
|
import { GroupPermission } from 'src/entities/group_permission.entity';
|
|
import { UserGroupPermission } from 'src/entities/user_group_permission.entity';
|
|
import { AppGroupPermission } from 'src/entities/app_group_permission.entity';
|
|
import { AllExceptionsFilter } from 'src/all-exceptions-filter';
|
|
import { Logger } from 'nestjs-pino';
|
|
import { WsAdapter } from '@nestjs/platform-ws';
|
|
import { AppsModule } from 'src/modules/apps/apps.module';
|
|
import { LibraryAppCreationService } from '@services/library_app_creation.service';
|
|
import { createMock, DeepMocked } from '@golevelup/ts-jest';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
import { CreateFileDto } from '@dto/create-file.dto';
|
|
import { CreatePluginDto } from '@dto/create-plugin.dto';
|
|
import * as request from 'supertest';
|
|
import { AppEnvironment } from 'src/entities/app_environments.entity';
|
|
import { defaultAppEnvironments } from 'src/helpers/utils.helper';
|
|
import { DataSourceOptions } from 'src/entities/data_source_options.entity';
|
|
import * as cookieParser from 'cookie-parser';
|
|
|
|
export async function createNestAppInstance(): Promise<INestApplication> {
|
|
let app: INestApplication;
|
|
|
|
const moduleRef = await Test.createTestingModule({
|
|
imports: [AppModule],
|
|
providers: [],
|
|
}).compile();
|
|
|
|
app = moduleRef.createNestApplication();
|
|
app.setGlobalPrefix('api');
|
|
app.use(cookieParser());
|
|
app.useGlobalFilters(new AllExceptionsFilter(moduleRef.get(Logger)));
|
|
app.useWebSocketAdapter(new WsAdapter(app));
|
|
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
|
|
app.enableVersioning({
|
|
type: VersioningType.URI,
|
|
defaultVersion: VERSION_NEUTRAL,
|
|
});
|
|
await app.init();
|
|
|
|
return app;
|
|
}
|
|
|
|
export async function createNestAppInstanceWithEnvMock(): Promise<{
|
|
app: INestApplication;
|
|
mockConfig: DeepMocked<ConfigService>;
|
|
}> {
|
|
let app: INestApplication;
|
|
|
|
const moduleRef = await Test.createTestingModule({
|
|
imports: [AppModule],
|
|
providers: [
|
|
{
|
|
provide: ConfigService,
|
|
useValue: createMock<ConfigService>(),
|
|
},
|
|
],
|
|
}).compile();
|
|
|
|
app = moduleRef.createNestApplication();
|
|
app.setGlobalPrefix('api');
|
|
app.use(cookieParser());
|
|
app.useGlobalFilters(new AllExceptionsFilter(moduleRef.get(Logger)));
|
|
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
|
|
app.useWebSocketAdapter(new WsAdapter(app));
|
|
await app.init();
|
|
|
|
return { app, mockConfig: moduleRef.get(ConfigService) };
|
|
}
|
|
|
|
export function authHeaderForUser(user: User, organizationId?: string, isPasswordLogin = true): string {
|
|
const configService = new ConfigService();
|
|
const jwtService = new JwtService({
|
|
secret: configService.get<string>('SECRET_KEY_BASE'),
|
|
});
|
|
const authPayload = {
|
|
username: user.id,
|
|
sub: user.email,
|
|
organizationId: organizationId || user.defaultOrganizationId,
|
|
isPasswordLogin,
|
|
};
|
|
const authToken = jwtService.sign(authPayload);
|
|
return `Bearer ${authToken}`;
|
|
}
|
|
|
|
export async function clearDB() {
|
|
const entities = getConnection().entityMetadatas;
|
|
for (const entity of entities) {
|
|
const repository = getConnection().getRepository(entity.name);
|
|
await repository.query(`TRUNCATE ${entity.tableName} RESTART IDENTITY CASCADE;`);
|
|
}
|
|
}
|
|
|
|
export async function createApplication(nestApp, { name, user, isPublic, slug }: any, shouldCreateEnvs = true) {
|
|
let appRepository: Repository<App>;
|
|
appRepository = nestApp.get('AppRepository');
|
|
|
|
user = user || (await (await createUser(nestApp, {})).user);
|
|
|
|
if (shouldCreateEnvs) {
|
|
await createAppEnvironments(nestApp, user.organizationId);
|
|
}
|
|
|
|
const newApp = await appRepository.save(
|
|
appRepository.create({
|
|
name,
|
|
user,
|
|
slug,
|
|
isPublic: isPublic || false,
|
|
organizationId: user.organizationId,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
})
|
|
);
|
|
|
|
await maybeCreateAdminAppGroupPermissions(nestApp, newApp);
|
|
await maybeCreateAllUsersAppGroupPermissions(nestApp, newApp);
|
|
|
|
return newApp;
|
|
}
|
|
|
|
export async function importAppFromTemplates(nestApp, user, identifier) {
|
|
const service = nestApp.select(AppsModule).get(LibraryAppCreationService);
|
|
|
|
return service.perform(user, identifier);
|
|
}
|
|
|
|
export async function createApplicationVersion(
|
|
nestApp,
|
|
application,
|
|
{ name = 'v0', definition = null, currentEnvironmentId = null } = {}
|
|
) {
|
|
let appVersionsRepository: Repository<AppVersion>;
|
|
let appEnvironmentsRepository: Repository<AppEnvironment>;
|
|
appVersionsRepository = nestApp.get('AppVersionRepository');
|
|
appEnvironmentsRepository = nestApp.get('AppEnvironmentRepository');
|
|
|
|
const environments = await appEnvironmentsRepository.find({
|
|
where: {
|
|
organizationId: application.organizationId,
|
|
},
|
|
});
|
|
|
|
const envId = currentEnvironmentId
|
|
? currentEnvironmentId
|
|
: defaultAppEnvironments.length > 1
|
|
? environments.find((env) => env.priority === 1)?.id
|
|
: environments[0].id;
|
|
|
|
return await appVersionsRepository.save(
|
|
appVersionsRepository.create({
|
|
app: application,
|
|
name,
|
|
currentEnvironmentId: envId,
|
|
definition,
|
|
})
|
|
);
|
|
}
|
|
export async function getAllEnvironments(nestApp, organizationId): Promise<AppEnvironment[]> {
|
|
let appEnvironmentRepository: Repository<AppEnvironment>;
|
|
appEnvironmentRepository = nestApp.get('AppEnvironmentRepository');
|
|
|
|
return await appEnvironmentRepository.find({
|
|
where: {
|
|
organizationId,
|
|
},
|
|
order: {
|
|
priority: 'ASC',
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function createAppEnvironments(nestApp, organizationId): Promise<AppEnvironment[]> {
|
|
let appEnvironmentRepository: Repository<AppEnvironment>;
|
|
appEnvironmentRepository = nestApp.get('AppEnvironmentRepository');
|
|
|
|
return await Promise.all(
|
|
defaultAppEnvironments.map(async (env) => {
|
|
return await appEnvironmentRepository.save(
|
|
appEnvironmentRepository.create({
|
|
organizationId,
|
|
name: env.name,
|
|
priority: env.priority,
|
|
isDefault: env.isDefault,
|
|
})
|
|
);
|
|
})
|
|
);
|
|
}
|
|
|
|
export async function createUser(
|
|
nestApp,
|
|
{
|
|
firstName,
|
|
lastName,
|
|
email,
|
|
groups,
|
|
organization,
|
|
status,
|
|
invitationToken,
|
|
formLoginStatus = true,
|
|
organizationName = `${email}'s workspace`,
|
|
ssoConfigs = [],
|
|
enableSignUp = false,
|
|
}: {
|
|
firstName?: string;
|
|
lastName?: string;
|
|
email?: string;
|
|
groups?: Array<string>;
|
|
organization?: Organization;
|
|
status?: string;
|
|
invitationToken?: string;
|
|
formLoginStatus?: boolean;
|
|
organizationName?: string;
|
|
ssoConfigs?: Array<any>;
|
|
enableSignUp?: boolean;
|
|
},
|
|
existingUser?: User
|
|
) {
|
|
let userRepository: Repository<User>;
|
|
let organizationRepository: Repository<Organization>;
|
|
let organizationUsersRepository: Repository<OrganizationUser>;
|
|
|
|
userRepository = nestApp.get('UserRepository');
|
|
organizationRepository = nestApp.get('OrganizationRepository');
|
|
organizationUsersRepository = nestApp.get('OrganizationUserRepository');
|
|
|
|
organization =
|
|
organization ||
|
|
(await organizationRepository.save(
|
|
organizationRepository.create({
|
|
name: organizationName,
|
|
enableSignUp,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
ssoConfigs: [
|
|
{
|
|
sso: 'form',
|
|
enabled: formLoginStatus,
|
|
},
|
|
...ssoConfigs,
|
|
],
|
|
})
|
|
));
|
|
|
|
let user: User;
|
|
|
|
if (!existingUser) {
|
|
user = await userRepository.save(
|
|
userRepository.create({
|
|
firstName: firstName || 'test',
|
|
lastName: lastName || 'test',
|
|
email: email || 'dev@tooljet.io',
|
|
password: 'password',
|
|
invitationToken,
|
|
defaultOrganizationId: organization.id,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
status: invitationToken ? 'invited' : 'active',
|
|
})
|
|
);
|
|
} else {
|
|
user = existingUser;
|
|
}
|
|
user.organizationId = organization.id;
|
|
|
|
const orgUser = await organizationUsersRepository.save(
|
|
organizationUsersRepository.create({
|
|
user: user,
|
|
organization,
|
|
invitationToken: status === 'invited' ? uuidv4() : null,
|
|
status: status || 'active',
|
|
role: 'all_users',
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
})
|
|
);
|
|
|
|
await maybeCreateDefaultGroupPermissions(nestApp, user.organizationId);
|
|
await createUserGroupPermissions(
|
|
nestApp,
|
|
user,
|
|
groups || ['all_users', 'admin'] // default groups
|
|
);
|
|
|
|
return { organization, user, orgUser };
|
|
}
|
|
|
|
export async function createUserGroupPermissions(nestApp, user, groups) {
|
|
const groupPermissionRepository: Repository<GroupPermission> = nestApp.get('GroupPermissionRepository');
|
|
|
|
const userGroupPermissionRepository: Repository<UserGroupPermission> = nestApp.get('UserGroupPermissionRepository');
|
|
|
|
let userGroupPermissions = [];
|
|
|
|
for (const group of groups) {
|
|
let groupPermission: GroupPermission;
|
|
|
|
if (group == 'admin' || group == 'all_users') {
|
|
groupPermission = await groupPermissionRepository.findOneOrFail({
|
|
where: {
|
|
organizationId: user.organizationId,
|
|
group: group,
|
|
},
|
|
});
|
|
} else {
|
|
groupPermission =
|
|
(await groupPermissionRepository.findOne({
|
|
where: {
|
|
organizationId: user.organizationId,
|
|
group: group,
|
|
},
|
|
})) ||
|
|
groupPermissionRepository.create({
|
|
organizationId: user.organizationId,
|
|
group: group,
|
|
});
|
|
await groupPermissionRepository.save(groupPermission);
|
|
}
|
|
|
|
const userGroupPermission = userGroupPermissionRepository.create({
|
|
groupPermissionId: groupPermission.id,
|
|
userId: user.id,
|
|
});
|
|
await userGroupPermissionRepository.save(userGroupPermission);
|
|
userGroupPermissions.push(userGroupPermission);
|
|
}
|
|
|
|
return userGroupPermissions;
|
|
}
|
|
|
|
export async function createAppGroupPermission(nestApp, app, groupId, permissions) {
|
|
const appGroupPermissionRepository: Repository<AppGroupPermission> = nestApp.get('AppGroupPermissionRepository');
|
|
|
|
const appGroupPermission = appGroupPermissionRepository.create({
|
|
groupPermissionId: groupId,
|
|
appId: app.id,
|
|
...permissions,
|
|
});
|
|
await appGroupPermissionRepository.save(appGroupPermission);
|
|
|
|
return appGroupPermission;
|
|
}
|
|
|
|
export async function createGroupPermission(nestApp, params) {
|
|
const groupPermissionRepository: Repository<GroupPermission> = nestApp.get('GroupPermissionRepository');
|
|
let groupPermission = groupPermissionRepository.create({
|
|
...params,
|
|
});
|
|
await groupPermissionRepository.save(groupPermission);
|
|
|
|
return groupPermission;
|
|
}
|
|
|
|
export async function maybeCreateDefaultGroupPermissions(nestApp, organizationId) {
|
|
const groupPermissionRepository: Repository<GroupPermission> = nestApp.get('GroupPermissionRepository');
|
|
|
|
const defaultGroups = ['all_users', 'admin'];
|
|
|
|
for (let group of defaultGroups) {
|
|
const orgDefaultGroupPermissions = await groupPermissionRepository.find({
|
|
where: {
|
|
organizationId: organizationId,
|
|
group: group,
|
|
},
|
|
});
|
|
|
|
if (orgDefaultGroupPermissions.length == 0) {
|
|
const groupPermission = groupPermissionRepository.create({
|
|
organizationId: organizationId,
|
|
group: group,
|
|
appCreate: group == 'admin',
|
|
appDelete: group == 'admin',
|
|
folderCreate: group == 'admin',
|
|
orgEnvironmentVariableCreate: group == 'admin',
|
|
orgEnvironmentVariableUpdate: group == 'admin',
|
|
orgEnvironmentVariableDelete: group == 'admin',
|
|
orgEnvironmentConstantCreate: group == 'admin',
|
|
orgEnvironmentConstantDelete: group == 'admin',
|
|
folderUpdate: group == 'admin',
|
|
folderDelete: group == 'admin',
|
|
});
|
|
await groupPermissionRepository.save(groupPermission);
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function maybeCreateAdminAppGroupPermissions(nestApp, app) {
|
|
const groupPermissionRepository: Repository<GroupPermission> = nestApp.get('GroupPermissionRepository');
|
|
const appGroupPermissionRepository: Repository<AppGroupPermission> = nestApp.get('AppGroupPermissionRepository');
|
|
|
|
const orgAdminGroupPermissions = await groupPermissionRepository.findOne({
|
|
where: {
|
|
organizationId: app.organizationId,
|
|
group: 'admin',
|
|
},
|
|
});
|
|
|
|
if (orgAdminGroupPermissions) {
|
|
const adminGroupPermissions = {
|
|
read: true,
|
|
update: true,
|
|
delete: true,
|
|
};
|
|
|
|
const appGroupPermission = appGroupPermissionRepository.create({
|
|
groupPermissionId: orgAdminGroupPermissions.id,
|
|
appId: app.id,
|
|
...adminGroupPermissions,
|
|
});
|
|
await appGroupPermissionRepository.save(appGroupPermission);
|
|
}
|
|
}
|
|
|
|
export async function maybeCreateAllUsersAppGroupPermissions(nestApp, app) {
|
|
const groupPermissionRepository: Repository<GroupPermission> = nestApp.get('GroupPermissionRepository');
|
|
const appGroupPermissionRepository: Repository<AppGroupPermission> = nestApp.get('AppGroupPermissionRepository');
|
|
|
|
const allUsersGroup = await groupPermissionRepository.findOne({
|
|
where: {
|
|
organizationId: app.organizationId,
|
|
group: 'all_users',
|
|
},
|
|
});
|
|
|
|
if (allUsersGroup) {
|
|
const permissions = {
|
|
read: false,
|
|
update: false,
|
|
delete: false,
|
|
};
|
|
|
|
const appGroupPermission = appGroupPermissionRepository.create({
|
|
groupPermissionId: allUsersGroup.id,
|
|
appId: app.id,
|
|
...permissions,
|
|
});
|
|
await appGroupPermissionRepository.save(appGroupPermission);
|
|
}
|
|
}
|
|
|
|
export async function addAppToGroupPermission(app: App, groupPermission: GroupPermission, permissions = {}) {
|
|
getManager().create(AppGroupPermission, {
|
|
groupPermissionId: groupPermission.id,
|
|
appId: app.id,
|
|
...permissions,
|
|
});
|
|
}
|
|
|
|
export async function addAllUsersGroupToUser(nestApp, user) {
|
|
const groupPermissionRepository: Repository<GroupPermission> = nestApp.get('GroupPermissionRepository');
|
|
const userGroupPermissionRepository: Repository<UserGroupPermission> = nestApp.get('UserGroupPermissionRepository');
|
|
|
|
const orgDefaultGroupPermissions = await groupPermissionRepository.findOneOrFail({
|
|
where: {
|
|
organizationId: user.organizationId,
|
|
group: 'all_users',
|
|
},
|
|
});
|
|
|
|
const userGroupPermission = userGroupPermissionRepository.create({
|
|
groupPermissionId: orgDefaultGroupPermissions.id,
|
|
userId: user.id,
|
|
});
|
|
await userGroupPermissionRepository.save(userGroupPermission);
|
|
|
|
return user;
|
|
}
|
|
|
|
export async function createDataSource(
|
|
nestApp,
|
|
{ appVersion, name, kind, type = 'default', options, environmentId = null }: any
|
|
) {
|
|
let dataSourceRepository: Repository<DataSource>;
|
|
dataSourceRepository = nestApp.get('DataSourceRepository');
|
|
|
|
const dataSource = await dataSourceRepository.save(
|
|
dataSourceRepository.create({
|
|
name,
|
|
kind,
|
|
appVersion,
|
|
type,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
})
|
|
);
|
|
|
|
environmentId && (await createDataSourceOption(nestApp, { dataSource, environmentId, options }));
|
|
|
|
return dataSource;
|
|
}
|
|
|
|
export async function createDataQuery(nestApp, { name = 'defaultquery', dataSource, appVersion, options }: any) {
|
|
let dataQueryRepository: Repository<DataQuery>;
|
|
dataQueryRepository = nestApp.get('DataQueryRepository');
|
|
|
|
return await dataQueryRepository.save(
|
|
dataQueryRepository.create({
|
|
options,
|
|
name,
|
|
dataSource,
|
|
appVersion,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
})
|
|
);
|
|
}
|
|
|
|
export async function createDataSourceOption(nestApp, { dataSource, environmentId, options }: any) {
|
|
let dataSourceOptionsRepository: Repository<DataSourceOptions>;
|
|
dataSourceOptionsRepository = nestApp.get('DataSourceOptionsRepository');
|
|
|
|
const dataSourcesService = nestApp.select(DataSourcesModule).get(DataSourcesService);
|
|
|
|
return await dataSourceOptionsRepository.save(
|
|
dataSourceOptionsRepository.create({
|
|
options: await dataSourcesService.parseOptionsForCreate(options),
|
|
dataSourceId: dataSource.id,
|
|
environmentId,
|
|
})
|
|
);
|
|
}
|
|
|
|
export async function createFile(nestApp: any) {
|
|
let fileRepository: Repository<File>;
|
|
fileRepository = nestApp.get('FileRepository');
|
|
const createFileDto = new CreateFileDto();
|
|
createFileDto.filename = 'testfile';
|
|
createFileDto.data = Buffer.from([1, 2, 3, 4]);
|
|
return await fileRepository.save(fileRepository.create(createFileDto));
|
|
}
|
|
|
|
export async function installPlugin(nestApp: any, { name, description, id, version }: any) {
|
|
let pluginRepository: Repository<Plugin>;
|
|
pluginRepository = nestApp.get('PluginRepository');
|
|
const createPluginDto = new CreatePluginDto();
|
|
createPluginDto.id = id;
|
|
createPluginDto.name = name;
|
|
createPluginDto.version = version;
|
|
createPluginDto.description = description;
|
|
|
|
const pluginsService = nestApp.select(PluginsModule).get(PluginsService);
|
|
|
|
return await pluginRepository.save(pluginsService.install(createPluginDto));
|
|
}
|
|
|
|
export async function createThread(_nestApp, { appId, x, y, userId, organizationId, appVersionsId }: any) {
|
|
const threadRepository = new ThreadRepository();
|
|
|
|
return await threadRepository.createThread(
|
|
{
|
|
appId,
|
|
x,
|
|
y,
|
|
isResolved: false,
|
|
organizationId,
|
|
appVersionsId,
|
|
pageId: 'placeholder',
|
|
},
|
|
userId,
|
|
organizationId
|
|
);
|
|
}
|
|
|
|
export async function setupOrganization(nestApp) {
|
|
const adminUserData = await createUser(nestApp, {
|
|
email: 'admin@tooljet.io',
|
|
groups: ['all_users', 'admin'],
|
|
});
|
|
const adminUser = adminUserData.user;
|
|
const organization = adminUserData.organization;
|
|
const defaultUserData = await createUser(nestApp, {
|
|
email: 'developer@tooljet.io',
|
|
groups: ['all_users'],
|
|
organization,
|
|
});
|
|
const defaultUser = defaultUserData.user;
|
|
|
|
const app = await createApplication(nestApp, {
|
|
user: adminUser,
|
|
name: 'sample app',
|
|
isPublic: false,
|
|
});
|
|
|
|
return { adminUser, defaultUser, app };
|
|
}
|
|
|
|
export const generateRedirectUrl = async (
|
|
email: string,
|
|
current_organization?: Organization,
|
|
isOrgInvitation?: boolean,
|
|
isSSO = true
|
|
) => {
|
|
const manager = getManager();
|
|
const user = await manager.findOneOrFail(User, { where: { email: email } });
|
|
|
|
const organizationToken = user.organizationUsers?.find(
|
|
(ou) => ou.organizationId === current_organization?.id
|
|
)?.invitationToken;
|
|
|
|
return `${process.env['TOOLJET_HOST']}${
|
|
isOrgInvitation ? `/organization-invitations/${organizationToken}` : `/invitations/${user.invitationToken}`
|
|
}${
|
|
organizationToken
|
|
? `${!isOrgInvitation ? `/workspaces/${organizationToken}` : ''}?oid=${current_organization?.id}&`
|
|
: isSSO
|
|
? '?'
|
|
: ''
|
|
}${isSSO ? 'source=sso' : ''}`;
|
|
};
|
|
|
|
export const createSSOMockConfig = (mockConfig) => {
|
|
jest.spyOn(mockConfig, 'get').mockImplementation((key: string) => {
|
|
switch (key) {
|
|
case 'SSO_GOOGLE_OAUTH2_CLIENT_ID':
|
|
return 'google-client-id';
|
|
case 'SSO_GIT_OAUTH2_CLIENT_ID':
|
|
return 'git-client-id';
|
|
case 'SSO_GIT_OAUTH2_CLIENT_SECRET':
|
|
return 'git-secret';
|
|
case 'SSO_ACCEPTED_DOMAINS':
|
|
return 'tooljet.io,tooljet.com';
|
|
default:
|
|
return process.env[key];
|
|
}
|
|
});
|
|
};
|
|
|
|
export const verifyInviteToken = async (app: INestApplication, user: User, verifyForSignup = false) => {
|
|
let organizationUsersRepository: Repository<OrganizationUser>;
|
|
organizationUsersRepository = app.get('OrganizationUserRepository');
|
|
|
|
const { invitationToken } = user;
|
|
const { invitationToken: orgInviteToken } = await organizationUsersRepository.findOneOrFail({
|
|
where: { userId: user.id },
|
|
});
|
|
const response = await request(app.getHttpServer()).get(
|
|
`/api/verify-invite-token?token=${invitationToken}${
|
|
!verifyForSignup && orgInviteToken ? `&organizationToken=${orgInviteToken}` : ''
|
|
}`
|
|
);
|
|
const {
|
|
body: { onboarding_details },
|
|
status,
|
|
} = response;
|
|
|
|
expect(status).toBe(200);
|
|
expect(Object.keys(onboarding_details)).toEqual(['password', 'questions']);
|
|
await user.reload();
|
|
expect(user.status).toBe('verified');
|
|
return response;
|
|
};
|
|
|
|
export const setUpAccountFromToken = async (app: INestApplication, user: User, org: Organization, payload) => {
|
|
const response = await request(app.getHttpServer()).post('/api/setup-account-from-token').send(payload);
|
|
const { status } = response;
|
|
expect(status).toBe(201);
|
|
|
|
const { email, first_name, last_name, current_organization_id } = response.body;
|
|
|
|
expect(email).toEqual(user.email);
|
|
expect(first_name).toEqual(user.firstName);
|
|
expect(last_name).toEqual(user.lastName);
|
|
expect(current_organization_id).toBe(org.id);
|
|
await user.reload();
|
|
expect(user.status).toBe('active');
|
|
expect(user.defaultOrganizationId).toBe(org.id);
|
|
};
|
|
|
|
export const getPathFromUrl = (url) => {
|
|
return url.split('?')[0];
|
|
};
|
|
|
|
export const createFirstUser = async (app: INestApplication) => {
|
|
let userRepository: Repository<User> = app.get('UserRepository');
|
|
|
|
await request(app.getHttpServer())
|
|
.post('/api/setup-admin')
|
|
.send({ email: 'firstuser@tooljet.com', name: 'Admin', password: 'password', workspace: 'tooljet' });
|
|
|
|
return await userRepository.findOneOrFail({
|
|
where: { email: 'firstuser@tooljet.com' },
|
|
relations: ['organizationUsers'],
|
|
});
|
|
};
|
|
|
|
export const generateAppDefaults = async (
|
|
app: INestApplication,
|
|
user: any,
|
|
{ isQueryNeeded = true, isDataSourceNeeded = true, isAppPublic = false, dsKind = 'restapi', dsOptions = [{}] }
|
|
) => {
|
|
const application = await createApplication(
|
|
app,
|
|
{
|
|
name: 'name',
|
|
user: user,
|
|
isPublic: isAppPublic,
|
|
},
|
|
false
|
|
);
|
|
|
|
const appEnvironments = await createAppEnvironments(app, user.organizationId);
|
|
const appVersion = await createApplicationVersion(app, application);
|
|
|
|
let dataQuery: any;
|
|
let dataSource: any;
|
|
if (isDataSourceNeeded) {
|
|
dataSource = await createDataSource(app, {
|
|
name: 'name',
|
|
kind: dsKind,
|
|
appVersion,
|
|
});
|
|
await createDataSourceOption(app, { dataSource, environmentId: appEnvironments[0].id, options: dsOptions });
|
|
|
|
if (isQueryNeeded) {
|
|
dataQuery = await createDataQuery(app, {
|
|
dataSource,
|
|
appVersion,
|
|
options: {
|
|
method: 'get',
|
|
url: 'https://api.github.com/repos/tooljet/tooljet/stargazers',
|
|
url_params: [],
|
|
headers: [],
|
|
body: [],
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
return { application, appVersion, dataSource, dataQuery };
|
|
};
|
|
|
|
export const getAppWithAllDetails = async (id: string) => {
|
|
const app = await getManager()
|
|
.createQueryBuilder(App, 'app')
|
|
.where('app.id = :id', { id })
|
|
.innerJoinAndSelect('app.appVersions', 'versions')
|
|
.leftJoinAndSelect('versions.dataSources', 'dataSources')
|
|
.leftJoinAndSelect('versions.dataQueries', 'dataQueries')
|
|
.getOneOrFail();
|
|
|
|
const dataQueries = [];
|
|
const dataSources = [];
|
|
app.appVersions.map((version) => {
|
|
dataSources.push(...version.dataSources);
|
|
dataQueries.push(...version.dataQueries);
|
|
version.dataSources = undefined;
|
|
version.dataQueries = undefined;
|
|
});
|
|
|
|
app['dataQueries'] = dataQueries;
|
|
app['dataSources'] = dataSources;
|
|
|
|
return app;
|
|
};
|
|
|
|
export const authenticateUser = async (app: INestApplication, email = 'admin@tooljet.io', password = 'password') => {
|
|
const sessionResponse = await request
|
|
.agent(app.getHttpServer())
|
|
.post('/api/authenticate')
|
|
.send({ email, password })
|
|
.expect(201);
|
|
|
|
return { user: sessionResponse.body, tokenCookie: sessionResponse.headers['set-cookie'] };
|
|
};
|
|
|
|
export const logoutUser = async (app: INestApplication, tokenCookie: any, organization_id: string) => {
|
|
return await request
|
|
.agent(app.getHttpServer())
|
|
.get('/api/logout')
|
|
.set('tj-workspace-id', organization_id)
|
|
.set('Cookie', tokenCookie)
|
|
.expect(200);
|
|
};
|