All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fi.evolver.ai.vaadin.view.AdminBaseView Maven / Gradle / Ivy

The newest version!
package fi.evolver.ai.vaadin.view;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Serial;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.TemporalAdjusters;
import java.util.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.vaadin.olli.FileDownloadWrapper;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.server.StreamResource;

import fi.evolver.ai.vaadin.ChatRepository;
import fi.evolver.ai.vaadin.admin.ChatExportGenerator;
import fi.evolver.ai.vaadin.admin.ChatReportGenerator;
import fi.evolver.ai.vaadin.component.ComponentSource;
import fi.evolver.ai.vaadin.component.i18n.VaadinTranslations;
import fi.evolver.ai.vaadin.entity.Chat;
import fi.evolver.ai.vaadin.util.AuthUtils;
import jakarta.annotation.PostConstruct;

public class AdminBaseView extends VerticalLayout implements BeforeEnterObserver {
	@Serial
	private static final long serialVersionUID = 1L;

	private static final Logger LOG = LoggerFactory.getLogger(AdminBaseView.class);

	private final VerticalLayout layout = new VerticalLayout();
	private final Locale localeFi = new Locale("fi", "FI");
	private final Button fetchChatsButton;
	private final Button fetchChatsFullButton;
	private final Button generateReportButton;
	private final DatePicker startDatePicker;
	private final DatePicker endDatePicker;

	private final ChatRepository chatRepository;

	public enum ExportType {
		ONLY_MESSAGES,
		FULL,
		REPORT
	}

	@Autowired(required = false)
	private List> additionalComponents = new ArrayList<>();

	public AdminBaseView(ChatRepository chatRepository) {
		this.chatRepository = chatRepository;
		this.fetchChatsButton = new Button(getTranslation("view.admin.fetchChats"));
		this.fetchChatsFullButton = new Button(getTranslation("view.admin.fetchFullChats"));
		this.generateReportButton = new Button(getTranslation("view.admin.generateReport"));
		this.startDatePicker = new DatePicker(getTranslation("view.admin.starDate"));
		this.endDatePicker = new DatePicker(getTranslation("view.admin.endDate"));
	}


	@PostConstruct
	private void createUi() {
		layout.add(
				new H2(getTranslation("view.admin.headline")),
				startDatePicker,
				endDatePicker,
				createFileDownloadWrapper(ExportType.ONLY_MESSAGES),
				createFileDownloadWrapper(ExportType.FULL),
				createFileDownloadWrapper(ExportType.REPORT));

		additionalComponents.stream()
			.sorted()
			.map(ComponentSource::getComponent)
			.forEach(layout::add);

		layout.setAlignItems(FlexComponent.Alignment.START);
		layout.setSpacing(true);
		layout.setMargin(true);
		add(layout);

		startDatePicker.setLocale(localeFi);
		endDatePicker.setLocale(localeFi);
		startDatePicker.setMin(LocalDate.now().minusYears(1));
		endDatePicker.setMax(LocalDate.now());
		startDatePicker.setValue(getInitialStartDate());
		endDatePicker.setValue(getInitialEndDate());

		fetchChatsButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
		fetchChatsFullButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
		generateReportButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
	}


	@Override
	public void beforeEnter(BeforeEnterEvent event) {
		if (!AuthUtils.isAdminUser())
			event.forwardTo("/");
	}


	private FileDownloadWrapper createFileDownloadWrapper(ExportType type) {
		FileDownloadWrapper wrapper = new FileDownloadWrapper(createStreamResource(type));
		wrapper.wrapComponent(getButton(type));

		return wrapper;
	}


	private StreamResource createStreamResource(ExportType type) {
		// For some reason locale becomes empty inside the StreamResource
		final Locale l = getLocale();
		final VaadinTranslations t = (s, params) -> getTranslation(l, s, params);
		return new StreamResource(generateFilename(type), (o, s) -> generateContent(o, type, t)) {
			@Serial
			private static final long serialVersionUID = 1L;

			@Override
			public Map getHeaders() {
				Map headers = new HashMap<>(super.getHeaders());
				headers.put("Content-Disposition", "attachment; filename=\"" + generateFilename(type) + "\"");

				return headers;
			}
		};
	}


	private Button getButton(ExportType type) {
		return switch (type) {
			case FULL			-> fetchChatsFullButton;
			case ONLY_MESSAGES	-> fetchChatsButton;
			case REPORT			-> generateReportButton;
			default				-> throw new IllegalArgumentException("Unexpected value: " + type);
		};
	}


	private String generateFilename(ExportType type) {
		String dateRangePart = "%s-%s".formatted(
				startDatePicker.getValue() != null ? startDatePicker.getValue() : getInitialStartDate(),
				endDatePicker.getValue() != null ? endDatePicker.getValue() : getInitialEndDate());

		return switch (type) {
			case FULL			-> "chats-full_%s.md".formatted(dateRangePart);
			case ONLY_MESSAGES	-> "chats_%s.md".formatted(dateRangePart);
			case REPORT			-> "chat-report_%s.xlsx".formatted(dateRangePart);
			default				-> throw new IllegalArgumentException("Unexpected value: " + type);
		};
	}


	private void generateContent(OutputStream out, ExportType type, VaadinTranslations t) {
		try {
			if (!AuthUtils.isAdminUser()) {
				showNotification(t.getTranslation("view.admin.forbiddenOperation"));
				return;
			}

			LocalDate startDate = startDatePicker.getValue();
			LocalDate endDate = endDatePicker.getValue();
			LocalDateTime startTime = parseStartTime(startDate);
			LocalDateTime endTime = parseEndTime(endDate);
			if (!isValidReportRange(startTime, endTime)) {
				showNotification(t.getTranslation("view.admin.invalidDateRange", startDate, endDate));
				return;
			}

			List chats = chatRepository.findAllByStartTimeGreaterThanEqualAndStartTimeLessThanEqualOrderByIdDesc(
					startTime,
					endTime);

			if (type == ExportType.REPORT)
				ChatReportGenerator.generateChatReport(chats, startDate, endDate, out);
			else
				out.write(ChatExportGenerator.generateChatExport(chats, startDate, endDate, type, this::getTranslation).getBytes());
		}
		catch (IOException e) {
			LOG.error("Failed generating file content", e);
			showNotification(t.getTranslation("view.admin.errorGenerating"));
		}
	}


	private static LocalDateTime parseEndTime(LocalDate endDate) {
		return endDate != null ? endDate.atTime(LocalTime.MAX) : null;
	}


	private static LocalDateTime parseStartTime(LocalDate startDate) {
		return startDate != null ? startDate.atStartOfDay() : null;
	}


	private static LocalDate getInitialStartDate() {
		return LocalDate.now().with(TemporalAdjusters.firstDayOfMonth());
	}


	private static LocalDate getInitialEndDate() {
		return LocalDate.now();
	}


	private static boolean isValidReportRange(LocalDateTime startTime, LocalDateTime endTime) {
		return startTime != null && endTime != null && startTime.isBefore(endTime);
	}


	private void showNotification(String message) {
		getUI().ifPresent(ui -> {
			ui.access(() -> {
				Notification.show(message, 6000, Notification.Position.MIDDLE);
			});
		});
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy