CAS API Table/Fixed Table Instructions
Overview
The CAS API and Expert integration automatically builds a table in Expert based on data from the CAS API. This prevents the need for manual updates.
Integration Steps
Review the integration steps below. More information for select steps is below.
Step # |
Task |
Owner |
---|---|---|
1 |
Identify which tables will have the integration, who the SME is for the content (e.g., account team), and when the integration is needed by (e.g., cycle rollover, etc.). |
E&K team |
2 |
Identify the data needed for the table, including:
|
E&K team |
3 |
Create the table "stub" (i.e., table header and column names). |
E&K team |
4 |
Create the AWS S3 folder, CAS API subscription, retrieve the backlog (if any), and place it in the AWS S3 folder. |
Implementation team |
5 |
Configure the BuildTable() function for the table. |
E&K team |
6 |
Test the BuildTable() function (e.g., test against backlog, test automated delivery, etc.). |
E&K team |
7 |
Publish the new table. |
E&K team |
Step 3: Create the Table Stub
You can use the existing table header for the new table stub. Be sure to edit the table properties and select First row for headings.
Step 4: Configure the CAS API Subscription
The CAS API subscription must be set before the file can be sent to the AWS S3 bucket. On Amazon, a script that combines the individual JSON entries into a combined file when a new file appears in the S3 bucket.
All files are delivered to subdirectories under the “CAS” directory; the file naming conventions are:
- CAS/atcas_organizations_<instanceId>/<instanceId>_<organizationId>.json
- CAS/csdcas_programs_<instanceId>/<instanceId>_<organizationId>_<programId>.json
AWS S3 Bucket Details
Bucket: services-liaison
Region: us-east-1
Access key: AKIARGNW5KGAKUEUCHHH
Secret key: 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
CAS API Account
Username: HelpCenter
Password (Prelaunch and Production): p1NbG9B6n
API key: rawsu40KkOUm77HDYOeCadparjOYtqddDaLW4og0
User level: Instance
CAS API Subscriptions
Endpoint: POST/v1/applicationForms/{applicationFormId}/subscriptions
Request Bodies:
Be sure to replace "CASNAMEHERE" with the name of the relevant CAS in lowercase letters (e.g. "atcas_...")
The program payloads feed pages called "Participating Programs", while the organization payloads mostly feed pages called "GRE Codes"
PROGRAMS
{ "notificationEmailAddress": "training@liaisonedu.com", "subscriptionDetails": [ { "event": "program.created", "responseType": "fullResponse", "destinationType": "AWSS3", "apiVersion": "v1", "awsS3Destination": { "awsS3PathTemplate": "CAS/CASNAMEHERE_programs_<instanceId>/<instanceId>_<organizationId>_<programId>.json", "awsS3BucketName": "services-liaison", "awsS3Region": "us-east-1", "awsS3AccessKey": "AKIARGNW5KGAKUEUCHHH", "awsS3SecretKey": "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" } }, { "event": "program.updated", "responseType": "fullResponse", "destinationType": "AWSS3", "apiVersion": "v1", "awsS3Destination": { "awsS3PathTemplate": "CAS/CASNAMEHERE_programs_<instanceId>/<instanceId>_<organizationId>_<programId>.json", "awsS3BucketName": "services-liaison", "awsS3Region": "us-east-1", "awsS3AccessKey": "AKIARGNW5KGAKUEUCHHH", "awsS3SecretKey": "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" } } ] }
ORGANIZATIONS
{ "notificationEmailAddress": "training@liaisonedu.com", "subscriptionDetails": [ { "event": "organization.created", "responseType": "fullResponse", "destinationType": "AWSS3", "apiVersion": "v1", "awsS3Destination": { "awsS3PathTemplate": "CAS/CASNAMEHERE_organizations_<instanceId>/<instanceId>_<organizationId>.json", "awsS3BucketName": "services-liaison", "awsS3Region": "us-east-1", "awsS3AccessKey": "AKIARGNW5KGAKUEUCHHH", "awsS3SecretKey": "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" } }, { "event": "organization.updated", "responseType": "fullResponse", "destinationType": "AWSS3", "apiVersion": "v1", "awsS3Destination": { "awsS3PathTemplate": "CAS/CASNAMEHERE_organizations_<instanceId>/<instanceId>_<organizationId>.json", "awsS3BucketName": "services-liaison", "awsS3Region": "us-east-1", "awsS3AccessKey": "AKIARGNW5KGAKUEUCHHH", "awsS3SecretKey": "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" } } ] }
Step 4 and a 1/2: Batch Retrieve Payloads (if necessary)
As of now, subscriptions to the organization.created and organization.updated events will not fire upon cycle rollover. Organizations, in fact, are not bound by cycles, but a table associates organizations with application forms (unicas_config.dbo.associationOrg). For CAS API-Help Center integrations that use organization payloads, it will be necessary to manually retrieve the organizations and upload them to the appropriate folder in the S3 bucket.
Steps
- Retrieve the relevant payloads in bulk using the appropriate script below
- Don't run any other API calls with the HelpCenter user account while the script is running!!!
- Drop the created folder into the S3 bucket (see above for credentials)
- Load the newly created folder into the /services-liaison/CAS/ folder on the S3 bucket
Tools
Python https://www.python.org/downloads/
PyCharm https://www.jetbrains.com/pycharm/do...ection=windows
CyberDuck (log into S3 bucket) https://cyberduck.io/download/
Scripts
Organizations
import json import os import pandas as pd import requests from datetime import datetime import time # pause in between file generation status checks # credentials, root url, application form id, organization id print("credentials, root url, application form id, organization id") casName = "csdcas" applicationFormId = 6801 username = 'HelpCenter' password = 'p1NbG9B6n' apiKey = 'rawsu40KkOUm77HDYOeCadparjOYtqddDaLW4og0' rootUrl = 'https://api.liaisonedu.com' saveDir = f'C:\\Users\\gmartin\\OneDrive - Liaison International\\001_Projects\\Client_Projects\\MindTouch\\{casName}_organizations_{applicationFormId}\\' print(casName) print(applicationFormId) print(username) print(password) print(apiKey) print(rootUrl) print(saveDir) runtime = datetime.now() runtimestamp = runtime.strftime("%Y-%m-%dT%H%M") print(runtimestamp) # Define a function for authorization print("Define a function for authorization") def auth(apiKey, UserName, Password, baseUrl): """ Authorize session for CAS API :param apiKey: user account's CAS API key :param UserName: user account's username :param Password: user account's password :param baseUrl: the base url for the target environment :return: Authorization Token to be used with all requests """ endpoint = "/v1/auth/token" url = baseUrl + endpoint payload = "{\"UserName\": \"" + UserName + "\",\"Password\": \"" + Password + "\"}" headers = { 'Content-Type': "application/json", 'x-api-key': apiKey } response = requests.request("POST", url, data=payload, headers=headers) responseJson = response.json() token = responseJson["Token"] # refreshToken = responseJson["RefreshToken"] return token # Define a function for interacting with the CAS API print("Define a function for interacting with the CAS API") def casapi(endpoint, apiKey, UserName, Password, baseUrl="https://api.liaisonedu.com", requestType="GET", payload="", contentJson=False, extraHeaders={}, urlParams={}): """ Interact with various CAS API endpoints :param endpoint: the URL indicating the desired endpoint (see https://api.liaisonedu.com/reference/index.htm) :param apiKey: the user's API key :param UserName: the user's username :param Password: the user's password :param baseUrl: the root URL for the environment you want to interact with (Defaults to prod: baseUrl="https://api.liaisonedu.com") :param requestType: the HTTP request to send e.g. "GET" (see https://api.liaisonedu.com/reference/index.htm) :param payload: OPTIONAL the body of the HTTP request :param contentJson: OPTIONAL adds a header indicating that the body of the request is "application/json" :param extraHeaders: OPTIONAL additional headers for the request; send key-values as dict :param urlParams: OPTIONAL additional URL string query parameters; send key-values as dict :return: raw response from endpoint """ if len(urlParams) == 0: url = baseUrl + endpoint else: i = 1 urlQueryStr = "" for urlParam, urlParamValue in urlParams.items(): if i == 1: urlQueryPart = "?" + urlParam + "=" + str(urlParamValue) else: urlQueryPart = "&" + urlParam + "=" + str(urlParamValue) urlQueryStr += urlQueryPart i += 1 url = baseUrl + endpoint + urlQueryStr token = auth(apiKey=apiKey, UserName=UserName, Password=Password, baseUrl=baseUrl) if contentJson: headers = { 'Authorization': token, 'x-api-key': apiKey, 'Content-Type': "application/json" } headers.update(extraHeaders) else: headers = { 'Authorization': token, 'x-api-key': apiKey, } headers.update(extraHeaders) response = requests.request(requestType, url, data=payload, headers=headers) return response # make directory if not exist print('# make directory if not exist') print('directory already exists?\t\t' + str(os.path.isdir(saveDir))) if not os.path.isdir(saveDir): os.mkdir(saveDir) print('directory created?\t\t' + str(os.path.isdir(saveDir))) # retrieve organization list print('# retrieve organization list') endpoint = f"/v1/applicationForms/{applicationFormId}/organizations" organizationList = casapi(endpoint, apiKey, username, password, rootUrl, "GET").json() print(len(organizationList)) # loop through organizations, retrieving details and saving as JSON print("# loop through organizations, retrieving details and saving as JSON") i = 0 now = datetime.now() nowtimestamp = now.strftime("%Y-%m-%dT%H%M") print(nowtimestamp) for organization in organizationList: time.sleep(2) organizationId = organization["id"] endpoint = f"/v1/applicationForms/{applicationFormId}/organizations/{organizationId}" i += 1 now = datetime.now() nowtimestamp = now.strftime("%Y-%m-%dT%H%M") response = casapi(endpoint, apiKey, username, password, rootUrl, "GET") organizationDetails = response.json() print(nowtimestamp + "\t\t" + str(i) + "\t" + str(len(organizationList) - i) + " left\t\t" + str(applicationFormId) + "\t" + str(organizationId) + "\t\t" + str(response.status_code)) if response.status_code == 200: with open(saveDir+f"{applicationFormId}_{organizationId}.json", 'w') as file: json.dump(organizationDetails, file) print("\r\rretrieval complete") now = datetime.now() runduration = (now-runtime).total_seconds()/60 print("run duration: " + str(runduration) + " minutes")
Programs
import json import os import pandas as pd import requests from datetime import datetime import time # pause in between file generation status checks # credentials, root url, application form id, organization id print("credentials, root url, application form id, organization id") casName = "unmc" applicationFormId = 6783 username = 'HelpCenter' password = 'p1NbG9B6n' apiKey = 'rawsu40KkOUm77HDYOeCadparjOYtqddDaLW4og0' rootUrl = 'https://api.liaisonedu.com' saveDir = f'C:\\Users\\gmartin\\OneDrive - Liaison International\\001_Projects\\Client_Projects\\MindTouch\\{casName}_programs_{applicationFormId}\\' print(casName) print(applicationFormId) print(username) print(password) print(apiKey) print(rootUrl) print(saveDir) runtime = datetime.now() runtimestamp = runtime.strftime("%Y-%m-%dT%H%M") print(runtimestamp) # Define a function for authorization print("Define a function for authorization") def auth(apiKey, UserName, Password, baseUrl): """ Authorize session for CAS API :param apiKey: user account's CAS API key :param UserName: user account's username :param Password: user account's password :param baseUrl: the base url for the target environment :return: Authorization Token to be used with all requests """ endpoint = "/v1/auth/token" url = baseUrl + endpoint payload = "{\"UserName\": \"" + UserName + "\",\"Password\": \"" + Password + "\"}" headers = { 'Content-Type': "application/json", 'x-api-key': apiKey } response = requests.request("POST", url, data=payload, headers=headers) responseJson = response.json() token = responseJson["Token"] # refreshToken = responseJson["RefreshToken"] return token # Define a function for interacting with the CAS API print("Define a function for interacting with the CAS API") def casapi(endpoint, apiKey, UserName, Password, baseUrl="https://api.liaisonedu.com", requestType="GET", payload="", contentJson=False, extraHeaders={}, urlParams={}): """ Interact with various CAS API endpoints :param endpoint: the URL indicating the desired endpoint (see https://api.liaisonedu.com/reference/index.htm) :param apiKey: the user's API key :param UserName: the user's username :param Password: the user's password :param baseUrl: the root URL for the environment you want to interact with (Defaults to prod: baseUrl="https://api.liaisonedu.com") :param requestType: the HTTP request to send e.g. "GET" (see https://api.liaisonedu.com/reference/index.htm) :param payload: OPTIONAL the body of the HTTP request :param contentJson: OPTIONAL adds a header indicating that the body of the request is "application/json" :param extraHeaders: OPTIONAL additional headers for the request; send key-values as dict :param urlParams: OPTIONAL additional URL string query parameters; send key-values as dict :return: raw response from endpoint """ if len(urlParams) == 0: url = baseUrl + endpoint else: i = 1 urlQueryStr = "" for urlParam, urlParamValue in urlParams.items(): if i == 1: urlQueryPart = "?" + urlParam + "=" + str(urlParamValue) else: urlQueryPart = "&" + urlParam + "=" + str(urlParamValue) urlQueryStr += urlQueryPart i += 1 url = baseUrl + endpoint + urlQueryStr token = auth(apiKey=apiKey, UserName=UserName, Password=Password, baseUrl=baseUrl) if contentJson: headers = { 'Authorization': token, 'x-api-key': apiKey, 'Content-Type': "application/json" } headers.update(extraHeaders) else: headers = { 'Authorization': token, 'x-api-key': apiKey, } headers.update(extraHeaders) response = requests.request(requestType, url, data=payload, headers=headers) return response # make directory if not exist print('# make directory if not exist') print('directory already exists?\t\t' + str(os.path.isdir(saveDir))) if not os.path.isdir(saveDir): os.mkdir(saveDir) print('directory created?\t\t' + str(os.path.isdir(saveDir))) # retrieve program list print('# retrieve program list') endpoint = f"/v1/applicationForms/{applicationFormId}/programs" programList = casapi(endpoint, apiKey, username, password, rootUrl, "GET").json() print(len(programList)) # loop through programs, retrieving details and saving as JSON print("# loop through programs, retrieving details and saving as JSON") i = 0 now = datetime.now() nowtimestamp = now.strftime("%Y-%m-%dT%H%M") print(nowtimestamp) for program in programList: # if (i == 50): # continue time.sleep(2) organizationId = program["orgId"] programId = program["id"] endpoint = f"/v1/applicationForms/{applicationFormId}/organizations/{organizationId}/programs/{programId}" i += 1 now = datetime.now() nowtimestamp = now.strftime("%Y-%m-%dT%H%M") response = casapi(endpoint,apiKey,username,password,rootUrl,"GET") programDetails = response.json() print(nowtimestamp + "\t\t" + str(i) + "\t" + str(len(programList) - i) + " left\t\t" + str(applicationFormId) + "\t" + str(organizationId) + "\t" + str(programId) + "\t\t" + str(response.status_code)) if response.status_code==200: with open(saveDir+f"{applicationFormId}_{organizationId}_{programId}.json", 'w') as file: json.dump(programDetails, file) print("\r\rretrieval complete") now = datetime.now() runduration = (now-runtime).total_seconds()/60 print("run duration: " + str(runduration) + " minutes")
Step 5: Configure the BuildTable Function
- Edit the page and insert the BuildTable script (https://help.liaisonedu.com/Template...tom/BuildTable), which downloads a combined JSON file and renders it based on the configuration options provided.
- On each table page, there are 3 components:
- Reference to the BuildTable script (usually collapsed).
- Configuration settings for the table (usually collapsed).
- Table with headers (these can be styled, resized, etc. as needed). The rows are filled in by the script.
Step B: Configure Table Settings
Sample options:
var data_url = 'https://services-liaison.s3.amazonaws.com/data_csdcas_programs_6709.json'; var SELECTOR = 'table tbody'; var SORT_ORDER = ["orgName", "name", "type", "deadlineDisplay"]; var DISPLAY_ORDER = ["state", "orgName", "name", "type", "deadlineDisplay", "programAttributes.supplementalApplicationRequired", "programAttributes.supplementalFeeRequired", "programAttributes.greRequired"]; var IGNORE = function(item) { return item.status == "draft" || item.status == "review"; }; var STYLE = function(row) { var styles = {}; if (row.concentration == "Application submitted by the deadline") { styles["deadlineDisplay"] = "background: #3498db; font-weight: bold;"; // blue } if (row.concentration == "Application and transcripts received by deadline") { styles["deadlineDisplay"] = "background: #f39c12; font-weight: bold;"; // orange } if (row.concentration == "Application verified by deadline") { styles["deadlineDisplay"] = "background: #27ae60; font-weight: bold;"; // green } styles.default = ""; return styles; } buildTable(data_url, {SELECTOR: SELECTOR, SORT_ORDER: SORT_ORDER, DISPLAY_ORDER: DISPLAY_ORDER, IGNORE: IGNORE, STYLE: STYLE});
The options include:
- data_url: the data file to load, in the following format. Note that the ID is unique to a CAS and cycle and is stored in the Master Delivery Schedule.
https://services-liaison.s3.amazonaws.com/data_csdcas_programs_ID.json https://services-liaison.s3.amazonaws.com/data_atcas_programs_ID.json
- SELECTOR: CSS selector for the table to use. If there are multiple tables on the page, you can target a specific one. Otherwise, just leave the default.
- SORT_ORDER: an array of fields to sort by (such as "orgName" first, then "name," etc.).
- DISPLAY_ORDER: an array of fields to display in the table columns, from left to right. Field names may be standard attributes ("state") or custom attributes ("programAttributes.attributeName"). In custom attributes, the programAttributes[].displayName should be used for filtering (corresponding to the value entered by the user in the “AttributeName” parameter - var DISPLAY_ORDER = ["name", "etsCode", "programAttributes.AttributeName"]; ). The value to be displayed in the row in the table would be programAttributes.attributeValues.attributeValue.
- IGNORE: a function that determines which rows to ignore (e.g., ignore items with a status). The incoming item is an object based on the JSON entries.
- STYLE: a function that determines the inline CSS style to apply to each cell. The incoming item is an object based on the JSON entries. The return value should be an object with an inline CSS style for each field name. For example:
styles["fieldName"] = "background: #aa0000;"
. You may optionally set a value forstyles.default
: this inline CSS will be added to cells where an exact field match isn't already there.
Step C: Set Up Table
Be sure that the table stub is already created with header rows labeled. Columns should match the DISPLAY_ORDER configuration settings, both in name and order.
Sample table:
State |
Institution |
Program |
Program Type |
Program Deadline |
Supplemental Application Required | Supplemental Fee Required | GRE Required |
Sample table HTML:
<table border="1" cellpadding="1" cellspacing="1" class="mt-responsive-table mt-table-big" id="program-table"> <thead> <tr> <td class="mt-noheading mt-column-width-10" style="background-color:rgb(30, 87, 160);vertical-align:middle;"> <p><span class="mt-font-size-12"><strong><span class="mt-color-ffffff">State</span></strong></span></p> </td> <td class="mt-noheading mt-column-width-10" style="background-color:rgb(30, 87, 160);vertical-align:middle;"><span class="mt-font-size-12"><font color="#ffffff"><b>Institution</b></font></span></td> <td class="mt-noheading mt-column-width-15" style="background-color:rgb(30, 87, 160);vertical-align:middle;"> <p><span class="mt-font-size-12"><strong><span class="mt-color-ffffff">Program</span></strong></span></p> </td> <td class="mt-noheading mt-column-width-15" style="background-color:rgb(30, 87, 160);vertical-align:middle;"> <p><span class="mt-font-size-12"><strong><span class="mt-color-ffffff">Program Type</span></strong></span></p> </td> <td class="mt-noheading mt-column-width-10" style="background-color:rgb(30, 87, 160);vertical-align:middle;"> <p><span class="mt-font-size-12"><strong><span class="mt-color-ffffff">Program Deadline</span></strong></span></p> </td> <td class="mt-noheading mt-column-width-10" style="background-color:rgb(30, 87, 160);vertical-align:middle;"><span class="mt-font-size-12"><font color="#ffffff"><b>Supplemental Application Required</b></font></span></td> <td class="mt-noheading mt-column-width-10" style="background-color:rgb(30, 87, 160);vertical-align:middle;"><span class="mt-font-size-12"><strong><span class="mt-color-ffffff">Supplemental Fee Required</span></strong></span></td> <td class="mt-noheading mt-column-width-15" style="background-color:rgb(30, 87, 160);vertical-align:middle;"><span class="mt-font-size-12"><font color="#ffffff"><b>GRE Required</b></font></span></td> </tr> </thead> <tbody> </tbody> </table>
Additional Style Code
If needed, you can use the codes below to further customize program data.
Make text invisible via CSS styling if there's a value you don't want users to see (NAFCAS example)
var STYLE = function(row) { var styles = {}; if (typeof row.campus == 'undefined') { styles["campus"] = "opacity: 0"; // hide the text - using opacity means users can't highlight the invisible text with the cursor } styles.default = ""; return styles; }
Apply snippets of default answer values when the answer values themselves aren't being picked up (CASPA example)
var STYLE = function(row) { var styles = {}; if (row.track.includes("Blue:")) { styles["deadlineDisplay"] = "background: #3498db; font-weight: bold;"; // blue } if (row.track.includes("Orange:")) { styles["deadlineDisplay"] = "background: #f39c12; font-weight: bold;"; // orange } if (row.track.includes("Green:")) { styles["deadlineDisplay"] = "background: #27ae60; font-weight: bold;"; // green } styles.default = ""; return styles; }
Fixed table headers
To add the fixed table headers functionality, you must add a specific class name to the <table> tag. Note that the fixed table header doesn't work in mobile. Instead, the default behavior was selected for ease of reading.
- Edit the page and click HTML in the top right of the editor.
- Enter the following tag in the <table> script:
- For a single-header table, add class="stick-one". If the table already has class attributes, then you can add stick-one to the existing class list. You must also edit the table properties and select First row for headings.
- For a double-header table, add class="stick-two". If the table already has class attributes, then you can add stick-two to the existing class list.