Skip to content

Commit 0a465e9

Browse files
authored
Release 1.2.0
Merge pull request #470 from cph-cachet/develop
2 parents 6c99354 + da53151 commit 0a465e9

File tree

113 files changed

+3565
-1814
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

113 files changed

+3565
-1814
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,23 +60,23 @@ Two key **design goals** differentiate this project from similar projects:
6060

6161
![Subsystem decomposition](https://i.imgur.com/hEsTHNk.png)
6262

63-
- [**Protocols**](docs/carp-protocols.md): Implements open standards which can describe a study protocol—how a study should be run. Essentially, this subsystem has no _technical_ dependencies on any particular sensor technology or application as it merely describes why, when, and what data should be collected.
63+
- [**Protocols**](docs/carp-protocols.md): Supports management of study protocols—how a study should be run. Essentially, this subsystem has no _technical_ dependencies on any particular sensor technology or application as the containing study protocols merely describe why, when, and what data should be collected.
6464

6565
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/dk.cachet.carp.protocols/carp.protocols.core/badge.svg)](https://mvnrepository.com/artifact/dk.cachet.carp.protocols) [![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/dk.cachet.carp.protocols/carp.protocols.core?server=https%3A%2F%2Foss.sonatype.org)](https://oss.sonatype.org/content/repositories/snapshots/dk/cachet/carp/protocols/)
6666

6767
- [**Studies**](docs/carp-studies.md): Supports management of research studies, including the recruitment of participants and assigning metadata (e.g., contact information). This subsystem maps pseudonymized data (managed by the 'deployments' subsystem) to actual participants.
6868

6969
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/dk.cachet.carp.studies/carp.studies.core/badge.svg)](https://mvnrepository.com/artifact/dk.cachet.carp.studies) [![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/dk.cachet.carp.studies/carp.studies.core?server=https%3A%2F%2Foss.sonatype.org)](https://oss.sonatype.org/content/repositories/snapshots/dk/cachet/carp/studies/)
7070

71-
- [**Deployments**](docs/carp-deployments.md): Maps the information specified in a study protocol to runtime configurations used by the 'clients' subystem to run the protocol on concrete devices (e.g., a smartphone) and allow researchers to monitor their state. To start collecting data, participants need to be invited, devices need to be registered, and consent needs to be given to collect the requested data.
71+
- [**Deployments**](docs/carp-deployments.md): Maps the information specified in a study protocol to runtime configurations used by 'client' subsystems to run the protocol on concrete devices (e.g., a smartphone) and allow researchers to monitor their state. To start collecting data, participants need to be invited, devices need to be registered, and consent needs to be given to collect the requested data.
7272

7373
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/dk.cachet.carp.deployments/carp.deployments.core/badge.svg)](https://mvnrepository.com/artifact/dk.cachet.carp.deployments) [![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/dk.cachet.carp.deployments/carp.deployments.core?server=https%3A%2F%2Foss.sonatype.org)](https://oss.sonatype.org/content/repositories/snapshots/dk/cachet/carp/deployments/)
7474

75-
- [**Clients**](docs/carp-clients.md): The runtime which performs the actual data collection on a device (e.g., desktop computer or smartphone). This subsystem contains reusable components which understand the runtime configuration derived from a study protocol by the deployment subsystem. Integrations with sensors are loaded through a 'device data collector' plug-in system to decouple sensing—not part of core—from sensing logic.
75+
- [**Clients**](docs/carp-clients.md): The runtime which performs the actual data collection on a device (e.g., desktop computer or smartphone). This subsystem contains reusable components which understand the runtime configuration derived from a study protocol by the 'deployment' subsystem. Integrations with sensors are loaded through a 'device data collector' plug-in system to decouple sensing—not part of core—from sensing logic.
7676

7777
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/dk.cachet.carp.clients/carp.clients.core/badge.svg?color=orange)](https://mvnrepository.com/artifact/dk.cachet.carp.clients) [![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/dk.cachet.carp.clients/carp.clients.core?server=https%3A%2F%2Foss.sonatype.org)](https://oss.sonatype.org/content/repositories/snapshots/dk/cachet/carp/clients/)
7878

79-
- [**Data**](docs/carp-data.md): Contains all pseudonymized data. In combination with the original study protocol, the full provenance of the data (when/why it was collected) is known.
79+
- [**Data**](docs/carp-data.md): Contains all pseudonymized data. In combination with the original study protocol, the full provenance of the data (when/why it was collected) is known. In combination with data from the 'studies' subsystem, the data can be linked back to individual participants.
8080

8181
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/dk.cachet.carp.data/carp.data.core/badge.svg)](https://mvnrepository.com/artifact/dk.cachet.carp.data) [![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/dk.cachet.carp.data/carp.data.core?server=https%3A%2F%2Foss.sonatype.org)](https://oss.sonatype.org/content/repositories/snapshots/dk/cachet/carp/data/)
8282

@@ -363,9 +363,9 @@ if ( status is StudyStatus.RegisteringDevices )
363363

364364
In case you want to contribute, please follow our [contribution guidelines](https://github.com/cph-cachet/carp.core-kotlin/blob/develop/CONTRIBUTING.md).
365365

366-
We recommend using IntelliJ IDEA 2022, as this is the development environment we use and is therefore fully tested.
366+
We recommend using IntelliJ IDEA 2023, as this is the development environment we use and is therefore fully tested.
367367

368-
- Open the project folder in IntelliJ 2022.
368+
- Open the project folder in IntelliJ 2023.
369369
- Make sure Google Chrome is installed; JS unit tests are run on headless Chrome.
370370
- In case you want to run TypeScript declaration tests (`verifyTsDeclarations`), install node.
371371
- To build/test/publish, click "Edit Configurations" to add configurations for [the included Gradle tasks](#gradle-tasks), or run them from the Gradle tool window.

build.gradle

Lines changed: 64 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,37 @@ buildscript {
33
ext {
44
// Version used for submodule artifacts.
55
// Snapshot publishing changes (or adds) the suffix after '-' with 'SNAPSHOT' prior to publishing.
6-
globalVersion = '1.1.2'
7-
clientsVersion = '1.1.2-alpha.1' // The clients subsystem is still expected to change drastically.
6+
globalVersion = '1.2.0'
7+
clientsVersion = '1.2.0-alpha.1' // The clients subsystem is still expected to change drastically.
88

99
versions = [
1010
// Kotlin multiplatform versions.
11-
kotlin:'1.8.21',
12-
serialization:'1.5.0',
13-
coroutines:'1.7.0',
14-
datetime:'0.4.0',
11+
kotlin:'1.9.23',
12+
serialization:'1.6.3',
13+
coroutines:'1.8.0',
14+
datetime:'0.5.0',
1515

1616
// JVM versions.
1717
jvmTarget:'1.8',
18-
dokkaPlugin:'1.8.10',
18+
dokkaPlugin:'1.9.20',
1919
reflections:'0.10.2',
2020

2121
// JS versions.
22-
nodePlugin:'4.0.0',
22+
nodePlugin:'7.0.2',
2323
bigJs:'6.2.1',
2424

2525
// DevOps versions.
26-
detektPlugin:'1.22.0',
26+
detektPlugin:'1.23.5',
2727
detektVerifyImplementation:'1.2.5',
2828
nexusPublishPlugin:'1.3.0',
29-
apacheCommons:'2.11.0'
29+
apacheCommons:'2.15.1'
3030
]
3131

3232
commonModule = subprojects.find { it.name == 'carp.common' }
3333
coreModules = subprojects.findAll { it.name.endsWith( '.core' ) }
34+
testModules = subprojects.findAll { it.name == 'carp.common.test' || it.name == 'carp.test' }
3435
publishNpmModule = subprojects.find { it.name == 'publish-npm-packages' }
36+
allModules = coreModules + testModules + commonModule + publishNpmModule
3537
devOpsModules =
3638
subprojects.findAll {it.name == 'carp.detekt' || it.name == 'rpc' } + publishNpmModule
3739
}
@@ -91,6 +93,27 @@ configure( subprojects - devOpsModules ) {
9193
generateTypeScriptDefinitions()
9294
}
9395

96+
targets.configureEach {
97+
compilations.configureEach {
98+
def isTestSourceSet = it.name == 'test'
99+
100+
compilerOptions.configure((Action) {
101+
// Treat compilation warning as errors for all compilation targets.
102+
it.allWarningsAsErrors = true
103+
104+
// We do not mind being early adopters of Jetbrains APIs likely to change in the future.
105+
it.optIn.add('kotlin.RequiresOptIn')
106+
it.optIn.add('kotlin.time.ExperimentalTime')
107+
it.optIn.add('kotlin.js.ExperimentalJsExport')
108+
if (isTestSourceSet)
109+
{
110+
it.optIn.add('kotlinx.coroutines.ExperimentalCoroutinesApi')
111+
}
112+
it.freeCompilerArgs.add('-Xexpect-actual-classes') // https://youtrack.jetbrains.com/issue/KT-61573
113+
} )
114+
}
115+
}
116+
94117
sourceSets {
95118
commonMain {
96119
dependencies {
@@ -110,29 +133,9 @@ configure( subprojects - devOpsModules ) {
110133
implementation "org.reflections:reflections:${versions.reflections}"
111134
}
112135
}
113-
114-
all {
115-
def isTestSourceSet = it.name.endsWith('Test')
116-
117-
languageSettings {
118-
// We do not mind being early adopters of Jetbrains APIs likely to change in the future.
119-
optIn('kotlin.RequiresOptIn')
120-
optIn('kotlin.time.ExperimentalTime')
121-
optIn('kotlin.js.ExperimentalJsExport')
122-
if (isTestSourceSet)
123-
{
124-
optIn('kotlinx.coroutines.ExperimentalCoroutinesApi')
125-
}
126-
}
127-
}
128136
}
129137
}
130138

131-
// Treat compilation warning as errors for all compilation targets.
132-
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
133-
kotlinOptions { allWarningsAsErrors = true }
134-
}
135-
136139
// Publish configuration.
137140
// For signing and publishing to work, a 'publish.properties' file needs to be added to the root containing:
138141
// The OpenPGP credentials to sign all artifacts:
@@ -233,15 +236,15 @@ task setSnapshotVersion {
233236

234237
// TypeScript ambient declaration verification.
235238
def typescriptFolder = 'typescript-declarations'
239+
def npmScope = "@cachet"
236240
apply plugin: 'com.github.node-gradle.node'
237241
task setupTsProject(type: NpmTask) {
238242
workingDir = file(typescriptFolder)
239243
args = ['install']
240244
}
241245
task copyTestJsSources(type: Copy, dependsOn: setupTsProject) {
242246
// Compile production sources for CARP, and the JS publication project (`publishNpmModule`).
243-
def projects = coreModules + commonModule + publishNpmModule
244-
projects.each {
247+
allModules.each {
245248
def project = it.name
246249
dependsOn("$project:jsProductionExecutableCompileSync")
247250
}
@@ -257,7 +260,7 @@ task copyTestJsSources(type: Copy, dependsOn: setupTsProject) {
257260
}
258261
eachFile { file ->
259262
// Compiled sources have the name of the module they represent, followed by ".js" and ".d.ts".
260-
// To be recognized by node, place them as "index.js" and "index.d.ts" in "node_modules/@cachet/<module-name>".
263+
// To be recognized by node, place them as "index.js" and "index.d.ts" in "node_modules/<scope>/<module-name>".
261264
def fileMatch = file.name =~ /(.+)\.(js|d\.ts)/
262265
def moduleName = fileMatch[0][1]
263266
def extension = fileMatch[0][2]
@@ -272,8 +275,8 @@ task copyTestJsSources(type: Copy, dependsOn: setupTsProject) {
272275
// Modify sources to act like modules with exported named members.
273276
file.filter { line ->
274277
// Compiled sources refer to other modules as adjacent .js source files.
275-
// Change these to the named modules created in the previous step.
276-
def namedModules = line.replaceAll(~/'\.\/(.+?)\.js'/, "'@cachet/\$1'")
278+
// Change these to the scoped modules created in the previous step.
279+
def namedModules = line.replaceAll(~/'\.\/(.+?)\.js'/, "'$npmScope/\$1'")
277280

278281
// Replace `any` types with actual types for which facades are specified.
279282
def replacedTypes = knownFacadeTypes.inject(namedModules) { curLine, type ->
@@ -298,9 +301,33 @@ task copyTestJsSources(type: Copy, dependsOn: setupTsProject) {
298301
additionalExports
299302
}
300303
}
301-
into "./$typescriptFolder/node_modules/@cachet/"
304+
into "./$typescriptFolder/node_modules/$npmScope/"
305+
}
306+
task packageTestJsSources(type: Copy, dependsOn: copyTestJsSources) {
307+
allModules.each {
308+
def project = it.name
309+
dependsOn("$project:jsPackageJson")
310+
dependsOn("$project:jsTestPackageJson")
311+
}
312+
313+
from("$rootDir/build/js/packages") {
314+
include "**/package.json"
315+
includeEmptyDirs = false
316+
}
317+
eachFile { file ->
318+
def moduleName = file.getFile().getParentFile().name
319+
file.filter { line ->
320+
// Add scope to module name.
321+
def changedName = line.replaceAll(~/("name": ).*/, "\$1 \"$npmScope/$moduleName\",")
322+
323+
// Point main source to 'index.js'.
324+
changedName.replaceAll(~/("main": ).*/, "\$1 \"index.js\",")
325+
}
326+
327+
}
328+
into "./$typescriptFolder/node_modules/$npmScope/"
302329
}
303-
task compileTs(type: NpmTask, dependsOn: copyTestJsSources) {
330+
task compileTs(type: NpmTask, dependsOn: packageTestJsSources) {
304331
workingDir = file(typescriptFolder)
305332
args = ['run', 'tsc']
306333
}
@@ -309,11 +336,6 @@ task verifyTsDeclarations(type: NodeTask, dependsOn: compileTs) {
309336
execOverrides {
310337
it.workingDir = typescriptFolder
311338
}
312-
args = [
313-
'--require', 'ts-node/register',
314-
'--require', 'jsdom-global/register',
315-
'./tests/**/*.ts'
316-
]
317339
}
318340

319341
// Add `carp.test` helpers.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
@file:Suppress( "NON_EXPORTABLE_TYPE" )
2+
3+
package dk.cachet.carp.common.application.devices
4+
5+
import dk.cachet.carp.common.application.Trilean
6+
import dk.cachet.carp.common.application.data.DataType
7+
import dk.cachet.carp.common.application.sampling.DataTypeSamplingSchemeMap
8+
import dk.cachet.carp.common.application.sampling.SamplingConfiguration
9+
import dk.cachet.carp.common.application.tasks.TaskConfigurationList
10+
import dk.cachet.carp.common.infrastructure.serialization.NotSerializable
11+
import kotlinx.serialization.Required
12+
import kotlinx.serialization.Serializable
13+
import kotlin.js.JsExport
14+
import kotlin.reflect.KClass
15+
16+
17+
/**
18+
* A website which participates in a study as a primary device.
19+
*/
20+
@Serializable
21+
@JsExport
22+
data class Website(
23+
override val roleName: String,
24+
override val isOptional: Boolean = false
25+
) : PrimaryDeviceConfiguration<WebsiteDeviceRegistration, WebsiteDeviceRegistrationBuilder>()
26+
{
27+
object Sensors : DataTypeSamplingSchemeMap()
28+
object Tasks : TaskConfigurationList()
29+
30+
override fun getSupportedDataTypes(): Set<DataType> = Sensors.keys
31+
override fun getDataTypeSamplingSchemes(): DataTypeSamplingSchemeMap = Sensors
32+
33+
override val defaultSamplingConfiguration: Map<DataType, SamplingConfiguration> = emptyMap()
34+
35+
override fun createDeviceRegistrationBuilder(): WebsiteDeviceRegistrationBuilder =
36+
WebsiteDeviceRegistrationBuilder()
37+
override fun getRegistrationClass(): KClass<WebsiteDeviceRegistration> = WebsiteDeviceRegistration::class
38+
override fun isValidRegistration( registration: WebsiteDeviceRegistration ): Trilean = Trilean.TRUE
39+
}
40+
41+
42+
/**
43+
* A [DeviceRegistration] for a [Website], specifying the [url] where the study runs.
44+
*/
45+
@Serializable
46+
@JsExport
47+
data class WebsiteDeviceRegistration(
48+
val url: String,
49+
/**
50+
* The HTTP User-Agent header of the user agent which made the HTTP request to [url].
51+
*/
52+
val userAgent: String,
53+
@Required
54+
override val deviceDisplayName: String? = userAgent
55+
) : DeviceRegistration()
56+
{
57+
@Required
58+
override val deviceId: String = url
59+
}
60+
61+
62+
@Suppress( "SERIALIZER_TYPE_INCOMPATIBLE" )
63+
@Serializable( NotSerializable::class )
64+
@JsExport
65+
class WebsiteDeviceRegistrationBuilder : DeviceRegistrationBuilder<WebsiteDeviceRegistration>()
66+
{
67+
/**
68+
* The web URL from which the [Website] is accessed.
69+
*/
70+
var url: String = ""
71+
72+
/**
73+
* The HTTP User-Agent header of the user agent which made the HTTP request to [url].
74+
*/
75+
var userAgent: String = ""
76+
77+
override fun build(): WebsiteDeviceRegistration = WebsiteDeviceRegistration( url, userAgent, deviceDisplayName )
78+
}

carp.common/src/commonMain/kotlin/dk/cachet/carp/common/application/services/ApplicationServiceEventBus.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ class EventSubscriptionBuilder(
5252
*/
5353
inline fun <
5454
reified TService : ApplicationService<TService, TEvent>,
55-
reified TEvent : IntegrationEvent<TService>> event(
55+
reified TEvent : IntegrationEvent<TService>
56+
> event(
5657
noinline handler: suspend (TEvent) -> Unit
5758
) = eventBus.registerHandler( TService::class, TEvent::class, subscriber, handler )
5859
}

carp.common/src/commonMain/kotlin/dk/cachet/carp/common/application/services/EventBus.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ abstract class EventBus
4343
*/
4444
fun <
4545
TService : ApplicationService<TService, TEvent>,
46-
TEvent : IntegrationEvent<TService>> registerHandler(
46+
TEvent : IntegrationEvent<TService>
47+
> registerHandler(
4748
eventSource: KClass<TService>,
4849
eventType: KClass<TEvent>,
4950
subscriber: Any,

carp.common/src/commonMain/kotlin/dk/cachet/carp/common/application/tasks/WebTask.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ data class WebTask(
4747
/**
4848
* Identifies the condition, defined by the study protocol, which caused the [WebTask] to be triggered.
4949
*/
50-
TRIGGER_ID( markup( "trigger id" ) );
50+
TRIGGER_ID( markup( "trigger id" ) )
5151
}
5252

5353

carp.common/src/commonMain/kotlin/dk/cachet/carp/common/domain/Snapshot.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,23 @@ import dk.cachet.carp.common.application.Immutable
44
import dk.cachet.carp.common.application.ImplementAsDataClass
55
import dk.cachet.carp.common.application.UUID
66
import kotlinx.datetime.Instant
7+
import kotlin.js.JsExport
78

89

910
/**
1011
* An immutable snapshot of an [AggregateRoot] at a given moment in time.
1112
*/
1213
@Immutable
1314
@ImplementAsDataClass
15+
@JsExport
1416
interface Snapshot<TAggregateRoot>
1517
{
1618
val id: UUID
1719

1820
/**
1921
* The date when the object represented by this snapshot was created.
2022
*/
23+
@Suppress( "NON_EXPORTABLE_TYPE" )
2124
val createdOn: Instant
2225

2326
/**

carp.common/src/commonMain/kotlin/dk/cachet/carp/common/infrastructure/serialization/Serialization.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,7 @@ val COMMON_SERIAL_MODULE = SerializersModule {
3131
subclass( HeartRate::class )
3232
subclass( InterbeatInterval::class )
3333
subclass( NonGravitationalAcceleration::class )
34-
// HACK: explicit serializer needs to be registered for object declarations due to limitation of the JS legacy backend.
35-
// https://github.com/Kotlin/kotlinx.serialization/issues/1138#issuecomment-707989920
36-
// This can likely be removed once we upgrade to the new IR backend.
37-
subclass( NoData::class, NoData.serializer() )
34+
subclass( NoData::class )
3835
subclass( PPG::class )
3936
subclass( SignalStrength::class )
4037
subclass( SensorSkinContact::class )
@@ -63,6 +60,7 @@ val COMMON_SERIAL_MODULE = SerializersModule {
6360
{
6461
subclass( CustomProtocolDevice::class )
6562
subclass( Smartphone::class )
63+
subclass( Website::class )
6664

6765
subclass( CustomPrimaryDeviceConfiguration::class )
6866
}
@@ -87,6 +85,7 @@ val COMMON_SERIAL_MODULE = SerializersModule {
8785
subclass( BLESerialNumberDeviceRegistration::class )
8886
subclass( DefaultDeviceRegistration::class )
8987
subclass( MACAddressDeviceRegistration::class )
88+
subclass( WebsiteDeviceRegistration::class )
9089

9190
subclass( CustomDeviceRegistration::class )
9291
defaultDeserializer { DeviceRegistrationSerializer }

0 commit comments

Comments
 (0)