Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Thumbs.db
.scannerwork/
out/
package-lock.json
pnpm-lock.yaml
Comment on lines 53 to +54
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .gitignore | sed -n '50,60p'

Repository: netgrif/application-builder

Length of output: 480


🏁 Script executed:

git log --oneline -20 -- .gitignore

Repository: netgrif/application-builder

Length of output: 102


🏁 Script executed:

ls -la | grep -E "(package\.json|pnpm|yarn|npm|lockfile)"

Repository: netgrif/application-builder

Length of output: 203


🏁 Script executed:

cat pnpm-workspace.yaml

Repository: netgrif/application-builder

Length of output: 174


🏁 Script executed:

cat package.json

Repository: netgrif/application-builder

Length of output: 5103


🏁 Script executed:

git log -p --follow -- .gitignore | grep -A 10 -B 10 "pnpm-lock.yaml" | head -50

Repository: netgrif/application-builder

Length of output: 53


🏁 Script executed:

ls -la pnpm-lock.yaml 2>&1

Repository: netgrif/application-builder

Length of output: 132


🏁 Script executed:

find . -maxdepth 1 -name "*lock*" -type f

Repository: netgrif/application-builder

Length of output: 53


🏁 Script executed:

git log --all --oneline | head -5

Repository: netgrif/application-builder

Length of output: 151


🏁 Script executed:

git show --stat 3c4b707 | head -30

Repository: netgrif/application-builder

Length of output: 1620


Remove pnpm-lock.yaml from .gitignore to ensure reproducible builds.

This project uses pnpm (evidenced by pnpm-workspace.yaml), making pnpm-lock.yaml the lock file that should be committed to ensure all developers and CI environments use identical dependency versions. Ignoring it leads to non-reproducible builds and "works on my machine" issues.

The package-lock.json entry is appropriate to ignore since the project uses pnpm, not npm.

🤖 Prompt for AI Agents
In @.gitignore around lines 53 - 54, Remove the pnpm lockfile from .gitignore so
the repository tracks pnpm-lock.yaml: edit .gitignore to delete the line
"pnpm-lock.yaml" (leave "package-lock.json" in place), add pnpm-lock.yaml to the
index and commit the lockfile so CI and all developers use the same dependency
versions.

### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@netgrif/application-builder",
"version": "2.1.0-RC.2",
"version": "2.1.0-RC.3",
"description": "Netgrif Application Builder for building, configuring and modeling applications for Application Engine.",
"homepage": "https://builder.netgrif.com",
"license": "SEE LICENSE IN LICENSE FILE",
Expand Down
187 changes: 90 additions & 97 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,105 +1,98 @@
<div class="app-container">
<mat-sidenav-container class="app-mat-sidenav-container" fxLayout="column">
<mat-sidenav fxFlex class="app-mat-viewnav" mode="side" disableClose="true" opened>
<div fxFlex fxLayout="column">
<a [href]="config.services.urls.netgrif" target="_blank" class="logo">
<img src="../assets/netgrif_logo.svg" alt="Netgrif logo" class="main-toolbar-logo">
</a>
<div fxLayout="column" fxLayoutAlign="start center" fxFlex="100"
style="padding-top: 4px; padding-bottom: 4px; overflow-y: hidden;">
<mat-action-list class="menu-items">
<mat-list-item class="field-list-mat-list-item"
(click)="openApplicationDialog()"
matTooltip="Application" matTooltipPosition="right"
style="margin-bottom: 4px;">
<mat-icon>app_registration</mat-icon>
</mat-list-item>
</mat-action-list>
<div fxFlex style="overflow-y: auto;">
<mat-action-list class="menu-items">
@for (process of applicationService.models; track process[0]) {
<!--TODO trieda active-process nefunguje pri prvotnej inicializácii-->
<mat-list-item class="field-list-mat-list-item"
[class.active-process]="process[0] === modelService.model.id"
(click)="switchToProcess(process[0])"
[matTooltip]="'['+process[0]+'] '+process[1].title.value"
matTooltipPosition="right">
<mat-icon>{{ process[1].icon }}</mat-icon>
</mat-list-item>
}
<mat-list-item class="field-list-mat-list-item"
(click)="applicationService.addNewEmptyModel()"
matTooltip="Add new process" matTooltipPosition="right"
style="margin: 4px 0;">
<mat-icon>add</mat-icon>
</mat-list-item>
</mat-action-list>
</div>
<div style="overflow-y: hidden;">
<mat-divider style="width: 80%;margin: auto;padding: 4px 0;"></mat-divider>
<mat-action-list class="menu-items">
<mat-list-item class="field-list-mat-list-item"
joyrideStep="{{tutorial.demo.step}}"
title="{{tutorial.demo.title}}"
text="{{tutorial.demo.text}}"
stepPosition="{{tutorial.demo.position}}"
(click)="openInTab(config.services.urls.engine)"
matTooltip="Netgrif Demo" matTooltipPosition="right">
<mat-icon>play_arrow</mat-icon>
</mat-list-item>
<mat-list-item class="field-list-mat-list-item"
joyrideStep="{{tutorial.youtube.step}}"
title="{{tutorial.youtube.title}}"
text="{{tutorial.youtube.text}}"
stepPosition="{{tutorial.youtube.position}}"
(click)="openInTab(config.services.urls.youtube)"
matTooltip="Youtube Channel" matTooltipPosition="right">
<mat-icon svgIcon="youtube"></mat-icon>
</mat-list-item>
<mat-list-item class="field-list-mat-list-item"
joyrideStep="{{tutorial.github.step}}"
title="{{tutorial.github.title}}"
text="{{tutorial.github.text}}"
stepPosition="{{tutorial.github.position}}"
(click)="openInTab(config.services.urls.github)"
matTooltip="Github" matTooltipPosition="right">
<mat-icon svgIcon="github">device_hub</mat-icon>
</mat-list-item>
<mat-list-item class="field-list-mat-list-item"
joyrideStep="{{tutorial.mortgage.step}}"
title="{{tutorial.mortgage.title}}"
text="{{tutorial.mortgage.text}}"
stepPosition="{{tutorial.mortgage.position}}"
(click)="addMortgage()"
matTooltip="Mortgage demo" matTooltipPosition="right">
<mat-icon>device_hub</mat-icon>
</mat-list-item>
</mat-action-list>
<mat-divider style="width: 80%;margin: auto;padding: 4px 0;"></mat-divider>
<mat-action-list class="menu-items">
<mat-list-item class="field-list-mat-list-item"
joyrideStep="{{tutorial.bug.step}}"
title="{{tutorial.bug.title}}"
text="{{tutorial.bug.text}}"
stepPosition="{{tutorial.bug.position}}"
(done)="tutorial.onClose()"
(click)="openInTab(config.services.urls.issues)"
matTooltip="Report a bug" matTooltipPosition="right">
<mat-icon>bug_report</mat-icon>
</mat-list-item>
<mat-list-item class="field-list-mat-list-item"
(click)="help()"
matTooltip="Help" matTooltipPosition="right">
<mat-icon>help</mat-icon>
</mat-list-item>
</mat-action-list>
</div>
</div>
</div>
<mat-sidenav-container class="app-mat-sidenav-container">
<mat-sidenav class="app-mat-viewnav" mode="side" disableClose="true" opened>
<a [href]="config.services.urls.netgrif" target="_blank" class="logo">
<img src="../assets/netgrif_logo.svg" alt="Netgrif logo" class="main-toolbar-logo">
</a>
<mat-action-list class="menu-items">
<mat-list-item class="field-list-mat-list-item"
joyrideStep="{{tutorial.demo.step}}"
title="{{tutorial.demo.title}}"
text="{{tutorial.demo.text}}"
stepPosition="{{tutorial.demo.position}}"
(click)="openInTab(config.services.urls.engine)"
matTooltip="Netgrif Demo" matTooltipPosition="right">
<mat-icon>play_arrow</mat-icon>
</mat-list-item>
<mat-list-item class="field-list-mat-list-item"
joyrideStep="{{tutorial.youtube.step}}"
title="{{tutorial.youtube.title}}"
text="{{tutorial.youtube.text}}"
stepPosition="{{tutorial.youtube.position}}"
(click)="openInTab(config.services.urls.youtube)"
matTooltip="Youtube Channel" matTooltipPosition="right">
<mat-icon svgIcon="youtube"></mat-icon>
</mat-list-item>
<mat-list-item class="field-list-mat-list-item"
joyrideStep="{{tutorial.github.step}}"
title="{{tutorial.github.title}}"
text="{{tutorial.github.text}}"
stepPosition="{{tutorial.github.position}}"
(click)="openInTab(config.services.urls.github)"
matTooltip="Github" matTooltipPosition="right">
<mat-icon svgIcon="github">device_hub</mat-icon>
</mat-list-item>
<mat-list-item class="field-list-mat-list-item"
joyrideStep="{{tutorial.mortgage.step}}"
title="{{tutorial.mortgage.title}}"
text="{{tutorial.mortgage.text}}"
stepPosition="{{tutorial.mortgage.position}}"
(click)="addMortgage()"
matTooltip="Mortgage demo" matTooltipPosition="right">
<mat-icon>device_hub</mat-icon>
</mat-list-item>
</mat-action-list>
<mat-action-list class="app-viewnaw-bottom-list menu-items">
<mat-list-item class="field-list-mat-list-item"
joyrideStep="{{tutorial.bug.step}}"
title="{{tutorial.bug.title}}"
text="{{tutorial.bug.text}}"
stepPosition="{{tutorial.bug.position}}"
(done)="tutorial.onClose()"
(click)="openInTab(config.services.urls.issues)"
matTooltip="Report a bug" matTooltipPosition="right">
<mat-icon>bug_report</mat-icon>
</mat-list-item>
<mat-list-item class="field-list-mat-list-item"
(click)="help()"
matTooltip="Help" matTooltipPosition="right">
<mat-icon>help</mat-icon>
</mat-list-item>
</mat-action-list>
</mat-sidenav>
<joyrideStep joyrideStep="{{tutorial.welcome.step}}" stepPosition="{{tutorial.welcome.position}}"
title="{{tutorial.welcome.title}}" text="{{tutorial.welcome.text}}"
(next)="tutorial.welcome.onNext()"></joyrideStep>
<mat-tab-group mat-stretch-tabs="false" mat-align-tabs="start" animationDuration="0ms" class="app-tabs"
[selectedIndex]="activeProcessIndex()"
(selectedTabChange)="changeTab($event)">
<mat-tab disabled="true">
<ng-template mat-tab-label>
<button mat-icon-button (click)="openApplicationDialog()">
<mat-icon class="tab-icon" matTooltip="Application" style="color: rgba(0,0,0,0.6)!important;">
app_registration
</mat-icon>
</button>
</ng-template>
</mat-tab>
@for (process of applicationService.models; track process[0]) {
<mat-tab label="{{process[0]}}">
<ng-template mat-tab-label>
@if (process[1].icon) {
<mat-icon class="tab-icon">{{ process[1].icon }}</mat-icon>
}
{{ process[1].title.value }} [{{ process[1].id }}]
</ng-template>
</mat-tab>
Comment on lines +78 to +86
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the applicationService and how models is defined
fd "\.ts$" | xargs rg -l "applicationService|class.*Service" | head -20

Repository: netgrif/application-builder

Length of output: 1088


🏁 Script executed:

#!/bin/bash
# Check the switchActiveModel method signature and its usage
rg -n "switchActiveModel" --type ts -B 2 -A 5

Repository: netgrif/application-builder

Length of output: 3668


🏁 Script executed:

#!/bin/bash
# Find where applicationService.models is used and how it's structured
rg -n "applicationService\.models" --type ts -B 2 -A 2

Repository: netgrif/application-builder

Length of output: 2712


🏁 Script executed:

#!/bin/bash
# Find the models getter in application.service.ts
rg -n "get models" --type ts -A 15 src/app/project-builder/application.service.ts

Repository: netgrif/application-builder

Length of output: 486


🏁 Script executed:

#!/bin/bash
# Also check how models is used in the component - does it iterate over entries?
cat -n src/app/app.component.html | sed -n '75,95p'

Repository: netgrif/application-builder

Length of output: 1101


Tab label mismatch breaks tab switching—textLabel does not match model ID format.

applicationService.models is a Map<string, PetriNet>. When iterating over it with @for, each process is [key, value] where process[0] is the model ID. However, the visible tab label (defined in ng-template mat-tab-label at lines 81-85) displays "{{ process[1].title.value }} [{{ process[1].id }}]", not the model ID.

In app.component.ts, changeTab() calls switchToProcess(tabEvent.tab.textLabel), which passes the visible label text (e.g., "Title [model-1]") to switchActiveModel(processId). Since switchActiveModel expects only the model ID key (e.g., "model-1"), the Map lookup this._models.get(processId) fails silently because the formatted string is not a key in the Map.

Fix: Use tabEvent.tab.labelContent.textContent or extract the model ID from tabEvent.index instead of relying on textLabel.

🤖 Prompt for AI Agents
In `@src/app/app.component.html` around lines 78 - 86, The tab label text (used by
changeTab()) contains a formatted title "[id]" string, so passing
tabEvent.tab.textLabel to switchToProcess()/switchActiveModel() fails to find
the Map key; update changeTab() to derive the model id from the tab index
instead of textLabel: use tabEvent.index to look up the key from
applicationService.models (e.g.
Array.from(applicationService.models.keys())[tabEvent.index]) and pass that id
to switchToProcess()/switchActiveModel(); alternatively, if you prefer parsing,
extract only the id from tabEvent.tab.labelContent.textContent before calling
switchToProcess(), but do not rely on tabEvent.tab.textLabel.

}
<mat-tab disabled="true">
<ng-template mat-tab-label>
<button mat-icon-button (click)="addNewEmptyModel()">
<mat-icon>add</mat-icon>
</button>
</ng-template>
</mat-tab>
</mat-tab-group>
<router-outlet></router-outlet>
</mat-sidenav-container>
</div>
10 changes: 5 additions & 5 deletions src/app/app.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
.logo {
display: flex;
justify-content: center;
padding-top: 0;
padding-top: 1px;
}

mat-action-list {
Expand Down Expand Up @@ -86,12 +86,12 @@
padding-top: 0;

.mdc-list-item {
//padding-left: 9px;
//padding-right: 9px;
padding-left: 9px;
padding-right: 9px;
height: 42px;
}
}

.active-process {
background-color: map-get(netgif-theme.$netgrif-blue, 900);
.tab-icon {
margin-right: 8px;
}
70 changes: 30 additions & 40 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
import {AfterViewInit, Component, HostListener} from '@angular/core';
import {AfterViewInit, Component, HostListener, OnInit} from '@angular/core';
import {MatDialog} from '@angular/material/dialog';
import {Router} from '@angular/router';
import {NetgrifApplicationEngine} from '@netgrif/components-core/';
import {JoyrideService} from 'ngx-joyride';
import {AppBuilderConfigurationService} from './app-builder-configuration.service';
import {DialogApplicationEditComponent} from './dialogs/dialog-application-edit/dialog-application-edit.component';
import {DialogConfirmComponent} from './dialogs/dialog-confirm/dialog-confirm.component';
import {DialogIntroComponent} from './dialogs/dialog-intro/dialog-intro.component';
import {ModelImportService} from './modeler/model-import-service';
import {MortgageService} from './modeler/mortgage.service';
import {ModelService} from './modeler/services/model/model.service';
import {ApplicationService} from './project-builder/application.service';
import {DatabaseStorageService} from './project-builder/database-storage.service';
import {TutorialService} from './tutorial/tutorial-service';
import {MatTabChangeEvent} from '@angular/material/tabs';

@Component({
selector: 'nab-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements AfterViewInit {
export class AppComponent implements OnInit, AfterViewInit {
title = 'Netgrif Application Builder';
config: NetgrifApplicationEngine;

Expand All @@ -30,46 +28,33 @@ export class AppComponent implements AfterViewInit {

constructor(
config: AppBuilderConfigurationService,
private router: Router,
private matDialog: MatDialog,
private readonly joyrideService: JoyrideService,
private _mortgageService: MortgageService,
private tutorialService: TutorialService,
private importService: ModelImportService,
private db: DatabaseStorageService,
public modelService: ModelService,
public applicationService: ApplicationService,
) {
this.config = config.get();
}

ngOnInit(): void {
this.applicationService.createApplication();
this.applicationService.switchToFirst();
}

ngAfterViewInit(): void {
// TODO: NAB-326 https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API
this.matDialog.open(DialogIntroComponent, {
width: '40%',
panelClass: 'dialog-width-40',
disableClose: true,
data: this.db.getAllApplications(),
});

/*const oldModel = localStorage.getItem(ModelerConfig.LOCALSTORAGE.DRAFT_MODEL.KEY);
if (!oldModel) {
return;
const apps = this.db.getAllApplications();
if (apps && apps.length > 0) {
this.matDialog.open(DialogIntroComponent, {
width: '40%',
panelClass: 'dialog-width-40',
disableClose: true,
data: apps,
});
}
const dialogRef = this.matDialog.open(DialogLocalStorageModelComponent, {
data: {
id: localStorage.getItem(ModelerConfig.LOCALSTORAGE.DRAFT_MODEL.ID),
timestamp: localStorage.getItem(ModelerConfig.LOCALSTORAGE.DRAFT_MODEL.TIMESTAMP),
title: localStorage.getItem(ModelerConfig.LOCALSTORAGE.DRAFT_MODEL.TITLE),
},
});
dialogRef.afterClosed().subscribe(result => {
if (result === true) {
this.importService.importFromXml(oldModel);
} else if (result === false) {
localStorage.clear();
}
});*/
}

openApplicationDialog() {
Expand All @@ -80,14 +65,7 @@ export class AppComponent implements AfterViewInit {
}

addMortgage() {
const dialogRef = this.matDialog.open(DialogConfirmComponent);

dialogRef.afterClosed().subscribe(result => {
if (result === true) {
this._mortgageService.loadModel();
this.router.navigate(['/modeler']);
}
});
this._mortgageService.loadModel();
}

help() {
Expand All @@ -105,8 +83,20 @@ export class AppComponent implements AfterViewInit {
window.open(url, '_blank');
}

switchToProcess(processId: string) {
switchToProcess(processId: string): void {
this.applicationService.switchActiveModel(processId);
}

changeTab(tabEvent: MatTabChangeEvent): void {
this.switchToProcess(tabEvent.tab.textLabel);
}
Comment on lines +90 to +92
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Tab switching relies on textLabel matching model ID.

The changeTab method uses tabEvent.tab.textLabel to identify which process to switch to. This works because the template sets label="{{process[0]}}" on each tab. However, this coupling between the template's label attribute and the switching logic is fragile—if the label format changes, tab switching will break silently.

Consider using the tab index (minus offsets for disabled tabs) to look up the model directly.

Alternative approach using index
 changeTab(tabEvent: MatTabChangeEvent): void {
-    this.switchToProcess(tabEvent.tab.textLabel);
+    const modelIndex = tabEvent.index - 1; // Account for first disabled tab
+    const models = Array.from(this.applicationService.models.values());
+    if (modelIndex >= 0 && modelIndex < models.length) {
+        this.switchToProcess(models[modelIndex].id);
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
changeTab(tabEvent: MatTabChangeEvent): void {
this.switchToProcess(tabEvent.tab.textLabel);
}
changeTab(tabEvent: MatTabChangeEvent): void {
const modelIndex = tabEvent.index - 1; // Account for first disabled tab
const models = Array.from(this.applicationService.models.values());
if (modelIndex >= 0 && modelIndex < models.length) {
this.switchToProcess(models[modelIndex].id);
}
}
🤖 Prompt for AI Agents
In `@src/app/app.component.ts` around lines 90 - 92, The changeTab method
currently relies on tabEvent.tab.textLabel to find a model, which is fragile;
update changeTab to use tabEvent.index to look up the corresponding model from
your in-memory list (e.g., the array used to render the tabs) and call
switchToProcess with that model's id, adjusting for any disabled/hidden tabs or
header offsets when computing the index; modify changeTab (and any template
bindings that assume label==id) to reference the model array and ensure
switchToProcess receives the canonical model id rather than the displayed
textLabel.


addNewEmptyModel(): void {
const model = this.applicationService.addNewEmptyModel();
this.applicationService.switchActiveModel(model.id);
}

activeProcessIndex(): number {
return this.applicationService.modelList.indexOf(this.applicationService.getActiveModel()) + 1;
}
}
Loading