Skip to content

Commit

Permalink
Merge pull request #34 from bcbi/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
AshlinHarris authored Aug 27, 2024
2 parents 9ba6419 + 70d117c commit 9f8e1c6
Show file tree
Hide file tree
Showing 40 changed files with 602 additions and 211 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "REDCap"
uuid = "ba918724-fbf9-5e4a-a61c-87e95654e718"
authors = ["Cory Cothrum", "Dilum Aluthge <dilum@aluthge.com>", "Ashlin Harris <ashlin_harris@brown.edu>"]
version = "2.1.0"
version = "2.2.0"

[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Expand Down
88 changes: 82 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@

A Julia frontend for the REDCap API

[REDCap](https://en.wikipedia.org/wiki/REDCap) is a data capture system for scientific research, especially clinical trials.

## Example
```julia
using REDCap

export_version()

project_token = create_project(
data=Dict(:project_title => "Test Project",:purpose => 0),
odm="Data_Dictionary.xml")
data = (project_title = "Test Project", purpose = 0),
odm = "Data_Dictionary.xml")

import_records(token=project_token, data="example.csv", format=:csv)

Expand All @@ -26,22 +28,96 @@ export_logging(token=project_token)
More examples can be found in the [documentation](https://docs.bcbi.brown.edu/REDCap.jl/latest/examples/).

## Syntax
Each REDCap method accepts a number of parameters that follow a shared naming convention.
Generally, a parameter of a given name shares a similar role in all methods where it can be used.
Parameters can hold various datatypes and might even be composed of multiple named attributes.

REDCap.jl is designed to closely follow the design and syntax patterns of REDCap.
Every REDCap API method is available as a function that supplies certain required parameters and checks user inputs for validity.
Type and coherency checks are quite strict, which prevents certain user errors that can be difficult to diagnose with the REDCap's error messages.
Return values and REDCap messages are returned as Strings directly, but the documentation shows how these can be parsed in useful ways.

Function arguments are named after RECap method parameters.
Function arguments are named after REDCap method parameters.
These are passed as named arguments and take values with intuitive types, with a few exceptions to note:

### Token and URL
Almost all REDCap methods accept a token that is unique to the project and user.
A super token can be used to generate a project and project-level token.
The URL must exactly match this example:
```https://example.example/redcap/api/```

Your REDCap token and your institution's REDCap API URL can be read by default from Julia's environment variables.
You can make them avaiable to REDCap.jl by putting the following lines in [your local Julia startup file](https://docs.julialang.org/en/v1/manual/command-line-interface/#Startup-file) (probably `~/.julia/config/startup.jl`):
```julia
ENV["REDCAP_API_TOKEN"] = "C0FFEEC0AC0AC0DEC0FFEEC0AC0AC0DE"
ENV["REDCAP_API_URL"] = "http://example.com/redcap/api/"
```
They can also be passed as ordinary arguments.
If you have a super token, you might wish to keep that in your startup file, generating and saving project-level tokens as needed.

### `data`
The `data` parameter accepts a collection (Dict, NamedTuple, etc.) or a String.
If you use a a collection, it will be translated internally into whatever `format` you use (xml by default).
A NamedTuple is the most elegant format:
```julia
import_project_info(
data=(
project_title="New name",
project_notes="New notes"
),
returnFormat=:csv,
)
```
But please keep in mind that a NamedTuple must contain at least one comma:
```julia
import_project_info(
data=(
project_title="New name", # this comma is required
),
returnFormat=:csv,
)
```
A `Dict` value is fine as well.
```julia
import_project_info(data=Dict(:project_title=>"New name"), returnFormat=:csv)
```
String values are parsed - if they end with a .csv, .json, or .xml file extension, they are treated as a file name; otherwise, they are assumed to be a formatted string and are sent directly as part of the API request.
```julia
data_string = """
[{"data_access_group_name":"CA Site","unique_group_name":"ca_site"},
{"data_access_group_name":"FL Site","unique_group_name":"fl_site"},
{"data_access_group_name":"New Site","unique_group_name":""}]
"""
out = open("data_file.json","w"); write(out, data_string); close(out)

import_DAGs(token=t,data=data_string, format=:json) # string is passed to the API

import_DAGs(token=t, data="data_file.json", format=:json) # string is pattern-matched as a filename

```
As for collections, only collections of scalar entries are currently supported.
So, a list of attributes and values is accepted, but a Dict containing multiple rows per column can only be read in from a file.

In the REDCap API, the presence of a `data` parameter often changes the behavior of a method.
For instance, most import methods are implemented as an export method with an added data parameter.
In REDCap.jl, it would be considered a bug for `import_project_data` to ever act as `export_project_data`, so the data paramater is almost always required where it is present.

### `format` and `returnFormat`
Supported options are `:csv`, `:json`, `:xml` (the default value), and sometimes `:odm`.
These values can be passed as Strings or Symbols.

Generally, the `format` parameter designates user input and the `returnFormat` parameter applies to REDCap messages and return values.
However, this is not consistent within REDCap.
REDCap.jl functions are designed to not accept any parameters that have no effect on the result.

## `content` and `action`
The `content` and `action` parameters are what define each REDCap method, for the most part.
In REDCap.jl, these are passed internally and don't need to be supplied by the user.
Instead, they're fixed for each function.

### Troubleshooting

### Data
The data parameter accepts either a filename, or a Julia `Dict`.
If a function call doesn't produce the expected results, try making debug messages visible for this package by running `ENV["JULIA_DEBUG"] = REDCap`.
Feel free to create an issue for any unexpected errors, or for feature requests.

## Acknowledgments
The contributors are grateful for the support of Mary McGrath, Paul Stey, Fernando Gelin, the Brown Data Science Institute, the Brown Center for Biomedical Informatics, and the Tufts CTSI Informatics core.
11 changes: 2 additions & 9 deletions docs/src/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,9 @@ export_metadata()
export_logging(format=:json) |> JSON.parse |> DataFrame

# Add ability to delete records
import_users(
data=Dict(
:username => "userName",
:record_delete => 1
)
)
import_users(data=(username = "userName",record_delete = 1))

delete_records(
records=["TM22-15374","TM22-16931","TM22-21015"]
)
delete_records(records=["TM22-15374","TM22-16931","TM22-21015"])
```

The following example is a more realistic workflow that creates a new project from an existing project XML (data dictionary) and uploads large CSV data files into it in segments.
Expand Down
4 changes: 3 additions & 1 deletion src/api_methods/arms.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ function export_arms(;
)
end

#All examples use JSON
#TODO: what is the proper format for multi-item XML? I can't find this anywhere...
function import_arms(;
url::redcap_url_input=get_url(),
token::redcap_token_input=get_token(),
Expand All @@ -51,7 +53,7 @@ function import_arms(;
override=override,
action=REDCap_action(:import),
format=REDCap_format(format),
data=REDCap_data(data,REDCap_format(format)),
data=REDCap_data(data,REDCap_format(format),xml_tag="arms"),
returnFormat=REDCap_format(returnFormat),
)
end
Expand Down
6 changes: 3 additions & 3 deletions src/api_methods/data_access_groups.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function export_DAGs(;
token=REDCap_token(token),
content=REDCap_content(:dag),
format=REDCap_format(format),
returnFormat=REDCap_format(returnFormat),
#returnFormat=REDCap_format(returnFormat),
)
end

Expand Down Expand Up @@ -63,7 +63,7 @@ function import_DAGs(;
content=REDCap_content(:dag),
action=REDCap_action(:import),
format=REDCap_format(format),
data=REDCap_data(data,REDCap_format(format)),
data=REDCap_data(data,REDCap_format(format),xml_tag="dags"),
returnFormat=REDCap_format(returnFormat),
)
end
Expand All @@ -81,7 +81,7 @@ function import_user_DAG_assignment(;
token=REDCap_token(token),
content=REDCap_content(:userDagMapping),
format=REDCap_format(format),
data=REDCap_data(data,REDCap_format(format)),
data=REDCap_data(data,REDCap_format(format),xml_tag="items"),
returnFormat=REDCap_format(returnFormat),
)
end
Expand Down
2 changes: 1 addition & 1 deletion src/api_methods/events.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function import_events(;
content=REDCap_content(:event),
action=REDCap_action(:import),
override=override,
data=REDCap_data(data,REDCap_format(format)),
data=REDCap_data(data,REDCap_format(format),xml_tag="events"),
)
end

2 changes: 1 addition & 1 deletion src/api_methods/instruments.jl
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function import_instrument_event_mappings(;
token=REDCap_token(token),
content=REDCap_content(:formEventMapping),
format=REDCap_format(format),
data=REDCap_data(data,REDCap_format(format)),
data=REDCap_data(data,REDCap_format(format),xml_tag="items"),
returnFormat=REDCap_format(returnFormat),
)
end
1 change: 1 addition & 0 deletions src/api_methods/metadata.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function export_metadata(;
)
end

#TODO: there is no guidance on what the metadata should look like... is itbasically like the odm parameter in create_project?
function import_metadata(;
data::redcap_data_input,
url::redcap_url_input=get_url(),
Expand Down
11 changes: 7 additions & 4 deletions src/api_methods/projects.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function create_project(;
content=REDCap_content(:project),
format=REDCap_format(format),
returnFormat=REDCap_format(returnFormat),
data=REDCap_data(data,REDCap_format(format)),
data=REDCap_data(data,REDCap_format(format)),#xml_tag="items")
url=REDCap_url(url),
token=REDCap_super_token(token),
odm=odm,
Expand Down Expand Up @@ -78,14 +78,16 @@ end
function export_project_info(;
url::redcap_url_input=get_url(),
token::redcap_token_input=get_token(),
returnFormat::redcap_returnFormat_input=nothing,
format::redcap_returnFormat_input=nothing,
#returnFormat::redcap_returnFormat_input=nothing,
)

REDCap.request(;
content=REDCap_content(:project),
url=REDCap_url(url),
token=REDCap_token(token),
returnFormat=REDCap_format(returnFormat),
format=REDCap_format(format),
#returnFormat=REDCap_format(returnFormat),
)
end

Expand Down Expand Up @@ -132,7 +134,8 @@ function import_project_info(;
token=REDCap_token(token),
content=REDCap_content(:project_settings),
format=REDCap_format(format),
data=REDCap_data(data,REDCap_format(format)),
data=REDCap_data(data,REDCap_format(format),xml_tag="items"),
#project_title, project_language, purpose, purpose_other, project_notes, custom_record_label, secondary_unique_field, is_longitudinal, surveys_enabled, scheduling_enabled, record_autonumbering_enabled, randomization_enabled, project_irb_number, project_grant_number, project_pi_firstname, project_pi_lastname, display_today_now_button, bypass_branching_erase_field_prompt
)
end

Expand Down
12 changes: 8 additions & 4 deletions src/api_methods/records.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ function delete_records(;
)
end

#TODO: Note, an export function with a data parameter
function export_records(;
data::redcap_data_input,
url::redcap_url_input=get_url(),
Expand All @@ -52,7 +53,7 @@ function export_records(;
exportBlankForGrayFormStatus::redcap_bool_input=nothing
)
REDCap.request(
data=REDCap_data(data,REDCap_format(format)),
data=REDCap_data(data,REDCap_format(format),xml_tag="records"),
url=REDCap_url(url),
token=REDCap_token(token),
content=REDCap_content(:record),
Expand All @@ -76,14 +77,16 @@ function export_records(;
)
end

#TODO: this was that functino that took a data parameter with no value in data, right?
# did that change in REDCap 14?
#if data == nothing, this is an export request
function generate_next_record_name(
url::redcap_url_input=get_url(),
token::redcap_token_input=get_token(),
)

REDCap.request(
data=REDCap_data(data,REDCap_format(format)),
#data=REDCap_data(data,REDCap_format(format),xml_tag="I have no clue"),
url=REDCap_url(url),
token=REDCap_token(token),
content=REDCap_content(:generateNextRecordName),
Expand All @@ -106,7 +109,7 @@ function import_records(;
)

REDCap.request(
data=REDCap_data(data,REDCap_format(format)),
data=REDCap_data(data,REDCap_format(format),xml_tag="records"),
url=REDCap_url(url),
token=REDCap_token(token),
content=REDCap_content(:record),
Expand All @@ -121,6 +124,7 @@ function import_records(;
)
end

#TODO: syntax may have changed between REDCap versions
function rename_record(;
url::redcap_url_input=get_url(),
token::redcap_token_input=get_token(),
Expand All @@ -130,7 +134,7 @@ function rename_record(;
)

REDCap.request(
data=REDCap_data(data,REDCap_format(format)),
#data=REDCap_data(data,REDCap_format(format),xml_tag="deprecated?"),
url=REDCap_url(url),
token=REDCap_token(token),
content=REDCap_content(:record),
Expand Down
4 changes: 2 additions & 2 deletions src/api_methods/user_roles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function import_user_roles(;
token=REDCap_token(token),
content=REDCap_content(:userRole),
format=REDCap_format(format),
data=REDCap_data(data,REDCap_format(format)),
data=REDCap_data(data,REDCap_format(format),xml_tag="users"),
returnFormat=REDCap_format(returnFormat),
)
end
Expand Down Expand Up @@ -84,7 +84,7 @@ function import_user_role_assignments(;
token=REDCap_token(token),
content=REDCap_content(:userRoleMapping),
format=REDCap_format(format),
data=REDCap_data(data,REDCap_format(format)),
data=REDCap_data(data,REDCap_format(format),xml_tag="items"),
returnFormat=REDCap_format(returnFormat),
)
end
7 changes: 6 additions & 1 deletion src/api_methods/users.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ function import_users(;
data::redcap_data_input,
)

#=
Data Export: 0=No Access, 2=De-Identified, 3=Remove Identifier Fields, 1=Full Data Set
Form Rights: 0=No Access, 2=Read Only, 1=View records/responses and edit records (survey responses are read-only), 3=Edit survey responses
Other attribute values: 0=No Access, 1=Access.
=#
#=
if isa(data,Dict)
@assert Symbol.(keys(data)) ⊆ [:username, :expiration, :data_access_group, :design, :alerts, :user_rights, :data_access_groups, :data_export, :reports, :stats_and_charts, :manage_survey_participants, :calendar, :data_import_tool, :data_comparison_tool, :logging, :file_repository, :data_quality_create, :data_quality_execute, :api_export, :api_import, :mobile_app, :mobile_app_download_data, :record_create, :record_rename, :record_delete, :lock_records_customization, :lock_records, :lock_records_all_forms, :forms, :forms_export]
Expand All @@ -53,7 +58,7 @@ function import_users(;
token=REDCap_token(token),
content=REDCap_content(:user),
format=REDCap_format(format),
data=REDCap_data(data,REDCap_format(format)),
data=REDCap_data(data,REDCap_format(format),xml_tag="users"),
returnFormat=REDCap_format(returnFormat),
)
end
Expand Down
Loading

2 comments on commit 9f8e1c6

@AshlinHarris
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/113900

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v2.2.0 -m "<description of version>" 9f8e1c6362f4d747251d1c1dd5b2bfdc4c2e073d
git push origin v2.2.0

Please sign in to comment.