jhelvy / surveydown

An attempt to build a markdown-based survey platform using Quarto & Shiny
12 stars 1 forks source link

Develop a `sd_config()` function for defining the survey configuration #11

Closed jhelvy closed 3 weeks ago

jhelvy commented 1 month ago

As we start adding more complex functionality, I'm thinking we should break some of this out into a sd_config() function that a user would need to specify outside of sd_server(). I'm thinking something like this:

config <- sd_config(
  db_url = 'url_to_googlesheet',
  db_key = 'credentials.json',
  skip_if = tibble::tribble(
    ~question_id,  ~question_value, ~target,
    "skip_to_end", "end",         "end"
  ),
  show_if = tibble::tribble(
    ~question_id,  ~question_value, ~target,
    "penguins",  "other",         "penguins_other"
  )
)

sd_server(
  input = input,
  session = session,
  config = config
)

One reason for this is simply development - it will be easier (I think) to debug and define the static inputs we need to be handed to sd_server() in a separate function. For example, right now inside the sd_server() I have code that scrapes the survey metadata (e.g. page names, question IDs, etc) from the rendered static html page. That only needs to be done once, and the results can be returned as a list. Likewise, authenticating the google account to write to googlesheets only needs to be done once. So sd_config() would take all the basic configuration settings that the user defines and have that return a list of inputs, which we would then hand to sd_server().

One nice thing about this is the user can verify that their settings are all properly set up by just running sd_config() without having to actually run the entire app. The other nice thing about this is we can handle all of the input checks inside sd_config() to make sure to stop the app and throw an error if something is missing or misspecified.

@Buneabt I also think we could define all of the database checks here. We might have db_url = 'url_to_googlesheet' and db_key = 'credentials.json' as the main arguments for defining the googlesheet database once a sheet is setup for the survey (which could be done manually or automatically). But when launching for the first time, we might also have an argument like db_initialize = TRUE, which would create a new google sheet based on the question ids, in which case the db_url argument would be ignored. The user would have to remember to then remove the db_initialize argument and replace it with a db_url argument for the created sheet, otherwise it will just keep making a new sheet.

Alternate design 1

An alternate design would be to even further separate out the survey control and the survey database, something like this:

db <- sd_database(
  url = 'url_to_googlesheet',
  key = 'credentials.json'
)

control <- sd_control(
  skip_if = tibble::tribble(
    ~question_id,  ~question_value, ~target,
    "skip_to_end", "end",         "end"
  ),
  show_if = tibble::tribble(
    ~question_id,  ~question_value, ~target,
    "penguins",  "other",         "penguins_other"
  )
)

sd_server(
  input = input,
  session = session,
  db = db
  control = control
)

This would work basically the same way as the sd_config() idea, but it would allow the user to isolate different components of the survey. Plus the argument names can be simplified some (e.g. url instead of db_url since it's inside the sd_database() function, which is clearly about the database).

Alternate design 2

Of course, if we don't think this is necessary or adds any real benefit, then this could all just be handled with the current single sd_server() function, something like this:

sd_server(
  input = input,
  session = session,
  db_url = 'url_to_googlesheet',
  db_key = 'credentials.json'
  skip_if = tibble::tribble(
    ~question_id,  ~question_value, ~target,
    "skip_to_end", "end",         "end"
  ),
  show_if = tibble::tribble(
    ~question_id,  ~question_value, ~target,
    "penguins",  "other",         "penguins_other"
  )
)

There is nothing necessarily bad about this approach, but I just feel like modularizing key parts of the survey might be easier to develop. For example, if we separate out the database with a sd_database() function (Alt design 1), we could potentially add support for more than one database backend in the future (e.g. supabase). That might add a new argument, like db_type = 'supabase' which would only affect the arguments to sd_database() function and not the sd_server() function.

Thoughts?

jhelvy commented 1 month ago

Actually, now that I'm thinking about this more, disaggregating too much may be a mistake at this stage. I realized that both the database and the control logic will need access to the survey metadata that's scraped from the rendered page, so alt design 1 may be a mistake. I still like having a sd_config() function though for setting up the database and control logic.

jhelvy commented 3 weeks ago

This was implemented in PR #12, using the proposed sd_config() function separated out from sd_server().