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

NickAkhmetov/HMP-180 Cell Types Landing Page #3246

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
149aaa4
Convert theme to TS
NickAkhmetov Aug 30, 2023
285a245
palette.link.main -> palette.common.link
NickAkhmetov Aug 30, 2023
c3a476a
convert JS styled component to TS to test intellisense
NickAkhmetov Aug 30, 2023
ca6de95
palette.halfShadow.main -> palette.common.halfShadow
NickAkhmetov Aug 30, 2023
771faaf
Remove unused `transparentGray` value
NickAkhmetov Aug 30, 2023
1f1220c
add `black` to palette, TODO: update values
NickAkhmetov Aug 30, 2023
4792498
changelog + provenance placeholders
NickAkhmetov Aug 30, 2023
35b34e3
restore hoverShadow, add to common
NickAkhmetov Aug 31, 2023
2b8fce7
finish filling in provenance colors
NickAkhmetov Aug 31, 2023
d6a0a59
Merge branch 'main' into nickakhmetov/hmp-371-mui-palette-cleanup
NickAkhmetov Aug 31, 2023
d2bbc75
rename `secondary.secondary` to `secondary.main`
NickAkhmetov Aug 31, 2023
f9d2ea5
Add story to display full palette
NickAkhmetov Aug 31, 2023
8b05914
don't insert alpha values if they don't already exist
NickAkhmetov Aug 31, 2023
14999de
restore caption colors to palette
NickAkhmetov Aug 31, 2023
3102b06
adjust filter to background color wherever it was being used before
NickAkhmetov Aug 31, 2023
31ef677
Drop high/medium/low emphasis
NickAkhmetov Aug 31, 2023
67621ea
revert to using `filter` for hover styles, credit base of hex2rgb, fi…
NickAkhmetov Sep 5, 2023
e977ce6
NickAkhmetov/Fix TypeScript issues in SWR helpers
NickAkhmetov Sep 5, 2023
ff657ed
Copy over boilerplate from initial implementation
NickAkhmetov Sep 5, 2023
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-hmp-180.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Added first pass implementation of cell types page.
1 change: 1 addition & 0 deletions CHANGELOG-hmp-371.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Update MUI Theme file to use TypeScript.
1 change: 1 addition & 0 deletions CHANGELOG-swr-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fix some TypeScript issues in the SWR helpers.
3 changes: 2 additions & 1 deletion context/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from . import (
routes_main, routes_browse, routes_api, routes_file_based,
routes_auth, routes_cells, routes_markdown, routes_notebooks,
routes_workspaces, default_config)
routes_workspaces, routes_cell_types, default_config)
from .flask_static_digest import FlaskStaticDigest
flask_static_digest = FlaskStaticDigest()

Expand Down Expand Up @@ -71,6 +71,7 @@ def create_app(testing=False):
app.register_blueprint(routes_markdown.blueprint)
app.register_blueprint(routes_notebooks.blueprint)
app.register_blueprint(routes_workspaces.blueprint)
app.register_blueprint(routes_cell_types.blueprint)

app.register_error_handler(400, bad_request)
app.register_error_handler(401, unauthorized)
Expand Down
92 changes: 92 additions & 0 deletions context/app/routes_cell_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from flask import render_template, current_app, jsonify, escape

from hubmap_api_py_client import Client
import requests

from .utils import get_default_flask_data, make_blueprint, get_organs

blueprint = make_blueprint(__name__)


def _get_client(app):
return Client(app.config['XMODALITY_ENDPOINT'] + '/api/')


@blueprint.route('/cell-types')
def cell_types_ui():
organs = get_organs()
return render_template(
'base-pages/react-content.html',
title='Cell Types',
flask_data={**get_default_flask_data(), 'organs': organs}
)


# Fetches list of all cell types
@blueprint.route('/cell-types/list.json')
def cell_types_list():
celltype_token_post = requests.post(
'https://cells.api.hubmapconsortium.org/api/celltype/',
{}).json()
celltype_token = celltype_token_post['results'][0]['query_handle']
celltype_list = requests.post('https://cells.api.hubmapconsortium.org/api/celltypeevaluation/', {
'key': celltype_token,
'set_type': 'cell_type',
'limit': 500}).json()
print(celltype_list)
celltype_list = celltype_list['results']
return jsonify(celltype_list)


# Fetches cell type description
@blueprint.route('/cell-types/<cell_type>/description.json')
def cell_type_description(cell_type):
headers = {"accept": 'application/json'}
celltype_concepts = requests.get(
f'https://ontology.api.hubmapconsortium.org/terms/%s/concepts' % escape(cell_type),
headers=headers).json()
if (len(celltype_concepts) == 0):
return jsonify('No description available')
concept = celltype_concepts[0]
description = requests.get(
f'https://ontology.api.hubmapconsortium.org/concepts/%s/definitions' % escape(concept),
headers=headers).json()
if (len(description) == 0):
return jsonify('No description available')
return jsonify(description[0]['definition'])


# Fetches dataset UUIDs for a given cell type
@blueprint.route('/cell-types/<cell_type>/datasets.json')
def cell_type_datasets(cell_type):
client = _get_client(current_app)
datasets = client.select_datasets(where='celltype', has=[cell_type]).get_list()
datasets = [dataset['uuid'] for dataset in list(datasets)]

return jsonify(datasets)


# Fetches list of organs for a given cell type
@blueprint.route('/cell-types/<cell_type>/organs.json')
def cell_type_organs(cell_type):
client = _get_client(current_app)
organs = client.select_organs(where='celltype', has=[cell_type]).get_list()
organs = [organ['grouping_name'] for organ in list(organs)]
organs = ', '.join([organ for organ in list(organs)])

return jsonify(organs)


# Fetches list of assays for a given cell type
@blueprint.route('/cell-types/<cell_type>/assays.json')
def cell_type_assays(cell_type):
# TODO: Uncomment when assays are available
# client = _get_client(current_app)
# assays = [assay['grouping_name']
# for assay
# in client.select_assays(where='celltype', has=[cell_type]).get_list()]
# assays = ','.join([dataset for dataset in list(assays)])

assays = ', '.join(['TBD'])

return jsonify(assays)
2 changes: 1 addition & 1 deletion context/app/static/js/components/Markdown/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const StyledPaper = styled(Paper)`
padding: 30px 40px 30px 40px;

a {
color: ${(props) => props.theme.palette.link.main};
color: ${(props) => props.theme.palette.common.link};
}

img {
Expand Down
9 changes: 9 additions & 0 deletions context/app/static/js/components/Routes/Routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const DatasetSearch = lazy(() => import('js/pages/entity-search/DatasetSearch'))
const Workspaces = lazy(() => import('js/pages/Workspaces'));
const WorkspacePleaseWait = lazy(() => import('js/pages/WorkspacePleaseWait'));
const Genes = lazy(() => import('js/pages/Genes'));
const CellTypes = lazy(() => import('js/pages/CellTypes'));

function Routes({ flaskData }) {
const {
Expand Down Expand Up @@ -280,6 +281,14 @@ function Routes({ flaskData }) {
);
}

if (urlPath.startsWith('/cell-types')) {
return (
<Route>
<CellTypes />
</Route>
);
}

if ('markdown' in flaskData) {
return (
<Route>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Placeholder for the cells type detail page in the future
export default {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import Box from '@mui/material/Box';
import { useCellTypesList } from '../hooks';

function Header() {
const cellList = useCellTypesList();
console.warn(cellList);
return <Box />;
}

export default Header;
14 changes: 14 additions & 0 deletions context/app/static/js/components/detailPage/cellTypes/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import useSWR from 'swr';

import { fetcher } from 'js/helpers/swr';

export const useCellTypesList = () => {
const { data, error } = useSWR(`/cell-types/list.json`, (url) => fetcher({ url }), {
revalidateOnFocus: false,
});
return {
cellTypes: data,
isLoading: !error && !data,
isError: error,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function DataProducts({ files }: DataProductsProps) {
<StyledInfoIcon color="primary" />
</SecondaryBackgroundTooltip>
</Box>
<FileSize size={totalFileSize} variant="body1" color="secondary.secondary" />
<FileSize size={totalFileSize} variant="body1" color="secondary.main" />
</Box>
{/* <Button variant="contained" color="primary" startIcon={<DownloadIcon />} sx={{ borderRadius: '4px' }}>
Download All
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Box from '@mui/material/Box';
const StyledTableRow = styled(TableRow)(({ theme }) => ({
borderBottom: `1px solid ${theme.palette.divider}`,
'&:hover': {
backgroundColor: theme.palette.hoverShadow.main,
backgroundColor: theme.palette.common.hoverShadow,
},
cursor: 'pointer',
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Chip from '@mui/material/Chip';
const StyledRow = styled(TableRow)`
border-bottom: 1px solid ${(props) => props.theme.palette.divider};
&:hover {
background-color: ${(props) => props.theme.palette.hoverShadow.main};
background-color: ${(props) => props.theme.palette.common.hoverShadow};
}
`;

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { styled } from '@mui/material/styles';

const LinkButton = styled('a')(({ theme }) => ({
color: theme.palette.white.main,
borderRadius: theme.spacing(0.5),
backgroundColor: theme.palette.primary.main,
...theme.typography.subtitle2,
marginTop: theme.spacing(1),
width: '175px',
boxSizing: 'content-box',
padding: theme.spacing(1, 4),
textAlign: 'center',
'&:hover': {
boxShadow: theme.shadows[8],
filter: theme.palette.primary.hover,
},
}));

export { LinkButton };
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const Flex = styled.div`
display: flex;
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
&:hover {
background-color: ${(props) => props.theme.palette.hoverShadow.main};
background-color: ${(props) => props.theme.palette.common.hoverShadow};
}
`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const StyledTableRow = styled(TableRow)`
padding-left: ${sidePadding};
padding-right: ${sidePadding};
& p {
color: ${(props) => props.theme.palette.halfShadow.main};
color: ${(props) => props.theme.palette.common.halfShadow};
margin: 0px;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const StyledCancelIcon = styled(CancelIcon)`

const SelectedFilterDiv = styled.div`
background-color: white;
border: 1px solid ${(props) => props.theme.palette.hoverShadow.main};
border: 1px solid ${(props) => props.theme.palette.common.hoverShadow};
border-radius: 8px;
margin: 8px 8px 0 0;
display: flex;
Expand Down
18 changes: 18 additions & 0 deletions context/app/static/js/helpers/swr/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
type SWRErrorInfo = {
info: object;
status: number;
};

class SWRError extends Error {
public info: object;

public status: number;

constructor(message: string, { info, status }: SWRErrorInfo) {
super(message);
this.info = info;
this.status = status;
}
}

export { SWRError };
30 changes: 20 additions & 10 deletions context/app/static/js/helpers/swr/fetchers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
type fetchOptionsType = {
urls: string;
import { SWRError } from './errors';

type FetchOptionsType = {
requestInit?: RequestInit;
expectedStatusCodes?: number[];
};

type SingleFetchOptionsType = FetchOptionsType & {
url: string;
};

type MultiFetchOptionsType = FetchOptionsType & {
urls: string[];
};

/**
* SWR fetcher which accepts an array of URLs and returns the responses as JSON
* A custom requestInit object can be passed to fetch as well.
Expand All @@ -17,13 +26,14 @@ type fetchOptionsType = {
* @param fetchOptions.expectedStatusCodes - Optional array of status codes which reflect a succesful request
*/

export async function multiFetcher<T>({ urls, requestInit = {}, expectedStatusCodes = [200] }: fetchOptionsType) {
export async function multiFetcher<T>({ urls, requestInit = {}, expectedStatusCodes = [200] }: MultiFetchOptionsType) {
const f = (url: string) =>
fetch(url, requestInit).then((response) => {
fetch(url, requestInit).then(async (response) => {
if (!expectedStatusCodes.includes(response.status)) {
const error = new Error(`The request to ${urls.join(', ')} failed.`);
error.info = response.json();
error.status = response.status;
const error = new SWRError(`The request to ${urls.join(', ')} failed.`, {
info: await response.json(),
status: response.status,
});
throw error;
}
return response.json();
Expand All @@ -39,14 +49,14 @@ export async function multiFetcher<T>({ urls, requestInit = {}, expectedStatusCo
* SWR fetcher which accepts a single URL and returns the response as JSON.
* A custom requestInit object can be passed to fetch as well.
* @example // without requestInit
* const { data } = useSWR({ urls, multiFetcher });
* const { data } = useSWR({ urls, fetcher });
* @example // with requestInit
* const { data } = useSWR({ urls, token }, ({ urls, token }) => multiFetcher({ urls, { headers: { Authorization: `Bearer ${token}` } } })
* @param fetchOptions
* @param fetchOptions.urls - Array of URLs to fetch
* @param fetchOptions.url - URL to fetch
* @param fetchOptions.requestInit - Optional RequestInit object to pass to fetch
* @param fetchOptions.expectedStatusCodes - Optional array of status codes which reflect a succesful request
*/
export async function fetcher<T>({ url, requestInit = {}, expectedStatusCodes = [200] }: fetchOptionsType) {
export async function fetcher<T>({ url, requestInit = {}, expectedStatusCodes = [200] }: SingleFetchOptionsType) {
return multiFetcher({ urls: [url], requestInit, expectedStatusCodes }).then((data) => data[0]) as Promise<T>;
}
13 changes: 13 additions & 0 deletions context/app/static/js/pages/CellTypes/CellTypes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Stack } from '@mui/material';
import Header from 'js/components/detailPage/cellTypes/LandingPage/Header';
import React from 'react';

function CellTypes() {
return (
<Stack spacing={3} direction="column">
<Header />
</Stack>
);
}

export default CellTypes;
3 changes: 3 additions & 0 deletions context/app/static/js/pages/CellTypes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import CellTypes from './CellTypes';

export default CellTypes;
2 changes: 1 addition & 1 deletion context/app/static/js/shared-styles/panels/Panel/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const PanelBox = styled.div`
flex-direction: column;

&:hover {
background-color: ${(props) => props.theme.palette.hoverShadow.main};
background-color: ${(props) => props.theme.palette.common.hoverShadow};
}
@media (min-width: ${(props) => props.theme.breakpoints.values.md}px) {
flex-direction: row;
Expand Down
2 changes: 1 addition & 1 deletion context/app/static/js/shared-styles/panels/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const PanelWrapper = styled.div`
flex-direction: column;

&:hover {
background-color: ${(props) => props.theme.palette.hoverShadow.main};
background-color: ${(props) => props.theme.palette.common.hoverShadow};
}
@media (min-width: ${(props) => props.theme.breakpoints.values.md}px) {
flex-direction: row;
Expand Down
3 changes: 3 additions & 0 deletions context/app/static/js/theme/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Theme from './theme';

export default Theme;
Loading
Loading