Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
acf9b17
initial implementation for DuosClient
bistline Jan 13, 2026
a26aa09
refactoring method names, adding user management
bistline Jan 13, 2026
dc126e1
adding registration migration, updating headers/schema for POST/PUT r…
bistline Jan 20, 2026
95f1d82
test updates, fixing consentGroups format
bistline Jan 20, 2026
fce2c88
adding stub service
bistline Jan 20, 2026
c1f9206
switching to Faraday to handle multipart form posts to DUOS
bistline Jan 27, 2026
88520d5
using study endpoints for updates/deletes, wiring in validation, test…
bistline Jan 27, 2026
6c14fe1
finishing test coverage
bistline Jan 27, 2026
966c059
finishing test coverage
bistline Jan 27, 2026
6c0b73b
finalizing update requests, minor refactoring & test fixes
bistline Feb 2, 2026
0554560
Adding duos study URL helper
bistline Feb 2, 2026
8e5dbe0
Leave duos identifiers in place in case study is not deleted on redac…
bistline Feb 2, 2026
1ce411f
Clear out duos IDs only on redaction in non-prod envs, remove client-…
bistline Feb 2, 2026
2fcd8c7
Clear out duos IDs only on redaction in non-prod envs, remove client-…
bistline Feb 2, 2026
e218aac
Addressing PR comments
bistline Feb 3, 2026
b90fdb9
fixing test regressions re: test hostname
bistline Feb 3, 2026
08467ab
Merge pull request #2345 from broadinstitute/jb-duos-registry-service
bistline Feb 3, 2026
e509327
Removing all AppCues integration (SCP-6095)
bistline Feb 3, 2026
4df738d
Merge pull request #2347 from broadinstitute/jb-appcues-removal
bistline Feb 3, 2026
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 Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ gem 'benchmark'
gem 'drb'
gem 'reline'
gem 'irb'
gem 'json_schemer'
gem 'faraday-multipart', require: 'faraday/multipart'

group :development, :test do
# Access an IRB console on exception pages or by using <%= console %> in views
Expand Down
9 changes: 9 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ GEM
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
hana (1.3.7)
hashie (4.1.0)
http-accept (1.7.0)
http-cookie (1.0.3)
Expand All @@ -254,6 +255,11 @@ GEM
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (2.6.2)
json_schemer (2.5.0)
bigdecimal
hana (~> 1.3)
regexp_parser (~> 2.0)
simpleidn (~> 0.2)
jwt (2.7.0)
listen (3.5.1)
rb-fsevent (~> 0.10, >= 0.10.3)
Expand Down Expand Up @@ -489,6 +495,7 @@ GEM
simplecov-html (0.13.1)
simplecov-lcov (0.8.0)
simplecov_json_formatter (0.1.4)
simpleidn (0.2.3)
sprockets (4.2.1)
concurrent-ruby (~> 1.0)
rack (>= 2.2.4, < 4)
Expand Down Expand Up @@ -575,6 +582,7 @@ DEPENDENCIES
drb
exponential-backoff
factory_bot_rails
faraday-multipart
flamegraph
font-awesome-sass!
gibberish
Expand All @@ -587,6 +595,7 @@ DEPENDENCIES
jquery-datatables-rails!
jquery-fileupload-rails
jquery-rails
json_schemer
listen
logger
memory_profiler
Expand Down
44 changes: 0 additions & 44 deletions app/javascript/lib/metrics-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,6 @@ export function logClick(event) {
return
}

if (window.Appcues) {
logAppcuesClicks()
}

// we use closest() so we don't lose clicks on, e.g. icons within a link/button
// (and we have to use $.closest since IE still doesn't have built-in support for it)
if (target.closest('a').length) {
Expand All @@ -115,44 +111,6 @@ export function logClick(event) {
}
}

/**
* Logs Appcues public events to Mixpanel
* https://docs.appcues.com/article/161-javascript-api
*
* Event prop building borrowed from Terra UI
* https://github.com/DataBiosphere/terra-ui/pull/2463/files
*/
function logAppcuesClicks() {
window.Appcues.on('all', (eventName, event) => {
const eventProps = {
'appcues.flowId': event.flowId,
'appcues.flowName': event.flowName,
'appcues.flowType': event.flowType,
'appcues.flowVersion': event.flowVersion,
'appcues.id': event.id,
'appcues.interaction.category': event.interaction?.category,
'appcues.interaction.destination': event.interaction?.destination,
'appcues.interaction.element': event.interaction?.element,
'appcues.interaction.fields': JSON.stringify(event.interaction?.fields),
'appcues.interaction.formId': event.interaction?.formId,
'appcues.interaction.text': event.interaction?.text, // not documented by Appcues, but observed and useful
'appcues.interactionType': event.interactionType,
'appcues.localeId': event.localeId,
'appcues.localeName': event.localeName,
'appcues.name': event.name,
'appcues.sessionId': event.sessionId,
'appcues.stepChildId': event.stepChildId,
'appcues.stepChildNumber': event.stepChildNumber,
'appcues.stepId': event.stepId,
'appcues.stepNumber': event.stepNumber,
'appcues.stepType': event.stepType,
'appcues.timestamp': event.timestamp
}
log(eventName, eventProps)
log('appcues:event', eventProps)
})
}

/** Log clicks on SVG element of analytics interest */
export function logClickOther(target) {
const props = {
Expand Down Expand Up @@ -522,8 +480,6 @@ export function log(name, props = {}) {

init = Object.assign(init, body)

window.Appcues && window.Appcues.identify(window.SCP.userId)

if (('SCP' in window && !window.SCP.isTest) || metricsApiMock) {
const url = `${bardDomain}/api/event/`
fetch(url, init).then(response => {
Expand Down
117 changes: 117 additions & 0 deletions app/lib/duos_registration_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# service containing business logic for managing Study registrations as datasets in DUOS
class DuosRegistrationService

# pointer to DUOS UI for auto-completing URLs
#
# * *returns*
# - (String) DUOS UI base URL, based on environment
def self.duos_ui_url
Rails.env.production? ? 'https://duos.org' : 'https://duos-k8s.dsde-dev.broadinstitute.org/'
end

# API client
#
# * *returns*
# - (DuosClient)
def self.client
@client ||= DuosClient.new
end

# determine if study is eligible for registering as a dataset in DUOS
# must meet all the following criteria:
# * public
# * initialized
# * has all required metadata for DUOS
#
# * *params*
# - +study+ (Study)
#
# * *returns*
# - (Boolean)
def self.study_eligible?(study)
has_required = required_metadata(study).map do |field, value|
if field == :donor_count
value > 0
else
value.any?
end
end.flatten.uniq == [true]

study.public && study.initialized && study.duos_dataset_id.blank? && has_required
end

# metadata values required for DUOS dataset registration
#
# * *params*
# - +study+ (Study)
#
# * *returns*
# - (Hash)
def self.required_metadata(study)
{
diseases: study.diseases,
species: study.species_list,
donor_count: study.donor_count,
data_types: study.data_types
}
end

# get a list of accessions for studies eligible for DUOS registration
#
# * *returns*
# - (Array<String>) list of study accessions
def self.eligible_studies
studies = Study.where(public: true, initialized: true, duos_dataset_id: nil, duos_study_id: nil)
studies.select { |study| study_eligible?(study) }.map(&:accession)
end

# register a study as a new dataset in DUOS
#
#
# * *params*
# - +study+ (Study)
#
# * *returns*
# - (Hash) DUOS dataset registration object
def self.register_study(study)
raise ArgumentError, "#{study.accession} is not eligible for DUOS registration" unless study_eligible?(study)

begin
dataset = client.create_dataset(study)
ids = client.identifiers_from_dataset(dataset)
study.update(**ids)
Rails.logger.info "Registered #{study.accession} in DUOS as #{ids}"
dataset
rescue ArgumentError => e
Rails.logger.error "Cannot validate #{study.accession} for DUOS: #{e.message}"
rescue Faraday::Error => e
Rails.logger.error "Unable to register #{study.accession} in DUOS: #{e.message} (#{e.try(:response_body)})"
ErrorTracker.report_exception(e, client.issuer, { study: })
nil
end
end

# redact a DUOS dataset registration
# in non-production environments, this is a deletion, otherwise publicVisibility is set to false
#
# * *params*
# - +study+ (Study)
#
# * *returns*
# - (Boolean)
def self.redact_study(study)
if Rails.env.production?
client.update_study(study.duos_study_id, publicVisibility: false)
else
client.delete_study(study.duos_study_id)
study.update(duos_dataset_id: nil, duos_study_id: nil)
end

Rails.logger.info "Redacted #{study.accession} in DUOS"
true
rescue Faraday::Error => e
Rails.logger.error "Unable to redact #{study.accession} in DUOS: (#{e.message}) #{e.try(:response_body)}"
ErrorTracker.report_exception(e, client.issuer, { study: })
false
end
end
Loading
Loading