Skip to content

Commit

Permalink
Merge branch 'main' into add-subflow-test
Browse files Browse the repository at this point in the history
  • Loading branch information
spokenbird authored Oct 30, 2023
2 parents 95e8ed4 + 95b79c3 commit b8efc53
Show file tree
Hide file tree
Showing 15 changed files with 476 additions and 31 deletions.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2335,6 +2335,14 @@ to see if a user is on, in the event that they don't have a Session or Submissio
It is a page in the flow that a user can enter without having an active Session; it is marking
that this page is an entry point into a flow.

### Disabled Flow Interceptor

The `DisabledFlowInterceptor` is an interceptor that prevents users from accessing a flow that has
been disabled through configuration. This interceptor will redirect users to a configured static screen
when they attempt to access a screen within a disabled flow.

For more information on disabling a flow, please see the [Disabling a Flow](#disabling-a-flow) section.

# How to use

## Configuration Details
Expand Down Expand Up @@ -2387,6 +2395,24 @@ form-flow:
path: 'name-of-file.yaml'
```

#### Disabling a Flow

The form flow library provides a configuration mechanism to disable a flow. This will prevent users
from being able to access a given flow, and will redirect the user to a configured screen if they
attempt to reach a page within a disabled flow.

Here are two examples of disabled flows, the first goes to the home page and the second goes to a
unique static page. The default is the home page if no `staticRedirectPage` is configured.

```yaml
form-flow:
disabled-flows:
- flow: ubi
staticRedirectPage: '/'
- flow: docUpload
staticRedirectPage: '/disabledFeature'
```

#### Design System

We are moving towards using a [custom theme](https://codeforamerica.github.io/uswds/dist/) of
Expand Down Expand Up @@ -2577,6 +2603,10 @@ any one flow, the `Submission` for each flow will be stored in the database.
point
to go back to the other flow. Otherwise, the flows may not get completed properly.**

The form flow library provides a configuration mechanism to disable a flow. This is useful if you are
still developing a flow, or if a flow has reached its end of life. For more information on
disabling flows see: [Disabling a Flow](#disabling-a-flow)

### Screens

Screens are the actual form that will be displayed to the user. Screens are specified as steps in
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ dependencies {
implementation 'org.flywaydb:flyway-core:9.22.3'
implementation 'org.webjars.npm:dropzone:5.9.3'
implementation 'com.amazonaws:aws-java-sdk-s3:1.12.364'
implementation 'com.smartystreets.api:smartystreets-java-sdk:3.14.0'
implementation 'com.smartystreets.api:smartystreets-java-sdk:3.14.1'
implementation 'org.apache.pdfbox:pdfbox:3.0.0'
implementation 'org.yaml:snakeyaml:2.2'
implementation 'com.mailgun:mailgun-java:1.1.0'
Expand All @@ -104,7 +104,7 @@ dependencies {
// we should take a look at this dependency and see if it's really testing things correctly and
// monitor the dependencies.
testImplementation 'org.apache.httpcomponents.client5:httpclient5:5.2.1'
testImplementation 'io.github.bonigarcia:webdrivermanager:5.5.3'
testImplementation 'io.github.bonigarcia:webdrivermanager:5.6.0'
testImplementation 'org.awaitility:awaitility'
testImplementation 'org.seleniumhq.selenium:selenium-java'
testImplementation 'org.postgresql:postgresql'
Expand Down
14 changes: 8 additions & 6 deletions src/main/java/formflow/library/ScreenController.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@
@Controller
@EnableAutoConfiguration
@Slf4j
@RequestMapping("/flow")
@RequestMapping(ScreenController.FLOW)
public class ScreenController extends FormFlowController {

public static final String FLOW = "/flow";
public static final String FLOW_SCREEN_PATH = "{flow}/{screen}";
private final ValidationService validationService;
private final AddressValidationService addressValidationService;
private final ConditionManager conditionManager;
Expand Down Expand Up @@ -85,7 +87,7 @@ public ScreenController(
* @param httpSession The current httpSession, not null
* @return the screen template with model data
*/
@GetMapping("{flow}/{screen}")
@GetMapping(FLOW_SCREEN_PATH)
ModelAndView getScreen(
@PathVariable String flow,
@PathVariable String screen,
Expand Down Expand Up @@ -145,7 +147,7 @@ ModelAndView getScreen(
* @param httpSession The HTTP session if it exists, can be null
* @return a redirect to endpoint that gets the next screen in the flow
*/
@PostMapping({"{flow}/{screen}", "{flow}/{screen}/submit"})
@PostMapping({FLOW_SCREEN_PATH, FLOW_SCREEN_PATH + "/submit"})
ModelAndView postScreen(
@RequestParam(required = false) MultiValueMap<String, String> formData,
@PathVariable String flow,
Expand Down Expand Up @@ -207,7 +209,7 @@ ModelAndView postScreen(
* @param httpSession The current httpSession, not null
* @return the screen template with model data
*/
@GetMapping({"{flow}/{screen}/{uuid}", "{flow}/{screen}/{uuid}/edit"})
@GetMapping({FLOW_SCREEN_PATH + "/{uuid}", FLOW_SCREEN_PATH + "/{uuid}/edit"})
ModelAndView getSubflowScreen(
@PathVariable String flow,
@PathVariable String screen,
Expand Down Expand Up @@ -255,7 +257,7 @@ ModelAndView getSubflowScreen(
* @param httpSession The HTTP session if it exists, not null
* @return a redirect to next screen
*/
@PostMapping({"{flow}/{screen}/{uuid}", "{flow}/{screen}/{uuid}/edit"})
@PostMapping({FLOW_SCREEN_PATH + "/{uuid}", FLOW_SCREEN_PATH + "/{uuid}/edit"})
RedirectView postSubflowScreen(
@RequestParam(required = false) MultiValueMap<String, String> formData,
@PathVariable String flow,
Expand Down Expand Up @@ -438,7 +440,7 @@ ModelAndView deleteSubflowIteration(
* @param httpSession The current httpSession, not null
* @return the screen template with model data, returns error page on error
*/
@GetMapping("{flow}/{screen}/navigation")
@GetMapping(FLOW_SCREEN_PATH + "/navigation")
ModelAndView navigation(
@PathVariable String flow,
@PathVariable String screen,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package formflow.library.config;


import static formflow.library.interceptors.DisabledFlowInterceptor.PATH_FORMAT;

import formflow.library.interceptors.DisabledFlowInterceptor;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/***
* Adds DisabledFlowInterceptorConfiguration to the Interceptor registry.
*/
@Configuration
public class DisabledFlowInterceptorConfiguration implements WebMvcConfigurer {

DisabledFlowPropertyConfiguration disabledFlowPropertyConfiguration;

public DisabledFlowInterceptorConfiguration(DisabledFlowPropertyConfiguration disabledFlowPropertyConfiguration) {
this.disabledFlowPropertyConfiguration = disabledFlowPropertyConfiguration;
}

/**
* Adds the DisabledFlowInterceptor to the Interceptor registry.
* @param registry The Interceptor registry.
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DisabledFlowInterceptor(disabledFlowPropertyConfiguration))
.addPathPatterns(List.of(PATH_FORMAT));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package formflow.library.config;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
//@ConditionalOnProperty(name = "form-flow.disabled-flows")
@ConfigurationProperties(prefix = "form-flow")
@Getter
@Setter
public class DisabledFlowPropertyConfiguration {
private List<Map<String, String>> disabledFlows = new ArrayList<>();


/**
* Checks if the flow with flowName is disabled.
* @param flowName the name of the flow to check.
* @return true if the flow is disabled, false otherwise.
*/
public boolean isFlowDisabled(String flowName) {
return this.disabledFlows.stream().anyMatch(flow -> flow.get("flow").equals(flowName));
}

/**
* Gets the static redirect page for a disabled flow.
* @param flowName the name of the flow to check.
* @return the static redirect page for the flow if disabled flows are configured and the flow is disabled.
*/
public String getDisabledFlowRedirect(String flowName) {
return disabledFlows.stream()
.filter(flow -> flow.get("flow").equals(flowName))
.map(flow -> flow.getOrDefault("staticRedirectPage", ""))
.findFirst()
.orElse(null);
}
}
5 changes: 2 additions & 3 deletions src/main/java/formflow/library/config/FlowConfiguration.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package formflow.library.config;

import formflow.library.config.submission.Action;
import formflow.library.config.submission.Condition;
import java.util.HashMap;

import java.util.List;
import lombok.Data;
import lombok.Getter;

/**
* Represents the configuration for a certain flow.
*/
@Data
@Getter
public class FlowConfiguration {

private String name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Flow;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.yaml.snakeyaml.DumperOptions;
Expand All @@ -21,7 +24,26 @@ public class FlowsConfigurationFactory implements FactoryBean<List<FlowConfigura

@Value("${form-flow.path:flows-config.yaml}")
String configPath;

DisabledFlowPropertyConfiguration disabledFlowPropertyConfiguration;


FlowsConfigurationFactory() {
this.disabledFlowPropertyConfiguration = null;
}

FlowsConfigurationFactory(DisabledFlowPropertyConfiguration disabledFlowPropertyConfiguration) {
this.disabledFlowPropertyConfiguration = disabledFlowPropertyConfiguration;
}

/**
* Takes in the flow configuration yaml file from the given form-flow.path application properties and parses it into a list of
* FlowConfiguration objects.
*
* Disabled flows will be excluded, detected using the form-flow.disabled-flows application property.
* @return list of FlowConfiguration objects.
* @throws IOException if the flow configuration file can't be found.
*/
@Override
public List<FlowConfiguration> getObject() throws IOException {
ClassPathResource classPathResource = new ClassPathResource(configPath);
Expand All @@ -36,7 +58,13 @@ public List<FlowConfiguration> getObject() throws IOException {
List<FlowConfiguration> appConfigs = new ArrayList<>();
try {
Iterable<Object> appConfigsIterable = yaml.loadAll(classPathResource.getInputStream());
appConfigsIterable.forEach(appConfig -> appConfigs.add((FlowConfiguration) appConfig));
appConfigsIterable.forEach(appConfig -> {
FlowConfiguration flowConfig = (FlowConfiguration) appConfig;
if (disabledFlowPropertyConfiguration == null || !disabledFlowPropertyConfiguration.isFlowDisabled(flowConfig.getName())) {
appConfigs.add(flowConfig);
}

});
} catch (IOException e) {
log.error("Can't find the flow configuration file: " + configPath, e);
throw e;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
package formflow.library.config;

import java.io.IOException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.Optional;

/**
* Factory for FlowsConfiguration configuration.
*/
@Configuration
public class FlowsConfigurationFactoryConfig {

@Autowired(required = false)
DisabledFlowPropertyConfiguration disabledFlowPropertyConfiguration;


/**
* Bean to get a FlowsConfigurationFactory object.
*
* @return flow configuration factory
*/
@Bean
public FlowsConfigurationFactory flowsConfigurationFactory() {
return new FlowsConfigurationFactory();
if (this.disabledFlowPropertyConfiguration == null) {
return new FlowsConfigurationFactory();
}
return new FlowsConfigurationFactory(disabledFlowPropertyConfiguration);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@ public class SessionContinuityInterceptorConfiguration implements WebMvcConfigur

@Autowired
List<FlowConfiguration> flowConfigurations;



/**
* Adds the SessionContinuityInterceptor to the Interceptor registry.
* @param registry the Interceptor registry.
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SessionContinuityInterceptor(flowConfigurations))
.addPathPatterns(List.of(SessionContinuityInterceptor.FLOW_PATH_FORMAT, SessionContinuityInterceptor.NAVIGATION_FLOW_PATH_FORMAT));
.addPathPatterns(List.of(SessionContinuityInterceptor.FLOW_PATH_FORMAT,
SessionContinuityInterceptor.NAVIGATION_FLOW_PATH_FORMAT));
}
}
Loading

0 comments on commit b8efc53

Please sign in to comment.