Skip to content

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.IssueTrackerAutoConfiguration
  • com.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.IssueListComponent
  • com.appjars.issuetracker.flow.view.ProjectListView
  • com.appjars.issuetracker.flow.view.TimeEntryListView
  • com.appjars.issuetracker.flow.view.ProjectActivityView
  • com.appjars.issuetracker.flow.view.UserListView
  • com.appjars.issuetracker.flow.view.GroupListView
  • com.appjars.issuetracker.flow.view.RoleListView
  • com.appjars.issuetracker.flow.view.TrackerListView
  • com.appjars.issuetracker.flow.view.IssueStatusListView
  • com.appjars.issuetracker.flow.view.WorkflowSummaryView
  • com.appjars.issuetracker.flow.view.CustomFieldListView
  • com.appjars.issuetracker.flow.view.EnumerationListView
  • com.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.