Skip to content

Commit

Permalink
Merge pull request #1853 from broadinstitute/development
Browse files Browse the repository at this point in the history
Release 1.52.0
  • Loading branch information
ehanna4 authored Aug 9, 2023
2 parents 0a672f5 + 82c2700 commit b53f67b
Show file tree
Hide file tree
Showing 52 changed files with 1,715 additions and 401 deletions.
1 change: 1 addition & 0 deletions .github/workflows/run-all-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ jobs:
-n single-cell-portal-test
- name: Preserve all test logs
uses: actions/upload-artifact@v3
if: always()
with:
name: test-logs
path: |
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/run-orch-smoketest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
-c bin/run_orch_smoke_test.sh
- name: Preserve all test logs
uses: actions/upload-artifact@v3
if: always()
with:
name: test-logs
path: |
Expand Down
52 changes: 45 additions & 7 deletions app/controllers/api/v1/site_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ class SiteController < ApiBaseController
before_action :authenticate_api_user!, only: [:download_data, :stream_data, :get_study_analysis_config,
:submit_study_analysis, :get_study_submissions,
:get_study_submission, :sync_submission_outputs]
before_action :set_study, except: [:studies, :check_terra_tos_acceptance, :analyses, :get_analysis]
before_action :set_study, except: [:studies, :check_terra_tos_acceptance, :analyses, :get_analysis, :renew_user_access_token]
before_action :set_analysis_configuration, only: [:get_analysis, :get_study_analysis_config]
before_action :check_study_detached, only: [:download_data, :stream_data, :get_study_analysis_config,
:submit_study_analysis, :get_study_submissions,
:get_study_submission, :sync_submission_outputs,
:renew_token]
before_action :check_study_view_permission, except: [:studies, :check_terra_tos_acceptance, :analyses, :get_analysis]
:renew_read_only_access_token]
before_action :check_study_view_permission, except: [:studies, :check_terra_tos_acceptance, :analyses, :get_analysis, :renew_user_access_token]
before_action :check_study_compute_permission,
only: [:get_study_analysis_config, :submit_study_analysis, :get_study_submissions,
:get_study_submission, :sync_submission_outputs]
Expand Down Expand Up @@ -162,14 +162,14 @@ def check_terra_tos_acceptance
end
end

swagger_path '/site/studies/{accession}/renew_token' do
swagger_path '/site/studies/{accession}/renew_read_only_access_token' do
operation :get do
key :tags, [
'Site'
]
key :summary, 'Renew a soon-expiring GCS access token for a study'
key :description, 'Get a new 1-hour access token, within the authentication session duration'
key :operationId, 'site_study_renew_token_path'
key :operationId, 'site_study_renew_read_only_access_token'
parameter do
key :name, :accession
key :in, :path
Expand All @@ -195,15 +195,53 @@ def check_terra_tos_acceptance
end
end

def renew_token
def renew_read_only_access_token
renewing_user = "an unauthenticated user (via read-only service account)"
if current_api_user
renewing_user = "user #{current_api_user.id}"
end
Rails.logger.info "Renewing token via SCP API for #{renewing_user} in study #{@study.accession}"
Rails.logger.info "Renewing read only access token via SCP API for #{renewing_user} in study #{@study.accession}"
render json: RequestUtils.get_read_access_token(@study, current_api_user, renew: true)
end


swagger_path '/site/renew_user_access_token' do
operation :get do
key :tags, [
'Site'
]
key :summary, 'Renew the user access token for a signed in user'
key :description, 'Get a new access token'
key :operationId, 'site_renew_user_access_token'
response 200 do
key :description, 'User access token for current signed in user'
end
response 204 do
key :description, 'No user credentials supplied'
end
response 403 do
key :description, ApiBaseController.forbidden('renew access token')
end
response 404 do
key :description, ApiBaseController.not_found(User)
end
response 406 do
key :description, ApiBaseController.not_acceptable
end
end
end

def renew_user_access_token
if current_api_user
Rails.logger.info "Renewing user access token via SCP API for #{current_api_user.id}"
render json: RequestUtils.get_user_access_token(current_api_user)
else
Rails.logger.info "Cannot get a user access token for a user not signed in"
head :no_content
end
end


swagger_path '/site/studies/{accession}/download' do
operation :get do
key :tags, [
Expand Down
160 changes: 158 additions & 2 deletions app/controllers/api/v1/visualization/annotations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ class AnnotationsController < ApiBaseController
end
end

#
def index
render json: AnnotationVizService.available_annotations(@study, cluster: nil, current_user: current_api_user)
end
Expand Down Expand Up @@ -156,6 +155,148 @@ def cell_values
render plain: AnnotationVizService.annotation_cell_values_tsv(@study, cell_cluster, annotation)
end

swagger_path '/studies/{accession}/annotations/facets' do
operation :get do
key :tags, [
'Visualization'
]
key :summary, 'Get facet assignments for a cluster'
key :description, 'Get annotation assignments (i.e facets) for specified cells from a cluster. ' \
'Only applicable to full resolution data (i.e. all cells)'
key :operationId, 'study_annotation_facets_path'
parameter do
key :name, :accession
key :in, :path
key :description, 'Study accession number'
key :example, 'SCP1234'
key :required, true
key :type, :string
end
parameter do
key :name, :annotations
key :in, :query
key :description, 'List of annotations'
key :example, 'cell_type__ontology_label--group--study,disease__ontology_label'
key :required, true
key :type, :string
end
parameter do
key :name, :cluster
key :in, :query
key :description, 'Name of requested cluster'
key :type, :string
key :required, true
end
response 200 do
key :description, 'Array of integer-based annotation assignments for all cells in requested cluster'
schema do
key :title, 'Annotations'
property :cells do
key :type, :array
key :description, 'Array of arrays of integer assignments of each cell for all requested annotations'
items do
key :type, :array
key :example, [[0, 0], [0, 1], [1, 2], [2, 2], [2, 0]]
items do
key :type, :integer
key :minItems, 2
end
end
end
property :facets do
key :type, :array
key :minItems, 2
key :example, [
{
annotation: 'cell_type__ontology_label--group--study',
groups: %w[eosinophil macrophage lymphocyte]
},
{
annotation: 'disease__ontology_label--group--study',
groups: ['Crohn disease', 'acute myeloid leukemia', 'chronic lymphocytic leukemia']
}
].as_json
items do
key :type, :object
property :annotation do
key :type, :string
key :description, 'Annotation identifier'
end
property :groups do
key :type, :array
key :description, 'List of unique values for requested annotation'
key :example, "['eosinophil', 'macrophage', 'lymphocyte']"
items do
key :type, :string
end
end
end
end
end
end
extend SwaggerResponses::StudyControllerResponses
end
end

def facets
cluster = ClusterVizService.get_cluster_group(@study, params)
if cluster.nil?
render json: { error: "Cannot find cluster: #{params[:cluster]}" }, status: :not_found and return
end

# need to check for presence as some clusters will not have them if cells were not found in all_cells_array
unless cluster.indexed
render json: { error: 'Cluster is not indexed' }, status: :bad_request and return
end

if params[:annotations].include?('--numeric--')
render json: { error: 'Cannot use numeric annotations for facets' }, status: :bad_request and return
end

annotations = self.class.get_facet_annotations(@study, cluster, params[:annotations])
missing = self.class.find_missing_annotations(annotations, params[:annotations])
if missing.any?
render json: { error: "Cannot find annotations: #{missing.join(', ')}" }, status: :not_found and return
end

# use new cell index arrays to load data much faster
indexed_cluster_cells = cluster.cell_index_array
annotation_arrays = {}
facets = []
# build arrays of annotation values, and populate facets response array
annotations.each do |annotation|
scope = annotation[:scope]
identifier = annotation[:identifier]

data_obj = scope == 'study' ? @study.cell_metadata.by_name_and_type(annotation[:name], 'group') : cluster
study_file_id = scope == 'study' ? @study.metadata_file.id : cluster.study_file_id
array_query = {
name: annotation[:name], array_type: 'annotations', linear_data_type: data_obj.class.name,
linear_data_id: data_obj.id, study_id: @study.id, study_file_id:
}
annotation_arrays[identifier] = DataArray.concatenate_arrays(array_query)
facets << { annotation: identifier, groups: annotation[:values] }
end

# iterate through indexed_cluster_cells to compute annotation value indices
# value => current entry from indexed_cluster_cells
# index => current position, will also be index of original cell name from cluster_cells
cells = indexed_cluster_cells.map.with_index do |value, index|
facets.map do |facet|
annotation = facet[:annotation]
scope = annotation.split('--').last
if scope == 'study'
label = annotation_arrays[annotation][value] || '--Unspecified--'
else
label = annotation_arrays[annotation][index] || '--Unspecified--'
end
facet[:groups].index(label)
end
end

render json: { cells:, facets: }
end

swagger_path '/studies/{accession}/annotations/gene_lists/{gene_list}' do
operation :get do
key :tags, [
Expand Down Expand Up @@ -210,15 +351,30 @@ def self.get_selected_annotation(study, params)
annot_scope: annot_params[:scope])
end

def self.get_facet_annotations(study, cluster, annot_param)
annotations = annot_param.split(',').map { |annot| convert_annotation_param(annot) }
annotations.map do |annotation|
AnnotationVizService.get_selected_annotation(study, cluster:, fallback: false, **annotation)
end.compact
end

def self.convert_annotation_param(annotation_param)
annot_name, annot_type, annot_scope = annotation_param.split('--')
{ annot_name:, annot_type:, annot_scope: }
end

# parses url params into an object with name, type, and scope keys
def self.get_annotation_params(url_params)
{
{
name: url_params[:annotation_name].blank? ? nil : url_params[:annotation_name],
type: url_params[:annotation_type].blank? ? nil : url_params[:annotation_type],
scope: url_params[:annotation_scope].blank? ? nil : url_params[:annotation_scope]
}
end

def self.find_missing_annotations(annotations, requested)
requested.split(',').reject { |id| annotations.detect { |annot| annot[:identifier] == id } }
end
end
end
end
Expand Down
Loading

0 comments on commit b53f67b

Please sign in to comment.