parent
668513e740
commit
509427b671
|
|
@ -210,10 +210,7 @@ public class FlywayAutoConfiguration {
|
||||||
map.from(properties.isCleanDisabled()).to(configuration::cleanDisabled);
|
map.from(properties.isCleanDisabled()).to(configuration::cleanDisabled);
|
||||||
map.from(properties.isCleanOnValidationError()).to(configuration::cleanOnValidationError);
|
map.from(properties.isCleanOnValidationError()).to(configuration::cleanOnValidationError);
|
||||||
map.from(properties.isGroup()).to(configuration::group);
|
map.from(properties.isGroup()).to(configuration::group);
|
||||||
map.from(properties.isIgnoreMissingMigrations()).to(configuration::ignoreMissingMigrations);
|
configureIgnoredMigrations(configuration, properties, map);
|
||||||
map.from(properties.isIgnoreIgnoredMigrations()).to(configuration::ignoreIgnoredMigrations);
|
|
||||||
map.from(properties.isIgnorePendingMigrations()).to(configuration::ignorePendingMigrations);
|
|
||||||
map.from(properties.isIgnoreFutureMigrations()).to(configuration::ignoreFutureMigrations);
|
|
||||||
map.from(properties.isMixed()).to(configuration::mixed);
|
map.from(properties.isMixed()).to(configuration::mixed);
|
||||||
map.from(properties.isOutOfOrder()).to(configuration::outOfOrder);
|
map.from(properties.isOutOfOrder()).to(configuration::outOfOrder);
|
||||||
map.from(properties.isSkipDefaultCallbacks()).to(configuration::skipDefaultCallbacks);
|
map.from(properties.isSkipDefaultCallbacks()).to(configuration::skipDefaultCallbacks);
|
||||||
|
|
@ -262,6 +259,18 @@ public class FlywayAutoConfiguration {
|
||||||
// No method reference for compatibility with Flyway version < 7.9
|
// No method reference for compatibility with Flyway version < 7.9
|
||||||
map.from(properties.getDetectEncoding())
|
map.from(properties.getDetectEncoding())
|
||||||
.to((detectEncoding) -> configuration.detectEncoding(detectEncoding));
|
.to((detectEncoding) -> configuration.detectEncoding(detectEncoding));
|
||||||
|
// No method reference for compatibility with Flyway version < 8.0
|
||||||
|
map.from(properties.getBaselineMigrationPrefix())
|
||||||
|
.to((baselineMigrationPrefix) -> configuration.baselineMigrationPrefix(baselineMigrationPrefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private void configureIgnoredMigrations(FluentConfiguration configuration, FlywayProperties properties,
|
||||||
|
PropertyMapper map) {
|
||||||
|
map.from(properties.isIgnoreMissingMigrations()).to(configuration::ignoreMissingMigrations);
|
||||||
|
map.from(properties.isIgnoreIgnoredMigrations()).to(configuration::ignoreIgnoredMigrations);
|
||||||
|
map.from(properties.isIgnorePendingMigrations()).to(configuration::ignorePendingMigrations);
|
||||||
|
map.from(properties.isIgnoreFutureMigrations()).to(configuration::ignoreFutureMigrations);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureFailOnMissingLocations(FluentConfiguration configuration,
|
private void configureFailOnMissingLocations(FluentConfiguration configuration,
|
||||||
|
|
|
||||||
|
|
@ -227,21 +227,25 @@ public class FlywayProperties {
|
||||||
/**
|
/**
|
||||||
* Whether to ignore missing migrations when reading the schema history table.
|
* Whether to ignore missing migrations when reading the schema history table.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
private boolean ignoreMissingMigrations;
|
private boolean ignoreMissingMigrations;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to ignore ignored migrations when reading the schema history table.
|
* Whether to ignore ignored migrations when reading the schema history table.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
private boolean ignoreIgnoredMigrations;
|
private boolean ignoreIgnoredMigrations;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to ignore pending migrations when reading the schema history table.
|
* Whether to ignore pending migrations when reading the schema history table.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
private boolean ignorePendingMigrations;
|
private boolean ignorePendingMigrations;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to ignore future migrations when reading the schema history table.
|
* Whether to ignore future migrations when reading the schema history table.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
private boolean ignoreFutureMigrations = true;
|
private boolean ignoreFutureMigrations = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -371,9 +375,9 @@ public class FlywayProperties {
|
||||||
private Boolean detectEncoding;
|
private Boolean detectEncoding;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filename prefix of state scripts. Requires Flyway Teams.
|
* Filename prefix for baseline migrations. Requires Flyway Teams.
|
||||||
*/
|
*/
|
||||||
private String stateScriptPrefix;
|
private String baselineMigrationPrefix;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prefix of placeholders in migration scripts.
|
* Prefix of placeholders in migration scripts.
|
||||||
|
|
@ -672,34 +676,46 @@ public class FlywayProperties {
|
||||||
this.group = group;
|
this.group = group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@DeprecatedConfigurationProperty(replacement = "spring.flyway.ignore-migration-patterns")
|
||||||
public boolean isIgnoreMissingMigrations() {
|
public boolean isIgnoreMissingMigrations() {
|
||||||
return this.ignoreMissingMigrations;
|
return this.ignoreMissingMigrations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void setIgnoreMissingMigrations(boolean ignoreMissingMigrations) {
|
public void setIgnoreMissingMigrations(boolean ignoreMissingMigrations) {
|
||||||
this.ignoreMissingMigrations = ignoreMissingMigrations;
|
this.ignoreMissingMigrations = ignoreMissingMigrations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@DeprecatedConfigurationProperty(replacement = "spring.flyway.ignore-migration-patterns")
|
||||||
public boolean isIgnoreIgnoredMigrations() {
|
public boolean isIgnoreIgnoredMigrations() {
|
||||||
return this.ignoreIgnoredMigrations;
|
return this.ignoreIgnoredMigrations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void setIgnoreIgnoredMigrations(boolean ignoreIgnoredMigrations) {
|
public void setIgnoreIgnoredMigrations(boolean ignoreIgnoredMigrations) {
|
||||||
this.ignoreIgnoredMigrations = ignoreIgnoredMigrations;
|
this.ignoreIgnoredMigrations = ignoreIgnoredMigrations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@DeprecatedConfigurationProperty(replacement = "spring.flyway.ignore-migration-patterns")
|
||||||
public boolean isIgnorePendingMigrations() {
|
public boolean isIgnorePendingMigrations() {
|
||||||
return this.ignorePendingMigrations;
|
return this.ignorePendingMigrations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void setIgnorePendingMigrations(boolean ignorePendingMigrations) {
|
public void setIgnorePendingMigrations(boolean ignorePendingMigrations) {
|
||||||
this.ignorePendingMigrations = ignorePendingMigrations;
|
this.ignorePendingMigrations = ignorePendingMigrations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@DeprecatedConfigurationProperty(replacement = "spring.flyway.ignore-migration-patterns")
|
||||||
public boolean isIgnoreFutureMigrations() {
|
public boolean isIgnoreFutureMigrations() {
|
||||||
return this.ignoreFutureMigrations;
|
return this.ignoreFutureMigrations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void setIgnoreFutureMigrations(boolean ignoreFutureMigrations) {
|
public void setIgnoreFutureMigrations(boolean ignoreFutureMigrations) {
|
||||||
this.ignoreFutureMigrations = ignoreFutureMigrations;
|
this.ignoreFutureMigrations = ignoreFutureMigrations;
|
||||||
}
|
}
|
||||||
|
|
@ -888,12 +904,12 @@ public class FlywayProperties {
|
||||||
this.detectEncoding = detectEncoding;
|
this.detectEncoding = detectEncoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getStateScriptPrefix() {
|
public String getBaselineMigrationPrefix() {
|
||||||
return this.stateScriptPrefix;
|
return this.baselineMigrationPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStateScriptPrefix(String stateScriptPrefix) {
|
public void setBaselineMigrationPrefix(String baselineMigrationPrefix) {
|
||||||
this.stateScriptPrefix = stateScriptPrefix;
|
this.baselineMigrationPrefix = baselineMigrationPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getScriptPlaceholderPrefix() {
|
public String getScriptPlaceholderPrefix() {
|
||||||
|
|
|
||||||
|
|
@ -872,6 +872,10 @@
|
||||||
"http://localhost:9200"
|
"http://localhost:9200"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "spring.flyway.baseline-migration-prefix",
|
||||||
|
"defaultValue": "B"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "spring.flyway.connect-retries-interval",
|
"name": "spring.flyway.connect-retries-interval",
|
||||||
"defaultValue": 120
|
"defaultValue": 120
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ class Flyway5xAutoConfigurationTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isStateScript() {
|
public boolean isBaselineMigration() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.autoconfigure.flyway;
|
||||||
|
|
||||||
|
import org.flywaydb.core.Flyway;
|
||||||
|
import org.flywaydb.core.api.Location;
|
||||||
|
import org.flywaydb.core.api.callback.Callback;
|
||||||
|
import org.flywaydb.core.api.callback.Context;
|
||||||
|
import org.flywaydb.core.api.callback.Event;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
|
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
|
||||||
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
|
import org.springframework.boot.testsupport.classpath.ClassPathOverrides;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link FlywayAutoConfiguration} with Flyway 7.x.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
@ClassPathOverrides("org.flywaydb:flyway-core:7.15.0")
|
||||||
|
class Flyway7xAutoConfigurationTests {
|
||||||
|
|
||||||
|
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||||
|
.withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class))
|
||||||
|
.withPropertyValues("spring.datasource.generate-unique-name=true");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void defaultFlyway() {
|
||||||
|
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(Flyway.class);
|
||||||
|
Flyway flyway = context.getBean(Flyway.class);
|
||||||
|
assertThat(flyway.getConfiguration().getLocations())
|
||||||
|
.containsExactly(new Location("classpath:db/migration"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void callbacksAreConfigured() {
|
||||||
|
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, CallbackConfiguration.class)
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(Flyway.class);
|
||||||
|
Flyway flyway = context.getBean(Flyway.class);
|
||||||
|
Callback callbackOne = context.getBean("callbackOne", Callback.class);
|
||||||
|
Callback callbackTwo = context.getBean("callbackTwo", Callback.class);
|
||||||
|
assertThat(flyway.getConfiguration().getCallbacks()).hasSize(2);
|
||||||
|
assertThat(flyway.getConfiguration().getCallbacks()).containsExactlyInAnyOrder(callbackTwo,
|
||||||
|
callbackOne);
|
||||||
|
verify(callbackOne, atLeastOnce()).handle(any(Event.class), any(Context.class));
|
||||||
|
verify(callbackTwo, atLeastOnce()).handle(any(Event.class), any(Context.class));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class CallbackConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Callback callbackOne() {
|
||||||
|
return mockCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Callback callbackTwo() {
|
||||||
|
return mockCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Callback mockCallback() {
|
||||||
|
Callback callback = mock(Callback.class);
|
||||||
|
given(callback.supports(any(Event.class), any(Context.class))).willReturn(true);
|
||||||
|
given(callback.getCallbackName()).willReturn("callback");
|
||||||
|
return callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -499,8 +499,9 @@ class FlywayAutoConfigurationTests {
|
||||||
assertThat(context).hasSingleBean(Flyway.class);
|
assertThat(context).hasSingleBean(Flyway.class);
|
||||||
Flyway flyway = context.getBean(Flyway.class);
|
Flyway flyway = context.getBean(Flyway.class);
|
||||||
assertThat(flyway.getConfiguration().getConnectRetries()).isEqualTo(5);
|
assertThat(flyway.getConfiguration().getConnectRetries()).isEqualTo(5);
|
||||||
assertThat(flyway.getConfiguration().isIgnoreMissingMigrations()).isTrue();
|
assertThat(flyway.getConfiguration().getBaselineDescription()).isEqualTo("<< Custom baseline >>");
|
||||||
assertThat(flyway.getConfiguration().isIgnorePendingMigrations()).isTrue();
|
assertThat(flyway.getConfiguration().getBaselineVersion())
|
||||||
|
.isEqualTo(MigrationVersion.fromVersion("1"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -657,6 +658,13 @@ class FlywayAutoConfigurationTests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void baselineMigrationPrefixIsCorrectlyMapped() {
|
||||||
|
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
|
||||||
|
.withPropertyValues("spring.flyway.baseline-migration-prefix=BL")
|
||||||
|
.run(validateFlywayTeamsPropertyOnly("baselineMigrationPrefix"));
|
||||||
|
}
|
||||||
|
|
||||||
private ContextConsumer<AssertableApplicationContext> validateFlywayTeamsPropertyOnly(String propertyName) {
|
private ContextConsumer<AssertableApplicationContext> validateFlywayTeamsPropertyOnly(String propertyName) {
|
||||||
return (context) -> {
|
return (context) -> {
|
||||||
assertThat(context).hasFailed();
|
assertThat(context).hasFailed();
|
||||||
|
|
@ -892,13 +900,13 @@ class FlywayAutoConfigurationTests {
|
||||||
@Bean
|
@Bean
|
||||||
@Order(1)
|
@Order(1)
|
||||||
FlywayConfigurationCustomizer customizerOne() {
|
FlywayConfigurationCustomizer customizerOne() {
|
||||||
return (configuration) -> configuration.connectRetries(5).ignorePendingMigrations(true);
|
return (configuration) -> configuration.connectRetries(5).baselineVersion("1");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Order(0)
|
@Order(0)
|
||||||
FlywayConfigurationCustomizer customizerTwo() {
|
FlywayConfigurationCustomizer customizerTwo() {
|
||||||
return (configuration) -> configuration.connectRetries(10).ignoreMissingMigrations(true);
|
return (configuration) -> configuration.connectRetries(10).baselineDescription("<< Custom baseline >>");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1008,7 +1016,7 @@ class FlywayAutoConfigurationTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isStateScript() {
|
public boolean isBaselineMigration() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,12 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
*/
|
*/
|
||||||
class FlywayPropertiesTests {
|
class FlywayPropertiesTests {
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
@Test
|
@Test
|
||||||
void defaultValuesAreConsistent() {
|
void defaultValuesAreConsistent() {
|
||||||
FlywayProperties properties = new FlywayProperties();
|
FlywayProperties properties = new FlywayProperties();
|
||||||
Configuration configuration = new FluentConfiguration();
|
Configuration configuration = new FluentConfiguration();
|
||||||
assertThat(configuration.getFailOnMissingLocations()).isEqualTo(properties.isFailOnMissingLocations());
|
assertThat(configuration.isFailOnMissingLocations()).isEqualTo(properties.isFailOnMissingLocations());
|
||||||
assertThat(properties.getLocations().stream().map(Location::new).toArray(Location[]::new))
|
assertThat(properties.getLocations().stream().map(Location::new).toArray(Location[]::new))
|
||||||
.isEqualTo(configuration.getLocations());
|
.isEqualTo(configuration.getLocations());
|
||||||
assertThat(properties.getEncoding()).isEqualTo(configuration.getEncoding());
|
assertThat(properties.getEncoding()).isEqualTo(configuration.getEncoding());
|
||||||
|
|
@ -61,7 +62,7 @@ class FlywayPropertiesTests {
|
||||||
assertThat(configuration.getLockRetryCount()).isEqualTo(50);
|
assertThat(configuration.getLockRetryCount()).isEqualTo(50);
|
||||||
assertThat(properties.getDefaultSchema()).isEqualTo(configuration.getDefaultSchema());
|
assertThat(properties.getDefaultSchema()).isEqualTo(configuration.getDefaultSchema());
|
||||||
assertThat(properties.getSchemas()).isEqualTo(Arrays.asList(configuration.getSchemas()));
|
assertThat(properties.getSchemas()).isEqualTo(Arrays.asList(configuration.getSchemas()));
|
||||||
assertThat(properties.isCreateSchemas()).isEqualTo(configuration.getCreateSchemas());
|
assertThat(properties.isCreateSchemas()).isEqualTo(configuration.isCreateSchemas());
|
||||||
assertThat(properties.getTable()).isEqualTo(configuration.getTable());
|
assertThat(properties.getTable()).isEqualTo(configuration.getTable());
|
||||||
assertThat(properties.getBaselineDescription()).isEqualTo(configuration.getBaselineDescription());
|
assertThat(properties.getBaselineDescription()).isEqualTo(configuration.getBaselineDescription());
|
||||||
assertThat(MigrationVersion.fromVersion(properties.getBaselineVersion()))
|
assertThat(MigrationVersion.fromVersion(properties.getBaselineVersion()))
|
||||||
|
|
@ -113,7 +114,8 @@ class FlywayPropertiesTests {
|
||||||
ignoreProperties(configuration, "callbacks", "classLoader", "dataSource", "javaMigrations",
|
ignoreProperties(configuration, "callbacks", "classLoader", "dataSource", "javaMigrations",
|
||||||
"javaMigrationClassProvider", "resourceProvider", "resolvers");
|
"javaMigrationClassProvider", "resourceProvider", "resolvers");
|
||||||
// Properties we don't want to expose
|
// Properties we don't want to expose
|
||||||
ignoreProperties(configuration, "resolversAsClassNames", "callbacksAsClassNames", "apiExtensions", "loggers");
|
ignoreProperties(configuration, "resolversAsClassNames", "callbacksAsClassNames", "apiExtensions", "loggers",
|
||||||
|
"driver");
|
||||||
// Handled by the conversion service
|
// Handled by the conversion service
|
||||||
ignoreProperties(configuration, "baselineVersionAsString", "encodingAsString", "locationsAsStrings",
|
ignoreProperties(configuration, "baselineVersionAsString", "encodingAsString", "locationsAsStrings",
|
||||||
"targetAsString");
|
"targetAsString");
|
||||||
|
|
|
||||||
|
|
@ -309,7 +309,7 @@ bom {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
library("Flyway", "7.15.0") {
|
library("Flyway", "8.0.0") {
|
||||||
group("org.flywaydb") {
|
group("org.flywaydb") {
|
||||||
modules = [
|
modules = [
|
||||||
"flyway-core"
|
"flyway-core"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue