mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-05 07:18:12 +08:00
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:
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ export const configDefaults = {
|
|||||||
displayNames: {},
|
displayNames: {},
|
||||||
parameters: {
|
parameters: {
|
||||||
"max-category-items": 1000
|
"max-category-items": 1000
|
||||||
}
|
},
|
||||||
|
links: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* colors */
|
/* colors */
|
||||||
|
|||||||
+2
-2
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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}")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user