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

Fix Dataset Configurations #4845

Merged
merged 51 commits into from
Oct 29, 2020
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
27af731
change getting of dataset configurations
grittaweisheit Sep 30, 2020
f98f2d1
[WIP] backend split layer and dataset config handling
Sep 30, 2020
31bbc1f
clean up frontend
grittaweisheit Oct 1, 2020
be446f6
add evolutions
Oct 1, 2020
b0c1066
merge master
Oct 1, 2020
aa6be99
fix frontend linting
Oct 1, 2020
fd7cf89
fix frontend tests
Oct 2, 2020
0528ed1
fix frontend tests 2
Oct 2, 2020
37da2ae
pretty frontend
Oct 2, 2020
4c2cdf4
update changelog and migrations guide
Oct 5, 2020
59f80a9
Apply suggestions from code review
youri-k Oct 5, 2020
9f54474
remove unnecessary invalidation of user
Oct 5, 2020
644ff27
Merge branch 'fix-dataset-configurations' of github.com:scalableminds…
Oct 5, 2020
065cf37
apply pr feedback
Oct 7, 2020
51bbca3
Merge branch 'master' into fix-dataset-configurations
youri-k Oct 8, 2020
58cafc5
check for volume ids when no config is found
Oct 9, 2020
44343d9
Merge branch 'fix-dataset-configurations' of github.com:scalableminds…
Oct 9, 2020
c469d1b
rename default view config to unify wk and datastore implementation
Oct 12, 2020
7818a06
rename backend types to match each other
Oct 13, 2020
eaf5081
Merge branch 'master' of github.com:scalableminds/webknossos into fix…
Oct 13, 2020
039d0a5
Merge branch 'master' of github.com:scalableminds/webknossos into fix…
Oct 14, 2020
4d100a4
add frontend part to validate DatasetConfiguration
Oct 14, 2020
667c8be
fix evolution after renaming
Oct 14, 2020
1b07231
move type and schema definitions
Oct 14, 2020
6f36e65
fix e2e tests
Oct 14, 2020
cc3c2d0
move datasource types
Oct 15, 2020
bdffbbe
update snapshots
Oct 15, 2020
5c2f2e9
run eliminate errors always because it checks for errors
Oct 15, 2020
dee2ecd
enforce validated configuration in api instead of model_initialization
philippotto Oct 15, 2020
8aaaabb
use new validation for dataset import as well
Oct 15, 2020
8981f57
fix frontend tests
Oct 15, 2020
ece440c
Merge branch 'master' of github.com:scalableminds/webknossos into fix…
Oct 15, 2020
ec89820
add table for layer view configurations
Oct 15, 2020
01941fc
fix for null values
Oct 15, 2020
1c65001
fix frontend test
Oct 15, 2020
1fda7f6
add additional frontend tests
Oct 16, 2020
fbfec83
fix pretty
Oct 16, 2020
ff52183
reversion of evolution
Oct 16, 2020
ab42998
rework update layers
Oct 16, 2020
bfc8f39
fix naming in reversion of evolution
Oct 16, 2020
8453954
update documentation
Oct 16, 2020
2939900
fix documentation
Oct 16, 2020
a3fa8e8
rename methods to new naming schema
Oct 19, 2020
6c55777
Merge branch 'master' of github.com:scalableminds/webknossos into fix…
Oct 20, 2020
7a74654
pr feedback
Oct 20, 2020
beb7411
merge master
Oct 26, 2020
2e9219a
bump schema version
Oct 26, 2020
526a75a
fix version number in evolution
Oct 26, 2020
22c66b2
Merge branch 'master' into fix-dataset-configurations
youri-k Oct 26, 2020
2b5562c
Merge branch 'master' into fix-dataset-configurations
youri-k Oct 28, 2020
b02021e
Merge branch 'master' into fix-dataset-configurations
youri-k Oct 28, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released

### Fixed
- Fixed failing histogram requests for float layers with NaN values. [#4834](https://github.com/scalableminds/webknossos/pull/4834)
- Fixed the disappearing of dataset settings after switching between view mode and trace mode. [#4845](https://github.com/scalableminds/webknossos/pull/4845)
youri-k marked this conversation as resolved.
Show resolved Hide resolved

### Removed
-
2 changes: 1 addition & 1 deletion MIGRATIONS.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ User-facing changes are documented in the [changelog](CHANGELOG.released.md).
-

### Postgres Evolutions:
-
- [056-add-layer-specific-view-configs.sql](conf/evolutions/056-add-layer-specific-view-configs.sql)
64 changes: 40 additions & 24 deletions app/controllers/ConfigurationController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,26 @@ import com.scalableminds.util.accesscontext.{DBAccessContext, GlobalAccessContex
import com.scalableminds.util.tools.Fox
import javax.inject.Inject
import models.binary.{DataSet, DataSetDAO, DataSetService}
import models.configuration.{DataSetConfiguration, DataSetConfigurationDefaults, UserConfiguration}
import models.user.{UserDataSetConfigurationDAO, UserService}
import models.configuration.{DataSetConfiguration, DataSetConfigurationDefaults, DataSetLayerId, UserConfiguration}
import models.user.{UserDataSetConfigurationDAO, UserDataSetLayerConfigurationDAO, UserService}
import oxalis.security.WkEnv
import com.mohiva.play.silhouette.api.Silhouette
import com.mohiva.play.silhouette.api.actions.{SecuredRequest, UserAwareRequest}
import play.api.i18n.{Messages, MessagesApi}
import play.api.libs.json.{JsObject, JsValue}
import play.api.libs.json.Json._
import play.api.mvc.PlayBodyParsers

import scala.concurrent.ExecutionContext

class ConfigurationController @Inject()(userService: UserService,
dataSetService: DataSetService,
dataSetDAO: DataSetDAO,
userDataSetConfigurationDAO: UserDataSetConfigurationDAO,
dataSetConfigurationDefaults: DataSetConfigurationDefaults,
sil: Silhouette[WkEnv])(implicit ec: ExecutionContext)
class ConfigurationController @Inject()(
userService: UserService,
dataSetService: DataSetService,
dataSetDAO: DataSetDAO,
userDataSetConfigurationDAO: UserDataSetConfigurationDAO,
userDataSetLayerConfigurationDAO: UserDataSetLayerConfigurationDAO,
dataSetConfigurationDefaults: DataSetConfigurationDefaults,
sil: Silhouette[WkEnv])(implicit ec: ExecutionContext, bodyParsers: PlayBodyParsers)
extends Controller {

def read = sil.UserAwareAction.async { implicit request =>
Expand All @@ -41,30 +44,45 @@ class ConfigurationController @Inject()(userService: UserService,
}
}

def readDataSet(organizationName: String, dataSetName: String) = sil.UserAwareAction.async { implicit request =>
request.identity.toFox.flatMap { user =>
for {
configurationJson: JsValue <- userDataSetConfigurationDAO.findOneForUserAndDataset(user._id, dataSetName)
} yield DataSetConfiguration(configurationJson.validate[Map[String, JsValue]].getOrElse(Map.empty))
}.orElse(
def readDataSet(organizationName: String, dataSetName: String) =
sil.UserAwareAction.async(validateJson[List[DataSetLayerId]]) { implicit request =>
request.identity.toFox.flatMap { user =>
for {
configurationJson: JsValue <- userDataSetConfigurationDAO.findOneForUserAndDataset(user._id, dataSetName)
initialConfigurationMap = configurationJson.validate[Map[String, JsValue]].getOrElse(Map.empty)
layerConfigJson <- userDataSetLayerConfigurationDAO.findAllByLayerNameForUserAndDataset(
request.body.map(_.name),
user._id,
dataSetName)
dataSet <- dataSetDAO.findOneByNameAndOrganizationName(dataSetName, organizationName)(GlobalAccessContext)
config <- dataSetConfigurationDefaults.constructInitialDefault(dataSet)
} yield config
)
.getOrElse(dataSetConfigurationDefaults.constructInitialDefault(List()))
.map(configuration => Ok(toJson(dataSetConfigurationDefaults.configurationOrDefaults(configuration))))
}
layerSourceDefaultViewConfigs <- dataSetConfigurationDefaults.getAllLayerSourceDefaultViewConfigForDataSet(
dataSet)
layerSettings = dataSetConfigurationDefaults.layerConfigurationOrDefaults(request.body,
layerConfigJson,
layerSourceDefaultViewConfigs)
} yield dataSetConfigurationDefaults.buildCompleteConfig(initialConfigurationMap, layerSettings)
}.orElse(
for {
dataSet <- dataSetDAO.findOneByNameAndOrganizationName(dataSetName, organizationName)(GlobalAccessContext)
config <- dataSetConfigurationDefaults.constructInitialDefault(dataSet)
} yield config
)
.getOrElse(dataSetConfigurationDefaults.constructInitialDefault(List()))
.map(configuration => Ok(toJson(configuration.configuration)))
}

def updateDataSet(organizationName: String, dataSetName: String) =
sil.SecuredAction.async(parse.json(maxLength = 20480)) { implicit request =>
for {
jsConfiguration <- request.body.asOpt[JsObject] ?~> "user.configuration.dataset.invalid"
conf = jsConfiguration.fields.toMap
dataSetConf = conf - "layers"
layerConf = conf.get("layers")
_ <- userService.updateDataSetConfiguration(request.identity,
dataSetName,
organizationName,
DataSetConfiguration(conf))
DataSetConfiguration(dataSetConf),
layerConf)
} yield {
JsonOk(Messages("user.configuration.dataset.updated"))
}
Expand All @@ -77,9 +95,7 @@ class ConfigurationController @Inject()(userService: UserService,
Fox.successful(
Ok(toJson(dataSetConfigurationDefaults.configurationOrDefaults(c, dataSet.sourceDefaultConfiguration))))
case _ =>
dataSetConfigurationDefaults
.constructInitialDefault(dataSet)
.map(c => Ok(toJson(dataSetConfigurationDefaults.configurationOrDefaults(c))))
dataSetConfigurationDefaults.constructInitialDefault(dataSet).map(c => Ok(toJson(c.configuration)))
}
}
}
Expand Down
31 changes: 30 additions & 1 deletion app/models/configuration/DataSetConfiguration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import javax.inject.Inject
import models.binary.{DataSet, DataSetService}
import play.api.libs.json._

case class DataSetLayerId(name: String, isSegmentationLayer: Boolean)
object DataSetLayerId { implicit val dataSetLayerId = Json.format[DataSetLayerId] }

case class DataSetConfiguration(configuration: Map[String, JsValue])

object DataSetConfiguration { implicit val dataSetConfigurationFormat = Json.format[DataSetConfiguration] }
Expand Down Expand Up @@ -40,7 +43,6 @@ class DataSetConfigurationDefaults @Inject()(dataSetService: DataSetService) {
DataSetConfiguration(
Map(
"fourBit" -> JsBoolean(false),
"quality" -> JsNumber(0),
"interpolation" -> JsBoolean(true),
"highlightHoveredCellId" -> JsBoolean(true),
"renderMissingDataBlack" -> JsBoolean(true),
Expand All @@ -55,6 +57,33 @@ class DataSetConfigurationDefaults @Inject()(dataSetService: DataSetService) {
sourceDefaultConfiguration.map(_.toMap).getOrElse(Map.empty) ++
configuration.configuration

def layerConfigurationOrDefaults(requestedLayer: List[DataSetLayerId],
existingLayerConfiguration: Map[String, JsValue],
sourceDefaultConfiguration: Map[String, JsValue]) =
requestedLayer.map {
case DataSetLayerId(name, isSegmentationLayer) =>
(name,
existingLayerConfiguration.getOrElse(
name,
sourceDefaultConfiguration.getOrElse(name,
Json.toJson(
if (isSegmentationLayer) initialDefaultPerSegmentationLayer
else initialDefaultPerColorLayer))))
}.toMap

def buildCompleteConfig(initialConfiguration: Map[String, JsValue], layerConfigurations: Map[String, JsValue]) =
DataSetConfiguration(initialConfiguration + ("layers" -> Json.toJson(layerConfigurations)))

def getAllLayerSourceDefaultViewConfigForDataSet(dataSet: DataSet)(
implicit ctx: DBAccessContext): Fox[Map[String, JsValue]] =
for {

dataSource <- dataSetService.dataSourceFor(dataSet)
dataLayers = dataSource.toUsable.map(d => d.dataLayers).getOrElse(List())
layerSourceDefaultViewConfig = dataLayers.flatMap(dl =>
dl.defaultViewConfiguration.map(c => (dl.name, Json.toJson(c.toMap))))
} yield layerSourceDefaultViewConfig.toMap

val initialDefaultPerColorLayer: Map[String, JsValue] = Map(
"brightness" -> JsNumber(0),
"contrast" -> JsNumber(1),
Expand Down
36 changes: 35 additions & 1 deletion app/models/user/User.scala
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ class UserDataSetConfigurationDAO @Inject()(sqlClient: SQLClient, userDAO: UserD
where d.name = ${dataSetName}
and c._user = ${userId}
""".as[String])
parsed = rows.map(Json.parse(_))
parsed = rows.map(Json.parse)
result <- parsed.headOption.toFox
} yield result

Expand Down Expand Up @@ -370,3 +370,37 @@ class UserDataSetConfigurationDAO @Inject()(sqlClient: SQLClient, userDAO: UserD
} yield ()

}

class UserDataSetLayerConfigurationDAO @Inject()(sqlClient: SQLClient, userDAO: UserDAO)(implicit ec: ExecutionContext)
extends SimpleSQLDAO(sqlClient) {

def findAllByLayerNameForUserAndDataset(layerNames: List[String], userId: ObjectId, dataSetName: String)(
implicit ctx: DBAccessContext): Fox[Map[String, JsValue]] =
for {
rows <- run(sql"""select layerName, configuration
from webknossos.user_dataSetLayerConfigurations
where _dataset in (select _id from webknossos.dataSets_ where name = $dataSetName)
and _user = $userId
and layerName in #${writeStructTupleWithQuotes(layerNames)}
""".as[(String, String)])
parsed = rows.map(t => (t._1, Json.parse(t._2)))
} yield parsed.toMap

def updateDatasetConfigurationForUserAndDatasetAndLayer(
userId: ObjectId,
dataSetId: ObjectId,
layerName: String,
configuration: JsValue)(implicit ctx: DBAccessContext): Fox[Unit] =
for {
_ <- userDAO.assertUpdateAccess(userId)
deleteQuery = sqlu"""delete from webknossos.user_dataSetLayerConfigurations
where _user = $userId and _dataSet = $dataSetId and layerName = $layerName"""
insertQuery = sqlu"""insert into webknossos.user_dataSetLayerConfigurations(_user, _dataSet, layerName, configuration)
values($userId, $dataSetId, $layerName, '#${sanitize(configuration.toString)}')"""
_ <- run(
DBIO.sequence(List(deleteQuery, insertQuery)).transactionally.withTransactionIsolation(Serializable),
retryCount = 50,
retryIfErrorContains = List(transactionSerializationError)
)
} yield ()
}
16 changes: 13 additions & 3 deletions app/models/user/UserService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class UserService @Inject()(conf: WkConf,
userTeamRolesDAO: UserTeamRolesDAO,
userExperiencesDAO: UserExperiencesDAO,
userDataSetConfigurationDAO: UserDataSetConfigurationDAO,
userDataSetLayerConfigurationDAO: UserDataSetLayerConfigurationDAO,
organizationDAO: OrganizationDAO,
teamDAO: TeamDAO,
teamMembershipService: TeamMembershipService,
Expand Down Expand Up @@ -146,15 +147,24 @@ class UserService @Inject()(conf: WkConf,
user: User,
dataSetName: String,
organizationName: String,
configuration: DataSetConfiguration)(implicit ctx: DBAccessContext, m: MessagesProvider) =
dataSetConfiguration: DataSetConfiguration,
layerConfiguration: Option[JsValue])(implicit ctx: DBAccessContext, m: MessagesProvider) =
for {
dataSet <- dataSetDAO.findOneByNameAndOrganizationName(dataSetName, organizationName) ?~> Messages(
"dataSet.notFound",
dataSetName)
layerMap = layerConfiguration.map(_.validate[Map[String, JsValue]].getOrElse(Map.empty)).getOrElse(Map.empty)
_ <- Fox.serialCombined(layerMap.toList) {
case (name, config) =>
userDataSetLayerConfigurationDAO.updateDatasetConfigurationForUserAndDatasetAndLayer(user._id,
dataSet._id,
name,
config)
}
_ <- userDataSetConfigurationDAO.updateDatasetConfigurationForUserAndDataset(user._id,
dataSet._id,
configuration.configuration)
_ = userCache.invalidateUser(user._id)
dataSetConfiguration.configuration)
_ = userCache.invalidateUser(user._id) // TODO Why do we need to invalidate the user here?
youri-k marked this conversation as resolved.
Show resolved Hide resolved
} yield ()

def updateLastTaskTypeId(user: User, lastTaskTypeId: Option[String])(implicit ctx: DBAccessContext) =
Expand Down
36 changes: 36 additions & 0 deletions conf/evolutions/056-add-layer-specific-view-configs.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
-- https://github.com/scalableminds/webknossos/pull/4XXX
youri-k marked this conversation as resolved.
Show resolved Hide resolved

START TRANSACTION;

CREATE TABLE webknossos.user_dataSetLayerConfigurations(
_user CHAR(24) NOT NULL,
_dataSet CHAR(24) NOT NULL,
layerName VARCHAR(256) NOT NULL,
configuration JSONB NOT NULL,
PRIMARY KEY (_user, _dataSet, layerName),
CONSTRAINT configurationIsJsonObject CHECK(jsonb_typeof(configuration) = 'object')
);

ALTER TABLE webknossos.user_dataSetLayerConfigurations
ADD CONSTRAINT user_ref FOREIGN KEY(_user) REFERENCES webknossos.users(_id) ON DELETE CASCADE DEFERRABLE,
ADD CONSTRAINT dataSet_ref FOREIGN KEY(_dataSet) REFERENCES webknossos.dataSets(_id) ON DELETE CASCADE DEFERRABLE;

-- Insert the layer configs into the new table
INSERT INTO webknossos.user_dataSetLayerConfigurations(_user, _dataset, layerName, configuration)
SELECT _user, _dataset, (js).key AS layerName, (js).value AS config FROM (SELECT _user, _dataset, jsonb_each(configuration->'layers') AS js FROM webknossos.user_dataSetConfigurations WHERE configuration ? 'layers') AS sub_q;


-- Remove layers field from old table
UPDATE webknossos.user_dataSetConfigurations
SET configuration = configuration - 'layers'
WHERE configuration ? 'layers';

--remove unused field quality
UPDATE webknossos.user_dataSetConfigurations
SET configuration = configuration - 'quality'
WHERE configuration ? 'quality';


UPDATE webknossos.releaseInformation SET schemaVersion = 56;

COMMIT TRANSACTION;
17 changes: 17 additions & 0 deletions conf/evolutions/reversions/056-add-layer-specific-view-configs.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

START TRANSACTION;

-- pre-aggregate all layer jsons into one json object, set json object in configuration
UPDATE webknossos.user_dataSetConfigurations dsC
SET configuration = jsonb_set(
dsC.configuration,
array['layers'],
subQ.json)
FROM (select _user, _dataset, jsonb_object_agg(layerName, configuration) AS json FROM webknossos.user_dataSetLayerConfigurations dlC GROUP BY _user, _dataset) AS subQ
WHERE dsC._user = subQ._user and dsC._dataset = subQ._dataset;

DROP TABLE webknossos.user_dataSetLayerConfigurations;

UPDATE webknossos.releaseInformation SET schemaVersion = 55;

COMMIT TRANSACTION;
2 changes: 1 addition & 1 deletion conf/webknossos.latest.routes
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ POST /auth/createOrganizationWithAdmin
# Configurations
GET /user/userConfiguration controllers.ConfigurationController.read
PUT /user/userConfiguration controllers.ConfigurationController.update
GET /dataSetConfigurations/:organizationName/:dataSetName controllers.ConfigurationController.readDataSet(organizationName: String, dataSetName: String)
POST /dataSetConfigurations/:organizationName/:dataSetName controllers.ConfigurationController.readDataSet(organizationName: String, dataSetName: String)
PUT /dataSetConfigurations/:organizationName/:dataSetName controllers.ConfigurationController.updateDataSet(organizationName: String, dataSetName: String)
GET /dataSetConfigurations/default/:organizationName/:dataSetName controllers.ConfigurationController.readDataSetDefault(organizationName: String, dataSetName: String)
PUT /dataSetConfigurations/default/:organizationName/:dataSetName controllers.ConfigurationController.updateDataSetDefault(organizationName: String, dataSetName: String)
Expand Down
12 changes: 10 additions & 2 deletions frontend/javascripts/admin/admin_rest_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
type APIProjectProgressReport,
type APIProjectUpdater,
type APIProjectWithAssignments,
type APIReducedDataLayer,
type APISampleDataset,
type APIScript,
type APIScriptCreator,
Expand Down Expand Up @@ -783,9 +784,16 @@ export function updateDataset(datasetId: APIDatasetId, dataset: APIDataset): Pro
);
}

export function getDatasetConfiguration(datasetId: APIDatasetId): Promise<Object> {
return Request.receiveJSON(
export function getDatasetViewConfiguration(
datasetId: APIDatasetId,
displayedLayers: Array<APIReducedDataLayer>,
): Promise<Object> {
return Request.sendJSONReceiveJSON(
`/api/dataSetConfigurations/${datasetId.owningOrganization}/${datasetId.name}`,
{
data: displayedLayers,
method: "POST",
},
);
}

Expand Down
5 changes: 5 additions & 0 deletions frontend/javascripts/admin/api_flow_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ export type APISegmentationLayer = {|

export type APIDataLayer = APIColorLayer | APISegmentationLayer;

export type APIReducedDataLayer = {|
+name: string,
+isSegmentationLayer: boolean,
|};

export type APIHistogramData = Array<{
numberOfElements: number,
elementCounts: Array<number>,
Expand Down
1 change: 0 additions & 1 deletion frontend/javascripts/oxalis/default_state.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ const defaultState: OxalisState = {
fourBit: false,
interpolation: false,
layers: {},
quality: 0,
loadingStrategy: "PROGRESSIVE_QUALITY",
highlightHoveredCellId: true,
segmentationPatternOpacity: 40,
Expand Down
Loading