-
Notifications
You must be signed in to change notification settings - Fork 24
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
Link Previews #7331
Merged
Merged
Link Previews #7331
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
691d3f8
add meta tags
knollengewaechs 1a75955
disable ci checks
fm3 8f9e24f
add workflow meta tags
knollengewaechs 3f51e5f
set static og tags
fm3 7685472
add og tags in backend
fm3 3c7ed22
also for annotations
fm3 f6afd43
unused import
fm3 db9ca36
unused import
fm3 d259759
1000x300
fm3 d0e0c41
Merge branch 'master' into react-helmet
fm3 14ad45f
remove frontend changes, as clients do not execute javascript
fm3 707bf09
naming after merge
fm3 dfba4da
use token from uri if available for access check
fm3 df6c020
defaults
fm3 46723f1
cleanup
fm3 562f7e6
add sharing token to thumbnail route
fm3 6447a99
resolve shortlinks, remove workflow-specific tags
fm3 c5c9662
cleanup, changelog
fm3 0f2014a
Merge branch 'master' into react-helmet
fm3 b67cc0e
implement feedback
fm3 c3de6b3
Merge branch 'master' into react-helmet
fm3 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
package oxalis.opengraph | ||
|
||
import akka.http.scaladsl.model.Uri | ||
import com.google.inject.Inject | ||
import com.scalableminds.util.accesscontext.DBAccessContext | ||
import com.scalableminds.util.enumeration.ExtendedEnumeration | ||
import com.scalableminds.util.tools.Fox | ||
import com.scalableminds.webknossos.datastore.models.datasource.{Category, DataLayerLike} | ||
import models.annotation.AnnotationDAO | ||
import models.binary.{Dataset, DatasetDAO, DatasetLayerDAO} | ||
import models.organization.{Organization, OrganizationDAO} | ||
import models.shortlinks.ShortLinkDAO | ||
import net.liftweb.common.Full | ||
import oxalis.security.URLSharing | ||
import java.net.URLDecoder | ||
import utils.{ObjectId, WkConf} | ||
|
||
import scala.concurrent.ExecutionContext | ||
|
||
case class OpenGraphTags( | ||
title: Option[String], | ||
description: Option[String], | ||
image: Option[String] | ||
) | ||
|
||
object OpenGraphPageType extends ExtendedEnumeration { | ||
val dataset, annotation, workflow, unknown = Value | ||
} | ||
|
||
class OpenGraphService @Inject()(datasetDAO: DatasetDAO, | ||
organizationDAO: OrganizationDAO, | ||
datasetLayerDAO: DatasetLayerDAO, | ||
annotationDAO: AnnotationDAO, | ||
shortLinkDAO: ShortLinkDAO, | ||
conf: WkConf) { | ||
|
||
def getOpenGraphTags(uriPath: String, sharingToken: Option[String])(implicit ec: ExecutionContext, | ||
ctx: DBAccessContext): Fox[OpenGraphTags] = | ||
for { | ||
(uriPathResolved, sharingTokenResolved) <- resolveShortLinkIfNeeded(uriPath, sharingToken) | ||
ctxWithToken = URLSharing.fallbackTokenAccessContext(sharingTokenResolved) | ||
pageType = detectPageType(uriPathResolved) | ||
tagsFox = pageType match { | ||
case OpenGraphPageType.dataset => datasetOpenGraphTags(uriPathResolved, sharingTokenResolved)(ec, ctxWithToken) | ||
case OpenGraphPageType.annotation => | ||
annotationOpenGraphTags(uriPathResolved, sharingTokenResolved)(ec, ctxWithToken) | ||
case OpenGraphPageType.workflow => | ||
Fox.successful(defaultTags(OpenGraphPageType.workflow)) // No sharing token mechanism for workflows yet | ||
case OpenGraphPageType.unknown => Fox.successful(defaultTags()) | ||
} | ||
// In error case (probably no access permissions), fall back to default, so the html template does not break | ||
tagsBox <- tagsFox.futureBox | ||
tags = tagsBox match { | ||
case Full(tags) => tags | ||
case _ => defaultTags(pageType) | ||
} | ||
} yield tags | ||
|
||
private def resolveShortLinkIfNeeded(uriPath: String, sharingToken: Option[String])( | ||
implicit ec: ExecutionContext): Fox[(String, Option[String])] = | ||
uriPath match { | ||
case shortLinkRouteRegex(key) => | ||
for { | ||
shortLink <- shortLinkDAO.findOneByKey(key) | ||
asUri: Uri = Uri(URLDecoder.decode(shortLink.longLink, "UTF-8")) | ||
} yield (asUri.path.toString, asUri.query().get("token").orElse(asUri.query().get("sharingToken"))) | ||
case _ => Fox.successful(uriPath, sharingToken) | ||
} | ||
|
||
private def detectPageType(uriPath: String) = | ||
uriPath match { | ||
case datasetRoute1Regex(_, _) | datasetRoute2Regex(_, _) => OpenGraphPageType.dataset | ||
case annotationRouteRegex(_) => OpenGraphPageType.annotation | ||
case workflowRouteRegex(_) => OpenGraphPageType.workflow | ||
case _ => OpenGraphPageType.unknown | ||
} | ||
|
||
private val shortLinkRouteRegex = "^/links/(.*)".r | ||
private val datasetRoute1Regex = "^/datasets/([^/^#]+)/([^/^#]+)/view".r | ||
private val datasetRoute2Regex = "^/datasets/([^/^#]+)/([^/^#]+)".r | ||
private val workflowRouteRegex = "^/workflows/([^/^#]+)".r | ||
private val annotationRouteRegex = "^/annotations/([^/^#]+)".r | ||
|
||
private def datasetOpenGraphTags(uriPath: String, token: Option[String])(implicit ec: ExecutionContext, | ||
ctx: DBAccessContext): Fox[OpenGraphTags] = | ||
uriPath match { | ||
case datasetRoute1Regex(organizationName, datasetName) => | ||
datasetOpenGraphTagsWithOrganizationName(organizationName, datasetName, token) | ||
case datasetRoute2Regex(organizationName, datasetName) => | ||
datasetOpenGraphTagsWithOrganizationName(organizationName, datasetName, token) | ||
case _ => Fox.failure("not a matching uri") | ||
} | ||
|
||
private def datasetOpenGraphTagsWithOrganizationName(organizationName: String, | ||
datasetName: String, | ||
token: Option[String])(implicit ctx: DBAccessContext) = | ||
for { | ||
dataset <- datasetDAO.findOneByNameAndOrganizationName(datasetName, organizationName) | ||
layers <- datasetLayerDAO.findAllForDataset(dataset._id) | ||
layerOpt = layers.find(_.category == Category.color) | ||
organization <- organizationDAO.findOne(dataset._organization) | ||
} yield | ||
OpenGraphTags( | ||
Some(s"${dataset.displayName.getOrElse(datasetName)} | WEBKNOSSOS"), | ||
Some(s"View this dataset in WEBKNOSSOS"), | ||
thumbnailUri(dataset, layerOpt, organization, token) | ||
) | ||
|
||
private def annotationOpenGraphTags(uriPath: String, token: Option[String])( | ||
implicit ec: ExecutionContext, | ||
ctx: DBAccessContext): Fox[OpenGraphTags] = | ||
uriPath match { | ||
case annotationRouteRegex(annotationId) => | ||
for { | ||
annotationIdValidated <- ObjectId.fromString(annotationId) | ||
annotation <- annotationDAO.findOne(annotationIdValidated) | ||
dataset: Dataset <- datasetDAO.findOne(annotation._dataSet) | ||
organization <- organizationDAO.findOne(dataset._organization) | ||
layers <- datasetLayerDAO.findAllForDataset(dataset._id) | ||
layerOpt = layers.find(_.category == Category.color) | ||
} yield | ||
OpenGraphTags( | ||
Some(s"${annotation.nameOpt.orElse(dataset.displayName).getOrElse(dataset.name)} | WEBKNOSSOS"), | ||
Some(s"View this annotation on dataset ${dataset.displayName.getOrElse(dataset.name)} in WEBKNOSSOS"), | ||
thumbnailUri(dataset, layerOpt, organization, token) | ||
) | ||
case _ => Fox.failure("not a matching uri") | ||
} | ||
|
||
private def thumbnailUri(dataset: Dataset, | ||
layerOpt: Option[DataLayerLike], | ||
organization: Organization, | ||
token: Option[String]): Option[String] = | ||
layerOpt.map { layer => | ||
val tokenParam = token.map(t => s"&sharingToken=$t").getOrElse("") | ||
s"${conf.Http.uri}/api/datasets/${organization.name}/${dataset.name}/layers/${layer.name}/thumbnail?w=1000&h=300$tokenParam" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the size could live in a constant somewhere |
||
} | ||
|
||
private def defaultTags(pageType: OpenGraphPageType.Value = OpenGraphPageType.unknown): OpenGraphTags = { | ||
val description = pageType match { | ||
case OpenGraphPageType.dataset => Some("View this dataset in WEBKNOSSOS") | ||
case OpenGraphPageType.annotation => Some("View this annotation in WEBKNOSSOS") | ||
case OpenGraphPageType.workflow => Some("View this voxelytics workflow report in WEBKNOSSOS") | ||
case _ => None // most clients will fall back to <meta name="description">, see template | ||
} | ||
OpenGraphTags( | ||
Some("WEBKNOSSOS"), | ||
description, | ||
None | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it be a lot of effort to rename oxalis --> webknossos?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, I’ll do that in a separate PR. Truth be told, the package structure doesn’t make that much sense even with the renaming, but it would certainly be an easy improvement