renalware-core 2.0.28 → 2.0.30

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7eb14ab1adcb2a713e62aca7a0f77d4abe7714a5d22c5af5623dfd9cd528656
4
- data.tar.gz: b58bef85d36bdb2af561a3229a51aac0bb690b3498cab8804076fdc4a5a5d149
3
+ metadata.gz: b29e1fc4d6a1bb7e86fdcf56f18246fd8e39119376366f7017838cedfd4b087f
4
+ data.tar.gz: d5cd02cdd2dcb4b42cd8c5d0ecc32f7dfce445e45a00adee57556b49adba4ad1
5
5
  SHA512:
6
- metadata.gz: f89c0255c15fa0edeca0f64fc3c07fa4a71b2a5103d8491cfe350ceb0adf23dd9665a10ac8ce003a2f95d5a8327c1c9325626311bb94dc8b033511d34405c75c
7
- data.tar.gz: 776a1be9ade866026a0ed0cc7956eeb7983194c6da7a57b192a74ca16b15721865fdfda5d1442826a0673b09e27caa60922550854352f5c05c239cbfd4f1cf52
6
+ metadata.gz: 529f354080faf23959520fbf19b5db5da08e2bbf9cacab2d76f35964c807e43ad509ca2f7259942ca336fff48489224b39ceb4db9d23c789604455d6020114a1
7
+ data.tar.gz: a5f9c60d939e1c7afde8800f06da5d9d65b61e373d34530a5e2f6cdd2354f2cc86c9d1455193d7dc7a45531204b845cb6f64d11363fabf94c4ca609da166a8cd
@@ -49,8 +49,7 @@ article.clinical-allergies {
49
49
  }
50
50
  }
51
51
 
52
- .clinical-header,
53
- .research-study-participation-alerts {
52
+ .clinical-header {
54
53
  background-color: $table-border-colour;
55
54
  padding: 0em .2em .2em .6em;
56
55
 
@@ -125,25 +124,17 @@ article.clinical-allergies {
125
124
  }
126
125
  }
127
126
 
128
- .research-study-participation-alerts {
129
- .lozenge {
130
- ul {
131
- li {
132
- background-color: $nhs-pink;
133
- color: $white;
134
- }
135
- }
136
- }
137
- }
138
-
139
127
  .patient-alerts {
140
- @media pring {
128
+ color: $white;
129
+
130
+ @media print {
141
131
  display: none;
142
132
  }
143
133
 
144
134
  &.lozenge {
145
135
  ul {
146
136
  margin: 0;
137
+ //font-size: 0.9rem;
147
138
 
148
139
  li {
149
140
  background-color: $nhs-orange;
@@ -157,29 +148,33 @@ article.clinical-allergies {
157
148
  background-color: darken($nhs-orange, 5);
158
149
  }
159
150
 
160
- &:hover,
161
- span:hover {
162
- color: white
163
- }
164
-
165
151
  &.urgent {
166
152
  background-color: $nhs-red;
167
153
 
168
154
  &:hover {
169
- background-color: darken($nhs-red, 5);
155
+ background-color: darken($nhs-red, 7);
170
156
  }
171
157
  }
172
158
 
173
159
  &.research-study-participant {
174
- background-color: $nhs-green;
160
+ background-color: darken($nhs-green, 3);
161
+ color: $white;
175
162
 
176
163
  &:hover {
177
164
  background-color: darken($nhs-green, 5);
178
165
  }
179
166
 
180
167
  a {
168
+ color: $white;;
169
+ border-bottom: 1px dotted $white
170
+ }
171
+
172
+ .date {
173
+ border-left: 1px solid $mid-grey;
174
+ }
175
+
176
+ span {
181
177
  color: $white;
182
- border-bottom: 1px dotted $light-grey;
183
178
  }
184
179
  }
185
180
 
@@ -187,7 +182,8 @@ article.clinical-allergies {
187
182
  padding: 0 .4rem;
188
183
  }
189
184
 
190
- .body {
185
+ .body,
186
+ .external_application_link {
191
187
  padding-left: 0;
192
188
  }
193
189
 
@@ -196,7 +192,6 @@ article.clinical-allergies {
196
192
  .actions {
197
193
  font-style: italic;
198
194
  border-left: 1px solid $winter-sky;
199
- color: $light-grey;
200
195
  white-space: nowrap;
201
196
 
202
197
  .fa {
@@ -66,7 +66,10 @@ module Renalware
66
66
  def study_params
67
67
  params
68
68
  .require(:research_study)
69
- .permit(:code, :description, :leader, :notes, :started_on, :terminated_on)
69
+ .permit(
70
+ :code, :description, :leader, :notes, :started_on, :terminated_on,
71
+ :application_url
72
+ )
70
73
  end
71
74
  end
72
75
  end
@@ -75,7 +75,7 @@ module Renalware
75
75
  session = session.becomes!(Session::Closed)
76
76
  session.profile = patient.hd_profile
77
77
  session.signed_off_at = Time.zone.now
78
- session.dry_weight = Renalware::Clinical::DryWeight.for_patient(patient).first
78
+ session.dry_weight = most_recent_dry_weight
79
79
  session
80
80
  end
81
81
 
@@ -83,6 +83,13 @@ module Renalware
83
83
  session_type.constantize
84
84
  end
85
85
 
86
+ def most_recent_dry_weight
87
+ Renalware::Clinical::DryWeight
88
+ .for_patient(patient)
89
+ .order(assessed_on: :desc)
90
+ .first
91
+ end
92
+
86
93
  def lookup_access_type_abbreviation(session)
87
94
  return unless session.document&.respond_to?(:info)
88
95
  access_type = Accesses::Type.find_by(name: session.document.info.access_type)
@@ -9,6 +9,7 @@ module Renalware
9
9
  acts_as_paranoid
10
10
  validates :participant_id, presence: true, uniqueness: { scope: :study }
11
11
  validates :study, presence: true
12
+ validates :external_id, uniqueness: true # added by a trigger
12
13
  belongs_to :study, touch: true
13
14
  belongs_to :patient,
14
15
  class_name: "Renalware::Patient",
@@ -18,6 +19,11 @@ module Renalware
18
19
  def to_s
19
20
  patient&.to_s
20
21
  end
22
+
23
+ def external_application_participant_url
24
+ return if study.application_url.blank?
25
+ study.application_url.gsub("{external_id}", external_id.to_s)
26
+ end
21
27
  end
22
28
  end
23
29
  end
@@ -2,5 +2,13 @@ li.patient-alert.research-study-participant
2
2
  i.fa.fa-warning
3
3
  span.title= link_to(participation.study.code, research_study_participants_path(participation.study))
4
4
  span.body participant
5
- span.date= l(participation.joined_on)
6
5
 
6
+ / A Study might have a url linking to an external study application. In this case show the link
7
+ / in the alert.
8
+ - if participation.external_application_participant_url.present?
9
+ span.external_application_link
10
+ = link_to(participation.external_application_participant_url, target: "_blank") do
11
+ i.fa.fa-external-link
12
+ |  Launch application
13
+
14
+ span.date= l(participation.joined_on)
@@ -2,6 +2,15 @@
2
2
  = f.input :code, wrapper: :horizontal_small
3
3
  = f.input :description, wrapper: :horizontal_large
4
4
  = f.input :leader, wrapper: :horizontal_small
5
+ = f.input :application_url,
6
+ wrapper: :horizontal_medium,
7
+ hint: "If there is an an external application associated with this study " \
8
+ "you can enter it here and, for participanting patients, the link " \
9
+ "will appear at the top of patient pages.<br/>" \
10
+ "Use the format https://my-research-app/participants/{external_id}<br/> if you " \
11
+ "The {external_id} placeholder will be replaced with " \
12
+ "research_study_particpant_external_id".html_safe,
13
+ placeholder: "e.g. https://my-research-app/participants/{external_id}"
5
14
  = f.input :started_on, as: :date_picker, wrapper: :horizontal_datepicker
6
15
  = f.input :terminated_on, as: :date_picker, wrapper: :horizontal_datepicker
7
16
  = f.input :notes, as: :text, wrapper: :horizontal_large, input_html: { rows: 6 }
@@ -9,6 +9,7 @@
9
9
  th.col-width-reference-no Hosp no
10
10
  th.col-width-tiny Age
11
11
  th Sex
12
+ th External ID
12
13
  th.col-width-small
13
14
  tbody
14
15
  - participants.each do |participant|
@@ -20,6 +21,7 @@
20
21
  td= participant.patient.hospital_identifiers
21
22
  td= participant.patient.age
22
23
  td= participant.patient.sex
24
+ td= participant.external_id
23
25
  td
24
26
  = link_to "Delete",
25
27
  research_study_participant_path(study, participant),
@@ -0,0 +1,28 @@
1
+ /*
2
+ See https://wiki.postgresql.org/wiki/Pseudo_encrypt
3
+ Encrypt an id eg 101 and generate a unique integer eg
4
+ select pseudo_encrypt(101); # => 1064847687
5
+ pseudo_encrypt will always return the same number for any input integer.
6
+ Useful creating a psuedo-random id that is guaranteed to be unique.
7
+ Could be used in conjunction with a sequence
8
+ */
9
+ CREATE OR REPLACE FUNCTION renalware.pseudo_encrypt(VALUE int) returns int AS $$
10
+ DECLARE
11
+ l1 int;
12
+ l2 int;
13
+ r1 int;
14
+ r2 int;
15
+ i int:=0;
16
+ BEGIN
17
+ l1:= (VALUE >> 16) & 65535;
18
+ r1:= VALUE & 65535;
19
+ WHILE i < 3 LOOP
20
+ l2 := r1;
21
+ r2 := l1 # ((((1366 * r1 + 150889) % 714025) / 714025.0) * 32767)::int;
22
+ l1 := l2;
23
+ r1 := r2;
24
+ i := i + 1;
25
+ END LOOP;
26
+ RETURN ((r1 << 16) + l1);
27
+ END;
28
+ $$ LANGUAGE plpgsql strict immutable;
@@ -0,0 +1,22 @@
1
+ set SEARCH_PATH=renalware,public;
2
+ CREATE OR REPLACE FUNCTION renalware.update_research_study_participants_from_trigger() RETURNS TRIGGER AS $body$
3
+ /*
4
+ TC 05/06/2018
5
+ After a participant is added to a study, assign them an external_id, to be used when sending this
6
+ data for example to an external study application.
7
+ We use pseudo_encrypt() to generate a random id which is guaranteed to be unique as it is based
8
+ on the id. Its not the most secure however as, without a secret, the id can be reverse engineered
9
+ if our pseudo_encrypt sql function open source (which it is). If this is deemed to be a problem
10
+ (our intention at this point is rudimentary obfuscation), a hospital can override replace this
11
+ function with a more secure one.
12
+ An alternative to using a trigger is to use an after_ or before_save hook in Rails. The trigger
13
+ approach is chosen as, unlike a traditional Rails app, some direct data manipulation can be expected
14
+ in Renalware, even if that is just during migration.
15
+ */
16
+ BEGIN
17
+ IF (TG_OP = 'INSERT') THEN
18
+ NEW.external_id = renalware.pseudo_encrypt(NEW.id::integer);
19
+ RETURN NEW;
20
+ END IF;
21
+ RETURN NULL;
22
+ END $body$ LANGUAGE plpgsql VOLATILE COST 100;
@@ -0,0 +1,9 @@
1
+ class CreatePseudoEncryptFunction < ActiveRecord::Migration[5.1]
2
+ def up
3
+ load_function "pseudo_encrypt_v01.sql"
4
+ end
5
+
6
+ def down
7
+ connection.execute("DROP FUNCTION renalware.pseudo_encrypt(int);")
8
+ end
9
+ end
@@ -0,0 +1,40 @@
1
+ class AddExternalIdToResearchStudyParticipants < ActiveRecord::Migration[5.1]
2
+ def change
3
+ # The external_id is for obfuscating the participant id using a random integer.
4
+ # It is used e.g. when passing to an external research application
5
+ add_column(
6
+ :research_study_participants,
7
+ :external_id,
8
+ :integer,
9
+ null: true
10
+ )
11
+
12
+ reversible do |direction|
13
+ direction.up do
14
+ # Create a function the trigger will call..
15
+ load_function("update_research_study_participants_from_trigger_v01.sql")
16
+
17
+ # .. and the trigger when a row is inserted
18
+ load_trigger("update_research_study_participants_trigger_v01.sql")
19
+
20
+ # Populate the just-created external_id column with the correct value that the trigger
21
+ # would otherwise create if it were added in the future.
22
+ connection.execute(
23
+ "UPDATE renalware.research_study_participants SET external_id = renalware.pseudo_encrypt(id::integer) where external_id is NULL;"
24
+ )
25
+ end
26
+ direction.down do
27
+ connection.execute("
28
+ DROP TRIGGER IF EXISTS update_research_study_participants_trigger ON renalware.research_study_participants;
29
+ DROP FUNCTION IF EXISTS update_research_study_participants_from_trigger();
30
+ ")
31
+ end
32
+ end
33
+
34
+ # Now we have created and populated the external_id we need to add a unique index.
35
+ # external_id is guaranteed to be unique because the generating function (called by the
36
+ # trigger when a row is inserted) is based on the id.
37
+ # See db/functions/update_research_study_participants_from_trigger_v01
38
+ add_index :research_study_participants, :external_id, unique: true
39
+ end
40
+ end
@@ -0,0 +1,5 @@
1
+ class AddApplicationUrlToResearchStudies < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :research_studies, :application_url, :string, null: true
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ -- Create the trigger which will call out function every time a row in pathology_observations
2
+ -- is inserted or updated
3
+ DROP TRIGGER IF EXISTS update_research_study_participants_trigger ON renalware.research_study_participants;
4
+ CREATE TRIGGER update_research_study_participants_trigger
5
+ BEFORE INSERT ON renalware.research_study_participants FOR EACH ROW
6
+ EXECUTE PROCEDURE renalware.update_research_study_participants_from_trigger();
@@ -23,18 +23,35 @@ module CoreExtensions
23
23
  load_sql_file(DatabaseObjectPaths.triggers, filename)
24
24
  end
25
25
 
26
- def load_sql_file(path, filename)
27
- connection.execute(File.read(path.join(filename)))
26
+ def load_sql_file(paths, filename)
27
+ found = false
28
+ paths.each do |path|
29
+ file_path = path.join(filename)
30
+ if File.exist?(file_path)
31
+ connection.execute(File.read(file_path))
32
+ found = true
33
+ end
34
+ end
35
+ unless found
36
+ raise "Cannot file #{filename} in #{paths.join(', ')}"
37
+ end
28
38
  end
29
39
 
40
+ # Make sure to look in the host Rails app as well as in the engine
30
41
  class DatabaseObjectPaths
31
42
  class << self
32
43
  def triggers
33
- Renalware::Engine.root.join("db", "triggers")
44
+ [
45
+ Rails.root.join("db", "triggers"),
46
+ Renalware::Engine.root.join("db", "triggers")
47
+ ]
34
48
  end
35
49
 
36
50
  def functions
37
- Renalware::Engine.root.join("db", "functions")
51
+ [
52
+ Rails.root.join("db", "functions"),
53
+ Renalware::Engine.root.join("db", "functions")
54
+ ]
38
55
  end
39
56
  end
40
57
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Renalware
4
- VERSION = "2.0.28"
4
+ VERSION = "2.0.30"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: renalware-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.28
4
+ version: 2.0.30
5
5
  platform: ruby
6
6
  authors:
7
7
  - Airslie
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-31 00:00:00.000000000 Z
11
+ date: 2018-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: active_type
@@ -2878,12 +2878,14 @@ files:
2878
2878
  - db/functions/import_practices_csv_v01.sql
2879
2879
  - db/functions/preprocess_hl7_message_v01.sql
2880
2880
  - db/functions/preprocess_hl7_message_v02.sql
2881
+ - db/functions/pseudo_encrypt_v01.sql
2881
2882
  - db/functions/refresh_all_matierialized_views_v01.sql
2882
2883
  - db/functions/refresh_current_observation_set_v01.sql
2883
2884
  - db/functions/update_current_observation_set_from_trigger_v01.sql
2884
2885
  - db/functions/update_current_observation_set_from_trigger_v02.sql
2885
2886
  - db/functions/update_current_observation_set_from_trigger_v03.sql
2886
2887
  - db/functions/update_current_observation_set_from_trigger_v04.sql
2888
+ - db/functions/update_research_study_participants_from_trigger_v01.sql
2887
2889
  - db/migrate/20141004150240_create_ethnicities.rb
2888
2890
  - db/migrate/20141010170329_create_death_causes.rb
2889
2891
  - db/migrate/20141020170329_create_patients.rb
@@ -3262,6 +3264,9 @@ files:
3262
3264
  - db/migrate/20180511171835_create_unique_indexes_on_obr_obr_codes.rb
3263
3265
  - db/migrate/20180514151627_create_system_messages.rb
3264
3266
  - db/migrate/20180516111411_create_view_patient_current_modalities.rb
3267
+ - db/migrate/20180605114332_create_pseudo_encrypt_function.rb
3268
+ - db/migrate/20180605141806_add_external_id_to_research_study_participants.rb
3269
+ - db/migrate/20180605175211_add_application_url_to_research_studies.rb
3265
3270
  - db/seeds.rb
3266
3271
  - db/seeds/default/accesses/access_pd_catheter_insertion_techniques.csv
3267
3272
  - db/seeds/default/accesses/access_pd_catheter_insertion_techniques.rb
@@ -3346,6 +3351,7 @@ files:
3346
3351
  - db/static.obsolete/medication_routes.yml
3347
3352
  - db/triggers/feed_messages_preprocessing_trigger_v01.sql
3348
3353
  - db/triggers/update_current_observation_set_trigger_v01.sql
3354
+ - db/triggers/update_research_study_participants_trigger_v01.sql
3349
3355
  - db/views/medication_current_prescriptions_v01.sql
3350
3356
  - db/views/pathology_current_key_observation_sets_v01.sql
3351
3357
  - db/views/pathology_current_key_observation_sets_v02.sql