Merge pull request #13318 from ToolJet/release/workflows-fixes

### Changelog

#### Enhancements

* Added optional chaining for `renderCopilot` function call as it's not guaranteed to be present
* Added export option for workflows in AppMenu

#### Fixes

* Fixed scroll issue for large set of logs in response
* Removed 'Choose from Template' option from import menu
* Fixed loop icon issue in logs and import workflow modal text issues
* Initialized `asyncQueryRuns` in the state to avoid accessing undefined values
* Workflow schedule cleanup
* Updated subproject commit reference
* Fixed: update subproject commit reference

#### Chores

* Updated subproject commits for frontend and server
* Updated subproject commit reference in `server/ee`
* Updated base repo for submodule commit
This commit is contained in:
Akshay 2025-07-10 18:56:42 +05:30 committed by GitHub
commit d5c7d6d695
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 60 additions and 23 deletions

@ -1 +1 @@
Subproject commit 113d2ec5525571afd0a08187552a5b0d0979ecc3
Subproject commit 5ebcf5ccda1d11cb73e77b9a9bf71c16e1404c7b

View file

@ -34,6 +34,7 @@ const initialState = {
showDeleteConfirmation: false,
renamingQueryId: null,
deletingQueryId: null,
asyncQueryRuns: [],
};
export const createQueryPanelSlice = (set, get) => ({

View file

@ -94,6 +94,11 @@ export const AppMenu = function AppMenu({
<Field text={appType === 'module' ? 'Export module' : 'Export app'} onClick={exportApp} />
</>
)}
{canUpdateApp && canCreateApp && appType === 'workflow' && (
<>
<Field text={'Export workflow'} onClick={exportApp} />
</>
)}
{canDeleteApp && (
<Field
text={

View file

@ -478,7 +478,7 @@ class HomePageComponent extends React.Component {
let installedPluginsInfo = [];
try {
if (this.state.dependentPlugins.length) {
({ installedPluginsInfo =[] } = await pluginsService.installDependentPlugins(
({ installedPluginsInfo = [] } = await pluginsService.installDependentPlugins(
this.state.dependentPlugins,
true
));
@ -486,7 +486,8 @@ class HomePageComponent extends React.Component {
if (importJSON.app[0].definition.appV2.type !== this.props.appType) {
toast.error(
`${this.props.appType === 'module' ? 'App' : 'Module'} could not be imported in ${this.props.appType === 'module' ? 'modules' : 'apps'
`${this.props.appType === 'module' ? 'App' : 'Module'} could not be imported in ${
this.props.appType === 'module' ? 'modules' : 'apps'
} section. Switch to ${this.props.appType === 'module' ? 'apps' : 'modules'} section and try again.`,
{ style: { maxWidth: '425px' } }
);
@ -495,7 +496,7 @@ class HomePageComponent extends React.Component {
}
const data = await appsService.importResource(requestBody, this.props.appType);
toast.success(`${this.props.appType === 'module' ? 'Module' : 'App'} imported successfully.`);
toast.success(`${this.getAppType()} imported successfully.`);
this.setState({ isImportingApp: false });
if (!isEmpty(data.imports.app)) {
@ -1154,13 +1155,14 @@ class HomePageComponent extends React.Component {
closeModal: () => this.setState({ showImportAppModal: false }),
processApp: this.importFile,
show: this.openImportAppModal,
title: 'Import app',
actionButton: 'Import app',
title: `Import ${this.getAppType().toLocaleLowerCase()}`,
actionButton: `Import ${this.getAppType().toLocaleLowerCase()}`,
actionLoadingButton: 'Importing',
fileContent: fileContent,
selectedAppName: fileName,
dependentPluginsDetail: dependentPluginsDetail,
dependentPlugins: dependentPlugins,
appType: this.props.appType,
},
template: {
modalType: 'template',
@ -1224,8 +1226,9 @@ class HomePageComponent extends React.Component {
<div className="groups-list">
<div
className={`border rounded text-sm container ${missingGroupsExpanded ? 'max-h-48 overflow-y-auto' : ''
}`}
className={`border rounded text-sm container ${
missingGroupsExpanded ? 'max-h-48 overflow-y-auto' : ''
}`}
>
<div style={{ color: 'var(--text-placeholder)' }} className="tj-text-xsm font-weight-500">
User groups
@ -1301,8 +1304,8 @@ class HomePageComponent extends React.Component {
this.props.appType === 'workflow'
? 'homePage.deleteWorkflowAndData'
: this.props.appType === 'front-end'
? 'homePage.deleteAppAndData'
: deleteModuleText,
? 'homePage.deleteAppAndData'
: deleteModuleText,
{
appName: appToBeDeleted?.name,
}
@ -1567,10 +1570,11 @@ class HomePageComponent extends React.Component {
{this.props.appType === 'module'
? 'Create new module'
: this.props.t(
`${this.props.appType === 'workflow' ? 'workflowsDashboard' : 'homePage'
}.header.createNewApplication`,
'Create new app'
)}
`${
this.props.appType === 'workflow' ? 'workflowsDashboard' : 'homePage'
}.header.createNewApplication`,
'Create new app'
)}
</>
</Button>
<Dropdown.Toggle
@ -1631,8 +1635,8 @@ class HomePageComponent extends React.Component {
classes="mb-3 small"
limits={
workflowInstanceLevelLimit.current >= workflowInstanceLevelLimit.total ||
100 > workflowInstanceLevelLimit.percentage >= 90 ||
workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1
100 > workflowInstanceLevelLimit.percentage >= 90 ||
workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1
? workflowInstanceLevelLimit
: workflowWorkspaceLevelLimit
}
@ -1726,8 +1730,8 @@ class HomePageComponent extends React.Component {
appType={this.props.appType}
workflowsLimit={
workflowInstanceLevelLimit.current >= workflowInstanceLevelLimit.total ||
100 > workflowInstanceLevelLimit.percentage >= 90 ||
workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1
100 > workflowInstanceLevelLimit.percentage >= 90 ||
workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1
? workflowInstanceLevelLimit
: workflowWorkspaceLevelLimit
}

View file

@ -17,7 +17,7 @@ const BaseImportAppMenu = ({
const { t } = useTranslation();
return (
<Dropdown.Menu className="import-lg-position new-app-dropdown">
{appType !== 'wzorkflow' && appType !== 'module' && (
{appType !== 'workflow' && appType !== 'module' && (
<Dropdown.Item
className="homepage-dropdown-style tj-text tj-text-xsm"
onClick={showTemplateLibraryModal}

@ -1 +1 @@
Subproject commit 80886c9d5e30e738e9b7bb2e9acd42d20fcbf810
Subproject commit 2f0948893fdea517479ba3cc2da421a06dff6a64

View file

@ -32,6 +32,7 @@ export function defineWorkflowAbility(
FEATURE_KEY.RELEASE,
FEATURE_KEY.VALIDATE_PRIVATE_APP_ACCESS,
FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS,
FEATURE_KEY.UPDATE_ICON,
],
App
);
@ -56,6 +57,7 @@ export function defineWorkflowAbility(
FEATURE_KEY.RELEASE,
FEATURE_KEY.VALIDATE_PRIVATE_APP_ACCESS,
FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS,
FEATURE_KEY.UPDATE_ICON,
],
App
);

View file

@ -52,6 +52,11 @@ export class AppsModule extends SubModule {
'services/page.util.service',
]);
const { AppsActionsListener, TemporalService } = await this.getProviders(configs, 'workflows', [
'listeners/app-actions.listener',
'services/temporal.service',
]);
return {
module: AppsModule,
imports: [
@ -74,6 +79,8 @@ export class AppsModule extends SubModule {
VersionRepository,
AppsRepository,
AppGitRepository,
AppsActionsListener,
TemporalService,
PageService,
EventsService,
AppsUtilService,

View file

@ -42,6 +42,7 @@ import { AUDIT_LOGS_REQUEST_CONTEXT_KEY } from '@modules/app/constants';
import { MODULES } from '@modules/app/constants/modules';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { AppGitRepository } from '@modules/app-git/repository';
import { WorkflowSchedule } from '@entities/workflow_schedule.entity';
@Injectable()
export class AppsService implements IAppsService {
@ -181,7 +182,24 @@ export class AppsService implements IAppsService {
const { organizationId } = user;
const { id } = app;
await this.appRepository.delete({ id, organizationId });
await dbTransactionWrap(async (manager: EntityManager) => {
const schedules = await manager
.createQueryBuilder(WorkflowSchedule, 'workflowSchedule')
.innerJoinAndSelect('workflowSchedule.workflow', 'appVersion')
.where('appVersion.appId = :appId', { appId: id })
.getMany();
// Emit event with schedule IDs for temporal schedule cleanup
if (schedules.length > 0) {
const scheduleIds = schedules.map((schedule) => schedule.id);
this.eventEmitter.emit('app.deleted', {
appId: id,
scheduleIds: scheduleIds,
});
}
await manager.delete(App, { id, organizationId });
});
//APP_DELETE audit
RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, {

View file

@ -4,8 +4,8 @@ import { OnEvent } from '@nestjs/event-emitter';
@Injectable()
export class AppsActionsListener {
constructor() {}
@OnEvent('beforeAppDelete')
@OnEvent('app.deleted')
async handleAppDeletion(args: { appId: string }) {
throw new Error('Method not implemented');
console.log(`App with ID ${args.appId} has been deleted.`);
}
}