Getting started with Issue Tracker
This guide explains the minimal steps to integrate Issue Tracker in a new application.
Requirements
Issue Tracker 2.0 requires Java 21 and Vaadin 25. It also requires a PostgreSQL database; the schema is generated on first run.
Prepare the Application
A new application created on start.vaadin.com can be used as a starting point for this tutorial. Open the site, select an empty project, choose Spring Boot as the framework, and download the generated archive. Extract it, import it into your IDE of choice, and verify the application starts correctly by running the main class.
Add the AppJars Repository
AppJars artifacts are published to the public AppJars Maven repository. Add it to your pom.xml so that Maven can resolve the AppJars dependencies:
<repositories>
<repository>
<id>appjars</id>
<name>AppJars Public Repository</name>
<url>https://maven.appjars.com</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
Add the Dependencies
Given that it is a monolithic application, the following dependencies representing the three layers of the appjar must be added:
<dependency>
<groupId>com.appjars</groupId>
<artifactId>appjars-issue-tracker-flow</artifactId>
</dependency>
<dependency>
<groupId>com.appjars</groupId>
<artifactId>appjars-issue-tracker-data-impl</artifactId>
</dependency>
<dependency>
<groupId>com.appjars</groupId>
<artifactId>appjars-issue-tracker-business-impl</artifactId>
</dependency>
After adding them, build the application to confirm that the dependencies are resolved correctly.
Modify the Main Application Class
The following annotations must be added to the main application class:
@EnableVaadin({"com.appjars.issuetracker.flow", "com.example.application"})
@EnableJpaRepositories(basePackages = {"com.appjars.issuetracker.dao.repository"})
@ComponentScan(basePackageClasses = {IssueTrackerAutoConfiguration.class, AppJarsAutoConfiguration.class, Application.class})
- @EnableVaadin: Instructs Vaadin to process the specified packages for views and components. The first entry covers the Issue Tracker package; the second covers the application's own views.
- @EnableJpaRepositories: Instructs Spring Data to scan the Issue Tracker repository package so that its JPA repositories are registered as beans.
- @ComponentScan: Instructs Spring to load the beans provided by the appjar as well as the beans of the application itself.
The import for @EnableJpaRepositories is org.springframework.data.jpa.repository.config.EnableJpaRepositories. The imports for the classes referenced in @ComponentScan are:
com.appjars.issuetracker.IssueTrackerAutoConfigurationcom.appjars.AppJarsAutoConfiguration
If the application already contains JPA entities, add an @EntityScan annotation that includes both the application's entity packages and the Issue Tracker module class:
@EntityScan(basePackageClasses = {IssueTrackerAutoConfiguration.class, YourEntity.class})
Then inject the RouteConfigurer provided by Issue Tracker and configure the router layout in a @PostConstruct method so that the appjar views share the same layout as the rest of the application:
@Autowired
@Qualifier("IssueTrackerRouteConfigurer")
RouteConfigurer routeConfigurer;
@PostConstruct
public void configure() {
routeConfigurer.setViewsRouterLayout(MainLayout.class);
}
The import for RouteConfigurer is com.appjars.issuetracker.flow.util.RouteConfigurer.
Implement LoggedInUsernameProvider
Issue Tracker requires the application to supply the username of the currently authenticated user. Implement the LoggedInUsernameProvider interface and register it as a Spring bean:
@Service
public class UserSessionUtils implements LoggedInUsernameProvider {
private final AuthenticationContext authenticationContext;
public UserSessionUtils(AuthenticationContext authenticationContext) {
this.authenticationContext = authenticationContext;
}
@Override
@Transactional
public Optional<String> getLoggedInUsername() {
return authenticationContext
.getAuthenticatedUser(UserDetails.class)
.map(UserDetails::getUsername);
}
}
The import for LoggedInUsernameProvider is com.appjars.issuetracker.business.service.LoggedInUsernameProvider.
If the application already uses User Manager, the existing AuthenticatedUser component can implement this interface directly by adding the getLoggedInUsername() method, rather than creating a separate service class.
Add the Views to the Navigation
Issue Tracker provides views for regular users and views for administrators. Add them to the application's navigation menu by inserting the following snippet at the end of the method createNavigation() in MainLayout, before returning nav:
SideNavItem issueTrackerItem = new SideNavItem("Issue Tracker");
issueTrackerItem.setPrefixComponent(LineAwesomeIcon.BUG_SOLID.create());
if (accessChecker.hasAccess(IssueListComponent.class)) {
issueTrackerItem.addItem(new SideNavItem("Issues", IssueListComponent.class, LineAwesomeIcon.LIST_ALT_SOLID.create()));
}
if (accessChecker.hasAccess(ProjectListView.class)) {
issueTrackerItem.addItem(new SideNavItem("Projects", ProjectListView.class, LineAwesomeIcon.PROJECT_DIAGRAM_SOLID.create()));
}
if (accessChecker.hasAccess(TimeEntryListView.class)) {
issueTrackerItem.addItem(new SideNavItem("Spent Time", TimeEntryListView.class, LineAwesomeIcon.CLOCK_SOLID.create()));
}
if (accessChecker.hasAccess(ProjectActivityView.class)) {
issueTrackerItem.addItem(new SideNavItem("Activity", ProjectActivityView.class, LineAwesomeIcon.STREAM_SOLID.create()));
}
SideNavItem adminItem = new SideNavItem("Administration");
adminItem.setPrefixComponent(LineAwesomeIcon.TOOLS_SOLID.create());
if (accessChecker.hasAccess(UserListView.class)) {
adminItem.addItem(new SideNavItem("Users", UserListView.class, LineAwesomeIcon.USER_SOLID.create()));
}
if (accessChecker.hasAccess(GroupListView.class)) {
adminItem.addItem(new SideNavItem("Groups", GroupListView.class, LineAwesomeIcon.USERS_SOLID.create()));
}
if (accessChecker.hasAccess(RoleListView.class)) {
adminItem.addItem(new SideNavItem("Roles", RoleListView.class, LineAwesomeIcon.THEATER_MASKS_SOLID.create()));
}
if (accessChecker.hasAccess(TrackerListView.class)) {
adminItem.addItem(new SideNavItem("Trackers", TrackerListView.class, LineAwesomeIcon.TAG_SOLID.create()));
}
if (accessChecker.hasAccess(IssueStatusListView.class)) {
adminItem.addItem(new SideNavItem("Issue Statuses", IssueStatusListView.class, LineAwesomeIcon.TOGGLE_ON_SOLID.create()));
}
if (accessChecker.hasAccess(WorkflowSummaryView.class)) {
adminItem.addItem(new SideNavItem("Workflows", WorkflowSummaryView.class, LineAwesomeIcon.SITEMAP_SOLID.create()));
}
if (accessChecker.hasAccess(CustomFieldListView.class)) {
adminItem.addItem(new SideNavItem("Custom Fields", CustomFieldListView.class, LineAwesomeIcon.TH_LIST_SOLID.create()));
}
if (accessChecker.hasAccess(EnumerationListView.class)) {
adminItem.addItem(new SideNavItem("Enumerations", EnumerationListView.class, LineAwesomeIcon.LIST_SOLID.create()));
}
if (accessChecker.hasAccess(SettingsView.class)) {
adminItem.addItem(new SideNavItem("Settings", SettingsView.class, LineAwesomeIcon.COG_SOLID.create()));
}
if (adminItem.getChildren().findAny().isPresent()) {
issueTrackerItem.addItem(adminItem);
}
nav.addItem(issueTrackerItem);
The imports for the view classes are:
com.appjars.issuetracker.flow.component.IssueListComponentcom.appjars.issuetracker.flow.view.ProjectListViewcom.appjars.issuetracker.flow.view.TimeEntryListViewcom.appjars.issuetracker.flow.view.ProjectActivityViewcom.appjars.issuetracker.flow.view.UserListViewcom.appjars.issuetracker.flow.view.GroupListViewcom.appjars.issuetracker.flow.view.RoleListViewcom.appjars.issuetracker.flow.view.TrackerListViewcom.appjars.issuetracker.flow.view.IssueStatusListViewcom.appjars.issuetracker.flow.view.WorkflowSummaryViewcom.appjars.issuetracker.flow.view.CustomFieldListViewcom.appjars.issuetracker.flow.view.EnumerationListViewcom.appjars.issuetracker.flow.view.SettingsView
Configure Application Properties
Issue Tracker requires a PostgreSQL database. Add the datasource configuration to application.properties:
spring.datasource.url=jdbc:postgresql://localhost:5432/myapp
spring.datasource.username=myapp
spring.datasource.password=secret
spring.datasource.driverClassName=org.postgresql.Driver
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.defer-datasource-initialization=true
spring.sql.init.mode=never
Issue Tracker sends email notifications for issue assignments and updates. Configure the SMTP connection:
spring.mail.host=localhost
spring.mail.port=587
spring.mail.protocol=smtp
spring.mail.username=username
spring.mail.password=secret
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
Issue Tracker supports file attachments on issues. Increase the multipart upload limits to allow meaningful file sizes:
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=100MB
Finally, add com.flowingcode and com.appjars to the list of whitelisted packages:
vaadin.allowed-packages = com.vaadin,org.vaadin,dev.hilla,com.example.application,com.flowingcode,com.appjars
Authentication
By default Issue Tracker owns user authentication: it stores the password hashes and offers user creation from its own Users view. If the application authenticates users elsewhere — through an existing identity provider, or the User Manager AppJar — disable that behaviour:
appjars.issuetracker.auth.enabled=false
The authentication fields and the New user button are then hidden, and the application becomes responsible for provisioning an Issue Tracker user for every principal it authenticates. See Authentication ownership for the full details.
Testing the Application
Start the application by running the main Spring Boot class. Issue Tracker creates the database tables on first run using the JPA schema generation.
After the application starts, log in and navigate to Administration > Settings to verify the appjar is loaded. Then navigate to Administration > Projects and create the first project. Once a project exists, open it and create an issue to confirm the full workflow is operational.