Skip to content

Commit

Permalink
feat: added global summary statistics policy #4467
Browse files Browse the repository at this point in the history
  • Loading branch information
ymarcon committed Dec 4, 2024
1 parent f6fc0ac commit a832731
Show file tree
Hide file tree
Showing 20 changed files with 172 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.google.common.collect.Sets;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import org.apache.shiro.SecurityUtils;
import org.obiba.magma.NoSuchValueTableException;
import org.obiba.magma.NoSuchVariableException;
import org.obiba.magma.ValueTable;
Expand All @@ -40,6 +41,8 @@
import org.obiba.mica.dataset.event.DatasetUpdatedEvent;
import org.obiba.mica.file.FileUtils;
import org.obiba.mica.file.service.FileSystemService;
import org.obiba.mica.micaConfig.domain.MicaConfig;
import org.obiba.mica.micaConfig.domain.SummaryStatisticsAccessPolicy;
import org.obiba.mica.micaConfig.service.MicaConfigService;
import org.obiba.mica.micaConfig.service.OpalService;
import org.obiba.mica.network.service.NetworkService;
Expand Down Expand Up @@ -518,6 +521,11 @@ protected EventBus getEventBus() {
return eventBus;
}

@Override
protected MicaConfig getMicaConfig() {
return micaConfigService.getConfig();
}

//
// Private methods
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import com.google.common.base.Strings;
import com.google.common.eventbus.EventBus;
import org.apache.shiro.SecurityUtils;
import org.obiba.magma.MagmaRuntimeException;
import org.obiba.magma.NoSuchValueTableException;
import org.obiba.magma.NoSuchVariableException;
Expand All @@ -26,10 +27,13 @@
import org.obiba.mica.dataset.NoSuchDatasetException;
import org.obiba.mica.dataset.domain.Dataset;
import org.obiba.mica.dataset.domain.DatasetVariable;
import org.obiba.mica.micaConfig.domain.MicaConfig;
import org.obiba.mica.micaConfig.domain.SummaryStatisticsAccessPolicy;
import org.obiba.mica.micaConfig.service.OpalService;
import org.obiba.mica.network.service.NetworkService;
import org.obiba.mica.spi.tables.StudyTableSource;
import org.obiba.mica.study.service.StudyService;
import org.obiba.mica.web.model.Mica;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -77,6 +81,44 @@ public abstract DatasetVariable getDatasetVariable(T dataset, String name)

protected abstract EventBus getEventBus();

protected abstract MicaConfig getMicaConfig();

/**
* Apply summary statistics visibility policy.
*
* @param summary
* @return
*/
public Mica.DatasetVariableAggregationDto getFilteredVariableSummary(Mica.DatasetVariableAggregationDto summary) {
SummaryStatisticsAccessPolicy policy = getMicaConfig().getSummaryStatisticsAccessPolicy();
if (summary == null || policy.equals(SummaryStatisticsAccessPolicy.OPEN_ALL)) return summary;
if (!SecurityUtils.getSubject().isAuthenticated()) {
if (policy.equals(SummaryStatisticsAccessPolicy.OPEN_STATS)) return summary;
// strip out detailed stats because user is not authenticated
summary = summary.toBuilder().clearFrequencies().build();
summary = summary.toBuilder().clearStatistics().build();
}
return summary;
}

/**
* Apply summary statistics visibility policy.
*
* @param summary
* @return
*/
public Mica.DatasetVariableAggregationsDto getFilteredVariableSummary(Mica.DatasetVariableAggregationsDto summary) {
SummaryStatisticsAccessPolicy policy = getMicaConfig().getSummaryStatisticsAccessPolicy();
if (summary == null || policy.equals(SummaryStatisticsAccessPolicy.OPEN_ALL)) return summary;
if (!SecurityUtils.getSubject().isAuthenticated()) {
if (policy.equals(SummaryStatisticsAccessPolicy.OPEN_STATS)) return summary;
// strip out detailed stats because user is not authenticated
summary = summary.toBuilder().clearFrequencies().build();
summary = summary.toBuilder().clearStatistics().build();
}
return summary;
}

/**
* Find all dataset identifiers.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.eventbus.EventBus;
import org.apache.shiro.SecurityUtils;
import org.obiba.magma.*;
import org.obiba.magma.support.Disposables;
import org.obiba.mica.NoSuchEntityException;
Expand All @@ -35,6 +36,8 @@
import org.obiba.mica.dataset.event.DatasetUpdatedEvent;
import org.obiba.mica.file.FileUtils;
import org.obiba.mica.file.service.FileSystemService;
import org.obiba.mica.micaConfig.domain.MicaConfig;
import org.obiba.mica.micaConfig.domain.SummaryStatisticsAccessPolicy;
import org.obiba.mica.micaConfig.service.MicaConfigService;
import org.obiba.mica.micaConfig.service.OpalService;
import org.obiba.mica.network.service.NetworkService;
Expand Down Expand Up @@ -438,6 +441,11 @@ protected EventBus getEventBus() {
return eventBus;
}

@Override
protected MicaConfig getMicaConfig() {
return micaConfigService.getConfig();
}

//
// Private methods
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ public class MicaConfig extends AbstractAuditableDocument implements Serializabl

private boolean variableSummaryRequiresAuthentication = false;

private SummaryStatisticsAccessPolicy summaryStatisticsAccessPolicy;

private boolean usePublicUrlForSharedLink = true;

private String style;
Expand Down Expand Up @@ -543,6 +545,21 @@ public boolean isVariableSummaryRequiresAuthentication() {

public void setVariableSummaryRequiresAuthentication(boolean variableSummaryRequiresAuthentication) {
this.variableSummaryRequiresAuthentication = variableSummaryRequiresAuthentication;
if (summaryStatisticsAccessPolicy == null) {
summaryStatisticsAccessPolicy = variableSummaryRequiresAuthentication ? SummaryStatisticsAccessPolicy.RESTRICTED_ALL : SummaryStatisticsAccessPolicy.OPEN_ALL;
}
}

public SummaryStatisticsAccessPolicy getSummaryStatisticsAccessPolicy() {
if (summaryStatisticsAccessPolicy == null) {
summaryStatisticsAccessPolicy = variableSummaryRequiresAuthentication ? SummaryStatisticsAccessPolicy.RESTRICTED_ALL : SummaryStatisticsAccessPolicy.OPEN_ALL;
}
return summaryStatisticsAccessPolicy;
}

public void setSummaryStatisticsAccessPolicy(SummaryStatisticsAccessPolicy summaryStatisticsAccessPolicy) {
this.summaryStatisticsAccessPolicy = summaryStatisticsAccessPolicy;
this.variableSummaryRequiresAuthentication = !SummaryStatisticsAccessPolicy.OPEN_ALL.equals(summaryStatisticsAccessPolicy);
}

public boolean hasStyle() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2022 OBiBa. All rights reserved.
*
* This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.obiba.mica.micaConfig.domain;

/**
* Specify the access policy to the variable stats.
*/
public enum SummaryStatisticsAccessPolicy {

OPEN_ALL, // open basic counts, detailed stats and contingency

OPEN_STATS, // open basic counts and detailed stats, restricted contingency

OPEN_BASICS, // open basic counts, restricted detailed stats and contingency

RESTRICTED_ALL, // restricted basic counts, detailed stats and contingency

}
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ Mica.MicaConfigDto asDto(@NotNull MicaConfig config, String language) {
builder.setIsCollectedDatasetEnabled(config.isStudyDatasetEnabled());
builder.setIsHarmonizedDatasetEnabled(config.isHarmonizationDatasetEnabled());
builder.setVariableSummaryRequiresAuthentication(config.isVariableSummaryRequiresAuthentication());
builder.setSummaryStatisticsAccessPolicy(config.getSummaryStatisticsAccessPolicy().name());
builder.setIsImportStudiesFeatureEnabled(config.isImportStudiesFeatureEnabled());

if(config.hasStyle()) builder.setStyle(config.getStyle());
Expand Down Expand Up @@ -249,6 +250,7 @@ MicaConfig fromDto(@NotNull Mica.MicaConfigDtoOrBuilder dto) {
config.setHarmonizationDatasetEnabled(dto.getIsHarmonizedDatasetEnabled());
config.setImportStudiesFeatureEnabled(dto.getIsImportStudiesFeatureEnabled());
if (dto.hasVariableSummaryRequiresAuthentication()) config.setVariableSummaryRequiresAuthentication(dto.getVariableSummaryRequiresAuthentication());
config.setSummaryStatisticsAccessPolicy(SummaryStatisticsAccessPolicy.valueOf(dto.getSummaryStatisticsAccessPolicy()));

if (dto.hasSignupEnabled()) config.setSignupEnabled(dto.getSignupEnabled());
config.setSignupWithPassword(dto.getSignupWithPassword());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ public DatasetVariable getVariable(@PathParam("id") String id, @PathParam("proje
@Path("/collected/{project}/{table}/{variableName}/_summary")
public Mica.DatasetVariableAggregationDto getVariableSummary(@PathParam("id") String id, @PathParam("project") String project, @PathParam("table") String table, @PathParam("variableName") String variableName) {
checkVariableSummaryAccess();
return collectedDatasetService.getVariableSummary(alternativeStudyDataset(id, project, table), variableName);
Mica.DatasetVariableAggregationDto summary = collectedDatasetService.getVariableSummary(alternativeStudyDataset(id, project, table), variableName);
return collectedDatasetService.getFilteredVariableSummary(summary);
}

private Dataset getDataset(String id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.obiba.mica.dataset.domain.Dataset;
import org.obiba.mica.dataset.domain.DatasetVariable;
import org.obiba.mica.dataset.domain.HarmonizationDataset;
import org.obiba.mica.micaConfig.domain.SummaryStatisticsAccessPolicy;
import org.obiba.mica.micaConfig.service.MicaConfigService;
import org.obiba.mica.micaConfig.service.TaxonomiesService;
import org.obiba.mica.security.service.SubjectAclService;
Expand Down Expand Up @@ -308,10 +309,18 @@ protected List<Taxonomy> getTaxonomies() {
protected void checkContingencyAccess() {
if (!micaConfigService.getConfig().isContingencyEnabled())
throw new ForbiddenException();
if (micaConfigService.getConfig().getSummaryStatisticsAccessPolicy().equals(SummaryStatisticsAccessPolicy.OPEN_ALL))
return;
// other require authentication
if (!SecurityUtils.getSubject().isAuthenticated())
throw new ForbiddenException();
}

protected void checkVariableSummaryAccess() {
if (!SecurityUtils.getSubject().isAuthenticated() && micaConfigService.getConfig().isVariableSummaryRequiresAuthentication())
if (micaConfigService.getConfig().getSummaryStatisticsAccessPolicy().name().startsWith("OPEN_"))
return;
// other require authentication
if (!SecurityUtils.getSubject().isAuthenticated())
throw new ForbiddenException();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public Mica.DatasetVariableAggregationDto getVariableAggregations(@QueryParam("s
StudyDataset dataset = getDataset(StudyDataset.class, datasetId);
StudyTable studyTable = dataset.getSafeStudyTable();
try {
return dtos.asDto(studyTable, datasetService.getVariableSummary(dataset, variableName), withStudySummary).build();
Mica.DatasetVariableAggregationDto summary = datasetService.getVariableSummary(dataset, variableName);
return dtos.asDto(studyTable, datasetService.getFilteredVariableSummary(summary), withStudySummary).build();
} catch (Exception e) {
if (log.isDebugEnabled())
log.warn("Unable to retrieve statistics: {}", e.getMessage(), e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,7 @@
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Response;
import org.apache.commons.math3.util.Pair;
import org.obiba.mica.core.domain.BaseStudyTable;
Expand All @@ -36,13 +31,13 @@
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;

import javax.inject.Inject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -91,7 +86,8 @@ public Mica.DatasetVariableAggregationsDto getVariableAggregations(@QueryParam("
BaseStudyTable opalTable = studyTables.get(i);
Future<Mica.DatasetVariableAggregationDto> futureResult = results.get(i);
try {
builder.add(dtos.asDto(opalTable, futureResult.get(), withStudySummary).build());
Mica.DatasetVariableAggregationDto summary = futureResult.get();
builder.add(dtos.asDto(opalTable, summary, withStudySummary).build());
} catch (Exception e) {
if (log.isDebugEnabled())
log.warn("Unable to retrieve statistics: {}", e.getMessage(), e);
Expand All @@ -108,9 +104,9 @@ public Mica.DatasetVariableAggregationsDto getVariableAggregations(@QueryParam("
if (allAggDto.hasStatistics()) aggDto.setStatistics(allAggDto.getStatistics());
aggDto.addAllFrequencies(allAggDto.getFrequenciesList());

aggDto.addAllAggregations(aggsDto);
aggDto.addAllAggregations(aggsDto.stream().map((summary) -> datasetService.getFilteredVariableSummary(summary)).toList());

return aggDto.build();
return datasetService.getFilteredVariableSummary(aggDto.build());
}

@GET
Expand Down Expand Up @@ -259,24 +255,25 @@ protected Future<Mica.DatasetVariableAggregationDto> getVariableFacet(Harmonizat
BaseStudyTable table) {
try {
String studyId = table.getStudyId();
return new AsyncResult<>(datasetService.getVariableSummary(dataset, variableName, studyId, table.getSource()));
Mica.DatasetVariableAggregationDto summary = datasetService.getVariableSummary(dataset, variableName, studyId, table.getSource());
return CompletableFuture.supplyAsync(() -> summary);
} catch (Exception e) {
if (log.isDebugEnabled())
log.warn("Unable to retrieve statistics: {}", e.getMessage(), e);
else
log.warn("Unable to retrieve statistics: {}", e.getMessage());
return new AsyncResult<>(null);
return CompletableFuture.supplyAsync(() -> null);
}
}

@Async
protected Future<Mica.DatasetVariableContingencyDto> getContingencyTable(HarmonizationDataset dataset, DatasetVariable var,
DatasetVariable crossVar, BaseStudyTable studyTable) {
try {
return new AsyncResult<>(datasetService.getContingencyTable(dataset, studyTable, var, crossVar));
return CompletableFuture.supplyAsync(() -> datasetService.getContingencyTable(dataset, studyTable, var, crossVar));
} catch (Exception e) {
log.warn("Unable to retrieve contingency statistics: " + e.getMessage(), e);
return new AsyncResult<>(null);
return CompletableFuture.supplyAsync(() -> null);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ public Mica.DatasetVariableAggregationDto getVariableAggregations(@QueryParam("s
for (BaseStudyTable baseTable : getBaseStudyTables(dataset)) {
if (baseTable.isFor(studyId, source)) {
try {
return dtos.asDto(baseTable,
datasetService.getVariableSummary(dataset, variableName, studyId, source), withStudySummary).build();
Mica.DatasetVariableAggregationDto summary = datasetService.getVariableSummary(dataset, variableName, studyId, source);
return dtos.asDto(baseTable, datasetService.getFilteredVariableSummary(summary), withStudySummary).build();
} catch (Exception e) {
if (log.isDebugEnabled())
log.warn("Unable to retrieve statistics: {}", e.getMessage(), e);
Expand Down
1 change: 1 addition & 0 deletions mica-web-model/src/main/protobuf/Mica.proto
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ message MicaConfigDto {
required bool isStudiesExportEnabled = 68;
required bool isNetworksExportEnabled = 69;
repeated string usableVariableTaxonomiesForConceptTagging = 70;
required string summaryStatisticsAccessPolicy = 71;
}

message PublicMicaConfigDto {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,6 @@
});
</#if>
<#if showVariableStatistics && (user?? || !config.variableSummaryRequiresAuthentication)>
makeSummary(${showHarmonizedVariableSummarySelector?c});
</#if>
makeSummary('${config.summaryStatisticsAccessPolicy}', ${showHarmonizedVariableSummarySelector?c});
});
</script>
5 changes: 3 additions & 2 deletions mica-webapp/src/main/resources/_templates/variable.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,10 @@
</#if>
</div>
<div class="card-body">
<#if user?? || !config.variableSummaryRequiresAuthentication>
<#if config.summaryStatisticsAccessPolicy?starts_with("OPEN_") || user??>
<@variableSummary variable=variable/>
<#else>
</#if>
<#if config.summaryStatisticsAccessPolicy.name() != "OPEN_ALL" && config.summaryStatisticsAccessPolicy.name() != "OPEN_STATS" && user?? == false>
<@message "sign-in-for-variable-statistics"/>
<a href="${contextPath}/signin?redirect=${contextPath}/variable/${variable.id}" class="btn btn-info"><@message "sign-in"/></a>
</#if>
Expand Down
10 changes: 8 additions & 2 deletions mica-webapp/src/main/resources/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -432,8 +432,14 @@
},
"openAccess": "Open access",
"openAccess-help": "Access to published documents is opened to everyone.",
"variableSummaryRequiresAuthentication": "Restricted access to variable summary statistics",
"variableSummaryRequiresAuthentication-help": "The user needs to be identified when accessing to the variable summary statistics (and related operations like contingency table).",
"variableSummaryRequiresAuthentication": "Access to variable summary statistics",
"variableSummaryRequiresAuthentication-help": "The user may need to be identified when accessing to the variable summary statistics (and related operations like contingency table).",
"summaryStatisticsAccessPolicy-label": {
"OPEN_ALL": "Open",
"OPEN_STATS": "Open basic counts open and detailed statistics and restricted contingency table",
"OPEN_BASICS": "Open basic counts open, restricted detailed statistics and contingency table",
"RESTRICTED_ALL": "Restricted statistics and contingency table"
},
"sections": "Sections",
"repository-enabled": "Repository enabled",
"repository-enabled-help": "Show network, initiative, study, protocol, dataset, variable pages with the search interface.",
Expand Down
Loading

0 comments on commit a832731

Please sign in to comment.