From e8fe12e92a217e25072fa7cf4874169640828c8b Mon Sep 17 00:00:00 2001 From: Robert Goldmann <deadlocker@gmx.de> Date: Mon, 16 May 2022 23:09:04 +0200 Subject: [PATCH] Fixed #678 - add link to created/edited item in successful save banner --- .../accounts/AccountController.java | 6 +- .../categories/CategoryController.java | 10 ++- .../budgetmaster/charts/ChartController.java | 13 +++- .../TemplateGroupController.java | 10 ++- .../templates/TemplateController.java | 19 ++++-- .../templates/TemplateService.java | 4 +- .../transactions/TransactionController.java | 14 ++-- .../notification/NotificationLinkBuilder.java | 26 +++++++ .../src/main/resources/static/css/style.css | 4 ++ .../unit/NotificationLinkBuilderTest.java | 68 +++++++++++++++++++ 10 files changed, 150 insertions(+), 24 deletions(-) create mode 100644 BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/utils/notification/NotificationLinkBuilder.java create mode 100644 BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/NotificationLinkBuilderTest.java diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountController.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountController.java index 3021685d1..cb3fca9f2 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountController.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountController.java @@ -7,6 +7,7 @@ import de.deadlocker8.budgetmaster.utils.Mappings; import de.deadlocker8.budgetmaster.utils.ResourceNotFoundException; import de.deadlocker8.budgetmaster.utils.WebRequestUtils; import de.deadlocker8.budgetmaster.utils.notification.Notification; +import de.deadlocker8.budgetmaster.utils.notification.NotificationLinkBuilder; import de.deadlocker8.budgetmaster.utils.notification.NotificationType; import de.thecodelabs.utils.util.Localization; import org.springframework.beans.factory.annotation.Autowired; @@ -183,7 +184,7 @@ public class AccountController extends BaseController if(isNewAccount) { account.setType(AccountType.CUSTOM); - accountService.getRepository().save(account); + account = accountService.getRepository().save(account); } else { @@ -195,7 +196,8 @@ public class AccountController extends BaseController return ReturnValues.IMPORT_STEP_2; } - WebRequestUtils.putNotification(webRequest, new Notification(Localization.getString("notification.account.save.success", account.getName()), NotificationType.SUCCESS)); + final String link = NotificationLinkBuilder.buildEditLink(request, account.getName(), Mappings.ACCOUNTS, account.getID()); + WebRequestUtils.putNotification(webRequest, new Notification(Localization.getString("notification.account.save.success", link), NotificationType.SUCCESS)); return ReturnValues.REDIRECT_SHOW_ALL; } diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryController.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryController.java index 46d91d048..ef1603db0 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryController.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryController.java @@ -5,6 +5,7 @@ import de.deadlocker8.budgetmaster.icon.IconService; import de.deadlocker8.budgetmaster.services.HelpersService; import de.deadlocker8.budgetmaster.utils.*; import de.deadlocker8.budgetmaster.utils.notification.Notification; +import de.deadlocker8.budgetmaster.utils.notification.NotificationLinkBuilder; import de.deadlocker8.budgetmaster.utils.notification.NotificationType; import de.thecodelabs.utils.util.ColorUtilsNonJavaFX; import de.thecodelabs.utils.util.Localization; @@ -15,6 +16,7 @@ import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.WebRequest; +import javax.servlet.http.HttpServletRequest; import java.util.List; import java.util.Optional; @@ -131,7 +133,8 @@ public class CategoryController extends BaseController } @PostMapping(value = "/newCategory") - public String post(WebRequest request, + public String post(HttpServletRequest servletRequest, + WebRequest request, Model model, @ModelAttribute("NewCategory") Category category, BindingResult bindingResult, @RequestParam(value = "iconImageID", required = false) Integer iconImageID, @RequestParam(value = "builtinIconIdentifier", required = false) String builtinIconIdentifier, @@ -155,9 +158,10 @@ public class CategoryController extends BaseController category.setType(CategoryType.CUSTOM); } - categoryService.save(category); + category = categoryService.save(category); - WebRequestUtils.putNotification(request, new Notification(Localization.getString("notification.category.save.success", category.getName()), NotificationType.SUCCESS)); + final String link = NotificationLinkBuilder.buildEditLink(servletRequest, category.getName(), Mappings.CATEGORIES, category.getID()); + WebRequestUtils.putNotification(request, new Notification(Localization.getString("notification.category.save.success", link), NotificationType.SUCCESS)); return ReturnValues.REDIRECT_SHOW_ALL; } diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/ChartController.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/ChartController.java index 7b30e3673..da43e1e44 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/ChartController.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/charts/ChartController.java @@ -14,6 +14,7 @@ import de.deadlocker8.budgetmaster.utils.Mappings; import de.deadlocker8.budgetmaster.utils.ResourceNotFoundException; import de.deadlocker8.budgetmaster.utils.WebRequestUtils; import de.deadlocker8.budgetmaster.utils.notification.Notification; +import de.deadlocker8.budgetmaster.utils.notification.NotificationLinkBuilder; import de.deadlocker8.budgetmaster.utils.notification.NotificationType; import de.thecodelabs.utils.util.Localization; import org.springframework.beans.factory.annotation.Autowired; @@ -23,6 +24,7 @@ import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.WebRequest; +import javax.servlet.http.HttpServletRequest; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -179,7 +181,11 @@ public class ChartController extends BaseController } @PostMapping(value = "/newChart") - public String post(WebRequest request, Model model, @ModelAttribute("NewChart") Chart chart, BindingResult bindingResult) + public String post(HttpServletRequest servletRequest, + WebRequest request, + Model model, + @ModelAttribute("NewChart") Chart chart, + BindingResult bindingResult) { ChartValidator userValidator = new ChartValidator(); userValidator.validate(chart, bindingResult); @@ -211,8 +217,9 @@ public class ChartController extends BaseController } } - chartService.getRepository().save(chart); - WebRequestUtils.putNotification(request, new Notification(Localization.getString("notification.chart.save.success", chart.getName()), NotificationType.SUCCESS)); + chart = chartService.getRepository().save(chart); + final String link = NotificationLinkBuilder.buildEditLink(servletRequest, chart.getName(), Mappings.CHARTS, chart.getID()); + WebRequestUtils.putNotification(request, new Notification(Localization.getString("notification.chart.save.success", link), NotificationType.SUCCESS)); return ReturnValues.REDIRECT_MANAGE; } diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/templategroup/TemplateGroupController.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/templategroup/TemplateGroupController.java index 1aa6c81a7..da470d698 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/templategroup/TemplateGroupController.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/templategroup/TemplateGroupController.java @@ -8,6 +8,7 @@ import de.deadlocker8.budgetmaster.utils.Mappings; import de.deadlocker8.budgetmaster.utils.ResourceNotFoundException; import de.deadlocker8.budgetmaster.utils.WebRequestUtils; import de.deadlocker8.budgetmaster.utils.notification.Notification; +import de.deadlocker8.budgetmaster.utils.notification.NotificationLinkBuilder; import de.deadlocker8.budgetmaster.utils.notification.NotificationType; import de.thecodelabs.utils.util.Localization; import org.springframework.beans.factory.annotation.Autowired; @@ -17,6 +18,7 @@ import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.WebRequest; +import javax.servlet.http.HttpServletRequest; import java.util.Optional; @@ -90,7 +92,8 @@ public class TemplateGroupController extends BaseController } @PostMapping(value = "/newTemplateGroup") - public String post(WebRequest request, + public String post(HttpServletRequest servletRequest, + WebRequest request, Model model, @ModelAttribute("NewTemplateGroup") TemplateGroup templateGroup, BindingResult bindingResult) { @@ -108,9 +111,10 @@ public class TemplateGroupController extends BaseController templateGroup.setType(TemplateGroupType.CUSTOM); - templateGroupService.getRepository().save(templateGroup); + templateGroup = templateGroupService.getRepository().save(templateGroup); - WebRequestUtils.putNotification(request, new Notification(Localization.getString("notification.template.save.success", templateGroup.getName()), NotificationType.SUCCESS)); + final String link = NotificationLinkBuilder.buildEditLink(servletRequest, templateGroup.getName(), Mappings.TEMPLATE_GROUPS, templateGroup.getID()); + WebRequestUtils.putNotification(request, new Notification(Localization.getString("notification.template.save.success", link), NotificationType.SUCCESS)); return ReturnValues.REDIRECT_ALL_ENTITIES; } diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateController.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateController.java index 1592aefe7..a868be082 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateController.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateController.java @@ -13,6 +13,7 @@ import de.deadlocker8.budgetmaster.utils.Mappings; import de.deadlocker8.budgetmaster.utils.ResourceNotFoundException; import de.deadlocker8.budgetmaster.utils.WebRequestUtils; import de.deadlocker8.budgetmaster.utils.notification.Notification; +import de.deadlocker8.budgetmaster.utils.notification.NotificationLinkBuilder; import de.deadlocker8.budgetmaster.utils.notification.NotificationType; import de.thecodelabs.utils.util.Localization; import org.springframework.beans.factory.annotation.Autowired; @@ -24,6 +25,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.WebRequest; import org.springframework.web.server.ResponseStatusException; +import javax.servlet.http.HttpServletRequest; import java.time.LocalDate; import java.util.Optional; @@ -80,7 +82,8 @@ public class TemplateController extends BaseController } @PostMapping(value = "/fromTransaction") - public String postFromTransaction(WebRequest request, + public String postFromTransaction(HttpServletRequest servletRequest, + WebRequest request, @RequestParam(value = "templateName") String templateName, @ModelAttribute("NewTransaction") Transaction transaction, @RequestParam(value = "includeCategory") Boolean includeCategory, @@ -97,9 +100,10 @@ public class TemplateController extends BaseController throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "templateName must not be empty"); } - templateService.createFromTransaction(templateName, transaction, includeCategory, includeAccount); + final Template newTemplate = templateService.createFromTransaction(templateName, transaction, includeCategory, includeAccount); - WebRequestUtils.putNotification(request, new Notification(Localization.getString("notification.template.add.success", templateName), NotificationType.SUCCESS)); + final String link = NotificationLinkBuilder.buildEditLink(servletRequest, newTemplate.getTemplateName(), Mappings.TEMPLATES, newTemplate.getID()); + WebRequestUtils.putNotification(request, new Notification(Localization.getString("notification.template.add.success", link), NotificationType.SUCCESS)); return ReturnValues.REDIRECT_ALL_ENTITIES; } @@ -178,7 +182,8 @@ public class TemplateController extends BaseController } @PostMapping(value = "/newTemplate") - public String post(WebRequest request, + public String post(HttpServletRequest servletRequest, + WebRequest request, Model model, @ModelAttribute("NewTemplate") Template template, BindingResult bindingResult, @RequestParam(value = "includeAccount", required = false) boolean includeAccount, @@ -238,8 +243,10 @@ public class TemplateController extends BaseController template.setTransferAccount(null); } - templateService.getRepository().save(template); - WebRequestUtils.putNotification(request, new Notification(Localization.getString("notification.template.save.success", template.getName()), NotificationType.SUCCESS)); + template = templateService.getRepository().save(template); + + final String link = NotificationLinkBuilder.buildEditLink(servletRequest, template.getTemplateName(), Mappings.CHARTS, template.getID()); + WebRequestUtils.putNotification(request, new Notification(Localization.getString("notification.template.save.success", link), NotificationType.SUCCESS)); return ReturnValues.REDIRECT_ALL_ENTITIES; } diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateService.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateService.java index c6fa79bb6..75a80443d 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateService.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateService.java @@ -89,7 +89,7 @@ public class TemplateService implements Resettable, AccessAllEntities<Template>, } } - public void createFromTransaction(String templateName, Transaction transaction, boolean includeCategory, boolean includeAccount) + public Template createFromTransaction(String templateName, Transaction transaction, boolean includeCategory, boolean includeAccount) { final Template template = new Template(templateName, transaction); if(!includeCategory) @@ -104,7 +104,7 @@ public class TemplateService implements Resettable, AccessAllEntities<Template>, template.setTemplateGroup(templateGroupService.getDefaultGroup()); - getRepository().save(template); + return getRepository().save(template); } public void prepareTemplateForNewTransaction(TransactionBase template, boolean prepareAccount) diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionController.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionController.java index d0ee062c3..3c0eb8f48 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionController.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionController.java @@ -22,6 +22,7 @@ import de.deadlocker8.budgetmaster.utils.Mappings; import de.deadlocker8.budgetmaster.utils.ResourceNotFoundException; import de.deadlocker8.budgetmaster.utils.WebRequestUtils; import de.deadlocker8.budgetmaster.utils.notification.Notification; +import de.deadlocker8.budgetmaster.utils.notification.NotificationLinkBuilder; import de.deadlocker8.budgetmaster.utils.notification.NotificationType; import de.thecodelabs.utils.util.Localization; import org.springframework.beans.factory.annotation.Autowired; @@ -148,7 +149,8 @@ public class TransactionController extends BaseController } @PostMapping(value = "/newTransaction") - public String post(WebRequest request, + public String post(HttpServletRequest servletRequest, + WebRequest request, Model model, @CookieValue("currentDate") String cookieDate, @ModelAttribute("NewTransaction") Transaction transaction, BindingResult bindingResult, @RequestParam(value = "isRepeating", required = false) boolean isRepeating, @@ -190,7 +192,7 @@ public class TransactionController extends BaseController final boolean isContinueActivated = action.equals(CONTINUE); - return handleRedirect(request, model, transaction.getID() != null, transaction, bindingResult, date, redirectUrl, isContinueActivated); + return handleRedirect(servletRequest, request, model, transaction.getID() != null, transaction, bindingResult, date, redirectUrl, isContinueActivated); } private void handlePreviousType(Transaction transaction, boolean isRepeating) @@ -226,7 +228,7 @@ public class TransactionController extends BaseController return new RepeatingOption(startDate, repeatingModifier, repeatingEnd); } - private String handleRedirect(WebRequest request, Model model, boolean isEdit, @ModelAttribute("NewTransaction") Transaction transaction, BindingResult bindingResult, LocalDate date, String url, boolean isContinueActivated) + private String handleRedirect(HttpServletRequest servletRequest, WebRequest request, Model model, boolean isEdit, @ModelAttribute("NewTransaction") Transaction transaction, BindingResult bindingResult, LocalDate date, String url, boolean isContinueActivated) { if(bindingResult.hasErrors()) { @@ -235,8 +237,10 @@ public class TransactionController extends BaseController return url; } - transactionService.getRepository().save(transaction); - WebRequestUtils.putNotification(request, new Notification(Localization.getString("notification.transaction.save.success", transaction.getName()), NotificationType.SUCCESS)); + transaction = transactionService.getRepository().save(transaction); + + final String link = NotificationLinkBuilder.buildEditLink(servletRequest, transaction.getName(), Mappings.TRANSACTIONS, transaction.getID()); + WebRequestUtils.putNotification(request, new Notification(Localization.getString("notification.transaction.save.success", link), NotificationType.SUCCESS)); if(isContinueActivated) { diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/utils/notification/NotificationLinkBuilder.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/utils/notification/NotificationLinkBuilder.java new file mode 100644 index 000000000..f9c344189 --- /dev/null +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/utils/notification/NotificationLinkBuilder.java @@ -0,0 +1,26 @@ +package de.deadlocker8.budgetmaster.utils.notification; + +import javax.servlet.http.HttpServletRequest; +import java.text.MessageFormat; + +public class NotificationLinkBuilder +{ + private NotificationLinkBuilder() + { + // empty + } + + public static String buildEditLink(HttpServletRequest request, String linkName, String relativeLinkPath, Integer id) + { + final String contextPath = request.getContextPath(); + + return MessageFormat.format("<a href=\"{0}{1}/{2}/edit\" class=\"text-default\">{3}</a>", contextPath, relativeLinkPath, String.valueOf(id), linkName); + } + + public static String build(HttpServletRequest request, String linkName, String relativeLinkPath) + { + final String contextPath = request.getContextPath(); + + return MessageFormat.format("<a href=\"{0}{1}\">{2}</a>", contextPath, relativeLinkPath, linkName); + } +} diff --git a/BudgetMasterServer/src/main/resources/static/css/style.css b/BudgetMasterServer/src/main/resources/static/css/style.css index 3c82c7084..04ff08070 100644 --- a/BudgetMasterServer/src/main/resources/static/css/style.css +++ b/BudgetMasterServer/src/main/resources/static/css/style.css @@ -248,6 +248,10 @@ main { cursor: pointer; } +.notification-item a { + text-decoration: underline; +} + .break-all { word-break: break-all; } diff --git a/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/NotificationLinkBuilderTest.java b/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/NotificationLinkBuilderTest.java new file mode 100644 index 000000000..d53424b24 --- /dev/null +++ b/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/NotificationLinkBuilderTest.java @@ -0,0 +1,68 @@ +package de.deadlocker8.budgetmaster.unit; + +import de.deadlocker8.budgetmaster.utils.notification.NotificationLinkBuilder; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import javax.servlet.http.HttpServletRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class NotificationLinkBuilderTest +{ + @Test + void test_buildEditLink_noContextPath() + { + HttpServletRequest mockedRequest = mock(HttpServletRequest.class); + + Mockito.when(mockedRequest.getContextPath()).thenReturn(""); + + assertThat(NotificationLinkBuilder.buildEditLink(mockedRequest, "My Link", "/accounts", 15)) + .isEqualTo("<a href=\"/accounts/15/edit\" class=\"text-default\">My Link</a>"); + } + + @Test + void test_buildEditLink_withContextPath() + { + HttpServletRequest mockedRequest = mock(HttpServletRequest.class); + + Mockito.when(mockedRequest.getContextPath()).thenReturn("/contextMatters"); + + assertThat(NotificationLinkBuilder.buildEditLink(mockedRequest, "My Link", "/accounts", 15)) + .isEqualTo("<a href=\"/contextMatters/accounts/15/edit\" class=\"text-default\">My Link</a>"); + } + + @Test + void test_buildEditLink_largeID() + { + HttpServletRequest mockedRequest = mock(HttpServletRequest.class); + + Mockito.when(mockedRequest.getContextPath()).thenReturn(""); + + assertThat(NotificationLinkBuilder.buildEditLink(mockedRequest, "My Link", "/accounts", 123456)) + .isEqualTo("<a href=\"/accounts/123456/edit\" class=\"text-default\">My Link</a>"); + } + + @Test + void test_build_noContextPath() + { + HttpServletRequest mockedRequest = mock(HttpServletRequest.class); + + Mockito.when(mockedRequest.getContextPath()).thenReturn(""); + + assertThat(NotificationLinkBuilder.build(mockedRequest, "My Link", "/accounts/0815")) + .isEqualTo("<a href=\"/accounts/0815\">My Link</a>"); + } + + @Test + void test_build_withContextPath() + { + HttpServletRequest mockedRequest = mock(HttpServletRequest.class); + + Mockito.when(mockedRequest.getContextPath()).thenReturn("/contextMatters"); + + assertThat(NotificationLinkBuilder.build(mockedRequest, "My Link", "/accounts/0815")) + .isEqualTo("<a href=\"/contextMatters/accounts/0815\">My Link</a>"); + } +} -- GitLab