public String getExcelDownloadSuggestedFilename() {
    if (!lastUsedTimerangeProvider.isPresent())
      throw ExceptionUtil.getIllegalStateException("exceptions.excel.nodata");

    TimerangeProvider timerangeProvider = lastUsedTimerangeProvider.get();

    return new StringBuilder(getText())
        .append('_')
        .append(FormattingUtil.formatDate(timerangeProvider.getStartDate()))
        .append('-')
        .append(FormattingUtil.formatDate(timerangeProvider.getEndDate()))
        .append(".xls")
        .toString();
  }
  public TaskStatusTreeTableColumn() {
    super(FormattingUtil.getFormatted("view.main.resolved"));

    setSortable(false);
    setCellValueFactory(
        param -> new SimpleObjectProperty(param.getValue().getValue().getResolvedDate()));
    setCellFactory(
        param -> {
          TreeTableCell<DisplayRow, Optional<LocalDateTime>> statusCell =
              new TreeTableCell<DisplayRow, Optional<LocalDateTime>>() {

                @Override
                protected void updateItem(Optional<LocalDateTime> item, boolean empty) {
                  super.updateItem(item, empty);
                  setText(StringUtils.EMPTY);

                  if (empty || !item.isPresent()) {
                    setGraphic(null);
                    setTooltip(null);
                  } else {
                    LOGGER.debug("Setting graphic on column with a resolved date");
                    setGraphic(new ImageView("/fx/img/accept.png"));
                    setTooltip(new Tooltip(FormattingUtil.formatDateTime(item.get())));
                  }
                }
              };

          statusCell.setAlignment(Pos.CENTER);
          return statusCell;
        });

    setPrefWidth(120);
  }
  public void writeDataToExcel(Sheet sheet) {
    LOGGER.debug("[{}] Exporting data to excel", getText());

    List<ExcelColumnRenderer> columnRendererList = new ArrayList<>();
    columnRendererList.add(new TaskStatusExcelColumn());
    columnRendererList.add(new TaskDescriptionExcelColumn());

    TimerangeProvider timerangeProvider = fetchTimereportContext.get().getTimerangeProvider();
    LocalDate startDate = timerangeProvider.getStartDate();
    LocalDate endDate = timerangeProvider.getEndDate();

    long amountOfDaysToDisplay = ChronoUnit.DAYS.between(startDate, endDate);
    for (int days = 0; days <= amountOfDaysToDisplay; days++) {
      LocalDate currentColumnDate = timerangeProvider.getStartDate().plus(days, ChronoUnit.DAYS);
      String displayDate = FormattingUtil.formatDate(currentColumnDate);
      columnRendererList.add(new WorklogExcelColumn(displayDate, currentColumnDate));
    }

    columnRendererList.add(new TaskWorklogSummaryExcelColumn());

    TreeItem<DisplayRow> root = taskTableView.getRoot();
    ObservableList<TreeItem<DisplayRow>> children = root.getChildren();

    for (int columnIndex = 0; columnIndex < columnRendererList.size(); columnIndex++) {
      ExcelColumnRenderer excelColumnRenderer = columnRendererList.get(columnIndex);
      excelColumnRenderer.renderCells(
          columnIndex,
          sheet,
          children,
          fetchTimereportContext.get().getGroupByCategory().isPresent());
    }

    // autosize column widths
    for (int i = 0; i < columnRendererList.size(); i++) {
      sheet.autoSizeColumn(i);
    }
  }
  private void updateStatisticsData(List<TaskWithWorklogs> displayResult) {

    if (!SettingsUtil.loadSettings().isShowStatistics()) {
      return;
    }

    statisticsView.getChildren().clear();

    WorklogStatistics statistics = new WorklogStatistics();

    // generic statistics
    displayResult.forEach(
        taskWithWorklogs -> {
          statistics.getTotalTimeSpent().addAndGet(taskWithWorklogs.getTotalInMinutes());

          for (WorklogItem worklogItem : taskWithWorklogs.getWorklogItemList()) {
            String employee = worklogItem.getUserDisplayname();

            // employee total time spent
            AtomicLong totalTimeSpent = statistics.getEmployeeToTotaltimeSpent().get(employee);
            if (totalTimeSpent == null) {
              totalTimeSpent = new AtomicLong(0);
              statistics.getEmployeeToTotaltimeSpent().put(employee, totalTimeSpent);
            }
            totalTimeSpent.addAndGet(worklogItem.getDurationInMinutes());

            // distinct tasks per employee
            Set<String> totalDistinctTasks =
                statistics.getEmployeeToTotalDistinctTasks().get(employee);
            if (totalDistinctTasks == null) {
              totalDistinctTasks = new HashSet<>();
              statistics.getEmployeeToTotalDistinctTasks().put(employee, totalDistinctTasks);
            }
            totalDistinctTasks.add(taskWithWorklogs.getIssue());

            // distinct tasks per employee per project
            Map<String, Set<String>> projectToDistinctTasks =
                statistics.getEmployeeToProjectToDistinctTasks().get(employee);
            if (projectToDistinctTasks == null) {
              projectToDistinctTasks = new HashMap<>();
              statistics
                  .getEmployeeToProjectToDistinctTasks()
                  .put(employee, projectToDistinctTasks);
            }

            Set<String> distinctTasks = projectToDistinctTasks.get(taskWithWorklogs.getProject());
            if (distinctTasks == null) {
              distinctTasks = new HashSet<>();
              projectToDistinctTasks.put(taskWithWorklogs.getProject(), distinctTasks);
            }

            distinctTasks.add(taskWithWorklogs.getIssue());

            // time spent per project
            Map<String, AtomicLong> projectToTimespent =
                statistics.getEmployeeToProjectToWorktime().get(employee);
            if (projectToTimespent == null) {
              projectToTimespent = new HashMap<>();
              statistics.getEmployeeToProjectToWorktime().put(employee, projectToTimespent);
            }

            AtomicLong timespentOnProject = projectToTimespent.get(taskWithWorklogs.getProject());
            if (timespentOnProject == null) {
              timespentOnProject = new AtomicLong(0);
              projectToTimespent.put(taskWithWorklogs.getProject(), timespentOnProject);
            }

            timespentOnProject.addAndGet(worklogItem.getDurationInMinutes());
          }
        });

    // render grid and bar graph
    final AtomicInteger currentGridRow = new AtomicInteger(0);

    GridPane employeeProjectSummaryGrid = new GridPane();
    employeeProjectSummaryGrid.setHgap(5);
    employeeProjectSummaryGrid.setVgap(5);

    NumberAxis projectEmployeeXAxis = new NumberAxis();
    projectEmployeeXAxis.setLabel(FormattingUtil.getFormatted("view.statistics.timespentinhours"));
    projectEmployeeXAxis.setTickLabelRotation(90);

    NumberAxis employeeProjectXAxis = new NumberAxis();
    employeeProjectXAxis.setLabel(FormattingUtil.getFormatted("view.statistics.timespentinhours"));
    employeeProjectXAxis.setTickLabelRotation(90);

    CategoryAxis projectEmployeeYAxis = new CategoryAxis();
    CategoryAxis employeeProjectYAxis = new CategoryAxis();

    StackedBarChart<Number, String> projectEmployeeBargraph =
        new StackedBarChart<>(projectEmployeeXAxis, projectEmployeeYAxis);
    StackedBarChart<Number, String> employeeProjectBargraph =
        new StackedBarChart<>(employeeProjectXAxis, employeeProjectYAxis);

    projectEmployeeBargraph.setTitle(
        FormattingUtil.getFormatted("view.statistics.byprojectandemployee"));
    employeeProjectBargraph.setTitle(
        FormattingUtil.getFormatted("view.statistics.byemployeeandproject"));

    Set<String> projectsToDisplay = new HashSet<>();
    displayResult.forEach(
        taskWithWorklogs -> {
          projectsToDisplay.add(taskWithWorklogs.getProject());
        });
    int projectEmployeeBargraphPreferedHeight =
        HEIGHT_PER_Y_AXIS_ELEMENT * projectsToDisplay.size()
            + HEIGHT_PER_X_AXIS_ELEMENT * statistics.getEmployeeToTotaltimeSpent().keySet().size()
            + ADDITIONAL_HEIGHT;
    projectEmployeeBargraph.setPrefHeight(projectEmployeeBargraphPreferedHeight);
    VBox.setVgrow(projectEmployeeBargraph, Priority.ALWAYS);

    int employeeProjectBargraphPreferedHeight =
        HEIGHT_PER_Y_AXIS_ELEMENT * statistics.getEmployeeToProjectToWorktime().keySet().size()
            + HEIGHT_PER_X_AXIS_ELEMENT * projectsToDisplay.size()
            + ADDITIONAL_HEIGHT;
    employeeProjectBargraph.setPrefHeight(employeeProjectBargraphPreferedHeight);
    VBox.setVgrow(employeeProjectBargraph, Priority.ALWAYS);

    Map<String, XYChart.Series<Number, String>> projectNameToSeries = Maps.newHashMap();

    statistics
        .getEmployeeToProjectToWorktime()
        .keySet()
        .stream()
        .sorted(COLLATOR::compare)
        .forEach(
            employee -> {

              // employee headline label
              Set<String> totalDistinctTasksOfEmployee =
                  statistics.getEmployeeToTotalDistinctTasks().get(employee);
              Label employeeLabel =
                  getBoldLabel(
                      FormattingUtil.getFormatted(
                          "view.statistics.somethingtoamountoftickets",
                          employee,
                          totalDistinctTasksOfEmployee.size()));
              employeeLabel.setPadding(new Insets(20, 0, 0, 0));
              GridPane.setConstraints(employeeLabel, 0, currentGridRow.getAndIncrement());
              GridPane.setColumnSpan(employeeLabel, 4);
              employeeProjectSummaryGrid.getChildren().addAll(employeeLabel);

              // bar graph data container
              XYChart.Series<Number, String> projectEmployeeSeries = new XYChart.Series<>();
              projectEmployeeSeries.setName(employee);
              projectEmployeeBargraph.getData().add(projectEmployeeSeries);

              // time spent per project
              Map<String, AtomicLong> projectToWorktime =
                  statistics.getEmployeeToProjectToWorktime().get(employee);
              Map<String, Label> projectToPercentageLabel = Maps.newHashMap();

              projectToWorktime
                  .keySet()
                  .stream()
                  .sorted(COLLATOR::compare)
                  .forEach(
                      projectName -> {
                        XYChart.Series<Number, String> employeeProjectSeries =
                            projectNameToSeries.get(projectName);
                        if (employeeProjectSeries == null) {
                          employeeProjectSeries = new XYChart.Series<>();
                          employeeProjectSeries.setName(projectName);
                          employeeProjectBargraph.getData().add(employeeProjectSeries);
                          projectNameToSeries.put(projectName, employeeProjectSeries);
                        }

                        // percentage label
                        Label percentageLabel = getBoldLabel("PLACEHOLDER");
                        percentageLabel.setAlignment(Pos.CENTER_RIGHT);
                        percentageLabel.setPadding(new Insets(0, 0, 0, 20));
                        GridPane.setConstraints(percentageLabel, 1, currentGridRow.get());
                        GridPane.setHalignment(percentageLabel, HPos.RIGHT);
                        projectToPercentageLabel.put(projectName, percentageLabel);

                        // project label
                        Set<String> distinctTasksPerProject =
                            statistics
                                .getEmployeeToProjectToDistinctTasks()
                                .get(employee)
                                .get(projectName);
                        Label projectLabel =
                            getBoldLabel(
                                FormattingUtil.getFormatted(
                                    "view.statistics.somethingtoamountoftickets",
                                    projectName,
                                    distinctTasksPerProject.size()));
                        GridPane.setConstraints(projectLabel, 2, currentGridRow.get());

                        // time spent for project label
                        long timespentInMinutes = projectToWorktime.get(projectName).longValue();
                        Label timespentLabel =
                            new Label(FormattingUtil.formatMinutes(timespentInMinutes, true));
                        GridPane.setConstraints(timespentLabel, 3, currentGridRow.get());
                        GridPane.setHgrow(timespentLabel, Priority.ALWAYS);
                        GridPane.setHalignment(timespentLabel, HPos.RIGHT);

                        employeeProjectSummaryGrid
                            .getChildren()
                            .addAll(percentageLabel, projectLabel, timespentLabel);
                        currentGridRow.incrementAndGet();

                        // bargraph data
                        projectEmployeeSeries
                            .getData()
                            .add(new XYChart.Data<>(timespentInMinutes / 60d, projectName));
                        employeeProjectSeries
                            .getData()
                            .addAll(new XYChart.Data<>(timespentInMinutes / 60d, employee));
                      });

              // total time spent
              Label totalLabel =
                  getBoldLabel(FormattingUtil.getFormatted("view.statistics.totaltimespent"));
              GridPane.setConstraints(totalLabel, 0, currentGridRow.get());
              GridPane.setColumnSpan(totalLabel, 4);

              Label timespentLabel =
                  new Label(
                      FormattingUtil.formatMinutes(
                          statistics.getEmployeeToTotaltimeSpent().get(employee).get(), true));
              GridPane.setConstraints(timespentLabel, 3, currentGridRow.get());
              GridPane.setHgrow(timespentLabel, Priority.ALWAYS);
              GridPane.setHalignment(timespentLabel, HPos.RIGHT);
              employeeProjectSummaryGrid.getChildren().addAll(totalLabel, timespentLabel);

              // set label now that we can calculate the percentage
              projectToWorktime
                  .keySet()
                  .forEach(
                      projectName -> {
                        Label percentageLabel = projectToPercentageLabel.get(projectName);

                        double totalSpentTime =
                            statistics.getEmployeeToTotaltimeSpent().get(employee).doubleValue();
                        double spentTimeOnProject =
                            projectToWorktime.get(projectName).doubleValue();

                        double percentage = spentTimeOnProject / totalSpentTime;
                        String percentageFormatted = FormattingUtil.formatPercentage(percentage);
                        percentageLabel.setText(percentageFormatted);
                      });

              currentGridRow.incrementAndGet();
            });

    // employeeProjectBargraph

    statisticsView
        .getChildren()
        .addAll(employeeProjectSummaryGrid, projectEmployeeBargraph, employeeProjectBargraph);

    // custom view statistics
    addAdditionalStatistics(statisticsView, statistics, displayResult);
  }
  protected void refreshWorklogTableViewAndResults() {

    // return early if no data present or still the same data
    // as the last time this tab was active
    Optional<FetchTimereportContext> reportContextOptional = this.fetchTimereportContext;
    if (!reportContextOptional.isPresent()
        || !reportContextOptional.get().getResult().isPresent()
        || !resultToDisplayChangedSinceLastRender) {
      LOGGER.debug(
          "[{}] No results to display or data not changed. Not refreshing TableView and data",
          getText());
      return;
    }

    SettingsUtil.Settings settings = SettingsUtil.loadSettings();
    if (settings.isShowStatistics() && statisticsView == null
        || !settings.isShowStatistics() && statisticsView != null) {
      // statistics are disabled and were previously rendered
      // or statistics are enabled and weren't rendered before
      // update content view
      LOGGER.debug("Updating contentView since settings for statistics seemed to have changed");
      setContent(getContentNode());
    }

    FetchTimereportContext timereportContext = reportContextOptional.get();
    TimerangeProvider timerangeProvider = timereportContext.getTimerangeProvider();

    // render the table columns if the timerange changed from last result
    if (!lastUsedTimerangeProvider.isPresent()
        || !lastUsedTimerangeProvider.get().equals(timerangeProvider)
        || (lastCollapseState.isPresent() && lastCollapseState.get() != settings.getCollapseState())
        || (lastHighlightState.isPresent()
            && lastHighlightState.get() != settings.getHighlightState())) {

      LOGGER.debug(
          "[{}] Regenerating columns for timerange {}",
          getText(),
          timerangeProvider.getReportTimerange().name());
      taskTableView.getColumns().clear();
      taskTableView.getColumns().add(taskStatusTreeTableColumn);
      taskTableView.getColumns().add(taskDescriptionTreeTableColumn);

      // render tables for all days in the selected timerange
      // e.g. timerange current month renders a column for
      // each day of the current month
      long amountOfDaysToDisplay =
          ChronoUnit.DAYS.between(timerangeProvider.getStartDate(), timerangeProvider.getEndDate());

      for (int days = 0; days <= amountOfDaysToDisplay; days++) {
        LocalDate currentColumnDate = timerangeProvider.getStartDate().plus(days, ChronoUnit.DAYS);
        String displayDate = FormattingUtil.formatDate(currentColumnDate);

        // worklog column
        taskTableView.getColumns().add(new WorklogTreeTableColumn(displayDate, currentColumnDate));
      }

      // also add another summary per task column
      taskTableView.getColumns().add(new TaskWorklogSummaryTreeTableColumn());

      lastUsedTimerangeProvider = Optional.of(timerangeProvider);
      lastCollapseState = Optional.of(settings.getCollapseState());
      lastHighlightState = Optional.of(settings.getHighlightState());
    }

    // refresh data
    LOGGER.debug("[{}] Refreshing items in TableView", getText());

    TreeItem<DisplayRow> root = taskTableView.getRoot();
    root.getChildren().clear();

    DisplayData displayData =
        getDisplayData(timereportContext, resultToDisplayChangedSinceLastRender);
    root.getChildren().addAll(displayData.getTreeRows());

    resultToDisplayChangedSinceLastRender = false;
  }