diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/BUILD.bazel b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/BUILD.bazel index 0bca30182bb..d5175377285 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/BUILD.bazel +++ b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/BUILD.bazel @@ -13,6 +13,7 @@ kt_jvm_library( "//src/main/proto/wfa/measurement/api/v2alpha:data_providers_service_kt_jvm_grpc_proto", "//src/main/proto/wfa/measurement/api/v2alpha:event_group_metadata_descriptors_service_kt_jvm_grpc_proto", "//src/main/proto/wfa/measurement/reporting/v2alpha:event_groups_service_kt_jvm_grpc_proto", + "//src/main/proto/wfa/measurement/reporting/v2alpha:metric_calculation_specs_service_kt_jvm_grpc_proto", "//src/main/proto/wfa/measurement/reporting/v2alpha:reporting_sets_service_kt_jvm_grpc_proto", "//src/main/proto/wfa/measurement/reporting/v2alpha:reports_service_kt_jvm_grpc_proto", "@wfa_common_jvm//imports/java/picocli", diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/README.md b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/README.md index 8fd697ca5b5..d35dca12a1a 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/README.md +++ b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/README.md @@ -98,20 +98,7 @@ Reporting \ --reporting-metric-entry=' key: "measurementConsumers/VCTqwV_vFXw/reportingSets/abc" value { - metric_calculation_specs { - display_name: "spec_1" - metric_specs { - reach { - privacy_params { - epsilon: 0.0041 - delta: 1.0E-12 - } - } - vid_sampling_interval { - width: 0.01 - } - } - } + metric_calculation_specs: "measurementConsumers/Dipo47pr5to/metricCalculationSpecs/abc" } ' ``` @@ -150,6 +137,84 @@ Reporting \ reports get measurementConsumers/VCTqwV_vFXw/reports/abcd ``` +### metric-calculation-specs + +#### create + +```shell +Reporting \ + --tls-cert-file=secretfiles/mc_tls.pem \ + --tls-key-file=secretfiles/mc_tls.key \ + --cert-collection-file=secretfiles/reporting_root.pem \ + --reporting-server-api-target=v2alpha.reporting.dev.halo-cmm.org:8443 \ + metric-calculation-specs create \ + --parent=measurementConsumers/VCTqwV_vFXw \ + --id=abcd \ + --display-name=display \ + --metric-spec=' + reach { + privacy_params { + epsilon: 0.0041 + delta: 1.0E-12 + } + } + vid_sampling_interval { + width: 0.01 + } + ' \ + --metric-spec=' + impression_count { + privacy_params { + epsilon: 0.0041 + delta: 1.0E-12 + } + } + vid_sampling_interval { + width: 0.01 + } + ' \ + --filter='person.gender == 1' \ + --grouping='person.gender == 1,person.gender == 2' \ + --grouping='person.age_group == 1,person.age_group == 2' \ + --day-of-the-week=2 \ + --day-window-count=5 +``` + +The `--metric-spec` option expects a +[`MetricSpec`](../../../../../../../../../proto/wfa/measurement/reporting/v2alpha/metric.proto) +protobuf message in text format. You can use shell quoting for a multiline string, or +use command substitution to read the message from a file e.g. `--metric-spec=$(cat +metric_spec.textproto)`. + +MetricFrequencySpec expects `--daily`, `--day-of-the-week`, or +`--day-of-the-month`. `--daily` is a boolean and `--day-of-the-week` is +represented by 1-7, with 1 being Monday. + +TrailingWindow expects `--day-window-count`, `--week-window-count`, or +`--month-window-count`. + +#### list + +```shell +Reporting \ + --tls-cert-file=secretfiles/mc_tls.pem \ + --tls-key-file=secretfiles/mc_tls.key \ + --cert-collection-file=secretfiles/reporting_root.pem \ + --reporting-server-api-target=v2alpha.reporting.dev.halo-cmm.org:8443 \ + metric-calculation-specs list --parent=measurementConsumers/VCTqwV_vFXw +``` + +#### get + +```shell +Reporting \ + --tls-cert-file=secretfiles/mc_tls.pem \ + --tls-key-file=secretfiles/mc_tls.key \ + --cert-collection-file=secretfiles/reporting_root.pem \ + --reporting-server-api-target=v2alpha.reporting.dev.halo-cmm.org:8443 \ + metric-calculation-specs get measurementConsumers/VCTqwV_vFXw/metricCalculationSpecs/abcd +``` + ### event-groups #### list diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/Reporting.kt b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/Reporting.kt index c4ff09793cf..5187abc6abd 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/Reporting.kt +++ b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/Reporting.kt @@ -16,6 +16,7 @@ package org.wfanet.measurement.reporting.service.api.v2alpha.tools +import com.google.type.DayOfWeek import com.google.type.date import com.google.type.dateTime import com.google.type.interval @@ -42,18 +43,26 @@ import org.wfanet.measurement.common.parseTextProto import org.wfanet.measurement.common.toProtoDuration import org.wfanet.measurement.common.toProtoTime import org.wfanet.measurement.reporting.v2alpha.EventGroupsGrpcKt.EventGroupsCoroutineStub +import org.wfanet.measurement.reporting.v2alpha.MetricCalculationSpec +import org.wfanet.measurement.reporting.v2alpha.MetricCalculationSpecKt +import org.wfanet.measurement.reporting.v2alpha.MetricCalculationSpecsGrpcKt.MetricCalculationSpecsCoroutineStub +import org.wfanet.measurement.reporting.v2alpha.MetricSpec import org.wfanet.measurement.reporting.v2alpha.Report import org.wfanet.measurement.reporting.v2alpha.ReportKt.reportingInterval import org.wfanet.measurement.reporting.v2alpha.ReportingSet import org.wfanet.measurement.reporting.v2alpha.ReportingSetKt import org.wfanet.measurement.reporting.v2alpha.ReportingSetsGrpcKt.ReportingSetsCoroutineStub import org.wfanet.measurement.reporting.v2alpha.ReportsGrpcKt.ReportsCoroutineStub +import org.wfanet.measurement.reporting.v2alpha.createMetricCalculationSpecRequest import org.wfanet.measurement.reporting.v2alpha.createReportRequest import org.wfanet.measurement.reporting.v2alpha.createReportingSetRequest +import org.wfanet.measurement.reporting.v2alpha.getMetricCalculationSpecRequest import org.wfanet.measurement.reporting.v2alpha.getReportRequest import org.wfanet.measurement.reporting.v2alpha.listEventGroupsRequest +import org.wfanet.measurement.reporting.v2alpha.listMetricCalculationSpecsRequest import org.wfanet.measurement.reporting.v2alpha.listReportingSetsRequest import org.wfanet.measurement.reporting.v2alpha.listReportsRequest +import org.wfanet.measurement.reporting.v2alpha.metricCalculationSpec import org.wfanet.measurement.reporting.v2alpha.report import org.wfanet.measurement.reporting.v2alpha.reportingSet import org.wfanet.measurement.reporting.v2alpha.timeIntervals @@ -469,6 +478,272 @@ class ReportsCommand : Runnable { override fun run() {} } +@CommandLine.Command(name = "create", description = ["Create a metric calculation spec"]) +class CreateMetricCalculationSpecCommand : Runnable { + @CommandLine.ParentCommand private lateinit var parent: MetricCalculationSpecsCommand + + @CommandLine.Option( + names = ["--parent"], + description = ["API resource name of the Measurement Consumer"], + required = true, + ) + private lateinit var measurementConsumerName: String + + @CommandLine.Option( + names = ["--display-name"], + description = ["Human-readable name for display purposes"], + required = false, + defaultValue = "", + ) + private lateinit var displayName: String + + @CommandLine.Option( + names = ["--metric-spec"], + description = ["MetricSpec protobuf messages in text format"], + required = true, + ) + lateinit var textFormatMetricSpecs: List + + @CommandLine.Option( + names = ["--grouping"], + description = ["Each grouping is a list of comma-separated predicates"], + required = false, + ) + lateinit var groupings: List + + @CommandLine.Option( + names = ["--filter"], + description = ["CEL filter predicate that will be conjoined to any Reporting Set filters"], + required = false, + defaultValue = "", + ) + private lateinit var filter: String + + class MetricFrequencySpecInput { + @CommandLine.Option( + names = ["--daily-frequency"], + description = ["Whether to use daily frequency"], + ) + var daily: Boolean = false + private set + + @CommandLine.Option( + names = ["--day-of-the-week"], + description = + [ + """ + Day of the week for weekly frequency. Represented by a number between 1 and 7, inclusive, + where Monday is 1 and Sunday is 7. + """ + ], + ) + var dayOfTheWeek: Int = 0 + private set + + @CommandLine.Option( + names = ["--day-of-the-month"], + description = + [ + """ + Day of the month for monthly frequency. Represented by a number between 1 and 31, inclusive. + """ + ], + ) + var dayOfTheMonth: Int = 0 + private set + } + + @CommandLine.ArgGroup( + exclusive = true, + multiplicity = "0..1", + heading = "Metric frequency specification\n", + ) + private lateinit var metricFrequencySpecInput: MetricFrequencySpecInput + + class TrailingWindowInput { + @CommandLine.Option(names = ["--day-window-count"], description = ["Size of day window"]) + var dayCount: Int = 0 + private set + + @CommandLine.Option( + names = ["--week-window-count"], + description = ["Size of week window"], + required = false, + ) + var weekCount: Int = 0 + private set + + @CommandLine.Option( + names = ["--month-window-count"], + description = ["Size of month window"], + required = false, + ) + var monthCount: Int = 0 + private set + } + + @CommandLine.ArgGroup( + exclusive = true, + multiplicity = "0..1", + heading = "Trailing window specification\n", + ) + private lateinit var trailingWindowInput: TrailingWindowInput + + @CommandLine.Option( + names = ["--id"], + description = ["Resource ID of the Metric Calculation Spec"], + required = true, + defaultValue = "", + ) + private lateinit var metricCalculationSpecId: String + + override fun run() { + val request = createMetricCalculationSpecRequest { + parent = measurementConsumerName + metricCalculationSpec = metricCalculationSpec { + displayName = this@CreateMetricCalculationSpecCommand.displayName + for (textFormatMetricSpec in textFormatMetricSpecs) { + metricSpecs += + parseTextProto(textFormatMetricSpec.reader(), MetricSpec.getDefaultInstance()) + } + + filter = this@CreateMetricCalculationSpecCommand.filter + + for (grouping in this@CreateMetricCalculationSpecCommand.groupings) { + groupings += MetricCalculationSpecKt.grouping { predicates += grouping.trim().split(',') } + } + + if (this@CreateMetricCalculationSpecCommand::metricFrequencySpecInput.isInitialized) { + metricFrequencySpec = + MetricCalculationSpecKt.metricFrequencySpec { + if (this@CreateMetricCalculationSpecCommand.metricFrequencySpecInput.daily) { + daily = MetricCalculationSpec.MetricFrequencySpec.Daily.getDefaultInstance() + } else if ( + this@CreateMetricCalculationSpecCommand.metricFrequencySpecInput.dayOfTheWeek > 0 + ) { + weekly = + MetricCalculationSpecKt.MetricFrequencySpecKt.weekly { + dayOfWeek = + DayOfWeek.forNumber( + this@CreateMetricCalculationSpecCommand.metricFrequencySpecInput + .dayOfTheWeek + ) + } + } else if ( + this@CreateMetricCalculationSpecCommand.metricFrequencySpecInput.dayOfTheMonth > 0 + ) { + monthly = + MetricCalculationSpecKt.MetricFrequencySpecKt.monthly { + dayOfMonth = + this@CreateMetricCalculationSpecCommand.metricFrequencySpecInput.dayOfTheMonth + } + } + } + } + + if (this@CreateMetricCalculationSpecCommand::trailingWindowInput.isInitialized) { + trailingWindow = + MetricCalculationSpecKt.trailingWindow { + if (this@CreateMetricCalculationSpecCommand.trailingWindowInput.dayCount > 0) { + count = this@CreateMetricCalculationSpecCommand.trailingWindowInput.dayCount + increment = MetricCalculationSpec.TrailingWindow.Increment.DAY + } else if ( + this@CreateMetricCalculationSpecCommand.trailingWindowInput.weekCount > 0 + ) { + count = this@CreateMetricCalculationSpecCommand.trailingWindowInput.weekCount + increment = MetricCalculationSpec.TrailingWindow.Increment.WEEK + } else if ( + this@CreateMetricCalculationSpecCommand.trailingWindowInput.monthCount > 0 + ) { + count = this@CreateMetricCalculationSpecCommand.trailingWindowInput.monthCount + increment = MetricCalculationSpec.TrailingWindow.Increment.MONTH + } + } + } + } + + metricCalculationSpecId = this@CreateMetricCalculationSpecCommand.metricCalculationSpecId + } + val metricCalculationSpec = + runBlocking(Dispatchers.IO) { + parent.metricCalculationSpecsStub.createMetricCalculationSpec(request) + } + + println(metricCalculationSpec) + } +} + +@CommandLine.Command(name = "list", description = ["List metric calculation specs"]) +class ListMetricCalculationSpecsCommand : Runnable { + @CommandLine.ParentCommand private lateinit var parent: MetricCalculationSpecsCommand + + @CommandLine.Option( + names = ["--parent"], + description = ["API resource name of the Measurement Consumer"], + required = true, + ) + private lateinit var measurementConsumerName: String + + @CommandLine.Mixin private lateinit var pageParams: PageParams + + override fun run() { + val request = listMetricCalculationSpecsRequest { + parent = measurementConsumerName + pageSize = pageParams.pageSize + pageToken = pageParams.pageToken + } + + val response = + runBlocking(Dispatchers.IO) { + parent.metricCalculationSpecsStub.listMetricCalculationSpecs(request) + } + + response.metricCalculationSpecsList.forEach { println(it.name) } + if (response.nextPageToken.isNotEmpty()) { + println("nextPageToken: ${response.nextPageToken}") + } + } +} + +@CommandLine.Command(name = "get", description = ["Get a metric calculation spec"]) +class GetMetricCalculationSpecCommand : Runnable { + @CommandLine.ParentCommand private lateinit var parent: MetricCalculationSpecsCommand + + @CommandLine.Parameters(description = ["API resource name of the Metric Calculation Spec"]) + private lateinit var metricCalculationSpecName: String + + override fun run() { + val request = getMetricCalculationSpecRequest { name = metricCalculationSpecName } + + val metricCalculationSpec = + runBlocking(Dispatchers.IO) { + parent.metricCalculationSpecsStub.getMetricCalculationSpec(request) + } + println(metricCalculationSpec) + } +} + +@CommandLine.Command( + name = "metric-calculation-specs", + sortOptions = false, + subcommands = + [ + CommandLine.HelpCommand::class, + CreateMetricCalculationSpecCommand::class, + ListMetricCalculationSpecsCommand::class, + GetMetricCalculationSpecCommand::class, + ], +) +class MetricCalculationSpecsCommand : Runnable { + @CommandLine.ParentCommand lateinit var parent: Reporting + + val metricCalculationSpecsStub: MetricCalculationSpecsCoroutineStub by lazy { + MetricCalculationSpecsCoroutineStub(parent.channel) + } + + override fun run() {} +} + @CommandLine.Command(name = "list", description = ["List event groups"]) class ListEventGroups : Runnable { @CommandLine.ParentCommand private lateinit var parent: EventGroupsCommand @@ -624,6 +899,7 @@ class EventGroupMetadataDescriptorsCommand : Runnable { CommandLine.HelpCommand::class, ReportingSetsCommand::class, ReportsCommand::class, + MetricCalculationSpecsCommand::class, EventGroupsCommand::class, DataProvidersCommand::class, EventGroupMetadataDescriptorsCommand::class, @@ -653,7 +929,8 @@ class Reporting : Runnable { } /** - * Reporting Set, Report, Event Group, Event Group Metadata Descriptor, and Data Provider methods. + * Reporting Set, Report, Metric Calculation Spec, Event Group, Event Group Metadata Descriptor, and + * Data Provider methods. * * Use the `help` command to see usage details. */ diff --git a/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/BUILD.bazel b/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/BUILD.bazel index d2ae0df60a7..63e4c32b5e3 100644 --- a/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/BUILD.bazel +++ b/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/BUILD.bazel @@ -20,6 +20,7 @@ kt_jvm_test( "//src/main/proto/wfa/measurement/api/v2alpha:data_providers_service_kt_jvm_grpc_proto", "//src/main/proto/wfa/measurement/api/v2alpha:event_group_metadata_descriptors_service_kt_jvm_grpc_proto", "//src/main/proto/wfa/measurement/reporting/v2alpha:event_groups_service_kt_jvm_grpc_proto", + "//src/main/proto/wfa/measurement/reporting/v2alpha:metric_calculation_specs_service_kt_jvm_grpc_proto", "//src/main/proto/wfa/measurement/reporting/v2alpha:reporting_sets_service_kt_jvm_grpc_proto", "//src/main/proto/wfa/measurement/reporting/v2alpha:reports_service_kt_jvm_grpc_proto", "@wfa_common_jvm//imports/java/com/google/common/truth", diff --git a/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/ReportingTest.kt b/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/ReportingTest.kt index 3b04ec2371d..d6c8b291365 100644 --- a/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/ReportingTest.kt +++ b/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/ReportingTest.kt @@ -18,6 +18,7 @@ package org.wfanet.measurement.reporting.service.api.v2alpha.tools import com.google.common.truth.Truth.assertThat import com.google.protobuf.duration +import com.google.type.DayOfWeek import com.google.type.date import com.google.type.dateTime import com.google.type.interval @@ -59,22 +60,31 @@ import org.wfanet.measurement.common.toProtoTime import org.wfanet.measurement.reporting.v2alpha.EventGroupsGrpcKt.EventGroupsCoroutineImplBase import org.wfanet.measurement.reporting.v2alpha.ListEventGroupsResponse import org.wfanet.measurement.reporting.v2alpha.ListReportingSetsResponse +import org.wfanet.measurement.reporting.v2alpha.MetricCalculationSpec +import org.wfanet.measurement.reporting.v2alpha.MetricCalculationSpecKt +import org.wfanet.measurement.reporting.v2alpha.MetricCalculationSpecsGrpcKt.MetricCalculationSpecsCoroutineImplBase +import org.wfanet.measurement.reporting.v2alpha.MetricSpec import org.wfanet.measurement.reporting.v2alpha.Report import org.wfanet.measurement.reporting.v2alpha.ReportKt import org.wfanet.measurement.reporting.v2alpha.ReportingSet import org.wfanet.measurement.reporting.v2alpha.ReportingSetKt import org.wfanet.measurement.reporting.v2alpha.ReportingSetsGrpcKt.ReportingSetsCoroutineImplBase import org.wfanet.measurement.reporting.v2alpha.ReportsGrpcKt.ReportsCoroutineImplBase +import org.wfanet.measurement.reporting.v2alpha.createMetricCalculationSpecRequest import org.wfanet.measurement.reporting.v2alpha.createReportRequest import org.wfanet.measurement.reporting.v2alpha.createReportingSetRequest import org.wfanet.measurement.reporting.v2alpha.eventGroup +import org.wfanet.measurement.reporting.v2alpha.getMetricCalculationSpecRequest import org.wfanet.measurement.reporting.v2alpha.getReportRequest import org.wfanet.measurement.reporting.v2alpha.listEventGroupsRequest import org.wfanet.measurement.reporting.v2alpha.listEventGroupsResponse +import org.wfanet.measurement.reporting.v2alpha.listMetricCalculationSpecsRequest +import org.wfanet.measurement.reporting.v2alpha.listMetricCalculationSpecsResponse import org.wfanet.measurement.reporting.v2alpha.listReportingSetsRequest import org.wfanet.measurement.reporting.v2alpha.listReportingSetsResponse import org.wfanet.measurement.reporting.v2alpha.listReportsRequest import org.wfanet.measurement.reporting.v2alpha.listReportsResponse +import org.wfanet.measurement.reporting.v2alpha.metricCalculationSpec import org.wfanet.measurement.reporting.v2alpha.report import org.wfanet.measurement.reporting.v2alpha.reportingSet import org.wfanet.measurement.reporting.v2alpha.timeIntervals @@ -91,6 +101,15 @@ class ReportingTest { onBlocking { listReports(any()) }.thenReturn(listReportsResponse { reports += REPORT }) onBlocking { getReport(any()) }.thenReturn(REPORT) } + private val metricCalculationSpecsServiceMock: MetricCalculationSpecsCoroutineImplBase = + mockService { + onBlocking { createMetricCalculationSpec(any()) }.thenReturn(METRIC_CALCULATION_SPEC) + onBlocking { listMetricCalculationSpecs(any()) } + .thenReturn( + listMetricCalculationSpecsResponse { metricCalculationSpecs += METRIC_CALCULATION_SPEC } + ) + onBlocking { getMetricCalculationSpec(any()) }.thenReturn(METRIC_CALCULATION_SPEC) + } private val eventGroupsServiceMock: EventGroupsCoroutineImplBase = mockService { onBlocking { listEventGroups(any()) } .thenReturn(listEventGroupsResponse { eventGroups += EVENT_GROUP }) @@ -123,6 +142,7 @@ class ReportingTest { listOf( reportingSetsServiceMock.bindService(), reportsServiceMock.bindService(), + metricCalculationSpecsServiceMock.bindService(), eventGroupsServiceMock.bindService(), dataProvidersServiceMock.bindService(), eventGroupMetadataDescriptorsServiceMock.bindService(), @@ -629,6 +649,198 @@ class ReportingTest { assertThat(parseTextProto(output.out.reader(), Report.getDefaultInstance())).isEqualTo(REPORT) } + @Test + fun `create metric calculation spec without frequency and window calls api with valid request`() { + val textFormatMetricSpecFile = TEXTPROTO_DIR.resolve("metric_spec.textproto").toFile() + + val displayName = "display" + val filter = "person.gender == 1" + val grouping1 = "person.gender == 1,person.gender == 2" + val grouping2 = "person.age_group == 1,person.age_group == 2" + + val args = + arrayOf( + "--tls-cert-file=$SECRETS_DIR/mc_tls.pem", + "--tls-key-file=$SECRETS_DIR/mc_tls.key", + "--cert-collection-file=$SECRETS_DIR/reporting_root.pem", + "--reporting-server-api-target=$HOST:${server.port}", + "metric-calculation-specs", + "create", + "--parent=$MEASUREMENT_CONSUMER_NAME", + "--id=$METRIC_CALCULATION_SPEC_ID", + "--display-name=$displayName", + "--metric-spec=${textFormatMetricSpecFile.readText()}", + "--filter=$filter", + "--grouping=$grouping1", + "--grouping=$grouping2", + ) + + val output = callCli(args) + + verifyProtoArgument( + metricCalculationSpecsServiceMock, + MetricCalculationSpecsCoroutineImplBase::createMetricCalculationSpec, + ) + .isEqualTo( + createMetricCalculationSpecRequest { + parent = MEASUREMENT_CONSUMER_NAME + metricCalculationSpecId = METRIC_CALCULATION_SPEC_ID + metricCalculationSpec = metricCalculationSpec { + this.displayName = displayName + metricSpecs += parseTextProto(textFormatMetricSpecFile, MetricSpec.getDefaultInstance()) + this.filter = filter + groupings += MetricCalculationSpecKt.grouping { predicates += grouping1.split(',') } + groupings += MetricCalculationSpecKt.grouping { predicates += grouping2.split(',') } + } + } + ) + + assertThat(output).status().isEqualTo(0) + assertThat(parseTextProto(output.out.reader(), MetricCalculationSpec.getDefaultInstance())) + .isEqualTo(METRIC_CALCULATION_SPEC) + } + + @Test + fun `create metric calculation spec with frequency and window calls api with valid request`() { + val textFormatMetricSpecFile = TEXTPROTO_DIR.resolve("metric_spec.textproto").toFile() + + val displayName = "display" + val filter = "person.gender == 1" + val grouping1 = "person.gender == 1,person.gender == 2" + val grouping2 = "person.age_group == 1,person.age_group == 2" + val dayOfTheWeek = 2 + val dayWindowCount = 5 + + val args = + arrayOf( + "--tls-cert-file=$SECRETS_DIR/mc_tls.pem", + "--tls-key-file=$SECRETS_DIR/mc_tls.key", + "--cert-collection-file=$SECRETS_DIR/reporting_root.pem", + "--reporting-server-api-target=$HOST:${server.port}", + "metric-calculation-specs", + "create", + "--parent=$MEASUREMENT_CONSUMER_NAME", + "--id=$METRIC_CALCULATION_SPEC_ID", + "--display-name=$displayName", + "--metric-spec=${textFormatMetricSpecFile.readText()}", + "--filter=$filter", + "--grouping=$grouping1", + "--grouping=$grouping2", + "--day-of-the-week=$dayOfTheWeek", + "--day-window-count=$dayWindowCount", + ) + + val output = callCli(args) + + verifyProtoArgument( + metricCalculationSpecsServiceMock, + MetricCalculationSpecsCoroutineImplBase::createMetricCalculationSpec, + ) + .isEqualTo( + createMetricCalculationSpecRequest { + parent = MEASUREMENT_CONSUMER_NAME + metricCalculationSpecId = METRIC_CALCULATION_SPEC_ID + metricCalculationSpec = metricCalculationSpec { + this.displayName = displayName + metricSpecs += parseTextProto(textFormatMetricSpecFile, MetricSpec.getDefaultInstance()) + this.filter = filter + groupings += MetricCalculationSpecKt.grouping { predicates += grouping1.split(',') } + groupings += MetricCalculationSpecKt.grouping { predicates += grouping2.split(',') } + metricFrequencySpec = + MetricCalculationSpecKt.metricFrequencySpec { + weekly = + MetricCalculationSpecKt.MetricFrequencySpecKt.weekly { + dayOfWeek = DayOfWeek.TUESDAY + } + } + trailingWindow = + MetricCalculationSpecKt.trailingWindow { + count = dayWindowCount + increment = MetricCalculationSpec.TrailingWindow.Increment.DAY + } + } + } + ) + + assertThat(output).status().isEqualTo(0) + assertThat(parseTextProto(output.out.reader(), MetricCalculationSpec.getDefaultInstance())) + .isEqualTo(METRIC_CALCULATION_SPEC) + } + + @Test + fun `create metric calculation spec with no --metric-spec fails`() { + val args = + arrayOf( + "--tls-cert-file=$SECRETS_DIR/mc_tls.pem", + "--tls-key-file=$SECRETS_DIR/mc_tls.key", + "--cert-collection-file=$SECRETS_DIR/reporting_root.pem", + "--reporting-server-api-target=$HOST:${server.port}", + "metric-calculation-specs", + "create", + "--parent=$MEASUREMENT_CONSUMER_NAME", + "--id=$METRIC_CALCULATION_SPEC_ID", + "--display-name=display", + "--filter='person.gender == 1'", + "--grouping='person.gender == 1,person.gender == 2'", + "--grouping='person.age_group == 1,person.age_group == 2'", + "--cumulative=true", + ) + + val capturedOutput = callCli(args) + + assertThat(capturedOutput).status().isEqualTo(2) + } + + @Test + fun `list metric calculation specs calls api with valid request`() { + val args = + arrayOf( + "--tls-cert-file=$SECRETS_DIR/mc_tls.pem", + "--tls-key-file=$SECRETS_DIR/mc_tls.key", + "--cert-collection-file=$SECRETS_DIR/reporting_root.pem", + "--reporting-server-api-target=$HOST:${server.port}", + "metric-calculation-specs", + "list", + "--parent=$MEASUREMENT_CONSUMER_NAME", + ) + callCli(args) + + verifyProtoArgument( + metricCalculationSpecsServiceMock, + MetricCalculationSpecsCoroutineImplBase::listMetricCalculationSpecs, + ) + .isEqualTo( + listMetricCalculationSpecsRequest { + parent = MEASUREMENT_CONSUMER_NAME + pageSize = 1000 + } + ) + } + + @Test + fun `get metric calculation spec calls api with valid request`() { + val args = + arrayOf( + "--tls-cert-file=$SECRETS_DIR/mc_tls.pem", + "--tls-key-file=$SECRETS_DIR/mc_tls.key", + "--cert-collection-file=$SECRETS_DIR/reporting_root.pem", + "--reporting-server-api-target=$HOST:${server.port}", + "metric-calculation-specs", + "get", + METRIC_CALCULATION_SPEC_NAME, + ) + val output = callCli(args) + + verifyProtoArgument( + metricCalculationSpecsServiceMock, + MetricCalculationSpecsCoroutineImplBase::getMetricCalculationSpec, + ) + .isEqualTo(getMetricCalculationSpecRequest { name = METRIC_CALCULATION_SPEC_NAME }) + assertThat(output).status().isEqualTo(0) + assertThat(parseTextProto(output.out.reader(), MetricCalculationSpec.getDefaultInstance())) + .isEqualTo(METRIC_CALCULATION_SPEC) + } + @Test fun `list event groups calls api with valid request`() { val args = @@ -807,6 +1019,15 @@ class ReportingTest { private const val REPORT_NAME = "$MEASUREMENT_CONSUMER_NAME/reports/$REPORT_ID" private val REPORT = report { name = REPORT_NAME } + private const val METRIC_CALCULATION_SPEC_ID = "b123" + private const val METRIC_CALCULATION_SPEC_NAME = + "$MEASUREMENT_CONSUMER_NAME/metricCalculationSpecs/$METRIC_CALCULATION_SPEC_ID" + private val METRIC_CALCULATION_SPEC = metricCalculationSpec { + name = METRIC_CALCULATION_SPEC_NAME + displayName = "displayName" + metricSpecs += MetricSpec.getDefaultInstance() + } + private const val EVENT_GROUP_NAME = "$MEASUREMENT_CONSUMER_NAME/eventGroups/1" private val EVENT_GROUP = eventGroup { name = EVENT_GROUP_NAME } private val DATA_PROVIDER = dataProvider { name = DATA_PROVIDER_NAME } diff --git a/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/metric_spec.textproto b/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/metric_spec.textproto new file mode 100644 index 00000000000..0ead8a3f0ae --- /dev/null +++ b/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/tools/metric_spec.textproto @@ -0,0 +1,11 @@ +# proto-file: wfa/measurement/reporting/v2alpha/metric.proto +# proto-message: MetricSpec +reach { + privacy_params { + epsilon: 0.0041 + delta: 1.0E-12 + } +} +vid_sampling_interval { + width: 0.01 +}