Skip to content
This repository has been archived by the owner on May 30, 2024. It is now read-only.

Commit

Permalink
prepare 5.8.0 release (#263)
Browse files Browse the repository at this point in the history
* CI fix

* more event test improvements

* misc cleanup + test improvements

* misc cleanup + test improvements

* make intVariation and doubleVariation non-nullable

* (5.0) don't use jar magic to find out our own version string

* fix test

* make intVariation and doubleVariation non-nullable

* better unit test coverage of LDClient and FeatureFlagsState

* test coverage improvements + minor fixes

* better temp file handling in file data source tests

* revert file data source implementation change for now

* revert unnecessary change

* comment about file watching on Mac

* add slight delay to avoid timing-dependent test flakiness

* test fixes/comments

* (5.0) use simpler and more stable logger names

* better instructions

* more convenient way to set EventSource logger name

* (5.0) add HTTP default headers method + some component refactoring

* don't need to pass the whole config object to describeConfiguration()

* simplify test logic for HTTP headers

* (5.0) final test coverage improvements, for now, with enforcement

* re-simplify DataBuilder

* increase timeouts

* misc fixes

* rm unnecessary override

* indents

* update benchmark code for API change

* support loading file data from a classpath resource

* update metadata so Releaser knows about 4.x branch

* minor test fixes

* make class final

* rm beta changelog items

* test data source

* more info about coverage in CONTRIBUTING.md

* misc fixes/tests

* use java-sdk-common 1.0.0

* use okhttp-eventsource 2.3.0

* use okhttp-eventsource 2.3.1 for thread fix

* fix flaky tests due to change in EventSource error reporting

* remove support for indirect put and indirect patch

* fix typo in javadoc example code

* clean up polling logic, fix status updating after an outage, don't reinit store unnecessarily (#256)

* slightly change semantics of boolean setters, improve tests, misc cleanup

* avoid NPEs if LDUser was deserialized by Gson (#257)

* avoid NPEs if LDUser was deserialized by Gson

* add test

* fix release metadata

* prepare 4.14.1 release (#200)

* Releasing version 4.14.1

* exclude Kotlin metadata from jar + fix misc Gradle problems

* update CI and Gradle to test with newer JDKs (#259)

* update okhttp to 3.14.9 (fixes incompatibility with OpenJDK 8.0.252)

* prepare 4.14.2 release (#205)

* Releasing version 4.14.2

* update okhttp to 4.8.1 (fixes incompatibility with OpenJDK 8.0.252)

* gitignore

* Bump SnakeYAML from 1.19 to 1.26 to address CVE-2017-18640

* prepare 4.14.3 release (#209)

* Releasing version 4.14.3

* comments

* only log initialization message once in polling mode

* [ch89935] Correct some logging call format strings (#264)

Also adds debug logs for full exception information in a couple locations.

* [ch90109] Remove outdated trackMetric comment from before service support. (#265)

* Fix compatibility with Java 7.

* Remove import that is no longer used.

* add Java 7 build (#267)

* prepare 4.14.4 release (#214)

* Releasing version 4.14.4

* add and use getSocketFactory

* alignment

* add socketFactory to builder

* test socket factory builder

* preserve dummy CI config file when pushing to gh-pages (#271)

* fix concatenation when base URI has a context path (#270)

* fix shaded jar builds to exclude Jackson classes and not modify Jackson return types (#268)

* add test httpClientCanUseCustomSocketFactory for DefaultFeatureRequestor

* add httpClientCanUseCustomSocketFactory() test for DefaultEventSenderTest

* add httpClientCanUseCustomSocketFactory() test to StreamProcessorTest

* pass URI to in customSocketFactory event test

* make test less ambiguous

* copy rules to new FlagBuilder instances (#273)

* Bump guava version (#274)

* Removed the guides link

* increment versions when loading file data, so FlagTracker will work (#275)

* increment versions when loading file data, so FlagTracker will work

* update doc comment about flag change events with file data

* add ability to ignore duplicate keys in file data (#276)

* add alias events (#278)

* add alias events and function
* update tests for new functionality
* update javadoc strings

* add validation of javadoc build to CI

* update commons-codec to 1.15 (#279)

* Add support for experiment rollouts

* add tests and use seed for allocating user to partition

* test serialization and add check for isExperiment

* fix PollingProcessorTest test race condition + other test issues (#282)

* use launchdarkly-java-sdk-common 1.1.0-alpha-expalloc.2

* Update src/test/java/com/launchdarkly/sdk/server/EvaluatorTest.java

Co-authored-by: Sam Stokes <sstokes@launchdarkly.com>

* Update src/test/java/com/launchdarkly/sdk/server/EvaluatorTest.java

Co-authored-by: Sam Stokes <sstokes@launchdarkly.com>

* Update src/test/java/com/launchdarkly/sdk/server/EvaluatorTest.java

Co-authored-by: Sam Stokes <sstokes@launchdarkly.com>

* Update src/test/java/com/launchdarkly/sdk/server/EvaluatorTest.java

Co-authored-by: Sam Stokes <sstokes@launchdarkly.com>

* changes per code review comments

* Please enter the commit message for your changes. Lines starting

* fix null pointer exception

* address code review comments

* address more comments

* missed a ! for isUntracked()

* fix default boolean for json

* make untracked FALSE by default

* refactoring of bucketing logic to remove the need for an extra result object (#283)

* add comment to enum

* various JSON fixes, update common-sdk (#284)

* simlpify the logic and make it match node/.Net sdks

* Update src/main/java/com/launchdarkly/sdk/server/EventFactory.java

Co-authored-by: Sam Stokes <sstokes@launchdarkly.com>

* add the same comment as the Node SDK

* Remove outdated/meaningless doc comment. (#286)

* protect against NPEs if flag/segment JSON contains a null value

* use java-sdk-common 1.2.0

* fix Jackson-related build issues (again) (#288)

* update to okhttp-eventsource patch for stream retry bug, improve tests (#289)

* update to okhttp-eventsource patch for stream retry bug, improve test

* add test for appropriate stream retry

* add public builder for FeatureFlagsState (#290)

* add public builder for FeatureFlagsState

* javadoc fixes

* clarify FileData doc comment to say you shouldn't use offline mode (#291)

* improve validation of SDK key so we won't throw an exception that contains the key (#293)

* fix javadoc link in FileData comment (#294)

* fix PollingProcessor 401 behavior and use new HTTP test helpers (#292)

* re-fix metadata to remove Jackson dependencies, also remove Class-Path from manifest (#295)

* make FeatureFlagsState.Builder.build() public (#297)

* clean up tests using java-test-helpers 1.1.0 (#296)

* use Releaser v2 config + newer CI images (#298)

* [ch123129] Fix `PollingDataSourceBuilder` example. (#299)

* Updates docs URLs

* always use US locale when parsing HTTP dates

* use Gson 2.8.9

* don't try to send more diagnostic events after an unrecoverable HTTP error

* ensure module-info file isn't copied into our jars during build

* use Gradle 7

* update build for benchmarks

* more Gradle 7 compatibility changes for benchmark job

* test with Java 17 in CI (#307)

* test with Java 17 in CI

* also test in Java 17 for Windows

* fix choco install command

* do date comparisons as absolute times, regardless of time zone (#310)

* fix suppression of nulls in JSON representations (#311)

* fix suppression of nulls in JSON representations

* distinguish between situations where we do or do not want to suppress nulls

* fix identify/track null user key check, also don't create index event for alias

* use latest java-sdk-common

* fix setting of trackEvents/trackReason in allFlagsState data when there's an experiment

* implement contract tests (#314)

* Merge Big Segments feature branch for 5.7.0 release (#316)

Includes Big Segments implementation and contract test support for the new behavior.

* Fix for pom including SDK common library as a dependency. (#317)

* Upload JUnit XML to CircleCI on failure (#320)

Fix a bug in the CircleCI config that was only uploading JUnit XML on _success_, not failure.

* Add application tag support (#319)

Co-authored-by: Eli Bishop <eli@launchdarkly.com>
Co-authored-by: LaunchDarklyCI <dev@launchdarkly.com>
Co-authored-by: LaunchDarklyCI <LaunchDarklyCI@users.noreply.github.com>
Co-authored-by: Gavin Whelan <gwhelan@launchdarkly.com>
Co-authored-by: ssrm <ssrm@users.noreply.github.com>
Co-authored-by: Harpo Roeder <hroeder@launchdarkly.com>
Co-authored-by: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com>
Co-authored-by: Elliot <35050275+Apache-HB@users.noreply.github.com>
Co-authored-by: Robert J. Neal <rneal@launchdarkly.com>
Co-authored-by: Robert J. Neal <robertjneal@users.noreply.github.com>
Co-authored-by: Sam Stokes <sstokes@launchdarkly.com>
Co-authored-by: LaunchDarklyReleaseBot <launchdarklyreleasebot@launchdarkly.com>
Co-authored-by: Ember Stevens <ember.stevens@launchdarkly.com>
Co-authored-by: ember-stevens <79482775+ember-stevens@users.noreply.github.com>
Co-authored-by: Alex Engelberg <alex.benjamin.engelberg@gmail.com>
  • Loading branch information
16 people authored Apr 18, 2022
1 parent c65c6e9 commit c7d4cab
Show file tree
Hide file tree
Showing 19 changed files with 321 additions and 13 deletions.
8 changes: 5 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,11 @@ jobs:
./gradlew jacocoTestReport
mkdir -p coverage/
cp -r build/reports/jacoco/test/* ./coverage
- run: mkdir -p ~/junit/
- run:
name: Save test results
command: find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/junit/ \;
command: |
mkdir -p ~/junit/
find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/junit/ \;
when: always

- run: make build-contract-tests
Expand Down Expand Up @@ -126,7 +127,8 @@ jobs:
name: save test results
command: |
mkdir .\junit
cp build/test-results/test/*.xml junit
cp build/test-results/test/*.xml junit -ErrorAction SilentlyContinue
when: always
- store_test_results:
path: .\junit
- store_artifacts:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public static class SdkConfigParams {
SdkConfigStreamParams streaming;
SdkConfigEventParams events;
SdkConfigBigSegmentsParams bigSegments;
SdkConfigTagParams tags;
}

public static class SdkConfigStreamParams {
Expand All @@ -49,6 +50,11 @@ public static class SdkConfigBigSegmentsParams {
Long statusPollIntervalMs;
Long staleAfterMs;
}

public static class SdkConfigTagParams {
String applicationId;
String applicationVersion;
}

public static class CommandParams {
String command;
Expand Down
12 changes: 12 additions & 0 deletions contract-tests/service/src/main/java/sdktest/SdkClientEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.launchdarkly.sdk.server.FlagsStateOption;
import com.launchdarkly.sdk.server.LDClient;
import com.launchdarkly.sdk.server.LDConfig;
import com.launchdarkly.sdk.server.integrations.ApplicationInfoBuilder;
import com.launchdarkly.sdk.server.integrations.BigSegmentsConfigurationBuilder;
import com.launchdarkly.sdk.server.integrations.EventProcessorBuilder;
import com.launchdarkly.sdk.server.integrations.StreamingDataSourceBuilder;
Expand Down Expand Up @@ -235,6 +236,17 @@ private LDConfig buildSdkConfig(SdkConfigParams params) {
}
builder.bigSegments(bsb);
}

if (params.tags != null) {
ApplicationInfoBuilder ab = Components.applicationInfo();
if (params.tags.applicationId != null) {
ab.applicationId(params.tags.applicationId);
}
if (params.tags.applicationVersion != null) {
ab.applicationVersion(params.tags.applicationVersion);
}
builder.applicationInfo(ab);
}

return builder.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public class TestService {
"all-flags-client-side-only",
"all-flags-details-only-for-tracked-flags",
"all-flags-with-reasons",
"big-segments"
"big-segments",
"tags"
};

static final Gson gson = new GsonBuilder().serializeNulls().create();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.launchdarkly.sdk.server;

import com.launchdarkly.sdk.server.interfaces.ApplicationInfo;
import com.launchdarkly.sdk.server.interfaces.BasicConfiguration;
import com.launchdarkly.sdk.server.interfaces.ClientContext;
import com.launchdarkly.sdk.server.interfaces.HttpConfiguration;
Expand Down Expand Up @@ -51,7 +52,8 @@ private ClientContextImpl(
ScheduledExecutorService sharedExecutor,
DiagnosticAccumulator diagnosticAccumulator
) {
this.basicConfiguration = new BasicConfiguration(sdkKey, configuration.offline, configuration.threadPriority);
ApplicationInfo applicationInfo = configuration.applicationInfoBuilder.createApplicationInfo();
this.basicConfiguration = new BasicConfiguration(sdkKey, configuration.offline, configuration.threadPriority, applicationInfo);

this.httpConfiguration = configuration.httpConfigFactory.createHttpConfiguration(basicConfiguration);
this.loggingConfiguration = configuration.loggingConfigFactory.createLoggingConfiguration(basicConfiguration);
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/com/launchdarkly/sdk/server/Components.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.launchdarkly.sdk.server.ComponentsImpl.PersistentDataStoreBuilderImpl;
import com.launchdarkly.sdk.server.ComponentsImpl.PollingDataSourceBuilderImpl;
import com.launchdarkly.sdk.server.ComponentsImpl.StreamingDataSourceBuilderImpl;
import com.launchdarkly.sdk.server.integrations.ApplicationInfoBuilder;
import com.launchdarkly.sdk.server.integrations.BigSegmentsConfigurationBuilder;
import com.launchdarkly.sdk.server.integrations.EventProcessorBuilder;
import com.launchdarkly.sdk.server.integrations.HttpConfigurationBuilder;
Expand Down Expand Up @@ -320,4 +321,27 @@ public static HttpAuthentication httpBasicAuthentication(String username, String
public static LoggingConfigurationBuilder logging() {
return new LoggingConfigurationBuilderImpl();
}

/**
* Returns a configuration builder for the SDK's application metadata.
* <p>
* Passing this to {@link LDConfig.Builder#applicationInfo(com.launchdarkly.sdk.server.integrations.ApplicationInfoBuilder)},
* after setting any desired properties on the builder, applies this configuration to the SDK.
* <pre><code>
* LDConfig config = new LDConfig.Builder()
* .applicationInfo(
* Components.applicationInfo()
* .applicationId("authentication-service")
* .applicationVersion("1.0.0")
* )
* .build();
* </code></pre>
*
* @return a builder object
* @since 5.8.0
* @see LDConfig.Builder#applicationInfo(com.launchdarkly.sdk.server.integrations.ApplicationInfoBuilder)
*/
public static ApplicationInfoBuilder applicationInfo() {
return new ApplicationInfoBuilder();
}
}
6 changes: 6 additions & 0 deletions src/main/java/com/launchdarkly/sdk/server/ComponentsImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,12 @@ public HttpConfiguration createHttpConfiguration(BasicConfiguration basicConfigu
ImmutableMap.Builder<String, String> headers = ImmutableMap.builder();
headers.put("Authorization", basicConfiguration.getSdkKey());
headers.put("User-Agent", "JavaClient/" + Version.SDK_VERSION);
if (basicConfiguration.getApplicationInfo() != null) {
String tagHeader = Util.applicationTagHeader(basicConfiguration.getApplicationInfo());
if (!tagHeader.isEmpty()) {
headers.put("X-LaunchDarkly-Tags", tagHeader);
}
}
if (wrapperName != null) {
String wrapperId = wrapperVersion == null ? wrapperName : (wrapperName + "/" + wrapperVersion);
headers.put("X-LaunchDarkly-Wrapper", wrapperId);
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/com/launchdarkly/sdk/server/LDConfig.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.launchdarkly.sdk.server;

import com.launchdarkly.sdk.EvaluationReason;
import com.launchdarkly.sdk.server.integrations.ApplicationInfoBuilder;
import com.launchdarkly.sdk.server.integrations.BigSegmentsConfigurationBuilder;
import com.launchdarkly.sdk.server.interfaces.BigSegmentStoreFactory;
import com.launchdarkly.sdk.server.interfaces.DataSourceFactory;
Expand All @@ -25,6 +26,7 @@ public final class LDConfig {

protected static final LDConfig DEFAULT = new Builder().build();

final ApplicationInfoBuilder applicationInfoBuilder;
final BigSegmentsConfigurationBuilder bigSegmentsConfigBuilder;
final DataSourceFactory dataSourceFactory;
final DataStoreFactory dataStoreFactory;
Expand All @@ -46,6 +48,8 @@ protected LDConfig(Builder builder) {
this.eventProcessorFactory = builder.eventProcessorFactory == null ? Components.sendEvents() :
builder.eventProcessorFactory;
}
this.applicationInfoBuilder = builder.applicationInfoBuilder == null ? Components.applicationInfo() :
builder.applicationInfoBuilder;
this.bigSegmentsConfigBuilder = builder.bigSegmentsConfigBuilder == null ?
Components.bigSegments(null) : builder.bigSegmentsConfigBuilder;
this.dataStoreFactory = builder.dataStoreFactory == null ? Components.inMemoryDataStore() :
Expand All @@ -72,6 +76,7 @@ protected LDConfig(Builder builder) {
* </pre>
*/
public static class Builder {
private ApplicationInfoBuilder applicationInfoBuilder = null;
private BigSegmentsConfigurationBuilder bigSegmentsConfigBuilder = null;
private DataSourceFactory dataSourceFactory = null;
private DataStoreFactory dataStoreFactory = null;
Expand All @@ -89,6 +94,22 @@ public static class Builder {
public Builder() {
}

/**
* Sets the SDK's application metadata, which may be used in LaunchDarkly analytics or other product features,
* but does not affect feature flag evaluations.
* <p>
* This object is normally a configuration builder obtained from {@link Components#applicationInfo()},
* which has methods for setting individual logging-related properties.
*
* @param applicationInfoBuilder a configuration builder object returned by {@link Components#applicationInfo()}
* @return the builder
* @since 5.8.0
*/
public Builder applicationInfo(ApplicationInfoBuilder applicationInfoBuilder) {
this.applicationInfoBuilder = applicationInfoBuilder;
return this;
}

/**
* Sets the configuration of the SDK's Big Segments feature.
* <p>
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/com/launchdarkly/sdk/server/Util.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.launchdarkly.sdk.server;

import com.launchdarkly.sdk.server.Loggers;
import com.launchdarkly.sdk.server.interfaces.ApplicationInfo;
import com.launchdarkly.sdk.server.interfaces.HttpAuthentication;
import com.launchdarkly.sdk.server.interfaces.HttpConfiguration;

Expand All @@ -13,7 +15,10 @@
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.concurrent.TimeUnit;

import static com.google.common.collect.Iterables.transform;
Expand Down Expand Up @@ -207,4 +212,35 @@ static URI concatenateUriPath(URI baseUri, String path) {
String addPath = path.startsWith("/") ? path.substring(1) : path;
return URI.create(uriStr + (uriStr.endsWith("/") ? "" : "/") + addPath);
}

// Tag values must not be empty, and only contain letters, numbers, `.`, `_`, or `-`.
private static Pattern TAG_VALUE_REGEX = Pattern.compile("^[\\w.-]+$");

/**
* Builds the "X-LaunchDarkly-Tags" HTTP header out of the configured application info.
*
* @param applicationInfo the application metadata
* @return a space-separated string of tags, e.g. "application-id/authentication-service application-version/1.0.0"
*/
static String applicationTagHeader(ApplicationInfo applicationInfo) {
String[][] tags = {
{"applicationId", "application-id", applicationInfo.getApplicationId()},
{"applicationVersion", "application-version", applicationInfo.getApplicationVersion()},
};
List<String> parts = new ArrayList<>();
for (String[] row : tags) {
String javaKey = row[0];
String tagKey = row[1];
String tagVal = row[2];
if (tagVal == null) {
continue;
}
if (!TAG_VALUE_REGEX.matcher(tagVal).matches()) {
Loggers.MAIN.warn("Value of ApplicationInfo.{} contained invalid characters and was discarded", javaKey);
continue;
}
parts.add(tagKey + "/" + tagVal);
}
return String.join(" ", parts);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.launchdarkly.sdk.server.integrations;

import com.launchdarkly.sdk.server.Components;
import com.launchdarkly.sdk.server.interfaces.ApplicationInfo;

/**
* Contains methods for configuring the SDK's application metadata.
* <p>
* Application metadata may be used in LaunchDarkly analytics or other product features, but does not affect feature flag evaluations.
* <p>
* If you want to set non-default values for any of these fields, create a builder with
* {@link Components#applicationInfo()}, change its properties with the methods of this class,
* and pass it to {@link com.launchdarkly.sdk.server.LDConfig.Builder#applicationInfo(ApplicationInfoBuilder)}:
* <pre><code>
* LDConfig config = new LDConfig.Builder()
* .applicationInfo(
* Components.applicationInfo()
* .applicationId("authentication-service")
* .applicationVersion("1.0.0")
* )
* .build();
* </code></pre>
* <p>
*
* @since 5.8.0
*/
public final class ApplicationInfoBuilder {
private String applicationId;
private String applicationVersion;

/**
* Create an empty ApplicationInfoBuilder.
*
* @see Components#applicationInfo()
*/
public ApplicationInfoBuilder() {}

/**
* Sets a unique identifier representing the application where the LaunchDarkly SDK is running.
* <p>
* This can be specified as any string value as long as it only uses the following characters: ASCII
* letters, ASCII digits, period, hyphen, underscore. A string containing any other characters will be
* ignored.
*
* @param applicationId the application identifier
* @return the builder
*/
public ApplicationInfoBuilder applicationId(String applicationId) {
this.applicationId = applicationId;
return this;
}

/**
* Sets a unique identifier representing the version of the application where the LaunchDarkly SDK
* is running.
* <p>
* This can be specified as any string value as long as it only uses the following characters: ASCII
* letters, ASCII digits, period, hyphen, underscore. A string containing any other characters will be
* ignored.
*
* @param applicationVersion the application version
* @return the builder
*/
public ApplicationInfoBuilder applicationVersion(String applicationVersion) {
this.applicationVersion = applicationVersion;
return this;
}

/**
* Called internally by the SDK to create the configuration object.
*
* @return the configuration object
*/
public ApplicationInfo createApplicationInfo() {
return new ApplicationInfo(applicationId, applicationVersion);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.launchdarkly.sdk.server.interfaces;

import com.launchdarkly.sdk.server.integrations.ApplicationInfoBuilder;

/**
* Encapsulates the SDK's application metadata.
* <p>
* See {@link ApplicationInfoBuilder} for more details on these properties.
*
* @since 5.8.0
*/
public final class ApplicationInfo {
private String applicationId;
private String applicationVersion;

/**
* Used internally by the SDK to store application metadata.
*
* @param applicationId the application ID
* @param applicationVersion the application version
* @see ApplicationInfoBuilder
*/
public ApplicationInfo(String applicationId, String applicationVersion) {
this.applicationId = applicationId;
this.applicationVersion = applicationVersion;
}

/**
* A unique identifier representing the application where the LaunchDarkly SDK is running.
*
* @return the application identifier, or null
*/
public String getApplicationId() {
return applicationId;
}

/**
* A unique identifier representing the version of the application where the
* LaunchDarkly SDK is running.
*
* @return the application version, or null
*/
public String getApplicationVersion() {
return applicationVersion;
}
}
Loading

0 comments on commit c7d4cab

Please sign in to comment.