diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7b03f331..4cb82baa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -50,9 +50,6 @@ jobs: - name: Check run: ./gradlew check - - name: Validate schemas - run: ./gradlew :radar-schemas-tools:run --args="validate $GITHUB_WORKSPACE" - docker: # The type of runner that the job will run on runs-on: ubuntu-latest diff --git a/docker/topic_init.sh b/docker/topic_init.sh index f005cd34..a6916ce0 100755 --- a/docker/topic_init.sh +++ b/docker/topic_init.sh @@ -1,24 +1,23 @@ #!/bin/bash +NUM_TRIES=${TOPIC_INIT_TRIES:-20} + if [ -z NO_VALIDATE ]; then radar-schemas-tools validate merged fi # Create topics -echo "Creating RADAR-base topics..." +echo "Creating RADAR-base topics. Will try ${NUM_TRIES} times..." -if ! radar-schemas-tools create -c "${KAFKA_CONFIG_PATH}" -p $KAFKA_NUM_PARTITIONS -r $KAFKA_NUM_REPLICATION -b $KAFKA_NUM_BROKERS -s "${KAFKA_BOOTSTRAP_SERVERS}" merged; then - echo "FAILED TO CREATE TOPICS ... Retrying" - if ! radar-schemas-tools create -c "${KAFKA_CONFIG_PATH}" -p $KAFKA_NUM_PARTITIONS -r $KAFKA_NUM_REPLICATION -b $KAFKA_NUM_BROKERS -s "${KAFKA_BOOTSTRAP_SERVERS}" merged; then +if radar-schemas-tools create -c "${KAFKA_CONFIG_PATH}" -p $KAFKA_NUM_PARTITIONS -r $KAFKA_NUM_REPLICATION -b $KAFKA_NUM_BROKERS -s "${KAFKA_BOOTSTRAP_SERVERS}" -n ${NUM_TRIES} merged; then + echo "Created topics" +else echo "FAILED TO CREATE TOPICS" exit 1 - else - echo "Created topics at second attempt" - fi -else - echo "Topics created." fi +echo "Topics created." + echo "Registering RADAR-base schemas..." if ! radar-schemas-tools register --force -u "$SCHEMA_REGISTRY_API_KEY" -p "$SCHEMA_REGISTRY_API_SECRET" "${KAFKA_SCHEMA_REGISTRY}" merged; then echo "FAILED TO REGISTER SCHEMAS" diff --git a/java-sdk/build.gradle b/java-sdk/build.gradle index 46e1736f..c8656d59 100644 --- a/java-sdk/build.gradle +++ b/java-sdk/build.gradle @@ -3,12 +3,12 @@ //---------------------------------------------------------------------------// plugins { - id 'com.github.davidmc24.gradle.plugin.avro-base' version '1.1.0' + id 'com.github.davidmc24.gradle.plugin.avro-base' version '1.2.0' id("io.github.gradle-nexus.publish-plugin") version "1.1.0" } allprojects { - version = '0.7.1' + version = '0.7.2' group = 'org.radarbase' } diff --git a/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/SchemaCatalogue.java b/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/SchemaCatalogue.java index d6ca11cd..fb60ac4e 100644 --- a/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/SchemaCatalogue.java +++ b/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/SchemaCatalogue.java @@ -1,7 +1,13 @@ package org.radarbase.schema; -import static java.util.function.Function.identity; -import static java.util.function.Predicate.not; +import kotlin.Pair; +import org.apache.avro.Schema; +import org.apache.avro.Schema.Parser; +import org.apache.avro.generic.GenericRecord; +import org.radarbase.config.AvroTopicConfig; +import org.radarbase.schema.validation.SchemaValidator; +import org.radarbase.schema.validation.rules.SchemaMetadata; +import org.radarbase.topic.AvroTopic; import java.io.IOException; import java.nio.file.Files; @@ -15,32 +21,33 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; -import java.util.function.Predicate; import java.util.stream.Collectors; -import org.apache.avro.Schema; -import org.apache.avro.Schema.Parser; -import org.apache.avro.generic.GenericRecord; -import org.radarbase.config.AvroTopicConfig; -import org.radarbase.schema.validation.SchemaValidator; -import org.radarbase.schema.validation.rules.SchemaMetadata; -import org.radarbase.topic.AvroTopic; + +import static java.util.function.Function.identity; +import static java.util.function.Predicate.not; public class SchemaCatalogue { private final Path root; private final Map schemas; - private final List unmappedFiles; + private final List unmappedFiles; public SchemaCatalogue(Path root) throws IOException { - this(root, null, p -> Files.isRegularFile(p) && SchemaValidator.isAvscFile(p)); + this(root, null); } - public SchemaCatalogue(Path root, Scope scope, Predicate filterPath) throws IOException { + public SchemaCatalogue(Path root, Scope scope) throws IOException { this.root = root.resolve("commons"); Map schemaTemp = new HashMap<>(); - List unmappedTemp = new ArrayList<>(); + List unmappedTemp = new ArrayList<>(); - loadSchemas(schemaTemp, unmappedTemp, scope, filterPath); + if (scope != null) { + loadSchemas(schemaTemp, unmappedTemp, scope); + } else { + for (Scope useScope : Scope.values()) { + loadSchemas(schemaTemp, unmappedTemp, useScope); + } + } schemas = Collections.unmodifiableMap(schemaTemp); unmappedFiles = Collections.unmodifiableList(unmappedTemp); @@ -55,20 +62,9 @@ public SchemaCatalogue(Path root, Scope scope, Predicate filterPath) throw * @throws IllegalArgumentException if the topic configuration is null */ public AvroTopic getGenericAvroTopic(AvroTopicConfig config) { - SchemaMetadata parsedKeySchema = schemas.get(config.getKeySchema()); - if (parsedKeySchema == null) { - throw new NoSuchElementException("Key schema " + config.getKeySchema() - + " for topic " + config.getTopic() + " not found."); - } - - SchemaMetadata parsedValueSchema = schemas.get(config.getValueSchema()); - if (parsedValueSchema == null) { - throw new NoSuchElementException("Value schema " + config.getValueSchema() - + " for topic " + config.getTopic() + " not found."); - } - + Pair schemaMetadata = getSchemaMetadata(config); return new AvroTopic<>(config.getTopic(), - parsedKeySchema.getSchema(), parsedValueSchema.getSchema(), + schemaMetadata.component1().getSchema(), schemaMetadata.component2().getSchema(), GenericRecord.class, GenericRecord.class); } @@ -76,28 +72,22 @@ public Map getSchemas() { return schemas; } - public List getUnmappedAvroFiles() { + public List getUnmappedAvroFiles() { return unmappedFiles; } private void loadSchemas( Map schemas, - List unmappedFiles, - Scope scope, - Predicate filterPath) throws IOException { + List unmappedFiles, + Scope scope) throws IOException { - Path walkRoot; - if (scope != null) { - walkRoot = scope.getPath(root); - if (walkRoot == null) { - throw new IllegalArgumentException("No scope directory for scope " + scope); - } - } else { - walkRoot = root; + Path walkRoot = scope.getPath(root); + if (walkRoot == null) { + return; } List avroFiles = Files.walk(walkRoot) - .filter(filterPath) + .filter(p -> Files.isRegularFile(p) && SchemaValidator.isAvscFile(p)) .collect(Collectors.toList()); int prevSize = -1; @@ -144,6 +134,31 @@ private void loadSchemas( unmappedFiles.addAll(avroFiles.stream() .filter(p -> !mappedPaths.contains(p)) + .map(p -> new SchemaMetadata(null, scope, p)) .collect(Collectors.toList())); } + + /** + * Returns an avro topic with the schemas from this catalogue. + * @param config avro topic configuration + * @return AvroTopic with + * @throws NoSuchElementException if the key or value schema do not exist in this catalogue. + * @throws NullPointerException if the key or value schema configurations are null + * @throws IllegalArgumentException if the topic configuration is null + */ + public Pair getSchemaMetadata(AvroTopicConfig config) { + SchemaMetadata parsedKeySchema = schemas.get(config.getKeySchema()); + if (parsedKeySchema == null) { + throw new NoSuchElementException("Key schema " + config.getKeySchema() + + " for topic " + config.getTopic() + " not found."); + } + + SchemaMetadata parsedValueSchema = schemas.get(config.getValueSchema()); + if (parsedValueSchema == null) { + throw new NoSuchElementException("Value schema " + config.getValueSchema() + + " for topic " + config.getTopic() + " not found."); + } + + return new Pair<>(parsedKeySchema, parsedValueSchema); + } } diff --git a/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/SchemaValidator.java b/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/SchemaValidator.java index 991ffd7d..81815d7f 100644 --- a/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/SchemaValidator.java +++ b/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/SchemaValidator.java @@ -16,21 +16,13 @@ package org.radarbase.schema.validation; -import static org.radarbase.schema.validation.rules.Validator.raise; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import kotlin.Pair; import org.apache.avro.Schema; import org.apache.avro.Schema.Parser; import org.radarbase.schema.SchemaCatalogue; import org.radarbase.schema.Scope; +import org.radarbase.schema.specification.DataProducer; +import org.radarbase.schema.specification.SourceCatalogue; import org.radarbase.schema.validation.config.ExcludeConfig; import org.radarbase.schema.validation.rules.RadarSchemaMetadataRules; import org.radarbase.schema.validation.rules.RadarSchemaRules; @@ -38,16 +30,24 @@ import org.radarbase.schema.validation.rules.SchemaMetadataRules; import org.radarbase.schema.validation.rules.Validator; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + /** * Validator for a set of RADAR-Schemas. */ public class SchemaValidator { public static final String AVRO_EXTENSION = "avsc"; - private final Path root; private final ExcludeConfig config; - private final Validator validator; private final SchemaMetadataRules rules; + private Validator validator; /** * Schema validator for given RADAR-Schemas directory. @@ -56,52 +56,75 @@ public class SchemaValidator { */ public SchemaValidator(Path root, ExcludeConfig config) { this.config = config; - this.root = root; this.rules = new RadarSchemaMetadataRules(root, config); - this.validator = rules.getValidator(); + this.validator = rules.getValidator(false); + } + + public Stream analyseSourceCatalogue( + Scope scope, SourceCatalogue catalogue) { + this.validator = rules.getValidator(true); + Stream> producers; + if (scope != null) { + producers = catalogue.getSources().stream() + .filter(s -> s.getScope().equals(scope)); + } else { + producers = catalogue.getSources().stream(); + } + + try { + return producers.flatMap(s -> s.getData().stream()) + .flatMap(d -> { + Pair metadata = + catalogue.getSchemaCatalogue().getSchemaMetadata(d); + return Stream.of(metadata.component1(), metadata.component2()); + }) + .sorted(Comparator.comparing(s -> s.getSchema().getFullName())) + .distinct() + .flatMap(this::validate) + .distinct(); + } finally { + this.validator = rules.getValidator(false); + } } /** * TODO. * @param scope TODO. */ - public Stream analyseFiles(Scope scope) { - try { - SchemaCatalogue schemaCatalogue = new SchemaCatalogue(root, scope, - p -> Files.isRegularFile(p) - && SchemaValidator.isAvscFile(p) - && !config.skipFile(p)); - - Map useTypes = schemaCatalogue.getSchemas().entrySet().stream() - .collect(Collectors.toMap(Entry::getKey, e -> e.getValue().getSchema())); - - return Stream.concat( - schemaCatalogue.getUnmappedAvroFiles().stream() - .map(p -> { - Parser parser = new Parser(); - parser.addTypes(useTypes); - try { - parser.parse(p.toFile()); - return null; - } catch (Exception ex) { - return new ValidationException("Cannot parse schema", ex); - } - }) - .filter(Objects::nonNull), - schemaCatalogue.getSchemas().values().stream() - .flatMap(this::validate) - ); - } catch (IOException ex) { - return raise("Failed to read files: " + ex, ex); + public Stream analyseFiles(Scope scope, SchemaCatalogue schemaCatalogue) { + if (scope == null) { + return analyseFiles(schemaCatalogue); } + this.validator = rules.getValidator(false); + Map useTypes = schemaCatalogue.getSchemas().entrySet().stream() + .filter(s -> s.getValue().getScope().equals(scope)) + .collect(Collectors.toMap(Entry::getKey, e -> e.getValue().getSchema())); + + return Stream.concat( + schemaCatalogue.getUnmappedAvroFiles().stream() + .filter(s -> s.getScope().equals(scope)) + .map(p -> { + Parser parser = new Parser(); + parser.addTypes(useTypes); + try { + parser.parse(p.getPath().toFile()); + return null; + } catch (Exception ex) { + return new ValidationException("Cannot parse schema", ex); + } + }) + .filter(Objects::nonNull), + schemaCatalogue.getSchemas().values().stream() + .flatMap(this::validate) + ).distinct(); } /** * TODO. */ - public Stream analyseFiles() { + public Stream analyseFiles(SchemaCatalogue schemaCatalogue) { return Arrays.stream(Scope.values()) - .flatMap(this::analyseFiles); + .flatMap(scope -> analyseFiles(scope, schemaCatalogue)); } /** Validate a single schema in given path. */ @@ -111,6 +134,9 @@ public Stream validate(Schema schema, Path path, Scope scop /** Validate a single schema in given path. */ public Stream validate(SchemaMetadata schemaMetadata) { + if (config.skipFile(schemaMetadata.getPath())) { + return Stream.empty(); + } return validator.apply(schemaMetadata); } diff --git a/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/SpecificationsValidator.java b/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/SpecificationsValidator.java index a3395e6e..0e15f82b 100644 --- a/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/SpecificationsValidator.java +++ b/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/SpecificationsValidator.java @@ -16,13 +16,14 @@ package org.radarbase.schema.validation; -import static org.radarbase.schema.validation.ValidationHelper.SPECIFICATIONS_PATH; +import org.radarbase.schema.Scope; +import org.radarbase.schema.validation.config.ExcludeConfig; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import org.radarbase.schema.Scope; -import org.radarbase.schema.validation.config.ExcludeConfig; + +import static org.radarbase.schema.validation.ValidationHelper.SPECIFICATIONS_PATH; /** * Validates RADAR-Schemas specifications. diff --git a/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/ValidationException.java b/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/ValidationException.java index fcb71412..935b5a2b 100644 --- a/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/ValidationException.java +++ b/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/ValidationException.java @@ -22,7 +22,7 @@ * TODO. */ public class ValidationException extends RuntimeException { - private static long serialVersionUID = 1; + private static final long serialVersionUID = 1; public ValidationException(String message) { super(message); @@ -47,6 +47,6 @@ public boolean equals(Object obj) { @Override public int hashCode() { - return Objects.hash(getMessage(), getCause()); + return getMessage().hashCode(); } } diff --git a/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/rules/SchemaMetadata.java b/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/rules/SchemaMetadata.java index 89c2eb7b..add41dfe 100644 --- a/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/rules/SchemaMetadata.java +++ b/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/rules/SchemaMetadata.java @@ -1,6 +1,8 @@ package org.radarbase.schema.validation.rules; import java.nio.file.Path; +import java.util.Objects; + import org.apache.avro.Schema; import org.radarbase.schema.Scope; @@ -32,4 +34,27 @@ public Scope getScope() { public Schema getSchema() { return schema; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SchemaMetadata that = (SchemaMetadata) o; + + return scope == that.scope + && Objects.equals(path, that.path) + && Objects.equals(schema, that.schema); + } + + @Override + public int hashCode() { + int result = scope != null ? scope.hashCode() : 0; + result = 31 * result + (path != null ? path.hashCode() : 0); + return result; + } } diff --git a/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/rules/SchemaMetadataRules.java b/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/rules/SchemaMetadataRules.java index 1e8e7c62..fc26be22 100644 --- a/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/rules/SchemaMetadataRules.java +++ b/java-sdk/radar-schemas-core/src/main/java/org/radarbase/schema/validation/rules/SchemaMetadataRules.java @@ -13,14 +13,14 @@ public interface SchemaMetadataRules { * Validates any schema file. It will choose the correct validation method based on the scope * and type of the schema. */ - default Validator getValidator() { + default Validator getValidator(final boolean validateScopeSpecific) { return metadata -> { SchemaRules schemaRules = getSchemaRules(); Validator validator = validateSchemaLocation(); if (metadata.getSchema().getType().equals(Schema.Type.ENUM)) { validator = validator.and(schema(schemaRules.validateEnum())); - } else { + } else if (validateScopeSpecific) { switch (metadata.getScope()) { case ACTIVE: validator = validator.and(schema(schemaRules.validateActiveSource())); @@ -35,6 +35,8 @@ default Validator getValidator() { validator = validator.and(schema(schemaRules.validateRecord())); break; } + } else { + validator = validator.and(schema(schemaRules.validateRecord())); } return validator.apply(metadata); }; diff --git a/java-sdk/radar-schemas-core/src/test/java/org/radarbase/schema/validation/SchemaValidatorTest.java b/java-sdk/radar-schemas-core/src/test/java/org/radarbase/schema/validation/SchemaValidatorTest.java index d8c6bf70..e4783a85 100644 --- a/java-sdk/radar-schemas-core/src/test/java/org/radarbase/schema/validation/SchemaValidatorTest.java +++ b/java-sdk/radar-schemas-core/src/test/java/org/radarbase/schema/validation/SchemaValidatorTest.java @@ -33,7 +33,9 @@ import org.apache.avro.SchemaBuilder; import org.junit.Before; import org.junit.Test; +import org.radarbase.schema.SchemaCatalogue; import org.radarbase.schema.Scope; +import org.radarbase.schema.specification.SourceCatalogue; import org.radarbase.schema.validation.config.ExcludeConfig; /** @@ -50,37 +52,79 @@ public void setUp() throws IOException { } @Test - public void active() { + public void active() throws IOException { testScope(ACTIVE); } @Test - public void monitor() { + public void activeSpecifications() throws IOException { + testFromSpecification(ACTIVE); + } + + @Test + public void monitor() throws IOException { testScope(MONITOR); } @Test - public void passive() { + public void monitorSpecifications() throws IOException { + testFromSpecification(MONITOR); + } + + @Test + public void passive() throws IOException { testScope(PASSIVE); } @Test - public void kafka() { + public void passiveSpecifications() throws IOException { + testFromSpecification(PASSIVE); + } + + @Test + public void kafka() throws IOException { testScope(KAFKA); } @Test - public void catalogue() { + public void kafkaSpecifications() throws IOException { + testFromSpecification(KAFKA); + } + + @Test + public void catalogue() throws IOException { testScope(CATALOGUE); } @Test - public void connector() { + public void catalogueSpecifications() throws IOException { + testFromSpecification(CATALOGUE); + } + + @Test + public void connectorSchemas() throws IOException { testScope(CONNECTOR); } - private void testScope(Scope scope) { - String result = SchemaValidator.format(validator.analyseFiles(scope)); + @Test + public void connectorSpecifications() throws IOException { + testFromSpecification(CONNECTOR); + } + + private void testFromSpecification(Scope scope) throws IOException { + SourceCatalogue sourceCatalogue = SourceCatalogue.load(ROOT); + String result = SchemaValidator.format( + validator.analyseSourceCatalogue(scope, sourceCatalogue)); + + if (!result.isEmpty()) { + fail(result); + } + } + + private void testScope(Scope scope) throws IOException { + SchemaCatalogue schemaCatalogue = new SchemaCatalogue(ROOT, scope); + String result = SchemaValidator.format( + validator.analyseFiles(scope, schemaCatalogue)); if (!result.isEmpty()) { fail(result); diff --git a/java-sdk/radar-schemas-registration/src/main/java/org/radarbase/schema/registration/KafkaTopics.java b/java-sdk/radar-schemas-registration/src/main/java/org/radarbase/schema/registration/KafkaTopics.java index ddefd832..ce16007d 100644 --- a/java-sdk/radar-schemas-registration/src/main/java/org/radarbase/schema/registration/KafkaTopics.java +++ b/java-sdk/radar-schemas-registration/src/main/java/org/radarbase/schema/registration/KafkaTopics.java @@ -62,8 +62,21 @@ public KafkaTopics(@NotNull Map kafkaProperties) { */ @Override public void initialize(int brokers) throws InterruptedException { + initialize(brokers, 20); + } + + /** + * Wait for brokers to become available. This uses a polling mechanism, retrying with sleep + * up to the supplied numTries on failures. The sleep time is doubled every retry + * iteration until the {@value #MAX_SLEEP} is reached which then takes precedence. + * + * @param brokers number of brokers to wait for. + * @param numTries Number of times to retry in case of failure. + * @throws InterruptedException when waiting for the brokers is interrupted. + */ + @Override + public void initialize(int brokers, int numTries) throws InterruptedException { int sleep = 2; - int numTries = 20; int numBrokers = 0; for (int tries = 0; tries < numTries && numBrokers < brokers; tries++) { diff --git a/java-sdk/radar-schemas-registration/src/main/java/org/radarbase/schema/registration/TopicRegistrar.java b/java-sdk/radar-schemas-registration/src/main/java/org/radarbase/schema/registration/TopicRegistrar.java index 0f877ab5..e741e952 100644 --- a/java-sdk/radar-schemas-registration/src/main/java/org/radarbase/schema/registration/TopicRegistrar.java +++ b/java-sdk/radar-schemas-registration/src/main/java/org/radarbase/schema/registration/TopicRegistrar.java @@ -66,6 +66,8 @@ int createTopics(@NotNull SourceCatalogue catalogue, int partitions, short repli */ void initialize(int brokers) throws InterruptedException; + void initialize(int brokers, int numTries) throws InterruptedException; + /** * Ensures this topicRegistrar instance is initialized for use. */ diff --git a/java-sdk/radar-schemas-tools/src/main/java/org/radarbase/schema/tools/KafkaTopicsCommand.java b/java-sdk/radar-schemas-tools/src/main/java/org/radarbase/schema/tools/KafkaTopicsCommand.java index 90932e5b..22c31d01 100644 --- a/java-sdk/radar-schemas-tools/src/main/java/org/radarbase/schema/tools/KafkaTopicsCommand.java +++ b/java-sdk/radar-schemas-tools/src/main/java/org/radarbase/schema/tools/KafkaTopicsCommand.java @@ -42,7 +42,8 @@ public int execute(Namespace options, CommandLineApp app) { try (KafkaTopics topics = new KafkaTopics(kafkaConfig)) { try { - topics.initialize(brokers); + int numTries = options.getInt("num_tries"); + topics.initialize(brokers, numTries); } catch (IllegalStateException ex) { logger.error("Kafka brokers not yet available. Aborting."); return 1; @@ -75,6 +76,11 @@ public void addParser(ArgumentParser parser) { .help("number of brokers that are expected to be available.") .type(Integer.class) .setDefault(3); + parser.addArgument("-n", "--num-tries") + .help("number of times to try the topic registration (in case there are failures).") + .type(Integer.class) + .setDefault(20) + .choices(new IntRangeArgumentChoice(1, 100)); parser.addArgument("-t", "--topic") .help("register the schemas of one topic") .type(String.class); diff --git a/java-sdk/radar-schemas-tools/src/main/java/org/radarbase/schema/tools/SubCommand.java b/java-sdk/radar-schemas-tools/src/main/java/org/radarbase/schema/tools/SubCommand.java index 37fa137b..19cfe5ee 100644 --- a/java-sdk/radar-schemas-tools/src/main/java/org/radarbase/schema/tools/SubCommand.java +++ b/java-sdk/radar-schemas-tools/src/main/java/org/radarbase/schema/tools/SubCommand.java @@ -1,5 +1,6 @@ package org.radarbase.schema.tools; +import net.sourceforge.argparse4j.inf.ArgumentChoice; import net.sourceforge.argparse4j.inf.ArgumentParser; import net.sourceforge.argparse4j.inf.Namespace; @@ -27,7 +28,7 @@ static void addRootArgument(ArgumentParser parser) { * Execute the subcommand based on the options and app given. * * @param options the options passed on the command line. - * @param app application with source catalogue. + * @param app application with source catalogue. * @return command exit code. */ int execute(Namespace options, CommandLineApp app); @@ -39,4 +40,25 @@ static void addRootArgument(ArgumentParser parser) { * @param parser argument parser of the current subcommand. */ void addParser(ArgumentParser parser); + + class IntRangeArgumentChoice implements ArgumentChoice { + + private final int minRange; + private final int maxRange; + + public IntRangeArgumentChoice(int minRange, int maxRange) { + this.minRange = minRange; + this.maxRange = maxRange; + } + + @Override + public boolean contains(Object val) { + return val instanceof Integer && (Integer) val >= minRange && (Integer) val <= maxRange; + } + + @Override + public String textualFormat() { + return "[allowed range: " + minRange + "-" + maxRange + "]"; + } + } } diff --git a/java-sdk/radar-schemas-tools/src/main/java/org/radarbase/schema/tools/ValidatorCommand.java b/java-sdk/radar-schemas-tools/src/main/java/org/radarbase/schema/tools/ValidatorCommand.java index 25b6110e..767f8c44 100644 --- a/java-sdk/radar-schemas-tools/src/main/java/org/radarbase/schema/tools/ValidatorCommand.java +++ b/java-sdk/radar-schemas-tools/src/main/java/org/radarbase/schema/tools/ValidatorCommand.java @@ -5,16 +5,23 @@ import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.AbstractMap; +import java.util.Comparator; import java.util.Set; import java.util.TreeSet; import java.util.stream.Stream; + +import kotlin.Pair; import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.inf.ArgumentParser; import net.sourceforge.argparse4j.inf.Namespace; +import org.radarbase.schema.SchemaCatalogue; import org.radarbase.schema.Scope; +import org.radarbase.schema.specification.DataProducer; import org.radarbase.schema.validation.SchemaValidator; import org.radarbase.schema.validation.ValidationException; import org.radarbase.schema.validation.config.ExcludeConfig; +import org.radarbase.schema.validation.rules.SchemaMetadata; public class ValidatorCommand implements SubCommand { @Override @@ -32,7 +39,8 @@ public int execute(Namespace options, CommandLineApp app) { System.out.println("Validated topics:"); app.getCatalogue().getSources().stream() .flatMap(s -> s.getData().stream()) - .flatMap(applyOrIllegalException(d -> d.getTopics(app.getCatalogue().getSchemaCatalogue()))) + .flatMap(applyOrIllegalException(d -> + d.getTopics(app.getCatalogue().getSchemaCatalogue()))) .map(t -> "- " + t.getName() + " [" + t.getKeySchema().getFullName() + ": " @@ -46,36 +54,26 @@ public int execute(Namespace options, CommandLineApp app) { return 1; } - if (options.getBoolean("full")) { - SchemaValidator validator = new SchemaValidator(app.getRoot(), config); - - Stream stream = validateSchemas( - options.getString("scope"), validator); + String scopeString = options.getString("scope"); + Scope scope = scopeString != null ? Scope.valueOf(scopeString) : null; - if (options.getBoolean("quiet")) { - if (stream.count() > 0) { - return 1; - } - } else { - String result = SchemaValidator.format(stream); + Stream exceptionStream = Stream.empty(); + SchemaValidator validator = new SchemaValidator(app.getRoot(), config); - System.out.println(result); - if (options.getBoolean("verbose")) { - System.out.println("Validated schemas:"); - Set names = new TreeSet<>( - validator.getValidatedSchemas().keySet()); - for (String name : names) { - System.out.println(" - " + name); - } - System.out.println(); - } - if (!result.isEmpty()) { - return 1; - } - } + if (options.getBoolean("full")) { + exceptionStream = validator.analyseFiles( + scope, + app.getCatalogue().getSchemaCatalogue()); + } + if (options.getBoolean("from_specification")) { + exceptionStream = Stream.concat( + exceptionStream, + validator.analyseSourceCatalogue(scope, app.getCatalogue())).distinct(); } - return 0; + return resolveValidation(exceptionStream, validator, + options.getBoolean("verbose"), + options.getBoolean("quiet")); } catch (IOException e) { System.err.println("Failed to load schemas: " + e); return 1; @@ -96,21 +94,41 @@ public void addParser(ArgumentParser parser) { parser.addArgument("-q", "--quiet") .help("only set exit code.") .action(Arguments.storeTrue()); + parser.addArgument("-S", "--from-specification") + .help("validation of all schemas referenced in specifications") + .action(Arguments.storeTrue()); parser.addArgument("-f", "--full") .help("full validation of contents") .action(Arguments.storeTrue()); SubCommand.addRootArgument(parser); } - - private Stream validateSchemas( - String scopeString, - SchemaValidator validator) { - if (scopeString == null) { - return validator.analyseFiles(); + private int resolveValidation(Stream stream, + SchemaValidator validator, + boolean verbose, + boolean quiet) { + if (quiet) { + if (stream.count() > 0) { + return 1; + } } else { - return validator.analyseFiles(Scope.valueOf(scopeString)); + String result = SchemaValidator.format(stream); + + System.out.println(result); + if (verbose) { + System.out.println("Validated schemas:"); + Set names = new TreeSet<>( + validator.getValidatedSchemas().keySet()); + for (String name : names) { + System.out.println(" - " + name); + } + System.out.println(); + } + if (!result.isEmpty()) { + return 1; + } } + return 0; } private ExcludeConfig loadConfig(Path root, String configSubPath) throws IOException { diff --git a/specifications/active/aRMT-1.14.0.yml b/specifications/active/aRMT-1.14.0.yml new file mode 100644 index 00000000..0394a004 --- /dev/null +++ b/specifications/active/aRMT-1.14.0.yml @@ -0,0 +1,251 @@ +name: aRMT +vendor: RADAR +model: aRMT-App +version: 1.14.0 +assessment_type: QUESTIONNAIRE +doc: aRMT Questionnaires definition. Includes Personal Health Questionnaire Depression Scale (PHQ-8), Experience sampling method (ESM) and RSES and other data. +data: + - type: THINC_IT + topic: notification_thinc_it + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://github.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/blob/master/questionnaires/thinc_it/thinc_it_armt.json + - type: ROMBERG_TEST + doc: The value of the task is in milliseconds (ms). + topic: task_romberg_test + value_schema: .active.task.Task + questionnaire_definition_url: https://github.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/blob/master/questionnaires/romberg_test/romberg_test_armt.json + - type: 2MW_TEST + doc: The value of the task is in milliseconds (ms). + topic: task_2MW_test + value_schema: .active.task.Task + questionnaire_definition_url: https://github.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/blob/master/questionnaires/2MW_test/2MW_test_armt.json + - type: TANDEM_WALKING_TEST + doc: The value of the task is in milliseconds (ms). + topic: task_tandem_walking_test + value_schema: .active.task.Task + questionnaire_definition_url: https://github.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/blob/master/questionnaires/tandem_walking_test/tandem_walking_test_armt.json + - type: PHQ8 + topic: questionnaire_phq8 + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/phq8/phq8_armt.json + - type: ESM + topic: questionnaire_esm + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/esm/esm_armt.json + - type: AUDIO + doc: A speech questionnaire that contain two parts, a scripted text part where users will record themselves reading a text aloud, and an unscripted text part where users will record themselves answering a question. + topic: questionnaire_audio + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/audio/audio_armt.json + - type: AUDIO_2 + doc: A speech questionnaire that contain two parts, a scripted text part where users will record themselves reading a text aloud, and an unscripted text part where users will record themselves answering a question. + topic: questionnaire_audio + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/audio_2/audio_2_armt.json + - type: AUDIO_3 + doc: A speech questionnaire that contain two parts, a scripted text part where users will record themselves reading a text aloud, and an unscripted text part where users will record themselves answering a question. + topic: questionnaire_audio + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/audio_3/audio_3_armt.json + - type: AUDIO_4 + topic: questionnaire_audio + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/audio_4/audio_4_armt.json + - type: RSES + topic: questionnaire_rses + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/rses/rses_armt.json + - type: PERCEIVED_DEFICITS_QUESTIONNAIRE + topic: questionnaire_perceived_deficits_questionnaire + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/perceived_deficits_questionnaire/perceived_deficits_questionnaire_armt.json + - type: PATIENT_DETERMINED_DISEASE_STEP + topic: questionnaire_patient_determined_disease_step + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/patient_determined_disease_step/patient_determined_disease_step_armt.json + - type: ESM28Q + topic: questionnaire_esm28q + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/esm28q/esm28q_armt.json + - type: BIPQ + topic: questionnaire_bipq + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/bipq/bipq_armt.json + - type: ESM_EPI_MOD_1 + topic: questionnaire_esm_epi_mod_1 + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/esm_epi_mod_1/esm_epi_mod_1_armt.json + - type: EVENING_ASSESSMENT + topic: questionnaire_evening_assessment + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/evening_assessment/evening_assessment_armt.json + - type: MORNING_ASSESSMENT + topic: questionnaire_morning_assessment + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/morning_assessment/morning_assessment_armt.json + - type: TAM + topic: questionnaire_tam + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/tam/tam_armt.json + - type: BAARS_IV + topic: questionnaire_baars_iv + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/baars_iv/baars_iv_armt.json + - type: ARI_SELF + topic: questionnaire_ari_self + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/ari_self/ari_self_armt.json + - type: GAD7 + topic: questionnaire_gad7 + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/gad7/gad7_armt.json + - type: RPQ + topic: questionnaire_rpq + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/rpq/rpq_armt.json + - type: ART_COGNITIVE_TEST + topic: questionnaire_art_cognitive_test + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/art_cognitive_test/art_cognitive_test_armt.json + - type: COVID19_DIAGNOSIS + topic: questionnaire_covid19_diagnosis + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/covid19_diagnosis/covid19_diagnosis_armt.json + - type: COVID19_SYMPTOMS + topic: questionnaire_covid19_symptoms + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/covid19_symptoms/covid19_symptoms_armt.json + - type: CNS_COVID19_BASELINE + topic: questionnaire_cns_covid19_baseline + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/cns_covid19_baseline/cns_covid19_baseline_armt.json + - type: CNS_COVID19_FOLLOWUP + topic: questionnaire_cns_covid19_followup + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/cns_covid19_followup/cns_covid19_followup_armt.json + - type: CNS_COVID19_FOLLOWUP_V2 + topic: questionnaire_cns_covid19_followup_v2 + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/cns_covid19_followup_v2/cns_covid19_followup_v2_armt.json + - type: CNS_COVID19_FOLLOWUP_V3 + topic: questionnaire_cns_covid19_followup_v3 + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/cns_covid19_followup_v3/cns_covid19_followup_v3_armt.json + - type: ART_COVID19_FOLLOWUP + topic: questionnaire_art_covid19_followup + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/art_covid19_followup/art_covid19_followup_armt.json + - type: ADHD_MEDICATION_USE + topic: questionnaire_adhd_medication_use + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/adhd_medication_use/adhd_medication_use_armt.json + - type: QIDS + topic: questionnaire_qids + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/qids/qids_armt.json + - type: WSAS + topic: questionnaire_wsas + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/wsas/wsas_armt.json + - type: SLEEP_QUESTIONS + topic: questionnaire_rapid_sleep_questions + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/sleep_questions/sleep_questions_armt.json + - type: AUDIO_WITHOUT_UNSCRIPTED + doc: A speech questionnaire similar to the AUDIO questionnaire but without the second unscripted task. + topic: questionnaire_audio_without_unscripted + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/audio_without_unscripted/audio_without_unscripted_armt.json + - type: AUDIO_WITHOUT_UNSCRIPTED_2 + doc: A speech questionnaire similar to the AUDIO questionnaire but without the second unscripted task. + topic: questionnaire_audio_without_unscripted_2 + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/audio_without_unscripted_2/audio_without_unscripted_armt.json + - type: AUDIO_WITHOUT_UNSCRIPTED_3 + doc: A speech questionnaire similar to the AUDIO questionnaire but without the second unscripted task. + topic: questionnaire_audio_without_unscripted_3 + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/audio_without_unscripted_3/audio_without_unscripted_armt.json + - type: CAT + topic: questionnaire_cat + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/cat/cat_armt.json + - type: PSQI + topic: questionnaire_psqi + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/psqi/psqi_armt.json + - type: FSS + topic: questionnaire_fss + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/fss/fss_armt.json + - type: PCFS + topic: questionnaire_pcfs + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/pcfs/pcfs_armt.json + - type: RALPMH_COVID_SYMPTOMS + topic: questionnaire_ralpmh_covid_symptoms + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/ralpmh_covid_symptoms/ralpmh_covid_symptoms_armt.json + - type: LIPF + topic: questionnaire_lipf + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/lipf/lipf_armt.json + - type: ERS + topic: questionnaire_ers + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/ers/ers_armt.json + - type: VAS + topic: questionnaire_vas + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/vas/vas_armt.json + - type: PULSE_OX + topic: questionnaire_pulse_ox + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/pulse_ox_response/pulse_ox_response_armt.json + - type: AUDIO_WITHOUT_UNSCRIPTED_4 + doc: A speech questionnaire that contains `The North Wind` scripted text where users will record themselves reading a text aloud. + topic: questionnaire_audio_without_unscripted_4 + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/audio_without_unscripted_4/audio_without_unscripted_4_armt.json + - type: AUDIO_COUNT + doc: A speech questionnaire where users will record themselves count from 1 to 20 aloud. + topic: questionnaire_audio_count + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/audio_count/audio_count_armt.json + - type: AUDIO_VOCALISATION + doc: A speech questionnaire where users will record themselves speak the vowels displayed on the screen aloud. + topic: questionnaire_audio_vocalisation + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/audio_vocalisation/audio_vocalisation_armt.json + - type: COMPLETION_LOG + doc: Information about the completeness of each questionnaire. + topic: questionnaire_completion_log + value_schema: .monitor.questionnaire.QuestionnaireCompletionLog + - type: TIMEZONE + doc: Timezone information sent along with each questionnaire. + topic: questionnaire_timezone + value_schema: .monitor.application.ApplicationTimeZone + - type: APP_EVENT + doc: Questionnaire application interaction event. + topic: questionnaire_app_event + value_schema: .monitor.questionnaire.QuestionnaireApplicationInteractionEvent + - type: EPI_SEIZURE_DIARY + topic: questionnaire_epi_seizure_diary + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/epi_seizure_diary/epi_seizure_diary_armt.json + - type: EPI_EVENING_QUESTIONNAIRE + topic: questionnaire_epi_evening_questionnaire + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/epi_evening_questionnaire/epi_evening_questionnaire_armt.json + - type: EPI_WSAS + topic: questionnaire_epi_wsas + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/epi_wsas/epi_wsas_armt.json + - type: BP_QUESTIONNAIRE + topic: questionnaire_bp + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/bp/bp_armt.json + - type: HLH_QUESTIONNAIRE + topic: questionnaire_hlh + value_schema: .active.questionnaire.Questionnaire + questionnaire_definition_url: https://raw.githubusercontent.com/RADAR-base/RADAR-REDCap-aRMT-Definitions/master/questionnaires/hlh/hlh_armt.json