Watershed’s Import Data feature enables you to import CSV files containing people, groups and permissions data. The templates map the columns of a CSV file to properties of Watershed people, groups and permissions configuration objects. You’ll need an import template for each type of CSV file you upload, and once the mapping has been created new CSVs following the same structure can be uploaded as often as required. A single CSV file can contain a combination of people, groups and permissions data, or this data may be contained in separate files.
Templates are created using the Import Templates settings page. Creating import templates is a complex task and is normally completed for you by the Watershed team. This guide explains how to create Import Templates.
- User Types
- Only Global Admins can create CSV import templates and upload CSV files.
- Pricing
- Available on paid plans (Analyst, CLO, and Enterprise).
- Expertise
- Only Experts should create CSV import templates.
Creating a New Import Template
To create a new Import Template, click the Add Import Template button on the Settings / Import Templates page. Enter a name for the template and then upload an example CSV source file which the template will be based on.
In the next two steps you specify template variables and create a template. We recommend that rather than specifying template variables up front, that you dive straight into creating the template, scrolling back up to specify template variables if and when you need them.
How the Template Works
The template is a JSON object with ‘people’, ‘groups’ and/or ‘permissions’ properties. Each of these properties contains an array of configuration objects with placeholder values and functions that are filled in using data from each row of the CSV. The template can also have a statements property which templates the import of xAPI statements based on the same CSV. Statement templates are explained here.
The template uses the Handlebars templating language. See CSV Templates Functions and Variables for a list of available functions. Values are inserted using the handlebars syntax {{columns.[Name of Column]}}. For example, the below syntax inserts the value of the Email Address field into the mbox property of a persona.
{ "name": "foo", "mbox": "mailto:{{columns.[Email Address]}}" }
You can also insert values using template variables using the syntax {{NameOfTemplateVariable}}
.
The People Template
The template’s people property contains an array of person objects to be created. If a person with the same customId as the one to be imported already exists, then the person will be updated.
Please note: the personas property of the person being added is handled a bit differently than the other properties. If a person already exists then any existing personas will be retained when the person is updated by the CSV import. If you need to remove personas from an existing person, this can be done either via the user interface or via API.
Person objects have the following properties:
Property |
Details |
customId String |
A unique identifier for the person. Must be unique. Often this will be an account name from another system or an email address. |
name String |
Name of the person to be used by Watershed. For example, a learner might have personas named “Mike” and “Michael”; the person name is what Watershed will use. |
imageUrl URI |
Link to a profile image for a person. This image will be downloaded and stored in Watershed when the CSV is uploaded, so the image does not need to be permanently available at this url. If not provided and one of the personas contains an email address linked to a Gravatar account, the Gravatar image will be used. Images can also be uploaded directly to Watershed via the UI or API. |
personas array |
List of xAPI agent personas associated with the person. This does not need to be a complete persona object, so long as it contains an inverse functional identifier. Typically, the persona will have an IFI that is either an mbox property or an account property. The persona can also contain a name property. |
parentGroupCustomIds array |
List of customIds of the groups this person belongs to. Use this property or Group.peopleCustomIds to add people to groups. Usually, only one of the two approaches should be used within a single template. |
preserve array |
List of properties that should not be overwritten by the template if they already exist. Currently only supports the "name" property. |
What are personas and xAPI agents?
Agents are the objects within xAPI statements that identify the person involved in the experience, normally the learner. They don’t just identify a person, but a persona of that person, such as their work email address or a personal social media account.
For example, the email addresses bob@example.com and bobby@example.net are both personas, which may refer ultimately to the same person, Bob. Another example might be bob12 at twitter.com, which would be considered an "account on a system" in xAPI terms.
The Groups Template
Groups are collections of people or other groups. They provide a way to model organization hierarchies, groups defined by roles or locations, and many other use cases for filtering and aggregating report data. Groups have some associated metadata, like name, and type. Permissions granting access to group data can be granted to specific people or other groups.
All groups have a group type, e.g. ‘Nashville’ might be a group of type ‘City’, or ‘Accountant’ might be a group of type ‘Job Role’. In Watershed, groups are organized and displayed by their type.
The template’s groups property contains an array of group objects to be created. If a group with the same customId as the one to be imported already exists, then the group will be updated.
Group objects have the following properties:
Property |
Details |
customId String |
A custom identifier for the Group, e.g. the identifier used in another system. Must be unique. |
name String |
The display name of the Group e.g. “Accountant”. |
description String |
A longer description for the group. |
type String |
The display name of the Group Type e.g. “Job Role”. Must be unique. |
imageUrl URI |
A url for an image associated with this group. |
parentGroupCustomIds array |
A list of customIds of the groups that are direct parents of this group. Use this property or childGroupCustomIds to create a hierarchical relationship between groups. Usually, only one of the two approaches should be used within a single template. |
childGroupCustomIds array |
A list of customIds of the groups that are direct children of this group. Use this property or parentGroupCustomIds to create a hierarchical relationship between groups. Usually, only one of the two approaches should be used within a single template. |
peopleCustomIds array |
A list of customIds of the people that are direct members of this group. Use this property or Person.parentGroupCustomIds to add people to groups. Usually, only one of the two approaches should be used within a single template. |
preserve array |
List of properties that should not be overwritten by the template if they already exist. Currently only supports the "name" property. |
The Action Property
When importing data about groups and people that already exist, Watershed needs to know how to handle that information. For example, if a person is in the group 'Nashville' and is assigned to another group of the same type, such as 'New York', what should be the resulting group memberships for that person? Should they be in both 'Nashville' and 'New York' groups, or just 'New York'?
This decision is governed by the action
property of people and group configuration objects. The action
property has several possible values:
create_update
is the default value. It will create groups where they don't exist and add people and groups to parents as defined in the template. With this setting the outcome of the example above would be that the person belongs to both 'Nashville' and 'New York' groups.
add_memberships
works in the same way as create_update
except that it will only add people to groups that already exist. So in the example above, if 'New York' did not already exist as a group, the person would remain in 'Nashville' only. An error that 'New York' was not found would be recorded.
add_memberships_if_existing
works in the same way as add_memberships
except that it will not log an error if the group is not found. You should only use add_memberships
when a group not existing would be a genuine error that you want to flag to the person importing the file. Use add_memberships_if_existing
when you don't want to generate an error.
create_replace
will enable replacement of group memberships, creating groups where they don't exist. So in the example above, the person would only belong to the 'New York' group after the import. This value should be used at template level for imports where the CSV is intended to represent the complete state of the world.
Note that group memberships may be defined in either the "people" objects or the "groups" objects. In order to create a replacement, the previous memberships must be explicitly cleared. To clear out existing group memberships, there are different approaches based on where your group membership is defined.
If your group membership is defined in the "people" objects (i.e., designating specific groups for each person), you will need to add "peopleCustomIds": []
to your groups object. This will clear out all memberships from that group, as shown in the example below. If you want to clear memberships from specific group types (such as "City"), then use the "groupTypesToReplace" field as shown.
{ "action": "create_replace", "groupTypesToReplace": [ "City" ], "people": [ { "name": "John Doe", "customId": "john-doe", "personas": [], "parentGroupCustomIds": [ "city: New York", "country: United States" ] } ], "groups": [ { "name": "New York", "type": "City", "customId": "city: New York", "peopleCustomIds": [] } ... ] }
If your group membership is defined in the “groups” objects (i.e., designating specific people within each group), you will need to add "parentGroupCustomIds": []
to your people object. This will clear out all group memberships for that person, as shown in the example below. If you want to clear memberships from specific group types (such as “City”), then use the "groupTypesToReplace" field as shown.
{ "action": "create_replace", "groupTypesToReplace": [ "City" ], "people": [ { "name": "John Doe", "customId": "john-doe", "personas": [], "parentGroupCustomIds": [] } ], "groups": [ { "name": "New York", "type": "City", "customId": "city: New York", "peopleCustomIds": ["john-doe"] }, ... ] }
replace_memberships
works in the same way as create_replace
except that it will only add people to groups that already exist. In the example above, if 'New York' already existed as a group, the person would be removed from 'Nashville' and added to 'New York'. If 'New York' was not found, an error would be recorded.
replace_memberships_if_existing
works in the same way as replace_memberships
except that it will not log an error if the group is not found. You should only use replace_memberships
when a group not existing would be a genuine error that you want to flag to the person importing the file. Use replace_memberships_if_existing
when you don't want to generate an error.
remove_memberships
can be used to remove a person or group from specified relationships with other groups or people. When remove_memberships
is specified, all existing memberships found in the parentGroupCustomIds, childGroupCustomIds, or peopleCustomIds properties will be removed from the person or group object. This action is typically used at the individual person or group level, as described below. Unlike add_memberships
and replace_memberships
, remove_memberships
does not record an error for any objects that are not found.
delete
will delete a the existing group or person specified by its customId. Existing people can also be specified by a matching persona. An error is not recorded if the specified object is not found.
The action
property can be applied to the template as a whole and to individual people and group objects. When applied to people and group objects, this will override the value on the template.
Here's an example of the syntax:
"people": [ { "name": "{{columns.[name]}}", "customId": "{{columns.[email]}}", "personas": [ ... ], "parentGroupCustomIds": [ ... ], "action": "create_update" } ],
The Permissions Template
Permissions control who can see data about a group of people. Permissions can be assigned to individual people or groups of people. Permissions apply to everybody inside the group that the permission is applied to, including any sub groups.
The template’s permissions property contains an array of permission objects to be created. Permission objects have the following properties:
Property |
Details |
target Group |
The group whose data the permission relates to. The value of this property is an object with a customId property. |
person Person |
The person who should be able to see data about the target group. The value of this property is an object with a customId property. Permissions include either a person or group property, not both. |
group Group |
The group who should be able to see data about the target group. The value of this property is an object with a customId property. Permissions include either a person or group property, not both. |
Template Errors
You may encounter errors while writing your CSV template. Any errors with the template will appear inside of a red text box as shown below. In the particular example below, the opening tag {{#*inline 'object'}}
does not match the closing tag {{/inlin}}
. Notice that the closing tag says inlin
when it should be inline
.
Preview
When you're ready to test the template, use the preview button to test it. If there are problems compiling the template (due to syntax errors etc.), you will receive error messages indicating this below the template.
Once the template compiles, clicking preview will open up a pop-up showing the statements, people, groups and permissions that the template will create for each row of the file. Use this to ensure that the correct data is being used.
Complete Example
Below is a complete example CSV template used to populate people, groups and permissions.
{ "people": [ { "name": "{{columns.[name]}}", "customId": "{{columns.[email]}}", "personas": [ { "name": "{{columns.[name]}}", "mbox": "mailto:{{columns.[email]}}" }, { "name": "{{columns.[name]}}", "account": { "homePage": "https://watershedlrs.com/fake-hr-system", "name": "{{columns.[emp id]}}" } } ], "parentGroupCustomIds": [ "whole-company"{{#if columns.[region]}} "Region: {{columns.[region]}}"{{/if}}{{#if columns.[division]}}, "Division: {{columns.[division]}"{{/if}} ] } ], "groups": [ { "name": "Whole Company", "customId": "Whole Company", "type": "Whole Company" } {{#if columns.[region]}}, { "name": "{{columns.[region]}}", "customId": "Region: {{columns.[region]}}", "parentGroupCustomIds": ["Whole Company"], "type": "Region" }{{/if}}{{#if columns.[division]}}, { "name": "{{columns.[division]}}", "customId": "Division: {{columns.[division]}", "type": "Division" {{#if columns.[region]}}, "parentGroupCustomIds": ["Region: {{columns.[region]}}"] {{/if}} }{{/if}} ], "permissions": [ {{#ifEquals columns.[manager] 'yes'}} {{#if columns.[region]}} {{#if columns.[division]}} { "target": { "customId": "Division: {{columns.[division]}}" }, "person": { "customId": "{{columns.[email]}}" } } {{else}} { "target": { "customId": "Region: {{columns.[region]}}" }, "person": { "customId": "{{columns.[email]}}" } } {{/if}} {{else}} { "target": { "customId": "Whole Company" }, "person": { "customId": "{{columns.[email]}}" } } {{/if}} {{/ifEquals}} ] }