Forsta
Step 1. Contact Forsta Support
Contact Forsta support and ask them to whitelist https://api.roundtable.ai
for API calls.
Step 2. Initialize Variables
Add this block to the start of the survey
<exec>
survey_id = gv.survey.root.alt
api_key = '' # Leave this empty
p.client_dict = {'responses': {}, 'questions': {}, 'question_histories': {}, 'survey_id': '', 'participant_id': '', 'num_non_empty': 0, 'question_labels': []}
</exec>
Step 3. Embed Tracking Code for Every Question
In the XML for your survey, add the following lines of code to every “Text” or “Essay” questions you want to track (text
and textarea
elements in XML). This code embeds our Javascript tracker for the question, automatically extracts the question and response, and sets up hidden rows to store the Alias API data for the question when it is later called. The actual response is stored in the shown “response” row.
<row label="response"/>
<row label="d1" cond="False">alias-flagged</row>
<row label="d2" cond="False">alias-checks</row>
<row label="d3" cond="False">alias-group</row>
<row label="d4" cond="False">alias-effort</row>
<style name="question.after"><![CDATA[<script src='https:/roundtable.ai/js/forsta-tracker-v1.js'></script>]]></style>
<style name="question.after" wrap="ready"><![CDATA[
const q = ${jsexport()};
const label = q['label']
const oes = document.querySelectorAll('input[type="text"], textarea');
oes.forEach(el => {
el.addEventListener('input', e => handleChange(e, 'input', el, label, q));
el.addEventListener('copy', e => handleChange(e, 'copy', el, label, q));
});
initializeClientDict(label, q);
]]></style>
Step 4. Call API and Store Data
Add the following code to the end of your survey and set the api_key
to your Alias API Key (generated on your account account).
This code will call the API on the page the code is embedded in. It generally takes a few seconds and must finish executing before the next page is loaded. The code must be placed after any open-ends you are tracking and on a separate page from them (i.e., after a <suspend/>
in XML).
<exec>
survey_id = gv.survey.root.alt
api_key = '' # Set your API key here
p.headers = {'api_key': api_key, 'Content-Type': 'application/json'}
p.data = {'responses': p.client_dict['responses'], 'questions': p.client_dict['questions'],'question_histories': p.client_dict['question_histories'], 'survey_id': survey_id, 'participant_id': uuid}
</exec>
<logic label="alias_api" cond="p.client_dict['num_non_empty'] != 0" api:data="p.data" api:headers="p.headers" api:method="POST" api:url="https://api.roundtable.ai/alias/latest" uses="api.1"/>
<exec>
# Define functions
def checks_array_to_string(array):
return ', '.join(map(str, array)) if array else "None"
def format_checks(response, keys):
return {key: checks_array_to_string(response.r.get('checks', {}).get(key, [])) for key in keys}
def format_response_groups(response, keys,type):
return {key: response.r.get(type, {}).get(key, 0) for key in keys}
def format_effort_ratings(response, keys,type):
return {key: response.r.get(type, {}).get(key, 'NA') for key in keys}
# Add all API data to hidden question
labels = p.client_dict['question_labels']
alias_data.d1.val = alias_api.r.get('flagged', False)
alias_data.d2.val = format_checks(alias_api, labels)
alias_data.d3.val = format_response_groups(alias_api,labels,'response_groups')
alias_data.d4.val = format_effort_ratings(alias_api,labels,'effort_ratings')
alias_data.d5.val = alias_api.r.get('num_checks_failed', 0)
alias_data.d6.val = alias_api.r.get('model', 'API not called')
alias_data.d7.val = alias_api.r.get('typing_url', 'None')
alias_data.d8.val = alias_api.r.get('error', False)
alias_data.d9.val = alias_api.r.get('problem', 'None')
# Add question-specific data to hidden rows of tracked questions
for label in labels:
flag_array = alias_api.r.get('checks',{}).get(label,[])
flag_str = checks_array_to_string(flag_array)
allQuestions[label].d1.val = flag_str != "None"
allQuestions[label].d2.val = flag_str
allQuestions[label].d3.val = alias_api.r.get('response_groups',{}).get(label,0)
allQuestions[label].d4.val = alias_api.r.get('effort_ratings',{}).get(label,'NA')
</exec>
<text
label="alias_data"
optional="0"
size="25"
translateable="0"
where="execute,survey,report">
<title>roundtable data holder</title>
<row label="d1">alias-flagged</row>
<row label="d2">alias-checks</row>
<row label="d3">alias-response_groups</row>
<row label="d4">alias-effort_scores</row>
<row label="d5">alias-num_checks_failed</row>
<row label="d6">alias-model</row>
<row label="d7">alias-typing_url</row>
<row label="d8">alias-error</row>
<row label="d9">alias-problem</row>
<style name="question.after" wrap="ready"><![CDATA[
// For debugging and test mode
sessionStorage.removeItem('client_dict');
sessionStorage.removeItem('initialized');
]]></style>
</text>
The Alias response will now be stored in your survey data in the alias_data
question, and flags - note that the flags, response groups, and effort scores for each tracked question will stil be stored in row variables next to the response.
Example XML Script
Here is a example of a simple survey with all of the Alias code integrated. Make sure to set your API key for the script to work when testing:
<?xml version="1.0" encoding="UTF-8"?>
<survey
alt="roundtable-example-survey"
autosave="0"
builder:wizardCompleted="1"
builderCompatible="1"
compat="153"
delphi="1"
extraVariables="source,record,decLang,list,userAgent"
fir="on"
html:showNumber="0"
mobile="compat"
mobileDevices="smartphone,tablet,desktop"
name="Survey"
secure="1"
setup="term,decLang,quota,time"
ss:disableBackButton="1"
ss:enableNavigation="1"
ss:hideProgressBar="0"
state="testing"
>
<samplesources default="0">
<samplesource list="0">
<title>Open Survey</title>
<invalid>You are missing information in the URL. Please verify the URL with the original invite.</invalid>
<completed>It seems you have already entered this survey.</completed>
<exit cond="terminated">Thank you for taking our survey.</exit>
<exit cond="qualified">Thank you for taking our survey. Your efforts are greatly appreciated!</exit>
<exit cond="overquota">Thank you for taking our survey.</exit>
</samplesource>
<samplesource list="1">
<title>Generic survey link</title>
<invalid>You are missing information in the URL. Please verify the URL with the original invite.</invalid>
<completed>It seems you have already completed this survey.</completed>
<exit cond="terminated">Thank you for taking our survey.</exit>
<exit cond="overquota">Thank you for taking our survey.</exit>
<exit cond="qualified">Thank you for taking our survey.</exit>
</samplesource>
</samplesources>
<suspend/>
<exec>
survey_id = gv.survey.root.alt
api_key = '' # Leave this empty
p.client_dict = {'responses': {}, 'questions': {}, 'question_histories': {}, 'survey_id': '', 'participant_id': '', 'num_non_empty': 0, 'question_labels': []}
</exec>
<text
label="q1"
size="25">
<title>What do you do first in the morning?</title>
<comment>Be specific</comment>
<row label="response"/>
<row label="d1" cond="False">alias-flagged</row>
<row label="d2" cond="False">alias-checks</row>
<row label="d3" cond="False">alias-group</row>
<row label="d4" cond="False">alias-effort</row>
<style name="question.after"><![CDATA[<script src='https://roundtable.ai/js/forsta-tracker-v1.js'></script>]]></style>
<style name="question.after" wrap="ready"><![CDATA[
const q = ${jsexport()};
const label = q['label']
const oes = document.querySelectorAll('input[type="text"], textarea');
oes.forEach(el => {
el.addEventListener('input', e => handleChange(e, 'input', el, label, q));
el.addEventListener('copy', e => handleChange(e, 'copy', el, label, q));
});
initializeClientDict(label, q);
]]></style>
</text>
<suspend/>
<textarea
label="q2"
height="10"
width="50">
<title>Tell me about yourself</title>
<comment>Be specific</comment>
<row label="response"/>
<row label="d1" cond="False">alias-flagged</row>
<row label="d2" cond="False">alias-checks</row>
<row label="d3" cond="False">alias-group</row>
<row label="d4" cond="False">alias-effort</row>
<style name="question.after"><![CDATA[<script src='https://roundtable.ai/js/forsta-tracker-v1.js'></script>]]></style>
<style name="question.after" wrap="ready"><![CDATA[
const q = ${jsexport()};
const label = q['label']
const oes = document.querySelectorAll('input[type="text"], textarea');
oes.forEach(el => {
el.addEventListener('input', e => handleChange(e, 'input', el, label, q));
el.addEventListener('copy', e => handleChange(e, 'copy', el, label, q));
});
initializeClientDict(label, q);
]]></style>
</textarea>
<suspend/>
<exec>
survey_id = gv.survey.root.alt
api_key = '' # Set your API key here
p.headers = {'api_key': api_key, 'Content-Type': 'application/json'}
p.data = {'responses': p.client_dict['responses'], 'questions': p.client_dict['questions'],'question_histories': p.client_dict['question_histories'], 'survey_id': survey_id, 'participant_id': uuid}
</exec>
<logic label="alias_api" cond="p.client_dict['num_non_empty'] != 0" api:data="p.data" api:headers="p.headers" api:method="POST" api:url="https://api.roundtable.ai/alias/latest" uses="api.1"/>
<exec>
# Define functions
def checks_array_to_string(array):
return ', '.join(map(str, array)) if array else "None"
def format_checks(response, keys):
return {key: checks_array_to_string(response.r.get('checks', {}).get(key, [])) for key in keys}
def format_response_groups(response, keys,type):
return {key: response.r.get(type, {}).get(key, 0) for key in keys}
def format_effort_ratings(response, keys,type):
return {key: response.r.get(type, {}).get(key, 'NA') for key in keys}
# Add all API data to hidden question
labels = p.client_dict['question_labels']
alias_data.d1.val = alias_api.r.get('flagged', False)
alias_data.d2.val = format_checks(alias_api, labels)
alias_data.d3.val = format_response_groups(alias_api,labels,'response_groups')
alias_data.d4.val = format_effort_ratings(alias_api,labels,'effort_ratings')
alias_data.d5.val = alias_api.r.get('num_checks_failed', 0)
alias_data.d6.val = alias_api.r.get('model', 'API not called')
alias_data.d7.val = alias_api.r.get('typing_url', 'None')
alias_data.d8.val = alias_api.r.get('error', False)
alias_data.d9.val = alias_api.r.get('problem', 'None')
# Add question-specific data to hidden rows of tracked questions
for label in labels:
flag_array = alias_api.r.get('checks',{}).get(label,[])
flag_str = checks_array_to_string(flag_array)
allQuestions[label].d1.val = flag_str != "None"
allQuestions[label].d2.val = flag_str
allQuestions[label].d3.val = alias_api.r.get('response_groups',{}).get(label,0)
allQuestions[label].d4.val = alias_api.r.get('effort_ratings',{}).get(label,'NA')
</exec>
<text
label="alias_data"
optional="0"
size="25"
translateable="0"
where="execute,survey,report">
<title>roundtable data holder</title>
<row label="d1">alias-flagged</row>
<row label="d2">alias-checks</row>
<row label="d3">alias-response_groups</row>
<row label="d4">alias-effort_scores</row>
<row label="d5">alias-num_checks_failed</row>
<row label="d6">alias-model</row>
<row label="d7">alias-typing_url</row>
<row label="d8">alias-error</row>
<row label="d9">alias-problem</row>
<style name="question.after" wrap="ready"><![CDATA[
// For debugging and test mode
sessionStorage.removeItem('client_dict');
sessionStorage.removeItem('initialized');
]]></style>
</text>
</survey>