diff --git a/frontend/src/Editor/QueryManager/Components/DataSourceSelect.jsx b/frontend/src/Editor/QueryManager/Components/DataSourceSelect.jsx index 7e8865cdfe..d1afd378f4 100644 --- a/frontend/src/Editor/QueryManager/Components/DataSourceSelect.jsx +++ b/frontend/src/Editor/QueryManager/Components/DataSourceSelect.jsx @@ -7,7 +7,6 @@ import { getWorkspaceId, decodeEntities } from '@/_helpers/utils'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import { useDataSources, useGlobalDataSources, useSampleDataSource } from '@/_stores/dataSourcesStore'; import { useDataQueriesActions } from '@/_stores/dataQueriesStore'; -import { staticDataSources as staticDatasources } from '../constants'; import { useQueryPanelActions } from '@/_stores/queryPanelStore'; import Search from '@/_ui/Icon/solidIcons/Search'; import { Tooltip } from 'react-tooltip'; @@ -16,7 +15,7 @@ import { canCreateDataSource } from '@/_helpers'; import './../queryManager.theme.scss'; import { DATA_SOURCE_TYPE } from '@/_helpers/constants'; -function DataSourceSelect({ isDisabled, selectRef, closePopup, workflowDataSources, onNewNode, defaultDataSources }) { +function DataSourceSelect({ isDisabled, selectRef, closePopup, workflowDataSources, onNewNode, staticDataSources }) { const dataSources = useDataSources(); const globalDataSources = useGlobalDataSources(); const sampleDataSource = useSampleDataSource(); @@ -33,11 +32,6 @@ function DataSourceSelect({ isDisabled, selectRef, closePopup, workflowDataSourc closePopup(); }; - const workflowsEnabled = window.public_config?.ENABLE_WORKFLOWS_FEATURE == 'true'; - const staticDataSources = workflowsEnabled - ? staticDatasources - : staticDatasources.filter((ds) => ds?.kind !== 'workflows'); - useEffect(() => { const shouldAddSampleDataSource = !!sampleDataSource; const allDataSources = [...dataSources, ...globalDataSources, shouldAddSampleDataSource && sampleDataSource].filter( @@ -148,7 +142,7 @@ function DataSourceSelect({ isDisabled, selectRef, closePopup, workflowDataSourc ), isDisabled: true, - options: defaultDataSources?.map((source) => ({ + options: staticDataSources?.map((source) => ({ label: (
{' '} diff --git a/frontend/src/_helpers/constants.js b/frontend/src/_helpers/constants.js index 1c3c3d48b6..79275fc6df 100644 --- a/frontend/src/_helpers/constants.js +++ b/frontend/src/_helpers/constants.js @@ -129,6 +129,7 @@ export const DATA_SOURCE_TYPE = { LOCAL: 'local', GLOBAL: 'global', STATIC: 'static', + DEFAULT: 'default', }; export const SAMPLE_DB_KIND = { diff --git a/server/src/dto/app-version-update.dto.ts b/server/src/dto/app-version-update.dto.ts index 6c7aadb71c..cc83273ba0 100644 --- a/server/src/dto/app-version-update.dto.ts +++ b/server/src/dto/app-version-update.dto.ts @@ -26,4 +26,17 @@ export class AppVersionUpdateDto { @IsOptional() pageSettings: any; + + // Workflow related fields + @IsOptional() + @IsString() + @IsUUID() + currentEnvironmentId: string; + + @IsOptional() + definition: any; + + @IsOptional() + @IsBoolean() + is_user_switched_version: boolean; } diff --git a/server/src/modules/apps/dto/index.ts b/server/src/modules/apps/dto/index.ts index 63d93bd9d3..532740273a 100644 --- a/server/src/modules/apps/dto/index.ts +++ b/server/src/modules/apps/dto/index.ts @@ -1,6 +1,7 @@ import { sanitizeInput } from '@helpers/utils.helper'; -import { IsString, IsOptional, IsNotEmpty, MaxLength, IsBoolean, IsUUID } from 'class-validator'; +import { IsString, IsOptional, IsNotEmpty, MaxLength, IsBoolean, IsUUID, IsEnum } from 'class-validator'; import { Exclude, Expose, Transform } from 'class-transformer'; +import { APP_TYPES } from '../constants'; export class AppCreateDto { @IsNotEmpty() @@ -12,6 +13,11 @@ export class AppCreateDto { @IsString() @MaxLength(200, { message: 'Maximum length has been reached.' }) icon?: string; + + @IsNotEmpty() + @IsString() + @IsEnum(APP_TYPES, { message: 'Invalid app type.' }) + type: string; } export class AppUpdateDto { diff --git a/server/src/modules/apps/service.ts b/server/src/modules/apps/service.ts index 7ad0ca351a..60c6f8bc15 100644 --- a/server/src/modules/apps/service.ts +++ b/server/src/modules/apps/service.ts @@ -60,9 +60,9 @@ export class AppsService implements IAppsService { protected readonly aiUtilService: AiUtilService ) {} async create(user: User, appCreateDto: AppCreateDto) { - const { name, icon } = appCreateDto; + const { name, icon, type } = appCreateDto; return await dbTransactionWrap(async (manager: EntityManager) => { - const app = await this.appsUtilService.create(name, user, APP_TYPES.FRONT_END, manager); + const app = await this.appsUtilService.create(name, user, type, manager); const appUpdateDto = new AppUpdateDto(); appUpdateDto.name = name; diff --git a/server/src/modules/apps/util.service.ts b/server/src/modules/apps/util.service.ts index 7be1609b32..36def10913 100644 --- a/server/src/modules/apps/util.service.ts +++ b/server/src/modules/apps/util.service.ts @@ -11,7 +11,7 @@ import { NotAcceptableException, NotFoundException, } from '@nestjs/common'; -import { EntityManager, SelectQueryBuilder } from 'typeorm'; +import { EntityManager, MoreThan, SelectQueryBuilder } from 'typeorm'; import { v4 as uuidv4 } from 'uuid'; import { AppsRepository } from './repository'; import { AppVersion } from '@entities/app_version.entity'; @@ -36,6 +36,7 @@ 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'; @Injectable() export class AppsUtilService implements IAppsUtilService { @@ -281,6 +282,70 @@ export class AppsUtilService implements IAppsUtilService { }, manager); } + async updateWorflowVersion(version: AppVersion, body: AppVersionUpdateDto, app: App) { + const { name, currentEnvironmentId, definition } = body; + const { currentVersionId, organizationId } = app; + let currentEnvironment: AppEnvironment; + + if (version.id === currentVersionId && !body?.is_user_switched_version) + throw new BadRequestException('You cannot update a released version'); + + if (currentEnvironmentId || definition) { + currentEnvironment = await AppEnvironment.findOne({ + where: { id: version.currentEnvironmentId }, + }); + } + + const editableParams = {}; + if (name) { + //means user is trying to update the name + const versionNameExists = await this.versionRepository.findOne({ + where: { name, appId: version.appId }, + }); + + if (versionNameExists) { + throw new BadRequestException('Version name already exists.'); + } + editableParams['name'] = name; + } + + //check if the user is trying to promote the environment & raise an error if the currentEnvironmentId is not correct + if (currentEnvironmentId) { + if (!(await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.MULTI_ENVIRONMENT))) { + throw new BadRequestException('You do not have permissions to perform this action'); + } + + if (version.currentEnvironmentId !== currentEnvironmentId) { + throw new NotAcceptableException(); + } + const nextEnvironment = await AppEnvironment.findOne({ + select: ['id'], + where: { + priority: MoreThan(currentEnvironment.priority), + organizationId, + }, + order: { priority: 'ASC' }, + }); + editableParams['currentEnvironmentId'] = nextEnvironment.id; + } + + if (definition) { + const environments = await AppEnvironment.count({ + where: { + organizationId, + }, + }); + if (environments > 1 && currentEnvironment.priority !== 1 && !body?.is_user_switched_version) { + throw new BadRequestException('You cannot update a promoted version'); + } + editableParams['definition'] = definition; + } + + editableParams['updatedAt'] = new Date(); + + return await this.versionRepository.update(version.id, editableParams); + } + protected async getEnvironmentOfVersion(versionId: string, manager: EntityManager): Promise { return manager .createQueryBuilder(AppEnvironment, 'app_environments') diff --git a/server/src/modules/data-queries/service.ts b/server/src/modules/data-queries/service.ts index 874b3b0ee2..e8852da22d 100644 --- a/server/src/modules/data-queries/service.ts +++ b/server/src/modules/data-queries/service.ts @@ -191,11 +191,12 @@ export class DataQueriesService implements IDataQueriesService { async changeQueryDataSource(user: User, queryId: string, dataSource: DataSource, newDataSourceId: string) { return dbTransactionWrap(async (manager: EntityManager) => { - const newDataSource = await this.dataSourceRepository.findOne({ where: { id: newDataSourceId } }); - if (dataSource.kind !== newDataSource.kind) { - throw new BadRequestException(); - } - return this.dataQueryRepository.updateOne(queryId, { dataSourceId: newDataSourceId }, manager); + const newDataSource = await this.dataSourceRepository.findOneOrFail({ where: { id: newDataSourceId } }); + // FIXME: Disabling this check as workflows can change data source of a query with different kind + // if (dataSource.kind !== newDataSource.kind && dataSource) { + // throw new BadRequestException(); + // } + return this.dataQueryRepository.updateOne(queryId, { dataSourceId: newDataSource.id }, manager); // TODO: Audit logs }); diff --git a/server/src/modules/versions/service.ts b/server/src/modules/versions/service.ts index c4e263456f..e50b66b50b 100644 --- a/server/src/modules/versions/service.ts +++ b/server/src/modules/versions/service.ts @@ -175,6 +175,10 @@ export class VersionService implements IVersionService { await this.versionsUtilService.updateVersion(appVersion, appVersionUpdateDto); + if (app.type === 'workflow') { + await this.appUtilService.updateWorflowVersion(appVersion, appVersionUpdateDto, app); + } + this.eventEmitter.emit('auditLogEntry', { userId: user.id, organizationId: user.organizationId, diff --git a/server/src/modules/workflows/module.ts b/server/src/modules/workflows/module.ts index 1e39e5123c..e71c52df50 100644 --- a/server/src/modules/workflows/module.ts +++ b/server/src/modules/workflows/module.ts @@ -98,6 +98,7 @@ export class WorkflowsModule { UserRepository, DataSourcesRepository, DataQueryRepository, + DataSourcesRepository, OrganizationConstantRepository, VersionRepository, AppsService,