Add configurable about this dataset links (#907)

* add about arg

* add simple url validator

* attach about link to config api

* add links to configDefaults

* add conditional link in top left and menu item

* whitespace

* change to lower case

* move --about arg before click.command()

if this fixes it I have no idea why

* change link>URL

* be more descriptive about URL

* Make error more explicit

* refactor attach_data to accept about

* format

* change icon

* add trailing parenthesis

* whitespace
This commit is contained in:
Severiano Badajoz
2019-09-25 19:23:49 -07:00
committed by GitHub
parent e64f4f06fb
commit 44cb276cdf
7 changed files with 62 additions and 17 deletions
@@ -7,15 +7,26 @@ import Logo from "../framework/logo";
@connect(state => ({ @connect(state => ({
responsive: state.responsive, responsive: state.responsive,
datasetTitle: state.config?.displayNames?.dataset ?? "", datasetTitle: state.config?.displayNames?.dataset ?? "",
aboutURL: state.config?.links?.["about-dataset"],
scatterplotXXaccessor: state.controls.scatterplotXXaccessor, scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
scatterplotYYaccessor: state.controls.scatterplotYYaccessor scatterplotYYaccessor: state.controls.scatterplotYYaccessor
})) }))
class LeftSideBar extends React.Component { class LeftSideBar extends React.Component {
render() { render() {
const { datasetTitle } = this.props; const { datasetTitle, aboutURL } = this.props;
const paddingToAvoidScrollBar = 15; const paddingToAvoidScrollBar = 15;
const displayTitle =
datasetTitle.length > globals.datasetTitleMaxCharacterCount
? `${datasetTitle.substring(
0,
Math.floor(globals.datasetTitleMaxCharacterCount / 2)
)}…${datasetTitle.slice(
-Math.floor(globals.datasetTitleMaxCharacterCount / 2)
)}`
: datasetTitle;
return ( return (
<div <div
style={{ style={{
@@ -69,14 +80,7 @@ class LeftSideBar extends React.Component {
}} }}
title={datasetTitle} title={datasetTitle}
> >
{datasetTitle.length > globals.datasetTitleMaxCharacterCount {aboutURL ? <a href={aboutURL}>{displayTitle}</a> : displayTitle}
? `${datasetTitle.substring(
0,
Math.floor(globals.datasetTitleMaxCharacterCount / 2)
)}…${datasetTitle.slice(
-Math.floor(globals.datasetTitleMaxCharacterCount / 2)
)}`
: datasetTitle}
</div> </div>
</div> </div>
); );
+8 -3
View File
@@ -38,7 +38,8 @@ import { tooltipHoverOpenDelay } from "../../globals";
celllist2: state.differential.celllist2, celllist2: state.differential.celllist2,
libraryVersions: state.config?.library_versions, // eslint-disable-line camelcase libraryVersions: state.config?.library_versions, // eslint-disable-line camelcase
undoDisabled: state["@@undoable/past"].length === 0, undoDisabled: state["@@undoable/past"].length === 0,
redoDisabled: state["@@undoable/future"].length === 0 redoDisabled: state["@@undoable/future"].length === 0,
aboutLink: state.config?.links?.["about-dataset"]
})) }))
class MenuBar extends React.Component { class MenuBar extends React.Component {
static isValidDigitKeyEvent(e) { static isValidDigitKeyEvent(e) {
@@ -267,7 +268,8 @@ class MenuBar extends React.Component {
clipPercentileMin, clipPercentileMin,
clipPercentileMax, clipPercentileMax,
layoutChoice, layoutChoice,
graphInteractionMode graphInteractionMode,
aboutLink
} = this.props; } = this.props;
const { pendingClipPercentiles } = this.state; const { pendingClipPercentiles } = this.state;
@@ -473,7 +475,10 @@ class MenuBar extends React.Component {
undoDisabled={undoDisabled} undoDisabled={undoDisabled}
redoDisabled={redoDisabled} redoDisabled={redoDisabled}
/> />
<InformationMenu libraryVersions={libraryVersions} /> <InformationMenu
libraryVersions={libraryVersions}
aboutLink={aboutLink}
/>
</div> </div>
); );
} }
+12 -1
View File
@@ -3,12 +3,23 @@ import React from "react";
import { Button, Popover, Menu, MenuItem, Position } from "@blueprintjs/core"; import { Button, Popover, Menu, MenuItem, Position } from "@blueprintjs/core";
function InformationMenu(props) { function InformationMenu(props) {
const { libraryVersions } = props; const { libraryVersions, aboutLink } = props;
return ( return (
<div style={{ marginLeft: 10 }} className="bp3-button-group"> <div style={{ marginLeft: 10 }} className="bp3-button-group">
<Popover <Popover
content={ content={
<Menu> <Menu>
{aboutLink ? (
<MenuItem
href={aboutLink}
target="_blank"
icon="document-open"
text="About this dataset"
/>
) : (
""
)}
<MenuItem <MenuItem
href="https://chanzuckerberg.github.io/cellxgene/faq.html" href="https://chanzuckerberg.github.io/cellxgene/faq.html"
target="_blank" target="_blank"
+2 -1
View File
@@ -15,7 +15,8 @@ export const configDefaults = {
displayNames: {}, displayNames: {},
parameters: { parameters: {
"max-category-items": 1000 "max-category-items": 1000
} },
links: {}
}; };
/* colors */ /* colors */
+2 -2
View File
@@ -33,6 +33,6 @@ class Server:
self.app.register_blueprint(resources.blueprint) self.app.register_blueprint(resources.blueprint)
self.app.add_url_rule("/", endpoint="index") self.app.add_url_rule("/", endpoint="index")
def attach_data(self, data, title="Demo"): def attach_data(self, data, title="Demo", about=""):
self.app.config.update(DATASET_TITLE=title) self.app.config.update(DATASET_TITLE=title, ABOUT_DATASET=about)
self.app.data = data self.app.data = data
+3
View File
@@ -65,6 +65,9 @@ class ConfigAPI(Resource):
"engine": f"cellxgene Scanpy engine version ", "engine": f"cellxgene Scanpy engine version ",
"dataset": current_app.config["DATASET_TITLE"], "dataset": current_app.config["DATASET_TITLE"],
}, },
"links": {
"about-dataset": current_app.config["ABOUT_DATASET"]
},
"parameters": { "parameters": {
"max-category-items": current_app.data.config["max_category_items"] "max-category-items": current_app.data.config["max_category_items"]
}, },
+22 -1
View File
@@ -6,6 +6,7 @@ from os.path import splitext, basename
import sys import sys
import warnings import warnings
import webbrowser import webbrowser
from urllib.parse import urlparse
import click import click
@@ -23,7 +24,11 @@ def common_args(func):
""" """
Decorator to contain CLI args that will be common to both CLI and GUI: title and engine args. Decorator to contain CLI args that will be common to both CLI and GUI: title and engine args.
""" """
@click.option("--title", "-t", help="Title to display (if omitted will use file name).") @click.option("--title", "-t", help="Title to display (if omitted will use file name).")
@click.option("--about",
help="A URL to more information about the dataset."
"(This must be an absolute URL including HTTP(S) protocol)")
@click.option( @click.option(
"--embedding", "--embedding",
"-e", "-e",
@@ -58,6 +63,7 @@ def common_args(func):
@functools.wraps(func) @functools.wraps(func)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
return func(*args, **kwargs) return func(*args, **kwargs)
return wrapper return wrapper
@@ -117,6 +123,7 @@ def launch(
diffexp_lfc_cutoff, diffexp_lfc_cutoff,
title, title,
scripts, scripts,
about,
experimental_label_file experimental_label_file
): ):
"""Launch the cellxgene data viewer. """Launch the cellxgene data viewer.
@@ -197,6 +204,20 @@ def launch(
if lf_ext and lf_ext != ".csv": if lf_ext and lf_ext != ".csv":
raise click.FileError(basename(experimental_label_file), hint="label file type must be .csv") raise click.FileError(basename(experimental_label_file), hint="label file type must be .csv")
if about:
def url_check(url):
try:
result = urlparse(url)
if all([result.scheme, result.netloc]):
return True
else:
return False
except ValueError:
return False
if not url_check(about):
raise click.ClickException("Must provide an absolute URL for --about. (Example format: http://example.com)")
# Setup app # Setup app
cellxgene_url = f"http://{host}:{port}" cellxgene_url = f"http://{host}:{port}"
@@ -221,7 +242,7 @@ def launch(
from server.app.scanpy_engine.scanpy_engine import ScanpyEngine from server.app.scanpy_engine.scanpy_engine import ScanpyEngine
try: try:
server.attach_data(ScanpyEngine(data_locator, e_args), title=title) server.attach_data(ScanpyEngine(data_locator, e_args), title=title, about=about)
except ScanpyFileError as e: except ScanpyFileError as e:
raise click.ClickException(f"{e}") raise click.ClickException(f"{e}")