Giter Club home page Giter Club logo

awell-extensions's People

Contributors

ashvinp-s avatar bejoinka avatar danijel avatar dependabot[bot] avatar dylan-cruz avatar ebomcke-awell avatar flaviof avatar japhetmataimp avatar kubiak85 avatar michal-grzelak avatar mohsinht avatar namdar12 avatar nckhell avatar orta21 avatar radoslawstepinski avatar rahulkeerthi avatar rajeev-stx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

awell-extensions's Issues

Dates are dangerously built

There's a local/UTC mismatch somewhere in this stringDate function (assuming it's in the parseISO function, but unsure)

import { stringDate } from '../../validation/generic.zod'
import { z } from 'zod'

describe('formatISO', () => {
  test('test formatISO date', async () => {
    const date = '2023-01-01'
    const zodDate = z.object({ date: stringDate })
    const parsed = zodDate.parse({ date })
    expect(parsed).toStrictEqual({ date })
  })
})

returns

$ yarn test -o
 FAIL  extensions/healthie/actions/__tests__/zod.ts
  formatISO
    ✕ test formatISO date (5 ms)

  ● formatISO › test formatISO date

    expect(received).toStrictEqual(expected) // deep equality

    - Expected  - 1
    + Received  + 1

      Object {
    -   "date": "2023-01-01",
    +   "date": "2022-12-31",
      }

       7 |     const zodDate = z.object({ date: stringDate })
       8 |     const parsed = zodDate.parse({ date })
    >  9 |     expect(parsed).toStrictEqual({ date })
         |                    ^
      10 |   })
      11 | })
      12 |

      at Object.<anonymous> (extensions/healthie/actions/__tests__/zod.ts:9:20)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        3.7 s, estimated 4 s

Potential issue(s) with healthie webhook

One of our customers reported a few issues in trying out the healthie extension.

  1. Multiple Patients -- 2 or even 4 of them -- are being created in Awell for every 1 Patient I create in Healthie.
  2. I can't seem to get the first step in the Care Flow, where we try to make the call back to Healthie to get the Patient's details, to work. The call does seem to be happening, according to the Activity Feed at https://care.sandbox.awellhealth.com/pathway/Rywt7IffRLxO/activity-feed, but no Data Points seem to be getting created as a result, and the Patient's Profile remains completely blank. I believe the initial webhook call from Healthie passes a "resource_id" (according to https://docs.gethealthie.com/docs/#webhooks), and I was expecting that ID to end up as the Patient Code in Awell, so that it could be used in subsequent API calls back to Healthie. But I'm probably missing a link somewhere. Can you take a look?

Healthie - Allow setting a date reminder interval when interval type is "once"

Problem

Also see #47 where we originally implemented reminders.

When we added the ability to configure reminders with the "Create task" action a couple of new action fields were added like:

  • reminderIntervalType (string)
  • reminderIntervalValue (string)

reminderIntervalValue is an action field of value type string but the problem is that depending on the value of thereminderIntervalType action field it should either be a string or a date.

When reminderIntervalType type is set to "daily", leave reminderIntervalValue blank. For "weekly" interval, send in comma separated all lower-case days of the week (e.g wednesday, friday). For "once", send in the date in ISO8601 format (e.g 2020-11-28).

So when reminderIntervalType equals "weekly", then reminderIntervalValue should be a comma-separated string of all lower-case days of the week. If it equals once, then reminderIntervalValue should be a date.

Limitations

There are a couple of limitations we have to work with:

  1. Action fields can only have one type (i.e. reminderIntervalValue cannot be a string and a date at the same time)
  2. We cannot conditionally show or hide an action field based on the value of another action field

Discovery

I would propose the following change but suggestions are welcome as well:

  • reminderIntervalType (string) - stays as is to prevent breaking changes
  • reminderIntervalValue (string) - stays as is to prevent breaking changes
  • add a new optional action field called reminderIntervalValueOnce which has a value type of date

Set the following descriptions for the action fields:

  • reminderIntervalValue: When the interval type is set to "daily" or "once", leave this field blank. For "weekly" interval, send in comma separated all lower-case days of the week (e.g wednesday, friday).
  • reminderIntervalValueOnce: When the interval type is set to "daily" or "weekly", leave this field blank. For "once" interval, set or select a date.

Make sure that technically we grab the right value to send to the API. If reminderIntervalValue equals weekly, then use the value of reminderIntervalValue (this should also be the default to avoid breaking changes). If reminderIntervalValue equals once, then use the value of reminderIntervalValueOnce.

[EXT-5] Canvas Medical tests

Right now there is only a single test for the canvas medical extension actions.
Each action should have its own tests. We can mock the api client. No need to test that separately (there should be unit tests in extensions-core that cover the client.

EXT-5

Sendbird extension

Brief from the customer

In the first stage of our integration, we will be using Sendbird Desk to manage chats between patients and the care team. It allows the care team to have a good UX while chatting with the patient, and it allows the patient to have a seamless mobile chat experience within the app.

To register a patient into Sendbird we need to follow these steps :

  1. Create a user
  2. Register the patient as a customer in Desk (We should be able to send custom fields with links and other unmapped information)
  3. Create a ticket for this patient (It’s really important that this happens only once)

Once the patient is registered in Sendbird we need to keep it up to date by updating it.

We also need to monitor the conversation, so we can trigger actions based on what happens to the ticket or within the chat.

⚠ Using Sendbird Desk require a second authentication mechanism compared to basic chat.

Functional discovery

Settings

  • Application ID (obfuscated)
  • API token (obfuscated) --> needed for the Chat API
  • Desk API token (obfuscated) --> needed for the Desk API

Actions

Get user

Uses the Chat API.

Actions fields:

  • User ID (string)

Data points:
The API will return a user resource and we want to store the following fields in data points

  • Nickname
  • Access token
  • Is active
  • Created at (store as ISO8601 date)
  • Last seen at (store as ISO8601 date)
  • Has ever logged in
  • Metadata (store this as a string for now because we can't store array/json objects yet)

Create user

Uses the Chat API.

Actions fields:

  • User ID (string)
  • Nickname (string)
  • Issue access token (boolean)
  • Metadata (JSON)

Data points:
The API will return a user resource and we want to store the following fields in data points

  • User ID

Update user

Uses the Chat API.

Actions fields:

  • User ID (string)
  • Nickname (string)
  • Metadata (JSON)
  • Issue new access token (boolean)

Data points:
None

Delete user

Uses the Chat API.

Actions fields:

  • User ID (string)

Data points:
None

Deactivate user

Uses the Chat API.

Actions fields:

  • User ID (string)
  • Leave all group channels upon deactivation

Data points:
None

Activate user

Uses the Chat API.

Actions fields:

  • User ID (string)

Data points:
None

Create customer

Uses the Desk API.

Action fields:

  • sendbirdId (string)

Data points:
The API will return a customer resource.

  • customerId

Get customer

Uses the Desk API.

Action fields:

  • customerId (string)

Data points:
The API will return a customer resource.

  • sendbirdId
  • channelType
  • project
  • createdAt
  • displayName
  • customFields (store this as a string for now because we can't store array/json objects yet)

Update customer's custom fields

Uses the Desk API.

Action fields:

  • customerId (string)
  • customFields (JSON)

Data points:
None

Create ticket

Uses the Desk API.

Action fields:

  • channelName (string)
  • customerId (string)
  • groupKey (string)
  • customFields (JSON)
  • priority (string)

Webhooks

Skip for now, first focus on the actions

Technical discovery

For @michal-grzelak :-)

Healthie: new action "Assign patient to group"

Documentation

This action uses the updatePatient mutation from Healthie but only assigns or removes a user_group_id from a user.

Action description

Assign or remove a patient from a group in Healthie.

Action fields

  • User ID - required
  • Group ID - optional - The ID of the group the patient will be assigned to. Leave blank to remove the patient from a group.

Data points

None

[EXT-8] New integration: Zus Health

Zus Health

Zus is known for having a "Zus Aggregated Profile" (ZAP), that contains patient information pulled (and de-duplicated) from many sources. What Zus does that is somewhat powerful is grab all of the "relevant" information, consolidate into the ZAP, and then exposes event subscriptions to customers so they can listen to ADT feeds or their corresponding FHIR Resources.

Our POC with Zus

We want a very simple POC with Zus: Subscribe to the Encounter FHIR resource and allow that to trigger a careflow. A simple example could be:

  • Patient is admitted to a third-party hospital for some event (e.g. heart condition)
  • Because a customer takes care of that patient, the customer is alerted to the hospital admittance through Zus
  • Zus sends a webhook to Awell, alerting Awell (and our customer) of the Encounter
  • A careflow is triggered...

This trigger could be from an admittance or from a discharge (imagine i'm a V1C--or any care provider--and one of my patients is discharged from the hospital... knowing the discharge would help me to check in with the patient and help to deliver optimal care)

The two webhooks we'll want to subscribe to are the discharge and the admittance. Both should be an Encounter resource.

Notes / Links

They have a Postman collection that can be forked, so that might make testing out various calls quite easy, but I'm not sure what they have to test webhooks.

Product guide

EXT-8

Twilio: add "From" number as an action field in the "Send SMS" action

What's changing

The "from" number is a good default "from" number, but there should also be a "From" field (Fieldtype.PHONE) that overrides the value in "from" settings for those folks who want to send messages from different phone numbers.

Functional requirements:

  • Create a new optional action field “From” number
  • If it has a value, use that one over the value defined on the settings level
  • If it doesn’t have a value, use the one from the settings (which is required)

Use case

Care provider ABC Health has 30 physician groups, each with its own phone number for texts and calls. Based on the physician group the patient is a part of, ABC Health needs to be able to send messages to the patient from the phone number associated with that physician group.

Breaking change?

No - because the new action field will be made optional.

Extension actions as classes

Let's do some discovery on supporting actions as classes to support a more familiar API pattern around dependency injection (i.e. decorators).

[EXT-16] [Healthie] Extend inputs to Send Form Completion Request action

Extend the Healthie custom action Send Form Completion Request to include the following additional input fields supported by the underlying mutation:

Field to add Description
is_recurring optional. Set to true if the Form completion should be recurring.
frequency required if is_recurring is set to true. Valid options are: Daily, Weekly, Monthly
period AM or PM.
minute for instance, if you want to trigger the completion request at 1:05 PM, use "5".
hour for instance, if you want to trigger the completion request at 1:05 PM, use "1".
weekday use the full weekday name, e.g. "Monday".
monthday number of the day of month, e.g. "27th".
recurrence_ends set to true if the recurrence should have an end date.
ends_on recurrence end date in the YYYY-MM-DD format.

The list can be found here: https://docs.gethealthie.com/docs/#creating-a-form-completion-request

From SyncLinear.com | EXT-16

Twilio: Send message using a Messaging Service SID

//EDIT NICK: see scope in my comment 👇

My understanding from this link is that Twilio supports sending messages from a phone number or from a messagingServiceSid (and potentially ALSO a phone number, which must be assigned to that messaging service id)

Twilio posted information about 10DLC here, so it's going to require some understanding of requirements from @nckhell before we convert into a todo for engineers.

we either need to create a new action that supports the messaging service SID, or we need to add an optional field in our current action. Needs further discovery

Consistency in elation fields

the patient field and datapoint is shown as patientId and shows up as both string and numeric.

I'm actually kind of indifferent to numeric or string. i've used string in the past because i don't see a reason to use any of numeric's functionality (e.g. > or <) and working with string that look like numbers can bite, but i absolutely understand and appreciate using numeric for fields we expect to be numbers. Either way, it should be consistent. 👍

e.g. getPatient:
image

getAppointment:
image

Awell Hosted Pages doesn't load "Book appointment" activity

Setup

https://www.loom.com/share/930a9f3cfe884abbba0fe634bcf63d17

Expected behavior

When a "Book appointment" action is activated for a given stakeholder, Awell Hosted Pages shows the booking widget when a visitor is the assigned stakeholder.

How to reproduce

Similar issues

Seems reasonable that the issue reported by @bejoinka (#23) has the same root cause, at least the outcome of the bug is identical.

Healthie - Evaluate API responses to assess the success of mutations

We have custom actions in our Healthie extension that perform a mutation in Healthie (create a resource, edit/update a resource, or delete a resource). Currently, we assess the success of those mutations based on the HTTP status code we receive in the response. I.e. if it’s a 200, we consider the mutation successful (eg: the task has been created) - if not, then we consider that the action failed and we call the onError function.

However, Healthie is a GraphQL API and as many GraphQL APIs do they can return a 200 OK response even when there’s an error with the mutation.

200 OK
This status code indicates that the request was successful and that the server is returning data. However, a GraphQL API can also return a 200 OK in cases where the request contains an error, such as an invalid object name or field, or when a record is not found.

Example: task creation

The Healthie API returns an HTTP 200 status code but the task was actually not created because the user associated with the API key doesn’t have permission to do so. Because we receive a 200 status, we do call the onComplete function, and the action is incorrectly marked as successfully completed.

image

What the user sees (success while it's not):

Screenshot 2023-06-07 at 12 30 16

To do

Go over all actions in Healthie that interact with the Healthie API through a mutation (creating, updating, or deleting a resource) and assess how and if we can check that action was really successful (eg: was the task really deleted? the patient really created?).

Functional requirements

Try to capture the error message from the Healthie API and return it with the onError function so the reason of failure is also communicated to the end user. Eg: in the example above You do not have permission to create this task.

Data to store for `Get booking` action

Store the following data points (key: path_to_value)

  1. calApptEventTypeId: booking.eventTypeId
  2. calApptTitle: booking.title
  3. calApptDescription: booking.descrtiopn
  4. calApptStartTime: booking.startTime
  5. calApptEndTime: booking.endTime
  6. calStatus: booking.status

Elation: Find Physician

(note to everyone... if you write a description and convert to an issue before you save your description, you lose your description)

Problem

It's difficult to find the primaryPhysicianId and caregiverPracticeId fields, which are necessary for various actions

Solution

Find Physicians action

Acceptance criteria

Leverage the elation api documentation for the uri

  • Given the ability to add the Find Physicians action to the step

  • When I add the Find Physicians action

  • All three query parameters are available as fields (strings are fine, even though the npi number is technically a number)

  • Given i run the Find Physicians action

  • When the number of responses != 1

  • Then provide an error with a message such as "Find Physicians returned {x} results, but the number of results must equal exactly 1."

  • Given i run the find physicians action

  • When the number of responses == 1

  • Then add the following datapoints in onComplete (assuming the result is saved as result):

physicianId: result.id,
physicianFirstName: result.first_name,
physicianLastName: result.last_name,
physicianCredentials: result.credentials,
physician.email: result.email,
physicianNPI: result.npi,
physicianUserId: result.user_id
caregiverPracticeId: result.practice

Please make sure the physicianId is congruent with primaryPhysicianId (primary_physician_id) as it shows up in fields in other places in elation. Do the same with the caregiver_practice_id.

  • Given Find Physicians works successfully
  • When I use the provided physicianId or caregiverPracticeId datapoints as inputs for fields in other elation actions
  • Then there are no errors resulting from an incorrect type

(fyi the physicianUserId datapoint can be used as the "author" field for the chart note)

Thanks!

Healthie: "Get patient" should try and parse the phone number to valid phone data point

Context

Screenshot 2023-04-17 at 15 45 32

Situation today

The "Get patient" action ingests new data points in a care flow, one of those is the phoneNumber data point which is typed as a string.

const dataPoints = {
  ...,
  phoneNumber: {
    key: 'phoneNumber',
    valueType: 'string',
  }
} satisfies Record<string, DataPointDefinition>

Although phone numbers are indeed strings, we have an even more restrictive data point type in our system for phone strings. This data point type only allows for storing valid E.164 phone strings which is useful for any downstream action you want to perform with the phone number as you can be sure the string stored in a phone data point is a semantically correct phone number according to the E.164 format.

To be situation

const dataPoints = {
  ...,
  phoneNumber: {
    key: 'phoneNumber',
    valueType: 'telephone',
  }
} satisfies Record<string, DataPointDefinition>

Store the string we receive from Healthie in a phone data point. Given that we do not know that the string we are receiving from Healthie is in fact an E.164 valid phone string, we will have to apply some validation on our side to check whether the string we receive is a valid E.164 string or we try to parse it to one. This validation logic (validating incoming string to be a phone number) will come in handy for other actions as well, so I would create something in the /lib folder for that. Also see what is already in lib/shared/validation.

If the validation fails, I would store nothing (or undefined) in the phone data point and log an event for it:

        await onComplete({
          data_points: {
            phoneNumber: undefined
          },
          events: [
            {
              date: new Date().toISOString(),
              text: { en: 'XYZ' },
              error: {
                category: 'XYZ',
                message: "Phone number from Healthie not stored because it isn't a valid E.164 phone number",
              },
            },
          ],
        })

What's blocking

Extensions don't support ingesting data points with the type phone yet:

export interface DataPointDefinition {
  key: string
  valueType: 'string' | 'number' | 'date' // add "| 'telephone'"
}

https://awellhealth.atlassian.net/browse/AST-5031

Cal.com: retrieve the cancel/rescheduling URL from the API and return it as a data point

The problem

Cal.com allows for canceling or rescheduling of already made appointments. They do that by sending a confirmation email of the appointment, and in that email, there's a link to reschedule/cancel the appointment. However, we have a customer (Better Health) who would like to take control over the notifications sent to their patients and therefore would like to disable all communication that Cal.com sends (like the confirmation email).

It would be neat I suppose if we could grab the cancel/reschedule links for use within our Email/SMS templates, which would allow us to move away from Cal.com notifications altogether, which would make for a more uniform experience.

What it needs

Return the reschedule/cancel URL of the appointment as a string data point which in itself is a fairly trivial effort. But there's some discovery needed to see whether this is actually possible.

Discovery

It's currently not clear whether Cal.com returns the rescheduling/cancel URL via their API and if not, whether there is another way we can (manually) compose that URL based on other parameters. Eg: if the cancel URL is always https://cal.com/cancel/{bookingId} then we can easily compose it by using the bookingId. So this part needs tech discovery.

If the discovery yields the result that we cannot retrieve or compose the cancel URL ourselves, then obviously we cannot solve the problem at hand for now.

Boundaries

It should be the "Get booking" action that returns the URL, not the "Book appointment" action.

if a stakeholder doesn't complete booking...

... when in hosted pages, there are no longer any more activities to complete for me, even though i still haven't scheduled an appointment.

shows in progress
image

but the hosted pages tells me i have nothing left to do
IMG_EEAE9A882EAE-1

Cloudinary - File upload extension

File upload extensions

These extensions allow users in hosted pages to upload a file to a cloud resource / bucket. The uri and other information about the upload should be passed as data points once the upload is complete.

Cloudinary

Nick began work on the Cloudinary extension, which supports file uploads. However, after some time this weekend, it wasn't immediately clear what was going on. There is already already some work in awell-extensions to handle, but we're going to need to do some work in hosted pages to support

Currently, the extension has some code here to support the extension vis-a-vis settings, but it's going to take some work in hosted pages to complete.

The extension has already been merged into main in order to support testing in hosted pages.

Could we timebox an effort here to try to get something up and running? How is one day?

Healthie - clean up field and datapoint types

There are still a couple leftover field and data point types that are incorrect or missing:

  • createTask has a dueDate field
  • updatePatient now supports some of these skipped / commented out fields

Update Healthie action: "Create task" with reminders

We want to add some additional action fields to the "Create task" action. Work on this action is blocked until an extension developer can use boolean action fields (https://awellhealth.atlassian.net/browse/AST-5024).

Docs

Additional action fields

  • isReminderEnabled (boolean) - Would you like to send reminders for this task?
  • reminderIntervalType (string) - At what interval would you like to send reminders? The options are "daily", "weekly", "once"
  • reminderIntervalValue (string) - When interval type is set to "daily", leave this field blank. For "weekly" interval, send in comma separated all lower-case days of the week (e.g wednesday, friday). For "once", send in the date in ISO8601 format (e.g 2020-11-28).
  • reminderTime (numeric) - Time to send the reminder. Expressed in the number of minutes from midnight.

Note, if you look at the schema you will see that some fields re conditional based on f.e. the interval type. It's not possible yet to show/hide action fields conditionally which means we will have to add descriptions to guide the user in what to configure and what not. Additionally, the code in onActivityCreated should be smart enough to only send the relevant values, even if a user entered more values.

Logic & validation

  • If isReminderEnabled equals false, don't pass in the reminder object as input to the GraphQL mutation.
  • reminderIntervalType should be "daily", "weekly", or "once". If not, return a clear error
  • If reminderIntervalType equals daily then reminderIntervalValue should be null (no validation needed, just omit it from the input)
  • If reminderIntervalType equals weekly then reminderIntervalValue should be a comma-separated string with only these possible values: monday, tuesday, wednesay, thursday, friday, saturday, sunday`. If not, return a clear error.
  • If reminderIntervalType equals once then reminderIntervalValue should be an ISO8601 string. If not, return a clear error.

Healthie - Get patient action: Return dob as a date data point instead of a string

Context

Screenshot 2023-04-18 at 13 56 55

https://awellhealth.atlassian.net/wiki/spaces/AWELL/pages/3492773961/Have+5+different+Extensions+implemented+in+a+live+care+flow?focusedCommentId=3493822565#Additional-field-mapping-problems

Situation today

The "Get patient" action ingests new data points in a care flow, one of those is the dob data point which is typed as a string.

const dataPoints = {
  ...,
  dob: {
    key: 'dob',
    valueType: 'string',
  },
} satisfies Record<string, DataPointDefinition>

To be situation

const dataPoints = {
  ...,
  dob: {
    key: 'dob',
    valueType: 'date', // should be ISO8601 date string
  },
} satisfies Record<string, DataPointDefinition>

Store the dob string we receive from Healthie in a date data point. We might need to try and parse the incoming string value from Healthie to a data and then use date-fns to parse it to ISO8601 string date.

Pseudocode

import { isValid, formatISO } from 'date-fns'

const getDateValue = (incoming_dob: string) => {
  const date = new Date(incoming_dob)
  const isValidDate = isValid(date)

  if (isValidDate) return formatISO(date)

  return undefined
}

[EXT-7] New Extension: Canvas Medical

Canvas Medical

Canvas Medical is another EHR. It is one of our goals to have five EHR integrations this quarter, and Canvas will be number 3!

What are the benefits of a Canvas integration?

Canvas' FHIR API adheres to the FHIR R4 specification (read more here). FHIR is becoming industry-standard so capturing these resources and mapping them to Awell will be useful beyond just Canvas' integration.

They have a nice postman collection, ready to be forked. Should make testing the API fairly simple. They also have an SDK. Again, seems like it has the opportunity to be pretty straightforward.

What endpoints should we support?

Original scope from V1C-52:

v1 Beta Scope:

  • Patient create
  • Patient update
  • Task create
  • Task update
  • Appointment create
  • Appointment update
  • Create questionnaire responses

Additional notes

  • Let's avoid deprecated fields and values
  • Any already completed actions not in the list above should, for now, not be removed but just not exported from the index in the extension folder
  • Do not use JSON data points as inputs or outputs in new actions as this is not supported by the platform yet. JSON as extension action field type is 👍
  • Done but excluded (i.e. will not be exported from actions index) due to lack of support for objects and arrays
    • Patient read
    • Appointment read
    • Task read
    • Read questionnaire responses

From SyncLinear.com | EXT-7

cm.com extension - Communications Platform for Messaging & Voice

cm.com is a communications Platform for Messaging & Voice and is in that perspective very similar to our Twilio extension. Heilig-Hart Lier (Belgium hospital) uses cm.com as their notification provider.

🎞 Functional requirements

Create the extension

  • Extension name: cm.com
  • Extension logo: https://res.cloudinary.com/da7x4rzl4/image/upload/v1687860653/Awell%20Extensions/cm-f4ffa018.png
  • Description: cm.com is a communications Platform for Messaging & Voice
  • Category: Communications
  • Author: Awell
  • Settings
    • productToken (string)
      • Label: Product token
      • Required: yes
      • Obfuscated: yes
      • Description: This is the product token for authentication. Visit https://gateway.cmtelecom.com/ to retrieve your product token.
    • fromName (string)
      • Label: From/sender name
      • Required: no
      • Obfuscated: no
      • Description: This is the sender's name. The maximum length is 11 alphanumerical characters or 16 digits
  • Make sure there’s a README and Changelog and that there’s content in those files (use Twilio as a reference)

Create the action

  • Name: Send SMS
  • Description: Send a text message to a recipient of your choice.
  • Not previewable
  • Action fields
    • fromName (string)
      • Label: From/sender name
      • Required: no
      • Description: This is the sender's name. The maximum length is 11 alphanumerical characters or 16 digits. When left blank, the "From name" from the extension settings will be used.
      • Note: if fromName is defined on the action level, then that takes precedence over the value specified in the extension settings.
    • recipient (string|phone)
      • Label: Recipient
      • Required: yes
      • Description: The phone number you would like to send the text message to.
    • message (text)
      • Label: Message
      • Required: yes
      • Description: The message you would like to send.
  • Validation:
    • Throw an error when fromName in settings AND in action field are empty
    • The recipient should be a valid phone number

🪓 Technical specs

Please have a look at https://developers.cm.com/messaging/docs/sms and https://developers.cm.com/messaging/reference/messages_sendmessage-1

You can ask Nick for an invite to cm.com. We have an Awell Health account there which allows you to get test credentials and allows you to actually test the sending of text messages.

Minimally required:

  • Test coverage (like Twilio) where you mock the API response
  • Make sure you test the actual sending of the text message to your own mobile number through the code to validate that it works

Making the request

{
  "messages": {
    "authentication": {
      "productToken": "{{productToken}}"
    },
    "msg": [
      {
        "from": "{{fromName}}",
        "body": {
          "type": "auto",
          "content": "{{message}}",
        },
        "reference": "{{AWELL_ACTIVITY_ID}}",
        "to": [
          {
            "number": "{{recipient}}"
          }
        ],
        "allowedChannels": [
          "SMS"
        ],
        "minimumNumberOfMessageParts": 1,
        "maximumNumberOfMessageParts": 8,
      }
    ]
  }

Why set maximumNumberOfMessageParts to 8?

The SMS standard theoretically permits up to 255 message parts (which could mean you would send messages of 153 times 255 = 39.000 characters). In practice you should try to limit your message to 8 message parts – thus 153 times 8 = 1.224 characters or 67 times 8 == 536 for unicode messages.

https://developers.cm.com/messaging/docs/sms#multipart

Why set allowedChannels to SMS only?
We want this action to only communicate over SMS, even if there are any other channels configured in cm.com.

[EXT-9] New extension: DrChrono

We would like to add support for the DrChrono EHR through a new extension.

The API docs can be found here.

The scope for this new action is not determined yet, so the recommendation is to start with a set of standard actions in the EHR integration category (create / update / get patient, create / get clinical note).

Note that this API uses a new authentication scheme that is not fully supported yet (Authorization Grant flow). At this time we have not decided how the initial authorization flow will be handled, so this should be excluded from the scope of this task. Instead you should assume that an authorization token is available in the token service and use that to authorize all API calls.

Given that the functional scope for this extension has not been internally shaped yet, the first tasks are:

  • Design and implement changes needed to the token service and auth client to support tokens coming from an Authorization Code grant flow
  • Examine the DrChrono API specs and provide recommendation on which actions can be implemented on top of their API.

EXT-9

Healthie: <span> tags in the middle of urls are incorrectly parsed by healthie

The issue

Healthie-SendChatMessage should strip <span> tags before sending because we wrap our variables in <span> tags and they are inappropriately handled by anchorme.

Why

This issue is blocking work from one of our customers who uses healthie to send dynamic links through Healthie.

How

When sending a chat to healthie, tags are parsed incorrectly.

Learned from Healthie they use anchorme, and it appears they may not have the appetite to solve it, so here's what we can do on our end:

import { load as cheerioLoad } from 'cheerio';
import anchorme from 'anchorme';

const htmlContent = '<p class="slate-p">https://securestaging.gethealthie.com/appointments/embed_appt?dietitian_id=52848&amp;provider_ids=[<span>52848</span>]&amp;appt_type_ids=[19420]&amp;org_level=true</p>';

// Parse the HTML content using cheerio
const $ = cheerioLoad(htmlContent);

// Remove all <span> tags
$('span').replaceWith(function () {
    return $(this).contents();
  });

// Get the modified HTML content
const modifiedHtmlContent = $.html();

// Pass the modified content to Anchorme for URL parsing
const parsedUrls = anchorme(modifiedHtmlContent);

// Unparsed content, for comparison
const unparsedContent = anchorme(htmlContent);

// Output the parsed URLs
console.log('unparsed:\n', unparsedContent, '\nparsed:\n', parsedUrls);

Output:

unparsed:
 <p class="slate-p"><a href="https://securestaging.gethealthie.com/appointments/embed_appt?dietitian_id=52848&amp;provider_ids=">https://securestaging.gethealthie.com/appointments/embed_appt?dietitian_id=52848&amp;provider_ids=</a>[<span>52848</span>]&amp;appt_type_ids=[19420]&amp;org_level=true</p> 
parsed:
 <html><head></head><body><p class="slate-p"><a href="https://securestaging.gethealthie.com/appointments/embed_appt?dietitian_id=52848&amp;provider_ids=[52848]&amp;appt_type_ids=[19420]&amp;org_level=true">https://securestaging.gethealthie.com/appointments/embed_appt?dietitian_id=52848&amp;provider_ids=[52848]&amp;appt_type_ids=[19420]&amp;org_level=true</a></p></body></html>

Sendgrid Extension

Need

We have a customer that uses Sendgrid and we therefore want to have a Sendgrid extension.

Context

  • We did some work in the past already on a Sendgrid extension but it was discarded back then. However, the PR might be useful to get you going quickly: #19
  • This MR should not be merged until Nick is back OR after @ebomcke-awell or @bejoinka acknowledged that it can be merged. Reason: we already have a Sendgrid plugin in our system (legacy) and we probably have to check if there are no conflicts with the extension that we are building or think about how to deprecate the legacy plugin. Anyway, development & reviewing can happen.

Why create the extension now?
Wellinks needs an additional action for Sendgrid and we don't want to build that into the legacy plugin anymore. So seems like the right thing to do is to create the extension now and add the additional actions immediately.

Functional scope

The basics

I am gonna keep this short as I believe there are sufficient references of extensions that integrate with other email providers (see Mailgun and Mailchimp extension) and Sendgrid has good documentation.

But, what's minimally needed:

  1. A new extension: "Sendgrid"
  2. Extensions settings, probably similar to Mailgun/Mailchimp extension
  3. Minimally two actions with similar action fields as Mailgun/Mailchimp:
  • Send email (to, subject, body)
  • Send email with template (to, template, subject, template variables)
  1. Create a README and Changelog
  2. Basic validation of settings and action fields

Additional "Add or update contact" action

See https://docs.sendgrid.com/api-reference/contacts/add-or-update-a-contact

Functional requirements:

  • The action will add or update only one contact (even tho the API endpoint allows an array of contacts)
  • Has the following action fields
    • List IDs (optional, a comma-separated string of list IDs the contact will be added to)
    • Email (string, email, required)
    • Custom fields (JSON, optional)
  • You don't have to take into account that it's an async API endpoint. If the API returns a 202 status code then you can mark the action as completed.

Please make sure that relevant information from the Sendgrid docs make it into the README. Eg: Please note that custom fields need to have been already created if you wish to set their values for the contacts being upserted. To do this, please use the "Create Custom Field Definition" endpoint. AND The contact to update will be determined only by the email field and any fields omitted from the request will remain as they were. A contact's ID cannot be used to update the contact..

[EXT-4] [Sendbird] Create user action: make `nickname` optional and set a sensible default

nickname is currently a required action field (because it's required for making the API call to Sendbird) but we want to make it optional and set a sensible default.

  • nickname becomes optional
  • If the action field has a value of undefined or empty string, set the nickname to {patientFirstName} {patientLastName} (both available in payload.patient.
  • If both the firstName and the lastName of the patient are not known in Awell and nickname action field is not specified either, then we should throw an error. I.e. we should never allow calling the Sendbird API when nickname has no value
  • Change the description of the action field to The user's nickname. Maximum length is 80 characters. If left empty, we will use the patient's first and last name.

EXT-4

New extension: DocuSign

We would like to create a new extension for signing documents through DocuSign.

The existing DropboxSign extension can be used as a reference for the functional scope of this new extension.

High level, the expected outcome is that this extension allows any care flow stakeholder to sign a document from within a hosted page session.

Given that this has not been internally shaped yet, the first task is to look at the developer docs and determine:

  • Which new actions are needed in this extension and which API calls each action relies on
  • What additional work is needed in the hosted pages repo to support document signing

Cache Service for authentication tokens

Problem: Tokens are not being reused

Currently, all requests made to an OAuth provider also always make a request to the authentication server to get a new token. While this functionality was acceptable for our POC, it's not okay in production and we need to improve upon it by leveraging a cache of some sort.

Solution: Use a cache to store the tokens.

The request flow should be something like this:

  • Check the cache to see if there is an access token
  • If there is an access token, check to see if it's valid (there should be a ttl on the token)
  • If valid token:
    • Use the valid token in the resource request
  • If invalid token:
    • Use the refresh token to acquire a new access token
    • Store the valid token in the cache (also include a timestamp pulled from "expires_in" for future token validation)
  • Use the token to hit the API endpoint
  • If 4[xx] response:
    • Attempt to revalidate the token with the authentication server
    • Retry the API (if the service has already retried the API, then throw the appropriate error)
  • If 2[xx] response:
    • Complete request

Other notes

There is already a retry mechanism in the API Client, but it's missing all of the appropriate token service functionality.

🛹 ==> 🛵 ==> 🚗

Eventually, we're going to want to use a shared cache (e.g. redis). Feel free to build this service as though there was a redis service in place:

  • HSET
  • HGET(ALL)

If someone is testing locally, though, we'd want to support an in-memory cache.

Questions

Q: What should we use for the hash?

A: I think a sufficient hash for the key would be a hash from the OAuth object... what do you think?

Q: How should we decide whether or not to use in-mem cache or redis cache?

A: I'm open to however you want to determine that at runtime

Q: Are there any third-party services out there that handle this sort of thing for us?

A: I'm open to suggestions here as well!

@ebomcke-awell feel free to comment!

Cal.com webhooks

We want to add support for Webhooks to the Cal.com extension

Documentation

https://cal.com/docs/core-features/webhooks

Updating the extension

Append a section to the readme

Add it right below "Custom Actions"

## Webhooks

Webhooks offer a great way to automate the flow with Awell when invitees schedule, cancel, or reschedule events, or when the meeting ends.

**Important notes:**
1. An Awell webhook endpoint can only listen to one event type. So make sure that when you create a webhook in Cal.com, the subscriber URL and the event trigger match the Awell webhook endpoint. This also means there can only be one event type per subscriber URL.
2. Using a secret to verify the authenticity of the received payload is not yet supported.
3. Custom payload templates are not supported, please use the default ones.

Add three webhooks

See https://github.com/awell-health/awell-extensions/tree/main/extensions/healthie/webhooks for reference.

Booking created

There's an example payload of the booking.created event available on their docs.

Store the bookingId and the bookingUid in a data point.

Booking cancelled

Unfortunately, there is no example payload of this event type available on their docs but I assume we can also store the bookingId and the bookingUid in a data point.

Booking rescheduled

Unfortunately, there is no example payload of this event type available on their docs but I assume we can also store the bookingId and the bookingUid in a data point.

[EXT-10] Better guidance around tests

After an issue related to onComplete() being called twice in an extension action, it seems as though we can do a better job guiding developers by providing stronger guardrails in our API.

For example, tests should use ToHaveBeenCalledTimes(1) instead of ToHaveBeenCalled() when checking for OnComplete or OnError to have been called.

In addition, we could potentially use code coverage to help us to spot gaps in extension tests.

EXT-10

[EXT-11] New extension: S3 - File Upload

File Uploads

Just like the Cloudinary extension, users may want to use an S3 bucket for a file upload.

S3 Bucket

Building a POC here should be timeboxed to a day as well. Given there is the potential to duplicate code from cloudinary, most of the work is likely in the hosted pages repo.

Other info

Currently, this issue is standing in as a placeholder until more details are fleshed out, including what the hosted page will look like for the upload.

EXT-11

Sendgrid update

Update after feedback from a customer.

All email action fields should be of type string

In all three actions of the extension where we have an email type action field, the type of the action field should be just string (so remove the stringType email). This is due to a limitation on our side which I forgot when scoping the initiative.

For internal reference: https://awellhealth.atlassian.net/jira/polaris/projects/AH/ideas/view/548618?selectedIssue=AH-176&issueViewLayout=sidebar&issueViewSection=capture&focusedInsightId=3144292

Add new action fields to add or update contact action

https://docs.sendgrid.com/api-reference/contacts/add-or-update-a-contact

Add the following action fields after email and make sure they are passed with the request

  • First name
  • Last name

Adjust from name label

Should be From label for the label and not fromLabel.

Screenshot 2023-07-03 at 21 56 54

Elation: Chart notes

image

Chart Notes

The non-visit note is a special kind of note that, as the name suggests, is not associated with a visit. These notes, in their simplest form, provide a chronological account of information about the patient. While they can be plain text, they can also contain vitals and links to other documents.

The use case for Non-Visit Notes

  • Patient books appointment (or patient is created)
  • Triggers a webhook (coming soon...)
  • we start a flow
  • We send text message with hosted pages link
  • Patient fills in some form in hosted pages
  • Data is piped back to Elation as a note

My understanding is that non-visit notes are the most appropriate format for this sort of information.

Tasks

  • Create a non-visit note
  • Update a non-visit note
  • Get a non-visit note
  • Delete a non-visit note (to complete CRUD functionality)

Details

In terms of how we want to handle, the goal here will be to not mutate the required object, but instead omit items we can't support. So, we'll support tags as an array of ints. we won't support note_documents or note_items yet.

The one exception is going to be bullets. We're going to have to support that, because that's where the note is. Here, we'll just support a single note:
image

  • category will always be 'Problem'
  • Author is equivalent to physicianUserId (see #107 )
  • text is the note itself.

What's missing?

Normally, the note is used for historical documents from other doctors. "Hey, you've got your old charts? we'll log them as non-visit notes, add them as documents, and include bullets to summarize the documents."-

That's the normal use case ☝️ ... but, we're going to be using these as a way to log inputs from forms.

If we get feedback from customers that the non-visit note is not the best way to use these notes, we'll make adjustments and perhaps add some other way to perform this use case... but that's where we're starting for now.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.