mirah-ruby 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|