diff --git a/applications/common/deploy/values-prod.yaml b/applications/common/deploy/values-prod.yaml
index 38769bc9..2eeb259c 100644
--- a/applications/common/deploy/values-prod.yaml
+++ b/applications/common/deploy/values-prod.yaml
@@ -1,4 +1,5 @@
harness:
env:
- name: SENTRY_DSN
- value: https://57b1ca8a70634782bd6c90dd119eeab5@sentry.metacell.us/9
+ value: https://57b1ca8a70634782bd6c90dd119eeab5@o4506739169951744.ingest.sentry.io/4506758817382404
+
diff --git a/applications/pgadmin/deploy/charts/values.yaml b/applications/pgadmin/deploy/charts/values.yaml
index f9389e4d..c41b89ce 100644
--- a/applications/pgadmin/deploy/charts/values.yaml
+++ b/applications/pgadmin/deploy/charts/values.yaml
@@ -18,9 +18,11 @@ pgadmin:
## pgadmin config, Any custom environment variabls. Settings in config.py can be overriden with an environment variable using the prefix: PGADMIN_CONFIG_
## eg turn off enhanced cookie protection for default AKS, loadbalancer installation
- #config:
- # - name: "PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION"
- # value: "False"
+ config:
+ - name: "PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION"
+ value: "False"
+ - name: "PGADMIN_CONFIG_WTF_CSRF_CHECK_DEFAULT"
+ value: "False"
## Enable persistence using Persistent Volume Claims
## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/
diff --git a/applications/portal/Dockerfile b/applications/portal/Dockerfile
index a6f50cec..b1efde6f 100644
--- a/applications/portal/Dockerfile
+++ b/applications/portal/Dockerfile
@@ -1,7 +1,6 @@
-ARG CLOUDHARNESS_FRONTEND_BUILD
ARG CLOUDHARNESS_DJANGO
-FROM $CLOUDHARNESS_FRONTEND_BUILD as frontend
+FROM node:20 as frontend
ENV APP_DIR=/app
@@ -17,6 +16,10 @@ RUN yarn build
FROM $CLOUDHARNESS_DJANGO
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ postgresql-client \
+ && rm -rf /var/lib/apt/lists/*
+
WORKDIR ${APP_DIR}
RUN mkdir -p ${APP_DIR}/static/www
diff --git a/applications/portal/README.md b/applications/portal/README.md
index 6fffafcf..a2c808d7 100644
--- a/applications/portal/README.md
+++ b/applications/portal/README.md
@@ -31,7 +31,7 @@ See [here](backend/README.md#Develop)
### Frontend
-Backend code is inside the *frontend* directory.
+Frontend code is inside the *frontend* directory.
Frontend is by default generated as a React web application, but no constraint about this specific technology.
@@ -111,7 +111,7 @@ The `KUBERNETES_SERVICE_HOST` switch will activate the creation of the keycloak
The base command to run tests is `python manage.py test`.
-To run tests locally eed to add configure environmental variables to get the
+To run tests locally you need to add configure environmental variables to get the
correct configuration and a working configure instance of the postgres database running.
If you already have a deployment with the database, first forward it:
diff --git a/applications/portal/api/openapi.yaml b/applications/portal/api/openapi.yaml
index b45bf33e..21e9ef0f 100644
--- a/applications/portal/api/openapi.yaml
+++ b/applications/portal/api/openapi.yaml
@@ -55,6 +55,16 @@ paths:
format: date-time
type: string
in: query
+ -
+ examples:
+ query parameter added as - status=rejected:
+ value: /api/antibodies?page=1&size=100&status=rejected
+ name: status
+ description: 'Add a status to filter the query - CURATED, REJECTED, QUEUE. '
+ schema:
+ type: string
+ in: query
+ required: false
responses:
'200':
content:
@@ -369,6 +379,16 @@ paths:
type: integer
in: path
required: true
+ /antibodies/export:
+ summary: Exports all antobodies in csv format
+ get:
+ tags:
+ - antibody
+ responses:
+ '200':
+ content:
+ text/csv: {}
+ description: All antibodies in csv format
components:
schemas:
FilterRequest:
@@ -739,3 +759,19 @@ components:
bearerFormat: JWT
type: http
x-bearerInfoFunc: cloudharness.auth.decode_token
+tags:
+ -
+ name: antibody
+ description: ''
+ -
+ name: search
+ description: ''
+ -
+ name: api
+ description: ''
+ -
+ name: test
+ description: ''
+ -
+ name: ingest
+ description: ''
diff --git a/applications/portal/backend/README.md b/applications/portal/backend/README.md
index bfa7869c..66ef0ba4 100644
--- a/applications/portal/backend/README.md
+++ b/applications/portal/backend/README.md
@@ -10,6 +10,17 @@ This example uses the [Django](https://www.djangoproject.com/) library on top of
## Requirements
Python >= 3
+Cloudharness libraries (check also dockerfile chain)
+
+```
+pip install -e cloud-harness/libraries/models
+pip install -e cloud-harness/libraries/cloudharness-common
+pip install -r cloud-harness/infrastructure/common-images/cloudharness-fastapi/libraries/fastapi/requirements.txt
+pip install -e cloud-harness/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django
+pip install -r applications/portal/backend/requirements.txt
+pip install -e applications/portal/backend
+```
+
## Local backend development
```
# store the accounts api admin password on the local disk
diff --git a/applications/portal/backend/api/admin.py b/applications/portal/backend/api/admin.py
index 7df09fe6..d5107354 100644
--- a/applications/portal/backend/api/admin.py
+++ b/applications/portal/backend/api/admin.py
@@ -12,7 +12,7 @@
from django.utils.encoding import smart_str
from django.utils.html import escape, format_html, format_html_join, mark_safe
from django.utils.text import format_lazy
-from import_export.admin import ImportMixin
+from import_export.admin import ImportExportModelAdmin
from keycloak.exceptions import KeycloakGetError
from cloudharness_django.models import Member
@@ -100,7 +100,7 @@ class AntibodyFilesAdmin(admin.TabularInline):
extra = 1
@admin.register(Antibody)
-class AntibodyAdmin(ImportMixin, admin.ModelAdmin):
+class AntibodyAdmin(ImportExportModelAdmin):
change_form_template = "admin/antibody_change_form.html"
diff --git a/applications/portal/backend/api/controllers/antibody_controller.py b/applications/portal/backend/api/controllers/antibody_controller.py
index 32134e78..457c02b1 100644
--- a/applications/portal/backend/api/controllers/antibody_controller.py
+++ b/applications/portal/backend/api/controllers/antibody_controller.py
@@ -8,7 +8,7 @@
from fastapi import HTTPException
from fastapi.encoders import jsonable_encoder
-from fastapi.responses import JSONResponse
+from fastapi.responses import JSONResponse, RedirectResponse
from api.services import antibody_service
from api.utilities.exceptions import AntibodyDataException
@@ -17,8 +17,7 @@
from openapi.models import UpdateAntibody as UpdateAntibodyDTO
from openapi.models import Antibody as AntibodyDTO
-
-def get_antibodies(page: int, size: int, updated_from: datetime, updated_to: datetime) -> PaginatedAntibodies:
+def get_antibodies(page: int, size: int, updated_from: datetime, updated_to: datetime, status=str) -> PaginatedAntibodies:
if page is None:
page = 1
if size is None:
@@ -28,7 +27,7 @@ def get_antibodies(page: int, size: int, updated_from: datetime, updated_to: dat
if size < 1:
raise HTTPException(status_code=400, detail="Size must be greater than 0")
try:
- return antibody_service.get_antibodies(int(page), int(size), updated_from, updated_to)
+ return antibody_service.get_antibodies(int(page), int(size), updated_from, updated_to, status)
except ValueError:
raise HTTPException(status_code=400, detail="Page and size must be integers")
@@ -80,3 +79,15 @@ def get_by_accession(accession_number: int) -> AntibodyDTO:
return antibody_service.get_antibody_by_accession(accession_number)
except Antibody.DoesNotExist as e:
raise HTTPException(status_code=404, detail=e.message)
+
+
+def get_antibodies_export():
+ from api.services.export_service import generate_antibodies_csv_file
+ fname = "static/www/antibodies_export.csv"
+
+ # check if file exists and it is created within 24 hours
+ # if not, generate a new file
+ if not os.path.exists(fname) or (datetime.now() - datetime.fromtimestamp(os.path.getmtime(fname))).days > 1:
+ generate_antibodies_csv_file(fname)
+ return RedirectResponse("/" + fname)
+ # return FileResponse(fname, filename="antibodies_export.csv")
\ No newline at end of file
diff --git a/applications/portal/backend/api/migrations/0008_auto_20240215_0459.py b/applications/portal/backend/api/migrations/0008_auto_20240215_0459.py
new file mode 100644
index 00000000..0be1168c
--- /dev/null
+++ b/applications/portal/backend/api/migrations/0008_auto_20240215_0459.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.9 on 2024-02-15 12:59
+
+from django.db import migrations, connection
+from cloudharness import log
+from api.repositories.maintainance import rechunk_catalog_number
+
+def chunk_catalog_number(apps, schema_editor):
+ Antibody = apps.get_model('api', 'Antibody')
+ rechunk_catalog_number(Antibody)
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("api", "0007_alter_antibody_comments"),
+ ]
+ operations = [
+ migrations.RunPython(chunk_catalog_number)
+ ]
diff --git a/applications/portal/backend/api/migrations/0009_auto_20240220_0451.py b/applications/portal/backend/api/migrations/0009_auto_20240220_0451.py
new file mode 100644
index 00000000..aafb6169
--- /dev/null
+++ b/applications/portal/backend/api/migrations/0009_auto_20240220_0451.py
@@ -0,0 +1,72 @@
+# Generated by Django 4.2.10 on 2024-02-20 12:51
+
+import api.models
+import django.contrib.postgres.indexes
+import django.contrib.postgres.search
+from django.db import migrations, models
+import django.db.models.deletion
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("api", "0008_auto_20240215_0459"),
+ ]
+
+ operations = [
+ migrations.RunSQL(
+ sql="""
+ DROP MATERIALIZED VIEW IF EXISTS antibody_search;
+ CREATE MATERIALIZED VIEW antibody_search AS
+ SELECT ix,
+ (
+ setweight(to_tsvector('english'::regconfig, (((
+ COALESCE(ab_name, ''::text) || ' '::text) ||
+ COALESCE(clone_id, ''::text) || ' '::text))
+ ), 'A'::"char") ||
+
+ setweight(to_tsvector('english'::regconfig, (((((((((((((((((((((((((
+ COALESCE(api_vendor.vendor, ''::text) || ' '::text) ||
+ COALESCE(api_specie.name, ''::text) || ' '::text) ||
+ COALESCE(target_subregion, ''::text) || ' '::text) ||
+ COALESCE(clonality, ''::text)) || ' '::text) ||
+ COALESCE(target_modification, ''::text)) || ' '::text) ||
+ COALESCE(epitope, ''::character varying)::text) || ' '::text) ||
+ COALESCE(product_isotype, ''::character varying)::text) || ' '::text) ||
+ COALESCE(ab_target, ''::character varying)::text) || ' '::text) ||
+ COALESCE(ab_target_entrez_gid, ''::character varying)::text) || ' '::text) ||
+ COALESCE(uniprot_id, ''::character varying)::text) || ' '::text) ||
+ COALESCE(product_isotype, ''::character varying)::text) || ' '::text) ||
+ COALESCE(product_conjugate, ''::text) ||
+ COALESCE(product_form, ''::character varying)::text) || ' '::text) ||
+ COALESCE(target_species_raw, ''::character varying)::text) || ' '::text) ||
+ COALESCE(kit_contents, ''::character varying)::text) || ' '::text)), 'C'::"char") ||
+ setweight(to_tsvector('english'::regconfig, (((
+ COALESCE(comments, ''::text) || ' '::text) ||
+ COALESCE(curator_comment, ''::text) || ' '::text))
+ ), 'D'::"char")
+
+ ) AS search_vector,
+ defining_citation,
+ disc_date,
+ status
+ FROM api_antibody
+ LEFT JOIN api_vendor ON api_vendor.id = api_antibody.vendor_id
+ LEFT JOIN api_specie ON api_specie.id = api_antibody.source_organism_id
+ """,
+ reverse_sql = '''
+ DROP MATERIALIZED VIEW antibody_search;
+ '''
+ ),
+ migrations.RunSQL(
+ sql="""CREATE UNIQUE INDEX IF NOT EXISTS antibody_search_idx
+ ON antibody_search
+ (ix);
+ """,
+ ),
+ migrations.AddIndex(
+ model_name="antibodysearch",
+ index=django.contrib.postgres.indexes.GinIndex(
+ fields=["search_vector"], name="antibody_search_fts_idx"
+ ),
+ )
+ ]
diff --git a/applications/portal/backend/api/models.py b/applications/portal/backend/api/models.py
index 6b5d1bd0..6dc91f49 100644
--- a/applications/portal/backend/api/models.py
+++ b/applications/portal/backend/api/models.py
@@ -277,8 +277,10 @@ def import_save(self):
if first_save:
super().save()
self._handle_duplicates()
+
if self.catalog_num:
- self.catalog_num_search = catalog_number_chunked(self.catalog_num)
+ self.catalog_num_search = catalog_number_chunked(self.catalog_num, self.cat_alt)
+
if not self.accession or not self.ab_id:
self._generate_automatic_attributes()
super().save()
@@ -296,7 +298,7 @@ def save(self, *args, update_search=True, **kwargs):
super(Antibody, self).save(*args, **kwargs)
if self.catalog_num:
- self.catalog_num_search = catalog_number_chunked(self.catalog_num)
+ self.catalog_num_search = catalog_number_chunked(self.catalog_num, self.cat_alt)
self._handle_duplicates(*args, **kwargs)
self._generate_related_fields(*args, **kwargs)
self._synchronize_target_species(old_species)
diff --git a/applications/portal/backend/api/repositories/maintainance.py b/applications/portal/backend/api/repositories/maintainance.py
index 888d182c..c74bb9c0 100644
--- a/applications/portal/backend/api/repositories/maintainance.py
+++ b/applications/portal/backend/api/repositories/maintainance.py
@@ -1,5 +1,7 @@
from django.db import connection
from cloudharness import log
+from api.utilities.functions import catalog_number_chunked
+
def refresh_search_view():
import threading
@@ -8,4 +10,21 @@ def refresh_search_view_thread():
with connection.cursor() as cursor:
cursor.execute("REFRESH MATERIALIZED VIEW CONCURRENTLY antibody_search;")
connection.commit()
- threading.Thread(target=refresh_search_view_thread).start()
\ No newline at end of file
+ threading.Thread(target=refresh_search_view_thread).start()
+
+
+def rechunk_catalog_number(Antibody_model):
+ i = 0
+ from api.models import STATUS
+ with connection.cursor() as cursor:
+ for antibody in Antibody_model.objects.filter(status=STATUS.CURATED).values('ix', 'catalog_num_search', 'catalog_num', 'cat_alt'):
+ i = i + 1
+ if i % 10000 == 0:
+ print("Migrated", i)
+ new_catalog_number_chunked = catalog_number_chunked(antibody['catalog_num'], antibody['cat_alt'])
+ if new_catalog_number_chunked != antibody['catalog_num_search']:
+
+ try:
+ cursor.execute(f"UPDATE api_antibody SET catalog_num_search = '{new_catalog_number_chunked}' WHERE ix={antibody['ix']};")
+ except Exception as e:
+ log.exception("`%s` `%s` %s`", antibody['catalog_num_search'], antibody['catalog_num'], antibody['cat_alt'])
\ No newline at end of file
diff --git a/applications/portal/backend/api/repositories/search_repository.py b/applications/portal/backend/api/repositories/search_repository.py
index 3b0860c1..49819433 100644
--- a/applications/portal/backend/api/repositories/search_repository.py
+++ b/applications/portal/backend/api/repositories/search_repository.py
@@ -64,7 +64,8 @@ def fts_antibodies(page: int = 0, size: int = settings.LIMIT_NUM_RESULTS, search
# search only allows alphanumeric characters and spaces
if might_be_catalog_number(search):
- cat_search = fts_by_catalog_number(re.sub(r'[^\w\s]', '', search), page, size)
+ cat_search = fts_by_catalog_number(search, page, size)
+
if cat_search:
return cat_search
diff --git a/applications/portal/backend/api/services/antibody_service.py b/applications/portal/backend/api/services/antibody_service.py
index f73b0111..5db037bf 100644
--- a/applications/portal/backend/api/services/antibody_service.py
+++ b/applications/portal/backend/api/services/antibody_service.py
@@ -10,9 +10,9 @@
from api.utilities.exceptions import DuplicatedAntibody, AntibodyDataException
from cloudharness import log
from openapi.models import AddAntibody as AddAntibodyDTO
-from openapi.models import UpdateAntibody as UpdateAntibodyDTO
+from openapi.models import UpdateAntibody as UpdateAntibodyDTO, Status
from openapi.models import Antibody as AntibodyDTO, PaginatedAntibodies
-
+from api.utilities.functions import check_if_status_exists_or_curated
antibody_mapper = AntibodyMapper()
@@ -25,9 +25,9 @@ def search_antibodies_by_catalog(search: str, page: int = 1, size: int = 50,
return PaginatedAntibodies(page=int(page), totalElements=p.count, items=items)
-def get_antibodies(page: int = 1, size: int = 50, date_from: datetime = None, date_to: datetime = None) -> PaginatedAntibodies:
+def get_antibodies(page: int = 1, size: int = 50, date_from: datetime = None, date_to: datetime = None, status:str=None) -> PaginatedAntibodies:
try:
- query = Antibody.objects.filter(status=STATUS.CURATED)
+ query = Antibody.objects.filter(status=check_if_status_exists_or_curated(status))
if date_from:
query = query.filter(lastedit_time__gte=date_from)
if date_to:
@@ -53,9 +53,6 @@ def create_antibody(body: AddAntibodyDTO, userid: str) -> AntibodyDTO:
antibody.uid = userid
antibody.save()
- if antibody.get_duplicate():
- raise DuplicatedAntibody(antibody_mapper.to_dto(antibody))
-
return antibody_mapper.to_dto(antibody)
diff --git a/applications/portal/backend/api/services/export_service.py b/applications/portal/backend/api/services/export_service.py
new file mode 100644
index 00000000..e832e007
--- /dev/null
+++ b/applications/portal/backend/api/services/export_service.py
@@ -0,0 +1,38 @@
+import os
+import subprocess
+import sys
+
+from cloudharness.applications import get_current_configuration
+from cloudharness.utils.config import CloudharnessConfig
+
+
+def generate_antibodies_csv_file(fname, status="CURATED"):
+ app = get_current_configuration()
+ my_env = os.environ
+ os.environ["PGPASSWORD"] = app.harness.database['pass']
+ # execute shell command
+ proc = subprocess.run([
+ "psql", "-h",
+ f"{app.db_name}.{CloudharnessConfig.get_namespace()}",
+ "-U", app.harness.database.user,
+ "-d", app.harness.database.postgres['initialdb'],
+ "-c",
+ f"\\copy ({app['export_query']} AND status='{status}') TO '{fname}' DELIMITER ',' CSV HEADER"],
+ env=my_env,
+ stderr=subprocess.STDOUT,
+ text=True
+ )
+ if proc.returncode != 0:
+ raise Exception("Error during csv export: %s", proc.stdout)
+
+
+if __name__ == '__main__':
+ def test_export(self):
+ from api.services import export_service
+ fname = "/tmp/f.csv"
+ export_service.generate_antibodies_csv_file(fname)
+
+ with open(fname) as f:
+ l = f.readlines()
+ assert l
+ test_export()
\ No newline at end of file
diff --git a/applications/portal/backend/api/tests.py b/applications/portal/backend/api/tests.py
index e0e10ed8..371c092e 100644
--- a/applications/portal/backend/api/tests.py
+++ b/applications/portal/backend/api/tests.py
@@ -14,6 +14,7 @@
from .admin import VendorAdmin
from .models import Vendor, Antibody, VendorDomain, VendorSynonym, Specie
+from api.utilities.functions import catalog_number_chunked
example_ab = {
"clonality": "cocktail",
@@ -136,22 +137,18 @@ def test_create(self):
a.status = STATUS.CURATED
a.save(update_search=False)
- try:
- duplicated = AddAntibodyDTO(**example_ab)
- duplicated.vendorName = "My vendor synonym" # should keep the same vendor and add a synonym
- create_antibody(duplicated, "bbb")
- self.fail("Should detect duplicate antibody")
- except DuplicatedAntibody as e:
- da = e.antibody
- assert da.accession != da.abId
- assert da.abId == ab.abId
- assert da.status == Status.QUEUE
- assert da.vendorId == ab.vendorId
- assert da.vendorName == "My vendorname"
+
+ duplicated = AddAntibodyDTO(**example_ab)
+ # duplicated.vendorName = "My vendor synonym" # should keep the same vendor and add a synonym
+ da = create_antibody(duplicated, "bbb")
+
+ assert da.accession != da.abId
+ assert da.abId == ab.abId
+ assert da.status == Status.QUEUE
+ assert da.vendorId == ab.vendorId
+ assert da.vendorName == "My vendorname"
- synonyms = VendorSynonym.objects.filter(vendor__id=da.vendorId)
- assert len(synonyms) == 1
-
+
assert VendorDomain.objects.all().count() == 2
assert Vendor.objects.all().count() == 1
@@ -173,16 +170,36 @@ def test_create(self):
assert ab.ix
assert ab.showLink is not None
+ ab_by_accession = get_antibody_by_accession(ab.accession)
+ assert ab_by_accession.abId == ab.abId
+ assert ab_by_accession.accession == ab.accession
+ assert ab_by_accession.vendorName == ab.vendorName
+
# Search by catalog number
self.assertEquals(len(fts_antibodies(search="N176A").items), 2)
assert len(fts_antibodies(search="N176A 35").items) == 1
assert len(fts_antibodies(search="N176A|35").items) == 1
+ assert len(fts_antibodies(search="N176A|35").items) == 1
assert len(fts_antibodies(search="N17").items) == 0
assert len(fts_antibodies(search="N17").items) == 0
+
+ a = Antibody.objects.get(ab_id=ab.abId)
+ a.catalog_num='N0304-AB635P-L'
+ a.cat_alt='N0304-AB635P-S'
+ a.save()
+
+ assert len(fts_antibodies(search="N0304-AB635P-L").items) == 1
+ assert len(fts_antibodies(search="N0304AB635PL").items) == 1
+ assert len(fts_antibodies(search="N0304-AB635P-S").items) == 1
+ assert len(fts_antibodies(search="635P-L").items) == 1
+ assert len(fts_antibodies(search="N0304-AB635P-X").items) == 0
+
# Search in plain fields
+ # FIXME full-text search is not working in the tests as cannot account for materialized view update
+
# refresh_search_view()
# import time
# time.sleep(60) # give time to the materialized view to be updated
@@ -208,10 +225,7 @@ def test_create(self):
# assert len(fts_antibodies(search="Rabbit").items) == 1
# assert len(fts_antibodies(search="Andrew Dingwall").items) == 1
- # ab_by_accession = get_antibody_by_accession(ab.accession)
- # assert ab_by_accession.abId == ab.abId
- # assert ab_by_accession.accession == ab.accession
- # assert ab_by_accession.vendorName == ab.vendorName
+
def test_update(self):
user_id = "aaaa"
@@ -246,6 +260,10 @@ def test_update(self):
dao.save()
assert dao.species.count() == 3
+
+
+
+
class VendorAdminTests(TestCase):
def setUp(self):
self.site = AdminSite()
@@ -309,3 +327,5 @@ def test_swap_ownership_antibodies(self):
# self.assertEquals(len(Antibody.objects.all()), 2)
# self.assertEquals(len(Vendor.objects.all()), 2)
# self.assertEquals(len(VendorDomain.objects.all()), 1)
+
+
diff --git a/applications/portal/backend/api/utilities/functions.py b/applications/portal/backend/api/utilities/functions.py
index dd00df49..0687c177 100644
--- a/applications/portal/backend/api/utilities/functions.py
+++ b/applications/portal/backend/api/utilities/functions.py
@@ -5,7 +5,7 @@
from django.db.models import AutoField
from django.forms import URLField
-from portal.settings import ANTIBODY_PERSISTENCE
+from portal.constants import ANTIBODY_PERSISTENCE
magic = 64544
prefix = "AB_"
@@ -34,10 +34,34 @@ def extract_base_url(url: Union[str, URLField]) -> str:
def get_antibody_persistence_directory(ab_id: str, filename: str) -> str:
return f'{ANTIBODY_PERSISTENCE}/{ab_id}/{filename}'
-def catalog_number_chunked(catalog_number: str, fill=' ') -> List[str]:
- if not catalog_number:
+def get_catalog_number_digits(catalog_number: str) -> List[str]:
+ return [c.lower() for c in re.split(r'(\d+)',re.sub(r'[^\w]', '', catalog_number)) if c]
+
+
+def get_catalog_non_alphanumeric(catalog_number: str) -> List[str]:
+ split_by_non_alphanumeric_array = [c.lower() for c in re.split(r'\W+', catalog_number) if c]
+ alphanumeric_catalogs = []
+ for c in split_by_non_alphanumeric_array:
+ alphanumeric_catalogs.extend(get_catalog_number_digits(c))
+ return alphanumeric_catalogs
+
+def catalog_number_chunked(catalog_number: str, catalog_alt_number: str=None, fill=' ') -> List[str]:
+ if not catalog_number and not catalog_alt_number:
return ""
try:
- return fill.join(c for c in re.split(r'(\d+)',re.sub(r'[^\w]', '', catalog_number)) if c).strip().lower()
+ cat_split_by_digits = get_catalog_number_digits(catalog_number)
+ cat_split_by_non_alphanumeric = get_catalog_non_alphanumeric(catalog_number)
+
+ cat_alt_split_by_digits = get_catalog_number_digits(catalog_alt_number) if catalog_alt_number else []
+ cat_alt_split_by_non_alphanumeric = get_catalog_non_alphanumeric(catalog_alt_number) if catalog_alt_number else []
+
+
+ joined_catalog_chunk = fill.join(set(cat_split_by_digits + cat_split_by_non_alphanumeric + cat_alt_split_by_digits + cat_alt_split_by_non_alphanumeric))
+
+ return joined_catalog_chunk.strip().lower()
except Exception as e:
- return ""
\ No newline at end of file
+ return ""
+
+def check_if_status_exists_or_curated(status: str) -> bool:
+ from api.models import STATUS
+ return status.upper() if (status and (status.upper() in STATUS.__members__)) else STATUS.CURATED
diff --git a/applications/portal/backend/main.py b/applications/portal/backend/main.py
index 6edbb9b7..082811c8 100644
--- a/applications/portal/backend/main.py
+++ b/applications/portal/backend/main.py
@@ -1,6 +1,6 @@
# generated by fastapi-codegen:
# filename: openapi.yaml
-# timestamp: 2023-12-13T16:50:55+00:00
+# timestamp: 2024-02-13T20:03:17+00:00
from __future__ import annotations
@@ -87,6 +87,7 @@ def start_auth_service():
start_auth_service()
threading.Thread(target=start_auth_service).start()
+
# start the kafka event listener when running in/for k8s
def start_event_listener():
try:
@@ -133,6 +134,7 @@ def get_antibodies(
size: Optional[int] = None,
updated_from: Optional[datetime] = None,
updated_to: Optional[datetime] = None,
+ status: Optional[str] = None,
) -> PaginatedAntibodies:
"""
List Antibodies
@@ -142,6 +144,7 @@ def get_antibodies(
size=size,
updated_from=updated_from,
updated_to=updated_to,
+ status=status,
)
@@ -157,6 +160,11 @@ def create_antibody(body: AddAntibody) -> None:
)
+@prefix_router.get('/antibodies/export', response_model=None, tags=['antibody'])
+def get_antibodies_export() -> None:
+ return antibody_controller.get_antibodies_export()
+
+
@prefix_router.get(
'/antibodies/search', response_model=PaginatedAntibodies, tags=['search']
)
diff --git a/applications/portal/backend/openapi/models.py b/applications/portal/backend/openapi/models.py
index f26b5c04..a4e438ad 100644
--- a/applications/portal/backend/openapi/models.py
+++ b/applications/portal/backend/openapi/models.py
@@ -1,6 +1,6 @@
# generated by fastapi-codegen:
# filename: openapi.yaml
-# timestamp: 2023-09-21T14:25:42+00:00
+# timestamp: 2024-02-13T20:03:17+00:00
from __future__ import annotations
diff --git a/applications/portal/backend/portal/constants.py b/applications/portal/backend/portal/constants.py
new file mode 100644
index 00000000..23ea3179
--- /dev/null
+++ b/applications/portal/backend/portal/constants.py
@@ -0,0 +1,121 @@
+# Default KC roles
+KC_ADMIN_ROLE = f"portal:administrator" # admin user
+KC_MANAGER_ROLE = f"portal:manager" # manager user
+KC_USER_ROLE = f"user" # customer user
+KC_ALL_ROLES = [
+ KC_ADMIN_ROLE,
+ KC_MANAGER_ROLE,
+ KC_USER_ROLE,
+]
+KC_PRIVILEGED_ROLES = [
+ KC_ADMIN_ROLE,
+ KC_MANAGER_ROLE,
+]
+
+KC_DEFAULT_USER_ROLE = None # don't add the user role to the realm default role
+
+# Database models settings
+IMPORT_EXPORT_USE_TRANSACTIONS = True
+
+# comment refers to max length of column at ingestion time (12/09/2022)
+ANTIBODY_NAME_MAX_LEN = 512 # 352
+ANTIBODY_TARGET_MAX_LEN = 1024 # 783
+ANTIBODY_TARGET_SPECIES_MAX_LEN = 4096 # 2047
+VENDOR_MAX_LEN = 512 # 200
+ANTIBODY_CATALOG_NUMBER_MAX_LEN = 256 # 165
+ANTIBODY_CLONALITY_MAX_LEN = 32
+ANTIBODY_CLONE_ID_MAX_LEN = 256 # 150
+URL_MAX_LEN = 2048 # 938
+ANTIGEN_ENTREZ_ID_MAX_LEN = 2048 # 1383
+ANTIBODY_PRODUCT_ISOTYPE_MAX_LEN = 256 # 150
+ANTIBODY_PRODUCT_CONJUGATE_MAX_LEN = 512 # 285
+ANTIBODY_PRODUCT_FORM_MAX_LEN = 1024 # 802
+ANTIBODY_TARGET_SUBREGION_MAX_LEN = 256 # 221
+ANTIBODY_TARGET_MODIFICATION_MAX_LEN = 128 # 67
+ANTIBODY_DEFINING_CITATION_MAX_LEN = 16384 # 9206
+ANTIBODY_DISC_DATE_MAX_LEN = 128 # 85
+ANTIBODY_ID_MAX_LEN = 32 # 8
+ANTIBODY_UID_MAX_LEN = 256
+STATUS_MAX_LEN = 8
+ANTIBODY_CAT_ALT_MAX_LEN = 512 # 334
+VENDOR_COMMERCIAL_TYPE_MAX_LEN = 32 # 10
+ANTIGEN_UNIPROT_ID_MAX_LEN = 255 # 32
+ANTIBODY_TARGET_EPITOPE_MAX_LEN = 1024 # 897
+VENDOR_NIF_MAX_LEN = 32 # 14
+VENDOR_EU_ID_MAX_LEN = 255
+APPLICATION_MAX_LEN = 255
+ANTIBODY_FILE_DISPLAY_NAME_MAX_LEN = 256
+ANTIBODY_FILES_HASH_MAX_LEN = 32
+ANTIBODY_FILE_TYPE_MAX_LEN = 32
+
+
+ANTIBODY_ANTIBODY_START_SEQ = 3000000 # 2858735
+ANTIBODY_VENDOR_DOMAIN_START_SEQ = 2000 # 813
+ANTIBODY_VENDOR_START_SEQ = 20000 # 12233
+ANTIBODY_FILE_START_SEQ = 10000 # 7001
+
+RAW_ANTIBODY_DATA_BASENAME = 'antibody_table'
+RAW_VENDOR_DATA_BASENAME = 'antibody_vendors'
+RAW_VENDOR_DOMAIN_DATA_BASENAME = 'antibody_vendors_domain'
+RAW_USERS_DATA_BASENAME = 'users_antibody'
+RAW_ANTIBODY_FILES_BASENAME = 'antibody_files'
+
+CHUNK_SIZE = 10 ** 4
+
+UID_KEY = 'uid'
+GUID_KEY = 'guid'
+
+# Used for data import/export processing
+ANTIBODY_HEADER = {'ab_name': "text", 'ab_target': "text", 'target_species': "text", 'vendor': "text",
+ 'vendor_id': "int", 'catalog_num': "text", 'clonality': "text",
+ 'source_organism': "text", 'clone_id': "text", 'url': "text", 'link': "text",
+ 'ab_target_entrez_gid': "text", 'product_isotype': "text",
+ 'product_conjugate': "text", 'product_form': "text", 'target_subregion': "text",
+ 'target_modification': "text", 'comments': "text",
+ 'feedback': "text", 'defining_citation': "text", 'disc_date': "text", 'curator_comment': "text",
+ 'id': "text", 'ab_id': "text", 'ab_id_old': "text",
+ 'of_record': "text", 'ix': "int", UID_KEY: "text", 'status': "text",
+ 'insert_time': "text", 'curate_time': "text", 'cat_alt': "text", 'commercial_type': "text",
+ 'uniprot_id': "text", 'epitope': "text", 'uid_legacy': "text", 'catalog_num_search': "text"}
+
+D_TYPES = ANTIBODY_HEADER.copy()
+for dt in D_TYPES:
+ if dt == 'int':
+ D_TYPES[dt] = 'int64'
+ else:
+ D_TYPES[dt] = 'unicode'
+
+MAX_TRIES = 10
+
+ORCID_URL = "https://orcid.org/"
+PROVIDER_ID = 'orcid'
+
+USERS_RELEVANT_HEADER = ['id', GUID_KEY, 'email', 'level', 'firstName', 'middleInitial', 'lastName', 'organization',
+ 'created', 'orcid_id']
+
+UID_INDEX = list(ANTIBODY_HEADER.keys()).index(UID_KEY)
+GUID_INDEX = USERS_RELEVANT_HEADER.index(GUID_KEY)
+EMAIL_INDEX = USERS_RELEVANT_HEADER.index('email')
+
+DEFAULT_UID = '43'
+
+# Search limit for Antibodies for returning without ranking
+LIMIT_NUM_RESULTS = 250
+
+FOR_NEW_KEY = 'for_new'
+FOR_EXTANT_KEY = 'for_extant'
+METHOD_KEY = 'method'
+IGNORE_KEY = 'ignore'
+INSERT_KEY = 'insert'
+UPDATE_KEY = 'update'
+DUPLICATE_KEY = 'duplicate'
+OVERRIDE_KEY = 'override'
+FILL_KEY = 'fill'
+SKIP_KEY = 'skip'
+ID_KEY = 'id'
+IX_KEY = 'ix'
+KC_USER_ID_KEY = 'keycloak_user'
+USER_KEY = 'user'
+REMOVE_KEYWORD = 'remove'
+
+ANTIBODY_PERSISTENCE = 'antibodies'
\ No newline at end of file
diff --git a/applications/portal/backend/portal/settings.py b/applications/portal/backend/portal/settings.py
index 03c7884e..e760149e 100644
--- a/applications/portal/backend/portal/settings.py
+++ b/applications/portal/backend/portal/settings.py
@@ -153,124 +153,4 @@
# portal specific roles
-# Default KC roles
-KC_ADMIN_ROLE = f"portal:administrator" # admin user
-KC_MANAGER_ROLE = f"portal:manager" # manager user
-KC_USER_ROLE = f"user" # customer user
-KC_ALL_ROLES = [
- KC_ADMIN_ROLE,
- KC_MANAGER_ROLE,
- KC_USER_ROLE,
-]
-KC_PRIVILEGED_ROLES = [
- KC_ADMIN_ROLE,
- KC_MANAGER_ROLE,
-]
-
-KC_DEFAULT_USER_ROLE = None # don't add the user role to the realm default role
-
-# Database models settings
-IMPORT_EXPORT_USE_TRANSACTIONS = True
-
-# comment refers to max length of column at ingestion time (12/09/2022)
-ANTIBODY_NAME_MAX_LEN = 512 # 352
-ANTIBODY_TARGET_MAX_LEN = 1024 # 783
-ANTIBODY_TARGET_SPECIES_MAX_LEN = 4096 # 2047
-VENDOR_MAX_LEN = 512 # 200
-ANTIBODY_CATALOG_NUMBER_MAX_LEN = 256 # 165
-ANTIBODY_CLONALITY_MAX_LEN = 32
-ANTIBODY_CLONE_ID_MAX_LEN = 256 # 150
-URL_MAX_LEN = 2048 # 938
-ANTIGEN_ENTREZ_ID_MAX_LEN = 2048 # 1383
-ANTIBODY_PRODUCT_ISOTYPE_MAX_LEN = 256 # 150
-ANTIBODY_PRODUCT_CONJUGATE_MAX_LEN = 512 # 285
-ANTIBODY_PRODUCT_FORM_MAX_LEN = 1024 # 802
-ANTIBODY_TARGET_SUBREGION_MAX_LEN = 256 # 221
-ANTIBODY_TARGET_MODIFICATION_MAX_LEN = 128 # 67
-ANTIBODY_DEFINING_CITATION_MAX_LEN = 16384 # 9206
-ANTIBODY_DISC_DATE_MAX_LEN = 128 # 85
-ANTIBODY_ID_MAX_LEN = 32 # 8
-ANTIBODY_UID_MAX_LEN = 256
-STATUS_MAX_LEN = 8
-ANTIBODY_CAT_ALT_MAX_LEN = 512 # 334
-VENDOR_COMMERCIAL_TYPE_MAX_LEN = 32 # 10
-ANTIGEN_UNIPROT_ID_MAX_LEN = 255 # 32
-ANTIBODY_TARGET_EPITOPE_MAX_LEN = 1024 # 897
-VENDOR_NIF_MAX_LEN = 32 # 14
-VENDOR_EU_ID_MAX_LEN = 255
-APPLICATION_MAX_LEN = 255
-ANTIBODY_FILE_DISPLAY_NAME_MAX_LEN = 256
-ANTIBODY_FILES_HASH_MAX_LEN = 32
-ANTIBODY_FILE_TYPE_MAX_LEN = 32
-
-
-ANTIBODY_ANTIBODY_START_SEQ = 3000000 # 2858735
-ANTIBODY_VENDOR_DOMAIN_START_SEQ = 2000 # 813
-ANTIBODY_VENDOR_START_SEQ = 20000 # 12233
-ANTIBODY_FILE_START_SEQ = 10000 # 7001
-
-RAW_ANTIBODY_DATA_BASENAME = 'antibody_table'
-RAW_VENDOR_DATA_BASENAME = 'antibody_vendors'
-RAW_VENDOR_DOMAIN_DATA_BASENAME = 'antibody_vendors_domain'
-RAW_USERS_DATA_BASENAME = 'users_antibody'
-RAW_ANTIBODY_FILES_BASENAME = 'antibody_files'
-
-CHUNK_SIZE = 10 ** 4
-
-UID_KEY = 'uid'
-GUID_KEY = 'guid'
-
-# Used for data import/export processing
-ANTIBODY_HEADER = {'ab_name': "text", 'ab_target': "text", 'target_species': "text", 'vendor': "text",
- 'vendor_id': "int", 'catalog_num': "text", 'clonality': "text",
- 'source_organism': "text", 'clone_id': "text", 'url': "text", 'link': "text",
- 'ab_target_entrez_gid': "text", 'product_isotype': "text",
- 'product_conjugate': "text", 'product_form': "text", 'target_subregion': "text",
- 'target_modification': "text", 'comments': "text",
- 'feedback': "text", 'defining_citation': "text", 'disc_date': "text", 'curator_comment': "text",
- 'id': "text", 'ab_id': "text", 'ab_id_old': "text",
- 'of_record': "text", 'ix': "int", UID_KEY: "text", 'status': "text",
- 'insert_time': "text", 'curate_time': "text", 'cat_alt': "text", 'commercial_type': "text",
- 'uniprot_id': "text", 'epitope': "text", 'uid_legacy': "text", 'catalog_num_search': "text"}
-
-D_TYPES = ANTIBODY_HEADER.copy()
-for dt in D_TYPES:
- if dt == 'int':
- D_TYPES[dt] = 'int64'
- else:
- D_TYPES[dt] = 'unicode'
-
-MAX_TRIES = 10
-
-ORCID_URL = "https://orcid.org/"
-PROVIDER_ID = 'orcid'
-
-USERS_RELEVANT_HEADER = ['id', GUID_KEY, 'email', 'level', 'firstName', 'middleInitial', 'lastName', 'organization',
- 'created', 'orcid_id']
-
-UID_INDEX = list(ANTIBODY_HEADER.keys()).index(UID_KEY)
-GUID_INDEX = USERS_RELEVANT_HEADER.index(GUID_KEY)
-EMAIL_INDEX = USERS_RELEVANT_HEADER.index('email')
-
-DEFAULT_UID = '43'
-
-# Search limit for Antibodies for returning without ranking
-LIMIT_NUM_RESULTS = 250
-
-FOR_NEW_KEY = 'for_new'
-FOR_EXTANT_KEY = 'for_extant'
-METHOD_KEY = 'method'
-IGNORE_KEY = 'ignore'
-INSERT_KEY = 'insert'
-UPDATE_KEY = 'update'
-DUPLICATE_KEY = 'duplicate'
-OVERRIDE_KEY = 'override'
-FILL_KEY = 'fill'
-SKIP_KEY = 'skip'
-ID_KEY = 'id'
-IX_KEY = 'ix'
-KC_USER_ID_KEY = 'keycloak_user'
-USER_KEY = 'user'
-REMOVE_KEYWORD = 'remove'
-
-ANTIBODY_PERSISTENCE = 'antibodies'
\ No newline at end of file
+from .constants import *
\ No newline at end of file
diff --git a/applications/portal/backend/requirements-freeze.txt b/applications/portal/backend/requirements-freeze.txt
new file mode 100644
index 00000000..4d8e4ec8
--- /dev/null
+++ b/applications/portal/backend/requirements-freeze.txt
@@ -0,0 +1,136 @@
+anyio==4.2.0
+argcomplete==2.1.2
+argo-workflows==5.0.0
+asgiref==3.7.2
+attrs==23.2.0
+beautifulsoup4==4.12.3
+black==24.1.1
+blinker==1.7.0
+Brotli @ file:///home/conda/feedstock_root/build_artifacts/brotli-split_1648883617327/work
+CacheControl @ file:///home/conda/feedstock_root/build_artifacts/cachecontrol-split_1706895135817/work
+cachetools==5.3.2
+certifi @ file:///home/conda/feedstock_root/build_artifacts/certifi_1707022139797/work/certifi
+cffi==1.16.0
+chardet==5.2.0
+charset-normalizer @ file:///home/conda/feedstock_root/build_artifacts/charset-normalizer_1698833585322/work
+click==8.1.7
+cloudharness @ file:///home/user/ucsd-antibody-registry/cloud-harness/libraries/cloudharness-common
+-e git+https://github.com/MetaCell/cloud-harness.git@c958a9e17dd094c1b4990c177ed68536baabda0a#egg=cloudharness_django&subdirectory=infrastructure/common-images/cloudharness-django/libraries/cloudharness-django
+-e git+https://github.com/MetaCell/cloud-harness.git@c958a9e17dd094c1b4990c177ed68536baabda0a#egg=cloudharness_model&subdirectory=libraries/models
+colorama==0.4.6
+cryptography==42.0.2
+cyclonedx-python-lib @ file:///home/conda/feedstock_root/build_artifacts/cyclonedx-python-lib_1659363848869/work/dist
+datamodel-code-generator==0.11.19
+defusedxml==0.7.1
+deprecation==2.1.0
+diff-match-patch==20230430
+Django==4.2.10
+django-admin-extra-buttons==1.5.7
+django-import-export==3.0.1
+dnspython==2.5.0
+ecdsa==0.18.0
+email-validator==2.1.0.post1
+et-xmlfile==1.1.0
+exceptiongroup==1.2.0
+fastapi==0.109.2
+fastapi-code-generator==0.3.5
+filelock==3.13.1
+Flask==3.0.2
+fonttools==4.38.0
+gdown==5.1.0
+genson==1.2.2
+google-auth==2.27.0
+h11==0.14.0
+html5lib @ file:///home/conda/feedstock_root/build_artifacts/html5lib_1592930327044/work
+httpcore==1.0.2
+httpx==0.26.0
+idna @ file:///home/conda/feedstock_root/build_artifacts/idna_1701026962277/work
+importlib-metadata @ file:///home/conda/feedstock_root/build_artifacts/importlib-metadata_1703269254275/work
+inflect==5.6.2
+isodate==0.6.1
+isort==5.13.2
+itsdangerous==2.1.2
+Jinja2==3.1.3
+jsonschema==3.2.0
+kafka-python==2.0.2
+kubernetes==29.0.0
+markdown-it-py @ file:///home/conda/feedstock_root/build_artifacts/markdown-it-py_1686175045316/work
+MarkupPy==1.14
+MarkupSafe==2.1.5
+mdurl @ file:///home/conda/feedstock_root/build_artifacts/mdurl_1704317613764/work
+msgpack @ file:///opt/conda/conda-bld/msgpack-python_1652362659880/work
+mypy-extensions==1.0.0
+nlopt==2.7.1
+numpy==1.26.4
+oauthlib==3.2.2
+odfpy==1.4.1
+openapi-schema-validator==0.1.6
+openapi-spec-validator==0.3.3
+openpyxl==3.1.2
+oyaml==1.0
+packageurl-python @ file:///home/conda/feedstock_root/build_artifacts/packageurl-python_1704811825047/work
+packaging @ file:///home/conda/feedstock_root/build_artifacts/packaging_1696202382185/work
+pandas==1.4.4
+pathspec==0.12.1
+pillow==10.2.0
+pip-api @ file:///home/conda/feedstock_root/build_artifacts/pip-api_1658274893957/work
+pip-requirements-parser @ file:///home/conda/feedstock_root/build_artifacts/pip-requirements-parser_1672265598496/work
+pip_audit @ file:///home/conda/feedstock_root/build_artifacts/pip-audit_1683232118275/work
+platformdirs==4.2.0
+-e git+ssh://git@github.com/MetaCell/scicrunch-antibody-registry.git@1f4c2ac43ac61b7ddb2cc5b754f9c4f01e5e8120#egg=portal&subdirectory=applications/portal/backend
+prance==0.22.2.22.0
+psutil==5.9.4
+psycopg2-binary==2.9.6
+pyaml==23.12.0
+pyasn1==0.5.1
+pyasn1-modules==0.3.0
+pycparser==2.21
+pydantic==1.10.14
+Pygments @ file:///home/conda/feedstock_root/build_artifacts/pygments_1700607939962/work
+pyhumps==3.8.0
+PyJWT==2.8.0
+PyOpenGL==3.1.6
+pyparsing @ file:///home/conda/feedstock_root/build_artifacts/pyparsing_1690737849915/work
+PyQt5==5.15.7
+PyQt5-Qt5==5.15.2
+PyQt5-sip==12.11.0
+pyrsistent==0.16.1
+PySnooper==0.5.0
+PySocks @ file:///home/conda/feedstock_root/build_artifacts/pysocks_1648857263093/work
+python-dateutil==2.8.2
+python-jose==3.3.0
+python-keycloak==3.7.0
+pytz==2024.1
+PyYAML==6.0.1
+requests @ file:///home/conda/feedstock_root/build_artifacts/requests_1684774241324/work
+requests-oauthlib==1.3.1
+requests-toolbelt==1.0.0
+rich @ file:///home/conda/feedstock_root/build_artifacts/rich-split_1700160075651/work/dist
+rsa==4.9
+ruamel.yaml==0.18.6
+ruamel.yaml.clib==0.2.8
+sentry-sdk==1.40.3
+shellingham==1.5.4
+six @ file:///home/conda/feedstock_root/build_artifacts/six_1620240208055/work
+sniffio==1.3.0
+sortedcontainers @ file:///home/conda/feedstock_root/build_artifacts/sortedcontainers_1621217038088/work
+soupsieve==2.5
+sqlparse==0.4.4
+starlette==0.36.3
+stringcase==1.2.0
+swagger_ui_bundle==1.1.0
+tablib==3.5.0
+toml @ file:///home/conda/feedstock_root/build_artifacts/toml_1604308577558/work
+tomli==2.0.1
+tqdm==4.66.1
+typed-ast==1.5.5
+typer==0.4.2
+typing_extensions @ file:///home/conda/feedstock_root/build_artifacts/typing_extensions_1702176139754/work
+urllib3 @ file:///home/conda/feedstock_root/build_artifacts/urllib3_1706638047111/work
+uvicorn==0.17.6
+webencodings @ file:///home/conda/feedstock_root/build_artifacts/webencodings_1694681268211/work
+websocket-client==1.7.0
+Werkzeug==3.0.1
+xlrd==2.0.1
+xlwt==1.3.0
+zipp @ file:///home/conda/feedstock_root/build_artifacts/zipp_1695255097490/work
diff --git a/applications/portal/backend/requirements.txt b/applications/portal/backend/requirements.txt
index 51dd69c5..c757e3b7 100644
--- a/applications/portal/backend/requirements.txt
+++ b/applications/portal/backend/requirements.txt
@@ -1,4 +1,9 @@
gdown>=4.6.0
pandas==1.4.4
django-import-export==3.0.1
-psycopg2-binary==2.9.6
\ No newline at end of file
+psycopg2-binary==2.9.6
+
+# Security
+starlette>=0.36.2
+pillow >= 10.2.0
+fastapi >= 0.109.1
\ No newline at end of file
diff --git a/applications/portal/backend/static/www/.gitignore b/applications/portal/backend/static/www/.gitignore
new file mode 100644
index 00000000..16f2dc5f
--- /dev/null
+++ b/applications/portal/backend/static/www/.gitignore
@@ -0,0 +1 @@
+*.csv
\ No newline at end of file
diff --git a/applications/portal/deploy/values-dev.yaml b/applications/portal/deploy/values-dev.yaml
index c9d548ad..f9fb14f4 100644
--- a/applications/portal/deploy/values-dev.yaml
+++ b/applications/portal/deploy/values-dev.yaml
@@ -3,3 +3,4 @@ harness:
replicas: 1
database:
size: 20Gi
+
diff --git a/applications/portal/deploy/values.yaml b/applications/portal/deploy/values.yaml
index c20fae1d..577d5afa 100644
--- a/applications/portal/deploy/values.yaml
+++ b/applications/portal/deploy/values.yaml
@@ -6,7 +6,7 @@ harness:
auto: true
deployment:
auto: true
- replicas: 3
+ replicas: 2
port: 8080
livenessProbe:
path: /api/live
@@ -14,7 +14,6 @@ harness:
path: /api/ready
dependencies:
build:
- - cloudharness-frontend-build
- cloudharness-django
hard:
- common
@@ -60,7 +59,6 @@ harness:
cpu: 200m
limits:
memory: 4Gi
- cpu: 8
accounts:
roles:
- administrator
@@ -69,3 +67,5 @@ harness:
test:
e2e:
enabled: true
+
+export_query: "SELECT 'AB_' || ab_id as rrid, ab_name, catalog_num, cat_alt, api_vendor.vendor as vendor_name, api_vendor.vendor || ' Cat# ' || catalog_num || ', RRID:AB_' || ab_id AS proper_citation from api_antibody LEFT JOIN api_vendor on api_vendor.id = vendor_id WHERE true"
\ No newline at end of file
diff --git a/applications/portal/frontend/package.json b/applications/portal/frontend/package.json
index 1e362115..cb1718cc 100644
--- a/applications/portal/frontend/package.json
+++ b/applications/portal/frontend/package.json
@@ -22,8 +22,8 @@
"@mui/material": "^5.10.6",
"@mui/styles": "^5.10.6",
"@mui/x-data-grid": "^5.14.0",
- "axios": "^0.21.4",
- "formik": "^2.2.9",
+ "axios": "^1.6.7",
+ "formik": "^2.4.5",
"keycloak-js": "^19.0.1",
"lodash.debounce": "^4.0.8",
"pluralize": "^8.0.0",
@@ -33,9 +33,9 @@
"react-gtm-module": "^2.0.11",
"react-router": "^5.0.0",
"react-router-dom": "^5.0.0",
- "react-slick": "^0.29.0",
+ "react-slick": "^0.30.0",
"slick-carousel": "^1.8.1",
- "use-clipboard-copy": "^0.1.2",
+ "use-clipboard-copy": "^0.2.0",
"yup": "^0.32.11"
},
"devDependencies": {
@@ -45,12 +45,11 @@
"@babel/preset-react": "^7.8.3",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.0.6",
- "babel-plugin-module-resolver": "^4.0.0",
+ "babel-plugin-module-resolver": "^5.0.0",
"babel-preset-minify": "^0.5.1",
"clean-webpack-plugin": "^3.0.0",
"compression-webpack-plugin": "^7.1.2",
"copy-webpack-plugin": "^6.2.1",
- "css-loader": "^5.2.4",
"dotenv": "^16.0.2",
"eslint": "5.16.0",
"eslint-plugin-jest": "^23.8.2",
@@ -59,17 +58,14 @@
"file-loader": "^5.0.2",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^5.5.0",
- "image-webpack-loader": "^8.1.0",
- "less": "^3.10.3",
- "less-loader": "^6.1.2",
- "less-vars-to-js": "^1.3.0",
- "raw-loader": "^4.0.2",
- "style-loader": "^1.1.3",
"ts-loader": "^9.0.0",
"typescript": "^4.8.3",
"webpack": "^5.61.0",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^4.5.0",
"webpack-merge": "^5.7.0"
+ },
+ "resolutions": {
+ "graceful-fs": "^4.2.11"
}
}
diff --git a/applications/portal/frontend/src/App.tsx b/applications/portal/frontend/src/App.tsx
index 065ade44..2900e319 100644
--- a/applications/portal/frontend/src/App.tsx
+++ b/applications/portal/frontend/src/App.tsx
@@ -1,5 +1,4 @@
import React, { useState } from "react";
-import "./styles/style.less";
import { CssBaseline } from "@mui/material";
import { ThemeProvider } from "@mui/material/styles";
import theme from "./theme/Theme";
diff --git a/applications/portal/frontend/src/assets/partners/Boster_Biological-logo.png b/applications/portal/frontend/src/assets/partners/Boster_Biological-logo.png
new file mode 100644
index 00000000..3697308b
Binary files /dev/null and b/applications/portal/frontend/src/assets/partners/Boster_Biological-logo.png differ
diff --git a/applications/portal/frontend/src/assets/partners/Fujirebio.jpeg b/applications/portal/frontend/src/assets/partners/Fujirebio.jpeg
new file mode 100644
index 00000000..1d820e4c
Binary files /dev/null and b/applications/portal/frontend/src/assets/partners/Fujirebio.jpeg differ
diff --git a/applications/portal/frontend/src/assets/partners/HUABIO.png b/applications/portal/frontend/src/assets/partners/HUABIO.png
new file mode 100644
index 00000000..86ec75d9
Binary files /dev/null and b/applications/portal/frontend/src/assets/partners/HUABIO.png differ
diff --git a/applications/portal/frontend/src/assets/partners/Institute_for_Protein_Innovation.png b/applications/portal/frontend/src/assets/partners/Institute_for_Protein_Innovation.png
new file mode 100644
index 00000000..769af555
Binary files /dev/null and b/applications/portal/frontend/src/assets/partners/Institute_for_Protein_Innovation.png differ
diff --git a/applications/portal/frontend/src/assets/partners/NanoTag.jpg b/applications/portal/frontend/src/assets/partners/NanoTag.jpg
new file mode 100644
index 00000000..fa22284f
Binary files /dev/null and b/applications/portal/frontend/src/assets/partners/NanoTag.jpg differ
diff --git a/applications/portal/frontend/src/assets/partners/antibodies_incorporated.jpg b/applications/portal/frontend/src/assets/partners/antibodies_incorporated.jpg
new file mode 100644
index 00000000..29378b24
Binary files /dev/null and b/applications/portal/frontend/src/assets/partners/antibodies_incorporated.jpg differ
diff --git a/applications/portal/frontend/src/assets/partners/fujifilm-wako-chemicals-usa-corporation-logo-vector.png b/applications/portal/frontend/src/assets/partners/fujifilm-wako-chemicals-usa-corporation-logo-vector.png
new file mode 100644
index 00000000..f815cb0b
Binary files /dev/null and b/applications/portal/frontend/src/assets/partners/fujifilm-wako-chemicals-usa-corporation-logo-vector.png differ
diff --git a/applications/portal/frontend/src/assets/partners/partners logos/Boster_Biological-logo.png b/applications/portal/frontend/src/assets/partners/partners logos/Boster_Biological-logo.png
new file mode 100644
index 00000000..3697308b
Binary files /dev/null and b/applications/portal/frontend/src/assets/partners/partners logos/Boster_Biological-logo.png differ
diff --git a/applications/portal/frontend/src/assets/partners/partners logos/Fujirebio.jpeg b/applications/portal/frontend/src/assets/partners/partners logos/Fujirebio.jpeg
new file mode 100644
index 00000000..1d820e4c
Binary files /dev/null and b/applications/portal/frontend/src/assets/partners/partners logos/Fujirebio.jpeg differ
diff --git a/applications/portal/frontend/src/assets/partners/partners logos/HUABIO.png b/applications/portal/frontend/src/assets/partners/partners logos/HUABIO.png
new file mode 100644
index 00000000..86ec75d9
Binary files /dev/null and b/applications/portal/frontend/src/assets/partners/partners logos/HUABIO.png differ
diff --git a/applications/portal/frontend/src/assets/partners/partners logos/Institute for Protein Innovation.png b/applications/portal/frontend/src/assets/partners/partners logos/Institute for Protein Innovation.png
new file mode 100644
index 00000000..769af555
Binary files /dev/null and b/applications/portal/frontend/src/assets/partners/partners logos/Institute for Protein Innovation.png differ
diff --git a/applications/portal/frontend/src/assets/partners/partners logos/antibodies_incorporated.jpg b/applications/portal/frontend/src/assets/partners/partners logos/antibodies_incorporated.jpg
new file mode 100644
index 00000000..29378b24
Binary files /dev/null and b/applications/portal/frontend/src/assets/partners/partners logos/antibodies_incorporated.jpg differ
diff --git a/applications/portal/frontend/src/assets/partners/partners logos/fujifilm-wako-chemicals-usa-corporation-logo-vector.png b/applications/portal/frontend/src/assets/partners/partners logos/fujifilm-wako-chemicals-usa-corporation-logo-vector.png
new file mode 100644
index 00000000..f815cb0b
Binary files /dev/null and b/applications/portal/frontend/src/assets/partners/partners logos/fujifilm-wako-chemicals-usa-corporation-logo-vector.png differ
diff --git a/applications/portal/frontend/src/assets/partners/partners logos/nanotag_biotechnologies_logo.jpeg b/applications/portal/frontend/src/assets/partners/partners logos/nanotag_biotechnologies_logo.jpeg
new file mode 100644
index 00000000..04b373b8
Binary files /dev/null and b/applications/portal/frontend/src/assets/partners/partners logos/nanotag_biotechnologies_logo.jpeg differ
diff --git a/applications/portal/frontend/src/components/About.tsx b/applications/portal/frontend/src/components/About.tsx
index e515298e..f8e8bb02 100644
--- a/applications/portal/frontend/src/components/About.tsx
+++ b/applications/portal/frontend/src/components/About.tsx
@@ -4,8 +4,7 @@ import { Box, Button, Container, Divider, Grid, Link, Typography } from '@mui/ma
import Slider from "react-slick";
import { vars } from "../theme/variables";
import { useHistory } from 'react-router-dom';
-import "slick-carousel/slick/slick.css";
-import "slick-carousel/slick/slick-theme.css";
+
const { footerBg, whiteColor, sepratorColor, primaryColor, contentBg, contentBorderColor, primaryTextColor, bannerHeadingColor } = vars;
const styles = {
@@ -191,6 +190,13 @@ const About = () => {
{ name: 'Niels Danbolt University of Oslo', url: 'https://www.uio.no/', img: './assets/partners/University_of_Oslo.png' },
{ name: 'NIH', url: 'https://www.nhpreagents.org/', img: './assets/partners/NIH.png' },
{ name: 'ichorbio', url: 'https://ichor.bio/', img: './assets/partners/ichorbio.png' },
+ { name: 'Antibodies Incorporated', url: 'https://www.antibodiesinc.com/', img: './assets/partners/antibodies_incorporated.jpg' },
+ { name: 'HUABIO', url: 'https://www.huabio.com/', img: './assets/partners/HUABIO.png' },
+ { name: 'NanoTag', url: 'https://nano-tag.com/', img: './assets/partners/NanoTag.jpg' },
+ { name: 'Institute for Protein Innovation', url: 'https://proteininnovation.org/', img: './assets/partners/Institute_for_Protein_Innovation.png' },
+ { name: 'Fujirebio', url: 'https://www.fujirebio.com/en', img: './assets/partners/Fujirebio.jpeg' },
+ { name: 'Boster Biological Technology', url: 'https://www.bosterbio.com/', img: './assets/partners/Boster_Biological-logo.png' },
+ { name: 'Fujifilm Wako USA', url: 'https://wakousa.com/', img: './assets/partners/fujifilm-wako-chemicals-usa-corporation-logo-vector.png' }
]
const history = useHistory();
const navigate = () => history.push('/');
@@ -212,7 +218,7 @@ const About = () => {
- We would like to thank our partners, who submit data to us regularly making author's jobs easier. Would you like to become a partner? Inquire here.
+ We would like to thank our partners, who submit data to us regularly making author's jobs easier. Would you like to become a partner? Inquire here.
@@ -287,7 +293,7 @@ const About = () => {
- We never delete records, so even when an antibody disappears from a vendor's catalog, or is sold to another vendor, we can trace the provenance of that antibody. (Bandrowski et al).
+ We never delete records, so even when an antibody disappears from a vendor's catalog, or is sold to another vendor, we can trace the provenance of that antibody. (Bandrowski et al).
diff --git a/applications/portal/frontend/src/components/NavBar/Navbar.tsx b/applications/portal/frontend/src/components/NavBar/Navbar.tsx
index d62c0a58..56a0d192 100644
--- a/applications/portal/frontend/src/components/NavBar/Navbar.tsx
+++ b/applications/portal/frontend/src/components/NavBar/Navbar.tsx
@@ -15,7 +15,7 @@ import NavLinks from "./NavLinks";
import HelpMenu from "./HelpMenu";
import { UserContext, User } from "../../services/UserService";
import UserAccountMenu from "./UserAccountMenu";
-import logo from "../../assets/logo.svg";
+
const Navbar = () => {
const user: User = React.useContext(UserContext)[0];
@@ -45,7 +45,7 @@ const Navbar = () => {
>
-
+
+
+
+