arthur-shaw / susoapi

R interface for Survey Solutions' APIs
https://arthur-shaw.github.io/susoapi/
Other
9 stars 5 forks source link

Add functions to work with calendar events 📆 #28

Open arthur-shaw opened 2 years ago

arthur-shaw commented 2 years ago

Objective 🎯

Provide survey managers a means of observing or dictating the schedule of interviews through getting/setting/updating/deleting calendar events associated with interviews and assignments.

User stories 📇

Tasks ⚙️

Set/update/delete events

Write R functions to wrap these GraphQL mutations:

Get events

Consider also either new R functions or extensions to R functions to get calendar event nodes:

Documentation ✍️

petbrueck commented 2 years ago

Hi Arthur,

I am also currently looking into R functions/libraries that allow HeadquartersMutation (addUserToMap to be precise).

ghql apparently does not support any mutation, so I guess no chance in adding this to susoapi itself for the moment?

Do you happen to know any R library that one could use/look into in the short-term for an HeadquartersMutation? Tried to find something, but no luck yet.

Current Workaround Make use of Zurab's ssaw Python library, to be called from R.

arthur-shaw commented 2 years ago

@petbrueck , I did a deep(-ish) dive on this and discovered the following:

  1. GraphQL requests can be done via simple curl requests
  2. httr can be used to make these requests via R
  3. GraphQL mutations are just requests where the query body includes the mutation instruction

In short, this means that we can perform mutations without ql

Let me touch on each point in turn before wrapping up with a sketch of th way forward and a request for a PR should you work on the map request 😉

1. GraphQL requests can be done via simple curl requests

When I saw this problem, my immediate reflex, like you, was to find other R packages that operate as GraphQL clients. While I found a few--here and (in a way) here--I realized that this problem could probably be solved via other means when I looked at gh's DESCRIPTION file and only saw httr for making requests. When I looked at the source code, I saw that the interface for GitHub's GraphQL API simply captured a query and transmitted it via httr via the correct verb.

To check whether I was understanding right, I did some Googling and discovered others making GraphQL requests with curl (for which httr is a wrapper) here and here)

2. httr can be used to make these requests via R

After a combination of tinkering and searching, I was able to make GraphQL requests via R.

Here's a nice Gist found on the web.

Here's those ideas translated into our context

my_url <- ''
my_user <-  ""
my_password <- ""
my_query <- '{
    questionnaireItems(
        id: "ccb8455d754942329ed10574e1df67fa"
        version: 3
        workspace: "ftf"
        where: {variable: {eq: "v113"}}
    ) {
        entityType
        variable
        label
        options {
        title
        value
        }
    }
    }'

# NOTE: it seems everyting is a POST request for GraphQL
httr::POST( 
    url = my_url,
    # use same approach to authenication as my other GraphQL wrappers--modified slightly
    # - use `add_headers` so I can add a named list of headers
    # - encode user-password pair
    httr::add_headers(
        Authorization = paste0(
            "Basic ", jsonlite::base64_enc(input = paste0(my_user, ":", my_password))
        )
    ),
    # transmit GraphQL query as the body of post 
    # consider it a query
    body = list(query = my_query),
    encode = "json"
)

3. GraphQL mutations are just requests where the query body includes the mutation instruction

After a few misteps--notably, naming the element of the body mutation--I tried simply keeping body = list(query = my_query), and that worked perfectly, to my surprise

Here's some code that updates an existing calendar event:

my_url <- ''
my_user <-  ""
my_password <- ""

my_query <- 'mutation {
        updateCalendarEvent(
            workspace: "primary", 
            publicKey: "8bbefc98-de2b-4317-9152-a4aa9a05f88b", 
            newStart: "2022-07-11T12:13:00"
            startTimezone: "UTC"
            comment: "Do it this time--seriously"
        ) 
        {
            interviewId
            publicKey
        }
        }'

httr::POST( 
    url = my_url,
    httr::add_headers(
        Authorization = paste0(
            "Basic ", jsonlite::base64_enc(input = paste0(my_user, ":", my_password))
        )
    ),
    body = list(query = my_query),
    encode = "json"
)

Next steps

There might be lots of rough edges to smooth, but the code above provides a blueprint for creating wrappers for GraphQL mutations.

(Honestly, the hardest part for me was figuring out how to write correct mutation. After countless false starts, I learned that a mutation needs also to return something. Hence, the mutation above updates a calendar event and returns the GUID for the calendar event. Without a return requested, the mutation doesn't work. Unfortunately, this is probably basic GraphQL knowledge that I failed to acquire while scanning too quickly through online documentation and tutorials 😉 )

If you happen to start work on wrapping any GraphQL mutations, please do submit a PR.

For the moment, I may concentrate on calendar events--which seem like lower hanging fruit for me. Even though I'd never used calendar event, the concept and API interface are fairly straightforward.

If you'd like to collaborate on wrapping the map mutations, let's connect. Honestly, I've never used this functionality. Consequently, I'm facing the dual learning curve of learning how maps work and how to make them work via the API.

petbrueck commented 2 years ago

Wow, thanks a ton @arthur-shaw for your deep dive and elaborate response! This indeed sounds fantastic!

Since the HeadquarterMutation addUserToMap becomes 'system-critical' day by day in an upcoming project, I will (need to) give it a try.

As a starter, I tried to tackle the query maps(...) to retrieve existing maps on the server. To this end, I (shamelessly) made an almost 1:1 replica of your get_interviews() set of function. See here. Will try to address the mutation later this week. I'll reach out to you seperately!