Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PRSD-689: Creates UpdateJourney base class #239

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
package uk.gov.communities.prsdb.webapp.constants.enums

import uk.gov.communities.prsdb.webapp.constants.REGISTER_LANDLORD_JOURNEY_URL
import uk.gov.communities.prsdb.webapp.constants.REGISTER_LA_USER_JOURNEY_URL
import uk.gov.communities.prsdb.webapp.constants.REGISTER_PROPERTY_JOURNEY_URL
import uk.gov.communities.prsdb.webapp.constants.UPDATE_LANDLORD_DETAILS_URL

enum class JourneyType(
val urlPathSegment: String,
) {
LANDLORD_REGISTRATION(REGISTER_LANDLORD_JOURNEY_URL),
LA_USER_REGISTRATION(REGISTER_LA_USER_JOURNEY_URL),
PROPERTY_REGISTRATION(REGISTER_PROPERTY_JOURNEY_URL),
UPDATE_LANDLORD_DETAILS(UPDATE_LANDLORD_DETAILS_URL),
enum class JourneyType {
LANDLORD_REGISTRATION,
LA_USER_REGISTRATION,
PROPERTY_REGISTRATION,
LANDLORD_DETAILS_UPDATE,
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import uk.gov.communities.prsdb.webapp.constants.UPDATE_PATH_SEGMENT
import uk.gov.communities.prsdb.webapp.exceptions.PrsdbWebException
import uk.gov.communities.prsdb.webapp.forms.journeys.PageData
import uk.gov.communities.prsdb.webapp.forms.journeys.UpdateLandlordDetailsJourney
import uk.gov.communities.prsdb.webapp.forms.steps.UpdateDetailsStepId
import uk.gov.communities.prsdb.webapp.helpers.DateTimeHelper
import uk.gov.communities.prsdb.webapp.models.viewModels.summaryModels.LandlordViewModel
import uk.gov.communities.prsdb.webapp.services.AddressDataService
Expand All @@ -39,14 +38,12 @@ class LandlordDetailsController(
model: Model,
principal: Principal,
): String {
updateDetailsJourney.initialiseJourneyDataIfNotInitialised(principal.name)
addLandlordDetailsToModel(model, principal, includeChangeLinks = true)
// TODO: PRSD-355 Remove this way of showing submit button
model.addAttribute("shouldShowSubmitButton", true)
addLandlordDetailsToModel(model, principal, includeChangeLinks = true)
return updateDetailsJourney.populateModelAndGetViewName(
UpdateDetailsStepId.UpdateDetails,
model,
null,
return updateDetailsJourney.populateModelAndGetViewNameForUpdateStep(
updateEntityId = principal.name,
model = model,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class RegisterPropertyController(
model: Model,
principal: Principal,
): String {
propertyRegistrationJourney.initialiseJourneyDataIfNotInitialised(principal.name)
propertyRegistrationJourney.loadJourneyDataIfNotLoaded(principal.name)

return propertyRegistrationJourney.populateModelAndGetTaskListViewName(model)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import org.springframework.validation.Validator
import org.springframework.web.server.ResponseStatusException
import org.springframework.web.util.UriComponentsBuilder
import uk.gov.communities.prsdb.webapp.constants.enums.JourneyType
import uk.gov.communities.prsdb.webapp.constants.enums.TaskStatus
import uk.gov.communities.prsdb.webapp.forms.steps.Step
import uk.gov.communities.prsdb.webapp.forms.steps.StepDetails
import uk.gov.communities.prsdb.webapp.forms.steps.StepId
Expand All @@ -17,41 +16,59 @@ import java.security.Principal
import java.util.Optional

abstract class Journey<T : StepId>(
private val journeyType: JourneyType,
protected val journeyType: JourneyType,
protected val validator: Validator,
protected val journeyDataService: JourneyDataService,
) : Iterable<StepDetails<T>> {
abstract val initialStepId: T
val steps: Set<Step<T>>
get() = sections.flatMap { section -> section.tasks }.flatMap { task -> task.steps }.toSet()

abstract val sections: List<JourneySection<T>>

open fun getUnreachableStepRedirect(journeyData: JourneyData) = "/${journeyType.urlPathSegment}/${initialStepId.urlPathSegment}"
protected val steps: Set<Step<T>>
get() = sections.flatMap { section -> section.tasks }.flatMap { task -> task.steps }.toSet()

protected abstract val journeyPathSegment: String

protected val defaultJourneyDataKey = journeyType.name

fun getStepId(stepName: String): T {
val step = steps.singleOrNull { step -> step.id.urlPathSegment == stepName }
if (step == null) {
throw ResponseStatusException(
HttpStatus.NOT_FOUND,
"Step $stepName not valid for journey ${journeyType.urlPathSegment}",
"Step $stepName not valid for journey ${journeyType.name}",
)
}
return step.id
}

fun loadJourneyDataIfNotLoaded(
principalName: String,
journeyDataKey: String? = null,
) {
val data = journeyDataService.getJourneyDataFromSession(journeyDataKey ?: defaultJourneyDataKey)
if (data.isEmpty()) {
/* TODO PRSD-589 Currently this looks the context up from the database,
takes the id, then passes the id to another method which retrieves it
from the database. When this is reworked, we should just pass the whole
context to an overload of journeyDataService.loadJourneyDataIntoSession().*/
val contextId = journeyDataService.getContextId(principalName, journeyType)
if (contextId != null) {
journeyDataService.loadJourneyDataIntoSession(contextId)
}
}
}

fun populateModelAndGetViewName(
stepId: StepId,
stepId: T,
model: Model,
subPageNumber: Int?,
submittedPageData: PageData? = null,
journeyDataKey: String? = null,
): String {
val journeyData: JourneyData = journeyDataService.getJourneyDataFromSession()
val requestedStep =
steps.singleOrNull { step -> step.id == stepId } ?: throw ResponseStatusException(
HttpStatus.NOT_FOUND,
"Step ${stepId.urlPathSegment} not valid for journey ${journeyType.urlPathSegment}",
)
val journeyData: JourneyData =
journeyDataService.getJourneyDataFromSession(journeyDataKey ?: defaultJourneyDataKey)
val requestedStep = getStep(stepId)
if (!isStepReachable(requestedStep, subPageNumber)) {
return "redirect:${getUnreachableStepRedirect(journeyData)}"
}
Expand All @@ -72,36 +89,29 @@ abstract class Journey<T : StepId>(
)
}

fun getSectionHeaderInfo(step: Step<T>): SectionHeaderViewModel? {
val sectionContainingStep = sections.single { it.isStepInSection(step.id) }
if (sectionContainingStep.headingKey == null) {
return null
}
return SectionHeaderViewModel(
sectionContainingStep.headingKey,
sections.indexOf(sectionContainingStep) + 1,
sections.size,
)
}

fun updateJourneyDataAndGetViewNameOrRedirect(
stepId: T,
pageData: PageData,
model: Model,
subPageNumber: Int?,
principal: Principal,
journeyDataKey: String? = null,
): String {
val currentStep =
steps.singleOrNull { step -> step.id == stepId } ?: throw ResponseStatusException(
HttpStatus.NOT_FOUND,
"Step ${stepId.urlPathSegment} not valid for journey ${journeyType.urlPathSegment}",
)
val journeyData = journeyDataService.getJourneyDataFromSession(journeyDataKey ?: defaultJourneyDataKey)

val currentStep = getStep(stepId)
if (!currentStep.isSatisfied(validator, pageData)) {
return populateModelAndGetViewName(stepId, model, subPageNumber, pageData)
return populateModelAndGetViewName(
stepId,
model,
subPageNumber,
pageData,
journeyDataKey ?: defaultJourneyDataKey,
)
}
val journeyData = journeyDataService.getJourneyDataFromSession()

val newJourneyData = currentStep.updatedJourneyData(journeyData, pageData, subPageNumber)
journeyDataService.setJourneyData(newJourneyData)
journeyDataService.setJourneyDataInSession(newJourneyData)

if (currentStep.saveAfterSubmit) {
val journeyDataContextId = journeyDataService.getContextId()
Expand All @@ -111,21 +121,24 @@ abstract class Journey<T : StepId>(
if (currentStep.handleSubmitAndRedirect != null) {
return "redirect:${currentStep.handleSubmitAndRedirect!!(newJourneyData, subPageNumber)}"
}
val (newStepId: StepId?, newSubPageNumber: Int?) = currentStep.nextAction(newJourneyData, subPageNumber)
val (newStepId: T?, newSubPageNumber: Int?) = currentStep.nextAction(newJourneyData, subPageNumber)
if (newStepId == null) {
throw IllegalStateException("Cannot compute next step from step ${currentStep.id.urlPathSegment}")
}
val redirectUrl =
UriComponentsBuilder
.newInstance()
.path("/${journeyType.urlPathSegment}/${newStepId.urlPathSegment}")
.path("/$journeyPathSegment/${newStepId.urlPathSegment}")
.queryParamIfPresent("subpage", Optional.ofNullable(newSubPageNumber))
.build(true)
.toUriString()
return "redirect:$redirectUrl"
}

fun isStepReachable(
override fun iterator(): Iterator<StepDetails<T>> =
ReachableStepDetailsIterator(journeyDataService.getJourneyDataFromSession(), steps, initialStepId, validator)

protected fun isStepReachable(
targetStep: Step<T>,
targetSubPageNumber: Int? = null,
): Boolean {
Expand All @@ -135,6 +148,32 @@ abstract class Journey<T : StepId>(
return getPrevStep(targetStep, targetSubPageNumber) != null
}

protected open fun getUnreachableStepRedirect(journeyData: JourneyData) = initialStepId.urlPathSegment

protected fun createSingleSectionWithSingleTaskFromSteps(
initialStepId: T,
steps: Set<Step<T>>,
): List<JourneySection<T>> = listOf(JourneySection.withOneTask(JourneyTask(initialStepId, steps)))

private fun getSectionHeaderInfo(step: Step<T>): SectionHeaderViewModel? {
val sectionContainingStep = sections.single { it.isStepInSection(step.id) }
if (sectionContainingStep.headingKey == null) {
return null
}
return SectionHeaderViewModel(
sectionContainingStep.headingKey,
sections.indexOf(sectionContainingStep) + 1,
sections.size,
)
}

private fun getStep(stepId: T) =
steps.singleOrNull { step -> step.id == stepId }
?: throw ResponseStatusException(
HttpStatus.NOT_FOUND,
"Step $stepId not valid for journey ${journeyType.name}",
)

private fun getPrevStep(
targetStep: Step<T>,
targetSubPageNumber: Int?,
Expand All @@ -156,20 +195,4 @@ abstract class Journey<T : StepId>(
.build(true)
.toUriString()
}

fun getTaskStatus(
task: JourneyTask<T>,
journeyData: JourneyData,
): TaskStatus {
val canTaskBeStarted = isStepReachable(task.steps.single { it.id == task.startingStepId })
return task.getTaskStatus(journeyData, validator, canTaskBeStarted)
}

protected fun <T : StepId> createSingleSectionWithSingleTaskFromSteps(
initialStepId: T,
steps: Set<Step<T>>,
): List<JourneySection<T>> = listOf(JourneySection.withOneTask(JourneyTask(initialStepId, steps)))

override fun iterator(): Iterator<StepDetails<T>> =
ReachableStepDetailsIterator(journeyDataService.getJourneyDataFromSession(), steps, initialStepId, validator)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,26 @@ package uk.gov.communities.prsdb.webapp.forms.journeys
import org.springframework.ui.Model
import org.springframework.validation.Validator
import uk.gov.communities.prsdb.webapp.constants.enums.JourneyType
import uk.gov.communities.prsdb.webapp.constants.enums.TaskStatus
import uk.gov.communities.prsdb.webapp.forms.steps.StepId
import uk.gov.communities.prsdb.webapp.forms.tasks.TaskListViewModelFactory
import uk.gov.communities.prsdb.webapp.services.JourneyDataService

abstract class JourneyWithTaskList<T : StepId>(
val journeyType: JourneyType,
journeyType: JourneyType,
validator: Validator,
journeyDataService: JourneyDataService,
) : Journey<T>(journeyType, validator, journeyDataService) {
abstract val taskListFactory: TaskListViewModelFactory<T>
abstract val taskListUrlSegment: String

final override fun getUnreachableStepRedirect(journeyData: JourneyData) = "/${journeyType.urlPathSegment}/$taskListUrlSegment"
final override fun getUnreachableStepRedirect(journeyData: JourneyData) = taskListUrlSegment

fun populateModelAndGetTaskListViewName(model: Model): String {
val journeyData = journeyDataService.getJourneyDataFromSession()
fun populateModelAndGetTaskListViewName(
model: Model,
journeyDataKey: String? = null,
): String {
val journeyData = journeyDataService.getJourneyDataFromSession(journeyDataKey ?: defaultJourneyDataKey)
model.addAttribute("taskListViewModel", taskListFactory.getTaskListViewModel(journeyData))
return "taskList"
}
Expand All @@ -29,10 +33,18 @@ abstract class JourneyWithTaskList<T : StepId>(
subtitleKey: String,
rootId: String,
) = TaskListViewModelFactory(
"registerProperty.title",
"registerProperty.taskList.heading",
"registerProperty.taskList.subtitle",
"register-property-task",
titleKey,
headingKey,
subtitleKey,
rootId,
sections,
) { task, journeyData -> getTaskStatus(task, journeyData) }

private fun getTaskStatus(
task: JourneyTask<T>,
journeyData: JourneyData,
): TaskStatus {
val canTaskBeStarted = isStepReachable(task.steps.single { it.id == task.startingStepId })
return task.getTaskStatus(journeyData, validator, canTaskBeStarted)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package uk.gov.communities.prsdb.webapp.forms.journeys
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.stereotype.Component
import org.springframework.validation.Validator
import uk.gov.communities.prsdb.webapp.constants.REGISTER_LA_USER_JOURNEY_URL
import uk.gov.communities.prsdb.webapp.constants.enums.JourneyType
import uk.gov.communities.prsdb.webapp.controllers.RegisterLAUserController.Companion.CONFIRMATION_PAGE_PATH_SEGMENT
import uk.gov.communities.prsdb.webapp.forms.pages.LaUserRegistrationCheckAnswersPage
Expand Down Expand Up @@ -42,13 +43,15 @@ class LaUserRegistrationJourney(
),
)

override val journeyPathSegment = REGISTER_LA_USER_JOURNEY_URL

fun initialiseJourneyData(token: String) {
val journeyData = journeyDataService.getJourneyDataFromSession()
val journeyData = journeyDataService.getJourneyDataFromSession(defaultJourneyDataKey)
val formData: PageData = mapOf("emailAddress" to invitationService.getEmailAddressForToken(token))
val emailStep = steps.single { step -> step.id == RegisterLaUserStepId.Email }

val newJourneyData = emailStep.updatedJourneyData(journeyData, formData, null)
journeyDataService.setJourneyData(newJourneyData)
journeyDataService.setJourneyDataInSession(newJourneyData)
}

private fun landingPageStep() =
Expand Down Expand Up @@ -81,7 +84,7 @@ class LaUserRegistrationJourney(
"fieldSetHint" to "forms.name.fieldSetHint",
"label" to "forms.name.label",
"submitButtonText" to "forms.buttons.continue",
"backUrl" to "/${JourneyType.LA_USER_REGISTRATION.urlPathSegment}/",
"backUrl" to "/$journeyPathSegment/",
),
),
nextAction = { _, _ -> Pair(RegisterLaUserStepId.Email, null) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ class LandlordRegistrationJourney(
),
)

override val journeyPathSegment = REGISTER_LANDLORD_JOURNEY_URL

private fun privacyNoticeTasks(): List<JourneyTask<LandlordRegistrationStepId>> = emptyList()

private fun registerDetailsTasks(): List<JourneyTask<LandlordRegistrationStepId>> =
Expand All @@ -86,7 +88,7 @@ class LandlordRegistrationJourney(
)

private fun identityTask() =
JourneyTask<LandlordRegistrationStepId>(
JourneyTask(
LandlordRegistrationStepId.VerifyIdentity,
setOf(
verifyIdentityStep(),
Expand Down Expand Up @@ -138,7 +140,7 @@ class LandlordRegistrationJourney(
"fieldSetHint" to "forms.name.fieldSetHint",
"label" to "forms.name.label",
"submitButtonText" to "forms.buttons.continue",
"backUrl" to "/${JourneyType.LANDLORD_REGISTRATION.urlPathSegment}",
"backUrl" to "/$journeyPathSegment",
),
shouldDisplaySectionHeader = true,
),
Expand Down
Loading