-
Notifications
You must be signed in to change notification settings - Fork 12
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
feat: portal redesign #3485
feat: portal redesign #3485
Changes from 21 commits
ef0a3f1
09a2a49
dbe5e43
563bba5
ae444d6
c11da6d
47eddb9
5c30a76
8821570
abef4eb
c3147c3
55961ef
03095fd
08280ba
8efccdc
e880f72
47b28f9
5efda7c
54a7de9
bf81761
1a79432
88d7459
5c63d70
f796135
1c7e9b6
731f2ce
54f7d06
91a2325
7b30180
8dc837b
d2ac93c
70797fe
9449356
8415bfa
cecc990
645ee71
47bc9cc
370b491
d632c62
98415eb
57e1ef6
7299627
d781aa2
ac05425
d82ece3
b057adc
3ba51e0
e4d95e3
aa01554
242c169
1ae9861
a47af3c
d15263b
f4897b3
2ceae6a
375ce8c
41ba64d
7cfd73e
182f9cd
a06e8a2
9ea51f0
d140005
dac2871
56be5e4
7c6bb31
21073a2
b75dcc2
4cd6e1c
270c7a8
72bc1a4
ba56193
a2debca
c8d7803
1877df4
2ca7c46
f4f22d1
178ff09
27d593c
96ab9d1
adbf9bc
0d1141d
c7c3326
4c01fb5
975d8b2
52a20a4
df5d9c3
081326d
ec6bdb9
9795c21
02f6513
1f9797a
724aaa7
ac80f59
9bb1d90
b76e010
2b5d3bd
9a75632
322754b
a5c9836
69f93d7
03d3c2a
96bdd47
6eeb297
2ace524
62998f0
3ad30cc
b705372
0548da7
837a2ec
369102d
1ba1706
36bfa21
ee506b5
f367d77
89d4940
a7676e2
a52774a
898a710
c2658fa
7b603bc
191dffa
366b014
2670abe
9034dfd
d014f84
02166d4
44575a1
4442758
f2c35fd
51b9212
da8c949
82dbf44
bdf4abe
5d07526
f171f68
8fc192c
bd8997a
a18caa0
6812c6f
4ca6975
4ecc989
5ad9821
b078993
cc8f67c
246a4fc
50203bd
16c78fe
e470d1d
dfd1bdf
9c75a11
dd49a1a
c91f1d6
4ca7937
b372b53
2bfc69c
bc9b7df
d85d043
f7aac2d
fc19054
e4d6afa
f71f85f
8fd48f5
acf1821
fa0f817
22b8384
5bffc30
2a15999
9c0a1e6
8c4ea50
a0b04ca
fef5d22
7f835c1
51b2e39
630aa64
00c4b40
5a73f65
a3ff048
3b9bfb0
f7d3e0d
e46aebc
597fb51
0e93738
a8473d9
6aa0ffa
ef94dd0
a985b44
97ea264
b997403
2e59b6a
7ca3661
8602a32
7bd523a
637c224
33bc4e4
dbb66dd
9d8b9e2
61021bc
8c5a05b
b3135e0
379e292
a4a0910
c9efaab
850be81
9832e57
b06d19f
f106477
049e409
ed194a8
210d6a3
764a4cc
794c305
c1429ac
b3deaca
7a8be4e
24fafb2
34f386d
7d2f98e
48cf1a9
69b29e3
0dd0319
fb7904c
0eb3314
ee9b967
511eaf1
30b6b2c
1314000
2e2fdf8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
from dataclasses import dataclass | ||
from typing import Iterable, Optional | ||
|
||
from backend.layers.common.entities import CollectionMetadata, CollectionVersion, DatasetArtifact, DatasetStatus, DatasetVersion | ||
from backend.layers.persistence.persistence import DatabaseProviderInterface | ||
from backend.layers.thirdparty.crossref_provider import CrossrefProviderInterface | ||
from backend.layers.thirdparty.step_function_provider import StepFunctionProviderInterface | ||
|
||
|
||
@dataclass | ||
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. I asume these datatypes will need to be reference by adjacent layers, should they be in a common location? 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. We can either put them all in the common folder or keep them closer to the layer which is most logically connected to them. I'm ok with both solutions. 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. UserInfo seems like an entity to me, so I'd suggest moving to Does 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. For now, |
||
class CollectionQueryFilter: | ||
visibility: Optional[str] # Use an enum | ||
owner: Optional[str] | ||
# TODO: add list of fields to be returned (if needed) | ||
|
||
@dataclass | ||
class UserInfo: | ||
user_id: str | ||
token: str | ||
|
||
|
||
class BusinessLogicInterface: | ||
|
||
# Get_collections | ||
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. this should be a docstring under get_collections def get_collections():
"""
Replaces get_collections_list and get_collections_index
Accepts a CollectionFilter class (or kwargs) with:
Date bounds
Visibility
Ownership
List of fields to be returned
Accepts an UserInfo object
Returns a list of dictionaries that only includes the selected fields for each collection
It should NOT add any information that is required by the current API for compatibility reasons (e.g. access_write). These will be delegated the upper layer
It should NOT do any operation that is required by Curation API assumptions (e.g. remove None values)
""" 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. reading these comments and how mentions the soon to be deprecaited functions, it reads more like notes for future implementation not actual documentation. Is that how I should be interpretting it? 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. Sorry, this is a copy-paste from the tech spec. Eventually, I'll rewrite it into proper docs. |
||
# Replaces get_collections_list and get_collections_index | ||
# Accepts a CollectionFilter class (or kwargs) with: | ||
# Date bounds | ||
# Visibility | ||
# Ownership | ||
# List of fields to be returned | ||
# Accepts an UserInfo object | ||
# Returns a list of dictionaries that only includes the selected fields for each collection | ||
# It should NOT add any information that is required by the current API for compatibility reasons (e.g. access_write). These will be delegated to the upper layer | ||
# It should NOT do any operation that is required by Curation API assumptions (e.g. remove None values) | ||
|
||
def get_collections(self, filter: CollectionQueryFilter, user_info: UserInfo) -> Iterable[CollectionVersion]: | ||
pass | ||
|
||
|
||
# Get_collection | ||
# Replaces get_collection_details | ||
# Returns a single collection, with no filtering options | ||
# Accepts: | ||
# Collection_id | ||
# UserInfo for validation | ||
# Should reuse most of of the code from the method above | ||
|
||
def get_collection(self, collection_id: str, user_info: UserInfo) -> CollectionVersion: | ||
pass | ||
|
||
def get_collection_version(self, version_id: str, user_info: UserInfo) -> CollectionVersion | ||
pass | ||
|
||
# Create_collection | ||
# Replaces the current create_collection | ||
# Accepts: | ||
# A dictionary (or Class) with the body of the collection to be created | ||
# UserInfo | ||
# Should validate the body accepted as param (see existing verify_collection_body) | ||
# Should call CrossrefProvider to retrieve publisher metadata information | ||
# This method currently collects errors in a list, which will be piped upstream to the API response. This is a good idea but it should be refactor into a generalized pattern (otherwise we’ll “pollute” the business layer with logic specific to the API layer). | ||
|
||
def create_collection(self, collection_metadata: CollectionMetadata, user_info: UserInfo) -> CollectionVersion: | ||
pass | ||
|
||
# Delete_collection | ||
# Replaces the current delete_collection | ||
# Accepts: | ||
# Collection_id | ||
# UserInfo | ||
# Performs authorization on user/collection | ||
|
||
def delete_collection(self, collection_id: str, user_info: UserInfo) -> None: | ||
pass | ||
|
||
# Update_collection | ||
# Replaces the current update_collection | ||
# Accepts: | ||
# Collection_id | ||
# UserInfo | ||
# A dataclass with the body to be updated | ||
# Should validate the body | ||
# Should handle DOI updates (re-use the existing logic with minimal refactors) | ||
# Can either return nothing or the metadata of the updated collection | ||
|
||
# TODO: body should be a dataclass? | ||
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. can't it be 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. Unfortunately no, |
||
def update_collection_version(self, version_id: str, body: dict, user_info: UserInfo) -> None: | ||
pass | ||
|
||
# Create_collection_version | ||
# Replaces the current post_collection_revision | ||
# Accepts: | ||
# Collection_id | ||
# UserInfo | ||
# Performs authorization on the collection | ||
# Since revision logic is database specific, it delegates to the underlying layer | ||
# Returns a handle to the revised collection (either id or the full collection metadata) | ||
|
||
def create_collection_version(self, collection_id: str, user_info: UserInfo) -> CollectionVersion: | ||
pass | ||
|
||
def delete_collection_version(self, version_id: str, user_info: UserInfo) -> None: | ||
pass | ||
|
||
|
||
# Publish_collection | ||
# Replaces post (in publish.py) | ||
# Accepts: | ||
# Collection_id | ||
# UserInfo | ||
# Performs validation to make sure that the collection can be published | ||
# [Currently] accepts data_submission_policy_version: what is this for? | ||
# [Currently] triggers Cloudfront invalidation for the index endpoints. This should arguably NOT be done here but by the API layer | ||
# Since revision logic is database specific, it delegates to the underlying layer | ||
|
||
def publish_collection_version(self, version_id: str, user_info: UserInfo) -> None: | ||
pass | ||
|
||
# Ingest_dataset | ||
# Replaces the existing Upload_from_link | ||
# Potentially, also replaces relink (I am not sure why they are 2 separate functions) | ||
# Accepts: | ||
# Collection_id | ||
# UserInfo | ||
# URL of the uploadable dataset | ||
# [Optional] a dataset_id to be replaced | ||
# This is one of the most complex functions. Other than the database provider, It will need two additional providers: | ||
# StepFunctionProvider (to call the SFN that triggers the upload) | ||
# DropboxProvider to interface with Dropbox (could be more generic: RemoteFileProvider?) | ||
# Should handle exceptions from all providers: | ||
# Should only raise custom exceptions | ||
|
||
def ingest_dataset(self, collection_version_id: str, url: str, existing_dataset_version_id: Optional[str], user_info: UserInfo) -> str: | ||
pass | ||
|
||
# Get_all_datasets | ||
# Replaces get_dataset_index | ||
|
||
def get_all_datasets(self) -> Iterable[DatasetVersion]: | ||
pass | ||
|
||
# Delete_dataset | ||
# Replaces delete_dataset | ||
|
||
def delete_dataset(self, dataset_version_id: str) -> None: | ||
pass | ||
|
||
|
||
# get_dataset_assets | ||
# Replaces get_dataset_assets | ||
|
||
def get_dataset_artifacts(self, dataset_id: str) -> Iterable[DatasetArtifact]: | ||
pass | ||
|
||
|
||
# Download_dataset_asset | ||
# Replaces post_dataset_asset | ||
|
||
|
||
def download_dataset_asset(self, dataset_id: str) -> str: | ||
pass | ||
|
||
# Get_dataset_status | ||
# Replaces get_status | ||
|
||
def get_dataset_status(self, dataset_id: str) -> DatasetStatus: | ||
pass | ||
|
||
|
||
# TODO: move it to a separate file | ||
class BusinessLogic(BusinessLogicInterface): | ||
|
||
database_provider: DatabaseProviderInterface | ||
crossref_provider: CrossrefProviderInterface | ||
step_function_provider: StepFunctionProviderInterface | ||
|
||
def __init__( | ||
self, | ||
database_provider: DatabaseProviderInterface, | ||
crossref_provider: CrossrefProviderInterface, | ||
step_function_provider: StepFunctionProviderInterface, | ||
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. is there anything to initialize in 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. I will get rid of it, thanks. I think @atolopko-czi suggested to use ABC which would make interfaces look neater. |
||
) -> None: | ||
self.crossref_provider = crossref_provider | ||
self.database_provider = database_provider | ||
self.step_function_provider = step_function_provider | ||
super().__init__() | ||
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. why are these class variables? 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. What do you think they should be? 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. These
Should be variables set in the 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. They're set in |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
from dataclasses import dataclass | ||
from datetime import datetime | ||
from typing import List, Optional | ||
from enum import Enum | ||
|
||
class DatasetStatus(Enum): | ||
NA = "N/A" | ||
WAITING = "Waiting" | ||
UPLOADING = "Uploading" | ||
UPLOADED = "Uploaded" | ||
FAILED = "Failed" | ||
CANCEL_PENDING = "Cancel pending" | ||
CANCELED = "Canceled" | ||
|
||
@dataclass | ||
class DatasetArtifact: | ||
type: str | ||
uri: str | ||
|
||
|
||
@dataclass | ||
class DatasetMetadata: | ||
organism: str | ||
tissue: str | ||
assay: str | ||
disease: str | ||
sex: str | ||
self_reported_ethnicity: str | ||
development_stage: str | ||
cell_type: str | ||
cell_count: int | ||
|
||
|
||
@dataclass | ||
class DatasetVersion: | ||
dataset_id: str | ||
version_id: str | ||
processing_status: DatasetStatus | ||
metadata: DatasetMetadata | ||
artifacts: List[DatasetArtifact] | ||
|
||
|
||
@dataclass | ||
class Link: | ||
name: str | ||
type: str | ||
uri: str | ||
|
||
@dataclass | ||
class CollectionMetadata: | ||
name: str | ||
description: str | ||
owner: str | ||
contact_name: str | ||
contact_email: str | ||
links: List[Link] # TODO: use a dataclass | ||
|
||
|
||
@dataclass | ||
class CollectionVersion: | ||
collection_id: str | ||
version_id: str | ||
owner: str | ||
metadata: CollectionMetadata | ||
publisher_metadata: Optional[dict] # TODO: use a dataclass | ||
datasets: List[DatasetVersion] | ||
published_at: Optional[datetime] |
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.
Will everything be nested under layers?
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.
I think for now it's good to keep it there for separation. Eventually we could just remove that folder and free up one level.