mirah-ruby 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.circleci/config.yml +93 -0
- data/.gitignore +14 -0
- data/.overcommit.yml +48 -0
- data/.overcommit_gems.rb +9 -0
- data/.rspec +3 -0
- data/.rubocop.yml +54 -0
- data/.yardopts +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +187 -0
- data/Rakefile +24 -0
- data/bin/autocorrect +28 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/setup_overcommit +12 -0
- data/lib/mirah.rb +44 -0
- data/lib/mirah/base_input_object.rb +65 -0
- data/lib/mirah/base_object.rb +98 -0
- data/lib/mirah/client.rb +420 -0
- data/lib/mirah/collection.rb +98 -0
- data/lib/mirah/data/appointment.rb +65 -0
- data/lib/mirah/data/organization.rb +31 -0
- data/lib/mirah/data/page_info.rb +40 -0
- data/lib/mirah/data/patient.rb +46 -0
- data/lib/mirah/data/practitioner.rb +53 -0
- data/lib/mirah/errors.rb +39 -0
- data/lib/mirah/filters.rb +7 -0
- data/lib/mirah/filters/appointment_filters.rb +16 -0
- data/lib/mirah/filters/organization_filters.rb +16 -0
- data/lib/mirah/filters/paging.rb +33 -0
- data/lib/mirah/filters/patient_filters.rb +16 -0
- data/lib/mirah/filters/practitioner_filters.rb +16 -0
- data/lib/mirah/graphql.rb +33 -0
- data/lib/mirah/graphql/fragments.rb +97 -0
- data/lib/mirah/graphql/mutations.rb +72 -0
- data/lib/mirah/graphql/queries.rb +196 -0
- data/lib/mirah/inputs.rb +7 -0
- data/lib/mirah/inputs/appointment_input.rb +40 -0
- data/lib/mirah/inputs/organization_input.rb +20 -0
- data/lib/mirah/inputs/patient_input.rb +40 -0
- data/lib/mirah/inputs/practitioner_input.rb +44 -0
- data/lib/mirah/push_result.rb +40 -0
- data/lib/mirah/serializers.rb +57 -0
- data/lib/mirah/version.rb +5 -0
- data/mirah-ruby.gemspec +41 -0
- data/schema.json +8936 -0
- metadata +221 -0
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
|
8
|
+
task default: :spec
|
9
|
+
|
10
|
+
require 'graphql/client'
|
11
|
+
require 'graphql/client/http'
|
12
|
+
|
13
|
+
task :dump_schema do
|
14
|
+
http = GraphQL::Client::HTTP.new('http://localhost:3000/integration_api/graphqlschema')
|
15
|
+
::GraphQL::Client.dump_schema(http, 'schema.json')
|
16
|
+
end
|
17
|
+
|
18
|
+
# Run by typing: rake yard
|
19
|
+
|
20
|
+
PATH = File.dirname(__FILE__)
|
21
|
+
|
22
|
+
require 'yard'
|
23
|
+
|
24
|
+
YARD::Rake::YardocTask.new
|
data/bin/autocorrect
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
config = YAML.load_file('.rubocop.yml')
|
7
|
+
|
8
|
+
# Check if the file is excluded from all cops
|
9
|
+
exclude_file = lambda do |file|
|
10
|
+
config['AllCops']['Exclude'].any? do |pattern|
|
11
|
+
File.fnmatch(pattern, file, File::FNM_PATHNAME)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
files = `git diff --name-only --diff-filter=ACMR --ignore-submodules=all --staged`
|
16
|
+
.split(/\n/)
|
17
|
+
.reject(&exclude_file)
|
18
|
+
|
19
|
+
if files.empty?
|
20
|
+
puts 'There is nothing staged to correct!'
|
21
|
+
exit
|
22
|
+
end
|
23
|
+
|
24
|
+
system({ 'BUNDLE_GEMFILE' => '.overcommit_gems.rb' },
|
25
|
+
"bundle exec rubocop --auto-correct --config=.rubocop.yml #{files.join(' ')}")
|
26
|
+
|
27
|
+
puts
|
28
|
+
puts 'Corrections applied. Review unstaged changes and apply any required manual corrections.'
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "mirah"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
# path to your application root.
|
5
|
+
APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
|
6
|
+
|
7
|
+
Dir.chdir APP_ROOT do
|
8
|
+
puts "== Installing overcommit =="
|
9
|
+
system({"BUNDLE_GEMFILE" => ".overcommit_gems.rb"}, "bundle check || bundle install")
|
10
|
+
system({"BUNDLE_GEMFILE" => ".overcommit_gems.rb"}, "bundle exec overcommit --install")
|
11
|
+
system({"BUNDLE_GEMFILE" => ".overcommit_gems.rb"}, "bundle exec overcommit --sign")
|
12
|
+
end
|
data/lib/mirah.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
require 'graphql/client'
|
6
|
+
require 'graphql/client/http'
|
7
|
+
|
8
|
+
require 'active_support/hash_with_indifferent_access'
|
9
|
+
require 'active_support/inflector'
|
10
|
+
|
11
|
+
require 'mirah/version'
|
12
|
+
require 'mirah/errors'
|
13
|
+
require 'mirah/serializers'
|
14
|
+
require 'mirah/base_object'
|
15
|
+
require 'mirah/collection'
|
16
|
+
require 'mirah/push_result'
|
17
|
+
require 'mirah/graphql'
|
18
|
+
|
19
|
+
Dir[File.dirname(__FILE__) + '/mirah/data/**/*.rb'].sort.each do |file|
|
20
|
+
require file
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'mirah/graphql/fragments'
|
24
|
+
require 'mirah/graphql/queries'
|
25
|
+
require 'mirah/graphql/mutations'
|
26
|
+
|
27
|
+
require 'mirah/filters'
|
28
|
+
|
29
|
+
Dir[File.dirname(__FILE__) + '/mirah/filters/**/*.rb'].sort.each do |file|
|
30
|
+
require file
|
31
|
+
end
|
32
|
+
|
33
|
+
require 'mirah/inputs'
|
34
|
+
require 'mirah/base_input_object'
|
35
|
+
|
36
|
+
Dir[File.dirname(__FILE__) + '/mirah/inputs/**/*.rb'].sort.each do |file|
|
37
|
+
require file
|
38
|
+
end
|
39
|
+
|
40
|
+
require 'mirah/client'
|
41
|
+
|
42
|
+
# Mirah ruby interoperability
|
43
|
+
module Mirah
|
44
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mirah
|
4
|
+
# This object provides a DSL by which you can easy create plain ruby objects with `attr_accessor` that will
|
5
|
+
# automatically be transformed back and forth from a GraphQL endpoint. This includes changing the format of the
|
6
|
+
# key from `ruby_style` to `graphqlStyle` and back again, and extra serialization where necessary for scalar types
|
7
|
+
# such as dates.
|
8
|
+
class BaseInputObject
|
9
|
+
def initialize(attrs = {})
|
10
|
+
attrs.each do |key, value|
|
11
|
+
raise Errors::InvalidParameter.new(key), "#{key} is not a valid parameter" unless respond_to? "#{key}="
|
12
|
+
|
13
|
+
send("#{key}=", value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def valid?
|
18
|
+
self.class.inputs.all? do |input|
|
19
|
+
!input[:required] || send(input[:name])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate!
|
24
|
+
self.class.inputs.all? do |input|
|
25
|
+
if input[:required] && !send(input[:name])
|
26
|
+
raise Errors::MissingParameter.new(input[:name]), "#{input[:name]} is missing"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_graphql_hash
|
32
|
+
self.class.inputs.each_with_object({}) do |input, obj|
|
33
|
+
value = input[:serializer].serialize(send(input[:name]))
|
34
|
+
obj[input[:graphql_name]] = value if value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.from_graphql_hash(graphql_data)
|
39
|
+
return nil unless graphql_data
|
40
|
+
|
41
|
+
attrs = inputs.each_with_object({}) do |input, obj|
|
42
|
+
value = graphql_data[input[:graphql_name]]
|
43
|
+
obj[input[:name]] = input[:serializer].deserialize(value)
|
44
|
+
end
|
45
|
+
|
46
|
+
new(attrs)
|
47
|
+
end
|
48
|
+
|
49
|
+
# @private
|
50
|
+
def self.inputs
|
51
|
+
@inputs ||= []
|
52
|
+
end
|
53
|
+
|
54
|
+
# @private
|
55
|
+
def self.input(name, required:, serializer: Serializers::ScalarSerializer)
|
56
|
+
inputs.push(
|
57
|
+
{ name: name,
|
58
|
+
serializer: serializer,
|
59
|
+
required: required,
|
60
|
+
graphql_name: name.to_s.camelize(:lower) }
|
61
|
+
)
|
62
|
+
attr_accessor name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mirah
|
4
|
+
# This object provides a DSL by which you can easy create plain ruby objects with `attr_accessor` that will
|
5
|
+
# automatically be transformed back and forth from a GraphQL endpoint. This includes changing the format of the
|
6
|
+
# key from `ruby_style` to `graphqlStyle` and back again, and extra serialization where necessary for scalar types
|
7
|
+
# such as dates.
|
8
|
+
class BaseObject
|
9
|
+
def initialize(attrs = {})
|
10
|
+
attrs.each do |key, value|
|
11
|
+
send("#{key}=", value)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_graphql_hash
|
16
|
+
self.class.attributes.each_with_object({}) do |attribute, obj|
|
17
|
+
value = attribute[:serializer].serialize(send(attribute[:name]))
|
18
|
+
write_value(obj, value, attribute)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.from_graphql_hash(graphql_data)
|
23
|
+
return nil unless graphql_data
|
24
|
+
|
25
|
+
attrs = attributes.each_with_object({}) do |attribute, obj|
|
26
|
+
value = read_value(graphql_data, attribute)
|
27
|
+
obj[attribute[:name]] = attribute[:serializer].deserialize(value)
|
28
|
+
end
|
29
|
+
|
30
|
+
new(attrs)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @private
|
34
|
+
def self.attributes
|
35
|
+
@attributes ||= []
|
36
|
+
end
|
37
|
+
|
38
|
+
# @private
|
39
|
+
def self.attribute(name, serializer: Serializers::ScalarSerializer, target: name, path: nil)
|
40
|
+
attributes.push(
|
41
|
+
{
|
42
|
+
name: name,
|
43
|
+
serializer: serializer,
|
44
|
+
path: path,
|
45
|
+
graphql_name: target.to_s.camelize(:lower)
|
46
|
+
}
|
47
|
+
)
|
48
|
+
attr_accessor name
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def write_value(object, value, attribute)
|
54
|
+
object_to_write = object
|
55
|
+
|
56
|
+
object_to_write = write_nested_value(object, attribute[:path].dup) if attribute[:path]
|
57
|
+
|
58
|
+
object_to_write[attribute[:graphql_name]] = value if value
|
59
|
+
end
|
60
|
+
|
61
|
+
def write_nested_value(object, path)
|
62
|
+
current_obj = object
|
63
|
+
|
64
|
+
loop do
|
65
|
+
path_part = path.shift
|
66
|
+
current_obj[path_part] ||= {}
|
67
|
+
current_obj = current_obj[path_part]
|
68
|
+
break current_obj unless path&.length&.positive?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class << self
|
73
|
+
private
|
74
|
+
|
75
|
+
def read_value(graphql_data, attribute)
|
76
|
+
if attribute[:path]
|
77
|
+
read_nested_path(graphql_data, attribute[:path].dup, attribute[:graphql_name])
|
78
|
+
elsif graphql_data
|
79
|
+
graphql_data[attribute[:graphql_name]]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def read_nested_path(data, path, target)
|
84
|
+
return data[target] unless path&.length&.positive?
|
85
|
+
|
86
|
+
new_data = data[path.shift]
|
87
|
+
|
88
|
+
return nil unless new_data
|
89
|
+
|
90
|
+
if new_data.is_a? Array
|
91
|
+
new_data.map { |item| read_nested_path(item, path, target) }
|
92
|
+
else
|
93
|
+
read_nested_path(new_data, path, target)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/lib/mirah/client.rb
ADDED
@@ -0,0 +1,420 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mirah
|
4
|
+
# A client designed to communicate with the Mirah system in a constrained set of well defined ways.
|
5
|
+
# This is a front to the more general Graphql backend which is available.
|
6
|
+
#
|
7
|
+
# == Patient Methods
|
8
|
+
# * {find_patient}
|
9
|
+
# * {find_patient_by_external_id}
|
10
|
+
# * {query_patients}
|
11
|
+
# * {push_patient}
|
12
|
+
#
|
13
|
+
# == Organization Methods
|
14
|
+
# * {find_organization}
|
15
|
+
# * {find_organization_by_external_id}
|
16
|
+
# * {query_organizations}
|
17
|
+
# * {push_organization}
|
18
|
+
#
|
19
|
+
# == Practitioner Methods
|
20
|
+
# * {find_practitioner}
|
21
|
+
# * {find_practitioner_by_external_id}
|
22
|
+
# * {query_practitioners}
|
23
|
+
# * {push_practitioner}
|
24
|
+
#
|
25
|
+
# == Appointment Methods
|
26
|
+
# * {find_appointment}
|
27
|
+
# * {find_appointment_by_external_id}
|
28
|
+
# * {query_appointments}
|
29
|
+
# * {push_appointment}
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# # Create a new client
|
33
|
+
# client = Mirah::Client.new(host: 'https://api.mirah.com', user_id: 'my_user_id', access_token: 'my_token')
|
34
|
+
#
|
35
|
+
# # find a patient
|
36
|
+
# client.find_patient_by_external_id('mrn0001')
|
37
|
+
class Client # rubocop:disable Metrics/ClassLength
|
38
|
+
# Construct a new Client with the given host and credentials.
|
39
|
+
# @param host [String] The host, e.g. 'https://api.mirah.com'
|
40
|
+
# @param user_id [String] Your user id
|
41
|
+
# @param access_token [String] Your secret API token.
|
42
|
+
def initialize(host:, user_id:, access_token:)
|
43
|
+
@client = Graphql.create_client(host: host)
|
44
|
+
@client_context = { user_id: user_id, access_token: access_token }
|
45
|
+
end
|
46
|
+
|
47
|
+
# Access to the underlying graphql client. You may use this for advanced queries that are not provided
|
48
|
+
# as part of the standard library.
|
49
|
+
attr_reader :client
|
50
|
+
|
51
|
+
#################################################################################################################
|
52
|
+
# PATIENT METHODS
|
53
|
+
#################################################################################################################
|
54
|
+
|
55
|
+
# Find a patient by the given Mirah internal UUID. This method should be used if you already know the Mirah
|
56
|
+
# identifier. If you wish to query by your own system identifier, you should use {#find_patient_by_external_id}
|
57
|
+
#
|
58
|
+
# @since 0.1.0
|
59
|
+
# @param id [String] Mirah UUID for the patient
|
60
|
+
# @return [Data::Patient, nil] the patient, or nil if the record does not exist.
|
61
|
+
def find_patient(id)
|
62
|
+
query_record(Graphql::Queries::PatientIdQuery, id, Data::Patient, 'patient')
|
63
|
+
end
|
64
|
+
|
65
|
+
# Find a patient by your external id. This is a convenience method. If you wish to query a list of patients
|
66
|
+
# by external id, please use {Client#query_patients}.
|
67
|
+
#
|
68
|
+
# @since 0.1.0
|
69
|
+
# @param external_id [String] The identifier of the system of record
|
70
|
+
# @return [Data::Patient, nil] the patient, or nil if the record does not exist.
|
71
|
+
def find_patient_by_external_id(external_id)
|
72
|
+
query_record_by_external_id(Graphql::Queries::PatientExternalIdQuery,
|
73
|
+
external_id,
|
74
|
+
Data::Patient,
|
75
|
+
'patientExternal')
|
76
|
+
end
|
77
|
+
|
78
|
+
# Query for patients. You may specify a set of parameters as defined in {Mirah::Filters::PatientFilters}.
|
79
|
+
# Results are returned in a paginated format. See {Collection} for how to page results.
|
80
|
+
#
|
81
|
+
# @since 0.1.0
|
82
|
+
# @param external_id [Array<String>] See {Mirah::Filters::PatientFilters#external_id}
|
83
|
+
# @param search [String] See {Mirah::Filters::PatientFilters#search}
|
84
|
+
# @return [Collection<Data::Patient>] a collection of pageable patients.
|
85
|
+
def query_patients(external_id: nil, search: nil)
|
86
|
+
query_connection(
|
87
|
+
Graphql::Queries::PatientQuery,
|
88
|
+
Filters::PatientFilters.new(external_id: external_id, search: search),
|
89
|
+
Filters::Paging.default,
|
90
|
+
Data::Patient,
|
91
|
+
'patients'
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Create or update a patient. You must specify an external identifier, all other parameters are optional,
|
96
|
+
# but you may receive errors if you attempt to specify too few parameters for a patient that does not exist.
|
97
|
+
#
|
98
|
+
# @since 0.1.0
|
99
|
+
# @param external_id [String] the external identifier for this patient
|
100
|
+
# @option input_values [String, nil] :given_name see {Data::Patient#given_name}
|
101
|
+
# @option input_values [String, nil] :family_name see {Data::Patient#family_name}
|
102
|
+
# @option input_values [Date, nil] :birth_date see {Data::Patient#birth_date}
|
103
|
+
# @option input_values [String, nil] :gender see {Data::Patient#gender}
|
104
|
+
# @option input_values [String, nil] :primary_language see {Data::Patient#primary_language}
|
105
|
+
# @option input_values [String, nil] :email see {Data::Patient#email}
|
106
|
+
# @option input_values [String, nil] :phone_number see {Data::Patient#phone_number}
|
107
|
+
# @return [PushResult<Data::Patient>] the operation result with a patient on success
|
108
|
+
def push_patient(external_id:, **input_values)
|
109
|
+
mutate(Graphql::Mutations::CreateOrUpdatePatientMutation,
|
110
|
+
Inputs::PatientInput.new(input_values.merge(external_id: external_id)),
|
111
|
+
Data::Patient, 'createOrUpdatePatient')
|
112
|
+
end
|
113
|
+
|
114
|
+
#################################################################################################################
|
115
|
+
# ORGANIZATION METHODS
|
116
|
+
#################################################################################################################
|
117
|
+
|
118
|
+
# Find an organization by the given Mirah internal UUID. This method should be used if you already know the Mirah
|
119
|
+
# identifier. If you wish to query by your own system identifier, you should use {#find_organization_by_external_id}
|
120
|
+
#
|
121
|
+
# @since 0.1.0
|
122
|
+
# @param id [String] Mirah UUID for the organization
|
123
|
+
# @return [Data::Organization, nil] the organization, or nil if the record does not exist.
|
124
|
+
def find_organization(id)
|
125
|
+
query_record(Graphql::Queries::OrganizationIdQuery, id, Data::Organization, 'organization')
|
126
|
+
end
|
127
|
+
|
128
|
+
# Find an organization by your external id. This is a convenience method. If you wish to query a list of
|
129
|
+
# organizations by external id, please use {Client#query_organizations}.
|
130
|
+
#
|
131
|
+
# @since 0.1.0
|
132
|
+
# @param external_id [String] The identifier of the system of record
|
133
|
+
# @return [Data::Organization, nil] the organization, or nil if the record does not exist.
|
134
|
+
def find_organization_by_external_id(external_id)
|
135
|
+
query_record_by_external_id(Graphql::Queries::OrganizationExternalIdQuery,
|
136
|
+
external_id,
|
137
|
+
Data::Organization,
|
138
|
+
'organizationExternal')
|
139
|
+
end
|
140
|
+
|
141
|
+
# Query for organizations. You may specify a set of parameters as defined in {Mirah::Filters::OrganizationFilters}.
|
142
|
+
# Results are returned in a paginated format. See {Collection} for how to page results.
|
143
|
+
|
144
|
+
# @since 0.1.0
|
145
|
+
# @param external_id [Array<String>] See {Mirah::Filters::OrganizationFilters#external_id}
|
146
|
+
# @param search [String] See {Mirah::Filters::OrganizationFilters#search}
|
147
|
+
# @return [Collection<Data::Organization>] a collection of pageable organizations.
|
148
|
+
def query_organizations(external_id: nil, search: nil)
|
149
|
+
query_connection(
|
150
|
+
Graphql::Queries::OrganizationQuery,
|
151
|
+
Filters::OrganizationFilters.new(external_id: external_id, search: search),
|
152
|
+
Filters::Paging.default,
|
153
|
+
Data::Organization,
|
154
|
+
'organizations'
|
155
|
+
)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Create or update an organization. You must specify an external identifier, all other parameters are optional,
|
159
|
+
# but you may receive errors if you attempt to specify too few parameters for an organization that does not exist.
|
160
|
+
#
|
161
|
+
# @since 0.1.0
|
162
|
+
# @param external_id [String] the external identifier for this organization
|
163
|
+
# @option input_values [String, nil] :name see {Data::Organization#name}
|
164
|
+
# @option input_values [String, nil] :external_part_of_id The external identifier for the parent organization
|
165
|
+
# @return [PushResult<Data::Organization>] the operation result with the organization on success
|
166
|
+
def push_organization(external_id:, **input_values)
|
167
|
+
mutate(Graphql::Mutations::CreateOrUpdateOrganizationMutation,
|
168
|
+
Inputs::OrganizationInput.new(input_values.merge(external_id: external_id)),
|
169
|
+
Data::Organization, 'createOrUpdateOrganization')
|
170
|
+
end
|
171
|
+
|
172
|
+
#################################################################################################################
|
173
|
+
# PRACTITIONER METHODS
|
174
|
+
#################################################################################################################
|
175
|
+
|
176
|
+
# Find an practitioner by the given Mirah internal UUID. This method should be used if you already know the Mirah
|
177
|
+
# identifier. If you wish to query by your own system identifier, you should use {#find_practitioner_by_external_id}
|
178
|
+
#
|
179
|
+
# @since 0.1.0
|
180
|
+
# @param id [String] Mirah UUID for the practitioner
|
181
|
+
# @return [Data::Practitioner, nil] the practitioner, or nil if the record does not exist.
|
182
|
+
def find_practitioner(id)
|
183
|
+
query_record(Graphql::Queries::PractitionerIdQuery, id, Data::Practitioner, 'practitioner')
|
184
|
+
end
|
185
|
+
|
186
|
+
# Find an practitioner by your external id. This is a convenience method. If you wish to query a list of
|
187
|
+
# practitioners by external id, please use {Client#query_practitioners}.
|
188
|
+
#
|
189
|
+
# @since 0.1.0
|
190
|
+
# @param external_id [String] The identifier of the system of record
|
191
|
+
# @return [Data::Practitioner, nil] the practitioner, or nil if the record does not exist.
|
192
|
+
def find_practitioner_by_external_id(external_id)
|
193
|
+
query_record_by_external_id(Graphql::Queries::PractitionerExternalIdQuery,
|
194
|
+
external_id,
|
195
|
+
Data::Practitioner,
|
196
|
+
'practitionerExternal')
|
197
|
+
end
|
198
|
+
|
199
|
+
# Query for practitioners. You may specify a set of parameters as defined in {Mirah::Filters::PractitionerFilters}.
|
200
|
+
# Results are returned in a paginated format. See {Collection} for how to page results.
|
201
|
+
#
|
202
|
+
# @since 0.1.0
|
203
|
+
# @param external_id [String] See {Mirah::Filters::PractitionerFilters#external_id}
|
204
|
+
# @param search [String] See {Mirah::Filters::PractitionerFilters#search}
|
205
|
+
# @return [Collection<Data::Practitioner>] a collection of pageable practitioners.
|
206
|
+
def query_practitioners(external_id: nil, search: nil)
|
207
|
+
query_connection(
|
208
|
+
Graphql::Queries::PractitionerQuery,
|
209
|
+
Filters::PractitionerFilters.new(external_id: external_id, search: search),
|
210
|
+
Filters::Paging.default,
|
211
|
+
Data::Practitioner,
|
212
|
+
'practitioners'
|
213
|
+
)
|
214
|
+
end
|
215
|
+
|
216
|
+
# Create or update an practitioner. You must specify an external identifier, all other parameters are optional,
|
217
|
+
# but you may receive errors if you attempt to specify too few parameters for an practitioner that does not exist.
|
218
|
+
#
|
219
|
+
# @since 0.1.0
|
220
|
+
# @param external_id [String] the external identifier for this practitioner
|
221
|
+
# @option input_values [String, nil] :given_name see {Data::Practitioner#given_name}
|
222
|
+
# @option input_values [String, nil] :family_name see {Data::Practitioner#family_name}
|
223
|
+
# @option input_values [String, nil] :title see {Data::Practitioner#title}
|
224
|
+
# @option input_values [String, nil] :suffix see {Data::Practitioner#suffix}
|
225
|
+
# @option input_values [String, nil] :email see {Data::Practitioner#email}
|
226
|
+
# @option input_values [String, nil] :default_practitioner_role see {Data::Practitioner#default_practitioner_role}
|
227
|
+
# @option input_values [String, nil] :sso_username see {Data::Practitioner#sso_username}
|
228
|
+
# @option input_values [Array<String>, nil] :external_organization_ids see
|
229
|
+
# {Data::Practitioner#external_organization_ids}
|
230
|
+
# @return [PushResult<Data::Practitioner>] the operation result with the practitioner on success
|
231
|
+
def push_practitioner(external_id:, **input_values)
|
232
|
+
mutate(Graphql::Mutations::CreateOrUpdatePractitionerMutation,
|
233
|
+
Inputs::PractitionerInput.new(input_values.merge(external_id: external_id)),
|
234
|
+
Data::Practitioner, 'createOrUpdatePractitioner')
|
235
|
+
end
|
236
|
+
|
237
|
+
#################################################################################################################
|
238
|
+
# APPOINTMENT METHODS
|
239
|
+
#################################################################################################################
|
240
|
+
|
241
|
+
# Find an appointment by the given Mirah internal UUID. This method should be used if you already know the Mirah
|
242
|
+
# identifier. If you wish to query by your own system identifier, you should use {#find_appointment_by_external_id}
|
243
|
+
#
|
244
|
+
# @since 0.1.0
|
245
|
+
# @param id [String] Mirah UUID for the appointment
|
246
|
+
# @return [Data::Appointment, nil] the appointment, or nil if the record does not exist.
|
247
|
+
def find_appointment(id)
|
248
|
+
query_record(Graphql::Queries::AppointmentIdQuery, id, Data::Appointment, 'appointment')
|
249
|
+
end
|
250
|
+
|
251
|
+
# Find an appointment by your external id. This is a convenience method. If you wish to query a list of
|
252
|
+
# appointments by external id, please use {Client#query_appointments}.
|
253
|
+
#
|
254
|
+
# @since 0.1.0
|
255
|
+
# @param external_id [String] The identifier of the system of record
|
256
|
+
# @return [Data::Appointment, nil] the appointment, or nil if the record does not exist.
|
257
|
+
def find_appointment_by_external_id(external_id)
|
258
|
+
query_record_by_external_id(Graphql::Queries::AppointmentExternalIdQuery,
|
259
|
+
external_id,
|
260
|
+
Data::Appointment,
|
261
|
+
'appointmentExternal')
|
262
|
+
end
|
263
|
+
|
264
|
+
# Query for appointments. You may specify a set of parameters as defined in {Mirah::Filters::AppointmentFilters}.
|
265
|
+
# Results are returned in a paginated format. See {Collection} for how to page results.
|
266
|
+
|
267
|
+
# @since 0.1.0
|
268
|
+
# @param external_id [Array<String>] See {Mirah::Filters::AppointmentFilters#external_id}
|
269
|
+
# @param status [String] See {Mirah::Filters::AppointmentFilters#external_id}
|
270
|
+
# @return [Collection<Data::Appointment>] a collection of pageable appointments.
|
271
|
+
def query_appointments(external_id: nil, status: nil)
|
272
|
+
query_connection(
|
273
|
+
Graphql::Queries::AppointmentQuery,
|
274
|
+
Filters::AppointmentFilters.new(external_id: external_id, status: status),
|
275
|
+
Filters::Paging.default,
|
276
|
+
Data::Appointment,
|
277
|
+
'appointments'
|
278
|
+
)
|
279
|
+
end
|
280
|
+
|
281
|
+
# Create or update an appointment. You must specify an external identifier, all other parameters are optional,
|
282
|
+
# but you may receive errors if you attempt to specify too few parameters for an appointment that does not exist.
|
283
|
+
#
|
284
|
+
# @since 0.1.0
|
285
|
+
# @param external_id [String] the external identifier for this appointment
|
286
|
+
# @param status [String] the status identifier of this appointment, see {Data::Appointment#status}
|
287
|
+
# @option input_values [String, nil] :start_date see {Data::Appointment#start_date}
|
288
|
+
# @option input_values [String, nil] :end_date see {Data::Appointment#end_date}
|
289
|
+
# @option input_values [String, nil] :minutes_duration see {Data::Appointment#minutes_duration}
|
290
|
+
# @option input_values [String, nil] :patient_id see {Data::Appointment#patient_id}
|
291
|
+
# @option input_values [String, nil] :external_patient_id see {Data::Appointment#external_patient_id}
|
292
|
+
# @option input_values [String, nil] :practitioner_id see {Data::Appointment#practitioner_id}
|
293
|
+
# @option input_values [String, nil] :external_practitioner_id see {Data::Appointment#external_practitioner_id}
|
294
|
+
# @option input_values [String, nil] :organization_id see {Data::Appointment#organization_id}
|
295
|
+
# @option input_values [String, nil] :external_organization_id see {Data::Appointment#external_organization_id}
|
296
|
+
# @return [PushResult<Data::Appointment>] the operation result with the appointment on success
|
297
|
+
def push_appointment(external_id:, status:, **input_values)
|
298
|
+
mutate(Graphql::Mutations::CreateOrUpdateAppointmentMutation,
|
299
|
+
Inputs::AppointmentInput.new(input_values.merge(external_id: external_id, status: status)),
|
300
|
+
Data::Appointment, 'createOrUpdateAppointment')
|
301
|
+
end
|
302
|
+
|
303
|
+
##################################################################################################################
|
304
|
+
# Internal methods
|
305
|
+
##################################################################################################################
|
306
|
+
|
307
|
+
# This is technically a public method so that collections can get the next page, but should not generally
|
308
|
+
# be invoked directly otherwise.
|
309
|
+
# @private
|
310
|
+
def query_connection(query, input, paging, data_klass, path)
|
311
|
+
variables = input.to_graphql_hash.merge(paging.to_graphql_hash)
|
312
|
+
response = client.query(query, variables: variables, context: client_context)
|
313
|
+
check_response_for_errors(response)
|
314
|
+
|
315
|
+
response_json = response.to_h
|
316
|
+
results = parse_graphql_connection_response(response_json, data_klass, path, 'nodes')
|
317
|
+
page_info = parse_page_info(response_json, path)
|
318
|
+
|
319
|
+
# Used to generate subsequent pages
|
320
|
+
query_details = { query: query, input: input, paging: paging, data_klass: data_klass, path: path }
|
321
|
+
Collection.new(results: results, page_info: page_info, client: self, query: query_details)
|
322
|
+
rescue StandardError => e
|
323
|
+
handle_errors(e)
|
324
|
+
end
|
325
|
+
|
326
|
+
private
|
327
|
+
|
328
|
+
attr_reader :client_context
|
329
|
+
|
330
|
+
def query_record(query, id, data_klass, path)
|
331
|
+
response = client.query(query, variables: { id: id }, context: client_context)
|
332
|
+
check_response_for_errors(response)
|
333
|
+
response_json = response.to_h
|
334
|
+
|
335
|
+
parse_graphql_response(response_json, data_klass, path)
|
336
|
+
rescue StandardError => e
|
337
|
+
handle_errors(e)
|
338
|
+
end
|
339
|
+
|
340
|
+
def query_record_by_external_id(query, external_id, data_klass, path)
|
341
|
+
response = client.query(query, variables: { externalId: external_id }, context: client_context)
|
342
|
+
check_response_for_errors(response)
|
343
|
+
response_json = response.to_h
|
344
|
+
|
345
|
+
parse_graphql_response(response_json, data_klass, path)
|
346
|
+
rescue StandardError => e
|
347
|
+
handle_errors(e)
|
348
|
+
end
|
349
|
+
|
350
|
+
def mutate(mutation, input, data_klass, path)
|
351
|
+
input.validate!
|
352
|
+
response = client.query(mutation, variables: { input: input.to_graphql_hash }, context: client_context)
|
353
|
+
|
354
|
+
response_json = response.to_h.dig('data', path)
|
355
|
+
|
356
|
+
if response_json
|
357
|
+
result = data_klass.from_graphql_hash(response_json['result']) if response_json['result']
|
358
|
+
|
359
|
+
status = response_json['status']
|
360
|
+
errors = response_json['errors']
|
361
|
+
else
|
362
|
+
status = 'ERROR'
|
363
|
+
errors = response.to_h['errors']
|
364
|
+
end
|
365
|
+
|
366
|
+
PushResult.new(result: result, status: status, errors: errors, input: input)
|
367
|
+
rescue StandardError => e
|
368
|
+
handle_errors(e)
|
369
|
+
end
|
370
|
+
|
371
|
+
# Parse the main part of the graphql response into the appropriate data class.
|
372
|
+
#
|
373
|
+
# @param response_json [GraphQL::Client::Response] the graphql error response
|
374
|
+
# @param data_klass [Class] the class to transform the response into
|
375
|
+
# @param path [String] the path in the response to look for the raw data
|
376
|
+
# @return [Class] an item of the type of `data_klass`
|
377
|
+
def parse_graphql_response(response_json, data_klass, path)
|
378
|
+
data_klass.from_graphql_hash(response_json.dig('data', path))
|
379
|
+
end
|
380
|
+
|
381
|
+
def parse_page_info(response_json, path)
|
382
|
+
Data::PageInfo.from_graphql_hash(response_json.dig('data', path, 'pageInfo'))
|
383
|
+
end
|
384
|
+
|
385
|
+
# Parse the main part of the graphql response into the appropriate data class with additional paging
|
386
|
+
# and connection information.
|
387
|
+
#
|
388
|
+
# @param response_json [GraphQL::Client::Response] the graphql error response
|
389
|
+
# @param data_klass [Class] the class to transform the response into
|
390
|
+
# @param path [String] the path in the response to look for the raw data
|
391
|
+
# @return [Collection<Class>] a wrapped collection with the results
|
392
|
+
def parse_graphql_connection_response(response_json, data_klass, path, item = 'nodes')
|
393
|
+
response_json.dig('data', path, item)&.map do |datum|
|
394
|
+
data_klass.from_graphql_hash(datum.to_h)
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
# Check that any errors raised have been wrapped in Mirah errors appropriately.
|
399
|
+
def handle_errors(e)
|
400
|
+
case e
|
401
|
+
when Error
|
402
|
+
raise
|
403
|
+
else
|
404
|
+
raise Errors::ClientError, e
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def check_response_for_errors(response)
|
409
|
+
if response.errors.any?
|
410
|
+
if response.errors[:data] == ['401 Unauthorized'] # rubocop:disable Style/GuardClause
|
411
|
+
raise Errors::InvalidCredentials, 'The credentials you have supplied are invalid'
|
412
|
+
else
|
413
|
+
raise Errors::ServerError, 'Unknown error from Mirah server: ' + response.errors.values.flatten.join(',')
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
raise Errors::ServerError, 'Data payload was empty' unless response.data
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|