Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env-example
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ EBI_SEARCH_ENDPOINT=http://example.com # if not specified, www will be used
SECRET_KEY=super_secret_key # if not specified, it uses get_random_secret_key
DJANGO_DEBUG=True # if not specified, it uses DJANGO_DEBUG=False
LOCAL_DEVELOPMENT=True # use this variable if you need the django-debug-toolbar, coverage and mock packages
DOORBELL_API_KEY=your_doorbell_api_key # Doorbell.io API key for feedback integration
DOORBELL_API_ID=your_doorbell_app_id # Doorbell.io application ID
10 changes: 10 additions & 0 deletions kubernetes/helm/templates/rnacentral.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ spec:
env:
- name: RNACENTRAL_ENV
value: {{ .Values.setEnv }}
- name: DOORBELL_API_KEY
valueFrom:
secretKeyRef:
name: doorbell-credentials
key: DOORBELL_API_KEY
- name: DOORBELL_API_ID
valueFrom:
secretKeyRef:
name: doorbell-credentials
key: DOORBELL_API_ID
envFrom:
- secretRef:
name: {{ .Values.database }}
Expand Down
108 changes: 90 additions & 18 deletions rnacentral/apiv1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1244,42 +1244,72 @@ class RelationshipsView(generics.ListAPIView):

def get_queryset(self):
"""Return relationship data for a given URS and taxid from RNA-KG API"""

node_id = f"{self.kwargs['pk']}_{self.kwargs['taxid']}"

# Get the relationships using the original endpoint
rna_kg_url = "https://rna-kg.biodata.di.unimi.it/api/v1/incoming/id"
relationships_params = {
'node_id': node_id,
'node_id_scheme': 'RNAcentral',
'filter_rnacentral_rels': 'false'
}

try:
relationships_response = requests.get(rna_kg_url, params=relationships_params, timeout=10)
relationships_response.raise_for_status()
relationships_data = relationships_response.json()
relationships = relationships_data.get('relationships', [])


# Apply filters if provided
search_query = self.request.query_params.get('search', '').strip().lower()
relationship_type_filter = self.request.query_params.get('relationship_type', '').strip()
source_filter = self.request.query_params.get('source', '').strip()

if search_query or relationship_type_filter or source_filter:
filtered_relationships = []
for rel in relationships:
# Text search filter
if search_query:
node_props = rel.get('node_properties', {})
label = (node_props.get('Label') or rel.get('node_id') or '').lower()
description = (node_props.get('Description') or '').lower()
if search_query not in label and search_query not in description:
continue

# Relationship type filter
if relationship_type_filter:
if rel.get('relationship_type', '') != relationship_type_filter:
continue

# Source filter
if source_filter:
sources = rel.get('relationship_properties', {}).get('Source', [])
if source_filter not in sources:
continue

filtered_relationships.append(rel)
relationships = filtered_relationships

# Create a mock queryset-like object for pagination
class RelationshipQuerySet:
def __init__(self, data):
self.data = data

def __iter__(self):
return iter(self.data)

def __len__(self):
return len(self.data)

def count(self):
return len(self.data)

def __getitem__(self, key):
return self.data[key]

return RelationshipQuerySet(relationships)

except Exception as e:
# Log the error if you have logging set up
# print(f"Error fetching RNA-KG relationships data: {e}")
Expand All @@ -1293,7 +1323,7 @@ def get_rna_sequence_rnakg_id(self):
'node_id': node_id,
'node_id_scheme': 'RNAcentral'
}

try:
node_response = requests.get(node_id_url, params=node_id_params, timeout=10)
if node_response.status_code == 200:
Expand All @@ -1302,22 +1332,64 @@ def get_rna_sequence_rnakg_id(self):
except Exception:
pass
return None


def get_filter_options(self):
"""Extract unique relationship types and sources from the full dataset"""
node_id = f"{self.kwargs['pk']}_{self.kwargs['taxid']}"
rna_kg_url = "https://rna-kg.biodata.di.unimi.it/api/v1/incoming/id"
relationships_params = {
'node_id': node_id,
'node_id_scheme': 'RNAcentral',
'filter_rnacentral_rels': 'false'
}

relationship_types = set()
sources = set()

try:
response = requests.get(rna_kg_url, params=relationships_params, timeout=10)
response.raise_for_status()
relationships = response.json().get('relationships', [])

for rel in relationships:
rel_type = rel.get('relationship_type')
if rel_type:
relationship_types.add(rel_type)

rel_sources = rel.get('relationship_properties', {}).get('Source', [])
for source in rel_sources:
sources.add(source)
except Exception:
pass

return {
'relationship_types': sorted(list(relationship_types)),
'sources': sorted(list(sources))
}

def list(self, request, *args, **kwargs):
"""Override list method to include RNA sequence rnakg_id in response"""
"""Override list method to include RNA sequence rnakg_id and filter options in response"""
queryset = self.filter_queryset(self.get_queryset())

page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
paginated_response = self.get_paginated_response(serializer.data)
# Add the RNA sequence rnakg_id to the response

# Add the RNA sequence rnakg_id and filter options to the response
rna_sequence_rnakg_id = self.get_rna_sequence_rnakg_id()
paginated_response.data['rna_sequence_rnakg_id'] = rna_sequence_rnakg_id


# Only fetch filter options on initial load (no filters applied)
if not any([
request.query_params.get('search'),
request.query_params.get('relationship_type'),
request.query_params.get('source')
]):
paginated_response.data['filter_options'] = self.get_filter_options()

return paginated_response

serializer = self.get_serializer(queryset, many=True)
response_data = {
'results': serializer.data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,94 @@
ctrl.pageSize = 20;
ctrl.showPagination = false;
ctrl.rnaSequenceRnakgId = null;
ctrl.filterText = '';
ctrl.serverSearch = '';
ctrl.pageSizeOptions = [10, 20, 50, 100];

// Dropdown filter options (populated from API)
ctrl.relationshipTypeFilter = '';
ctrl.sourceFilter = '';
ctrl.relationshipTypes = [];
ctrl.sources = [];

ctrl.filterByTargetOrDescription = function(rel) {
// Text filter (client-side for current page)
if (ctrl.filterText) {
var searchText = ctrl.filterText.toLowerCase();
var target = (rel.node_properties.Label || rel.node_id || '').toLowerCase();
var description = (rel.node_properties.Description || '').toLowerCase();
if (target.indexOf(searchText) === -1 && description.indexOf(searchText) === -1) {
return false;
}
}
return true;
};

ctrl.applyDropdownFilters = function() {
ctrl.loadRelationships(1);
};

ctrl.clearAllFilters = function() {
ctrl.filterText = '';
ctrl.serverSearch = '';
ctrl.relationshipTypeFilter = '';
ctrl.sourceFilter = '';
ctrl.loadRelationships(1);
};

ctrl.hasActiveFilters = function() {
return ctrl.filterText || ctrl.serverSearch || ctrl.relationshipTypeFilter || ctrl.sourceFilter;
};

ctrl.onFilterChange = function() {
// Clear server search message when filter text is manually cleared
if (!ctrl.filterText && ctrl.serverSearch) {
ctrl.serverSearch = '';
ctrl.loadRelationships(1);
}
};

ctrl.onSearchKeypress = function(event) {
if (event.keyCode === 13) {
ctrl.searchAll();
}
};

ctrl.searchAll = function() {
ctrl.serverSearch = ctrl.filterText;
ctrl.loadRelationships(1);
};

ctrl.clearFilter = function() {
ctrl.filterText = '';
ctrl.serverSearch = '';
ctrl.loadRelationships(1);
};

ctrl.changePageSize = function() {
ctrl.loadRelationships(1);
};

ctrl.loadRelationships = function(page, append) {
if (!ctrl.taxid || !ctrl.upi) {
return;
}

page = page || 1;
append = append || false;
ctrl.loading = true;
ctrl.error = false;

var relationshipsUrl = '/api/v1/rna/' + ctrl.upi + '/relationships/' + ctrl.taxid + '?page=' + page;

var relationshipsUrl = '/api/v1/rna/' + ctrl.upi + '/relationships/' + ctrl.taxid + '?page=' + page + '&page_size=' + ctrl.pageSize;
if (ctrl.serverSearch) {
relationshipsUrl += '&search=' + encodeURIComponent(ctrl.serverSearch);
}
if (ctrl.relationshipTypeFilter) {
relationshipsUrl += '&relationship_type=' + encodeURIComponent(ctrl.relationshipTypeFilter);
}
if (ctrl.sourceFilter) {
relationshipsUrl += '&source=' + encodeURIComponent(ctrl.sourceFilter);
}

$http.get(relationshipsUrl)
.then(function(response) {
Expand All @@ -55,6 +131,13 @@
ctrl.currentPage = page;
ctrl.showPagination = ctrl.totalCount > ctrl.pageSize;
ctrl.rnaSequenceRnakgId = response.data.rna_sequence_rnakg_id;

// Populate filter options from API (only on initial load)
if (response.data.filter_options) {
ctrl.relationshipTypes = response.data.filter_options.relationship_types || [];
ctrl.sources = response.data.filter_options.sources || [];
}

ctrl.loading = false;
})
.catch(function(error) {
Expand Down Expand Up @@ -147,6 +230,53 @@
<span ng-if="ctrl.showPagination">Showing {{ctrl.getStartRecord()}} - {{ctrl.getEndRecord()}} of {{ctrl.totalCount}} relationships.</span>
</p>

<div class="row" style="margin-bottom: 10px;">
<div class="col-sm-5">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-search"></i></span>
<input type="text" class="form-control" placeholder="Search by target or description..." ng-model="ctrl.filterText" ng-keypress="ctrl.onSearchKeypress($event)" ng-change="ctrl.onFilterChange()">
<span class="input-group-btn">
<button class="btn btn-primary" type="button" ng-click="ctrl.searchAll()" title="Search all pages">
Search all
</button>
</span>
</div>
<small class="text-muted" ng-if="ctrl.filterText && !ctrl.serverSearch">
Filtering current page. Press Enter or click "Search all" to search all pages.
</small>
<small class="text-muted" ng-if="ctrl.serverSearch">
Showing results matching "{{ctrl.serverSearch}}" across all pages.
</small>
</div>
<div class="col-sm-3">
<select class="form-control" ng-model="ctrl.relationshipTypeFilter" ng-change="ctrl.applyDropdownFilters()">
<option value="">All relationship types</option>
<option ng-repeat="type in ctrl.relationshipTypes" ng-value="type">{{type}}</option>
</select>
</div>
<div class="col-sm-2">
<select class="form-control" ng-model="ctrl.sourceFilter" ng-change="ctrl.applyDropdownFilters()">
<option value="">All sources</option>
<option ng-repeat="source in ctrl.sources" ng-value="source">{{source}}</option>
</select>
</div>
<div class="col-sm-2">
<div class="input-group">
<select class="form-control" ng-model="ctrl.pageSize" ng-change="ctrl.changePageSize()">
<option ng-repeat="size in ctrl.pageSizeOptions" ng-value="size">{{size}}</option>
</select>
<span class="input-group-addon">/ page</span>
</div>
</div>
</div>
<div class="row" style="margin-bottom: 15px;" ng-if="ctrl.hasActiveFilters()">
<div class="col-sm-12">
<button class="btn btn-default btn-sm" type="button" ng-click="ctrl.clearAllFilters()">
<i class="fa fa-times"></i> Clear all filters
</button>
</div>
</div>

<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
Expand All @@ -159,7 +289,7 @@
</tr>
</thead>
<tbody>
<tr ng-repeat="rel in ctrl.relationships">
<tr ng-repeat="rel in ctrl.relationships | filter:ctrl.filterByTargetOrDescription">
<td>
<a ng-if="rel.rel_rnakg_id" href="https://rna-kg.biodata.di.unimi.it/relationship/{{rel.rel_rnakg_id}}" target="_blank" class="label label-info" style="text-decoration: none;">
{{ rel.relationship_type || "Unknown" }}
Expand Down Expand Up @@ -234,9 +364,18 @@
</div>
</div>

<div ng-if="!ctrl.loading && !ctrl.error && (!ctrl.relationships || ctrl.relationships.length === 0)" class="alert alert-info">
<i class="fa fa-info-circle"></i>
No relationship data is currently available for this RNA sequence.
<div ng-if="!ctrl.loading && !ctrl.error && (!ctrl.relationships || ctrl.relationships.length === 0)">
<div ng-if="ctrl.hasActiveFilters()" class="alert alert-warning">
<i class="fa fa-filter"></i>
No relationships match the current filters.
<button class="btn btn-default btn-sm" style="margin-left: 10px;" type="button" ng-click="ctrl.clearAllFilters()">
<i class="fa fa-times"></i> Clear all filters
</button>
</div>
<div ng-if="!ctrl.hasActiveFilters()" class="alert alert-info">
<i class="fa fa-info-circle"></i>
No relationship data is currently available for this RNA sequence.
</div>
</div>
`
};
Expand Down
3 changes: 3 additions & 0 deletions rnacentral/portal/static/r2dt-web/dist/r2dt-web.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
1 change: 1 addition & 0 deletions rnacentral/portal/static/r2dt-web/dist/r2dt-web.js.map

Large diffs are not rendered by default.

Loading