mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 00:48:25 +00:00
fix: import bugs
This commit is contained in:
parent
5149a136af
commit
20dacb0d0d
6 changed files with 201 additions and 30 deletions
|
|
@ -116,6 +116,8 @@ class HomePageComponent extends React.Component {
|
|||
shouldAutoImportPlugin: false,
|
||||
dependentPlugins: [],
|
||||
dependentPluginsDetail: {},
|
||||
showMissingGroupsModal: false,
|
||||
missingGroups: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -356,17 +358,24 @@ class HomePageComponent extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
importFile = async (importJSON, appName) => {
|
||||
importFile = async (importJSON, appName, skipPagePermissionsGroupCheck = false) => {
|
||||
this.setState({ isImportingApp: true });
|
||||
// For backward compatibility with legacy app import
|
||||
const organization_id = this.state.currentUser?.organization_id;
|
||||
const isLegacyImport = isEmpty(importJSON.tooljet_version);
|
||||
if (isLegacyImport) {
|
||||
importJSON = { app: [{ definition: importJSON, appName: appName }], tooljet_version: importJSON.tooljetVersion };
|
||||
importJSON = {
|
||||
app: [{ definition: importJSON, appName: appName }],
|
||||
tooljet_version: importJSON.tooljetVersion,
|
||||
};
|
||||
} else {
|
||||
importJSON.app[0].appName = appName;
|
||||
}
|
||||
const requestBody = { organization_id, ...importJSON };
|
||||
const requestBody = {
|
||||
organization_id,
|
||||
...importJSON,
|
||||
skip_page_permissions_group_check: skipPagePermissionsGroupCheck,
|
||||
};
|
||||
let installedPluginsInfo = [];
|
||||
try {
|
||||
if (this.state.dependentPlugins.length) {
|
||||
|
|
@ -388,6 +397,10 @@ class HomePageComponent extends React.Component {
|
|||
this.props.navigate(`/${getWorkspaceId()}/database`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error?.error?.type === 'permission-check') {
|
||||
this.setState({ showMissingGroupsModal: true, missingGroups: error?.error?.data });
|
||||
return;
|
||||
}
|
||||
if (installedPluginsInfo.length) {
|
||||
const pluginsId = installedPluginsInfo.map((pluginInfo) => pluginInfo.id);
|
||||
await pluginsService.uninstallPlugins(pluginsId);
|
||||
|
|
@ -888,6 +901,8 @@ class HomePageComponent extends React.Component {
|
|||
showGroupMigrationBanner,
|
||||
dependentPlugins,
|
||||
dependentPluginsDetail,
|
||||
showMissingGroupsModal,
|
||||
missingGroups,
|
||||
} = this.state;
|
||||
const modalConfigs = {
|
||||
create: {
|
||||
|
|
@ -953,6 +968,29 @@ class HomePageComponent extends React.Component {
|
|||
configs={modalConfigs}
|
||||
onCommitChange={this.handleCommitChange}
|
||||
/>
|
||||
<ModalBase
|
||||
title={'Missing groups in workspace'}
|
||||
handleConfirm={() => this.importFile(fileContent, fileName, true)}
|
||||
show={showMissingGroupsModal}
|
||||
isLoading={importingApp}
|
||||
handleClose={() => this.setState({ showMissingGroupsModal: false })}
|
||||
confirmBtnProps={{
|
||||
title: 'Import',
|
||||
tooltipMessage: '',
|
||||
}}
|
||||
darkMode={this.props.darkMode}
|
||||
>
|
||||
<p className="tj-text-sm">
|
||||
The following group permissions are missing for page permissions. Are you sure you want to continue?
|
||||
</p>
|
||||
<div className="item-list">
|
||||
{missingGroups.map((item, index) => (
|
||||
<div key={index}>
|
||||
<span className="tj-text-sm">{`${index + 1}. ${item}`}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ModalBase>
|
||||
{showRenameAppModal && (
|
||||
<AppModal
|
||||
show={() => this.setState({ showRenameAppModal: true })}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { IsUUID, IsOptional, IsString, IsDefined, ValidateNested } from 'class-validator';
|
||||
import { IsUUID, IsOptional, IsString, IsDefined, ValidateNested, IsBoolean } from 'class-validator';
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
import { ValidateTooljetDatabaseSchema } from './validators/tooljet-database.validator';
|
||||
import { TjdbSchemaToLatestVersion } from './transformers/resource-transformer';
|
||||
|
|
@ -28,6 +28,10 @@ export class ImportResourcesDto {
|
|||
// and instantiated data
|
||||
@ValidateTooljetDatabaseSchema({ each: true })
|
||||
tooljet_database: ImportTooljetDatabaseDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
skip_page_permissions_group_check?: boolean;
|
||||
}
|
||||
|
||||
export class ImportAppDto {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
export const APP_ERROR_TYPE = {
|
||||
IMPORT_EXPORT_SERVICE: {
|
||||
UNSUPPORTED_VERSION_ERROR: 'Apps built on later versions of ToolJet cannot be imported',
|
||||
PAGE_PERMISSION_GROUP_ERROR: 'Following groups are missing from the workspace',
|
||||
PERMISSION_CHECK: 'permission-check',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,10 +19,14 @@ export class PagePermissionsRepository extends Repository<PagePermission> {
|
|||
});
|
||||
|
||||
return pagePermissions.map((permission) => {
|
||||
return {
|
||||
...permission,
|
||||
users: permission.users,
|
||||
};
|
||||
if (permission.type === PAGE_PERMISSION_TYPE.GROUP) {
|
||||
return {
|
||||
...permission,
|
||||
groups: permission.users,
|
||||
users: undefined,
|
||||
};
|
||||
}
|
||||
return permission;
|
||||
});
|
||||
}, manager || this.manager);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { BadRequestException, HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { isEmpty, set } from 'lodash';
|
||||
import { App } from 'src/entities/app.entity';
|
||||
import { AppEnvironment } from 'src/entities/app_environments.entity';
|
||||
|
|
@ -33,6 +33,11 @@ import { DataSourcesUtilService } from '@modules/data-sources/util.service';
|
|||
import { DataSourcesRepository } from '@modules/data-sources/repository';
|
||||
import { AppEnvironmentUtilService } from '@modules/app-environments/util.service';
|
||||
import { ComponentsService } from './component.service';
|
||||
import { GroupPermissions } from '@entities/group_permissions.entity';
|
||||
import { APP_ERROR_TYPE } from '@helpers/error_type.constant';
|
||||
import { PAGE_PERMISSION_TYPE } from '@modules/app-permissions/constants';
|
||||
import { PagePermission } from '@entities/page_permissions.entity';
|
||||
import { PageUser } from '@entities/page_users.entity';
|
||||
interface AppResourceMappings {
|
||||
defaultDataSourceIdMapping: Record<string, string>;
|
||||
dataQueryMapping: Record<string, string>;
|
||||
|
|
@ -51,7 +56,17 @@ type DefaultDataSourceName =
|
|||
| 'tooljetdbdefault'
|
||||
| 'workflowsdefault';
|
||||
|
||||
type NewRevampedComponent = 'Text' | 'TextInput' | 'PasswordInput' | 'NumberInput' | 'Table' | 'Button' | 'Checkbox' | 'Divider' | 'VerticalDivider' | 'Link';
|
||||
type NewRevampedComponent =
|
||||
| 'Text'
|
||||
| 'TextInput'
|
||||
| 'PasswordInput'
|
||||
| 'NumberInput'
|
||||
| 'Table'
|
||||
| 'Button'
|
||||
| 'Checkbox'
|
||||
| 'Divider'
|
||||
| 'VerticalDivider'
|
||||
| 'Link';
|
||||
|
||||
const DefaultDataSourceNames: DefaultDataSourceName[] = [
|
||||
'restapidefault',
|
||||
|
|
@ -82,7 +97,7 @@ export class AppImportExportService {
|
|||
protected appEnvironmentUtilService: AppEnvironmentUtilService,
|
||||
protected readonly entityManager: EntityManager,
|
||||
protected componentsService: ComponentsService
|
||||
) { }
|
||||
) {}
|
||||
|
||||
async export(user: User, id: string, searchParams: any = {}): Promise<{ appV2: App }> {
|
||||
// https://github.com/typeorm/typeorm/issues/3857
|
||||
|
|
@ -174,23 +189,41 @@ export class AppImportExportService {
|
|||
}
|
||||
|
||||
const pages = await manager
|
||||
.createQueryBuilder(Page, 'pages')
|
||||
.where('pages.appVersionId IN(:...versionId)', {
|
||||
.createQueryBuilder(Page, 'page')
|
||||
.leftJoinAndSelect('page.permissions', 'permission')
|
||||
.leftJoinAndSelect('permission.users', 'pageUser')
|
||||
.leftJoinAndSelect('pageUser.permissionGroup', 'permissionGroup')
|
||||
.where('page.appVersionId IN(:...versionId)', {
|
||||
versionId: appVersions.map((v) => v.id),
|
||||
})
|
||||
.orderBy('pages.created_at', 'ASC')
|
||||
.orderBy('page.created_at', 'ASC')
|
||||
.getMany();
|
||||
|
||||
const pagesWithPermissionGroups = pages.map((page) => {
|
||||
const groupPermission = page.permissions.find((perm) => perm.type === 'GROUP');
|
||||
|
||||
return {
|
||||
...page,
|
||||
permissions: groupPermission
|
||||
? {
|
||||
permissionGroup: groupPermission.users
|
||||
.map((user) => user.permissionGroup?.name)
|
||||
.filter((name): name is string => Boolean(name)),
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
const components =
|
||||
pages.length > 0
|
||||
? await manager
|
||||
.createQueryBuilder(Component, 'components')
|
||||
.leftJoinAndSelect('components.layouts', 'layouts')
|
||||
.where('components.pageId IN(:...pageId)', {
|
||||
pageId: pages.map((v) => v.id),
|
||||
})
|
||||
.orderBy('components.created_at', 'ASC')
|
||||
.getMany()
|
||||
.createQueryBuilder(Component, 'components')
|
||||
.leftJoinAndSelect('components.layouts', 'layouts')
|
||||
.where('components.pageId IN(:...pageId)', {
|
||||
pageId: pages.map((v) => v.id),
|
||||
})
|
||||
.orderBy('components.created_at', 'ASC')
|
||||
.getMany()
|
||||
: [];
|
||||
|
||||
const events = await manager
|
||||
|
|
@ -202,7 +235,7 @@ export class AppImportExportService {
|
|||
.getMany();
|
||||
|
||||
appToExport['components'] = components;
|
||||
appToExport['pages'] = pages;
|
||||
appToExport['pages'] = pagesWithPermissionGroups;
|
||||
appToExport['events'] = events;
|
||||
appToExport['dataQueries'] = dataQueries;
|
||||
appToExport['dataSources'] = dataSources;
|
||||
|
|
@ -800,6 +833,10 @@ export class AppImportExportService {
|
|||
});
|
||||
}
|
||||
|
||||
if (page.permissions) {
|
||||
pageCreated.permissions = page.permissions;
|
||||
}
|
||||
|
||||
appResourceMappings.pagesMapping[page.id] = pageCreated.id;
|
||||
|
||||
isHomePage = importingAppVersion.homePageId === page.id;
|
||||
|
|
@ -808,6 +845,9 @@ export class AppImportExportService {
|
|||
updateHomepageId = pageCreated.id;
|
||||
}
|
||||
|
||||
//create page permissions of page if flag enabled in dto
|
||||
await this.createPagePermissionsForGroups(pageCreated, user.organizationId, manager);
|
||||
|
||||
const pageComponents = importingComponents.filter((component) => component.pageId === page.id);
|
||||
|
||||
const newComponentIdsMap = {};
|
||||
|
|
@ -924,6 +964,7 @@ export class AppImportExportService {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
// relink page groups
|
||||
const updateArr = [];
|
||||
for (const { pageId, groupId } of pageGroupIdArr) {
|
||||
|
|
@ -1059,10 +1100,10 @@ export class AppImportExportService {
|
|||
const options =
|
||||
importingDataSource.kind === 'tooljetdb'
|
||||
? this.replaceTooljetDbTableIds(
|
||||
importingQuery.options,
|
||||
externalResourceMappings['tooljet_database'],
|
||||
organizationId
|
||||
)
|
||||
importingQuery.options,
|
||||
externalResourceMappings['tooljet_database'],
|
||||
organizationId
|
||||
)
|
||||
: importingQuery.options;
|
||||
|
||||
const newQuery = manager.create(DataQuery, {
|
||||
|
|
@ -1315,6 +1356,76 @@ export class AppImportExportService {
|
|||
return pageSettings;
|
||||
}
|
||||
|
||||
async checkIfGroupPermissionsExist(pages, organizationId) {
|
||||
const allGroupNames = new Set<string>();
|
||||
|
||||
for (const page of pages) {
|
||||
const groupNames = page.permissions?.permissionGroup || [];
|
||||
for (const name of groupNames) {
|
||||
allGroupNames.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!allGroupNames.size) return;
|
||||
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const existingGroups = await manager
|
||||
.createQueryBuilder(GroupPermissions, 'gp')
|
||||
.where('gp.name IN (:...names)', { names: Array.from(allGroupNames) })
|
||||
.andWhere('gp.organizationId = :organizationId', { organizationId })
|
||||
.select(['gp.name'])
|
||||
.getMany();
|
||||
|
||||
const existingGroupNames = new Set(existingGroups.map((g) => g.name));
|
||||
|
||||
const missingGroups = Array.from(allGroupNames).filter((name) => !existingGroupNames.has(name));
|
||||
|
||||
if (missingGroups.length > 0) {
|
||||
throw new HttpException(
|
||||
{
|
||||
message: { type: APP_ERROR_TYPE.IMPORT_EXPORT_SERVICE.PERMISSION_CHECK, data: missingGroups },
|
||||
},
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async createPagePermissionsForGroups(page, organizationId: string, manager: EntityManager) {
|
||||
const groupNames = page.permissions?.permissionGroup || [];
|
||||
if (!groupNames.length) return;
|
||||
|
||||
const existingGroups = await manager
|
||||
.createQueryBuilder(GroupPermissions, 'gp')
|
||||
.where('gp.name IN (:...names)', { names: groupNames })
|
||||
.andWhere('gp.organizationId = :organizationId', { organizationId })
|
||||
.getMany();
|
||||
|
||||
const groupMap = new Map(existingGroups.map((g) => [g.name, g]));
|
||||
|
||||
// Filter to only existing group names
|
||||
const validGroupNames = groupNames.filter((name) => groupMap.has(name));
|
||||
|
||||
// If no valid group names exist, do not create permissions
|
||||
if (!validGroupNames.length) return;
|
||||
|
||||
const permission = manager.create(PagePermission, {
|
||||
pageId: page.id,
|
||||
type: PAGE_PERMISSION_TYPE.GROUP,
|
||||
});
|
||||
|
||||
const savedPermission = await manager.save(permission);
|
||||
|
||||
const pageUsers = validGroupNames.map((name) =>
|
||||
manager.create(PageUser, {
|
||||
pagePermissionsId: savedPermission.id,
|
||||
permissionGroupsId: groupMap.get(name).id,
|
||||
})
|
||||
);
|
||||
|
||||
await manager.save(pageUsers);
|
||||
}
|
||||
|
||||
async createAppVersionsForImportedApp(
|
||||
manager: EntityManager,
|
||||
user: User,
|
||||
|
|
@ -1627,10 +1738,10 @@ export class AppImportExportService {
|
|||
options:
|
||||
dataSourceId == defaultDataSourceIds['tooljetdb']
|
||||
? this.replaceTooljetDbTableIds(
|
||||
query.options,
|
||||
externalResourceMappings['tooljet_database'],
|
||||
user.organizationId
|
||||
)
|
||||
query.options,
|
||||
externalResourceMappings['tooljet_database'],
|
||||
user.organizationId
|
||||
)
|
||||
: query.options,
|
||||
});
|
||||
await manager.save(newQuery);
|
||||
|
|
|
|||
|
|
@ -69,6 +69,18 @@ export class ImportExportResourcesService {
|
|||
let tableNameMapping = {};
|
||||
const imports = { app: [], tooljet_database: [], tableNameMapping: {} };
|
||||
const importingVersion = importResourcesDto.tooljet_version;
|
||||
const skipPagePermissionsGroupCheck = importResourcesDto.skip_page_permissions_group_check;
|
||||
|
||||
if (!isEmpty(importResourcesDto.app) && !skipPagePermissionsGroupCheck) {
|
||||
for (const appImportDto of importResourcesDto.app) {
|
||||
let appParams = appImportDto.definition;
|
||||
if (appParams?.appV2) {
|
||||
appParams = { ...appParams.appV2 };
|
||||
const pages = appParams?.pages;
|
||||
pages?.length && (await this.appImportExportService.checkIfGroupPermissionsExist(pages, user.organizationId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isEmpty(importResourcesDto.tooljet_database)) {
|
||||
const res = await this.tooljetDbImportExportService.bulkImport(importResourcesDto, importingVersion, cloning);
|
||||
|
|
|
|||
Loading…
Reference in a new issue