Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional destinationStudyId for clone.featureSet #992

Merged
merged 11 commits into from
Sep 10, 2024
3 changes: 2 additions & 1 deletion annotationProcessor/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,8 @@ org.reflections:reflections:0.10.2=checkstyle
org.scala-lang:scala-library:2.13.10=productionRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
org.slf4j:jcl-over-slf4j:2.0.13=productionRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
org.slf4j:jul-to-slf4j:2.0.13=pmd,productionRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
org.slf4j:slf4j-api:2.0.13=compileClasspath,productionRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
org.slf4j:slf4j-api:2.0.13=spotbugs,spotbugsSlf4j,testFixturesRuntimeClasspath
org.slf4j:slf4j-api:2.0.16=compileClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.slf4j:slf4j-simple:2.0.0=spotbugsSlf4j
org.slf4j:slf4j-simple:2.0.13=productionRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-autoconfigure:3.3.1=productionRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ gradle.ext {
vPmd = '7.1.0'
vTerraCommonLib = '1.1.16-SNAPSHOT'
vApacheCommonsText = '1.12.0'
vSlf4jApi = '2.0.13'
vSlf4jApi = '2.0.16'
vSpotBugs = '4.8.5'
vJackson = '2.17.1'
vJersey3 = '3.1.7' // Java 17 compatible
Expand Down
3 changes: 2 additions & 1 deletion cli/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ org.pcollections:pcollections:3.2.0=pmd
org.reflections:reflections:0.10.2=checkstyle
org.slf4j:jul-to-slf4j:1.7.36=pmd
org.slf4j:slf4j-api:2.0.0=spotbugsSlf4j
org.slf4j:slf4j-api:2.0.13=compileClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
org.slf4j:slf4j-api:2.0.13=spotbugs
org.slf4j:slf4j-api:2.0.16=compileClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
org.slf4j:slf4j-simple:2.0.0=spotbugsSlf4j
org.xmlresolver:xmlresolver:5.2.2=checkstyle,pmd,spotbugs
empty=spotbugsPlugins,testAnnotationProcessor,testFixturesAnnotationProcessor,testFixturesCompileClasspath
Original file line number Diff line number Diff line change
Expand Up @@ -136,16 +136,28 @@ public ResponseEntity<ApiFeatureSet> updateFeatureSet(
public ResponseEntity<ApiFeatureSet> cloneFeatureSet(
String studyId, String featureSetId, ApiFeatureSetCloneInfo body) {
UserId user = SpringAuthentication.getCurrentUser();
accessControlService.throwIfUnauthorized(
user, Permissions.forActions(STUDY, CREATE_FEATURE_SET), ResourceId.forStudy(studyId));

// should have read access to original feature set
accessControlService.throwIfUnauthorized(
user,
Permissions.forActions(FEATURE_SET, READ),
ResourceId.forFeatureSet(studyId, featureSetId));

// should have write access to create feature set in destination study
String destinationStudyId = (body.getStudyId() != null) ? body.getStudyId() : studyId;
accessControlService.throwIfUnauthorized(
user,
Permissions.forActions(STUDY, CREATE_FEATURE_SET),
ResourceId.forStudy(destinationStudyId));

FeatureSet clonedFeatureSet =
featureSetService.cloneFeatureSet(
studyId, featureSetId, user.getEmail(), body.getDisplayName(), body.getDescription());
studyId,
featureSetId,
user.getEmail(),
destinationStudyId,
body.getDisplayName(),
body.getDescription());
return ResponseEntity.ok(FeatureSetsApiController.toApiObject(clonedFeatureSet));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import bio.terra.tanagra.service.accesscontrol.ResourceId;
import bio.terra.tanagra.service.accesscontrol.ResourceType;
import bio.terra.tanagra.service.authentication.UserId;
import java.util.Set;

public interface UnderlayAccessControl extends FineGrainedAccessControl {
@Override
Expand All @@ -21,7 +20,7 @@ default ResourceCollection listStudies(UserId user, int offset, int limit) {

@Override
default Permissions createStudy(UserId user) {
return Permissions.forActions(ResourceType.STUDY, Set.of(Action.CREATE));
return Permissions.forActions(ResourceType.STUDY, Action.CREATE);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public ResourceCollection listUnderlays(UserId user, int offset, int limit) {
@Override
public Permissions createStudy(UserId user) {
// Workbench creates a study as part of workspace creation. Always allow.
return Permissions.forActions(ResourceType.STUDY, Set.of(Action.CREATE));
return Permissions.forActions(ResourceType.STUDY, Action.CREATE);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public Permissions getUnderlay(UserId user, ResourceId underlay) {
@Override
public Permissions createStudy(UserId user) {
return apiIsAuthorizedUser(user.getEmail())
? Permissions.forActions(ResourceType.STUDY, Set.of(Action.CREATE))
? Permissions.forActions(ResourceType.STUDY, Action.CREATE)
: Permissions.empty(ResourceType.STUDY);
}

Expand Down Expand Up @@ -172,36 +172,28 @@ private Map<ResourceId, Permissions> listAllPermissions(UserId user, ResourceTyp

/** Admin service underlay permission -> Core service underlay permission. */
private static Set<Action> fromUnderlayApiAction(ResourceAction apiAction) {
switch (apiAction) {
case ALL:
case READ:
return ResourceType.UNDERLAY.getActions();
case UPDATE:
case CREATE:
case DELETE:
default:
return switch (apiAction) {
case ALL, READ -> ResourceType.UNDERLAY.getActions();
default -> { // also case UPDATE, CREATE, DELETE
LOGGER.warn("Unknown mapping for underlay API resource action {}", apiAction);
return Set.of();
}
yield Set.of();
}
};
}

/** Admin service study permission -> Core service study permission. */
private static Set<Action> fromStudyApiAction(ResourceAction apiAction) {
switch (apiAction) {
case ALL:
return ResourceType.STUDY.getActions();
case READ:
return Set.of(Action.READ);
case UPDATE:
return Set.of(Action.UPDATE, Action.CREATE_COHORT, Action.CREATE_FEATURE_SET);
case CREATE:
return Set.of(Action.CREATE);
case DELETE:
return Set.of(Action.DELETE);
default:
return switch (apiAction) {
case ALL -> ResourceType.STUDY.getActions();
case READ -> Set.of(Action.READ);
case UPDATE -> Set.of(Action.UPDATE, Action.CREATE_COHORT, Action.CREATE_FEATURE_SET);
case CREATE -> Set.of(Action.CREATE);
case DELETE -> Set.of(Action.DELETE);
default -> {
LOGGER.warn("Unknown mapping for study API resource action {}", apiAction);
return Set.of();
}
yield Set.of();
}
};
}

protected ResourceList apiListAuthorizedResources(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ public FeatureSet cloneFeatureSet(
String studyId,
String featureSetId,
String userEmail,
String destinationStudyId,
@Nullable String displayName,
@Nullable String description) {
FeatureSet original = getFeatureSet(studyId, featureSetId);
Expand All @@ -125,7 +126,7 @@ public FeatureSet cloneFeatureSet(
.criteria(original.getCriteria())
.excludeOutputAttributesPerEntity(original.getExcludeOutputAttributesPerEntity());

featureSetDao.createFeatureSet(studyId, featureSetBuilder.build());
featureSetDao.createFeatureSet(destinationStudyId, featureSetBuilder.build());
return featureSetDao.getFeatureSet(featureSetBuilder.getId());
}
}
2 changes: 2 additions & 0 deletions service/src/main/resources/api/service_openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2338,6 +2338,8 @@ components:
FeatureSetCloneInfo:
type: object
properties:
studyId:
dexamundsen marked this conversation as resolved.
Show resolved Hide resolved
$ref: "#/components/schemas/StudyId"
displayName:
$ref: "#/components/schemas/FeatureSetDisplayName"
description:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ void listAllOrSelected() {
assertNotNull(featureSet3);
LOGGER.info("Created feature set {} at {}", featureSet3.getId(), featureSet3.getCreated());

// List all cohorts in study2. Those from study1 are not displayed
// List all feature sets in study2. Those from study1 are not displayed
List<FeatureSet> allFeatureSets =
featureSetService.listFeatureSets(
ResourceCollection.allResourcesAllPermissions(
Expand Down Expand Up @@ -260,11 +260,16 @@ void cloneFeatureSet() {
assertNotNull(featureSet1);
LOGGER.info("Created feature set {} at {}", featureSet1.getId(), featureSet1.getCreated());

// Clone the feature set without new displayName and verify
// Clone the feature set into the same study without new displayName and verify
String newDescription = "cloned feature set description";
FeatureSet clonedFeatureSet1 =
featureSetService.cloneFeatureSet(
study1.getId(), featureSet1.getId(), USER_EMAIL_2, null, newDescription);
study1.getId(),
featureSet1.getId(),
USER_EMAIL_2,
study1.getId(),
null,
newDescription);
assertEquals(
2,
featureSetService
Expand Down Expand Up @@ -299,20 +304,24 @@ void cloneFeatureSet() {
assertNotNull(featureSet2);
LOGGER.info("Created feature set {} at {}", featureSet2.getId(), featureSet2.getCreated());

// Clone the feature set without new description and verify
// Clone the feature set into different study without new description and verify
String newDisplayName = "cloned feature set displayName";
FeatureSet clonedFeatureSet2 =
featureSetService.cloneFeatureSet(
study1.getId(), featureSet2.getId(), USER_EMAIL_2, newDisplayName, null);
assertEquals(
4,
featureSetService
.listFeatureSets(
ResourceCollection.allResourcesAllPermissions(
ResourceType.FEATURE_SET, ResourceId.forStudy(study1.getId())),
0,
10)
.size());
study1.getId(),
featureSet2.getId(),
USER_EMAIL_2,
study2.getId(),
newDisplayName,
null);
List<FeatureSet> study2FeatureSets =
featureSetService.listFeatureSets(
ResourceCollection.allResourcesAllPermissions(
ResourceType.FEATURE_SET, ResourceId.forStudy(study2.getId())),
0,
10);
assertEquals(1, study2FeatureSets.size());
assertEquals(clonedFeatureSet2.getId(), study2FeatureSets.get(0).getId());
assertEquals(newDisplayName, clonedFeatureSet2.getDisplayName());
assertEquals("Copy of: " + featureSet2.getDescription(), clonedFeatureSet2.getDescription());
dexamundsen marked this conversation as resolved.
Show resolved Hide resolved

Expand Down
2 changes: 1 addition & 1 deletion underlay/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ dependencies {
implementation 'jakarta.annotation:jakarta.annotation-api:3.0.0'

implementation "org.apache.commons:commons-text:${gradle.vApacheCommonsText}"
implementation "org.slf4j:slf4j-simple:${gradle.vSlf4jApi}"
testFixturesImplementation "org.slf4j:slf4j-api:${gradle.vSlf4jApi}"
implementation 'org.slf4j:slf4j-simple:2.0.16'

// GCP libraries versions are controlled by the BOM specified in buildSrc.
implementation 'com.google.cloud:google-cloud-bigquery'
Expand Down
4 changes: 2 additions & 2 deletions underlay/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ org.pcollections:pcollections:3.2.0=pmd
org.reflections:reflections:0.10.2=checkstyle
org.slf4j:jul-to-slf4j:1.7.36=pmd
org.slf4j:slf4j-api:2.0.0=spotbugsSlf4j
org.slf4j:slf4j-api:2.0.13=spotbugs,testFixturesCompileClasspath
org.slf4j:slf4j-api:2.0.16=compileClasspath,compileProtoPath,runtimeClasspath,testCompileClasspath,testCompileProtoPath,testFixturesCompileProtoPath,testFixturesRuntimeClasspath,testRuntimeClasspath
org.slf4j:slf4j-api:2.0.13=spotbugs
org.slf4j:slf4j-api:2.0.16=compileClasspath,compileProtoPath,runtimeClasspath,testCompileClasspath,testCompileProtoPath,testFixturesCompileClasspath,testFixturesCompileProtoPath,testFixturesRuntimeClasspath,testRuntimeClasspath
org.slf4j:slf4j-simple:2.0.0=spotbugsSlf4j
org.slf4j:slf4j-simple:2.0.16=compileClasspath,compileProtoPath,runtimeClasspath,testCompileClasspath,testCompileProtoPath,testFixturesCompileProtoPath,testFixturesRuntimeClasspath,testRuntimeClasspath
org.threeten:threeten-extra:1.8.0=compileClasspath,compileProtoPath,runtimeClasspath,testCompileClasspath,testCompileProtoPath,testFixturesCompileProtoPath,testFixturesRuntimeClasspath,testRuntimeClasspath
Expand Down