diff --git a/DEPLOYING.md b/DEPLOYING.md index 14f1405f..144f7fcf 100644 --- a/DEPLOYING.md +++ b/DEPLOYING.md @@ -45,6 +45,7 @@ The following are the steps to perform a deployment in production. In case you w - `CGDS_CHUNK_SIZE`: size **in bytes** of the chunk in which the files of a CGDS study are downloaded, the bigger it is, the faster the download is, but the more server memory it consumes. Default `2097152`, i.e. 2MB. - `THRESHOLD_ORDINAL`: number of different values for the GEM (CNA) information to be considered ordinal, if the number is <= to this value then it is considered categorical/ordinal and a boxplot is displayed, otherwise, it is considered continuous and the common correlation graph is displayed. Default `5`. - `THRESHOLD_GEM_SIZE_TO_COLLECT`: GEM file size threshold (in MB) for the GEM dataset to be available in memory. This has a HUGE impact on the performance of the analysis. If the size is less than or equal to this threshold, it is allocated in memory, otherwise, it will be read lazily from the disk. If None GGCA automatically allocates in memory when the GEM dataset size is small (<= 100MB). Therefore, if you want to force to always use RAM to improve performance you should set a very high threshold, on the contrary, if you want a minimum memory usage at the cost of poor performance, set it to `0`. Default `None`. + - `MIN_PASSWORD_LEN`: Defines the minimum required length for user passwords when updating their profile. If the provided password is shorter than this length, the update will be rejected. Default `8`. - PostgreSQL: - `POSTGRES_USERNAME`: PostgreSQL connection username. **Must be equal to** `POSTGRES_USER`. - `POSTGRES_PASSWORD`: PostgreSQL connection password. **Must be equal to** `POSTGRES_PASSWORD`. @@ -179,7 +180,7 @@ To integrate with [Modulector][modulector] and/or [BioAPI][bioapi] using `docker name: 'multiomix-network' ``` 3. The new versions of BioAPI and Modulector already come with service names suitable for integration with Multiomix. But **if you have any old version of those platforms**, change the Modulector and BioAPI configuration so that it does not conflict with the Multiomix configuration: - 1. Rename all the services in the Modulector and BioAPI `docker-compose.yml` files with the suffix `_modulector` and `_bioapi`. And rename `web` service to `modulector` or `bioapi` respectively. **NOTE:** do not forget to rename the `depends_on` parameters, and the database connection parameters to point to the new services names. + 1. Rename all the services in the Modulector and BioAPI `docker-compose.yml` files with the suffix `_modulector` and `_bioapi`. For example `mongo_bioapi`, `web_bioapi` and `nginx_bioapi` in the case of BioAPI. **NOTE:** do not forget to rename the `depends_on` parameters, and the database connection parameters to point to the new services names. 2. Change the following block in the NGINX configuration files. In Modulector it's `config/nginx/conf.d/modulector.conf`, in BioAPI it's `/nginx/conf.d/default.conf`: ``` # Old @@ -191,7 +192,7 @@ To integrate with [Modulector][modulector] and/or [BioAPI][bioapi] using `docker # New upstream web { ip_hash; - server modulector:8000; # Or bioapi, dependening on which config file you're + server web_modulector:8000; # Or web_bioapi, dependening on which config file you're editing } ``` 4. Set Multiomix parameters: diff --git a/src/api_service/migrations/0061_alter_experiment_shared_users.py b/src/api_service/migrations/0061_alter_experiment_shared_users.py new file mode 100644 index 00000000..b7cfa5af --- /dev/null +++ b/src/api_service/migrations/0061_alter_experiment_shared_users.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.15 on 2024-10-23 13:51 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('api_service', '0060_experiment_shared_users'), + ] + + operations = [ + migrations.AlterField( + model_name='experiment', + name='shared_users', + field=models.ManyToManyField(blank=True, related_name='shared_users_correlation_analysis', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/src/api_service/mrna_service.py b/src/api_service/mrna_service.py index 16a59f3d..d431f53e 100644 --- a/src/api_service/mrna_service.py +++ b/src/api_service/mrna_service.py @@ -13,10 +13,28 @@ class MRNAService(object): def __init__(self): modulector_settings = settings.MODULECTOR_SETTINGS - self.url_modulector_prefix = f"http://{modulector_settings['host']}:{modulector_settings['port']}" + self.url_modulector_prefix = self.__build_url(modulector_settings) bioapi_settings = settings.BIOAPI_SETTINGS - self.url_bioapi_prefix = f"http://{bioapi_settings['host']}:{bioapi_settings['port']}" + self.url_bioapi_prefix = self.__build_url(bioapi_settings) + + @staticmethod + def __build_url(settings: Dict[str, Any]) -> str: + """ + Constructs the URL based on the settings provided. + If the port is the default for the protocol (80 for http, 443 for https), it is omitted. + Otherwise, the port is included in the URL. + @param settings: Dictionary containing protocol, host, and port information. + @return: Constructed URL as a string. + """ + protocol = settings['protocol'] + host = settings['host'] + port = settings['port'] + + if (protocol == 'http' and port == 80) or (protocol == 'https' and port == 443): + return f"{protocol}://{host}" + else: + return f"{protocol}://{host}:{port}" @staticmethod def __generate_rest_query_params(get_request: QueryDict) -> str: diff --git a/src/api_service/websocket_functions.py b/src/api_service/websocket_functions.py index fec39e9f..237e34b9 100644 --- a/src/api_service/websocket_functions.py +++ b/src/api_service/websocket_functions.py @@ -106,3 +106,25 @@ def send_update_cluster_label_set_command(user_id: int): 'command': 'update_cluster_labels_sets' } send_message(user_group_name, message) + +def send_update_institutions_command(user_id: int): + """ + Sends a message indicating that a Institution state update has occurred + @param user_id: Institution's user's id to send the WS message + """ + user_group_name = f'notifications_{user_id}' + message = { + 'command': 'update_institutions' + } + send_message(user_group_name, message) + +def send_update_user_for_institution_command(user_id: int): + """ + Sends a message indicating that a Institution_user state update has occurred + @param user_id: Institution's user's id to send the WS message + """ + user_group_name = f'notifications_{user_id}' + message = { + 'command': 'update_user_for_institution' + } + send_message(user_group_name, message) \ No newline at end of file diff --git a/src/biomarkers/migrations/0019_biomarker_is_public.py b/src/biomarkers/migrations/0019_biomarker_is_public.py new file mode 100644 index 00000000..3b43a55e --- /dev/null +++ b/src/biomarkers/migrations/0019_biomarker_is_public.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.15 on 2024-11-21 18:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('biomarkers', '0018_alter_biomarker_state'), + ] + + operations = [ + migrations.AddField( + model_name='biomarker', + name='is_public', + field=models.BooleanField(default=False), + ), + ] diff --git a/src/biomarkers/migrations/0020_biomarker_shared_institutions.py b/src/biomarkers/migrations/0020_biomarker_shared_institutions.py new file mode 100644 index 00000000..856bc683 --- /dev/null +++ b/src/biomarkers/migrations/0020_biomarker_shared_institutions.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.15 on 2024-11-21 18:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('institutions', '0004_auto_20220923_2322'), + ('biomarkers', '0019_biomarker_is_public'), + ] + + operations = [ + migrations.AddField( + model_name='biomarker', + name='shared_institutions', + field=models.ManyToManyField(blank=True, related_name='biomarkers', to='institutions.institution'), + ), + ] diff --git a/src/biomarkers/models.py b/src/biomarkers/models.py index 14857468..33a5df4e 100644 --- a/src/biomarkers/models.py +++ b/src/biomarkers/models.py @@ -3,6 +3,8 @@ from django.db import models from django.db.models import QuerySet from queryset_sequence import QuerySetSequence + +from institutions.models import Institution from tags.models import Tag from api_service.websocket_functions import send_update_biomarkers_command from user_files.models_choices import MoleculeType @@ -57,10 +59,11 @@ class Biomarker(models.Model): statistical_validations: QuerySet['statistical_properties.StatisticalValidation'] inference_experiments: QuerySet['inferences.InferenceExperiment'] methylations: QuerySet['MethylationIdentifier'] + trained_models: QuerySet['trained_models.TrainedModel'] cnas: QuerySet['CNAIdentifier'] mirnas: QuerySet['MiRNAIdentifier'] mrnas: QuerySet['MRNAIdentifier'] - + is_public = models.BooleanField(blank=False, null=False, default=False) name: str = models.CharField(max_length=300) description: Optional[str] = models.TextField(null=True, blank=True) tag: Optional[Tag] = models.ForeignKey(Tag, on_delete=models.SET_NULL, default=None, blank=True, null=True) @@ -68,6 +71,7 @@ class Biomarker(models.Model): origin: int = models.IntegerField(choices=BiomarkerOrigin.choices) state: int = models.IntegerField(choices=BiomarkerState.choices) user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) + shared_institutions = models.ManyToManyField(Institution, related_name='biomarkers', blank=True) def __str__(self) -> str: return self.name diff --git a/src/biomarkers/serializers.py b/src/biomarkers/serializers.py index a66be68b..d0f8fc7c 100644 --- a/src/biomarkers/serializers.py +++ b/src/biomarkers/serializers.py @@ -1,4 +1,5 @@ from rest_framework import serializers +from genes.serializers import GeneGEMWithType from user_files.models_choices import MoleculeType from .models import Biomarker, MRNAIdentifier, MethylationIdentifier, CNAIdentifier, MiRNAIdentifier, MoleculeIdentifier from tags.serializers import TagSerializer @@ -164,5 +165,14 @@ def get_was_already_used(ins: Biomarker) -> bool: This avoids the user to edit a Biomarker that was already used and generate inconsistencies. """ return ins.was_already_used + + +class BiomarkerFromCorrelationAnalysisSerializer(serializers.Serializer): + """ + Serializer for BiomarkerFromCorrelationAnalysis, including correlation analysis ID, gene GEM list, and correlation threshold. + """ + correlation_analysis_id = serializers.IntegerField() + gene_gem_list = GeneGEMWithType(many=True, required=False) + correlation_threshold = serializers.IntegerField(required=False) diff --git a/src/biomarkers/urls.py b/src/biomarkers/urls.py index c13293d6..06ee0197 100644 --- a/src/biomarkers/urls.py +++ b/src/biomarkers/urls.py @@ -18,4 +18,5 @@ path('methylation-sites', views.MethylationSites.as_view(), name='methylation_sites'), path('methylation-sites-finder', views.MethylationSites.as_view(), name='methylation_sites_finder'), path('biomarker-molecules', views.BiomarkerMolecules.as_view(), name='biomarker_molecules'), + path('biomarker-correlation-api', views.BiomarkerCorrelationAPIView.as_view(), name='biomarker_correlation_api'), ] diff --git a/src/biomarkers/views.py b/src/biomarkers/views.py index 5e09b62d..c78b8ef4 100644 --- a/src/biomarkers/views.py +++ b/src/biomarkers/views.py @@ -10,21 +10,26 @@ from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView +from api_service.models import Experiment from api_service.mrna_service import global_mrna_service from biomarkers.models import Biomarker, BiomarkerState, BiomarkerOrigin, MoleculeIdentifier -from biomarkers.serializers import BiomarkerSerializer, MoleculeIdentifierSerializer, \ +from biomarkers.serializers import BiomarkerFromCorrelationAnalysisSerializer, BiomarkerSerializer, MoleculeIdentifierSerializer, \ BiomarkerSimpleSerializer, BiomarkerSimpleUpdateSerializer from common.pagination import StandardResultsSetPagination from common.response import generate_json_response_or_404 -from django.db.models import QuerySet +from django.db.models import QuerySet, Q + class BiomarkerList(generics.ListAPIView): """REST endpoint: list for Biomarker model""" def get_queryset(self): + user = self.request.user only_successful = self.request.GET.get('onlySuccessful') == 'true' - biomarkers = Biomarker.objects.filter(user=self.request.user) + biomarkers = Biomarker.objects.filter( + Q(is_public=True) | Q(user=user) | Q(shared_institutions__institutionadministration__user=user)).distinct() + if only_successful: # FIXME: this is VERY slow. Taking more than 20secs in production. Must parametrize the DB, maybe # FIXME: autovacuum settings could help @@ -112,7 +117,6 @@ def get(self, request: Request, pk: int): self.__copy_molecules_instances(biomarker_copy, biomarker.cnas.all()) self.__copy_molecules_instances(biomarker_copy, biomarker.methylations.all()) - return Response({'ok': True}) @@ -142,6 +146,7 @@ def get_gene_aliases(genes_ids: List[str]) -> Optional[Dict]: method='post' ) + def find_genes_from_request(request: Request) -> List[Dict]: """ Generates the structure for the frontend for a list of genes. The needed structure is a list of dicts with @@ -273,3 +278,22 @@ def get_queryset(self): filter_backends = [filters.OrderingFilter, filters.SearchFilter, DjangoFilterBackend] search_fields = ['identifier'] ordering_fields = ['identifier'] + + +class BiomarkerCorrelationAPIView(APIView): + """Validates the request data and retrieves the corresponding experiment.""" + + def post(self, request, *args, **kwargs): + # Instantiate the serializer with the received data + serializer = BiomarkerFromCorrelationAnalysisSerializer(data=request.data) + + # Validate the data (returns a 400 error if the structure is incorrect) + serializer.is_valid(raise_exception=True) + validated_data = serializer.validated_data + + # Here the Biomarker is created validating which parameters were sent from the frontend + cor_analysis = get_object_or_404(Experiment, pk=validated_data['correlation_analysis_id']) + + return Response({ + "ok": True, + }) \ No newline at end of file diff --git a/src/feature_selection/fs_algorithms_spark.py b/src/feature_selection/fs_algorithms_spark.py index bc2125bb..6cff6ab2 100644 --- a/src/feature_selection/fs_algorithms_spark.py +++ b/src/feature_selection/fs_algorithms_spark.py @@ -32,6 +32,10 @@ def __get_clustering_algorithm_value(cluster_algorithm: ClusteringAlgorithm) -> """Gets the corresponding string value for the parameter 'clustering-algorithm' of the EMR integration.""" if cluster_algorithm == ClusteringAlgorithm.SPECTRAL: return 'spectral' + if cluster_algorithm == ClusteringAlgorithm.BK_MEANS: + return 'bk_means' + if cluster_algorithm == ClusteringAlgorithm.WARD: + return 'ward' return 'k_means' # Default is kmeans diff --git a/src/feature_selection/fs_models.py b/src/feature_selection/fs_models.py index 4570b80f..6ef092a5 100644 --- a/src/feature_selection/fs_models.py +++ b/src/feature_selection/fs_models.py @@ -1,6 +1,6 @@ from typing import Literal, Union, Optional from django.conf import settings -from sklearn.cluster import KMeans, SpectralClustering +from sklearn.cluster import KMeans, SpectralClustering, BisectingKMeans, AgglomerativeClustering from sksurv.ensemble import RandomSurvivalForest from sksurv.svm import FastKernelSurvivalSVM from .models import ClusteringAlgorithm @@ -12,7 +12,7 @@ SVMOptimizerOptions = Literal["avltree", "rbtree"] # Available models for clustering -ClusteringModels = Union[KMeans, SpectralClustering] +ClusteringModels = Union[KMeans, SpectralClustering, BisectingKMeans, AgglomerativeClustering] def get_clustering_model(clustering_algorithm: ClusteringAlgorithm, @@ -28,6 +28,10 @@ def get_clustering_model(clustering_algorithm: ClusteringAlgorithm, return KMeans(n_clusters=number_of_clusters, random_state=random_state, n_init='auto') elif clustering_algorithm == ClusteringAlgorithm.SPECTRAL: return SpectralClustering(n_clusters=number_of_clusters, random_state=random_state) + elif clustering_algorithm == ClusteringAlgorithm.WARD: + return AgglomerativeClustering(n_clusters=number_of_clusters, linkage='ward') + elif clustering_algorithm == ClusteringAlgorithm.BK_MEANS: + return BisectingKMeans(n_clusters=number_of_clusters, random_state=random_state) raise Exception(f'Invalid clustering_algorithm parameter: {clustering_algorithm}') diff --git a/src/feature_selection/migrations/0056_alter_clusteringparameters_algorithm_and_more.py b/src/feature_selection/migrations/0056_alter_clusteringparameters_algorithm_and_more.py new file mode 100644 index 00000000..53051cd6 --- /dev/null +++ b/src/feature_selection/migrations/0056_alter_clusteringparameters_algorithm_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.15 on 2024-10-23 13:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('feature_selection', '0055_alter_fsexperiment_app_name_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='clusteringparameters', + name='algorithm', + field=models.IntegerField(choices=[(1, 'K Means'), (2, 'Spectral'), (3, 'Bk Means')], default=1), + ), + migrations.AlterField( + model_name='clusteringtimesrecord', + name='algorithm', + field=models.IntegerField(choices=[(1, 'K Means'), (2, 'Spectral'), (3, 'Bk Means')]), + ), + ] diff --git a/src/feature_selection/migrations/0057_alter_clusteringparameters_algorithm_and_more.py b/src/feature_selection/migrations/0057_alter_clusteringparameters_algorithm_and_more.py new file mode 100644 index 00000000..514406b9 --- /dev/null +++ b/src/feature_selection/migrations/0057_alter_clusteringparameters_algorithm_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.15 on 2025-01-02 20:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('feature_selection', '0056_alter_clusteringparameters_algorithm_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='clusteringparameters', + name='algorithm', + field=models.IntegerField(choices=[(1, 'K Means'), (2, 'Spectral'), (3, 'Bk Means'), (4, 'Ward')], default=1), + ), + migrations.AlterField( + model_name='clusteringtimesrecord', + name='algorithm', + field=models.IntegerField(choices=[(1, 'K Means'), (2, 'Spectral'), (3, 'Bk Means'), (4, 'Ward')]), + ), + ] diff --git a/src/feature_selection/models.py b/src/feature_selection/models.py index 1b99b92a..3435913c 100644 --- a/src/feature_selection/models.py +++ b/src/feature_selection/models.py @@ -33,6 +33,8 @@ class ClusteringAlgorithm(models.IntegerChoices): """Clustering algorithm.""" K_MEANS = 1 SPECTRAL = 2 # TODO: implement in backend + BK_MEANS = 3 + WARD = 4 class ClusteringMetric(models.IntegerChoices): diff --git a/src/feature_selection/views.py b/src/feature_selection/views.py index 4739b878..aaa9e9bf 100644 --- a/src/feature_selection/views.py +++ b/src/feature_selection/views.py @@ -209,7 +209,15 @@ def __get_clustering_parameters_columns(row: pd.Series) -> Tuple[int, Clustering parameters_desc = row['parameters'] params = parameters_desc.split('_') number_of_clusters, algorithm_description, scoring_method = params[0], params[2], params[4] - algorithm = ClusteringAlgorithm.K_MEANS if algorithm_description == 'k-means' else ClusteringAlgorithm.SPECTRAL + # algorithm = ClusteringAlgorithm.K_MEANS if algorithm_description == 'k-means' else ClusteringAlgorithm.SPECTRAL + if algorithm_description == 'k-means': + algorithm = ClusteringAlgorithm.K_MEANS + elif algorithm_description == 'spectral': + algorithm = ClusteringAlgorithm.SPECTRAL + elif algorithm_description == 'ward': + algorithm = ClusteringAlgorithm.WARD + else: + algorithm = ClusteringAlgorithm.BK_MEANS scoring = ClusteringScoringMethod.C_INDEX if scoring_method == 'concordance-index' \ else ClusteringScoringMethod.LOG_LIKELIHOOD return number_of_clusters, algorithm, scoring diff --git a/src/frontend/serializers.py b/src/frontend/serializers.py deleted file mode 100644 index a36cb4a0..00000000 --- a/src/frontend/serializers.py +++ /dev/null @@ -1,31 +0,0 @@ -from rest_framework import serializers -from django.contrib.auth import get_user_model -from institutions.models import Institution - - -class UserSerializer(serializers.ModelSerializer): - class Meta: - model = get_user_model() - fields = ['id', 'username', 'is_superuser'] - - def to_representation(self, instance): - # Adds custom fields - data = super(UserSerializer, self).to_representation(instance) - - # Check if current user has any institutions where he's admin - data['is_institution_admin'] = Institution.objects.filter( - institutionadministration__user=instance, - institutionadministration__is_institution_admin=True - ).exists() - - # User at this point is not anonymous - data['is_anonymous'] = False - - return data - - -class UserSimpleSerializer(serializers.ModelSerializer): - """User serializer with fewer data""" - class Meta: - model = get_user_model() - fields = ['id', 'username'] diff --git a/src/frontend/static/frontend/img/genes-icons/ActiveDriverDb.webp b/src/frontend/static/frontend/img/genes-icons/ActiveDriverDb.webp new file mode 100644 index 00000000..8f77a1a4 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/ActiveDriverDb.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/Alliance-Genome.webp b/src/frontend/static/frontend/img/genes-icons/Alliance-Genome.webp new file mode 100644 index 00000000..f8bef6f5 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/Alliance-Genome.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/AlphaFold.webp b/src/frontend/static/frontend/img/genes-icons/AlphaFold.webp new file mode 100644 index 00000000..abf72b03 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/AlphaFold.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/AmiGO.webp b/src/frontend/static/frontend/img/genes-icons/AmiGO.webp new file mode 100644 index 00000000..7a0df318 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/AmiGO.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/BioGrid.webp b/src/frontend/static/frontend/img/genes-icons/BioGrid.webp new file mode 100644 index 00000000..0f8558de Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/BioGrid.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/BioPlex.webp b/src/frontend/static/frontend/img/genes-icons/BioPlex.webp new file mode 100644 index 00000000..ff09791a Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/BioPlex.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/ClinVar.webp b/src/frontend/static/frontend/img/genes-icons/ClinVar.webp new file mode 100644 index 00000000..0b0458a6 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/ClinVar.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/ComplexPortal.webp b/src/frontend/static/frontend/img/genes-icons/ComplexPortal.webp new file mode 100644 index 00000000..8fd34e3b Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/ComplexPortal.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/DGIDB.webp b/src/frontend/static/frontend/img/genes-icons/DGIDB.webp new file mode 100644 index 00000000..9598335a Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/DGIDB.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/Ensembl.webp b/src/frontend/static/frontend/img/genes-icons/Ensembl.webp new file mode 100644 index 00000000..b38d069e Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/Ensembl.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/ExpressionAtlas.webp b/src/frontend/static/frontend/img/genes-icons/ExpressionAtlas.webp new file mode 100644 index 00000000..823176dd Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/ExpressionAtlas.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/GDC.webp b/src/frontend/static/frontend/img/genes-icons/GDC.webp new file mode 100644 index 00000000..5fddb5e0 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/GDC.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/GTEX.webp b/src/frontend/static/frontend/img/genes-icons/GTEX.webp new file mode 100644 index 00000000..63884b74 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/GTEX.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/GWAS-Catalog.webp b/src/frontend/static/frontend/img/genes-icons/GWAS-Catalog.webp new file mode 100644 index 00000000..1d281b87 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/GWAS-Catalog.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/GeneCards.webp b/src/frontend/static/frontend/img/genes-icons/GeneCards.webp new file mode 100644 index 00000000..510609c1 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/GeneCards.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/GeneMANIA.webp b/src/frontend/static/frontend/img/genes-icons/GeneMANIA.webp new file mode 100644 index 00000000..1e1969d8 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/GeneMANIA.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/Google.webp b/src/frontend/static/frontend/img/genes-icons/Google.webp new file mode 100644 index 00000000..fa343541 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/Google.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/HGNC.webp b/src/frontend/static/frontend/img/genes-icons/HGNC.webp new file mode 100644 index 00000000..77d598e9 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/HGNC.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/HI-CBrowser.webp b/src/frontend/static/frontend/img/genes-icons/HI-CBrowser.webp new file mode 100644 index 00000000..56dc7f76 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/HI-CBrowser.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/HPO.webp b/src/frontend/static/frontend/img/genes-icons/HPO.webp new file mode 100644 index 00000000..f2b823b4 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/HPO.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/Human-Protein-Atlas.webp b/src/frontend/static/frontend/img/genes-icons/Human-Protein-Atlas.webp new file mode 100644 index 00000000..b71ff531 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/Human-Protein-Atlas.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/ICGC.webp b/src/frontend/static/frontend/img/genes-icons/ICGC.webp new file mode 100644 index 00000000..f4d597c8 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/ICGC.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/IntAct.webp b/src/frontend/static/frontend/img/genes-icons/IntAct.webp new file mode 100644 index 00000000..a23f0d20 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/IntAct.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/InterPro.webp b/src/frontend/static/frontend/img/genes-icons/InterPro.webp new file mode 100644 index 00000000..bb6e5644 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/InterPro.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/Interactome-Atlas.webp b/src/frontend/static/frontend/img/genes-icons/Interactome-Atlas.webp new file mode 100644 index 00000000..d46d25f2 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/Interactome-Atlas.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/MalaCards.webp b/src/frontend/static/frontend/img/genes-icons/MalaCards.webp new file mode 100644 index 00000000..fa587d02 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/MalaCards.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/Monarch.webp b/src/frontend/static/frontend/img/genes-icons/Monarch.webp new file mode 100644 index 00000000..e895dc80 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/Monarch.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/NCBI.webp b/src/frontend/static/frontend/img/genes-icons/NCBI.webp new file mode 100644 index 00000000..cbd79183 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/NCBI.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/OMIM.webp b/src/frontend/static/frontend/img/genes-icons/OMIM.webp new file mode 100644 index 00000000..8f2119c0 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/OMIM.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/Open-Targets.webp b/src/frontend/static/frontend/img/genes-icons/Open-Targets.webp new file mode 100644 index 00000000..9c59cee0 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/Open-Targets.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/PDB.webp b/src/frontend/static/frontend/img/genes-icons/PDB.webp new file mode 100644 index 00000000..2b1466b0 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/PDB.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/PathCards.webp b/src/frontend/static/frontend/img/genes-icons/PathCards.webp new file mode 100644 index 00000000..aacf30d0 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/PathCards.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/PathwayCommons.webp b/src/frontend/static/frontend/img/genes-icons/PathwayCommons.webp new file mode 100644 index 00000000..e6e77400 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/PathwayCommons.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/Pharmgkb.webp b/src/frontend/static/frontend/img/genes-icons/Pharmgkb.webp new file mode 100644 index 00000000..c826da96 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/Pharmgkb.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/PubMed.webp b/src/frontend/static/frontend/img/genes-icons/PubMed.webp new file mode 100644 index 00000000..f9225b6e Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/PubMed.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/Reactome.webp b/src/frontend/static/frontend/img/genes-icons/Reactome.webp new file mode 100644 index 00000000..69a7bb19 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/Reactome.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/STRING.webp b/src/frontend/static/frontend/img/genes-icons/STRING.webp new file mode 100644 index 00000000..8b15b246 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/STRING.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/UCSCBrowser.webp b/src/frontend/static/frontend/img/genes-icons/UCSCBrowser.webp new file mode 100644 index 00000000..7740a907 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/UCSCBrowser.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/Uniprot.webp b/src/frontend/static/frontend/img/genes-icons/Uniprot.webp new file mode 100644 index 00000000..404fa98e Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/Uniprot.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/cBioPortal.webp b/src/frontend/static/frontend/img/genes-icons/cBioPortal.webp new file mode 100644 index 00000000..f1286f92 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/cBioPortal.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/dgv.webp b/src/frontend/static/frontend/img/genes-icons/dgv.webp new file mode 100644 index 00000000..a4827005 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/dgv.webp differ diff --git a/src/frontend/static/frontend/img/genes-icons/harvard.webp b/src/frontend/static/frontend/img/genes-icons/harvard.webp new file mode 100644 index 00000000..2be530d5 Binary files /dev/null and b/src/frontend/static/frontend/img/genes-icons/harvard.webp differ diff --git a/src/frontend/static/frontend/src/components/MainNavbar.tsx b/src/frontend/static/frontend/src/components/MainNavbar.tsx index 1d97cc26..f4e4e5ad 100644 --- a/src/frontend/static/frontend/src/components/MainNavbar.tsx +++ b/src/frontend/static/frontend/src/components/MainNavbar.tsx @@ -99,6 +99,9 @@ const MainNavbar = (props: MainNavbarProps) => { + + + } {/* Analysis menu */} @@ -173,21 +176,21 @@ const MainNavbar = (props: MainNavbarProps) => { /> } - - {/* Institutions panel (only for user who are admin of at least one institution) */} - {currentUser.is_institution_admin && - - } } + {/* Institutions */} + {currentUser && + + + Institutions + + + + } + {/* About us */} diff --git a/src/frontend/static/frontend/src/components/biomarkers/BiomarkersPanel.tsx b/src/frontend/static/frontend/src/components/biomarkers/BiomarkersPanel.tsx index a3b58c97..8b0cd8cf 100644 --- a/src/frontend/static/frontend/src/components/biomarkers/BiomarkersPanel.tsx +++ b/src/frontend/static/frontend/src/components/biomarkers/BiomarkersPanel.tsx @@ -4,8 +4,8 @@ import { Header, Button, Modal, Table, DropdownItemProps, Icon, Confirm, Form } import { DjangoCGDSStudy, DjangoSurvivalColumnsTupleSimple, DjangoTag, DjangoUserFile, TagType } from '../../utils/django_interfaces' import ky, { Options } from 'ky' import { getDjangoHeader, alertGeneralError, formatDateLocale, cleanRef, getFilenameFromSource, makeSourceAndAppend, getDefaultSource } from '../../utils/util_functions' -import { NameOfCGDSDataset, Nullable, CustomAlert, CustomAlertTypes, SourceType, OkResponse } from '../../utils/interfaces' -import { Biomarker, BiomarkerType, BiomarkerOrigin, ConfirmModal, FormBiomarkerData, MoleculesSectionData, MoleculesTypeOfSelection, SaveBiomarkerStructure, SaveMoleculeStructure, FeatureSelectionPanelData, SourceStateBiomarker, FeatureSelectionAlgorithm, FitnessFunction, FitnessFunctionParameters, BiomarkerState, AdvancedAlgorithm as AdvancedAlgorithmParameters, BBHAVersion, BiomarkerSimple, CrossValidationParameters } from './types' +import { NameOfCGDSDataset, Nullable, CustomAlert, CustomAlertTypes, SourceType, OkResponse, ConfirmModal } from '../../utils/interfaces' +import { Biomarker, BiomarkerType, BiomarkerOrigin, FormBiomarkerData, MoleculesSectionData, MoleculesTypeOfSelection, SaveBiomarkerStructure, SaveMoleculeStructure, FeatureSelectionPanelData, SourceStateBiomarker, FeatureSelectionAlgorithm, FitnessFunction, FitnessFunctionParameters, BiomarkerState, AdvancedAlgorithm as AdvancedAlgorithmParameters, BBHAVersion, BiomarkerSimple, CrossValidationParameters } from './types' import { ManualForm } from './modalContentBiomarker/manualForm/ManualForm' import { PaginatedTable, PaginationCustomFilter } from '../common/PaginatedTable' import { TableCellWithTitle } from '../common/TableCellWithTitle' @@ -1704,7 +1704,7 @@ export class BiomarkersPanel extends React.Component<{}, BiomarkersPanelState> { {/* Stop button */} {isInProcess && this.setState({ biomarkerToStop: biomarker })} /> } @@ -1712,7 +1712,7 @@ export class BiomarkersPanel extends React.Component<{}, BiomarkersPanelState> { {/* Delete button */} {!isInProcess && this.confirmBiomarkerDeletion(biomarker)} /> diff --git a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/genes/GeneInformation.tsx b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/genes/GeneInformation.tsx index a0836d44..bf652a04 100644 --- a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/genes/GeneInformation.tsx +++ b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/molecules/genes/GeneInformation.tsx @@ -66,247 +66,247 @@ export const GeneInformation = (props: GeneInformationProps) => { source: 'PubMed', rel: 'PubMed', url: `https://pubmed.ncbi.nlm.nih.gov/?term=${moleculeKey}+`, - icon: 'https://www.gene-list.com/build/images/pubmed.svg' + icon: '/static/frontend/img/genes-icons/PubMed.webp' }, { source: 'Google', rel: 'Google', url: `https://scholar.google.ca/scholar?hl=en&as_sdt=0%2C5&q=${moleculeKey}+`, - icon: 'https://www.gene-list.com/build/images/google.svg' + icon: '/static/frontend/img/genes-icons/Google.webp' }, { source: 'GeneCards', rel: 'GeneCards', url: `https://www.genecards.org/cgi-bin/carddisp.pl?gene=${moleculeKey}`, - icon: 'https://www.gene-list.com/build/images/gene_cards.svg' + icon: '/static/frontend/img/genes-icons/GeneCards.webp' }, { source: 'MalaCards', rel: 'MalaCards', url: `https://www.malacards.org/search/results?query=${moleculeKey}`, - icon: 'https://www.gene-list.com/build/images/malacards.svg' + icon: '/static/frontend/img/genes-icons/MalaCards.webp' }, { source: 'PathCards', rel: 'PathCards', url: `https://pathcards.genecards.org/Search/Results?query=${moleculeKey}`, - icon: 'https://www.gene-list.com/build/images/pathcards.svg' + icon: '/static/frontend/img/genes-icons/PathCards.webp' }, { source: 'Reactome', rel: 'Reactome', url: `https://reactome.org/content/query?q=${moleculeKey}&species=Homo+sapiens&species=Entries+without+species&cluster=true`, - icon: 'https://www.gene-list.com/build/images/reactome.svg' + icon: '/static/frontend/img/genes-icons/Reactome.webp' }, { source: 'GeneMANIA', rel: 'GeneMANIA', url: `https://genemania.org/search/homo-sapiens/${moleculeKey}`, - icon: 'https://www.gene-list.com/build/images/genemania.svg' + icon: '/static/frontend/img/genes-icons/GeneMANIA.webp' }, { source: 'Pathway Commons', rel: 'Pathway Commons', url: `https://apps.pathwaycommons.org/search?type=Pathway&q=${moleculeKey}`, - icon: 'https://www.gene-list.com/build/images/pathway_commons.svg' + icon: '/static/frontend/img/genes-icons/PathwayCommons.webp' }, { source: 'STRING', rel: 'STRING', url: `https://string-db.org/newstring_cgi/show_network_section.pl?identifiers=${moleculeKey}`, - icon: 'https://www.gene-list.com/build/images/string.svg' + icon: '/static/frontend/img/genes-icons/STRING.webp' }, { source: 'IntAct', rel: 'IntAct', url: `https://www.ebi.ac.uk/intact/search?query=${moleculeKey}`, - icon: 'https://www.gene-list.com/build/images/intact.svg' + icon: '/static/frontend/img/genes-icons/IntAct.webp' }, { source: 'Interactome Atlas', rel: 'Interactome Atlas', url: `http://www.interactome-atlas.org/search/${moleculeKey}`, - icon: 'https://www.gene-list.com/build/images/interactomeatlas.svg' + icon: '/static/frontend/img/genes-icons/Interactome-Atlas.webp' }, { source: 'Complex Portal', rel: 'Complex Portal', url: `https://www.ebi.ac.uk/complexportal/complex/search?query=${moleculeKey}`, - icon: 'https://www.gene-list.com/build/images/complex_portal.svg' + icon: '/static/frontend/img/genes-icons/ComplexPortal.webp' }, { source: 'BioGrid', rel: 'BioGrid', url: `https://thebiogrid.org//summary/homo-sapiens/${moleculeKey}.html`, - icon: 'https://www.gene-list.com/build/images/biogrid.svg' + icon: '/static/frontend/img/genes-icons/BioGrid.webp' }, { source: 'DGV', rel: 'DGV', url: `http://dgv.tcag.ca/gb2/gbrowse/dgv2_hg19/?name=${moleculeKey};search=Search`, - icon: 'https://www.gene-list.com/build/images/dgv.svg' + icon: '/static/frontend/img/genes-icons/DGV.webp' }, { source: 'ClinVar', rel: 'ClinVar', url: `https://www.ncbi.nlm.nih.gov/clinvar/?term=${moleculeKey}%5Bgene%5D&redir=gene`, - icon: 'https://www.gene-list.com/build/images/ncbi.svg' + icon: '/static/frontend/img/genes-icons/ClinVar.webp' }, { source: 'DGIDB', rel: 'DGIDB', url: `https://www.dgidb.org/genes/${moleculeKey}#_interactions`, - icon: 'https://www.gene-list.com/build/images/dgibd.svg' + icon: '/static/frontend/img/genes-icons/dgidb.webp' }, { source: 'NCBI', rel: 'NCBI', url: `https://www.ncbi.nlm.nih.gov/gene/${jsonResponse.data[moleculeKey].entrez_id}`, - icon: 'https://www.gene-list.com/build/images/ncbi.svg' + icon: '/static/frontend/img/genes-icons/NCBI.webp' }, { source: 'Ensembl', rel: 'Ensembl', url: `http://www.ensembl.org/id/${jsonResponse.data[moleculeKey].ensembl_gene_id}`, - icon: 'https://www.gene-list.com/build/images/ensembl.svg' + icon: '/static/frontend/img/genes-icons/Ensembl.webp' }, { source: 'UCSC Browser', rel: 'UCSC Browser', url: `http://genome.ucsc.edu/cgi-bin/hgTracks?db=hg38&singleSearch=knownCanonical&position=${moleculeKey}`, - icon: 'https://www.gene-list.com/build/images/ucsc.svg' + icon: '/static/frontend/img/genes-icons/UCSCBrowser.webp' }, { source: 'Uniprot', rel: 'Uniprot', url: `http://www.uniprot.org/uniprot/${jsonResponse.data[moleculeKey].uniprot_ids}`, - icon: 'https://www.gene-list.com/build/images/uniprot.svg' + icon: '/static/frontend/img/genes-icons/Uniprot.webp' }, { source: 'Human Protein Atlas', rel: 'Human Protein Atlas', url: `https://www.proteinatlas.org/${jsonResponse.data[moleculeKey].uniprot_ids}`, - icon: 'https://www.gene-list.com/build/images/hpa.svg' + icon: '/static/frontend/img/genes-icons/Human-Protein-Atlas.webp' }, { source: 'Alliance Genome', rel: 'Alliance Genome', url: `https://www.proteinatlas.org/${jsonResponse.data[moleculeKey].hgnc_id}`, - icon: 'https://www.gene-list.com/build/images/alliance_genome_human.svg' + icon: '/static/frontend/img/genes-icons/Alliance-Genome.webp' }, { source: 'HGNC', rel: 'HGNC', url: `https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/${jsonResponse.data[moleculeKey].hgnc_id}`, - icon: 'https://www.gene-list.com/build/images/hgnc.svg' + icon: '/static/frontend/img/genes-icons/HGNC.webp' }, { source: 'Monarch', rel: 'Monarch', url: `https://monarchinitiative.org/gene/${jsonResponse.data[moleculeKey].hgnc_id}`, - icon: 'https://www.gene-list.com/build/images/monarch.svg' + icon: '/static/frontend/img/genes-icons/Monarch.webp' }, { source: 'HI-C Browser', rel: 'HI-C Browser', url: `http://3dgenome.fsm.northwestern.edu/view.php?method=Hi-C&species=human&assembly=hg38&source=inside&tissue=GM12878&type=Rao_2014-raw&c_url=&transfer=&gene=${jsonResponse.data[moleculeKey].uniprot_ids}&sessionID=&browser=none`, - icon: 'https://www.gene-list.com/build/images/hi_c_browser.svg' + icon: '/static/frontend/img/genes-icons/HI-CBrowser.webp' }, { source: 'InterPro', rel: 'InterPro', url: `https://www.ebi.ac.uk/interpro/protein/UniProt/${jsonResponse.data[moleculeKey].hgnc_id}`, - icon: 'https://www.gene-list.com/build/images/interpro.svg' + icon: '/static/frontend/img/genes-icons/InterPro.webp' }, { source: 'PDB', rel: 'PDB', url: `https://www.rcsb.org/search?request=%7B%22query%22%3A%7B%22type%22%3A%22group%22%2C%22logical_operator%22%3A%22and%22%2C%22nodes%22%3A%5B%7B%22type%22%3A%22group%22%2C%22logical_operator%22%3A%22and%22%2C%22nodes%22%3A%5B%7B%22type%22%3A%22group%22%2C%22nodes%22%3A%5B%7B%22type%22%3A%22terminal%22%2C%22service%22%3A%22full_text%22%2C%22parameters%22%3A%7B%22value%22%3A%22${moleculeKey}%22%7D%7D%5D%2C%22logical_operator%22%3A%22and%22%7D%5D%2C%22label%22%3A%22full_text%22%7D%5D%7D%2C%22return_type%22%3A%22entry%22%2C%22request_options%22%3A%7B%22pager%22%3A%7B%22start%22%3A0%2C%22rows%22%3A25%7D%2C%22scoring_strategy%22%3A%22combined%22%2C%22sort%22%3A%5B%7B%22sort_by%22%3A%22score%22%2C%22direction%22%3A%22desc%22%7D%5D%7D%2C%22request_info%22%3A%7B%22query_id%22%3A%225a4bea3ded0ab9c9fd19c0405097e1b2%22%7D%7D`, - icon: 'https://www.gene-list.com/build/images/pdb.svg' + icon: '/static/frontend/img/genes-icons/PDB.webp' }, { source: 'AlphaFold', rel: 'AlphaFold', url: `https://alphafold.ebi.ac.uk/entry/${jsonResponse.data[moleculeKey].uniprot_ids}`, - icon: 'https://www.gene-list.com/build/images/alphafold.svg' + icon: '/static/frontend/img/genes-icons/AlphaFold.webp' }, { source: 'Amigo', rel: 'Amigo', url: `http://amigo.geneontology.org/amigo/gene_product/UniProtKB:${jsonResponse.data[moleculeKey].uniprot_ids}`, - icon: 'https://www.gene-list.com/build/images/amigo.svg' + icon: '/static/frontend/img/genes-icons/Amigo.webp' }, { source: 'BioPlex', rel: 'BioPlex', url: `https://bioplex.hms.harvard.edu/explorer/externalQuery.php?geneQuery=${jsonResponse.data[moleculeKey].entrez_id}`, - icon: 'https://www.gene-list.com/build/images/bioplex.svg' + icon: '/static/frontend/img/genes-icons/BioPlex.webp' }, { source: 'GTEX', rel: 'GTEX', url: `https://www.gtexportal.org/home/gene/${jsonResponse.data[moleculeKey].ensembl_gene_id}`, - icon: 'https://www.gene-list.com/build/images/gtex.svg' + icon: '/static/frontend/img/genes-icons/GTEX.webp' }, { source: 'GDC', rel: 'GDC', url: `https://portal.gdc.cancer.gov/genes/${jsonResponse.data[moleculeKey].ensembl_gene_id}`, - icon: 'https://www.gene-list.com/build/images/tcga.svg' + icon: '/static/frontend/img/genes-icons/GDC.webp' }, { source: 'ICGC', rel: 'ICGC', url: `https://dcc.icgc.org/genes/${jsonResponse.data[moleculeKey].ensembl_gene_id}`, - icon: 'https://www.gene-list.com/build/images/pcawg.svg' + icon: '/static/frontend/img/genes-icons/ICGC.webp' }, { source: 'cBioPortal', rel: 'cBioPortal', url: `http://www.cbioportal.org/results/cancerTypesSummary?cancer_study_list=laml_tcga_pan_can_atlas_2018%2Cacc_tcga_pan_can_atlas_2018%2Cblca_tcga_pan_can_atlas_2018%2Clgg_tcga_pan_can_atlas_2018%2Cbrca_tcga_pan_can_atlas_2018%2Ccesc_tcga_pan_can_atlas_2018%2Cchol_tcga_pan_can_atlas_2018%2Ccoadread_tcga_pan_can_atlas_2018%2Cdlbc_tcga_pan_can_atlas_2018%2Cesca_tcga_pan_can_atlas_2018%2Cgbm_tcga_pan_can_atlas_2018%2Chnsc_tcga_pan_can_atlas_2018%2Ckich_tcga_pan_can_atlas_2018%2Ckirc_tcga_pan_can_atlas_2018%2Ckirp_tcga_pan_can_atlas_2018%2Clihc_tcga_pan_can_atlas_2018%2Cluad_tcga_pan_can_atlas_2018%2Clusc_tcga_pan_can_atlas_2018%2Cmeso_tcga_pan_can_atlas_2018%2Cov_tcga_pan_can_atlas_2018%2Cpaad_tcga_pan_can_atlas_2018%2Cpcpg_tcga_pan_can_atlas_2018%2Cprad_tcga_pan_can_atlas_2018%2Csarc_tcga_pan_can_atlas_2018%2Cskcm_tcga_pan_can_atlas_2018%2Cstad_tcga_pan_can_atlas_2018%2Ctgct_tcga_pan_can_atlas_2018%2Cthym_tcga_pan_can_atlas_2018%2Cthca_tcga_pan_can_atlas_2018%2Cucs_tcga_pan_can_atlas_2018%2Cucec_tcga_pan_can_atlas_2018%2Cuvm_tcga_pan_can_atlas_2018&Z_SCORE_THRESHOLD=2.0&RPPA_SCORE_THRESHOLD=2.0&profileFilter=mutations%2Cfusion%2Cgistic&case_set_id=all&gene_list=${moleculeKey}&geneset_list=%20&tab_index=tab_visualize&Action=Submit`, - icon: 'https://www.gene-list.com/build/images/cbioportal.svg' + icon: '/static/frontend/img/genes-icons/cBioPortal.webp' }, { source: 'OMIM', rel: 'OMIM', url: `https://omim.org/entry/616125${jsonResponse.data[moleculeKey].omim_id}`, - icon: 'https://www.gene-list.com/build/images/omim.svg' + icon: '/static/frontend/img/genes-icons/OMIM.webp' }, { source: 'HPO', rel: 'HPO', url: `https://hpo.jax.org/app/browse/gene/${jsonResponse.data[moleculeKey].entrez_id}`, - icon: 'https://www.gene-list.com/build/images/hpo.svg' + icon: '/static/frontend/img/genes-icons/HPO.webp' }, { source: 'Open Targets', rel: 'Open Targets', url: `https://genetics.opentargets.org/gene/ENSG00000164169${jsonResponse.data[moleculeKey].ensembl_gene_id}`, - icon: 'https://www.gene-list.com/build/images/open_targets.svg' + icon: '/static/frontend/img/genes-icons/Open-Targets.webp' }, { source: 'Expression Atlas', rel: 'Expression Atlas', url: `https://www.ebac.uk/gxa/search?geneQuery=%5B%7B%22value%22%3A%22PRMT9%22%7D%5D&species=&conditionQuery=%5B%5D&bs=%7B%22homo%20sapiens%22%3A%5B%22ORGANISM_PART%22%5D%2C%22chlorocebus%20sabaeus%22%3A%5B%22ORGANISM_PART%22%5D%2C%22danio%20rerio%22%3A%5B%22DEVELOPMENTAL_STAGE%22%5D%2C%22equus%20caballus%22%3A%5B%22ORGANISM_PART%22%5D%2C%22gallus%20gallus%22%3A%5B%22ORGANISM_PART%22%5D%2C%22mus%20musculus%22%3A%5B%22ORGANISM_PART%22%5D%2C%22papio%20anubis%22%3A%5B%22ORGANISM_PART%22%5D%2C%22rattus%20norvegicus%22%3A%5B%22ORGANISM_PART%22%5D%7D&ds=%7B%22kingdom%22%3A%5B%22animals%22%5D%7D#baseline${jsonResponse.data[moleculeKey].entrez_id}`, - icon: 'https://www.gene-list.com/build/images/expression_atlas.svg' + icon: '/static/frontend/img/genes-icons/ExpressionAtlas.webp' }, { source: 'GWAS Catalog', rel: 'GWAS Catalog', url: `https://www.ebi.ac.uk/gwas/search?query=${moleculeKey}`, - icon: 'https://www.gene-list.com/build/images/gwas.svg' + icon: '/static/frontend/img/genes-icons/GWAS-Catalog.webp' }, { source: 'ActiveDriverDb', rel: 'ActiveDriverDb', url: `https://activedriverdb.org/gene/show/${moleculeKey}`, - icon: 'https://www.gene-list.com/build/images/activedriverdb.svg' + icon: '/static/frontend/img/genes-icons/ActiveDriverDb.webp' }, { source: 'Pharmgkb', rel: 'Pharmgkb', url: `https://www.pharmgkb.org/search?query=${moleculeKey}`, - icon: 'https://www.gene-list.com/build/images/pharmgkb.svg' + icon: '/static/frontend/img/genes-icons/Pharmgkb.webp' } ] setLinksData(linksData) diff --git a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/stat-validations/result/StatisticalValidationResultKaplanMeier.tsx b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/stat-validations/result/StatisticalValidationResultKaplanMeier.tsx index 0c6ec5fe..3979f855 100644 --- a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/stat-validations/result/StatisticalValidationResultKaplanMeier.tsx +++ b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/stat-validations/result/StatisticalValidationResultKaplanMeier.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useRef, useState } from 'react' import ky from 'ky' -import { Button, Form, Grid, Header, Icon, Modal, Statistic } from 'semantic-ui-react' +import { Button, Form, Grid, Header, Icon, Modal, Statistic, GridColumn } from 'semantic-ui-react' import { alertGeneralError, listToDropdownOptions } from '../../../../../utils/util_functions' import { StatisticalValidationForTable, KaplanMeierResultData, FitnessFunction } from '../../../types' import { KaplanMeier } from '../../../../pipeline/experiment-result/gene-gem-details/survival-analysis/KaplanMeierUtils' @@ -188,10 +188,10 @@ export const StatisticalValidationResultKaplanMeier = (props: StatisticalValidat return ( - + {getKaplanMeierPanel()} - - + + {/* Clustering metrics. */} } - + ) diff --git a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/stat-validations/result/StatisticalValidationResultModal.tsx b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/stat-validations/result/StatisticalValidationResultModal.tsx index 3aab6102..60231a03 100644 --- a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/stat-validations/result/StatisticalValidationResultModal.tsx +++ b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/stat-validations/result/StatisticalValidationResultModal.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react' import { ActiveStatValidationsItemMenu, StatisticalValidationForTable } from '../../../types' import { StatisticalValidationMenu } from '../StatisticalValidationMenu' import { StatisticalValidationResultMetrics } from './StatisticalValidationResultMetrics' -import { Grid, Segment } from 'semantic-ui-react' +import { Grid, Segment, GridColumn } from 'semantic-ui-react' import { StatisticalValidationResultBestFeatures } from './StatisticalValidationResultBestFeatures' import { StatisticalValidationResultHeatMap } from './StatisticalValidationResultHeatMap' import { StatisticalValidationResultKaplanMeier } from './StatisticalValidationResultKaplanMeier' @@ -41,27 +41,25 @@ export const StatisticalValidationResultModal = (props: StatisticalValidationRes return ( <> - - - - - - - - - - {/* Menu */} - + + + + + + + + + {/* Menu */} + - {/* Selected menu option */} - {getSelectedComponent()} - - - + {/* Selected menu option */} + {getSelectedComponent()} + + ) diff --git a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/trained-models/NewClusteringModelForm.tsx b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/trained-models/NewClusteringModelForm.tsx index 54faf816..d4cd21a2 100644 --- a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/trained-models/NewClusteringModelForm.tsx +++ b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/trained-models/NewClusteringModelForm.tsx @@ -15,13 +15,29 @@ interface NewClusteringModelFormProps { } export const NewClusteringModelForm = (props: NewClusteringModelFormProps) => { - // TODO: add an InfoPopup for all the inputs return ( <> + +

K-Means: Groups data by minimizing intra-cluster variance; effective for clustering RNA and miRNA expression profiles.

+

Spectral Clustering: Uses graph-based similarity to identify complex patterns; ideal for integrating methylation and CNA data.

+

BK-Means: A hierarchical variation of K-Means, suitable for layered clustering of clinical and multi-omics datasets.

+

Ward’s Method: Minimizes variance in hierarchical clustering; well-suited for combining RNA and methylation data in integrated analyses.

+ + } + onTop={false} + onEvent='hover' + noBorder + extraClassName='pull-right' + /> + + } options={clusteringAlgorithmOptions} placeholder='Select an algorithm' name='algorithm' @@ -29,7 +45,6 @@ export const NewClusteringModelForm = (props: NewClusteringModelFormProps) => { onChange={props.handleChangeParams} /> - {/* TODO: add InfoPopup */} { props.handleChangeOptimalNClusters(checked ?? false) }} @@ -39,7 +54,17 @@ export const NewClusteringModelForm = (props: NewClusteringModelFormProps) => { {!props.parameters.lookForOptimalNClusters && + + + } name='nClusters' min={2} max={10} @@ -51,7 +76,22 @@ export const NewClusteringModelForm = (props: NewClusteringModelFormProps) => { + +

Cox Regression: A proportional hazards model to identify associations between multi-omics features (RNA, miRNA, methylation) and clinical outcomes over time.

+

Log-Rank Test: A non-parametric test to compare the survival distributions of two or more groups; currently not available.

+ + } + onTop={false} + onEvent='hover' + noBorder + extraClassName='pull-right' + /> + + } options={clusteringMetricOptions} placeholder='Select a metric' name='metric' @@ -64,7 +104,22 @@ export const NewClusteringModelForm = (props: NewClusteringModelFormProps) => { + +

C-Index: A measure of concordance between predicted and observed survival outcomes; higher values indicate better model performance.

+

Log Likelihood: The probability of observing the data given the model; lower values indicate better model performance.

+ + } + onTop={false} + onEvent='hover' + noBorder + extraClassName='pull-right' + /> + + } options={clusteringScoringMethodOptions} placeholder='Select a method' name='scoringMethod' @@ -75,7 +130,17 @@ export const NewClusteringModelForm = (props: NewClusteringModelFormProps) => { + + + } placeholder='An integer number' type='number' step={1} diff --git a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/trained-models/NewRFModelForm.tsx b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/trained-models/NewRFModelForm.tsx index f77a3136..db9b737d 100644 --- a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/trained-models/NewRFModelForm.tsx +++ b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/trained-models/NewRFModelForm.tsx @@ -1,6 +1,8 @@ import React from 'react' import { Form, InputOnChangeData } from 'semantic-ui-react' import { RFParameters } from '../../types' +import { InfoPopup } from '../../../pipeline/experiment-result/gene-gem-details/InfoPopup' +import { InputLabel } from '../../../common/InputLabel' interface NewSVMModelFormProps { /** Getter of the selected params to handle in the form. */ @@ -16,18 +18,38 @@ export const NewRFModelForm = (props: NewSVMModelFormProps) => { const lookForOptimalNEstimators = props.parameters.lookForOptimalNEstimators return ( <> - {/* TODO: add InfoPopup */} + { props.handleChangeOptimalNEstimators(checked ?? false) }} - label='Search for the optimal number of trees' + label={ + + + + } /> {!lookForOptimalNEstimators && + + + } type='number' min={10} max={20} @@ -40,7 +62,17 @@ export const NewRFModelForm = (props: NewSVMModelFormProps) => { + + + } placeholder='An integer number' type='number' min={3} @@ -52,7 +84,17 @@ export const NewRFModelForm = (props: NewSVMModelFormProps) => { + + + } placeholder='An integer number' type='number' step={1} diff --git a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/trained-models/NewSVMModelForm.tsx b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/trained-models/NewSVMModelForm.tsx index 2451fce9..ed0e07b2 100644 --- a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/trained-models/NewSVMModelForm.tsx +++ b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/trained-models/NewSVMModelForm.tsx @@ -2,6 +2,8 @@ import React from 'react' import { Form, InputOnChangeData } from 'semantic-ui-react' import { SVMKernelOptions } from '../../utils' import { SVMParameters } from '../../types' +import { InfoPopup } from '../../../pipeline/experiment-result/gene-gem-details/InfoPopup' +import { InputLabel } from '../../../common/InputLabel' interface NewSVMModelFormProps { /** Getter of the selected params to handle in the form. */ @@ -11,13 +13,28 @@ interface NewSVMModelFormProps { } export const NewSVMModelForm = (props: NewSVMModelFormProps) => { - // TODO: add an InfoPopup for all the inputs return ( <> + +

Linear Kernel: Best for linearly separable data; commonly used for simple genomic or clinical feature classification.

+

Polynomial Kernel: Captures non-linear patterns; effective for complex relationships in multi-omics data.

+

RBF Kernel: Maps data to a higher-dimensional space; ideal for handling non-linear separations in RNA and methylation analyses.

+ + } + onTop={false} + onEvent='hover' + noBorder + extraClassName='pull-right' + /> + + } options={SVMKernelOptions} placeholder='Select a kernel' name='kernel' @@ -28,7 +45,17 @@ export const NewSVMModelForm = (props: NewSVMModelFormProps) => { + + + } placeholder='100-2000' name='maxIterations' value={props.parameters.maxIterations ?? ''} @@ -37,7 +64,17 @@ export const NewSVMModelForm = (props: NewSVMModelFormProps) => { + + + } placeholder='An integer number' type='number' step={1} diff --git a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/trained-models/NewTrainedModelModal.tsx b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/trained-models/NewTrainedModelModal.tsx index e4d4fb5d..3c4e01de 100644 --- a/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/trained-models/NewTrainedModelModal.tsx +++ b/src/frontend/static/frontend/src/components/biomarkers/biomarker-details-modal/trained-models/NewTrainedModelModal.tsx @@ -10,6 +10,8 @@ import { DjangoCGDSStudy, DjangoUserFile } from '../../../../utils/django_interf import ky from 'ky' import { NewClusteringModelForm } from './NewClusteringModelForm' import { NewRFModelForm } from './NewRFModelForm' +import { InfoPopup } from '../../../pipeline/experiment-result/gene-gem-details/InfoPopup' +import { InputLabel } from '../../../common/InputLabel' declare const urlNewTrainedModel: string @@ -437,7 +439,17 @@ export const NewTrainedModelModal = (props: NewTrainedModelModalProps) => { + + + } placeholder='An integer number' type='number' step={1} diff --git a/src/frontend/static/frontend/src/components/biomarkers/labels/ClusteringAlgorithmLabel.tsx b/src/frontend/static/frontend/src/components/biomarkers/labels/ClusteringAlgorithmLabel.tsx index 79c41539..1e7d3d50 100644 --- a/src/frontend/static/frontend/src/components/biomarkers/labels/ClusteringAlgorithmLabel.tsx +++ b/src/frontend/static/frontend/src/components/biomarkers/labels/ClusteringAlgorithmLabel.tsx @@ -28,6 +28,14 @@ export const ClusteringAlgorithmLabel = (props: ClusteringAlgorithmLabelProps) = color = 'blue' description = 'Spectral' break + case ClusteringAlgorithm.BK_MEANS: + color = 'blue' + description = 'Bisecting KMeans' + break + case ClusteringAlgorithm.WARD: + color = 'blue' + description = 'Ward' + break default: color = 'blue' description = '' diff --git a/src/frontend/static/frontend/src/components/biomarkers/types.ts b/src/frontend/static/frontend/src/components/biomarkers/types.ts index 2d26753e..20e63fbc 100644 --- a/src/frontend/static/frontend/src/components/biomarkers/types.ts +++ b/src/frontend/static/frontend/src/components/biomarkers/types.ts @@ -99,13 +99,6 @@ interface Biomarker extends BiomarkerSimple { mrnas: SaveMoleculeStructure[] } -interface ConfirmModal { - confirmModal: boolean, - headerText: string, - contentText: string, - onConfirm: Function, -} - /** Represents a molecule info to show in molecules Dropdown. */ type MoleculeSymbol = { key: string, @@ -208,7 +201,9 @@ enum FitnessFunction { /** Clustering algorithm. */ enum ClusteringAlgorithm { K_MEANS = 1, - SPECTRAL = 2 + SPECTRAL = 2, + BK_MEANS = 3, + WARD = 4 } /** Clustering metric to optimize. */ @@ -628,7 +623,6 @@ export { MoleculesMultipleSelection, MoleculesSectionData, MoleculeSectionItem, - ConfirmModal, MoleculeSymbol, MoleculesSymbolFinder, ClusteringScoringMethod, diff --git a/src/frontend/static/frontend/src/components/biomarkers/utils.ts b/src/frontend/static/frontend/src/components/biomarkers/utils.ts index 971cd35a..cbc13eca 100644 --- a/src/frontend/static/frontend/src/components/biomarkers/utils.ts +++ b/src/frontend/static/frontend/src/components/biomarkers/utils.ts @@ -36,13 +36,15 @@ const SVMKernelOptions: DropdownItemProps[] = [ /** Available options for a Clustering algorithm. */ const clusteringAlgorithmOptions: DropdownItemProps[] = [ { key: ClusteringAlgorithm.K_MEANS, text: 'K-Means', value: ClusteringAlgorithm.K_MEANS }, - { key: ClusteringAlgorithm.SPECTRAL, text: 'Spectral', value: ClusteringAlgorithm.SPECTRAL } + { key: ClusteringAlgorithm.SPECTRAL, text: 'Spectral', value: ClusteringAlgorithm.SPECTRAL }, + { key: ClusteringAlgorithm.BK_MEANS, text: 'BK-Means', value: ClusteringAlgorithm.BK_MEANS }, + { key: ClusteringAlgorithm.WARD, text: 'Ward', value: ClusteringAlgorithm.WARD } ] /** Available options for a Clustering metric to optimize. */ const clusteringMetricOptions: DropdownItemProps[] = [ { key: ClusteringMetric.COX_REGRESSION, text: 'Cox-Regression', value: ClusteringMetric.COX_REGRESSION }, - { key: ClusteringMetric.LOG_RANK_TEST, text: 'Log-Rank test', value: ClusteringMetric.LOG_RANK_TEST, disabled: true } // TODO: implement in backend + { key: ClusteringMetric.LOG_RANK_TEST, text: 'Log-Rank test', value: ClusteringMetric.LOG_RANK_TEST, disabled: true } ] /** Available options for a Clustering scoring method for Cox-Regression. */ diff --git a/src/frontend/static/frontend/src/components/institutions/InstitutionForm.tsx b/src/frontend/static/frontend/src/components/institutions/InstitutionForm.tsx new file mode 100644 index 00000000..ef82c92c --- /dev/null +++ b/src/frontend/static/frontend/src/components/institutions/InstitutionForm.tsx @@ -0,0 +1,176 @@ +import ky from 'ky' +import React, { useEffect, useState } from 'react' +import { Form, Button } from 'semantic-ui-react' +import { getDjangoHeader } from '../../utils/util_functions' +import { CustomAlertTypes, Nullable } from '../../utils/interfaces' +import { DjangoInstitution } from '../../utils/django_interfaces' + +const defaultForm: { + id: undefined | number; + name: string; + location: string; + email: string; + telephone_number: string; + isLoading: boolean; +} = { + id: undefined, + name: '', + location: '', + email: '', + telephone_number: '', + isLoading: false +} + +declare const urlCreateInstitution: string +declare const urlEditInstitution: string + +interface Props { + institutionToEdit: Nullable, + handleResetInstitutionToEdit: (callbackToCancel: () => void) => void, + handleUpdateAlert(isOpen: boolean, type: CustomAlertTypes, message: string, callback: Nullable, isEdit?: boolean): void, +} + +const InstitutionForm = (props: Props) => { + const [formData, setFormData] = useState(defaultForm) + + /** + * Handle form state data + * @param e event + * @param param.name key name to edit field in form + * @param param.value key value to edit field in form + */ + + const handleChange = (e, { name, value }) => { + setFormData({ ...formData, [name]: value }) + } + + /** + * Handle user form to edit or create + */ + const handleSubmit = () => { + const myHeaders = getDjangoHeader() + + if (props.institutionToEdit?.id) { + const jsonParams = { + id: formData.id, + name: formData.name, + location: formData.location, + email: formData.email, + telephone_number: formData.telephone_number + } + const editUrl = `${urlEditInstitution}/${formData.id}/` + + ky.patch(editUrl, { headers: myHeaders, json: jsonParams }).then((response) => { + setFormData(prevState => ({ ...prevState, isLoading: true })) + response.json().then((jsonResponse: DjangoInstitution) => { + props.handleUpdateAlert(true, CustomAlertTypes.SUCCESS, `Institution ${jsonResponse.name} Updated!`, () => setFormData(defaultForm), true) + }).catch((err) => { + setFormData(prevState => ({ ...prevState, isLoading: false })) + props.handleUpdateAlert(true, CustomAlertTypes.ERROR, 'Error creating an institution!', () => setFormData(prevState => ({ ...prevState, isLoading: false }))) + console.error('Error parsing JSON ->', err) + }) + }).catch((err) => { + props.handleUpdateAlert(true, CustomAlertTypes.ERROR, 'Error creating an institution!', () => setFormData(prevState => ({ ...prevState, isLoading: false }))) + console.error('Error adding new Institution ->', err) + }) + } else { + const jsonParams = { + name: formData.name, + location: formData.location, + email: formData.email, + telephone_number: formData.telephone_number + } + ky.post(urlCreateInstitution, { headers: myHeaders, json: jsonParams }).then((response) => { + setFormData(prevState => ({ ...prevState, isLoading: true })) + response.json().then((jsonResponse: DjangoInstitution) => { + props.handleUpdateAlert(true, CustomAlertTypes.SUCCESS, `Institution ${jsonResponse.name} created!`, () => setFormData(defaultForm)) + }).catch((err) => { + props.handleUpdateAlert(true, CustomAlertTypes.ERROR, 'Error creating an institution!', () => setFormData(prevState => ({ ...prevState, isLoading: false }))) + setFormData(prevState => ({ ...prevState, isLoading: false })) + console.error('Error parsing JSON ->', err) + }) + }).catch((err) => { + props.handleUpdateAlert(true, CustomAlertTypes.ERROR, 'Error creating an institution!', () => setFormData(prevState => ({ ...prevState, isLoading: false }))) + console.error('Error adding new Institution ->', err) + }) + } + } + + /** + * Handle if user Reset or cancel edit + */ + const handleCancelForm = () => { + if (props.institutionToEdit) { + props.handleResetInstitutionToEdit(() => setFormData(defaultForm)) + } else { + setFormData(defaultForm) + } + } + + /** + * use effect to handle if a institution for edit is sent + */ + useEffect(() => { + if (props.institutionToEdit) { + setFormData({ + ...props.institutionToEdit, + id: props.institutionToEdit?.id, + isLoading: false + }) + } + }, [props.institutionToEdit]) + return ( + <> +
+ + + + + + + + + ) +} + +export default InstitutionForm diff --git a/src/frontend/static/frontend/src/components/institutions/InstitutionModal.tsx b/src/frontend/static/frontend/src/components/institutions/InstitutionModal.tsx new file mode 100644 index 00000000..8ae7c689 --- /dev/null +++ b/src/frontend/static/frontend/src/components/institutions/InstitutionModal.tsx @@ -0,0 +1,256 @@ +import React, { useContext, useEffect, useRef, useState } from 'react' +import { + ModalHeader, + ModalContent, + Modal, + Icon, + Segment, + Table, + Button, + Select +} from 'semantic-ui-react' +import { DjangoInstitutionUser } from '../../utils/django_interfaces' +import { CustomAlertTypes, Nullable, SemanticListItem } from '../../utils/interfaces' +import { PaginatedTable } from '../common/PaginatedTable' +import { TableCellWithTitle } from '../common/TableCellWithTitle' +import { InstitutionTableData } from './InstitutionsPanel' +import ky from 'ky' +import { getDjangoHeader } from '../../utils/util_functions' +import { CurrentUserContext } from '../Base' + +declare const urlGetUsersCandidates: string +declare const urlEditInstitutionAdmin: string +declare const urlNonUserListInstitution: string +declare const urlAddRemoveUserToInstitution: string + +export interface InstitutionModalState { + isOpen: boolean, + institution: Nullable +} + +interface Props extends InstitutionModalState { + /* Close modal function */ + handleCloseModal: () => void, + handleChangeConfirmModalState: (setOption: boolean, headerText: string, contentText: string, onConfirm: Function) => void, + handleUpdateAlert(isOpen: boolean, type: CustomAlertTypes, message: string, callback: Nullable): void, +} + +/** + * Institution modal + * @param {Props} props Props for component + * @returns Component + */ +export const InstitutionModal = (props: Props) => { + const [userIdToAdd, setUserIdToAdd] = useState(0) + const [userList, setUserList] = useState([]) + const abortController = useRef(new AbortController()) + const currentUser = useContext(CurrentUserContext) + + /** + * Function to add user to institution. + */ + const handleAddUser = () => { + if (userIdToAdd) { + const myHeaders = getDjangoHeader() + const body = { + userId: userIdToAdd, + isAdding: true, + institutionId: props.institution?.id + } + ky.post(urlAddRemoveUserToInstitution, { headers: myHeaders, signal: abortController.current.signal, json: body }).then((response) => { + response.json().then(() => { + usersListNonInInstitution() + }).catch((err) => { + console.error('Error parsing JSON ->', err) + }) + }).catch((err) => { + console.error('Error getting users ->', err) + }) + } + } + + /** + * Function to remove user from institution. + * @param idUserCandidate user id to remove from institution. + */ + const handleRemoveUser = (idUserCandidate: number) => { + const myHeaders = getDjangoHeader() + const body = { + userId: idUserCandidate, + isAdding: false, + institutionId: props.institution?.id + } + ky.post(urlAddRemoveUserToInstitution, { headers: myHeaders, signal: abortController.current.signal, json: body }).then((response) => { + response.json().then(() => { + usersListNonInInstitution() + }).catch((err) => { + console.error('Error parsing JSON ->', err) + }) + }).catch((err) => { + console.error('Error getting users ->', err) + }) + } + + /** + * Function to manage admin. + * @param adminSwitched switch if true is going to be admin, false if going to be normal user + * @param idInstitution institution id. + */ + const handleSwitchUserAdmin = (adminSwitched: boolean, idInstitution: number) => { + const myHeaders = getDjangoHeader() + const editUrl = `${urlEditInstitutionAdmin}/${idInstitution}/` + + ky.patch(editUrl, { headers: myHeaders }).then((response) => { + response.json().then(() => { + props.handleUpdateAlert(true, CustomAlertTypes.SUCCESS, adminSwitched ? 'User is admin!' : 'User is not admin!', null) + }).catch((err) => { + props.handleUpdateAlert(true, CustomAlertTypes.ERROR, 'Error for switch role!', null) + console.error('Error parsing JSON ->', err) + }) + }).catch((err) => { + props.handleUpdateAlert(true, CustomAlertTypes.ERROR, 'Error for switch role!', null) + console.error('Error adding new Institution ->', err) + }) + } + + /** + * Function to search users that are not in institution. + */ + const usersListNonInInstitution = () => { + const myHeaders = getDjangoHeader() + + const editUrl = `${urlNonUserListInstitution}/${props.institution?.id}/` + + ky.get(editUrl, { headers: myHeaders, signal: abortController.current.signal }).then((response) => { + response.json().then((jsonResponse: { id: number, username: string }[]) => { + setUserList(jsonResponse.map(user => ({ key: user.id.toString(), value: user.id.toString(), text: user.username }))) + }).catch((err) => { + console.error('Error parsing JSON ->', err) + }) + }).catch((err) => { + console.error('Error getting users ->', err) + }) + } + + useEffect(() => { + if (props.institution?.is_user_admin && props.institution?.id) { + usersListNonInInstitution() + } + + return () => { + abortController.current.abort() + } + }, [props.institution?.id]) + + return ( + } + > + {props.institution?.name} + + +